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/conf/api.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
__all__ = [
|
2
2
|
"API_VERSION",
|
3
3
|
"BASE_REST_FRAMEWORK",
|
4
|
-
"
|
4
|
+
"BASE_SPECTACULAR_SETTINGS",
|
5
5
|
"COMMON_SPEC",
|
6
6
|
"LINK_FETCHER",
|
7
|
-
"ZDS_CLIENT_CLASS",
|
8
7
|
"GEMMA_URL_TEMPLATE",
|
9
8
|
"GEMMA_URL_COMPONENTTYPE",
|
10
9
|
"GEMMA_URL_INFORMATIEMODEL",
|
@@ -13,43 +12,30 @@ __all__ = [
|
|
13
12
|
"NOTIFICATIONS_KANAAL",
|
14
13
|
"NOTIFICATIONS_DISABLED",
|
15
14
|
"JWT_LEEWAY",
|
15
|
+
"SECURITY_DEFINITION_NAME",
|
16
16
|
"COMMONGROUND_API_COMMON_GET_DOMAIN",
|
17
|
+
"JWT_SPECTACULAR_SETTINGS",
|
17
18
|
]
|
18
19
|
|
19
20
|
API_VERSION = "1.0.0-rc1" # semantic version
|
20
21
|
|
22
|
+
SECURITY_DEFINITION_NAME = "JWT-Claims"
|
23
|
+
|
21
24
|
BASE_REST_FRAMEWORK = {
|
25
|
+
"DEFAULT_SCHEMA_CLASS": "vng_api_common.schema.AutoSchema",
|
22
26
|
"DEFAULT_RENDERER_CLASSES": (
|
23
27
|
"djangorestframework_camel_case.render.CamelCaseJSONRenderer",
|
24
28
|
),
|
25
29
|
"DEFAULT_PARSER_CLASSES": (
|
26
30
|
"djangorestframework_camel_case.parser.CamelCaseJSONParser",
|
27
31
|
),
|
28
|
-
"DEFAULT_AUTHENTICATION_CLASSES": (
|
29
|
-
# 'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
30
|
-
# 'rest_framework.authentication.SessionAuthentication',
|
31
|
-
# 'rest_framework.authentication.BasicAuthentication'
|
32
|
-
),
|
33
32
|
# there is no authentication of 'end-users', only authorization (via JWT)
|
34
33
|
# of applications
|
35
34
|
"DEFAULT_AUTHENTICATION_CLASSES": (),
|
36
|
-
# 'DEFAULT_PERMISSION_CLASSES': (
|
37
|
-
# 'oauth2_provider.contrib.rest_framework.TokenHasReadWriteScope',
|
38
|
-
# # 'rest_framework.permissions.IsAuthenticated',
|
39
|
-
# # 'rest_framework.permissions.AllowAny',
|
40
|
-
# ),
|
41
35
|
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
|
42
|
-
|
43
|
-
# # Generic view behavior
|
44
|
-
# 'DEFAULT_PAGINATION_CLASS': 'ztc.api.utils.pagination.HALPagination',
|
45
|
-
"DEFAULT_FILTER_BACKENDS": (
|
46
|
-
"vng_api_common.filters.Backend",
|
47
|
-
# 'rest_framework.filters.SearchFilter',
|
48
|
-
# 'rest_framework.filters.OrderingFilter',
|
49
|
-
),
|
36
|
+
"DEFAULT_FILTER_BACKENDS": ("vng_api_common.filters.Backend",),
|
50
37
|
#
|
51
38
|
# # Filtering
|
52
|
-
# 'SEARCH_PARAM': 'zoek', # 'search',
|
53
39
|
"ORDERING_PARAM": "ordering", # 'ordering',
|
54
40
|
#
|
55
41
|
# Versioning
|
@@ -61,41 +47,40 @@ BASE_REST_FRAMEWORK = {
|
|
61
47
|
"EXCEPTION_HANDLER": "vng_api_common.views.exception_handler",
|
62
48
|
"TEST_REQUEST_DEFAULT_FORMAT": "json",
|
63
49
|
}
|
64
|
-
|
65
|
-
BASE_SWAGGER_SETTINGS = {
|
50
|
+
BASE_SPECTACULAR_SETTINGS = {
|
66
51
|
"DEFAULT_GENERATOR_CLASS": "vng_api_common.generators.OpenAPISchemaGenerator",
|
67
|
-
"
|
68
|
-
"
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
"
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
52
|
+
"SERVE_INCLUDE_SCHEMA": False,
|
53
|
+
"POSTPROCESSING_HOOKS": [
|
54
|
+
"drf_spectacular.hooks.postprocess_schema_enums",
|
55
|
+
"drf_spectacular.contrib.djangorestframework_camel_case.camelize_serializer_fields",
|
56
|
+
],
|
57
|
+
"SCHEMA_PATH_PREFIX": "/api/v1",
|
58
|
+
}
|
59
|
+
|
60
|
+
# add to SPECTACULAR_SETTINGS if you are using the AuthMiddleware
|
61
|
+
JWT_SPECTACULAR_SETTINGS = {
|
62
|
+
"APPEND_COMPONENTS": {
|
63
|
+
"securitySchemes": {
|
64
|
+
SECURITY_DEFINITION_NAME: {
|
65
|
+
"type": "http",
|
66
|
+
"bearerFormat": "JWT",
|
67
|
+
"scheme": "bearer",
|
68
|
+
}
|
69
|
+
},
|
70
|
+
},
|
71
|
+
"SECURITY": [
|
72
|
+
{
|
73
|
+
SECURITY_DEFINITION_NAME: [],
|
74
|
+
}
|
75
|
+
],
|
90
76
|
}
|
91
77
|
|
78
|
+
|
92
79
|
REDOC_SETTINGS = {"EXPAND_RESPONSES": "200,201", "SPEC_URL": "openapi.json"}
|
93
80
|
|
94
81
|
# See: https://github.com/Rebilly/ReDoc#redoc-options-object
|
95
82
|
LINK_FETCHER = "requests.get"
|
96
83
|
|
97
|
-
ZDS_CLIENT_CLASS = "zds_client.Client"
|
98
|
-
|
99
84
|
GEMMA_URL_TEMPLATE = "https://www.gemmaonline.nl/index.php/{informatiemodel}_{versie}/doc/{componenttype}/{component}"
|
100
85
|
GEMMA_URL_COMPONENTTYPE = "objecttype"
|
101
86
|
GEMMA_URL_INFORMATIEMODEL = "Rgbz"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from django_setup_configuration.models import ConfigurationModel
|
2
|
+
from pydantic import Field
|
3
|
+
|
4
|
+
from vng_api_common.authorizations.models import Applicatie
|
5
|
+
from vng_api_common.models import JWTSecret
|
6
|
+
|
7
|
+
|
8
|
+
class SingleJWTSecretConfigurationModel(ConfigurationModel):
|
9
|
+
class Meta:
|
10
|
+
django_model_refs = {
|
11
|
+
JWTSecret: [
|
12
|
+
"identifier",
|
13
|
+
"secret",
|
14
|
+
]
|
15
|
+
}
|
16
|
+
|
17
|
+
|
18
|
+
class JWTSecretsConfigurationModel(ConfigurationModel):
|
19
|
+
items: list[SingleJWTSecretConfigurationModel] = Field(default_factory=list)
|
20
|
+
|
21
|
+
|
22
|
+
class SingleApplicatieConfigurationModel(ConfigurationModel):
|
23
|
+
client_ids: list[str]
|
24
|
+
|
25
|
+
class Meta:
|
26
|
+
django_model_refs = {
|
27
|
+
Applicatie: ["uuid", "client_ids", "label", "heeft_alle_autorisaties"]
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
class ApplicatieConfigurationModel(ConfigurationModel):
|
32
|
+
items: list[SingleApplicatieConfigurationModel] = Field(default_factory=list)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from django_setup_configuration.configuration import BaseConfigurationStep
|
2
|
+
|
3
|
+
from vng_api_common.authorizations.models import Applicatie
|
4
|
+
from vng_api_common.models import JWTSecret
|
5
|
+
|
6
|
+
from .models import ApplicatieConfigurationModel, JWTSecretsConfigurationModel
|
7
|
+
|
8
|
+
|
9
|
+
class JWTSecretsConfigurationStep(BaseConfigurationStep[JWTSecretsConfigurationModel]):
|
10
|
+
"""
|
11
|
+
Configure credentials for Applications that need access
|
12
|
+
"""
|
13
|
+
|
14
|
+
verbose_name = "Configuration to create credentials"
|
15
|
+
config_model = JWTSecretsConfigurationModel
|
16
|
+
namespace = "vng_api_common_credentials"
|
17
|
+
enable_setting = "vng_api_common_credentials_config_enable"
|
18
|
+
|
19
|
+
def execute(self, model: JWTSecretsConfigurationModel):
|
20
|
+
for config in model.items:
|
21
|
+
JWTSecret.objects.update_or_create(
|
22
|
+
identifier=config.identifier,
|
23
|
+
defaults={"secret": config.secret},
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
class ApplicatieConfigurationStep(BaseConfigurationStep[ApplicatieConfigurationModel]):
|
28
|
+
"""
|
29
|
+
Configure Applicatie used for authorization
|
30
|
+
"""
|
31
|
+
|
32
|
+
verbose_name = "Configuration to create applicaties"
|
33
|
+
config_model = ApplicatieConfigurationModel
|
34
|
+
namespace = "vng_api_common_applicaties"
|
35
|
+
enable_setting = "vng_api_common_applicaties_config_enable"
|
36
|
+
|
37
|
+
def execute(self, model: ApplicatieConfigurationModel):
|
38
|
+
for config in model.items:
|
39
|
+
Applicatie.objects.update_or_create(
|
40
|
+
uuid=config.uuid,
|
41
|
+
defaults={
|
42
|
+
"client_ids": config.client_ids,
|
43
|
+
"label": config.label,
|
44
|
+
"heeft_alle_autorisaties": config.heeft_alle_autorisaties,
|
45
|
+
},
|
46
|
+
)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
2
|
+
|
3
|
+
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
|
4
|
+
from drf_spectacular.openapi import OpenApiTypes
|
5
|
+
from drf_spectacular.plumbing import build_basic_type
|
6
|
+
|
7
|
+
|
8
|
+
class Base64FileFileFieldExtension(OpenApiSerializerFieldExtension):
|
9
|
+
target_class = "drf_extra_fields.fields.Base64FileField"
|
10
|
+
match_subclasses = True
|
11
|
+
|
12
|
+
def map_serializer_field(self, auto_schema, direction):
|
13
|
+
base64_schema = {
|
14
|
+
**build_basic_type(OpenApiTypes.BYTE),
|
15
|
+
"description": _("Base64 encoded binary content."),
|
16
|
+
}
|
17
|
+
|
18
|
+
uri_schema = {
|
19
|
+
**build_basic_type(OpenApiTypes.URI),
|
20
|
+
"description": _("Download URL of the binary content."),
|
21
|
+
}
|
22
|
+
|
23
|
+
if direction == "request":
|
24
|
+
return base64_schema
|
25
|
+
elif direction == "response":
|
26
|
+
return uri_schema if not self.target.represent_in_base64 else base64_schema
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from drf_spectacular.extensions import OpenApiSerializerExtension
|
2
|
+
from drf_spectacular.openapi import AutoSchema
|
3
|
+
|
4
|
+
|
5
|
+
class GegevensGroepFieldExtension(OpenApiSerializerExtension):
|
6
|
+
target_class = "vng_api_common.serializers.GegevensGroepSerializer"
|
7
|
+
match_subclasses = True
|
8
|
+
|
9
|
+
def map_serializer(self, auto_schema: AutoSchema, direction):
|
10
|
+
schema = auto_schema._map_serializer(
|
11
|
+
self.target, direction, bypass_extensions=True
|
12
|
+
)
|
13
|
+
|
14
|
+
del schema["description"]
|
15
|
+
|
16
|
+
return schema
|
@@ -0,0 +1,270 @@
|
|
1
|
+
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
|
2
|
+
from drf_spectacular.plumbing import ResolvedComponent
|
3
|
+
|
4
|
+
|
5
|
+
class GeometryFieldExtension(OpenApiSerializerFieldExtension):
|
6
|
+
target_class = "rest_framework_gis.fields.GeometryField"
|
7
|
+
match_subclasses = True
|
8
|
+
priority = 1
|
9
|
+
|
10
|
+
def get_name(self):
|
11
|
+
return "GeoJSONGeometry"
|
12
|
+
|
13
|
+
def map_serializer_field(self, auto_schema, direction):
|
14
|
+
geometry = ResolvedComponent(
|
15
|
+
name="Geometry",
|
16
|
+
type=ResolvedComponent.SCHEMA,
|
17
|
+
object="Geometry",
|
18
|
+
schema={
|
19
|
+
"type": "object",
|
20
|
+
"title": "Geometry",
|
21
|
+
"description": "GeoJSON geometry",
|
22
|
+
"required": ["type"],
|
23
|
+
"externalDocs": {
|
24
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1"
|
25
|
+
},
|
26
|
+
"properties": {
|
27
|
+
"type": {
|
28
|
+
"type": "string",
|
29
|
+
"enum": [
|
30
|
+
"Point",
|
31
|
+
"MultiPoint",
|
32
|
+
"LineString",
|
33
|
+
"MultiLineString",
|
34
|
+
"Polygon",
|
35
|
+
"MultiPolygon",
|
36
|
+
"Feature",
|
37
|
+
"FeatureCollection",
|
38
|
+
"GeometryCollection",
|
39
|
+
],
|
40
|
+
"description": "The geometry type",
|
41
|
+
}
|
42
|
+
},
|
43
|
+
},
|
44
|
+
)
|
45
|
+
point_2d = ResolvedComponent(
|
46
|
+
name="Point2D",
|
47
|
+
type=ResolvedComponent.SCHEMA,
|
48
|
+
object="Point2D",
|
49
|
+
schema={
|
50
|
+
"type": "array",
|
51
|
+
"title": "Point2D",
|
52
|
+
"description": "A 2D point",
|
53
|
+
"items": {"type": "number"},
|
54
|
+
"maxItems": 2,
|
55
|
+
"minItems": 2,
|
56
|
+
},
|
57
|
+
)
|
58
|
+
point = ResolvedComponent(
|
59
|
+
name="Point",
|
60
|
+
type=ResolvedComponent.SCHEMA,
|
61
|
+
object="Point",
|
62
|
+
schema={
|
63
|
+
"type": "object",
|
64
|
+
"description": "GeoJSON point geometry",
|
65
|
+
"externalDocs": {
|
66
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.2"
|
67
|
+
},
|
68
|
+
"allOf": [
|
69
|
+
geometry.ref,
|
70
|
+
{
|
71
|
+
"type": "object",
|
72
|
+
"required": ["coordinates"],
|
73
|
+
"properties": {"coordinates": point_2d.ref},
|
74
|
+
},
|
75
|
+
],
|
76
|
+
},
|
77
|
+
)
|
78
|
+
|
79
|
+
multi_point = ResolvedComponent(
|
80
|
+
name="MultiPoint",
|
81
|
+
type=ResolvedComponent.SCHEMA,
|
82
|
+
object="MultiPoint",
|
83
|
+
schema={
|
84
|
+
"type": "object",
|
85
|
+
"description": "GeoJSON multi-point geometry",
|
86
|
+
"externalDocs": {
|
87
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.3"
|
88
|
+
},
|
89
|
+
"allOf": [
|
90
|
+
geometry.ref,
|
91
|
+
{
|
92
|
+
"type": "object",
|
93
|
+
"required": ["coordinates"],
|
94
|
+
"properties": {
|
95
|
+
"coordinates": {"type": "array", "items": point_2d.ref}
|
96
|
+
},
|
97
|
+
},
|
98
|
+
],
|
99
|
+
},
|
100
|
+
)
|
101
|
+
|
102
|
+
line_string = ResolvedComponent(
|
103
|
+
name="LineString",
|
104
|
+
type=ResolvedComponent.SCHEMA,
|
105
|
+
object="LineString",
|
106
|
+
schema={
|
107
|
+
"type": "object",
|
108
|
+
"description": "GeoJSON line-string geometry",
|
109
|
+
"externalDocs": {
|
110
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.4"
|
111
|
+
},
|
112
|
+
"allOf": [
|
113
|
+
geometry.ref,
|
114
|
+
{
|
115
|
+
"type": "object",
|
116
|
+
"required": ["coordinates"],
|
117
|
+
"properties": {
|
118
|
+
"coordinates": {
|
119
|
+
"type": "array",
|
120
|
+
"items": point_2d.ref,
|
121
|
+
"minItems": 2,
|
122
|
+
}
|
123
|
+
},
|
124
|
+
},
|
125
|
+
],
|
126
|
+
},
|
127
|
+
)
|
128
|
+
|
129
|
+
multi_line_string = ResolvedComponent(
|
130
|
+
name="MultiLineString",
|
131
|
+
type=ResolvedComponent.SCHEMA,
|
132
|
+
object="MultiLineString",
|
133
|
+
schema={
|
134
|
+
"type": "object",
|
135
|
+
"description": "GeoJSON multi-line-string geometry",
|
136
|
+
"externalDocs": {
|
137
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.5"
|
138
|
+
},
|
139
|
+
"allOf": [
|
140
|
+
geometry.ref,
|
141
|
+
{
|
142
|
+
"type": "object",
|
143
|
+
"required": ["coordinates"],
|
144
|
+
"properties": {
|
145
|
+
"coordinates": {
|
146
|
+
"type": "array",
|
147
|
+
"items": {
|
148
|
+
"type": "array",
|
149
|
+
"items": point_2d.ref,
|
150
|
+
},
|
151
|
+
}
|
152
|
+
},
|
153
|
+
},
|
154
|
+
],
|
155
|
+
},
|
156
|
+
)
|
157
|
+
|
158
|
+
polygon = ResolvedComponent(
|
159
|
+
name="Polygon",
|
160
|
+
type=ResolvedComponent.SCHEMA,
|
161
|
+
object="Polygon",
|
162
|
+
schema={
|
163
|
+
"type": "object",
|
164
|
+
"description": "GeoJSON polygon geometry",
|
165
|
+
"externalDocs": {
|
166
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.6"
|
167
|
+
},
|
168
|
+
"allOf": [
|
169
|
+
geometry.ref,
|
170
|
+
{
|
171
|
+
"type": "object",
|
172
|
+
"required": ["coordinates"],
|
173
|
+
"properties": {
|
174
|
+
"coordinates": {
|
175
|
+
"type": "array",
|
176
|
+
"items": {
|
177
|
+
"type": "array",
|
178
|
+
"items": point_2d.ref,
|
179
|
+
},
|
180
|
+
}
|
181
|
+
},
|
182
|
+
},
|
183
|
+
],
|
184
|
+
},
|
185
|
+
)
|
186
|
+
|
187
|
+
multi_polygon = ResolvedComponent(
|
188
|
+
name="MultiPolygon",
|
189
|
+
type=ResolvedComponent.SCHEMA,
|
190
|
+
object="MultiPolygon",
|
191
|
+
schema={
|
192
|
+
"type": "object",
|
193
|
+
"description": "GeoJSON multi-polygon geometry",
|
194
|
+
"externalDocs": {
|
195
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.7"
|
196
|
+
},
|
197
|
+
"allOf": [
|
198
|
+
geometry.ref,
|
199
|
+
{
|
200
|
+
"type": "object",
|
201
|
+
"required": ["coordinates"],
|
202
|
+
"properties": {
|
203
|
+
"coordinates": {
|
204
|
+
"type": "array",
|
205
|
+
"items": {
|
206
|
+
"type": "array",
|
207
|
+
"items": {
|
208
|
+
"type": "array",
|
209
|
+
"items": point_2d.ref,
|
210
|
+
},
|
211
|
+
},
|
212
|
+
}
|
213
|
+
},
|
214
|
+
},
|
215
|
+
],
|
216
|
+
},
|
217
|
+
)
|
218
|
+
|
219
|
+
geometry_collection = ResolvedComponent(
|
220
|
+
name="GeometryCollection",
|
221
|
+
type=ResolvedComponent.SCHEMA,
|
222
|
+
object="GeometryCollection",
|
223
|
+
schema={
|
224
|
+
"type": "object",
|
225
|
+
"description": "GeoJSON geometry collection",
|
226
|
+
"externalDocs": {
|
227
|
+
"url": "https://tools.ietf.org/html/rfc7946#section-3.1.8"
|
228
|
+
},
|
229
|
+
"allOf": [
|
230
|
+
geometry.ref,
|
231
|
+
{
|
232
|
+
"type": "object",
|
233
|
+
"required": ["geometries"],
|
234
|
+
"properties": {
|
235
|
+
"geometries": {"type": "array", "items": geometry.ref}
|
236
|
+
},
|
237
|
+
},
|
238
|
+
],
|
239
|
+
},
|
240
|
+
)
|
241
|
+
|
242
|
+
for component in [
|
243
|
+
geometry,
|
244
|
+
point_2d,
|
245
|
+
point,
|
246
|
+
multi_point,
|
247
|
+
line_string,
|
248
|
+
multi_line_string,
|
249
|
+
polygon,
|
250
|
+
multi_polygon,
|
251
|
+
geometry_collection,
|
252
|
+
]:
|
253
|
+
auto_schema.registry.register_on_missing(component)
|
254
|
+
|
255
|
+
return {
|
256
|
+
"title": "GeoJSONGeometry",
|
257
|
+
"type": "object",
|
258
|
+
"oneOf": [
|
259
|
+
point.ref,
|
260
|
+
multi_point.ref,
|
261
|
+
line_string.ref,
|
262
|
+
multi_line_string.ref,
|
263
|
+
polygon.ref,
|
264
|
+
multi_polygon.ref,
|
265
|
+
geometry_collection.ref,
|
266
|
+
],
|
267
|
+
"discriminator": {
|
268
|
+
"propertyName": "type",
|
269
|
+
},
|
270
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
2
|
+
|
3
|
+
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
|
4
|
+
from drf_spectacular.openapi import AutoSchema
|
5
|
+
|
6
|
+
|
7
|
+
class HyperlinkedRelatedFieldExtension(OpenApiSerializerFieldExtension):
|
8
|
+
target_class = "vng_api_common.serializers.LengthHyperlinkedRelatedField"
|
9
|
+
match_subclasses = True
|
10
|
+
|
11
|
+
def map_serializer_field(self, auto_schema: AutoSchema, direction):
|
12
|
+
default_schema = auto_schema._map_serializer_field(
|
13
|
+
self.target, direction, bypass_extensions=True
|
14
|
+
)
|
15
|
+
return {
|
16
|
+
**default_schema,
|
17
|
+
"minLength": self.target.min_length,
|
18
|
+
"maxLength": self.target.max_length,
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
class HyperlinkedIdentityFieldExtension(OpenApiSerializerFieldExtension):
|
23
|
+
target_class = "rest_framework.serializers.HyperlinkedIdentityField"
|
24
|
+
match_subclasses = True
|
25
|
+
|
26
|
+
def map_serializer_field(self, auto_schema: AutoSchema, direction):
|
27
|
+
default_schema = auto_schema._map_serializer_field(
|
28
|
+
self.target, direction, bypass_extensions=True
|
29
|
+
)
|
30
|
+
return {
|
31
|
+
**default_schema,
|
32
|
+
"minLength": 1,
|
33
|
+
"maxLength": 1000,
|
34
|
+
"description": _(
|
35
|
+
"URL-referentie naar dit object. Dit is de unieke identificatie en locatie van dit object."
|
36
|
+
),
|
37
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from drf_spectacular.extensions import OpenApiSerializerExtension
|
2
|
+
from drf_spectacular.plumbing import ResolvedComponent
|
3
|
+
from drf_spectacular.settings import spectacular_settings
|
4
|
+
|
5
|
+
from ..utils import underscore_to_camel
|
6
|
+
|
7
|
+
|
8
|
+
class PolymorphicSerializerExtension(OpenApiSerializerExtension):
|
9
|
+
target_class = "vng_api_common.polymorphism.PolymorphicSerializer"
|
10
|
+
match_subclasses = True
|
11
|
+
|
12
|
+
def map_serializer(self, auto_schema, direction):
|
13
|
+
if not getattr(self.target, "discriminator", None):
|
14
|
+
raise AttributeError(
|
15
|
+
"'PolymorphicSerializer' derived serializers need to have 'discriminator' set"
|
16
|
+
)
|
17
|
+
|
18
|
+
discriminator = self.target.discriminator
|
19
|
+
|
20
|
+
# resolve component with base path
|
21
|
+
base_schema = auto_schema._map_serializer(
|
22
|
+
self.target, direction, bypass_extensions=True
|
23
|
+
)
|
24
|
+
base_name = f"Base_{self.target.__class__.__name__}"
|
25
|
+
if direction == "request" and spectacular_settings.COMPONENT_SPLIT_REQUEST:
|
26
|
+
base_name = base_name + "Request"
|
27
|
+
base_component = ResolvedComponent(
|
28
|
+
name=base_name,
|
29
|
+
type=ResolvedComponent.SCHEMA,
|
30
|
+
object=base_name,
|
31
|
+
schema=base_schema,
|
32
|
+
)
|
33
|
+
auto_schema.registry.register_on_missing(base_component)
|
34
|
+
|
35
|
+
components = {}
|
36
|
+
# resolve sub components and components
|
37
|
+
for resource_type, sub_serializer in discriminator.mapping.items():
|
38
|
+
if not sub_serializer or not sub_serializer.fields:
|
39
|
+
schema = {"allOf": [base_component.ref]}
|
40
|
+
else:
|
41
|
+
sub_component = auto_schema.resolve_serializer(
|
42
|
+
sub_serializer, direction
|
43
|
+
)
|
44
|
+
schema = {"allOf": [base_component.ref, sub_component.ref]}
|
45
|
+
|
46
|
+
component_name = f"{resource_type.value}_{self.target.__class__.__name__}"
|
47
|
+
if direction == "request" and spectacular_settings.COMPONENT_SPLIT_REQUEST:
|
48
|
+
component_name = component_name + "Request"
|
49
|
+
component = ResolvedComponent(
|
50
|
+
name=component_name,
|
51
|
+
type=ResolvedComponent.SCHEMA,
|
52
|
+
object=component_name,
|
53
|
+
schema=schema,
|
54
|
+
)
|
55
|
+
auto_schema.registry.register_on_missing(component)
|
56
|
+
|
57
|
+
components[resource_type.value] = component
|
58
|
+
|
59
|
+
return {
|
60
|
+
"oneOf": [component.ref for _, component in components.items()],
|
61
|
+
"discriminator": {
|
62
|
+
"propertyName": underscore_to_camel(discriminator.discriminator_field),
|
63
|
+
"mapping": {
|
64
|
+
resource: component.ref["$ref"]
|
65
|
+
for resource, component in components.items()
|
66
|
+
},
|
67
|
+
},
|
68
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from drf_spectacular.contrib.django_filters import DjangoFilterExtension
|
2
|
+
|
3
|
+
from vng_api_common.utils import underscore_to_camel
|
4
|
+
|
5
|
+
|
6
|
+
class CamelizeFilterExtension(DjangoFilterExtension):
|
7
|
+
priority = 1
|
8
|
+
|
9
|
+
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs):
|
10
|
+
"""
|
11
|
+
camelize query parameters
|
12
|
+
"""
|
13
|
+
parameters = super().get_schema_operation_parameters(
|
14
|
+
auto_schema, *args, **kwargs
|
15
|
+
)
|
16
|
+
|
17
|
+
for parameter in parameters:
|
18
|
+
parameter["name"] = underscore_to_camel(parameter["name"])
|
19
|
+
|
20
|
+
return parameters
|
vng_api_common/filters.py
CHANGED
@@ -26,7 +26,6 @@ logger = logging.getLogger(__name__)
|
|
26
26
|
|
27
27
|
|
28
28
|
class Backend(DjangoFilterBackend):
|
29
|
-
# Taken from drf_yasg.inspectors.field.CamelCaseJSONFilter
|
30
29
|
def _is_camel_case(self, view):
|
31
30
|
return any(
|
32
31
|
issubclass(parser, CamelCaseJSONParser) for parser in view.parser_classes
|