我应该如何处理Django Rest Framework唯一字段超链接关系?

时间:2019-04-10 14:24:30

标签: python django python-3.x django-rest-framework

我正在尝试为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",
      }
    ],
}

1 个答案:

答案 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)