How to cache Django Rest Framework API calls?
Asked Answered
C

3

41

I'm using Memcached as backend to my django app. This code works fine in normal django query:

def get_myobj():
        cache_key = 'mykey'
        result = cache.get(cache_key, None)
        if not result:
            result = Product.objects.all().filter(draft=False)
            cache.set(cache_key, result)
        return result

But it doesn't work when used with django-rest-framework api calls:

class ProductListAPIView(generics.ListAPIView):
    def get_queryset(self):
        product_list = Product.objects.all()
        return product_list
    serializer_class = ProductSerializer

I'm about to try DRF-extensions which provide caching functionality:

https://github.com/chibisov/drf-extensions

but the build status on github is currently saying "build failing".

My app is very read-heavy on api calls. Is there a way to cache these calls?

Thank you.

Carnivore answered 12/7, 2016 at 4:25 Comment(8)
Did you decorate the method with "@cache_response()" ?Eurhythmic
Hi. @cache_response is from DRF-extensions which I haven't try implementing it yet because the build status says "build failing" on their github page : github.com/chibisov/drf-extensionsCarnivore
You realize that the view you pasted doesn't call the cache ?Hallux
Yes I modify values in admin and reload the drf web-browsable api. Values always changed after refresh. Default timeout should be 5 mins if my memory servesCarnivore
But product list on website does not change if refreshed within 5 mins interval. So I assume the cache is working(for website)Carnivore
ok, so now, what's the question here ? Are you asking why it doesn't work or how should you make it work ?Hallux
Hi, The question is how to cache the django-rest-framework api calls. Most of my users are using Android app, not the website. The app make lots of api calls to request the product list, which DRF returns .json. I want to cache this .json response so my Postgres DB won't be hit every time user request a product list. ;)Carnivore
Oh sorry. I just realize that you maybe talking about drf-extension. Let's not use that because its status is ' build-failing' ;)Carnivore
H
59

Ok, so, in order to use caching for your queryset:

class ProductListAPIView(generics.ListAPIView):
    def get_queryset(self):
        return get_myobj()
    serializer_class = ProductSerializer

You'd probably want to set a timeout on the cache set though (like 60 seconds):

cache.set(cache_key, result, 60)

If you want to cache the whole view:

from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

class ProductListAPIView(generics.ListAPIView):
    serializer_class = ProductSerializer

    @method_decorator(cache_page(60))
    def dispatch(self, *args, **kwargs):
        return super(ProductListAPIView, self).dispatch(*args, **kwargs)
Hallux answered 13/7, 2016 at 8:43 Comment(13)
Thanks! Works perfectly.Carnivore
When I try this exactly as shown, I get an error 'ShopsList' object has no attribute 'method'. Any ideas?Acree
That's a another question that can't be answered within the current thread.Hallux
@Acree did you ever figure this out? Or did you post a seperate question for this?Anhwei
everyone, the second part should actually read @method_decorator(cache_page(60)) where you also from django.utils.decorators import method_decoratorMika
Is there a way to make this work when using APIView's as_view() ?Bassist
yes @eugene, just call the cache_page on the view within the URL Router patterns cache_page(YourView.as_view())Olander
@linovia I implemented your code, however no cache calls are seen in the Django Debug toolbar. Is it something with the Django version? we use Django 1.11Olander
I don't how that would relate to Django.Hallux
I've applied the method_decorator to viewset's dispatch. Is there a way to exclude a certain view from being cached?Bassist
I don't see any reason it wouldn't.Hallux
@Hallux how would we clear/refresh the cache when a product is updated or created?Stauder
@RayhanMuktader, do you have any solution for the updating of cache ?Reefer
A
3

I just implemented this to use on my serializers

def cache_me(cache):
    def true_decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            instance = args[1]
            cache_key = '%s.%s' % (instance.facility, instance.id)
            logger.debug('%s cache_key: %s' % (cache, cache_key))
            try:
                data = caches[cache].get(cache_key)
                if data is not None:
                    return data
            except:
                pass
            logger.info('did not cache')
            data = f(*args, **kwargs)
            try:
                caches[cache].set(cache_key, data)
            except:
                pass
            return data
        return wrapper
    return true_decorator

then i override the to_representation method on my serializers, so it caches the serialized output per instance.

class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        exclude = ('is_deleted', 'facility',)

    @cache_me('mymodel')
    def to_representation(self, instance):
       return super(MyModelSerializer, self).to_representation(instance)
Accolade answered 10/7, 2017 at 13:51 Comment(0)
H
0

Below codes using dispatch works fine.

 @method_decorator(cache_page(60 * 60 * 2), 'dispatch')
 @method_decorator(vary_on_cookie, 'dispatch')
 class SampleViewSet(viewsets.ModelViewSet):
    queryset         = Sample.objects.all()
    serializer_class = SampleSerializer

    def get_queryset(self):
        queryset = Sample.objects.filter(is_active=True)
        return queryset
Haftarah answered 19/12, 2023 at 5:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.