app/django/views/static.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """
       
     2 Views and functions for serving static files. These are only to be used
       
     3 during development, and SHOULD NOT be used in a production setting.
       
     4 """
       
     5 
       
     6 import mimetypes
       
     7 import os
       
     8 import posixpath
       
     9 import re
       
    10 import stat
       
    11 import urllib
       
    12 from email.Utils import parsedate_tz, mktime_tz
       
    13 
       
    14 from django.template import loader
       
    15 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
       
    16 from django.template import Template, Context, TemplateDoesNotExist
       
    17 from django.utils.http import http_date
       
    18 
       
    19 def serve(request, path, document_root=None, show_indexes=False):
       
    20     """
       
    21     Serve static files below a given point in the directory structure.
       
    22 
       
    23     To use, put a URL pattern such as::
       
    24 
       
    25         (r'^(?P<path>.*)$', 'django.views.static.serve', {'document_root' : '/path/to/my/files/'})
       
    26 
       
    27     in your URLconf. You must provide the ``document_root`` param. You may
       
    28     also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
       
    29     of the directory.  This index view will use the template hardcoded below,
       
    30     but if you'd like to override it, you can create a template called
       
    31     ``static/directory_index``.
       
    32     """
       
    33 
       
    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     if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
       
    60                               statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
       
    61         return HttpResponseNotModified()
       
    62     mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream'
       
    63     contents = open(fullpath, 'rb').read()
       
    64     response = HttpResponse(contents, mimetype=mimetype)
       
    65     response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
       
    66     response["Content-Length"] = len(contents)
       
    67     return response
       
    68 
       
    69 DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
       
    70 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
       
    71 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
       
    72   <head>
       
    73     <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
       
    74     <meta http-equiv="Content-Language" content="en-us" />
       
    75     <meta name="robots" content="NONE,NOARCHIVE" />
       
    76     <title>Index of {{ directory|escape }}</title>
       
    77   </head>
       
    78   <body>
       
    79     <h1>Index of {{ directory|escape }}</h1>
       
    80     <ul>
       
    81       {% for f in file_list %}
       
    82       <li><a href="{{ f|urlencode }}">{{ f|escape }}</a></li>
       
    83       {% endfor %}
       
    84     </ul>
       
    85   </body>
       
    86 </html>
       
    87 """
       
    88 
       
    89 def directory_index(path, fullpath):
       
    90     try:
       
    91         t = loader.get_template('static/directory_index')
       
    92     except TemplateDoesNotExist:
       
    93         t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
       
    94     files = []
       
    95     for f in os.listdir(fullpath):
       
    96         if not f.startswith('.'):
       
    97             if os.path.isdir(os.path.join(fullpath, f)):
       
    98                 f += '/'
       
    99             files.append(f)
       
   100     c = Context({
       
   101         'directory' : path + '/',
       
   102         'file_list' : files,
       
   103     })
       
   104     return HttpResponse(t.render(c))
       
   105 
       
   106 def was_modified_since(header=None, mtime=0, size=0):
       
   107     """
       
   108     Was something modified since the user last downloaded it?
       
   109 
       
   110     header
       
   111       This is the value of the If-Modified-Since header.  If this is None,
       
   112       I'll just return True.
       
   113 
       
   114     mtime
       
   115       This is the modification time of the item we're talking about.
       
   116 
       
   117     size
       
   118       This is the size of the item we're talking about.
       
   119     """
       
   120     try:
       
   121         if header is None:
       
   122             raise ValueError
       
   123         matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
       
   124                            re.IGNORECASE)
       
   125         header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
       
   126         header_len = matches.group(3)
       
   127         if header_len and int(header_len) != size:
       
   128             raise ValueError
       
   129         if mtime > header_mtime:
       
   130             raise ValueError
       
   131     except (AttributeError, ValueError):
       
   132         return True
       
   133     return False