Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixes #15270 -- Moved back the serve view to django.views.static due …

…to dependency conflicts with the contrib app staticfiles (reverts parts of r14293). Added a helper function that generates URL patterns for serving static and media files during development. Thanks to Carl for reviewing the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15530 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit a26034ffbf8951276b79ccb298423bc809246637 1 parent 6b1191b
Jannis Leidel jezdez authored
26 django/conf/urls/static.py
View
@@ -0,0 +1,26 @@
+import re
+from django.conf import settings
+from django.conf.urls.defaults import patterns, url
+from django.core.exceptions import ImproperlyConfigured
+
+def static(prefix, view='django.views.static.serve', **kwargs):
+ """
+ Helper function to return a URL pattern for serving files in debug mode.
+
+ from django.conf import settings
+ from django.conf.urls.static import static
+
+ urlpatterns = patterns('',
+ # ... the rest of your URLconf goes here ...
+ ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+
+ """
+ if not settings.DEBUG:
+ return []
+ elif not prefix:
+ raise ImproperlyConfigured("Empty static prefix not permitted")
+ elif '://' in prefix:
+ raise ImproperlyConfigured("URL '%s' not allowed as static prefix" % prefix)
+ return patterns('',
+ url(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), view, **kwargs),
+ )
24 django/contrib/staticfiles/urls.py
View
@@ -1,28 +1,16 @@
-import re
from django.conf import settings
-from django.conf.urls.defaults import patterns, url, include
-from django.core.exceptions import ImproperlyConfigured
+from django.conf.urls.static import static
urlpatterns = []
-# only serve non-fqdn URLs
-if settings.DEBUG:
- urlpatterns += patterns('',
- url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
- )
-
def staticfiles_urlpatterns(prefix=None):
"""
Helper function to return a URL pattern for serving static files.
"""
- if not settings.DEBUG:
- return []
if prefix is None:
prefix = settings.STATIC_URL
- if not prefix or '://' in prefix:
- raise ImproperlyConfigured(
- "The prefix for the 'staticfiles_urlpatterns' helper is invalid.")
- if prefix.startswith("/"):
- prefix = prefix[1:]
- return patterns('',
- url(r'^%s' % re.escape(prefix), include(urlpatterns)),)
+ return static(prefix, view='django.contrib.staticfiles.views.serve')
+
+# Only append if urlpatterns are empty
+if settings.DEBUG and not urlpatterns:
+ urlpatterns += staticfiles_urlpatterns()
153 django/contrib/staticfiles/views.py
View
@@ -3,27 +3,21 @@
development, and SHOULD NOT be used in a production setting.
"""
-import mimetypes
import os
import posixpath
-import re
-import stat
import urllib
-from email.Utils import parsedate_tz, mktime_tz
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
-from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
-from django.template import loader, Template, Context, TemplateDoesNotExist
-from django.utils.http import http_date
+from django.http import Http404
+from django.views import static
-from django.contrib.staticfiles import finders, utils
+from django.contrib.staticfiles import finders
-
-def serve(request, path, document_root=None, show_indexes=False, insecure=False):
+def serve(request, path, document_root=None, insecure=False, **kwargs):
"""
Serve static files below a given point in the directory structure or
- from locations inferred from the static files finders.
+ from locations inferred from the staticfiles finders.
To use, put a URL pattern such as::
@@ -31,135 +25,14 @@ def serve(request, path, document_root=None, show_indexes=False, insecure=False)
in your URLconf.
- If you provide the ``document_root`` parameter, the file won't be looked
- up with the staticfiles finders, but in the given filesystem path, e.g.::
-
- (r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve', {'document_root' : '/path/to/my/files/'})
-
- You may also set ``show_indexes`` to ``True`` if you'd like to serve a
- basic index of the directory. This index view will use the
- template hardcoded below, but if you'd like to override it, you can create
- a template called ``static/directory_index.html``.
+ It automatically falls back to django.views.static
"""
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 or "
- "the --insecure option of 'runserver' is "
- "used")
- if not document_root:
- path = os.path.normpath(path)
- absolute_path = finders.find(path)
- if not absolute_path:
- raise Http404('"%s" could not be found' % path)
+ raise ImproperlyConfigured("The staticfiles view can only be used in "
+ "debug mode or if the the --insecure "
+ "option of 'runserver' is used")
+ normalized_path = posixpath.normpath(urllib.unquote(path)).lstrip('/')
+ absolute_path = finders.find(normalized_path)
+ if absolute_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))
- path = path.lstrip('/')
- newpath = ''
- for part in path.split('/'):
- if not part:
- # Strip empty path components.
- continue
- drive, part = os.path.splitdrive(part)
- head, part = os.path.split(part)
- if part in (os.curdir, os.pardir):
- # Strip '.' and '..' in path.
- continue
- newpath = os.path.join(newpath, part).replace('\\', '/')
- if newpath and path != newpath:
- return HttpResponseRedirect(newpath)
- fullpath = os.path.join(document_root, newpath)
- if os.path.isdir(fullpath):
- if show_indexes:
- return directory_index(newpath, fullpath)
- raise Http404("Directory indexes are not allowed here.")
- if not os.path.exists(fullpath):
- raise Http404('"%s" does not exist' % fullpath)
- # Respect the If-Modified-Since header.
- statobj = os.stat(fullpath)
- mimetype, encoding = mimetypes.guess_type(fullpath)
- mimetype = mimetype or 'application/octet-stream'
- if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
- statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
- return HttpResponseNotModified(mimetype=mimetype)
- contents = open(fullpath, 'rb').read()
- response = HttpResponse(contents, mimetype=mimetype)
- response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
- response["Content-Length"] = len(contents)
- if encoding:
- response["Content-Encoding"] = encoding
- return response
-
-
-DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <meta http-equiv="Content-Language" content="en-us" />
- <meta name="robots" content="NONE,NOARCHIVE" />
- <title>Index of {{ directory }}</title>
- </head>
- <body>
- <h1>Index of {{ directory }}</h1>
- <ul>
- {% ifnotequal directory "/" %}
- <li><a href="../">../</a></li>
- {% endifnotequal %}
- {% for f in file_list %}
- <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
- {% endfor %}
- </ul>
- </body>
-</html>
-"""
-
-def directory_index(path, fullpath):
- try:
- t = loader.select_template(['static/directory_index.html',
- 'static/directory_index'])
- except TemplateDoesNotExist:
- t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
- files = []
- for f in os.listdir(fullpath):
- if not f.startswith('.'):
- if os.path.isdir(os.path.join(fullpath, f)):
- f += '/'
- files.append(f)
- c = Context({
- 'directory' : path + '/',
- 'file_list' : files,
- })
- return HttpResponse(t.render(c))
-
-def was_modified_since(header=None, mtime=0, size=0):
- """
- Was something modified since the user last downloaded it?
-
- header
- This is the value of the If-Modified-Since header. If this is None,
- I'll just return True.
-
- mtime
- This is the modification time of the item we're talking about.
-
- size
- This is the size of the item we're talking about.
- """
- try:
- if header is None:
- raise ValueError
- matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
- re.IGNORECASE)
- header_date = parsedate_tz(matches.group(1))
- if header_date is None:
- raise ValueError
- header_mtime = mktime_tz(header_date)
- header_len = matches.group(3)
- if header_len and int(header_len) != size:
- raise ValueError
- if mtime > header_mtime:
- raise ValueError
- except (AttributeError, ValueError, OverflowError):
- return True
- return False
+ return static.serve(request, path, document_root=document_root, **kwargs)
6 django/core/servers/basehttp.py
View
@@ -18,8 +18,9 @@
from django.core.management.color import color_style
from django.utils.http import http_date
from django.utils._os import safe_join
+from django.views import static
-from django.contrib.staticfiles import handlers, views as static
+from django.contrib.staticfiles import handlers
__version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler']
@@ -677,8 +678,7 @@ def file_path(self, url):
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)
+ return static.serve(request, path, document_root=document_root)
def _should_handle(self, path):
"""
121 django/views/static.py
View
@@ -9,7 +9,6 @@
import re
import stat
import urllib
-import warnings
from email.Utils import parsedate_tz, mktime_tz
from django.template import loader
@@ -17,11 +16,7 @@
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
-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, insecure=False):
+def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.
@@ -35,7 +30,113 @@ def serve(request, path, document_root=None, show_indexes=False, insecure=False)
but if you'd like to override it, you can create a template called
``static/directory_index.html``.
"""
- 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, insecure)
+ path = posixpath.normpath(urllib.unquote(path))
+ path = path.lstrip('/')
+ newpath = ''
+ for part in path.split('/'):
+ if not part:
+ # Strip empty path components.
+ continue
+ drive, part = os.path.splitdrive(part)
+ head, part = os.path.split(part)
+ if part in (os.curdir, os.pardir):
+ # Strip '.' and '..' in path.
+ continue
+ newpath = os.path.join(newpath, part).replace('\\', '/')
+ if newpath and path != newpath:
+ return HttpResponseRedirect(newpath)
+ fullpath = os.path.join(document_root, newpath)
+ if os.path.isdir(fullpath):
+ if show_indexes:
+ return directory_index(newpath, fullpath)
+ raise Http404("Directory indexes are not allowed here.")
+ if not os.path.exists(fullpath):
+ raise Http404('"%s" does not exist' % fullpath)
+ # Respect the If-Modified-Since header.
+ statobj = os.stat(fullpath)
+ mimetype, encoding = mimetypes.guess_type(fullpath)
+ mimetype = mimetype or 'application/octet-stream'
+ if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
+ statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
+ return HttpResponseNotModified(mimetype=mimetype)
+ contents = open(fullpath, 'rb').read()
+ response = HttpResponse(contents, mimetype=mimetype)
+ response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
+ response["Content-Length"] = len(contents)
+ if encoding:
+ response["Content-Encoding"] = encoding
+ return response
+
+
+DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en-us" />
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ <title>Index of {{ directory }}</title>
+ </head>
+ <body>
+ <h1>Index of {{ directory }}</h1>
+ <ul>
+ {% ifnotequal directory "/" %}
+ <li><a href="../">../</a></li>
+ {% endifnotequal %}
+ {% for f in file_list %}
+ <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
+ {% endfor %}
+ </ul>
+ </body>
+</html>
+"""
+
+def directory_index(path, fullpath):
+ try:
+ t = loader.select_template(['static/directory_index.html',
+ 'static/directory_index'])
+ except TemplateDoesNotExist:
+ t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
+ files = []
+ for f in os.listdir(fullpath):
+ if not f.startswith('.'):
+ if os.path.isdir(os.path.join(fullpath, f)):
+ f += '/'
+ files.append(f)
+ c = Context({
+ 'directory' : path + '/',
+ 'file_list' : files,
+ })
+ return HttpResponse(t.render(c))
+
+def was_modified_since(header=None, mtime=0, size=0):
+ """
+ Was something modified since the user last downloaded it?
+
+ header
+ This is the value of the If-Modified-Since header. If this is None,
+ I'll just return True.
+
+ mtime
+ This is the modification time of the item we're talking about.
+
+ size
+ This is the size of the item we're talking about.
+ """
+ try:
+ if header is None:
+ raise ValueError
+ matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
+ re.IGNORECASE)
+ header_date = parsedate_tz(matches.group(1))
+ if header_date is None:
+ raise ValueError
+ header_mtime = mktime_tz(header_date)
+ header_len = matches.group(3)
+ if header_len and int(header_len) != size:
+ raise ValueError
+ if mtime > header_mtime:
+ raise ValueError
+ except (AttributeError, ValueError, OverflowError):
+ return True
+ return False
71 docs/howto/static-files.txt
View
@@ -2,8 +2,6 @@
Managing static files
=====================
-.. currentmodule:: django.contrib.staticfiles
-
.. versionadded:: 1.3
Django developers mostly concern themselves with the dynamic parts of web
@@ -109,10 +107,9 @@ the framework see :doc:`the staticfiles reference </ref/contrib/staticfiles>`.
:setting:`MEDIA_URL` different from your :setting:`STATIC_ROOT` and
:setting:`STATIC_URL`. You will need to arrange for serving of files in
:setting:`MEDIA_ROOT` yourself; ``staticfiles`` does not deal with
- user-uploaded files at all. You can, however, use ``staticfiles``'
- :func:`~django.contrib.staticfiles.views.serve` view for serving
- :setting:`MEDIA_ROOT` in development; see
- :ref:`staticfiles-serve-other-directories`.
+ user-uploaded files at all. You can, however, use
+ :func:`~django.views.static.serve` view for serving :setting:`MEDIA_ROOT`
+ in development; see :ref:`staticfiles-other-directories`.
.. _staticfiles-in-templates:
@@ -241,8 +238,64 @@ files in app directories.
:setting:`STATIC_URL` setting can't be empty or a full URL, such as
``http://static.example.com/``.
-For a few more details, including an alternate method of enabling this view,
-see :ref:`staticfiles-development-view`.
+For a few more details on how the ``staticfiles`` can be used during
+development, see :ref:`staticfiles-development-view`.
+
+.. _staticfiles-other-directories:
+
+Serving other directories
+-------------------------
+
+.. currentmodule:: django.views.static
+.. function:: serve(request, path, document_root, show_indexes=False)
+
+There may be files other than your project's static assets that, for
+convenience, you'd like to have Django serve for you in local development.
+The :func:`~django.views.static.serve` view can be used to serve any directory
+you give it. (Again, this view is **not** hardened for production
+use, and should be used only as a development aid; you should serve these files
+in production using a real front-end webserver).
+
+The most likely example is user-uploaded content in :setting:`MEDIA_ROOT`.
+``staticfiles`` is intended for static assets and has no built-in handling
+for user-uploaded files, but you can have Django serve your
+:setting:`MEDIA_ROOT` by appending something like this to your URLconf::
+
+ from django.conf import settings
+
+ # ... the rest of your URLconf goes here ...
+
+ if settings.DEBUG:
+ urlpatterns += patterns('',
+ url(r'^media/(?P<path>.*)$', 'django.views.static', {
+ 'document_root': settings.MEDIA_ROOT,
+ }),
+ )
+
+Note, the snippet assumes your :setting:`MEDIA_URL` has a value of
+``'/media/'``. This will call the :func:`~django.views.static.serve` view,
+passing in the path from the URLconf and the (required) ``document_root``
+parameter.
+
+.. currentmodule:: django.conf.urls.static
+.. function:: static(prefix, view='django.views.static.serve', **kwargs)
+
+Since it can become a bit cumbersome to define this URL pattern, Django
+ships with a small URL helper function
+:func:`~django.conf.urls.static.static` that taks as parameters the prefix
+such as :setting:`MEDIA_URL` and a dotted path to a view, such as
+``'django.views.static.serve'``. Any other function parameter will be
+transparently passed to the view.
+
+An example for serving :setting:`MEDIA_URL` (``'/media/'``) during
+development::
+
+ from django.conf import settings
+ from django.conf.urls.static import static
+
+ urlpatterns = patterns('',
+ # ... the rest of your URLconf goes here ...
+ ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
.. _staticfiles-production:
@@ -395,7 +448,7 @@ Upgrading from ``django-staticfiles``
=====================================
``django.contrib.staticfiles`` began its life as `django-staticfiles`_. If
-you're upgrading from `django-staticfiles`_ < ``1.0``` (e.g. ``0.3.4``) to
+you're upgrading from `django-staticfiles`_ older than 1.0 (e.g. 0.3.4) to
``django.contrib.staticfiles``, you'll need to make a few changes:
* Application files should now live in a ``static`` directory in each app
28 docs/ref/contrib/staticfiles.txt
View
@@ -317,31 +317,3 @@ already defined pattern list. Use it like this::
This helper function will only work if :setting:`DEBUG` is ``True``
and your :setting:`STATIC_URL` setting is neither empty nor a full
URL such as ``http://static.example.com/``.
-
-.. _staticfiles-serve-other-directories:
-
-Serving other directories
-"""""""""""""""""""""""""
-
-There may be files other than your project's static assets that, for
-convenience, you'd like to have Django serve for you in local development. The
-:func:`~django.contrib.staticfiles.views.serve` view can be used to serve any
-directory you give it. (Again, this view is **not** hardened for production
-use, and should be used only as a development aid; you should serve these files
-in production using a real front-end webserver).
-
-The most likely example is user-uploaded content in :setting:`MEDIA_ROOT`.
-``staticfiles`` is intended for static assets and has no built-in handling for
-user-uploaded files, but you can have Django serve your :setting:`MEDIA_ROOT`
-by appending something like this to your URLconf::
-
- from django.conf import settings
-
- if settings.DEBUG:
- urlpatterns += patterns('django.contrib.staticfiles.views',
- url(r'^media/(?P<path>.*)$', 'serve',
- {'document_root': settings.MEDIA_ROOT}),
- )
-
-This snippet assumes you've also set your :setting:`MEDIA_URL` (in development)
-to ``/media/``.
5 tests/regressiontests/staticfiles_tests/tests.py
View
@@ -293,9 +293,8 @@ def setUp(self):
settings.DEBUG = False
def test_disabled_serving(self):
- self.assertRaisesRegexp(ImproperlyConfigured, 'The view to serve '
- 'static files can only be used if the DEBUG setting is True',
- self._response, 'test.txt')
+ self.assertRaisesRegexp(ImproperlyConfigured, 'The staticfiles view '
+ 'can only be used in debug mode ', self._response, 'test.txt')
class TestServeStaticWithDefaultURL(TestServeStatic, TestDefaults):
Please sign in to comment.
Something went wrong with that request. Please try again.