Code

Ticket #12323: 12323.8.diff

File 12323.8.diff, 68.3 KB (added by jezdez, 4 years ago)

more fixed. removed optionappcommand class.

Line 
1diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
2index 2dabf3b..731c7e1 100644
3--- a/django/conf/global_settings.py
4+++ b/django/conf/global_settings.py
5@@ -194,7 +194,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
6     'django.contrib.auth.context_processors.auth',
7     'django.core.context_processors.debug',
8     'django.core.context_processors.i18n',
9-    'django.core.context_processors.media',
10+    'django.contrib.staticfiles.context_processors.media',
11 #    'django.core.context_processors.request',
12     'django.contrib.messages.context_processors.messages',
13 )
14@@ -202,11 +202,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
15 # Output to use in template system for invalid (e.g. misspelled) variables.
16 TEMPLATE_STRING_IF_INVALID = ''
17 
18-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
19-# trailing slash.
20-# Examples: "http://foo.com/media/", "/media/".
21-ADMIN_MEDIA_PREFIX = '/media/'
22-
23 # Default e-mail address to use for various automated correspondence from
24 # the site managers.
25 DEFAULT_FROM_EMAIL = 'webmaster@localhost'
26@@ -551,3 +546,34 @@ TEST_DATABASE_COLLATION = None
27 
28 # The list of directories to search for fixtures
29 FIXTURE_DIRS = ()
30+
31+###############
32+# STATICFILES #
33+###############
34+
35+# Absolute path to the directory that holds media.
36+# Example: "/home/media/media.lawrence.com/static/"
37+STATICFILES_ROOT = ''
38+
39+# URL that handles the static files served from STATICFILES_ROOT.
40+# Example: "http://media.lawrence.com/static/"
41+STATICFILES_URL = '/static/'
42+
43+# A list of locations of additional static files
44+STATICFILES_DIRS = ()
45+
46+# The default file storage backend used during the build process
47+STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
48+
49+# List of finder classes that know how to find static files in
50+# various locations.
51+STATICFILES_FINDERS = (
52+    'django.contrib.staticfiles.finders.FileSystemFinder',
53+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
54+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
55+)
56+
57+# URL prefix for admin media -- CSS, JavaScript and images.
58+# Make sure to use a trailing slash.
59+# Examples: "http://foo.com/static/admin/", "/static/admin/".
60+ADMIN_MEDIA_PREFIX = '/static/admin/'
61diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
62index 3c783d4..7791b47 100644
63--- a/django/conf/project_template/settings.py
64+++ b/django/conf/project_template/settings.py
65@@ -44,7 +44,7 @@ USE_I18N = True
66 USE_L10N = True
67 
68 # Absolute path to the directory that holds media.
69-# Example: "/home/media/media.lawrence.com/"
70+# Example: "/home/media/media.lawrence.com/media/"
71 MEDIA_ROOT = ''
72 
73 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
74@@ -52,10 +52,29 @@ MEDIA_ROOT = ''
75 # Examples: "http://media.lawrence.com", "http://example.com/media/"
76 MEDIA_URL = ''
77 
78-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
79-# trailing slash.
80-# Examples: "http://foo.com/media/", "/media/".
81-ADMIN_MEDIA_PREFIX = '/media/'
82+# Absolute path to the directory that holds media.
83+# Example: "/home/media/media.lawrence.com/static/"
84+STATICFILES_ROOT = ''
85+
86+# URL that handles the static files served from STATICFILES_ROOT.
87+# Example: "http://static.lawrence.com/", "http://example.com/static/"
88+STATICFILES_URL = '/static/'
89+
90+# URL prefix for admin media -- CSS, JavaScript and images.
91+# Make sure to use a trailing slash.
92+# Examples: "http://foo.com/static/admin/", "/static/admin/".
93+ADMIN_MEDIA_PREFIX = '/static/admin/'
94+
95+# A list of locations of additional static files
96+STATICFILES_DIRS = ()
97+
98+# List of finder classes that know how to find static files in
99+# various locations.
100+STATICFILES_FINDERS = (
101+    'django.contrib.staticfiles.finders.FileSystemFinder',
102+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
103+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
104+)
105 
106 # Make this unique, and don't share it with anybody.
107 SECRET_KEY = ''
108@@ -89,6 +108,7 @@ INSTALLED_APPS = (
109     'django.contrib.sessions',
110     'django.contrib.sites',
111     'django.contrib.messages',
112+    'django.contrib.staticfiles',
113     # Uncomment the next line to enable the admin:
114     # 'django.contrib.admin',
115     # Uncomment the next line to enable admin documentation:
116diff --git a/django/contrib/staticfiles/__init__.py b/django/contrib/staticfiles/__init__.py
117new file mode 100644
118index 0000000..e69de29
119diff --git a/django/contrib/staticfiles/context_processors.py b/django/contrib/staticfiles/context_processors.py
120new file mode 100644
121index 0000000..e162861
122--- /dev/null
123+++ b/django/contrib/staticfiles/context_processors.py
124@@ -0,0 +1,7 @@
125+from django.conf import settings
126+
127+def media(request):
128+    return {
129+        'STATICFILES_URL': settings.STATICFILES_URL,
130+        'MEDIA_URL': settings.MEDIA_URL,
131+    }
132diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
133new file mode 100644
134index 0000000..eb8c538
135--- /dev/null
136+++ b/django/contrib/staticfiles/finders.py
137@@ -0,0 +1,244 @@
138+import os
139+from django.conf import settings
140+from django.db import models
141+from django.core.exceptions import ImproperlyConfigured
142+from django.core.files.storage import default_storage, Storage, FileSystemStorage
143+from django.utils.datastructures import SortedDict
144+from django.utils.functional import memoize, LazyObject
145+from django.utils.importlib import import_module
146+
147+from django.contrib.staticfiles import utils
148+from django.contrib.staticfiles.storage import AppMediaStorage
149+
150+_finders = {}
151+
152+
153+class BaseFinder(object):
154+    """
155+    A base file finder to be used for custom staticfiles finder classes.
156+
157+    """
158+    def find(self, path, all=False):
159+        """
160+        Given a relative file path this ought to find an
161+        absolute file path.
162+
163+        If the ``all`` parameter is ``False`` (default) only
164+        the first found file path will be returned; if set
165+        to ``True`` a list of all found files paths is returned.
166+        """
167+        raise NotImplementedError()
168+
169+    def list(self, ignore_patterns=[]):
170+        """
171+        Given an optional list of paths to ignore, this should return
172+        a three item iterable with path, prefix and a storage instance.
173+        """
174+        raise NotImplementedError()
175+
176+
177+class FileSystemFinder(BaseFinder):
178+    """
179+    A static files finder that uses the ``STATICFILES_DIRS`` setting
180+    to locate files.
181+    """
182+    storages = SortedDict()
183+    locations = set()
184+
185+    def __init__(self, apps=None, *args, **kwargs):
186+        for root in settings.STATICFILES_DIRS:
187+            if isinstance(root, (list, tuple)):
188+                prefix, root = root
189+            else:
190+                prefix = ''
191+            self.locations.add((prefix, root))
192+        # Don't initialize multiple storages for the same location
193+        for prefix, root in self.locations:
194+            self.storages[root] = FileSystemStorage(location=root)
195+        super(FileSystemFinder, self).__init__(*args, **kwargs)
196+
197+    def find(self, path, all=False):
198+        """
199+        Looks for files in the extra media locations
200+        as defined in ``STATICFILES_DIRS``.
201+        """
202+        matches = []
203+        for prefix, root in self.locations:
204+            matched_path = self.find_location(root, path, prefix)
205+            if matched_path:
206+                if not all:
207+                    return matched_path
208+                matches.append(matched_path)
209+        return matches
210+
211+    def find_location(self, root, path, prefix=None):
212+        """
213+        Find a requested static file in a location, returning the found
214+        absolute path (or ``None`` if no match).
215+        """
216+        if prefix:
217+            prefix = '%s/' % prefix
218+            if not path.startswith(prefix):
219+                return None
220+            path = path[len(prefix):]
221+        path = os.path.join(root, path)
222+        if os.path.exists(path):
223+            return path
224+
225+    def list(self, ignore_patterns):
226+        """
227+        List all files in all locations.
228+        """
229+        for prefix, root in self.locations:
230+            storage = self.storages[root]
231+            for path in utils.get_files(storage, ignore_patterns):
232+                yield path, prefix, storage
233+
234+
235+class AppDirectoriesFinder(BaseFinder):
236+    """
237+    A static files finder that looks in the ``media`` directory of each app.
238+    """
239+    storages = {}
240+    storage_class = AppMediaStorage
241+
242+    def __init__(self, apps=None, *args, **kwargs):
243+        if apps is not None:
244+            self.apps = apps
245+        else:
246+            self.apps = models.get_apps()
247+        for app in self.apps:
248+            self.storages[app] = self.storage_class(app)
249+        super(AppDirectoriesFinder, self).__init__(*args, **kwargs)
250+
251+    def list(self, ignore_patterns):
252+        """
253+        List all files in all app storages.
254+        """
255+        for storage in self.storages.itervalues():
256+            if storage.is_usable:
257+                prefix = storage.get_prefix()
258+                for path in utils.get_files(storage, ignore_patterns):
259+                    yield path, prefix, storage
260+
261+    def find(self, path, all=False):
262+        """
263+        Looks for files in the app directories.
264+        """
265+        matches = []
266+        for app in self.apps:
267+            app_matches = self.find_in_app(app, path)
268+            if app_matches:
269+                if not all:
270+                    return app_matches
271+                matches.append(app_matches)
272+        return matches
273+
274+    def find_in_app(self, app, path):
275+        """
276+        Find a requested static file in an app's media locations.
277+        """
278+        storage = self.storages[app]
279+        prefix = storage.get_prefix()
280+        if prefix:
281+            prefix = '%s/' % prefix
282+            if not path.startswith(prefix):
283+                return None
284+            path = path[len(prefix):]
285+        # only try to find a file if the source dir actually exists
286+        if storage.is_usable:
287+            if storage.exists(path):
288+                matched_path = storage.path(path)
289+                if matched_path:
290+                    return matched_path
291+
292+
293+class DefaultStorageFinder(BaseFinder):
294+    """
295+    A static files finder that uses the default storage backend.
296+    """
297+    storage = default_storage
298+
299+    def __init__(self, storage=None, *args, **kwargs):
300+        if storage is not None:
301+            self.storage = storage
302+        # Make sure we have an storage instance here.
303+        if not isinstance(self.storage, (Storage, LazyObject)):
304+            self.storage = self.storage()
305+        super(DefaultStorageFinder, self).__init__(*args, **kwargs)
306+
307+    def find(self, path, all=False):
308+        """
309+        Last resort, looks for files in the default file storage if it's local.
310+        """
311+        try:
312+            self.storage.path('')
313+        except NotImplementedError:
314+            pass
315+        else:
316+            if self.storage.exists(path):
317+                match = self.storage.path(path)
318+                if all:
319+                    match = [match]
320+                return match
321+        return []
322+
323+    def list(self, ignore_patterns):
324+        """
325+        List all files of the storage.
326+        """
327+        for path in utils.get_files(self.storage, ignore_patterns):
328+            yield path, '', self.storage
329+
330+
331+def find(path, all=False):
332+    """
333+    Find a requested static file, first looking in any defined extra media
334+    locations and next in any (non-excluded) installed apps.
335+   
336+    If no matches are found and the static location is local, look for a match
337+    there too.
338+   
339+    If ``all`` is ``False`` (default), return the first matching
340+    absolute path (or ``None`` if no match). Otherwise return a list of
341+    found absolute paths.
342+   
343+    """
344+    matches = []
345+    for finder in get_finders():
346+        result = finder.find(path, all=all)
347+        if not all and result:
348+            return result
349+        if not isinstance(result, (list, tuple)):
350+            result = [result]
351+        matches.extend(result)
352+    if matches:
353+        return matches
354+    # No match.
355+    return all and [] or None
356+
357+def get_finders():
358+    for finder_path in settings.STATICFILES_FINDERS:
359+        yield get_finder(finder_path)
360+
361+def _get_finder(import_path):
362+    """
363+    Imports the message storage class described by import_path, where
364+    import_path is the full Python path to the class.
365+    """
366+    module, attr = import_path.rsplit('.', 1)
367+    try:
368+        mod = import_module(module)
369+    except ImportError, e:
370+        raise ImproperlyConfigured('Error importing module %s: "%s"' %
371+                                   (module, e))
372+    try:
373+        Finder = getattr(mod, attr)
374+    except AttributeError:
375+        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
376+                                   'class.' % (module, attr))
377+    if not issubclass(Finder, BaseFinder):
378+        raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
379+                                   (Finder, BaseFinder))
380+    return Finder()
381+get_finder = memoize(_get_finder, _finders, 1)
382diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
383new file mode 100644
384index 0000000..ffd8d1d
385--- /dev/null
386+++ b/django/contrib/staticfiles/handlers.py
387@@ -0,0 +1,64 @@
388+import os
389+import urllib
390+from urlparse import urlparse
391+
392+from django.conf import settings
393+from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT
394+from django.http import Http404
395+from django.views import static
396+
397+class StaticFilesHandler(WSGIHandler):
398+    """
399+    WSGI middleware that intercepts calls to the static files directory, as
400+    defined by the STATICFILES_URL setting, and serves those files.
401+    """
402+    media_dir = settings.STATICFILES_ROOT
403+    media_url = settings.STATICFILES_URL
404+
405+    def __init__(self, application, media_dir=None):
406+        self.application = application
407+        if media_dir:
408+            self.media_dir = media_dir
409+
410+    def file_path(self, url):
411+        """
412+        Returns the relative path to the media file on disk for the given URL.
413+
414+        The passed URL is assumed to begin with ``media_url``.  If the
415+        resultant file path is outside the media directory, then a ValueError
416+        is raised.
417+        """
418+        # Remove ``media_url``.
419+        relative_url = url[len(self.media_url):]
420+        return urllib.url2pathname(relative_url)
421+
422+    def serve(self, request, path):
423+        from django.contrib.staticfiles import finders
424+        absolute_path = finders.find(path)
425+        if not absolute_path:
426+            raise Http404('%r could not be matched to a static file.' % path)
427+        absolute_path, filename = os.path.split(absolute_path)
428+        return static.serve(request, path=filename, document_root=absolute_path)
429+
430+    def __call__(self, environ, start_response):
431+        media_url_bits = urlparse(self.media_url)
432+        # Ignore all requests if the host is provided as part of the media_url.
433+        # Also ignore requests that aren't under the media path.
434+        if (media_url_bits[1] or
435+                not environ['PATH_INFO'].startswith(media_url_bits[2])):
436+            return self.application(environ, start_response)
437+        request = self.application.request_class(environ)
438+        try:
439+            response = self.serve(request, self.file_path(environ['PATH_INFO']))
440+        except Http404:
441+            status = '404 NOT FOUND'
442+            start_response(status, {'Content-type': 'text/plain'}.items())
443+            return [str('Page not found: %s' % environ['PATH_INFO'])]
444+        status_text = STATUS_CODE_TEXT[response.status_code]
445+        status = '%s %s' % (response.status_code, status_text)
446+        response_headers = [(str(k), str(v)) for k, v in response.items()]
447+        for c in response.cookies.values():
448+            response_headers.append(('Set-Cookie', str(c.output(header=''))))
449+        start_response(status, response_headers)
450+        return response
451+
452diff --git a/django/contrib/staticfiles/management/__init__.py b/django/contrib/staticfiles/management/__init__.py
453new file mode 100644
454index 0000000..e69de29
455diff --git a/django/contrib/staticfiles/management/commands/__init__.py b/django/contrib/staticfiles/management/commands/__init__.py
456new file mode 100644
457index 0000000..e69de29
458diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
459new file mode 100644
460index 0000000..67c7a80
461--- /dev/null
462+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
463@@ -0,0 +1,161 @@
464+import os
465+import sys
466+import shutil
467+from optparse import make_option
468+
469+from django.conf import settings
470+from django.core.files.storage import FileSystemStorage, get_storage_class
471+from django.core.management.base import CommandError, NoArgsCommand
472+
473+from django.contrib.staticfiles import utils, finders
474+
475+class Command(NoArgsCommand):
476+    """
477+    Command that allows to copy or symlink media files from different
478+    locations to the settings.STATICFILES_ROOT.
479+    """
480+    option_list = NoArgsCommand.option_list + (
481+        make_option('--noinput', action='store_false', dest='interactive',
482+            default=True, help="Do NOT prompt the user for input of any "
483+                "kind."),
484+        make_option('-i', '--ignore', action='append', default=[],
485+            dest='ignore_patterns', metavar='PATTERN',
486+            help="Ignore files or directories matching this glob-style "
487+                "pattern. Use multiple times to ignore more."),
488+        make_option('-n', '--dry-run', action='store_true', dest='dry_run',
489+            help="Do everything except modify the filesystem."),
490+        make_option('-l', '--link', action='store_true', dest='link',
491+            help="Create a symbolic link to each file instead of copying."),
492+        make_option('--no-default-ignore', action='store_false',
493+            dest='use_default_ignore_patterns', default=True,
494+            help="Don't ignore the common private glob-style patterns 'CVS', "
495+                "'.*' and '*~'."),
496+    )
497+    help = "Collect static files from apps and other locations in a single location."
498+
499+    def handle_noargs(self, **options):
500+        ignore_patterns = options['ignore_patterns']
501+        if options['use_default_ignore_patterns']:
502+            ignore_patterns += ['CVS', '.*', '*~']
503+        ignore_patterns = list(set(ignore_patterns))
504+        self.copied_files = []
505+        self.symlinked_files = []
506+        self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
507+
508+        try:
509+            self.destination_paths = utils.get_files(self.destination_storage, ignore_patterns)
510+        except OSError:
511+            # The destination storage location may not exist yet. It'll get
512+            # created when the first file is copied.
513+            self.destination_paths = []
514+
515+        try:
516+            self.destination_storage.path('')
517+        except NotImplementedError:
518+            self.destination_local = False
519+        else:
520+            self.destination_local = True
521+
522+        if options.get('link', False):
523+            if sys.platform == 'win32':
524+                raise CommandError("Symlinking is not supported by this "
525+                                   "platform (%s)." % sys.platform)
526+            if not self.destination_local:
527+                raise CommandError("Can't symlink to a remote destination.")
528+
529+        # Warn before doing anything more.
530+        if options.get('interactive'):
531+            confirm = raw_input("""
532+You have requested to collate static files and collect them at the destination
533+location as specified in your settings file, %r.
534+
535+This will overwrite existing files.
536+Are you sure you want to do this?
537+
538+Type 'yes' to continue, or 'no' to cancel: """ % settings.STATICFILES_ROOT)
539+            if confirm != 'yes':
540+                raise CommandError("Static files build cancelled.")
541+
542+        for finder in finders.get_finders():
543+            for source, prefix, storage in finder.list(ignore_patterns):
544+                self.copy_file(source, prefix, storage, **options)
545+
546+        verbosity = int(options.get('verbosity', 1))
547+        count = len(self.copied_files) + len(self.symlinked_files)
548+        if verbosity >= 1:
549+            self.stdout.write("%s static file%s collected.\n" %
550+                              (count, count != 1 and 's' or ''))
551+
552+    def copy_file(self, source, prefix, source_storage, **options):
553+        """
554+        Attempt to copy (or symlink) ``source`` to ``destination``,
555+        returning True if successful.
556+        """
557+        source_path = source_storage.path(source)
558+        if prefix:
559+            destination = '/'.join([prefix, source])
560+        else:
561+            destination = source
562+        dry_run = options.get('dry_run', False)
563+        verbosity = int(options.get('verbosity', 1))
564+
565+        if destination in self.copied_files:
566+            if verbosity >= 2:
567+                self.stdout.write("Skipping duplicate file (already copied "
568+                                  "earlier):\n  %s\n" % destination)
569+            return False
570+        if destination in self.symlinked_files:
571+            if verbosity >= 2:
572+                self.stdout.write("Skipping duplicate file (already linked "
573+                                  "earlier):\n  %s\n" % destination)
574+            return False
575+        if destination in self.destination_paths:
576+            if dry_run:
577+                if verbosity >= 2:
578+                    self.stdout.write("Pretending to delete:\n  %s\n"
579+                                      % destination)
580+            else:
581+                if verbosity >= 2:
582+                    self.stdout.write("Deleting:\n  %s\n" % destination)
583+                self.destination_storage.delete(destination)
584+
585+        if options.get('link', False):
586+            destination_path = self.destination_storage.path(destination)
587+            if dry_run:
588+                if verbosity >= 1:
589+                    self.stdout.write("Pretending to symlink:\n  %s\nto:\n  %s\n"
590+                                      % (source_path, destination_path))
591+            else:
592+                if verbosity >= 1:
593+                    self.stdout.write("Symlinking:\n  %s\nto:\n  %s\n"
594+                                      % (source_path, destination_path))
595+                try:
596+                    os.makedirs(os.path.dirname(destination_path))
597+                except OSError:
598+                    pass
599+                os.symlink(source_path, destination_path)
600+            self.symlinked_files.append(destination)
601+        else:
602+            if dry_run:
603+                if verbosity >= 1:
604+                    self.stdout.write("Pretending to copy:\n  %s\nto:\n  %s\n"
605+                                      % (source_path, destination))
606+            else:
607+                if self.destination_local:
608+                    destination_path = self.destination_storage.path(destination)
609+                    try:
610+                        os.makedirs(os.path.dirname(destination_path))
611+                    except OSError:
612+                        pass
613+                    shutil.copy2(source_path, destination_path)
614+                    if verbosity >= 1:
615+                        self.stdout.write("Copying:\n  %s\nto:\n  %s\n"
616+                                          % (source_path, destination_path))
617+                else:
618+                    source_file = source_storage.open(source)
619+                    self.destination_storage.write(destination, source_file)
620+                    if verbosity >= 1:
621+                        self.stdout.write("Copying:\n  %s\nto:\n  %s\n"
622+                                          % (source_path, destination))
623+            self.copied_files.append(destination)
624+        return True
625diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py
626new file mode 100644
627index 0000000..0f13277
628--- /dev/null
629+++ b/django/contrib/staticfiles/management/commands/findstatic.py
630@@ -0,0 +1,24 @@
631+import os
632+from optparse import make_option
633+from django.core.management.base import LabelCommand
634+
635+from django.contrib.staticfiles import finders
636+
637+class Command(LabelCommand):
638+    help = "Finds the absolute paths for the given static file(s)."
639+    args = "[file ...]"
640+    label = 'static file'
641+    option_list = LabelCommand.option_list + (
642+        make_option('--first', action='store_false', dest='all', default=True,
643+                    help="Only return the first match for each static file."),
644+    )
645+
646+    def handle_label(self, path, **options):
647+        verbosity = int(options.get('verbosity', 1))
648+        result = finders.find(path, all=options['all'])
649+        if result:
650+            output = '\n  '.join((os.path.realpath(path) for path in result))
651+            self.stdout.write("Found %r here:\n  %s\n" % (path, output))
652+        else:
653+            if verbosity >= 1:
654+                self.stdout.write("No matching file found for %r.\n" % path)
655diff --git a/django/contrib/staticfiles/models.py b/django/contrib/staticfiles/models.py
656new file mode 100644
657index 0000000..e69de29
658diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
659new file mode 100644
660index 0000000..07609ad
661--- /dev/null
662+++ b/django/contrib/staticfiles/storage.py
663@@ -0,0 +1,83 @@
664+import os
665+from django.conf import settings
666+from django.core.exceptions import ImproperlyConfigured
667+from django.core.files.storage import FileSystemStorage
668+from django.utils.importlib import import_module
669+
670+from django.contrib.staticfiles import utils
671+
672+
673+class StaticFilesStorage(FileSystemStorage):
674+    """
675+    Standard file system storage for site media files.
676+   
677+    The defaults for ``location`` and ``base_url`` are
678+    ``STATICFILES_ROOT`` and ``STATICFILES_URL``.
679+    """
680+    def __init__(self, location=None, base_url=None, *args, **kwargs):
681+        if location is None:
682+            location = settings.STATICFILES_ROOT
683+        if base_url is None:
684+            base_url = settings.STATICFILES_URL
685+        if not location:
686+            raise ImproperlyConfigured("You're using the staticfiles app "
687+                "without having set the STATICFILES_ROOT setting. Set it to "
688+                "the absolute path of the directory that holds static media.")
689+        if not base_url:
690+            raise ImproperlyConfigured("You're using the staticfiles app "
691+                "without having set the STATICFILES_URL setting. Set it to "
692+                "URL that handles the files served from STATICFILES_ROOT.")
693+        super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
694+
695+
696+class AppMediaStorage(FileSystemStorage):
697+    """
698+    A file system storage backend that takes an app module and works
699+    for the ``media`` directory of it.
700+    """
701+    source_dir = 'media'
702+
703+    def __init__(self, app, *args, **kwargs):
704+        """
705+        Returns a static file storage if available in the given app.
706+
707+        """
708+        # ``app`` is actually the models module of the app.
709+        # Remove the '.models'.
710+        bits = app.__name__.split('.')[:-1]
711+        self.app = app
712+        self.app_name = bits[-1]
713+        self.app_module = '.'.join(bits)
714+
715+        # The models module (``app``) may be a package in which case
716+        # ``dirname(app.__file__)`` would be wrong.
717+        # Import the actual app as opposed to the models module.
718+        app = import_module(self.app_module)
719+        app_root = os.path.dirname(app.__file__)
720+        self.location = os.path.join(app_root, self.source_dir)
721+        super(AppMediaStorage, self).__init__(self.location, *args, **kwargs)
722+
723+    @property
724+    def is_usable(self):
725+        return os.path.isdir(self.location)
726+
727+    def get_prefix(self):
728+        """
729+        Return the path name that should be prepended to files for this app.
730+        """
731+        if self.app_module == 'django.contrib.admin':
732+            return self.app_name
733+        return None
734+
735+    def get_files(self, ignore_patterns=[]):
736+        """
737+        Return a list containing the relative source paths for all files that
738+        should be copied for an app.
739+        """
740+        files = []
741+        prefix = self.get_prefix()
742+        for path in utils.get_files(self, ignore_patterns):
743+            if prefix:
744+                path = '/'.join([prefix, path])
745+            files.append(path)
746+        return files
747diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py
748new file mode 100644
749index 0000000..131b102
750--- /dev/null
751+++ b/django/contrib/staticfiles/urls.py
752@@ -0,0 +1,29 @@
753+import re
754+from django.conf import settings
755+from django.conf.urls.defaults import patterns, url, include
756+from django.core.exceptions import ImproperlyConfigured
757+
758+urlpatterns = []
759+
760+# only serve non-fqdn URLs
761+if not settings.DEBUG:
762+    urlpatterns += patterns('',
763+        url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
764+    )
765+
766+def staticfiles_urlpatterns(prefix=None):
767+    """
768+    Helper function to return a URL pattern for serving static files.
769+    """
770+    if settings.DEBUG:
771+        return []
772+    if prefix is None:
773+        prefix = settings.STATICFILES_URL
774+    if '://' in prefix:
775+        raise ImproperlyConfigured(
776+            "The STATICFILES_URL setting is a full URL, not a path and "
777+            "can't be used with the urls.staticfiles_urlpatterns() helper.")
778+    if prefix.startswith("/"):
779+        prefix = prefix[1:]
780+    return patterns('',
781+        url(r'^%s' % re.escape(prefix), include(urlpatterns)),)
782diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py
783new file mode 100644
784index 0000000..11d5269
785--- /dev/null
786+++ b/django/contrib/staticfiles/utils.py
787@@ -0,0 +1,31 @@
788+import os
789+import fnmatch
790+
791+def get_files(storage, ignore_patterns=[], location=''):
792+    """
793+    Recursively walk the storage directories gathering a complete list of files
794+    that should be copied, returning this list.
795+   
796+    """
797+    def is_ignored(path):
798+        """
799+        Return True or False depending on whether the ``path`` should be
800+        ignored (if it matches any pattern in ``ignore_patterns``).
801+       
802+        """
803+        for pattern in ignore_patterns:
804+            if fnmatch.fnmatchcase(path, pattern):
805+                return True
806+        return False
807+
808+    directories, files = storage.listdir(location)
809+    static_files = [location and '/'.join([location, fn]) or fn
810+                    for fn in files
811+                    if not is_ignored(fn)]
812+    for dir in directories:
813+        if is_ignored(dir):
814+            continue
815+        if location:
816+            dir = '/'.join([location, dir])
817+        static_files.extend(get_files(storage, ignore_patterns, dir))
818+    return static_files
819diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py
820new file mode 100644
821index 0000000..ac53a6f
822--- /dev/null
823+++ b/django/contrib/staticfiles/views.py
824@@ -0,0 +1,36 @@
825+"""
826+Views and functions for serving static files. These are only to be used during
827+development, and SHOULD NOT be used in a production setting.
828+
829+"""
830+import os
831+from django import http
832+from django.conf import settings
833+from django.core.exceptions import ImproperlyConfigured
834+from django.views import static
835+
836+from django.contrib.staticfiles import finders
837+
838+
839+def serve(request, path, show_indexes=False):
840+    """
841+    Serve static files from locations inferred from the static files finders.
842+
843+    To use, put a URL pattern such as::
844+
845+        (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve')
846+
847+    in your URLconf. You may also set ``show_indexes`` to ``True`` if you'd
848+    like to serve a basic index of the directory.  This index view will use the
849+    template hardcoded below, but if you'd like to override it, you can create
850+    a template called ``static/directory_index``.
851+    """
852+    if settings.DEBUG:
853+        raise ImproperlyConfigured("The view to serve static files can only "
854+                                   "be used if the DEBUG setting is True")
855+    absolute_path = finders.find(path)
856+    if not absolute_path:
857+        raise http.Http404("%r could not be matched to a static file." % path)
858+    absolute_path, filename = os.path.split(absolute_path)
859+    return static.serve(request, path=filename, document_root=absolute_path,
860+                        show_indexes=show_indexes)
861diff --git a/django/core/context_processors.py b/django/core/context_processors.py
862index 7a59728..f24c6cf 100644
863--- a/django/core/context_processors.py
864+++ b/django/core/context_processors.py
865@@ -71,7 +71,15 @@ def media(request):
866     Adds media-related context variables to the context.
867 
868     """
869-    return {'MEDIA_URL': settings.MEDIA_URL}
870+    import warnings
871+    warnings.warn(
872+        "The context processor at `django.core.context_processors.media` is " \
873+        "deprecated; use the path `django.contrib.staticfiles.context_processors.media` " \
874+        "instead.",
875+        PendingDeprecationWarning
876+    )
877+    from django.contrib.staticfiles.context_processors import media as media_context_processor
878+    return media_context_processor(request)
879 
880 def request(request):
881     return {'request': request}
882diff --git a/django/core/management/base.py b/django/core/management/base.py
883index 6b9ce6e..e8b72f0 100644
884--- a/django/core/management/base.py
885+++ b/django/core/management/base.py
886@@ -199,6 +199,7 @@ class BaseCommand(object):
887         stderr.
888 
889         """
890+        verbosity = options.get('verbosity', 1)
891         # Switch to English, because django-admin.py creates database content
892         # like permissions, and those shouldn't contain any translations.
893         # But only do this if we can assume we have a working settings file,
894diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
895index fc2c694..21391e8 100644
896--- a/django/core/management/commands/runserver.py
897+++ b/django/core/management/commands/runserver.py
898@@ -1,7 +1,9 @@
899-from django.core.management.base import BaseCommand, CommandError
900 from optparse import make_option
901 import os
902 import sys
903+import warnings
904+
905+from django.core.management.base import BaseCommand, CommandError
906 
907 class Command(BaseCommand):
908     option_list = BaseCommand.option_list + (
909@@ -20,6 +22,7 @@ class Command(BaseCommand):
910         import django
911         from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
912         from django.core.handlers.wsgi import WSGIHandler
913+        from django.contrib.staticfiles.handlers import StaticFilesHandler
914         if args:
915             raise CommandError('Usage is runserver %s' % self.args)
916         if not addrport:
917@@ -56,7 +59,10 @@ class Command(BaseCommand):
918             translation.activate(settings.LANGUAGE_CODE)
919 
920             try:
921-                handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
922+                handler = WSGIHandler()
923+                handler = StaticFilesHandler(handler)
924+                # serve admin media like old-school (deprecation pending)
925+                handler = AdminMediaHandler(handler, admin_media_path)
926                 run(addr, int(port), handler)
927             except WSGIServerException, e:
928                 # Use helpful error messages instead of ugly tracebacks.
929diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
930index dae4297..2da05de 100644
931--- a/django/core/servers/basehttp.py
932+++ b/django/core/servers/basehttp.py
933@@ -8,16 +8,17 @@ been reviewed for security issues. Don't use it for production use.
934 """
935 
936 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
937-import mimetypes
938 import os
939 import re
940-import stat
941 import sys
942 import urllib
943+import warnings
944 
945 from django.core.management.color import color_style
946 from django.utils.http import http_date
947 from django.utils._os import safe_join
948+from django.contrib.staticfiles.handlers import StaticFilesHandler
949+from django.views import static
950 
951 __version__ = "0.1"
952 __all__ = ['WSGIServer','WSGIRequestHandler']
953@@ -633,86 +634,48 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
954 
955         sys.stderr.write(msg)
956 
957-class AdminMediaHandler(object):
958+
959+class AdminMediaHandler(StaticFilesHandler):
960     """
961     WSGI middleware that intercepts calls to the admin media directory, as
962     defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
963     Use this ONLY LOCALLY, for development! This hasn't been tested for
964     security and is not super efficient.
965     """
966-    def __init__(self, application, media_dir=None):
967+
968+    @property
969+    def media_dir(self):
970+        import django
971+        return os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
972+
973+    @property
974+    def media_url(self):
975         from django.conf import settings
976-        self.application = application
977-        if not media_dir:
978-            import django
979-            self.media_dir = \
980-                os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
981-        else:
982-            self.media_dir = media_dir
983-        self.media_url = settings.ADMIN_MEDIA_PREFIX
984+        return settings.ADMIN_MEDIA_PREFIX
985+
986+    def __init__(self, application, media_dir=None):
987+        warnings.warn('The AdminMediaHandler handler is deprecated; use the '
988+            '`django.contrib.staticfiles.handlers.StaticFilesHandler` instead.',
989+            PendingDeprecationWarning)
990+        super(AdminMediaHandler, self).__init__(application, media_dir)
991 
992     def file_path(self, url):
993         """
994         Returns the path to the media file on disk for the given URL.
995 
996-        The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX.  If the
997+        The passed URL is assumed to begin with ``media_url``.  If the
998         resultant file path is outside the media directory, then a ValueError
999         is raised.
1000         """
1001-        # Remove ADMIN_MEDIA_PREFIX.
1002+        # Remove ``media_url``.
1003         relative_url = url[len(self.media_url):]
1004         relative_path = urllib.url2pathname(relative_url)
1005         return safe_join(self.media_dir, relative_path)
1006 
1007-    def __call__(self, environ, start_response):
1008-        import os.path
1009-
1010-        # Ignore requests that aren't under ADMIN_MEDIA_PREFIX. Also ignore
1011-        # all requests if ADMIN_MEDIA_PREFIX isn't a relative URL.
1012-        if self.media_url.startswith('http://') or self.media_url.startswith('https://') \
1013-            or not environ['PATH_INFO'].startswith(self.media_url):
1014-            return self.application(environ, start_response)
1015+    def serve(self, request, path):
1016+        document_root, path = os.path.split(path)
1017+        return static.serve(request, path, document_root=document_root)
1018 
1019-        # Find the admin file and serve it up, if it exists and is readable.
1020-        try:
1021-            file_path = self.file_path(environ['PATH_INFO'])
1022-        except ValueError: # Resulting file path was not valid.
1023-            status = '404 NOT FOUND'
1024-            headers = {'Content-type': 'text/plain'}
1025-            output = ['Page not found: %s' % environ['PATH_INFO']]
1026-            start_response(status, headers.items())
1027-            return output
1028-        if not os.path.exists(file_path):
1029-            status = '404 NOT FOUND'
1030-            headers = {'Content-type': 'text/plain'}
1031-            output = ['Page not found: %s' % environ['PATH_INFO']]
1032-        else:
1033-            try:
1034-                fp = open(file_path, 'rb')
1035-            except IOError:
1036-                status = '401 UNAUTHORIZED'
1037-                headers = {'Content-type': 'text/plain'}
1038-                output = ['Permission denied: %s' % environ['PATH_INFO']]
1039-            else:
1040-                # This is a very simple implementation of conditional GET with
1041-                # the Last-Modified header. It makes media files a bit speedier
1042-                # because the files are only read off disk for the first
1043-                # request (assuming the browser/client supports conditional
1044-                # GET).
1045-                mtime = http_date(os.stat(file_path)[stat.ST_MTIME])
1046-                headers = {'Last-Modified': mtime}
1047-                if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
1048-                    status = '304 NOT MODIFIED'
1049-                    output = []
1050-                else:
1051-                    status = '200 OK'
1052-                    mime_type = mimetypes.guess_type(file_path)[0]
1053-                    if mime_type:
1054-                        headers['Content-Type'] = mime_type
1055-                    output = [fp.read()]
1056-                    fp.close()
1057-        start_response(status, headers.items())
1058-        return output
1059 
1060 def run(addr, port, wsgi_handler):
1061     server_address = (addr, port)
1062diff --git a/docs/index.txt b/docs/index.txt
1063index e456d04..61ce622 100644
1064--- a/docs/index.txt
1065+++ b/docs/index.txt
1066@@ -185,6 +185,7 @@ Other batteries included
1067     * :doc:`Signals <topics/signals>`
1068     * :doc:`Sitemaps <ref/contrib/sitemaps>`
1069     * :doc:`Sites <ref/contrib/sites>`
1070+    * :doc:`Static Files <ref/contrib/staticfiles>`
1071     * :doc:`Syndication feeds (RSS/Atom) <ref/contrib/syndication>`
1072     * :doc:`Unicode in Django <ref/unicode>`
1073     * :doc:`Web design helpers <ref/contrib/webdesign>`
1074diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt
1075index 90edf72..5e308dc 100644
1076--- a/docs/ref/contrib/index.txt
1077+++ b/docs/ref/contrib/index.txt
1078@@ -38,6 +38,7 @@ those packages have.
1079    redirects
1080    sitemaps
1081    sites
1082+   staticfiles
1083    syndication
1084    webdesign
1085 
1086diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
1087new file mode 100644
1088index 0000000..a88a807
1089--- /dev/null
1090+++ b/docs/ref/contrib/staticfiles.txt
1091@@ -0,0 +1,216 @@
1092+===================
1093+The staticfiles app
1094+===================
1095+
1096+.. module:: django.contrib.staticfiles
1097+   :synopsis: An app for handling static files.
1098+
1099+.. versionadded:: 1.3
1100+
1101+This is a Django app that provides helpers for serving static files.
1102+
1103+Helpers
1104+=======
1105+
1106+``media`` context processor
1107+---------------------------
1108+
1109+To refer to static file assets from a template, ensure you have set the
1110+:ref:`staticfiles-url` setting to the URL path where the static files are
1111+served.
1112+
1113+Next, add the ``media`` context processor to your
1114+:setting:`TEMPLATE_CONTEXT_PROCESSORS` setting::
1115+
1116+   TEMPLATE_CONTEXT_PROCESSORS = (
1117+       'django.contrib.staticfiles.context_processors.media',
1118+   )
1119+
1120+Templates rendered with ``RequestContext`` will now have access to a
1121+:ref:`staticfiles-url` context variable, e.g.:
1122+
1123+.. code-block:: html+django
1124+
1125+   <link href="{{ STATICFILES_URL }}css/polls.css" rel="stylesheet" type="text/css" />
1126+
1127+Serving static files during development
1128+---------------------------------------
1129+
1130+.. warning:: Don't use this on production servers.
1131+  This feature is **only intended for development**.
1132+  Please, don't shoot yourself in the foot. Thanks.
1133+
1134+To serve static files in and :ref:`staticfiles-url` add the following snippet
1135+to the end of your primary URL configuration::
1136+
1137+   from django.conf import settings
1138+   
1139+   if settings.DEBUG:
1140+       urlpatterns = patterns('django.contrib.staticfiles.views',
1141+           url(r'^site_media/static/(?P<path>.*)$', 'serve'),
1142+       )
1143+
1144+Alternatively, you can make use of the helper function
1145+:function:`django.contrib.staticfiles.urls.staticfiles_urlpatterns` to append
1146+the proper URL pattern for serving static files to your already defined
1147+pattern list::
1148+
1149+   from django.contrib.staticfiles.urls import staticfiles_urlpatterns
1150+   
1151+   # ...
1152+   
1153+   urlpatterns += staticfiles_urlpatterns()
1154+
1155+Note, the begin of the pattern ``r'^static/'`` should be equal to your
1156+``STATICFILES_URL`` setting.
1157+
1158+Management Commands
1159+===================
1160+
1161+.. highlight:: console
1162+
1163+.. _collectstatic:
1164+
1165+collectstatic
1166+-------------
1167+
1168+Collects the static files from all activated finders in the
1169+:ref:`staticfiles-storage`::
1170+
1171+   $ python manage.py collectstatic
1172+
1173+Duplicate file names are resolved in a similar way to how template resolution
1174+works. Files are searched by using the
1175+:ref:`enabled finders <staticfiles-finders>`. The default is to look in all
1176+locations defined in :ref:`staticfiles-dirs` and in the ``media`` directory
1177+of apps specified by the :setting:`INSTALLED_APPS` setting.
1178+
1179+Some commonly used options are:
1180+
1181+- ``--noinput``
1182+    Do NOT prompt the user for input of any kind.
1183+
1184+- ``-i PATTERN`` or ``--ignore=PATTERN``
1185+    Ignore files or directories matching this glob-style pattern. Use multiple
1186+    times to ignore more.
1187+
1188+- ``-n`` or ``--dry-run``
1189+    Do everything except modify the filesystem.
1190+
1191+- ``-l`` or ``--link``
1192+    Create a symbolic link to each file instead of copying.
1193+
1194+- ``--no-default-ignore``
1195+    Don't ignore the common private glob-style patterns ``'CVS'``, ``'.*'``
1196+    and ``'*~'``.
1197+
1198+For a full list of options, refer to the collectstatic management command
1199+help by running::
1200+
1201+   $ python manage.py collectstatic --help
1202+
1203+.. _findstatic:
1204+
1205+findstatic
1206+----------
1207+
1208+Searches for one or more relative paths with the enabled finders.
1209+For example::
1210+
1211+   $ python manage.py findstatic css/base.css admin/js/core.js
1212+   /home/special.polls.com/core/media/css/base.css
1213+   /home/polls.com/core/media/css/base.css
1214+   /home/polls.com/src/django/contrib/admin/media/js/core.js
1215+
1216+By default, all matching locations are found. To only return the first match
1217+for each relative path, use the ``--first`` option::
1218+
1219+   $ python manage.py findstatic css/base.css --first
1220+   /home/special.polls.com/core/media/css/base.css
1221+
1222+Settings
1223+========
1224+
1225+.. highlight:: python
1226+
1227+STATICFILES_ROOT
1228+----------------
1229+
1230+Default: ``''`` (Empty string)
1231+
1232+The absolute path to the directory that holds static files::
1233+
1234+   STATICFILES_ROOT = "/home/polls.com/polls/site_media/static/"
1235+
1236+This is used by the default static files storage backend (i.e. if you use a
1237+different ``STATICFILES_STORAGE``, you don't need to set this).
1238+
1239+.. _staticfiles-url:
1240+
1241+STATICFILES_URL
1242+---------------
1243+
1244+Default: ``'/static/'``
1245+
1246+URL that handles the files served from STATICFILES_ROOT, e.g.::
1247+
1248+   STATICFILES_URL = '/site_media/static/'
1249+
1250+Note that this should **always** have a trailing slash.
1251+
1252+.. _staticfiles-dirs:
1253+
1254+STATICFILES_DIRS
1255+----------------
1256+
1257+Default: ``[]``
1258+
1259+This setting defines the additional locations the staticfiles app will
1260+traverse if the ``FileSystemFinder`` finder is enabled, e.g. if you use the
1261+:ref:`collectstatic` or :ref:`findstatic` management command or use the
1262+static file serving view.
1263+
1264+It should be defined as a sequence of ``(prefix, path)`` tuples, e.g.::
1265+
1266+   STATICFILES_DIRS = (
1267+       ('', '/home/special.polls.com/polls/media'),
1268+       ('', '/home/polls.com/polls/media'),
1269+       ('common', '/opt/webfiles/common'),
1270+   )
1271+
1272+.. _staticfiles-storage:
1273+
1274+STATICFILES_STORAGE
1275+-------------------
1276+
1277+Default: ``'django.contrib.staticfiles.storage.StaticFilesStorage'``
1278+
1279+The storage to use when collecting static files to a single location with
1280+the :ref:`collectstatic` management command.
1281+
1282+.. _staticfiles-finders:
1283+
1284+STATICFILES_FINDERS
1285+-------------------
1286+
1287+Default::
1288+
1289+    ("django.contrib.staticfiles.finders.FileSystemFinder",
1290+    "django.contrib.staticfiles.finders.AppDirectoriesFinder")
1291+
1292+The list of finder backends that know how to find static files in
1293+various locations.
1294+
1295+If you know you only keep your files in one of those locations, just omit
1296+the unnecessary finders.
1297+
1298+One finder is disabled by default:
1299+:class:`django.contrib.staticfiles.finders.DefaultStorageFinder`. If added to
1300+your :ref:`staticfiles-finders` setting, it will look for static files in the
1301+default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE`
1302+setting.
1303+
1304+.. note:: When using the ``AppDirectoriesFinder`` finder, make sure
1305+   your apps can be found by Django's app loading mechanism. Simply include
1306+   a ``models`` module (an empty ``models.py`` file suffices) and add the
1307+   app to the :setting:`INSTALLED_APPS` setting of your site.
1308diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
1309index 6ad5af9..d4e6e53 100644
1310--- a/docs/ref/settings.txt
1311+++ b/docs/ref/settings.txt
1312@@ -1482,7 +1482,7 @@ Default::
1313     ("django.contrib.auth.context_processors.auth",
1314     "django.core.context_processors.debug",
1315     "django.core.context_processors.i18n",
1316-    "django.core.context_processors.media",
1317+    "django.contrib.staticfiles.context_processors.media",
1318     "django.contrib.messages.context_processors.messages")
1319 
1320 A tuple of callables that are used to populate the context in ``RequestContext``.
1321diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
1322index 2ac4e65..e2614f2 100644
1323--- a/docs/ref/templates/api.txt
1324+++ b/docs/ref/templates/api.txt
1325@@ -309,7 +309,7 @@ and return a dictionary of items to be merged into the context. By default,
1326     ("django.contrib.auth.context_processors.auth",
1327     "django.core.context_processors.debug",
1328     "django.core.context_processors.i18n",
1329-    "django.core.context_processors.media",
1330+    "django.contrib.staticfiles.context_processors.media",
1331     "django.contrib.messages.context_processors.messages")
1332 
1333 .. versionadded:: 1.2
1334@@ -432,6 +432,11 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
1335 ``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
1336 value of the :setting:`MEDIA_URL` setting.
1337 
1338+.. versionchanged:: 1.3
1339+    This context processor has been moved to the new :ref:`staticfiles` app.
1340+    Please use the new ``django.contrib.staticfiles.context_processors.media``
1341+    context processor.
1342+
1343 django.core.context_processors.csrf
1344 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1345 
1346diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
1347index 4763982..29f169c 100644
1348--- a/tests/regressiontests/servers/tests.py
1349+++ b/tests/regressiontests/servers/tests.py
1350@@ -9,6 +9,7 @@ from django.test import TestCase
1351 from django.core.handlers.wsgi import WSGIHandler
1352 from django.core.servers.basehttp import AdminMediaHandler
1353 
1354+from django.conf import settings
1355 
1356 class AdminMediaHandlerTests(TestCase):
1357 
1358@@ -25,7 +26,7 @@ class AdminMediaHandlerTests(TestCase):
1359         """
1360         # Cases that should work on all platforms.
1361         data = (
1362-            ('/media/css/base.css', ('css', 'base.css')),
1363+            ('%scss/base.css' % settings.ADMIN_MEDIA_PREFIX, ('css', 'base.css')),
1364         )
1365         # Cases that should raise an exception.
1366         bad_data = ()
1367@@ -34,19 +35,19 @@ class AdminMediaHandlerTests(TestCase):
1368         if os.sep == '/':
1369             data += (
1370                 # URL, tuple of relative path parts.
1371-                ('/media/\\css/base.css', ('\\css', 'base.css')),
1372+                ('%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX, ('\\css', 'base.css')),
1373             )
1374             bad_data += (
1375-                '/media//css/base.css',
1376-                '/media////css/base.css',
1377-                '/media/../css/base.css',
1378+                '%s/css/base.css' % settings.ADMIN_MEDIA_PREFIX,
1379+                '%s///css/base.css' % settings.ADMIN_MEDIA_PREFIX,
1380+                '%s../css/base.css' % settings.ADMIN_MEDIA_PREFIX,
1381             )
1382         elif os.sep == '\\':
1383             bad_data += (
1384-                '/media/C:\css/base.css',
1385-                '/media//\\css/base.css',
1386-                '/media/\\css/base.css',
1387-                '/media/\\\\css/base.css'
1388+                '%sC:\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
1389+                '%s/\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
1390+                '%s\\css/base.css' % settings.ADMIN_MEDIA_PREFIX,
1391+                '%s\\\\css/base.css' % settings.ADMIN_MEDIA_PREFIX
1392             )
1393         for url, path_tuple in data:
1394             try:
1395diff --git a/tests/regressiontests/staticfiles_tests/__init__.py b/tests/regressiontests/staticfiles_tests/__init__.py
1396new file mode 100644
1397index 0000000..e69de29
1398diff --git a/tests/regressiontests/staticfiles_tests/apps/__init__.py b/tests/regressiontests/staticfiles_tests/apps/__init__.py
1399new file mode 100644
1400index 0000000..e69de29
1401diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py b/tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py
1402new file mode 100644
1403index 0000000..e69de29
1404diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt b/tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt
1405new file mode 100644
1406index 0000000..aa264ca
1407--- /dev/null
1408+++ b/tests/regressiontests/staticfiles_tests/apps/no_label/media/file2.txt
1409@@ -0,0 +1 @@
1410+file2 in no_label_app
1411diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/models.py b/tests/regressiontests/staticfiles_tests/apps/no_label/models.py
1412new file mode 100644
1413index 0000000..e69de29
1414diff --git a/tests/regressiontests/staticfiles_tests/apps/test/__init__.py b/tests/regressiontests/staticfiles_tests/apps/test/__init__.py
1415new file mode 100644
1416index 0000000..e69de29
1417diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden b/tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden
1418new file mode 100644
1419index 0000000..cef6c23
1420--- /dev/null
1421+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/.hidden
1422@@ -0,0 +1 @@
1423+This file should be ignored.
1424diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS b/tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS
1425new file mode 100644
1426index 0000000..cef6c23
1427--- /dev/null
1428+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/CVS
1429@@ -0,0 +1 @@
1430+This file should be ignored.
1431diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~
1432new file mode 100644
1433index 0000000..cef6c23
1434--- /dev/null
1435+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/backup~
1436@@ -0,0 +1 @@
1437+This file should be ignored.
1438diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt
1439new file mode 100644
1440index 0000000..169a206
1441--- /dev/null
1442+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file.txt
1443@@ -0,0 +1 @@
1444+In app media directory.
1445diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt
1446new file mode 100644
1447index 0000000..9f9a8d9
1448--- /dev/null
1449+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/file1.txt
1450@@ -0,0 +1 @@
1451+file1 in the app dir
1452\ No newline at end of file
1453diff --git a/tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme b/tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme
1454new file mode 100644
1455index 0000000..d7df09c
1456--- /dev/null
1457+++ b/tests/regressiontests/staticfiles_tests/apps/test/media/test/test.ignoreme
1458@@ -0,0 +1 @@
1459+This file should be ignored.
1460\ No newline at end of file
1461diff --git a/tests/regressiontests/staticfiles_tests/apps/test/models.py b/tests/regressiontests/staticfiles_tests/apps/test/models.py
1462new file mode 100644
1463index 0000000..e69de29
1464diff --git a/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt b/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt
1465new file mode 100644
1466index 0000000..c62c93d
1467--- /dev/null
1468+++ b/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt
1469@@ -0,0 +1 @@
1470+File in otherdir.
1471diff --git a/tests/regressiontests/staticfiles_tests/models.py b/tests/regressiontests/staticfiles_tests/models.py
1472new file mode 100644
1473index 0000000..e69de29
1474diff --git a/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt b/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt
1475new file mode 100644
1476index 0000000..04326a2
1477--- /dev/null
1478+++ b/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt
1479@@ -0,0 +1 @@
1480+Can we find this file?
1481diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test.txt b/tests/regressiontests/staticfiles_tests/project/documents/test.txt
1482new file mode 100644
1483index 0000000..04326a2
1484--- /dev/null
1485+++ b/tests/regressiontests/staticfiles_tests/project/documents/test.txt
1486@@ -0,0 +1 @@
1487+Can we find this file?
1488diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt b/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt
1489new file mode 100644
1490index 0000000..fdeaa23
1491--- /dev/null
1492+++ b/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt
1493@@ -0,0 +1,2 @@
1494+In STATICFILES_DIRS directory.
1495+
1496diff --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
1497new file mode 100644
1498index 0000000..466922d
1499--- /dev/null
1500+++ b/tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt
1501@@ -0,0 +1 @@
1502+Media file.
1503diff --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
1504new file mode 100644
1505index 0000000..2eda9ce
1506--- /dev/null
1507+++ b/tests/regressiontests/staticfiles_tests/project/site_media/static/test/storage.txt
1508@@ -0,0 +1 @@
1509+Yeah!
1510\ No newline at end of file
1511diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
1512new file mode 100644
1513index 0000000..bf38839
1514--- /dev/null
1515+++ b/tests/regressiontests/staticfiles_tests/tests.py
1516@@ -0,0 +1,297 @@
1517+import tempfile
1518+import shutil
1519+import os
1520+import sys
1521+from cStringIO import StringIO
1522+import posixpath
1523+
1524+from django.test import TestCase, Client
1525+from django.conf import settings
1526+from django.core.exceptions import ImproperlyConfigured
1527+from django.core.management import call_command
1528+from django.db.models.loading import load_app
1529+
1530+from django.contrib.staticfiles import finders, storage
1531+
1532+TEST_ROOT = os.path.dirname(__file__)
1533+
1534+
1535+class StaticFilesTestCase(TestCase):
1536+    """
1537+    Test case with a couple utility assertions.
1538+    """
1539+    def setUp(self):
1540+        self.old_staticfiles_url = settings.STATICFILES_URL
1541+        self.old_staticfiles_root = settings.STATICFILES_ROOT
1542+        self.old_staticfiles_dirs = settings.STATICFILES_DIRS
1543+        self.old_staticfiles_finders = settings.STATICFILES_FINDERS
1544+        self.old_installed_apps = settings.INSTALLED_APPS
1545+        self.old_media_root = settings.MEDIA_ROOT
1546+        self.old_media_url = settings.MEDIA_URL
1547+        self.old_admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
1548+
1549+        # We have to load these apps to test staticfiles.
1550+        load_app('regressiontests.staticfiles_tests.apps.test')
1551+        load_app('regressiontests.staticfiles_tests.apps.no_label')
1552+        site_media = os.path.join(TEST_ROOT, 'project', 'site_media')
1553+        settings.MEDIA_ROOT =  os.path.join(site_media, 'media')
1554+        settings.MEDIA_URL = '/media/'
1555+        settings.ADMIN_MEDIA_PREFIX = posixpath.join(settings.STATICFILES_URL, 'admin/')
1556+        settings.STATICFILES_ROOT = os.path.join(site_media, 'static')
1557+        settings.STATICFILES_URL = '/static/'
1558+        settings.STATICFILES_DIRS = (
1559+            os.path.join(TEST_ROOT, 'project', 'documents'),
1560+        )
1561+        settings.STATICFILES_FINDERS = (
1562+            'django.contrib.staticfiles.finders.FileSystemFinder',
1563+            'django.contrib.staticfiles.finders.AppDirectoriesFinder',
1564+            'django.contrib.staticfiles.finders.DefaultStorageFinder',
1565+        )
1566+
1567+    def tearDown(self):
1568+        settings.MEDIA_ROOT = self.old_media_root
1569+        settings.MEDIA_URL = self.old_media_url
1570+        settings.ADMIN_MEDIA_PREFIX = self.old_admin_media_prefix
1571+        settings.STATICFILES_ROOT = self.old_staticfiles_root
1572+        settings.STATICFILES_URL = self.old_staticfiles_url
1573+        settings.STATICFILES_DIRS = self.old_staticfiles_dirs
1574+        settings.STATICFILES_FINDERS = self.old_staticfiles_finders
1575+        settings.INSTALLED_APPS = self.old_installed_apps
1576+
1577+    def assertFileContains(self, filepath, text):
1578+        self.failUnless(text in self._get_file(filepath),
1579+                        "'%s' not in '%s'" % (text, filepath))
1580+
1581+    def assertFileNotFound(self, filepath):
1582+        self.assertRaises(IOError, self._get_file, filepath)
1583+
1584+
1585+class BuildStaticTestCase(StaticFilesTestCase):
1586+    """
1587+    Tests shared by all file-resolving features (collectstatic,
1588+    findstatic, and static serve view).
1589+
1590+    This relies on the asserts defined in UtilityAssertsTestCase, but
1591+    is separated because some test cases need those asserts without
1592+    all these tests.
1593+    """
1594+    def setUp(self):
1595+        super(BuildStaticTestCase, self).setUp()
1596+        self.old_staticfiles_storage = settings.STATICFILES_STORAGE
1597+        self.old_root = settings.STATICFILES_ROOT
1598+        settings.STATICFILES_ROOT = tempfile.mkdtemp()
1599+        self.run_collectstatic()
1600+
1601+    def tearDown(self):
1602+        shutil.rmtree(settings.STATICFILES_ROOT)
1603+        settings.STATICFILES_ROOT = self.old_root
1604+        super(BuildStaticTestCase, self).tearDown()
1605+
1606+    def run_collectstatic(self, **kwargs):
1607+        call_command('collectstatic', interactive=False, verbosity='0',
1608+                     ignore_patterns=['*.ignoreme'], **kwargs)
1609+
1610+    def _get_file(self, filepath):
1611+        assert filepath, 'filepath is empty.'
1612+        filepath = os.path.join(settings.STATICFILES_ROOT, filepath)
1613+        return open(filepath).read()
1614+
1615+
1616+class TestDefaults(object):
1617+    """
1618+    A few standard test cases.
1619+    """
1620+    def test_staticfiles_dirs(self):
1621+        """
1622+        Can find a file in a STATICFILES_DIRS directory.
1623+
1624+        """
1625+        self.assertFileContains('test.txt', 'Can we find')
1626+
1627+    def test_staticfiles_dirs_subdir(self):
1628+        """
1629+        Can find a file in a subdirectory of a STATICFILES_DIRS
1630+        directory.
1631+
1632+        """
1633+        self.assertFileContains('subdir/test.txt', 'Can we find')
1634+
1635+    def test_staticfiles_dirs_priority(self):
1636+        """
1637+        File in STATICFILES_DIRS has priority over file in app.
1638+
1639+        """
1640+        self.assertFileContains('test/file.txt', 'STATICFILES_DIRS')
1641+
1642+    def test_app_files(self):
1643+        """
1644+        Can find a file in an app media/ directory.
1645+
1646+        """
1647+        self.assertFileContains('test/file1.txt', 'file1 in the app dir')
1648+
1649+
1650+class TestBuildStatic(BuildStaticTestCase, TestDefaults):
1651+    """
1652+    Test ``collectstatic`` management command.
1653+    """
1654+    def test_ignore(self):
1655+        """
1656+        Test that -i patterns are ignored.
1657+        """
1658+        self.assertFileNotFound('test/test.ignoreme')
1659+
1660+    def test_common_ignore_patterns(self):
1661+        """
1662+        Common ignore patterns (*~, .*, CVS) are ignored.
1663+        """
1664+        self.assertFileNotFound('test/.hidden')
1665+        self.assertFileNotFound('test/backup~')
1666+        self.assertFileNotFound('test/CVS')
1667+
1668+
1669+class TestBuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase, TestDefaults):
1670+    """
1671+    Test ``--exclude-dirs`` and ``--no-default-ignore`` options for
1672+    ``collectstatic`` management command.
1673+    """
1674+    def run_collectstatic(self):
1675+        super(TestBuildStaticExcludeNoDefaultIgnore, self).run_collectstatic(
1676+            use_default_ignore_patterns=False)
1677+
1678+    def test_no_common_ignore_patterns(self):
1679+        """
1680+        With --no-default-ignore, common ignore patterns (*~, .*, CVS)
1681+        are not ignored.
1682+
1683+        """
1684+        self.assertFileContains('test/.hidden', 'should be ignored')
1685+        self.assertFileContains('test/backup~', 'should be ignored')
1686+        self.assertFileContains('test/CVS', 'should be ignored')
1687+
1688+
1689+class TestBuildStaticDryRun(BuildStaticTestCase):
1690+    """
1691+    Test ``--dry-run`` option for ``collectstatic`` management command.
1692+    """
1693+    def run_collectstatic(self):
1694+        super(TestBuildStaticDryRun, self).run_collectstatic(dry_run=True)
1695+
1696+    def test_no_files_created(self):
1697+        """
1698+        With --dry-run, no files created in destination dir.
1699+        """
1700+        self.assertEquals(os.listdir(settings.STATICFILES_ROOT), [])
1701+
1702+
1703+if sys.platform != 'win32':
1704+    class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults):
1705+        """
1706+        Test ``--link`` option for ``collectstatic`` management command.
1707+
1708+        Note that by inheriting ``TestDefaults`` we repeat all
1709+        the standard file resolving tests here, to make sure using
1710+        ``--link`` does not change the file-selection semantics.
1711+        """
1712+        def run_collectstatic(self):
1713+            super(TestBuildStaticLinks, self).run_collectstatic(link=True)
1714+
1715+        def test_links_created(self):
1716+            """
1717+            With ``--link``, symbolic links are created.
1718+
1719+            """
1720+            self.failUnless(os.path.islink(os.path.join(settings.STATICFILES_ROOT, 'test.txt')))
1721+
1722+
1723+class TestServeStatic(StaticFilesTestCase):
1724+    """
1725+    Test static asset serving view.
1726+    """
1727+    urls = "regressiontests.staticfiles_tests.urls"
1728+
1729+    def _response(self, filepath):
1730+        return self.client.get(
1731+            posixpath.join(settings.STATICFILES_URL, filepath))
1732+
1733+    def assertFileContains(self, filepath, text):
1734+        self.assertContains(self._response(filepath), text)
1735+
1736+    def assertFileNotFound(self, filepath):
1737+        self.assertEquals(self._response(filepath).status_code, 404)
1738+
1739+
1740+class TestServeAdminMedia(TestServeStatic):
1741+    """
1742+    Test serving media from django.contrib.admin.
1743+    """
1744+    def test_serve_admin_media(self):
1745+        media_file = posixpath.join(
1746+            settings.ADMIN_MEDIA_PREFIX, 'css/base.css')
1747+        response = self.client.get(media_file)
1748+        self.assertContains(response, 'body')
1749+
1750+
1751+class FinderTestCase(object):
1752+    """
1753+    Base finder test mixin
1754+    """
1755+    def test_find_first(self):
1756+        src, dst = self.find_first
1757+        self.assertEquals(self.finder.find(src), dst)
1758+
1759+    def test_find_all(self):
1760+        src, dst = self.find_all
1761+        self.assertEquals(self.finder.find(src, all=True), dst)
1762+
1763+
1764+class TestFileSystemFinder(StaticFilesTestCase, FinderTestCase):
1765+    """
1766+    Test FileSystemFinder.
1767+    """
1768+    def setUp(self):
1769+        super(TestFileSystemFinder, self).setUp()
1770+        self.finder = finders.FileSystemFinder()
1771+        test_file_path = os.path.join(TEST_ROOT, 'project/documents/test/file.txt')
1772+        self.find_first = ("test/file.txt", test_file_path)
1773+        self.find_all = ("test/file.txt", [test_file_path])
1774+
1775+
1776+class TestAppDirectoriesFinder(StaticFilesTestCase, FinderTestCase):
1777+    """
1778+    Test AppDirectoriesFinder.
1779+    """
1780+    def setUp(self):
1781+        super(TestAppDirectoriesFinder, self).setUp()
1782+        self.finder = finders.AppDirectoriesFinder()
1783+        test_file_path = os.path.join(TEST_ROOT, 'apps/test/media/test/file1.txt')
1784+        self.find_first = ("test/file1.txt", test_file_path)
1785+        self.find_all = ("test/file1.txt", [test_file_path])
1786+
1787+
1788+class TestDefaultStorageFinder(StaticFilesTestCase, FinderTestCase):
1789+    """
1790+    Test DefaultStorageFinder.
1791+    """
1792+    def setUp(self):
1793+        super(TestDefaultStorageFinder, self).setUp()
1794+        self.finder = finders.DefaultStorageFinder(
1795+            storage=storage.StaticFilesStorage(location=settings.MEDIA_ROOT))
1796+        test_file_path = os.path.join(settings.MEDIA_ROOT, 'media-file.txt')
1797+        self.find_first = ("media-file.txt", test_file_path)
1798+        self.find_all = ("media-file.txt", [test_file_path])
1799+
1800+
1801+class TestMiscFinder(TestCase):
1802+    """
1803+    A few misc finder tests.
1804+    """
1805+    def test_get_finder(self):
1806+        self.assertTrue(isinstance(finders.get_finder(
1807+            "django.contrib.staticfiles.finders.FileSystemFinder"),
1808+            finders.FileSystemFinder))
1809+        self.assertRaises(ImproperlyConfigured,
1810+            finders.get_finder, "django.contrib.staticfiles.finders.FooBarFinder")
1811+        self.assertRaises(ImproperlyConfigured,
1812+            finders.get_finder, "foo.bar.FooBarFinder")
1813+
1814diff --git a/tests/regressiontests/staticfiles_tests/urls.py b/tests/regressiontests/staticfiles_tests/urls.py
1815new file mode 100644
1816index 0000000..061ec64
1817--- /dev/null
1818+++ b/tests/regressiontests/staticfiles_tests/urls.py
1819@@ -0,0 +1,6 @@
1820+from django.conf import settings
1821+from django.conf.urls.defaults import *
1822+
1823+urlpatterns = patterns('',
1824+    url(r'^static/(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
1825+)
1826diff --git a/tests/runtests.py b/tests/runtests.py
1827index a5f7479..055c910 100755
1828--- a/tests/runtests.py
1829+++ b/tests/runtests.py
1830@@ -27,6 +27,7 @@ ALWAYS_INSTALLED_APPS = [
1831     'django.contrib.comments',
1832     'django.contrib.admin',
1833     'django.contrib.admindocs',
1834+    'django.contrib.staticfiles',
1835 ]
1836 
1837 def get_test_models():
1838diff --git a/tests/urls.py b/tests/urls.py
1839index 01d6408..e06dc33 100644
1840--- a/tests/urls.py
1841+++ b/tests/urls.py
1842@@ -41,4 +41,7 @@ urlpatterns = patterns('',
1843 
1844     # special headers views
1845     (r'special_headers/', include('regressiontests.special_headers.urls')),
1846+
1847+    # static files handling
1848+    (r'^', include('regressiontests.staticfiles_tests.urls')),
1849 )