Reputation: 2727
Here is the problem: I have blog app and I cache the post output view for 5 minutes.
@cache_page(60 * 5)
def article(request, slug):
...
However, I'd like to invalidate the cache whenever a new comment is added to the post. I'm wondering how best to do so?
I've seen this related question, but it is outdated.
Upvotes: 12
Views: 3749
Reputation: 6428
This was the first hit for me when searching for a solution, and the current answer wasn't terribly helpful, so after a lot of poking around Django's source, I have an answer for this one.
Yes you can know the key programmatically, but it takes a little work.
Django's page caching works by referencing the request
object, specifically the request path and query string. This means that for every request to your page that has a different query string, you will have a different cache key. For most cases, this isn't likely to be a problem, since the page you want to cache/invalidate will be a known string like /blog/my-awesome-year
, so to invalidate this, you just need to use Django's RequestFactory
:
from django.core.cache import cache
from django.test import RequestFactory
from django.urls import reverse
from django.utils.cache import get_cache_key
cache.delete(get_cache_key(RequestFactory().get("/blog/my-awesome-year")))
If your URLs are a fixed list of values (ie. no differing query strings) then you can stop here. However if you've got lots of different query strings (say ?q=xyz
for a search page or something), then your best bet is probably to create a separate cache for each view. Then you can just pass cache="cachename"
to cache_page()
and later clear that entire cache with:
from django.core.cache import caches
caches["my_cache_name"].clear()
Doing this also requires a change to settings.py
:
CACHES = {
"default": {
# Or whatever caching backend you use
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
},
"my_cache_name": {
# Or whatever caching backend you use
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "my_cache_name"
},
}
It only really works for unauthenticated pages. The minute your user is logged in, the cookie data is made part of the cache key creation process, and therefore recreating that key programmatically becomes much harder. I suppose you could try pulling the cookie data out of your session store, but there could be thousands of keys in there, and you'd have to invalidate/pre-cache each and every one of them.
Upvotes: 4
Reputation: 1
After going deep into the code, It's very clear that we can do it but we need to have the API URL which you want to invalidate (with query and headers) which looks kind of impossible but if yes than Django uses md5 for hash keys you can try to generate the same hash using the exact same url with the headers.
def _generate_cache_key(request, method, headerlist, key_prefix):
"""Return a cache key from the headers given in the header list."""
ctx = hashlib.md5()
for header in headerlist:
value = request.META.get(header)
if value is not None:
ctx.update(value.encode())
url = hashlib.md5(iri_to_uri(request.build_absolute_uri()).encode('ascii'))
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
key_prefix, method, url.hexdigest(), ctx.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
It doesn't look very easy then you can set a prefix for each URL you have and find all the keys with the same prefix and delete them one by one that is the much better option.
cache_page(DEFAULT_TIMEOUT, None, 'your-cache-prefix')
I don't recommend to change the query params everytime you want to show the uncached data but yes it's a way other way is you can pass no-cache header when you want the fresh data. It will work by default.
Header no-cache and also modifying the query params will never invalidate your actual cache.
Upvotes: 0
Reputation: 37934
I would cache in a bit different way:
def article(request, slug):
cached_article = cache.get('article_%s' % slug)
if not cached_article:
cached_article = Article.objects.get(slug=slug)
cache.set('article_%s' % slug, cached_article, 60*5)
return render(request, 'article/detail.html', {'article':cached_article})
then saving the new comment to this article object:
# ...
# add the new comment to this article object, then
if cache.get('article_%s' % article.slug):
cache.delete('article_%s' % article.slug)
# ...
Upvotes: 1