Как сделать запрос 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, и у вас есть та же модель сзади и спереди. Надеюсь, что это может быть полезно.