Django休息框架没有使用FK创建具有unique = True字段的模型的对象

时间:2017-04-25 18:47:06

标签: django django-rest-framework

我有两个这样的模型:

class Sector(models.Model):
    name = models.CharField(max_length=100, db_index=True, unique=True)  # HERE IF I REMOVE unique=True, it works correctly

class Address(models.Model):
    ...
    sector = models.ForeignKey(Sector, null=True, blank=True)

地址模型的序列化器:

在视图中,我有这个:

    address_serialized = AddressSerializer(data=request.data)
    if address_serialized.is_valid():
        address_serialized.save(client=client)

永远不会进入create功能。我有一个序列化的创建函数,如下所示:

class AddressSerializer(serializers.ModelSerializer):
    city_gps = CitySerializer(required=False)
    sector = SectorSerializer(required=False)

    class Meta:
        model = Address
        fields = (..., "sector")

    def create(self, validated_data):
        ...
        sector_dict = validated_data.get("sector", None)   
        sector = None

        if sector_dict and "name" in sector_dict and city_gps:
            if Sector.objects.filter(name=sector_dict["name"], city=city_gps).exists():
                sector = Sector.objects.get(name=sector_dict["name"], city=city_gps)

        # pdb.set_trace()
        if "sector" in validated_data:
            validated_data.pop("sector")
        if "city_gps" in validated_data:
            validated_data.pop("city_gps")

        address = Address.objects.create(sector=sector, city_gps=city_gps, **validated_data)

        return address

代码从不接触此函数,is_valid()返回False。消息是

  

{" sector":{" name":["此名称的部门已经存在。"]}}

我需要能够使用FK为现有扇区创建一个新地址。我怎样才能做到这一点?任何建议都会有所帮助。

修改

视图如下所示:

class ClientProfileAddressCreateView(APIView):
    # throttle_scope = '1persecond'
    renderer_classes = (JSONRenderer,)
    permission_classes = (IsAuthenticated,)

    def post(self, request):

        try:
            client = Client.objects.get(user=request.user)
        except ObjectDoesNotExist:
            return Response({"error": "A client profile for the logged user does not exit"},
                            status=status.HTTP_404_NOT_FOUND)

        address_serialized = AddressSerializer(data=request.data)
        print("address_serialized.is_valid: %s" % address_serialized.is_valid())  # Returns False when unique=True in models
        if address_serialized.is_valid():
            # print("address_serialized: %s" % address_serialized.data)
            address_serialized.save(client=client)
        else:
            return Response(data=address_serialized.errors, status=status.HTTP_400_BAD_REQUEST)

        return Response(data=address_serialized.data, status=status.HTTP_201_CREATED)

1 个答案:

答案 0 :(得分:2)

这是嵌套序列化程序和唯一约束的已知问题。

总是真的很棒的事情就是打印Serializer - 它可以为你提供很多额外的信息。

当你有这样的json:

{
    "Sector": {
        "name": "Sector XYZ"
    },
    "address_line_one": “Some Random Address”
}

Django REST框架不知道您是否正在创建或获取Sector对象,因此它会强制对每个请求进行验证。

您需要做的是:

class SectorSerializer(serializers.ModelSerializer):
    # Your fields.
    class Meta:
        model = Address
        fields = ("Your Fields",)

        extra_kwargs = {
            'name': {
                'validators': [],
            }
        }

然后要处理验证,您需要重做创建/更新部分以适应唯一性约束并引发异常/验证错误。

我希望这会有所帮助。

有用的链接:This SO AnswerDealing with unique constraints in nested serializers

编辑:

根据cezar的请求:我将添加覆盖序列化程序的create方法的样子。我没有尝试过这段代码,但逻辑是这样的。

class SectorSerializer(serializers.ModelSerializer):
    # Your fields.
    class Meta:
        model = Address
        fields = ("Your Fields",)

        extra_kwargs = {
            'name': {
                'validators': [],
            }
        }

    def create(self, validated_data):
        raise_errors_on_nested_writes('create', self, validated_data)

        ModelClass = self.Meta.model

        info = model_meta.get_field_info(ModelClass)
        many_to_many = {}
        for field_name, relation_info in info.relations.items():
            if relation_info.to_many and (field_name in validated_data):
                many_to_many[field_name] = validated_data.pop(field_name)

        # FIELD CHECK
        your_field = validated_data.get("your_field","") # or validated_data["your_field"] 
        try:
            YourModel.objects.filter(your_check=your_field).get()
            raise ValidationError("Your error")
        except YourModel.DoesNotExist:
            # if it doesn't exist it means that no model containing that field exists so pass it. You can use YourQuerySet.exists() but then the logic changes
            pass

        try:
            instance = ModelClass.objects.create(**validated_data)
        except TypeError:
            tb = traceback.format_exc()
            msg = (
                'Got a `TypeError` when calling `%s.objects.create()`. '
                'This may be because you have a writable field on the '
                'serializer class that is not a valid argument to '
                '`%s.objects.create()`. You may need to make the field '
                'read-only, or override the %s.create() method to handle '
                'this correctly.\nOriginal exception was:\n %s' %
                (
                    ModelClass.__name__,
                    ModelClass.__name__,
                    self.__class__.__name__,
                    tb
                )
            )
            raise TypeError(msg)

        # Save many-to-many relationships after the instance is created.
        if many_to_many:
            for field_name, value in many_to_many.items():
                field = getattr(instance, field_name)
                field.set(value)

        return instance