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
@@ -5,17 +5,14 @@ from typing import Callable
5
5
 
6
6
  from django.conf import settings
7
7
  from django.core.exceptions import ValidationError
8
- from django.core.validators import RegexValidator
9
8
  from django.utils import timezone
10
9
  from django.utils.deconstruct import deconstructible
11
10
  from django.utils.module_loading import import_string
12
11
  from django.utils.translation import gettext_lazy as _
13
12
 
14
- import requests
15
13
  from rest_framework import serializers, validators
16
14
 
17
- from .client import get_client
18
- from .constants import RSIN_LENGTH
15
+ from .constants import BSN_LENGTH, RSIN_LENGTH
19
16
  from .oas import fetcher, obj_has_shape
20
17
 
21
18
  logger = logging.getLogger(__name__)
@@ -24,6 +21,90 @@ logger = logging.getLogger(__name__)
24
21
  WORD_REGEX = re.compile(r"[\w\-]+$", re.ASCII)
25
22
 
26
23
 
24
+ class BaseIdentifierValidator:
25
+ """
26
+ Validator base class that performs common validation logic.
27
+ Digit check, length, and optional 11-proof check.
28
+ """
29
+
30
+ error_messages = {
31
+ "isdigit": _("Voer een numerieke waarde in"),
32
+ "length": _("Waarde moet %(identifier_length)s tekens lang zijn"),
33
+ "11proefnumber": _("Ongeldige code"),
34
+ }
35
+
36
+ def __init__(
37
+ self,
38
+ value: str,
39
+ identifier_length: int,
40
+ validate_11proef: bool = False,
41
+ ):
42
+ self.value = value
43
+ self.identifier_length = identifier_length
44
+ self.validate_11proef = validate_11proef
45
+
46
+ def validate_isdigit(self) -> None:
47
+ """Validates that the value contains only digits."""
48
+ if not self.value.isdigit():
49
+ raise ValidationError(self.error_messages["isdigit"], code="only-digits")
50
+
51
+ def validate_length(self) -> None:
52
+ """Validates that the length of the value is within the allowed sizes."""
53
+ if len(self.value) != self.identifier_length:
54
+ raise ValidationError(
55
+ self.error_messages["length"]
56
+ % {"identifier_length": self.identifier_length},
57
+ code="invalid-length",
58
+ )
59
+
60
+ def validate_11proefnumber(self) -> None:
61
+ """Validates the value based on the 11-proof check."""
62
+ total = 0
63
+ for multiplier, char in enumerate(reversed(self.value), start=1):
64
+ if multiplier == 1:
65
+ total += -multiplier * int(char)
66
+ else:
67
+ total += multiplier * int(char)
68
+
69
+ if total % 11 != 0:
70
+ raise ValidationError(self.error_messages["11proefnumber"], code="invalid")
71
+
72
+ def validate(self) -> None:
73
+ self.validate_isdigit()
74
+ self.validate_length()
75
+ if self.validate_11proef:
76
+ self.validate_11proefnumber()
77
+
78
+
79
+ def validate_rsin(value: str) -> None:
80
+ """
81
+ Validates that a string value is a valid RSIN number by applying the
82
+ '11-proef' checking.
83
+
84
+ :param value: String object representing a presumably good RSIN number.
85
+ """
86
+
87
+ validator = BaseIdentifierValidator(
88
+ value, identifier_length=RSIN_LENGTH, validate_11proef=True
89
+ )
90
+ validator.error_messages["11proefnumber"] = _("Onjuist RSIN nummer")
91
+ validator.validate()
92
+
93
+
94
+ def validate_bsn(value: str) -> None:
95
+ """
96
+ Validates that a string value is a valid BSN number by applying the
97
+ '11-proef' checking.
98
+
99
+ :param value: String object representing a presumably good BSN number.
100
+ """
101
+ validator = BaseIdentifierValidator(
102
+ value, identifier_length=BSN_LENGTH, validate_11proef=True
103
+ )
104
+ validator.error_messages["11proefnumber"] = _("Onjuist BSN nummer")
105
+ validator.validate()
106
+
107
+
27
108
  @deconstructible
