web/lib/django/views/static.py
author ymh <ymh.work@gmail.com>
Wed, 02 Jun 2010 18:57:35 +0200
changeset 38 77b6da96e6f1
permissions -rw-r--r--
update django
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
38
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
"""
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
Views and functions for serving static files. These are only to be used
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
during development, and SHOULD NOT be used in a production setting.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
"""
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
import mimetypes
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
import os
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
import posixpath
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
import re
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
import stat
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
import urllib
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
from email.Utils import parsedate_tz, mktime_tz
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
from django.template import loader
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
from django.template import Template, Context, TemplateDoesNotExist
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
from django.utils.http import http_date
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
def serve(request, path, document_root=None, show_indexes=False):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
    Serve static files below a given point in the directory structure.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
    To use, put a URL pattern such as::
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
        (r'^(?P<path>.*)$', 'django.views.static.serve', {'document_root' : '/path/to/my/files/'})
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
    in your URLconf. You must provide the ``document_root`` param. You may
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
    also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
    of the directory.  This index view will use the template hardcoded below,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
    but if you'd like to override it, you can create a template called
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
    ``static/directory_index.html``.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
    # Clean up given path to only allow serving files below document_root.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
    path = posixpath.normpath(urllib.unquote(path))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
    path = path.lstrip('/')
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
    newpath = ''
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
    for part in path.split('/'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
        if not part:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
            # Strip empty path components.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
            continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
        drive, part = os.path.splitdrive(part)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
        head, part = os.path.split(part)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
        if part in (os.curdir, os.pardir):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
            # Strip '.' and '..' in path.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
            continue
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
        newpath = os.path.join(newpath, part).replace('\\', '/')
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
    if newpath and path != newpath:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
        return HttpResponseRedirect(newpath)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
    fullpath = os.path.join(document_root, newpath)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
    if os.path.isdir(fullpath):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
        if show_indexes:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
            return directory_index(newpath, fullpath)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
        raise Http404("Directory indexes are not allowed here.")
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
    if not os.path.exists(fullpath):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
        raise Http404('"%s" does not exist' % fullpath)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
    # Respect the If-Modified-Since header.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
    statobj = os.stat(fullpath)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
    mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
    if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
                              statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
        return HttpResponseNotModified(mimetype=mimetype)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
    contents = open(fullpath, 'rb').read()
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
    response = HttpResponse(contents, mimetype=mimetype)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
    response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
    response["Content-Length"] = len(contents)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
    return response
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
  <head>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
    <meta http-equiv="Content-Language" content="en-us" />
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
    <meta name="robots" content="NONE,NOARCHIVE" />
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
    <title>Index of {{ directory }}</title>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
  </head>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
  <body>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
    <h1>Index of {{ directory }}</h1>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
    <ul>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
      {% ifnotequal directory "/" %}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
      <li><a href="../">../</a></li>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
      {% endifnotequal %}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
      {% for f in file_list %}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
      <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
      {% endfor %}
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
    </ul>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
  </body>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
</html>
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    90
"""
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    91
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    92
def directory_index(path, fullpath):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    93
    try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    94
        t = loader.select_template(['static/directory_index.html',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    95
                'static/directory_index'])
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    96
    except TemplateDoesNotExist:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    97
        t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    98
    files = []
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
    99
    for f in os.listdir(fullpath):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   100
        if not f.startswith('.'):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   101
            if os.path.isdir(os.path.join(fullpath, f)):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   102
                f += '/'
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   103
            files.append(f)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   104
    c = Context({
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   105
        'directory' : path + '/',
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   106
        'file_list' : files,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   107
    })
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   108
    return HttpResponse(t.render(c))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   109
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   110
def was_modified_since(header=None, mtime=0, size=0):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   111
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   112
    Was something modified since the user last downloaded it?
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   113
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   114
    header
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   115
      This is the value of the If-Modified-Since header.  If this is None,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   116
      I'll just return True.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   117
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   118
    mtime
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   119
      This is the modification time of the item we're talking about.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   120
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   121
    size
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   122
      This is the size of the item we're talking about.
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   123
    """
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   124
    try:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   125
        if header is None:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   126
            raise ValueError
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   127
        matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   128
                           re.IGNORECASE)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   129
        header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   130
        header_len = matches.group(3)
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   131
        if header_len and int(header_len) != size:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   132
            raise ValueError
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   133
        if mtime > header_mtime:
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   134
            raise ValueError
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   135
    except (AttributeError, ValueError):
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   136
        return True
77b6da96e6f1 update django
ymh <ymh.work@gmail.com>
parents:
diff changeset
   137
    return False