/bangkokhotel/lib/python2.5/site-packages/django/views/decorators/http.py

https://bitbucket.org/luisrodriguez/bangkokhotel · Python · 166 lines · 128 code · 19 blank · 19 comment · 30 complexity · 7c4bfb12d3a36499957eaed921c2ff51 MD5 · raw file

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