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
@@ -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
@@ -1,16 +1,7 @@
1
- from collections import OrderedDict
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
- OpenAPISchemaGenerator as _OpenAPISchemaGenerator,
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
- endpoint_enumerator_class = EndpointEnumerator
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
- endpoints = self.get_endpoints(request)
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
- endpoints = self.get_endpoints(request)
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
- parameters = super().get_path_parameters(path, view_cls)
108
-
109
- # see if we can specify UUID a bit more
110
- for parameter in parameters:
111
- # the most pragmatic of checks
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 named_path_components + ["headers"]
35
+ return super().create_view(callback, method, request=request)
134
36
 
135
- def get_overrides(self, view, method) -> dict:
136
- if method == "HEAD":
137
- return {}
138
- return super().get_overrides(view, method)
37
+ # todo support registering and reusing Response components