Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #12323 and #11582 -- Extended the ability to handle static file…

…s. 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
  • Loading branch information...
commit cfc19f84def07fb950ae8789ed0655eae4f66a92 1 parent a014ee0
@jezdez jezdez authored
Showing with 2,014 additions and 319 deletions.
  1. +32 −6 django/conf/global_settings.py
  2. +25 −5 django/conf/project_template/settings.py
  3. 0  django/contrib/staticfiles/__init__.py
  4. +7 −0 django/contrib/staticfiles/context_processors.py
  5. +254 −0 django/contrib/staticfiles/finders.py
  6. +72 −0 django/contrib/staticfiles/handlers.py
  7. 0  django/contrib/staticfiles/management/__init__.py
  8. 0  django/contrib/staticfiles/management/commands/__init__.py
  9. +184 −0 django/contrib/staticfiles/management/commands/collectstatic.py
  10. +24 −0 django/contrib/staticfiles/management/commands/findstatic.py
  11. 0  django/contrib/staticfiles/models.py
  12. +84 −0 django/contrib/staticfiles/storage.py
  13. 0  django/contrib/staticfiles/templatetags/__init__.py
  14. +43 −0 django/contrib/staticfiles/templatetags/staticfiles.py
  15. +29 −0 django/contrib/staticfiles/urls.py
  16. +30 −0 django/contrib/staticfiles/utils.py
  17. +159 −0 django/contrib/staticfiles/views.py
  18. +9 −1 django/core/context_processors.py
  19. +8 −2 django/core/management/commands/runserver.py
  20. +23 −62 django/core/servers/basehttp.py
  21. +9 −108 django/views/static.py
  22. +360 −123 docs/howto/static-files.txt
  23. +2 −1  docs/index.txt
  24. +1 −0  docs/ref/contrib/index.txt
  25. +283 −0 docs/ref/contrib/staticfiles.txt
  26. +1 −1  docs/ref/settings.txt
  27. +8 −1 docs/ref/templates/api.txt
  28. +10 −9 tests/regressiontests/servers/tests.py
  29. 0  tests/regressiontests/staticfiles_tests/__init__.py
  30. 0  tests/regressiontests/staticfiles_tests/apps/__init__.py
  31. 0  tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py
  32. 0  tests/regressiontests/staticfiles_tests/apps/no_label/models.py
  33. +1 −0  tests/regressiontests/staticfiles_tests/apps/no_label/static/file2.txt
  34. 0  tests/regressiontests/staticfiles_tests/apps/test/__init__.py
  35. 0  tests/regressiontests/staticfiles_tests/apps/test/models.py
  36. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt
  37. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/static/test/.hidden
  38. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/static/test/CVS
  39. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/static/test/backup~
  40. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/static/test/file.txt
  41. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/static/test/file1.txt
  42. +1 −0  tests/regressiontests/staticfiles_tests/apps/test/static/test/test.ignoreme
  43. 0  tests/regressiontests/staticfiles_tests/models.py
  44. +1 −0  tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt
  45. +1 −0  tests/regressiontests/staticfiles_tests/project/documents/test.txt
  46. +2 −0  tests/regressiontests/staticfiles_tests/project/documents/test/file.txt
  47. +1 −0  tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt
  48. +1 −0  tests/regressiontests/staticfiles_tests/project/site_media/static/test/storage.txt
  49. +330 −0 tests/regressiontests/staticfiles_tests/tests.py
  50. 0  tests/regressiontests/staticfiles_tests/urls/__init__.py
  51. +6 −0 tests/regressiontests/staticfiles_tests/urls/default.py
  52. +3 −0  tests/regressiontests/staticfiles_tests/urls/helper.py
  53. +1 −0  tests/runtests.py
  54. +3 −0  tests/urls.py
