Django Caching, i18n en Authenticatie

Django bied geweldige tools voor caching eenvoudig maken. Het heeft naar mijn mening wel 2 grote beperkingen:

  • Het stuurt automatisch Cache-control headers, waardoor de client’s cache verhinderd dat de pagina goed herlaad als de taal gewijzigd word en er geen taal prefix in de url gebruikt word
  • Het cached niet per gebruiker, waardoor als we niet opletten er mogelijks private informatie gelekt kan worden.

Beide van deze beperkingen waren oorspronkelijk een probleem bij het herschrijven van Skyz. Het eerste probleem kon deels verholpen worden door taalprefixes toe te voeren aan de url’s, de andere problemen kon ik oplossen op de volgende manieren.

Cache control headers verwijderen

Het eerste probleem kan opgelost worden door de @cache_page decorator te wrappen in een @never_cache decorator (door deze boven de @cache_page decorator te plaatsen zoals ik in een vorige post heb toegelicht), of met een stukje middleware om nooit pagina’s te cachen

# app/middleware.py
from django.utils.cache import add_never_cache_headers

def disable_client_side_caching_middleware(get_response):
    def middleware(request):
        response = get_response(request)
        add_never_cache_headers(response)
        return response

    return middleware

Als we deze middleware activeren, zal een pagina nooit gecached worden. Het is wel belangrijk om op te merken dat een downstream cache (zoals varnish) dan ook nooit een pagina zal cachen.

Per user caching

Het tweede probleem is iets moeilijker om op te lossen. Ik heb het opgelost door aangemelde gebruikers een ongecachede versie te tonen, maar anonieme gebruikers wel een gecachte versie te tonen.

Om dit te doen heb ik een decorator-wrapper geschreven:

# app/decorators.py
def conditional_cache(decorator):
    def _decorator(view):
        decorated_view = decorator(view)  # This holds the view with cache decorator
        def _view(request, *args, **kwargs):
            if request.user.is_authenticated:     # If user is staff
                return view(request, *args, **kwargs)
            else:
                return decorated_view(request, *args, **kwargs)
        return _view
    return _decorator

We gebruiken nu in plaats van de @cache_page decorator het volgende:

@conditional_cache(decorator=cache_page(300))
def view(request):
    # do stuff =)