web-dev-qa-db-ger.com

Django Rest Framework POST Aktualisieren, falls vorhanden oder erstellen

Ich bin neu bei DRF .. Ich habe die API-Dokumente gelesen, vielleicht ist es nicht wahrnehmbar, aber ich konnte keinen praktischen Weg finden, dies zu tun.

Ich habe ein Antwortobjekt, das eine Eins-zu-Eins-Beziehung zu einer Frage hat.

Auf der Vorderseite habe ich die Methode POST verwendet, um eine an api/answers gesendete Antwort zu erstellen, und die zu aktualisierende PUT-Methode an z. api/Antworten/24

Aber ich möchte es auf der Serverseite behandeln. Ich werde nur eine POST - Methode an api/answers senden, und DRF prüft anhand von answer_id oder question_id (da es sich um eins zu eins handelt), ob das Objekt vorhanden ist. Wenn dies der Fall ist, wird die vorhandene aktualisiert, andernfalls wird eine neue Antwort erstellt.

Wo ich es implementieren sollte, konnte ich nicht verstehen. Create im serializer oder in ViewSet oder etwas anderes überschreiben?

Mein Modell, Serializer und Ansicht sind wie folgt:

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',)
11
Ali Ankarali

Antwort von @Nirri hat mir auch geholfen, aber ich habe mit Django QuerySet API Shortcut eine elegantere Lösung gefunden:

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

Es tut genau das gleiche - wenn Answer zu Question nicht existiert, wird es erstellt, andernfalls - zurückgegeben, wie es durch question-Feldsuche erfolgt.

Diese Verknüpfung aktualisiert das Objekt jedoch nicht. QuerySet API hat eine andere Methode für eine update -Operation, die update_or_create heißt und in anderer Antwort im Thread gepostet wird.

9
Damaged Organic

Leider beantwortet Ihre bereitgestellte und akzeptierte Antwort Ihre ursprüngliche Frage nicht, da das Modell nicht aktualisiert wird. Dies wird jedoch leicht durch eine andere bequeme Methode erreicht: 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

Dadurch sollte ein Answer-Objekt in der Datenbank erstellt werden, wenn eines mit question=validated_data['question'] nicht vorhanden ist und die Antwort aus validated_data['answer'] entnommen wurde. Wenn es bereits existiert, setzt Django sein Antwortattribut auf validated_data['answer'].

Wie aus der Antwort von Nirri hervorgeht, sollte sich diese Funktion im Serializer befinden. Wenn Sie das generische ListCreateView verwenden, ruft es die create-Funktion auf, sobald eine Post-Anfrage gesendet wird, und generiert die entsprechende Antwort.

20
K Moe

Ich würde die Create-Methode der Serialisierer verwenden. 

Hier können Sie überprüfen, ob die Frage (mit der ID, die Sie im Feld "Primärschlüssel" für die Frage angegeben haben) bereits eine Antwort hat, und wenn ja, das Objekt abrufen und aktualisieren, andernfalls eine neue erstellen.

Die erste Option würde also so aussehen:

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

Die zweite Möglichkeit wäre zu prüfen, ob die Antwort mit der Antwort-ID existiert.

Antwort-IDs werden nicht in den überprüften Daten von Post-Requests angezeigt, es sei denn, Sie haben eine Art Problemumgehung verwendet und diese manuell als read_only = false -Felder definiert:

id = serializers.IntegerField(read_only=False)

Sie sollten dies jedoch durchdenken. Es gibt einen guten Grund, dass die Methoden PUT und POST als separate Entitäten existieren, und Sie sollten die Anforderungen am Frontend trennen.

3
xtrinch

Ich habe die Serialisiererlösung ausprobiert, aber es scheint eine Ausnahme zu sein, bevor die Serialisierfunktion create(self, validated_data) aufgerufen wird. Das ist, weil ich ModelViewSet verwende (was wiederum class CreatedModelMixin verwendet). Weitere Studien zeigen, dass hier eine Ausnahme ausgelöst wurde:

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

Da ich alle vom Framework bereitgestellten Features beibehalten möchte, ziehe ich es vor, die Ausnahmen zu erfassen und die Route zu aktualisieren, um zu aktualisieren:

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

Meine App kann POST /api/my_model/ immer mit Parametern aufrufen (hier uuid = Primärschlüssel).

Wäre es jedoch besser, wenn wir dies in der Funktion update behandeln würden?

    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
1
John Pang

Ebenfalls:

try:
   serializer.instance = YourModel.objects.get(...)
except YourModel.DoesNotExist:
   pass

if serializer.is_valid():
   serializer.save()    # will INSERT or UPDATE your validated data
0