2005-10-09 08:55:08 +08:00
|
|
|
"""
|
2005-10-28 09:30:30 +08:00
|
|
|
Decorators for views based on HTTP headers.
|
2005-10-09 08:55:08 +08:00
|
|
|
"""
|
|
|
|
|
2012-09-21 03:03:24 +08:00
|
|
|
import logging
|
2009-03-22 15:58:29 +08:00
|
|
|
from calendar import timegm
|
2011-03-28 10:11:19 +08:00
|
|
|
from functools import wraps
|
2009-03-22 15:58:29 +08:00
|
|
|
|
2015-06-05 21:26:48 +08:00
|
|
|
from django.http import HttpResponseNotAllowed
|
2005-10-09 08:55:08 +08:00
|
|
|
from django.middleware.http import ConditionalGetMiddleware
|
2015-06-05 21:26:48 +08:00
|
|
|
from django.utils.cache import get_conditional_response
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.utils.decorators import available_attrs, decorator_from_middleware
|
2015-06-05 21:26:48 +08:00
|
|
|
from django.utils.http import http_date, quote_etag
|
2009-03-22 15:58:29 +08:00
|
|
|
|
2005-10-09 08:55:08 +08:00
|
|
|
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
|
2005-10-28 09:30:30 +08:00
|
|
|
|
2012-09-21 03:03:24 +08:00
|
|
|
logger = logging.getLogger('django.request')
|
2010-10-04 23:12:39 +08:00
|
|
|
|
|
|
|
|
2005-10-28 09:30:30 +08:00
|
|
|
def require_http_methods(request_method_list):
|
|
|
|
"""
|
|
|
|
Decorator to make a view only accept particular request methods. Usage::
|
2006-06-20 11:17:57 +08:00
|
|
|
|
2005-10-28 09:30:30 +08:00
|
|
|
@require_http_methods(["GET", "POST"])
|
|
|
|
def my_view(request):
|
|
|
|
# I can assume now that only GET or POST requests make it this far
|
2006-06-20 11:17:57 +08:00
|
|
|
# ...
|
|
|
|
|
2006-06-20 12:34:13 +08:00
|
|
|
Note that request methods should be in uppercase.
|
2005-10-28 09:30:30 +08:00
|
|
|
"""
|
|
|
|
def decorator(func):
|
2011-05-02 00:46:02 +08:00
|
|
|
@wraps(func, assigned=available_attrs(func))
|
2005-10-28 09:30:30 +08:00
|
|
|
def inner(request, *args, **kwargs):
|
2006-06-20 12:34:13 +08:00
|
|
|
if request.method not in request_method_list:
|
2016-03-29 06:33:29 +08:00
|
|
|
logger.warning(
|
|
|
|
'Method Not Allowed (%s): %s', request.method, request.path,
|
|
|
|
extra={'status_code': 405, 'request': request}
|
2010-10-04 23:12:39 +08:00
|
|
|
)
|
2006-06-20 11:17:57 +08:00
|
|
|
return HttpResponseNotAllowed(request_method_list)
|
2005-10-28 09:30:30 +08:00
|
|
|
return func(request, *args, **kwargs)
|
2011-05-02 00:46:02 +08:00
|
|
|
return inner
|
2005-10-28 09:30:30 +08:00
|
|
|
return decorator
|
|
|
|
|
|
|
|
require_GET = require_http_methods(["GET"])
|
2015-05-24 21:30:51 +08:00
|
|
|
require_GET.__doc__ = "Decorator to require that a view only accepts the GET method."
|
2005-10-28 09:30:30 +08:00
|
|
|
|
|
|
|
require_POST = require_http_methods(["POST"])
|
2015-05-24 21:30:51 +08:00
|
|
|
require_POST.__doc__ = "Decorator to require that a view only accepts the POST method."
|
2009-03-22 15:58:29 +08:00
|
|
|
|
2011-04-28 21:04:16 +08:00
|
|
|
require_safe = require_http_methods(["GET", "HEAD"])
|
2015-05-24 21:30:51 +08:00
|
|
|
require_safe.__doc__ = "Decorator to require that a view only accepts safe methods: GET and HEAD."
|
2011-04-28 21:04:16 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2009-03-22 15:58:29 +08:00
|
|
|
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
|
2016-09-09 23:00:21 +08:00
|
|
|
parameters as the view itself. The ETag function should return a string (or
|
2016-01-01 03:29:52 +08:00
|
|
|
None if the resource doesn't exist), while the last_modified function
|
2009-03-22 15:58:29 +08:00
|
|
|
should return a datetime object (or None if the resource doesn't exist).
|
|
|
|
|
2016-09-01 21:32:20 +08:00
|
|
|
The ETag function should return a complete ETag, including quotes (e.g.
|
|
|
|
'"etag"'), since that's the only way to distinguish between weak and strong
|
|
|
|
ETags. If an unquoted ETag is returned (e.g. 'etag'), it will be converted
|
|
|
|
to a strong ETag by adding quotes.
|
2009-03-22 15:58:29 +08:00
|
|
|
|
|
|
|
This decorator will either pass control to the wrapped view function or
|
2016-09-01 21:32:20 +08:00
|
|
|
return an HTTP 304 response (unmodified) or 412 response (precondition
|
|
|
|
failed), depending upon the request method. In either case, it will add the
|
|
|
|
generated ETag and Last-Modified headers to the response if it doesn't
|
|
|
|
already have them.
|
2009-03-22 15:58:29 +08:00
|
|
|
"""
|
|
|
|
def decorator(func):
|
2012-02-10 02:57:06 +08:00
|
|
|
@wraps(func, assigned=available_attrs(func))
|
2009-03-22 15:58:29 +08:00
|
|
|
def inner(request, *args, **kwargs):
|
|
|
|
# Compute values (if any) for the requested resource.
|
2014-12-21 05:14:46 +08:00
|
|
|
def get_last_modified():
|
|
|
|
if last_modified_func:
|
|
|
|
dt = last_modified_func(request, *args, **kwargs)
|
|
|
|
if dt:
|
|
|
|
return timegm(dt.utctimetuple())
|
|
|
|
|
2016-09-01 21:32:20 +08:00
|
|
|
# The value from etag_func() could be quoted or unquoted.
|
2014-12-21 05:14:46 +08:00
|
|
|
res_etag = etag_func(request, *args, **kwargs) if etag_func else None
|
2016-09-01 21:32:20 +08:00
|
|
|
res_etag = quote_etag(res_etag) if res_etag is not None else None
|
2014-12-21 05:14:46 +08:00
|
|
|
res_last_modified = get_last_modified()
|
2009-03-22 15:58:29 +08:00
|
|
|
|
2015-06-05 21:26:48 +08:00
|
|
|
response = get_conditional_response(
|
|
|
|
request,
|
|
|
|
etag=res_etag,
|
|
|
|
last_modified=res_last_modified,
|
|
|
|
)
|
2009-03-22 15:58:29 +08:00
|
|
|
|
|
|
|
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'):
|
2011-03-01 22:28:06 +08:00
|
|
|
response['Last-Modified'] = http_date(res_last_modified)
|
2009-03-22 15:58:29 +08:00
|
|
|
if res_etag and not response.has_header('ETag'):
|
2016-09-01 21:32:20 +08:00
|
|
|
response['ETag'] = res_etag
|
2009-03-22 15:58:29 +08:00
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
return inner
|
|
|
|
return decorator
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2009-03-22 15:58:29 +08:00
|
|
|
# Shortcut decorators for common cases based on ETag or Last-Modified only
|
2009-03-24 11:01:46 +08:00
|
|
|
def etag(etag_func):
|
|
|
|
return condition(etag_func=etag_func)
|
2009-03-22 15:58:29 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2009-03-24 11:01:46 +08:00
|
|
|
def last_modified(last_modified_func):
|
|
|
|
return condition(last_modified_func=last_modified_func)
|