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.
- commonground_api_common-1.12.2.data/scripts/patch_content_types → commonground_api_common-2.4.1.data/scripts/generate_schema +2 -4
- {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/METADATA +47 -40
- {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/RECORD +47 -52
- {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/WHEEL +1 -1
- vng_api_common/__init__.py +1 -1
- vng_api_common/admin.py +1 -20
- vng_api_common/api/views.py +1 -0
- vng_api_common/apps.py +44 -26
- vng_api_common/audittrails/utils.py +44 -0
- vng_api_common/authorizations/admin.py +1 -1
- vng_api_common/authorizations/middleware.py +244 -0
- vng_api_common/authorizations/migrations/0016_remove_authorizationsconfig_api_root_and_more.py +76 -0
- vng_api_common/authorizations/models.py +62 -3
- vng_api_common/authorizations/utils.py +17 -0
- vng_api_common/authorizations/validators.py +5 -11
- vng_api_common/caching/etags.py +2 -1
- vng_api_common/client.py +61 -29
- vng_api_common/conf/api.py +33 -48
- vng_api_common/contrib/setup_configuration/models.py +32 -0
- vng_api_common/contrib/setup_configuration/steps.py +46 -0
- vng_api_common/extensions/file.py +26 -0
- vng_api_common/extensions/gegevensgroep.py +16 -0
- vng_api_common/extensions/geojson.py +270 -0
- vng_api_common/extensions/hyperlink.py +37 -0
- vng_api_common/extensions/polymorphic.py +68 -0
- vng_api_common/extensions/query.py +20 -0
- vng_api_common/filters.py +0 -1
- vng_api_common/generators.py +12 -113
- vng_api_common/middleware.py +1 -227
- vng_api_common/migrations/0006_delete_apicredential.py +120 -0
- vng_api_common/mocks.py +4 -1
- vng_api_common/models.py +10 -111
- vng_api_common/notifications/api/views.py +8 -8
- vng_api_common/notifications/handlers.py +8 -3
- vng_api_common/notifications/migrations/0011_remove_subscription_config_and_more.py +23 -0
- vng_api_common/oas.py +6 -10
- vng_api_common/pagination.py +10 -0
- vng_api_common/routers.py +3 -3
- vng_api_common/schema.py +414 -158
- vng_api_common/tests/schema.py +13 -0
- vng_api_common/utils.py +0 -22
- vng_api_common/validators.py +111 -113
- vng_api_common/views.py +35 -20
- commonground_api_common-1.12.2.data/scripts/generate_schema +0 -39
- commonground_api_common-1.12.2.data/scripts/use_external_components +0 -16
- vng_api_common/inspectors/cache.py +0 -57
- vng_api_common/inspectors/fields.py +0 -126
- vng_api_common/inspectors/files.py +0 -121
- vng_api_common/inspectors/geojson.py +0 -360
- vng_api_common/inspectors/polymorphic.py +0 -72
- vng_api_common/inspectors/query.py +0 -96
- vng_api_common/inspectors/utils.py +0 -40
- vng_api_common/inspectors/view.py +0 -547
- vng_api_common/management/commands/generate_autorisaties.py +0 -43
- vng_api_common/management/commands/generate_notificaties.py +0 -40
- vng_api_common/management/commands/generate_swagger.py +0 -197
- vng_api_common/management/commands/patch_error_contenttypes.py +0 -61
- vng_api_common/management/commands/use_external_components.py +0 -94
- vng_api_common/notifications/constants.py +0 -3
- vng_api_common/notifications/models.py +0 -97
- vng_api_common/templates/vng_api_common/api_schema_to_markdown_table.md +0 -16
- vng_api_common/templates/vng_api_common/autorisaties.md +0 -15
- vng_api_common/templates/vng_api_common/notificaties.md +0 -24
- {commonground_api_common-1.12.2.dist-info → commonground_api_common-2.4.1.dist-info}/top_level.txt +0 -0
- /vng_api_common/{inspectors → contrib}/__init__.py +0 -0
- /vng_api_common/{management → contrib/setup_configuration}/__init__.py +0 -0
- /vng_api_common/{management/commands → extensions}/__init__.py +0 -0
vng_api_common/validators.py
CHANGED
@@ -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 .
|
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
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
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
|
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
|
331
|
+
if instance:
|
332
332
|
# In case of a partial update
|
333
|
-
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 =
|
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
|
-
|
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
|
-
{
|
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
|
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
|
383
|
+
if not instance:
|
386
384
|
return
|
387
385
|
|
388
|
-
current_value = getattr(
|
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
|
-
|
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
|
-
(
|
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.
|
150
|
-
"
|
161
|
+
response: requests.Response = ac_client.get(
|
162
|
+
"applicaties", params={"clientIds": client_id}
|
151
163
|
)
|
152
|
-
|
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("
|
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
|
-
|
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.
|
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
|
218
|
+
except requests.HTTPError as exc:
|
207
219
|
error = True
|
208
|
-
message = _(
|
209
|
-
|
210
|
-
)
|
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
|