pythondjangocachingauthorizationdjango-grappelli

Django 1.11: disable cache for authentificated users


We have a legacy application written in python 2.7 and django 1.11 (and no resources to migrate). Also it uses grappelli for authorization. We tried to add Edit links for some pages (each of the pages displays detailed info on a Round object) that should be visible only for authorized users with rights to edit a Round ( APPNAME | round | Can change round in the grappelli web interface). In the template, the permission is checked like so:

{% if perms.round.can_change_round %}
    &emsp;<a href="{{link_to_change_round}}" class="stuff-only-link">{% trans 'Edit' %}</a>
{% endif %}

The problem occurs when the following events take place in a short time interval:

  1. A user which has the permission to edit a Round visits a page - and sees the Edit link.
  2. A user which has no permission to edit a Round (e.g. anonymous user) visits the same page - and also sees the link!

Revelant settings (settings.py) are:

CACHES = {
    'default': {
    #   'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}

SOLO_CACHE = 'default'
SOLO_CACHE_TIMEOUT = 5*60

When I change cache to dummy, the problem disappears. Thus, it seems to be an obvious solution to totally disable caching for authorized users. To be more precise:

a) If a user is anonymous (the most of real site users) - the requested page can be written to the cache and can be readed from the cache;

b) If a user is authorized (about 5-7 users) - the requested page can NOT be written to the cache and can NOT be readed from the cache.

How do I achieve this?


Solution

  • Many thanks to @Melvin for the links to the documentation. After an hour of googling, an answer was found and adapted. The code is:

    EDIT: Originally, the cache was function-based. So, "/rounds/1" gave the same (cached) value as "/rounds/2". We add the full URL to the cache key to fix the problem.

    # -*- encoding: utf-8 -*-
    '''
    Python >= 2.4
    Django >= 1.0
    
    Author: eu@rafaelsdm.com
    '''
    # https://djangosnippets.org/snippets/2524/
    # https://stackoverflow.com/questions/20146741/django-per-user-view-caching
    # https://stackoverflow.com/questions/62913281/django-1-11-disable-cache-for-authentificated-users
    
    from django.core.cache import cache
    
    def cache_per_user(ttl=None, prefix=None):
        '''Decorador que faz cache da view pra cada usuario
        * ttl - Tempo de vida do cache, não enviar esse parametro significa que o
          cache vai durar até que o servidor reinicie ou decida remove-lo 
        * prefix - Prefixo a ser usado para armazenar o response no cache. Caso nao
          seja informado sera usado 'view_cache_'+function.__name__
        * cache_post - Informa se eh pra fazer cache de requisicoes POST
        * O cache para usuarios anonimos é compartilhado com todos
        * A chave do cache será uma das possiveis opcoes:
            '%s_%s'%(prefix, user.id)
            '%s_anonymous'%(prefix)
            'view_cache_%s_%s'%(function.__name__, user.id)
            'view_cache_%s_anonymous'%(function.__name__)
        '''
        def decorator(function):
            def apply_cache(request, *args, **kwargs):
    
                # No caching for authorized users:
                # they have to see the results of their edits immideately!
    
                can_cache = request.user.is_anonymous() and request.method == 'GET'
    
                # Gera a chave do cache
                if prefix:
                    CACHE_KEY = '%s_%s'%(prefix, 'anonymous')
                else:
                    CACHE_KEY = 'view_cache_%s_%s_%s'%(function.__name__, request.get_full_path(), 'anonymous')
    
                if can_cache:
                    response = cache.get(CACHE_KEY, None)
                else:
                    response = None
    
                if not response:
                    print 'Not in cache: %s'%(CACHE_KEY)
                    response = function(request, *args, **kwargs)
                    if can_cache:
                        cache.set(CACHE_KEY, response, ttl)
                return response
            return apply_cache
        return decorator
    

    and then in views.py:

    from cache_per_user import cache_per_user as cache_page
    #...
    #
    #
    @cache_page(cache_duration)
    def round_detail(request, pk):