diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 2dabf3b..731c7e1 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -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.media',
 #    '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/'
diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
index 3c783d4..7791b47 100644
--- a/django/conf/project_template/settings.py
+++ b/django/conf/project_template/settings.py
@@ -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:
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..eb8c538
--- /dev/null
+++ b/django/contrib/staticfiles/finders.py
@@ -0,0 +1,244 @@
+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 AppMediaStorage
+
+_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 = AppMediaStorage
+
+    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.is_usable:
+                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.is_usable:
+            if storage.exists(path):
+                matched_path = storage.path(path)
+                if matched_path:
+                    return matched_path
+
+
+class DefaultStorageFinder(BaseFinder):
+    """
+    A static files finder that uses the default storage backend.
+    """
+    storage = default_storage
+
+    def __init__(self, storage=None, *args, **kwargs):
+        if storage is not None:
+            self.storage = storage
+        # Make sure we have an storage instance here.
+        if not isinstance(self.storage, (Storage, LazyObject)):
+            self.storage = self.storage()
+        super(DefaultStorageFinder, self).__init__(*args, **kwargs)
+
+    def find(self, path, all=False):
+        """
+        Last resort, 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
+
+
+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)
diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
new file mode 100644
index 0000000..ffd8d1d
--- /dev/null
+++ b/django/contrib/staticfiles/handlers.py
@@ -0,0 +1,64 @@
+import os
+import urllib
+from urlparse import urlparse
+
+from django.conf import settings
+from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
+from django.http import Http404
+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):
+        media_url_bits = urlparse(self.media_url)
+        # Ignore all requests if the host is provided as part of the media_url.
+        # Also ignore requests that aren't under the media path.
+        if (media_url_bits[1] or
+                not environ['PATH_INFO'].startswith(media_url_bits[2])):
+            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..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
--- /dev/null
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -0,0 +1,161 @@
+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, NoArgsCommand
+
+from django.contrib.staticfiles import utils, finders
+
+class Command(NoArgsCommand):
+    """
+    Command that allows to copy or symlink media files from different
+    locations to the settings.STATICFILES_ROOT.
+    """
+    option_list = NoArgsCommand.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('--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 = "Collect static files from apps and other locations in a single location."
+
+    def handle_noargs(self, **options):
+        ignore_patterns = options['ignore_patterns']
+        if options['use_default_ignore_patterns']:
+            ignore_patterns += ['CVS', '.*', '*~']
+        ignore_patterns = list(set(ignore_patterns))
+        self.copied_files = []
+        self.symlinked_files = []
+        self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
+
+        try:
+            self.destination_paths = utils.get_files(self.destination_storage, ignore_patterns)
+        except OSError:
+            # The destination storage location may not exist yet. It'll get
+            # created when the first file is copied.
+            self.destination_paths = []
+
+        try:
+            self.destination_storage.path('')
+        except NotImplementedError:
+            self.destination_local = False
+        else:
+            self.destination_local = True
+
+        if options.get('link', False):
+            if sys.platform == 'win32':
+                raise CommandError("Symlinking is not supported by this "
+                                   "platform (%s)." % sys.platform)
+            if not self.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 files and collect them at the destination
+location as specified in your settings file, %r.
+
+This will overwrite existing files.
+Are you sure you want to do this?
+
+Type 'yes' to continue, or 'no' to cancel: """ % settings.STATICFILES_ROOT)
+            if confirm != 'yes':
+                raise CommandError("Static files build cancelled.")
+
+        for finder in finders.get_finders():
+            for source, prefix, storage in finder.list(ignore_patterns):
+                self.copy_file(source, prefix, storage, **options)
+
+        verbosity = int(options.get('verbosity', 1))
+        count = len(self.copied_files) + len(self.symlinked_files)
+        if verbosity >= 1:
+            self.stdout.write("%s static file%s collected.\n" %
+                              (count, count != 1 and 's' or ''))
+
+    def copy_file(self, source, prefix, source_storage, **options):
+        """
+        Attempt to copy (or symlink) ``source`` to ``destination``,
+        returning True if successful.
+        """
+        source_path = source_storage.path(source)
+        if prefix:
+            destination = '/'.join([prefix, source])
+        else:
+            destination = source
+        dry_run = options.get('dry_run', False)
+        verbosity = int(options.get('verbosity', 1))
+
+        if destination in self.copied_files:
+            if verbosity >= 2:
+                self.stdout.write("Skipping duplicate file (already copied "
+                                  "earlier):\n  %s\n" % destination)
+            return False
+        if destination in self.symlinked_files:
+            if verbosity >= 2:
+                self.stdout.write("Skipping duplicate file (already linked "
+                                  "earlier):\n  %s\n" % destination)
+            return False
+        if destination in self.destination_paths:
+            if dry_run:
+                if verbosity >= 2:
+                    self.stdout.write("Pretending to delete:\n  %s\n"
+                                      % destination)
+            else:
+                if verbosity >= 2:
+                    self.stdout.write("Deleting:\n  %s\n" % destination)
+                self.destination_storage.delete(destination)
+
+        if options.get('link', False):
+            destination_path = self.destination_storage.path(destination)
+            if dry_run:
+                if verbosity >= 1:
+                    self.stdout.write("Pretending to symlink:\n  %s\nto:\n  %s\n"
+                                      % (source_path, destination_path))
+            else:
+                if verbosity >= 1:
+                    self.stdout.write("Symlinking:\n  %s\nto:\n  %s\n"
+                                      % (source_path, destination_path))
+                try:
+                    os.makedirs(os.path.dirname(destination_path))
+                except OSError:
+                    pass
+                os.symlink(source_path, destination_path)
+            self.symlinked_files.append(destination)
+        else:
+            if dry_run:
+                if verbosity >= 1:
+                    self.stdout.write("Pretending to copy:\n  %s\nto:\n  %s\n"
+                                      % (source_path, destination))
+            else:
+                if self.destination_local:
+                    destination_path = self.destination_storage.path(destination)
+                    try:
+                        os.makedirs(os.path.dirname(destination_path))
+                    except OSError:
+                        pass
+                    shutil.copy2(source_path, destination_path)
+                    if verbosity >= 1:
+                        self.stdout.write("Copying:\n  %s\nto:\n  %s\n"
+                                          % (source_path, destination_path))
+                else:
+                    source_file = source_storage.open(source)
+                    self.destination_storage.write(destination, source_file)
+                    if verbosity >= 1:
+                        self.stdout.write("Copying:\n  %s\nto:\n  %s\n"
+                                          % (source_path, destination))
+            self.copied_files.append(destination)
+        return True
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
--- /dev/null
+++ b/django/contrib/staticfiles/management/commands/findstatic.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 = "[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, path, **options):
+        verbosity = int(options.get('verbosity', 1))
+        result = finders.find(path, all=options['all'])
+        if result:
+            output = '\n  '.join((os.path.realpath(path) for path in result))
+            self.stdout.write("Found %r here:\n  %s\n" % (path, output))
+        else:
+            if verbosity >= 1:
+                self.stdout.write("No matching file found for %r.\n" % path)
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
--- /dev/null
+++ b/django/contrib/staticfiles/storage.py
@@ -0,0 +1,83 @@
+import os
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.storage import FileSystemStorage
+from django.utils.importlib import import_module
+
+from django.contrib.staticfiles import utils
+
+
+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)
+
+
+class AppMediaStorage(FileSystemStorage):
+    """
+    A file system storage backend that takes an app module and works
+    for the ``media`` directory of it.
+    """
+    source_dir = 'media'
+
+    def __init__(self, app, *args, **kwargs):
+        """
+        Returns a static file storage if available in the given app.
+
+        """
+        # ``app`` is actually the models module of the app.
+        # Remove the '.models'.
+        bits = app.__name__.split('.')[:-1]
+        self.app = app
+        self.app_name = bits[-1]
+        self.app_module = '.'.join(bits)
+
+        # The models module (``app``) 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(self.app_module)
+        app_root = os.path.dirname(app.__file__)
+        self.location = os.path.join(app_root, self.source_dir)
+        super(AppMediaStorage, self).__init__(self.location, *args, **kwargs)
+
+    @property
+    def is_usable(self):
+        return os.path.isdir(self.location)
+
+    def get_prefix(self):
+        """
+        Return the path name that should be prepended to files for this app.
+        """
+        if self.app_module == 'django.contrib.admin':
+            return self.app_name
+        return None
+
+    def get_files(self, ignore_patterns=[]):
+        """
+        Return a list containing the relative source paths for all files that
+        should be copied for an app.
+        """
+        files = []
+        prefix = self.get_prefix()
+        for path in utils.get_files(self, ignore_patterns):
+            if prefix:
+                path = '/'.join([prefix, path])
+            files.append(path)
+        return files
diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py
new file mode 100644
index 0000000..131b102
--- /dev/null
+++ b/django/contrib/staticfiles/urls.py
@@ -0,0 +1,29 @@
+import re
+from django.conf import settings
+from django.conf.urls.defaults import patterns, url, include
+from django.core.exceptions import ImproperlyConfigured
+
+urlpatterns = []
+
+# only serve non-fqdn URLs
+if not settings.DEBUG:
+    urlpatterns += patterns('',
+        url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
+    )
+
+def staticfiles_urlpatterns(prefix=None):
+    """
+    Helper function to return a URL pattern for serving static files.
+    """
+    if settings.DEBUG:
+        return []
+    if prefix is None:
+        prefix = settings.STATICFILES_URL
+    if '://' in prefix:
+        raise ImproperlyConfigured(
+            "The STATICFILES_URL setting is a full URL, not a path and "
+            "can't be used with the urls.staticfiles_urlpatterns() helper.")
+    if prefix.startswith("/"):
+        prefix = prefix[1:]
+    return patterns('',
+        url(r'^%s' % re.escape(prefix), include(urlpatterns)),)
diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py
new file mode 100644
index 0000000..11d5269
--- /dev/null
+++ b/django/contrib/staticfiles/utils.py
@@ -0,0 +1,31 @@
+import os
+import fnmatch
+
+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..ac53a6f
--- /dev/null
+++ b/django/contrib/staticfiles/views.py
@@ -0,0 +1,36 @@
+"""
+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.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+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``.
+    """
+    if settings.DEBUG:
+        raise ImproperlyConfigured("The view to serve static files can only "
+                                   "be used if the DEBUG setting is True")
+    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 7a59728..f24c6cf 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 6b9ce6e..e8b72f0 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -199,6 +199,7 @@ class BaseCommand(object):
         stderr.
 
         """
+        verbosity = options.get('verbosity', 1)
         # 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,
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/docs/index.txt b/docs/index.txt
index e456d04..61ce622 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -185,6 +185,7 @@ Other batteries included
     * :doc:`Signals <topics/signals>`
     * :doc:`Sitemaps <ref/contrib/sitemaps>`
     * :doc:`Sites <ref/contrib/sites>`
+    * :doc:`Static Files <ref/contrib/staticfiles>`
     * :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
     * :doc:`Unicode in Django <ref/unicode>`
     * :doc:`Web design helpers <ref/contrib/webdesign>`
diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt
index 90edf72..5e308dc 100644
--- a/docs/ref/contrib/index.txt
+++ b/docs/ref/contrib/index.txt
@@ -38,6 +38,7 @@ those packages have.
    redirects
    sitemaps
    sites
+   staticfiles
    syndication
    webdesign
 
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
new file mode 100644
index 0000000..a88a807
--- /dev/null
+++ b/docs/ref/contrib/staticfiles.txt
@@ -0,0 +1,216 @@
+===================
+The staticfiles app
+===================
+
+.. module:: django.contrib.staticfiles
+   :synopsis: An app for handling static files.
+
+.. versionadded:: 1.3
+
+This is a Django app that provides helpers for serving static files.
+
+Helpers
+=======
+
+``media`` context processor
+---------------------------
+
+To refer to static file assets from a template, ensure you have set the
+:ref:`staticfiles-url` setting to the URL path where the static files are
+served.
+
+Next, add the ``media`` context processor to your
+:setting:`TEMPLATE_CONTEXT_PROCESSORS` setting::
+
+   TEMPLATE_CONTEXT_PROCESSORS = (
+       'django.contrib.staticfiles.context_processors.media',
+   )
+
+Templates rendered with ``RequestContext`` will now have access to a
+:ref:`staticfiles-url` context variable, e.g.:
+
+.. code-block:: html+django
+
+   <link href="{{ STATICFILES_URL }}css/polls.css" rel="stylesheet" type="text/css" />
+
+Serving static files during development
+---------------------------------------
+
+.. warning:: Don't use this on production servers.
+  This feature is **only intended for development**.
+  Please, don't shoot yourself in the foot. Thanks.
+
+To serve static files in and :ref:`staticfiles-url` add the following snippet
+to the end of your primary URL configuration::
+
+   from django.conf import settings
+   
+   if settings.DEBUG:
+       urlpatterns = patterns('django.contrib.staticfiles.views',
+           url(r'^site_media/static/(?P<path>.*)$', 'serve'),
+       )
+
+Alternatively, you can make use of the helper function
+:function:`django.contrib.staticfiles.urls.staticfiles_urlpatterns` to append
+the proper URL pattern for serving static files to your already defined
+pattern list::
+
+   from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+   
+   # ...
+   
+   urlpatterns += staticfiles_urlpatterns()
+
+Note, the begin of the pattern ``r'^static/'`` should be equal to your
+``STATICFILES_URL`` setting.
+
+Management Commands
+===================
+
+.. highlight:: console
+
+.. _collectstatic:
+
+collectstatic
+-------------
+
+Collects the static files from all activated finders in the
+:ref:`staticfiles-storage`::
+
+   $ python manage.py collectstatic
+
+Duplicate file names are resolved in a similar way to how template resolution
+works. Files are searched by using the
+:ref:`enabled finders <staticfiles-finders>`. The default is to look in all
+locations defined in :ref:`staticfiles-dirs` and in the ``media`` directory
+of apps specified by the :setting:`INSTALLED_APPS` setting.
+
+Some commonly used options are:
+
+- ``--noinput``
+    Do NOT prompt the user for input of any kind.
+
+- ``-i PATTERN`` or ``--ignore=PATTERN``
+    Ignore files or directories matching this glob-style pattern. Use multiple
+    times to ignore more.
+
+- ``-n`` or ``--dry-run``
+    Do everything except modify the filesystem.
+
+- ``-l`` or ``--link``
+    Create a symbolic link to each file instead of copying.
+
+- ``--no-default-ignore``
+    Don't ignore the common private glob-style patterns ``'CVS'``, ``'.*'``
+    and ``'*~'``.
+
+For a full list of options, refer to the collectstatic management command
+help by running::
+
+   $ python manage.py collectstatic --help
+
+.. _findstatic:
+
+findstatic
+----------
+
+Searches for one or more relative paths with the enabled finders.
+For example::
+
+   $ python manage.py findstatic css/base.css admin/js/core.js
+   /home/special.polls.com/core/media/css/base.css
+   /home/polls.com/core/media/css/base.css
+   /home/polls.com/src/django/contrib/admin/media/js/core.js
+
+By default, all matching locations are found. To only return the first match
+for each relative path, use the ``--first`` option::
+
+   $ python manage.py findstatic css/base.css --first
+   /home/special.polls.com/core/media/css/base.css
+
+Settings
+========
+
+.. highlight:: python
+
+STATICFILES_ROOT
+----------------
+
+Default: ``''`` (Empty string)
+
+The absolute path to the directory that holds static files::
+
+   STATICFILES_ROOT = "/home/polls.com/polls/site_media/static/"
+
+This is used by the default static files storage backend (i.e. if you use a
+different ``STATICFILES_STORAGE``, you don't need to set this).
+
+.. _staticfiles-url:
+
+STATICFILES_URL
+---------------
+
+Default: ``'/static/'``
+
+URL that handles the files served from STATICFILES_ROOT, e.g.::
+
+   STATICFILES_URL = '/site_media/static/'
+
+Note that this should **always** have a trailing slash.
+
+.. _staticfiles-dirs:
+
+STATICFILES_DIRS
+----------------
+
+Default: ``[]``
+
+This setting defines the additional locations the staticfiles app will
+traverse if the ``FileSystemFinder`` finder is enabled, e.g. if you use the
+:ref:`collectstatic` or :ref:`findstatic` management command or use the
+static file serving view.
+
+It should be defined as a sequence of ``(prefix, path)`` tuples, e.g.::
+
+   STATICFILES_DIRS = (
+       ('', '/home/special.polls.com/polls/media'),
+       ('', '/home/polls.com/polls/media'),
+       ('common', '/opt/webfiles/common'),
+   )
+
+.. _staticfiles-storage:
+
+STATICFILES_STORAGE
+-------------------
+
+Default: ``'django.contrib.staticfiles.storage.StaticFilesStorage'``
+
+The storage to use when collecting static files to a single location with
+the :ref:`collectstatic` management command.
+
+.. _staticfiles-finders:
+
+STATICFILES_FINDERS
+-------------------
+
+Default::
+
+    ("django.contrib.staticfiles.finders.FileSystemFinder",
+    "django.contrib.staticfiles.finders.AppDirectoriesFinder")
+
+The list of finder backends that know how to find static files in
+various locations.
+
+If you know you only keep your files in one of those locations, just omit
+the unnecessary finders.
+
+One finder is disabled by default:
+:class:`django.contrib.staticfiles.finders.DefaultStorageFinder`. If added to
+your :ref:`staticfiles-finders` setting, it will look for static files in the
+default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE`
+setting.
+
+.. note:: When using the ``AppDirectoriesFinder`` finder, make sure
+   your apps can be found by Django's app loading mechanism. Simply include
+   a ``models`` module (an empty ``models.py`` file suffices) and add the
+   app to the :setting:`INSTALLED_APPS` setting of your site.
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 6ad5af9..d4e6e53 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1482,7 +1482,7 @@ Default::
     ("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.contrib.messages.context_processors.messages")
 
 A tuple of callables that are used to populate the context in ``RequestContext``.
diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
index 2ac4e65..e2614f2 100644
--- a/docs/ref/templates/api.txt
+++ b/docs/ref/templates/api.txt
@@ -309,7 +309,7 @@ and return a dictionary of items to be merged into the context. By default,
     ("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.contrib.messages.context_processors.messages")
 
 .. versionadded:: 1.2
@@ -432,6 +432,11 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
 ``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
 value of the :setting:`MEDIA_URL` setting.
 
+.. versionchanged:: 1.3
+    This context processor has been moved to the new :ref:`staticfiles` app.
+    Please use the new ``django.contrib.staticfiles.context_processors.media``
+    context processor.
+
 django.core.context_processors.csrf
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
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..bf38839
--- /dev/null
+++ b/tests/regressiontests/staticfiles_tests/tests.py
@@ -0,0 +1,297 @@
+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_staticfiles_finders = settings.STATICFILES_FINDERS
+        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'),
+        )
+        settings.STATICFILES_FINDERS = (
+            'django.contrib.staticfiles.finders.FileSystemFinder',
+            'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+            'django.contrib.staticfiles.finders.DefaultStorageFinder',
+        )
+
+    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.STATICFILES_FINDERS = self.old_staticfiles_finders
+        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 BuildStaticTestCase(StaticFilesTestCase):
+    """
+    Tests shared by all file-resolving features (collectstatic,
+    findstatic, 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_ROOT = tempfile.mkdtemp()
+        self.run_collectstatic()
+
+    def tearDown(self):
+        shutil.rmtree(settings.STATICFILES_ROOT)
+        settings.STATICFILES_ROOT = self.old_root
+        super(BuildStaticTestCase, self).tearDown()
+
+    def run_collectstatic(self, **kwargs):
+        call_command('collectstatic', 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(object):
+    """
+    A few standard test cases.
+    """
+    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 ``collectstatic`` 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, TestDefaults):
+    """
+    Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
+    ``collectstatic`` management command.
+    """
+    def run_collectstatic(self):
+        super(TestBuildStaticExcludeNoDefaultIgnore, self).run_collectstatic(
+            use_default_ignore_patterns=False)
+
+    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 ``collectstatic`` management command.
+    """
+    def run_collectstatic(self):
+        super(TestBuildStaticDryRun, self).run_collectstatic(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 ``collectstatic`` management command.
+
+        Note that by inheriting ``TestDefaults`` we repeat all
+        the standard file resolving tests here, to make sure using
+        ``--link`` does not change the file-selection semantics.
+        """
+        def run_collectstatic(self):
+            super(TestBuildStaticLinks, self).run_collectstatic(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(object):
+    """
+    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 TestDefaultStorageFinder(StaticFilesTestCase, FinderTestCase):
+    """
+    Test DefaultStorageFinder.
+    """
+    def setUp(self):
+        super(TestDefaultStorageFinder, self).setUp()
+        self.finder = finders.DefaultStorageFinder(
+            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.assertTrue(isinstance(finders.get_finder(
+            "django.contrib.staticfiles.finders.FileSystemFinder"),
+            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 a5f7479..055c910 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -27,6 +27,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')),
 )
