8
8
from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
10
from calendar import timegm
11
from datetime import timedelta
12
from email.Utils import formatdate
10
14
from django.utils.decorators import decorator_from_middleware
15
from django.utils.http import parse_etags, quote_etag
11
16
from django.middleware.http import ConditionalGetMiddleware
12
from django.http import HttpResponseNotAllowed
17
from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse
14
20
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
36
42
require_GET.__doc__ = "Decorator to require that a view only accept the GET method."
38
44
require_POST = require_http_methods(["POST"])
39
require_POST.__doc__ = "Decorator to require that a view only accept the POST method."
b'\\ No newline at end of file'
45
require_POST.__doc__ = "Decorator to require that a view only accept the POST method."
47
def condition(etag_func=None, last_modified_func=None):
49
Decorator to support conditional retrieval (or change) for a view
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).
58
If both parameters are provided, all the preconditions must be met before
59
the view is processed.
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.
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
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.
79
etags = parse_etags(if_none_match or if_match)
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.
88
# Compute values (if any) for the requested resource.
90
res_etag = etag_func(request, *args, **kwargs)
93
if last_modified_func:
94
dt = last_modified_func(request, *args, **kwargs)
96
res_last_modified = formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT'
98
res_last_modified = None
100
res_last_modified = 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
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()
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()
124
response = func(request, *args, **kwargs)
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)
137
# Shortcut decorators for common cases based on ETag or Last-Modified only
139
return condition(etag_func=etag_func)
141
def last_modified(last_modified_func):
142
return condition(last_modified_func=last_modified_func)