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.
Files changed (65) hide show
  1. commonground_api_common-1.13.0.data/scripts/patch_content_types → commonground_api_common-2.4.1.data/scripts/generate_schema +2 -4
  2. {commonground_api_common-1.13.0.dist-info → commonground_api_common-2.4.1.dist-info}/METADATA +45 -37
  3. {commonground_api_common-1.13.0.dist-info → commonground_api_common-2.4.1.dist-info}/RECORD +45 -50
  4. {commonground_api_common-1.13.0.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/client.py +61 -29
  16. vng_api_common/conf/api.py +33 -48
  17. vng_api_common/contrib/setup_configuration/models.py +32 -0
  18. vng_api_common/contrib/setup_configuration/steps.py +46 -0
  19. vng_api_common/extensions/file.py +26 -0
  20. vng_api_common/extensions/gegevensgroep.py +16 -0
  21. vng_api_common/extensions/geojson.py +270 -0
  22. vng_api_common/extensions/hyperlink.py +37 -0
  23. vng_api_common/extensions/polymorphic.py +68 -0
  24. vng_api_common/extensions/query.py +20 -0
  25. vng_api_common/filters.py +0 -1
  26. vng_api_common/generators.py +12 -113
  27. vng_api_common/middleware.py +1 -227
  28. vng_api_common/migrations/0006_delete_apicredential.py +120 -0
  29. vng_api_common/mocks.py +4 -1
  30. vng_api_common/models.py +10 -111
  31. vng_api_common/notifications/api/views.py +8 -8
  32. vng_api_common/notifications/handlers.py +8 -3
  33. vng_api_common/notifications/migrations/0011_remove_subscription_config_and_more.py +23 -0
  34. vng_api_common/oas.py +6 -10
  35. vng_api_common/pagination.py +10 -0
  36. vng_api_common/routers.py +3 -3
  37. vng_api_common/schema.py +414 -158
  38. vng_api_common/tests/schema.py +13 -0
  39. vng_api_common/utils.py +0 -22
  40. vng_api_common/validators.py +92 -73
  41. vng_api_common/views.py +35 -20
  42. commonground_api_common-1.13.0.data/scripts/generate_schema +0 -39
  43. commonground_api_common-1.13.0.data/scripts/use_external_components +0 -16
  44. vng_api_common/inspectors/cache.py +0 -57
  45. vng_api_common/inspectors/fields.py +0 -126
  46. vng_api_common/inspectors/files.py +0 -121
  47. vng_api_common/inspectors/geojson.py +0 -360
  48. vng_api_common/inspectors/polymorphic.py +0 -72
  49. vng_api_common/inspectors/query.py +0 -91
  50. vng_api_common/inspectors/utils.py +0 -40
  51. vng_api_common/inspectors/view.py +0 -547
  52. vng_api_common/management/commands/generate_autorisaties.py +0 -43
  53. vng_api_common/management/commands/generate_notificaties.py +0 -40
  54. vng_api_common/management/commands/generate_swagger.py +0 -197
  55. vng_api_common/management/commands/patch_error_contenttypes.py +0 -61
  56. vng_api_common/management/commands/use_external_components.py +0 -94
  57. vng_api_common/notifications/constants.py +0 -3
  58. vng_api_common/notifications/models.py +0 -97
  59. vng_api_common/templates/vng_api_common/api_schema_to_markdown_table.md +0 -16
  60. vng_api_common/templates/vng_api_common/autorisaties.md +0 -15
  61. vng_api_common/templates/vng_api_common/notificaties.md +0 -24
  62. {commonground_api_common-1.13.0.dist-info → commonground_api_common-2.4.1.dist-info}/top_level.txt +0 -0
  63. /vng_api_common/{inspectors → contrib}/__init__.py +0 -0
  64. /vng_api_common/{management → contrib/setup_configuration}/__init__.py +0 -0
  65. /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
@@ -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
- {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,
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
- 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
@@ -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)