Django Rest Framework POST Обновление, если существующее или созданное
Я новичок в DRF.
Я читал документы API, возможно, это не замечательно, но я не мог найти удобный способ сделать это.
У меня есть объект Answer, который имеет отношение "один к одному" с вопросом.
На лицевой стороне я использовал метод POST для создания ответа, отправленного на api/answers, и метод PUT для обновления, отправленного, например. апи/ответы/24
Но я хочу обработать его на стороне сервера. Я только отправлю метод POST в api/answers, и DRF проверит на основе answer_id или question_id (поскольку это один к одному), если объект существует.
Если это произойдет, оно обновит существующее, если оно не создаст новый ответ.
Где я должен это реализовать, я не мог понять. Переопределить создание в сериализаторе или в ViewSet или что-то еще?
Моя модель, сериализатор и представление выглядят следующим образом:
class Answer(models.Model):
question = models.OneToOneField(Question, on_delete=models.CASCADE, related_name='answer')
answer = models.CharField(max_length=1,
choices=ANSWER_CHOICES,
null=True,
blank=True)
class AnswerSerializer(serializers.ModelSerializer):
question = serializers.PrimaryKeyRelatedField(many=False, queryset=Question.objects.all())
class Meta:
model = Answer
fields = (
'id',
'answer',
'question',
)
class AnswerViewSet(ModelViewSet):
queryset = Answer.objects.all()
serializer_class = AnswerSerializer
filter_fields = ('question', 'answer',)
Ответы
Ответ 1
К сожалению, ваш предоставленный и принятый ответ не отвечает на ваш первоначальный вопрос, так как он не обновляет модель. Это, однако, легко достигается с помощью другого метода удобства: update-or-create
def create(self, validated_data):
answer, created = Answer.objects.update_or_create(
question=validated_data.get('question', None),
defaults={'answer': validated_data.get('answer', None)})
return answer
Это должно создать объект Answer
в базе данных, если один с question=validated_data['question']
не существует с ответом, взятым из validated_data['answer']
. Если он уже существует, django установит свой атрибут ответа на validated_data['answer']
.
Как отмечено в ответе Нирри, эта функция должна находиться внутри сериализатора. Если вы используете общий ListCreateView, он вызовет функцию create после отправки почтового запроса и создания соответствующего ответа.
Ответ 2
Мне также помог ответ @Nirri, но я нашел более элегантное решение с помощью QuerySet API :
def create(self, validated_data):
answer, created = Answer.objects.get_or_create(
question=validated_data.get('question', None),
defaults={'answer': validated_data.get('answer', None)})
return answer
Он делает то же самое - если Answer
с этим Question
не существует, он будет создан, иначе - возвращен как есть при поиске поля question
.
Этот ярлык, однако, не будет обновлять объект. QuerySet API имеет другой метод для операции update
, который называется update_or_create
и опубликован в другом ответе вниз по цепочке.
Ответ 3
Я бы использовал метод создания сериализаторов.
В нем вы можете проверить, есть ли у ответа на вопрос (с идентификатором, который вы задаете в поле, связанном с первичным ключом "вопрос"), и, если он есть, получить объект и обновить его, в противном случае создать новый.
Таким образом, первый вариант будет выглядеть примерно так:
class AnswerSerializer(serializers.ModelSerializer):
question = serializers.PrimaryKeyRelatedField(many=False, queryset=Question.objects.all())
class Meta:
model = Answer
fields = (
'id',
'answer',
'question',
)
def create(self, validated_data):
question_id = validated_data.get('question', None)
if question_id is not None:
question = Question.objects.filter(id=question_id).first()
if question is not None:
answer = question.answer
if answer is not None:
# update your answer
return answer
answer = Answer.objects.create(**validated_data)
return answer
Второй вариант - проверить, существует ли ответ с идентификатором ответа.
Идентификатор ответа не будет отображаться в проверенных данных запросов на публикацию, если вы не использовали своего рода обходной путь и не определили их вручную как поля read_only = false:
id = serializers.IntegerField(read_only=False)
Но вы должны все же переосмыслить это. Есть веская причина, по которой метод PUT и методы POST существуют как отдельные объекты, и вы должны разделять запросы на внешнем интерфейсе.
Ответ 4
Также:
try:
serializer.instance = YourModel.objects.get(...)
except YourModel.DoesNotExist:
pass
if serializer.is_valid():
serializer.save() # will INSERT or UPDATE your validated data
Ответ 5
Я попробовал решение для сериализатора, но, похоже, возникла исключительная ситуация, прежде чем он включил функцию сериализатора create(self, validated_data)
. Это потому, что я использую ModelViewSet
(который в свою очередь использует class CreatedModelMixin
). Дальнейшие исследования показывают, что возникшее здесь исключение:
rest_framework/mixins.py
class CreateModelMixin(object):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) <== Here
Поскольку я хочу сохранить все функции, предоставляемые фреймворком, я предпочитаю перехватывать исключения и использовать маршрут для обновления:
from rest_framework.exceptions import ValidationError
class MyViewSet(viewsets.ModelViewSet)
def create(self, request, *args, **kwargs):
pk_field = 'uuid'
try:
response = super().create(request, args, kwargs)
except ValidationError as e:
codes = e.get_codes()
# Check if error due to item exists
if pk_field in codes and codes[pk_field][0] == 'unique':
# Feed the lookup field otherwise update() will failed
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
self.kwargs[lookup_url_kwarg] = request.data[pk_field]
return super().update(request, *args, **kwargs)
else:
raise e
return response
Мое приложение всегда может вызвать POST /api/my_model/
с параметрами (здесь uuid = первичный ключ).
Однако было бы лучше, если бы мы справились с этим в функции update
?
def update(self, request, *args, **kwargs):
try:
response = super().update(request, *args, **kwargs)
except Http404:
mutable = request.data._mutable
request.data._mutable = True
request.data["uuid"] = kwargs["pk"]
request.data._mutable = mutable
return super().create(request, *args, **kwargs)
return response
Ответ 6
Лучший и более обобщенный способ применить это - обновить объект ModelSerializer с помощью потенциального экземпляра, если он существует. Это позволяет DRF следовать стандартным протоколам и легко абстрагироваться от моделей.
Чтобы сделать вещи общими, начните с создания класса UpdateOrCreate, который будет наследоваться вместе с modelSerializer при создании экземпляра. В этом добавим def update_or_create_helper
.
Затем наследуйте класс UpdateOrCreate
для каждого сериализатора, с которым вы хотите получить функциональность, и добавьте простое определение is_valid
, специфичное для этой модели.
serializers.py
class UpdateOrCreate:
def update_or_create_helper(self, obj_model, pk):
# Check to see if data has been given to the serializer
if hasattr(self, 'initial_data'):
# Pull the object from the db
obj = obj_model.objects.filter(pk=self.initial_data[pk])
# Check if one and only one object exists with matching criteria
if len(obj)==1:
# If you want to allow for partial updates
self.partial = True
# Add the current instance to the object
self.instance = obj[0]
# Continue normally
return super().is_valid()
...
# Instantiate the model with your standard ModelSerializer
# Inherit the UpdateOrCreate class
class MyModelSerializer(serializers.ModelSerializer, UpdateOrCreate):
class Meta:
model = MyModel
fields = ['pk', 'other_fields']
# Extend is_valid to include the newly created update_or_create_helper
def is_valid(self):
return self.update_or_create_helper(obj_model=MyModel, pk='pk')