Commit cfc19f84 authored by Jannis Leidel's avatar Jannis Leidel
Browse files

Fixed #12323 and #11582 -- Extended the ability to handle static files. Thanks...

Fixed #12323 and #11582 -- Extended the ability to handle static files. Thanks to all for helping with the original app, the patch, documentation and general support.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14293 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent a014ee02
Loading
Loading
Loading
Loading
+32 −6
Original line number Diff line number Diff line
@@ -194,7 +194,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
    'django.contrib.staticfiles.context_processors.staticfiles',
#    'django.core.context_processors.request',
    'django.contrib.messages.context_processors.messages',
)
@@ -202,11 +202,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
# Output to use in template system for invalid (e.g. misspelled) variables.
TEMPLATE_STRING_IF_INVALID = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

# Default e-mail address to use for various automated correspondence from
# the site managers.
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
@@ -551,3 +546,34 @@ TEST_DATABASE_COLLATION = None

# The list of directories to search for fixtures
FIXTURE_DIRS = ()

###############
# STATICFILES #
###############

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/static/"
STATICFILES_ROOT = ''

# URL that handles the static files served from STATICFILES_ROOT.
# Example: "http://media.lawrence.com/static/"
STATICFILES_URL = '/static/'

# A list of locations of additional static files
STATICFILES_DIRS = ()

# The default file storage backend used during the build process
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
+25 −5
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ USE_I18N = True
USE_L10N = True

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
@@ -52,10 +52,29 @@ MEDIA_ROOT = ''
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/static/"
STATICFILES_ROOT = ''

# URL that handles the static files served from STATICFILES_ROOT.
# Example: "http://static.lawrence.com/", "http://example.com/static/"
STATICFILES_URL = '/static/'

# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'

# A list of locations of additional static files
STATICFILES_DIRS = ()

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

