Django REST框架ModelSerializer get_or_create功能

时间:2014-07-29 22:28:15

标签: django django-rest-framework

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

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

7 个答案:

答案 0 :(得分:17)

根据我的经验,nmgeek的解决方案无法在DRF 3+中工作,因为serializer.is_valid()正确地尊重了模型的unique_together约束。您可以通过删除UniqueTogetherValidator并覆盖序列化程序的创建方法来解决此问题。

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

答案 1 :(得分: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)

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

答案 2 :(得分:2)

有几种情况下,序列化程序可能需要能够根据视图接收的数据获取或创建对象 - 对于视图来说,查找/创建功能不合逻辑 - 我本周遇到了这个问题。

是的,可以在Serializer中拥有get_or_create功能。这里的文档中有一个提示:http://www.django-rest-framework.org/api-guide/serializers#specifying-which-fields-should-be-write-only其中:

    已编写
  • restore_object方法来实例化新用户。
  • instance属性固定为None,以确保此方法不会用于更新用户。

我认为您可以更进一步将get_or_create填充到restore_object中 - 在此示例中,将用户从发布到视图的电子邮件地址加载:

class UserFromEmailSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        fields = [
            'email',
        ]

    def restore_object(self, attrs, instance=None):
        assert instance is None, 'Cannot update users with UserFromEmailSerializer'

        (user_object, created) = get_user_model().objects.get_or_create(
            email=attrs.get('email')
        )

        # You can extend here to work on `user_object` as required - update etc.

        return user_object

现在,您可以在视图的post方法中使用序列化程序,例如:

def post(self, request, format=None):

    # Serialize "new" member's email
    serializer = UserFromEmailSerializer(data=request.DATA)

    if not serializer.is_valid():
        return Response(serializer.errors,
                        status=status.HTTP_400_BAD_REQUEST)

    # Loaded or created user is now available in the serializer object:
    person=serializer.object
    # Save / update etc.

答案 3 :(得分:2)

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

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

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

请注意,对UPDATE的调用采用Python3语法。如果使用Python 2,您希望使用旧样式:super()

super(MyModelSerializer, self).is_valid(**kwargs)

答案 4 :(得分:0)

更好的方法是使用PUT动词,然后覆盖get_object()中的ModelViewSet方法。我在这里回答:https://stackoverflow.com/a/35024782/3025825

答案 5 :(得分:0)

我知道这是黑客,但万一您需要快速解决方案 附言当然,不支持编辑



class ExpoDeviceViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated, ]
    serializer_class = ExpoDeviceSerializer

    def get_queryset(self):
        user = self.request.user
        return ExpoDevice.objects.filter(user=user)

    def perform_create(self, serializer):
        existing_token = self.request.user.expo_devices.filter(
            token=serializer.validated_data['token']).first()
        if existing_token:
            return existing_token
        return serializer.save(user=self.request.user)

答案 6 :(得分:0)

一个简单的解决方法是使用 to_internal_value 方法:

class MyModelSerializer(serializers.ModelSerializer):

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

    class Meta:
        model = models.MyModel