Ticket #12323: 12323.3.diff

File 12323.3.diff, 61.7 KB (added by Jannis Leidel, 14 years ago)

Caching the file finders.

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index 2714bfb..6d156ec 100644
    a b TEMPLATE_CONTEXT_PROCESSORS = (  
    193193    'django.contrib.auth.context_processors.auth',
    194194    'django.core.context_processors.debug',
    195195    'django.core.context_processors.i18n',
    196     'django.core.context_processors.media',
     196    'django.contrib.staticfiles.context_processors.media',
    197197#    'django.core.context_processors.request',
    198198    'django.contrib.messages.context_processors.messages',
    199199)
    TEMPLATE_CONTEXT_PROCESSORS = (  
    201201# Output to use in template system for invalid (e.g. misspelled) variables.
    202202TEMPLATE_STRING_IF_INVALID = ''
    203203
    204 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
    205 # trailing slash.
    206 # Examples: "http://foo.com/media/", "/media/".
    207 ADMIN_MEDIA_PREFIX = '/media/'
    208 
    209204# Default e-mail address to use for various automated correspondence from
    210205# the site managers.
    211206DEFAULT_FROM_EMAIL = 'webmaster@localhost'
    TEST_DATABASE_COLLATION = None  
    522517
    523518# The list of directories to search for fixtures
    524519FIXTURE_DIRS = ()
     520
     521###############
     522# STATICFILES #
     523###############
     524
     525# Absolute path to the directory that holds media.
     526# Example: "/home/media/media.lawrence.com/static/"
     527STATICFILES_ROOT = ''
     528
     529# URL that handles the static files served from STATICFILES_ROOT.
     530# Example: "http://media.lawrence.com/static/"
     531STATICFILES_URL = '/static/'
     532
     533# A tuple of two-tuples with a name and the path of additional directories
     534# which hold static files and should be taken into account during resolving
     535STATICFILES_DIRS = ()
     536
     537# The default file storage backend used during the build process
     538STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
     539
     540# List of finder classes that know how to find static files in
     541# various locations.
     542STATICFILES_FINDERS = (
     543    'django.contrib.staticfiles.finders.FileSystemFinder',
     544    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
     545    'django.contrib.staticfiles.finders.StorageFinder',
     546)
     547
     548# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
     549# trailing slash.
     550# Examples: "http://foo.com/media/admin/", "/media/admin/".
     551ADMIN_MEDIA_PREFIX = '/static/admin/'
  • 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..9c7cb3b
    - +  
     1import os
     2from django.conf import settings
     3from django.db import models
     4from django.core.exceptions import ImproperlyConfigured
     5from django.utils.importlib import import_module
     6from django.core.files.storage import default_storage
     7from django.utils.functional import memoize
     8
     9from django.contrib.staticfiles import utils
     10
     11_finders = {}
     12
     13
     14class BaseFinder(object):
     15    """
     16    A base file finder to be used for custom staticfiles finder classes.
     17
     18    """
     19    def find(self, path, all=False):
     20        """
     21        Given a relative file path this ought to find an
     22        absolute file path.
     23
     24        If the ``all`` parameter is ``False`` (default) only
     25        the first found file path will be returned; if set
     26        to ``True`` a list of all found files paths is returned.
     27        """
     28        raise NotImplementedError("Finder subclasses need to implement find()")
     29
     30
     31class FileSystemFinder(BaseFinder):
     32    """
     33    A static files finder that uses the ``STATICFILES_DIRS`` setting
     34    to locate files.
     35    """
     36    def find(self, path, all=False):
     37        """
     38        Looks for files in the extra media locations
     39        as defined in ``STATICFILES_DIRS``.
     40        """
     41        matches = []
     42        for root in settings.STATICFILES_DIRS:
     43            if isinstance(root, (list, tuple)):
     44                prefix, root = root
     45            else:
     46                prefix = ''
     47            matched_path = self.find_location(root, path, prefix)
     48            if matched_path:
     49                if not all:
     50                    return matched_path
     51                matches.append(matched_path)
     52        return matches
     53
     54    def find_location(self, root, path, prefix=None):
     55        """
     56        Find a requested static file in a location, returning the found
     57        absolute path (or ``None`` if no match).
     58        """
     59        if prefix:
     60            prefix = '%s/' % prefix
     61            if not path.startswith(prefix):
     62                return None
     63            path = path[len(prefix):]
     64        path = os.path.join(root, path)
     65        if os.path.exists(path):
     66            return path
     67
     68
     69class AppDirectoriesFinder(BaseFinder):
     70    """
     71    A static files finder that looks in the ``media`` directory of each app.
     72    """
     73    def find(self, path, all=False):
     74        """
     75        Looks for files in the app directories.
     76        """
     77        matches = []
     78        for app in models.get_apps():
     79            app_matches = self.find_in_app(app, path, all=all)
     80            if app_matches:
     81                if not all:
     82                    return app_matches
     83                matches.extend(app_matches)
     84        return matches
     85
     86    def find_in_app(self, app, path, all=False):
     87        """
     88        Find a requested static file in an app's media locations.
     89
     90        If ``all`` is ``False`` (default), return the first matching
     91        absolute path (or ``None`` if no match). Otherwise return a list of
     92        found absolute paths.
     93
     94        """
     95        prefix = utils.get_app_prefix(app)
     96        if prefix:
     97            prefix = '%s/' % prefix
     98            if not path.startswith(prefix):
     99                return []
     100            path = path[len(prefix):]
     101        paths = []
     102        storage = utils.app_static_storage(app)
     103        if storage and storage.exists(path):
     104            matched_path = storage.path(path)
     105            if not all:
     106                return matched_path
     107            paths.append(matched_path)
     108        return paths
     109
     110
     111class StorageFinder(BaseFinder):
     112    """
     113    A static files finder that uses the default storage backend.
     114    """
     115    static_storage = default_storage
     116
     117    def __init__(self, storage=None, *args, **kwargs):
     118        if storage is not None:
     119            self.static_storage = storage
     120        super(StorageFinder, self).__init__(*args, **kwargs)
     121
     122    def find(self, path, all=False):
     123        """
     124        Last resort, looks for files in the
     125        static files storage if it's local.
     126        """
     127        try:
     128            self.static_storage.path('')
     129        except NotImplementedError:
     130            pass
     131        else:
     132            if self.static_storage.exists(path):
     133                match = self.static_storage.path(path)
     134                if all:
     135                    match = [match]
     136                return match
     137        return []
     138
     139
     140def find(path, all=False):
     141    """
     142    Find a requested static file, first looking in any defined extra media
     143    locations and next in any (non-excluded) installed apps.
     144   
     145    If no matches are found and the static location is local, look for a match
     146    there too.
     147   
     148    If ``all`` is ``False`` (default), return the first matching
     149    absolute path (or ``None`` if no match). Otherwise return a list of
     150    found absolute paths.
     151   
     152    """
     153    matches = []
     154    for finder_path in settings.STATICFILES_FINDERS:
     155        finder = get_finder(finder_path)
     156        result = finder.find(path, all=all)
     157        if not all and result:
     158            return result
     159        if not isinstance(result, (list, tuple)):
     160            result = [result]
     161        matches.extend(result)
     162
     163    if matches:
     164        return matches
     165
     166    # No match.
     167    return all and [] or None
     168
     169
     170def _get_finder(import_path):
     171    """
     172    Imports the message storage class described by import_path, where
     173    import_path is the full Python path to the class.
     174    """
     175    try:
     176        dot = import_path.rindex('.')
     177    except ValueError:
     178        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
     179    module, classname = import_path[:dot], import_path[dot + 1:]
     180    try:
     181        mod = import_module(module)
     182    except ImportError, e:
     183        raise ImproperlyConfigured('Error importing module %s: "%s"' %
     184                                   (module, e))
     185    try:
     186        cls = getattr(mod, classname)
     187    except AttributeError:
     188        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
     189                                   'class.' % (module, classname))
     190    if not issubclass(cls, BaseFinder):
     191        raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
     192                                   (cls, BaseFinder))
     193    return cls()
     194
     195get_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..4765591
    - +  
     1import os
     2import urllib
     3
     4
     5from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
     6from django.http import Http404
     7from django.conf import settings
     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        # Ignore requests that aren't under the media prefix.
     45        # Also ignore all requests if prefix isn't a relative URL.
     46        if (self.media_url.startswith('http://') or
     47            self.media_url.startswith('https://') or
     48                not environ['PATH_INFO'].startswith(self.media_url)):
     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/__init__.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..1bca2d4
    - +  
     1import logging
     2
     3
  • new file django/contrib/staticfiles/management/commands/build_static.py

    diff --git a/django/contrib/staticfiles/management/commands/build_static.py b/django/contrib/staticfiles/management/commands/build_static.py
    new file mode 100644
    index 0000000..bb0b989
    - +  
     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, OptionalAppCommand
     9
     10from django.contrib.staticfiles import utils
     11
     12
     13class Command(OptionalAppCommand):
     14    """
     15    Command that allows to copy or symlink media files from different
     16    locations to the settings.STATICFILES_ROOT.
     17    """
     18    option_list = OptionalAppCommand.option_list + (
     19        make_option('--noinput', action='store_false', dest='interactive',
     20            default=True, help="Do NOT prompt the user for input of any "
     21                "kind."),
     22        make_option('-i', '--ignore', action='append', default=[],
     23            dest='ignore_patterns', metavar='PATTERN',
     24            help="Ignore files or directories matching this glob-style "
     25                "pattern. Use multiple times to ignore more."),
     26        make_option('-n', '--dry-run', action='store_true', dest='dry_run',
     27            help="Do everything except modify the filesystem."),
     28        make_option('-l', '--link', action='store_true', dest='link',
     29            help="Create a symbolic link to each file instead of copying."),
     30        make_option('--exclude-dirs', action='store_true',
     31            help="Exclude additional static locations specified in the "
     32                "STATICFILES_DIRS setting."),
     33        make_option('--no-default-ignore', action='store_false',
     34            dest='use_default_ignore_patterns', default=True,
     35            help="Don't ignore the common private glob-style patterns 'CVS', "
     36                "'.*' and '*~'."),
     37    )
     38    help = ("Copy static media files from apps and other locations in a "
     39            "single location.")
     40
     41    def handle(self, *app_labels, **options):
     42        ignore_patterns = options['ignore_patterns']
     43        if options['use_default_ignore_patterns']:
     44            ignore_patterns += ['CVS', '.*', '*~']
     45            options['ignore_patterns'] = ignore_patterns
     46        options['skipped_files'] = []
     47        options['copied_files'] = []
     48        options['symlinked_files'] = []
     49        options['destination_storage'] = get_storage_class(settings.STATICFILES_STORAGE)()
     50        try:
     51            destination_paths = utils.get_files(options['destination_storage'], ignore_patterns)
     52        except OSError:
     53            # The destination storage location may not exist yet. It'll get
     54            # created when the first file is copied.
     55            destination_paths = []
     56        options['destination_paths'] = destination_paths
     57        try:
     58            options['destination_storage'].path('')
     59            destination_local = True
     60        except NotImplementedError:
     61            destination_local = False
     62        options['destination_local'] = destination_local
     63        if options.get('link', False):
     64            if sys.platform == 'win32':
     65                message = "Symlinking is not supported by this platform (%s)."
     66                raise CommandError(message % sys.platform)
     67            if not destination_local:
     68                raise CommandError("Can't symlink to a remote destination.")
     69        # Warn before doing anything more.
     70        if options.get('interactive'):
     71            confirm = raw_input("""
     72You have requested to collate static media files and copy them to the
     73destination location as specified in your settings file.
     74
     75This will overwrite existing files. Are you sure you want to do this?
     76
     77Type 'yes' to continue, or 'no' to cancel: """)
     78            if confirm != 'yes':
     79                raise CommandError("Static files build cancelled.")
     80        return super(Command, self).handle(*app_labels, **options)
     81
     82    def pre_handle_apps(self, **options):
     83        """
     84        Copy all files from a directory.
     85
     86        """
     87        if not options.get('exclude_dirs', False):
     88            ignore_patterns = options['ignore_patterns']
     89            for root in settings.STATICFILES_DIRS:
     90                if isinstance(root, (list, tuple)):
     91                    prefix, root = root
     92                else:
     93                    prefix = ''
     94                source_storage = FileSystemStorage(location=root)
     95                for source in utils.get_files(source_storage, ignore_patterns):
     96                    self.copy_file(source, prefix, source_storage, **options)
     97
     98    def post_handle_apps(self, **options):
     99        copied_files = options['copied_files']
     100        symlinked_files = options['symlinked_files']
     101        logger = self.get_logger()
     102        count = len(copied_files) + len(symlinked_files)
     103        logger.info("%s static file%s built." % (count,
     104                                                 count != 1 and 's' or ''))
     105
     106    def handle_app(self, app, **options):
     107        """
     108        Copy all static media files from an application.
     109       
     110        """
     111        ignore_patterns = options['ignore_patterns']
     112        prefix = utils.get_app_prefix(app)
     113        storage = utils.app_static_storage(app)
     114        if storage:
     115            for path in utils.get_files(storage, ignore_patterns):
     116                self.copy_file(path, prefix, storage, **options)
     117
     118    def excluded_app(self, app, **options):
     119        """
     120        Gather all the static media files for excluded apps.
     121       
     122        This is so a warning can be issued for later copied files which would
     123        normally be copied by excluded apps.
     124       
     125        """
     126        skipped_files = options['skipped_files']
     127        ignore_patterns = options['ignore_patterns']
     128        all_files = utils.get_files_for_app(app, ignore_patterns)
     129        skipped_files.extend(all_files)
     130
     131    def copy_file(self, source, destination_prefix, source_storage, **options):
     132        """
     133        Attempt to copy (or symlink) `source` to `destination`, returning True
     134        if successful.
     135        """
     136        destination_storage = options['destination_storage']
     137        dry_run = options.get('dry_run', False)
     138        logger = self.get_logger()
     139        if destination_prefix:
     140            destination = '/'.join([destination_prefix, source])
     141        else:
     142            destination = source
     143
     144        if destination in options['copied_files']:
     145            logger.warning("Skipping duplicate file (already copied earlier):"
     146                           "\n  %s" % destination)
     147            return False
     148        if destination in options['symlinked_files']:
     149            logger.warning("Skipping duplicate file (already linked earlier):"
     150                           "\n  %s" % destination)
     151            return False
     152        if destination in options['skipped_files']:
     153            logger.warning("Copying file that would normally be provided by "
     154                           "an excluded application:\n  %s" % destination)
     155        source_path = source_storage.path(source)
     156        if destination in options['destination_paths']:
     157            if dry_run:
     158                logger.info("Pretending to delete:\n  %s" % destination)
     159            else:
     160                logger.debug("Deleting:\n  %s" % destination)
     161                destination_storage.delete(destination)
     162        if options.get('link', False):
     163            destination_path = destination_storage.path(destination)
     164            if dry_run:
     165                logger.info("Pretending to symlink:\n  %s\nto:\n  %s"
     166                            % (source_path, destination_path))
     167            else:
     168                logger.debug("Symlinking:\n  %s\nto:\n  %s"
     169                             % (source_path, destination_path))
     170                try:
     171                    os.makedirs(os.path.dirname(destination_path))
     172                except OSError:
     173                    pass
     174                os.symlink(source_path, destination_path)
     175            options['symlinked_files'].append(destination)
     176        else:
     177            if dry_run:
     178                logger.info("Pretending to copy:\n  %s\nto:\n  %s"
     179                            % (source_path, destination))
     180            else:
     181                if options['destination_local']:
     182                    destination_path = destination_storage.path(destination)
     183                    try:
     184                        os.makedirs(os.path.dirname(destination_path))
     185                    except OSError:
     186                        pass
     187                    shutil.copy2(source_path, destination_path)
     188                    logger.debug("Copying:\n  %s\nto:\n  %s"
     189                                 % (source_path, destination_path))
     190                else:
     191                    source_file = source_storage.open(source)
     192                    destination_storage.write(destination, source_file)
     193                    logger.debug("Copying:\n  %s\nto:\n  %s"
     194                                 % (source_path, destination))
     195            options['copied_files'].append(destination)
     196        return True
  • new file django/contrib/staticfiles/management/commands/find_static.py

    diff --git a/django/contrib/staticfiles/management/commands/find_static.py b/django/contrib/staticfiles/management/commands/find_static.py
    new file mode 100644
    index 0000000..9fdff6b
    - +  
     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 = "[static_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, media_path, **options):
     17        logger = self.get_logger()
     18        all = options['all']
     19        result = finders.find(media_path, all=all)
     20        if not result:
     21            logger.warning("No matching file found for %r." % media_path)
     22            return
     23        output = '\n  '.join((os.path.realpath(path) for path in result))
     24        logger.info("Found %r here:\n  %s" % (media_path, output))
  • 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..b8d883c
    - +  
     1import os, posixpath
     2
     3from django.core.exceptions import ImproperlyConfigured
     4from django.conf import settings
     5from django.core.files.storage import FileSystemStorage, get_storage_class
     6from django.utils.functional import LazyObject
     7
     8
     9class StaticFilesStorage(FileSystemStorage):
     10    """
     11    Standard file system storage for site media files.
     12   
     13    The defaults for ``location`` and ``base_url`` are
     14    ``STATICFILES_ROOT`` and ``STATICFILES_URL``.
     15    """
     16    def __init__(self, location=None, base_url=None, *args, **kwargs):
     17        if location is None:
     18            location = settings.STATICFILES_ROOT
     19        if base_url is None:
     20            base_url = settings.STATICFILES_URL
     21        if not location:
     22            raise ImproperlyConfigured("You're using the staticfiles app "
     23                "without having set the STATICFILES_ROOT setting. Set it to "
     24                "the absolute path of the directory that holds static media.")
     25        if not base_url:
     26            raise ImproperlyConfigured("You're using the staticfiles app "
     27                "without having set the STATICFILES_URL setting. Set it to "
     28                "URL that handles the files served from STATICFILES_ROOT.")
     29        super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
  • 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..b533747
    - +  
     1import os
     2import fnmatch
     3
     4from django.core.files.storage import FileSystemStorage
     5from django.utils.importlib import import_module
     6
     7def get_files_for_app(app, ignore_patterns=[]):
     8    """
     9    Return a list containing the relative source paths for all files that
     10    should be copied for an app.
     11   
     12    """
     13    prefix = get_app_prefix(app)
     14    files = []
     15    storage = app_static_storage(app)
     16    if storage:
     17        for path in get_files(storage, ignore_patterns):
     18            if prefix:
     19                path = '/'.join([prefix, path])
     20            files.append(path)
     21    return files
     22
     23def app_static_storage(app):
     24    """
     25    Returns a static file storage if available in the given app.
     26   
     27    """
     28    # "app" is actually the models module of the app. Remove the '.models'.
     29    app_module = '.'.join(app.__name__.split('.')[:-1])
     30
     31    # The models module may be a package in which case dirname(app.__file__)
     32    # would be wrong. Import the actual app as opposed to the models module.
     33    app = import_module(app_module)
     34    app_root = os.path.dirname(app.__file__)
     35    location = os.path.join(app_root, 'media')
     36    if not os.path.isdir(location):
     37        return None
     38    return FileSystemStorage(location=location)
     39
     40def get_app_prefix(app):
     41    """
     42    Return the path name that should be prepended to files for this app.
     43   
     44    """
     45    # "app" is actually the models module of the app. Remove the '.models'.
     46    bits = app.__name__.split('.')[:-1]
     47    app_name = bits[-1]
     48    app_module = '.'.join(bits)
     49    if app_module == 'django.contrib.admin':
     50        return app_name
     51    else:
     52        return None
     53
     54def get_files(storage, ignore_patterns=[], location=''):
     55    """
     56    Recursively walk the storage directories gathering a complete list of files
     57    that should be copied, returning this list.
     58   
     59    """
     60    def is_ignored(path):
     61        """
     62        Return True or False depending on whether the ``path`` should be
     63        ignored (if it matches any pattern in ``ignore_patterns``).
     64       
     65        """
     66        for pattern in ignore_patterns:
     67            if fnmatch.fnmatchcase(path, pattern):
     68                return True
     69        return False
     70
     71    directories, files = storage.listdir(location)
     72    static_files = [location and '/'.join([location, fn]) or fn
     73                    for fn in files
     74                    if not is_ignored(fn)]
     75    for dir in directories:
     76        if is_ignored(dir):
     77            continue
     78        if location:
     79            dir = '/'.join([location, dir])
     80        static_files.extend(get_files(storage, ignore_patterns, dir))
     81    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 a5f29df..08bdc75 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 dcd83ff..e00ebc1 100644
    a b be executed through ``django-admin.py`` or ``manage.py``).  
    55"""
    66
    77import os
     8import logging
    89import sys
    910from optparse import make_option, OptionParser
    1011
    class BaseCommand(object):  
    135136    requires_model_validation = True
    136137    output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
    137138
     139    # OMG FIXME -- what is wrong with you? we don't want to use our own logger here, use the real thing man!
     140    logger_parent = 'django.core.management.base.commands'
     141    logger_name = 'base'
     142    logger_handler = logging.StreamHandler()
     143    logger_format = '%(message)s'
     144    logger_levels = {
     145        0: logging.ERROR,
     146        1: logging.INFO,
     147        2: logging.DEBUG,
     148    }
     149
    138150    def __init__(self):
    139151        self.style = color_style()
    140152
     153    def get_logger(self):
     154        parts = [self.logger_parent, self.logger_name]
     155        name = '.'.join([part for part in parts if part])
     156        return logging.getLogger(name)
     157
    141158    def get_version(self):
    142159        """
    143160        Return the Django version, which should be correct for all
    class BaseCommand(object):  
    199216        stderr.
    200217
    201218        """
     219        verbosity = options.get('verbosity', 1)
     220        logger = self.get_logger()
     221        logger.setLevel(self.logger_levels[int(verbosity)])
     222        formatter = logging.Formatter(self.logger_format)
     223        self.logger_handler.setFormatter(formatter)
     224        logger.addHandler(self.logger_handler)
    202225        # Switch to English, because django-admin.py creates database content
    203226        # like permissions, and those shouldn't contain any translations.
    204227        # But only do this if we can assume we have a working settings file,
    class BaseCommand(object):  
    232255        except CommandError, e:
    233256            self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
    234257            sys.exit(1)
     258        finally:
     259            logger.removeHandler(self.logger_handler)
    235260
    236261    def validate(self, app=None, display_num_errors=False):
    237262        """
    class AppCommand(BaseCommand):  
    297322        """
    298323        raise NotImplementedError()
    299324
     325class OptionalAppCommand(BaseCommand):
     326    """
     327    A management command which optionally takes one or more installed
     328    application names as arguments, and does something with each of them.
     329
     330    If no application names are provided, all the applications are used.
     331
     332    The order in which applications are processed is determined by the order
     333    given in INSTALLED_APPS. This differs from Django's AppCommand (it uses the
     334    order the apps are given in the management command).
     335
     336    Rather than implementing ``handle()``, subclasses must implement
     337    ``handle_app()``, which will be called once for each application.
     338
     339    Subclasses can also optionally implement ``excluded_app()`` to run
     340    processes on apps which were excluded.
     341
     342    """
     343    args = '[appname appname ...]'
     344    option_list = BaseCommand.option_list + (
     345        make_option('-e', '--exclude', dest='exclude', action='append',
     346            default=[], help='App to exclude (use multiple --exclude to '
     347            'exclude multiple apps).'),
     348    )
     349
     350    def handle(self, *app_labels, **options):
     351        from django.db import models
     352        # Get all the apps, checking for common errors.
     353        try:
     354            all_apps = models.get_apps()
     355        except (ImproperlyConfigured, ImportError), e:
     356            raise CommandError("%s. Are you sure your INSTALLED_APPS setting "
     357                               "is correct?" % e)
     358        # Build the app_list.
     359        app_list = []
     360        used = 0
     361        for app in all_apps:
     362            app_label = app.__name__.split('.')[-2]
     363            if not app_labels or app_label in app_labels:
     364                used += 1
     365                if app_label not in options['exclude']:
     366                    app_list.append(app)
     367        # Check that all app_labels were used.
     368        if app_labels and used != len(app_labels):
     369            raise CommandError('Could not find the following app(s): %s' %
     370                               ', '.join(app_labels))
     371        # Handle all the apps (either via handle_app or excluded_app),
     372        # collating any output.
     373        output = []
     374        pre_output = self.pre_handle_apps(**options)
     375        if pre_output:
     376            output.append(pre_output)
     377        for app in all_apps:
     378            if app in app_list:
     379                handle_method = self.handle_app
     380            else:
     381                handle_method = self.excluded_app
     382            app_output = handle_method(app, **options)
     383            if app_output:
     384                output.append(app_output)
     385        post_output = self.post_handle_apps(**options)
     386        if post_output:
     387            output.append(post_output)
     388        return '\n'.join(output)
     389
     390    def handle_app(self, app, **options):
     391        """
     392        Perform the command's actions for ``app``, which will be the
     393        Python module corresponding to an application name given on
     394        the command line.
     395
     396        """
     397        raise NotImplementedError()
     398
     399    def excluded_app(self, app, **options):
     400        """
     401        A hook for commands to parse apps which were excluded.
     402
     403        """
     404
     405    def pre_handle_apps(self, **options):
     406        """
     407        A hook for commands to do something before the applications are
     408        processed.
     409
     410        """
     411
     412    def post_handle_apps(self, **options):
     413        """
     414        A hook for commands to do something after all applications have been
     415        processed.
     416
     417        """
     418
     419
    300420class LabelCommand(BaseCommand):
    301421    """
    302422    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)
  • 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..0f29d60
    - +  
     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_installed_apps = settings.INSTALLED_APPS
     28        self.old_media_root = settings.MEDIA_ROOT
     29        self.old_media_url = settings.MEDIA_URL
     30        self.old_admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
     31
     32        # We have to load these apps to test staticfiles.
     33        load_app('regressiontests.staticfiles_tests.apps.test')
     34        load_app('regressiontests.staticfiles_tests.apps.no_label')
     35        site_media = os.path.join(TEST_ROOT, 'project', 'site_media')
     36        settings.MEDIA_ROOT =  os.path.join(site_media, 'media')
     37        settings.MEDIA_URL = '/media/'
     38        settings.ADMIN_MEDIA_PREFIX = posixpath.join(settings.STATICFILES_URL, 'admin/')
     39        settings.STATICFILES_ROOT = os.path.join(site_media, 'static')
     40        settings.STATICFILES_URL = '/static/'
     41        settings.STATICFILES_DIRS = (
     42            os.path.join(TEST_ROOT, 'project', 'documents'),
     43        )
     44
     45    def tearDown(self):
     46        settings.MEDIA_ROOT = self.old_media_root
     47        settings.MEDIA_URL = self.old_media_url
     48        settings.ADMIN_MEDIA_PREFIX = self.old_admin_media_prefix
     49        settings.STATICFILES_ROOT = self.old_staticfiles_root
     50        settings.STATICFILES_URL = self.old_staticfiles_url
     51        settings.STATICFILES_DIRS = self.old_staticfiles_dirs
     52        settings.INSTALLED_APPS = self.old_installed_apps
     53
     54    def assertFileContains(self, filepath, text):
     55        self.failUnless(text in self._get_file(filepath),
     56                        "'%s' not in '%s'" % (text, filepath))
     57
     58    def assertFileNotFound(self, filepath):
     59        self.assertRaises(IOError, self._get_file, filepath)
     60
     61
     62class TestingStaticFilesStorage(storage.StaticFilesStorage):
     63
     64    def __init__(self, location=None, base_url=None, *args, **kwargs):
     65        location = tempfile.mkdtemp()
     66        settings.STATICFILES_ROOT = location
     67        super(TestingStaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
     68
     69
     70class BuildStaticTestCase(StaticFilesTestCase):
     71    """
     72    Tests shared by all file-resolving features (build_static,
     73    find_static, and static serve view).
     74
     75    This relies on the asserts defined in UtilityAssertsTestCase, but
     76    is separated because some test cases need those asserts without
     77    all these tests.
     78    """
     79    def setUp(self):
     80        super(BuildStaticTestCase, self).setUp()
     81        self.old_staticfiles_storage = settings.STATICFILES_STORAGE
     82        self.old_root = settings.STATICFILES_ROOT
     83        settings.STATICFILES_STORAGE = 'regressiontests.staticfiles_tests.tests.TestingStaticFilesStorage'
     84        self.run_build_static()
     85
     86    def tearDown(self):
     87        shutil.rmtree(settings.STATICFILES_ROOT)
     88        settings.STATICFILES_ROOT = self.old_root
     89        settings.STATICFILES_STORAGE = self.old_staticfiles_storage
     90        super(BuildStaticTestCase, self).tearDown()
     91
     92    def run_build_static(self, **kwargs):
     93        call_command('build_static', interactive=False, verbosity='0',
     94                     ignore_patterns=['*.ignoreme'], **kwargs)
     95
     96    def _get_file(self, filepath):
     97        assert filepath, 'filepath is empty.'
     98        filepath = os.path.join(settings.STATICFILES_ROOT, filepath)
     99        return open(filepath).read()
     100
     101
     102class TestDefaults(object):
     103    def test_staticfiles_dirs(self):
     104        """
     105        Can find a file in a STATICFILES_DIRS directory.
     106
     107        """
     108        self.assertFileContains('test.txt', 'Can we find')
     109
     110    def test_staticfiles_dirs_subdir(self):
     111        """
     112        Can find a file in a subdirectory of a STATICFILES_DIRS
     113        directory.
     114
     115        """
     116        self.assertFileContains('subdir/test.txt', 'Can we find')
     117
     118    def test_staticfiles_dirs_priority(self):
     119        """
     120        File in STATICFILES_DIRS has priority over file in app.
     121
     122        """
     123        self.assertFileContains('test/file.txt', 'STATICFILES_DIRS')
     124
     125    def test_app_files(self):
     126        """
     127        Can find a file in an app media/ directory.
     128
     129        """
     130        self.assertFileContains('test/file1.txt', 'file1 in the app dir')
     131
     132
     133class TestBuildStatic(BuildStaticTestCase, TestDefaults):
     134    """
     135    Test ``build_static`` management command.
     136    """
     137    def test_ignore(self):
     138        """
     139        Test that -i patterns are ignored.
     140        """
     141        self.assertFileNotFound('test/test.ignoreme')
     142
     143    def test_common_ignore_patterns(self):
     144        """
     145        Common ignore patterns (*~, .*, CVS) are ignored.
     146        """
     147        self.assertFileNotFound('test/.hidden')
     148        self.assertFileNotFound('test/backup~')
     149        self.assertFileNotFound('test/CVS')
     150
     151
     152class TestBuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase):
     153    """
     154    Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
     155    ``build_static`` management command.
     156    """
     157    def run_build_static(self):
     158        super(TestBuildStaticExcludeNoDefaultIgnore, self).run_build_static(
     159            exclude_dirs=True, use_default_ignore_patterns=False)
     160
     161    def test_exclude_dirs(self):
     162        """
     163        With --exclude-dirs, cannot find file in
     164        STATICFILES_DIRS.
     165
     166        """
     167        self.assertFileNotFound('test.txt')
     168
     169    def test_no_common_ignore_patterns(self):
     170        """
     171        With --no-default-ignore, common ignore patterns (*~, .*, CVS)
     172        are not ignored.
     173
     174        """
     175        self.assertFileContains('test/.hidden', 'should be ignored')
     176        self.assertFileContains('test/backup~', 'should be ignored')
     177        self.assertFileContains('test/CVS', 'should be ignored')
     178
     179
     180class TestBuildStaticDryRun(BuildStaticTestCase):
     181    """
     182    Test ``--dry-run`` option for ``build_static`` management command.
     183    """
     184    def run_build_static(self):
     185        super(TestBuildStaticDryRun, self).run_build_static(dry_run=True)
     186
     187    def test_no_files_created(self):
     188        """
     189        With --dry-run, no files created in destination dir.
     190        """
     191        self.assertEquals(os.listdir(settings.STATICFILES_ROOT), [])
     192
     193
     194if sys.platform != 'win32':
     195    class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults):
     196        """
     197        Test ``--link`` option for ``build_static`` management command.
     198
     199        Note that by inheriting ``BaseFileResolutionTests`` we repeat all
     200        the standard file resolving tests here, to make sure using
     201        ``--link`` does not change the file-selection semantics.
     202        """
     203        def run_build_static(self):
     204            super(TestBuildStaticLinks, self).run_build_static(link=True)
     205
     206        def test_links_created(self):
     207            """
     208            With ``--link``, symbolic links are created.
     209
     210            """
     211            self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt')))
     212
     213
     214class TestServeStatic(StaticFilesTestCase):
     215    """
     216    Test static asset serving view.
     217    """
     218    urls = "regressiontests.staticfiles_tests.urls"
     219
     220    def _response(self, filepath):
     221        return self.client.get(
     222            posixpath.join(settings.STATICFILES_URL, filepath))
     223
     224    def assertFileContains(self, filepath, text):
     225        self.assertContains(self._response(filepath), text)
     226
     227    def assertFileNotFound(self, filepath):
     228        self.assertEquals(self._response(filepath).status_code, 404)
     229
     230
     231class TestServeAdminMedia(TestServeStatic):
     232    """
     233    Test serving media from django.contrib.admin.
     234    """
     235    def test_serve_admin_media(self):
     236        media_file = posixpath.join(
     237            settings.ADMIN_MEDIA_PREFIX, 'css/base.css')
     238        response = self.client.get(media_file)
     239        self.assertContains(response, 'body')
     240
     241
     242class FinderTestCase(object):
     243    """
     244    Base finder test mixin
     245    """
     246    def test_find_first(self):
     247        src, dst = self.find_first
     248        self.assertEquals(self.finder.find(src), dst)
     249
     250    def test_find_all(self):
     251        src, dst = self.find_all
     252        self.assertEquals(self.finder.find(src, all=True), dst)
     253
     254
     255class TestFileSystemFinder(StaticFilesTestCase, FinderTestCase):
     256    """
     257    Test FileSystemFinder.
     258    """
     259    def setUp(self):
     260        super(TestFileSystemFinder, self).setUp()
     261        self.finder = finders.FileSystemFinder()
     262        test_file_path = os.path.join(TEST_ROOT, 'project/documents/test/file.txt')
     263        self.find_first = ("test/file.txt", test_file_path)
     264        self.find_all = ("test/file.txt", [test_file_path])
     265
     266
     267class TestAppDirectoriesFinder(StaticFilesTestCase, FinderTestCase):
     268    """
     269    Test AppDirectoriesFinder.
     270    """
     271    def setUp(self):
     272        super(TestAppDirectoriesFinder, self).setUp()
     273        self.finder = finders.AppDirectoriesFinder()
     274        test_file_path = os.path.join(TEST_ROOT, 'apps/test/media/test/file1.txt')
     275        self.find_first = ("test/file1.txt", test_file_path)
     276        self.find_all = ("test/file1.txt", [test_file_path])
     277
     278
     279class TestStorageFinder(StaticFilesTestCase, FinderTestCase):
     280    """
     281    Test StorageFinder.
     282    """
     283    def setUp(self):
     284        super(TestStorageFinder, self).setUp()
     285        self.finder = finders.StorageFinder(
     286            storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT))
     287        test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt')
     288        self.find_first = ("media-file.txt", test_file_path)
     289        self.find_all = ("media-file.txt", [test_file_path])
     290
     291
     292class TestMiscFinder(TestCase):
     293    """
     294    A few misc finder tests.
     295    """
     296    def test_get_finder(self):
     297        self.assertTrue(isinstance(finders.get_finder(
     298            "django.contrib.staticfiles.finders.FileSystemFinder"),
     299            finders.FileSystemFinder))
     300        self.assertRaises(ImproperlyConfigured,
     301            finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder")
     302        self.assertRaises(ImproperlyConfigured,
     303            finders.get_finder, "foo.bar.FooBarFinder")
     304
  • 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 a52152b..c23f9fd 100755
    a b ALWAYS_INSTALLED_APPS = [  
    2828    'django.contrib.comments',
    2929    'django.contrib.admin',
    3030    'django.contrib.admindocs',
     31    'django.contrib.staticfiles',
    3132]
    3233
    3334def 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