Django per user view caching
Asked Answered
H

6

12

I need a per user caching. The regular view caching does unfortunately not support user-based caching.

I tried the template fragment caching like this:

{% load cache %}
{% cache 500 "mythingy" request.user %}
    ... HTML stuff ...
{% endcache %}

but it's slow as hell.

Does anybody know a faster way to achieve what I need?

Herwig answered 22/11, 2013 at 14:3 Comment(0)
H
5

I found the solution!

Here is this Portuguese code snippet which works like a charm!

The good thing is that I don't need to fiddle around my template code but can use a clean decorator!

Code is included below

# -*- encoding: utf-8 -*-
'''
Python >= 2.4
Django >= 1.0

Author: [email protected]
'''
from django.core.cache import cache

def cache_per_user(ttl=None, prefix=None, cache_post=False):
    '''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):
            # Gera a parte do usuario que ficara na chave do cache
            if request.user.is_anonymous():
                user = 'anonymous'
            else:
                user = request.user.id

            # Gera a chave do cache
            if prefix:
                CACHE_KEY = '%s_%s'%(prefix, user)
            else:
                CACHE_KEY = 'view_cache_%s_%s'%(function.__name__, user)       

            # Verifica se pode fazer o cache do request
            if not cache_post and request.method == 'POST':
                can_cache = False
            else:
                can_cache = True

            if can_cache:
                response = cache.get(CACHE_KEY, None)
            else:
                response = None

            if not response:
                response = function(request, *args, **kwargs)
                if can_cache:
                    cache.set(CACHE_KEY, response, ttl)
            return response
        return apply_cache
    return decorator
Herwig answered 22/11, 2013 at 14:4 Comment(2)
I just tested this and can confirm it works with Django 1.6.8.Grooms
Doesn't this cache PATCH, PUT and DELETE requests?Spikelet
J
32

As of Django >=1.7, using the cache_page along with vary_on_cookie decorators on your view should solve this.

Something like this:

from django.views.decorators.vary import vary_on_cookie
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
@vary_on_cookie
def view_to_cache(request):
    ...

Take note of the order of decorators as vary_on_cookie should be processed before it gets to cache_page.

Jere answered 28/2, 2016 at 11:49 Comment(1)
While it answers the question, it's worth to note that it does not work if you want to implement a dedicated cache for guests, since every guest has a different session cookie (at most installations).Thousandfold
H
5

I found the solution!

Here is this Portuguese code snippet which works like a charm!

The good thing is that I don't need to fiddle around my template code but can use a clean decorator!

Code is included below

# -*- encoding: utf-8 -*-
'''
Python >= 2.4
Django >= 1.0

Author: [email protected]
'''
from django.core.cache import cache

def cache_per_user(ttl=None, prefix=None, cache_post=False):
    '''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):
            # Gera a parte do usuario que ficara na chave do cache
            if request.user.is_anonymous():
                user = 'anonymous'
            else:
                user = request.user.id

            # Gera a chave do cache
            if prefix:
                CACHE_KEY = '%s_%s'%(prefix, user)
            else:
                CACHE_KEY = 'view_cache_%s_%s'%(function.__name__, user)       

            # Verifica se pode fazer o cache do request
            if not cache_post and request.method == 'POST':
                can_cache = False
            else:
                can_cache = True

            if can_cache:
                response = cache.get(CACHE_KEY, None)
            else:
                response = None

            if not response:
                response = function(request, *args, **kwargs)
                if can_cache:
                    cache.set(CACHE_KEY, response, ttl)
            return response
        return apply_cache
    return decorator
Herwig answered 22/11, 2013 at 14:4 Comment(2)
I just tested this and can confirm it works with Django 1.6.8.Grooms
Doesn't this cache PATCH, PUT and DELETE requests?Spikelet
C
5

For people using django rest framework and maybe others:

def cache_per_user(timeout):
  def decorator(view_func):
    @wraps(view_func, assigned=available_attrs(view_func))
    def _wrapped_view(request, *args, **kwargs):
        user_id = 'not_auth'
        if request.user.is_authenticated:
            user_id = request.user.id

        return cache_page(timeout, key_prefix="_user_{}_".format(user_id))(view_func)(request, *args, **kwargs)

    return _wrapped_view

  return decorator

Usage:

@method_decorator(cache_per_user(3600))
Clause answered 8/11, 2018 at 14:14 Comment(0)
W
2

