Сопоставление 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.

Вернуться на верх