The @cache_page decorator
is 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?
然后我将保存一个用于blog视图的所有缓存键的列表,然后将“blog”缓存空间过期。但我对《被解救的姜戈》不是很有经验,所以我想知道是否有人知道更好的方法?
14 个解决方案
#1
43
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条目对象被保存时,会调用invalidate_blog_index,缓存的视图也会过期。NB:我还没有做过详细的测试,但是到目前为止,它对我来说还不错。
#2
11
I wrote Django-groupcache for 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():它稍微复杂一些,但它将允许您根据模型实体更改自动取消缓存响应。
#3
6
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 decorator最后将使用CacheMiddleware,它将基于请求(查看django.utils.cache.get_cache_key)和key_prefix(在您的例子中是“blog”)生成一个缓存键。注意,“blog”只是一个前缀,而不是整个缓存键。
You can get notified via django's post_save signal when 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)的函数中,因此不应该直接使用这个函数。但是,您可以构建一个具有与原始缓存视图相同的path属性的对象,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_page
decorator.
cache_page decorator对缓存进行了大量抽象,因此很难直接删除某个缓存对象。您可以创建自己的键并以相同的方式处理它们,但是这需要更多的编程,并且不像cache_page decorator那样抽象。
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为您执行基于时间的缓存键过期操作,但是在正确的时间自定义删除缓存键更加困难。
#4
5
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-url the 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-key -key -are-now-generated from-the-request- 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中循环它们,获取适当的密钥,并为每一个进行清理。
#5
5
Django view cache invalidation for v1.7 and above. Tested on Django 1.9.
Django视图缓存无效的v1.7和以上。1.9测试Django。
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/')
状态消息= invalidate_cache(path = / api / v1 /博客/)
#6
3
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-cache the 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...
而不是使用缓存页面修饰符,您可以手动缓存博客对象(或相似的)如果没有评论,然后当有一个第一次评论,重新的博客对象,这样最新的(假设的对象有属性引用任何评论),然后让缓存数据的评论博客然后到期没有麻烦重新…
#7
3
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://*.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
#8
3
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 decorator:
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
自我的地方。鼻涕-从瓶子里发出的声音
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
#9
1
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
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注册为回调。
#10
0
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-Modified
header).
与显式缓存过期不同,您可以在每次有人评论文章时使用新的“key_prefix”。例如,它可能是上一篇文章的注释的datetime(您甚至可以将这个值与最后修改的标题结合)。
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 CacheMiddleware
with dynamic "key_prefix" support included. For example:
不幸的是,Django(包括cache_page())不支持动态的“key_prefix”es(在Django 1.9上检查过),但是存在解决方案。您可以实现自己的cache_page(),它可以使用包含动态“key_prefix”支持的扩展cachemileware。例如:
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"""
#11
0
Duncan's answer works well with Django 1.9. But if we need invalidate url with GET-parameter we have to make a little changes in request. Eg for .../?mykey=myvalue
邓肯的答案对Django 1.9很适用。但是如果我们需要使用GET-parameter的invalidate url,我们必须对请求做一点修改。例如…/ ? mykey =括号
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')
#12
0
I struggled with a similar situation and here is the solution I came up with, I started it on an earlier version of Django but it is currently in use on version 2.0.3.
我遇到了类似的情况,这里是我提出的解决方案,我在Django的早期版本中开始使用它,但是它目前正在2.0.3版中使用。
First issue: when you set things to be cached in Django, it sets headers so that downstream caches -- including the browser cache -- cache your page.
第一个问题:当您设置要在Django中缓存的内容时,它会设置标题,以便下游缓存(包括浏览器缓存)缓存您的页面。
To override that, you need to set middleware. I cribbed this from elsewhere on *, but can't find it at the moment. In appname/middleware.py
:
要覆盖它,需要设置中间件。我从*上其他地方抄袭了这个,但是现在找不到。在浏览器名称/ middleware.py:
from django.utils.cache import add_never_cache_headers
class Disable(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
add_never_cache_headers(response)
return response
Then in settings.py
, to MIDDLEWARE
, add:
然后在设置。py,中间件,添加:
'appname.middleware.downstream_caching.Disable',
Keep in mind that this approach completely disables downstream caching, which may not be what you want.
请记住,这种方法完全禁用下游缓存,这可能不是您想要的。
Finally, I added to my views.py
:
最后,我补充了我的观点。
def expire_page(request, path=None, query_string=None, method='GET'):
"""
:param request: "real" request, or at least one providing the same scheme, host, and port as what you want to expire
:param path: The path you want to expire, if not the path on the request
:param query_string: The query string you want to expire, as opposed to the path on the request
:param method: the HTTP method for the page, if not GET
:return: None
"""
if query_string is not None:
request.META['QUERY_STRING'] = query_string
if path is not None:
request.path = path
request.method = method
# get_raw_uri and method show, as of this writing, everything used in the cache key
# print('req uri: {} method: {}'.format(request.get_raw_uri(), request.method))
key = get_cache_key(request)
if key in cache:
cache.delete(key)
I didn't like having to pass in a request
object, but as of this writing, it provides the scheme/protocol, host, and port for the request, pretty much any request object for your site/app will do, as long as you pass in the path and query string.
我不喜欢必须传递一个请求对象,但是在编写本文时,它为请求提供了scheme/protocol、主机和端口,只要您传入路径和查询字符串,几乎任何站点/应用程序的请求对象都可以这样做。
#13
0
One more updated version of Duncan's answer: had to figure out correct meta fields: (tested on Django 1.9.8)
邓肯回答的另一个更新版本是:必须找到正确的元字段:(在Django 1.9.8测试)
def invalidate_cache(path=''):
import socket
from django.core.cache import cache
from django.http import HttpRequest
from django.utils.cache import get_cache_key
request = HttpRequest()
domain = 'www.yourdomain.com'
request.META = {'SERVER_NAME': socket.gethostname(), 'SERVER_PORT':8000, "HTTP_HOST": domain, 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br'}
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)
#14
-1
The solution is simple, and do not require any additional work.
解决方案很简单,不需要任何额外的工作。
Example
@cache_page(60 * 10)
def our_team(request, sorting=None):
...
This will set the response to cache with the default key.
这将使用默认键将响应设置为缓存。
Expire a view cache
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)
Simple, Clean, Fast.
简单、干净、快速。
#1
43
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条目对象被保存时,会调用invalidate_blog_index,缓存的视图也会过期。NB:我还没有做过详细的测试,但是到目前为止,它对我来说还不错。
#2
11
I wrote Django-groupcache for 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():它稍微复杂一些,但它将允许您根据模型实体更改自动取消缓存响应。
#3
6
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 decorator最后将使用CacheMiddleware,它将基于请求(查看django.utils.cache.get_cache_key)和key_prefix(在您的例子中是“blog”)生成一个缓存键。注意,“blog”只是一个前缀,而不是整个缓存键。
You can get notified via django's post_save signal when 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)的函数中,因此不应该直接使用这个函数。但是,您可以构建一个具有与原始缓存视图相同的path属性的对象,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_page
decorator.
cache_page decorator对缓存进行了大量抽象,因此很难直接删除某个缓存对象。您可以创建自己的键并以相同的方式处理它们,但是这需要更多的编程,并且不像cache_page decorator那样抽象。
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为您执行基于时间的缓存键过期操作,但是在正确的时间自定义删除缓存键更加困难。
#4
5
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-url the 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-key -key -are-now-generated from-the-request- 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中循环它们,获取适当的密钥,并为每一个进行清理。
#5
5
Django view cache invalidation for v1.7 and above. Tested on Django 1.9.
Django视图缓存无效的v1.7和以上。1.9测试Django。
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/')
状态消息= invalidate_cache(path = / api / v1 /博客/)
#6
3
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-cache the 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...
而不是使用缓存页面修饰符,您可以手动缓存博客对象(或相似的)如果没有评论,然后当有一个第一次评论,重新的博客对象,这样最新的(假设的对象有属性引用任何评论),然后让缓存数据的评论博客然后到期没有麻烦重新…
#7
3
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://*.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
#8
3
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 decorator:
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
自我的地方。鼻涕-从瓶子里发出的声音
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
#9
1
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
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注册为回调。
#10
0
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-Modified
header).
与显式缓存过期不同,您可以在每次有人评论文章时使用新的“key_prefix”。例如,它可能是上一篇文章的注释的datetime(您甚至可以将这个值与最后修改的标题结合)。
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 CacheMiddleware
with dynamic "key_prefix" support included. For example:
不幸的是,Django(包括cache_page())不支持动态的“key_prefix”es(在Django 1.9上检查过),但是存在解决方案。您可以实现自己的cache_page(),它可以使用包含动态“key_prefix”支持的扩展cachemileware。例如:
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"""
#11
0
Duncan's answer works well with Django 1.9. But if we need invalidate url with GET-parameter we have to make a little changes in request. Eg for .../?mykey=myvalue
邓肯的答案对Django 1.9很适用。但是如果我们需要使用GET-parameter的invalidate url,我们必须对请求做一点修改。例如…/ ? mykey =括号
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')
#12
0
I struggled with a similar situation and here is the solution I came up with, I started it on an earlier version of Django but it is currently in use on version 2.0.3.
我遇到了类似的情况,这里是我提出的解决方案,我在Django的早期版本中开始使用它,但是它目前正在2.0.3版中使用。
First issue: when you set things to be cached in Django, it sets headers so that downstream caches -- including the browser cache -- cache your page.
第一个问题:当您设置要在Django中缓存的内容时,它会设置标题,以便下游缓存(包括浏览器缓存)缓存您的页面。
To override that, you need to set middleware. I cribbed this from elsewhere on *, but can't find it at the moment. In appname/middleware.py
:
要覆盖它,需要设置中间件。我从*上其他地方抄袭了这个,但是现在找不到。在浏览器名称/ middleware.py:
from django.utils.cache import add_never_cache_headers
class Disable(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
add_never_cache_headers(response)
return response
Then in settings.py
, to MIDDLEWARE
, add:
然后在设置。py,中间件,添加:
'appname.middleware.downstream_caching.Disable',
Keep in mind that this approach completely disables downstream caching, which may not be what you want.
请记住,这种方法完全禁用下游缓存,这可能不是您想要的。
Finally, I added to my views.py
:
最后,我补充了我的观点。
def expire_page(request, path=None, query_string=None, method='GET'):
"""
:param request: "real" request, or at least one providing the same scheme, host, and port as what you want to expire
:param path: The path you want to expire, if not the path on the request
:param query_string: The query string you want to expire, as opposed to the path on the request
:param method: the HTTP method for the page, if not GET
:return: None
"""
if query_string is not None:
request.META['QUERY_STRING'] = query_string
if path is not None:
request.path = path
request.method = method
# get_raw_uri and method show, as of this writing, everything used in the cache key
# print('req uri: {} method: {}'.format(request.get_raw_uri(), request.method))
key = get_cache_key(request)
if key in cache:
cache.delete(key)
I didn't like having to pass in a request
object, but as of this writing, it provides the scheme/protocol, host, and port for the request, pretty much any request object for your site/app will do, as long as you pass in the path and query string.
我不喜欢必须传递一个请求对象,但是在编写本文时,它为请求提供了scheme/protocol、主机和端口,只要您传入路径和查询字符串,几乎任何站点/应用程序的请求对象都可以这样做。
#13
0
One more updated version of Duncan's answer: had to figure out correct meta fields: (tested on Django 1.9.8)
邓肯回答的另一个更新版本是:必须找到正确的元字段:(在Django 1.9.8测试)
def invalidate_cache(path=''):
import socket
from django.core.cache import cache
from django.http import HttpRequest
from django.utils.cache import get_cache_key
request = HttpRequest()
domain = 'www.yourdomain.com'
request.META = {'SERVER_NAME': socket.gethostname(), 'SERVER_PORT':8000, "HTTP_HOST": domain, 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br'}
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)
#14
-1
The solution is simple, and do not require any additional work.
解决方案很简单,不需要任何额外的工作。
Example
@cache_page(60 * 10)
def our_team(request, sorting=None):
...
This will set the response to cache with the default key.
这将使用默认键将响应设置为缓存。
Expire a view cache
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)
Simple, Clean, Fast.
简单、干净、快速。