diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 2714bfb..6d156ec 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -193,7 +193,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.media',
 #    'django.core.context_processors.request',
     'django.contrib.messages.context_processors.messages',
 )
@@ -201,11 +201,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'
@@ -522,3 +517,35 @@ 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 tuple of two-tuples with a name and the path of additional directories
+# which hold static files and should be taken into account during resolving
+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.StorageFinder',
+)
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/admin/", "/media/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
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
--- /dev/null
+++ b/django/contrib/staticfiles/context_processors.py
@@ -0,0 +1,7 @@
+from django.conf import settings
+
+def media(request):
+    return {
+        'STATICFILES_URL': settings.STATICFILES_URL,
+        'MEDIA_URL': settings.MEDIA_URL,
+    }
diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
new file mode 100644
index 0000000..167df2b
--- /dev/null
+++ b/django/contrib/staticfiles/finders.py
@@ -0,0 +1,190 @@
+import os
+from django.conf import settings
+from django.db import models
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+from django.core.files.storage import default_storage
+
+from django.contrib.staticfiles import utils
+
+
+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("Finder subclasses need to implement find()")
+
+
+class FileSystemFinder(BaseFinder):
+    """
+    A static files finder that uses the ``STATICFILES_DIRS`` setting
+    to locate files.
+    """
+    def find(self, path, all=False):
+        """
+        Looks for files in the extra media locations
+        as defined in ``STATICFILES_DIRS``.
+        """
+        matches = []
+        for root in settings.STATICFILES_DIRS:
+            if isinstance(root, (list, tuple)):
+                prefix, root = root
+            else:
+                prefix = ''
+            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
+
+
+class AppDirectoriesFinder(BaseFinder):
+    """
+    A static files finder that looks in the ``media`` directory of each app.
+    """
+    def find(self, path, all=False):
+        """
+        Looks for files in the app directories.
+        """
+        matches = []
+        for app in models.get_apps():
+            app_matches = self.find_in_app(app, path, all=all)
+            if app_matches:
+                if not all:
+                    return app_matches
+                matches.extend(app_matches)
+        return matches
+
+    def find_in_app(self, app, path, all=False):
+        """
+        Find a requested static file in an app's media locations.
+
+        If ``all`` is ``False`` (default), return the first matching
+        absolute path (or ``None`` if no match). Otherwise return a list of
+        found absolute paths.
+
+        """
+        prefix = utils.get_app_prefix(app)
+        if prefix:
+            prefix = '%s/' % prefix
+            if not path.startswith(prefix):
+                return []
+            path = path[len(prefix):]
+        paths = []
+        storage = utils.app_static_storage(app)
+        if storage and storage.exists(path):
+            matched_path = storage.path(path)
+            if not all:
+                return matched_path
+            paths.append(matched_path)
+        return paths
+
+
+class StorageFinder(BaseFinder):
+    """
+    A static files finder that uses the default storage backend.
+    """
+    static_storage = default_storage
+
+    def __init__(self, storage=None, *args, **kwargs):
+        if storage is not None:
+            self.static_storage = storage
+        super(StorageFinder, self).__init__(*args, **kwargs)
+
+    def find(self, path, all=False):
+        """
+        Last resort, looks for files in the
+        static files storage if it's local.
+        """
+        try:
+            self.static_storage.path('')
+        except NotImplementedError:
+            pass
+        else:
+            if self.static_storage.exists(path):
+                match = self.static_storage.path(path)
+                if all:
+                    match = [match]
+                return match
+        return []
+
+
+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_path in settings.STATICFILES_FINDERS:
+        finder = get_finder(finder_path)()
+        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_finder(import_path):
+    """
+    Imports the message storage class described by import_path, where
+    import_path is the full Python path to the class.
+    """
+    try:
+        dot = import_path.rindex('.')
+    except ValueError:
+        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
+    module, classname = import_path[:dot], import_path[dot + 1:]
+    try:
+        mod = import_module(module)
+    except ImportError, e:
+        raise ImproperlyConfigured('Error importing module %s: "%s"' %
+                                   (module, e))
+    try:
+        cls = getattr(mod, classname)
+    except AttributeError:
+        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
+                                   'class.' % (module, classname))
+    if not issubclass(cls, BaseFinder):
+        raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
+                                   (cls, BaseFinder))
+    return cls
diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
new file mode 100644
index 0000000..4765591
--- /dev/null
+++ b/django/contrib/staticfiles/handlers.py
@@ -0,0 +1,64 @@
+import os
+import urllib
+
+
+from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
+from django.http import Http404
+from django.conf import settings
+from django.views import static
+
+class StaticFilesHandler(WSGIHandler):
+    """
+    WSGI middleware that intercepts calls to the static files directory, as
+    defined by the STATICFILES_URL setting, and serves those files.
+    """
+    media_dir = settings.STATICFILES_ROOT
+    media_url = settings.STATICFILES_URL
+
+    def __init__(self, application, media_dir=None):
+        self.application = application
+        if media_dir:
+            self.media_dir = media_dir
+
+    def file_path(self, url):
+        """
+        Returns the relative path to the media file on disk for the given URL.
+
+        The passed URL is assumed to begin with ``media_url``.  If the
+        resultant file path is outside the media directory, then a ValueError
+        is raised.
+        """
+        # Remove ``media_url``.
+        relative_url = url[len(self.media_url):]
+        return urllib.url2pathname(relative_url)
+
+    def serve(self, request, path):
+        from django.contrib.staticfiles import finders
+        absolute_path = finders.find(path)
+        if not absolute_path:
+            raise Http404('%r could not be matched to a static file.' % path)
+        absolute_path, filename = os.path.split(absolute_path)
+        return static.serve(request, path=filename, document_root=absolute_path)
+
+    def __call__(self, environ, start_response):
+        # Ignore requests that aren't under the media prefix.
+        # Also ignore all requests if prefix isn't a relative URL.
+        if (self.media_url.startswith('http://') or
+            self.media_url.startswith('https://') or
+                not environ['PATH_INFO'].startswith(self.media_url)):
+            return self.application(environ, start_response)
+        request = self.application.request_class(environ)
+        try:
+            response = self.serve(request, self.file_path(environ['PATH_INFO']))
+        except Http404:
+            status = '404 NOT FOUND'
+            start_response(status, {'Content-type': 'text/plain'}.items())
+            return [str('Page not found: %s' % environ['PATH_INFO'])]
+        status_text = STATUS_CODE_TEXT[response.status_code]
+        status = '%s %s' % (response.status_code, status_text)
+        response_headers = [(str(k), str(v)) for k, v in response.items()]
+        for c in response.cookies.values():
+            response_headers.append(('Set-Cookie', str(c.output(header=''))))
+        start_response(status, response_headers)
+        return response
+
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
--- /dev/null
+++ b/django/contrib/staticfiles/management/commands/__init__.py
@@ -0,0 +1,3 @@
+import logging
+
+
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
--- /dev/null
+++ b/django/contrib/staticfiles/management/commands/build_static.py
@@ -0,0 +1,196 @@
+import os
+import sys
+import shutil
+from optparse import make_option
+
+from django.conf import settings
+from django.core.files.storage import FileSystemStorage, get_storage_class
+from django.core.management.base import CommandError, OptionalAppCommand
+
+from django.contrib.staticfiles import utils
+
+
+class Command(OptionalAppCommand):
+    """
+    Command that allows to copy or symlink media files from different
+    locations to the settings.STATICFILES_ROOT.
+    """
+    option_list = OptionalAppCommand.option_list + (
+        make_option('--noinput', action='store_false', dest='interactive',
+            default=True, help="Do NOT prompt the user for input of any "
+                "kind."),
+        make_option('-i', '--ignore', action='append', default=[],
+            dest='ignore_patterns', metavar='PATTERN',
+            help="Ignore files or directories matching this glob-style "
+                "pattern. Use multiple times to ignore more."),
+        make_option('-n', '--dry-run', action='store_true', dest='dry_run',
+            help="Do everything except modify the filesystem."),
+        make_option('-l', '--link', action='store_true', dest='link',
+            help="Create a symbolic link to each file instead of copying."),
+        make_option('--exclude-dirs', action='store_true',
+            help="Exclude additional static locations specified in the "
+                "STATICFILES_DIRS setting."),
+        make_option('--no-default-ignore', action='store_false',
+            dest='use_default_ignore_patterns', default=True,
+            help="Don't ignore the common private glob-style patterns 'CVS', "
+                "'.*' and '*~'."),
+    )
+    help = ("Copy static media files from apps and other locations in a "
+            "single location.")
+
+    def handle(self, *app_labels, **options):
+        ignore_patterns = options['ignore_patterns']
+        if options['use_default_ignore_patterns']:
+            ignore_patterns += ['CVS', '.*', '*~']
+            options['ignore_patterns'] = ignore_patterns 
+        options['skipped_files'] = []
+        options['copied_files'] = []
+        options['symlinked_files'] = []
+        options['destination_storage'] = get_storage_class(settings.STATICFILES_STORAGE)()
+        try:
+            destination_paths = utils.get_files(options['destination_storage'], ignore_patterns)
+        except OSError:
+            # The destination storage location may not exist yet. It'll get
+            # created when the first file is copied.
+            destination_paths = []
+        options['destination_paths'] = destination_paths
+        try:
+            options['destination_storage'].path('')
+            destination_local = True
+        except NotImplementedError:
+            destination_local = False
+        options['destination_local'] = destination_local
+        if options.get('link', False):
+            if sys.platform == 'win32':
+                message = "Symlinking is not supported by this platform (%s)."
+                raise CommandError(message % sys.platform)
+            if not destination_local:
+                raise CommandError("Can't symlink to a remote destination.")
+        # Warn before doing anything more.
+        if options.get('interactive'):
+            confirm = raw_input("""
+You have requested to collate static media files and copy them to the
+destination location as specified in your settings file. 
+
+This will overwrite existing files. Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """)
+            if confirm != 'yes':
+                raise CommandError("Static files build cancelled.")
+        return super(Command, self).handle(*app_labels, **options)
+
+    def pre_handle_apps(self, **options):
+        """
+        Copy all files from a directory.
+
+        """
+        if not options.get('exclude_dirs', False):
+            ignore_patterns = options['ignore_patterns']
+            for root in settings.STATICFILES_DIRS:
+                if isinstance(root, (list, tuple)):
+                    prefix, root = root
+                else:
+                    prefix = ''
+                source_storage = FileSystemStorage(location=root)
+                for source in utils.get_files(source_storage, ignore_patterns):
+                    self.copy_file(source, prefix, source_storage, **options)
+
+    def post_handle_apps(self, **options):
+        copied_files = options['copied_files']
+        symlinked_files = options['symlinked_files']
+        logger = self.get_logger()
+        count = len(copied_files) + len(symlinked_files)
+        logger.info("%s static file%s built." % (count,
+                                                 count != 1 and 's' or ''))
+
+    def handle_app(self, app, **options):
+        """
+        Copy all static media files from an application.
+        
+        """
+        ignore_patterns = options['ignore_patterns']
+        prefix = utils.get_app_prefix(app)
+        storage = utils.app_static_storage(app)
+        if storage:
+            for path in utils.get_files(storage, ignore_patterns):
+                self.copy_file(path, prefix, storage, **options)
+
+    def excluded_app(self, app, **options):
+        """
+        Gather all the static media files for excluded apps.
+        
+        This is so a warning can be issued for later copied files which would
+        normally be copied by excluded apps.
+        
+        """
+        skipped_files = options['skipped_files']
+        ignore_patterns = options['ignore_patterns']
+        all_files = utils.get_files_for_app(app, ignore_patterns)
+        skipped_files.extend(all_files)
+
+    def copy_file(self, source, destination_prefix, source_storage, **options):
+        """
+        Attempt to copy (or symlink) `source` to `destination`, returning True
+        if successful.
+        """
+        destination_storage = options['destination_storage']
+        dry_run = options.get('dry_run', False)
+        logger = self.get_logger()
+        if destination_prefix:
+            destination = '/'.join([destination_prefix, source])
+        else:
+            destination = source
+
+        if destination in options['copied_files']:
+            logger.warning("Skipping duplicate file (already copied earlier):"
+                           "\n  %s" % destination)
+            return False
+        if destination in options['symlinked_files']:
+            logger.warning("Skipping duplicate file (already linked earlier):"
+                           "\n  %s" % destination)
+            return False
+        if destination in options['skipped_files']:
+            logger.warning("Copying file that would normally be provided by "
+                           "an excluded application:\n  %s" % destination)
+        source_path = source_storage.path(source)
+        if destination in options['destination_paths']:
+            if dry_run:
+                logger.info("Pretending to delete:\n  %s" % destination)
+            else:
+                logger.debug("Deleting:\n  %s" % destination)
+                destination_storage.delete(destination)
+        if options.get('link', False):
+            destination_path = destination_storage.path(destination)
+            if dry_run:
+                logger.info("Pretending to symlink:\n  %s\nto:\n  %s"
+                            % (source_path, destination_path))
+            else:
+                logger.debug("Symlinking:\n  %s\nto:\n  %s"
+                             % (source_path, destination_path))
+                try:
+                    os.makedirs(os.path.dirname(destination_path))
+                except OSError:
+                    pass
+                os.symlink(source_path, destination_path)
+            options['symlinked_files'].append(destination)
+        else:
+            if dry_run:
+                logger.info("Pretending to copy:\n  %s\nto:\n  %s"
+                            % (source_path, destination))
+            else:
+                if options['destination_local']:
+                    destination_path = destination_storage.path(destination)
+                    try:
+                        os.makedirs(os.path.dirname(destination_path))
+                    except OSError:
+                        pass
+                    shutil.copy2(source_path, destination_path)
+                    logger.debug("Copying:\n  %s\nto:\n  %s"
+                                 % (source_path, destination_path))
+                else:
+                    source_file = source_storage.open(source)
+                    destination_storage.write(destination, source_file)
+                    logger.debug("Copying:\n  %s\nto:\n  %s"
+                                 % (source_path, destination))
+            options['copied_files'].append(destination)
+        return True
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
--- /dev/null
+++ b/django/contrib/staticfiles/management/commands/find_static.py
@@ -0,0 +1,24 @@
+import os
+from optparse import make_option
+from django.core.management.base import LabelCommand
+
+from django.contrib.staticfiles import finders
+
+class Command(LabelCommand):
+    help = "Finds the absolute paths for the given static file(s)."
+    args = "[static_file ...]"
+    label = 'static file'
+    option_list = LabelCommand.option_list + (
+        make_option('--first', action='store_false', dest='all', default=True,
+                    help="Only return the first match for each static file."),
+    )
+
+    def handle_label(self, media_path, **options):
+        logger = self.get_logger()
+        all = options['all']
+        result = finders.find(media_path, all=all)
+        if not result:
+            logger.warning("No matching file found for %r." % media_path)
+            return
+        output = '\n  '.join((os.path.realpath(path) for path in result))
+        logger.info("Found %r here:\n  %s" % (media_path, output))
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
--- /dev/null
+++ b/django/contrib/staticfiles/storage.py
@@ -0,0 +1,29 @@
+import os, posixpath
+
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+from django.core.files.storage import FileSystemStorage, get_storage_class
+from django.utils.functional import LazyObject
+
+
+class StaticFilesStorage(FileSystemStorage):
+    """
+    Standard file system storage for site media files.
+    
+    The defaults for ``location`` and ``base_url`` are
+    ``STATICFILES_ROOT`` and ``STATICFILES_URL``.
+    """
+    def __init__(self, location=None, base_url=None, *args, **kwargs):
+        if location is None:
+            location = settings.STATICFILES_ROOT
+        if base_url is None:
+            base_url = settings.STATICFILES_URL
+        if not location:
+            raise ImproperlyConfigured("You're using the staticfiles app "
+                "without having set the STATICFILES_ROOT setting. Set it to "
+                "the absolute path of the directory that holds static media.")
+        if not base_url:
+            raise ImproperlyConfigured("You're using the staticfiles app "
+                "without having set the STATICFILES_URL setting. Set it to "
+                "URL that handles the files served from STATICFILES_ROOT.")
+        super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py
new file mode 100644
index 0000000..f8a30ac
--- /dev/null
+++ b/django/contrib/staticfiles/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import patterns, url
+from django.conf import settings
+
+urlpatterns = []
+
+# only serve non-fqdn URLs
+if ':' not in settings.STATICFILES_URL:
+    urlpatterns += patterns('',
+        url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
+    )
diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py
new file mode 100644
index 0000000..b533747
--- /dev/null
+++ b/django/contrib/staticfiles/utils.py
@@ -0,0 +1,81 @@
+import os
+import fnmatch
+
+from django.core.files.storage import FileSystemStorage
+from django.utils.importlib import import_module
+
+def get_files_for_app(app, ignore_patterns=[]):
+    """
+    Return a list containing the relative source paths for all files that
+    should be copied for an app.
+    
+    """
+    prefix = get_app_prefix(app)
+    files = []
+    storage = app_static_storage(app)
+    if storage:
+        for path in get_files(storage, ignore_patterns):
+            if prefix:
+                path = '/'.join([prefix, path])
+            files.append(path)
+    return files
+
+def app_static_storage(app):
+    """
+    Returns a static file storage if available in the given app.
+    
+    """
+    # "app" is actually the models module of the app. Remove the '.models'. 
+    app_module = '.'.join(app.__name__.split('.')[:-1])
+
+    # The models module may be a package in which case dirname(app.__file__)
+    # would be wrong. Import the actual app as opposed to the models module.
+    app = import_module(app_module)
+    app_root = os.path.dirname(app.__file__)
+    location = os.path.join(app_root, 'media')
+    if not os.path.isdir(location):
+        return None
+    return FileSystemStorage(location=location)
+
+def get_app_prefix(app):
+    """
+    Return the path name that should be prepended to files for this app.
+    
+    """
+    # "app" is actually the models module of the app. Remove the '.models'. 
+    bits = app.__name__.split('.')[:-1]
+    app_name = bits[-1]
+    app_module = '.'.join(bits)
+    if app_module == 'django.contrib.admin':
+        return app_name
+    else:
+        return None
+
+def get_files(storage, ignore_patterns=[], location=''):
+    """
+    Recursively walk the storage directories gathering a complete list of files
+    that should be copied, returning this list.
+    
+    """
+    def is_ignored(path):
+        """
+        Return True or False depending on whether the ``path`` should be
+        ignored (if it matches any pattern in ``ignore_patterns``).
+        
+        """
+        for pattern in ignore_patterns:
+            if fnmatch.fnmatchcase(path, pattern):
+                return True
+        return False
+
+    directories, files = storage.listdir(location)
+    static_files = [location and '/'.join([location, fn]) or fn
+                    for fn in files
+                    if not is_ignored(fn)]
+    for dir in directories:
+        if is_ignored(dir):
+            continue
+        if location:
+            dir = '/'.join([location, dir])
+        static_files.extend(get_files(storage, ignore_patterns, dir))
+    return static_files
diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py
new file mode 100644
index 0000000..ffd5264
--- /dev/null
+++ b/django/contrib/staticfiles/views.py
@@ -0,0 +1,31 @@
+"""
+Views and functions for serving static files. These are only to be used during
+development, and SHOULD NOT be used in a production setting.
+
+"""
+import os
+from django import http
+from django.views import static
+
+from django.contrib.staticfiles import finders
+
+
+def serve(request, path, show_indexes=False):
+    """
+    Serve static files from locations inferred from the static files finders.
+
+    To use, put a URL pattern such as::
+
+        (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve')
+
+    in your URLconf. You may also set ``show_indexes`` to ``True`` if you'd
+    like to serve a basic index of the directory.  This index view will use the
+    template hardcoded below, but if you'd like to override it, you can create
+    a template called ``static/directory_index``.
+    """
+    absolute_path = finders.find(path)
+    if not absolute_path:
+        raise http.Http404('%r could not be matched to a static file.' % path)
+    absolute_path, filename = os.path.split(absolute_path)
+    return static.serve(request, path=filename, document_root=absolute_path,
+                        show_indexes=show_indexes)
diff --git a/django/core/context_processors.py b/django/core/context_processors.py
index a5f29df..08bdc75 100644
--- a/django/core/context_processors.py
+++ b/django/core/context_processors.py
@@ -71,7 +71,15 @@ def media(request):
     Adds media-related context variables to the context.
 
     """
-    return {'MEDIA_URL': settings.MEDIA_URL}
+    import warnings
+    warnings.warn(
+        "The context processor at `django.core.context_processors.media` is " \
+        "deprecated; use the path `django.contrib.staticfiles.context_processors.media` " \
+        "instead.",
+        PendingDeprecationWarning
+    )
+    from django.contrib.staticfiles.context_processors import media as media_context_processor
+    return media_context_processor(request)
 
 def request(request):
     return {'request': request}
diff --git a/django/core/management/base.py b/django/core/management/base.py
index dcd83ff..e00ebc1 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -5,6 +5,7 @@ be executed through ``django-admin.py`` or ``manage.py``).
 """
 
 import os
