Функциональность Django REST Framework ModelSerializer get_or_create

StackOverflow https://stackoverflow.com//questions/25026034

  •  21-12-2019
  •  | 
  •  

Вопрос

Когда я пытаюсь десериализовать некоторые данные в объект, если я включаю уникальное поле и присваиваю ему значение, которое уже присвоено объекту в базе данных, я получаю ошибку ограничения ключа.Это имеет смысл, поскольку он пытается создать объект с уникальным значением, которое уже используется.

Есть ли способ использовать функциональность типа get_or_create для ModelSerializer?Я хочу иметь возможность передавать сериализатору некоторые данные, и если существует объект с заданным уникальным полем, просто верните этот объект.

Это было полезно?

Решение

В моем опыте решения 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
.

Другие советы

Метод rectore_Object serializer был удален, начиная с версии REST REST 3.0.

Простой способ добавить функциональность 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)
.

Тем не менее, мне кажется, что полученный код является более компактным или простым для понимания, чем если вы запрашиваете, если экземпляр существует, то обновите или сохраняют в зависимости от результата запроса.

Существует несколько сценариев, в которых сериализатору может потребоваться возможность получать или создавать объекты на основе данных, полученных представлением - где для представления нелогично выполнять функции поиска/создания - я столкнулся с этим на этой неделе.

Да, возможно иметь get_or_create функциональность в сериализаторе.В документации есть подсказка по этому поводу: http://www.django-rest-framework.org/api-guide/serializers#specifying-what-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.

Ответ @Groady работает, но теперь вы потеряли возможность проверять уникальность при создании новых объектов (UniqueValidator был удален из вашего списка валидаторов независимо от обстоятельств).Вся идея использования сериализатора заключается в том, что у вас есть комплексный способ создания нового объекта, который проверяет целостность данных, которые вы хотите использовать для создания объекта.Удаление проверки — это не то, что вам нужно.Вы ДЕЙСТВИТЕЛЬНО хотите, чтобы эта проверка присутствовала при создании новых объектов, вы просто хотите иметь возможность передавать данные в сериализатор и получать правильное поведение под капотом (get_or_create), проверку и все включено.

Я бы рекомендовал перезаписать ваш is_valid() вместо этого метод в сериализаторе.С помощью приведенного ниже кода вы сначала проверяете, существует ли объект в вашей базе данных, если нет, вы продолжаете полную проверку, как обычно.Если он существует, вы просто прикрепляете этот объект к своему сериализатору, а затем продолжаете проверку как обычно, как если бы вы создали экземпляр сериализатора со связанным объектом и данными.Затем, когда вы нажмете Serializer.save(), вы просто вернете уже созданный объект и сможете использовать тот же шаблон кода на высоком уровне:создайте экземпляр вашего сериализатора с данными, позвоните .is_valid(), тогда позвони .save() и получите экземпляр вашей модели (а-ля get_or_create).Нет необходимости перезаписывать .create() или .update().

Предостережение здесь в том, что вы получите ненужную UPDATE транзакция в вашей базе данных, когда вы нажимаете .save(), но затраты на один дополнительный вызов базы данных, чтобы иметь чистый API разработчика с полной проверкой, кажутся оправданными.Это также позволяет вам расширять использование пользовательских моделей. Manager и пользовательских моделей. QuerySet, чтобы однозначно идентифицировать вашу модель только по нескольким полям (какими бы ни были основные идентифицирующие поля), а затем использовать остальные данные в initial_data в сериализаторе как обновление рассматриваемого объекта, что позволяет вам захватывать уникальные объекты из подмножества полей данных и обрабатывать оставшиеся поля как обновления объекта (в этом случае UPDATE звонок не будет лишним).

Обратите внимание, что вызовы super() находятся в синтаксисе Python3.Если вы используете Python 2, вы захотите использовать старый стиль: super(MyModelSerializer, self).is_valid(**kwargs)

from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned


class MyModelSerializer(serializers.ModelSerializer):

    def is_valid(self, raise_exception=False):
        if hasattr(self, 'initial_data'):
            # If we are instantiating with data={something}
            try:
                # Try to get the object in question
                obj = Security.objects.get(**self.initial_data)
            except (ObjectDoesNotExist, MultipleObjectsReturned):
                # Except not finding the object or the data being ambiguous
                # for defining it. Then validate the data as usual
                return super().is_valid(raise_exception)
            else:
                # If the object is found add it to the serializer. Then
                # validate the data as usual
                self.instance = obj
                return super().is_valid(raise_exception)
        else:
            # If the Serializer was instantiated with just an object, and no
            # data={something} proceed as usual 
            return super().is_valid(raise_exception)

    class Meta:
        model = models.MyModel

Лучший способ сделать это — использовать PUT вместо этого глагол, а затем переопределить get_object() метод в ModelViewSet.Я ответил на это здесь: https://stackoverflow.com/a/35024782/3025825.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top