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:
parent
a014ee0288
commit
cfc19f84de
|
@ -194,7 +194,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.core.context_processors.debug',
|
'django.core.context_processors.debug',
|
||||||
'django.core.context_processors.i18n',
|
'django.core.context_processors.i18n',
|
||||||
'django.core.context_processors.media',
|
'django.contrib.staticfiles.context_processors.staticfiles',
|
||||||
# 'django.core.context_processors.request',
|
# 'django.core.context_processors.request',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'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.
|
# Output to use in template system for invalid (e.g. misspelled) variables.
|
||||||
TEMPLATE_STRING_IF_INVALID = ''
|
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
|
# Default e-mail address to use for various automated correspondence from
|
||||||
# the site managers.
|
# the site managers.
|
||||||
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
|
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
|
||||||
|
@ -551,3 +546,34 @@ TEST_DATABASE_COLLATION = None
|
||||||
|
|
||||||
# The list of directories to search for fixtures
|
# The list of directories to search for fixtures
|
||||||
FIXTURE_DIRS = ()
|
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/'
|
||||||
|
|
|
@ -44,7 +44,7 @@ USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
# Absolute path to the directory that holds media.
|
# Absolute path to the directory that holds media.
|
||||||
# Example: "/home/media/media.lawrence.com/"
|
# Example: "/home/media/media.lawrence.com/media/"
|
||||||
MEDIA_ROOT = ''
|
MEDIA_ROOT = ''
|
||||||
|
|
||||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
# 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/"
|
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||||
MEDIA_URL = ''
|
MEDIA_URL = ''
|
||||||
|
|
||||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
# Absolute path to the directory that holds media.
|
||||||
# trailing slash.
|
# Example: "/home/media/media.lawrence.com/static/"
|
||||||
# Examples: "http://foo.com/media/", "/media/".
|
STATICFILES_ROOT = ''
|
||||||
ADMIN_MEDIA_PREFIX = '/media/'
|
|
||||||
|
# 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.
|
# Make this unique, and don't share it with anybody.
|
||||||
SECRET_KEY = ''
|
SECRET_KEY = ''
|
||||||
|
@ -89,6 +108,7 @@ INSTALLED_APPS = (
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
# Uncomment the next line to enable the admin:
|
# Uncomment the next line to enable the admin:
|
||||||
# 'django.contrib.admin',
|
# 'django.contrib.admin',
|
||||||
# Uncomment the next line to enable admin documentation:
|
# Uncomment the next line to enable admin documentation:
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
def staticfiles(request):
|
||||||
|
return {
|
||||||
|
'STATICFILES_URL': settings.STATICFILES_URL,
|
||||||
|
'MEDIA_URL': settings.MEDIA_URL,
|
||||||
|
}
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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))
|
||||||
|
|
|
@ -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)),)
|
|
@ -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
|
|
@ -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
|
|
@ -71,7 +71,15 @@ def media(request):
|
||||||
Adds media-related context variables to the context.
|
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):
|
def request(request):
|
||||||
return {'request': request}
|
return {'request': request}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
|
@ -20,6 +22,7 @@ class Command(BaseCommand):
|
||||||
import django
|
import django
|
||||||
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
|
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
|
||||||
from django.core.handlers.wsgi import WSGIHandler
|
from django.core.handlers.wsgi import WSGIHandler
|
||||||
|
from django.contrib.staticfiles.handlers import StaticFilesHandler
|
||||||
if args:
|
if args:
|
||||||
raise CommandError('Usage is runserver %s' % self.args)
|
raise CommandError('Usage is runserver %s' % self.args)
|
||||||
if not addrport:
|
if not addrport:
|
||||||
|
@ -56,7 +59,10 @@ class Command(BaseCommand):
|
||||||
translation.activate(settings.LANGUAGE_CODE)
|
translation.activate(settings.LANGUAGE_CODE)
|
||||||
|
|
||||||
try:
|
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)
|
run(addr, int(port), handler)
|
||||||
except WSGIServerException, e:
|
except WSGIServerException, e:
|
||||||
# Use helpful error messages instead of ugly tracebacks.
|
# Use helpful error messages instead of ugly tracebacks.
|
||||||
|
|
|
@ -8,16 +8,17 @@ been reviewed for security issues. Don't use it for production use.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import stat
|
|
||||||
import sys
|
import sys
|
||||||
import urllib
|
import urllib
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.core.management.color import color_style
|
from django.core.management.color import color_style
|
||||||
from django.utils.http import http_date
|
from django.utils.http import http_date
|
||||||
from django.utils._os import safe_join
|
from django.utils._os import safe_join
|
||||||
|
from django.contrib.staticfiles.handlers import StaticFilesHandler
|
||||||
|
from django.views import static
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
__all__ = ['WSGIServer','WSGIRequestHandler']
|
__all__ = ['WSGIServer','WSGIRequestHandler']
|
||||||
|
@ -633,86 +634,46 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
sys.stderr.write(msg)
|
sys.stderr.write(msg)
|
||||||
|
|
||||||
class AdminMediaHandler(object):
|
|
||||||
|
class AdminMediaHandler(StaticFilesHandler):
|
||||||
"""
|
"""
|
||||||
WSGI middleware that intercepts calls to the admin media directory, as
|
WSGI middleware that intercepts calls to the admin media directory, as
|
||||||
defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
|
defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
|
||||||
Use this ONLY LOCALLY, for development! This hasn't been tested for
|
Use this ONLY LOCALLY, for development! This hasn't been tested for
|
||||||
security and is not super efficient.
|
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
|
from django.conf import settings
|
||||||
self.application = application
|
return settings.ADMIN_MEDIA_PREFIX
|
||||||
if not media_dir:
|
|
||||||
import django
|
def __init__(self, application, media_dir=None):
|
||||||
self.media_dir = \
|
warnings.warn('The AdminMediaHandler handler is deprecated; use the '
|
||||||
os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
|
'`django.contrib.staticfiles.handlers.StaticFilesHandler` instead.',
|
||||||
else:
|
PendingDeprecationWarning)
|
||||||
self.media_dir = media_dir
|
super(AdminMediaHandler, self).__init__(application, media_dir)
|
||||||
self.media_url = settings.ADMIN_MEDIA_PREFIX
|
|
||||||
|
|
||||||
def file_path(self, url):
|
def file_path(self, url):
|
||||||
"""
|
"""
|
||||||
Returns the path to the media file on disk for the given 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
|
resultant file path is outside the media directory, then a ValueError
|
||||||
is raised.
|
is raised.
|
||||||
"""
|
"""
|
||||||
# Remove ADMIN_MEDIA_PREFIX.
|
# Remove ``media_url``.
|
||||||
relative_url = url[len(self.media_url):]
|
relative_url = url[len(self.media_url):]
|
||||||
relative_path = urllib.url2pathname(relative_url)
|
relative_path = urllib.url2pathname(relative_url)
|
||||||
return safe_join(self.media_dir, relative_path)
|
return safe_join(self.media_dir, relative_path)
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def serve(self, request, path):
|
||||||
import os.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):
|
def run(addr, port, wsgi_handler):
|
||||||
server_address = (addr, port)
|
server_address = (addr, port)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import posixpath
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import urllib
|
import urllib
|
||||||
|
import warnings
|
||||||
from email.Utils import parsedate_tz, mktime_tz
|
from email.Utils import parsedate_tz, mktime_tz
|
||||||
|
|
||||||
from django.template import loader
|
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.template import Template, Context, TemplateDoesNotExist
|
||||||
from django.utils.http import http_date
|
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):
|
def serve(request, path, document_root=None, show_indexes=False):
|
||||||
"""
|
"""
|
||||||
Serve static files below a given point in the directory structure.
|
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
|
but if you'd like to override it, you can create a template called
|
||||||
``static/directory_index.html``.
|
``static/directory_index.html``.
|
||||||
"""
|
"""
|
||||||
|
warnings.warn("The view at `django.views.static.serve` is deprecated; "
|
||||||
# Clean up given path to only allow serving files below document_root.
|
"use the path `django.contrib.staticfiles.views.serve` "
|
||||||
path = posixpath.normpath(urllib.unquote(path))
|
"instead.", PendingDeprecationWarning)
|
||||||
path = path.lstrip('/')
|
return staticfiles_serve(request, path, document_root, show_indexes)
|
||||||
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
|
|
||||||
|
|
|
@ -1,162 +1,399 @@
|
||||||
=========================
|
=====================
|
||||||
How to serve static files
|
Managing static files
|
||||||
=========================
|
=====================
|
||||||
|
|
||||||
.. module:: django.views.static
|
.. currentmodule:: django.contrib.staticfiles
|
||||||
:synopsis: Serving of static files during development.
|
|
||||||
|
|
||||||
Django itself doesn't serve static (media) files, such as images, style sheets,
|
.. versionadded:: 1.3
|
||||||
or video. It leaves that job to whichever Web server you choose.
|
|
||||||
|
|
||||||
The reasoning here is that standard Web servers, such as Apache_, lighttpd_ and
|
Django developers mostly concern themselves with the dynamic parts of web
|
||||||
Cherokee_, are much more fine-tuned at serving static files than a Web
|
applications -- the views and templates that render anew for each request. But
|
||||||
application framework.
|
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
|
For small projects, this isn't a big deal, because you can just keep the media
|
||||||
use the :func:`django.views.static.serve` view to serve media files.
|
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/
|
That's what ``django.contrib.staticfiles`` is for: it collects media from each
|
||||||
.. _lighttpd: http://www.lighttpd.net/
|
of your applications (and any other places you specify) into a single location
|
||||||
.. _Cherokee: http://www.cherokee-project.com/
|
that can easily be served in production.
|
||||||
|
|
||||||
.. seealso::
|
.. note::
|
||||||
|
|
||||||
If you just need to serve the admin media from a nonstandard location, see
|
If you've used the `django-staticfiles`_ third-party app before, then
|
||||||
the :djadminopt:`--adminmedia` parameter to :djadmin:`runserver`.
|
``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.
|
||||||
|
|
||||||
|
If you're upgrading from ``django-staticfiles``, please see `Upgrading from
|
||||||
|
django-staticfiles`_, below, for a few minor changes you'll need to make.
|
||||||
|
|
||||||
The big, fat disclaimer
|
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/
|
||||||
=======================
|
|
||||||
|
|
||||||
Using this method is **inefficient** and **insecure**. Do not use this in a
|
Using ``django.contrib.staticfiles``
|
||||||
production setting. Use this only for development.
|
====================================
|
||||||
|
|
||||||
For information on serving static files in an Apache production environment,
|
Here's the basic usage in a nutshell:
|
||||||
see the :ref:`Django mod_wsgi documentation <serving-media-files>`.
|
|
||||||
|
|
||||||
How to do it
|
1. Put your media somewhere that staticfiles will find it..
|
||||||
============
|
|
||||||
|
|
||||||
Here's the formal definition of the :func:`~django.views.static.serve` view:
|
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.
|
||||||
|
|
||||||
.. function:: def serve(request, path, document_root, show_indexes=False)
|
2. Add some ``staticfiles``-related settings to your settings file.
|
||||||
|
|
||||||
To use it, just put this in your :doc:`URLconf </topics/http/urls>`::
|
First, you'll need to make sure that ``django.contrib.staticfiles`` is in
|
||||||
|
your :setting:`INSTALLED_APPS`.
|
||||||
|
|
||||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
|
Next, you'll need to edit :setting:`STATICFILES_ROOT` to point to where
|
||||||
{'document_root': '/path/to/media'}),
|
you'd like your static media stored. For example::
|
||||||
|
|
||||||
...where ``site_media`` is the URL where your media will be rooted, and
|
STATICFILES_ROOT = "/home/jacob/projects/mysite.com/static_media"
|
||||||
``/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.
|
|
||||||
|
|
||||||
Given the above URLconf:
|
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/foo.jpg`` will be made available at the URL
|
There are a number of other options available that let you control *how*
|
||||||
``/site_media/foo.jpg``.
|
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/to/media/css/mystyles.css`` will be made available
|
3. Run the :djadmin:`collectstatic` management command::
|
||||||
at the URL ``/site_media/css/mystyles.css``.
|
|
||||||
|
|
||||||
* The file ``/path/bar.jpg`` will not be accessible, because it doesn't
|
./manage.py collectstatic
|
||||||
fall under the document root.
|
|
||||||
|
|
||||||
Of course, it's not compulsory to use a fixed string for the
|
This'll churn through your static file storage and move them into the
|
||||||
``'document_root'`` value. You might wish to make that an entry in your
|
directory given by :setting:`STATICFILES_ROOT`.
|
||||||
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::
|
|
||||||
|
|
||||||
STATIC_DOC_ROOT = '/path/to/media'
|
4. Deploy that media.
|
||||||
|
|
||||||
...we could write the above :doc:`URLconf </topics/http/urls>` entry as::
|
If you're using the built-in development server, you can quickly
|
||||||
|
serve static media locally by adding::
|
||||||
|
|
||||||
from django.conf import settings
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
...
|
urlpatterns += staticfiles_urlpatterns()
|
||||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
|
|
||||||
{'document_root': settings.STATIC_DOC_ROOT}),
|
|
||||||
|
|
||||||
Be careful not to use the same path as your :setting:`ADMIN_MEDIA_PREFIX` (which defaults
|
to the bottom of your URLconf. See :ref:`staticfiles-development` for
|
||||||
to ``/media/``) as this will overwrite your URLconf entry.
|
details.
|
||||||
|
|
||||||
Directory listings
|
When it comes time to deploy to production, :ref:`staticfiles-production`
|
||||||
==================
|
covers some common deployment strategies for static files.
|
||||||
|
|
||||||
Optionally, you can pass the ``show_indexes`` parameter to the
|
However you choose to deploy those files, you'll probably need to refer
|
||||||
:func:`~django.views.static.serve` view. This is ``False`` by default. If it's
|
to them in your templates. The easiest method is to use the included
|
||||||
``True``, Django will display file listings for directories.
|
context processor which will allow template code like:
|
||||||
|
|
||||||
For example::
|
.. code-block:: html+django
|
||||||
|
|
||||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
|
<img src="{{ STATICFILES_URL }}images/hi.jpg />
|
||||||
{'document_root': '/path/to/media', 'show_indexes': True}),
|
|
||||||
|
|
||||||
You can customize the index view by creating a template called
|
See :ref:`staticfiles-in-templates` for more details, including an
|
||||||
``static/directory_index.html``. That template gets two objects in its context:
|
alternate method (using a template tag).
|
||||||
|
|
||||||
* ``directory`` -- the directory name (a string)
|
Those are the basics. For more details on common configuration options, read on;
|
||||||
* ``file_list`` -- a list of file names (as strings) in the directory
|
for a detailed reference of the settings, commands, and other bits included with
|
||||||
|
the framework see :doc:`the staticfiles reference </ref/contrib/staticfiles>`.
|
||||||
|
|
||||||
Here's the default ``static/directory_index.html`` template:
|
.. _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
|
.. code-block:: html+django
|
||||||
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
<img src="{{ STATICFILES_URL }}images/hi.jpg />
|
||||||
<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>
|
|
||||||
|
|
||||||
.. versionchanged:: 1.0.3
|
If ``{{ STATICFILES_URL }}`` isn't working in your template, you're probably not
|
||||||
Prior to Django 1.0.3, there was a bug in the view that provided directory
|
using :class:`~django.template.RequestContext` when rendering the template.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
With a template tag
|
||||||
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.
|
|
||||||
|
|
||||||
Do this by wrapping an ``if DEBUG`` statement around the
|
The second option is the :ttag:`get_staticfiles_prefix` template tag. You can
|
||||||
:func:`django.views.static.serve` inclusion. Here's a full example URLconf::
|
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 *
|
.. code-block:: html+django
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
{% load staticfiles %}
|
||||||
(r'^articles/2003/$', 'news.views.special_case_2003'),
|
<img src="{% get_staticfiles_prefix %}images/hi.jpg" />
|
||||||
(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'),
|
|
||||||
)
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
There's also a second form you can use to avoid extra processing if you need the
|
||||||
urlpatterns += patterns('',
|
value multiple times:
|
||||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}),
|
|
||||||
|
.. 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
|
.. _staticfiles-from-cdn:
|
||||||
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.
|
|
||||||
|
|
||||||
Of course, the catch here is that you'll have to remember to set ``DEBUG=False``
|
Serving static media from a cloud service or CDN
|
||||||
in your production settings file. But you should be doing that anyway.
|
------------------------------------------------
|
||||||
|
|
||||||
|
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>`.
|
|
@ -155,7 +155,7 @@ The development process
|
||||||
:doc:`Apache/mod_python <howto/deployment/modpython>` |
|
:doc:`Apache/mod_python <howto/deployment/modpython>` |
|
||||||
:doc:`FastCGI/SCGI/AJP <howto/deployment/fastcgi>` |
|
:doc:`FastCGI/SCGI/AJP <howto/deployment/fastcgi>` |
|
||||||
:doc:`Apache authentication <howto/apache-auth>` |
|
: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>`
|
:doc:`Tracking code errors by e-mail <howto/error-reporting>`
|
||||||
|
|
||||||
Other batteries included
|
Other batteries included
|
||||||
|
@ -185,6 +185,7 @@ Other batteries included
|
||||||
* :doc:`Signals <topics/signals>`
|
* :doc:`Signals <topics/signals>`
|
||||||
* :doc:`Sitemaps <ref/contrib/sitemaps>`
|
* :doc:`Sitemaps <ref/contrib/sitemaps>`
|
||||||
* :doc:`Sites <ref/contrib/sites>`
|
* :doc:`Sites <ref/contrib/sites>`
|
||||||
|
* :doc:`Static Files <ref/contrib/staticfiles>`
|
||||||
* :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
|
* :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
|
||||||
* :doc:`Unicode in Django <ref/unicode>`
|
* :doc:`Unicode in Django <ref/unicode>`
|
||||||
* :doc:`Web design helpers <ref/contrib/webdesign>`
|
* :doc:`Web design helpers <ref/contrib/webdesign>`
|
||||||
|
|
|
@ -38,6 +38,7 @@ those packages have.
|
||||||
redirects
|
redirects
|
||||||
sitemaps
|
sitemaps
|
||||||
sites
|
sites
|
||||||
|
staticfiles
|
||||||
syndication
|
syndication
|
||||||
webdesign
|
webdesign
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -1482,7 +1482,7 @@ Default::
|
||||||
("django.contrib.auth.context_processors.auth",
|
("django.contrib.auth.context_processors.auth",
|
||||||
"django.core.context_processors.debug",
|
"django.core.context_processors.debug",
|
||||||
"django.core.context_processors.i18n",
|
"django.core.context_processors.i18n",
|
||||||
"django.core.context_processors.media",
|
"django.contrib.staticfiles.context_processors.staticfiles",
|
||||||
"django.contrib.messages.context_processors.messages")
|
"django.contrib.messages.context_processors.messages")
|
||||||
|
|
||||||
A tuple of callables that are used to populate the context in ``RequestContext``.
|
A tuple of callables that are used to populate the context in ``RequestContext``.
|
||||||
|
|
|
@ -289,6 +289,8 @@ you'll see below.
|
||||||
Subclassing Context: RequestContext
|
Subclassing Context: RequestContext
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
.. class:: django.template.RequestContext
|
||||||
|
|
||||||
Django comes with a special ``Context`` class,
|
Django comes with a special ``Context`` class,
|
||||||
``django.template.RequestContext``, that acts slightly differently than the
|
``django.template.RequestContext``, that acts slightly differently than the
|
||||||
normal ``django.template.Context``. The first difference is that it takes an
|
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.contrib.auth.context_processors.auth",
|
||||||
"django.core.context_processors.debug",
|
"django.core.context_processors.debug",
|
||||||
"django.core.context_processors.i18n",
|
"django.core.context_processors.i18n",
|
||||||
"django.core.context_processors.media",
|
"django.contrib.staticfiles.context_processors.staticfiles",
|
||||||
"django.contrib.messages.context_processors.messages")
|
"django.contrib.messages.context_processors.messages")
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
.. 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
|
``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
|
||||||
value of the :setting:`MEDIA_URL` setting.
|
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
|
django.core.context_processors.csrf
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.test import TestCase
|
||||||
from django.core.handlers.wsgi import WSGIHandler
|
from django.core.handlers.wsgi import WSGIHandler
|
||||||
from django.core.servers.basehttp import AdminMediaHandler
|
from django.core.servers.basehttp import AdminMediaHandler
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
class AdminMediaHandlerTests(TestCase):
|
class AdminMediaHandlerTests(TestCase):
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ class AdminMediaHandlerTests(TestCase):
|
||||||
"""
|
"""
|
||||||
# Cases that should work on all platforms.
|
# Cases that should work on all platforms.
|
||||||
data = (
|
data = (
|
||||||
('/media/css/base.css', ('css', 'base.css')),
|
('%scss/base.css' % settings.ADMIN_MEDIA_PREFIX, ('css', 'base.css')),
|
||||||
)
|
)
|
||||||
# Cases that should raise an exception.
|
# Cases that should raise an exception.
|
||||||
bad_data = ()
|
bad_data = ()
|
||||||
|
@ -34,19 +35,19 @@ class AdminMediaHandlerTests(TestCase):
|
||||||
if os.sep == '/':
|
if os.sep == '/':
|
||||||
data += (
|
data += (
|
||||||
# URL, tuple of relative path parts.
|
# 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 += (
|
bad_data += (
|
||||||
'/media//css/base.css',
|
'%s/css/base.css' % settings.ADMIN_MEDIA_PREFIX,
|
||||||
'/media////css/base.css',
|
'%s///css/base.css' % settings.ADMIN_MEDIA_PREFIX,
|
||||||
'/media/../css/base.css',
|
'%s../css/base.css' % settings.ADMIN_MEDIA_PREFIX,
|
||||||
)
|
)
|
||||||
elif os.sep == '\\':
|
elif os.sep == '\\':
|
||||||
bad_data += (
|
bad_data += (
|
||||||
'/media/C:\css/base.css',
|
'%sC:\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
|
||||||
'/media//\\css/base.css',
|
'%s/\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
|
||||||
'/media/\\css/base.css',
|
'%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
|
||||||
'/media/\\\\css/base.css'
|
'%s\\\\css/base.css' % settings.ADMIN_MEDIA_PREFIX
|
||||||
)
|
)
|
||||||
for url, path_tuple in data:
|
for url, path_tuple in data:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
file2 in no_label_app
|
|
@ -0,0 +1 @@
|
||||||
|
File in otherdir.
|
|
@ -0,0 +1 @@
|
||||||
|
This file should be ignored.
|
|
@ -0,0 +1 @@
|
||||||
|
This file should be ignored.
|
|
@ -0,0 +1 @@
|
||||||
|
This file should be ignored.
|
|
@ -0,0 +1 @@
|
||||||
|
In app media directory.
|
|
@ -0,0 +1 @@
|
||||||
|
file1 in the app dir
|
|
@ -0,0 +1 @@
|
||||||
|
This file should be ignored.
|
|
@ -0,0 +1 @@
|
||||||
|
Can we find this file?
|
|
@ -0,0 +1 @@
|
||||||
|
Can we find this file?
|
|
@ -0,0 +1,2 @@
|
||||||
|
In STATICFILES_DIRS directory.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Media file.
|
|
@ -0,0 +1 @@
|
||||||
|
Yeah!
|
|
@ -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)
|
|
@ -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'),
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
|
||||||
|
urlpatterns = staticfiles_urlpatterns()
|
|
@ -27,6 +27,7 @@ ALWAYS_INSTALLED_APPS = [
|
||||||
'django.contrib.comments',
|
'django.contrib.comments',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.admindocs',
|
'django.contrib.admindocs',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_test_models():
|
def get_test_models():
|
||||||
|
|
|
@ -41,4 +41,7 @@ urlpatterns = patterns('',
|
||||||
|
|
||||||
# special headers views
|
# special headers views
|
||||||
(r'special_headers/', include('regressiontests.special_headers.urls')),
|
(r'special_headers/', include('regressiontests.special_headers.urls')),
|
||||||
|
|
||||||
|
# static files handling
|
||||||
|
(r'^', include('regressiontests.staticfiles_tests.urls.default')),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue