When specifying paths to assets like JavaScript files, but also CSS, the Django documentation states:
Any links to the file in the codebase should point to the compressed version.
This statement however does not properly reflect Django's internal handling of such assets, for instance in
we see, that Django only serves the minimized version of the file, when not in DEBUG mode.
This inconsistent handling of assets can cause other problems for third party apps, if they follow the documentation as show in the first link.
If in their Media definition they refer to the minimized version of a Django asset, say
'admin/js/vendor/jquery/jquery.min.js', then
the automatic sorting does not work anymore in DEBUG mode. If on the other side they refer to 'admin/js/vendor/jquery/jquery.js',
then automatic sorting fails in production.
There are two solutions to this problem.
Either
The documentation explicitly states that when a Media definition refers to internal Django assets, one must distinguish between the
minimized and unminimized version, just as Django's internal Media definitions do (see code examples above).
This however can easily be forgotten and errors therefore become visible only after deployment.
Or
We rewrite the FileSystemFinder and AppDirectoriesFinder so that in DEBUG mode, Django looks if an unminimized version
of the same asset exists, and if so it then serves that. Doing that automatically is easy, because the general convention is, that the .min
infix is always placed immediately before the .js- or .css file extension.
In many of my projects I therefore use these two alternative finders, which do exactly that:
import os
from django.conf import settings
from django.contrib.staticfiles.finders import (FileSystemFinder as FileSystemFinderBase, AppDirectoriesFinder as AppDirectoriesFinderBase)
class FileSystemFinder(FileSystemFinderBase):
"""
In debug mode, serve /static/any/asset.min.ext as /static/any/asset.ext
"""
locations = []
serve_unminimized = getattr(settings, 'DEBUG', False)
def find_location(self, root, path, prefix=None):
if self.serve_unminimized:
# search for the unminimized version, and if it exists, return it
base, ext = os.path.splitext(path)
base, minext = os.path.splitext(base)
if minext == '.min':
unminimized_path = super().find_location(root, base + ext, prefix)
if unminimized_path:
return unminimized_path
# otherwise proceed with the given one
path = super().find_location(root, path, prefix)
return path
class AppDirectoriesFinder(AppDirectoriesFinderBase):
serve_unminimized = getattr(settings, 'DEBUG', False)
def find_in_app(self, app, path):
matched_path = super().find_in_app(app, path)
if matched_path and self.serve_unminimized:
base, ext = os.path.splitext(matched_path)
base, minext = os.path.splitext(base)
if minext == '.min':
storage = self.storages.get(app, None)
path = base + ext
if storage.exists(path):
path = storage.path(path)
if path:
return path
return matched_path
In my opinion this approach makes Django's internal and 3rd party package code more readable, because it removes the clutter of
case distinction between DEBUG and production mode for each referred JavaScript and CSS asset. It also might be less error prone.