Django REST框架ModelSerializer get_or_create功能

eyu*_*elt 14 django django-rest-framework

当我尝试将一些数据反序列化为一个对象时,如果我包含一个唯一的字段并给它一个已分配给数据库中对象的值,我会得到一个键约束错误.这是有道理的,因为它正在尝试创建一个具有已使用的唯一值的对象.

有没有办法为ModelSerializer提供get_or_create类型的功能?我希望能够为Serializer提供一些数据,如果存在具有给定唯一字段的对象,则只返回该对象.

Gro*_*ady 21

根据我的经验,nmgeek的解决方案不适用于DRF 3+,因为serializer.is_valid()正确地遵循了模型的unique_together约束.您可以通过删除UniqueTogetherValidator并覆盖序列化程序的create方法来解决此问题.

class MyModelSerializer(serializers.ModelSerializer):

    def run_validators(self, value):
        for validator in self.validators:
            if isinstance(validator, validators.UniqueTogetherValidator):
                self.validators.remove(validator)
        super(MyModelSerializer, self).run_validators(value)

    def create(self, validated_data):
        instance, _ = models.MyModel.objects.get_or_create(**validated_data)
        return instance

    class Meta:
        model = models.MyModel
Run Code Online (Sandbox Code Playgroud)

  • 我不确定这个答案。我已经实现了它,UniqeValidator 不是在这里捕获的,而是在字段的类上捕获的。因此,这可能适用于 UniqueTogether,但不适用于 Unique。 (2认同)

nmg*_*eek 12

从3.0版本的REST Framework开始删除了Serializer restore_object方法.

添加get_or_create功能的简单方法如下:

class MyObjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyObject
        fields = (
                  'unique_field',
                  'other_field',
                  )

    def get_or_create(self):
        defaults = self.validated_data.copy()
        identifier = defaults.pop('unique_field')
        return MyObject.objects.get_or_create(unique_field=identifier, defaults=defaults)

def post(self, request, format=None):
    serializer = MyObjectSerializer(data=request.data)
    if serializer.is_valid():
        instance, created = serializer.get_or_create()
        if not created:
            serializer.update(instance, serializer.validated_data)
        return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Run Code Online (Sandbox Code Playgroud)

但是,在我看来,生成的代码比查询实例是否存在更紧凑或易于理解,然后根据查询结果进行更新或保存.


Col*_*cks 5

@Groady 的答案有效,但您现在已经失去了在创建新对象时验证唯一性的能力(UniqueValidator 已从您的验证器列表中删除,无论情况如何)。使用序列化程序的整个想法是您有一种全面的方法来创建一个新对象,该对象验证要用于创建对象的数据的完整性。删除验证不是您想要的。您确实希望在创建新对象时存在此验证,您只是希望能够在序列化程序中抛出数据并在引擎盖下获得正确的行为 (get_or_create)、验证和所有内容。

我建议is_valid()改为在序列化程序上覆盖您的方法。使用下面的代码,您首先检查对象是否存在于您的数据库中,如果不存在,则像往常一样继续进行完整验证。如果它确实存在,您只需将此对象附加到您的序列化程序,然后像往常一样继续验证,就像您使用关联的对象和数据实例化了序列化程序一样。然后,当您点击 serializer.save() 时,您将简单地取回您已经创建的对象,并且您可以在高级别的代码模式中使用相同的代码模式:使用数据实例化您的序列化程序,调用.is_valid(),然后调用.save()并返回您的模型实例(一个la get_or_create)。无需覆盖.create().update().

这里需要注意的是,UPDATE当您点击 时,您将在数据库上获得一个不必要的事务.save(),但一次额外的数据库调用成本似乎值得拥有一个干净的开发人员 API 和完整的验证。它还允许您使用自定义models.Manager 和自定义models.QuerySet 的可扩展性,以仅从几个字段(无论主要标识字段是什么)中唯一标识您的模型,然后使用initial_data序列化程序中的其余数据作为更新有问题的对象,从而允许您从数据字段的子集中获取唯一的对象,并将剩余的字段视为对象的更新(在这种情况下,UPDATE调用不会是额外的)。

请注意,对 的调用super()采用 Python3 语法。如果使用 Python 2,您会想要使用旧样式:super(MyModelSerializer, self).is_valid(**kwargs)

from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned


class MyModelSerializer(serializers.ModelSerializer):

    def is_valid(self, raise_exception=False):
        if hasattr(self, 'initial_data'):
            # If we are instantiating with data={something}
            try:
                # Try to get the object in question
                obj = Security.objects.get(**self.initial_data)
            except (ObjectDoesNotExist, MultipleObjectsReturned):
                # Except not finding the object or the data being ambiguous
                # for defining it. Then validate the data as usual
                return super().is_valid(raise_exception)
            else:
                # If the object is found add it to the serializer. Then
                # validate the data as usual
                self.instance = obj
                return super().is_valid(raise_exception)
        else:
            # If the Serializer was instantiated with just an object, and no
            # data={something} proceed as usual 
            return super().is_valid(raise_exception)

    class Meta:
        model = models.MyModel
Run Code Online (Sandbox Code Playgroud)