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

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 

13 

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 

20 

21 

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. 

26 

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 

33 

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) 

38 

39 # Suffix may be set by some Views, such as a ViewSet. 

40 suffix = getattr(view, 'suffix', None) 

41 if suffix: 

42 name += ' ' + suffix 

43 

44 return name 

45 

46 

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. 

51 

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 '' 

58 

59 description = formatting.dedent(smart_str(description)) 

60 if html: 

61 return formatting.markup_description(description) 

62 return description 

63 

64 

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) 

69 

70 

71def exception_handler(exc, context): 

72 """ 

73 Returns the response that should be used for any given exception. 

74 

75 By default we handle the REST framework `APIException`, and also 

76 Django's built-in `Http404` and `PermissionDenied` exceptions. 

77 

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() 

85 

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 

92 

93 if isinstance(exc.detail, (list, dict)): 

94 data = exc.detail 

95 else: 

96 data = {'detail': exc.detail} 

97 

98 set_rollback() 

99 return Response(data, status=exc.status_code, headers=headers) 

100 

101 return None 

102 

103 

104class APIView(View): 

105 

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 

115 

116 # Allow dependency injection of other settings to make testing easier. 

117 settings = api_settings 

118 

119 schema = DefaultSchema() 

120 

121 @classmethod 

122 def as_view(cls, **initkwargs): 

123 """ 

124 Store the original class on the view function. 

125 

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 

137 

138 view = super().as_view(**initkwargs) 

139 view.cls = cls 

140 view.initkwargs = initkwargs 

141 

142 # Note: session based authentication is explicitly CSRF validated, 

143 # all other authentication is CSRF exempt. 

144 return csrf_exempt(view) 

145 

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() 

152 

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 

161 

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) 

168 

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) 

176 

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) 

182 

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) 

191 

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 } 

204 

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 } 

218 

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 } 

230 

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) 

238 

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) 

246 

247 # API policy instantiation methods 

248 

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) 

255 

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] 

261 

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] 

267 

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] 

273 

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] 

279 

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] 

285 

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 

293 

294 def get_exception_handler(self): 

295 """ 

296 Returns the exception handler that this view uses. 

297 """ 

298 return self.settings.EXCEPTION_HANDLER 

299 

300 # API policy implementation methods 

301 

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() 

308 

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 

315 

316 def perform_authentication(self, request): 

317 """ 

318 Perform authentication on the incoming request. 

319 

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 

325 

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 ) 

338 

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 ) 

351 

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()) 

361 

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 ] 

369 

370 duration = max(durations, default=None) 

371 self.throttled(request, duration) 

372 

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) 

382 

383 # Dispatch methods 

384 

385 def initialize_request(self, request, *args, **kwargs): 

386 """ 

387 Returns the initial request object. 

388 """ 

389 parser_context = self.get_parser_context(request) 

390 

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 ) 

398 

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) 

404 

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 

408 

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 

412 

413 # Ensure that the incoming request is permitted 

414 self.perform_authentication(request) 

415 self.check_permissions(request) 

416 self.check_throttles(request) 

417 

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 ) 

428 

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 

433 

434 response.accepted_renderer = request.accepted_renderer 

435 response.accepted_media_type = request.accepted_media_type 

436 response.renderer_context = self.get_renderer_context() 

437 

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)) 

442 

443 for key, value in self.headers.items(): 

444 response[key] = value 

445 

446 return response 

447 

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) 

457 

458 if auth_header: 

459 exc.auth_header = auth_header 

460 else: 

461 exc.status_code = status.HTTP_403_FORBIDDEN 

462 

463 exception_handler = self.get_exception_handler() 

464 

465 context = self.get_exception_handler_context() 

466 response = exception_handler(exc, context) 

467 

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) 

470 

471 response.exception = True 

472 return response 

473 

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 

481 

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? 

495 

496 try: 

497 self.initial(request, *args, **kwargs) 

498 

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 

505 

506 response = handler(request, *args, **kwargs) 

507 

508 except Exception as exc: 

509 response = self.handle_exception(exc) 

510 

511 self.response = self.finalize_response(request, response, *args, **kwargs) 

512 return self.response 

513 

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)