web/lib/django/views/decorators/http.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 Decorators for views based on HTTP headers.
       
     3 """
       
     4 
       
     5 try:
       
     6     from functools import wraps
       
     7 except ImportError:
       
     8     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
       
     9 
       
    10 from calendar import timegm
       
    11 from datetime import timedelta
       
    12 from email.Utils import formatdate
       
    13 
       
    14 from django.utils.decorators import decorator_from_middleware
       
    15 from django.utils.http import parse_etags, quote_etag
       
    16 from django.middleware.http import ConditionalGetMiddleware
       
    17 from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse
       
    18 
       
    19 
       
    20 conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
       
    21 
       
    22 def require_http_methods(request_method_list):
       
    23     """
       
    24     Decorator to make a view only accept particular request methods.  Usage::
       
    25 
       
    26         @require_http_methods(["GET", "POST"])
       
    27         def my_view(request):
       
    28             # I can assume now that only GET or POST requests make it this far
       
    29             # ...
       
    30 
       
    31     Note that request methods should be in uppercase.
       
    32     """
       
    33     def decorator(func):
       
    34         def inner(request, *args, **kwargs):
       
    35             if request.method not in request_method_list:
       
    36                 return HttpResponseNotAllowed(request_method_list)
       
    37             return func(request, *args, **kwargs)
       
    38         return wraps(func)(inner)
       
    39     return decorator
       
    40 
       
    41 require_GET = require_http_methods(["GET"])
       
    42 require_GET.__doc__ = "Decorator to require that a view only accept the GET method."
       
    43 
       
    44 require_POST = require_http_methods(["POST"])
       
    45 require_POST.__doc__ = "Decorator to require that a view only accept the POST method."
       
    46 
       
    47 def condition(etag_func=None, last_modified_func=None):
       
    48     """
       
    49     Decorator to support conditional retrieval (or change) for a view
       
    50     function.
       
    51 
       
    52     The parameters are callables to compute the ETag and last modified time for
       
    53     the requested resource, respectively. The callables are passed the same
       
    54     parameters as the view itself. The Etag function should return a string (or
       
    55     None if the resource doesn't exist), whilst the last_modified function
       
    56     should return a datetime object (or None if the resource doesn't exist).
       
    57 
       
    58     If both parameters are provided, all the preconditions must be met before
       
    59     the view is processed.
       
    60 
       
    61     This decorator will either pass control to the wrapped view function or
       
    62     return an HTTP 304 response (unmodified) or 412 response (preconditions
       
    63     failed), depending upon the request method.
       
    64 
       
    65     Any behavior marked as "undefined" in the HTTP spec (e.g. If-none-match
       
    66     plus If-modified-since headers) will result in the view function being
       
    67     called.
       
    68     """
       
    69     def decorator(func):
       
    70         def inner(request, *args, **kwargs):
       
    71             # Get HTTP request headers
       
    72             if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE")
       
    73             if_none_match = request.META.get("HTTP_IF_NONE_MATCH")
       
    74             if_match = request.META.get("HTTP_IF_MATCH")
       
    75             if if_none_match or if_match:
       
    76                 # There can be more than one ETag in the request, so we
       
    77                 # consider the list of values.
       
    78                 try:
       
    79                     etags = parse_etags(if_none_match or if_match)
       
    80                 except ValueError:
       
    81                     # In case of invalid etag ignore all ETag headers.
       
    82                     # Apparently Opera sends invalidly quoted headers at times
       
    83                     # (we should be returning a 400 response, but that's a
       
    84                     # little extreme) -- this is Django bug #10681.
       
    85                     if_none_match = None
       
    86                     if_match = None
       
    87 
       
    88             # Compute values (if any) for the requested resource.
       
    89             if etag_func:
       
    90                 res_etag = etag_func(request, *args, **kwargs)
       
    91             else:
       
    92                 res_etag = None
       
    93             if last_modified_func:
       
    94                 dt = last_modified_func(request, *args, **kwargs)
       
    95                 if dt:
       
    96                     res_last_modified = formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT'
       
    97                 else:
       
    98                     res_last_modified = None
       
    99             else:
       
   100                 res_last_modified = None
       
   101 
       
   102             response = None
       
   103             if not ((if_match and (if_modified_since or if_none_match)) or
       
   104                     (if_match and if_none_match)):
       
   105                 # We only get here if no undefined combinations of headers are
       
   106                 # specified.
       
   107                 if ((if_none_match and (res_etag in etags or
       
   108                         "*" in etags and res_etag)) and
       
   109                         (not if_modified_since or
       
   110                             res_last_modified == if_modified_since)):
       
   111                     if request.method in ("GET", "HEAD"):
       
   112                         response = HttpResponseNotModified()
       
   113                     else:
       
   114                         response = HttpResponse(status=412)
       
   115                 elif if_match and ((not res_etag and "*" in etags) or
       
   116                         (res_etag and res_etag not in etags)):
       
   117                     response = HttpResponse(status=412)
       
   118                 elif (not if_none_match and if_modified_since and
       
   119                         request.method == "GET" and
       
   120                         res_last_modified == if_modified_since):
       
   121                     response = HttpResponseNotModified()
       
   122 
       
   123             if response is None:
       
   124                 response = func(request, *args, **kwargs)
       
   125 
       
   126             # Set relevant headers on the response if they don't already exist.
       
   127             if res_last_modified and not response.has_header('Last-Modified'):
       
   128                 response['Last-Modified'] = res_last_modified
       
   129             if res_etag and not response.has_header('ETag'):
       
   130                 response['ETag'] = quote_etag(res_etag)
       
   131 
       
   132             return response
       
   133 
       
   134         return inner
       
   135     return decorator
       
   136 
       
   137 # Shortcut decorators for common cases based on ETag or Last-Modified only
       
   138 def etag(etag_func):
       
   139     return condition(etag_func=etag_func)
       
   140 
       
   141 def last_modified(last_modified_func):
       
   142     return condition(last_modified_func=last_modified_func)
       
   143