This project is intended as an easy way to manage multiple static text assets (CSS and Javascript) in a Django projects.
- A template tag to echo collections of CSS files;
- A combination function with optional delimter;
- A sample Django project using the template tags;
- JS as well as CSS template tags;
- A Django management command to combine files (for building);
- Support for command line minification/compression when building (YUI compressor, JSMin, Icy etc.).
- Support for filename versioning (using SHA1 sums, file modification times, etc.).
- Support for versioning other static assets (e.g. images, Flash files, etc.).
- Unit tests;
- Sample web server configuration for Gzip, mod_deflate etc. (since you really shouldn't be serving static assets via Django in production).
The configuration is intended to be easy to read and use.
Add the static_management
directory into your Django application, and included it in installed_apps
.
You can include the current version as a git submodule
as follows:
git submodule add git://github.com/bradleywright/django-static-management.git django_site_dir/static_management
Substitute django_site_dir
with the root directory of your Django application (where manage.py
and settings.py
live).
Add the following construct (or similar) in settings.py
:
STATIC_MANAGEMENT = {
'css': {
'css/mymainfile.css' : [
'css/myfile.css',
'css/anotherfile.css'
],
},
'js': {
'js/myjsfile.js' : [
'js/mynewfile.js',
'js/anotherfile.js'
],
}
}
What happens is that files inside css
and js
are combined as follows:
css/mymainfile.css
(the "key" of the key/value pair) is the target file, which is created automatically from the list of files (the "value" of the key/value pair) beside it;- If the files do not exist, the entire file is skipped;
- Paths are relative to
settings.MEDIA_ROOT
(so you're unlikely to need to move files around in an already working Django project). - The combined css file should be in the same directory as the files it's constructed from.
Other files may inherit from css/mymainfile.css
(for example, IE hack files) by including it in their list of files, like so:
...
'css/myie6.css' : [
'css/mymainfile.css', # inherits from main
'css/ie6.css'
]
...
In your templates, you use the static_combo
and static_asset
template tag libraries:
{% load static_combo static_asset %}
{% static_combo_css "css/mymainfile.css" %}
...
<img alt="Company logo" src="{% static_asset "img/logo.png" %}">
...
{% static_combo_js "js/myjsfile.js" %}
Where css/mymainfile.css
is the "combined" file name from your settings. In DEBUG
mode, this will echo out all the files in order (for debugging purposes). In production mode, it will only echo the "combined" file name.
By default the CSS template tag uses HTML 4.01 style link
elements (non self-closing) - you may override this with a setting like:
STATIC_MANAGEMENT_CSS_LINK = '<link rel="stylesheet" type="text/css" href="%s" />\n'
The Javascript template tag uses the standard construct, and only needs to be overridden if you want to force UTF-8 encoding in your files:
STATIC_MANAGEMENT_SCRIPT_SRC = '<script type="text/javascript" charset="utf-8" src="%s"></script>\n'
In order to help meet performance recommendations which encourage the use of Expires:
headers, static management supports versioning files.
To use, simply define a dictionary that maps relative filenames to versioned filenames:
STATIC_MANAGEMENT_VERSIONS = {
'js/main.js': 'http://cdn1.example.com/js/main.15274671.js',
'js/main.css': 'http://cdn2.example.com/js/main.82773622.css',
'img/logo.png': 'http://cdn1.example.com/img/logo.99182772.png',
'img/icon.png': 'http://cdn2.example.com/img/icon.31901927.png'
}
To provide the absolute URL for versioned files, define the STATIC_MANAGEMENT_HOSTNAMES
setting to include a list of hostnames:
STATIC_MANAGEMENT_HOSTNAMES = [
'http://cdn1.example.com/',
'http://cdn2.example.com/'
]
Specify the type of file versioning with a setting like:
STATIC_MANAGEMENT_VERSIONER = 'static_management.versioners.SHA1Sum'
The following pre-rolled versioners are included:
SHA1Sum
- Calculates the SHA1 sum of a file's contents and uses the first 8 characters for the versionMD5Sum
- Same asSHA1Sum
but using the MD5 algorithm insteadFileTimestamp
- Uses the UNIX time representation of the file modification time to generate a version
Custom versioners are simple callables that take a single filename argument.
If the STATIC_MANAGEMENT_ASSET_PATHS
value is set, the combiner will traverse the filesystem looking for files which match the STATIC_MANAGEMENT_ASSET_PATTERN
regular expression, adding them to the STATIC_MANAGEMENT_VERSIONS
setting. For example:
STATIC_MANAGEMENT_ASSET_PATHS = [('static/swf/', False), ('static/img/', False)]
STATIC_MANAGEMENT_ASSET_PATTERN = '.*(\.(png|jpg|gif|swf))'
STATIC_MANAGEMENT_ASSET_PATHS
should include a list of pairs where the first element is the path and the second is a boolean value which specifies whether or not the path should be recursed.
Any usage of the static_combo_asset
template tag will produce the fully-qualified path for the asset. After the versioning of static assets is complete, it will modify the combined CSS file(s) by replacing filenames with full versioned URIs.
The following command will generate all the files as per your settings:
./manage.py static_combine
Passing an argument of --compress
to the above command will run the compression script of your choice, as specified in: settings.STATIC_MANAGEMENT_COMPRESS_CMD
. This should be a string representing the script you want to run. The only caveat is that it must accept a filepath as an argument and return output to stdout
(the management command reads from stdout
). Following is an example using YUI Compressor (which this command was designed to use):
# settings.py
STATIC_MANAGEMENT_COMPRESS_CMD = 'java -jar /home/myuser/yuicompressor-2.4.2/build/yuicompressor-2.4.2.jar %s'
where %s
represents the path of the file to be compressed.
The --output
argument will generate a list of versioned filenames and output them using the method of your choice, as specified in: settings.STATIC_MANAGEMENT_VERSION_WRITER
. This should be the name of a callable object or class, which takes a dictionary of the structured defined above in `STATIC_MANAGEMENT_VERSIONS.
Note: It is often useful to use this mechanism to write the list of files to a configuration file and read from the same file in settings.py
.
The --write-version
argument will copy the relative filename (e.g. js/main.js
) to the versioned filename (e.g. js/main.123456.js
).
The 1.0 tag of this project contains a sample Django project to help demonstrate usage. This code structure will not be followed beyond the initial version--a git submodule
is now the preferred way of using this application.
This work contains some samples of the YUI library, which is licensed under a BSD License. My own work here is licensed similarly.