28
109
  class AlphanumericExcludingDiacritic:
29
110
  """
@@ -75,37 +156,6 @@ def validate_non_negative_string(value):
75
156
  raise ValidationError("De waarde moet een niet-negatief getal zijn.")
76
157
 
77
158
 
78
- validate_digits = RegexValidator(
79
- regex="^[0-9]+$", message="Waarde moet numeriek zijn.", code="only-digits"
80
- )
81
-
82
-
83
- def validate_rsin(value):
84
- """
85
- Validates that a string value is a valid RSIN number by applying the
86
- '11-proef' checking.
87
-
88
- :param value: String object representing a presumably good RSIN number.
89
- """
90
- # Initial sanity checks.
91
- validate_digits(value)
92
- if len(value) != RSIN_LENGTH:
93
- raise ValidationError(
94
- "RSIN moet %s tekens lang zijn." % RSIN_LENGTH, code="invalid-length"
95
- )
96
-
97
- # 11-proef check.
98
- total = 0
99
- for multiplier, char in enumerate(reversed(value), start=1):
100
- if multiplier == 1:
101
- total += -multiplier * int(char)
102
- else:
103
- total += multiplier * int(char)
104
-
105
- if total % 11 != 0:
106
- raise ValidationError("Onjuist RSIN nummer.", code="invalid")
107
-
108
-
109
159
  class URLValidator:
110
160
  """
111
161
  Validate that the URL actually resolves to a HTTP 200
@@ -206,64 +256,21 @@ class ResourceValidator(URLValidator):
206
256
 
207
257
 
208
258
  class InformatieObjectUniqueValidator(validators.UniqueTogetherValidator):
259
+ requires_context = True
260
+
209
261
  def __init__(self, parent_field, field: str):
210
262
  self.parent_field = parent_field
211
263
  self.field = field
212
264
  super().__init__(None, (parent_field, field))
213
265
 
214
- def set_context(self, serializer_field):
215
- serializer = serializer_field.parent
216
- super().set_context(serializer)
217
-
218
- self.queryset = serializer.Meta.model._default_manager.all()
219
- self.parent_object = serializer.context["parent_object"]
220
-
221
- def __call__(self, informatieobject: str):
222
- attrs = {self.parent_field: self.parent_object, self.field: informatieobject}
266
+ def __call__(self, informatieobject: str, serializer):
267
+ attrs = {
268
+ self.parent_field: serializer.context["parent_object"],
269
+ self.field: informatieobject,
270
+ }
223
271
  super().__call__(attrs)
224
272
 
225
273
 
226
- class ObjectInformatieObjectValidator:
227
- """
228
- Validate that the INFORMATIEOBJECT is linked already in the DRC.
229
- """
230
-
231
- message = _(
232
- "Het informatieobject is in het DRC nog niet gerelateerd aan dit object."
233
- )
234
- code = "inconsistent-relation"
235
-
236
- def set_context(self, serializer):
237
- """
238
- This hook is called by the serializer instance,
239
- prior to the validation call being made.
240
- """
241
- self.parent_object = serializer.context["parent_object"]
242
- self.request = serializer.context["request"]
243
-
244
- def __call__(self, informatieobject: str):
245
- object_url = self.parent_object.get_absolute_api_url(self.request)
246
-
247
- # dynamic so that it can be mocked in tests easily
248
- client = get_client(informatieobject)
249
-
250
- try:
251
- oios = client.list(
252
- "objectinformatieobject",
253
- query_params={
254
- "informatieobject": informatieobject,
255
- "object": object_url,
256
- },
257
- )
258
- except requests.HTTPError as exc:
259
- raise serializers.ValidationError(
260
- exc.args[0], code="relation-validation-error"
261
- ) from exc
262
-
263
- if len(oios) == 0:
264
- raise serializers.ValidationError(self.message, code=self.code)
265
-
266
-
267
274
  @deconstructible
268
275
  class UntilNowValidator:
269
276
  """
@@ -309,42 +316,35 @@ class UniekeIdentificatieValidator:
309
316
  :param identificatie_field: naam van het veld dat de identificatie bevat
310
317
  """
311
318
 
312
- message = _("Deze identificatie bestaat al binnen de organisatie")
319
+ message = _("Deze identificatie ({identificatie}) bestaat al binnen de organisatie")
313
320
  code = "identificatie-niet-uniek"
321
+ requires_context = True
314
322
 
315
323
  def __init__(self, organisatie_field: str, identificatie_field="identificatie"):
316
324
  self.organisatie_field = organisatie_field
317
325
  self.identificatie_field = identificatie_field
318
326
 
319
- def set_context(self, serializer):
320
- """
321
- This hook is called by the serializer instance,
322
- prior to the validation call being made.
323
- """
324
- # Determine the existing instance, if this is an update operation.
325
- self.instance = getattr(serializer, "instance", None)
326
- self.model = serializer.Meta.model
327
-
328
- def __call__(self, attrs: dict):
327
+ def __call__(self, attrs: dict, serializer):
328
+ instance = getattr(serializer, "instance", None)
329
329
  identificatie = attrs.get(self.identificatie_field)
330
330
  if not identificatie:
331
- if self.instance:
331
+ if instance:
332
332
  # In case of a partial update
333
- identificatie = self.instance.identificatie
333
+ identificatie = instance.identificatie
334
334
  else:
335
335
  # identification is being generated, and the generation checks for
336
336
  # uniqueness
337
337
  return
338
338
 
339
339
  organisatie = attrs.get(self.organisatie_field)
340
- pk = self.instance.pk if self.instance else None
340
+ pk = instance.pk if instance else None
341
341
 
342
342
  # if we're updating an instance, setting the current values will not
343
343
  # trigger an error because the instance-to-be-updated is excluded from
344
344
  # the queryset. If either bronorganisatie or identificatie changes,
345
345
  # and it already exists, it will raise a validation error
