在Django中使用视图caching?
@cache_page decorator
真棒。 但是对于我的博客,我想保留一个caching页面,直到有人对post发表评论。 这听起来像一个好主意,因为人们很less评论,所以保持在memcached页面,而没有人的意见会很好。 我在想,以前一定有人有这个问题? 这不同于每个url的caching。
所以我想到的解决scheme是:
@cache_page( 60 * 15, "blog" ); def blog( request ) ...
然后,我会保留一个列表,用于博客视图的所有caching键,然后有办法过期的“博客”caching空间。 但是我对Django不是很有经验,所以我想知道是否有人知道更好的方法呢?
下面是我写的一个解决scheme,就是在我自己的一些项目中正在谈论的内容:
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键入视图请求的这些caching,所以这样做是为caching视图创build一个假请求对象,用它来获取caching键,然后过期。
要按照您所说的方式使用它,请尝试如下所示:
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)
所以基本上,当有一个博客的Entry对象被保存时,invalidate_blog_index被调用,并且caching的视图已经过期。 NB:还没有广泛的testing过,但是到目前为止我的工作还是很好的。
我为这种情况编写了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():它稍微牵涉一点,但是它可以让你根据模型实体的变化自动解除响应。
这在django 1.7上不起作用; 你可以在这里看到https://docs.djangoproject.com/en/dev/releases/1.7/#cache-keys-are-now-generated-from-the-request-s-absolute-url新的caching键是使用完整的URL生成,所以只有path假的请求将不起作用。; 您必须设置正确的请求主机值。
fake_meta = {'HTTP_HOST':'myhost',} request.META = fake_meta
如果你有多个域使用相同的视图,你应该在HTTP_HOST中循环它们,获得正确的密钥,并为每个域进行清理。
caching页面修饰器最终将使用CacheMiddleware,它将根据请求(查看django.utils.cache.get_cache_key
)和key_prefix(在您的案例中为“blog”)生成caching键。 请注意,“博客”只是一个前缀,而不是整个caching键。
当保存评论时,你可以通过django的post_save信号得到通知,然后你可以尝试为适当的页面build立caching键,最后说cache.delete(key)
。
然而,这需要cache_key,它是用先前caching视图的请求构造的。 保存评论时,此请求对象不可用。 你可以在没有合适的请求对象的情况下构造caching键,但是这个构造发生在一个标记为private( _generate_cache_header_key
)的函数中,所以你不应该直接使用这个函数。 然而,你可以build立一个对象,其path属性与原始caching视图相同,Django不会注意到,但我不build议这样做。
caching页面修饰器为您提取caching很多,并且很难直接删除某个caching对象。 你可以用自己的方式来cache_page
你自己的键,并以相同的方式处理它们,但是这需要更多的编程,而不像cache_page
修饰符那样抽象。
当您的评论显示在多个视图(即包含评论计数的索引页面和单个博客条目页面)时,您还必须删除多个caching对象。
总结一下:Django会为您执行caching密钥的基于时间的过期,但是在正确的时间自定义删除caching密钥更为棘手。
如果没有评论,您可以手动caching博客post对象(或类似的),而不是使用caching页面修饰器,然后当有第一条评论时, 重新caching博客post对象以使其保持最新(假设对象具有引用任何评论的属性),但只是让评论的博客post的caching数据过期,然后不用再重新caching…
FWIW我不得不修改mazelife的解决scheme来使其工作:
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
我有同样的问题,我不想搞乱HTTP_HOST,所以我创build了我自己的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
要过期的页面caching只需要调用:
cache.set('map_view:' + self.slug, None, 0)
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视图caching失效v1.7及更高版本。 在Django 1.9上testing
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)
用法:
status, message = invalidate_cache(path='/api/v1/blog/')
每次有人发表评论时,您可以使用新的“key_prefix”,而不是明确的caching过期。 例如,它可能是最后发表评论的date时间(你甚至可以把这个值与Last-Modified
标题结合起来)。
不幸的是,Django(包括cache_page()
)不支持dynamic的“key_prefix”es(在Django 1.9上进行检查),但是存在解决方法。 你可以实现自己的cache_page()
,它可以使用包含dynamic“key_prefix”支持的扩展CacheMiddleware
。 例如:
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"""
Duncan的答案在Django 1.9中效果很好。 但是如果我们需要使用GET参数来使URL失效,我们必须对请求做一些修改。 例如对于… /?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')
解决scheme很简单,不需要额外的工作。
例
@cache_page(60 * 10) def our_team(request, sorting=None): ...
这将使用默认键将响应设置为caching。
过期视图caching
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)
简单,清洁,快速。