Сопоставление DRF GenericViewSet с http-главами через DefaultRouter
Моя цель - реализовать добавление рецепта в текущий список избранного пользователя и его удаление с помощью Django Rest Framework 3.12.x.
POST api/recipes/{int:recipe_id}/favorite
DELETE api/recipes/{int:recipe_id}/favorite
Тело запроса пустое. User_id получен из self.context['request'].user.id.
Т.е. мне нужно обслуживать POST и DELETE на одном и том же URL.
Я реализовал это на DefaultRouter в сочетании с GenericViewSet, но не могу заставить эту реализацию обслуживать DELETE. Только POST обслуживается должным образом.
Вопрос в том, что не так в конфигурации? Почему DRF возвращает 405 "Method Not Allowed" при вызове DELETE и возвращает Http заголовок Allow: POST, OPTIONS?
Конфигурация маршрутизатора:
from rest_framework import routers
from api.views import FavoriteViewSet
_v1_router = routers.DefaultRouter()
_v1_router.register(
r'recipes/(?P<recipe_id>\d+)/favorite',
FavoriteViewSet,
basename='favorite')
Вид:
class FavoriteViewSet(
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
permission_classes = (IsAuthenticated,)
http_method_names = ('post', 'delete', 'head', 'options',)
queryset = Favorite.objects.all()
serializer_class = FavoriteSerializer
pagination_class = None
def get_permissions(self):
return super().get_permissions()
def get_object(self):
return super().get_object()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def destroy(self, request, *args, **kwargs):
super().destroy(request, *args, **kwargs)
def get_serializer_class(self):
return super().get_serializer_class()
def dispatch(self, request, *args, **kwargs):
p1 = hasattr(self, 'get')
p2 = hasattr(self, 'post')
p3 = hasattr(self, 'delete')
p4 = hasattr(self, 'options')
return super().dispatch(request, *args, **kwargs)
Проведя некоторое время в отладчике, я выяснил, что маршрутизатор настраивает два экземпляра моего View, но обслуживает запрос каждый раз на первом.
def as_view(cls, actions=None, initkwargs) - вызывается дважды при инициализации. С параметрами:
post: create
suffix: List, basename: favorite, detail: False
get: retrieve, delete: destroy
suffix: Instance, basename: favorite, detail: True
Я вижу, что что-то не так с конфигурацией, но пока не могу понять, как сделать DELETE-запрос к тому же URL, который обслуживается на втором экземпляре.
В отладчике я вижу, что в диспетчере:
def dispatch(self, request, *args, **kwargs):
p1 = hasattr(self, 'get')
p2 = hasattr(self, 'post')
p3 = hasattr(self, 'delete')
p4 = hasattr(self, 'options')
return super().dispatch(request, *args, **kwargs)
На выбранном экземпляре представления только p2 и p4 являются True, поэтому представление не видит присутствия атрибута 'delete' в моем представлении и отклоняет запрос с 405.