django-compressor ================= `django-compressor docs `_ Warning: much of the documentation is casual about saying things that are only true in some scenarios, without making clear that that's the case. ACTUALLY USING ~~~~~~~~~~~~~~ Here are some practical scenarios for using django-compressor. For what to put in your templates, you can go by the django-compressor documentation, and be sure to use {% static %} and not STATIC_URL. For what to put in your settings... it's a lot more complicated. Set the compressor filters and precompilers however you want. For the rest, keep reading. Scenario: Development using runserver, DEBUG, not offline --------------------------------------------------------- If DEBUG is True, then compressor won't even do anything and so everything should just work. Scenario: Running using local files, not offline ------------------------------------------------ This is the typical small server situation. You unpack your project on the server, run collectstatic, point nginx or some other server at STATIC_ROOT and go. Example settings: .. code-block:: python # Django settings DEBUG = False STATIC_ROOT = '/var/www/static/' STATIC_URL = '/static/' # set compressor filters and precompilers as desired. # leave other compressor settings at defaults. .. code-block:: nginx # nginx settings location /static { alias /var/www/static; } Scenario: running using local files, with offline ------------------------------------------------- Like the previous scenario, but you want compressor to do all its work at deploy time so the results are cached and ready to go immediately when you start your server. .. code-block:: python # Django settings like before, plus: COMPRESS_OFFLINE = True Now at deploy time you have more steps: .. code-block:: bash $ python manage.py collectstatic $ python manage.py compress Run ``compress`` *after* collectstatic so that compressor can find its input files. It'll write its output files under ``{STATIC_ROOT}/CACHE``, and get them from there at runtime. Scenario: running with storage on the network, with offline ----------------------------------------------------------- In this scenario, you're putting your static files somewhere off of the server where you're running Django. For example, S3. Or just your own static file server somewhere. Whatever. Let's start with how this would be setup without django-compressor, then we can modify it to add django-compressor. .. code-block:: python # settings/no_compressor.py STATIC_ROOT = None # Unused STATIC_URL = None # Unused STATIC_FILE_STORAGE = 'package.of.FileStorageClass' At deploy time you can just run collectstatic, and all your static files will be pushed to the network: .. code-block:: bash $ python manage.py collectstatic And at runtime, ``{% static %}`` will ask your file storage class to come up with a URL for each file, which will turn out to be on your other server, or S3, or whatever. Now, suppose we want to add compressor with offline processing (not using offline makes no sense with network storage). Here are the settings you can use at runtime for that, assuming things have been prepared correctly: .. code-block:: python # settings/deployed.py # Django settings we'll use in production STATIC_ROOT = None # Unused STATIC_URL = None # Unused STATIC_FILE_STORAGE = 'path.to.network.filestorage' COMPRESS_ENABLED = True COMPRESS_OFFLINE = True The preparation is the tricky part. It turns out that for the ``compress`` command to work, a copy of the static files must be gathered in a local directory first. Most of the tools we might use to compile, compress, etc. are going to read local files and write local output. To gather the static files into a local directory, we might, for example, use a different settings file that uses the default file storage class, and run collectstatic. E.g.: .. code-block:: python # settings/gather.py # Django settings when first running collectstatic from .deployed import * # Override a few settings to make storage local STATIC_ROOT = '/path/to/tmp/dir' STATIC_URL = None # Unused STATIC_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' .. code-block:: bash $ python manage.py collectstatic --settings=settings.gather After running ``collectstatic`` with these settings, all your source static files will be gathered under '/path/to/tmp/dir'. Now you could run ``compress``, and the resulting files would be added under `/path/to/tmp/dir`. There's an important *gotcha* that will cause problems, though - for compressor to match up the output it makes now with what it'll be looking for later, the contents of each ``{% compress %}`` tag must be identical now to what it'll be then, which means the URLs must point at the production file server. We can accomplish this by setting STATIC_URL before running the compress: .. code-block:: python # settings/compress.py # Django settings when running compress command from .deployed import * # Override a few settings to make storage local, but URLs look remote STATIC_ROOT = '/path/to/tmp/dir' STATIC_URL = 'https://something.s3.somewhere/static/' # URL prefix for runtime STATIC_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' .. code-block:: bash $ python manage.py compress --settings=settings.compress The problem now is to get all these files onto the remote server. You could just use ``rsync`` or ``s3cmd`` or something, which will work fine. But for maximum flexibility, let's figure out a way to do it using Django. Our approach will be to tell Django that our SOURCE static files are in '/path/to/tmp/dir', and we want them collected using our production file storage class, which will put them where we want them. .. code-block:: python # Django settings when running collectstatic again after compress, # to copy the resulting files to the network # settings/copy_upstream.py from .deployed import * # Set up for network file storage # Tell collectstatic to use the files we collected and compressed STATICFILES_FINDERS = ['django.contrib.staticfiles.finders.FileSystemFinder'] STATICFILES_DIRS = ['/path/to/tmp/dir'] .. code-block:: bash $ python manage.py collectstatic --settings=settings.copy_upstream That should copy things to the network. Then if you run using the 'deployed' settings, things should work! TODO: TEST THAT!!!!!!!!!!!!!!!!!!!! Other approaches ---------------- The compressor docs suggest a different approach -- hack the storage class you're using so when you run collectstatic, it saves a copy of each file into a local directory in addition to pushing it upstream. Then you can use the same storage class for collectstatic, compress, and runtime. More detailed notes ~~~~~~~~~~~~~~~~~~~ Cache ----- For some things, compressor uses the cache named by ``COMPRESS_CACHE_BACKEND``, which defaults to ``None``, which gives us the default Django cache. Principles of compression ------------------------- Whether compressor is processing templates offline ahead of time or at runtime, there are some common principles. First, if ``COMPRESS_ENABLED`` is False, the ``{% compress %}`` tag will simply render as its contents; compressor won't change anything. Otherwise, compressor will 1. parse the contents of the tag and figure out which css and javascript files would be included 2. fetch those files (See "accessing the files to be compressed") 3. run those files through any configured preprocessors 4. concatenate the result and save it using COMPRESS_STORAGE 5. at rendering, the tag and contents will be replaced with one or two HTML elements that will load the compressed file instead of the original ones. Offline ------- If ``COMPRESS_OFFLINE`` is True, compressor expects all uses of ``{% compress ... %}`` in templates to have been pre-processed by running ``manage.py compress`` ahead of time, which puts the results in compressor's *offline* cache. If anything it needs at run-time is not found there, things break/throw errors/render wrong etc. .. note:: If COMPRESS_OFFLINE is True and files have not been pre-compressed, compressor will *not* compress them at runtime. Things will break. The offline cache manifest is a json file, stored using COMPRESS_STORAGE, in the subdirectory ``COMPRESS_OUTPUT_DIR`` (default: ``CACHE``), using the filename ``COMPRESS_OFFLINE_MANIFEST`` (default: ``manifest.json``). The keys in the offline cache manifest are generated from *the template content inside each compress tag*, *not* the contents of the compressed files. So, you must arrange to re-run the offline compression anytime your content files might have changed, or it'll be serving up compressed files generated from the old file contents. .. note:: It sounds like you must *also* be *sure* the contents of the compress tags don't change between precompressing and runtime, for example by changing the URL prefix! The values in the offline cache manifest are paths of the compressed files in COMPRESS_STORAGE. .. note:: RECOMMENDATION FROM DOCS: make ``COMPRESS_OFFLINE_MANIFEST`` change depending on the current code revision, so that during deploys, servers running different versions of the code will each use the manifest appropriate for the version of the code they're running. Otherwise, servers might use the wrong manifest and strange things could happen. Not offline ----------- If ``COMPRESS_OFFLINE`` is False, compressor will look in COMPRESS_STORAGE for previously processed results, but if not found, will create them on the fly and save them to use again. Storage ------- Compressor uses a `Django storage class `_ for some of its operations, controlled by the setting ``COMPRESS_STORAGE``. The default storage class is ``compressor.storage.CompressorFileStorage``, which is a subclass of the standard filesystem storage class. It uses ``COMPRESS_ROOT`` as the base directory in the local filesystem to store files in, and builds URLs by prefixing file paths within the storage with ``COMPRESS_URL``. If you change ``COMPRESS_STORAGE``, then *ignore* anything in the docs about ``COMPRESS_ROOT`` and ``COMPRESS_URL`` as they won't apply anymore (except in a few cases... see exceptions noted as they come up, below). Accessing the files to be compressed ------------------------------------ For each file to be compressed, compressor starts with the URL from the rendered original content inside the compress tag. For example, if part of the content is ````, then it extracts ``"http://example.com/foo.js"`` as the URL. It checks that the URL starts with COMPRESS_STORAGE's ``base_url``, or if accessing that fails (quite possible since ``base_url`` is not a standard part of the file storage class API), uses ``COMPRESS_URL``. .. note:: This is a place where compressor can use COMPRESS_URL even if it's not using its default storage. If the URL doesn't start with that string, compressor throws a possibly misleading error, "'%s' isn't accessible via COMPRESS_URL ('%s') and can't be compressed". Otherwise, compressor tries to come up with a local filepath to access the file, as follows: * Try to get a local filepath from COMPRESS_STORAGE using ``.path()``. * If that's not implemented (for example, for remote storages), it tries again using ``compressor.storage.CompressorFileStorage`` (regardless of what COMPRESS_STORAGE is set to), so basically it's going to look for it under COMPRESS_ROOT. * If it still can't get a local filepath, throws an error: "'%s' could not be found in the COMPRESS_ROOT '%s'%s" which is very misleading if you're not using a storage class that looks at COMPRESS_ROOT.