commonground-api-common 2.2.0__py3-none-any.whl → 2.3.0__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-2.2.0.data/scripts/patch_content_types → commonground_api_common-2.3.0.data/scripts/generate_schema +2 -4
- {commonground_api_common-2.2.0.dist-info → commonground_api_common-2.3.0.dist-info}/METADATA +10 -3
- {commonground_api_common-2.2.0.dist-info → commonground_api_common-2.3.0.dist-info}/RECORD +25 -38
- {commonground_api_common-2.2.0.dist-info → commonground_api_common-2.3.0.dist-info}/WHEEL +1 -1
- vng_api_common/__init__.py +1 -1
- vng_api_common/api/views.py +1 -0
- vng_api_common/apps.py +43 -26
- vng_api_common/audittrails/utils.py +44 -0
- vng_api_common/conf/api.py +33 -45
- vng_api_common/contrib/setup_configuration/models.py +14 -0
- vng_api_common/contrib/setup_configuration/steps.py +24 -1
- 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/notifications/api/views.py +3 -3
- vng_api_common/oas.py +6 -7
- vng_api_common/schema.py +414 -158
- vng_api_common/views.py +1 -1
- commonground_api_common-2.2.0.data/scripts/generate_schema +0 -39
- commonground_api_common-2.2.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/__init__.py +0 -0
- vng_api_common/management/commands/__init__.py +0 -0
- 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/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-2.2.0.dist-info → commonground_api_common-2.3.0.dist-info}/top_level.txt +0 -0
- /vng_api_common/{inspectors → extensions}/__init__.py +0 -0
@@ -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
|
vng_api_common/generators.py
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
-
from
|
2
|
-
from typing import List
|
3
|
-
|
4
|
-
from drf_yasg import openapi
|
5
|
-
from drf_yasg.generators import (
|
1
|
+
from drf_spectacular.generators import (
|
6
2
|
EndpointEnumerator as _EndpointEnumerator,
|
7
|
-
|
3
|
+
SchemaGenerator as _OpenAPISchemaGenerator,
|
8
4
|
)
|
9
|
-
from drf_yasg.utils import get_consumes, get_produces
|
10
|
-
from rest_framework.schemas.utils import is_list_view
|
11
|
-
from rest_framework.settings import api_settings
|
12
|
-
|
13
|
-
from vng_api_common.utils import get_view_summary
|
14
5
|
|
15
6
|
|
16
7
|
class EndpointEnumerator(_EndpointEnumerator):
|
@@ -29,110 +20,18 @@ class EndpointEnumerator(_EndpointEnumerator):
|
|
29
20
|
|
30
21
|
|
31
22
|
class OpenAPISchemaGenerator(_OpenAPISchemaGenerator):
|
32
|
-
|
33
|
-
|
34
|
-
def get_tags(self, request=None, public=False):
|
35
|
-
"""Retrieve the tags for the root schema.
|
36
|
-
|
37
|
-
:param request: the request used for filtering accessible endpoints and finding the spec URI
|
38
|
-
:param bool public: if True, all endpoints are included regardless of access through `request`
|
39
|
-
|
40
|
-
:return: List of tags containing the tag name and a description.
|
41
|
-
"""
|
42
|
-
tags = {}
|
23
|
+
endpoint_inspector_cls = EndpointEnumerator
|
43
24
|
|
44
|
-
|
45
|
-
for path, (view_cls, methods) in sorted(endpoints.items()):
|
46
|
-
if "{" in path:
|
47
|
-
continue
|
48
|
-
|
49
|
-
tag = path.rsplit("/", 1)[-1]
|
50
|
-
if tag in tags:
|
51
|
-
continue
|
52
|
-
|
53
|
-
# exclude special non-rest actions
|
54
|
-
if tag.startswith("_"):
|
55
|
-
continue
|
56
|
-
tags[tag] = get_view_summary(view_cls)
|
57
|
-
|
58
|
-
return [
|
59
|
-
OrderedDict([("name", operation), ("description", desc)])
|
60
|
-
for operation, desc in sorted(tags.items())
|
61
|
-
]
|
62
|
-
|
63
|
-
def get_schema(self, request=None, public=False):
|
64
|
-
"""
|
65
|
-
Rewrite parent class to add 'responses' in components
|
25
|
+
def create_view(self, callback, method, request=None):
|
66
26
|
"""
|
67
|
-
|
68
|
-
components = self.reference_resolver_class(
|
69
|
-
openapi.SCHEMA_DEFINITIONS, "responses", force_init=True
|
70
|
-
)
|
71
|
-
self.consumes = get_consumes(api_settings.DEFAULT_PARSER_CLASSES)
|
72
|
-
self.produces = get_produces(api_settings.DEFAULT_RENDERER_CLASSES)
|
73
|
-
paths, prefix = self.get_paths(endpoints, components, request, public)
|
74
|
-
|
75
|
-
security_definitions = self.get_security_definitions()
|
76
|
-
if security_definitions:
|
77
|
-
security_requirements = self.get_security_requirements(security_definitions)
|
78
|
-
else:
|
79
|
-
security_requirements = None
|
80
|
-
|
81
|
-
url = self.url
|
82
|
-
if url is None and request is not None:
|
83
|
-
url = request.build_absolute_uri()
|
84
|
-
|
85
|
-
return openapi.Swagger(
|
86
|
-
info=self.info,
|
87
|
-
paths=paths,
|
88
|
-
consumes=self.consumes or None,
|
89
|
-
produces=self.produces or None,
|
90
|
-
tags=self.get_tags(request, public),
|
91
|
-
security_definitions=security_definitions,
|
92
|
-
security=security_requirements,
|
93
|
-
_url=url,
|
94
|
-
_prefix=prefix,
|
95
|
-
_version=self.version,
|
96
|
-
**dict(components),
|
97
|
-
)
|
98
|
-
|
99
|
-
def get_path_parameters(self, path, view_cls):
|
100
|
-
"""Return a list of Parameter instances corresponding to any templated path variables.
|
101
|
-
|
102
|
-
:param str path: templated request path
|
103
|
-
:param type view_cls: the view class associated with the path
|
104
|
-
:return: path parameters
|
105
|
-
:rtype: list[openapi.Parameter]
|
27
|
+
workaround for HEAD method which doesn't have action
|
106
28
|
"""
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
if not parameter.name.endswith("_uuid"):
|
113
|
-
continue
|
114
|
-
parameter.format = openapi.FORMAT_UUID
|
115
|
-
parameter.description = "Unieke resource identifier (UUID4)"
|
116
|
-
return parameters
|
117
|
-
|
118
|
-
def get_operation_keys(self, subpath, method, view) -> List[str]:
|
119
|
-
if method != "HEAD":
|
120
|
-
return super().get_operation_keys(subpath, method, view)
|
121
|
-
|
122
|
-
assert not is_list_view(
|
123
|
-
subpath, method, view
|
124
|
-
), "HEAD requests are only supported on detail endpoints"
|
125
|
-
|
126
|
-
# taken from DRF schema generation
|
127
|
-
named_path_components = [
|
128
|
-
component
|
129
|
-
for component in subpath.strip("/").split("/")
|
130
|
-
if "{" not in component
|
131
|
-
]
|
29
|
+
if method == "HEAD":
|
30
|
+
view = super(_OpenAPISchemaGenerator, self).create_view(
|
31
|
+
callback, method, request=request
|
32
|
+
)
|
33
|
+
return view
|
132
34
|
|
133
|
-
return
|
35
|
+
return super().create_view(callback, method, request=request)
|
134
36
|
|
135
|
-
|
136
|
-
if method == "HEAD":
|
137
|
-
return {}
|
138
|
-
return super().get_overrides(view, method)
|
37
|
+
# todo support registering and reusing Response components
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from django.conf import settings
|
2
2
|
from django.utils.module_loading import import_string
|
3
3
|
|
4
|
-
from
|
4
|
+
from drf_spectacular.utils import extend_schema
|
5
5
|
from notifications_api_common.api.serializers import NotificatieSerializer
|
6
6
|
from notifications_api_common.constants import SCOPE_NOTIFICATIES_PUBLICEREN_LABEL
|
7
7
|
from rest_framework import status
|
@@ -18,7 +18,7 @@ class NotificationBaseView(APIView):
|
|
18
18
|
Abstract view to receive webhooks
|
19
19
|
"""
|
20
20
|
|
21
|
-
|
21
|
+
schema = None
|
22
22
|
|
23
23
|
permission_classes = (AuthScopesRequired,)
|
24
24
|
required_scopes = Scope(SCOPE_NOTIFICATIES_PUBLICEREN_LABEL, private=True)
|
@@ -26,7 +26,7 @@ class NotificationBaseView(APIView):
|
|
26
26
|
def get_serializer(self, *args, **kwargs):
|
27
27
|
return NotificatieSerializer(*args, **kwargs)
|
28
28
|
|
29
|
-
@
|
29
|
+
@extend_schema(
|
30
30
|
responses={
|
31
31
|
204: "",
|
32
32
|
400: ValidatieFoutSerializer,
|
vng_api_common/oas.py
CHANGED
@@ -6,15 +6,14 @@ from typing import Union
|
|
6
6
|
|
7
7
|
import requests
|
8
8
|
import yaml
|
9
|
-
from drf_yasg import openapi
|
10
9
|
|
11
10
|
TYPE_MAP = {
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
"object": dict,
|
12
|
+
"string": str,
|
13
|
+
"number": (float, int),
|
14
|
+
"integer": int,
|
15
|
+
"boolean": bool,
|
16
|
+
"array": list,
|
18
17
|
}
|
19
18
|
|
20
19
|
|