Кэширование Django только для аутентифицированных пользователей

Вопрос

В Django, как создать единую кешированную версию страницы (то же самое для всех пользователей), которая доступна только для аутентифицированных пользователей?

Настройка

Страницы, которые я хочу кэшировать, доступны только для аутентифицированных пользователей (они используют @login_required в представлении). Эти страницы одинаковы для всех пользователей, прошедших проверку подлинности (например, нет необходимости настраивать vary_on_headers на основе уникальных пользователей).

Однако я не хочу, чтобы эти кэшированные страницы были видимыми для пользователей, не прошедших проверку подлинности.

То, что я пробовал до сих пор

  • Кэш уровня страницы (отображает страницы, предназначенные для входа в систему пользователям без регистрации)
  • Посмотрел на использование vary_on_headers, но мне не нужны отдельные кешированные страницы для каждого пользователя.
  • Я проверил кэширование фрагмента шаблона, но, если я не смущен, это не удовлетворит мои потребности.
  • Существенный поиск (кажется, что все хотят сделать обратное)

Спасибо!

Пример просмотра

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

settings.py(соответствующая часть)

# 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'
    }
}

Решение

Основываясь на ответе @Tisho, я решил эту проблему

  • Создание файла decorators.py в моем приложении
  • Добавление к нему приведенного ниже кода
  • Импорт функции в views.py
  • Применение его в качестве декоратора к представлениям, которые я хотел кэшировать только для зарегистрированных пользователей

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

Для зарегистрированных пользователей он будет кэшировать страницу (или обслуживать их кэшированную страницу) для не зарегистрированных пользователей, она просто даст им обычный вид, который был украшен @login_required и потребует от них входа в систему.

Ответы

Ответ 1

Декоратор cache_page по умолчанию принимает переменную с именем key_prefix. Однако он может быть передан только как строковый параметр. Таким образом, вы можете написать свой собственный декоратор, который будет динамически изменять этот prefix_key на основе значения is_authenticated. Вот пример:

from django.views.decorators.cache import cache_page

def cache_on_auth(timeout):
    def decorator(view_func):
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            return cache_page(timeout, key_prefix="_auth_%s_" % request.user.is_authenticated())(view_func)(request, *args, **kwargs)
        return _wrapped_view
    return decorator

а затем используйте его в представлении:

@cache_on_auth(60*60)
def myview(request)

Затем созданная cache_key будет выглядеть так:

cache key:   
views.decorators.cache.cache_page._auth_False_.GET.123456.123456

если пользователь аутентифицирован и

cache key:   
views.decorators.cache.cache_page._auth_True_.GET.789012.789012

если пользователь не аутентифицирован.

Ответ 2

Если украшатель @wrap в ответе @Tisho заставляет ваш мозг болеть, или если явное решение лучше, чем неявное, вот простой процедурный способ обслуживания различных результатов кеша:

from django.views.decorators.cache import cache_page

def index(request):
    """
    :type request: HttpRequest
    """
    is_authenticated = request.user.is_authenticated()
    if is_authenticated:
        return render_user(request)
    else:
        return render_visitor(request)

@cache_page(5, key_prefix='user_cache')
def render_user(request):
    print 'refreshing user_cache'
    return render(request, 'home-user.html', {})

@cache_page(10, key_prefix='visitor_cache')
def render_visitor(request):
    print 'refreshing visitor_cache'
    return render(request, 'home-visitor.html', {})

Ответ 3

Я бы посоветовал использовать промежуточное ПО кэша, если вы хотите тонкую настройку своих способностей кэширования.

Однако, если вы хотите сохранить его, вы можете попробовать что-то вроде (не говоря, что он будет работать как есть, но что-то похожее на него):

@never_cache
def dynamic_index(request):
    # do dynamic stuff

def cached_index(request):
    return dynamic_index(request)

@never_cache
def index(request):

    if request.user.is_authenticaded():
        return cached_index(request)

    return dynamic_index(request)

В худшем случае вы можете использовать cache.set('view_name', template_rendering_result) и cache.get, чтобы просто кэшировать HTML вручную.