Fixed #12323 and #11582 -- Extended the ability to handle static files. Thanks to all for helping with the original app, the patch, documentation and general support.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14293 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-10-20 01:33:24 +00:00
parent a014ee0288
commit cfc19f84de
54 changed files with 2008 additions and 313 deletions

View File

@ -194,7 +194,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.contrib.staticfiles.context_processors.staticfiles',
# 'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
)
@ -202,11 +202,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
# Output to use in template system for invalid (e.g. misspelled) variables.
TEMPLATE_STRING_IF_INVALID = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Default e-mail address to use for various automated correspondence from
# the site managers.
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
@ -551,3 +546,34 @@ TEST_DATABASE_COLLATION = None
# The list of directories to search for fixtures
FIXTURE_DIRS = ()
###############
# STATICFILES #
###############
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/static/"
STATICFILES_ROOT = ''
# URL that handles the static files served from STATICFILES_ROOT.
# Example: "http://media.lawrence.com/static/"
STATICFILES_URL = '/static/'
# A list of locations of additional static files
STATICFILES_DIRS = ()
# The default file storage backend used during the build process
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'

View File

@ -44,7 +44,7 @@ USE_I18N = True
USE_L10N = True
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
@ -52,10 +52,29 @@ MEDIA_ROOT = ''
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/static/"
STATICFILES_ROOT = ''
# URL that handles the static files served from STATICFILES_ROOT.
# Example: "http://static.lawrence.com/", "http://example.com/static/"
STATICFILES_URL = '/static/'
# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
# A list of locations of additional static files
STATICFILES_DIRS = ()
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = ''
@ -89,6 +108,7 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:

View File

View File

@ -0,0 +1,7 @@
from django.conf import settings
def staticfiles(request):
return {
'STATICFILES_URL': settings.STATICFILES_URL,
'MEDIA_URL': settings.MEDIA_URL,
}

View File

@ -0,0 +1,254 @@
import os
from django.conf import settings
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import default_storage, Storage, FileSystemStorage
from django.utils.datastructures import SortedDict
from django.utils.functional import memoize, LazyObject
from django.utils.importlib import import_module
from django.contrib.staticfiles import utils
from django.contrib.staticfiles.storage import AppStaticStorage
_finders = {}
class BaseFinder(object):
"""
A base file finder to be used for custom staticfiles finder classes.
"""
def find(self, path, all=False):
"""
Given a relative file path this ought to find an
absolute file path.
If the ``all`` parameter is ``False`` (default) only
the first found file path will be returned; if set
to ``True`` a list of all found files paths is returned.
"""
raise NotImplementedError()
def list(self, ignore_patterns=[]):
"""
Given an optional list of paths to ignore, this should return
a three item iterable with path, prefix and a storage instance.
"""
raise NotImplementedError()
class FileSystemFinder(BaseFinder):
"""
A static files finder that uses the ``STATICFILES_DIRS`` setting
to locate files.
"""
storages = SortedDict()
locations = set()
def __init__(self, apps=None, *args, **kwargs):
for root in settings.STATICFILES_DIRS:
if isinstance(root, (list, tuple)):
prefix, root = root
else:
prefix = ''
self.locations.add((prefix, root))
# Don't initialize multiple storages for the same location
for prefix, root in self.locations:
self.storages[root] = FileSystemStorage(location=root)
super(FileSystemFinder, self).__init__(*args, **kwargs)
def find(self, path, all=False):
"""
Looks for files in the extra media locations
as defined in ``STATICFILES_DIRS``.
"""
matches = []
for prefix, root in self.locations:
matched_path = self.find_location(root, path, prefix)
if matched_path:
if not all:
return matched_path
matches.append(matched_path)
return matches
def find_location(self, root, path, prefix=None):
"""
Find a requested static file in a location, returning the found
absolute path (or ``None`` if no match).
"""
if prefix:
prefix = '%s/' % prefix
if not path.startswith(prefix):
return None
path = path[len(prefix):]
path = os.path.join(root, path)
if os.path.exists(path):
return path
def list(self, ignore_patterns):
"""
List all files in all locations.
"""
for prefix, root in self.locations:
storage = self.storages[root]
for path in utils.get_files(storage, ignore_patterns):
yield path, prefix, storage
class AppDirectoriesFinder(BaseFinder):
"""
A static files finder that looks in the ``media`` directory of each app.
"""
storages = {}
storage_class = AppStaticStorage
def __init__(self, apps=None, *args, **kwargs):
if apps is not None:
self.apps = apps
else:
self.apps = models.get_apps()
for app in self.apps:
self.storages[app] = self.storage_class(app)
super(AppDirectoriesFinder, self).__init__(*args, **kwargs)
def list(self, ignore_patterns):
"""
List all files in all app storages.
"""
for storage in self.storages.itervalues():
if storage.exists(''): # check if storage location exists
prefix = storage.get_prefix()
for path in utils.get_files(storage, ignore_patterns):
yield path, prefix, storage
def find(self, path, all=False):
"""
Looks for files in the app directories.
"""
matches = []
for app in self.apps:
app_matches = self.find_in_app(app, path)
if app_matches:
if not all:
return app_matches
matches.append(app_matches)
return matches
def find_in_app(self, app, path):
"""
Find a requested static file in an app's media locations.
"""
storage = self.storages[app]
prefix = storage.get_prefix()
if prefix:
prefix = '%s/' % prefix
if not path.startswith(prefix):
return None
path = path[len(prefix):]
# only try to find a file if the source dir actually exists
if storage.exists(path):
matched_path = storage.path(path)
if matched_path:
return matched_path
class BaseStorageFinder(BaseFinder):
"""
A base static files finder to be used to extended
with an own storage class.
"""
storage = None
def __init__(self, storage=None, *args, **kwargs):
if storage is not None:
self.storage = storage
if self.storage is None:
raise ImproperlyConfigured("The staticfiles storage finder %r "
"doesn't have a storage class "
"assigned." % self.__class__)
# Make sure we have an storage instance here.
if not isinstance(self.storage, (Storage, LazyObject)):
self.storage = self.storage()
super(BaseStorageFinder, self).__init__(*args, **kwargs)
def find(self, path, all=False):
"""
Looks for files in the default file storage, if it's local.
"""
try:
self.storage.path('')
except NotImplementedError:
pass
else:
if self.storage.exists(path):
match = self.storage.path(path)
if all:
match = [match]
return match
return []
def list(self, ignore_patterns):
"""
List all files of the storage.
"""
for path in utils.get_files(self.storage, ignore_patterns):
yield path, '', self.storage
class DefaultStorageFinder(BaseStorageFinder):
"""
A static files finder that uses the default storage backend.
"""
storage = default_storage
def find(path, all=False):
"""
Find a requested static file, first looking in any defined extra media
locations and next in any (non-excluded) installed apps.
If no matches are found and the static location is local, look for a match
there too.
If ``all`` is ``False`` (default), return the first matching
absolute path (or ``None`` if no match). Otherwise return a list of
found absolute paths.
"""
matches = []
for finder in get_finders():
result = finder.find(path, all=all)
if not all and result:
return result
if not isinstance(result, (list, tuple)):
result = [result]
matches.extend(result)
if matches:
return matches
# No match.
return all and [] or None
def get_finders():
for finder_path in settings.STATICFILES_FINDERS:
yield get_finder(finder_path)
def _get_finder(import_path):
"""
Imports the message storage class described by import_path, where
import_path is the full Python path to the class.
"""
module, attr = import_path.rsplit('.', 1)
try:
mod = import_module(module)
except ImportError, e:
raise ImproperlyConfigured('Error importing module %s: "%s"' %
(module, e))
try:
Finder = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a "%s" '
'class.' % (module, attr))
if not issubclass(Finder, BaseFinder):
raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
(Finder, BaseFinder))
return Finder()
get_finder = memoize(_get_finder, _finders, 1)

View File

@ -0,0 +1,72 @@
import os
import urllib
from urlparse import urlparse
from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
from django.http import Http404
from django.contrib.staticfiles.views import serve
class StaticFilesHandler(WSGIHandler):
"""
WSGI middleware that intercepts calls to the static files directory, as
defined by the STATICFILES_URL setting, and serves those files.
"""
def __init__(self, application, media_dir=None):
self.application = application
if media_dir:
self.media_dir = media_dir
else:
self.media_dir = self.get_media_dir()
self.media_url = self.get_media_url()
def get_media_dir(self):
from django.conf import settings
return settings.STATICFILES_ROOT
def get_media_url(self):
from django.conf import settings
return settings.STATICFILES_URL
def file_path(self, url):
"""
Returns the relative path to the media file on disk for the given URL.
The passed URL is assumed to begin with ``media_url``. If the
resultant file path is outside the media directory, then a ValueError
is raised.
"""
# Remove ``media_url``.
relative_url = url[len(self.media_url):]
return urllib.url2pathname(relative_url)
def serve(self, request, path):
from django.contrib.staticfiles import finders
absolute_path = finders.find(path)
if not absolute_path:
raise Http404('%r could not be matched to a static file.' % path)
absolute_path, filename = os.path.split(absolute_path)
return serve(request, path=filename, document_root=absolute_path)
def __call__(self, environ, start_response):
media_url_bits = urlparse(self.media_url)
# Ignore all requests if the host is provided as part of the media_url.
# Also ignore requests that aren't under the media path.
if (media_url_bits[1] or
not environ['PATH_INFO'].startswith(media_url_bits[2])):
return self.application(environ, start_response)
request = self.application.request_class(environ)
try:
response = self.serve(request, self.file_path(environ['PATH_INFO']))
except Http404:
status = '404 NOT FOUND'
start_response(status, {'Content-type': 'text/plain'}.items())
return [str('Page not found: %s' % environ['PATH_INFO'])]
status_text = STATUS_CODE_TEXT[response.status_code]
status = '%s %s' % (response.status_code, status_text)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append(('Set-Cookie', str(c.output(header=''))))
start_response(status, response_headers)
return response

