base.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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(
  44. 'The method name %s is not accepted as a keyword argument '
  45. 'to %s().' % (key, cls.__name__)
  46. )
  47. if not hasattr(cls, key):
  48. raise TypeError("%s() received an invalid keyword %r. as_view "
  49. "only accepts arguments that are already "
  50. "attributes of the class." % (cls.__name__, key))
  51. def view(request, *args, **kwargs):
  52. self = cls(**initkwargs)
  53. self.setup(request, *args, **kwargs)
  54. if not hasattr(self, 'request'):
  55. raise AttributeError(
  56. "%s instance has no 'request' attribute. Did you override "
  57. "setup() and forget to call super()?" % cls.__name__
  58. )
  59. return self.dispatch(request, *args, **kwargs)
  60. view.view_class = cls
  61. view.view_initkwargs = initkwargs
  62. # take name and docstring from class
  63. update_wrapper(view, cls, updated=())
  64. # and possible attributes set by decorators
  65. # like csrf_exempt from dispatch
  66. update_wrapper(view, cls.dispatch, assigned=())
  67. return view
  68. def setup(self, request, *args, **kwargs):
  69. """Initialize attributes shared by all view methods."""
  70. if hasattr(self, 'get') and not hasattr(self, 'head'):
  71. self.head = self.get
  72. self.request = request
  73. self.args = args
  74. self.kwargs = kwargs
  75. def dispatch(self, request, *args, **kwargs):
  76. # Try to dispatch to the right method; if a method doesn't exist,
  77. # defer to the error handler. Also defer to the error handler if the
  78. # request method isn't on the approved list.
  79. if request.method.lower() in self.http_method_names:
  80. handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
  81. else:
  82. handler = self.http_method_not_allowed
  83. return handler(request, *args, **kwargs)
  84. def http_method_not_allowed(self, request, *args, **kwargs):
  85. logger.warning(
  86. 'Method Not Allowed (%s): %s', request.method, request.path,
  87. extra={'status_code': 405, 'request': request}
  88. )
  89. return HttpResponseNotAllowed(self._allowed_methods())
  90. def options(self, request, *args, **kwargs):
  91. """Handle responding to requests for the OPTIONS HTTP verb."""
  92. response = HttpResponse()
  93. response.headers['Allow'] = ', '.join(self._allowed_methods())
  94. response.headers['Content-Length'] = '0'
  95. return response
  96. def _allowed_methods(self):
  97. return [m.upper() for m in self.http_method_names if hasattr(self, m)]
  98. class TemplateResponseMixin:
  99. """A mixin that can be used to render a template."""
  100. template_name = None
  101. template_engine = None
  102. response_class = TemplateResponse
  103. content_type = None
  104. def render_to_response(self, context, **response_kwargs):
  105. """
  106. Return a response, using the `response_class` for this view, with a
  107. template rendered with the given context.
  108. Pass response_kwargs to the constructor of the response class.
  109. """
  110. response_kwargs.setdefault('content_type', self.content_type)
  111. return self.response_class(
  112. request=self.request,
  113. template=self.get_template_names(),
  114. context=context,
  115. using=self.template_engine,
  116. **response_kwargs
  117. )
  118. def get_template_names(self):
  119. """
  120. Return a list of template names to be used for the request. Must return
  121. a list. May not be called if render_to_response() is overridden.
  122. """
  123. if self.template_name is None:
  124. raise ImproperlyConfigured(
  125. "TemplateResponseMixin requires either a definition of "
  126. "'template_name' or an implementation of 'get_template_names()'")
  127. else:
  128. return [self.template_name]
  129. class TemplateView(TemplateResponseMixin, ContextMixin, View):
  130. """
  131. Render a template. Pass keyword arguments from the URLconf to the context.
  132. """
  133. def get(self, request, *args, **kwargs):
  134. context = self.get_context_data(**kwargs)
  135. return self.render_to_response(context)
  136. class RedirectView(View):
  137. """Provide a redirect on any GET request."""
  138. permanent = False
  139. url = None
  140. pattern_name = None
  141. query_string = False
  142. def get_redirect_url(self, *args, **kwargs):
  143. """
  144. Return the URL redirect to. Keyword arguments from the URL pattern
  145. match generating the redirect request are provided as kwargs to this
  146. method.
  147. """
  148. if self.url:
  149. url = self.url % kwargs
  150. elif self.pattern_name:
  151. url = reverse(self.pattern_name, args=args, kwargs=kwargs)
  152. else:
  153. return None
  154. args = self.request.META.get('QUERY_STRING', '')
  155. if args and self.query_string:
  156. url = "%s?%s" % (url, args)
  157. return url
  158. def get(self, request, *args, **kwargs):
  159. url = self.get_redirect_url(*args, **kwargs)
  160. if url:
  161. if self.permanent:
  162. return HttpResponsePermanentRedirect(url)
  163. else:
  164. return HttpResponseRedirect(url)
  165. else:
  166. logger.warning(
  167. 'Gone: %s', request.path,
  168. extra={'status_code': 410, 'request': request}
  169. )
  170. return HttpResponseGone()
  171. def head(self, request, *args, **kwargs):
  172. return self.get(request, *args, **kwargs)
  173. def post(self, request, *args, **kwargs):
  174. return self.get(request, *args, **kwargs)
  175. def options(self, request, *args, **kwargs):
  176. return self.get(request, *args, **kwargs)
  177. def delete(self, request, *args, **kwargs):
  178. return self.get(request, *args, **kwargs)
  179. def put(self, request, *args, **kwargs):
  180. return self.get(request, *args, **kwargs)
  181. def patch(self, request, *args, **kwargs):
  182. return self.get(request, *args, **kwargs)