web/lib/django/views/decorators/http.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/views/decorators/http.py	Wed Jan 20 00:34:04 2010 +0100
@@ -0,0 +1,143 @@
+"""
+Decorators for views based on HTTP headers.
+"""
+
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
+from calendar import timegm
+from datetime import timedelta
+from email.Utils import formatdate
+
+from django.utils.decorators import decorator_from_middleware
+from django.utils.http import parse_etags, quote_etag
+from django.middleware.http import ConditionalGetMiddleware
+from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse
+
+
+conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
+
+def require_http_methods(request_method_list):
+    """
+    Decorator to make a view only accept particular request methods.  Usage::
+
+        @require_http_methods(["GET", "POST"])
+        def my_view(request):
+            # I can assume now that only GET or POST requests make it this far
+            # ...
+
+    Note that request methods should be in uppercase.
+    """
+    def decorator(func):
+        def inner(request, *args, **kwargs):
+            if request.method not in request_method_list:
+                return HttpResponseNotAllowed(request_method_list)
+            return func(request, *args, **kwargs)
+        return wraps(func)(inner)
+    return decorator
+
+require_GET = require_http_methods(["GET"])
+require_GET.__doc__ = "Decorator to require that a view only accept the GET method."
+
+require_POST = require_http_methods(["POST"])
+require_POST.__doc__ = "Decorator to require that a view only accept the POST method."
+
+def condition(etag_func=None, last_modified_func=None):
+    """
+    Decorator to support conditional retrieval (or change) for a view
+    function.
+
+    The parameters are callables to compute the ETag and last modified time for
+    the requested resource, respectively. The callables are passed the same
+    parameters as the view itself. The Etag function should return a string (or
+    None if the resource doesn't exist), whilst the last_modified function
+    should return a datetime object (or None if the resource doesn't exist).
+
+    If both parameters are provided, all the preconditions must be met before
+    the view is processed.
+
+    This decorator will either pass control to the wrapped view function or
+    return an HTTP 304 response (unmodified) or 412 response (preconditions
+    failed), depending upon the request method.
+
+    Any behavior marked as "undefined" in the HTTP spec (e.g. If-none-match
+    plus If-modified-since headers) will result in the view function being
+    called.
+    """
+    def decorator(func):
+        def inner(request, *args, **kwargs):
+            # Get HTTP request headers
+            if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE")
+            if_none_match = request.META.get("HTTP_IF_NONE_MATCH")
+            if_match = request.META.get("HTTP_IF_MATCH")
+            if if_none_match or if_match:
+                # There can be more than one ETag in the request, so we
+                # consider the list of values.
+                try:
+                    etags = parse_etags(if_none_match or if_match)
+                except ValueError:
+                    # In case of invalid etag ignore all ETag headers.
+                    # Apparently Opera sends invalidly quoted headers at times
+                    # (we should be returning a 400 response, but that's a
+                    # little extreme) -- this is Django bug #10681.
+                    if_none_match = None
+                    if_match = None
+
+            # Compute values (if any) for the requested resource.
+            if etag_func:
+                res_etag = etag_func(request, *args, **kwargs)
+            else:
+                res_etag = None
+            if last_modified_func:
+                dt = last_modified_func(request, *args, **kwargs)
+                if dt:
+                    res_last_modified = formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT'
+                else:
+                    res_last_modified = None
+            else:
+                res_last_modified = None
+
+            response = None
+            if not ((if_match and (if_modified_since or if_none_match)) or
+                    (if_match and if_none_match)):
+                # We only get here if no undefined combinations of headers are
+                # specified.
+                if ((if_none_match and (res_etag in etags or
+                        "*" in etags and res_etag)) and
+                        (not if_modified_since or
+                            res_last_modified == if_modified_since)):
+                    if request.method in ("GET", "HEAD"):
+                        response = HttpResponseNotModified()
+                    else:
+                        response = HttpResponse(status=412)
+                elif if_match and ((not res_etag and "*" in etags) or
+                        (res_etag and res_etag not in etags)):
+                    response = HttpResponse(status=412)
+                elif (not if_none_match and if_modified_since and
+                        request.method == "GET" and
+                        res_last_modified == if_modified_since):
+                    response = HttpResponseNotModified()
+
+            if response is None:
+                response = func(request, *args, **kwargs)
+
+            # Set relevant headers on the response if they don't already exist.
+            if res_last_modified and not response.has_header('Last-Modified'):
+                response['Last-Modified'] = res_last_modified
+            if res_etag and not response.has_header('ETag'):
+                response['ETag'] = quote_etag(res_etag)
+
+            return response
+
+        return inner
+    return decorator
+
+# Shortcut decorators for common cases based on ETag or Last-Modified only
+def etag(etag_func):
+    return condition(etag_func=etag_func)
+
+def last_modified(last_modified_func):
+    return condition(last_modified_func=last_modified_func)
+