View File

@ -0,0 +1,184 @@
import os
import sys
import shutil
from optparse import make_option
from django.conf import settings
from django.core.files.storage import get_storage_class
from django.core.management.base import CommandError, NoArgsCommand
from django.contrib.staticfiles import finders
class Command(NoArgsCommand):
"""
Command that allows to copy or symlink media files from different
locations to the settings.STATICFILES_ROOT.
"""
option_list = NoArgsCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive',
default=True, help="Do NOT prompt the user for input of any "
"kind."),
make_option('-i', '--ignore', action='append', default=[],
dest='ignore_patterns', metavar='PATTERN',
help="Ignore files or directories matching this glob-style "
"pattern. Use multiple times to ignore more."),
make_option('-n', '--dry-run', action='store_true', dest='dry_run',
default=False, help="Do everything except modify the filesystem."),
make_option('-l', '--link', action='store_true', dest='link',
default=False, help="Create a symbolic link to each file instead of copying."),
make_option('--no-default-ignore', action='store_false',
dest='use_default_ignore_patterns', default=True,
help="Don't ignore the common private glob-style patterns 'CVS', "
"'.*' and '*~'."),
)
help = "Collect static files from apps and other locations in a single location."
def handle_noargs(self, **options):
symlink = options['link']
ignore_patterns = options['ignore_patterns']
if options['use_default_ignore_patterns']:
ignore_patterns += ['CVS', '.*', '*~']
ignore_patterns = list(set(ignore_patterns))
self.copied_files = []
self.symlinked_files = []
self.unmodified_files = []
self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
try:
self.destination_storage.path('')
except NotImplementedError:
self.destination_local = False
else:
self.destination_local = True
if symlink:
if sys.platform == 'win32':
raise CommandError("Symlinking is not supported by this "
"platform (%s)." % sys.platform)
if not self.destination_local:
raise CommandError("Can't symlink to a remote destination.")
# Warn before doing anything more.
if options.get('interactive'):
confirm = raw_input("""
You have requested to collate static files and collect them at the destination
location as specified in your settings file.
This will overwrite existing files.
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: """)
if confirm != 'yes':
raise CommandError("Static files build cancelled.")
for finder in finders.get_finders():
for source, prefix, storage in finder.list(ignore_patterns):
self.copy_file(source, prefix, storage, **options)
verbosity = int(options.get('verbosity', 1))
actual_count = len(self.copied_files) + len(self.symlinked_files)
unmodified_count = len(self.unmodified_files)
if verbosity >= 1:
self.stdout.write("\n%s static file%s %s to '%s'%s.\n"
% (actual_count, actual_count != 1 and 's' or '',
symlink and 'symlinked' or 'copied',
settings.STATICFILES_ROOT,
unmodified_count and ' (%s unmodified)'
% unmodified_count or ''))
def copy_file(self, source, prefix, source_storage, **options):
"""
Attempt to copy (or symlink) ``source`` to ``destination``,
returning True if successful.
"""
source_path = source_storage.path(source)
try:
source_last_modified = source_storage.modified_time(source)
except (OSError, NotImplementedError):
source_last_modified = None
if prefix:
destination = '/'.join([prefix, source])
else:
destination = source
symlink = options['link']
dry_run = options['dry_run']
verbosity = int(options.get('verbosity', 1))
if destination in self.copied_files:
if verbosity >= 2:
self.stdout.write("Skipping '%s' (already copied earlier)\n"
% destination)
return False
if destination in self.symlinked_files:
if verbosity >= 2:
self.stdout.write("Skipping '%s' (already linked earlier)\n"
% destination)
return False
if self.destination_storage.exists(destination):
try:
destination_last_modified = \
self.destination_storage.modified_time(destination)
except (OSError, NotImplementedError):
# storage doesn't support ``modified_time`` or failed.
pass
else:
destination_is_link= os.path.islink(
self.destination_storage.path(destination))
if destination_last_modified == source_last_modified:
if (not symlink and not destination_is_link):
if verbosity >= 2:
self.stdout.write("Skipping '%s' (not modified)\n"
% destination)
self.unmodified_files.append(destination)
return False
if dry_run:
if verbosity >= 2:
self.stdout.write("Pretending to delete '%s'\n"
% destination)
else:
if verbosity >= 2:
self.stdout.write("Deleting '%s'\n" % destination)
self.destination_storage.delete(destination)
if symlink:
destination_path = self.destination_storage.path(destination)
if dry_run:
if verbosity >= 1:
self.stdout.write("Pretending to symlink '%s' to '%s'\n"
% (source_path, destination_path))
else:
if verbosity >= 1:
self.stdout.write("Symlinking '%s' to '%s'\n"
% (source_path, destination_path))
try:
os.makedirs(os.path.dirname(destination_path))
except OSError:
pass
os.symlink(source_path, destination_path)
self.symlinked_files.append(destination)
else:
if dry_run:
if verbosity >= 1:
self.stdout.write("Pretending to copy '%s' to '%s'\n"
% (source_path, destination))
else:
if self.destination_local:
destination_path = self.destination_storage.path(destination)
try:
os.makedirs(os.path.dirname(destination_path))
except OSError:
pass
shutil.copy2(source_path, destination_path)
if verbosity >= 1:
self.stdout.write("Copying '%s' to '%s'\n"
% (source_path, destination_path))
else:
source_file = source_storage.open(source)
self.destination_storage.save(destination, source_file)
if verbosity >= 1:
self.stdout.write("Copying %s to %s\n"
% (source_path, destination))
self.copied_files.append(destination)
return True

View File

@ -0,0 +1,24 @@
import os
from optparse import make_option
from django.core.management.base import LabelCommand
from django.contrib.staticfiles import finders
class Command(LabelCommand):
help = "Finds the absolute paths for the given static file(s)."
args = "[file ...]"
label = 'static file'
option_list = LabelCommand.option_list + (
make_option('--first', action='store_false', dest='all', default=True,
help="Only return the first match for each static file."),
)
def handle_label(self, path, **options):
verbosity = int(options.get('verbosity', 1))
result = finders.find(path, all=options['all'])
if result:
output = '\n '.join((os.path.realpath(path) for path in result))
self.stdout.write("Found %r here:\n %s\n" % (path, output))
else:
if verbosity >= 1:
self.stdout.write("No matching file found for %r.\n" % path)

View File

View File

@ -0,0 +1,84 @@
import os
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import FileSystemStorage
from django.utils.importlib import import_module
from django.contrib.staticfiles import utils
class StaticFilesStorage(FileSystemStorage):
"""
Standard file system storage for site media files.
The defaults for ``location`` and ``base_url`` are
``STATICFILES_ROOT`` and ``STATICFILES_URL``.
"""
def __init__(self, location=None, base_url=None, *args, **kwargs):
if location is None:
location = settings.STATICFILES_ROOT
if base_url is None:
base_url = settings.STATICFILES_URL
if not location:
raise ImproperlyConfigured("You're using the staticfiles app "
"without having set the STATICFILES_ROOT setting. Set it to "
"the absolute path of the directory that holds static media.")
if not base_url:
raise ImproperlyConfigured("You're using the staticfiles app "
"without having set the STATICFILES_URL setting. Set it to "
"URL that handles the files served from STATICFILES_ROOT.")
super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
class AppStaticStorage(FileSystemStorage):
"""
A file system storage backend that takes an app module and works
for the ``static`` directory of it.
"""
source_dir = 'static'
def __init__(self, app, *args, **kwargs):
"""
Returns a static file storage if available in the given app.
"""
# app is actually the models module of the app. Remove the '.models'.
bits = app.__name__.split('.')[:-1]
self.app_name = bits[-1]
self.app_module = '.'.join(bits)
# The models module (app) may be a package in which case
# dirname(app.__file__) would be wrong. Import the actual app
# as opposed to the models module.
app = import_module(self.app_module)
location = self.get_location(os.path.dirname(app.__file__))
super(AppStaticStorage, self).__init__(location, *args, **kwargs)
def get_location(self, app_root):
"""
Given the app root, return the location of the static files of an app,
by default 'static'. We special case the admin app here since it has
its static files in 'media'.
"""
if self.app_module == 'django.contrib.admin':
return os.path.join(app_root, 'media')
return os.path.join(app_root, self.source_dir)
def get_prefix(self):
"""
Return the path name that should be prepended to files for this app.
"""
if self.app_module == 'django.contrib.admin':
return self.app_name
return None
def get_files(self, ignore_patterns=[]):
"""
Return a list containing the relative source paths for all files that
should be copied for an app.
"""
files = []
prefix = self.get_prefix()
for path in utils.get_files(self, ignore_patterns):
if prefix:
path = '/'.join([prefix, path])
files.append(path)
return files

