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