346
346
  combination_exists = (
347
- self.model.objects
347
+ serializer.Meta.model.objects
348
348
  # in case of an update, exclude the current object. for a create, this
349
349
  # will be None
350
350
  .exclude(pk=pk)
@@ -359,7 +359,12 @@ class UniekeIdentificatieValidator:
359
359
 
360
360
  if combination_exists:
361
361
  raise serializers.ValidationError(
362
- {self.identificatie_field: self.message}, code=self.code
362
+ {
363
+ self.identificatie_field: self.message.format(
364
+ identificatie=identificatie
365
+ )
366
+ },
367
+ code=self.code,
363
368
  )
364
369
 
365
370
 
@@ -370,22 +375,15 @@ class IsImmutableValidator:
370
375
 
371
376
  message = _("Dit veld mag niet gewijzigd worden.")
372
377
  code = "wijzigen-niet-toegelaten"
378
+ requires_context = True
373
379
 
374
- def set_context(self, serializer_field):
375
- """
376
- This hook is called by the serializer instance,
377
- prior to the validation call being made.
378
- """
379
- # Determine the existing instance, if this is an update operation.
380
- self.serializer_field = serializer_field
381
- self.instance = getattr(serializer_field.parent, "instance", None)
382
-
383
- def __call__(self, new_value):
380
+ def __call__(self, new_value, serializer_field):
381
+ instance = getattr(serializer_field.parent, "instance", None)
384
382
  # no instance -> it's not an update
385
- if not self.instance:
383
+ if not instance:
386
384
  return
387
385
 
388
- current_value = getattr(self.instance, self.serializer_field.field_name)
386
+ current_value = getattr(instance, serializer_field.field_name)
389
387
 
390
388
  if new_value != current_value:
391
389
  raise serializers.ValidationError(self.message, code=self.code)
vng_api_common/views.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  from collections import OrderedDict
4
+ from typing import Optional
4
5
 
5
6
  from django.apps import apps
6
7
  from django.conf import settings
@@ -13,7 +14,8 @@ import requests
13
14
  from rest_framework import exceptions as drf_exceptions, status
14
15
  from rest_framework.response import Response
15
16
  from rest_framework.views import exception_handler as drf_exception_handler
16
- from zds_client import ClientError
17
+
18
+ from vng_api_common.client import Client
17
19
 
18
20
  from . import exceptions
19
21
  from .compat import sentry_client
@@ -128,12 +130,20 @@ def _test_ac_config() -> list:
128
130
  auth_config = AuthorizationsConfig.get_solo()
129
131
 
130
132
  # check if AC auth is configured
131
- ac_client = AuthorizationsConfig.get_client()
132
- has_ac_auth = ac_client.auth is not None
133
+ ac_client: Optional[Client] = AuthorizationsConfig.get_client()
134
+ has_ac_auth = ac_client.auth is not None if ac_client else False
133
135
 
134
136
  checks = [
135
137
  (_("Type of component"), auth_config.get_component_display(), None),
136
- (_("AC"), auth_config.api_root, auth_config.api_root.endswith("/")),
138
+ (
139
+ _("AC"),
140
+ (
141
+ auth_config.authorizations_api_service.api_root
142
+ if ac_client
143
+ else _("Missing")
144
+ ),
145
+ bool(ac_client),
146
+ ),
137
147
  (
138
148
  _("Credentials for AC"),
139
149
  _("Configured") if has_ac_auth else _("Missing"),
@@ -145,18 +155,17 @@ def _test_ac_config() -> list:
145
155
  if has_ac_auth:
146
156
  error = False
147
157
 
158
+ client_id = ac_client.auth.service.client_id
159
+
148
160
  try:
149
- ac_client.list(
150
- "applicatie", query_params={"clientIds": ac_client.auth.client_id}
161
+ response: requests.Response = ac_client.get(
162
+ "applicaties", params={"clientIds": client_id}
151
163
  )
152
- except requests.ConnectionError:
164
+
165
+ response.raise_for_status()
166
+ except requests.RequestException:
153
167
  error = True
154
168
  message = _("Could not connect with AC")
155
- except ClientError as exc:
156
- error = True
157
- message = _(
158
- "Cannot retrieve authorizations: HTTP {status_code} - {error_code}"
159
- ).format(status_code=exc.args[0]["status"], error_code=exc.args[0]["code"])
160
169
  else:
161
170
  message = _("Can retrieve authorizations")
162
171
 
@@ -165,15 +174,17 @@ def _test_ac_config() -> list:
165
174
  return checks
166
175
 
167
176
 
168
- def _test_nrc_config() -> list:
169
- if not apps.is_installed("vng_api_common.notifications"):
177
+ def _test_nrc_config(check_autorisaties_subscription=True) -> list:
178
+ if not apps.is_installed("notifications_api_common"):
170
179
  return []
171
180
 
172
181
  from notifications_api_common.models import NotificationsConfig, Subscription
173
182
 
174
183
  nrc_config = NotificationsConfig.get_solo()
184
+ nrc_client: Optional[Client] = NotificationsConfig.get_client()
175
185
 
176
- nrc_client = NotificationsConfig.get_client()
186
+ if not nrc_client:
187
+ return [((_("NRC"), _("Missing"), False))]
177
188
 
178
189
  has_nrc_auth = nrc_client.auth is not None if nrc_client else False
179
190
 
@@ -199,20 +210,24 @@ def _test_nrc_config() -> list:
199
210
  error = False
200
211
 
201
212
  try:
202
- nrc_client.list("kanaal")
213
+ response: requests.Response = nrc_client.get("kanaal")
214
+ response.raise_for_status()
203
215
  except requests.ConnectionError:
204
216
  error = True
205
217
  message = _("Could not connect with NRC")
206
- except ClientError as exc:
218
+ except requests.HTTPError as exc:
207
219
  error = True
208
- message = _(
209
- "Cannot retrieve kanalen: HTTP {status_code} - {error_code}"
210
- ).format(status_code=exc.args[0]["status"], error_code=exc.args[0]["code"])
220
+ message = _("Cannot retrieve kanalen: HTTP {status_code}").format(
221
+ status_code=exc.response.status_code
222
+ )
211
223
  else:
212
224
  message = _("Can retrieve kanalen")
213
225
 
214
226
  checks.append((_("NRC connection and authorizations"), message, not error))
215
227
 
228
+ if not check_autorisaties_subscription:
229
+ return checks
230
+
216
231
  # check if there's a subscription for AC notifications
217
232
  has_sub = (
218
233
  Subscription.objects.filter(channels__contains=["autorisaties"])
@@ -1,39 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Run this script from the root of the repository
4
-
5
- set -e
6
-
7
- if [[ -z "$VIRTUAL_ENV" ]]; then
8
- echo "You need to activate your virtual env before running this script"
9
- exit 1
10
- fi
11
-
12
- echo "Generating Swagger schema"
13
- src/manage.py generate_swagger \
14
- ./src/swagger2.0.json \
15
- --overwrite \
16
- --format=json \
17
- --mock-request \
18
- --url https://example.com/api/v1
19
-
20
- echo "Converting Swagger to OpenAPI 3.0..."
21
- npm run convert
22
- patch_content_types
23
-
24
- echo "Generating unresolved OpenAPI 3.0 schema"
25
- use_external_components
26
-
27
- echo "Generating resources document"
28
- src/manage.py generate_swagger \
29
- ./src/resources.md \
30
- --overwrite \
31
- --mock-request \
32
- --url https://example.com/api/v1 \
33
- --to-markdown-table
34
-
35
- echo "Generating autorisaties.md"
36
- src/manage.py generate_autorisaties --output-file ./src/autorisaties.md
37
-
38
- echo "Generating notificaties.md"
39
- src/manage.py generate_notificaties --output-file ./src/notificaties.md
@@ -1,16 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Run this script from the root of the repository
4
-
5
- set -e
6
-
7
- if [[ -z "$VIRTUAL_ENV" ]]; then
8
- echo "You need to activate your virtual env before running this script"
9
- exit 1
10
- fi
11
-
12
- source_file=${1:-./src/openapi.yaml}
13
- output=${2:-./src/openapi_unresolved.yaml}
14
- manage=${MANAGE:-src/manage.py}
15
-
16
- python $manage use_external_components $source_file $output
@@ -1,57 +0,0 @@
1
- from collections import OrderedDict
2
-
3
- from django.utils.translation import gettext_lazy as _
4
-
5
- from drf_yasg import openapi
6
- from rest_framework.views import APIView
7
-
8
- from ..caching.introspection import has_cache_header
9
-
10
- CACHE_REQUEST_HEADERS = [
11
- openapi.Parameter(
12
- name="If-None-Match",
13
- type=openapi.TYPE_STRING,
14
- in_=openapi.IN_HEADER,
15
- required=False,
16
- description=_(
17
- "Perform conditional requests. This header should contain one or "
18
- "multiple ETag values of resources the client has cached. If the "
19
- "current resource ETag value is in this set, then an HTTP 304 "
20
- "empty body will be returned. See "
21
- "[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) "
22
- "for details."
23
- ),
24
- examples={
25
- "oneValue": {
26
- "summary": _("One ETag value"),
27
- "value": '"79054025255fb1a26e4bc422aef54eb4"',
28
- },
29
- "multipleValues": {
30
- "summary": _("Multiple ETag values"),
31
- "value": '"79054025255fb1a26e4bc422aef54eb4", "e4d909c290d0fb1ca068ffaddf22cbd0"',
32
- },
33
- },
34
- )
35
- ]
36
-
37
-
38
- def get_cache_headers(view: APIView) -> OrderedDict:
39
- if not has_cache_header(view):
40
- return OrderedDict()
41
-
42
- return OrderedDict(
43
- (
44
- (
45
- "ETag",
46
- openapi.Schema(
47
- type=openapi.TYPE_STRING,
48
- description=_(
49
- "De ETag berekend op de response body JSON. "
50
- "Indien twee resources exact dezelfde ETag hebben, dan zijn "
51
- "deze resources identiek aan elkaar. Je kan de ETag gebruiken "
52
- "om caching te implementeren."
53
- ),
54
- ),
55
- ),
56
- )
57
- )
@@ -1,126 +0,0 @@
1
- import logging
2
-
3
- from drf_yasg import openapi
4
- from drf_yasg.inspectors.base import NotHandled
5
- from drf_yasg.inspectors.field import FieldInspector, InlineSerializerInspector
6
- from rest_framework import serializers
7
-
8
- from ..serializers import GegevensGroepSerializer, LengthHyperlinkedRelatedField
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- TYPES_MAP = {
14
- str: openapi.TYPE_STRING,
15
- int: openapi.TYPE_INTEGER,
16
- bool: openapi.TYPE_BOOLEAN,
17
- }
18
-
19
-
20
- class ReadOnlyFieldInspector(FieldInspector):
21
- """
22
- Provides conversion for derived ReadOnlyField from model fields.
23
-
24
- This inspector looks at the type hint to determine the type/format of
25
- a model property.
26
- """
27
-
28
- def field_to_swagger_object(
29
- self, field, swagger_object_type, use_references, **kwargs
30
- ):
31
- SwaggerType, ChildSwaggerType = self._get_partial_types(
32
- field, swagger_object_type, use_references, **kwargs
33
- )
34
-
35
- if (
36
- isinstance(field, serializers.ReadOnlyField)
37
- and swagger_object_type == openapi.Schema
38
- ):
39
- prop = getattr(field.parent.Meta.model, field.source)
40
- if not isinstance(prop, property):
41
- return NotHandled
42
-
43
- return_type = prop.fget.__annotations__.get("return")
44
- if return_type is None: # no type annotation, too bad...
45
- logger.debug(
46
- "Missing return type annotation for prop %s on model %s",
47
- field.source,
48
- field.parent.Meta.model,
49
- )
50
- return NotHandled
51
-
52
- type_ = TYPES_MAP.get(return_type)
53
- if type_ is None:
54
- logger.debug("Missing type mapping for %r", return_type)
55
-
56
- return SwaggerType(type=type_ or openapi.TYPE_STRING)
57
-
58
- return NotHandled
59
-
60
-
61
- class HyperlinkedIdentityFieldInspector(FieldInspector):
62
- def field_to_swagger_object(
63
- self, field, swagger_object_type, use_references, **kwargs
64
- ):
65
- SwaggerType, ChildSwaggerType = self._get_partial_types(
66
- field, swagger_object_type, use_references, **kwargs
67
- )
68
-
69
- if (
70
- isinstance(field, serializers.HyperlinkedIdentityField)
71
- and swagger_object_type == openapi.Schema
72
- ):
73
- return SwaggerType(
74
- type=openapi.TYPE_STRING,
75
- format=openapi.FORMAT_URI,
76
- min_length=1,
77
- max_length=1000,
78
- description="URL-referentie naar dit object. Dit is de unieke identificatie en locatie van dit object.",
79
- )
80
-
81
- return NotHandled
82
-
83
-
84
- class HyperlinkedRelatedFieldInspector(FieldInspector):
85
- def field_to_swagger_object(
86
- self, field, swagger_object_type, use_references, **kwargs
87
- ):
88
- SwaggerType, ChildSwaggerType = self._get_partial_types(
89
- field, swagger_object_type, use_references, **kwargs
90
- )
91
-
92
- if (
93
- isinstance(field, LengthHyperlinkedRelatedField)
94
- and swagger_object_type == openapi.Schema
95
- ):
96
- max_length = field.max_length
97
- min_length = field.min_length
98
- return SwaggerType(
99
- type=openapi.TYPE_STRING,
100
- format=openapi.FORMAT_URI,
101
- min_length=min_length,
102
- max_length=max_length,
103
- description=field.help_text,
104
- )
105
-
106
- return NotHandled
107
-
108
-
109
- class GegevensGroepInspector(InlineSerializerInspector):
110
- def process_result(self, result, method_name, obj, **kwargs):
111
- if not isinstance(result, openapi.Schema.OR_REF):
112
- return result
113
-
114
- if not isinstance(obj, GegevensGroepSerializer):
115
- return result
116
-
117
- if method_name != "field_to_swagger_object":
118
- return result
119
-
120
- if not obj.allow_null:
121
- return result
122
-
123
- schema = openapi.resolve_ref(result, self.components)
124
- schema.x_nullable = True
125
-
126
- return result