View File

@ -0,0 +1,43 @@
from django import template
from django.utils.encoding import iri_to_uri
register = template.Library()
class StaticFilesPrefixNode(template.Node):
def __init__(self, varname=None):
self.varname = varname
def render(self, context):
try:
from django.conf import settings
except ImportError:
prefix = ''
else:
prefix = iri_to_uri(settings.STATICFILES_URL)
if self.varname is None:
return prefix
context[self.varname] = prefix
return ''
@register.tag
def get_staticfiles_prefix(parser, token):
"""
Populates a template variable with the prefix (settings.STATICFILES_URL).
Usage::
{% get_staticfiles_prefix [as varname] %}
Examples::
{% get_staticfiles_prefix %}
{% get_staticfiles_prefix as staticfiles_prefix %}
"""
tokens = token.contents.split()
if len(tokens) > 1 and tokens[1] != 'as':
raise template.TemplateSyntaxError(
"First argument in '%s' must be 'as'" % tokens[0])
return StaticFilesPrefixNode(varname=(len(tokens) > 1 and tokens[2] or None))

View File

@ -0,0 +1,29 @@
import re
from django.conf import settings
from django.conf.urls.defaults import patterns, url, include
from django.core.exceptions import ImproperlyConfigured
urlpatterns = []
# only serve non-fqdn URLs
if not settings.DEBUG:
urlpatterns += patterns('',
url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
)
def staticfiles_urlpatterns(prefix=None):
"""
Helper function to return a URL pattern for serving static files.
"""
if settings.DEBUG:
return []
if prefix is None:
prefix = settings.STATICFILES_URL
if '://' in prefix:
raise ImproperlyConfigured(
"The STATICFILES_URL setting is a full URL, not a path and "
"can't be used with the urls.staticfiles_urlpatterns() helper.")
if prefix.startswith("/"):
prefix = prefix[1:]
return patterns('',
url(r'^%s' % re.escape(prefix), include(urlpatterns)),)

View File

@ -0,0 +1,30 @@
import fnmatch
def get_files(storage, ignore_patterns=[], location=''):
"""
Recursively walk the storage directories gathering a complete list of files
that should be copied, returning this list.
"""
def is_ignored(path):
"""
Return True or False depending on whether the ``path`` should be
ignored (if it matches any pattern in ``ignore_patterns``).
"""
for pattern in ignore_patterns:
if fnmatch.fnmatchcase(path, pattern):
return True
return False
directories, files = storage.listdir(location)
static_files = [location and '/'.join([location, fn]) or fn
for fn in files
if not is_ignored(fn)]
for dir in directories:
if is_ignored(dir):
continue
if location:
dir = '/'.join([location, dir])
static_files.extend(get_files(storage, ignore_patterns, dir))
return static_files

View File

@ -0,0 +1,159 @@
"""
Views and functions for serving static files. These are only to be used during
development, and SHOULD NOT be used in a production setting.
"""
import mimetypes
import os
import posixpath
import re
import stat
import urllib
from email.Utils import parsedate_tz, mktime_tz
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
from django.template import loader, Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
from django.contrib.staticfiles import finders
def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure or
from locations inferred from the static files finders.
To use, put a URL pattern such as::
(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve')
in your URLconf.
If you provide the ``document_root`` parameter, the file won't be looked
up with the staticfiles finders, but in the given filesystem path, e.g.::
(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve', {'document_root' : '/path/to/my/files/'})
You may also set ``show_indexes`` to ``True`` if you'd like to serve a
basic index of the directory. This index view will use the
template hardcoded below, but if you'd like to override it, you can create
a template called ``static/directory_index.html``.
"""
if settings.DEBUG:
raise ImproperlyConfigured("The view to serve static files can only "
"be used if the DEBUG setting is True")
if not document_root:
absolute_path = finders.find(path)
if not absolute_path:
raise Http404("%r could not be matched to a static file." % path)
document_root, path = os.path.split(absolute_path)
# Clean up given path to only allow serving files below document_root.
path = posixpath.normpath(urllib.unquote(path))
path = path.lstrip('/')
newpath = ''
for part in path.split('/'):
if not part:
# Strip empty path components.
continue
drive, part = os.path.splitdrive(part)
head, part = os.path.split(part)
if part in (os.curdir, os.pardir):
# Strip '.' and '..' in path.
continue
newpath = os.path.join(newpath, part).replace('\\', '/')
if newpath and path != newpath:
return HttpResponseRedirect(newpath)
fullpath = os.path.join(document_root, newpath)
if os.path.isdir(fullpath):
if show_indexes:
return directory_index(newpath, fullpath)
raise Http404("Directory indexes are not allowed here.")
if not os.path.exists(fullpath):
raise Http404('"%s" does not exist' % fullpath)
# Respect the If-Modified-Since header.
statobj = os.stat(fullpath)
mimetype, encoding = mimetypes.guess_type(fullpath)
mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
return HttpResponseNotModified(mimetype=mimetype)
contents = open(fullpath, 'rb').read()
response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
response["Content-Length"] = len(contents)
if encoding:
response["Content-Encoding"] = encoding
return response
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<meta name="robots" content="NONE,NOARCHIVE" />
<title>Index of {{ directory }}</title>
</head>
<body>
<h1>Index of {{ directory }}</h1>
<ul>
{% ifnotequal directory "/" %}
<li><a href="../">../</a></li>
{% endifnotequal %}
{% for f in file_list %}
<li><a href="{{ f|urlencode }}">{{ f }}</a></li>
{% endfor %}
</ul>
</body>
</html>
"""
def directory_index(path, fullpath):
try:
t = loader.select_template(['static/directory_index.html',
'static/directory_index'])
except TemplateDoesNotExist:
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
files = []
for f in os.listdir(fullpath):
if not f.startswith('.'):
if os.path.isdir(os.path.join(fullpath, f)):
f += '/'
files.append(f)
c = Context({
'directory' : path + '/',
'file_list' : files,
})
return HttpResponse(t.render(c))
def was_modified_since(header=None, mtime=0, size=0):
"""
Was something modified since the user last downloaded it?
header
This is the value of the If-Modified-Since header. If this is None,
I'll just return True.
mtime
This is the modification time of the item we're talking about.
size
This is the size of the item we're talking about.
"""
try:
if header is None:
raise ValueError
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
re.IGNORECASE)
header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
header_len = matches.group(3)
if header_len and int(header_len) != size:
raise ValueError
if mtime > header_mtime:
raise ValueError
except (AttributeError, ValueError, OverflowError):
return True
return False

View File

@ -71,7 +71,15 @@ def media(request):
Adds media-related context variables to the context.
"""
return {'MEDIA_URL': settings.MEDIA_URL}
import warnings
warnings.warn(
"The context processor at `django.core.context_processors.media` is " \
"deprecated; use the path `django.contrib.staticfiles.context_processors.staticfiles` " \
"instead.",
PendingDeprecationWarning
)
from django.contrib.staticfiles.context_processors import staticfiles as staticfiles_context_processor
return staticfiles_context_processor(request)
def request(request):
return {'request': request}

View File

@ -1,7 +1,9 @@
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
import os
import sys
import warnings
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
@ -20,6 +22,7 @@ class Command(BaseCommand):
import django
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
from django.core.handlers.wsgi import WSGIHandler
from django.contrib.staticfiles.handlers import StaticFilesHandler
if args:
raise CommandError('Usage is runserver %s' % self.args)
if not addrport:
@ -56,7 +59,10 @@ class Command(BaseCommand):
translation.activate(settings.LANGUAGE_CODE)
try:
handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
handler = WSGIHandler()
handler = StaticFilesHandler(handler)
# serve admin media like old-school (deprecation pending)
handler = AdminMediaHandler(handler, admin_media_path)
run(addr, int(port), handler)
except WSGIServerException, e:
# Use helpful error messages instead of ugly tracebacks.

View File

@ -8,16 +8,17 @@ been reviewed for security issues. Don't use it for production use.
"""
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import mimetypes
import os
import re
import stat
import sys
import urllib
import warnings
from django.core.management.color import color_style
from django.utils.http import http_date
from django.utils._os import safe_join
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.views import static
__version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler']
@ -633,86 +634,46 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
sys.stderr.write(msg)
class AdminMediaHandler(object):
class AdminMediaHandler(StaticFilesHandler):
"""
WSGI middleware that intercepts calls to the admin media directory, as
defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
Use this ONLY LOCALLY, for development! This hasn't been tested for
security and is not super efficient.
"""
def __init__(self, application, media_dir=None):
def get_media_dir(self):
import django
return os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
def get_media_url(self):
from django.conf import settings
self.application = application
if not media_dir:
import django
self.media_dir = \
os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
else:
self.media_dir = media_dir
self.media_url = settings.ADMIN_MEDIA_PREFIX
return settings.ADMIN_MEDIA_PREFIX
def __init__(self, application, media_dir=None):
warnings.warn('The AdminMediaHandler handler is deprecated; use the '
'`django.contrib.staticfiles.handlers.StaticFilesHandler` instead.',
PendingDeprecationWarning)
super(AdminMediaHandler, self).__init__(application, media_dir)
def file_path(self, url):
"""
Returns the path to the media file on disk for the given URL.
The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX. If the
The passed URL is assumed to begin with ``media_url``. If the
resultant file path is outside the media directory, then a ValueError
is raised.
"""
# Remove ADMIN_MEDIA_PREFIX.
# Remove ``media_url``.
relative_url = url[len(self.media_url):]
relative_path = urllib.url2pathname(relative_url)
return safe_join(self.media_dir, relative_path)
def __call__(self, environ, start_response):
import os.path
def serve(self, request, path):
document_root, path = os.path.split(path)
return static.serve(request, path, document_root=document_root)
# Ignore requests that aren't under ADMIN_MEDIA_PREFIX. Also ignore
# all requests if ADMIN_MEDIA_PREFIX isn't a relative URL.
if self.media_url.startswith('http://') or self.media_url.startswith('https://') \
or not environ['PATH_INFO'].startswith(self.media_url):
return self.application(environ, start_response)
# Find the admin file and serve it up, if it exists and is readable.
try:
file_path = self.file_path(environ['PATH_INFO'])
except ValueError: # Resulting file path was not valid.
status = '404 NOT FOUND'
headers = {'Content-type': 'text/plain'}
output = ['Page not found: %s' % environ['PATH_INFO']]
start_response(status, headers.items())
return output
if not os.path.exists(file_path):
status = '404 NOT FOUND'
headers = {'Content-type': 'text/plain'}
output = ['Page not found: %s' % environ['PATH_INFO']]
else:
try:
fp = open(file_path, 'rb')
except IOError:
status = '401 UNAUTHORIZED'
headers = {'Content-type': 'text/plain'}
output = ['Permission denied: %s' % environ['PATH_INFO']]
else:
# This is a very simple implementation of conditional GET with
# the Last-Modified header. It makes media files a bit speedier
# because the files are only read off disk for the first
# request (assuming the browser/client supports conditional
# GET).
mtime = http_date(os.stat(file_path)[stat.ST_MTIME])
headers = {'Last-Modified': mtime}
if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
status = '304 NOT MODIFIED'
output = []
else:
status = '200 OK'
mime_type = mimetypes.guess_type(file_path)[0]
if mime_type:
headers['Content-Type'] = mime_type
output = [fp.read()]
fp.close()
start_response(status, headers.items())
return output
def run(addr, port, wsgi_handler):
server_address = (addr, port)

View File

@ -9,6 +9,7 @@ import posixpath
import re
import stat
import urllib
import warnings
from email.Utils import parsedate_tz, mktime_tz
from django.template import loader
@ -16,6 +17,10 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpRespons
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
from django.contrib.staticfiles.views import \
directory_index, was_modified_since, serve as staticfiles_serve
def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.
@ -30,111 +35,7 @@ def serve(request, path, document_root=None, show_indexes=False):
but if you'd like to override it, you can create a template called
``static/directory_index.html``.
"""
# Clean up given path to only allow serving files below document_root.
path = posixpath.normpath(urllib.unquote(path))
path = path.lstrip('/')
newpath = ''
for part in path.split('/'):
if not part:
# Strip empty path components.
continue
drive, part = os.path.splitdrive(part)
head, part = os.path.split(part)
if part in (os.curdir, os.pardir):
# Strip '.' and '..' in path.
continue
newpath = os.path.join(newpath, part).replace('\\', '/')
if newpath and path != newpath:
return HttpResponseRedirect(newpath)
fullpath = os.path.join(document_root, newpath)
if os.path.isdir(fullpath):
if show_indexes:
return directory_index(newpath, fullpath)
raise Http404("Directory indexes are not allowed here.")
if not os.path.exists(fullpath):
raise Http404('"%s" does not exist' % fullpath)
# Respect the If-Modified-Since header.
statobj = os.stat(fullpath)
mimetype, encoding = mimetypes.guess_type(fullpath)
mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
return HttpResponseNotModified(mimetype=mimetype)
contents = open(fullpath, 'rb').read()
response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
response["Content-Length"] = len(contents)
if encoding:
response["Content-Encoding"] = encoding
return response
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<meta name="robots" content="NONE,NOARCHIVE" />
<title>Index of {{ directory }}</title>
</head>
<body>
<h1>Index of {{ directory }}</h1>
<ul>
{% ifnotequal directory "/" %}
<li><a href="../">../</a></li>
{% endifnotequal %}
{% for f in file_list %}
<li><a href="{{ f|urlencode }}">{{ f }}</a></li>
{% endfor %}
</ul>
</body>
</html>
"""
def directory_index(path, fullpath):
try:
t = loader.select_template(['static/directory_index.html',
'static/directory_index'])
except TemplateDoesNotExist:
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
files = []
for f in os.listdir(fullpath):
if not f.startswith('.'):
if os.path.isdir(os.path.join(fullpath, f)):
f += '/'
files.append(f)
c = Context({
'directory' : path + '/',
'file_list' : files,
})
return HttpResponse(t.render(c))
def was_modified_since(header=None, mtime=0, size=0):
"""
Was something modified since the user last downloaded it?
header
This is the value of the If-Modified-Since header. If this is None,
I'll just return True.
mtime
This is the modification time of the item we're talking about.
size
This is the size of the item we're talking about.
"""
try:
if header is None:
raise ValueError
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
re.IGNORECASE)
header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
header_len = matches.group(3)
if header_len and int(header_len) != size:
raise ValueError
if mtime > header_mtime:
raise ValueError
except (AttributeError, ValueError, OverflowError):
return True
return False
warnings.warn("The view at `django.views.static.serve` is deprecated; "
"use the path `django.contrib.staticfiles.views.serve` "
"instead.", PendingDeprecationWarning)
return staticfiles_serve(request, path, document_root, show_indexes)

