80

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,558
  • 11
  • 44
  • 54

9 Answers9

103

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
  • 10,356
  • 3
  • 36
  • 50
Kristian
  • 6,357
  • 4
  • 36
  • 37
  • 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
  • 9
    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
  • 1
    My tests showed `@never_cache` seems to disable the server-side cache as well. – Peter F Apr 07 '21 at 07:44
56

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
  • 136,138
  • 45
  • 251
  • 267
Meilo
  • 3,378
  • 3
  • 28
  • 34
  • 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
17

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,969
  • 3
  • 37
  • 53
  • 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
11

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
  • 5,057
  • 6
  • 45
  • 71
8

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,558
  • 11
  • 44
  • 54
  • 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
7

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
  • 1,055
  • 1
  • 12
  • 26
6

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
  • 37,389
  • 14
  • 105
  • 140
0

For Django 4+:

from django.utils.cache import add_never_cache_headers


def disable_client_side_caching_middleware(get_response):
    def middleware(request):
        response = get_response(request)
        add_never_cache_headers(response)
        return response

    return middleware
MIDDLEWARE_CLASSES = (
    ...
    'my_app.middleware.disable_client_side_caching_middleware'
    ...
)
François Constant
  • 5,531
  • 1
  • 33
  • 39
0

For anyone with newer version of Django 4.0+

class DisableClientSideCachingMiddleware:
    """
    Middleware to disable client side caching, that is on browser.
    Adds a Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private header ,
    to a response to indicate that a page should never be cached.

    If not added and the api returns a cached response, the browser also caches the data into disk cache.
    Meaning we don't want caching to happen at browser level on client side as clinent will see data from
    disk cache even if it is changed on server side.

    """

    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 add this middleware in your settings,

MIDDLEWARE_CLASSES = (
    ...
    'your_app.middleware.DisableClientSideCachingMiddleware'
    ...
)
Shubham Devgan
  • 609
  • 6
  • 18