Подтвердить что ты не робот

Завершить кеш-представление в Django?

@cache_page decorator является удивительным. Но для моего блога я хотел бы сохранить страницу в кеше, пока кто-то не комментирует сообщение. Это звучит как отличная идея, так как люди редко комментируют, поэтому сохраняют страницы в memcached, в то время как никто не будет замечательным. Я думаю, что раньше у кого-то была эта проблема? И это отличается от кэширования на каждый URL.

Итак, решение, о котором я думаю, это:

@cache_page( 60 * 15, "blog" );
def blog( request ) ...

И затем я бы сохранил список всех ключей кеша, используемых для просмотра блога, а затем истекал срок хранения кеша "блог". Но я не очень опытен с Django, поэтому мне интересно, знает ли кто-нибудь лучший способ сделать это?

4b9b3361

Ответ 1

Вот решение, которое я написал, чтобы сделать то, о чем вы говорите, в некоторых моих собственных проектах:

def expire_view_cache(view_name, args=[], namespace=None, key_prefix=None):
    """
    This function allows you to invalidate any view-level cache. 
        view_name: view function you wish to invalidate or it named url pattern
        args: any arguments passed to the view function
        namepace: optioal, if an application namespace is needed
        key prefix: for the @cache_page decorator for the function (if any)
    """
    from django.core.urlresolvers import reverse
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key
    from django.core.cache import cache
    # create a fake request object
    request = HttpRequest()
    # Loookup the request path:
    if namespace:
        view_name = namespace + ":" + view_name
    request.path = reverse(view_name, args=args)
    # get cache key, expire if the cached item exists:
    key = get_cache_key(request, key_prefix=key_prefix)
    if key:
        if cache.get(key):
            # Delete the cache entry.  
            #
            # Note that there is a possible race condition here, as another 
            # process / thread may have refreshed the cache between
            # the call to cache.get() above, and the cache.set(key, None) 
            # below.  This may lead to unexpected performance problems under 
            # severe load.
            cache.set(key, None, 0)
        return True
    return False

Django связывает эти кэши запроса вида, поэтому это создает объект поддельного запроса для кэшированного представления, использует его для извлечения ключа кеша, а затем истекает его.

Чтобы использовать его так, как вы говорите, попробуйте что-то вроде:

from django.db.models.signals import post_save
from blog.models import Entry

def invalidate_blog_index(sender, **kwargs):
    expire_view_cache("blog")

post_save.connect(invalidate_portfolio_index, sender=Entry)

Итак, в основном, когда когда-либо сохраняется объект записи в блоге, вызывается invalidate_blog_index и истекло время ожидания кеширования. NB: не тестировали это широко, но до сих пор это работало нормально.

Ответ 2

Я написал Django-groupcache для таких ситуаций (вы можете скачать код здесь). В вашем случае вы можете написать:

from groupcache.decorators import cache_tagged_page

@cache_tagged_page("blog", 60 * 15)
def blog(request):
    ...

Оттуда вы можете просто сделать следующее:

from groupcache.utils import uncache_from_tag

# Uncache all view responses tagged as "blog"
uncache_from_tag("blog") 

Посмотрите также на cache_page_against_model(): он немного больше задействован, но он позволит вам автоматически сканировать ответы на основе изменений модели объекта.

Ответ 3

Это не будет работать на django 1.7; как вы можете видеть здесь https://docs.djangoproject.com/en/dev/releases/1.7/#cache-keys-are-now-generated-from-the-request-s-absolute-url, новые ключи кеша генерируются с полным URL-адресом, поэтому недействительный поддельный запрос не будет работать. Необходимо правильно настроить значение хоста.

fake_meta = {'HTTP_HOST':'myhost',}
request.META = fake_meta

Если у вас несколько доменов, работающих с одними и теми же представлениями, вы должны циклически их запускать в HTTP_HOST, получить правильный ключ и сделать чистую для каждого.

Ответ 4

Декодер cache_page будет использовать CacheMiddleware в конце, который будет генерировать кэш-ключ на основе запроса (посмотрите django.utils.cache.get_cache_key) и key_prefix ( "блог" в вашем случае). Обратите внимание, что "блог" - это только префикс, а не весь кеш-ключ.