# Make this unique, and don't share it with anybody.
SECRET_KEY = ''
@@ -89,6 +108,7 @@ INSTALLED_APPS = (
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
+0 −0

Empty file added.

+7 −0
Original line number Diff line number Diff line
from django.conf import settings

def staticfiles(request):
    return {
        'STATICFILES_URL': settings.STATICFILES_URL,
        'MEDIA_URL': settings.MEDIA_URL,
    }
+254 −0
Original line number Diff line number Diff line
import os
from django.conf import settings
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import default_storage, Storage, FileSystemStorage
from django.utils.datastructures import SortedDict
from django.utils.functional import memoize, LazyObject
from django.utils.importlib import import_module

from django.contrib.staticfiles import utils
from django.contrib.staticfiles.storage import AppStaticStorage

_finders = {}


class BaseFinder(object):
    """
    A base file finder to be used for custom staticfiles finder classes.

    """
    def find(self, path, all=False):
        """
        Given a relative file path this ought to find an
        absolute file path.

        If the ``all`` parameter is ``False`` (default) only
        the first found file path will be returned; if set
        to ``True`` a list of all found files paths is returned.
        """
        raise NotImplementedError()

    def list(self, ignore_patterns=[]):
        """
        Given an optional list of paths to ignore, this should return
        a three item iterable with path, prefix and a storage instance.
        """
        raise NotImplementedError()


class FileSystemFinder(BaseFinder):
    """
    A static files finder that uses the ``STATICFILES_DIRS`` setting
    to locate files.
    """
    storages = SortedDict()
    locations = set()

    def __init__(self, apps=None, *args, **kwargs):
        for root in settings.STATICFILES_DIRS:
            if isinstance(root, (list, tuple)):
                prefix, root = root
            else:
                prefix = ''
            self.locations.add((prefix, root))
        # Don't initialize multiple storages for the same location
        for prefix, root in self.locations:
            self.storages[root] = FileSystemStorage(location=root)
        super(FileSystemFinder, self).__init__(*args, **kwargs)

    def find(self, path, all=False):
        """
        Looks for files in the extra media locations
        as defined in ``STATICFILES_DIRS``.
        """
        matches = []
        for prefix, root in self.locations:
            matched_path = self.find_location(root, path, prefix)
            if matched_path:
                if not all:
                    return matched_path
                matches.append(matched_path)
        return matches

    def find_location(self, root, path, prefix=None):
        """
        Find a requested static file in a location, returning the found
        absolute path (or ``None`` if no match).
        """
        if prefix:
            prefix = '%s/' % prefix
            if not path.startswith(prefix):
                return None
            path = path[len(prefix):]
        path = os.path.join(root, path)
        if os.path.exists(path):
            return path

    def list(self, ignore_patterns):
        """
        List all files in all locations.
        """
        for prefix, root in self.locations:
            storage = self.storages[root]
            for path in utils.get_files(storage, ignore_patterns):
                yield path, prefix, storage


class AppDirectoriesFinder(BaseFinder):
    """
    A static files finder that looks in the ``media`` directory of each app.
    """
    storages = {}
    storage_class = AppStaticStorage

    def __init__(self, apps=None, *args, **kwargs):
        if apps is not None:
            self.apps = apps
        else:
            self.apps = models.get_apps()
        for app in self.apps:
            self.storages[app] = self.storage_class(app)
        super(AppDirectoriesFinder, self).__init__(*args, **kwargs)

    def list(self, ignore_patterns):
        """
        List all files in all app storages.
        """
        for storage in self.storages.itervalues():
            if storage.exists(''): # check if storage location exists
                prefix = storage.get_prefix()
                for path in utils.get_files(storage, ignore_patterns):
                    yield path, prefix, storage

    def find(self, path, all=False):
        """
        Looks for files in the app directories.
        """
        matches = []
        for app in self.apps:
            app_matches = self.find_in_app(app, path)
            if app_matches:
                if not all:
                    return app_matches
                matches.append(app_matches)
        return matches

    def find_in_app(self, app, path):
        """
        Find a requested static file in an app's media locations.
        """
        storage = self.storages[app]
        prefix = storage.get_prefix()
        if prefix:
            prefix = '%s/' % prefix
            if not path.startswith(prefix):
                return None
            path = path[len(prefix):]
        # only try to find a file if the source dir actually exists
        if storage.exists(path):
            matched_path = storage.path(path)
            if matched_path:
                return matched_path


class BaseStorageFinder(BaseFinder):
    """
    A base static files finder to be used to extended
    with an own storage class.
    """
    storage = None

    def __init__(self, storage=None, *args, **kwargs):
        if storage is not None:
            self.storage = storage
        if self.storage is None:
            raise ImproperlyConfigured("The staticfiles storage finder %r "
                                       "doesn't have a storage class "
                                       "assigned." % self.__class__)
        # Make sure we have an storage instance here.
        if not isinstance(self.storage, (Storage, LazyObject)):
            self.storage = self.storage()
        super(BaseStorageFinder, self).__init__(*args, **kwargs)

    def find(self, path, all=False):
        """
        Looks for files in the default file storage, if it's local.
        """
        try:
            self.storage.path('')
        except NotImplementedError:
            pass
        else:
            if self.storage.exists(path):
                match = self.storage.path(path)
                if all:
                    match = [match]
                return match
        return []

    def list(self, ignore_patterns):
        """
        List all files of the storage.
        """
        for path in utils.get_files(self.storage, ignore_patterns):
            yield path, '', self.storage

class DefaultStorageFinder(BaseStorageFinder):
    """
    A static files finder that uses the default storage backend.
    """
    storage = default_storage


def find(path, all=False):
    """
    Find a requested static file, first looking in any defined extra media
    locations and next in any (non-excluded) installed apps.
    
    If no matches are found and the static location is local, look for a match
    there too.
    
    If ``all`` is ``False`` (default), return the first matching
    absolute path (or ``None`` if no match). Otherwise return a list of
    found absolute paths.
    
    """
    matches = []
    for finder in get_finders():
        result = finder.find(path, all=all)
        if not all and result:
            return result
        if not isinstance(result, (list, tuple)):
            result = [result]
        matches.extend(result)
    if matches:
        return matches
    # No match.
    return all and [] or None

def get_finders():
    for finder_path in settings.STATICFILES_FINDERS:
        yield get_finder(finder_path)

def _get_finder(import_path):
    """
    Imports the message storage class described by import_path, where
    import_path is the full Python path to the class.
    """
    module, attr = import_path.rsplit('.', 1)
    try:
        mod = import_module(module)
    except ImportError, e:
        raise ImproperlyConfigured('Error importing module %s: "%s"' %
                                   (module, e))
    try:
        Finder = getattr(mod, attr)
    except AttributeError:
        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
                                   'class.' % (module, attr))
    if not issubclass(Finder, BaseFinder):
        raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
                                   (Finder, BaseFinder))
    return Finder()
get_finder = memoize(_get_finder, _finders, 1)
Loading