View File

@ -1,162 +1,399 @@
=========================
How to serve static files
=========================
=====================
Managing static files
=====================
.. module:: django.views.static
:synopsis: Serving of static files during development.
.. currentmodule:: django.contrib.staticfiles
Django itself doesn't serve static (media) files, such as images, style sheets,
or video. It leaves that job to whichever Web server you choose.
.. versionadded:: 1.3
The reasoning here is that standard Web servers, such as Apache_, lighttpd_ and
Cherokee_, are much more fine-tuned at serving static files than a Web
application framework.
Django developers mostly concern themselves with the dynamic parts of web
applications -- the views and templates that render anew for each request. But
web applications have other parts: the static media files (images, CSS,
Javascript, etc.) that are needed to render a complete web page.
With that said, Django does support static files **during development**. You can
use the :func:`django.views.static.serve` view to serve media files.
For small projects, this isn't a big deal, because you can just keep the media
somewhere your web server can find it. However, in bigger projects -- especially
those comprised of multiple apps -- dealing with the multiple sets of static
files provided by each application starts to get tricky.
.. _Apache: http://httpd.apache.org/
.. _lighttpd: http://www.lighttpd.net/
.. _Cherokee: http://www.cherokee-project.com/
That's what ``django.contrib.staticfiles`` is for: it collects media from each
of your applications (and any other places you specify) into a single location
that can easily be served in production.
.. seealso::
.. note::
If you just need to serve the admin media from a nonstandard location, see
the :djadminopt:`--adminmedia` parameter to :djadmin:`runserver`.
If you've used the `django-staticfiles`_ third-party app before, then
``django.contrib.staticfiles`` will look very familiar. That's because
they're essentially the same code: ``django.contrib.staticfiles`` started
its life as `django-staticfiles`_ and was merged into Django 1.3.
The big, fat disclaimer
=======================
If you're upgrading from ``django-staticfiles``, please see `Upgrading from
django-staticfiles`_, below, for a few minor changes you'll need to make.
Using this method is **inefficient** and **insecure**. Do not use this in a
production setting. Use this only for development.
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/
For information on serving static files in an Apache production environment,
see the :ref:`Django mod_wsgi documentation <serving-media-files>`.
Using ``django.contrib.staticfiles``
====================================
How to do it
============
Here's the basic usage in a nutshell:
Here's the formal definition of the :func:`~django.views.static.serve` view:
1. Put your media somewhere that staticfiles will find it..
.. function:: def serve(request, path, document_root, show_indexes=False)
Most of the time this place will be in a ``static`` directory within your
application, but it could also be a specific directory you've put into
your settings file. See the the documentation for the
:setting:`STATICFILES_DIRS` and :setting:`STATICFILES_FINDERS` settings
for details on where you can put media.
To use it, just put this in your :doc:`URLconf </topics/http/urls>`::
2. Add some ``staticfiles``-related settings to your settings file.
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': '/path/to/media'}),
First, you'll need to make sure that ``django.contrib.staticfiles`` is in
your :setting:`INSTALLED_APPS`.
...where ``site_media`` is the URL where your media will be rooted, and
``/path/to/media`` is the filesystem root for your media. This will call the
:func:`~django.views.static.serve` view, passing in the path from the URLconf
and the (required) ``document_root`` parameter.
Next, you'll need to edit :setting:`STATICFILES_ROOT` to point to where
you'd like your static media stored. For example::
Given the above URLconf:
STATICFILES_ROOT = "/home/jacob/projects/mysite.com/static_media"
* The file ``/path/to/media/foo.jpg`` will be made available at the URL
``/site_media/foo.jpg``.
You may also want to set the :setting:`STATICFILES_URL` setting at this
time, though the default value (of ``/static/``) is perfect for local
development.
* The file ``/path/to/media/css/mystyles.css`` will be made available
at the URL ``/site_media/css/mystyles.css``.
There are a number of other options available that let you control *how*
media is stored, where ``staticfiles`` searches for files, and how files
will be served; see :ref:`the staticfiles settings reference
<staticfiles-settings>` for details.
* The file ``/path/bar.jpg`` will not be accessible, because it doesn't
fall under the document root.
3. Run the :djadmin:`collectstatic` management command::
Of course, it's not compulsory to use a fixed string for the
``'document_root'`` value. You might wish to make that an entry in your
settings file and use the setting value there. That will allow you and
other developers working on the code to easily change the value as
required. For example, if we have a line in ``settings.py`` that says::
./manage.py collectstatic
STATIC_DOC_ROOT = '/path/to/media'
This'll churn through your static file storage and move them into the
directory given by :setting:`STATICFILES_ROOT`.
...we could write the above :doc:`URLconf </topics/http/urls>` entry as::
4. Deploy that media.
from django.conf import settings
...
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.STATIC_DOC_ROOT}),
If you're using the built-in development server, you can quickly
serve static media locally by adding::
Be careful not to use the same path as your :setting:`ADMIN_MEDIA_PREFIX` (which defaults
to ``/media/``) as this will overwrite your URLconf entry.
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
Directory listings
==================
to the bottom of your URLconf. See :ref:`staticfiles-development` for
details.
Optionally, you can pass the ``show_indexes`` parameter to the
:func:`~django.views.static.serve` view. This is ``False`` by default. If it's
``True``, Django will display file listings for directories.
When it comes time to deploy to production, :ref:`staticfiles-production`
covers some common deployment strategies for static files.
For example::
However you choose to deploy those files, you'll probably need to refer
to them in your templates. The easiest method is to use the included
context processor which will allow template code like:
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': '/path/to/media', 'show_indexes': True}),
.. code-block:: html+django
You can customize the index view by creating a template called
``static/directory_index.html``. That template gets two objects in its context:
<img src="{{ STATICFILES_URL }}images/hi.jpg />
* ``directory`` -- the directory name (a string)
* ``file_list`` -- a list of file names (as strings) in the directory
See :ref:`staticfiles-in-templates` for more details, including an
alternate method (using a template tag).
Here's the default ``static/directory_index.html`` template:
Those are the basics. For more details on common configuration options, read on;
for a detailed reference of the settings, commands, and other bits included with
the framework see :doc:`the staticfiles reference </ref/contrib/staticfiles>`.
.. _staticfiles-in-templates:
Referring to static files in templates
======================================
At some point, you'll probably need to link to static files in your templates.
You could, of course, simply hardcode the path to you assets in the templates:
.. code-block:: html
<img src="http://media.example.com/static/myimage.jpg" />
Of course, there are some serious problems with this: it doesn't work well in
development, and it makes it *very* hard to change where you've deployed your
media. If, for example, you wanted to switch to using a content delivery network
(CDN), then you'd need to change more or less every single template.
A far better way is to use the value of the :setting:`STATICFILES_URL` setting
directly in your templates. This means that a switch of media servers only
requires changing that single value. Much better!
``staticfiles`` inludes two built-in ways of getting at this setting in your
templates: a context processor and a template tag.
With a context processor
------------------------
The included context processor is the easy way. Simply make sure
``'django.contrib.staticfiles.context_processors.staticfiles'`` is in your
:setting:`TEMPLATE_CONTEXT_PROCESSORS`. It's there by default, and if you're
editing that setting by hand it should look something like::
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.contrib.staticfiles.context_processors.staticfiles',
)
Once that's done, you can refer to :setting:`STATICFILES_URL` in your templates:
.. code-block:: html+django
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<meta name="robots" content="NONE,NOARCHIVE" />
<title>Index of {{ directory }}</title>
</head>
<body>
<h1>Index of {{ directory }}</h1>
<ul>
{% for f in file_list %}
<li><a href="{{ f }}">{{ f }}</a></li>
{% endfor %}
</ul>
</body>
</html>
<img src="{{ STATICFILES_URL }}images/hi.jpg />
.. versionchanged:: 1.0.3
Prior to Django 1.0.3, there was a bug in the view that provided directory
listings. The template that was loaded had to be called
``static/directory_listing`` (with no ``.html`` extension). For backwards
compatibility with earlier versions, Django will still load templates with
the older (no extension) name, but it will prefer the
``directory_index.html`` version.
If ``{{ STATICFILES_URL }}`` isn't working in your template, you're probably not
using :class:`~django.template.RequestContext` when rendering the template.
Limiting use to DEBUG=True
==========================
As a brief refresher, context processors add variables into the contexts of
every template. However, context processors require that you use
:class:`~django.template.RequestContext` when rendering templates. This happens
automatically if you're using a :doc:`generic view </ref/class-based-views>`,
but in views written by hand you'll need to explicitally use ``RequestContext``
To see how that works, and to read more details, check out
:ref:`subclassing-context-requestcontext`.
Because URLconfs are just plain Python modules, you can use Python logic to
make the static-media view available only in development mode. This is a handy
trick to make sure the static-serving view doesn't slip into a production
setting by mistake.
With a template tag
-------------------
Do this by wrapping an ``if DEBUG`` statement around the
:func:`django.views.static.serve` inclusion. Here's a full example URLconf::
The second option is the :ttag:`get_staticfiles_prefix` template tag. You can
use this if you're not using :class:`~django.template.RequestContext`, or if you
need more control over exactly where and how :setting:`STATICFILES_URL` is
injected into the template. Here's an example:
from django.conf.urls.defaults import *
from django.conf import settings
.. code-block:: html+django
urlpatterns = patterns('',
(r'^articles/2003/$', 'news.views.special_case_2003'),
(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive'),
(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'news.views.article_detail'),
)
{% load staticfiles %}
<img src="{% get_staticfiles_prefix %}images/hi.jpg" />
if settings.DEBUG:
urlpatterns += patterns('',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}),
There's also a second form you can use to avoid extra processing if you need the
value multiple times:
.. code-block:: html+django
{% load staticfiles %}
{% get_staticfiles_prefix as STATIC_PREFIX %}
<img src="{{ STATIC_PREFIX }}images/hi.jpg" />
<img src="{{ STATIC_PREFIX }}images/hi2.jpg" />
.. _staticfiles-development:
Serving static files in development
===================================
The static files tools are mostly designed to help with getting static media
successfully deployed into production. This usually means a separate, dedicated
media server, which is a lot of overhead to mess with when developing locally.
Thus, the ``staticfiles`` app ships with a quick and dirty helper view that you
can use to serve media locally in development.
To enable this view, you'll add a couple of lines to your URLconf. The first
line goes at the top of the file, and the last line at the bottom::
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# ... the rest of your URLconf goes here ...
urlpatterns += staticfiles_urlpatterns()
This will inspect your :setting:`STATICFILES_URL` and
:setting:`STATICFILES_ROOT` settings and wire up the view to serve static media
accordingly. Remember to run :djadmin:`collectstatic` when your media changes;
the view only serves static files that have been collected.
.. warning::
This will only work if :setting:`DEBUG` is ``True``.
That's because this view is **grossly inefficient** and probably
**insecure**. This is only intended for local development, and should
**never be used in production**.
For a few more details, including an alternate method of enabling this view,
see :ref:`staticfiles-development-view`.
.. _staticfiles-production:
Serving static files in production
==================================
The basic outline of putting static files into production a simple: un the
:djadmin:`collectstatic` command when static media changes, then arrange for the
collected media directory (:setting:`STATICFILES_ROOT`) to be moved to the media
server and served.
Of course, as with all deployment tasks, the devil's in the details. Every
production setup will be a bit different, so you'll need to adapt the basic
outline to fit your needs. Below are a few common patterns that might help.
Serving the app and your static files from the same server
----------------------------------------------------------
If you want to serve your media from the same server that's already serving your
app, the basic outline gets modified to look something like:
* Push your code up to the deployment server.
* On the server, run :djadmin:`collectmedia` to move all the media into
:setting:`STATICFILES_ROOT`.
* Point your web server at :setting:`STATICFILES_ROOT`. For example, here's
of :ref:`how to do this under Apache and mod_wsgi <serving-media-files>`.
You'll probably want to automate this process, especially if you've got multiple
web servers. There's any number of ways to do this automation, but one option
that many Django developers enjoy is `Fabric`__.
__ http://fabfile.org/
Below, and in the following sections, we'll show off a few example fabfiles
(i.e. Fabric scripts) that automate these media deployment options. The syntax
of a fabfile is fairly streightforward but won't be covered here; consult `Fabric's documentation`__, for a complete explanation of the syntax..
__ http://docs.fabfile.org/
So, a fabfile to deploy media to a couple of web servers might look something
like::
from fabric.api import *
# Hosts to deploy onto
env.hosts = ['www1.example.com', 'www2.example.com']
# Where your project code lives on the server
env.project_root = '/home/www/myproject'
def deploy_static():
with cd(env.project_root):
run('./manage.py collectstatic')
Serving static files from a dedicated media server
--------------------------------------------------
Most larger Django apps use a separate Web server -- i.e., one that's not also
running Django -- for serving media. This server often runs a different type of
web server -- faster but less full-featured. Some good choices are:
* lighttpd_
* Nginx_
* TUX_
* Cherokee_
* A stripped-down version of Apache_
.. _lighttpd: http://www.lighttpd.net/
.. _Nginx: http://wiki.nginx.org/Main
.. _TUX: http://en.wikipedia.org/wiki/TUX_web_server
.. _Apache: http://httpd.apache.org/
.. _Cherokee: http://www.cherokee-project.com/
Configuring these servers is out of scope of this document; check each server's
respective documentation for instructions.
Since your media server won't be running Django, you'll need to modify the
deployment strategy to look something like:
* When your media changes, run :djadmin:`collectstatic` locally.
* Push your local :setting:`STATICFILES_ROOT` up to the media server
into the directory that's being served. ``rsync`` is a good
choice for this step since it only needs to transfer the
bits of static media that have changed.
Here's how this might look in a fabfile::
from fabric.api import *
from fabric.contrib import project
# Where the static files get collected locally
env.local_static_root = '/tmp/static'
# Where the static files should go remotely
env.remote_static_root = '/home/www/media.example.com'
@roles('media')
def deploy_static():
local('./manage.py collectstatic')
project.rysnc_project(
remote_dir = env.remote_static_root,
local_dir = env.local_static_root,
delete = True
)
This code is straightforward. It imports the settings and checks the value of
the :setting:`DEBUG` setting. If it evaluates to ``True``, then ``site_media``
will be associated with the ``django.views.static.serve`` view. If not, then the
view won't be made available.
.. _staticfiles-from-cdn:
Of course, the catch here is that you'll have to remember to set ``DEBUG=False``
in your production settings file. But you should be doing that anyway.
Serving static media from a cloud service or CDN
------------------------------------------------
Another common tactic is to serve media from a cloud storage provider like
Amazon's S3__ and/or a CDN (content delivery network). This lets you ignore the
problems of serving media, and can often make for faster-loading webpages
(especially when using a CDN).
When using these services, the basic workflow would look a bit like the above,
except that instead of using ``rsync`` to transfer your media to the server
you'd need to transfer the media to the storage provider or CDN.
There's any number of ways you might do this, but if the provider has an API a
:doc:`custom file storage backend </howto/custom-file-storage>` will make the
process incredibly simple. If you've written or are using a 3rd party custom
storage backend, you can tell :djadmin:`collectstatic` to use it by setting
:setting:`STATICFILES_STORAGE` to the storage engine.
For example, if you've written an S3 storage backend in
``myproject.storage.S3Storage`` you could use it with::
STATICFILES_STORAGE = 'storages.backends.s3.S3Storage'
Once that's done, all you have to do is run :djadmin:`collectstatic` and your
media would be pushed through your storage package up to S3. If you later needed
to swich to a different storage provider, it could be as simple as changing your
:setting:`STATICFILES_STORAGE` setting.
For details on how you'd write one of these backends,
:doc:`/howto/custom-file-storage`.
.. seealso::
The `django-storages`__ project is a 3rd party app that provides many
storage backends for many common file storage APIs (including S3).
__ http://s3.amazonaws.com/
__ http://code.welldev.org/django-storages/wiki/S3Storage
Upgrading from ``django-staticfiles``
=====================================
``django.contrib.staticfiles`` began its life as `django-staticfiles`_. If
you're upgrading from `django-staticfiles`_ to ``django.contrib.staticfiles``,
you'll need to make a few changes:
* Application files should now live in a ``static`` directory in each app
(`django-staticfiles`_ used the name ``media``, which was slightly
confusing).
* The management commands ``build_static`` and ``resolve_static`` are now
called :djadmin:`collectstatic` and :djadmin:`findstatic`.
* The settings ``STATIC_URL`` and ``STATIC_ROOT`` were renamed to
:setting:`STATICFILES_URL` and :setting:`STATICFILES_ROOT`.
* The settings ``STATICFILES_PREPEND_LABEL_APPS``,
``STATICFILES_MEDIA_DIRNAMES`` and ``STATICFILES_EXCLUDED_APPS`` were
removed.
* The setting ``STATICFILES_RESOLVERS`` was removed, and replaced by the new
:setting:`STATICFILES_FINDERS`.
* The default for :setting:`STATICFILES_STORAGE` was renamed from
``staticfiles.storage.StaticFileStorage`` to
``staticfiles.storage.StaticFilesStorage``
Learn more
==========
This document has covered the basics and some common usage patterns. For
complete details on all the settings, commands, template tags, and other pieces
include in ``django.contrib.staticfiles``, see :doc:`the statcfiles reference
</ref/contrib/staticfiles>`.