The following is an improved version for the accepted solution does not consider the request parameters.

decorator_of_cache_per_user.py

from django.core.cache import cache as core_cache

def cache_key(request):
    if request.user.is_anonymous():
        user = 'anonymous'
    else:
        user = request.user.id

    q = getattr(request, request.method)
    q.lists()
    urlencode = q.urlencode(safe='()')

    CACHE_KEY = 'view_cache_%s_%s_%s' % (request.path, user, urlencode)
    return CACHE_KEY

def cache_per_user_function(ttl=None, prefix=None, cache_post=False):
    def decorator(function):
        def apply_cache(request, *args, **kwargs):
            CACHE_KEY = cache_key(request)

            if prefix:
                CACHE_KEY = '%s_%s' % (prefix, CACHE_KEY)

            if not cache_post and request.method == 'POST':
                can_cache = False
            else:
                can_cache = True

            if can_cache:
                response = core_cache.get(CACHE_KEY, None)
            else:
                response = None

            if not response:
                response = function(request, *args, **kwargs)
                if can_cache:
                    core_cache.set(CACHE_KEY, response, ttl)
            return response
        return apply_cache
    return decorator
Wilde answered 21/12, 2015 at 19:40 Comment(0)
C
2

I added some code updates for people who uses rest_framework Response and APIView.

from django.core.cache import cache
from rest_framework.renderers import JSONRenderer

def cache_per_user_view(ttl=None, prefix=None, cache_post=False):
    '''Decorator that caches the view for each user
    * ttl - Cache lifetime, not sending this parameter means that the cache will last until the server restarts or decides to remove it
    * prefix - Prefix to be used to store the response in the cache. If not informed, it will be used 'view_cache_'+function.__name__
    * cache_post - Informs whether to cache POST requests
    * The cache for anonymous users is shared with everyone
    * The cache key will be one of the possible options:
        '%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(self, request, *args, **kwargs):
            # Generates the user part that will be in the cache key
            if request.user.is_anonymous:
                user = 'anonymous'
            else:
                user = request.user.id

            # Generates the cache key
            if prefix:
                CACHE_KEY = '%s_%s'%(prefix, user)
            else:
                CACHE_KEY = 'view_cache_%s_%s'%(function.__name__, user)       

            # Check if you can cache the request
            if not cache_post and request.method == 'POST':
                can_cache = False
            else:
                can_cache = True

            if can_cache:
                response = cache.get(CACHE_KEY, None)
            else:
                response = None

            if not response:
                response = function(self, request, *args, **kwargs)
                response.accepted_renderer = JSONRenderer()
                response.accepted_media_type = "application/json"
                response.renderer_context = {}
                response.render()
                if can_cache:
                    cache.set(CACHE_KEY, response, ttl)
            return response
        return apply_cache
    return decorator
Chasse answered 21/12, 2023 at 11:12 Comment(0)
A
0

in this version i include in the cache key the different *args and **kwargs based on the accepted answer

def cache_per_user(ttl=None, prefix=None, cache_post=False):
'''Decorator that caches the view for each user
    * ttl - Cache lifetime, not sending this parameter means the
      cache will last until the server restarts or decides to remove it
    * prefix - Prefix to be used to cache the response. if not
      be informed will use 'view_cache_'+function.__name__
    * cache_post - Informs whether to cache POST requests
    * Cache for anonymous users is shared with everyone
    * The cache key will be one of the possible options:
        '%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):
        if request.user.is_anonymous():
            user = 'anonymous'
        else:
            user = request.user.id

        if prefix:
            base_key = '%s_%s' % (prefix, user)
        else:
            base_key = 'view_cache_%s_%s' % (function.__name__, user)

        # Include function arguments in the cache key
        args_key = '_'.join([str(arg) for arg in args])

        # Include keyword arguments in the cache key
        kwargs_key = '_'.join(['%s=%s' % (key, value) for key, value in kwargs.items()])

        # Generate the cache key
        CACHE_KEY = '%s_%s_%s' % (base_key, args_key, kwargs_key)

        if not cache_post and request.method == 'POST':
            can_cache = False
        else:
            can_cache = True

        if can_cache:
            response = cache.get(CACHE_KEY, None)
        else:
            response = None

        if not response:
            response = function(request, *args, **kwargs)
            if can_cache:
                cache.set(CACHE_KEY, response, ttl)
        return response

    return apply_cache

return decorator
Agateware answered 12/7, 2023 at 12:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.