我正在尝试为DRF HyperlinkRelatedModel Serializer编写自定义更新。但实际上我只是在撞墙。
它引发唯一约束错误。首先,我想对电子邮件设置一个唯一的约束,该约束无法正常工作,因此我将其删除。现在,我在uuid
字段上遇到了同样的错误。
有人可以指导我完成此事,并提供一些有关处理此类关系的建议。
以下是我到目前为止的内容,它的意思是创建或更新Recipient
并将其添加到Email
。
我认为我需要编写某种形式的自定义验证,但我不确定该怎么做。任何帮助将不胜感激。
{
"recipients": [
{
"uuid": [
"recipient with this uuid already exists."
]
}
]
}
更新
这消除了验证错误。现在,我不知道如何重新添加验证以进行定期更新。
extra_kwargs = {
'uuid': {
'validators': [],
}
}
模型
class Recipient(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=255)
email_address = models.EmailField()
class Email(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
subject = models.CharField(max_length=500)
body = models.TextField()
recipients = models.ManyToManyField(Recipient, related_name='email')
序列化器
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from schedule_email.models import Recipient, Email, ScheduledMail
class RecipientSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Recipient
fields = ('url', 'uuid', 'name', 'email_address', 'recipient_type')
# I saw somewhere that this might remove the validation.
extra_kwargs = {
'uuid': {
'validators': [],
}
}
class EmailSerializer(serializers.HyperlinkedModelSerializer):
recipients = RecipientSerializer(many=True, required=False)
class Meta:
model = Email
fields = ('url', 'uuid', 'subject', 'body', 'recipients', 'delivery_service')
def create(self, validated_data):
recipient_data = validated_data.pop('recipients')
email = Email.objects.create(**validated_data)
for recipient in recipient_data:
email.recipients.add(Recipient.objects.create(**recipient))
return email
def update(self, instance, validated_data):
recipients_data = validated_data.pop('recipients')
for field, value in validated_data.items():
setattr(instance, field, value)
for recipient_data in recipients_data:
if 'uuid' in recipient_data.keys() and instance.recipients.get(pk=recipient_data['uuid']):
Recipient.objects.update(**recipient_data)
elif 'uuid' in recipient_data.keys() and Recipient.objects.get(pk=recipient_data['uuid']):
instance.recipients.add(Recipient.objects.update(**recipient_data))
elif 'uuid' in recipient_data.keys():
raise ValidationError('No recipient with this UUID was found: %s' % recipient_data['uuid'])
else:
recipient = Recipient.objects.create(**recipient_data)
instance.recipients.add(recipient)
return instance
以下是我可能提出的发布/发布请求示例。我可能不需要uuid
字段,我无法锻炼如何从超链接URL中获取Recipient
实例。
示例发布/投放
{
"subject": "Greeting",
"body": "Hello All",
"recipients": [
{
"url": "http://localhost:8000/api/recipient/53614a41-7155-4d8b-adb1-66ccec60bc87/",
"uuid": "53614a41-7155-4d8b-adb1-66ccec60bc87"
"name": "Jane",
"email_address": "jane@example.com",
},
{
"name": "John",
"email_address": "john@example.com",
}
],
}
答案 0 :(得分:1)
通过您的关系结构,在创建电子邮件实例时,您还可以传递收件人实例的数据,无论是新收件人还是现有收件人。您提到的验证错误发生是因为,当您使用嵌套序列化程序时,在创建或更新时,DRF会调用嵌套序列化程序的 is_valid 方法,并且当您为现有收件人传递收件人数据时,DRF会尝试将其验证为如果使用您提供的数据(包括 uuid )创建新的收件人,并引发验证错误。为了解决这个问题,在您的 EmailSerializer 中,您可以禁用收件人字段的默认验证,并为其添加自定义验证器方法,然后运行验证:
class EmailSerializer(serializers.HyperlinkedModelSerializer):
...
def validate_recipients(self, value):
for recipient_data in value:
if recipient_data.get('uuid'):
try:
recipient = Recipient.objects.get(uuid=recipient_data.get('uuid'))
except Recipient.DoesNotExist:
# raise a validation error here
else:
serializer = RecipientSerializer(recipient)
serializer.is_valid(raise_exception=True) # This will run validation for Recipient update
else:
serializer = RecipientSerializer(data=recipient_data)
serializer.is_valid(raise_exception=True) # This will run validation for Recipient create
return value
上面的代码首先检查是否为接收者提供了uuid,如果是,则希望它是现有接收者的数据,如果不是,则希望它是新接收者的数据,并相应地运行验证。然后,在您的EmailSerializer的创建方法中,您可以通过其序列化器来创建/更新收件人,如下所示:
for recipient in recipient_data:
if recipient.get('uuid'):
serializer = RecipientSerializer(Recipient.objects.get(uuid=recipient.get(
'uuid'))) # We know this wont raise an exception because we checked for this in validation
else:
serializer = RecipientSerializer(data=recipient)
serializer.is_valid() # Need to call this before save, even though we know the the data is valid at this point
serializer.save() # This will either update an existing recipient or createa new one
email.recipients.add(serializer.instance)
EmailSerilaizer的更新方法应该类似,但是您还要考虑从电子邮件收件人列表中删除收件人的情况。
注意:不要在序列化程序的创建/更新方法内引发ValidationError,不要在validate方法中运行验证,并且仅将create / update方法用于创建/更新。应当以这种思维方式来编写这些方法:如果我完成了该方法,则所提供的数据必须是有效的,因此我将继续创建/更新实例。并记下您的验证信息。
示例序列化程序
class RecipientSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Recipient
fields = ('url', 'uuid', 'name', 'email_address', 'recipient_type')
extra_kwargs = {
'uuid': {
'validators': [],
}
}
class EmailSerializer(serializers.HyperlinkedModelSerializer):
recipients = RecipientSerializer(many=True, required=False)
class Meta:
model = Email
fields = ('url', 'uuid', 'subject', 'body', 'recipients', 'delivery_service')
def create(self, validated_data):
recipient_data = validated_data.pop('recipients')
email = Email.objects.create(**validated_data)
self.add_recipients(email, recipient_data)
return email
def update(self, instance, validated_data):
recipient_data = validated_data.pop('recipients')
for field, value in validated_data.items():
setattr(instance, field, value)
self.add_recipients(instance, recipient_data)
return instance
def validate_recipients(self, recipients_data):
validated_data = []
for recipient_data in recipients_data:
if recipient_data.get('uuid'):
try:
recipient = Recipient.objects.get(uuid=recipient_data.get('uuid'))
except Recipient.DoesNotExist:
raise ValidationError('No recipient with this UUID was found: %s' % recipient_data.get('uuid'))
serializer = RecipientSerializer(recipient, data=recipient_data)
else:
serializer = RecipientSerializer(data=recipient_data)
serializer.is_valid(raise_exception=True)
validated_data.append(serializer.validated_data)
return validated_data
def add_recipients(self, email, recipients_data):
for recipient_data in recipients_data:
if recipient_data.get('uuid'):
serializer = RecipientSerializer(
Recipient.objects.get(uuid=recipient_data.get('uuid')),
data=recipient_data
)
else:
serializer = RecipientSerializer(data=recipient_data)
serializer.is_valid()
serializer.save()
email.recipients.add(serializer.instance)