You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import logging
  2. from functools import update_wrapper
  3. from django.core.exceptions import ImproperlyConfigured
  4. from django.http import (
  5. HttpResponse, HttpResponseGone, HttpResponseNotAllowed,
  6. HttpResponsePermanentRedirect, HttpResponseRedirect,
  7. )
  8. from django.template.response import TemplateResponse
  9. from django.urls import reverse
  10. from django.utils.decorators import classonlymethod
  11. logger = logging.getLogger('django.request')
  12. class ContextMixin:
  13. """
  14. A default context mixin that passes the keyword arguments received by
  15. get_context_data() as the template context.
  16. """
  17. extra_context = None
  18. def get_context_data(self, **kwargs):
  19. kwargs.setdefault('view', self)
  20. if self.extra_context is not None:
  21. kwargs.update(self.extra_context)
  22. return kwargs
  23. class View:
  24. """
  25. Intentionally simple parent class for all views. Only implements
  26. dispatch-by-method and simple sanity checking.
  27. """
  28. http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
  29. def __init__(self, **kwargs):
  30. """
  31. Constructor. Called in the URLconf; can contain helpful extra
  32. keyword arguments, and other things.
  33. """
  34. # Go through keyword arguments, and either save their values to our
  35. # instance, or raise an error.
  36. for key, value in kwargs.items():
  37. setattr(self, key, value)
  38. @classonlymethod
  39. def as_view(cls, **initkwargs):
  40. """Main entry point for a request-response process."""
  41. for key in initkwargs:
  42. if key in cls.http_method_names:
  43. raise TypeError("You tried to pass in the %s method name as a "
  44. "keyword argument to %s(). Don't do that."
  45. % (key, cls.__name__))
  46. if not hasattr(cls, key):
  47. raise TypeError("%s() received an invalid keyword %r. as_view "
  48. "only accepts arguments that are already "
  49. "attributes of the class." % (cls.__name__, key))
  50. def view(request, *args, **kwargs):
  51. self = cls(**initkwargs)
  52. if hasattr(self, 'get') and not hasattr(self, 'head'):
  53. self.head = self.get
  54. self.setup(request, *args, **kwargs)
  55. if not hasattr(self, 'request'):
  56. raise AttributeError(
  57. "%s instance has no 'request' attribute. Did you override "
  58. "setup() and forget to call super()?" % cls.__name__
  59. )
  60. return self.dispatch(request, *args, **kwargs)
  61. view.view_class = cls
  62. view.view_initkwargs = initkwargs
  63. # take name and docstring from class
  64. update_wrapper(view, cls, updated=())
  65. # and possible attributes set by decorators
  66. # like csrf_exempt from dispatch
  67. update_wrapper(view, cls.dispatch, assigned=())
  68. return view
  69. def setup(self, request, *args, **kwargs):
  70. """Initialize attributes shared by all view methods."""
  71. self.request = request
  72. self.args = args
  73. self.kwargs = kwargs
  74. def dispatch(self, request, *args, **kwargs):
  75. # Try to dispatch to the right method; if a method doesn't exist,
  76. # defer to the error handler. Also defer to the error handler if the
  77. # request method isn't on the approved list.
  78. if request.method.lower() in self.http_method_names:
  79. handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
  80. else:
  81. handler = self.http_method_not_allowed
  82. return handler(request, *args, **kwargs)
  83. def http_method_not_allowed(self, request, *args, **kwargs):
  84. logger.warning(
  85. 'Method Not Allowed (%s): %s', request.method, request.path,
  86. extra={'status_code': 405, 'request': request}
  87. )
  88. return HttpResponseNotAllowed(self._allowed_methods())
  89. def options(self, request, *args, **kwargs):
  90. """Handle responding to requests for the OPTIONS HTTP verb."""
  91. response = HttpResponse()
  92. response['Allow'] = ', '.join(self._allowed_methods())
  93. response['Content-Length'] = '0'
  94. return response
  95. def _allowed_methods(self):
  96. return [m.upper() for m in self.http_method_names if hasattr(self, m)]
  97. class TemplateResponseMixin:
  98. """A mixin that can be used to render a template."""
  99. template_name = None
  100. template_engine = None
  101. response_class = TemplateResponse
  102. content_type = None
  103. def render_to_response(self, context, **response_kwargs):
  104. """
  105. Return a response, using the `response_class` for this view, with a
  106. template rendered with the given context.
  107. Pass response_kwargs to the constructor of the response class.
  108. """
  109. response_kwargs.setdefault('content_type', self.content_type)
  110. return self.response_class(
  111. request=self.request,
  112. template=self.get_template_names(),
  113. context=context,
  114. using=self.template_engine,
  115. **response_kwargs
  116. )
  117. def get_template_names(self):
  118. """
  119. Return a list of template names to be used for the request. Must return
  120. a list. May not be called if render_to_response() is overridden.
  121. """
  122. if self.template_name is None:
  123. raise ImproperlyConfigured(
  124. "TemplateResponseMixin requires either a definition of "
  125. "'template_name' or an implementation of 'get_template_names()'")
  126. else:
  127. return [self.template_name]
  128. class TemplateView(TemplateResponseMixin, ContextMixin, View):
  129. """
  130. Render a template. Pass keyword arguments from the URLconf to the context.
  131. """
  132. def get(self, request, *args, **kwargs):
  133. context = self.get_context_data(**kwargs)
  134. return self.render_to_response(context)
  135. class RedirectView(View):
  136. """Provide a redirect on any GET request."""
  137. permanent = False
  138. url = None
  139. pattern_name = None
  140. query_string = False
  141. def get_redirect_url(self, *args, **kwargs):
  142. """
  143. Return the URL redirect to. Keyword arguments from the URL pattern
  144. match generating the redirect request are provided as kwargs to this
  145. method.
  146. """
  147. if self.url:
  148. url = self.url % kwargs
  149. elif self.pattern_name:
  150. url = reverse(self.pattern_name, args=args, kwargs=kwargs)
  151. else:
  152. return None
  153. args = self.request.META.get('QUERY_STRING', '')
  154. if args and self.query_string:
  155. url = "%s?%s" % (url, args)
  156. return url
  157. def get(self, request, *args, **kwargs):
  158. url = self.get_redirect_url(*args, **kwargs)
  159. if url:
  160. if self.permanent:
  161. return HttpResponsePermanentRedirect(url)
  162. else:
  163. return HttpResponseRedirect(url)
  164. else:
  165. logger.warning(
  166. 'Gone: %s', request.path,
  167. extra={'status_code': 410, 'request': request}
  168. )
  169. return HttpResponseGone()
  170. def head(self, request, *args, **kwargs):
  171. return self.get(request, *args, **kwargs)
  172. def post(self, request, *args, **kwargs):
  173. return self.get(request, *args, **kwargs)
  174. def options(self, request, *args, **kwargs):
  175. return self.get(request, *args, **kwargs)
  176. def delete(self, request, *args, **kwargs):
  177. return self.get(request, *args, **kwargs)
  178. def put(self, request, *args, **kwargs):
  179. return self.get(request, *args, **kwargs)
  180. def patch(self, request, *args, **kwargs):
  181. return self.get(request, *args, **kwargs)