40

Is there an easy way to turn off caching of static files in Django's development server?

I'm starting the server with the standard command:

python manage.py runserver

I've got settings.py configured to serve up static files from the /static directory of my Django project. I've also got a middleware class that sets the Cache-Control header to must-revalidate, no-cache for development, but that only seems to affect URLs that are not in my /static directory.

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
aaronstacy
  • 6,189
  • 13
  • 59
  • 72
  • 2
    Making this non-caching behavior the default was the subject of [this ticket](https://code.djangoproject.com/ticket/27572), and was deferred pending a possible merger with whitenoise (which has already solved this problem). – Kevin Christopher Henry Nov 13 '18 at 06:16
  • You could just test your sites in an [incognito window](https://www.quora.com/Does-incognito-mode-on-Chrome-use-the-cache-that-was-previously-stored). The caches won't be saved. – Samy Nov 26 '18 at 22:29

11 Answers11

21

@Erik Forsberg's answer worked for me. Here's what I had to do:

  • Comment out the staticfiles app from INSTALLED_APPS in settings.py:

    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.messages',
        #'django.contrib.staticfiles',
    )
    
  • Leave my STATIC_URL variable set in settings.py:

    STATIC_URL = '/static/'
    
  • Add an entry to my project's base urls.py:

    # static files w/ no-cache headers
    url(r'^static/(?P<path>.*)$', 'django.views.static.serve',
        {'document_root': settings.STATIC_ROOT}),
    

Note that I'm also setting the Cache-Control headers in a middleware class nocache.py:

class NoCache(object):
    def process_response(self, request, response):
        """
        set the "Cache-Control" header to "must-revalidate, no-cache"
        """
        if request.path.startswith('/static/'):
            response['Cache-Control'] = 'must-revalidate, no-cache'
        return response

And then including that in settings.py:

if DEBUG:
    MIDDLEWARE_CLASSES = (
        'django.middleware.common.CommonMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'nocache.NoCache',
    )
djangonaut
  • 7,233
  • 5
  • 37
  • 52
aaronstacy
  • 6,189
  • 13
  • 59
  • 72
  • 1
    You can also do this without disabling the `staticfiles` app by starting the `runserver` command with the `--nostatic` option. This will prevent the `staticfiles` app from serving the static files. See my answer for more info. – gitaarik Aug 15 '13 at 10:11
  • One problem I saw with this approach is some apps like "rest framework" break when they have {% load staticfiles %} in their template files. Other than that, it fixed the problem of caching – Rajesh Chamarthi Oct 21 '15 at 04:20
  • Oddly enough, I couldn't get this to work. Firefox/Chrome seem to ignore the cache-control, the Django isn't sending back the right header. The only workaround I could find was to append a random number to the media's URL. – Cerin Mar 10 '17 at 00:11
  • 3
    Quite complicated for something that should be added out of the box – Vadorequest Oct 08 '18 at 13:53
20

Django's contrib.staticfiles app automatically serves staticfiles for you by overriding the runserver command. With this configuration you can't control the way it serves the static files.

You can prevent the staticfiles app from serving the static files by adding the --nostatic option to the runserver command:

./manage.py runserver --nostatic

Then you can write an url config to manually serve the static files with headers that prevent the browser from caching the response:

from django.conf import settings
from django.contrib.staticfiles.views import serve as serve_static
from django.views.decorators.cache import never_cache

urlpatterns = patterns('', )

if settings.DEBUG:
    urlpatterns += patterns('',
        url(r'^static/(?P<path>.*)$', never_cache(serve_static)),
    )

If you want your manage.py runserver command to have the --nostatic option on by default, you can put this in your manage.py:

if '--nostatic' not in sys.argv:
    sys.argv.append('--nostatic')
gitaarik
  • 42,736
  • 12
  • 98
  • 105
  • 2
    This is the simplest fix. – zengr Sep 03 '14 at 01:12
  • 3
    Seems a little strange that this is necessary; during development (the only time you should be using runserver according to recommendations) you don't really want things to cache do you? Seems to me that the default behavior during development should be to never_cache, and to make an optional argument that would turn on caching for runserver. Realize this isn't the place for Django suggestions, but it just struck me when researching this topic. – Nick Spacek Oct 07 '14 at 18:38
  • 1
    This answer won't always work. It can be simplified and made more generally useful. If `urlpatterns` includes a catch-all url at the end it will not work. So add the new url to the beginning of `urlpatterns`. And if you do not have `STATIC_URL` set to "static" it will not work. So use `urlpatterns = static(settings.STATIC_URL, view=never_cache(serve_static)) + urlpatterns`. (The `static` function can be found in `django.conf.urls.static`.) – nmgeek May 01 '16 at 18:29
  • Certain `manage.py` commands don't recognize the `--nostatic` flag. Updating `manage.py` to use and updated if statement of `if sys.argv[1] == 'runserver' and '--nostatic' not in sys.argv: ...` seemed to fix it. – Foot Apr 29 '21 at 19:31
15

Assuming you're using django.views.static.serve, it doesn't look like it - but writing your own view that just calls django.views.static.serve, adding the Cache-Control header should be rather easy.

Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
Erik Forsberg
  • 4,819
  • 3
  • 27
  • 31
12

Use whitenoise. There's a lot of issues with the static file serving in runserver and they're all already fixed in whitenoise. It's also WAY faster.

They've talked about just replacing the built-in static serving with it, but no one has gotten around to it yet.

Steps to use it in development...

Install with pip install whitenoise

Add the following to the end of settings.py:

if DEBUG:
    MIDDLEWARE = [
        'whitenoise.middleware.WhiteNoiseMiddleware',
    ] + MIDDLEWARE
    INSTALLED_APPS = [
        'whitenoise.runserver_nostatic',
    ] + INSTALLED_APPS
Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130
Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
10

My very simple solution:

from django.contrib.staticfiles.views import serve
from django.views.decorators.cache import never_cache

static_view = never_cache(serve)
urlpatterns += static_view(settings.MEDIA_URL,
                           document_root=settings.MEDIA_ROOT)
Vladislav
  • 1,318
  • 16
  • 15
  • 4
    Hmm. This does not work for me. I get this error: `TypeError: serve() takes at least 2 arguments (1 given)`. Maybe you meant to use `django.conf.urls.static` instead? Like: `urlpatterns = static(settings.STATIC_URL, view=never_cache(serve)) + urlpatterns` – nmgeek May 01 '16 at 17:33
9

In newer versions of Django a very simple solution is modify the project urls like so:

from django.conf.urls.static import static
from django.contrib.staticfiles.views import serve
from django.views.decorators.cache import cache_control
from django.conf import settings

# YOUR urlpatterns here... 

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, view=cache_control(no_cache=True, must_revalidate=True)(serve))

