Как сделать запрос PATCH с использованием инфраструктуры DJANGO REST

Я не очень разбираюсь в Django REST framework и уже пробовал много вещей, но не могу заставить мой запрос PATCH работать.

У меня есть модель сериализатора. Это тот же самый файл, который я использую для добавления новой записи, и в идеале я хотел бы использовать его повторно при обновлении записи.

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
        """
        .....

Я пытался использовать представление на основе классов:

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

Мои URL-адреса:

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

Мой звонок в JS:

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

В этом случае я хотел бы, чтобы мое описание было обновлено, но я получаю ошибки, что поля обязательны для заполнения (для "проекта" и всего остального). Проверка не пройдена. Если добавить к вызову AJAX все поля, он все равно не сможет получить "проект".

Я также пытался составить собственное мнение:

@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) 

Это не сработало для частичного обновления по той же причине (проверка полей не удалась) и не сработало, даже если я отправил все поля. Создает новую запись вместо редактирования существующей.

Я хотел бы повторно использовать тот же сериализатор и проверки, но я открыт для любых других предложений. Также, если у кого-то есть рабочий код (ajax code-> api view-> serializer), это было бы здорово.

Ответы

Ответ 1

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

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

Документация
Вам не нужно писать partial_update или перезаписывать метод update. Просто используйте метод patch.

Ответ 2

Убедитесь, что у вас есть "PATCH" в http_method_names. В качестве альтернативы вы можете написать это так:

@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)]

Как указано в документации:

По умолчанию сериализаторам должны быть переданы значения для всех обязательных полей, иначе они вызовут ошибки проверки. Вы можете использовать частичный аргумент, чтобы разрешить частичное обновление.

Переопределите метод update по вашему мнению:

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)

Или просто переопределите метод partial_update в вашем представлении:

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

Сериализатор вызывает метод обновления ModelSerializer (см. Источники):

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

Обновление отправляет значения validated_data в данный экземпляр. Обратите внимание, что обновление не должно предполагать, что все поля доступны. Это помогает справляться с частичными обновлениями (запросами PATCH).

Ответ 3

Метод patch работает для меня, используя viewset в DRF. Я меняю код:

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

Используйте взамен ModelViewSet и переопределите метод perform_update из 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

Это. Не возвращайте ничего в этом методе. UpdateModelMixin реализовал метод update для возврата обновленных данных в качестве ответа для вас, а также очищает _prefetched_objects_cache. Смотрите исходный код здесь.

Ответ 5

Я тоже столкнулся с этой проблемой, решил ее, переопределив get_serializer_method и добавив собственную логику для обработки частичного обновления. Python 3

 class ViewSet(viewsets.ModelViewSet):
     def get_serializer_class(self):
         if self.action == "partial_update":
             return PartialUpdateSerializer

Примечание: вам может потребоваться переопределить функцию part_update на сериализаторе. Вот так:

class PartialUpdateSerializer(serializers.Serializer):
    def partial_update(self, instance, validated_data):
       *custom logic*
       return super().update(instance, validated_data)

Ответ 6

Другая возможность - сделать запрос по URL. Например, у меня есть такая модель

      class Author(models.Model):
        FirstName = models.CharField(max_length=70)
        MiddleName = models.CharField(max_length=70)
        LastName = models.CharField(max_length=70)
        Gender = models.CharField(max_length=1, choices = GENDERS)
        user = models.ForeignKey(User, default = 1, on_delete = models.CASCADE, related_name='author_user')
        IsActive = models.BooleanField(default=True)
        class Meta:
          ordering = ['LastName']

И такой вид

      class Author(viewsets.ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer

Так что можете ввести http://127.0.0.1:8000/author/, чтобы получить или опубликовать авторов. Если я хочу сделать запрос PATCH, вы можете указать http://127.0.0.1:8000/author/ID_AUTHOR от вашего клиента. Например, в angular2 вы можете получить что-то вроде этого

       patchRequest(item: any): Observable<Author> {
        return this.http.patch('http://127.0.0.1:8000/author/1', item);
       }

Предполагается, что вы настроили CORS, и у вас есть та же модель сзади и спереди. Надеюсь, что это может быть полезно.