Ticket #12323: 12323.6.diff

File 12323.6.diff, 60.6 KB (added by Jannis Leidel, 14 years ago)

Refactored utilities and management command.

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index f75982f..97b6c7d 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  
    550545
    551546# The list of directories to search for fixtures
    552547FIXTURE_DIRS = ()
     548
     549###############
     550# STATICFILES #
     551###############
     552
     553# Absolute path to the directory that holds media.
     554# Example: "/home/media/media.lawrence.com/static/"
     555STATICFILES_ROOT = ''
     556
     557# URL that handles the static files served from STATICFILES_ROOT.
     558# Example: "http://media.lawrence.com/static/"
     559STATICFILES_URL = '/static/'
     560
     561# A tuple of two-tuples with a name and the path of additional directories
     562# which hold static files and should be taken into account during resolving
     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.StorageFinder',
     574)
     575
     576# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
     577# trailing slash.
     578# Examples: "http://foo.com/media/admin/", "/media/admin/".
     579ADMIN_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..1f46c60
    - +  
     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 StorageFinder(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(StorageFinder, 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)
  • 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..4ac8aa5
    - +  
     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.StorageFinder',
     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 TestingStaticFilesStorage(storage.StaticFilesStorage):
     70
     71    def __init__(self, location=None, base_url=None, *args, **kwargs):
     72        location = tempfile.mkdtemp()
     73        settings.STATICFILES_ROOT = location
     74        super(TestingStaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
     75
     76
     77class BuildStaticTestCase(StaticFilesTestCase):
     78    """
     79    Tests shared by all file-resolving features (collectstatic,
     80    findstatic, and static serve view).
     81
     82    This relies on the asserts defined in UtilityAssertsTestCase, but
     83    is separated because some test cases need those asserts without
     84    all these tests.
     85    """
     86    def setUp(self):
     87        super(BuildStaticTestCase, self).setUp()
     88        self.old_staticfiles_storage = settings.STATICFILES_STORAGE
     89        self.old_root = settings.STATICFILES_ROOT
     90        settings.STATICFILES_STORAGE = 'regressiontests.staticfiles_tests.tests.TestingStaticFilesStorage'
     91        self.run_collectstatic()
     92
     93    def tearDown(self):
     94        shutil.rmtree(settings.STATICFILES_ROOT)
     95        settings.STATICFILES_ROOT = self.old_root
     96        settings.STATICFILES_STORAGE = self.old_staticfiles_storage
     97        super(BuildStaticTestCase, self).tearDown()
     98
     99    def run_collectstatic(self, **kwargs):
     100        call_command('collectstatic', interactive=False, verbosity='0',
     101                     ignore_patterns=['*.ignoreme'], **kwargs)
     102
     103    def _get_file(self, filepath):
     104        assert filepath, 'filepath is empty.'
     105        filepath = os.path.join(settings.STATICFILES_ROOT, filepath)
     106        return open(filepath).read()
     107
     108
     109class TestDefaults(object):
     110    """
     111    A few standard test cases.
     112    """
     113    def test_staticfiles_dirs(self):
     114        """
     115        Can find a file in a STATICFILES_DIRS directory.
     116
     117        """
     118        self.assertFileContains('test.txt', 'Can we find')
     119
     120    def test_staticfiles_dirs_subdir(self):
     121        """
     122        Can find a file in a subdirectory of a STATICFILES_DIRS
     123        directory.
     124
     125        """
     126        self.assertFileContains('subdir/test.txt', 'Can we find')
     127
     128    def test_staticfiles_dirs_priority(self):
     129        """
     130        File in STATICFILES_DIRS has priority over file in app.
     131
     132        """
     133        self.assertFileContains('test/file.txt', 'STATICFILES_DIRS')
     134
     135    def test_app_files(self):
     136        """
     137        Can find a file in an app media/ directory.
     138
     139        """
     140        self.assertFileContains('test/file1.txt', 'file1 in the app dir')
     141
     142
     143class TestBuildStatic(BuildStaticTestCase, TestDefaults):
     144    """
     145    Test ``collectstatic`` management command.
     146    """
     147    def test_ignore(self):
     148        """
     149        Test that -i patterns are ignored.
     150        """
     151        self.assertFileNotFound('test/test.ignoreme')
     152
     153    def test_common_ignore_patterns(self):
     154        """
     155        Common ignore patterns (*~, .*, CVS) are ignored.
     156        """
     157        self.assertFileNotFound('test/.hidden')
     158        self.assertFileNotFound('test/backup~')
     159        self.assertFileNotFound('test/CVS')
     160
     161
     162class TestBuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase, TestDefaults):
     163    """
     164    Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
     165    ``collectstatic`` management command.
     166    """
     167    def run_collectstatic(self):
     168        super(TestBuildStaticExcludeNoDefaultIgnore, self).run_collectstatic(
     169            use_default_ignore_patterns=False)
     170
     171    def test_no_common_ignore_patterns(self):
     172        """
     173        With --no-default-ignore, common ignore patterns (*~, .*, CVS)
     174        are not ignored.
     175
     176        """
     177        self.assertFileContains('test/.hidden', 'should be ignored')
     178        self.assertFileContains('test/backup~', 'should be ignored')
     179        self.assertFileContains('test/CVS', 'should be ignored')
     180
     181
     182class TestBuildStaticDryRun(BuildStaticTestCase):
     183    """
     184    Test ``--dry-run`` option for ``collectstatic`` management command.
     185    """
     186    def run_collectstatic(self):
     187        super(TestBuildStaticDryRun, self).run_collectstatic(dry_run=True)
     188
     189    def test_no_files_created(self):
     190        """
     191        With --dry-run, no files created in destination dir.
     192        """
     193        self.assertEquals(os.listdir(settings.STATICFILES_ROOT), [])
     194
     195
     196if sys.platform != 'win32':
     197    class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults):
     198        """
     199        Test ``--link`` option for ``collectstatic`` management command.
     200
     201        Note that by inheriting ``TestDefaults`` we repeat all
     202        the standard file resolving tests here, to make sure using
     203        ``--link`` does not change the file-selection semantics.
     204        """
     205        def run_collectstatic(self):
     206            super(TestBuildStaticLinks, self).run_collectstatic(link=True)
     207
     208        def test_links_created(self):
     209            """
     210            With ``--link``, symbolic links are created.
     211
     212            """
     213            self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt')))
     214
     215
     216class TestServeStatic(StaticFilesTestCase):
     217    """
     218    Test static asset serving view.
     219    """
     220    urls = "regressiontests.staticfiles_tests.urls"
     221
     222    def _response(self, filepath):
     223        return self.client.get(
     224            posixpath.join(settings.STATICFILES_URL, filepath))
     225
     226    def assertFileContains(self, filepath, text):
     227        self.assertContains(self._response(filepath), text)
     228
     229    def assertFileNotFound(self, filepath):
     230        self.assertEquals(self._response(filepath).status_code, 404)
     231
     232
     233class TestServeAdminMedia(TestServeStatic):
     234    """
     235    Test serving media from django.contrib.admin.
     236    """
     237    def test_serve_admin_media(self):
     238        media_file = posixpath.join(
     239            settings.ADMIN_MEDIA_PREFIX, 'css/base.css')
     240        response = self.client.get(media_file)
     241        self.assertContains(response, 'body')
     242
     243
     244class FinderTestCase(object):
     245    """
     246    Base finder test mixin
     247    """
     248    def test_find_first(self):
     249        src, dst = self.find_first
     250        self.assertEquals(self.finder.find(src), dst)
     251
     252    def test_find_all(self):
     253        src, dst = self.find_all
     254        self.assertEquals(self.finder.find(src, all=True), dst)
     255
     256
     257class TestFileSystemFinder(StaticFilesTestCase, FinderTestCase):
     258    """
     259    Test FileSystemFinder.
     260    """
     261    def setUp(self):
     262        super(TestFileSystemFinder, self).setUp()
     263        self.finder = finders.FileSystemFinder()
     264        test_file_path = os.path.join(TEST_ROOT, 'project/documents/test/file.txt')
     265        self.find_first = ("test/file.txt", test_file_path)
     266        self.find_all = ("test/file.txt", [test_file_path])
     267
     268
     269class TestAppDirectoriesFinder(StaticFilesTestCase, FinderTestCase):
     270    """
     271    Test AppDirectoriesFinder.
     272    """
     273    def setUp(self):
     274        super(TestAppDirectoriesFinder, self).setUp()
     275        self.finder = finders.AppDirectoriesFinder()
     276        test_file_path = os.path.join(TEST_ROOT, 'apps/test/media/test/file1.txt')
     277        self.find_first = ("test/file1.txt", test_file_path)
     278        self.find_all = ("test/file1.txt", [test_file_path])
     279
     280
     281class TestStorageFinder(StaticFilesTestCase, FinderTestCase):
     282    """
     283    Test StorageFinder.
     284    """
     285    def setUp(self):
     286        super(TestStorageFinder, self).setUp()
     287        self.finder = finders.StorageFinder(
     288            storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT))
     289        test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt')
     290        self.find_first = ("media-file.txt", test_file_path)
     291        self.find_all = ("media-file.txt", [test_file_path])
     292
     293
     294class TestMiscFinder(TestCase):
     295    """
     296    A few misc finder tests.
     297    """
     298    def test_get_finder(self):
     299        self.assertTrue(isinstance(finders.get_finder(
     300            "django.contrib.staticfiles.finders.FileSystemFinder"),
     301            finders.FileSystemFinder))
     302        self.assertRaises(ImproperlyConfigured,
     303            finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder")
     304        self.assertRaises(ImproperlyConfigured,
     305            finders.get_finder, "foo.bar.FooBarFinder")
     306
  • 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