Permalink
Browse files

Fixed #14524, #14582, #14617, #14665 and #14667 -- Tweaked staticfile…

…s 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
  • Loading branch information...
1 parent 216fdfa commit 8e96584f6363cba7cbbac41771a4318dde9f46dd @jezdez jezdez committed Nov 11, 2010
@@ -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 @@ def __init__(self, application, media_dir=None):
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 @@ def file_path(self, url):
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)
@@ -71,6 +71,9 @@ def handle_noargs(self, **options):
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 @@ def copy_file(self, source, prefix, source_storage, **options):
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"
@@ -27,6 +27,8 @@ def __init__(self, location=None, base_url=None, *args, **kwargs):
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)
@@ -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('',
@@ -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 is_ignored(path):
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")
@@ -17,10 +17,10 @@
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))
@@ -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 @@ def handle(self, addrport='', *args, **options):
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 @@ def inner_run():
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)
@@ -651,12 +651,6 @@ def get_media_url(self):
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 @@ def file_path(self, url):
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):
View
@@ -21,7 +21,7 @@
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)
@@ -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 </ref/contrib/staticfiles>`.
+.. 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 <serving-media-files>`.
+ :ref:`how to do this under Apache and mod_wsgi <serving-media-files>`.
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
==========
View
@@ -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" "]"
Oops, something went wrong.

0 comments on commit 8e96584

Please sign in to comment.