当我尝试将某些数据反序列化为对象时,如果我包含一个唯一的字段并将其赋值给已分配给数据库中的对象,则会出现键约束错误。这是有道理的,因为它正在尝试创建一个具有已使用的唯一值的对象。
有没有办法为ModelSerializer提供get_or_create类型的功能?我希望能够为Serializer提供一些数据,如果存在具有给定唯一字段的对象,则只返回该对象。
答案 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