Django Caching for Authenticated Users Only

Viewed 6478

Question

In Django, how can create a single cached version of a page (same for all users) that's only visible to authenticated users?

Setup

The pages I wish to cache are only available to authenticated users (they use @login_required on the view). These pages are the same for all authenticated users (e.g. no need to setup vary_on_headers based on unique users).

However, I don't want these cached pages to be visible to non-authenticated users.

What I've tried so far

  • Page level cache (shows pages intended for logged in users to non-logged in users)
  • Looked into using vary_on_headers, but I don't need individually cached pages for each user
  • I checked out template fragment caching, but unless I'm confused, this won't meet my needs
  • Substantial searching (seems that everyone wants to do the reverse)

Thanks!

Example View

@login_required
@cache_page(60 * 60)
def index(request):
    '''Display the home page'''
    return render(request, 'index.html')

settings.py (relevant portion)

# Add the below for memcache
MIDDLEWARE_CLASSES += (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

# Enable memcache
# https://devcenter.heroku.com/articles/memcache#using_memcache_from_python
CACHES = {
    'default': {
        'BACKEND': 'django_pylibmc.memcached.PyLibMCCache'
    }
}

Solution

Based on the answer by @Tisho I solved this problem by

  1. Creating a decorators.py file in my app
  2. Adding the below code to it
  3. Importing the function in views.py
  4. Applying it as a decorator to the views I wanted to cache for logged in users only

decorators.py

from functools import wraps
from django.views.decorators.cache import cache_page
from django.utils.decorators import available_attrs


def cache_on_auth(timeout):
    def decorator(view_func):
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            if request.user.is_authenticated():
                return cache_page(timeout)(view_func)(request, *args, **kwargs)
            else:
                return view_func(request, *args, **kwargs)
        return _wrapped_view
    return decorator

For logged in users, it would cache the page (or serve them the cached page) for non-logged in users, it would just give them the regular view, which was decorated with @login_required and would require them to login.

5 Answers

Here's an updated version of @Tisho's answer that works for Django 3.0 (the old version no longer works since Python 2 support was dropped in Django 3.0):

from django.views.decorators.cache import cache_page
from functools import wraps, WRAPPER_ASSIGNMENTS


def cache_on_auth(timeout):
    """
    Caches views up to two times: Once for authenticated users, and
    once for unauthenticated users.
    """
    def decorator(view_func):
        @wraps(view_func, assigned=WRAPPER_ASSIGNMENTS)
        def _wrapped_view(request, *args, **kwargs):
            result = cache_page(
                timeout,
                key_prefix=(f"_auth_{request.user.is_authenticated}_"))
            return result(view_func)(request, *args, **kwargs)
        return _wrapped_view
    return decorator

I know this is a very old question, but we have a new alternative to solve this.

You can use the decorator cache_control passing private as True to protect your data.

Related