views.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. """
  2. Provides an APIView class that is the base of all views in REST framework.
  3. """
  4. from django.conf import settings
  5. from django.core.exceptions import PermissionDenied
  6. from django.db import connections, models
  7. from django.http import Http404
  8. from django.http.response import HttpResponseBase
  9. from django.utils.cache import cc_delim_re, patch_vary_headers
  10. from django.utils.encoding import smart_str
  11. from django.views.decorators.csrf import csrf_exempt
  12. from django.views.generic import View
  13. from rest_framework import exceptions, status
  14. from rest_framework.request import Request
  15. from rest_framework.response import Response
  16. from rest_framework.schemas import DefaultSchema
  17. from rest_framework.settings import api_settings
  18. from rest_framework.utils import formatting
  19. def get_view_name(view):
  20. """
  21. Given a view instance, return a textual name to represent the view.
  22. This name is used in the browsable API, and in OPTIONS responses.
  23. This function is the default for the `VIEW_NAME_FUNCTION` setting.
  24. """
  25. # Name may be set by some Views, such as a ViewSet.
  26. name = getattr(view, 'name', None)
  27. if name is not None:
  28. return name
  29. name = view.__class__.__name__
  30. name = formatting.remove_trailing_string(name, 'View')
  31. name = formatting.remove_trailing_string(name, 'ViewSet')
  32. name = formatting.camelcase_to_spaces(name)
  33. # Suffix may be set by some Views, such as a ViewSet.
  34. suffix = getattr(view, 'suffix', None)
  35. if suffix:
  36. name += ' ' + suffix
  37. return name
  38. def get_view_description(view, html=False):
  39. """
  40. Given a view instance, return a textual description to represent the view.
  41. This name is used in the browsable API, and in OPTIONS responses.
  42. This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.
  43. """
  44. # Description may be set by some Views, such as a ViewSet.
  45. description = getattr(view, 'description', None)
  46. if description is None:
  47. description = view.__class__.__doc__ or ''
  48. description = formatting.dedent(smart_str(description))
  49. if html:
  50. return formatting.markup_description(description)
  51. return description
  52. def set_rollback():
  53. for db in connections.all():
  54. if db.settings_dict['ATOMIC_REQUESTS'] and db.in_atomic_block:
  55. db.set_rollback(True)
  56. def exception_handler(exc, context):
  57. """
  58. Returns the response that should be used for any given exception.
  59. By default we handle the REST framework `APIException`, and also
  60. Django's built-in `Http404` and `PermissionDenied` exceptions.
  61. Any unhandled exceptions may return `None`, which will cause a 500 error
  62. to be raised.
  63. """
  64. if isinstance(exc, Http404):
  65. exc = exceptions.NotFound()
  66. elif isinstance(exc, PermissionDenied):
  67. exc = exceptions.PermissionDenied()
  68. if isinstance(exc, exceptions.APIException):
  69. headers = {}
  70. if getattr(exc, 'auth_header', None):
  71. headers['WWW-Authenticate'] = exc.auth_header
  72. if getattr(exc, 'wait', None):
  73. headers['Retry-After'] = '%d' % exc.wait
  74. if isinstance(exc.detail, (list, dict)):
  75. data = exc.detail
  76. else:
  77. data = {'detail': exc.detail}
  78. set_rollback()
  79. return Response(data, status=exc.status_code, headers=headers)
  80. return None
  81. class APIView(View):
  82. # The following policies may be set at either globally, or per-view.
  83. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
  84. parser_classes = api_settings.DEFAULT_PARSER_CLASSES
  85. authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
  86. throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
  87. permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
  88. content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
  89. metadata_class = api_settings.DEFAULT_METADATA_CLASS
  90. versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
  91. # Allow dependency injection of other settings to make testing easier.
  92. settings = api_settings
  93. schema = DefaultSchema()
  94. @classmethod
  95. def as_view(cls, **initkwargs):
  96. """
  97. Store the original class on the view function.
  98. This allows us to discover information about the view when we do URL
  99. reverse lookups. Used for breadcrumb generation.
  100. """
  101. if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
  102. def force_evaluation():
  103. raise RuntimeError(
  104. 'Do not evaluate the `.queryset` attribute directly, '
  105. 'as the result will be cached and reused between requests. '
  106. 'Use `.all()` or call `.get_queryset()` instead.'
  107. )
  108. cls.queryset._fetch_all = force_evaluation
  109. view = super().as_view(**initkwargs)
  110. view.cls = cls
  111. view.initkwargs = initkwargs
  112. # Note: session based authentication is explicitly CSRF validated,
  113. # all other authentication is CSRF exempt.
  114. return csrf_exempt(view)
  115. @property
  116. def allowed_methods(self):
  117. """
  118. Wrap Django's private `_allowed_methods` interface in a public property.
  119. """
  120. return self._allowed_methods()
  121. @property
  122. def default_response_headers(self):
  123. headers = {
  124. 'Allow': ', '.join(self.allowed_methods),
  125. }
  126. if len(self.renderer_classes) > 1:
  127. headers['Vary'] = 'Accept'
  128. return headers
  129. def http_method_not_allowed(self, request, *args, **kwargs):
  130. """
  131. If `request.method` does not correspond to a handler method,
  132. determine what kind of exception to raise.
  133. """
  134. raise exceptions.MethodNotAllowed(request.method)
  135. def permission_denied(self, request, message=None, code=None):
  136. """
  137. If request is not permitted, determine what kind of exception to raise.
  138. """
  139. if request.authenticators and not request.successful_authenticator:
  140. raise exceptions.NotAuthenticated()
  141. raise exceptions.PermissionDenied(detail=message, code=code)
  142. def throttled(self, request, wait):
  143. """
  144. If request is throttled, determine what kind of exception to raise.
  145. """
  146. raise exceptions.Throttled(wait)
  147. def get_authenticate_header(self, request):
  148. """
  149. If a request is unauthenticated, determine the WWW-Authenticate
  150. header to use for 401 responses, if any.
  151. """
  152. authenticators = self.get_authenticators()
  153. if authenticators:
  154. return authenticators[0].authenticate_header(request)
  155. def get_parser_context(self, http_request):
  156. """
  157. Returns a dict that is passed through to Parser.parse(),
  158. as the `parser_context` keyword argument.
  159. """
  160. # Note: Additionally `request` and `encoding` will also be added
  161. # to the context by the Request object.
  162. return {
  163. 'view': self,
  164. 'args': getattr(self, 'args', ()),
  165. 'kwargs': getattr(self, 'kwargs', {})
  166. }
  167. def get_renderer_context(self):
  168. """
  169. Returns a dict that is passed through to Renderer.render(),
  170. as the `renderer_context` keyword argument.
  171. """
  172. # Note: Additionally 'response' will also be added to the context,
  173. # by the Response object.
  174. return {
  175. 'view': self,
  176. 'args': getattr(self, 'args', ()),
  177. 'kwargs': getattr(self, 'kwargs', {}),
  178. 'request': getattr(self, 'request', None)
  179. }
  180. def get_exception_handler_context(self):
  181. """
  182. Returns a dict that is passed through to EXCEPTION_HANDLER,
  183. as the `context` argument.
  184. """
  185. return {
  186. 'view': self,
  187. 'args': getattr(self, 'args', ()),
  188. 'kwargs': getattr(self, 'kwargs', {}),
  189. 'request': getattr(self, 'request', None)
  190. }
  191. def get_view_name(self):
  192. """
  193. Return the view name, as used in OPTIONS responses and in the
  194. browsable API.
  195. """
  196. func = self.settings.VIEW_NAME_FUNCTION
  197. return func(self)
  198. def get_view_description(self, html=False):
  199. """
  200. Return some descriptive text for the view, as used in OPTIONS responses
  201. and in the browsable API.
  202. """
  203. func = self.settings.VIEW_DESCRIPTION_FUNCTION
  204. return func(self, html)
  205. # API policy instantiation methods
  206. def get_format_suffix(self, **kwargs):
  207. """
  208. Determine if the request includes a '.json' style format suffix
  209. """
  210. if self.settings.FORMAT_SUFFIX_KWARG:
  211. return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)
  212. def get_renderers(self):
  213. """
  214. Instantiates and returns the list of renderers that this view can use.
  215. """
  216. return [renderer() for renderer in self.renderer_classes]
  217. def get_parsers(self):
  218. """
  219. Instantiates and returns the list of parsers that this view can use.
  220. """
  221. return [parser() for parser in self.parser_classes]
  222. def get_authenticators(self):
  223. """
  224. Instantiates and returns the list of authenticators that this view can use.
  225. """
  226. return [auth() for auth in self.authentication_classes]
  227. def get_permissions(self):
  228. """
  229. Instantiates and returns the list of permissions that this view requires.
  230. """
  231. return [permission() for permission in self.permission_classes]
  232. def get_throttles(self):
  233. """
  234. Instantiates and returns the list of throttles that this view uses.
  235. """
  236. return [throttle() for throttle in self.throttle_classes]
  237. def get_content_negotiator(self):
  238. """
  239. Instantiate and return the content negotiation class to use.
  240. """
  241. if not getattr(self, '_negotiator', None):
  242. self._negotiator = self.content_negotiation_class()
  243. return self._negotiator
  244. def get_exception_handler(self):
  245. """
  246. Returns the exception handler that this view uses.
  247. """
  248. return self.settings.EXCEPTION_HANDLER
  249. # API policy implementation methods
  250. def perform_content_negotiation(self, request, force=False):
  251. """
  252. Determine which renderer and media type to use render the response.
  253. """
  254. renderers = self.get_renderers()
  255. conneg = self.get_content_negotiator()
  256. try:
  257. return conneg.select_renderer(request, renderers, self.format_kwarg)
  258. except Exception:
  259. if force:
  260. return (renderers[0], renderers[0].media_type)
  261. raise
  262. def perform_authentication(self, request):
  263. """
  264. Perform authentication on the incoming request.
  265. Note that if you override this and simply 'pass', then authentication
  266. will instead be performed lazily, the first time either
  267. `request.user` or `request.auth` is accessed.
  268. """
  269. request.user
  270. def check_permissions(self, request):
  271. """
  272. Check if the request should be permitted.
  273. Raises an appropriate exception if the request is not permitted.
  274. """
  275. for permission in self.get_permissions():
  276. if not permission.has_permission(request, self):
  277. self.permission_denied(
  278. request,
  279. message=getattr(permission, 'message', None),
  280. code=getattr(permission, 'code', None)
  281. )
  282. def check_object_permissions(self, request, obj):
  283. """
  284. Check if the request should be permitted for a given object.
  285. Raises an appropriate exception if the request is not permitted.
  286. """
  287. for permission in self.get_permissions():
  288. if not permission.has_object_permission(request, self, obj):
  289. self.permission_denied(
  290. request,
  291. message=getattr(permission, 'message', None),
  292. code=getattr(permission, 'code', None)
  293. )
  294. def check_throttles(self, request):
  295. """
  296. Check if request should be throttled.
  297. Raises an appropriate exception if the request is throttled.
  298. """
  299. throttle_durations = []
  300. for throttle in self.get_throttles():
  301. if not throttle.allow_request(request, self):
  302. throttle_durations.append(throttle.wait())
  303. if throttle_durations:
  304. # Filter out `None` values which may happen in case of config / rate
  305. # changes, see #1438
  306. durations = [
  307. duration for duration in throttle_durations
  308. if duration is not None
  309. ]
  310. duration = max(durations, default=None)
  311. self.throttled(request, duration)
  312. def determine_version(self, request, *args, **kwargs):
  313. """
  314. If versioning is being used, then determine any API version for the
  315. incoming request. Returns a two-tuple of (version, versioning_scheme)
  316. """
  317. if self.versioning_class is None:
  318. return (None, None)
  319. scheme = self.versioning_class()
  320. return (scheme.determine_version(request, *args, **kwargs), scheme)
  321. # Dispatch methods
  322. def initialize_request(self, request, *args, **kwargs):
  323. """
  324. Returns the initial request object.
  325. """
  326. parser_context = self.get_parser_context(request)
  327. return Request(
  328. request,
  329. parsers=self.get_parsers(),
  330. authenticators=self.get_authenticators(),
  331. negotiator=self.get_content_negotiator(),
  332. parser_context=parser_context
  333. )
  334. def initial(self, request, *args, **kwargs):
  335. """
  336. Runs anything that needs to occur prior to calling the method handler.
  337. """
  338. self.format_kwarg = self.get_format_suffix(**kwargs)
  339. # Perform content negotiation and store the accepted info on the request
  340. neg = self.perform_content_negotiation(request)
  341. request.accepted_renderer, request.accepted_media_type = neg
  342. # Determine the API version, if versioning is in use.
  343. version, scheme = self.determine_version(request, *args, **kwargs)
  344. request.version, request.versioning_scheme = version, scheme
  345. # Ensure that the incoming request is permitted
  346. self.perform_authentication(request)
  347. self.check_permissions(request)
  348. self.check_throttles(request)
  349. def finalize_response(self, request, response, *args, **kwargs):
  350. """
  351. Returns the final response object.
  352. """
  353. # Make the error obvious if a proper response is not returned
  354. assert isinstance(response, HttpResponseBase), (
  355. 'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
  356. 'to be returned from the view, but received a `%s`'
  357. % type(response)
  358. )
  359. if isinstance(response, Response):
  360. if not getattr(request, 'accepted_renderer', None):
  361. neg = self.perform_content_negotiation(request, force=True)
  362. request.accepted_renderer, request.accepted_media_type = neg
  363. response.accepted_renderer = request.accepted_renderer
  364. response.accepted_media_type = request.accepted_media_type
  365. response.renderer_context = self.get_renderer_context()
  366. # Add new vary headers to the response instead of overwriting.
  367. vary_headers = self.headers.pop('Vary', None)
  368. if vary_headers is not None:
  369. patch_vary_headers(response, cc_delim_re.split(vary_headers))
  370. for key, value in self.headers.items():
  371. response[key] = value
  372. return response
  373. def handle_exception(self, exc):
  374. """
  375. Handle any exception that occurs, by returning an appropriate response,
  376. or re-raising the error.
  377. """
  378. if isinstance(exc, (exceptions.NotAuthenticated,
  379. exceptions.AuthenticationFailed)):
  380. # WWW-Authenticate header for 401 responses, else coerce to 403
  381. auth_header = self.get_authenticate_header(self.request)
  382. if auth_header:
  383. exc.auth_header = auth_header
  384. else:
  385. exc.status_code = status.HTTP_403_FORBIDDEN
  386. exception_handler = self.get_exception_handler()
  387. context = self.get_exception_handler_context()
  388. response = exception_handler(exc, context)
  389. if response is None:
  390. self.raise_uncaught_exception(exc)
  391. response.exception = True
  392. return response
  393. def raise_uncaught_exception(self, exc):
  394. if settings.DEBUG:
  395. request = self.request
  396. renderer_format = getattr(request.accepted_renderer, 'format')
  397. use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
  398. request.force_plaintext_errors(use_plaintext_traceback)
  399. raise exc
  400. # Note: Views are made CSRF exempt from within `as_view` as to prevent
  401. # accidental removal of this exemption in cases where `dispatch` needs to
  402. # be overridden.
  403. def dispatch(self, request, *args, **kwargs):
  404. """
  405. `.dispatch()` is pretty much the same as Django's regular dispatch,
  406. but with extra hooks for startup, finalize, and exception handling.
  407. """
  408. self.args = args
  409. self.kwargs = kwargs
  410. request = self.initialize_request(request, *args, **kwargs)
  411. self.request = request
  412. self.headers = self.default_response_headers # deprecate?
  413. try:
  414. self.initial(request, *args, **kwargs)
  415. # Get the appropriate handler method
  416. if request.method.lower() in self.http_method_names:
  417. handler = getattr(self, request.method.lower(),
  418. self.http_method_not_allowed)
  419. else:
  420. handler = self.http_method_not_allowed
  421. response = handler(request, *args, **kwargs)
  422. except Exception as exc:
  423. response = self.handle_exception(exc)
  424. self.response = self.finalize_response(request, response, *args, **kwargs)
  425. return self.response
  426. def options(self, request, *args, **kwargs):
  427. """
  428. Handler method for HTTP 'OPTIONS' request.
  429. """
  430. if self.metadata_class is None:
  431. return self.http_method_not_allowed(request, *args, **kwargs)
  432. data = self.metadata_class().determine_metadata(request, self)
  433. return Response(data, status=status.HTTP_200_OK)