View File

@ -155,7 +155,7 @@ The development process
:doc:`Apache/mod_python <howto/deployment/modpython>` |
:doc:`FastCGI/SCGI/AJP <howto/deployment/fastcgi>` |
:doc:`Apache authentication <howto/apache-auth>` |
:doc:`Serving static files <howto/static-files>` |
:doc:`Handling static files <howto/static-files>` |
:doc:`Tracking code errors by e-mail <howto/error-reporting>`
Other batteries included
@ -185,6 +185,7 @@ Other batteries included
* :doc:`Signals <topics/signals>`
* :doc:`Sitemaps <ref/contrib/sitemaps>`
* :doc:`Sites <ref/contrib/sites>`
* :doc:`Static Files <ref/contrib/staticfiles>`
* :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
* :doc:`Unicode in Django <ref/unicode>`
* :doc:`Web design helpers <ref/contrib/webdesign>`

View File

@ -38,6 +38,7 @@ those packages have.
redirects
sitemaps
sites
staticfiles
syndication
webdesign

View File

@ -0,0 +1,283 @@
===================
The staticfiles app
===================
.. module:: django.contrib.staticfiles
:synopsis: An app for handling static files.
.. versionadded:: 1.3
``django.contrib.staticfiles`` collects media from each of your applications
(and any other places you specify) into a single location that can easily be
served in production.
.. seealso::
For an introduction to the static files app and some usage examples, see
:doc:`/howto/static-files`.
.. _staticfiles-settings:
Settings
========
.. highlight:: python
The following settings control the behavior of the static files app. Only
:setting:`STATICFILES_ROOT` is required, but you'll probably also need to
configure :setting:`STATICFILES_URL` as well.
.. setting:: STATICFILES_ROOT
STATICFILES_ROOT
----------------
Default: ``''`` (Empty string)
The absolute path to the directory that holds static files::
STATICFILES_ROOT = "/home/example.com/static/"
This is a **required setting** unless you've overridden
:setting:`STATICFILES_STORAGE` and are using a custom storage backend.
.. setting:: STATICFILES_URL
STATICFILES_URL
---------------
Default: ``'/static/'``
The URL that handles the files served from :setting:`STATICFILES_ROOT`, e.g.::
STATICFILES_URL = '/site_media/static/'
... or perhaps::
STATICFILES_URL = 'http://media.exmaple.com/'
This should **always** have a trailing slash.
.. setting:: STATICFILES_DIRS
STATICFILES_DIRS
----------------
Default: ``[]``
This setting defines the additional locations the staticfiles app will traverse
if the :class:`FileSystemFinder` finder is enabled, e.g. if you use the
:djadmin:`collectstatic` or :djadmin:`findstatic` management command or use the
static file serving view.
It should be defined as a sequence of ``(prefix, path)`` tuples, e.g.::
STATICFILES_DIRS = (
('', '/home/special.polls.com/polls/media'),
('', '/home/polls.com/polls/media'),
('common', '/opt/webfiles/common'),
)
.. setting:: STATICFILES_STORAGE
STATICFILES_STORAGE
-------------------
Default: ``'django.contrib.staticfiles.storage.StaticFilesStorage'``
The file storage engine to use when collecting static files with the
:djadmin:`collectstatic` management command.
For an example, see :ref:`staticfiles-from-cdn`.
.. setting:: STATICFILES_FINDERS
STATICFILES_FINDERS
-------------------
Default::
("django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder")
The list of finder backends that know how to find static files in
various locations.
The default will find files stored in the :setting:`STATICFILES_DIRS` setting
(using :class:`django.contrib.staticfiles.finders.FileSystemFinder`) and in a
``static`` subdirectory of each app (using
:class:`django.contrib.staticfiles.finders.AppDirectoriesFinder`)
One finder is disabled by default:
:class:`django.contrib.staticfiles.finders.DefaultStorageFinder`. If added to
your :setting:`STATICFILES_FINDERS` setting, it will look for static files in
the default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE`
setting.
.. note::
When using the :class:AppDirectoriesFinder` finder, make sure your apps can
be found by Django's app loading mechanism. Simply include a ``models``
module (an empty ``models.py`` file suffices) and add the app to the
:setting:`INSTALLED_APPS` setting of your site.
Static file finders are currently considered a private interface, and this
interface is thus undocumented.
Management Commands
===================
.. highlight:: console
``django.contrib.staticfiles`` exposes two management commands.
collectstatic
-------------
.. django-admin:: collectstatic
Collects the static files into :setting:`STATICFILES_ROOT`.
Duplicate file names are resolved in a similar way to how template resolution
works: files from apps later in :setting:`INSTALLED_APPS` overwrite those from
earlier apps, and files from storage directories later in
:setting:`STATICFILES_DIRS` overwrite those from earlier. If you're confused,
the :djadmin:`findstatic` command can help show you where
Files are searched by using the :ref:`enabled finders
<staticfiles-finders>`. The default is to look in all locations defined in
:ref:`staticfiles-dirs` and in the ``media`` directory of apps specified by the
:setting:`INSTALLED_APPS` setting.
Some commonly used options are:
``--noinput``
Do NOT prompt the user for input of any kind.
``-i PATTERN`` or ``--ignore=PATTERN``
Ignore files or directories matching this glob-style pattern. Use multiple
times to ignore more.
``-n`` or ``--dry-run``
Do everything except modify the filesystem.
``-l`` or ``--link``
Create a symbolic link to each file instead of copying.
``--no-default-ignore``
Don't ignore the common private glob-style patterns ``'CVS'``, ``'.*'``
and ``'*~'``.
For a full list of options, refer to the commands own help by running::
$ python manage.py collectstatic --help
findstatic
----------
.. django-admin:: findstatic
Searches for one or more relative paths with the enabled finders.
For example::
$ python manage.py findstatic css/base.css admin/js/core.js
/home/special.polls.com/core/media/css/base.css
/home/polls.com/core/media/css/base.css
/home/polls.com/src/django/contrib/admin/media/js/core.js
By default, all matching locations are found. To only return the first match
for each relative path, use the ``--first`` option::
$ python manage.py findstatic css/base.css --first
/home/special.polls.com/core/media/css/base.css
This is a debugging aid; it'll show you exactly which static file will be
collected for a given path.
Other Helpers
=============
The ``media`` context processor
-------------------------------
.. function:: django.contrib.staticfiles.context_processors.staticfiles
This context processor adds the :setting:`STATICFILES_URL` into each template
context as the variable ``{{ STATICFILES_URL }}``. To use it, make sure that
``'django.contrib.staticfiles.context_processors.staticfiles'`` appears
somewhere in your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
Remember, only templates rendered with :class:`~django.template.RequestContext`
will have acces to the data provided by this (and any) context processor.
.. templatetag:: get_staticfiles_prefix
The ``get_staticfiles_prefix`` templatetag
==========================================
.. highlight:: html+django
If you're not using :class:`~django.template.RequestContext`, or if you need
more control over exactly where and how :setting:`STATICFILES_URL` is injected
into the template, you can use the :ttag:`get_staticfiles_prefix` template tag
instead::
{% load staticfiles %}
<img src="{% get_staticfiles_prefix %}images/hi.jpg" />
There's also a second form you can use to avoid extra processing if you need the
value multiple times::
{% load staticfiles %}
{% get_staticfiles_prefix as STATIC_PREFIX %}
<img src="{{ STATIC_PREFIX }}images/hi.jpg" />
<img src="{{ STATIC_PREFIX }}images/hi2.jpg" />
.. _staticfiles-development-view:
Static file development view
----------------------------
.. highlight:: python
.. function:: django.contrib.staticfiles.views.serve(request, path)
This view function serves static media in in development.
.. warning::
This view will only work if :setting:`DEBUG` is ``True``.
That's because this view is **grossly inefficient** and probably
**insecure**. This is only intended for local development, and should
**never be used in production**.
To use the view, add the following snippet to the end of your primary URL
configuration::
from django.conf import settings
if settings.DEBUG:
urlpatterns = patterns('django.contrib.staticfiles.views',
url(r'^static/(?P<path>.*)$', 'serve'),
)
Note, the begin of the pattern (``r'^static/'``) should be your
:setting:`STATICFILES_URL` setting.
Since this is a bit finicky, there's also a helper function that'll do this for you:
.. function:: django.contrib.staticfiles.urls.staticfiles_urlpatterns()
This will return the proper URL pattern for serving static files to your
already defined pattern list. Use it like this::
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# ... the rest of your URLconf here ...
urlpatterns += staticfiles_urlpatterns()

