web-dev-qa-db-fra.com

Comment faire une requête PATCH en utilisant Django REST cadre

Je ne connais pas très bien le framework Django REST et j'ai essayé beaucoup de choses, mais je ne peux pas faire fonctionner ma demande PATCH.

J'ai un sérialiseur de modèle. C’est le même que j’utilise pour ajouter une nouvelle entrée et, idéalement, je souhaiterais le réutiliser lors de la mise à jour d’une entrée.

class TimeSerializer(serializers.ModelSerializer):
    class Meta:
        model = TimeEntry
        fields = ('id', 'project', 'amount', 'description', 'date')

    def __init__(self, user, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        super(TimeSerializer, self).__init__(*args, **kwargs)
        self.user = user

    def validate_project(self, attrs, source):
        """
        Check that the project is correct
        """
        .....

    def validate_amount(self, attrs, source):
        """
        Check the amount in valid
        """
        .....

J'ai essayé d'utiliser une vue basée sur les classes:

class UserViewSet(generics.UpdateAPIView):
    """
    API endpoint that allows timeentries to be edited.
    """
    queryset = TimeEntry.objects.all()
    serializer_class = TimeSerializer

Mes URL sont:

url(r'^api/edit/(?P<pk>\d+)/$', UserViewSet.as_view(), name='timeentry_api_edit'),

Mon appel JS est:

var putData = { 'id': '51', 'description': "new desc" }
$.ajax({
    url: '/en/hours/api/edit/' + id + '/',
    type: "PATCH",
    data: putData,
    success: function(data, textStatus, jqXHR) {
        // ....
    }
}

Dans ce cas, j'aurais souhaité que ma description soit mise à jour, mais des erreurs m'indiquent que les champs sont obligatoires (pour 'projet' et tout le reste). La validation échoue. Si vous ajoutez à l'appel à AJAX tous les champs, il échoue quand il doit récupérer le 'projet'.

J'ai aussi essayé de faire mon propre point de vue:

@api_view(['PATCH'])
@permission_classes([permissions.IsAuthenticated])
def edit_time(request):

if request.method == 'PATCH':
    serializer = TimeSerializer(request.user, data=request.DATA, partial=True)
    if serializer.is_valid():
        time_entry = serializer.save()
    return Response(status=status.HTTP_201_CREATED) 
return Response(status=status.HTTP_400_BAD_REQUEST) 

Cela ne fonctionnait pas pour une mise à jour partielle pour la même raison (la validation des champs échouait) et cela ne fonctionnait pas même si j'avais envoyé tous les champs. Il crée une nouvelle entrée au lieu de modifier celle existante. 

Je voudrais réutiliser le même sérialiseur et les mêmes validations, mais je suis ouvert à toute autre suggestion. Aussi, si quelqu'un a un morceau de code de travail (code ajax-> api view-> sérialiseur) serait génial.

18
Vlad
class DetailView(APIView):
    def get_object(self, pk):
        return TestModel.objects.get(pk=pk)

    def patch(self, request, pk):
        testmodel = self.get_object(pk)
        serializer = TestModelSerializer(testmodel, data=request.data, partial=True) # set partial=True to update a data partially
        if serializer.is_valid():
            serializer.save()
            return JsonReponse(code=201, data=serializer.data)
        return JsonResponse(code=400, data="wrong parameters")

Documentation
Vous n'avez pas besoin d'écrire le partial_update ni d'écraser la méthode update. Utilisez simplement la méthode patch.

9
ramwin

Assurez-vous que vous avez "PATCH" dans http_method_names. Sinon, vous pouvez l'écrire comme ceci:

@property
def allowed_methods(self):
    """
    Return the list of allowed HTTP methods, uppercased.
    """
    self.http_method_names.append("patch")
    return [method.upper() for method in self.http_method_names
            if hasattr(self, method)]

Comme indiqué dans documentation :

Par défaut, les sérialiseurs doivent recevoir des valeurs pour tous les champs obligatoires, sinon ils généreront des erreurs de validation. Vous pouvez utiliser l'argument partiel pour autoriser les mises à jour partielles.

Remplacez la méthode update dans votre vue:

def update(self, request, *args, **kwargs):
    instance = self.get_object()
    serializer = TimeSerializer(instance, data=request.data, partial=True)
    serializer.is_valid(raise_exception=True)
    serializer.save(customer_id=customer, **serializer.validated_data)
    return Response(serializer.validated_data)

Ou remplacez simplement la méthode partial_update dans votre vue:

def partial_update(self, request, *args, **kwargs):
    kwargs['partial'] = True
    return self.update(request, *args, **kwargs)

Le sérialiseur appelle la méthode update de ModelSerializer (voir sources ):

def update(self, instance, validated_data):
    raise_errors_on_nested_writes('update', self, validated_data)

    # Simply set each attribute on the instance, and then save it.
    # Note that unlike `.create()` we don't need to treat many-to-many
    # relationships as being a special case. During updates we already
    # have an instance pk for the relationships to be associated with.
    for attr, value in validated_data.items():
        setattr(instance, attr, value)
    instance.save()

    return instance

Mise à jour envoie les valeurs validated_data à l'instance donnée. Notez que la mise à jour ne doit pas supposer que tous les champs sont disponibles. Cela aide à gérer les mises à jour partielles (demandes de PATCH).

7
M.Void

La méthode patch s’applique pour moi en utilisant viewset dans DRF. Je te change de code:

class UserViewSet(viewsets.ModelViewSet):
    queryset = TimeEntry.objects.all()
    serializer_class = TimeSerializer

    def perform_update(self, serializer):
        user_instance = serializer.instance
        request = self.request
        serializer.save(**modified_attrs)
        return Response(status=status.HTTP_200_OK)
4
seenu s

Utilisez plutôt ModelViewSet et substituez la méthode perform_update à partir de UpdateModelMixin

class UserViewSet(viewsets.ModelViewSet):
    queryset = TimeEntry.objects.all()
    serializer_class = TimeSerializer

    def perform_update(self, serializer):
        serializer.save()
        # you may also do additional things here
        # e.g.: signal other components about this update

C'est tout. Ne retournez rien dans cette méthode. UpdateModelMixin a mis en œuvre la méthode update pour renvoyer les données mises à jour en réponse pour vous et efface également _prefetched_objects_cache. Voir le code source ici .

0
Raymond