Ticket #12323: 12323.7.diff

File 12323.7.diff, 70.6 KB (added by Jannis Leidel, 14 years ago)

initial docs

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index 2dabf3b..731c7e1 100644
    a b TEMPLATE_CONTEXT_PROCESSORS = (  
    194194    'django.contrib.auth.context_processors.auth',
    195195    'django.core.context_processors.debug',
    196196    'django.core.context_processors.i18n',
    197     'django.core.context_processors.media',
     197    'django.contrib.staticfiles.context_processors.media',
    198198#    'django.core.context_processors.request',
    199199    'django.contrib.messages.context_processors.messages',
    200200)
    TEMPLATE_CONTEXT_PROCESSORS = (  
    202202# Output to use in template system for invalid (e.g. misspelled) variables.
    203203TEMPLATE_STRING_IF_INVALID = ''
    204204
    205 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
    206 # trailing slash.
    207 # Examples: "http://foo.com/media/", "/media/".
    208 ADMIN_MEDIA_PREFIX = '/media/'
    209 
    210205# Default e-mail address to use for various automated correspondence from
    211206# the site managers.
    212207DEFAULT_FROM_EMAIL = 'webmaster@localhost'
    TEST_DATABASE_COLLATION = None  
    551546
    552547# The list of directories to search for fixtures
    553548FIXTURE_DIRS = ()
     549
     550###############
     551# STATICFILES #
     552###############
     553
     554# Absolute path to the directory that holds media.
     555# Example: "/home/media/media.lawrence.com/static/"
     556STATICFILES_ROOT = ''
     557
     558# URL that handles the static files served from STATICFILES_ROOT.
     559# Example: "http://media.lawrence.com/static/"
     560STATICFILES_URL = '/static/'
     561
     562# A list of locations of additional static files
     563STATICFILES_DIRS = ()
     564
     565# The default file storage backend used during the build process
     566STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
     567
     568# List of finder classes that know how to find static files in
     569# various locations.
     570STATICFILES_FINDERS = (
     571    'django.contrib.staticfiles.finders.FileSystemFinder',
     572    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
     573#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
     574)
     575
     576# URL prefix for admin media -- CSS, JavaScript and images.
     577# Make sure to use a trailing slash.
     578# Examples: "http://foo.com/static/admin/", "/static/admin/".
     579ADMIN_MEDIA_PREFIX = '/static/admin/'
  • django/conf/project_template/settings.py

    diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
    index 3c783d4..7791b47 100644
    a b USE_I18N = True  
    4444USE_L10N = True
    4545
    4646# Absolute path to the directory that holds media.
    47 # Example: "/home/media/media.lawrence.com/"
     47# Example: "/home/media/media.lawrence.com/media/"
    4848MEDIA_ROOT = ''
    4949
    5050# URL that handles the media served from MEDIA_ROOT. Make sure to use a
    MEDIA_ROOT = ''  
    5252# Examples: "http://media.lawrence.com", "http://example.com/media/"
    5353MEDIA_URL = ''
    5454
    55 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
    56 # trailing slash.
    57 # Examples: "http://foo.com/media/", "/media/".
    58 ADMIN_MEDIA_PREFIX = '/media/'
     55# Absolute path to the directory that holds media.
     56# Example: "/home/media/media.lawrence.com/static/"
     57STATICFILES_ROOT = ''
     58
     59# URL that handles the static files served from STATICFILES_ROOT.
     60# Example: "http://static.lawrence.com/", "http://example.com/static/"
     61STATICFILES_URL = '/static/'
     62
     63# URL prefix for admin media -- CSS, JavaScript and images.
     64# Make sure to use a trailing slash.
     65# Examples: "http://foo.com/static/admin/", "/static/admin/".
     66ADMIN_MEDIA_PREFIX = '/static/admin/'
     67
     68# A list of locations of additional static files
     69STATICFILES_DIRS = ()
     70
     71# List of finder classes that know how to find static files in
     72# various locations.
     73STATICFILES_FINDERS = (
     74    'django.contrib.staticfiles.finders.FileSystemFinder',
     75    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
     76#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
     77)
    5978
    6079# Make this unique, and don't share it with anybody.
    6180SECRET_KEY = ''
    INSTALLED_APPS = (  
    89108    'django.contrib.sessions',
    90109    'django.contrib.sites',
    91110    'django.contrib.messages',
     111    'django.contrib.staticfiles',
    92112    # Uncomment the next line to enable the admin:
    93113    # 'django.contrib.admin',
    94114    # Uncomment the next line to enable admin documentation:
  • new file django/contrib/staticfiles/context_processors.py

    diff --git a/django/contrib/staticfiles/__init__.py b/django/contrib/staticfiles/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/django/contrib/staticfiles/context_processors.py b/django/contrib/staticfiles/context_processors.py
    new file mode 100644
    index 0000000..e162861
    - +  
     1from django.conf import settings
     2
     3def media(request):
     4    return {
     5        'STATICFILES_URL': settings.STATICFILES_URL,
     6        'MEDIA_URL': settings.MEDIA_URL,
     7    }
  • new file django/contrib/staticfiles/finders.py

    diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
    new file mode 100644
    index 0000000..eb8c538
    - +  
     1import os
     2from django.conf import settings
     3from django.db import models
     4from django.core.exceptions import ImproperlyConfigured
     5from django.core.files.storage import default_storage, Storage, FileSystemStorage
     6from django.utils.datastructures import SortedDict
     7from django.utils.functional import memoize, LazyObject
     8from django.utils.importlib import import_module
     9
     10from django.contrib.staticfiles import utils
     11from django.contrib.staticfiles.storage import AppMediaStorage
     12
     13_finders = {}
     14
     15
     16class BaseFinder(object):
     17    """
     18    A base file finder to be used for custom staticfiles finder classes.
     19
     20    """
     21    def find(self, path, all=False):
     22        """
     23        Given a relative file path this ought to find an
     24        absolute file path.
     25
     26        If the ``all`` parameter is ``False`` (default) only
     27        the first found file path will be returned; if set
     28        to ``True`` a list of all found files paths is returned.
     29        """
     30        raise NotImplementedError()
     31
     32    def list(self, ignore_patterns=[]):
     33        """
     34        Given an optional list of paths to ignore, this should return
     35        a three item iterable with path, prefix and a storage instance.
     36        """
     37        raise NotImplementedError()
     38
     39
     40class FileSystemFinder(BaseFinder):
     41    """
     42    A static files finder that uses the ``STATICFILES_DIRS`` setting
     43    to locate files.
     44    """
     45    storages = SortedDict()
     46    locations = set()
     47
     48    def __init__(self, apps=None, *args, **kwargs):
     49        for root in settings.STATICFILES_DIRS:
     50            if isinstance(root, (list, tuple)):
     51                prefix, root = root
     52            else:
     53                prefix = ''
     54            self.locations.add((prefix, root))
     55        # Don't initialize multiple storages for the same location
     56        for prefix, root in self.locations:
     57            self.storages[root] = FileSystemStorage(location=root)
     58        super(FileSystemFinder, self).__init__(*args, **kwargs)
     59
     60    def find(self, path, all=False):
     61        """
     62        Looks for files in the extra media locations
     63        as defined in ``STATICFILES_DIRS``.
     64        """
     65        matches = []
     66        for prefix, root in self.locations:
     67            matched_path = self.find_location(root, path, prefix)
     68            if matched_path:
     69                if not all:
     70                    return matched_path
     71                matches.append(matched_path)
     72        return matches
     73
     74    def find_location(self, root, path, prefix=None):
     75        """
     76        Find a requested static file in a location, returning the found
     77        absolute path (or ``None`` if no match).
     78        """
     79        if prefix:
     80            prefix = '%s/' % prefix
     81            if not path.startswith(prefix):
     82                return None
     83            path = path[len(prefix):]
     84        path = os.path.join(root, path)
     85        if os.path.exists(path):
     86            return path
     87
     88    def list(self, ignore_patterns):
     89        """
     90        List all files in all locations.
     91        """
     92        for prefix, root in self.locations:
     93            storage = self.storages[root]
     94            for path in utils.get_files(storage, ignore_patterns):
     95                yield path, prefix, storage
     96
     97
     98class AppDirectoriesFinder(BaseFinder):
     99    """
     100    A static files finder that looks in the ``media`` directory of each app.
     101    """
     102    storages = {}
     103    storage_class = AppMediaStorage
     104
     105    def __init__(self, apps=None, *args, **kwargs):
     106        if apps is not None:
     107            self.apps = apps
     108        else:
     109            self.apps = models.get_apps()
     110        for app in self.apps:
     111            self.storages[app] = self.storage_class(app)
     112        super(AppDirectoriesFinder, self).__init__(*args, **kwargs)
     113
     114    def list(self, ignore_patterns):
     115        """
     116        List all files in all app storages.
     117        """
     118        for storage in self.storages.itervalues():
     119            if storage.is_usable:
     120                prefix = storage.get_prefix()
     121                for path in utils.get_files(storage, ignore_patterns):
     122                    yield path, prefix, storage
     123
     124    def find(self, path, all=False):
     125        """
     126        Looks for files in the app directories.
     127        """
     128        matches = []
     129        for app in self.apps:
     130            app_matches = self.find_in_app(app, path)
     131            if app_matches:
     132                if not all:
     133                    return app_matches
     134                matches.append(app_matches)
     135        return matches
     136
     137    def find_in_app(self, app, path):
     138        """
     139        Find a requested static file in an app's media locations.
     140        """
     141        storage = self.storages[app]
     142        prefix = storage.get_prefix()
     143        if prefix:
     144            prefix = '%s/' % prefix
     145            if not path.startswith(prefix):
     146                return None
     147            path = path[len(prefix):]
     148        # only try to find a file if the source dir actually exists
     149        if storage.is_usable:
     150            if storage.exists(path):
     151                matched_path = storage.path(path)
     152                if matched_path:
     153                    return matched_path
     154
     155
     156class DefaultStorageFinder(BaseFinder):
     157    """
     158    A static files finder that uses the default storage backend.
     159    """
     160    storage = default_storage
     161
     162    def __init__(self, storage=None, *args, **kwargs):
     163        if storage is not None:
     164            self.storage = storage
     165        # Make sure we have an storage instance here.
     166        if not isinstance(self.storage, (Storage, LazyObject)):
     167            self.storage = self.storage()
     168        super(DefaultStorageFinder, self).__init__(*args, **kwargs)
     169
     170    def find(self, path, all=False):
     171        """
     172        Last resort, looks for files in the default file storage if it's local.
     173        """
     174        try:
     175            self.storage.path('')
     176        except NotImplementedError:
     177            pass
     178        else:
     179            if self.storage.exists(path):
     180                match = self.storage.path(path)
     181                if all:
     182                    match = [match]
     183                return match
     184        return []
     185
     186    def list(self, ignore_patterns):
     187        """
     188        List all files of the storage.
     189        """
     190        for path in utils.get_files(self.storage, ignore_patterns):
     191            yield path, '', self.storage
     192
     193
     194def find(path, all=False):
     195    """
     196    Find a requested static file, first looking in any defined extra media
     197    locations and next in any (non-excluded) installed apps.
     198   
     199    If no matches are found and the static location is local, look for a match
     200    there too.
     201   
     202    If ``all`` is ``False`` (default), return the first matching
     203    absolute path (or ``None`` if no match). Otherwise return a list of
     204    found absolute paths.
     205   
     206    """
     207    matches = []
     208    for finder in get_finders():
     209        result = finder.find(path, all=all)
     210        if not all and result:
     211            return result
     212        if not isinstance(result, (list, tuple)):
     213            result = [result]
     214        matches.extend(result)
     215    if matches:
     216        return matches
     217    # No match.
     218    return all and [] or None
     219
     220def get_finders():
     221    for finder_path in settings.STATICFILES_FINDERS:
     222        yield get_finder(finder_path)
     223
     224def _get_finder(import_path):
     225    """
     226    Imports the message storage class described by import_path, where
     227    import_path is the full Python path to the class.
     228    """
     229    module, attr = import_path.rsplit('.', 1)
     230    try:
     231        mod = import_module(module)
     232    except ImportError, e:
     233        raise ImproperlyConfigured('Error importing module %s: "%s"' %
     234                                   (module, e))
     235    try:
     236        Finder = getattr(mod, attr)
     237    except AttributeError:
     238        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
     239                                   'class.' % (module, attr))
     240    if not issubclass(Finder, BaseFinder):
     241        raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
     242                                   (Finder, BaseFinder))
     243    return Finder()
     244get_finder = memoize(_get_finder, _finders, 1)
  • new file django/contrib/staticfiles/handlers.py

    diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
    new file mode 100644
    index 0000000..ffd8d1d
    - +  
     1import os
     2import urllib
     3from urlparse import urlparse
     4
     5from django.conf import settings
     6from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
     7from django.http import Http404
     8from django.views import static
     9
     10class StaticFilesHandler(WSGIHandler):
     11    """
     12    WSGI middleware that intercepts calls to the static files directory, as
     13    defined by the STATICFILES_URL setting, and serves those files.
     14    """
     15    media_dir = settings.STATICFILES_ROOT
     16    media_url = settings.STATICFILES_URL
     17
     18    def __init__(self, application, media_dir=None):
     19        self.application = application
     20        if media_dir:
     21            self.media_dir = media_dir
     22
     23    def file_path(self, url):
     24        """
     25        Returns the relative path to the media file on disk for the given URL.
     26
     27        The passed URL is assumed to begin with ``media_url``.  If the
     28        resultant file path is outside the media directory, then a ValueError
     29        is raised.
     30        """
     31        # Remove ``media_url``.
     32        relative_url = url[len(self.media_url):]
     33        return urllib.url2pathname(relative_url)
     34
     35    def serve(self, request, path):
     36        from django.contrib.staticfiles import finders
     37        absolute_path = finders.find(path)
     38        if not absolute_path:
     39            raise Http404('%r could not be matched to a static file.' % path)
     40        absolute_path, filename = os.path.split(absolute_path)
     41        return static.serve(request, path=filename, document_root=absolute_path)
     42
     43    def __call__(self, environ, start_response):
     44        media_url_bits = urlparse(self.media_url)
     45        # Ignore all requests if the host is provided as part of the media_url.
     46        # Also ignore requests that aren't under the media path.
     47        if (media_url_bits[1] or
     48                not environ['PATH_INFO'].startswith(media_url_bits[2])):
     49            return self.application(environ, start_response)
     50        request = self.application.request_class(environ)
     51        try:
     52            response = self.serve(request, self.file_path(environ['PATH_INFO']))
     53        except Http404:
     54            status = '404 NOT FOUND'
     55            start_response(status, {'Content-type': 'text/plain'}.items())
     56            return [str('Page not found: %s' % environ['PATH_INFO'])]
     57        status_text = STATUS_CODE_TEXT[response.status_code]
     58        status = '%s %s' % (response.status_code, status_text)
     59        response_headers = [(str(k), str(v)) for k, v in response.items()]
     60        for c in response.cookies.values():
     61            response_headers.append(('Set-Cookie', str(c.output(header=''))))
     62        start_response(status, response_headers)
     63        return response
     64
  • new file django/contrib/staticfiles/management/commands/collectstatic.py

    diff --git a/django/contrib/staticfiles/management/__init__.py b/django/contrib/staticfiles/management/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/django/contrib/staticfiles/management/commands/__init__.py b/django/contrib/staticfiles/management/commands/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
    new file mode 100644
    index 0000000..67c7a80
    - +  
     1import os
     2import sys
     3import shutil
     4from optparse import make_option
     5
     6from django.conf import settings
     7from django.core.files.storage import FileSystemStorage, get_storage_class
     8from django.core.management.base import CommandError, NoArgsCommand
     9
     10from django.contrib.staticfiles import utils, finders
     11
     12class Command(NoArgsCommand):
     13    """
     14    Command that allows to copy or symlink media files from different
     15    locations to the settings.STATICFILES_ROOT.
     16    """
     17    option_list = NoArgsCommand.option_list + (
     18        make_option('--noinput', action='store_false', dest='interactive',
     19            default=True, help="Do NOT prompt the user for input of any "
     20                "kind."),
     21        make_option('-i', '--ignore', action='append', default=[],
     22            dest='ignore_patterns', metavar='PATTERN',
     23            help="Ignore files or directories matching this glob-style "
     24                "pattern. Use multiple times to ignore more."),
     25        make_option('-n', '--dry-run', action='store_true', dest='dry_run',
     26            help="Do everything except modify the filesystem."),
     27        make_option('-l', '--link', action='store_true', dest='link',
     28            help="Create a symbolic link to each file instead of copying."),
     29        make_option('--no-default-ignore', action='store_false',
     30            dest='use_default_ignore_patterns', default=True,
     31            help="Don't ignore the common private glob-style patterns 'CVS', "
     32                "'.*' and '*~'."),
     33    )
     34    help = "Collect static files from apps and other locations in a single location."
     35
     36    def handle_noargs(self, **options):
     37        ignore_patterns = options['ignore_patterns']
     38        if options['use_default_ignore_patterns']:
     39            ignore_patterns += ['CVS', '.*', '*~']
     40        ignore_patterns = list(set(ignore_patterns))
     41        self.copied_files = []
     42        self.symlinked_files = []
     43        self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
     44
     45        try:
     46            self.destination_paths = utils.get_files(self.destination_storage, ignore_patterns)
     47        except OSError:
     48            # The destination storage location may not exist yet. It'll get
     49            # created when the first file is copied.
     50            self.destination_paths = []
     51
     52        try:
     53            self.destination_storage.path('')
     54        except NotImplementedError:
     55            self.destination_local = False
     56        else:
     57            self.destination_local = True
     58
     59        if options.get('link', False):
     60            if sys.platform == 'win32':
     61                raise CommandError("Symlinking is not supported by this "
     62                                   "platform (%s)." % sys.platform)
     63            if not self.destination_local:
     64                raise CommandError("Can't symlink to a remote destination.")
     65
     66        # Warn before doing anything more.
     67        if options.get('interactive'):
     68            confirm = raw_input("""
     69You have requested to collate static files and collect them at the destination
     70location as specified in your settings file, %r.
     71
     72This will overwrite existing files.
     73Are you sure you want to do this?
     74
     75Type 'yes' to continue, or 'no' to cancel: """ % settings.STATICFILES_ROOT)
     76            if confirm != 'yes':
     77                raise CommandError("Static files build cancelled.")
     78
     79        for finder in finders.get_finders():
     80            for source, prefix, storage in finder.list(ignore_patterns):
     81                self.copy_file(source, prefix, storage, **options)
     82
     83        verbosity = int(options.get('verbosity', 1))
     84        count = len(self.copied_files) + len(self.symlinked_files)
     85        if verbosity >= 1:
     86            self.stdout.write("%s static file%s collected.\n" %
     87                              (count, count != 1 and 's' or ''))
     88
     89    def copy_file(self, source, prefix, source_storage, **options):
     90        """
     91        Attempt to copy (or symlink) ``source`` to ``destination``,
     92        returning True if successful.
     93        """
     94        source_path = source_storage.path(source)
     95        if prefix:
     96            destination = '/'.join([prefix, source])
     97        else:
     98            destination = source
     99        dry_run = options.get('dry_run', False)
     100        verbosity = int(options.get('verbosity', 1))
     101
     102        if destination in self.copied_files:
     103            if verbosity >= 2:
     104                self.stdout.write("Skipping duplicate file (already copied "
     105                                  "earlier):\n  %s\n" % destination)
     106            return False
     107        if destination in self.symlinked_files:
     108            if verbosity >= 2:
     109                self.stdout.write("Skipping duplicate file (already linked "
     110                                  "earlier):\n  %s\n" % destination)
     111            return False
     112        if destination in self.destination_paths:
     113            if dry_run:
     114                if verbosity >= 2:
     115                    self.stdout.write("Pretending to delete:\n  %s\n"
     116                                      % destination)
     117            else:
     118                if verbosity >= 2:
     119                    self.stdout.write("Deleting:\n  %s\n" % destination)
     120                self.destination_storage.delete(destination)
     121
     122        if options.get('link', False):
     123            destination_path = self.destination_storage.path(destination)
     124            if dry_run:
     125                if verbosity >= 1:
     126                    self.stdout.write("Pretending to symlink:\n  %s\nto:\n  %s\n"
     127                                      % (source_path, destination_path))
     128            else:
     129                if verbosity >= 1:
     130                    self.stdout.write("Symlinking:\n  %s\nto:\n  %s\n"
     131                                      % (source_path, destination_path))
     132                try:
     133                    os.makedirs(os.path.dirname(destination_path))
     134                except OSError:
     135                    pass
     136                os.symlink(source_path, destination_path)
     137            self.symlinked_files.append(destination)
     138        else:
     139            if dry_run:
     140                if verbosity >= 1:
     141                    self.stdout.write("Pretending to copy:\n  %s\nto:\n  %s\n"
     142                                      % (source_path, destination))
     143            else:
     144                if self.destination_local:
     145                    destination_path = self.destination_storage.path(destination)
     146                    try:
     147                        os.makedirs(os.path.dirname(destination_path))
     148                    except OSError:
     149                        pass
     150                    shutil.copy2(source_path, destination_path)
     151                    if verbosity >= 1:
     152                        self.stdout.write("Copying:\n  %s\nto:\n  %s\n"
     153                                          % (source_path, destination_path))
     154                else:
     155                    source_file = source_storage.open(source)
     156                    self.destination_storage.write(destination, source_file)
     157                    if verbosity >= 1:
     158                        self.stdout.write("Copying:\n  %s\nto:\n  %s\n"
     159                                          % (source_path, destination))
     160            self.copied_files.append(destination)
     161        return True
  • new file django/contrib/staticfiles/management/commands/findstatic.py

    diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py
    new file mode 100644
    index 0000000..0f13277
    - +  
     1import os
     2from optparse import make_option
     3from django.core.management.base import LabelCommand
     4
     5from django.contrib.staticfiles import finders
     6
     7class Command(LabelCommand):
     8    help = "Finds the absolute paths for the given static file(s)."
     9    args = "[file ...]"
     10    label = 'static file'
     11    option_list = LabelCommand.option_list + (
     12        make_option('--first', action='store_false', dest='all', default=True,
     13                    help="Only return the first match for each static file."),
     14    )
     15
     16    def handle_label(self, path, **options):
     17        verbosity = int(options.get('verbosity', 1))
     18        result = finders.find(path, all=options['all'])
     19        if result:
     20            output = '\n  '.join((os.path.realpath(path) for path in result))
     21            self.stdout.write("Found %r here:\n  %s\n" % (path, output))
     22        else:
     23            if verbosity >= 1:
     24                self.stdout.write("No matching file found for %r.\n" % path)
  • new file django/contrib/staticfiles/storage.py

    diff --git a/django/contrib/staticfiles/models.py b/django/contrib/staticfiles/models.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
    new file mode 100644
    index 0000000..07609ad
    - +  
     1import os
     2from django.conf import settings
     3from django.core.exceptions import ImproperlyConfigured
     4from django.core.files.storage import FileSystemStorage
     5from django.utils.importlib import import_module
     6
     7from django.contrib.staticfiles import utils
     8
     9
     10class StaticFilesStorage(FileSystemStorage):
     11    """
     12    Standard file system storage for site media files.
     13   
     14    The defaults for ``location`` and ``base_url`` are
     15    ``STATICFILES_ROOT`` and ``STATICFILES_URL``.
     16    """
     17    def __init__(self, location=None, base_url=None, *args, **kwargs):
     18        if location is None:
     19            location = settings.STATICFILES_ROOT
     20        if base_url is None:
     21            base_url = settings.STATICFILES_URL
     22        if not location:
     23            raise ImproperlyConfigured("You're using the staticfiles app "
     24                "without having set the STATICFILES_ROOT setting. Set it to "
     25                "the absolute path of the directory that holds static media.")
     26        if not base_url:
     27            raise ImproperlyConfigured("You're using the staticfiles app "
     28                "without having set the STATICFILES_URL setting. Set it to "
     29                "URL that handles the files served from STATICFILES_ROOT.")
     30        super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
     31
     32
     33class AppMediaStorage(FileSystemStorage):
     34    """
     35    A file system storage backend that takes an app module and works
     36    for the ``media`` directory of it.
     37    """
     38    source_dir = 'media'
     39
     40    def __init__(self, app, *args, **kwargs):
     41        """
     42        Returns a static file storage if available in the given app.
     43
     44        """
     45        # ``app`` is actually the models module of the app.
     46        # Remove the '.models'.
     47        bits = app.__name__.split('.')[:-1]
     48        self.app = app
     49        self.app_name = bits[-1]
     50        self.app_module = '.'.join(bits)
     51
     52        # The models module (``app``) may be a package in which case
     53        # ``dirname(app.__file__)`` would be wrong.
     54        # Import the actual app as opposed to the models module.
     55        app = import_module(self.app_module)
     56        app_root = os.path.dirname(app.__file__)
     57        self.location = os.path.join(app_root, self.source_dir)
     58        super(AppMediaStorage, self).__init__(self.location, *args, **kwargs)
     59
     60    @property
     61    def is_usable(self):
     62        return os.path.isdir(self.location)
     63
     64    def get_prefix(self):
     65        """
     66        Return the path name that should be prepended to files for this app.
     67        """
     68        if self.app_module == 'django.contrib.admin':
     69            return self.app_name
     70        return None
     71
     72    def get_files(self, ignore_patterns=[]):
     73        """
     74        Return a list containing the relative source paths for all files that
     75        should be copied for an app.
     76        """
     77        files = []
     78        prefix = self.get_prefix()
     79        for path in utils.get_files(self, ignore_patterns):
     80            if prefix:
     81                path = '/'.join([prefix, path])
     82            files.append(path)
     83        return files
  • new file django/contrib/staticfiles/urls.py

    diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py
    new file mode 100644
    index 0000000..f8a30ac
    - +  
     1from django.conf.urls.defaults import patterns, url
     2from django.conf import settings
     3
     4urlpatterns = []
     5
     6# only serve non-fqdn URLs
     7if ':' not in settings.STATICFILES_URL:
     8    urlpatterns += patterns('',
     9        url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
     10    )
  • new file django/contrib/staticfiles/utils.py

    diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py
    new file mode 100644
    index 0000000..11d5269
    - +  
     1import os
     2import fnmatch
     3
     4def get_files(storage, ignore_patterns=[], location=''):
     5    """
     6    Recursively walk the storage directories gathering a complete list of files
     7    that should be copied, returning this list.
     8   
     9    """
     10    def is_ignored(path):
     11        """
     12        Return True or False depending on whether the ``path`` should be
     13        ignored (if it matches any pattern in ``ignore_patterns``).
     14       
     15        """
     16        for pattern in ignore_patterns:
     17            if fnmatch.fnmatchcase(path, pattern):
     18                return True
     19        return False
     20
     21    directories, files = storage.listdir(location)
     22    static_files = [location and '/'.join([location, fn]) or fn
     23                    for fn in files
     24                    if not is_ignored(fn)]
     25    for dir in directories:
     26        if is_ignored(dir):
     27            continue
     28        if location:
     29            dir = '/'.join([location, dir])
     30        static_files.extend(get_files(storage, ignore_patterns, dir))
     31    return static_files
  • new file django/contrib/staticfiles/views.py

    diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py
    new file mode 100644
    index 0000000..ffd5264
    - +  
     1"""
     2Views and functions for serving static files. These are only to be used during
     3development, and SHOULD NOT be used in a production setting.
     4
     5"""
     6import os
     7from django import http
     8from django.views import static
     9
     10from django.contrib.staticfiles import finders
     11
     12
     13def serve(request, path, show_indexes=False):
     14    """
     15    Serve static files from locations inferred from the static files finders.
     16
     17    To use, put a URL pattern such as::
     18
     19        (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve')
     20
     21    in your URLconf. You may also set ``show_indexes`` to ``True`` if you'd
     22    like to serve a basic index of the directory.  This index view will use the
     23    template hardcoded below, but if you'd like to override it, you can create
     24    a template called ``static/directory_index``.
     25    """
     26    absolute_path = finders.find(path)
     27    if not absolute_path:
     28        raise http.Http404('%r could not be matched to a static file.' % path)
     29    absolute_path, filename = os.path.split(absolute_path)
     30    return static.serve(request, path=filename, document_root=absolute_path,
     31                        show_indexes=show_indexes)
  • django/core/context_processors.py

    diff --git a/django/core/context_processors.py b/django/core/context_processors.py
    index 7a59728..f24c6cf 100644
    a b def media(request):  
    7171    Adds media-related context variables to the context.
    7272
    7373    """
    74     return {'MEDIA_URL': settings.MEDIA_URL}
     74    import warnings
     75    warnings.warn(
     76        "The context processor at `django.core.context_processors.media` is " \
     77        "deprecated; use the path `django.contrib.staticfiles.context_processors.media` " \
     78        "instead.",
     79        PendingDeprecationWarning
     80    )
     81    from django.contrib.staticfiles.context_processors import media as media_context_processor
     82    return media_context_processor(request)
    7583
    7684def request(request):
    7785    return {'request': request}
  • django/core/management/base.py

    diff --git a/django/core/management/base.py b/django/core/management/base.py
    index 6b9ce6e..341cd1d 100644
    a b class BaseCommand(object):  
    199199        stderr.
    200200
    201201        """
     202        verbosity = options.get('verbosity', 1)
    202203        # Switch to English, because django-admin.py creates database content
    203204        # like permissions, and those shouldn't contain any translations.
    204205        # But only do this if we can assume we have a working settings file,
    class AppCommand(BaseCommand):  
    297298        """
    298299        raise NotImplementedError()
    299300
     301class OptionalAppCommand(BaseCommand):
     302    """
     303    A management command which optionally takes one or more installed
     304    application names as arguments, and does something with each of them.
     305
     306    If no application names are provided, all the applications are used.
     307
     308    The order in which applications are processed is determined by the order
     309    given in INSTALLED_APPS. This differs from Django's AppCommand (it uses the
     310    order the apps are given in the management command).
     311
     312    Rather than implementing ``handle()``, subclasses must implement
     313    ``handle_app()``, which will be called once for each application.
     314
     315    Subclasses can also optionally implement ``excluded_app()`` to run
     316    processes on apps which were excluded.
     317
     318    """
     319    args = '[appname appname ...]'
     320    option_list = BaseCommand.option_list + (
     321        make_option('-e', '--exclude', dest='exclude', action='append',
     322            default=[], help='App to exclude (use multiple --exclude to '
     323            'exclude multiple apps).'),
     324    )
     325
     326    def handle(self, *app_labels, **options):
     327        from django.db import models
     328        # Get all the apps, checking for common errors.
     329        try:
     330            all_apps = models.get_apps()
     331        except (ImproperlyConfigured, ImportError), e:
     332            raise CommandError("%s. Are you sure your INSTALLED_APPS setting "
     333                               "is correct?" % e)
     334        # Build the app_list.
     335        app_list = []
     336        used = 0
     337        for app in all_apps:
     338            app_label = app.__name__.split('.')[-2]
     339            if not app_labels or app_label in app_labels:
     340                used += 1
     341                if app_label not in options['exclude']:
     342                    app_list.append(app)
     343        # Check that all app_labels were used.
     344        if app_labels and used != len(app_labels):
     345            raise CommandError('Could not find the following app(s): %s' %
     346                               ', '.join(app_labels))
     347        # Handle all the apps (either via handle_app or excluded_app),
     348        # collating any output.
     349        output = []
     350        pre_output = self.pre_handle_apps(**options)
     351        if pre_output:
     352            output.append(pre_output)
     353        for app in all_apps:
     354            if app in app_list:
     355                handle_method = self.handle_app
     356            else:
     357                handle_method = self.excluded_app
     358            app_output = handle_method(app, **options)
     359            if app_output:
     360                output.append(app_output)
     361        post_output = self.post_handle_apps(**options)
     362        if post_output:
     363            output.append(post_output)
     364        return '\n'.join(output)
     365
     366    def handle_app(self, app, **options):
     367        """
     368        Perform the command's actions for ``app``, which will be the
     369        Python module corresponding to an application name given on
     370        the command line.
     371
     372        """
     373        raise NotImplementedError()
     374
     375    def excluded_app(self, app, **options):
     376        """
     377        A hook for commands to parse apps which were excluded.
     378
     379        """
     380
     381    def pre_handle_apps(self, **options):
     382        """
     383        A hook for commands to do something before the applications are
     384        processed.
     385
     386        """
     387
     388    def post_handle_apps(self, **options):
     389        """
     390        A hook for commands to do something after all applications have been
     391        processed.
     392
     393        """
     394
     395
    300396class LabelCommand(BaseCommand):
    301397    """
    302398    A management command which takes one or more arbitrary arguments
  • django/core/management/commands/runserver.py

    diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
    index fc2c694..21391e8 100644
    a b  
    1 from django.core.management.base import BaseCommand, CommandError
    21from optparse import make_option
    32import os
    43import sys
     4import warnings
     5
     6from django.core.management.base import BaseCommand, CommandError
    57
    68class Command(BaseCommand):
    79    option_list = BaseCommand.option_list + (
    class Command(BaseCommand):  
    2022        import django
    2123        from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
    2224        from django.core.handlers.wsgi import WSGIHandler
     25        from django.contrib.staticfiles.handlers import StaticFilesHandler
    2326        if args:
    2427            raise CommandError('Usage is runserver %s' % self.args)
    2528        if not addrport:
    class Command(BaseCommand):  
    5659            translation.activate(settings.LANGUAGE_CODE)
    5760
    5861            try:
    59                 handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
     62                handler = WSGIHandler()
     63                handler = StaticFilesHandler(handler)
     64                # serve admin media like old-school (deprecation pending)
     65                handler = AdminMediaHandler(handler, admin_media_path)
    6066                run(addr, int(port), handler)
    6167            except WSGIServerException, e:
    6268                # Use helpful error messages instead of ugly tracebacks.
  • django/core/servers/basehttp.py

    diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
    index dae4297..2da05de 100644
    a b been reviewed for security issues. Don't use it for production use.  
    88"""
    99
    1010from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
    11 import mimetypes
    1211import os
    1312import re
    14 import stat
    1513import sys
    1614import urllib
     15import warnings
    1716
    1817from django.core.management.color import color_style
    1918from django.utils.http import http_date
    2019from django.utils._os import safe_join
     20from django.contrib.staticfiles.handlers import StaticFilesHandler
     21from django.views import static
    2122
    2223__version__ = "0.1"
    2324__all__ = ['WSGIServer','WSGIRequestHandler']
    class WSGIRequestHandler(BaseHTTPRequestHandler):  
    633634
    634635        sys.stderr.write(msg)
    635636
    636 class AdminMediaHandler(object):
     637
     638class AdminMediaHandler(StaticFilesHandler):
    637639    """
    638640    WSGI middleware that intercepts calls to the admin media directory, as
    639641    defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
    640642    Use this ONLY LOCALLY, for development! This hasn't been tested for
    641643    security and is not super efficient.
    642644    """
    643     def __init__(self, application, media_dir=None):
     645
     646    @property
     647    def media_dir(self):
     648        import django
     649        return os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
     650
     651    @property
     652    def media_url(self):
    644653        from django.conf import settings
    645         self.application = application
    646         if not media_dir:
    647             import django
    648             self.media_dir = \
    649                 os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
    650         else:
    651             self.media_dir = media_dir
    652         self.media_url = settings.ADMIN_MEDIA_PREFIX
     654        return settings.ADMIN_MEDIA_PREFIX
     655
     656    def __init__(self, application, media_dir=None):
     657        warnings.warn('The AdminMediaHandler handler is deprecated; use the '
     658            '`django.contrib.staticfiles.handlers.StaticFilesHandler` instead.',
     659            PendingDeprecationWarning)
     660        super(AdminMediaHandler, self).__init__(application, media_dir)
    653661
    654662    def file_path(self, url):
    655663        """
    656664        Returns the path to the media file on disk for the given URL.
    657665
    658         The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX.  If the
     666        The passed URL is assumed to begin with ``media_url``.  If the
    659667        resultant file path is outside the media directory, then a ValueError
    660668        is raised.
    661669        """
    662         # Remove ADMIN_MEDIA_PREFIX.
     670        # Remove ``media_url``.
    663671        relative_url = url[len(self.media_url):]
    664672        relative_path = urllib.url2pathname(relative_url)
    665673        return safe_join(self.media_dir, relative_path)
    666674
    667     def __call__(self, environ, start_response):
    668         import os.path
    669 
    670         # Ignore requests that aren't under ADMIN_MEDIA_PREFIX. Also ignore
    671         # all requests if ADMIN_MEDIA_PREFIX isn't a relative URL.
    672         if self.media_url.startswith('http://') or self.media_url.startswith('https://') \
    673             or not environ['PATH_INFO'].startswith(self.media_url):
    674             return self.application(environ, start_response)
     675    def serve(self, request, path):
     676        document_root, path = os.path.split(path)
     677        return static.serve(request, path, document_root=document_root)
    675678
    676         # Find the admin file and serve it up, if it exists and is readable.
    677         try:
    678             file_path = self.file_path(environ['PATH_INFO'])
    679         except ValueError: # Resulting file path was not valid.
    680             status = '404 NOT FOUND'
    681             headers = {'Content-type': 'text/plain'}
    682             output = ['Page not found: %s' % environ['PATH_INFO']]
    683             start_response(status, headers.items())
    684             return output
    685         if not os.path.exists(file_path):
    686             status = '404 NOT FOUND'
    687             headers = {'Content-type': 'text/plain'}
    688             output = ['Page not found: %s' % environ['PATH_INFO']]
    689         else:
    690             try:
    691                 fp = open(file_path, 'rb')
    692             except IOError:
    693                 status = '401 UNAUTHORIZED'
    694                 headers = {'Content-type': 'text/plain'}
    695                 output = ['Permission denied: %s' % environ['PATH_INFO']]
    696             else:
    697                 # This is a very simple implementation of conditional GET with
    698                 # the Last-Modified header. It makes media files a bit speedier
    699                 # because the files are only read off disk for the first
    700                 # request (assuming the browser/client supports conditional
    701                 # GET).
    702                 mtime = http_date(os.stat(file_path)[stat.ST_MTIME])
    703                 headers = {'Last-Modified': mtime}
    704                 if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
    705                     status = '304 NOT MODIFIED'
    706                     output = []
    707                 else:
    708                     status = '200 OK'
    709                     mime_type = mimetypes.guess_type(file_path)[0]
    710                     if mime_type:
    711                         headers['Content-Type'] = mime_type
    712                     output = [fp.read()]
    713                     fp.close()
    714         start_response(status, headers.items())
    715         return output
    716679
    717680def run(addr, port, wsgi_handler):
    718681    server_address = (addr, port)
  • docs/index.txt

    diff --git a/docs/index.txt b/docs/index.txt
    index e456d04..61ce622 100644
    a b Other batteries included  
    185185    * :doc:`Signals <topics/signals>`
    186186    * :doc:`Sitemaps <ref/contrib/sitemaps>`
    187187    * :doc:`Sites <ref/contrib/sites>`
     188    * :doc:`Static Files <ref/contrib/staticfiles>`
    188189    * :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
    189190    * :doc:`Unicode in Django <ref/unicode>`
    190191    * :doc:`Web design helpers <ref/contrib/webdesign>`
  • docs/ref/contrib/index.txt

    diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt
    index 90edf72..5e308dc 100644
    a b those packages have.  
    3838   redirects
    3939   sitemaps
    4040   sites
     41   staticfiles
    4142   syndication
    4243   webdesign
    4344
  • new file docs/ref/contrib/staticfiles.txt

    diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
    new file mode 100644
    index 0000000..607bf49
    - +  
     1===================
     2The staticfiles app
     3===================
     4
     5.. module:: django.contrib.staticfiles
     6   :synopsis: An app for handling static files.
     7
     8.. versionadded:: 1.3
     9
     10This is a Django app that provides helpers for serving static files.
     11
     12Helpers
     13=======
     14
     15``media`` context processor
     16---------------------------
     17
     18To refer to static file assets from a template, ensure you have set the
     19:ref:`staticfiles-url` setting to the URL path where the static files are
     20served.
     21
     22Next, add the ``media`` context processor to your
     23:setting:`TEMPLATE_CONTEXT_PROCESSORS` setting::
     24
     25   TEMPLATE_CONTEXT_PROCESSORS = (
     26       'django.contrib.staticfiles.context_processors.media',
     27   )
     28
     29Templates rendered with ``RequestContext`` will now have access to a
     30:ref:`staticfiles-url` context variable, e.g.:
     31
     32.. code-block:: html+django
     33
     34   <link href="{{ STATICFILES_URL }}css/polls.css" rel="stylesheet" type="text/css" />
     35
     36Serving static files during development
     37---------------------------------------
     38
     39.. warning:: Don't use this on production servers.
     40  This feature is **only intended for development**.
     41  Please, don't shoot yourself in the foot. Thanks.
     42
     43To serve static files in and :ref:`staticfiles-url` add the following snippet
     44to the end of your primary URL configuration::
     45
     46   from django.conf import settings
     47   
     48   if settings.DEBUG:
     49       urlpatterns = patterns('django.contrib.staticfiles.views',
     50           url(r'^site_media/static/(?P<path>.*)$', 'serve'),
     51       )
     52
     53Note, the begin of the pattern ``r'^static/'`` should be equal to your
     54``STATICFILES_URL`` setting.
     55
     56Management Commands
     57===================
     58
     59.. highlight:: console
     60
     61.. _collectstatic:
     62
     63collectstatic
     64-------------
     65
     66Collects the static files from all activated finders in the
     67:ref:`staticfiles-storage`::
     68
     69   $ python manage.py collectstatic
     70
     71Duplicate file names are resolved in a similar way to how template resolution
     72works. Files are searched by using the
     73:ref:`enabled finders <staticfiles-finders>`. The default is to look in all
     74locations defined in :ref:`staticfiles-dirs` and in the ``media`` directory
     75of apps specified by the :setting:`INSTALLED_APPS` setting.
     76
     77Some commonly used options are:
     78
     79- ``--noinput``
     80    Do NOT prompt the user for input of any kind.
     81
     82- ``-i PATTERN`` or ``--ignore=PATTERN``
     83    Ignore files or directories matching this glob-style pattern. Use multiple
     84    times to ignore more.
     85
     86- ``-n`` or ``--dry-run``
     87    Do everything except modify the filesystem.
     88
     89- ``-l`` or ``--link``
     90    Create a symbolic link to each file instead of copying.
     91
     92- ``--no-default-ignore``
     93    Don't ignore the common private glob-style patterns ``'CVS'``, ``'.*'``
     94    and ``'*~'``.
     95
     96For a full list of options, refer to the collectstatic management command
     97help by running::
     98
     99   $ python manage.py collectstatic --help
     100
     101.. _findstatic:
     102
     103findstatic
     104----------
     105
     106Searches for one or more relative paths with the enabled finders.
     107For example::
     108
     109   $ python manage.py findstatic css/base.css admin/js/core.js
     110   /home/special.polls.com/core/media/css/base.css
     111   /home/polls.com/core/media/css/base.css
     112   /home/polls.com/src/django/contrib/admin/media/js/core.js
     113
     114By default, all matching locations are found. To only return the first match
     115for each relative path, use the ``--first`` option::
     116
     117   $ python manage.py findstatic css/base.css --first
     118   /home/special.polls.com/core/media/css/base.css
     119
     120Settings
     121========
     122
     123.. highlight:: python
     124
     125STATICFILES_ROOT
     126----------------
     127
     128Default: ``''`` (Empty string)
     129
     130The absolute path to the directory that holds static files::
     131
     132   STATICFILES_ROOT = "/home/polls.com/polls/site_media/static/"
     133
     134This is used by the default static files storage backend (i.e. if you use a
     135different ``STATICFILES_STORAGE``, you don't need to set this).
     136
     137.. _staticfiles-url:
     138
     139STATICFILES_URL
     140---------------
     141
     142Default: ``'/static/'``
     143
     144URL that handles the files served from STATICFILES_ROOT, e.g.::
     145
     146   STATICFILES_URL = '/site_media/static/'
     147
     148Note that this should **always** have a trailing slash.
     149
     150.. _staticfiles-dirs:
     151
     152STATICFILES_DIRS
     153----------------
     154
     155Default: ``[]``
     156
     157This setting defines the additional locations the staticfiles app will
     158traverse if the ``FileSystemFinder`` finder is enabled, e.g. if you use the
     159:ref:`collectstatic` or :ref:`findstatic` management command or use the
     160static file serving view.
     161
     162It should be defined as a sequence of ``(prefix, path)`` tuples, e.g.::
     163
     164   STATICFILES_DIRS = (
     165       ('', '/home/special.polls.com/polls/media'),
     166       ('', '/home/polls.com/polls/media'),
     167       ('common', '/opt/webfiles/common'),
     168   )
     169
     170.. _staticfiles-storage:
     171
     172STATICFILES_STORAGE
     173-------------------
     174
     175Default: ``'django.contrib.staticfiles.storage.StaticFilesStorage'``
     176
     177The storage to use when collecting static files to a single location with
     178the :ref:`collectstatic` management command.
     179
     180.. _staticfiles-finders:
     181
     182STATICFILES_FINDERS
     183-------------------
     184
     185Default::
     186
     187    ("django.contrib.staticfiles.finders.FileSystemFinder",
     188    "django.contrib.staticfiles.finders.AppDirectoriesFinder")
     189
     190The list of finder backends that know how to find static files in
     191various locations.
     192
     193If you know you only keep your files in one of those locations, just omit
     194the unnecessary finders.
     195
     196One finder is disabled by default:
     197:class:`django.contrib.staticfiles.finders.DefaultStorageFinder`. If added to
     198your :ref:`staticfiles-finders` setting, it will look for static files in the
     199default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE`
     200setting.
     201
     202.. note:: When using the ``AppDirectoriesFinder`` finder, make sure
     203   your apps can be found by Django's app loading mechanism. Simply include
     204   a ``models`` module (an empty ``models.py`` file suffices) and add the
     205   app to the :setting:`INSTALLED_APPS` setting of your site.
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    index 6ad5af9..d4e6e53 100644
    a b Default::  
    14821482    ("django.contrib.auth.context_processors.auth",
    14831483    "django.core.context_processors.debug",
    14841484    "django.core.context_processors.i18n",
    1485     "django.core.context_processors.media",
     1485    "django.contrib.staticfiles.context_processors.media",
    14861486    "django.contrib.messages.context_processors.messages")
    14871487
    14881488A tuple of callables that are used to populate the context in ``RequestContext``.
  • docs/ref/templates/api.txt

    diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
    index 2ac4e65..e2614f2 100644
    a b and return a dictionary of items to be merged into the context. By default,  
    309309    ("django.contrib.auth.context_processors.auth",
    310310    "django.core.context_processors.debug",
    311311    "django.core.context_processors.i18n",
    312     "django.core.context_processors.media",
     312    "django.contrib.staticfiles.context_processors.media",
    313313    "django.contrib.messages.context_processors.messages")
    314314
    315315.. versionadded:: 1.2
    If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every  
    432432``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
    433433value of the :setting:`MEDIA_URL` setting.
    434434
     435.. versionchanged:: 1.3
     436    This context processor has been moved to the new :ref:`staticfiles` app.
     437    Please use the new ``django.contrib.staticfiles.context_processors.media``
     438    context processor.
     439
    435440django.core.context_processors.csrf
    436441~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    437442
  • tests/regressiontests/servers/tests.py

    diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
    index 4763982..29f169c 100644
    a b from django.test import TestCase  
    99from django.core.handlers.wsgi import WSGIHandler
    1010from django.core.servers.basehttp import AdminMediaHandler
    1111
     12from django.conf import settings
    1213
    1314class AdminMediaHandlerTests(TestCase):
    1415
    class AdminMediaHandlerTests(TestCase):  
    2526        """
    2627        # Cases that should work on all platforms.
    2728        data = (
    28             ('/media/css/base.css', ('css', 'base.css')),
     29            ('%scss/base.css' % settings.ADMIN_MEDIA_PREFIX, ('css', 'base.css')),
    2930        )
    3031        # Cases that should raise an exception.
    3132        bad_data = ()
    class AdminMediaHandlerTests(TestCase):  
    3435        if os.sep == '/':
    3536            data += (
    3637                # URL, tuple of relative path parts.
    37                 ('/media/\\css/base.css', ('\\css', 'base.css')),
     38                ('%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX, ('\\css', 'base.css')),
    3839            )
    3940            bad_data += (
    40                 '/media//css/base.css',
    41                 '/media////css/base.css',
    42                 '/media/../css/base.css',
     41                '%s/css/base.css' % settings.ADMIN_MEDIA_PREFIX,
     42                '%s///css/base.css' % settings.ADMIN_MEDIA_PREFIX,
     43                '%s../css/base.css' % settings.ADMIN_MEDIA_PREFIX,
    4344            )
    4445        elif os.sep == '\\':
    4546            bad_data += (
    46                 '/media/C:\css/base.css',
    47                 '/media//\\css/base.css',
    48                 '/media/\\css/base.css',
    49                 '/media/\\\\css/base.css'
     47                '%sC:\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
     48                '%s/\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
     49                '%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
     50                '%s\\\\css/base.css' % settings.ADMIN_MEDIA_PREFIX
    5051            )
    5152        for url, path_tuple in data:
    5253            try:
  • new file tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt

    diff --git a/tests/regressiontests/staticfiles_tests/__init__.py b/tests/regressiontests/staticfiles_tests/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/apps/__init__.py b/tests/regressiontests/staticfiles_tests/apps/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py b/tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt b/tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt
    new file mode 100644
    index 0000000..aa264ca
    - +  
     1file2 in no_label_app
  • new file tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden

    diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/models.py b/tests/regressiontests/staticfiles_tests/apps/no_label/models.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/__init__.py b/tests/regressiontests/staticfiles_tests/apps/test/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden b/tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden
    new file mode 100644
    index 0000000..cef6c23
    - +  
     1This file should be ignored.
  • new file tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS

    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS b/tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS
    new file mode 100644
    index 0000000..cef6c23
    - +  
     1This file should be ignored.
  • new file tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~

    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~
    new file mode 100644
    index 0000000..cef6c23
    - +  
     1This file should be ignored.
  • new file tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt

    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt
    new file mode 100644
    index 0000000..169a206
    - +  
     1In app media directory.
  • new file tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt

    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt
    new file mode 100644
    index 0000000..9f9a8d9
    - +  
     1file1 in the app dir
     2 No newline at end of file
  • new file tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme

    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme b/tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme
    new file mode 100644
    index 0000000..d7df09c
    - +  
     1This file should be ignored.
     2 No newline at end of file
  • new file tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt

    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/models.py b/tests/regressiontests/staticfiles_tests/apps/test/models.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt b/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt
    new file mode 100644
    index 0000000..c62c93d
    - +  
     1File in otherdir.
  • new file tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt

    diff --git a/tests/regressiontests/staticfiles_tests/models.py b/tests/regressiontests/staticfiles_tests/models.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt b/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt
    new file mode 100644
    index 0000000..04326a2
    - +  
     1Can we find this file?
  • new file tests/regressiontests/staticfiles_tests/project/documents/test.txt

    diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test.txt b/tests/regressiontests/staticfiles_tests/project/documents/test.txt
    new file mode 100644
    index 0000000..04326a2
    - +  
     1Can we find this file?
  • new file tests/regressiontests/staticfiles_tests/project/documents/test/file.txt

    diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt b/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt
    new file mode 100644
    index 0000000..fdeaa23
    - +  
     1In STATICFILES_DIRS directory.
     2
  • new file tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt

    diff --git a/tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt b/tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt
    new file mode 100644
    index 0000000..466922d
    - +  
     1Media file.
  • new file tests/regressiontests/staticfiles_tests/project/site_media/static/test/storage.txt

    diff --git a/tests/regressiontests/staticfiles_tests/project/site_media/static/test/storage.txt b/tests/regressiontests/staticfiles_tests/project/site_media/static/test/storage.txt
    new file mode 100644
    index 0000000..2eda9ce
    - +  
     1Yeah!
     2 No newline at end of file
  • new file tests/regressiontests/staticfiles_tests/tests.py

    diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
    new file mode 100644
    index 0000000..bf38839
    - +  
     1import tempfile
     2import shutil
     3import os
     4import sys
     5from cStringIO import StringIO
     6import posixpath
     7
     8from django.test import TestCase, Client
     9from django.conf import settings
     10from django.core.exceptions import ImproperlyConfigured
     11from django.core.management import call_command
     12from django.db.models.loading import load_app
     13
     14from django.contrib.staticfiles import finders, storage
     15
     16TEST_ROOT = os.path.dirname(__file__)
     17
     18
     19class StaticFilesTestCase(TestCase):
     20    """
     21    Test case with a couple utility assertions.
     22    """
     23    def setUp(self):
     24        self.old_staticfiles_url = settings.STATICFILES_URL
     25        self.old_staticfiles_root = settings.STATICFILES_ROOT
     26        self.old_staticfiles_dirs = settings.STATICFILES_DIRS
     27        self.old_staticfiles_finders = settings.STATICFILES_FINDERS
     28        self.old_installed_apps = settings.INSTALLED_APPS
     29        self.old_media_root = settings.MEDIA_ROOT
     30        self.old_media_url = settings.MEDIA_URL
     31        self.old_admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
     32
     33        # We have to load these apps to test staticfiles.
     34        load_app('regressiontests.staticfiles_tests.apps.test')
     35        load_app('regressiontests.staticfiles_tests.apps.no_label')
     36        site_media = os.path.join(TEST_ROOT, 'project', 'site_media')
     37        settings.MEDIA_ROOT =  os.path.join(site_media, 'media')
     38        settings.MEDIA_URL = '/media/'
     39        settings.ADMIN_MEDIA_PREFIX = posixpath.join(settings.STATICFILES_URL, 'admin/')
     40        settings.STATICFILES_ROOT = os.path.join(site_media, 'static')
     41        settings.STATICFILES_URL = '/static/'
     42        settings.STATICFILES_DIRS = (
     43            os.path.join(TEST_ROOT, 'project', 'documents'),
     44        )
     45        settings.STATICFILES_FINDERS = (
     46            'django.contrib.staticfiles.finders.FileSystemFinder',
     47            'django.contrib.staticfiles.finders.AppDirectoriesFinder',
     48            'django.contrib.staticfiles.finders.DefaultStorageFinder',
     49        )
     50
     51    def tearDown(self):
     52        settings.MEDIA_ROOT = self.old_media_root
     53        settings.MEDIA_URL = self.old_media_url
     54        settings.ADMIN_MEDIA_PREFIX = self.old_admin_media_prefix
     55        settings.STATICFILES_ROOT = self.old_staticfiles_root
     56        settings.STATICFILES_URL = self.old_staticfiles_url
     57        settings.STATICFILES_DIRS = self.old_staticfiles_dirs
     58        settings.STATICFILES_FINDERS = self.old_staticfiles_finders
     59        settings.INSTALLED_APPS = self.old_installed_apps
     60
     61    def assertFileContains(self, filepath, text):
     62        self.failUnless(text in self._get_file(filepath),
     63                        "'%s' not in '%s'" % (text, filepath))
     64
     65    def assertFileNotFound(self, filepath):
     66        self.assertRaises(IOError, self._get_file, filepath)
     67
     68
     69class BuildStaticTestCase(StaticFilesTestCase):
     70    """
     71    Tests shared by all file-resolving features (collectstatic,
     72    findstatic, and static serve view).
     73
     74    This relies on the asserts defined in UtilityAssertsTestCase, but
     75    is separated because some test cases need those asserts without
     76    all these tests.
     77    """
     78    def setUp(self):
     79        super(BuildStaticTestCase, self).setUp()
     80        self.old_staticfiles_storage = settings.STATICFILES_STORAGE
     81        self.old_root = settings.STATICFILES_ROOT
     82        settings.STATICFILES_ROOT = tempfile.mkdtemp()
     83        self.run_collectstatic()
     84
     85    def tearDown(self):
     86        shutil.rmtree(settings.STATICFILES_ROOT)
     87        settings.STATICFILES_ROOT = self.old_root
     88        super(BuildStaticTestCase, self).tearDown()
     89
     90    def run_collectstatic(self, **kwargs):
     91        call_command('collectstatic', interactive=False, verbosity='0',
     92                     ignore_patterns=['*.ignoreme'], **kwargs)
     93
     94    def _get_file(self, filepath):
     95        assert filepath, 'filepath is empty.'
     96        filepath = os.path.join(settings.STATICFILES_ROOT, filepath)
     97        return open(filepath).read()
     98
     99
     100class TestDefaults(object):
     101    """
     102    A few standard test cases.
     103    """
     104    def test_staticfiles_dirs(self):
     105        """
     106        Can find a file in a STATICFILES_DIRS directory.
     107
     108        """
     109        self.assertFileContains('test.txt', 'Can we find')
     110
     111    def test_staticfiles_dirs_subdir(self):
     112        """
     113        Can find a file in a subdirectory of a STATICFILES_DIRS
     114        directory.
     115
     116        """
     117        self.assertFileContains('subdir/test.txt', 'Can we find')
     118
     119    def test_staticfiles_dirs_priority(self):
     120        """
     121        File in STATICFILES_DIRS has priority over file in app.
     122
     123        """
     124        self.assertFileContains('test/file.txt', 'STATICFILES_DIRS')
     125
     126    def test_app_files(self):
     127        """
     128        Can find a file in an app media/ directory.
     129
     130        """
     131        self.assertFileContains('test/file1.txt', 'file1 in the app dir')
     132
     133
     134class TestBuildStatic(BuildStaticTestCase, TestDefaults):
     135    """
     136    Test ``collectstatic`` management command.
     137    """
     138    def test_ignore(self):
     139        """
     140        Test that -i patterns are ignored.
     141        """
     142        self.assertFileNotFound('test/test.ignoreme')
     143
     144    def test_common_ignore_patterns(self):
     145        """
     146        Common ignore patterns (*~, .*, CVS) are ignored.
     147        """
     148        self.assertFileNotFound('test/.hidden')
     149        self.assertFileNotFound('test/backup~')
     150        self.assertFileNotFound('test/CVS')
     151
     152
     153class TestBuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase, TestDefaults):
     154    """
     155    Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
     156    ``collectstatic`` management command.
     157    """
     158    def run_collectstatic(self):
     159        super(TestBuildStaticExcludeNoDefaultIgnore, self).run_collectstatic(
     160            use_default_ignore_patterns=False)
     161
     162    def test_no_common_ignore_patterns(self):
     163        """
     164        With --no-default-ignore, common ignore patterns (*~, .*, CVS)
     165        are not ignored.
     166
     167        """
     168        self.assertFileContains('test/.hidden', 'should be ignored')
     169        self.assertFileContains('test/backup~', 'should be ignored')
     170        self.assertFileContains('test/CVS', 'should be ignored')
     171
     172
     173class TestBuildStaticDryRun(BuildStaticTestCase):
     174    """
     175    Test ``--dry-run`` option for ``collectstatic`` management command.
     176    """
     177    def run_collectstatic(self):
     178        super(TestBuildStaticDryRun, self).run_collectstatic(dry_run=True)
     179
     180    def test_no_files_created(self):
     181        """
     182        With --dry-run, no files created in destination dir.
     183        """
     184        self.assertEquals(os.listdir(settings.STATICFILES_ROOT), [])
     185
     186
     187if sys.platform != 'win32':
     188    class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults):
     189        """
     190        Test ``--link`` option for ``collectstatic`` management command.
     191
     192        Note that by inheriting ``TestDefaults`` we repeat all
     193        the standard file resolving tests here, to make sure using
     194        ``--link`` does not change the file-selection semantics.
     195        """
     196        def run_collectstatic(self):
     197            super(TestBuildStaticLinks, self).run_collectstatic(link=True)
     198
     199        def test_links_created(self):
     200            """
     201            With ``--link``, symbolic links are created.
     202
     203            """
     204            self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt')))
     205
     206
     207class TestServeStatic(StaticFilesTestCase):
     208    """
     209    Test static asset serving view.
     210    """
     211    urls = "regressiontests.staticfiles_tests.urls"
     212
     213    def _response(self, filepath):
     214        return self.client.get(
     215            posixpath.join(settings.STATICFILES_URL, filepath))
     216
     217    def assertFileContains(self, filepath, text):
     218        self.assertContains(self._response(filepath), text)
     219
     220    def assertFileNotFound(self, filepath):
     221        self.assertEquals(self._response(filepath).status_code, 404)
     222
     223
     224class TestServeAdminMedia(TestServeStatic):
     225    """
     226    Test serving media from django.contrib.admin.
     227    """
     228    def test_serve_admin_media(self):
     229        media_file = posixpath.join(
     230            settings.ADMIN_MEDIA_PREFIX, 'css/base.css')
     231        response = self.client.get(media_file)
     232        self.assertContains(response, 'body')
     233
     234
     235class FinderTestCase(object):
     236    """
     237    Base finder test mixin
     238    """
     239    def test_find_first(self):
     240        src, dst = self.find_first
     241        self.assertEquals(self.finder.find(src), dst)
     242
     243    def test_find_all(self):
     244        src, dst = self.find_all
     245        self.assertEquals(self.finder.find(src, all=True), dst)
     246
     247
     248class TestFileSystemFinder(StaticFilesTestCase, FinderTestCase):
     249    """
     250    Test FileSystemFinder.
     251    """
     252    def setUp(self):
     253        super(TestFileSystemFinder, self).setUp()
     254        self.finder = finders.FileSystemFinder()
     255        test_file_path = os.path.join(TEST_ROOT, 'project/documents/test/file.txt')
     256        self.find_first = ("test/file.txt", test_file_path)
     257        self.find_all = ("test/file.txt", [test_file_path])
     258
     259
     260class TestAppDirectoriesFinder(StaticFilesTestCase, FinderTestCase):
     261    """
     262    Test AppDirectoriesFinder.
     263    """
     264    def setUp(self):
     265        super(TestAppDirectoriesFinder, self).setUp()
     266        self.finder = finders.AppDirectoriesFinder()
     267        test_file_path = os.path.join(TEST_ROOT, 'apps/test/media/test/file1.txt')
     268        self.find_first = ("test/file1.txt", test_file_path)
     269        self.find_all = ("test/file1.txt", [test_file_path])
     270
     271
     272class TestDefaultStorageFinder(StaticFilesTestCase, FinderTestCase):
     273    """
     274    Test DefaultStorageFinder.
     275    """
     276    def setUp(self):
     277        super(TestDefaultStorageFinder, self).setUp()
     278        self.finder = finders.DefaultStorageFinder(
     279            storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT))
     280        test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt')
     281        self.find_first = ("media-file.txt", test_file_path)
     282        self.find_all = ("media-file.txt", [test_file_path])
     283
     284
     285class TestMiscFinder(TestCase):
     286    """
     287    A few misc finder tests.
     288    """
     289    def test_get_finder(self):
     290        self.assertTrue(isinstance(finders.get_finder(
     291            "django.contrib.staticfiles.finders.FileSystemFinder"),
     292            finders.FileSystemFinder))
     293        self.assertRaises(ImproperlyConfigured,
     294            finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder")
     295        self.assertRaises(ImproperlyConfigured,
     296            finders.get_finder, "foo.bar.FooBarFinder")
     297
  • new file tests/regressiontests/staticfiles_tests/urls.py

    diff --git a/tests/regressiontests/staticfiles_tests/urls.py b/tests/regressiontests/staticfiles_tests/urls.py
    new file mode 100644
    index 0000000..061ec64
    - +  
     1from django.conf import settings
     2from django.conf.urls.defaults import *
     3
     4urlpatterns = patterns('',
     5    url(r'^static/(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
     6)
  • tests/runtests.py

    diff --git a/tests/runtests.py b/tests/runtests.py
    index a5f7479..055c910 100755
    a b ALWAYS_INSTALLED_APPS = [  
    2727    'django.contrib.comments',
    2828    'django.contrib.admin',
    2929    'django.contrib.admindocs',
     30    'django.contrib.staticfiles',
    3031]
    3132
    3233def get_test_models():
  • tests/urls.py

    diff --git a/tests/urls.py b/tests/urls.py
    index 01d6408..e06dc33 100644
    a b urlpatterns = patterns('',  
    4141
    4242    # special headers views
    4343    (r'special_headers/', include('regressiontests.special_headers.urls')),
     44
     45    # static files handling
     46    (r'^', include('regressiontests.staticfiles_tests.urls')),
    4447)
Back to Top