commonground-api-common 1.12.2__py3-none-any.whl → 2.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. commonground_api_common-1.12.2.data/scripts/patch_content_types → commonground_api_common-2.4.1.data/scripts/generate_schema +2 -4
  2. {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/METADATA +47 -40
  3. {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/RECORD +47 -52
  4. {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/WHEEL +1 -1
  5. vng_api_common/__init__.py +1 -1
  6. vng_api_common/admin.py +1 -20
  7. vng_api_common/api/views.py +1 -0
  8. vng_api_common/apps.py +44 -26
  9. vng_api_common/audittrails/utils.py +44 -0
  10. vng_api_common/authorizations/admin.py +1 -1
  11. vng_api_common/authorizations/middleware.py +244 -0
  12. vng_api_common/authorizations/migrations/0016_remove_authorizationsconfig_api_root_and_more.py +76 -0
  13. vng_api_common/authorizations/models.py +62 -3
  14. vng_api_common/authorizations/utils.py +17 -0
  15. vng_api_common/authorizations/validators.py +5 -11
  16. vng_api_common/caching/etags.py +2 -1
  17. vng_api_common/client.py +61 -29
  18. vng_api_common/conf/api.py +33 -48
  19. vng_api_common/contrib/setup_configuration/models.py +32 -0
  20. vng_api_common/contrib/setup_configuration/steps.py +46 -0
  21. vng_api_common/extensions/file.py +26 -0
  22. vng_api_common/extensions/gegevensgroep.py +16 -0
  23. vng_api_common/extensions/geojson.py +270 -0
  24. vng_api_common/extensions/hyperlink.py +37 -0
  25. vng_api_common/extensions/polymorphic.py +68 -0
  26. vng_api_common/extensions/query.py +20 -0
  27. vng_api_common/filters.py +0 -1
  28. vng_api_common/generators.py +12 -113
  29. vng_api_common/middleware.py +1 -227
  30. vng_api_common/migrations/0006_delete_apicredential.py +120 -0
  31. vng_api_common/mocks.py +4 -1
  32. vng_api_common/models.py +10 -111
  33. vng_api_common/notifications/api/views.py +8 -8
  34. vng_api_common/notifications/handlers.py +8 -3
  35. vng_api_common/notifications/migrations/0011_remove_subscription_config_and_more.py +23 -0
  36. vng_api_common/oas.py +6 -10
  37. vng_api_common/pagination.py +10 -0
  38. vng_api_common/routers.py +3 -3
  39. vng_api_common/schema.py +414 -158
  40. vng_api_common/tests/schema.py +13 -0
  41. vng_api_common/utils.py +0 -22
  42. vng_api_common/validators.py +111 -113
  43. vng_api_common/views.py +35 -20
  44. commonground_api_common-1.12.2.data/scripts/generate_schema +0 -39
  45. commonground_api_common-1.12.2.data/scripts/use_external_components +0 -16
  46. vng_api_common/inspectors/cache.py +0 -57
  47. vng_api_common/inspectors/fields.py +0 -126
  48. vng_api_common/inspectors/files.py +0 -121
  49. vng_api_common/inspectors/geojson.py +0 -360
  50. vng_api_common/inspectors/polymorphic.py +0 -72
  51. vng_api_common/inspectors/query.py +0 -96
  52. vng_api_common/inspectors/utils.py +0 -40
  53. vng_api_common/inspectors/view.py +0 -547
  54. vng_api_common/management/commands/generate_autorisaties.py +0 -43
  55. vng_api_common/management/commands/generate_notificaties.py +0 -40
  56. vng_api_common/management/commands/generate_swagger.py +0 -197
  57. vng_api_common/management/commands/patch_error_contenttypes.py +0 -61
  58. vng_api_common/management/commands/use_external_components.py +0 -94
  59. vng_api_common/notifications/constants.py +0 -3
  60. vng_api_common/notifications/models.py +0 -97
  61. vng_api_common/templates/vng_api_common/api_schema_to_markdown_table.md +0 -16
  62. vng_api_common/templates/vng_api_common/autorisaties.md +0 -15
  63. vng_api_common/templates/vng_api_common/notificaties.md +0 -24
  64. {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/top_level.txt +0 -0
  65. /vng_api_common/{inspectors → contrib}/__init__.py +0 -0
  66. /vng_api_common/{management → contrib/setup_configuration}/__init__.py +0 -0
  67. /vng_api_common/{management/commands → extensions}/__init__.py +0 -0
@@ -1,547 +0,0 @@
1
- import inspect
2
- import logging
3
- from collections import OrderedDict
4
- from itertools import chain
5
- from typing import Optional, Tuple, Union
6
-
7
- from django.apps import apps
8
- from django.conf import settings
9
- from django.utils.translation import gettext, gettext_lazy as _
10
-
11
- from drf_yasg import openapi
12
- from drf_yasg.inspectors import SwaggerAutoSchema
13
- from drf_yasg.utils import get_consumes
14
- from rest_framework import exceptions, serializers, status, viewsets
15
-
16
- from ..constants import HEADER_AUDIT, HEADER_LOGRECORD_ID, VERSION_HEADER
17
- from ..exceptions import Conflict, Gone, PreconditionFailed
18
- from ..geo import GeoMixin
19
- from ..permissions import BaseAuthRequired, get_required_scopes
20
- from ..search import is_search_view
21
- from ..serializers import (
22
- FoutSerializer,
23
- ValidatieFoutSerializer,
24
- add_choice_values_help_text,
25
- )
26
- from .cache import CACHE_REQUEST_HEADERS, get_cache_headers, has_cache_header
27
-
28
- logger = logging.getLogger(__name__)
29
-
30
- TYPE_TO_FIELDMAPPING = {
31
- openapi.TYPE_INTEGER: serializers.IntegerField,
32
- openapi.TYPE_NUMBER: serializers.FloatField,
33
- openapi.TYPE_STRING: serializers.CharField,
34
- openapi.TYPE_BOOLEAN: serializers.BooleanField,
35
- openapi.TYPE_ARRAY: serializers.ListField,
36
- }
37
-
38
- COMMON_ERRORS = [
39
- exceptions.AuthenticationFailed,
40
- exceptions.NotAuthenticated,
41
- exceptions.PermissionDenied,
42
- exceptions.NotAcceptable,
43
- Conflict,
44
- Gone,
45
- exceptions.UnsupportedMediaType,
46
- exceptions.Throttled,
47
- exceptions.APIException,
48
- ]
49
-
50
- DEFAULT_ACTION_ERRORS = {
51
- "create": COMMON_ERRORS + [exceptions.ParseError, exceptions.ValidationError],
52
- "list": COMMON_ERRORS,
53
- "retrieve": COMMON_ERRORS + [exceptions.NotFound],
54
- "update": COMMON_ERRORS
55
- + [exceptions.ParseError, exceptions.ValidationError, exceptions.NotFound],
56
- "partial_update": COMMON_ERRORS
57
- + [exceptions.ParseError, exceptions.ValidationError, exceptions.NotFound],
58
- "destroy": COMMON_ERRORS + [exceptions.NotFound],
59
- }
60
-
61
- HTTP_STATUS_CODE_TITLES = {
62
- status.HTTP_100_CONTINUE: "Continue",
63
- status.HTTP_101_SWITCHING_PROTOCOLS: "Switching protocols",
64
- status.HTTP_200_OK: "OK",
65
- status.HTTP_201_CREATED: "Created",
66
- status.HTTP_202_ACCEPTED: "Accepted",
67
- status.HTTP_203_NON_AUTHORITATIVE_INFORMATION: "Non authoritative information",
68
- status.HTTP_204_NO_CONTENT: "No content",
69
- status.HTTP_205_RESET_CONTENT: "Reset content",
70
- status.HTTP_206_PARTIAL_CONTENT: "Partial content",
71
- status.HTTP_207_MULTI_STATUS: "Multi status",
72
- status.HTTP_300_MULTIPLE_CHOICES: "Multiple choices",
73
- status.HTTP_301_MOVED_PERMANENTLY: "Moved permanently",
74
- status.HTTP_302_FOUND: "Found",
75
- status.HTTP_303_SEE_OTHER: "See other",
76
- status.HTTP_304_NOT_MODIFIED: "Not modified",
77
- status.HTTP_305_USE_PROXY: "Use proxy",
78
- status.HTTP_306_RESERVED: "Reserved",
79
- status.HTTP_307_TEMPORARY_REDIRECT: "Temporary redirect",
80
- status.HTTP_400_BAD_REQUEST: "Bad request",
81
- status.HTTP_401_UNAUTHORIZED: "Unauthorized",
82
- status.HTTP_402_PAYMENT_REQUIRED: "Payment required",
83
- status.HTTP_403_FORBIDDEN: "Forbidden",
84
- status.HTTP_404_NOT_FOUND: "Not found",
85
- status.HTTP_405_METHOD_NOT_ALLOWED: "Method not allowed",
86
- status.HTTP_406_NOT_ACCEPTABLE: "Not acceptable",
87
- status.HTTP_407_PROXY_AUTHENTICATION_REQUIRED: "Proxy authentication required",
88
- status.HTTP_408_REQUEST_TIMEOUT: "Request timeout",
89
- status.HTTP_409_CONFLICT: "Conflict",
90
- status.HTTP_410_GONE: "Gone",
91
- status.HTTP_411_LENGTH_REQUIRED: "Length required",
92
- status.HTTP_412_PRECONDITION_FAILED: "Precondition failed",
93
- status.HTTP_413_REQUEST_ENTITY_TOO_LARGE: "Request entity too large",
94
- status.HTTP_414_REQUEST_URI_TOO_LONG: "Request uri too long",
95
- status.HTTP_415_UNSUPPORTED_MEDIA_TYPE: "Unsupported media type",
96
- status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE: "Requested range not satisfiable",
97
- status.HTTP_417_EXPECTATION_FAILED: "Expectation failed",
98
- status.HTTP_422_UNPROCESSABLE_ENTITY: "Unprocessable entity",
99
- status.HTTP_423_LOCKED: "Locked",
100
- status.HTTP_424_FAILED_DEPENDENCY: "Failed dependency",
101
- status.HTTP_428_PRECONDITION_REQUIRED: "Precondition required",
102
- status.HTTP_429_TOO_MANY_REQUESTS: "Too many requests",
103
- status.HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE: "Request header fields too large",
104
- status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable for legal reasons",
105
- status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal server error",
106
- status.HTTP_501_NOT_IMPLEMENTED: "Not implemented",
107
- status.HTTP_502_BAD_GATEWAY: "Bad gateway",
108
- status.HTTP_503_SERVICE_UNAVAILABLE: "Service unavailable",
109
- status.HTTP_504_GATEWAY_TIMEOUT: "Gateway timeout",
110
- status.HTTP_505_HTTP_VERSION_NOT_SUPPORTED: "HTTP version not supported",
111
- status.HTTP_507_INSUFFICIENT_STORAGE: "Insufficient storage",
112
- status.HTTP_511_NETWORK_AUTHENTICATION_REQUIRED: "Network authentication required",
113
- }
114
-
115
- AUDIT_TRAIL_ENABLED = apps.is_installed("vng_api_common.audittrails")
116
-
117
- AUDIT_REQUEST_HEADERS = [
118
- openapi.Parameter(
119
- name=HEADER_LOGRECORD_ID,
120
- type=openapi.TYPE_STRING,
121
- in_=openapi.IN_HEADER,
122
- required=False,
123
- description=gettext(
124
- "Identifier of the request, traceable throughout the network"
125
- ),
126
- ),
127
- openapi.Parameter(
128
- name=HEADER_AUDIT,
129
- type=openapi.TYPE_STRING,
130
- in_=openapi.IN_HEADER,
131
- required=False,
132
- description=gettext("Explanation why the request is done"),
133
- ),
134
- ]
135
-
136
-
137
- def response_header(description: str, type: str, format: str = None) -> OrderedDict:
138
- header = OrderedDict(
139
- (("schema", OrderedDict((("type", type),))), ("description", description))
140
- )
141
- if format is not None:
142
- header["schema"]["format"] = format
143
- return header
144
-
145
-
146
- version_header = response_header(
147
- "Geeft een specifieke API-versie aan in de context van een specifieke aanroep. Voorbeeld: 1.2.1.",
148
- type=openapi.TYPE_STRING,
149
- )
150
-
151
- location_header = response_header(
152
- "URL waar de resource leeft.", type=openapi.TYPE_STRING, format=openapi.FORMAT_URI
153
- )
154
-
155
-
156
- def _view_supports_audittrail(view: viewsets.ViewSet) -> bool:
157
- if not AUDIT_TRAIL_ENABLED:
158
- return False
159
-
160
- if not hasattr(view, "action"):
161
- logger.debug("Could not determine view action for view %r", view)
162
- return False
163
-
164
- # local imports, since you get errors if you try to import non-installed app
165
- # models
166
- from ..audittrails.viewsets import AuditTrailMixin
167
-
168
- relevant_bases = [
169
- base for base in view.__class__.__bases__ if issubclass(base, AuditTrailMixin)
170
- ]
171
- if not relevant_bases:
172
- return False
173
-
174
- # check if the view action is listed in any of the audit trail mixins
175
- action = view.action
176
- if action == "partial_update": # partial update is self.update(partial=True)
177
- action = "update"
178
-
179
- # if the current view action is not provided by any of the audit trail
180
- # related bases, then it's not audit trail enabled
181
- action_in_audit_bases = any(
182
- action in dict(inspect.getmembers(base)) for base in relevant_bases
183
- )
184
-
185
- return action_in_audit_bases
186
-
187
-
188
- class ResponseRef(openapi._Ref):
189
- def __init__(self, resolver, response_name, ignore_unresolved=False):
190
- """
191
- Adds a reference to a named Response defined in the ``#/responses/`` object.
192
- """
193
- assert "responses" in resolver.scopes
194
- super().__init__(
195
- resolver, response_name, "responses", openapi.Response, ignore_unresolved
196
- )
197
-
198
-
199
- class AutoSchema(SwaggerAutoSchema):
200
- @property
201
- def model(self):
202
- if hasattr(self.view, "queryset") and self.view.queryset is not None:
203
- return self.view.queryset.model
204
-
205
- if hasattr(self.view, "get_queryset"):
206
- qs = self.view.get_queryset()
207
- return qs.model
208
- return None
209
-
210
- @property
211
- def _is_search_view(self):
212
- return is_search_view(self.view)
213
-
214
- def get_operation_id(self, operation_keys=None) -> str:
215
- """
216
- Simply return the model name as lowercase string, postfixed with the operation name.
217
- """
218
- operation_keys = operation_keys or self.operation_keys
219
-
220
- operation_id = self.overrides.get("operation_id", "")
221
- if operation_id:
222
- return operation_id
223
-
224
- action = operation_keys[-1]
225
- if self.model is not None:
226
- model_name = self.model._meta.model_name
227
- return f"{model_name}_{action}"
228
- else:
229
- operation_id = "_".join(operation_keys)
230
- return operation_id
231
-
232
- def should_page(self):
233
- if self._is_search_view:
234
- return hasattr(self.view, "paginator")
235
- return super().should_page()
236
-
237
- def get_request_serializer(self):
238
- if not self._is_search_view:
239
- return super().get_request_serializer()
240
-
241
- Base = self.view.get_search_input_serializer_class()
242
-
243
- filter_fields = []
244
- for filter_backend in self.view.filter_backends:
245
- filter_fields += (
246
- self.probe_inspectors(
247
- self.filter_inspectors, "get_filter_parameters", filter_backend()
248
- )
249
- or []
250
- )
251
-
252
- filters = {}
253
- for parameter in filter_fields:
254
- help_text = parameter.description
255
- # we can't get the verbose_label back from the enum, so the inspector
256
- # in vng_api_common.inspectors.fields leaves a filter field reference behind
257
- _filter_field = getattr(parameter, "_filter_field", None)
258
- choices = getattr(_filter_field, "extra", {}).get("choices", [])
259
- if choices:
260
- FieldClass = serializers.ChoiceField
261
- extra = {"choices": choices}
262
- value_display_mapping = add_choice_values_help_text(choices)
263
- help_text += f"\n\n{value_display_mapping}"
264
- else:
265
- FieldClass = TYPE_TO_FIELDMAPPING[parameter.type]
266
- extra = {}
267
-
268
- filters[parameter.name] = FieldClass(
269
- help_text=help_text, required=parameter.required, **extra
270
- )
271
-
272
- SearchSerializer = type(Base.__name__, (Base,), filters)
273
- return SearchSerializer()
274
-
275
- def _get_search_responses(self):
276
- response_status = status.HTTP_200_OK
277
- response_schema = self.serializer_to_schema(self.get_view_serializer())
278
- schema = openapi.Schema(type=openapi.TYPE_ARRAY, items=response_schema)
279
- if self.should_page():
280
- schema = self.get_paginated_response(schema) or schema
281
- return OrderedDict({str(response_status): schema})
282
-
283
- def register_error_responses(self):
284
- ref_responses = self.components.with_scope("responses")
285
-
286
- if not ref_responses.keys():
287
- # general errors
288
- general_classes = list(chain(*DEFAULT_ACTION_ERRORS.values()))
289
- # add geo and validation errors
290
- exception_classes = general_classes + [
291
- PreconditionFailed,
292
- exceptions.ValidationError,
293
- ]
294
- status_codes = sorted({e.status_code for e in exception_classes})
295
-
296
- fout_schema = self.serializer_to_schema(FoutSerializer())
297
- validation_fout_schema = self.serializer_to_schema(
298
- ValidatieFoutSerializer()
299
- )
300
- for status_code in status_codes:
301
- schema = (
302
- validation_fout_schema
303
- if status_code == exceptions.ValidationError.status_code
304
- else fout_schema
305
- )
306
- response = openapi.Response(
307
- description=HTTP_STATUS_CODE_TITLES.get(status_code, ""),
308
- schema=schema,
309
- )
310
- self.set_response_headers(str(status_code), response)
311
- ref_responses.set(str(status_code), response)
312
-
313
- def _get_error_responses(self) -> OrderedDict:
314
- """
315
- Add the appropriate possible error responses to the schema.
316
-
317
- E.g. - we know that HTTP 400 on a POST/PATCH/PUT leads to validation
318
- errors, 403 to Permission Denied etc.
319
- """
320
- # only supports viewsets
321
- if not hasattr(self.view, "action"):
322
- return OrderedDict()
323
-
324
- self.register_error_responses()
325
-
326
- action = self.view.action
327
- if (
328
- action not in DEFAULT_ACTION_ERRORS and self._is_search_view
329
- ): # similar to a CREATE
330
- action = "create"
331
-
332
- # general errors
333
- general_klasses = DEFAULT_ACTION_ERRORS.get(action)
334
- if general_klasses is None:
335
- logger.debug("Unknown action %s, no default error responses added")
336
- return OrderedDict()
337
-
338
- exception_klasses = general_klasses[:]
339
- # add geo and validation errors
340
- has_validation_errors = self.get_filter_parameters() or any(
341
- issubclass(klass, exceptions.ValidationError) for klass in exception_klasses
342
- )
343
- if has_validation_errors:
344
- exception_klasses.append(exceptions.ValidationError)
345
-
346
- if isinstance(self.view, GeoMixin):
347
- exception_klasses.append(PreconditionFailed)
348
-
349
- status_codes = sorted({e.status_code for e in exception_klasses})
350
-
351
- return OrderedDict(
352
- [
353
- (status_code, ResponseRef(self.components, str(status_code)))
354
- for status_code in status_codes
355
- ]
356
- )
357
-
358
- def get_default_responses(self) -> OrderedDict:
359
- if self._is_search_view:
360
- responses = self._get_search_responses()
361
- serializer = self.get_view_serializer()
362
- else:
363
- responses = super().get_default_responses()
364
- serializer = self.get_request_serializer() or self.get_view_serializer()
365
-
366
- # inject any headers
367
- _responses = OrderedDict()
368
- custom_headers = OrderedDict()
369
- for status_, schema in responses.items():
370
- if serializer is not None:
371
- custom_headers = (
372
- self.probe_inspectors(
373
- self.field_inspectors,
374
- "get_response_headers",
375
- serializer,
376
- {"field_inspectors": self.field_inspectors},
377
- status=status_,
378
- )
379
- or OrderedDict()
380
- )
381
-
382
- # add the cache headers, if applicable
383
- for header, header_schema in get_cache_headers(self.view).items():
384
- custom_headers[header] = header_schema
385
-
386
- assert isinstance(schema, openapi.Schema.OR_REF) or schema == ""
387
- response = openapi.Response(
388
- description=HTTP_STATUS_CODE_TITLES.get(int(status_), ""),
389
- schema=schema or None,
390
- headers=custom_headers,
391
- )
392
- _responses[status_] = response
393
-
394
- for status_code, response in self._get_error_responses().items():
395
- _responses[status_code] = response
396
-
397
- return _responses
398
-
399
- @staticmethod
400
- def set_response_headers(
401
- status_code: str, response: Union[openapi.Response, ResponseRef]
402
- ):
403
- if not isinstance(response, openapi.Response):
404
- return
405
-
406
- response.setdefault("headers", OrderedDict())
407
- response["headers"][VERSION_HEADER] = version_header
408
-
409
- if status_code == "201":
410
- response["headers"]["Location"] = location_header
411
-
412
- def get_response_schemas(self, response_serializers):
413
- # parent class doesn't support responses as ref objects,
414
- # so we temporary remove them
415
- ref_responses = OrderedDict()
416
- for status_code, serializer in response_serializers.copy().items():
417
- if isinstance(serializer, ResponseRef):
418
- ref_responses[str(status_code)] = response_serializers.pop(status_code)
419
-
420
- responses = super().get_response_schemas(response_serializers)
421
-
422
- # and add them again
423
- responses.update(ref_responses)
424
- responses = OrderedDict(sorted(responses.items()))
425
-
426
- # add the Api-Version headers
427
- for status_code, response in responses.items():
428
- self.set_response_headers(status_code, response)
429
-
430
- return responses
431
-
432
- def get_request_content_type_header(self) -> Optional[openapi.Parameter]:
433
- if self.method not in ["POST", "PUT", "PATCH"]:
434
- return None
435
-
436
- consumes = get_consumes(self.get_parser_classes())
437
- return openapi.Parameter(
438
- name="Content-Type",
439
- in_=openapi.IN_HEADER,
440
- type=openapi.TYPE_STRING,
441
- required=True,
442
- enum=consumes,
443
- description=_("Content type of the request body."),
444
- )
445
-
446
- def add_manual_parameters(self, parameters):
447
- base = super().add_manual_parameters(parameters)
448
-
449
- content_type = self.get_request_content_type_header()
450
- if content_type is not None:
451
- base = [content_type] + base
452
-
453
- if self._is_search_view:
454
- serializer = self.get_request_serializer()
455
- else:
456
- serializer = self.get_request_serializer() or self.get_view_serializer()
457
-
458
- extra = []
459
- if serializer is not None:
460
- extra = (
461
- self.probe_inspectors(
462
- self.field_inspectors,
463
- "get_request_header_parameters",
464
- serializer,
465
- {"field_inspectors": self.field_inspectors},
466
- )
467
- or []
468
- )
469
- result = base + extra
470
-
471
- if has_cache_header(self.view):
472
- result += CACHE_REQUEST_HEADERS
473
-
474
- if _view_supports_audittrail(self.view):
475
- result += AUDIT_REQUEST_HEADERS
476
-
477
- return result
478
-
479
- def get_security(self):
480
- """Return a list of security requirements for this operation.
481
-
482
- Returning an empty list marks the endpoint as unauthenticated (i.e. removes all accepted
483
- authentication schemes). Returning ``None`` will inherit the top-level secuirty requirements.
484
-
485
- :return: security requirements
486
- :rtype: list[dict[str,list[str]]]"""
487
- permissions = self.view.get_permissions()
488
- scope_permissions = [
489
- perm for perm in permissions if isinstance(perm, BaseAuthRequired)
490
- ]
491
-
492
- if not scope_permissions:
493
- return super().get_security()
494
-
495
- if len(permissions) != len(scope_permissions):
496
- logger.warning(
497
- "Can't represent all permissions in OAS for path %s and method %s",
498
- self.path,
499
- self.method,
500
- )
501
-
502
- required_scopes = []
503
- for perm in scope_permissions:
504
- scopes = get_required_scopes(self.request, self.view)
505
- if scopes is None:
506
- continue
507
- required_scopes.append(scopes)
508
-
509
- if not required_scopes:
510
- return None # use global security
511
-
512
- scopes = [str(scope) for scope in sorted(required_scopes)]
513
-
514
- # operation level security
515
- return [{settings.SECURITY_DEFINITION_NAME: scopes}]
516
-
517
- # all of these break if you accept method HEAD because the view.action is None
518
- def is_list_view(self) -> bool:
519
- if self.method == "HEAD":
520
- return False
521
- return super().is_list_view()
522
-
523
- def get_summary_and_description(self) -> Tuple[str, str]:
524
- if self.method != "HEAD":
525
- return super().get_summary_and_description()
526
-
527
- default_description = _(
528
- "De headers voor een specifiek(e) {model_name} opvragen"
529
- ).format(model_name=self.model._meta.model_name.upper())
530
- default_summary = _(
531
- "Vraag de headers op die je bij een GET request zou krijgen."
532
- )
533
-
534
- description = self.overrides.get("operation_description", default_description)
535
- summary = self.overrides.get("operation_summary", default_summary)
536
- return description, summary
537
-
538
- # patch around drf-yasg not taking overrides into account
539
- # TODO: contribute back in PR
540
- def get_produces(self) -> list:
541
- produces = super().get_produces()
542
- return self.overrides.get("produces", produces)
543
-
544
-
545
- # translations aren't picked up/defined in DRF, so we need to hook them up here
546
- _("A page number within the paginated result set.")
547
- _("Number of results to return per page.")
@@ -1,43 +0,0 @@
1
- import logging
2
- import os
3
-
4
- from django.conf import settings
5
- from django.core.management import BaseCommand
6
- from django.template.loader import render_to_string
7
-
8
- from ...scopes import SCOPE_REGISTRY
9
-
10
-
11
- class Command(BaseCommand):
12
- """
13
- Generate a markdown file documenting the auth scopes of the component
14
- """
15
-
16
- def add_arguments(self, parser):
17
- super().add_arguments(parser)
18
-
19
- parser.add_argument(
20
- "--output-file",
21
- dest="output_file",
22
- default=None,
23
- help="Name of the output file",
24
- )
25
-
26
- def handle(self, output_file, *args, **options):
27
- scopes = sorted(
28
- (scope for scope in SCOPE_REGISTRY if not scope.children),
29
- key=lambda s: s.label,
30
- )
31
-
32
- template = "vng_api_common/autorisaties.md"
33
- markdown = render_to_string(
34
- template,
35
- context={
36
- "scopes": scopes,
37
- "project_name": settings.PROJECT_NAME,
38
- "site_title": settings.SITE_TITLE,
39
- },
40
- )
41
-
42
- with open(output_file, "w") as f:
43
- f.write(markdown)
@@ -1,40 +0,0 @@
1
- import logging
2
- import os
3
-
4
- from django.conf import settings
5
- from django.core.management import BaseCommand
6
- from django.template.loader import render_to_string
7
-
8
- from notifications_api_common.kanalen import KANAAL_REGISTRY
9
-
10
-
11
- class Command(BaseCommand):
12
- """
13
- Generate a markdown file documenting the notification channels of the component
14
- """
15
-
16
- def add_arguments(self, parser):
17
- super().add_arguments(parser)
18
-
19
- parser.add_argument(
20
- "--output-file",
21
- dest="output_file",
22
- default=None,
23
- help="Name of the output file",
24
- )
25
-
26
- def handle(self, output_file, *args, **options):
27
- kanalen = sorted(KANAAL_REGISTRY, key=lambda s: s.label)
28
-
29
- template = "vng_api_common/notificaties.md"
30
- markdown = render_to_string(
31
- template,
32
- context={
33
- "kanalen": kanalen,
34
- "project_name": settings.PROJECT_NAME,
35
- "site_title": settings.SITE_TITLE,
36
- },
37
- )
38
-
39
- with open(output_file, "w") as f:
40
- f.write(markdown)