Set up static web page resources -- Images, CSS, JavaScript
The set up process for static
resources in Django projects varies considerably if a project runs
with DEBUG=True
or DEBUG=False
. This
means static resource deployment depends on whether you're doing
work on a development environment -- where you generally use
DEBUG=True
-- or on a production environment -- where
you generally use DEBUG=False
.
Considering you'll always start a Django project in a development environment and later migrate to a production environment, I'll describe the development set up process first and later describe the production set up process.
Set up static resources in a development environment (DEBUG=False)
By default when
DEBUG=False
, Django automatically sets up static
resources from two major locations. The first location are
static
folders in all Django apps and the second
location are folders declared in the STATICFILES_DIR
variable in settings.py
.
Although you'll need to manually
create the static
folder inside Django apps, it's this
easy to set up static resources in a project. Because Django sets
up all the static
folders for every project app, it's
a recommended practice to further add a sub-directory to the
static
folder
(e.g.<app_folder>/static/<app_name>/<static_files_here>
)
to qualify static resources and avoid potential naming conflicts.
Listing 5-10 illustrates a sample directory structure for static
resources.
Listing 5-10. Django app structure with static directories
+-<BASE_DIR_project_name> | +-manage.py | +-bootstrap-3.1.1-dist+ | +-bootstrap.min.js | +-jquery-1-11-1-dist+ | +jquery.min.js | +-jquery-ui-1.10.4+ | +jquery-ui.min.js | +-website-static-default+ | +-favicon.ico | +-robots.txt | | +---+-<PROJECT_DIR_project_name> | +-__init__.py +-settings.py +-urls.py +-wsgi.py | +-about(app)-+ | +-__init__.py | +-models.py | +-tests.py | +-views.py | +-static-+ | | | +-about-+ | +-img-+ | | +-logo.png | | | +-css-+ | +-custom.css +-stores(app)-+ +-__init__.py +-models.py +-tests.py +-views.py +-static-+ | +-stores-+ +-img-+ | +-coffee.gif | +-css-+ +-custom.css
As illustrated in listing 5-10,
all Django app directories have a static
sub-directory
that contain static resources. Anything under these static
sub-directories is set up for access.
Also notice in listing 5-10 the
importance of the app name sub-directory within the
static
sub-directories that acts as a namespace. If
static resources were placed directly below the static
folder in all apps, in this scenario it would lead to two identical
file paths named /static/css/custom.css
. In which case
a call to load this static resource would lead to a conflict.
Technically, Django always uses the first file it finds, but will
the first one be the right one ? By using an app name sub-directory
inside static
it avoids any potential conflict, with
one static resource set up at
/static/about/css/custom.css
and the other at
/static/stores/css/custom.css
.
Because there can be static resources that don't necessarily belong to a specific project app, Django also supports the ability to set up static resources stored on any sub-directory.
If you look again at listing 5-10
in between the BASE_DIR
and PROJECT_DIR
,
you'll see there are various sub-folders that contain popular
static resource libraries -- jquery
,
jquery-ui
and bootstrap
-- as well as a
sub-folder website-static-default
with a web site's
standard static resources -- robots.txt &
favicon.ico
.
In order to set up these
additional static resources, you define the location of these
directories in the STATICFILES_DIR
variable in
settings.py
. Listing 5-11 illustrates an example of a
STATICFILES_DIR
definition.
Listing 5-11 Django STATICFILES_DIR definition with namespaces in settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATICFILES_DIRS = ('%s/website-static-default/'% (BASE_DIR), ('bootstrap','%s/bootstrap-3.1.1-dist/'% (BASE_DIR)), ('jquery','%s/jquery-1-11-1-dist/'% (BASE_DIR)), ('jquery-ui','%s/jquery-ui-1.10.4/'% (BASE_DIR)),)
As you can see in listing 5-11,
STATICFILES_DIRS
accepts a list of directories. In
this case, all directories are under a Django project's
BASE_DIR
, so it's using the BASE_DIR
variable which dynamically determines the parent directory. Another
aspect of the directory list in listing 5-11 is you can optionally
declare a namespace, similar to the approach used in an app's
static
sub-directories.
The first directory definition in
listing 5-11 is a simple string (i.e. it has no namepsace), which
means the static resources in website-static-default
is set up with a direct access pattern. The remaining directory
definitions are tuples and not strings. By using a tuple, it
defines the first part of the tuple as the namespace and the second
part as the directory with the static resources. Definitions with a
namespace mean that all static resources under a given directory
will use a prefix namespace in their access pattern (e.g. to access
static resources on bootstrap-3.1.1-dist
the access
pattern should be prefixed with bootstrap
).
Now that you know where and how to set up all static resources, lets take a quick a look at how Django visualizes these static resources to understand what the final access patterns for static resources looks like. Listing 5-12 shows a visualization of the static resources presented in the previous listings.
Listing 5-12 Django visualization of static resources in apps and STATICFILES_DIRS
+-favicon.ico +-robots.txt | +-jquery+ | +jquery.min.js | +-jquery-ui+ | +jquery-ui.min.js | +-bootstrap+ | +-bootstrap.css | +-about-+ | +-img-+ | | +-logo.png | | | +-css-+ | +-custom.css | +-stores-+ +-img-+ | +-coffee.gif | +-css-+ +-custom.css
The files
favicon.ico
& robots.txt
in listing
5-12 are in the top level of the visualization because their source
directory -- website-static-default
-- was defined
without a namespace in STATICFILES_DIRS
.
The remainder of the static
resources are all grouped in sub-folders because we either defined
a namespace for them in STATICFILES_DIRS
or defined a
sub-folder as a namespace within an app's static
sub-directory.
Now that you understand how
Django visualizes static resources as a group and how this
determines the final access pattern for static resources, let's
turn our attention to the STATIC_URL
variable in
settings.py
.
The STATIC_URL
is
used to define a URL entry point into Django's visualization of
static resources presented in listing 5-12. By default,
STATIC_URL
is assigned the /static/
value. This means that if STATIC_URL='/static/'
, the
static resource robots.txt
becomes accessible at the
URL /static/robots.txt
, just like
stores/img/coffee.gif
becomes accessible at the URL
/static/stores/img/coffee.gif
.
This means you access static
resources on the /static/
URL, or on a different URL
if you change the STATIC_URL
value. However, don't go
and hard-code these static resources paths on templates!
(e.g.<img
src="/static/stores/img/coffee.gif"/>
). You should use a
variable so the final path is determined dynamically in case
STATIC_URL
changes. The next section describes how to
do this in Django templates.
Caution Automatic access to static resources only works with Django's built-in web server and when DEBUG=True
The previous set up process for
static resources has a little 'behind the scenes' help from Django.
It only works with Django's built-in web server (i.e. python
manage.py runserver
) and only if DEBUG=True
. As
soon as you change to a different web server or switch
DEBUG=False
even using Django's built-in web server,
no static resource will be available as visualized in listing
5-12.
The primary reason behind this
behavior is because reserving and dispatching static resources from
an application's main web server/URL structure
(e.g./static/
) is very inefficient. So this just works
as a convenience in development using Django's built-in web server
and when DEBUG=True
. Of course, you can assign a full
URL domain to STATIC_URL
(e.g.
http://static.coffeehouse.com/) but this assumes you've already set
up the project's static resources on a production-like environment,
something that I'll discuss shortly once I describe how to access
static resources in Django and Jinja templates.
Access static resources in Django templates
The recommended approach to
reference static resources in Django templates is through
staticfiles app via the {% static %}
tag. Listing 5-13
illustrates various examples of the staticfiles app syntax.
Listing 5-13 Django {% static %} tag to reference static resources
{% load static %} # For static resource at about/img/logo.png <img src="{% static 'about/img/logo.gif' %}"> # For static resource at bootstrap/bootstrap.css <link href="{% static 'bootstrap/bootstrap.css' %}" rel="stylesheet"> # For static resource at jquery/jquery.min.js <script src="{% static 'jquery/jquery.min.js' %}"></script>
First it's important to note the
{% load static %}
tag in listing 5-13 is available
through the staticfiles app which is installed by default on all
Django projects in the INSTALLED_APPS
variable. If for
some reason you modified the default values in
INSTALLED_APPS
, make sure you have the
django.contrib.staticfiles
value in the
INSTALLED_APPS
variable or none of what follows will
work.
As you can see in listing 5-13,
at the top of the template you always declare the {% load
static %}
statement. Once this is done, a template can use
the {% static %}
tag to generate dynamic paths for
static resources. In most circumstances, the {% static
%}
tag relies on the STATIC_URL
variable in
settings.py
to generate an appropriate path to the
static resources.
For more advanced cases, the
{% static %}
tag uses a combination of the same
STATIC_URL
variable and the backing storage technology
(e.g. CDN-'Content Delivery Network') configuration to generate an
appropriate path to the static resources.
For example, notice in listing
5-13 how the {% static %}
tag is always followed by a
file path identical to the Django visualization of static resources
in listing 5-12. Due to the STATIC_URL
variable having
a value of /static/
, it means the {% static
%}
statements in listing 5-13 get substituted with this
value (e.g. {% static 'bootstrap/bootstrap.css' %}
becomes /static/bootstrap/bootstrap.css
).
The cases where the {%
static %}
tag gets substituted for something different than
the STATIC_URL
variable are when a Django project uses
a non-standard back-end to serve static resources -- this last
scenario is briefly discussed in the side-bar below.
In the early versions of Django, Django templates used the STATIC_URL variable directly in templates (e.g. <img src="{{STATIC_URL}}about/img/logo.gif">). A trace of this remains in the fact that you can still gain access to the STATIC_URL variable on all Django templates via the django.template.context_processors.static context processor.
However, with the underlying technology to serve static resources becoming more sophisticated, the STATIC_URL variable by itself proves insufficient. For example, static serving technologies like CDNs or Amazon S3 often use special tokens to enforce authentication or caching strategies. This means a statement like <img src="{{STATIC_URL}}about/img/logo.gif"> needs to be converted into something like <img src="http://cdnprovider.com/about/img/logo.gif?token=e354534566"> or <img src="http://staticresources.com/about/img/logo32AzTB9r5.gif">. And while it's possible to change the STATIC_URL variable to a full domain, what becomes difficult is to modify the static resource's path itself.
Re-writing a static resource's path with a tag like {% static %} is easy. Because {% static %} can take a static resource's base string (e.g. about/img/logo.gif) and dynamically produce a full path with the STATIC_URL variable and any special tokens required by the underlying static serving technology. This process is achieved by using a custom storage class -- designed for the static serving technology.
Granted not all projects require the use of advanced static serving technologies. But by using the {% static %} tag of the staticfiles app to declare static resources in Django templates, you ensure a Django project is capable of using any static serving technology, from the most basic to the most advanced.
Access static resources in Jinja templates
Jinja templates offer an
alternative to Django's own templates, as described in the previous
chapter on Jinja templates. But unlike Django templates, you'll have to follow a
different set up to use something like Django's {% static
%}
tag from the staticfiles app in Jinja templates.
To be able to use the same
staticfiles app / {% static %}
tag behavior in Jinja
templates, you'll need to set up a global variable named static
that hooks into this functionality. In the previous chapter on
Jinja template, the section Jinja globals: Set up data for access on all Jinja
templates in Django, like Django context processors describes how
to create a global variable with this functionality.
Set up static resources in a production environment (DEBUG=True)
When you switch your Django
project's DEBUG
variable to True
or
change to a different web server (e.g. Apache, Nginx), you'll be
surprised that none of the static resources in your project appear
anymore. Don't be alarmed, this is by design. It isn't too
difficult to set up Django to serve static resources when
DEBUG=True
with Django's built-in web server or if you
switch to a third party web server.
Tip You can access static resources to make Django's built-in web server serve static resources as if DEBUG=False when it's actually set to DEBUG=True. Run the web server with the --insecure flag: python manage.py runserver --insecure.
Django's built-in web server
(i.e. python manage.py runserver
) is really a
convenience tool to get up and running quickly, which as part of
this convenience also serves static resources when
DEBUG=False
.
However, it really is wasteful to
allow the same web server process to handle both dynamic content
(Django web pages) and static resources (Images, CSS, JavaScript).
The recommended approach is to use a separate web server entirely
to serve static resources, which is why Django goes to the extent
of breaking this convenience in its built-in web server when
switching the DEBUG=True
.
The first thing you need to do
when DEBUG=True
is create a directory to hold a copy
of all the static resources Django visualizes as static resources.
Previously you learned that when DEBUG=False
, Django
visualizes static resources from several locations and
sub-directories in a single tree -- illustrated in listing 5-12.
It's precisely this single tree Django visualizes that you need to
create a copy of to run on a production environment.
You'll need to define the
STATIC_ROOT
variable in settings.py.
The
value you assign to STATIC_ROOT
should be a directory
and it will be where Django copies all of your project's static
resources -- identical to how Django visualizes them when
DEBUG=True
as illustrated in listing 5-12. Note this
directory should be empty, as it's overwritten constantly each time
you perform a syncing process. The location of this directory could
be anywhere on you system depending on your needs. For simplicity,
I'll keep the STATIC_ROOT
directory under the Django
project's BASE_DIR
as STATIC_ROOT =
'%s/coffeestatic/'% (BASE_DIR
).
To trigger the syncing process
(i.e. copy all static resources to STATIC_ROOT
) you'll
need to use the collectstatic
command available in the
manage.py
script. Listing 5-14 illustrates the sample
output of the syncing process.
Listing 5-14 Django collectstatic command to copy all static resources
[user@coffeehouse ~]$ python manage.py collectstatic You have requested to collect static files at the destination location as specified in your settings: /www/STORE/coffeestatic This will overwrite existing files! Are you sure you want to do this? Type 'yes' to continue, or 'no' to cancel: yes yes Copying '/www/STORE/website-static-default/sitemap.xml' Copying '/www/STORE/website-static-default/robots.txt' Copying '/www/STORE/website-static-default/favicon.ico' .... .... .... Copying '/www/STORE/coffeehouse/about/static/css/custom.css' 732 static files copied to '/www/STORE/coffeestatic'.
Once you collect all your
project's static resources in a single folder -- in this case
/www/STORE/coffeestatic
-- they're ready to be set up
on a production server (e.g. Apache, Nginx or AWS S3). Keep in mind
the directory/file structure generated by
collectstatic
is identical to the one visualized by
Django in the previous section illustrated in listing 5-12.
The final step you need to do is
update the STATIC_URL
value in
settings.py
to reflect the new location of the static
resources. For example, if you mount the
/www/STORE/coffeestatic/
directory on Apache or Nginx
under the http://static.coffeehouse.com/
domain, you
would set STATIC_URL='http://static.coffeehouse.com'
.
Similarly, if you copy the static resources in
/www/STORE/coffeestatic/
to an Amazon AWS S3 bucket
named http://coffeehouse.s3.amazonaws.com
, you would
set
STATIC_URL='http://coffeehouse.s3.amazonaws.com'
Once you make this last change,
all the statements in your Django templates that use the {%
static %}
tag get updated with this new full-domain URL. In
which case a resource like
/www/STORE/coffeestatic/bootstrap/bootstrap.css
becomes available at
http://static.coffeehouse.com/bootstrap/bootstrap.css
or
http://coffeehouse.s3.amazonaws.com/bootstrap/bootstrap.css
.