Ticket #15270: 15270.1.diff
File 15270.1.diff, 17.4 KB (added by , 14 years ago) |
---|
-
new file django/conf/urls/static.py
diff --git a/django/conf/urls/static.py b/django/conf/urls/static.py new file mode 100644 index 0000000..07a67cc
- + 1 import re 2 from django.conf import settings 3 from django.conf.urls.defaults import patterns, url 4 from django.core.exceptions import ImproperlyConfigured 5 6 def static(prefix, view='django.views.static.serve', **kwargs): 7 """ 8 Helper function to return a URL pattern for serving files in debug mode. 9 10 urlpatterns += static( 11 prefix=settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 12 """ 13 if not settings.DEBUG: 14 return [] 15 elif not prefix: 16 raise ImproperlyConfigured("Empty static prefix not permitted") 17 elif '://' in prefix: 18 raise ImproperlyConfigured("URL '%s' not allowed as static prefix" % prefix) 19 return patterns('', 20 url(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), view, **kwargs), 21 ) -
django/contrib/staticfiles/urls.py
diff --git a/django/contrib/staticfiles/urls.py b/django/contrib/staticfiles/urls.py index aa4ab45..04062c1 100644
a b 1 import re2 1 from django.conf import settings 3 from django.conf.urls.defaults import patterns, url, include 4 from django.core.exceptions import ImproperlyConfigured 2 from django.conf.urls.static import static 5 3 6 4 urlpatterns = [] 7 5 8 # only serve non-fqdn URLs9 if settings.DEBUG:10 urlpatterns += patterns('',11 url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),12 )13 14 6 def staticfiles_urlpatterns(prefix=None): 15 7 """ 16 8 Helper function to return a URL pattern for serving static files. 17 9 """ 18 if not settings.DEBUG:19 return []20 10 if prefix is None: 21 11 prefix = settings.STATIC_URL 22 if not prefix or '://' in prefix: 23 raise ImproperlyConfigured( 24 "The prefix for the 'staticfiles_urlpatterns' helper is invalid.") 25 if prefix.startswith("/"): 26 prefix = prefix[1:] 27 return patterns('', 28 url(r'^%s' % re.escape(prefix), include(urlpatterns)),) 12 return static(prefix, view='django.contrib.staticfiles.views.serve') 13 14 # Only append if urlpatterns are empty 15 if settings.DEBUG and not urlpatterns: 16 urlpatterns += staticfiles_urlpatterns() -
django/contrib/staticfiles/views.py
diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py index f5a6ec3..4d2a50d 100644
a b Views and functions for serving static files. These are only to be used during 3 3 development, and SHOULD NOT be used in a production setting. 4 4 5 5 """ 6 import mimetypes7 6 import os 8 import posixpath9 import re10 import stat11 import urllib12 from email.Utils import parsedate_tz, mktime_tz13 7 14 8 from django.conf import settings 15 9 from django.core.exceptions import ImproperlyConfigured 16 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified 17 from django.template import loader, Template, Context, TemplateDoesNotExist 18 from django.utils.http import http_date 10 from django.http import Http404 11 from django.views.static import serve as static_serve 19 12 20 from django.contrib.staticfiles import finders , utils13 from django.contrib.staticfiles import finders 21 14 22 23 def serve(request, path, document_root=None, show_indexes=False, insecure=False): 15 def serve(request, path, document_root=None, insecure=False, **kwargs): 24 16 """ 25 17 Serve static files below a given point in the directory structure or 26 18 from locations inferred from the static files finders. … … def serve(request, path, document_root=None, show_indexes=False, insecure=False) 42 34 a template called ``static/directory_index.html``. 43 35 """ 44 36 if not settings.DEBUG and not insecure: 45 raise ImproperlyConfigured("The view to serve static files can only " 46 "be used if the DEBUG setting is True or " 47 "the --insecure option of 'runserver' is " 48 "used") 37 raise ImproperlyConfigured("The staticfiles view can only be used in " 38 "debug mode or if the the --insecure " 39 "option of 'runserver' is used") 49 40 if not document_root: 50 41 path = os.path.normpath(path) 51 42 absolute_path = finders.find(path) 52 43 if not absolute_path: 53 44 raise Http404('"%s" could not be found' % path) 54 45 document_root, path = os.path.split(absolute_path) 55 # Clean up given path to only allow serving files below document_root. 56 path = posixpath.normpath(urllib.unquote(path)) 57 path = path.lstrip('/') 58 newpath = '' 59 for part in path.split('/'): 60 if not part: 61 # Strip empty path components. 62 continue 63 drive, part = os.path.splitdrive(part) 64 head, part = os.path.split(part) 65 if part in (os.curdir, os.pardir): 66 # Strip '.' and '..' in path. 67 continue 68 newpath = os.path.join(newpath, part).replace('\\', '/') 69 if newpath and path != newpath: 70 return HttpResponseRedirect(newpath) 71 fullpath = os.path.join(document_root, newpath) 72 if os.path.isdir(fullpath): 73 if show_indexes: 74 return directory_index(newpath, fullpath) 75 raise Http404("Directory indexes are not allowed here.") 76 if not os.path.exists(fullpath): 77 raise Http404('"%s" does not exist' % fullpath) 78 # Respect the If-Modified-Since header. 79 statobj = os.stat(fullpath) 80 mimetype, encoding = mimetypes.guess_type(fullpath) 81 mimetype = mimetype or 'application/octet-stream' 82 if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), 83 statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): 84 return HttpResponseNotModified(mimetype=mimetype) 85 contents = open(fullpath, 'rb').read() 86 response = HttpResponse(contents, mimetype=mimetype) 87 response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) 88 response["Content-Length"] = len(contents) 89 if encoding: 90 response["Content-Encoding"] = encoding 91 return response 92 93 94 DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ 95 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 96 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 97 <head> 98 <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> 99 <meta http-equiv="Content-Language" content="en-us" /> 100 <meta name="robots" content="NONE,NOARCHIVE" /> 101 <title>Index of {{ directory }}</title> 102 </head> 103 <body> 104 <h1>Index of {{ directory }}</h1> 105 <ul> 106 {% ifnotequal directory "/" %} 107 <li><a href="../">../</a></li> 108 {% endifnotequal %} 109 {% for f in file_list %} 110 <li><a href="{{ f|urlencode }}">{{ f }}</a></li> 111 {% endfor %} 112 </ul> 113 </body> 114 </html> 115 """ 116 117 def directory_index(path, fullpath): 118 try: 119 t = loader.select_template(['static/directory_index.html', 120 'static/directory_index']) 121 except TemplateDoesNotExist: 122 t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template') 123 files = [] 124 for f in os.listdir(fullpath): 125 if not f.startswith('.'): 126 if os.path.isdir(os.path.join(fullpath, f)): 127 f += '/' 128 files.append(f) 129 c = Context({ 130 'directory' : path + '/', 131 'file_list' : files, 132 }) 133 return HttpResponse(t.render(c)) 134 135 def was_modified_since(header=None, mtime=0, size=0): 136 """ 137 Was something modified since the user last downloaded it? 138 139 header 140 This is the value of the If-Modified-Since header. If this is None, 141 I'll just return True. 142 143 mtime 144 This is the modification time of the item we're talking about. 145 146 size 147 This is the size of the item we're talking about. 148 """ 149 try: 150 if header is None: 151 raise ValueError 152 matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, 153 re.IGNORECASE) 154 header_date = parsedate_tz(matches.group(1)) 155 if header_date is None: 156 raise ValueError 157 header_mtime = mktime_tz(header_date) 158 header_len = matches.group(3) 159 if header_len and int(header_len) != size: 160 raise ValueError 161 if mtime > header_mtime: 162 raise ValueError 163 except (AttributeError, ValueError, OverflowError): 164 return True 165 return False 46 return static_serve(request, path, document_root=document_root, **kwargs) -
django/core/servers/basehttp.py
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 7772a0b..34c76f4 100644
a b import warnings 18 18 from django.core.management.color import color_style 19 19 from django.utils.http import http_date 20 20 from django.utils._os import safe_join 21 from django.views.static import serve 21 22 22 from django.contrib.staticfiles import handlers , views as static23 from django.contrib.staticfiles import handlers 23 24 24 25 __version__ = "0.1" 25 26 __all__ = ['WSGIServer','WSGIRequestHandler'] … … class AdminMediaHandler(handlers.StaticFilesHandler): 677 678 678 679 def serve(self, request): 679 680 document_root, path = os.path.split(self.file_path(request.path)) 680 return static.serve(request, path, 681 document_root=document_root, insecure=True) 681 return serve(request, path, document_root=document_root) 682 682 683 683 def _should_handle(self, path): 684 684 """ -
django/views/static.py
diff --git a/django/views/static.py b/django/views/static.py index 2ce886f..9b34312 100644
a b import posixpath 9 9 import re 10 10 import stat 11 11 import urllib 12 import warnings13 12 from email.Utils import parsedate_tz, mktime_tz 14 13 15 14 from django.template import loader … … from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpRespons 17 16 from django.template import Template, Context, TemplateDoesNotExist 18 17 from django.utils.http import http_date 19 18 20 from django.contrib.staticfiles.views import (directory_index,21 was_modified_since, serve as staticfiles_serve)22 19 23 24 def serve(request, path, document_root=None, show_indexes=False, insecure=False): 20 def serve(request, path, document_root=None, show_indexes=False): 25 21 """ 26 22 Serve static files below a given point in the directory structure. 27 23 … … def serve(request, path, document_root=None, show_indexes=False, insecure=False) 35 31 but if you'd like to override it, you can create a template called 36 32 ``static/directory_index.html``. 37 33 """ 38 warnings.warn("The view at `django.views.static.serve` is deprecated; " 39 "use the path `django.contrib.staticfiles.views.serve` " 40 "instead.", PendingDeprecationWarning) 41 return staticfiles_serve(request, path, document_root, show_indexes, insecure) 34 # Clean up given path to only allow serving files below document_root. 35 path = posixpath.normpath(urllib.unquote(path)) 36 path = path.lstrip('/') 37 newpath = '' 38 for part in path.split('/'): 39 if not part: 40 # Strip empty path components. 41 continue 42 drive, part = os.path.splitdrive(part) 43 head, part = os.path.split(part) 44 if part in (os.curdir, os.pardir): 45 # Strip '.' and '..' in path. 46 continue 47 newpath = os.path.join(newpath, part).replace('\\', '/') 48 if newpath and path != newpath: 49 return HttpResponseRedirect(newpath) 50 fullpath = os.path.join(document_root, newpath) 51 if os.path.isdir(fullpath): 52 if show_indexes: 53 return directory_index(newpath, fullpath) 54 raise Http404("Directory indexes are not allowed here.") 55 if not os.path.exists(fullpath): 56 raise Http404('"%s" does not exist' % fullpath) 57 # Respect the If-Modified-Since header. 58 statobj = os.stat(fullpath) 59 mimetype, encoding = mimetypes.guess_type(fullpath) 60 mimetype = mimetype or 'application/octet-stream' 61 if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), 62 statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): 63 return HttpResponseNotModified(mimetype=mimetype) 64 contents = open(fullpath, 'rb').read() 65 response = HttpResponse(contents, mimetype=mimetype) 66 response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) 67 response["Content-Length"] = len(contents) 68 if encoding: 69 response["Content-Encoding"] = encoding 70 return response 71 72 73 DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ 74 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 75 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 76 <head> 77 <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> 78 <meta http-equiv="Content-Language" content="en-us" /> 79 <meta name="robots" content="NONE,NOARCHIVE" /> 80 <title>Index of {{ directory }}</title> 81 </head> 82 <body> 83 <h1>Index of {{ directory }}</h1> 84 <ul> 85 {% ifnotequal directory "/" %} 86 <li><a href="../">../</a></li> 87 {% endifnotequal %} 88 {% for f in file_list %} 89 <li><a href="{{ f|urlencode }}">{{ f }}</a></li> 90 {% endfor %} 91 </ul> 92 </body> 93 </html> 94 """ 95 96 def directory_index(path, fullpath): 97 try: 98 t = loader.select_template(['static/directory_index.html', 99 'static/directory_index']) 100 except TemplateDoesNotExist: 101 t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template') 102 files = [] 103 for f in os.listdir(fullpath): 104 if not f.startswith('.'): 105 if os.path.isdir(os.path.join(fullpath, f)): 106 f += '/' 107 files.append(f) 108 c = Context({ 109 'directory' : path + '/', 110 'file_list' : files, 111 }) 112 return HttpResponse(t.render(c)) 113 114 def was_modified_since(header=None, mtime=0, size=0): 115 """ 116 Was something modified since the user last downloaded it? 117 118 header 119 This is the value of the If-Modified-Since header. If this is None, 120 I'll just return True. 121 122 mtime 123 This is the modification time of the item we're talking about. 124 125 size 126 This is the size of the item we're talking about. 127 """ 128 try: 129 if header is None: 130 raise ValueError 131 matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, 132 re.IGNORECASE) 133 header_date = parsedate_tz(matches.group(1)) 134 if header_date is None: 135 raise ValueError 136 header_mtime = mktime_tz(header_date) 137 header_len = matches.group(3) 138 if header_len and int(header_len) != size: 139 raise ValueError 140 if mtime > header_mtime: 141 raise ValueError 142 except (AttributeError, ValueError, OverflowError): 143 return True 144 return False -
docs/howto/static-files.txt
diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 94e04bb..b68004d 100644
a b the framework see :doc:`the staticfiles reference </ref/contrib/staticfiles>`. 109 109 :setting:`MEDIA_URL` different from your :setting:`STATIC_ROOT` and 110 110 :setting:`STATIC_URL`. You will need to arrange for serving of files in 111 111 :setting:`MEDIA_ROOT` yourself; ``staticfiles`` does not deal with 112 user-uploaded files at all. You can, however, use ``staticfiles``' 113 :func:`~django.contrib.staticfiles.views.serve` view for serving 114 :setting:`MEDIA_ROOT` in development; see 115 :ref:`staticfiles-serve-other-directories`. 112 user-uploaded files at all. You can, however, use 113 :func:`~django.views.static.serve` view for serving :setting:`MEDIA_ROOT` 114 in development; see :ref:`staticfiles-serve-other-directories`. 116 115 117 116 .. _staticfiles-in-templates: 118 117 -
docs/ref/contrib/staticfiles.txt
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index dd26fe5..9a9ee0b 100644
a b by appending something like this to your URLconf:: 343 343 {'document_root': settings.MEDIA_ROOT}), 344 344 ) 345 345 346 Luckily Django also ships with a small URL helper function that shrinks 347 that down a bit:: 348 349 from django.conf import settings 350 from django.conf.urls.static import static 351 352 urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 353 346 354 This snippet assumes you've also set your :setting:`MEDIA_URL` (in development) 347 355 to ``/media/``. -
tests/regressiontests/staticfiles_tests/tests.py
diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index d524254..0ceb11b 100644
a b class TestServeDisabled(TestServeStatic): 293 293 settings.DEBUG = False 294 294 295 295 def test_disabled_serving(self): 296 self.assertRaisesRegexp(ImproperlyConfigured, 'The view to serve ' 297 'static files can only be used if the DEBUG setting is True', 298 self._response, 'test.txt') 296 self.assertRaisesRegexp(ImproperlyConfigured, 'The staticfiles view ' 297 'can only be used in debug mode ', self._response, 'test.txt') 299 298 300 299 301 300 class TestServeStaticWithDefaultURL(TestServeStatic, TestDefaults):