Django REST Framework ModelSerializer Функция get_or_create

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

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

Ответ 1

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

Да, в 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 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.

Ответ 2

По моему опыту решение 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

Ответ 3

Метод Serializer restore_object был удален с версии 3.0 REST Framework версии 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)

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

Ответ 4

@Ответ на Groady работает, но теперь вы потеряли способность проверять уникальность при создании новых объектов (UniqueValidator был удален из вашего списка валидаторов, независимо от того, как cicumstance). Вся идея использования сериализатора заключается в том, что у вас есть всеобъемлющий способ создания нового объекта, который проверяет целостность данных, которые вы хотите использовать для создания объекта. Удаление проверки не является тем, что вы хотите. Вы хотите, чтобы эта валидация присутствовала при создании новых объектов, вы просто хотели бы получить данные в своем сериализаторе и получить правильное поведение под капотом (get_or_create), валидацию и все включенные.

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

Опасность здесь заключается в том, что вы получите ненужную транзакцию UPDATE в своей базе данных, когда вы нажмете .save(), но стоимость одного дополнительного вызова базы данных, чтобы иметь чистый API-интерфейс разработчика с полной проверкой, все еще существует. Это также позволяет расширять использование пользовательских моделей .Manager и custom models.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

Ответ 5

Лучший способ сделать это - использовать вместо глагола PUT, а затем переопределить метод get_object() в ModelViewSet. Я ответил на это здесь: fooobar.com/info/548339/....