Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Doesn't Serve STATIC_ROOT in DEBUG

I'm using Python 3.5 and Django 1.10 to run a development server:

./manage.py runserver 0.0.0.0:8000

In my settings.py I have:

DEBUG       = True
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL  = '/static/'

And an app directory, with a static subdirectory for its static files:

proj/
    proj/
        ...
    app/
        static/
            a.txt
        ...
    static/
        b.txt

Pretty standard.

However: Django doesn't serve the STATIC_ROOT when DEBUG = True. It returns app/static/a.txt for /static/a.txt alright, but not static/b.txt for /static/b.txt.

Changing settings.py to read:

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

Works - but then I have to comment out STATIC_ROOT (otherwise Django complains it can't be in STATICFILES_DIRS).

Now, I can't just "use a different external static directory", e.g. static2, because I'm using django-sass-processor, which compiles .sass files into .css files, and puts these .css files in the STATIC_ROOT (which, as I've said, is inaccessible).

Things I've tried:

  1. Settings up NGINX to serve that directory (like in a production environment). Works, but there just has to be another way.

  2. Configuring django-sass-processor to write the .css files into said "different external static directory", e.g. static2, and including it in STATICFILES_DIRS. Again, works, but it just can't be that complicated!

  3. Manually adding static files URLs in urls.py:

    if settings.DEBUG:
        urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    

    This one was quite a trip, so I thought I'd share it to save other people the trouble: it turns out that Django does that anyway, and actually overwrites my URLs - but it includes, as I've said, only my apps' static directories and those in STATICFILES_DIRS.

    I even changed DEBUG to False and removed the if - but that didn't work as well, because then the django.conf.urls.static.static function actually returns an empty list. So, I implemented it myself using django.views.static.serve, and it finally worked, but again - it doesn't make sense I have to turn off debugging and manually implement serving static file.

Update

  1. If you're working with django-sass-processor and running into similar problems, they've actually provided a solution I just noticed in their documentation: a special static finder you can add in your settings.py like so:

    STATICFILES_FINDERS = [
        'django.contrib.staticfiles.finders.FileSystemFinder',
        'django.contrib.staticfiles.finders.AppDirectoriesFinder',
        'sass_processor.finders.CssFinder',
    ]
    

    (The first two are Django's default finders, so when you override this configuration you should include them manually).

    But even now, for anything other than .css files, the STATIC_ROOT is actually the only static directory that is inaccessible via /static/, which I find pretty odd. So, I'd still like to solve (or at least understand...) it.

like image 553
Dan Gittik Avatar asked Oct 29 '16 17:10

Dan Gittik


People also ask

Why does debug false setting make my Django static files access fail?

Learning programming: Why does DEBUG=False setting make my Django Static Files Access fail? Because the Django development server does not serve static files. You need to have a web server like nginx or Apache to serve your static files.


1 Answers

At first glance it seems very strange that Django doesn't serve files from STATIC_ROOT, but it makes sense when you consider the workflow Django intends for static files. Django expects that you bundle static files alongside apps and you save project-level static files in a separate directory that is checked into version control. Then, when you run manage.py collectstatic the STATICFILES_FINDERS are used to gather up all the files into a single directory (which should not be in version control) so they can be deployed to AWS S3 or whatever.

So the staticfiles finders have two jobs:

  1. find a file via path, used for DEBUG mode static file serving
  2. list all of the available files, used for collectstatic

If you were able to include STATIC_ROOT in your STATICFILES_DIRS then the collectstatic command would probably get stuck in a loop. Also, the Django developers don't intend for you to save files in STATIC_ROOT since it shouldn't be checked into version control.

However, there's times when you actually do want to serve files from STATIC_ROOT. In my case, I have a Celery task that creates thumbnails of uploaded images and saves them to the static files storage. In production that ends up saving on AWS S3 where it can be served.

So, if you have a valid use case for serving files from STATIC_ROOT just define your own file finder and add it's path to STATICFILES_FINDERS:

from django.contrib.staticfiles.finders import BaseFinder
from django.contrib.staticfiles.storage import staticfiles_storage

class StaticRootFinder(BaseFinder):
    """
    For debug mode only. Serves files from STATIC_ROOT.
    """
    def find(self, path, all=False):
        full_path = staticfiles_storage.path(path)
        if staticfiles_storage.exists(full_path):
            return [full_path] if all else full_path
        return []

    def list(self, ignore_patterns):
        return iter(())
like image 57
Benjamin Dummer Avatar answered Oct 04 '22 01:10

Benjamin Dummer