From 8e96584f6363cba7cbbac41771a4318dde9f46dd Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 11 Nov 2010 21:43:49 +0000 Subject: [PATCH] Fixed #14524, #14582, #14617, #14665 and #14667 -- Tweaked staticfiles app. * Updated StaticFilesHandler and AdminMediaHandler to make use of the 404 handler if needed. * Updated runserver management command to serve static files only in DEBUG mode (or if specified the --insecure option) and if the staticfiles app is in INSTALLED_APPS. Also added an option to disable serving completely (--nostatic). * Added check in debug mode if STATICFILES_* settings are different to MEDIA_* settings. * Removed a faulty PendingDeprecationWarning in AdminMediaHandler that is triggered every time runserver is used. * Fixed an issue with the modification time checks when running collectstatic. * Extended and refined documentation. Thanks to everyone for input, especially to Carl Meyer, Ted Kaemming and Adam Vandenberg for patches. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14533 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/staticfiles/handlers.py | 72 ++++++++++--------- .../management/commands/collectstatic.py | 5 +- django/contrib/staticfiles/storage.py | 2 + django/contrib/staticfiles/urls.py | 6 +- django/contrib/staticfiles/utils.py | 15 ++++ django/contrib/staticfiles/views.py | 12 ++-- django/core/management/commands/runserver.py | 12 +++- django/core/servers/basehttp.py | 24 ++++--- django/views/static.py | 4 +- docs/howto/static-files.txt | 41 +++++++++-- docs/man/django-admin.1 | 2 +- docs/ref/contrib/staticfiles.txt | 26 ++++--- docs/ref/django-admin.txt | 25 +++++++ docs/ref/settings.txt | 22 +++--- 14 files changed, 190 insertions(+), 78 deletions(-) diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py index 8681c55c16a..20b04960da3 100644 --- a/django/contrib/staticfiles/handlers.py +++ b/django/contrib/staticfiles/handlers.py @@ -1,10 +1,10 @@ -import os import urllib from urlparse import urlparse -from django.core.handlers.wsgi import WSGIHandler, STATUS_CODE_TEXT -from django.http import Http404 +from django.conf import settings +from django.core.handlers.wsgi import WSGIHandler +from django.contrib.staticfiles import utils from django.contrib.staticfiles.views import serve class StaticFilesHandler(WSGIHandler): @@ -18,16 +18,28 @@ class StaticFilesHandler(WSGIHandler): self.media_dir = media_dir else: self.media_dir = self.get_media_dir() - self.media_url = self.get_media_url() + self.media_url = urlparse(self.get_media_url()) + if settings.DEBUG: + utils.check_settings() + super(StaticFilesHandler, self).__init__() def get_media_dir(self): - from django.conf import settings return settings.STATICFILES_ROOT def get_media_url(self): - from django.conf import settings return settings.STATICFILES_URL + def _should_handle(self, path): + """ + Checks if the path should be handled. Ignores the path if: + + * the host is provided as part of the media_url + * the request's path isn't under the media path (or equal) + * settings.DEBUG isn't True + """ + return (self.media_url[2] != path and + path.startswith(self.media_url[2]) and not self.media_url[1]) + def file_path(self, url): """ Returns the relative path to the media file on disk for the given URL. @@ -37,36 +49,28 @@ class StaticFilesHandler(WSGIHandler): is raised. """ # Remove ``media_url``. - relative_url = url[len(self.media_url):] + relative_url = url[len(self.media_url[2]):] 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 serve(request, path=filename, document_root=absolute_path) + def serve(self, request): + """ + Actually serves the request path. + """ + return serve(request, self.file_path(request.path), insecure=True) + + def get_response(self, request): + from django.http import Http404 + + if self._should_handle(request.path): + try: + return self.serve(request) + except Http404, e: + if settings.DEBUG: + from django.views import debug + return debug.technical_404_response(request, e) + return super(StaticFilesHandler, self).get_response(request) 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])): + if not self._should_handle(environ['PATH_INFO']): 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 - + return super(StaticFilesHandler, self).__call__(environ, start_response) diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index daf7ab26f06..d1212235959 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -71,6 +71,9 @@ Type 'yes' to continue, or 'no' to cancel: """) if confirm != 'yes': raise CommandError("Static files build cancelled.") + # Use ints for file times (ticket #14665) + os.stat_float_times(False) + for finder in finders.get_finders(): for source, prefix, storage in finder.list(ignore_patterns): self.copy_file(source, prefix, storage, **options) @@ -126,7 +129,7 @@ Type 'yes' to continue, or 'no' to cancel: """) else: destination_is_link = os.path.islink( self.destination_storage.path(destination)) - if destination_last_modified == source_last_modified: + if destination_last_modified >= source_last_modified: if (not symlink and not destination_is_link): if verbosity >= 2: self.stdout.write("Skipping '%s' (not modified)\n" diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index 87b569af761..b4bfea74d44 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -27,6 +27,8 @@ class StaticFilesStorage(FileSystemStorage): 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.") + if settings.DEBUG: + utils.check_settings() super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs) diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py index efe459b879e..70f04f2ec92 100644 --- a/django/contrib/staticfiles/urls.py +++ b/django/contrib/staticfiles/urls.py @@ -19,10 +19,14 @@ def staticfiles_urlpatterns(prefix=None): return [] if prefix is None: prefix = settings.STATICFILES_URL + if not prefix: + raise ImproperlyConfigured( + "The prefix for the 'staticfiles_urlpatterns' helper is empty. " + "Make sure the STATICFILES_URL setting is set correctly.") 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.") + "can't be used with the 'staticfiles_urlpatterns' helper.") if prefix.startswith("/"): prefix = prefix[1:] return patterns('', diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py index f5a30befe9b..0071dbd370f 100644 --- a/django/contrib/staticfiles/utils.py +++ b/django/contrib/staticfiles/utils.py @@ -1,4 +1,6 @@ import fnmatch +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured def get_files(storage, ignore_patterns=[], location=''): """ @@ -28,3 +30,16 @@ def get_files(storage, ignore_patterns=[], location=''): dir = '/'.join([location, dir]) static_files.extend(get_files(storage, ignore_patterns, dir)) return static_files + +def check_settings(): + """ + Checks if the MEDIA_(ROOT|URL) and STATICFILES_(ROOT|URL) + settings have the same value. + """ + if settings.MEDIA_URL == settings.STATICFILES_URL: + raise ImproperlyConfigured("The MEDIA_URL and STATICFILES_URL " + "settings must have individual values") + if ((settings.MEDIA_ROOT and settings.STATICFILES_ROOT) and + (settings.MEDIA_ROOT == settings.STATICFILES_ROOT)): + raise ImproperlyConfigured("The MEDIA_ROOT and STATICFILES_ROOT " + "settings must have individual values") diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py index b2c47d64f19..147dec93f0b 100644 --- a/django/contrib/staticfiles/views.py +++ b/django/contrib/staticfiles/views.py @@ -17,10 +17,10 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpRespons from django.template import loader, Template, Context, TemplateDoesNotExist from django.utils.http import http_date -from django.contrib.staticfiles import finders +from django.contrib.staticfiles import finders, utils -def serve(request, path, document_root=None, show_indexes=False): +def serve(request, path, document_root=None, show_indexes=False, insecure=False): """ Serve static files below a given point in the directory structure or from locations inferred from the static files finders. @@ -41,13 +41,15 @@ def serve(request, path, document_root=None, show_indexes=False): template hardcoded below, but if you'd like to override it, you can create a template called ``static/directory_index.html``. """ - if not settings.DEBUG: + if not settings.DEBUG and not insecure: raise ImproperlyConfigured("The view to serve static files can only " - "be used if the DEBUG setting is True") + "be used if the DEBUG setting is True or " + "the --insecure option of 'runserver' is " + "used") if not document_root: absolute_path = finders.find(path) if not absolute_path: - raise Http404("%r could not be matched to a static file." % path) + raise Http404('"%s" could not be found' % path) document_root, path = os.path.split(absolute_path) # Clean up given path to only allow serving files below document_root. path = posixpath.normpath(urllib.unquote(path)) diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index 21391e82ad3..49e270d04a5 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -9,6 +9,10 @@ class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--noreload', action='store_false', dest='use_reloader', default=True, help='Tells Django to NOT use the auto-reloader.'), + make_option('--nostatic', action="store_false", dest='use_static_handler', default=True, + help='Tells Django to NOT automatically serve static files at STATICFILES_URL.'), + make_option('--insecure', action="store_true", dest='insecure_serving', default=False, + help='Allows serving static files even if DEBUG is True.'), make_option('--adminmedia', dest='admin_media_path', default='', help='Specifies the directory from which to serve admin media.'), ) @@ -42,6 +46,8 @@ class Command(BaseCommand): use_reloader = options.get('use_reloader', True) admin_media_path = options.get('admin_media_path', '') shutdown_message = options.get('shutdown_message', '') + use_static_handler = options.get('use_static_handler', True) + insecure_serving = options.get('insecure_serving', False) quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C' def inner_run(): @@ -60,7 +66,11 @@ class Command(BaseCommand): try: handler = WSGIHandler() - handler = StaticFilesHandler(handler) + allow_serving = (settings.DEBUG and use_static_handler or + (use_static_handler and insecure_serving)) + if (allow_serving and + "django.contrib.staticfiles" in settings.INSTALLED_APPS): + handler = StaticFilesHandler(handler) # serve admin media like old-school (deprecation pending) handler = AdminMediaHandler(handler, admin_media_path) run(addr, int(port), handler) diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index ab38e98f138..9c92a88b441 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -651,12 +651,6 @@ class AdminMediaHandler(StaticFilesHandler): from django.conf import settings 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. @@ -666,13 +660,23 @@ class AdminMediaHandler(StaticFilesHandler): is raised. """ # Remove ``media_url``. - relative_url = url[len(self.media_url):] + relative_url = url[len(self.media_url[2]):] relative_path = urllib.url2pathname(relative_url) return safe_join(self.media_dir, relative_path) - def serve(self, request, path): - document_root, path = os.path.split(path) - return static.serve(request, path, document_root=document_root) + def serve(self, request): + document_root, path = os.path.split(self.file_path(request.path)) + return static.serve(request, path, + document_root=document_root, insecure=True) + + def _should_handle(self, path): + """ + Checks if the path should be handled. Ignores the path if: + + * the host is provided as part of the media_url + * the request's path isn't under the media path + """ + return path.startswith(self.media_url[2]) and not self.media_url[1] def run(addr, port, wsgi_handler): diff --git a/django/views/static.py b/django/views/static.py index eee9885c021..a4a4c8db7cc 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -21,7 +21,7 @@ from django.contrib.staticfiles.views import \ directory_index, was_modified_since, serve as staticfiles_serve -def serve(request, path, document_root=None, show_indexes=False): +def serve(request, path, document_root=None, show_indexes=False, insecure=False): """ Serve static files below a given point in the directory structure. @@ -38,4 +38,4 @@ def serve(request, path, document_root=None, show_indexes=False): warnings.warn("The view at `django.views.static.serve` is deprecated; " "use the path `django.contrib.staticfiles.views.serve` " "instead.", PendingDeprecationWarning) - return staticfiles_serve(request, path, document_root, show_indexes) + return staticfiles_serve(request, path, document_root, show_indexes, insecure) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 8d274096178..8dca80eb84d 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -37,7 +37,7 @@ Using ``django.contrib.staticfiles`` Here's the basic usage in a nutshell: - 1. Put your media somewhere that staticfiles will find it.. + 1. Put your media somewhere that staticfiles will find it. Most of the time this place will be in a ``static`` directory within your application, but it could also be a specific directory you've put into @@ -69,12 +69,19 @@ Here's the basic usage in a nutshell: ./manage.py collectstatic This'll churn through your static file storage and move them into the - directory given by :setting:`STATICFILES_ROOT`. + directory given by :setting:`STATICFILES_ROOT`. (This is not necessary + in local development if you are using :djadmin:`runserver` or adding + ``staticfiles_urlpatterns`` to your URLconf; see below). 4. Deploy that media. - If you're using the built-in development server, you can quickly - serve static media locally by adding:: + If you're using the built-in development server (the + :djadmin:`runserver` management command) and have the :setting:`DEBUG` + setting set to ``True``, your staticfiles will automatically be served + from :setting:`STATICFILES_URL` in development. + + If you are using some other server for local development, you can + quickly serve static media locally by adding:: from django.contrib.staticfiles.urls import staticfiles_urlpatterns urlpatterns += staticfiles_urlpatterns() @@ -100,6 +107,18 @@ Those are the basics. For more details on common configuration options, read on; for a detailed reference of the settings, commands, and other bits included with the framework see :doc:`the staticfiles reference `. +.. note:: + + In previous versions of Django, it was common to place static assets in + :setting:`MEDIA_ROOT` along with user-uploaded files, and serve them both at + :setting:`MEDIA_URL`. Part of the purpose of introducing the ``staticfiles`` + app is to make it easier to keep static files separate from user-uploaded + files. For this reason, you will probably want to make your + :setting:`MEDIA_ROOT` and :setting:`MEDIA_URL` different from your + :setting:`STATICFILES_ROOT` and :setting:`STATICFILES_URL`. You will need to + arrange for serving of files in :setting:`MEDIA_ROOT` yourself; + ``staticfiles`` does not deal with user-uploaded media at all. + .. _staticfiles-in-templates: Referring to static files in templates @@ -192,8 +211,12 @@ media server, which is a lot of overhead to mess with when developing locally. Thus, the ``staticfiles`` app ships with a quick and dirty helper view that you can use to serve files locally in development. -To enable this view, you'll add a couple of lines to your URLconf. The first -line goes at the top of the file, and the last line at the bottom:: +This view is automatically enabled and will serve your static files at +:setting:`STATICFILES_URL` when you use the built-in :djadmin:`runserver`. + +To enable this view if you are using some other server for local development, +you'll add a couple of lines to your URLconf. The first line goes at the top of +the file, and the last line at the bottom:: from django.contrib.staticfiles.urls import staticfiles_urlpatterns @@ -242,7 +265,7 @@ app, the basic outline gets modified to look something like: * On the server, run :djadmin:`collectstatic` to move all the media into :setting:`STATICFILES_ROOT`. * Point your web server at :setting:`STATICFILES_ROOT`. For example, here's - of :ref:`how to do this under Apache and mod_wsgi `. + :ref:`how to do this under Apache and mod_wsgi `. You'll probably want to automate this process, especially if you've got multiple web servers. There's any number of ways to do this automation, but one option @@ -393,6 +416,10 @@ you'll need to make a few changes: ``staticfiles.storage.StaticFileStorage`` to ``staticfiles.storage.StaticFilesStorage`` + * If using :djadmin:`runserver` for local development (and the + :setting:`DEBUG` setting is ``True``), you no longer need to add + anything to your URLconf for serving static files in development. + Learn more ========== diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index a402bab65ae..539d88c5c50 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -75,7 +75,7 @@ Runs this project as a FastCGI application. Requires flup. Use .B runfcgi help for help on the KEY=val pairs. .TP -.BI "runserver [" "\-\-noreload" "] [" "\-\-adminmedia=ADMIN_MEDIA_PATH" "] [" "port|ipaddr:port" "]" +.BI "runserver [" "\-\-noreload" "] [" "\-\-nostatic" "] [" "\-\-insecure" "] [" "\-\-adminmedia=ADMIN_MEDIA_PATH" "] [" "port|ipaddr:port" "]" Starts a lightweight Web server for development. .TP .BI "shell [" "\-\-plain" "]" diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 046e84f8ca4..f82fd790243 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -34,13 +34,21 @@ STATICFILES_ROOT Default: ``''`` (Empty string) -The absolute path to the directory that holds static files:: +The absolute path to the directory that the :djadmin:`collectstatic` management +command will collect static files into, for serving from +:setting:`STATICFILES_URL`:: STATICFILES_ROOT = "/home/example.com/static/" This is a **required setting** unless you've overridden :setting:`STATICFILES_STORAGE` and are using a custom storage backend. +This is not a place to store your static files permanently under version +control; you should do that in directories that will be found by your +:setting:`STATICFILES_FINDERS` (by default, per-app ``static/`` subdirectories, +and any directories you include in :setting:`STATICFILES_DIRS`). Files from +those locations will be collected into :setting:`STATICFILES_ROOT`. + .. setting:: STATICFILES_URL STATICFILES_URL @@ -51,7 +59,7 @@ Default: ``'/static/'`` The URL that handles the files served from :setting:`STATICFILES_ROOT`, e.g.:: STATICFILES_URL = '/site_media/static/' - + ... or perhaps:: STATICFILES_URL = 'http://static.example.com/' @@ -130,7 +138,7 @@ your :setting:`STATICFILES_FINDERS` setting, it will look for static files in the default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE` setting. -.. note:: +.. note:: When using the :class:`AppDirectoriesFinder` finder, make sure your apps can be found by Django's app loading mechanism. Simply include a ``models`` @@ -271,11 +279,13 @@ This view function serves static files in development. **insecure**. This is only intended for local development, and should **never be used in production**. -To use the view, add the following snippet to the end of your primary URL -configuration:: +This view is automatically enabled by :djadmin:`runserver` (with a +:setting:`DEBUG` setting set to ``True``). To use the view with a different +local development server, 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'^static/(?P.*)$', 'serve'), @@ -292,8 +302,8 @@ This will return the proper URL pattern for serving static files to your already defined pattern list. Use it like this:: from django.contrib.staticfiles.urls import staticfiles_urlpatterns - + # ... the rest of your URLconf here ... - + urlpatterns += staticfiles_urlpatterns() diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index ea49e2372d2..14fc69f99ed 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -681,6 +681,31 @@ Example usage:: django-admin.py runserver --noreload +.. django-admin-option:: --nostatic + +Use the ``--nostatic`` option to disable serving of static files with the +:doc:`staticfiles ` app entirely. + +Example usage:: + + django-admin.py runserver --nostatic + +.. django-admin-option:: --insecure + +Use the ``--insecure`` option to force serving of static files with the +:doc:`staticfiles ` app even if the :setting:`DEBUG` +setting is ``False``. By using this you acknoledge the fact that it's +**grossly inefficient** and probably **insecure**. This is only intended for +local development, and should **never be used in production**. + +See the :doc:`reference documentation of the app ` +for more details and learn how to :doc:`manage and deploy static files +` correctly. + +Example usage:: + + django-admin.py runserver --insecure + Examples of using different ports and addresses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 78b5fd995e9..2c338df6c7d 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -53,7 +53,7 @@ Default: ``'/media/'`` The URL prefix for admin media -- CSS, JavaScript and images used by the Django administrative interface. Make sure to use a trailing -slash, and to have this be different from the ``MEDIA_URL`` setting +slash, and to have this be different from the :setting:`MEDIA_URL` setting (since the same URL cannot be mapped onto two different sets of files). @@ -1104,8 +1104,12 @@ MEDIA_ROOT Default: ``''`` (Empty string) -Absolute path to the directory that holds media for this installation. -Example: ``"/home/media/media.lawrence.com/"`` See also ``MEDIA_URL``. +Absolute path to the directory that holds media for this installation, used +for :doc:`managing stored files `. + +Example: ``"/home/media/media.lawrence.com/"`` + +See also :setting:`MEDIA_URL`. .. setting:: MEDIA_URL @@ -1114,15 +1118,15 @@ MEDIA_URL Default: ``''`` (Empty string) -URL that handles the media served from ``MEDIA_ROOT``. +URL that handles the media served from :setting:`MEDIA_ROOT`, used +for :doc:`managing stored files `. + Example: ``"http://media.lawrence.com"`` Note that this should have a trailing slash if it has a path component. -Good: ``"http://www.example.com/static/"`` -Bad: ``"http://www.example.com/static"`` - -.. setting:: MIDDLEWARE_CLASSES + * Good: ``"http://www.example.com/static/"`` + * Bad: ``"http://www.example.com/static"`` MESSAGE_LEVEL ------------- @@ -1161,6 +1165,8 @@ Default:: Sets the mapping of message levels to message tags. See the :doc:`messages documentation ` for more details. +.. setting:: MIDDLEWARE_CLASSES + MIDDLEWARE_CLASSES ------------------