Ticket #15270: 15270.1.diff

File 15270.1.diff, 17.4 KB (added by jezdez, 5 years ago)

moves back the code, adds static url pattern helper that is used by staticfiles now, too. probably needs more doc updates in the howto section

  • 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
    - +  
     1import re
     2from django.conf import settings
     3from django.conf.urls.defaults import patterns, url
     4from django.core.exceptions import ImproperlyConfigured
     5
     6def 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 re
    21from django.conf import settings
    3 from django.conf.urls.defaults import patterns, url, include
    4 from django.core.exceptions import ImproperlyConfigured
     2from django.conf.urls.static import static
    53
    64urlpatterns = []
    75
    8 # only serve non-fqdn URLs
    9 if settings.DEBUG:
    10     urlpatterns += patterns('',
    11         url(r'^(?P<path>.*)$', 'django.contrib.staticfiles.views.serve'),
    12     )
    13 
    146def staticfiles_urlpatterns(prefix=None):
    157    """
    168    Helper function to return a URL pattern for serving static files.
    179    """
    18     if not settings.DEBUG:
    19         return []
    2010    if prefix is None:
    2111        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
     15if 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 
    33development, and SHOULD NOT be used in a production setting.
    44
    55"""
    6 import mimetypes
    76import os
    8 import posixpath
    9 import re
    10 import stat
    11 import urllib
    12 from email.Utils import parsedate_tz, mktime_tz
    137
    148from django.conf import settings
    159from 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
     10from django.http import Http404
     11from django.views.static import serve as static_serve
    1912
    20 from django.contrib.staticfiles import finders, utils
     13from django.contrib.staticfiles import finders
    2114
    22 
    23 def serve(request, path, document_root=None, show_indexes=False, insecure=False):
     15def serve(request, path, document_root=None, insecure=False, **kwargs):
    2416    """
    2517    Serve static files below a given point in the directory structure or
    2618    from locations inferred from the static files finders.
    def serve(request, path, document_root=None, show_indexes=False, insecure=False) 
    4234    a template called ``static/directory_index.html``.
    4335    """
    4436    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")
    4940    if not document_root:
    5041        path = os.path.normpath(path)
    5142        absolute_path = finders.find(path)
    5243        if not absolute_path:
    5344            raise Http404('"%s" could not be found' % path)
    5445        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 
    1818from django.core.management.color import color_style
    1919from django.utils.http import http_date
    2020from django.utils._os import safe_join
     21from django.views.static import serve
    2122
    22 from django.contrib.staticfiles import handlers, views as static
     23from django.contrib.staticfiles import handlers
    2324
    2425__version__ = "0.1"
    2526__all__ = ['WSGIServer','WSGIRequestHandler']
    class AdminMediaHandler(handlers.StaticFilesHandler): 
    677678
    678679    def serve(self, request):
    679680        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)
    682682
    683683    def _should_handle(self, path):
    684684        """
  • django/views/static.py

    diff --git a/django/views/static.py b/django/views/static.py
    index 2ce886f..9b34312 100644
    a b import posixpath 
    99import re
    1010import stat
    1111import urllib
    12 import warnings
    1312from email.Utils import parsedate_tz, mktime_tz
    1413
    1514from django.template import loader
    from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpRespons 
    1716from django.template import Template, Context, TemplateDoesNotExist
    1817from django.utils.http import http_date
    1918
    20 from django.contrib.staticfiles.views import (directory_index,
    21     was_modified_since, serve as staticfiles_serve)
    2219
    23 
    24 def serve(request, path, document_root=None, show_indexes=False, insecure=False):
     20def serve(request, path, document_root=None, show_indexes=False):
    2521    """
    2622    Serve static files below a given point in the directory structure.
    2723
    def serve(request, path, document_root=None, show_indexes=False, insecure=False) 
    3531    but if you'd like to override it, you can create a template called
    3632    ``static/directory_index.html``.
    3733    """
    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
     73DEFAULT_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
     96def 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
     114def 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>`. 
    109109   :setting:`MEDIA_URL` different from your :setting:`STATIC_ROOT` and
    110110   :setting:`STATIC_URL`. You will need to arrange for serving of files in
    111111   :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`.
    116115
    117116.. _staticfiles-in-templates:
    118117
  • 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:: 
    343343               {'document_root': settings.MEDIA_ROOT}),
    344344       )
    345345
     346Luckily Django also ships with a small URL helper function that shrinks
     347that 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
    346354This snippet assumes you've also set your :setting:`MEDIA_URL` (in development)
    347355to ``/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): 
    293293        settings.DEBUG = False
    294294
    295295    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')
    299298
    300299
    301300class TestServeStaticWithDefaultURL(TestServeStatic, TestDefaults):
Back to Top