View
38 django/conf/global_settings.py
@@ -194,7 +194,7 @@
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
- 'django.core.context_processors.media',
+ 'django.contrib.staticfiles.context_processors.staticfiles',
# 'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
)
@@ -202,11 +202,6 @@
# Output to use in template system for invalid (e.g. misspelled) variables.
TEMPLATE_STRING_IF_INVALID = ''
-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
-# trailing slash.
-# Examples: "http://foo.com/media/", "/media/".
-ADMIN_MEDIA_PREFIX = '/media/'
-
# Default e-mail address to use for various automated correspondence from
# the site managers.
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
@@ -551,3 +546,34 @@
# The list of directories to search for fixtures
FIXTURE_DIRS = ()
+
+###############
+# STATICFILES #
+###############
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/static/"
+STATICFILES_ROOT = ''
+
+# URL that handles the static files served from STATICFILES_ROOT.
+# Example: "http://media.lawrence.com/static/"
+STATICFILES_URL = '/static/'
+
+# A list of locations of additional static files
+STATICFILES_DIRS = ()
+
+# The default file storage backend used during the build process
+STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# URL prefix for admin media -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
View
30 django/conf/project_template/settings.py
@@ -44,7 +44,7 @@
USE_L10N = True
# Absolute path to the directory that holds media.
-# Example: "/home/media/media.lawrence.com/"
+# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
@@ -52,10 +52,29 @@
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
-# trailing slash.
-# Examples: "http://foo.com/media/", "/media/".
-ADMIN_MEDIA_PREFIX = '/media/'
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/static/"
+STATICFILES_ROOT = ''
+
+# URL that handles the static files served from STATICFILES_ROOT.
+# Example: "http://static.lawrence.com/", "http://example.com/static/"
+STATICFILES_URL = '/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# A list of locations of additional static files
+STATICFILES_DIRS = ()
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
# Make this unique, and don't share it with anybody.
SECRET_KEY = ''
@@ -89,6 +108,7 @@
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
+ 'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
View
0  django/contrib/staticfiles/__init__.py
No changes.
View
7 django/contrib/staticfiles/context_processors.py
@@ -0,0 +1,7 @@
+from django.conf import settings
+
+def staticfiles(request):
+ return {
+ 'STATICFILES_URL': settings.STATICFILES_URL,
+ 'MEDIA_URL': settings.MEDIA_URL,
+ }
View
254 django/contrib/staticfiles/finders.py
@@ -0,0 +1,254 @@
+import os
+from django.conf import settings
+from django.db import models
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.storage import default_storage, Storage, FileSystemStorage
+from django.utils.datastructures import SortedDict
+from django.utils.functional import memoize, LazyObject
+from django.utils.importlib import import_module
+
+from django.contrib.staticfiles import utils
+from django.contrib.staticfiles.storage import AppStaticStorage
+
+_finders = {}
+
+
+class BaseFinder(object):
+ """
+ A base file finder to be used for custom staticfiles finder classes.
+
+ """
+ def find(self, path, all=False):
+ """
+ Given a relative file path this ought to find an
+ absolute file path.
+
+ If the ``all`` parameter is ``False`` (default) only
+ the first found file path will be returned; if set
+ to ``True`` a list of all found files paths is returned.
+ """
+ raise NotImplementedError()
+
+ def list(self, ignore_patterns=[]):
+ """
+ Given an optional list of paths to ignore, this should return
+ a three item iterable with path, prefix and a storage instance.
+ """
+ raise NotImplementedError()
+
+
+class FileSystemFinder(BaseFinder):
+ """
+ A static files finder that uses the ``STATICFILES_DIRS`` setting
+ to locate files.
+ """
+ storages = SortedDict()
+ locations = set()
+
+ def __init__(self, apps=None, *args, **kwargs):
+ for root in settings.STATICFILES_DIRS:
+ if isinstance(root, (list, tuple)):
+ prefix, root = root
+ else:
+ prefix = ''
+ self.locations.add((prefix, root))
+ # Don't initialize multiple storages for the same location
+ for prefix, root in self.locations:
+ self.storages[root] = FileSystemStorage(location=root)
+ super(FileSystemFinder, self).__init__(*args, **kwargs)
+
+ def find(self, path, all=False):
+ """
+ Looks for files in the extra media locations
+ as defined in ``STATICFILES_DIRS``.
+ """
+ matches = []
+ for prefix, root in self.locations:
+ matched_path = self.find_location(root, path, prefix)
+ if matched_path:
+ if not all:
+ return matched_path
+ matches.append(matched_path)
+ return matches
+
+ def find_location(self, root, path, prefix=None):
+ """
+ Find a requested static file in a location, returning the found
+ absolute path (or ``None`` if no match).
+ """
+ if prefix:
+ prefix = '%s/' % prefix
+ if not path.startswith(prefix):
+ return None
+ path = path[len(prefix):]
+ path = os.path.join(root, path)
+ if os.path.exists(path):
+ return path
+
+ def list(self, ignore_patterns):
+ """
+ List all files in all locations.
+ """
+ for prefix, root in self.locations:
+ storage = self.storages[root]
+ for path in utils.get_files(storage, ignore_patterns):
+ yield path, prefix, storage
+
+
+class AppDirectoriesFinder(BaseFinder):
+ """
+ A static files finder that looks in the ``media`` directory of each app.
+ """
+ storages = {}
+ storage_class = AppStaticStorage
+
+ def __init__(self, apps=None, *args, **kwargs):
+ if apps is not None:
+ self.apps = apps
+ else:
+ self.apps = models.get_apps()
+ for app in self.apps:
+ self.storages[app] = self.storage_class(app)
+ super(AppDirectoriesFinder, self).__init__(*args, **kwargs)
+
+ def list(self, ignore_patterns):
+ """
+ List all files in all app storages.
+ """
+ for storage in self.storages.itervalues():
+ if storage.exists(''): # check if storage location exists
+ prefix = storage.get_prefix()
+ for path in utils.get_files(storage, ignore_patterns):
+ yield path, prefix, storage
+
+ def find(self, path, all=False):
+ """
+ Looks for files in the app directories.
+ """
+ matches = []
+ for app in self.apps:
+ app_matches = self.find_in_app(app, path)
+ if app_matches:
+ if not all:
+ return app_matches
+ matches.append(app_matches)
+ return matches
+
+ def find_in_app(self, app, path):
+ """
+ Find a requested static file in an app's media locations.
+ """
+ storage = self.storages[app]
+ prefix = storage.get_prefix()
+ if prefix:
+ prefix = '%s/' % prefix
+ if not path.startswith(prefix):
+ return None
+ path = path[len(prefix):]
+ # only try to find a file if the source dir actually exists
+ if storage.exists(path):
+ matched_path = storage.path(path)
+ if matched_path:
+ return matched_path
+
+
+class BaseStorageFinder(BaseFinder):
+ """
+ A base static files finder to be used to extended
+ with an own storage class.
+ """
+ storage = None
+
+ def __init__(self, storage=None, *args, **kwargs):
+ if storage is not None:
+ self.storage = storage
+ if self.storage is None:
+ raise ImproperlyConfigured("The staticfiles storage finder %r "
+ "doesn't have a storage class "
+ "assigned." % self.__class__)
+ # Make sure we have an storage instance here.
+ if not isinstance(self.storage, (Storage, LazyObject)):
+ self.storage = self.storage()
+ super(BaseStorageFinder, self).__init__(*args, **kwargs)
+
+ def find(self, path, all=False):
+ """
+ Looks for files in the default file storage, if it's local.
+ """
+ try:
+ self.storage.path('')
+ except NotImplementedError:
+ pass
+ else:
+ if self.storage.exists(path):
+ match = self.storage.path(path)
+ if all:
+ match = [match]
+ return match
+ return []
+
+ def list(self, ignore_patterns):
+ """
+ List all files of the storage.
+ """
+ for path in utils.get_files(self.storage, ignore_patterns):
+ yield path, '', self.storage
+
+class DefaultStorageFinder(BaseStorageFinder):
+ """
+ A static files finder that uses the default storage backend.
+ """
+ storage = default_storage
+
+
+def find(path, all=False):
+ """
+ Find a requested static file, first looking in any defined extra media
+ locations and next in any (non-excluded) installed apps.
+
+ If no matches are found and the static location is local, look for a match
+ there too.
+
+ If ``all`` is ``False`` (default), return the first matching
+ absolute path (or ``None`` if no match). Otherwise return a list of
+ found absolute paths.
+
+ """
+ matches = []
+ for finder in get_finders():
+ result = finder.find(path, all=all)
+ if not all and result:
+ return result
+ if not isinstance(result, (list, tuple)):
+ result = [result]
+ matches.extend(result)
+ if matches:
+ return matches
+ # No match.
+ return all and [] or None
+
+def get_finders():
+ for finder_path in settings.STATICFILES_FINDERS:
+ yield get_finder(finder_path)
+
+def _get_finder(import_path):
+ """
+ Imports the message storage class described by import_path, where
+ import_path is the full Python path to the class.
+ """
+ module, attr = import_path.rsplit('.', 1)
+ try:
+ mod = import_module(module)
+ except ImportError, e:
+ raise ImproperlyConfigured('Error importing module %s: "%s"' %
+ (module, e))
+ try:
+ Finder = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured('Module "%s" does not define a "%s" '
+ 'class.' % (module, attr))
+ if not issubclass(Finder, BaseFinder):
+ raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
+ (Finder, BaseFinder))
+ return Finder()
+get_finder = memoize(_get_finder, _finders, 1)
View
72 django/contrib/staticfiles/handlers.py
@@ -0,0 +1,72 @@
+import os
+import urllib
+from urlparse import urlparse
+
+from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
+from django.http import Http404
+
+from django.contrib.staticfiles.views import serve
+
+class StaticFilesHandler(WSGIHandler):
+ """
+ WSGI middleware that intercepts calls to the static files directory, as
+ defined by the STATICFILES_URL setting, and serves those files.
+ """
+ def __init__(self, application, media_dir=None):
+ self.application = application
+ if media_dir:
+ self.media_dir = media_dir
+ else:
+ self.media_dir = self.get_media_dir()
+ self.media_url = self.get_media_url()
+
+ def get_media_dir(self):
+ from django.conf import settings
+ return settings.STATICFILES_ROOT
+
+ def get_media_url(self):
+ from django.conf import settings
+ return settings.STATICFILES_URL
+
+ def file_path(self, url):
+ """
+ Returns the relative path to the media file on disk for the given URL.
+
+ The passed URL is assumed to begin with ``media_url``. If the
+ resultant file path is outside the media directory, then a ValueError
+ is raised.
+ """
+ # Remove ``media_url``.
+ relative_url = url[len(self.media_url):]
+ return urllib.url2pathname(relative_url)
+
+ def serve(self, request, path):
+ from django.contrib.staticfiles import finders
+ absolute_path = finders.find(path)
+ if not absolute_path:
+ raise Http404('%r could not be matched to a static file.' % path)
+ absolute_path, filename = os.path.split(absolute_path)
+ return serve(request, path=filename, document_root=absolute_path)
+
+ def __call__(self, environ, start_response):
+ media_url_bits = urlparse(self.media_url)
+ # Ignore all requests if the host is provided as part of the media_url.
+ # Also ignore requests that aren't under the media path.
+ if (media_url_bits[1] or
+ not environ['PATH_INFO'].startswith(media_url_bits[2])):
+ return self.application(environ, start_response)
+ request = self.application.request_class(environ)
+ try:
+ response = self.serve(request, self.file_path(environ['PATH_INFO']))
+ except Http404:
+ status = '404 NOT FOUND'
+ start_response(status, {'Content-type': 'text/plain'}.items())
+ return [str('Page not found: %s' % environ['PATH_INFO'])]
+ status_text = STATUS_CODE_TEXT[response.status_code]
+ status = '%s %s' % (response.status_code, status_text)
+ response_headers = [(str(k), str(v)) for k, v in response.items()]
+ for c in response.cookies.values():
+ response_headers.append(('Set-Cookie', str(c.output(header=''))))
+ start_response(status, response_headers)
+ return response
+
View
0  django/contrib/staticfiles/management/__init__.py
No changes.
View
0  django/contrib/staticfiles/management/commands/__init__.py
No changes.
View
184 django/contrib/staticfiles/management/commands/collectstatic.py
@@ -0,0 +1,184 @@
+import os
+import sys
+import shutil
+from optparse import make_option
+
+from django.conf import settings
+from django.core.files.storage import get_storage_class
+from django.core.management.base import CommandError, NoArgsCommand
+
+from django.contrib.staticfiles import finders
+
+class Command(NoArgsCommand):
+ """
+ Command that allows to copy or symlink media files from different
+ locations to the settings.STATICFILES_ROOT.
+ """
+ option_list = NoArgsCommand.option_list + (
+ make_option('--noinput', action='store_false', dest='interactive',
+ default=True, help="Do NOT prompt the user for input of any "
+ "kind."),
+ make_option('-i', '--ignore', action='append', default=[],
+ dest='ignore_patterns', metavar='PATTERN',
+ help="Ignore files or directories matching this glob-style "
+ "pattern. Use multiple times to ignore more."),
+ make_option('-n', '--dry-run', action='store_true', dest='dry_run',
+ default=False, help="Do everything except modify the filesystem."),
+ make_option('-l', '--link', action='store_true', dest='link',
+ default=False, help="Create a symbolic link to each file instead of copying."),
+ make_option('--no-default-ignore', action='store_false',
+ dest='use_default_ignore_patterns', default=True,
+ help="Don't ignore the common private glob-style patterns 'CVS', "
+ "'.*' and '*~'."),
+ )
+ help = "Collect static files from apps and other locations in a single location."
+
+ def handle_noargs(self, **options):
+ symlink = options['link']
+ ignore_patterns = options['ignore_patterns']
+ if options['use_default_ignore_patterns']:
+ ignore_patterns += ['CVS', '.*', '*~']
+ ignore_patterns = list(set(ignore_patterns))
+ self.copied_files = []
+ self.symlinked_files = []
+ self.unmodified_files = []
+ self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
+
+ try:
+ self.destination_storage.path('')
+ except NotImplementedError:
+ self.destination_local = False
+ else:
+ self.destination_local = True
+
+ if symlink:
+ if sys.platform == 'win32':
+ raise CommandError("Symlinking is not supported by this "
+ "platform (%s)." % sys.platform)
+ if not self.destination_local:
+ raise CommandError("Can't symlink to a remote destination.")
+
+ # Warn before doing anything more.
+ if options.get('interactive'):
+ confirm = raw_input("""
+You have requested to collate static files and collect them at the destination
+location as specified in your settings file.
+
+This will overwrite existing files.
+Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """)
+ if confirm != 'yes':
+ raise CommandError("Static files build cancelled.")
+
+ for finder in finders.get_finders():
+ for source, prefix, storage in finder.list(ignore_patterns):
+ self.copy_file(source, prefix, storage, **options)
+
+ verbosity = int(options.get('verbosity', 1))
+ actual_count = len(self.copied_files) + len(self.symlinked_files)
+ unmodified_count = len(self.unmodified_files)
+ if verbosity >= 1:
+ self.stdout.write("\n%s static file%s %s to '%s'%s.\n"
+ % (actual_count, actual_count != 1 and 's' or '',
+ symlink and 'symlinked' or 'copied',
+ settings.STATICFILES_ROOT,
+ unmodified_count and ' (%s unmodified)'
+ % unmodified_count or ''))
+
+ def copy_file(self, source, prefix, source_storage, **options):
+ """
+ Attempt to copy (or symlink) ``source`` to ``destination``,
+ returning True if successful.
+ """
+ source_path = source_storage.path(source)
+ try:
+ source_last_modified = source_storage.modified_time(source)
+ except (OSError, NotImplementedError):
+ source_last_modified = None
+ if prefix:
+ destination = '/'.join([prefix, source])
+ else:
+ destination = source
+ symlink = options['link']
+ dry_run = options['dry_run']
+ verbosity = int(options.get('verbosity', 1))
+
+ if destination in self.copied_files:
+ if verbosity >= 2:
+ self.stdout.write("Skipping '%s' (already copied earlier)\n"
+ % destination)
+ return False
+
+ if destination in self.symlinked_files:
+ if verbosity >= 2:
+ self.stdout.write("Skipping '%s' (already linked earlier)\n"
+ % destination)
+ return False
+
+ if self.destination_storage.exists(destination):
+ try:
+ destination_last_modified = \
+ self.destination_storage.modified_time(destination)
+ except (OSError, NotImplementedError):
+ # storage doesn't support ``modified_time`` or failed.
+ pass
+ else:
+ destination_is_link= os.path.islink(
+ self.destination_storage.path(destination))
+ if destination_last_modified == source_last_modified:
+ if (not symlink and not destination_is_link):
+ if verbosity >= 2:
+ self.stdout.write("Skipping '%s' (not modified)\n"
+ % destination)
+ self.unmodified_files.append(destination)
+ return False
+ if dry_run:
+ if verbosity >= 2:
+ self.stdout.write("Pretending to delete '%s'\n"
+ % destination)
+ else:
+ if verbosity >= 2:
+ self.stdout.write("Deleting '%s'\n" % destination)
+ self.destination_storage.delete(destination)
+
+ if symlink:
+ destination_path = self.destination_storage.path(destination)
+ if dry_run:
+ if verbosity >= 1:
+ self.stdout.write("Pretending to symlink '%s' to '%s'\n"
+ % (source_path, destination_path))
+ else:
+ if verbosity >= 1:
+ self.stdout.write("Symlinking '%s' to '%s'\n"
+ % (source_path, destination_path))
+ try:
+ os.makedirs(os.path.dirname(destination_path))
+ except OSError:
+ pass
+ os.symlink(source_path, destination_path)
+ self.symlinked_files.append(destination)
+ else:
+ if dry_run:
+ if verbosity >= 1:
+ self.stdout.write("Pretending to copy '%s' to '%s'\n"
+ % (source_path, destination))
+ else:
+ if self.destination_local:
+ destination_path = self.destination_storage.path(destination)
+ try:
+ os.makedirs(os.path.dirname(destination_path))
+ except OSError:
+ pass
+ shutil.copy2(source_path, destination_path)
+ if verbosity >= 1:
+ self.stdout.write("Copying '%s' to '%s'\n"
+ % (source_path, destination_path))
+ else:
+ source_file = source_storage.open(source)
+ self.destination_storage.save(destination, source_file)
+ if verbosity >= 1:
+ self.stdout.write("Copying %s to %s\n"
+ % (source_path, destination))
+ self.copied_files.append(destination)
+ return True
View
24 django/contrib/staticfiles/management/commands/findstatic.py
@@ -0,0 +1,24 @@
+import os
+from optparse import make_option
+from django.core.management.base import LabelCommand
+
+from django.contrib.staticfiles import finders
+
+class Command(LabelCommand):
+ help = "Finds the absolute paths for the given static file(s)."
+ args = "[file ...]"
+ label = 'static file'
+ option_list = LabelCommand.option_list + (
+ make_option('--first', action='store_false', dest='all', default=True,
+ help="Only return the first match for each static file."),
+ )
+
+ def handle_label(self, path, **options):
+ verbosity = int(options.get('verbosity', 1))
+ result = finders.find(path, all=options['all'])
+ if result:
+ output = '\n '.join((os.path.realpath(path) for path in result))
+ self.stdout.write("Found %r here:\n %s\n" % (path, output))
+ else:
+ if verbosity >= 1:
+ self.stdout.write("No matching file found for %r.\n" % path)
View
0  django/contrib/staticfiles/models.py
No changes.
View
84 django/contrib/staticfiles/storage.py
@@ -0,0 +1,84 @@
+import os
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.storage import FileSystemStorage
+from django.utils.importlib import import_module
+
+from django.contrib.staticfiles import utils
+
+
+class StaticFilesStorage(FileSystemStorage):
+ """
+ Standard file system storage for site media files.
+
+ The defaults for ``location`` and ``base_url`` are
+ ``STATICFILES_ROOT`` and ``STATICFILES_URL``.
+ """
+ def __init__(self, location=None, base_url=None, *args, **kwargs):
+ if location is None:
+ location = settings.STATICFILES_ROOT
+ if base_url is None:
+ base_url = settings.STATICFILES_URL
+ if not location:
+ raise ImproperlyConfigured("You're using the staticfiles app "
+ "without having set the STATICFILES_ROOT setting. Set it to "
+ "the absolute path of the directory that holds static media.")
+ if not base_url:
+ raise ImproperlyConfigured("You're using the staticfiles app "
+ "without having set the STATICFILES_URL setting. Set it to "
+ "URL that handles the files served from STATICFILES_ROOT.")
+ super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
+
+
+class AppStaticStorage(FileSystemStorage):
+ """
+ A file system storage backend that takes an app module and works
+ for the ``static`` directory of it.
+ """
+ source_dir = 'static'
+
+ def __init__(self, app, *args, **kwargs):
+ """
+ Returns a static file storage if available in the given app.
+ """
+ # app is actually the models module of the app. Remove the '.models'.
+ bits = app.__name__.split('.')[:-1]
+ self.app_name = bits[-1]
+ self.app_module = '.'.join(bits)
+ # The models module (app) may be a package in which case
+ # dirname(app.__file__) would be wrong. Import the actual app
+ # as opposed to the models module.
+ app = import_module(self.app_module)
+ location = self.get_location(os.path.dirname(app.__file__))
+ super(AppStaticStorage, self).__init__(location, *args, **kwargs)
+
+ def get_location(self, app_root):
+ """
+ Given the app root, return the location of the static files of an app,
+ by default 'static'. We special case the admin app here since it has
+ its static files in 'media'.
+ """
+ if self.app_module == 'django.contrib.admin':
+ return os.path.join(app_root, 'media')
+ return os.path.join(app_root, self.source_dir)
+
+ def get_prefix(self):
+ """
+ Return the path name that should be prepended to files for this app.
+ """
+ if self.app_module == 'django.contrib.admin':
+ return self.app_name
+ return None
+
+ def get_files(self, ignore_patterns=[]):
+ """
+ Return a list containing the relative source paths for all files that
+ should be copied for an app.
+ """
+ files = []
+ prefix = self.get_prefix()
+ for path in utils.get_files(self, ignore_patterns):
+ if prefix:
+ path = '/'.join([prefix, path])
+ files.append(path)
+ return files
View
0  django/contrib/staticfiles/templatetags/__init__.py
No changes.
View
43 django/contrib/staticfiles/templatetags/staticfiles.py
@@ -0,0 +1,43 @@
+from django import template
+from django.utils.encoding import iri_to_uri
+
+register = template.Library()
+
+class StaticFilesPrefixNode(template.Node):
+
+ def __init__(self, varname=None):
+ self.varname = varname
+
+ def render(self, context):
+ try:
+ from django.conf import settings
+ except ImportError:
+ prefix = ''
+ else:
+ prefix = iri_to_uri(settings.STATICFILES_URL)
+ if self.varname is None:
+ return prefix
+ context[self.varname] = prefix
+ return ''
+
+@register.tag
+def get_staticfiles_prefix(parser, token):
+ """
+ Populates a template variable with the prefix (settings.STATICFILES_URL).
+
+ Usage::
+
+ {% get_staticfiles_prefix [as varname] %}
+
+ Examples::
+
+ {% get_staticfiles_prefix %}
+ {% get_staticfiles_prefix as staticfiles_prefix %}
+
+ """
+ tokens = token.contents.split()
+ if len(tokens) > 1 and tokens[1] != 'as':
+ raise template.TemplateSyntaxError(
+ "First argument in '%s' must be 'as'" % tokens[0])
+ return StaticFilesPrefixNode(varname=(len(tokens) > 1 and tokens[2] or None))
+
View
29 django/contrib/staticfiles/urls.py
@@ -0,0 +1,29 @@
+import re
+from django.conf import settings
+from django.conf.urls.defaults import patterns, url, include
+from django.core.exceptions import ImproperlyConfigured
+
+urlpatterns = []
+
+# only serve non-fqdn URLs
+if not settings.DEBUG:
+ urlpatterns += patterns('',
+ url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
+ )
+
+def staticfiles_urlpatterns(prefix=None):
+ """
+ Helper function to return a URL pattern for serving static files.
+ """
+ if settings.DEBUG:
+ return []
+ if prefix is None:
+ prefix = settings.STATICFILES_URL
+ if '://' in prefix:
+ raise ImproperlyConfigured(
+ "The STATICFILES_URL setting is a full URL, not a path and "
+ "can't be used with the urls.staticfiles_urlpatterns() helper.")
+ if prefix.startswith("/"):
+ prefix = prefix[1:]
+ return patterns('',
+ url(r'^%s' % re.escape(prefix), include(urlpatterns)),)
View
30 django/contrib/staticfiles/utils.py
@@ -0,0 +1,30 @@
+import fnmatch
+
+def get_files(storage, ignore_patterns=[], location=''):
+ """
+ Recursively walk the storage directories gathering a complete list of files
+ that should be copied, returning this list.
+
+ """
+ def is_ignored(path):
+ """
+ Return True or False depending on whether the ``path`` should be
+ ignored (if it matches any pattern in ``ignore_patterns``).
+
+ """
+ for pattern in ignore_patterns:
+ if fnmatch.fnmatchcase(path, pattern):
+ return True
+ return False
+
+ directories, files = storage.listdir(location)
+ static_files = [location and '/'.join([location, fn]) or fn
+ for fn in files
+ if not is_ignored(fn)]
+ for dir in directories:
+ if is_ignored(dir):
+ continue
+ if location:
+ dir = '/'.join([location, dir])
+ static_files.extend(get_files(storage, ignore_patterns, dir))
+ return static_files
View
159 django/contrib/staticfiles/views.py
@@ -0,0 +1,159 @@
+"""
+Views and functions for serving static files. These are only to be used during
+development, and SHOULD NOT be used in a production setting.
+
+"""
+import mimetypes
+import os
+import posixpath
+import re
+import stat
+import urllib
+from email.Utils import parsedate_tz, mktime_tz
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
+from django.template import loader, Template, Context, TemplateDoesNotExist
+from django.utils.http import http_date
+
+from django.contrib.staticfiles import finders
+
+
+def serve(request, path, document_root=None, show_indexes=False):
+ """
+ Serve static files below a given point in the directory structure or
+ from locations inferred from the static files finders.
+
+ To use, put a URL pattern such as::
+
+ (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve')
+
+ in your URLconf.
+
+ If you provide the ``document_root`` parameter, the file won't be looked
+ up with the staticfiles finders, but in the given filesystem path, e.g.::
+
+ (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve', {'document_root' : '/path/to/my/files/'})
+
+ You may also set ``show_indexes`` to ``True`` if you'd like to serve a
+ basic index of the directory. This index view will use the
+ template hardcoded below, but if you'd like to override it, you can create
+ a template called ``static/directory_index.html``.
+ """
+ if settings.DEBUG:
+ raise ImproperlyConfigured("The view to serve static files can only "
+ "be used if the DEBUG setting is True")
+ if not document_root:
+ absolute_path = finders.find(path)
+ if not absolute_path:
+ raise Http404("%r could not be matched to a static file." % path)
+ document_root, path = os.path.split(absolute_path)
+ # Clean up given path to only allow serving files below document_root.
+ path = posixpath.normpath(urllib.unquote(path))
+ path = path.lstrip('/')
+ newpath = ''
+ for part in path.split('/'):
+ if not part:
+ # Strip empty path components.
+ continue
+ drive, part = os.path.splitdrive(part)
+ head, part = os.path.split(part)
+ if part in (os.curdir, os.pardir):
+ # Strip '.' and '..' in path.
+ continue
+ newpath = os.path.join(newpath, part).replace('\\', '/')
+ if newpath and path != newpath:
+ return HttpResponseRedirect(newpath)
+ fullpath = os.path.join(document_root, newpath)
+ if os.path.isdir(fullpath):
+ if show_indexes:
+ return directory_index(newpath, fullpath)
+ raise Http404("Directory indexes are not allowed here.")
+ if not os.path.exists(fullpath):
+ raise Http404('"%s" does not exist' % fullpath)
+ # Respect the If-Modified-Since header.
+ statobj = os.stat(fullpath)
+ mimetype, encoding = mimetypes.guess_type(fullpath)
+ mimetype = mimetype or 'application/octet-stream'
+ if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
+ statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
+ return HttpResponseNotModified(mimetype=mimetype)
+ contents = open(fullpath, 'rb').read()
+ response = HttpResponse(contents, mimetype=mimetype)
+ response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
+ response["Content-Length"] = len(contents)
+ if encoding:
+ response["Content-Encoding"] = encoding
+ return response
+
+
+DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en-us" />
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <title>Index of {{ directory }}</title>
+ </head>
+ <body>
+ <h1>Index of {{ directory }}</h1>
+ <ul>
+ {% ifnotequal directory "/" %}
+ <li><a href="../">../</a></li>
+ {% endifnotequal %}
+ {% for f in file_list %}
+ <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
+ {% endfor %}
+ </ul>
+ </body>
+</html>
+"""
+
+def directory_index(path, fullpath):
+ try:
+ t = loader.select_template(['static/directory_index.html',
+ 'static/directory_index'])
+ except TemplateDoesNotExist:
+ t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
+ files = []
+ for f in os.listdir(fullpath):
+ if not f.startswith('.'):
+ if os.path.isdir(os.path.join(fullpath, f)):
+ f += '/'
+ files.append(f)
+ c = Context({
+ 'directory' : path + '/',
+ 'file_list' : files,
+ })
+ return HttpResponse(t.render(c))
+
+def was_modified_since(header=None, mtime=0, size=0):
+ """
+ Was something modified since the user last downloaded it?
+
+ header
+ This is the value of the If-Modified-Since header. If this is None,
+ I'll just return True.
+
+ mtime
+ This is the modification time of the item we're talking about.
+
+ size
+ This is the size of the item we're talking about.
+ """
+ try:
+ if header is None:
+ raise ValueError
+ matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
+ re.IGNORECASE)
+ header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
+ header_len = matches.group(3)
+ if header_len and int(header_len) != size:
+ raise ValueError
+ if mtime > header_mtime:
+ raise ValueError
+ except (AttributeError, ValueError, OverflowError):
+ return True
+ return False
View
10 django/core/context_processors.py
@@ -71,7 +71,15 @@ def media(request):
Adds media-related context variables to the context.
"""
- return {'MEDIA_URL': settings.MEDIA_URL}
+ import warnings
+ warnings.warn(
+ "The context processor at `django.core.context_processors.media` is " \
+ "deprecated; use the path `django.contrib.staticfiles.context_processors.staticfiles` " \
+ "instead.",
+ PendingDeprecationWarning
+ )
+ from django.contrib.staticfiles.context_processors import staticfiles as staticfiles_context_processor
+ return staticfiles_context_processor(request)
def request(request):
return {'request': request}
View
10 django/core/management/commands/runserver.py
@@ -1,7 +1,9 @@
-from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
import os
import sys
+import warnings
+
+from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
@@ -20,6 +22,7 @@ def handle(self, addrport='', *args, **options):
import django
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
from django.core.handlers.wsgi import WSGIHandler
+ from django.contrib.staticfiles.handlers import StaticFilesHandler
if args:
raise CommandError('Usage is runserver %s' % self.args)
if not addrport:
@@ -56,7 +59,10 @@ def inner_run():
translation.activate(settings.LANGUAGE_CODE)
try:
- handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
+ handler = WSGIHandler()
+ handler = StaticFilesHandler(handler)
+ # serve admin media like old-school (deprecation pending)
+ handler = AdminMediaHandler(handler, admin_media_path)
run(addr, int(port), handler)
except WSGIServerException, e:
# Use helpful error messages instead of ugly tracebacks.
View
85 django/core/servers/basehttp.py
@@ -8,16 +8,17 @@
"""
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
-import mimetypes
import os
import re
-import stat
import sys
import urllib
+import warnings
from django.core.management.color import color_style
from django.utils.http import http_date
from django.utils._os import safe_join
+from django.contrib.staticfiles.handlers import StaticFilesHandler
+from django.views import static
__version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler']
@@ -633,86 +634,46 @@ def log_message(self, format, *args):
sys.stderr.write(msg)
-class AdminMediaHandler(object):
+
+class AdminMediaHandler(StaticFilesHandler):
"""
WSGI middleware that intercepts calls to the admin media directory, as
defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
Use this ONLY LOCALLY, for development! This hasn't been tested for
security and is not super efficient.
"""
- def __init__(self, application, media_dir=None):
+
+ def get_media_dir(self):
+ import django
+ return os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
+
+ def get_media_url(self):
from django.conf import settings
- self.application = application
- if not media_dir:
- import django
- self.media_dir = \
- os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
- else:
- self.media_dir = media_dir
- self.media_url = settings.ADMIN_MEDIA_PREFIX
+ return settings.ADMIN_MEDIA_PREFIX
+
+ def __init__(self, application, media_dir=None):
+ warnings.warn('The AdminMediaHandler handler is deprecated; use the '
+ '`django.contrib.staticfiles.handlers.StaticFilesHandler` instead.',
+ PendingDeprecationWarning)
+ super(AdminMediaHandler, self).__init__(application, media_dir)
def file_path(self, url):
"""
Returns the path to the media file on disk for the given URL.
- The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX. If the
+ The passed URL is assumed to begin with ``media_url``. If the
resultant file path is outside the media directory, then a ValueError
is raised.
"""
- # Remove ADMIN_MEDIA_PREFIX.
+ # Remove ``media_url``.
relative_url = url[len(self.media_url):]
relative_path = urllib.url2pathname(relative_url)
return safe_join(self.media_dir, relative_path)
- def __call__(self, environ, start_response):
- import os.path
-
- # 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)
+ def serve(self, request, path):
+ document_root, path = os.path.split(path)
+ return static.serve(request, path, document_root=document_root)
- # Find the admin file and serve it up, if it exists and is readable.
- try:
- file_path = self.file_path(environ['PATH_INFO'])
- except ValueError: # Resulting file path was not valid.
- status = '404 NOT FOUND'
- headers = {'Content-type': 'text/plain'}
- output = ['Page not found: %s' % environ['PATH_INFO']]
- start_response(status, headers.items())
- return output
- if not os.path.exists(file_path):
- status = '404 NOT FOUND'
- headers = {'Content-type': 'text/plain'}
- output = ['Page not found: %s' % environ['PATH_INFO']]
- else:
- try:
- fp = open(file_path, 'rb')
- except IOError:
- status = '401 UNAUTHORIZED'
- headers = {'Content-type': 'text/plain'}
- output = ['Permission denied: %s' % environ['PATH_INFO']]
- else:
- # This is a very simple implementation of conditional GET with
- # the Last-Modified header. It makes media files a bit speedier
- # because the files are only read off disk for the first
- # request (assuming the browser/client supports conditional
- # GET).
- mtime = http_date(os.stat(file_path)[stat.ST_MTIME])
- headers = {'Last-Modified': mtime}
- if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
- status = '304 NOT MODIFIED'
- output = []
- else:
- status = '200 OK'
- mime_type = mimetypes.guess_type(file_path)[0]
- if mime_type:
- headers['Content-Type'] = mime_type
- output = [fp.read()]
- fp.close()
- start_response(status, headers.items())
- return output
def run(addr, port, wsgi_handler):
server_address = (addr, port)
View
117 django/views/static.py
@@ -9,6 +9,7 @@
import re
import stat
import urllib
+import warnings
from email.Utils import parsedate_tz, mktime_tz
from django.template import loader
@@ -16,6 +17,10 @@
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
+from django.contrib.staticfiles.views import \
+ directory_index, was_modified_since, serve as staticfiles_serve
+
+
def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.
@@ -30,111 +35,7 @@ def serve(request, path, document_root=None, show_indexes=False):
but if you'd like to override it, you can create a template called
``static/directory_index.html``.
"""
-
- # Clean up given path to only allow serving files below document_root.
- path = posixpath.normpath(urllib.unquote(path))
- path = path.lstrip('/')
- newpath = ''
- for part in path.split('/'):
- if not part:
- # Strip empty path components.
- continue
- drive, part = os.path.splitdrive(part)
- head, part = os.path.split(part)
- if part in (os.curdir, os.pardir):
- # Strip '.' and '..' in path.
- continue
- newpath = os.path.join(newpath, part).replace('\\', '/')
- if newpath and path != newpath:
- return HttpResponseRedirect(newpath)
- fullpath = os.path.join(document_root, newpath)
- if os.path.isdir(fullpath):
- if show_indexes:
- return directory_index(newpath, fullpath)
- raise Http404("Directory indexes are not allowed here.")
- if not os.path.exists(fullpath):
- raise Http404('"%s" does not exist' % fullpath)
- # Respect the If-Modified-Since header.
- statobj = os.stat(fullpath)
- mimetype, encoding = mimetypes.guess_type(fullpath)
- mimetype = mimetype or 'application/octet-stream'
- if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
- statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
- return HttpResponseNotModified(mimetype=mimetype)
- contents = open(fullpath, 'rb').read()
- response = HttpResponse(contents, mimetype=mimetype)
- response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
- response["Content-Length"] = len(contents)
- if encoding:
- response["Content-Encoding"] = encoding
- return response
-
-DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <meta http-equiv="Content-Language" content="en-us" />
- <meta name="robots" content="NONE,NOARCHIVE" />
- <title>Index of {{ directory }}</title>
- </head>
- <body>
- <h1>Index of {{ directory }}</h1>
- <ul>
- {% ifnotequal directory "/" %}
- <li><a href="../">../</a></li>
- {% endifnotequal %}
- {% for f in file_list %}
- <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
- {% endfor %}
- </ul>
- </body>
-</html>
-"""
-
-def directory_index(path, fullpath):
- try:
- t = loader.select_template(['static/directory_index.html',
- 'static/directory_index'])
- except TemplateDoesNotExist:
- t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
- files = []
- for f in os.listdir(fullpath):
- if not f.startswith('.'):
- if os.path.isdir(os.path.join(fullpath, f)):
- f += '/'
- files.append(f)
- c = Context({
- 'directory' : path + '/',
- 'file_list' : files,
- })
- return HttpResponse(t.render(c))
-
-def was_modified_since(header=None, mtime=0, size=0):
- """
- Was something modified since the user last downloaded it?
-
- header
- This is the value of the If-Modified-Since header. If this is None,
- I'll just return True.
-
- mtime
- This is the modification time of the item we're talking about.
-
- size
- This is the size of the item we're talking about.
- """
- try:
- if header is None:
- raise ValueError
- matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
- re.IGNORECASE)
- header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
- header_len = matches.group(3)
- if header_len and int(header_len) != size:
- raise ValueError
- if mtime > header_mtime:
- raise ValueError
- except (AttributeError, ValueError, OverflowError):
- return True
- return False
+ warnings.warn("The view at `django.views.static.serve` is deprecated; "
+ "use the path `django.contrib.staticfiles.views.serve` "
+ "instead.", PendingDeprecationWarning)
+ return staticfiles_serve(request, path, document_root, show_indexes)
View
483 docs/howto/static-files.txt
@@ -1,162 +1,399 @@
-=========================
-How to serve static files
-=========================
+=====================
+Managing static files
+=====================
-.. module:: django.views.static
- :synopsis: Serving of static files during development.
+.. currentmodule:: django.contrib.staticfiles
-Django itself doesn't serve static (media) files, such as images, style sheets,
-or video. It leaves that job to whichever Web server you choose.
+.. versionadded:: 1.3
-The reasoning here is that standard Web servers, such as Apache_, lighttpd_ and
-Cherokee_, are much more fine-tuned at serving static files than a Web
-application framework.
+Django developers mostly concern themselves with the dynamic parts of web
+applications -- the views and templates that render anew for each request. But
+web applications have other parts: the static media files (images, CSS,
+Javascript, etc.) that are needed to render a complete web page.
-With that said, Django does support static files **during development**. You can
-use the :func:`django.views.static.serve` view to serve media files.
+For small projects, this isn't a big deal, because you can just keep the media
+somewhere your web server can find it. However, in bigger projects -- especially
+those comprised of multiple apps -- dealing with the multiple sets of static
+files provided by each application starts to get tricky.
-.. _Apache: http://httpd.apache.org/
-.. _lighttpd: http://www.lighttpd.net/
-.. _Cherokee: http://www.cherokee-project.com/
+That's what ``django.contrib.staticfiles`` is for: it collects media from each
+of your applications (and any other places you specify) into a single location
+that can easily be served in production.
-.. seealso::
+.. note::
- If you just need to serve the admin media from a nonstandard location, see
- the :djadminopt:`--adminmedia` parameter to :djadmin:`runserver`.
+ If you've used the `django-staticfiles`_ third-party app before, then
+ ``django.contrib.staticfiles`` will look very familiar. That's because
+ they're essentially the same code: ``django.contrib.staticfiles`` started
+ its life as `django-staticfiles`_ and was merged into Django 1.3.
+
+ 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
-production setting. Use this only for development.
+Using ``django.contrib.staticfiles``
+====================================
-For information on serving static files in an Apache production environment,
-see the :ref:`Django mod_wsgi documentation <serving-media-files>`.
+Here's the basic usage in a nutshell:
-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',
- {'document_root': '/path/to/media'}),
+ Next, you'll need to edit :setting:`STATICFILES_ROOT` to point to where
+ you'd like your static media stored. For example::
-...where ``site_media`` is the URL where your media will be rooted, and
-``/path/to/media`` is the filesystem root for your media. This will call the
-:func:`~django.views.static.serve` view, passing in the path from the URLconf
-and the (required) ``document_root`` parameter.
+ STATICFILES_ROOT = "/home/jacob/projects/mysite.com/static_media"
-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
- ``/site_media/foo.jpg``.
+ There are a number of other options available that let you control *how*
+ media is stored, where ``staticfiles`` searches for files, and how files
+ will be served; see :ref:`the staticfiles settings reference
+ <staticfiles-settings>` for details.
- * The file ``/path/to/media/css/mystyles.css`` will be made available
- at the URL ``/site_media/css/mystyles.css``.
+ 3. Run the :djadmin:`collectstatic` management command::
- * The file ``/path/bar.jpg`` will not be accessible, because it doesn't
- fall under the document root.
+ ./manage.py collectstatic
-Of course, it's not compulsory to use a fixed string for the
-``'document_root'`` value. You might wish to make that an entry in your
-settings file and use the setting value there. That will allow you and
-other developers working on the code to easily change the value as
-required. For example, if we have a line in ``settings.py`` that says::
+ This'll churn through your static file storage and move them into the
+ directory given by :setting:`STATICFILES_ROOT`.
- 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
- ...
- (r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': settings.STATIC_DOC_ROOT}),
+ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+ urlpatterns += staticfiles_urlpatterns()
-Be careful not to use the same path as your :setting:`ADMIN_MEDIA_PREFIX` (which defaults
-to ``/media/``) as this will overwrite your URLconf entry.
+ to the bottom of your URLconf. See :ref:`staticfiles-development` for
+ 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
-:func:`~django.views.static.serve` view. This is ``False`` by default. If it's
-``True``, Django will display file listings for directories.
+ However you choose to deploy those files, you'll probably need to refer
+ to them in your templates. The easiest method is to use the included
+ context processor which will allow template code like:
-For example::
+ .. code-block:: html+django
- (r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': '/path/to/media', 'show_indexes': True}),
+ <img src="{{ STATICFILES_URL }}images/hi.jpg />
-You can customize the index view by creating a template called
-``static/directory_index.html``. That template gets two objects in its context:
+ See :ref:`staticfiles-in-templates` for more details, including an
+ alternate method (using a template tag).
- * ``directory`` -- the directory name (a string)
- * ``file_list`` -- a list of file names (as strings) in the directory
+Those are the basics. For more details on common configuration options, read on;
+for a detailed reference of the settings, commands, and other bits included with
+the framework see :doc:`the staticfiles reference </ref/contrib/staticfiles>`.
-Here's the default ``static/directory_index.html`` template:
+.. _staticfiles-in-templates:
-.. code-block:: html+django
+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.
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <meta http-equiv="Content-Language" content="en-us" />
- <meta name="robots" content="NONE,NOARCHIVE" />
- <title>Index of {{ directory }}</title>
- </head>
- <body>
- <h1>Index of {{ directory }}</h1>
- <ul>
- {% for f in file_list %}
- <li><a href="{{ f }}">{{ f }}</a></li>
- {% endfor %}
- </ul>
- </body>
- </html>
-
-.. versionchanged:: 1.0.3
- Prior to Django 1.0.3, there was a bug in the view that provided directory
- listings. The template that was loaded had to be called
- ``static/directory_listing`` (with no ``.html`` extension). For backwards
- compatibility with earlier versions, Django will still load templates with
- the older (no extension) name, but it will prefer the
- ``directory_index.html`` version.
-
-Limiting use to DEBUG=True
-==========================
-
-Because URLconfs are just plain Python modules, you can use Python logic to
-make the static-media view available only in development mode. This is a handy
-trick to make sure the static-serving view doesn't slip into a production
-setting by mistake.
-
-Do this by wrapping an ``if DEBUG`` statement around the
-:func:`django.views.static.serve` inclusion. Here's a full example URLconf::
-
- from django.conf.urls.defaults import *
- from django.conf import settings
-
- urlpatterns = patterns('',
- (r'^articles/2003/$', 'news.views.special_case_2003'),
- (r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive'),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'news.views.article_detail'),
+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',
)
- if settings.DEBUG:
- urlpatterns += patterns('',
- (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}),
+Once that's done, you can refer to :setting:`STATICFILES_URL` in your templates:
+
+.. code-block:: html+django
+
+ <img src="{{ STATICFILES_URL }}images/hi.jpg />
+
+If ``{{ STATICFILES_URL }}`` isn't working in your template, you're probably not
+using :class:`~django.template.RequestContext` when rendering the template.
+
+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`.
+
+With a template tag
+-------------------
+
+The second option is the :ttag:`get_staticfiles_prefix` template tag. You can
+use this if you're not using :class:`~django.template.RequestContext`, or if you
+need more control over exactly where and how :setting:`STATICFILES_URL` is
+injected into the template. Here's an example:
+
+.. code-block:: html+django
+
+ {% 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:
+
+.. code-block:: html+django
+
+ {% load staticfiles %}
+ {% get_staticfiles_prefix as STATIC_PREFIX %}
+
+ <img src="{{ STATIC_PREFIX }}images/hi.jpg" />
+ <img src="{{ STATIC_PREFIX }}images/hi2.jpg" />
+
+.. _staticfiles-development:
+
+Serving static files in development
+===================================
+
+The static files tools are mostly designed to help with getting static media
+successfully deployed into production. This usually means a separate, dedicated
+media server, which is a lot of overhead to mess with when developing locally.
+Thus, the ``staticfiles`` app ships with a quick and dirty helper view that you
+can use to serve media locally in development.
+
+To enable this view, you'll add a couple of lines to your URLconf. The first
+line goes at the top of the file, and the last line at the bottom::
+
+ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+
+ # ... the rest of your URLconf goes here ...
+
+ urlpatterns += staticfiles_urlpatterns()
+
+This will inspect your :setting:`STATICFILES_URL` and
+:setting:`STATICFILES_ROOT` settings and wire up the view to serve static media
+accordingly. Remember to run :djadmin:`collectstatic` when your media changes;
+the view only serves static files that have been collected.
+
+.. warning::
+
+ This will only work if :setting:`DEBUG` is ``True``.
+
+ That's because this view is **grossly inefficient** and probably
+ **insecure**. This is only intended for local development, and should
+ **never be used in production**.
+
+For a few more details, including an alternate method of enabling this view,
+see :ref:`staticfiles-development-view`.
+
+.. _staticfiles-production:
+
+Serving static files in production
+==================================
+
+The basic outline of putting static files into production a simple: un the
+:djadmin:`collectstatic` command when static media changes, then arrange for the
+collected media directory (:setting:`STATICFILES_ROOT`) to be moved to the media
+server and served.
+
+Of course, as with all deployment tasks, the devil's in the details. Every
+production setup will be a bit different, so you'll need to adapt the basic
+outline to fit your needs. Below are a few common patterns that might help.
+
+Serving the app and your static files from the same server
+----------------------------------------------------------
+
+If you want to serve your media from the same server that's already serving your
+app, the basic outline gets modified to look something like:
+
+ * Push your code up to the deployment server.
+ * On the server, run :djadmin:`collectmedia` to move all the media into
+ :setting:`STATICFILES_ROOT`.
+ * Point your web server at :setting:`STATICFILES_ROOT`. For example, here's
+ of :ref:`how to do this under Apache and mod_wsgi <serving-media-files>`.
+
+You'll probably want to automate this process, especially if you've got multiple
+web servers. There's any number of ways to do this automation, but one option
+that many Django developers enjoy is `Fabric`__.
+
+__ http://fabfile.org/
+
+Below, and in the following sections, we'll show off a few example fabfiles
+(i.e. Fabric scripts) that automate these media deployment options. The syntax
+of a fabfile is fairly streightforward but won't be covered here; consult `Fabric's documentation`__, for a complete explanation of the syntax..
+
+__ http://docs.fabfile.org/
+
+So, a fabfile to deploy media to a couple of web servers might look something
+like::
+
+ from fabric.api import *
+
+ # Hosts to deploy onto
+ env.hosts = ['www1.example.com', 'www2.example.com']
+
+ # Where your project code lives on the server
+ env.project_root = '/home/www/myproject'
+
+ def deploy_static():
+ with cd(env.project_root):
+ run('./manage.py collectstatic')
+
+Serving static files from a dedicated media server
+--------------------------------------------------
+
+Most larger Django apps use a separate Web server -- i.e., one that's not also
+running Django -- for serving media. This server often runs a different type of
+web server -- faster but less full-featured. Some good choices are:
+
+ * lighttpd_
+ * Nginx_
+ * TUX_
+ * Cherokee_
+ * A stripped-down version of Apache_
+
+.. _lighttpd: http://www.lighttpd.net/
+.. _Nginx: http://wiki.nginx.org/Main
+.. _TUX: http://en.wikipedia.org/wiki/TUX_web_server
+.. _Apache: http://httpd.apache.org/
+.. _Cherokee: http://www.cherokee-project.com/
+
+Configuring these servers is out of scope of this document; check each server's
+respective documentation for instructions.
+
+Since your media server won't be running Django, you'll need to modify the
+deployment strategy to look something like:
+
+ * When your media changes, run :djadmin:`collectstatic` locally.
+ * Push your local :setting:`STATICFILES_ROOT` up to the media server
+ into the directory that's being served. ``rsync`` is a good
+ choice for this step since it only needs to transfer the
+ bits of static media that have changed.
+
+Here's how this might look in a fabfile::
+
+ from fabric.api import *
+ from fabric.contrib import project
+
+ # Where the static files get collected locally
+ env.local_static_root = '/tmp/static'
+
+ # Where the static files should go remotely
+ env.remote_static_root = '/home/www/media.example.com'
+
+ @roles('media')
+ def deploy_static():
+ local('./manage.py collectstatic')
+ project.rysnc_project(
+ remote_dir = env.remote_static_root,
+ local_dir = env.local_static_root,
+ delete = True
)
-This code is straightforward. It imports the settings and checks the value of
-the :setting:`DEBUG` setting. If it evaluates to ``True``, then ``site_media``
-will be associated with the ``django.views.static.serve`` view. If not, then the
-view won't be made available.
+.. _staticfiles-from-cdn:
+
+Serving static media from a cloud service or CDN
+------------------------------------------------
+
+Another common tactic is to serve media from a cloud storage provider like
+Amazon's S3__ and/or a CDN (content delivery network). This lets you ignore the
+problems of serving media, and can often make for faster-loading webpages
+(especially when using a CDN).
+
+When using these services, the basic workflow would look a bit like the above,
+except that instead of using ``rsync`` to transfer your media to the server
+you'd need to transfer the media to the storage provider or CDN.
+
+There's any number of ways you might do this, but if the provider has an API a
+:doc:`custom file storage backend </howto/custom-file-storage>` will make the
+process incredibly simple. If you've written or are using a 3rd party custom
+storage backend, you can tell :djadmin:`collectstatic` to use it by setting
+:setting:`STATICFILES_STORAGE` to the storage engine.
+
+For example, if you've written an S3 storage backend in
+``myproject.storage.S3Storage`` you could use it with::
+
+ STATICFILES_STORAGE = 'storages.backends.s3.S3Storage'
+
+Once that's done, all you have to do is run :djadmin:`collectstatic` and your
+media would be pushed through your storage package up to S3. If you later needed
+to swich to a different storage provider, it could be as simple as changing your
+:setting:`STATICFILES_STORAGE` setting.
+
+For details on how you'd write one of these backends,
+:doc:`/howto/custom-file-storage`.
+
+.. seealso::
-Of course, the catch here is that you'll have to remember to set ``DEBUG=False``
-in your production settings file. But you should be doing that anyway.
+ The `django-storages`__ project is a 3rd party app that provides many
+ storage backends for many common file storage APIs (including S3).
+
+__ http://s3.amazonaws.com/
+__ http://code.welldev.org/django-storages/wiki/S3Storage
+
+Upgrading from ``django-staticfiles``
+=====================================
+
+``django.contrib.staticfiles`` began its life as `django-staticfiles`_. If
+you're upgrading from `django-staticfiles`_ to ``django.contrib.staticfiles``,
+you'll need to make a few changes:
+
+ * Application files should now live in a ``static`` directory in each app
+ (`django-staticfiles`_ used the name ``media``, which was slightly
+ confusing).
+
+ * The management commands ``build_static`` and ``resolve_static`` are now
+ called :djadmin:`collectstatic` and :djadmin:`findstatic`.
+
+ * The settings ``STATIC_URL`` and ``STATIC_ROOT`` were renamed to
+ :setting:`STATICFILES_URL` and :setting:`STATICFILES_ROOT`.
+
+ * The settings ``STATICFILES_PREPEND_LABEL_APPS``,
+ ``STATICFILES_MEDIA_DIRNAMES`` and ``STATICFILES_EXCLUDED_APPS`` were
+ removed.
+
+ * The setting ``STATICFILES_RESOLVERS`` was removed, and replaced by the new
+ :setting:`STATICFILES_FINDERS`.
+
+ * The default for :setting:`STATICFILES_STORAGE` was renamed from
+ ``staticfiles.storage.StaticFileStorage`` to
+ ``staticfiles.storage.StaticFilesStorage``
+
+Learn more
+==========
+
+This document has covered the basics and some common usage patterns. For
+complete details on all the settings, commands, template tags, and other pieces
+include in ``django.contrib.staticfiles``, see :doc:`the statcfiles reference
+</ref/contrib/staticfiles>`.
View
3  docs/index.txt
@@ -155,7 +155,7 @@ The development process
:doc:`Apache/mod_python <howto/deployment/modpython>` |
:doc:`FastCGI/SCGI/AJP <howto/deployment/fastcgi>` |
:doc:`Apache authentication <howto/apache-auth>` |
- :doc:`Serving static files <howto/static-files>` |
+ :doc:`Handling static files <howto/static-files>` |
:doc:`Tracking code errors by e-mail <howto/error-reporting>`
Other batteries included
@@ -185,6 +185,7 @@ Other batteries included
* :doc:`Signals <topics/signals>`
* :doc:`Sitemaps <ref/contrib/sitemaps>`
* :doc:`Sites <ref/contrib/sites>`
+ * :doc:`Static Files <ref/contrib/staticfiles>`
* :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
* :doc:`Unicode in Django <ref/unicode>`
* :doc:`Web design helpers <ref/contrib/webdesign>`
View
1  docs/ref/contrib/index.txt
@@ -38,6 +38,7 @@ those packages have.
redirects
sitemaps
sites
+ staticfiles
syndication
webdesign
View
283 docs/ref/contrib/staticfiles.txt
@@ -0,0 +1,283 @@
+===================
+The staticfiles app
+===================
+
+.. module:: django.contrib.staticfiles
+ :synopsis: An app for handling static files.
+
+.. versionadded:: 1.3
+
+``django.contrib.staticfiles`` collects media from each of your applications
+(and any other places you specify) into a single location that can easily be
+served in production.
+
+.. seealso::
+
+ For an introduction to the static files app and some usage examples, see
+ :doc:`/howto/static-files`.
+
+.. _staticfiles-settings:
+
+Settings
+========
+
+.. highlight:: python
+
+The following settings control the behavior of the static files app. Only
+:setting:`STATICFILES_ROOT` is required, but you'll probably also need to
+configure :setting:`STATICFILES_URL` as well.
+
+.. setting:: STATICFILES_ROOT
+
+STATICFILES_ROOT
+----------------
+
+Default: ``''`` (Empty string)
+
+The absolute path to the directory that holds static files::
+
+ STATICFILES_ROOT = "/home/example.com/static/"
+
+This is a **required setting** unless you've overridden
+:setting:`STATICFILES_STORAGE` and are using a custom storage backend.
+
+.. setting:: STATICFILES_URL
+
+STATICFILES_URL
+---------------
+
+Default: ``'/static/'``
+
+The URL that handles the files served from :setting:`STATICFILES_ROOT`, e.g.::
+
+ STATICFILES_URL = '/site_media/static/'
+
+... or perhaps::
+
+ STATICFILES_URL = 'http://media.exmaple.com/'
+
+This should **always** have a trailing slash.
+
+.. setting:: STATICFILES_DIRS
+
+STATICFILES_DIRS
+----------------
+
+Default: ``[]``
+
+This setting defines the additional locations the staticfiles app will traverse
+if the :class:`FileSystemFinder` finder is enabled, e.g. if you use the
+:djadmin:`collectstatic` or :djadmin:`findstatic` management command or use the
+static file serving view.
+
+It should be defined as a sequence of ``(prefix, path)`` tuples, e.g.::
+
+ STATICFILES_DIRS = (
+ ('', '/home/special.polls.com/polls/media'),
+ ('', '/home/polls.com/polls/media'),
+ ('common', '/opt/webfiles/common'),
+ )
+
+.. setting:: STATICFILES_STORAGE
+
+STATICFILES_STORAGE
+-------------------
+
+Default: ``'django.contrib.staticfiles.storage.StaticFilesStorage'``
+
+The file storage engine to use when collecting static files with the
+:djadmin:`collectstatic` management command.
+
+For an example, see :ref:`staticfiles-from-cdn`.
+
+.. setting:: STATICFILES_FINDERS
+
+STATICFILES_FINDERS
+-------------------
+
+Default::
+
+ ("django.contrib.staticfiles.finders.FileSystemFinder",
+ "django.contrib.staticfiles.finders.AppDirectoriesFinder")
+
+The list of finder backends that know how to find static files in
+various locations.
+
+The default will find files stored in the :setting:`STATICFILES_DIRS` setting
+(using :class:`django.contrib.staticfiles.finders.FileSystemFinder`) and in a
+``static`` subdirectory of each app (using
+:class:`django.contrib.staticfiles.finders.AppDirectoriesFinder`)
+
+One finder is disabled by default:
+:class:`django.contrib.staticfiles.finders.DefaultStorageFinder`. If added to
+your :setting:`STATICFILES_FINDERS` setting, it will look for static files in
+the default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE`
+setting.
+
+.. note::
+
+ When using the :class:AppDirectoriesFinder` finder, make sure your apps can
+ be found by Django's app loading mechanism. Simply include a ``models``
+ module (an empty ``models.py`` file suffices) and add the app to the
+ :setting:`INSTALLED_APPS` setting of your site.
+
+Static file finders are currently considered a private interface, and this
+interface is thus undocumented.
+
+Management Commands
+===================
+
+.. highlight:: console
+
+``django.contrib.staticfiles`` exposes two management commands.
+
+collectstatic
+-------------
+
+.. django-admin:: collectstatic
+
+Collects the static files into :setting:`STATICFILES_ROOT`.
+
+Duplicate file names are resolved in a similar way to how template resolution
+works: files from apps later in :setting:`INSTALLED_APPS` overwrite those from
+earlier apps, and files from storage directories later in
+:setting:`STATICFILES_DIRS` overwrite those from earlier. If you're confused,
+the :djadmin:`findstatic` command can help show you where
+
+Files are searched by using the :ref:`enabled finders
+<staticfiles-finders>`. The default is to look in all locations defined in
+:ref:`staticfiles-dirs` and in the ``media`` directory of apps specified by the
+:setting:`INSTALLED_APPS` setting.
+
+Some commonly used options are:
+
+``--noinput``
+ Do NOT prompt the user for input of any kind.
+
+``-i PATTERN`` or ``--ignore=PATTERN``
+ Ignore files or directories matching this glob-style pattern. Use multiple
+ times to ignore more.
+
+``-n`` or ``--dry-run``
+ Do everything except modify the filesystem.
+
+``-l`` or ``--link``
+ Create a symbolic link to each file instead of copying.
+
+``--no-default-ignore``
+ Don't ignore the common private glob-style patterns ``'CVS'``, ``'.*'``
+ and ``'*~'``.
+
+For a full list of options, refer to the commands own help by running::
+
+ $ python manage.py collectstatic --help
+
+findstatic
+----------
+
+.. django-admin:: findstatic
+
+Searches for one or more relative paths with the enabled finders.
+
+For example::
+
+ $ python manage.py findstatic css/base.css admin/js/core.js
+ /home/special.polls.com/core/media/css/base.css
+ /home/polls.com/core/media/css/base.css
+ /home/polls.com/src/django/contrib/admin/media/js/core.js
+
+By default, all matching locations are found. To only return the first match
+for each relative path, use the ``--first`` option::
+
+ $ python manage.py findstatic css/base.css --first
+ /home/special.polls.com/core/media/css/base.css
+
+This is a debugging aid; it'll show you exactly which static file will be
+collected for a given path.
+
+Other Helpers
+=============
+
+The ``media`` context processor
+-------------------------------
+
+.. function:: django.contrib.staticfiles.context_processors.staticfiles
+
+This context processor adds the :setting:`STATICFILES_URL` into each template
+context as the variable ``{{ STATICFILES_URL }}``. To use it, make sure that
+``'django.contrib.staticfiles.context_processors.staticfiles'`` appears
+somewhere in your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
+
+Remember, only templates rendered with :class:`~django.template.RequestContext`
+will have acces to the data provided by this (and any) context processor.
+
+.. templatetag:: get_staticfiles_prefix
+
+The ``get_staticfiles_prefix`` templatetag
+==========================================
+
+.. highlight:: html+django
+
+If you're not using :class:`~django.template.RequestContext`, or if you need
+more control over exactly where and how :setting:`STATICFILES_URL` is injected
+into the template, you can use the :ttag:`get_staticfiles_prefix` template tag
+instead::
+
+ {% load staticfiles %}
+ <img src="{% get_staticfiles_prefix %}images/hi.jpg" />
+
+There's also a second form you can use to avoid extra processing if you need the
+value multiple times::
+
+ {% load staticfiles %}
+ {% get_staticfiles_prefix as STATIC_PREFIX %}
+
+ <img src="{{ STATIC_PREFIX }}images/hi.jpg" />
+ <img src="{{ STATIC_PREFIX }}images/hi2.jpg" />
+
+.. _staticfiles-development-view:
+
+Static file development view
+----------------------------
+
+.. highlight:: python
+
+.. function:: django.contrib.staticfiles.views.serve(request, path)
+
+This view function serves static media in in development.
+
+.. warning::
+
+ This view will only work if :setting:`DEBUG` is ``True``.
+
+ That's because this view is **grossly inefficient** and probably
+ **insecure**. This is only intended for local development, and should
+ **never be used in production**.
+
+To use the view, add the following snippet to the end of your primary URL
+configuration::
+
+ from django.conf import settings
+
+ if settings.DEBUG:
+ urlpatterns = patterns('django.contrib.staticfiles.views',
+ url(r'^static/(?P<path>.*)$', 'serve'),
+ )
+
+Note, the begin of the pattern (``r'^static/'``) should be your
+:setting:`STATICFILES_URL` setting.
+
+Since this is a bit finicky, there's also a helper function that'll do this for you:
+
+.. function:: django.contrib.staticfiles.urls.staticfiles_urlpatterns()
+
+This will return the proper URL pattern for serving static files to your
+already defined pattern list. Use it like this::
+
+ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+
+ # ... the rest of your URLconf here ...
+
+ urlpatterns += staticfiles_urlpatterns()
+
+
View
2  docs/ref/settings.txt
@@ -1482,7 +1482,7 @@ Default::
("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
- "django.core.context_processors.media",
+ "django.contrib.staticfiles.context_processors.staticfiles",
"django.contrib.messages.context_processors.messages")
A tuple of callables that are used to populate the context in ``RequestContext``.
View
9 docs/ref/templates/api.txt
@@ -289,6 +289,8 @@ you'll see below.
Subclassing Context: RequestContext
-----------------------------------
+.. class:: django.template.RequestContext
+
Django comes with a special ``Context`` class,
``django.template.RequestContext``, that acts slightly differently than the
normal ``django.template.Context``. The first difference is that it takes an
@@ -309,7 +311,7 @@ and return a dictionary of items to be merged into the context. By default,
("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
- "django.core.context_processors.media",
+ "django.contrib.staticfiles.context_processors.staticfiles",
"django.contrib.messages.context_processors.messages")
.. versionadded:: 1.2
@@ -432,6 +434,11 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
value of the :setting:`MEDIA_URL` setting.
+.. versionchanged:: 1.3
+ This context processor has been moved to the new :ref:`staticfiles` app.
+ Please use the new ``django.contrib.staticfiles.context_processors.staticfiles``
+ context processor.
+
django.core.context_processors.csrf
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
19 tests/regressiontests/servers/tests.py
@@ -9,6 +9,7 @@
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler
+from django.conf import settings
class AdminMediaHandlerTests(TestCase):
@@ -25,7 +26,7 @@ def test_media_urls(self):
"""
# Cases that should work on all platforms.
data = (
- ('/media/css/base.css', ('css', 'base.css')),
+ ('%scss/base.css' % settings.ADMIN_MEDIA_PREFIX, ('css', 'base.css')),
)
# Cases that should raise an exception.
bad_data = ()
@@ -34,19 +35,19 @@ def test_media_urls(self):
if os.sep == '/':
data += (
# URL, tuple of relative path parts.
- ('/media/\\css/base.css', ('\\css', 'base.css')),
+ ('%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX, ('\\css', 'base.css')),
)
bad_data += (
- '/media//css/base.css',
- '/media////css/base.css',
- '/media/../css/base.css',
+ '%s/css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+ '%s///css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+ '%s../css/base.css' % settings.ADMIN_MEDIA_PREFIX,
)
elif os.sep == '\\':
bad_data += (
- '/media/C:\css/base.css',
- '/media//\\css/base.css',
- '/media/\\css/base.css',
- '/media/\\\\css/base.css'
+ '%sC:\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+ '%s/\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+ '%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+ '%s\\\\css/base.css' % settings.ADMIN_MEDIA_PREFIX
)
for url, path_tuple in data:
try:
View
0  tests/regressiontests/staticfiles_tests/__init__.py
No changes.
View
0  tests/regressiontests/staticfiles_tests/apps/__init__.py
No changes.
View
0  tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py
No changes.
View
0  tests/regressiontests/staticfiles_tests/apps/no_label/models.py
No changes.
View
1  tests/re