Функциональность Django REST Framework ModelSerializer get_or_create
-
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.