I arrived at this by looking at how staticfiles modifies the urls automatically and just adding a view decorator. I really don't understand why this isn't the default as this is for development ONLY. The view is able to properly handle a "If-Modified-Since" HTTP header so a request is always made but contents are only transferred on changes (judged by looking at the modification timestamp on the file).

For this to work you must add --nostatic when using runserver, otherwise the above changes are simply ignored.

IMPORTANT EDIT: What I had before didn't work because I wasn't using --nostatic and the never_cache decorator also included no-store which meant unchanged files were always being re-transferred instead of returning 304 Not Modified

Jure C.
  • 3,013
  • 28
  • 33
Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
1

For newer Django, the way middleware classes are written has changed a bit.

Follow all the instructions from @aaronstacy above, but for the middleware class, use this:

class NoCache(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response['Cache-Control'] = 'must-revalidate, no-cache'
        return response
Clayton Gulick
  • 9,755
  • 2
  • 36
  • 26
1

It's so simple if you are using Django 2.0+

Step-1 : Make 'django.contrib.staticfiles' as comment in settings.py(Project level)

INSTALLED_APPS = [

# 'django.contrib.staticfiles',

]

Step-2 : Import followings in urls.py (Project level)

     from django.conf.urls.static import static

     from django.contrib.staticfiles.views import serve

     from django.views.decorators.cache import never_cache

     from . import settings

Step-3 : Add following line in urls.py(project level) after urlpatterns

urlpatterns = [

]

if settings.DEBUG:

    urlpatterns += static(settings.STATIC_URL, view=never_cache(serve))

Make sure that STATIC_URL is declared in your settings.py

STATIC_URL = '/static/'
0

The staticfiles app implements serving of static files by overriding the runserver command. We can do the same: override this command again and implement a custom handler which turns off caching.

Note that your django application must be before django.contrib.staticfiles in INSTALLED_APPS, otherwise your command will be ignored.

# your_django_app/management/commands/runserver.py

from django.utils.cache import add_never_cache_headers
from django.contrib.staticfiles.handlers import (
    StaticFilesHandler as BaseStaticFilesHandler,
)
from django.contrib.staticfiles.management.commands.runserver import (
    Command as RunserverCommand,
)


class StaticFilesHandler(BaseStaticFilesHandler):
    def serve(self, request):
        response = super().serve(request)
        add_never_cache_headers(response)
        return response


class Command(RunserverCommand):
    def get_handler(self, *args, **options):
        handler = super().get_handler(*args, **options)

        # Check that serving static files is enabled
        if isinstance(handler, BaseStaticFilesHandler):
            # If yes, replace the original handler with our custom handler
            handler = StaticFilesHandler(handler.application)

        return handler
andreymal
  • 129
  • 1
  • 10
0

With whitenoise, you can disable your browser to cache the static files of Django.

So first, install whitenoise as shown below:

pip install whitenoise

Then, you must set whitenoise.runserver_nostatic just before django.contrib.staticfiles in INSTALLED_APPS and set WhiteNoiseMiddleware just after SecurityMiddleware in MIDDLEWARE in settings.py as shown below otherwise your browser still caches the static files in Django:

# "settings.py"

INSTALLED_APPS = [
    ...
    'whitenoise.runserver_nostatic', # Here
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', # Here
    ...
]

Lastly, I recommend to clear browser's cache after setting whitenoise following my answer here because your browser will still keep the cache of the static files of Django. *On Google Chrome, you can open the page to clear cache with Ctrl+Shift+Del which also works on Firefox. Or you can empty cache and reload the page with Ctrl+F5, Shift+F5 or Ctrl+Shift+R (Ctrl+F5 or Ctrl+Shift+R in Firefox). Or you can use the Google Chrome extension Clear Cache to clear cache

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
-1

This has nothing with Django, because nothing changed after I reinstall Django using pip.

This is the behavior of browser, so you just need to clear cached images files of your browser.

Ref

Chrome Clear cache and cookies

Cloud
  • 2,859
  • 2
  • 20
  • 23