python 使 Django 中的视图缓存过期?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2268417/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-04 00:13:36  来源:igfitidea点击:

Expire a view-cache in Django?

pythondjangocaching

提问by Nixarn

The @cache_page decoratoris awesome. But for my blog I would like to keep a page in cache until someone comments on a post. This sounds like a great idea as people rarely comment so keeping the pages in memcached while nobody comments would be great. I'm thinking that someone must have had this problem before? And this is different than caching per url.

@cache_page decorator真棒。但是对于我的博客,我想在缓存中保留一个页面,直到有人对帖子发表评论。这听起来是个好主意,因为人们很少发表评论,因此将页面保存在 memcached 中而没有人发表评论会很棒。我在想之前一定有人遇到过这个问题吗?这与按 url 缓存不同。

So a solution I'm thinking of is:

所以我想到的一个解决方案是:

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

And then I'd keep a list of all cache keys used for the blog view and then have way of expire the "blog" cache space. But I'm not super experienced with Django so I'm wondering if someone knows a better way of doing this?

然后我会保留用于博客视图的所有缓存键的列表,然后让“博客”缓存空间过期。但是我对 Django 不是非常有经验,所以我想知道是否有人知道这样做的更好方法?

采纳答案by mazelife

This solution works for django versions before 1.7

此解决方案适用于 1.7 之前的 Django 版本

Here's a solution I wrote to do just what you're talking about on some of my own projects:

这是我写的一个解决方案,用于在我自己的一些项目中执行您所说的操作:

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's 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 keys these caches of the view request, so what this does is creates a fake request object for the cached view, uses that to fetch the cache key, then expires it.

Django 对视图请求的这些缓存进行键控,因此它所做的是为缓存视图创建一个假请求对象,使用它来获取缓存键,然后使其过期。

To use it in the way you're talking about, try something like:

要以您所说的方式使用它,请尝试以下操作:

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)

So basically, when ever a blog Entry object is saved, invalidate_blog_index is called and the cached view is expired. NB: haven't tested this extensively, but it's worked fine for me so far.

所以基本上,当一个 blog Entry 对象被保存时, invalidate_blog_index 被调用并且缓存的视图过期。注意:尚未对此进行广泛测试,但到目前为止对我来说效果很好。

回答by Syfou

I wrote Django-groupcachefor this kind of situations (you can download the code here). In your case, you could write:

我为这种情况编写了Django-groupcache(您可以在此处下载代码)。在你的情况下,你可以写:

from groupcache.decorators import cache_tagged_page

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

From there, you could simply do later on:

从那里,您可以稍后简单地执行以下操作:

from groupcache.utils import uncache_from_tag

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

Have a look at cache_page_against_model() as well: it's slightly more involved, but it will allow you to uncache responses automatically based on model entity changes.

也看看 cache_page_against_model() :它稍微复杂一点,但它允许您根据模型实体更改自动取消缓存响应。

回答by nesdis

With the latest version of Django(>=2.0) what you are looking for is very easy to implement:

使用最新版本的 Django(>=2.0),您正在寻找的东西很容易实现:

from django.utils.cache import learn_cache_key
from django.core.cache import cache
from django.views.decorators.cache import cache_page

keys = set()