+import logging
 import sys
 from optparse import make_option, OptionParser
 
@@ -135,9 +136,25 @@ class BaseCommand(object):
     requires_model_validation = True
     output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
 
+    # OMG FIXME -- what is wrong with you? we don't want to use our own logger here, use the real thing man!
+    logger_parent = 'django.core.management.base.commands'
+    logger_name = 'base'
+    logger_handler = logging.StreamHandler()
+    logger_format = '%(message)s'
+    logger_levels = {
+        0: logging.ERROR,
+        1: logging.INFO,
+        2: logging.DEBUG,
+    }
+
     def __init__(self):
         self.style = color_style()
 
+    def get_logger(self):
+        parts = [self.logger_parent, self.logger_name]
+        name = '.'.join([part for part in parts if part])
+        return logging.getLogger(name)
+
     def get_version(self):
         """
         Return the Django version, which should be correct for all
@@ -199,6 +216,12 @@ class BaseCommand(object):
         stderr.
 
         """
+        verbosity = options.get('verbosity', 1)
+        logger = self.get_logger()
+        logger.setLevel(self.logger_levels[int(verbosity)])
+        formatter = logging.Formatter(self.logger_format)
+        self.logger_handler.setFormatter(formatter)
+        logger.addHandler(self.logger_handler)
         # Switch to English, because django-admin.py creates database content
         # like permissions, and those shouldn't contain any translations.
         # But only do this if we can assume we have a working settings file,
