Permalink
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...
1 parent a014ee0 commit cfc19f84def07fb950ae8789ed0655eae4f66a92 @jezdez jezdez committed Oct 20, 2010
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
@@ -194,19 +194,14 @@
'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',
)
# 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/'
@@ -44,18 +44,37 @@
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
# trailing slash if there is a path component (optional in other cases).
# 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:
@@ -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)
Oops, something went wrong.

0 comments on commit cfc19f8

Please sign in to comment.