При сохранении комментария вы можете получить уведомление через django post_save signal, затем вы можете попытаться создать кэш-ключ для соответствующей страницы (s) и, наконец, скажем cache.delete(key).

Однако для этого требуется cache_key, который создается с запросом для ранее кэшированного представления. Этот объект запроса недоступен, если комментарий сохраняется. Вы можете создать ключ кеша без соответствующего объекта запроса, но эта конструкция происходит в функции, помеченной как private (_generate_cache_header_key), поэтому вы не должны использовать эту функцию напрямую. Однако вы можете создать объект с атрибутом пути, который будет таким же, как и для исходного кэшированного представления, и Django не заметил бы, но я не рекомендую это.

Элемент-обработчик cache_page абстрактно кэширует для вас немного и затрудняет непосредственное удаление определенного объекта кеша. Вы можете создавать свои собственные ключи и обрабатывать их одинаково, но для этого требуется еще некоторое программирование и не так абстрактны, как декоратор cache_page.

Вам также придется удалять несколько объектов кеша, когда ваши комментарии отображаются в нескольких представлениях (например, индексная страница с комментариями и отдельными страницами в блоге).

Подводя итог: Django использует время для ключей кеширования по времени, но выборочное удаление ключей кеша в нужное время более сложно.

Ответ 5

Вместо использования декоратора страницы кэша вы можете вручную кэшировать объект post post (или аналогичный), если комментариев нет, а затем, когда есть первый комментарий, повторно кэшируйте объект post blog, чтобы он обновлялся (предполагая, что у объекта есть атрибуты, которые ссылаются на любые комментарии), но затем просто пусть эти кэшированные данные для прокомментированного сообщения в блоге истекают, а затем не беспокоят повторное кэширование...

Ответ 6

FWIW Мне пришлось модифицировать решение mazelife, чтобы заставить его работать:

def expire_view_cache(view_name, args=[], namespace=None, key_prefix=None, method="GET"):
    """
    This function allows you to invalidate any view-level cache. 
        view_name: view function you wish to invalidate or it named url pattern
        args: any arguments passed to the view function
        namepace: optioal, if an application namespace is needed
        key prefix: for the @cache_page decorator for the function (if any)

        from: http://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django
        added: method to request to get the key generating properly
    """
    from django.core.urlresolvers import reverse
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key
    from django.core.cache import cache
    # create a fake request object
    request = HttpRequest()
    request.method = method
    # Loookup the request path:
    if namespace:
        view_name = namespace + ":" + view_name
    request.path = reverse(view_name, args=args)
    # get cache key, expire if the cached item exists:
    key = get_cache_key(request, key_prefix=key_prefix)
    if key:
        if cache.get(key):
            cache.set(key, None, 0)
        return True
    return False

Ответ 7

У меня была такая же проблема, и я не хотел связываться с HTTP_HOST, поэтому я создал собственный обработчик cache_page:

from django.core.cache import cache


def simple_cache_page(cache_timeout):
    """
    Decorator for views that tries getting the page from the cache and
    populates the cache if the page isn't in the cache yet.

    The cache is keyed by view name and arguments.
    """
    def _dec(func):
        def _new_func(*args, **kwargs):
            key = func.__name__
            if kwargs:
                key += ':' + ':'.join([kwargs[key] for key in kwargs])

            response = cache.get(key)
            if not response:
                response = func(*args, **kwargs)
                cache.set(key, response, cache_timeout)
            return response
        return _new_func
    return _dec

К кешу с истекшим сроком нужно просто позвонить:

cache.set('map_view:' + self.slug, None, 0)

где self.slug - param из urls.py

url(r'^map/(?P<slug>.+)$', simple_cache_page(60 * 60 * 24)(map_view), name='map'), 

Django 1.11, Python 3.4.3

Ответ 8

Ошибка кэширования представления Django для v1.7 и выше. Протестировано на Django 1.9.