@@ -232,6 +255,8 @@ class BaseCommand(object):
         except CommandError, e:
             self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
             sys.exit(1)
+        finally:
+            logger.removeHandler(self.logger_handler)
 
     def validate(self, app=None, display_num_errors=False):
         """
@@ -297,6 +322,101 @@ class AppCommand(BaseCommand):
         """
         raise NotImplementedError()
 
+class OptionalAppCommand(BaseCommand):
+    """
+    A management command which optionally takes one or more installed
+    application names as arguments, and does something with each of them.
+
+    If no application names are provided, all the applications are used.
+
+    The order in which applications are processed is determined by the order
+    given in INSTALLED_APPS. This differs from Django's AppCommand (it uses the
+    order the apps are given in the management command).
+
+    Rather than implementing ``handle()``, subclasses must implement
+    ``handle_app()``, which will be called once for each application.
+
+    Subclasses can also optionally implement ``excluded_app()`` to run
+    processes on apps which were excluded.
+
+    """
+    args = '[appname appname ...]'
+    option_list = BaseCommand.option_list + (
+        make_option('-e', '--exclude', dest='exclude', action='append',
+            default=[], help='App to exclude (use multiple --exclude to '
+            'exclude multiple apps).'),
+    )
+
+    def handle(self, *app_labels, **options):
+        from django.db import models
+        # Get all the apps, checking for common errors.
+        try:
+            all_apps = models.get_apps()
+        except (ImproperlyConfigured, ImportError), e:
+            raise CommandError("%s. Are you sure your INSTALLED_APPS setting "
+                               "is correct?" % e)
+        # Build the app_list.
+        app_list = []
+        used = 0
+        for app in all_apps:
+            app_label = app.__name__.split('.')[-2]
+            if not app_labels or app_label in app_labels:
+                used += 1
+                if app_label not in options['exclude']:
+                    app_list.append(app)
+        # Check that all app_labels were used.
+        if app_labels and used != len(app_labels): 
+            raise CommandError('Could not find the following app(s): %s' %
+                               ', '.join(app_labels))
+        # Handle all the apps (either via handle_app or excluded_app),
+        # collating any output.
+        output = []
+        pre_output = self.pre_handle_apps(**options)
+        if pre_output:
+            output.append(pre_output)
+        for app in all_apps:
+            if app in app_list:
+                handle_method = self.handle_app
+            else:
+                handle_method = self.excluded_app
+            app_output = handle_method(app, **options)
+            if app_output:
+                output.append(app_output)
+        post_output = self.post_handle_apps(**options)
+        if post_output:
+            output.append(post_output)
+        return '\n'.join(output)
+
+    def handle_app(self, app, **options):
+        """
+        Perform the command's actions for ``app``, which will be the
+        Python module corresponding to an application name given on
+        the command line.
+
+        """
+        raise NotImplementedError()
+
+    def excluded_app(self, app, **options):
+        """
+        A hook for commands to parse apps which were excluded.
+
+        """
+
+    def pre_handle_apps(self, **options):
+        """
+        A hook for commands to do something before the applications are
+        processed.
+
+        """
+
+    def post_handle_apps(self, **options):
+        """
+        A hook for commands to do something after all applications have been
+        processed.
+
+        """
+
+
 class LabelCommand(BaseCommand):
     """
     A management command which takes one or more arbitrary arguments
diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
index fc2c694..21391e8 100644
--- a/django/core/management/commands/runserver.py
+++ b/django/core/management/commands/runserver.py
@@ -1,7 +1,9 @@
-from django.core.management.base import BaseCommand, CommandError
 from optparse import make_option
 import os
 import sys
+import warnings
+
+from django.core.management.base import BaseCommand, CommandError
 
 class Command(BaseCommand):
     option_list = BaseCommand.option_list + (
@@ -20,6 +22,7 @@ class Command(BaseCommand):
         import django
         from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
         from django.core.handlers.wsgi import WSGIHandler
+        from django.contrib.staticfiles.handlers import StaticFilesHandler
         if args:
             raise CommandError('Usage is runserver %s' % self.args)
         if not addrport:
@@ -56,7 +59,10 @@ class Command(BaseCommand):
             translation.activate(settings.LANGUAGE_CODE)
 
             try:
-                handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
+                handler = WSGIHandler()
+                handler = StaticFilesHandler(handler)
+                # serve admin media like old-school (deprecation pending)
+                handler = AdminMediaHandler(handler, admin_media_path)
                 run(addr, int(port), handler)
             except WSGIServerException, e:
                 # Use helpful error messages instead of ugly tracebacks.
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index dae4297..2da05de 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -8,16 +8,17 @@ been reviewed for security issues. Don't use it for production use.
 """
 
 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
