Как я должен обрабатывать отношения Django Rest Framework Unique Field с гиперссылками? - PullRequest
0 голосов
/ 10 апреля 2019

Я пытаюсь написать собственное обновление для 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, которое я не смог понять, как получить экземпляр Recipient из URL-адреса гиперссылки.

Пример Post / Put

{
    "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 Ответ

1 голос
/ 10 апреля 2019

Со своей структурой отношений при создании экземпляра Email вы также передаете данные для экземпляров Recipient , как новых, так и существующих получателей.Упомянутая ошибка проверки происходит потому, что при использовании вложенных сериализаторов при создании или обновлении 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 внутри методов создания / обновления сериализаторов, запускайте проверки в методах проверки и используйте методы создания / обновления только для создания/ обновление.Напишите эти методы с таким складом ума: если я добрался до этого метода, предоставленные данные должны быть действительными, поэтому я просто продолжу создавать / обновлять экземпляр.И запишите свои проверки, помня об этом тоже.

Пример сериализаторов

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...