def invalidate_cache(path=''):
    ''' this function uses Django caching function get_cache_key(). Since 1.7, 
        Django has used more variables from the request object (scheme, host, 
        path, and query string) in order to create the MD5 hashed part of the
        cache_key. Additionally, Django will use your server timezone and 
        language as properties as well. If internationalization is important to
        your application, you will most likely need to adapt this function to
        handle that appropriately.
    '''
    from django.core.cache import cache
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key

    # Bootstrap request:
    #   request.path should point to the view endpoint you want to invalidate
    #   request.META must include the correct SERVER_NAME and SERVER_PORT as django uses these in order
    #   to build a MD5 hashed value for the cache_key. Similarly, we need to artificially set the 
    #   language code on the request to 'en-us' to match the initial creation of the cache_key. 
    #   YMMV regarding the language code.        
    request = HttpRequest()
    request.META = {'SERVER_NAME':'localhost','SERVER_PORT':8000}
    request.LANGUAGE_CODE = 'en-us'
    request.path = path

    try:
        cache_key = get_cache_key(request)
        if cache_key :
            if cache.has_key(cache_key):
                cache.delete(cache_key)
                return (True, 'successfully invalidated')
            else:
                return (False, 'cache_key does not exist in cache')
        else:
            raise ValueError('failed to create cache_key')
    except (ValueError, Exception) as e:            
        return (False, e)

Использование:

status, message = invalidate_cache(path='/api/v1/blog/')

Ответ 9

Вместо явного истечения срока кеша вы, вероятно, можете использовать новый "key_prefix" каждый раз, когда кто-то комментирует сообщение. Например. это может быть datetime последнего комментария комментария (вы даже можете комбинировать это значение с заголовком Last-Modified).

К сожалению, Django (включая cache_page()) не поддерживает динамические "key_prefix" es (отмечен Django 1.9), но существует обходное решение. Вы можете реализовать свой собственный cache_page(), который может использовать расширенный CacheMiddleware с включенной динамической поддержкой "key_prefix" . Например:

from django.middleware.cache import CacheMiddleware
from django.utils.decorators import decorator_from_middleware_with_args

def extended_cache_page(cache_timeout, key_prefix=None, cache=None):
    return decorator_from_middleware_with_args(ExtendedCacheMiddleware)(
        cache_timeout=cache_timeout,
        cache_alias=cache,
        key_prefix=key_prefix,
    )

class ExtendedCacheMiddleware(CacheMiddleware):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if callable(self.key_prefix):
            self.key_function = self.key_prefix

    def key_function(self, request, *args, **kwargs):
        return self.key_prefix

    def get_key_prefix(self, request):
        return self.key_function(
            request,
            *request.resolver_match.args,
            **request.resolver_match.kwargs
        )

    def process_request(self, request):
        self.key_prefix = self.get_key_prefix(request)
        return super().process_request(request)

    def process_response(self, request, response):
        self.key_prefix = self.get_key_prefix(request)
        return super().process_response(request, response)

Затем в вашем коде:

from django.utils.lru_cache import lru_cache

@lru_cache()
def last_modified(request, blog_id):
    """return fresh key_prefix"""

@extended_cache_page(60 * 15, key_prefix=last_modified)
def view_blog(request, blog_id):
    """view blog page with comments"""

Ответ 10

Ответ Duncan хорошо работает с Django 1.9. Но если нам нужен недействительный url с параметром GET, мы должны внести небольшие изменения в запрос. Например, для... /? Mykey = myvalue

request.META = {'SERVER_NAME':'127.0.0.1','SERVER_PORT':8000, 'REQUEST_METHOD':'GET', 'QUERY_STRING': 'mykey=myvalue'}
request.GET.__setitem__(key='mykey', value='myvalue')

Ответ 11

Решение прост и не требует дополнительной работы.

Пример

@cache_page(60 * 10)
def our_team(request, sorting=None):
    ...

Это установит ответ на кеш с ключом по умолчанию.

Истекает кеш просмотра

from django.utils.cache import get_cache_key
from django.core.cache import cache

# This will remove the cache value and set it to None
cache.set(get_cache_key(request), None)

Простой, чистый, быстрый.