View File

@ -1482,7 +1482,7 @@ Default::
("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.contrib.staticfiles.context_processors.staticfiles",
"django.contrib.messages.context_processors.messages")
A tuple of callables that are used to populate the context in ``RequestContext``.

View File

@ -289,6 +289,8 @@ you'll see below.
Subclassing Context: RequestContext
-----------------------------------
.. class:: django.template.RequestContext
Django comes with a special ``Context`` class,
``django.template.RequestContext``, that acts slightly differently than the
normal ``django.template.Context``. The first difference is that it takes an
@ -309,7 +311,7 @@ and return a dictionary of items to be merged into the context. By default,
("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.contrib.staticfiles.context_processors.staticfiles",
"django.contrib.messages.context_processors.messages")
.. versionadded:: 1.2
@ -432,6 +434,11 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
value of the :setting:`MEDIA_URL` setting.
.. versionchanged:: 1.3
This context processor has been moved to the new :ref:`staticfiles` app.
Please use the new ``django.contrib.staticfiles.context_processors.staticfiles``
context processor.
django.core.context_processors.csrf
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -9,6 +9,7 @@ from django.test import TestCase
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler
from django.conf import settings
class AdminMediaHandlerTests(TestCase):
@ -25,7 +26,7 @@ class AdminMediaHandlerTests(TestCase):
"""
# Cases that should work on all platforms.
data = (
('/media/css/base.css', ('css', 'base.css')),
('%scss/base.css' % settings.ADMIN_MEDIA_PREFIX, ('css', 'base.css')),
)
# Cases that should raise an exception.
bad_data = ()
@ -34,19 +35,19 @@ class AdminMediaHandlerTests(TestCase):
if os.sep == '/':
data += (
# URL, tuple of relative path parts.
('/media/\\css/base.css', ('\\css', 'base.css')),
('%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX, ('\\css', 'base.css')),
)
bad_data += (
'/media//css/base.css',
'/media////css/base.css',
'/media/../css/base.css',
'%s/css/base.css' % settings.ADMIN_MEDIA_PREFIX,
'%s///css/base.css' % settings.ADMIN_MEDIA_PREFIX,
'%s../css/base.css' % settings.ADMIN_MEDIA_PREFIX,
)
elif os.sep == '\\':
bad_data += (
'/media/C:\css/base.css',
'/media//\\css/base.css',
'/media/\\css/base.css',
'/media/\\\\css/base.css'
'%sC:\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
'%s/\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
'%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
'%s\\\\css/base.css' % settings.ADMIN_MEDIA_PREFIX
)
for url, path_tuple in data:
try:

View File

@ -0,0 +1 @@
file2 in no_label_app

View File

@ -0,0 +1 @@
File in otherdir.

View File

@ -0,0 +1 @@
This file should be ignored.

View File

@ -0,0 +1 @@
This file should be ignored.

View File

@ -0,0 +1 @@
This file should be ignored.

View File

@ -0,0 +1 @@
In app media directory.

View File

@ -0,0 +1 @@
file1 in the app dir

View File

@ -0,0 +1 @@
This file should be ignored.

View File

@ -0,0 +1 @@
Can we find this file?

View File

@ -0,0 +1 @@
Can we find this file?

View File

@ -0,0 +1,2 @@
In STATICFILES_DIRS directory.

View File

@ -0,0 +1,330 @@
import tempfile
import shutil
import os
import sys
import posixpath
from django.test import TestCase
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django.db.models.loading import load_app
from django.template import Template, Context
from django.contrib.staticfiles import finders, storage
TEST_ROOT = os.path.dirname(__file__)
class StaticFilesTestCase(TestCase):
"""
Test case with a couple utility assertions.
"""
def setUp(self):
self.old_staticfiles_url = settings.STATICFILES_URL
self.old_staticfiles_root = settings.STATICFILES_ROOT
self.old_staticfiles_dirs = settings.STATICFILES_DIRS
self.old_staticfiles_finders = settings.STATICFILES_FINDERS
self.old_installed_apps = settings.INSTALLED_APPS
self.old_media_root = settings.MEDIA_ROOT
self.old_media_url = settings.MEDIA_URL
self.old_admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
# We have to load these apps to test staticfiles.
load_app('regressiontests.staticfiles_tests.apps.test')
load_app('regressiontests.staticfiles_tests.apps.no_label')
site_media = os.path.join(TEST_ROOT, 'project', 'site_media')
settings.MEDIA_ROOT = os.path.join(site_media, 'media')
settings.MEDIA_URL = '/media/'
settings.STATICFILES_ROOT = os.path.join(site_media, 'static')
settings.STATICFILES_URL = '/static/'
settings.ADMIN_MEDIA_PREFIX = '/static/admin/'
settings.STATICFILES_DIRS = (
os.path.join(TEST_ROOT, 'project', 'documents'),
)
settings.STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
def tearDown(self):
settings.MEDIA_ROOT = self.old_media_root
settings.MEDIA_URL = self.old_media_url
settings.ADMIN_MEDIA_PREFIX = self.old_admin_media_prefix
settings.STATICFILES_ROOT = self.old_staticfiles_root
settings.STATICFILES_URL = self.old_staticfiles_url
settings.STATICFILES_DIRS = self.old_staticfiles_dirs
settings.STATICFILES_FINDERS = self.old_staticfiles_finders
settings.INSTALLED_APPS = self.old_installed_apps
def assertFileContains(self, filepath, text):
self.failUnless(text in self._get_file(filepath),
"'%s' not in '%s'" % (text, filepath))
def assertFileNotFound(self, filepath):
self.assertRaises(IOError, self._get_file, filepath)
class BuildStaticTestCase(StaticFilesTestCase):
"""
Tests shared by all file-resolving features (collectstatic,
findstatic, and static serve view).
This relies on the asserts defined in UtilityAssertsTestCase, but
is separated because some test cases need those asserts without
all these tests.
"""
def setUp(self):
super(BuildStaticTestCase, self).setUp()
self.old_staticfiles_storage = settings.STATICFILES_STORAGE
self.old_root = settings.STATICFILES_ROOT
settings.STATICFILES_ROOT = tempfile.mkdtemp()
self.run_collectstatic()
def tearDown(self):
shutil.rmtree(settings.STATICFILES_ROOT)
settings.STATICFILES_ROOT = self.old_root
super(BuildStaticTestCase, self).tearDown()
def run_collectstatic(self, **kwargs):
call_command('collectstatic', interactive=False, verbosity='0',
ignore_patterns=['*.ignoreme'], **kwargs)
def _get_file(self, filepath):
assert filepath, 'filepath is empty.'
filepath = os.path.join(settings.STATICFILES_ROOT, filepath)
return open(filepath).read()
class TestDefaults(object):
"""
A few standard test cases.
"""
def test_staticfiles_dirs(self):
"""
Can find a file in a STATICFILES_DIRS directory.
"""
self.assertFileContains('test.txt', 'Can we find')
def test_staticfiles_dirs_subdir(self):
"""
Can find a file in a subdirectory of a STATICFILES_DIRS
directory.
"""
self.assertFileContains('subdir/test.txt', 'Can we find')
def test_staticfiles_dirs_priority(self):
"""
File in STATICFILES_DIRS has priority over file in app.
"""
self.assertFileContains('test/file.txt', 'STATICFILES_DIRS')
def test_app_files(self):
"""
Can find a file in an app media/ directory.
"""
self.assertFileContains('test/file1.txt', 'file1 in the app dir')
class TestBuildStatic(BuildStaticTestCase, TestDefaults):
"""
Test ``collectstatic`` management command.
"""
def test_ignore(self):
"""
Test that -i patterns are ignored.
"""
self.assertFileNotFound('test/test.ignoreme')
def test_common_ignore_patterns(self):
"""
Common ignore patterns (*~, .*, CVS) are ignored.
"""
self.assertFileNotFound('test/.hidden')
self.assertFileNotFound('test/backup~')
self.assertFileNotFound('test/CVS')
class TestBuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase, TestDefaults):
"""
Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
``collectstatic`` management command.
"""
def run_collectstatic(self):
super(TestBuildStaticExcludeNoDefaultIgnore, self).run_collectstatic(
use_default_ignore_patterns=False)
def test_no_common_ignore_patterns(self):
"""
With --no-default-ignore, common ignore patterns (*~, .*, CVS)
are not ignored.
"""
self.assertFileContains('test/.hidden', 'should be ignored')
self.assertFileContains('test/backup~', 'should be ignored')
self.assertFileContains('test/CVS', 'should be ignored')
class TestBuildStaticDryRun(BuildStaticTestCase):
"""
Test ``--dry-run`` option for ``collectstatic`` management command.
"""
def run_collectstatic(self):
super(TestBuildStaticDryRun, self).run_collectstatic(dry_run=True)
def test_no_files_created(self):
"""
With --dry-run, no files created in destination dir.
"""
self.assertEquals(os.listdir(settings.STATICFILES_ROOT), [])
if sys.platform != 'win32':
class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults):
"""
Test ``--link`` option for ``collectstatic`` management command.
Note that by inheriting ``TestDefaults`` we repeat all
the standard file resolving tests here, to make sure using
``--link`` does not change the file-selection semantics.
"""
def run_collectstatic(self):
super(TestBuildStaticLinks, self).run_collectstatic(link=True)
def test_links_created(self):
"""
With ``--link``, symbolic links are created.
"""
self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt')))
class TestServeStatic(StaticFilesTestCase):
"""
Test static asset serving view.
"""
def _response(self, filepath):
return self.client.get(
posixpath.join(settings.STATICFILES_URL, filepath))
def assertFileContains(self, filepath, text):
self.assertContains(self._response(filepath), text)
def assertFileNotFound(self, filepath):
self.assertEquals(self._response(filepath).status_code, 404)
class TestServeStaticWithDefaultURL(TestServeStatic, TestDefaults):
"""
Test static asset serving view with staticfiles_urlpatterns helper.
"""
urls = "regressiontests.staticfiles_tests.urls.default"
class TestServeStaticWithURLHelper(TestServeStatic, TestDefaults):
"""
Test static asset serving view with staticfiles_urlpatterns helper.
"""
urls = "regressiontests.staticfiles_tests.urls.helper"
class TestServeAdminMedia(TestServeStatic):
"""
Test serving media from django.contrib.admin.
"""
def _response(self, filepath):
return self.client.get(
posixpath.join(settings.ADMIN_MEDIA_PREFIX, filepath))
def test_serve_admin_media(self):
self.assertFileContains('css/base.css', 'body')
class FinderTestCase(object):
"""
Base finder test mixin
"""
def test_find_first(self):
src, dst = self.find_first
self.assertEquals(self.finder.find(src), dst)
def test_find_all(self):
src, dst = self.find_all
self.assertEquals(self.finder.find(src, all=True), dst)
class TestFileSystemFinder(StaticFilesTestCase, FinderTestCase):
"""
Test FileSystemFinder.
"""
def setUp(self):
super(TestFileSystemFinder, self).setUp()
self.finder = finders.FileSystemFinder()
test_file_path = os.path.join(TEST_ROOT, 'project/documents/test/file.txt')
self.find_first = ("test/file.txt", test_file_path)
self.find_all = ("test/file.txt", [test_file_path])
class TestAppDirectoriesFinder(StaticFilesTestCase, FinderTestCase):
"""
Test AppDirectoriesFinder.
"""
def setUp(self):
super(TestAppDirectoriesFinder, self).setUp()
self.finder = finders.AppDirectoriesFinder()
test_file_path = os.path.join(TEST_ROOT, 'apps/test/static/test/file1.txt')
self.find_first = ("test/file1.txt", test_file_path)
self.find_all = ("test/file1.txt", [test_file_path])
class TestDefaultStorageFinder(StaticFilesTestCase, FinderTestCase):
"""
Test DefaultStorageFinder.
"""
def setUp(self):
super(TestDefaultStorageFinder, self).setUp()
self.finder = finders.DefaultStorageFinder(
storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT))
test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt')
self.find_first = ("media-file.txt", test_file_path)
self.find_all = ("media-file.txt", [test_file_path])
class TestMiscFinder(TestCase):
"""
A few misc finder tests.
"""
def test_get_finder(self):
self.assertTrue(isinstance(finders.get_finder(
"django.contrib.staticfiles.finders.FileSystemFinder"),
finders.FileSystemFinder))
self.assertRaises(ImproperlyConfigured,
finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder")
self.assertRaises(ImproperlyConfigured,
finders.get_finder, "foo.bar.FooBarFinder")
class TemplateTagTest(TestCase):
def test_get_staticfiles_prefix(self):
"""
Test the get_staticfiles_prefix helper return the STATICFILES_URL setting.
"""
self.assertEquals(Template(
"{% load staticfiles %}"
"{% get_staticfiles_prefix %}"
).render(Context()), settings.STATICFILES_URL)
def test_get_staticfiles_prefix_with_as(self):
"""
Test the get_staticfiles_prefix helper return the STATICFILES_URL setting.
"""
self.assertEquals(Template(
"{% load staticfiles %}"
"{% get_staticfiles_prefix as staticfiles_prefix %}"
"{{ staticfiles_prefix }}"
).render(Context()), settings.STATICFILES_URL)

View File

@ -0,0 +1,6 @@
from django.conf import settings
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^static/(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
)

View File

@ -0,0 +1,3 @@
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns = staticfiles_urlpatterns()

View File

@ -27,6 +27,7 @@ ALWAYS_INSTALLED_APPS = [
'django.contrib.comments',
'django.contrib.admin',
'django.contrib.admindocs',
'django.contrib.staticfiles',
]
def get_test_models():

View File

@ -41,4 +41,7 @@ urlpatterns = patterns('',
# special headers views
(r'special_headers/', include('regressiontests.special_headers.urls')),
# static files handling
(r'^', include('regressiontests.staticfiles_tests.urls.default')),
)