Caching problem with Django Rest Framework

Hei All,

I’ve got an issue on which I’m a bit stuck. I’m trying to cache a DRF viewset with the following:


from django.utils.decorators import method_decorator
from django.views.decorators.vary import vary_on_cookie
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.views.decorators.cache import cache_page

CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


class CasesView(generics.ListAPIView):
    serializer_class = CaseSerializer
    permission_classes = [IsAuthenticated]

    @method_decorator(cache_page(CACHE_TTL))
    @method_decorator(vary_on_cookie)
    def get(self, request, *args, **kwargs):
        species_list = Species.objects.all()
        permitted_species = [species.species for species in species_list]
        species = self.request.query_params.get("species", None)

        if species is not None and species not in permitted_species:
            return Response("Invalid species", status.HTTP_400_BAD_REQUEST)
        return super().get(request, *args, **kwargs)

When hitting the endpoint I get an exception and the exception I am getting is: 'NewCaseAPIView' object has no attribute 'method'. The full stack trace is below.

Anyone have an idea of what I am doing wrong here?

Cheers,

Conor

web_1    | Traceback (most recent call last):
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
web_1    |     response = get_response(request)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
web_1    |     response = self.process_exception_by_middleware(e, request)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
web_1    |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
web_1    |     response = view_func(request, *args, **kwargs)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
web_1    |     return view_func(*args, **kwargs)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 71, in view
web_1    |     return self.dispatch(request, *args, **kwargs)
web_1    |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
web_1    |     response = self.handle_exception(exc)
web_1    |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
web_1    |     self.raise_uncaught_exception(exc)
web_1    |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
web_1    |     raise exc
web_1    |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
web_1    |     response = handler(request, *args, **kwargs)
web_1    |   File "/usr/local/lib/python3.8/site-packages/rest_framework/generics.py", line 208, in get
web_1    |     return self.retrieve(request, *args, **kwargs)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/utils/decorators.py", line 122, in _wrapped_view
web_1    |     result = middleware.process_request(request)
web_1    |   File "/usr/local/lib/python3.8/site-packages/django/middleware/cache.py", line 132, in process_request
web_1    |     if request.method not in ('GET', 'HEAD'):
web_1    | AttributeError: 'NewCaseAPIView' object has no attribute 'method

I believe the currently-accepted method for caching a class-based view is by specifying the cache in the URL conf.

https://docs.djangoproject.com/en/3.0/topics/cache/#specifying-per-view-cache-in-the-urlconf

You’re a gem, Ken! Thank you.

I was actually doing this, but didn’t think it was working, so I tried to see if I could cache the views. Turns out my front-end developer buddy was sending cache-control: no-cache

I also found this link which led me to believe that what I was doing was fine.

https://www.django-rest-framework.org/api-guide/caching/

It might be the case that it is no longer supported. If it isn’t, I’ll suggest a change to the documentation.

Cheers,

Conor

1 Like

I’ve also found that if you want to cache a viewset, then you can add the caching decorator to dispatch()

As an example:

class FurtherReadingViewSet(viewsets.ReadOnlyModelViewSet):

    serializer_class = FurtherReadingSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return FurtherReading.objects.all().filter(
            case__id=self.kwargs["case"],
            case__species__species__exact=self.kwargs["species"],
        )

    @method_decorator(cache_page(CACHE_TTL))
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)
2 Likes

@conor Thanks for sharing this.
Could you please also explain how are you invalidating the cache.