72

I'm using the render_to_response shortcut and don't want to craft a specific Response object to add additional headers to prevent client-side caching.

I'd like to have a response that contains:

  • Pragma: no-cache
  • Cache-control : no-cache
  • Cache-control: must-revalidate

And all the other nifty ways that browsers will hopefully interpret as directives to avoid caching.

Is there a no-cache middleware or something similar that can do the trick with minimal code intrusion?

Lorenzo
  • 4,108
  • 10
  • 40
  • 52

7 Answers7

98

You can achieve this using the cache_control decorator. Example from the documentation:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
   # ...
Pi Delport
  • 9,482
  • 3
  • 36
  • 50
Kristian
  • 6,097
  • 3
  • 32
  • 36
  • 16
    To make this work on all browsers (specifically FireFox and Opera, it worked fine on IE and Safari/Chrome) I needed to manually add `response["Cache-Control"] = "no-cache, no-store, must-revalidate"` along with `@never_cache`. `@never_cache` calls `add_never_cache_headers()` and this in turn calls `patch_cache_control()` but this only adds `Cache-Control:max-age=0`, which apparently is not enough for these browsers. See http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers – AJJ Jul 25 '12 at 16:05
  • 9
    After exploring the django code a bit more I found a cleaner way of adding that header: `patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True) ` – AJJ Jul 25 '12 at 16:18
  • 6
    Ah, there is already an open ticket for this at code.djangoproject.com: [@never_cache decorator should add 'no-cache' & 'must-revalidate'](https://code.djangoproject.com/ticket/13008) – AJJ Jul 25 '12 at 16:32
  • 2
    @AJJ I think you also missed `response['Pragma'] = 'no-cache'` – Ory Band Dec 24 '14 at 12:47
  • 7
    Update in 2018: ```@never_cache``` has been fixed to work on all browsers. – mathew Jan 30 '18 at 20:14
  • How do I handled this with class based views? – Hussain Feb 05 '18 at 02:08
  • My tests showed `@never_cache` seems to disable the server-side cache as well. – Peter F Apr 07 '21 at 07:44
52

This approach (slight modification of L. De Leo's solution) with a custom middleware has worked well for me as a site wide solution:

from django.utils.cache import add_never_cache_headers

class DisableClientSideCachingMiddleware(object):
    def process_response(self, request, response):
        add_never_cache_headers(response)
        return response

This makes use of add_never_cache_headers.


If you want to combine this with UpdateCacheMiddleware and FetchFromCacheMiddleware, to enable server-side caching while disabling client-side caching, you need to add DisableClientSideCachingMiddleware before everything else, like this:

MIDDLEWARE_CLASSES = (
    'custom.middleware.DisableClientSideCachingMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',
    # ... all other middleware ...
    'django.middleware.cache.FetchFromCacheMiddleware',
)
Flimm
  • 97,949
  • 30
  • 201
  • 217
Meilo
  • 3,128
  • 3
  • 25
  • 33
  • 5
    +1 for using `add_never_cache_headers` instead of manually inserting headers. – Michael Mior Jan 04 '12 at 21:41
  • I've packaged something based upon this. It is now available on PyPI, and github. https://github.com/incuna/django-never-cache-post – meshy Apr 10 '13 at 08:25
  • Just an fyi: This doesn't really work for Opera and page-caching, because add_never_cache just sets max-age to zero, and O, and Opera doesn't seem to honor max-age for that purpose. See http://my.opera.com/yngve/blog/2007/02/27/introducing-cache-contexts-or-why-the – AdamC Nov 05 '13 at 16:26
  • Not exactly, @AdamC. Going back at least to Django 1.5, `add_never_cache_headers` also sets Expires and Last-Modified headers to the current time. It will also set ETag headers to avert caching if USE_ETAGS is set in your settings. See https://github.com/django/django/blob/master/django/utils/cache.py – B Robster Jan 23 '15 at 23:44
  • Awesome answer. I posted a rewrite for django 1.10+ https://stackoverflow.com/a/47684405/2800876 – Zags Dec 06 '17 at 22:20
  • This works great. Side note: `add_never_cache_headers()` only sets headers if they are not set yet - it doesn't overwrite existing headers. In my case the 'Expires' header was already set. To overwrite it this line is sufficient: `response['Expires'] = http_date(time.time())`. It can be added either directly above or below `add_never_cache_headers()`. The additional imports: ```import time from django.utils.http import http_date``` – Peter F Apr 07 '21 at 08:55
16

To supplement existing answers. Here is a decorator that adds additional headers to disable caching:

from django.views.decorators.cache import patch_cache_control
from functools import wraps

def never_ever_cache(decorated_function):
    """Like Django @never_cache but sets more valid cache disabling headers.

    @never_cache only sets Cache-Control:max-age=0 which is not
    enough. For example, with max-axe=0 Firefox returns cached results
    of GET calls when it is restarted.
    """
    @wraps(decorated_function)
    def wrapper(*args, **kwargs):
        response = decorated_function(*args, **kwargs)
        patch_cache_control(
            response, no_cache=True, no_store=True, must_revalidate=True,
            max_age=0)
        return response
    return wrapper

And you can use it like:

class SomeView(View):
    @method_decorator(never_ever_cache)
    def get(self, request):
        return HttpResponse('Hello')
Jan Wrobel
  • 6,419
  • 1
  • 31
  • 49
  • Can someone explain the down vote? I wonder if something is fundamentally wrong with the code, because I depend on it in a production system. – Jan Wrobel Jan 09 '13 at 16:39
  • +1 Works fine for me as well and I do not see any problem either. To hear a reason from the downvoter would be really appreciated. – zerm Jan 24 '13 at 11:03
7

Actually writing my own middleware was easy enough:

from django.http import HttpResponse


class NoCacheMiddleware(object):

    def process_response(self, request, response):

        response['Pragma'] = 'no-cache'
        response['Cache-Control'] = 'no-cache must-revalidate proxy-revalidate'

        return response

Still doesn't really behave like i wanted but so neither does the @never_cache decorator

Lorenzo
  • 4,108
  • 10
  • 40
  • 52
  • 1
    This answer to *Making sure a web page is not cached, across all browsers* is quite detailed: http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers – AJJ Jul 25 '12 at 16:09
5

Regarding the Google Chrome browser (Version 34.0.1847.116 m) and the other browsers, I found that only the @cache_control decorator is working. I use Django 1.6.2.

Use it like this:

@cache_control(max_age=0, no_cache=True, no_store=True, must_revalidate=True)
def view(request):
    ...
Erwan
  • 735
  • 1
  • 8
  • 22
5

Here is a rewrite of @Meilo's answer for Django 1.10+:

from django.utils.cache import add_never_cache_headers

class DisableClientCachingMiddleware(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
Zags
  • 26,675
  • 10
  • 77
  • 107
5

I was scratching my head when the three magic meta didn't work in Firefox and Safari.

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

Apparently it can happen because some browsers will ignore the client side meta, so it should be handled at server side.

I tried all the answers from this post for my class based views (django==1.11.6). But referring to answers from @Lorenzo and @Zags, I decided to write a middleware which I think is a simple one.

So adding to other good answers,

# middleware.py
class DisableBrowserCacheMiddleware(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response['Pragma'] = 'no-cache'
        response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
        response['Expires'] = '0'
        return response

# settings.py
MIDDLEWARE = [
    'myapp.middleware.DisableBrowserCacheMiddleware',
    ...
Hussain
  • 4,349
  • 5
  • 38
  • 63