commonground-api-common 1.13.0__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.13.0.data/scripts/patch_content_types → commonground_api_common-2.4.1.data/scripts/generate_schema +2 -4
- {commonground_api_common-1.13.0.dist-info → commonground_api_common-2.4.1.dist-info}/METADATA +45 -37
- {commonground_api_common-1.13.0.dist-info → commonground_api_common-2.4.1.dist-info}/RECORD +45 -50
- {commonground_api_common-1.13.0.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/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 +92 -73
- vng_api_common/views.py +35 -20
- commonground_api_common-1.13.0.data/scripts/generate_schema +0 -39
- commonground_api_common-1.13.0.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 -91
- 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.13.0.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
|
@@ -221,42 +271,6 @@ class InformatieObjectUniqueValidator(validators.UniqueTogetherValidator):
|
|
221
271
|
super().__call__(attrs)
|
222
272
|
|
223
273
|
|
224
|
-
class ObjectInformatieObjectValidator:
|
225
|
-
"""
|
226
|
-
Validate that the INFORMATIEOBJECT is linked already in the DRC.
|
227
|
-
"""
|
228
|
-
|
229
|
-
message = _(
|
230
|
-
"Het informatieobject is in het DRC nog niet gerelateerd aan dit object."
|
231
|
-
)
|
232
|
-
code = "inconsistent-relation"
|
233
|
-
requires_context = True
|
234
|
-
|
235
|
-
def __call__(self, informatieobject: str, serializer):
|
236
|
-
object_url = serializer.context["parent_object"].get_absolute_api_url(
|
237
|
-
self.request
|
238
|
-
)
|
239
|
-
|
240
|
-
# dynamic so that it can be mocked in tests easily
|
241
|
-
client = get_client(informatieobject)
|
242
|
-
|
243
|
-
try:
|
244
|
-
oios = client.list(
|
245
|
-
"objectinformatieobject",
|
246
|
-
query_params={
|
247
|
-
"informatieobject": informatieobject,
|
248
|
-
"object": object_url,
|
249
|
-
},
|
250
|
-
)
|
251
|
-
except requests.HTTPError as exc:
|
252
|
-
raise serializers.ValidationError(
|
253
|
-
exc.args[0], code="relation-validation-error"
|
254
|
-
) from exc
|
255
|
-
|
256
|
-
if len(oios) == 0:
|
257
|
-
raise serializers.ValidationError(self.message, code=self.code)
|
258
|
-
|
259
|
-
|
260
274
|
@deconstructible
|
261
275
|
class UntilNowValidator:
|
262
276
|
"""
|
@@ -302,7 +316,7 @@ class UniekeIdentificatieValidator:
|
|
302
316
|
:param identificatie_field: naam van het veld dat de identificatie bevat
|
303
317
|
"""
|
304
318
|
|
305
|
-
message = _("Deze identificatie bestaat al binnen de organisatie")
|
319
|
+
message = _("Deze identificatie ({identificatie}) bestaat al binnen de organisatie")
|
306
320
|
code = "identificatie-niet-uniek"
|
307
321
|
requires_context = True
|
308
322
|
|
@@ -345,7 +359,12 @@ class UniekeIdentificatieValidator:
|
|
345
359
|
|
346
360
|
if combination_exists:
|
347
361
|
raise serializers.ValidationError(
|
348
|
-
{
|
362
|
+
{
|
363
|
+
self.identificatie_field: self.message.format(
|
364
|
+
identificatie=identificatie
|
365
|
+
)
|
366
|
+
},
|
367
|
+
code=self.code,
|
349
368
|
)
|
350
369
|
|
351
370
|
|
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
|
@@ -1,121 +0,0 @@
|
|
1
|
-
from collections import OrderedDict
|
2
|
-
|
3
|
-
from django.utils.translation import gettext as _
|
4
|
-
|
5
|
-
from drf_extra_fields.fields import Base64FieldMixin
|
6
|
-
from drf_yasg import openapi
|
7
|
-
from drf_yasg.inspectors import (
|
8
|
-
CamelCaseJSONFilter,
|
9
|
-
FieldInspector,
|
10
|
-
NotHandled,
|
11
|
-
ViewInspector,
|
12
|
-
)
|
13
|
-
from drf_yasg.utils import filter_none, get_serializer_ref_name
|
14
|
-
from rest_framework import serializers
|
15
|
-
|
16
|
-
|
17
|
-
class FileFieldInspector(CamelCaseJSONFilter):
|
18
|
-
def get_schema(self, serializer):
|
19
|
-
if self.method not in ViewInspector.body_methods:
|
20
|
-
return NotHandled
|
21
|
-
|
22
|
-
# only do this if there are base64 mixin fields
|
23
|
-
if any(
|
24
|
-
isinstance(field, Base64FieldMixin) for field in serializer.fields.values()
|
25
|
-
):
|
26
|
-
return self.probe_field_inspectors(serializer, openapi.Schema, True)
|
27
|
-
|
28
|
-
return NotHandled
|
29
|
-
|
30
|
-
def field_to_swagger_object(
|
31
|
-
self, field, swagger_object_type, use_references, **kwargs
|
32
|
-
):
|
33
|
-
if isinstance(field, serializers.Serializer):
|
34
|
-
return self._serializer_to_swagger_object(
|
35
|
-
field, swagger_object_type, use_references, **kwargs
|
36
|
-
)
|
37
|
-
|
38
|
-
if not isinstance(field, Base64FieldMixin):
|
39
|
-
return NotHandled
|
40
|
-
|
41
|
-
SwaggerType, ChildSwaggerType = self._get_partial_types(
|
42
|
-
field, swagger_object_type, use_references, **kwargs
|
43
|
-
)
|
44
|
-
|
45
|
-
type_b64 = SwaggerType(
|
46
|
-
type=openapi.TYPE_STRING,
|
47
|
-
format=openapi.FORMAT_BASE64,
|
48
|
-
description=_("Base64 encoded binary content."),
|
49
|
-
)
|
50
|
-
type_uri = SwaggerType(
|
51
|
-
type=openapi.TYPE_STRING,
|
52
|
-
read_only=True,
|
53
|
-
format=openapi.FORMAT_URI,
|
54
|
-
description=_("Download URL of the binary content."),
|
55
|
-
)
|
56
|
-
|
57
|
-
if swagger_object_type == openapi.Schema:
|
58
|
-
# on writes, it's always b64
|
59
|
-
if self.method in ViewInspector.body_methods:
|
60
|
-
return type_b64
|
61
|
-
|
62
|
-
# if not representing in base64, it's a link
|
63
|
-
return type_uri if not field.represent_in_base64 else type_b64
|
64
|
-
|
65
|
-
return NotHandled
|
66
|
-
|
67
|
-
def _serializer_to_swagger_object(
|
68
|
-
self, serializer, swagger_object_type, use_references, **kwargs
|
69
|
-
):
|
70
|
-
if self.method not in ViewInspector.body_methods:
|
71
|
-
return NotHandled
|
72
|
-
|
73
|
-
if not any(
|
74
|
-
isinstance(field, Base64FieldMixin) for field in serializer.fields.values()
|
75
|
-
):
|
76
|
-
return NotHandled
|
77
|
-
|
78
|
-
SwaggerType, ChildSwaggerType = self._get_partial_types(
|
79
|
-
serializer, swagger_object_type, use_references, **kwargs
|
80
|
-
)
|
81
|
-
|
82
|
-
ref_name = get_serializer_ref_name(serializer)
|
83
|
-
ref_name = f"{ref_name}Data" if ref_name else None
|
84
|
-
|
85
|
-
def make_schema_definition():
|
86
|
-
properties = OrderedDict()
|
87
|
-
required = []
|
88
|
-
for property_name, child in serializer.fields.items():
|
89
|
-
prop_kwargs = {"read_only": bool(child.read_only) or None}
|
90
|
-
prop_kwargs = filter_none(prop_kwargs)
|
91
|
-
|
92
|
-
child_schema = self.probe_field_inspectors(
|
93
|
-
child, ChildSwaggerType, use_references, **prop_kwargs
|
94
|
-
)
|
95
|
-
properties[property_name] = child_schema
|
96
|
-
|
97
|
-
if child.required and not getattr(child_schema, "read_only", False):
|
98
|
-
required.append(property_name)
|
99
|
-
|
100
|
-
result = SwaggerType(
|
101
|
-
type=openapi.TYPE_OBJECT,
|
102
|
-
properties=properties,
|
103
|
-
required=required or None,
|
104
|
-
)
|
105
|
-
if not ref_name and "title" in result:
|
106
|
-
# on an inline model, the title is derived from the field name
|
107
|
-
# but is visually displayed like the model name, which is confusing
|
108
|
-
# it is better to just remove title from inline models
|
109
|
-
del result.title
|
110
|
-
|
111
|
-
# Provide an option to add manual paremeters to a schema
|
112
|
-
# for example, to add examples
|
113
|
-
# self.add_manual_fields(serializer, result)
|
114
|
-
return self.process_result(result, None, None)
|
115
|
-
|
116
|
-
if not ref_name or not use_references:
|
117
|
-
return make_schema_definition()
|
118
|
-
|
119
|
-
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
120
|
-
definitions.setdefault(ref_name, make_schema_definition)
|
121
|
-
return openapi.SchemaRef(definitions, ref_name)
|