-import mimetypes
 import os
 import re
-import stat
 import sys
 import urllib
+import warnings
 
 from django.core.management.color import color_style
 from django.utils.http import http_date
 from django.utils._os import safe_join
+from django.contrib.staticfiles.handlers import StaticFilesHandler
+from django.views import static
 
 __version__ = "0.1"
 __all__ = ['WSGIServer','WSGIRequestHandler']
@@ -633,86 +634,48 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
 
         sys.stderr.write(msg)
 
-class AdminMediaHandler(object):
+
+class AdminMediaHandler(StaticFilesHandler):
     """
     WSGI middleware that intercepts calls to the admin media directory, as
     defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
     Use this ONLY LOCALLY, for development! This hasn't been tested for
     security and is not super efficient.
     """
-    def __init__(self, application, media_dir=None):
+
+    @property
+    def media_dir(self):
+        import django
+        return os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
+
+    @property
+    def media_url(self):
         from django.conf import settings
-        self.application = application
-        if not media_dir:
-            import django
-            self.media_dir = \
-                os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
-        else:
-            self.media_dir = media_dir
-        self.media_url = settings.ADMIN_MEDIA_PREFIX
+        return settings.ADMIN_MEDIA_PREFIX
+
+    def __init__(self, application, media_dir=None):
+        warnings.warn('The AdminMediaHandler handler is deprecated; use the '
+            '`django.contrib.staticfiles.handlers.StaticFilesHandler` instead.',
+            PendingDeprecationWarning)
+        super(AdminMediaHandler, self).__init__(application, media_dir)
 
     def file_path(self, url):
         """
         Returns the path to the media file on disk for the given URL.
 
-        The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX.  If the
+        The passed URL is assumed to begin with ``media_url``.  If the
         resultant file path is outside the media directory, then a ValueError
         is raised.
         """
