【问题标题】:Django rest framework not creating object with FK to a model with unique=True fieldDjango rest 框架不使用 FK 为具有 unique=True 字段的模型创建对象
【发布时间】:2017-04-25 18:47:05
【问题描述】:

我有两个这样的模型:

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)

【问题讨论】:

  • 您可能想尝试is_valid(raise_exception=True) 进行诊断...django-rest-framework.org/api-guide/serializers/…
  • 您能给我们提供您的意见吗?在serializer 中更改required=True 也会改变什么吗?
  • 另外,您能否检查您的数据库中是否有包含 None/Blank 的扇区条目?
  • @BorkoKovacev 我添加了视图。不确定“检查我的数据库以获取其中包含无/空白的扇区条目”是什么意思……带有空扇区的地址?
  • @alejoss 感谢您的意见。我将尝试一些我认为会导致问题的方法,并将在几个小时内回答。是的,带有空扇区的地址,很抱歉造成混淆。

标签: django django-rest-framework


【解决方案1】:

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

真正令人敬畏的事情是打印序列化程序 - 它可以为您提供很多额外的信息。

当你有这样的 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

【讨论】:

  • 您能否最终提供一个手动实现唯一验证的简单示例?哈瓦拉 ;)
  • 有多种方法可以实现这一点 - 您可以在 view 本身覆盖 create/update post/putperform_create/perform_update .或者您可以在序列化程序中通过覆盖create/update 来实现。我将编辑我的答案以包括序列化程序检查 - 和 nema na cemu :) @cezar
  • 现在检查@cezar
猜你喜欢
  • 1970-01-01
  • 2014-11-11
  • 2014-07-07
  • 1970-01-01
  • 2015-08-11
  • 2013-04-26
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多