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