gatlanticus
gatlanticus

Reputation: 1226

Django Rest Framework: Use DELETE on GenericAPIView without implementing Retrieve

I'm using Django Rest Framework and want to be able to delete a Content instance via DELETE to /api/content/<int:pk>/. I don't want to implement any method to respond to GET requests.

When I include a .retrieve() method as follows, the DELETE request works:

class ContentViewSet(GenericViewSet):
    def get_queryset(self):
        return Content.objects.filter(user=self.request.user)

    def retrieve(self, request, pk=None):
        pass    #this works, but I don't want .retrieve() at all

    def delete(self, request, pk=None):
        content = self.get_object()
        #look up some info info here
        content.delete()
        return Response('return some info')

If I replace .retrieve() with RetrieveModelMixin it also works. However, if I remove both of these, which is what want to do, I get the following error.

django.urls.exceptions.NoReverseMatch: Reverse for 'content-detail' not found. 'content-detail' is not a valid view function or pattern name.

I haven't tested, but I assume the same thing would happen with PUT and PATCH.

My questions are:

  1. How can I allow DELETE without implementing a .retrieve() method, and
  2. Why can't DRF create the urlconf without .retrieve() implemented?

UPDATE: Failing test and complete error traceback caused by removing .retrieve() method

from rest_framework.test import APITestCase, APIClient
from myapp.models import Content

class ContentTestCase(APITestCase):
    def setUp(self):
        self.content = Content.objects.create(title='New content')
        self.client = APIClient()

    def test_DELETE_content(self):
        url = reverse('content-detail', kwargs={'pk':self.content.pk})
        response = self.client.delete(url)
        self.assertEqual(response.status_code, 200)

Results in:

Traceback (most recent call last):
  File "myproject/myapp/tests.py", line 548, in test_DELETE_content
    url = reverse('content-detail', kwargs={'pk':self.content})
  File "python3.6/site-packages/rest_framework/reverse.py", line 50, in reverse
    url = _reverse(viewname, args, kwargs, request, format, **extra)
  File "python3.6/site-packages/rest_framework/reverse.py", line 63, in _reverse
    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
  File "python3.6/site-packages/django/urls/base.py", line 90, in reverse
    return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
  File "python3.6/site-packages/django/urls/resolvers.py", line 636, in _reverse_with_prefix
    raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'content-detail' not found. 'content-detail' is not a valid view function or pattern name.

Upvotes: 2

Views: 4141

Answers (3)

menecio
menecio

Reputation: 84

Wild guess here but did you use a SimpleRouter or a DefaultRouter to build your urlpatterns?

If so, that's your problem. The router uses a viewset and expects to have all methods implemented. More info here

What you can do is just add your url to urlpatterns like you would normally do on django using the .as_view() method.

Upvotes: 1

JPG
JPG

Reputation: 88509

  1. How can I allow DELETE without implementing a .retrieve() method?

Just remove the retrieve() method from the view class. Which means, the GenericViewSet doesn't provide any HTTP Actions unless it's defined in your class.

So, the following will be your code snippet,

class ContentViewSet(GenericViewSet):

    def get_queryset(self):
        return Content.objects.filter(user=self.request.user)

    def delete(self, request, pk=None):
        content = self.get_object()
        # look up some info info here
        content.delete()
        return Response('return some info')

or you could use mixin classes here,

from rest_framework.mixins import DestroyModelMixin


class ContentViewSet(DestroyModelMixin, GenericViewSet):

    def get_queryset(self):
        return Content.objects.filter(user=self.request.user)

  1. Why can't DRF create the urlconf without .retrieve() implemented?

I'm not sure how you've defined your URLs. When I tried with DRF Router, it's only creating the URL conf for defined actions.

You've got GET and DELETE actions on your end-point because of you'd defined the retrieve() method in your view class.

Hope this help :)

Upvotes: 2

gatlanticus
gatlanticus

Reputation: 1226

My solution for part 1. is to include the mixin but restrict the http_method_names:

class ContentViewSet(RetrieveModelMixin, GenericViewSet):
    http_method_names = ['delete']
    ...

However, I still don't know why I have to include RetrieveModelMixin at all.

Upvotes: 0

Related Questions