-        # Remove ADMIN_MEDIA_PREFIX.
+        # Remove ``media_url``.
         relative_url = url[len(self.media_url):]
         relative_path = urllib.url2pathname(relative_url)
         return safe_join(self.media_dir, relative_path)
 
-    def __call__(self, environ, start_response):
-        import os.path
-
-        # Ignore requests that aren't under ADMIN_MEDIA_PREFIX. Also ignore
-        # all requests if ADMIN_MEDIA_PREFIX isn't a relative URL.
-        if self.media_url.startswith('http://') or self.media_url.startswith('https://') \
-            or not environ['PATH_INFO'].startswith(self.media_url):
-            return self.application(environ, start_response)
+    def serve(self, request, path):
+        document_root, path = os.path.split(path)
+        return static.serve(request, path, document_root=document_root)
 
-        # Find the admin file and serve it up, if it exists and is readable.
-        try:
-            file_path = self.file_path(environ['PATH_INFO'])
-        except ValueError: # Resulting file path was not valid.
-            status = '404 NOT FOUND'
-            headers = {'Content-type': 'text/plain'}
-            output = ['Page not found: %s' % environ['PATH_INFO']]
-            start_response(status, headers.items())
-            return output
-        if not os.path.exists(file_path):
-            status = '404 NOT FOUND'
-            headers = {'Content-type': 'text/plain'}
-            output = ['Page not found: %s' % environ['PATH_INFO']]
-        else:
-            try:
-                fp = open(file_path, 'rb')
-            except IOError:
-                status = '401 UNAUTHORIZED'
-                headers = {'Content-type': 'text/plain'}
-                output = ['Permission denied: %s' % environ['PATH_INFO']]
-            else:
-                # This is a very simple implementation of conditional GET with
-                # the Last-Modified header. It makes media files a bit speedier
-                # because the files are only read off disk for the first
-                # request (assuming the browser/client supports conditional
-                # GET).
-                mtime = http_date(os.stat(file_path)[stat.ST_MTIME])
-                headers = {'Last-Modified': mtime}
-                if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
-                    status = '304 NOT MODIFIED'
-                    output = []
-                else:
-                    status = '200 OK'
-                    mime_type = mimetypes.guess_type(file_path)[0]
-                    if mime_type:
-                        headers['Content-Type'] = mime_type
-                    output = [fp.read()]
-                    fp.close()
-        start_response(status, headers.items())
-        return output
 
 def run(addr, port, wsgi_handler):
     server_address = (addr, port)
diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
index 4763982..29f169c 100644
--- a/tests/regressiontests/servers/tests.py
+++ b/tests/regressiontests/servers/tests.py
@@ -9,6 +9,7 @@ from django.test import TestCase
 from django.core.handlers.wsgi import WSGIHandler
 from django.core.servers.basehttp import AdminMediaHandler
 
+from django.conf import settings
 
 class AdminMediaHandlerTests(TestCase):
 
@@ -25,7 +26,7 @@ class AdminMediaHandlerTests(TestCase):
         """
         # Cases that should work on all platforms.
         data = (
-            ('/media/css/base.css', ('css', 'base.css')),
+            ('%scss/base.css' % settings.ADMIN_MEDIA_PREFIX, ('css', 'base.css')),
         )
         # Cases that should raise an exception.
         bad_data = ()
@@ -34,19 +35,19 @@ class AdminMediaHandlerTests(TestCase):
         if os.sep == '/':
             data += (
                 # URL, tuple of relative path parts.
-                ('/media/\\css/base.css', ('\\css', 'base.css')),
+                ('%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX, ('\\css', 'base.css')),
             )
             bad_data += (
-                '/media//css/base.css',
-                '/media////css/base.css',
-                '/media/../css/base.css',
+                '%s/css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+                '%s///css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+                '%s../css/base.css' % settings.ADMIN_MEDIA_PREFIX,
             )
         elif os.sep == '\\':
             bad_data += (
-                '/media/C:\css/base.css',
-                '/media//\\css/base.css',
-                '/media/\\css/base.css',
-                '/media/\\\\css/base.css'
+                '%sC:\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+                '%s/\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+                '%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
+                '%s\\\\css/base.css' % settings.ADMIN_MEDIA_PREFIX
             )
         for url, path_tuple in data:
             try:
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt
@@ -0,0 +1 @@
+file2 in no_label_app
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden
@@ -0,0 +1 @@
+This file should be ignored.
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS
@@ -0,0 +1 @@
+This file should be ignored.
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~
@@ -0,0 +1 @@
+This file should be ignored.
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt
@@ -0,0 +1 @@
+In app media directory.
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt
@@ -0,0 +1 @@
+file1 in the app dir
\ No newline at end of file
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme
@@ -0,0 +1 @@
+This file should be ignored.
\ No newline at end of file
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt
@@ -0,0 +1 @@
+File in otherdir.
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt
@@ -0,0 +1 @@
+Can we find this file?
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/project/documents/test.txt
@@ -0,0 +1 @@
+Can we find this file?
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt
@@ -0,0 +1,2 @@
+In STATICFILES_DIRS directory.
+
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt
@@ -0,0 +1 @@
+Media file.
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
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/project/site_media/static/test/storage.txt
@@ -0,0 +1 @@
+Yeah!
\ No newline at end of file
diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
new file mode 100644
index 0000000..123a946
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/tests.py
@@ -0,0 +1,303 @@
+import tempfile
+import shutil
+import os
+import sys
+from cStringIO import StringIO
+import posixpath
+
+from django.test import TestCase, Client
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.management import call_command
+from django.db.models.loading import load_app
+
+from django.contrib.staticfiles import finders, storage
+
+TEST_ROOT = os.path.dirname(__file__)
+
+
+class StaticFilesTestCase(TestCase):
+    """
+    Test case with a couple utility assertions.
+    """
+    def setUp(self):
+        self.old_staticfiles_url = settings.STATICFILES_URL
+        self.old_staticfiles_root = settings.STATICFILES_ROOT
+        self.old_staticfiles_dirs = settings.STATICFILES_DIRS
+        self.old_installed_apps = settings.INSTALLED_APPS
+        self.old_media_root = settings.MEDIA_ROOT
+        self.old_media_url = settings.MEDIA_URL
+        self.old_admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
+
+        # We have to load these apps to test staticfiles.
+        load_app('regressiontests.staticfiles_tests.apps.test')
+        load_app('regressiontests.staticfiles_tests.apps.no_label')
+        site_media = os.path.join(TEST_ROOT, 'project', 'site_media')
+        settings.MEDIA_ROOT =  os.path.join(site_media, 'media')
+        settings.MEDIA_URL = '/media/'
+        settings.ADMIN_MEDIA_PREFIX = posixpath.join(settings.STATICFILES_URL, 'admin/')
+        settings.STATICFILES_ROOT = os.path.join(site_media, 'static')
+        settings.STATICFILES_URL = '/static/'
+        settings.STATICFILES_DIRS = (
+            os.path.join(TEST_ROOT, 'project', 'documents'),
+        )
+
+    def tearDown(self):
+        settings.MEDIA_ROOT = self.old_media_root
+        settings.MEDIA_URL = self.old_media_url
+        settings.ADMIN_MEDIA_PREFIX = self.old_admin_media_prefix
+        settings.STATICFILES_ROOT = self.old_staticfiles_root
+        settings.STATICFILES_URL = self.old_staticfiles_url
+        settings.STATICFILES_DIRS = self.old_staticfiles_dirs
+        settings.INSTALLED_APPS = self.old_installed_apps
+
+    def assertFileContains(self, filepath, text):
+        self.failUnless(text in self._get_file(filepath),
+                        "'%s' not in '%s'" % (text, filepath))
+
+    def assertFileNotFound(self, filepath):
+        self.assertRaises(IOError, self._get_file, filepath)
+
+
+class TestingStaticFilesStorage(storage.StaticFilesStorage):
+
+    def __init__(self, location=None, base_url=None, *args, **kwargs):
+        location = tempfile.mkdtemp()
+        settings.STATICFILES_ROOT = location
+        super(TestingStaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
+
+
+class BuildStaticTestCase(StaticFilesTestCase):
+    """
+    Tests shared by all file-resolving features (build_static,
+    find_static, and static serve view).
+
+    This relies on the asserts defined in UtilityAssertsTestCase, but
+    is separated because some test cases need those asserts without
+    all these tests.
+    """
+    def setUp(self):
+        super(BuildStaticTestCase, self).setUp()
+        self.old_staticfiles_storage = settings.STATICFILES_STORAGE
+        self.old_root = settings.STATICFILES_ROOT
+        settings.STATICFILES_STORAGE = 'regressiontests.staticfiles_tests.tests.TestingStaticFilesStorage'
+        self.run_build_static()
+
+    def tearDown(self):
+        shutil.rmtree(settings.STATICFILES_ROOT)
+        settings.STATICFILES_ROOT = self.old_root
+        settings.STATICFILES_STORAGE = self.old_staticfiles_storage
+        super(BuildStaticTestCase, self).tearDown()
+
+    def run_build_static(self, **kwargs):
+        call_command('build_static', interactive=False, verbosity='0',
+                     ignore_patterns=['*.ignoreme'], **kwargs)
+
+    def _get_file(self, filepath):
+        assert filepath, 'filepath is empty.'
+        filepath = os.path.join(settings.STATICFILES_ROOT, filepath)
+        return open(filepath).read()
+
+
+class TestDefaults:
+    def test_staticfiles_dirs(self):
+        """
+        Can find a file in a STATICFILES_DIRS directory.
+
+        """
+        self.assertFileContains('test.txt', 'Can we find')
+
+    def test_staticfiles_dirs_subdir(self):
+        """
+        Can find a file in a subdirectory of a STATICFILES_DIRS
+        directory.
+
+        """
+        self.assertFileContains('subdir/test.txt', 'Can we find')
+
+    def test_staticfiles_dirs_priority(self):
+        """
+        File in STATICFILES_DIRS has priority over file in app.
+
+        """
+        self.assertFileContains('test/file.txt', 'STATICFILES_DIRS')
+
+    def test_app_files(self):
+        """
+        Can find a file in an app media/ directory.
+
+        """
+        self.assertFileContains('test/file1.txt', 'file1 in the app dir')
+
+
+class TestBuildStatic(BuildStaticTestCase, TestDefaults):
+    """
+    Test ``build_static`` management command.
+    """
+    def test_ignore(self):
+        """
+        Test that -i patterns are ignored.
+        """
+        self.assertFileNotFound('test/test.ignoreme')
+
+    def test_common_ignore_patterns(self):
+        """
+        Common ignore patterns (*~, .*, CVS) are ignored.
+        """
+        self.assertFileNotFound('test/.hidden')
+        self.assertFileNotFound('test/backup~')
+        self.assertFileNotFound('test/CVS')
+
+
+class TestBuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase):
+    """
+    Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
+    ``build_static`` management command.
+    """
+    def run_build_static(self):
+        super(TestBuildStaticExcludeNoDefaultIgnore, self).run_build_static(
+            exclude_dirs=True, use_default_ignore_patterns=False)
+
+    def test_exclude_dirs(self):
+        """
+        With --exclude-dirs, cannot find file in
+        STATICFILES_DIRS.
+
+        """
+        self.assertFileNotFound('test.txt')
+
+    def test_no_common_ignore_patterns(self):
+        """
+        With --no-default-ignore, common ignore patterns (*~, .*, CVS)
+        are not ignored.
+
+        """
+        self.assertFileContains('test/.hidden', 'should be ignored')
+        self.assertFileContains('test/backup~', 'should be ignored')
+        self.assertFileContains('test/CVS', 'should be ignored')
+
+
+class TestBuildStaticDryRun(BuildStaticTestCase):
+    """
+    Test ``--dry-run`` option for ``build_static`` management command.
+    """
+    def run_build_static(self):
+        super(TestBuildStaticDryRun, self).run_build_static(dry_run=True)
+
+    def test_no_files_created(self):
+        """
+        With --dry-run, no files created in destination dir.
+        """
+        self.assertEquals(os.listdir(settings.STATICFILES_ROOT), [])
+
+
+if sys.platform != 'win32':
+    class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults):
+        """
+        Test ``--link`` option for ``build_static`` management command.
+
+        Note that by inheriting ``BaseFileResolutionTests`` we repeat all
+        the standard file resolving tests here, to make sure using
+        ``--link`` does not change the file-selection semantics.
+        """
+        def run_build_static(self):
+            super(TestBuildStaticLinks, self).run_build_static(link=True)
+
+        def test_links_created(self):
+            """
+            With ``--link``, symbolic links are created.
+
+            """
+            self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt')))
+
+
+class TestServeStatic(StaticFilesTestCase):
+    """
+    Test static asset serving view.
+    """
+    urls = "regressiontests.staticfiles_tests.urls"
+
+    def _response(self, filepath):
+        return self.client.get(
+            posixpath.join(settings.STATICFILES_URL, filepath))
+
+    def assertFileContains(self, filepath, text):
+        self.assertContains(self._response(filepath), text)
+
+    def assertFileNotFound(self, filepath):
+        self.assertEquals(self._response(filepath).status_code, 404)
+
+
+class TestServeAdminMedia(TestServeStatic):
+    """
+    Test serving media from django.contrib.admin.
+    """
+    def test_serve_admin_media(self):
+        media_file = posixpath.join(
+            settings.ADMIN_MEDIA_PREFIX, 'css/base.css')
+        response = self.client.get(media_file)
+        self.assertContains(response, 'body')
+
+
+class FinderTestCase:
+    """
+    Base finder test mixin
+    """
+    def test_find_first(self):
+        src, dst = self.find_first
+        self.assertEquals(self.finder.find(src), dst)
+
+    def test_find_all(self):
+        src, dst = self.find_all
+        self.assertEquals(self.finder.find(src, all=True), dst)
+
+
+class TestFileSystemFinder(StaticFilesTestCase, FinderTestCase):
+    """
+    Test FileSystemFinder.
+    """
+    def setUp(self):
+        super(TestFileSystemFinder, self).setUp()
+        self.finder = finders.FileSystemFinder()
+        test_file_path = os.path.join(TEST_ROOT, 'project/documents/test/file.txt')
+        self.find_first = ("test/file.txt", test_file_path)
+        self.find_all = ("test/file.txt", [test_file_path])
+
+
+class TestAppDirectoriesFinder(StaticFilesTestCase, FinderTestCase):
+    """
+    Test AppDirectoriesFinder.
+    """
+    def setUp(self):
+        super(TestAppDirectoriesFinder, self).setUp()
+        self.finder = finders.AppDirectoriesFinder()
+        test_file_path = os.path.join(TEST_ROOT, 'apps/test/media/test/file1.txt')
+        self.find_first = ("test/file1.txt", test_file_path)
+        self.find_all = ("test/file1.txt", [test_file_path])
+
+
+class TestStorageFinder(StaticFilesTestCase, FinderTestCase):
+    """
+    Test StorageFinder.
+    """
+    def setUp(self):
+        super(TestStorageFinder, self).setUp()
+        self.finder = finders.StorageFinder(
+            storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT))
+        test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt')
+        self.find_first = ("media-file.txt", test_file_path)
+        self.find_all = ("media-file.txt", [test_file_path])
+
+
+class TestMiscFinder(TestCase):
+    """
+    A few misc finder tests.
+    """
+    def test_get_finder(self):
+        self.assertEquals(finders.FileSystemFinder,
+            finders.get_finder("django.contrib.staticfiles.finders.FileSystemFinder"))
+        self.assertRaises(ImproperlyConfigured,
+            finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder")
+        self.assertRaises(ImproperlyConfigured,
+            finders.get_finder, "foo.bar.FooBarFinder")
+
diff --git a/tests/regressiontests/staticfiles_tests/urls.py b/tests/regressiontests/staticfiles_tests/urls.py
new file mode 100644
index 0000000..061ec64
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/urls.py
@@ -0,0 +1,6 @@
+from django.conf import settings
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    url(r'^static/(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
+)
diff --git a/tests/runtests.py b/tests/runtests.py
index a52152b..c23f9fd 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -28,6 +28,7 @@ ALWAYS_INSTALLED_APPS = [
     'django.contrib.comments',
     'django.contrib.admin',
     'django.contrib.admindocs',
+    'django.contrib.staticfiles',
 ]
 
 def get_test_models():
diff --git a/tests/urls.py b/tests/urls.py
index 01d6408..e06dc33 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -41,4 +41,7 @@ urlpatterns = patterns('',
 
     # special headers views
     (r'special_headers/', include('regressiontests.special_headers.urls')),
+
+    # static files handling
+    (r'^', include('regressiontests.staticfiles_tests.urls')),
 )