@cache_page( 60 * 15, "blog" );
def blog( request ):
    response = render(request, 'template')
    keys.add(learn_cache_key(request, response)
    return response

def invalidate_cache()
    cache.delete_many(keys)

You can register the invalidate_cache as a callback when someone updates a post in the blog via a pre_save signal.

当有人通过 pre_save 信号更新博客中的帖子时,您可以将 invalidate_cache 注册为回调。

回答by stefanw

The cache_page decorator will use CacheMiddleware in the end which will generate a cache key based on the request (look at django.utils.cache.get_cache_key) and the key_prefix ("blog" in your case). Note that "blog" is only a prefix, not the whole cache key.

cache_page 装饰器最终将使用 CacheMiddleware,它将根据请求(查看django.utils.cache.get_cache_key)和 key_prefix(在您的情况下为“博客”)生成缓存键。请注意,“blog”只是一个前缀,而不是整个缓存键。

You can get notified via django's post_save signalwhen a comment is saved, then you can try to build the cache key for the appropriate page(s) and finally say cache.delete(key).

您可以在保存评论时通过django 的 post_save 信号获得通知,然后您可以尝试为适当的页面构建缓存键,最后说cache.delete(key).

However this requires the cache_key, which is constructed with the request for the previously cached view. This request object is not available when a comment is saved. You could construct the cache key without the proper request object, but this construction happens in a function marked as private (_generate_cache_header_key), so you are not supposed to use this function directly. However, you could build an object that has a path attribute that is the same as for the original cached view and Django wouldn't notice, but I don't recommend that.

但是,这需要 cache_key,它是根据对先前缓存的视图的请求构建的。保存评论时,此请求对象不可用。您可以在没有正确请求对象的情况下构造缓存键,但这种构造发生在标记为 private ( _generate_cache_header_key)的函数中,因此您不应直接使用该函数。但是,您可以构建一个具有与原始缓存视图相同的路径属性的对象,Django 不会注意到,但我不建议这样做。

The cache_page decorator abstracts caching quite a bit for you and makes it hard to delete a certain cache object directly. You could make up your own keys and handle them in the same way, but this requires some more programming and is not as abstract as the cache_pagedecorator.

cache_page 装饰器为您抽象了缓存,并且很难直接删除某个缓存对象。您可以创建自己的键并以相同的方式处理它们,但这需要更多的编程并且不像cache_page装饰器那样抽象。

You will also have to delete multiple cache objects when your comments are displayed in multiple views (i.e. index page with comment counts and individual blog entry pages).

当您的评论显示在多个视图中时,您还必须删除多个缓存对象(即带有评论计数的索引页面和单个博客条目页面)。

To sum up: Django does time based expiration of cache keys for you, but custom deletion of cache keys at the right time is more tricky.

总结一下:Django 为你做基于时间的缓存键过期,但是在正确的时间自定义删除缓存键更棘手。

回答by maikelpac

This won't work on django 1.7; as you can see here https://docs.djangoproject.com/en/dev/releases/1.7/#cache-keys-are-now-generated-from-the-request-s-absolute-urlthe new cache keys are generated with the full URL, so a path-only fake request won't work. You must setup properly request host value.

这不适用于 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

If you have multiple domains working with the same views, you should cycle them in the HTTP_HOST, get proper key and do the clean for each one.

如果您有多个域使用相同的视图,您应该在 HTTP_HOST 中循环它们,获取正确的密钥并对每个域进行清理。

回答by Duncan

Django view cache invalidation for v1.7 and above. Tested on Django 1.9.

v1.7 及更高版本的 Django 视图缓存失效。在 Django 1.9 上测试。

def invalidate_cache(path=''):
    ''' this function uses Django's 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's 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)

Usage:

用法:

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

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

回答by dpn

FWIW I had to modify mazelife's solution to get it working:

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's 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

回答by Steve Jalim

Instead of using the cache page decorator, you could manually cache the blog post object (or similar) if there are no comments, and then when there's a first comment, re-cachethe blog post object so that it's up to date (assuming the object has attributes that reference any comments), but then just let that cached data for the commented blog post expire and then no bother re-cacheing...

如果没有评论,您可以手动缓存博客文章对象(或类似的),而不是使用缓存页面装饰器,然后当有第一条评论时,重新缓存博客文章对象,以便它是最新的(假设对象具有引用任何评论的属性),但只需让评论博客文章的缓存数据过期,然后就不用费心重新缓存了...

回答by Mykhailo

I had same problem and I didn't want to mess with HTTP_HOST, so I created my own cache_page decorator:

我遇到了同样的问题,我不想弄乱 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

To expired page cache just need to call:

要过期的页面缓存只需要调用:

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

where self.slug - param from urls.py

其中 self.slug - 来自 urls.py 的参数

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

Django 1.11, Python 3.4.3

Django 1.11,Python 3.4.3

回答by renskiy

Instead of explicit cache expiration you could probably use new "key_prefix" every time somebody comment the post. E.g. it might be datetime of the last post's comment (you could even combine this value with the Last-Modifiedheader).

每次有人评论帖子时,您都可以使用新的“key_prefix”代替显式缓存过期。例如,它可能是最后一篇文章评论的日期时间(您甚至可以将此值与Last-Modified标题结合起来)。

Unfortunately Django (including cache_page()) does not support dynamic "key_prefix"es (checked on Django 1.9) but there is workaround exists. You can implement your own cache_page()which may use extended CacheMiddlewarewith dynamic "key_prefix" support included. For example:

不幸的是 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)

Then in your code:

然后在你的代码中:

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"""