Je suis nouveau sur DRF ... Je lis la documentation de l’API, c’est peut-être inconscient, mais je n’ai pas trouvé un moyen pratique de le faire.
J'ai un objet Answer qui entretient une relation personnelle avec une question.
Sur le recto, j’utilisais la méthode POST pour créer une réponse envoyée à api/answers et la méthode PUT pour la mise à jour envoyée à, par exemple. api/réponses/24
Mais je veux le gérer du côté du serveur. Je n'enverrai qu'une méthode POST à api/answers et DRF vérifiera en fonction de answer_id ou de question_id (puisqu'il s'agit d'une réponse à une) si l'objet existe. Si c'est le cas, il mettra à jour l'existant, sinon il créera une nouvelle réponse.
Où je devrais le mettre en application, je ne pouvais pas comprendre. Remplacer créer dans le sérialiseur ou dans ViewSet ou quelque chose d'autre?
Mon modèle, mon sérialiseur et ma vue sont les suivants:
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',)
La réponse publiée par @Nirri m'a aussi aidé, mais j'ai trouvé une solution plus élégante avec Django QuerySet API raccourci:
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
Il fait exactement la même chose - si Answer
à cette Question
n'existe pas, il sera créé, sinon - renvoyé tel quel par la recherche dans le champ question
.
Ce raccourci, cependant, ne mettra pas à jour l'objet. QuerySet API a une autre méthode pour une opération update
, appelée update_or_create
et publiée dans autre réponse en aval du fil .
Malheureusement, votre réponse fournie et acceptée ne répond pas à votre question initiale, car elle ne met pas à jour le modèle. Ceci est cependant facilement réalisable par une autre méthode pratique: 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
Cela devrait créer un objet Answer
dans la base de données s'il en existe un avec question=validated_data['question']
avec la réponse extraite de validated_data['answer']
. S'il existe déjà, Django définira son attribut answer sur validated_data['answer']
.
Comme indiqué dans la réponse de Nirri, cette fonction devrait résider dans le sérialiseur. Si vous utilisez le nom générique ListCreateView , la fonction de création sera appelée une fois la demande d'envoi envoyée et générera la réponse correspondante.
J'utiliserais la méthode create des sérialiseurs.
Vous pouvez y vérifier si la question (avec l'ID de celle-ci que vous indiquez dans le champ relatif à la clé primaire de la question) a déjà une réponse. Si c'est le cas, récupérez l'objet et mettez-le à jour, sinon créez-en un nouveau.
Donc, la première option serait quelque chose comme:
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
La deuxième option serait de vérifier si la réponse avec l'identifiant de réponse existe.
Les ID de réponse n'apparaissent pas dans les données validées des demandes de publication, à moins que vous n'ayez utilisé une sorte de solution de contournement et que vous les ayez définis manuellement comme des champs read_only = false:
id = serializers.IntegerField(read_only=False)
Mais vous devriez cependant repenser cela de fond en bout. Il y a une bonne raison pour que la méthode PUT et les méthodes POST existent en tant qu'entités séparées, et vous devez séparer les demandes sur le front-end.
J'ai essayé la solution de sérialiseur, mais il semble qu'une exception soit levée avant d'appuyer sur la fonction de sérialiseur create(self, validated_data)
. C'est parce que j'utilise ModelViewSet
(qui utilise à son tour class CreatedModelMixin
). Une étude plus approfondie révèle que l'exception évoquée ici:
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
Puisque je veux conserver toutes les fonctionnalités fournies par le framework, je préfère donc capturer les exceptions et acheminer par la route pour mettre à jour:
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
Mon application peut toujours appeler POST /api/my_model/
avec des paramètres (ici, uuid = clé primaire).
Cependant, serait-il préférable de gérer cela dans la fonction 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
Également:
try:
serializer.instance = YourModel.objects.get(...)
except YourModel.DoesNotExist:
pass
if serializer.is_valid():
serializer.save() # will INSERT or UPDATE your validated data