0

I have been struggling for hours with this: I just can't figure a proper way to cache an object queryset result (object = queryset.get()) in order to avoid re-hitting the database on each view request.

This is my current (simplified) code, and as you can see, I override get_object() to add some extra data (not only the today variable), check if object is in sessions and add object to session.

views.py

from myapp import MyModel
from django.core.cache.utils import make_template_fragment_key
from django.views.generic import DetailView

class myClassView(DetailView):
    model = MyModel

    def get_object(self,queryset=None):

        if queryset is None:
            queryset = self.get_queryset()
        pk = self.kwargs.get(self.pk_url_kwarg, None) 
        if pk is not None:
            queryset = queryset.filter(pk=pk) 
        else:
            raise AttributeError("My error message.")
        try:
            today = datetime.today().strftime('%Y%m%d')
            cache_key = make_template_fragment_key('some_name', [pk, today])

            if cache.has_key(cache_key):
                object = self.request.session[cache_key]
                return object
            else:
                object = queryset.get()
                object.id = my_id
                object.today = today

                # Add object to session
                self.request.session[cache_key] = object

        except queryset.model.DoesNotExist:
            raise Http404("Error 404")
        return object

The above only works if I add the following:

settings.py

SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

But I don't like this hack since it is not secure for Django 1.6 and newer versions because, according to How To Use Sessions (Django 1.7 documents):

If the SECRET_KEY is not kept secret and you are using the PickleSerializer, this can lead to arbitrary remote code execution

If I don't add the SESSIONS_SERIALIZER line I get a "django object is not JSON serializable" error. However, elsewhere my code breaks and I get KeyError errors when trying to pull data from session. This issue is solved converting my string keys into integers. Before changing the settings file Django was converting the str keys into integers automatically when session data was getting requested.

So considering this session serializer security issue I'd prefer other option. So I read here and here about caching get_object(), but I just don't get how to fit that into my get_object() bit. I tried..

if cache.has_key(cache_key):
    self._object = super(myClassView,self).get_object(queryset=None) 
    return self._object

...but it fails. This seems the best solution so far. But how do I implement this into my code? Or, is there a better idea? I'm all ears. Thanks!

Community
  • 1
  • 1
BringBackCommodore64
  • 4,910
  • 3
  • 27
  • 30

1 Answers1

1

You should step back and reassess the situation. What are you trying to achieve? The get_object is a method that get called in the detailed view to access one specific object from the database. If you access this method the first time the object gets invalidated and cached in the Queryset. In order to cache the get_queryset method you need a good cache backend like Redis or Memcached in place so that you can do a simple Write-through Cache operation:

if cache.has_key(cache_key):
   object = cache.get(cache_key)
   return object
else:
   object = queryset.get(pk=pk)
   cache.set(cache_key,object)
   return object

Note that the django objects are serialized in the cache backend and retrieved as objects when deserialised. That approach is the just a starting point. You cache the object the first time it misses. You can also add a post_save,post_update signal to save the object in the cache every time the model is saved or updated:

@receiver(post_save, sender=MyModel)
@receiver(post_delete, sender=MyModel)
def add_MyModel_to_cache(sender, **kwargs):
    object = kwargs['instance']
    cache.set(cache_key,object)

You have to carefully review what you want to cache and when as it is very easy to misjudge requests

Fanis Despoudis
  • 362
  • 1
  • 7