commonground-api-common 2.1.2__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.
Files changed (45) hide show
  1. commonground_api_common-2.1.2.data/scripts/patch_content_types → commonground_api_common-2.3.0.data/scripts/generate_schema +2 -4
  2. {commonground_api_common-2.1.2.dist-info → commonground_api_common-2.3.0.dist-info}/METADATA +12 -3
  3. {commonground_api_common-2.1.2.dist-info → commonground_api_common-2.3.0.dist-info}/RECORD +27 -36
  4. {commonground_api_common-2.1.2.dist-info → commonground_api_common-2.3.0.dist-info}/WHEEL +1 -1
  5. vng_api_common/__init__.py +1 -1
  6. vng_api_common/api/views.py +1 -0
  7. vng_api_common/apps.py +43 -26
  8. vng_api_common/audittrails/utils.py +44 -0
  9. vng_api_common/conf/api.py +33 -45
  10. vng_api_common/contrib/setup_configuration/models.py +32 -0
  11. vng_api_common/contrib/setup_configuration/steps.py +46 -0
  12. vng_api_common/extensions/file.py +26 -0
  13. vng_api_common/extensions/gegevensgroep.py +16 -0
  14. vng_api_common/extensions/geojson.py +270 -0
  15. vng_api_common/extensions/hyperlink.py +37 -0
  16. vng_api_common/extensions/polymorphic.py +68 -0
  17. vng_api_common/extensions/query.py +20 -0
  18. vng_api_common/filters.py +0 -1
  19. vng_api_common/generators.py +12 -113
  20. vng_api_common/notifications/api/views.py +3 -3
  21. vng_api_common/oas.py +6 -7
  22. vng_api_common/schema.py +414 -158
  23. vng_api_common/views.py +1 -1
  24. commonground_api_common-2.1.2.data/scripts/generate_schema +0 -39
  25. commonground_api_common-2.1.2.data/scripts/use_external_components +0 -16
  26. vng_api_common/inspectors/cache.py +0 -57
  27. vng_api_common/inspectors/fields.py +0 -126
  28. vng_api_common/inspectors/files.py +0 -121
  29. vng_api_common/inspectors/geojson.py +0 -360
  30. vng_api_common/inspectors/polymorphic.py +0 -72
  31. vng_api_common/inspectors/query.py +0 -91
  32. vng_api_common/inspectors/utils.py +0 -40
  33. vng_api_common/inspectors/view.py +0 -547
  34. vng_api_common/management/commands/generate_autorisaties.py +0 -43
  35. vng_api_common/management/commands/generate_notificaties.py +0 -40
  36. vng_api_common/management/commands/generate_swagger.py +0 -197
  37. vng_api_common/management/commands/patch_error_contenttypes.py +0 -61
  38. vng_api_common/management/commands/use_external_components.py +0 -94
  39. vng_api_common/templates/vng_api_common/api_schema_to_markdown_table.md +0 -16
  40. vng_api_common/templates/vng_api_common/autorisaties.md +0 -15
  41. vng_api_common/templates/vng_api_common/notificaties.md +0 -24
  42. {commonground_api_common-2.1.2.dist-info → commonground_api_common-2.3.0.dist-info}/top_level.txt +0 -0
  43. /vng_api_common/{inspectors → contrib}/__init__.py +0 -0
  44. /vng_api_common/{management → contrib/setup_configuration}/__init__.py +0 -0
  45. /vng_api_common/{management/commands → extensions}/__init__.py +0 -0
vng_api_common/schema.py CHANGED
@@ -1,177 +1,433 @@
1
- import json
2
1
  import logging
3
- import os
4
- from urllib.parse import urlsplit
2
+ from typing import Dict, List, Optional, Type
5
3
 
6
4
  from django.conf import settings
7
- from django.urls import get_script_prefix
8
-
9
- from drf_yasg import openapi
10
- from drf_yasg.app_settings import swagger_settings
11
- from drf_yasg.codecs import yaml_sane_dump, yaml_sane_load
12
- from drf_yasg.generators import OpenAPISchemaGenerator as _OpenAPISchemaGenerator
13
- from drf_yasg.renderers import SwaggerJSONRenderer, SwaggerYAMLRenderer, _SpecRenderer
14
- from drf_yasg.utils import get_consumes, get_produces
15
- from drf_yasg.views import get_schema_view
16
- from rest_framework import exceptions, permissions
17
- from rest_framework.response import Response
18
- from rest_framework.settings import api_settings
19
-
20
- logger = logging.getLogger(__name__)
5
+ from django.utils.translation import gettext_lazy as _
21
6
 
7
+ from drf_spectacular import openapi
8
+ from drf_spectacular.utils import OpenApiExample, OpenApiParameter, OpenApiTypes
9
+ from rest_framework import exceptions, serializers, status
22
10
 
23
- class OpenAPISchemaGenerator(_OpenAPISchemaGenerator):
24
- def get_schema(self, request=None, public=False):
25
- """
26
- Rewrite parent class to add 'responses' in components
27
- """
28
- endpoints = self.get_endpoints(request)
29
- components = self.reference_resolver_class(
30
- openapi.SCHEMA_DEFINITIONS, "responses", force_init=True
31
- )
32
- self.consumes = get_consumes(api_settings.DEFAULT_PARSER_CLASSES)
33
- self.produces = get_produces(api_settings.DEFAULT_RENDERER_CLASSES)
34
- paths, prefix = self.get_paths(endpoints, components, request, public)
11
+ from .audittrails.utils import _view_supports_audittrail
12
+ from .caching.introspection import has_cache_header
13
+ from .constants import HEADER_AUDIT, HEADER_LOGRECORD_ID, VERSION_HEADER
14
+ from .exceptions import Conflict, Gone, PreconditionFailed
15
+ from .geo import DEFAULT_CRS, HEADER_ACCEPT, HEADER_CONTENT, GeoMixin
16
+ from .permissions import BaseAuthRequired, get_required_scopes
17
+ from .serializers import FoutSerializer, ValidatieFoutSerializer
18
+ from .views import ERROR_CONTENT_TYPE
35
19
 
36
- security_definitions = self.get_security_definitions()
37
- if security_definitions:
38
- security_requirements = self.get_security_requirements(security_definitions)
39
- else:
40
- security_requirements = None
41
-
42
- url = self.url
43
- if url is None and request is not None:
44
- url = request.build_absolute_uri()
45
-
46
- return openapi.Swagger(
47
- info=self.info,
48
- paths=paths,
49
- consumes=self.consumes or None,
50
- produces=self.produces or None,
51
- security_definitions=security_definitions,
52
- security=security_requirements,
53
- _url=url,
54
- _prefix=prefix,
55
- _version=self.version,
56
- **dict(components),
57
- )
58
-
59
- def get_path_parameters(self, path, view_cls):
60
- """Return a list of Parameter instances corresponding to any templated path variables.
20
+ logger = logging.getLogger(__name__)
61
21
 
62
- :param str path: templated request path
63
- :param type view_cls: the view class associated with the path
64
- :return: path parameters
65
- :rtype: list[openapi.Parameter]
22
+ COMMON_ERRORS = [
23
+ exceptions.AuthenticationFailed,
24
+ exceptions.NotAuthenticated,
25
+ exceptions.PermissionDenied,
26
+ exceptions.NotAcceptable,
27
+ Conflict,
28
+ Gone,
29
+ exceptions.UnsupportedMediaType,
30
+ exceptions.Throttled,
31
+ exceptions.APIException,
32
+ ]
33
+
34
+ DEFAULT_ACTION_ERRORS = {
35
+ "create": COMMON_ERRORS + [exceptions.ParseError, exceptions.ValidationError],
36
+ "list": COMMON_ERRORS,
37
+ "retrieve": COMMON_ERRORS + [exceptions.NotFound],
38
+ "update": COMMON_ERRORS
39
+ + [exceptions.ParseError, exceptions.ValidationError, exceptions.NotFound],
40
+ "partial_update": COMMON_ERRORS
41
+ + [exceptions.ParseError, exceptions.ValidationError, exceptions.NotFound],
42
+ "destroy": COMMON_ERRORS + [exceptions.NotFound],
43
+ }
44
+
45
+ HTTP_STATUS_CODE_TITLES = {
46
+ status.HTTP_100_CONTINUE: "Continue",
47
+ status.HTTP_101_SWITCHING_PROTOCOLS: "Switching protocols",
48
+ status.HTTP_200_OK: "OK",
49
+ status.HTTP_201_CREATED: "Created",
50
+ status.HTTP_202_ACCEPTED: "Accepted",
51
+ status.HTTP_203_NON_AUTHORITATIVE_INFORMATION: "Non authoritative information",
52
+ status.HTTP_204_NO_CONTENT: "No content",
53
+ status.HTTP_205_RESET_CONTENT: "Reset content",
54
+ status.HTTP_206_PARTIAL_CONTENT: "Partial content",
55
+ status.HTTP_207_MULTI_STATUS: "Multi status",
56
+ status.HTTP_300_MULTIPLE_CHOICES: "Multiple choices",
57
+ status.HTTP_301_MOVED_PERMANENTLY: "Moved permanently",
58
+ status.HTTP_302_FOUND: "Found",
59
+ status.HTTP_303_SEE_OTHER: "See other",
60
+ status.HTTP_304_NOT_MODIFIED: "Not modified",
61
+ status.HTTP_305_USE_PROXY: "Use proxy",
62
+ status.HTTP_306_RESERVED: "Reserved",
63
+ status.HTTP_307_TEMPORARY_REDIRECT: "Temporary redirect",
64
+ status.HTTP_400_BAD_REQUEST: "Bad request",
65
+ status.HTTP_401_UNAUTHORIZED: "Unauthorized",
66
+ status.HTTP_402_PAYMENT_REQUIRED: "Payment required",
67
+ status.HTTP_403_FORBIDDEN: "Forbidden",
68
+ status.HTTP_404_NOT_FOUND: "Not found",
69
+ status.HTTP_405_METHOD_NOT_ALLOWED: "Method not allowed",
70
+ status.HTTP_406_NOT_ACCEPTABLE: "Not acceptable",
71
+ status.HTTP_407_PROXY_AUTHENTICATION_REQUIRED: "Proxy authentication required",
72
+ status.HTTP_408_REQUEST_TIMEOUT: "Request timeout",
73
+ status.HTTP_409_CONFLICT: "Conflict",
74
+ status.HTTP_410_GONE: "Gone",
75
+ status.HTTP_411_LENGTH_REQUIRED: "Length required",
76
+ status.HTTP_412_PRECONDITION_FAILED: "Precondition failed",
77
+ status.HTTP_413_REQUEST_ENTITY_TOO_LARGE: "Request entity too large",
78
+ status.HTTP_414_REQUEST_URI_TOO_LONG: "Request uri too long",
79
+ status.HTTP_415_UNSUPPORTED_MEDIA_TYPE: "Unsupported media type",
80
+ status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE: "Requested range not satisfiable",
81
+ status.HTTP_417_EXPECTATION_FAILED: "Expectation failed",
82
+ status.HTTP_422_UNPROCESSABLE_ENTITY: "Unprocessable entity",
83
+ status.HTTP_423_LOCKED: "Locked",
84
+ status.HTTP_424_FAILED_DEPENDENCY: "Failed dependency",
85
+ status.HTTP_428_PRECONDITION_REQUIRED: "Precondition required",
86
+ status.HTTP_429_TOO_MANY_REQUESTS: "Too many requests",
87
+ status.HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE: "Request header fields too large",
88
+ status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable for legal reasons",
89
+ status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal server error",
90
+ status.HTTP_501_NOT_IMPLEMENTED: "Not implemented",
91
+ status.HTTP_502_BAD_GATEWAY: "Bad gateway",
92
+ status.HTTP_503_SERVICE_UNAVAILABLE: "Service unavailable",
93
+ status.HTTP_504_GATEWAY_TIMEOUT: "Gateway timeout",
94
+ status.HTTP_505_HTTP_VERSION_NOT_SUPPORTED: "HTTP version not supported",
95
+ status.HTTP_507_INSUFFICIENT_STORAGE: "Insufficient storage",
96
+ status.HTTP_511_NETWORK_AUTHENTICATION_REQUIRED: "Network authentication required",
97
+ }
98
+
99
+
100
+ class AutoSchema(openapi.AutoSchema):
101
+ method_mapping = dict(
102
+ **openapi.AutoSchema.method_mapping,
103
+ head="headers",
104
+ )
105
+
106
+ def get_auth(self) -> List[Dict[str, List[str]]]:
66
107
  """
67
- parameters = super().get_path_parameters(path, view_cls)
68
-
69
- # see if we can specify UUID a bit more
70
- for parameter in parameters:
71
- # the most pragmatic of checks
72
- if not parameter.name.endswith("_uuid"):
73
- continue
74
- parameter.format = openapi.FORMAT_UUID
75
- parameter.description = "Unieke resource identifier (UUID4)"
76
- return parameters
77
-
78
-
79
- DefaultSchemaView = get_schema_view(
80
- # validators=['flex', 'ssv'],
81
- public=True,
82
- permission_classes=(permissions.AllowAny,),
83
- )
84
-
85
-
86
- class OpenAPIV3RendererMixin:
87
- def render(self, data, media_type=None, renderer_context=None):
88
- if "openapi" in data or "swagger" in data:
89
- if self.format == ".yaml":
90
- return yaml_sane_dump(data, False)
91
- elif self.format == ".json":
92
- return json.dumps(data)
93
-
94
- return super().render(
95
- data, media_type=media_type, renderer_context=renderer_context
96
- )
97
-
98
-
99
- SPEC_RENDERERS = (
100
- type("SwaggerYAMLRenderer", (OpenAPIV3RendererMixin, SwaggerYAMLRenderer), {}),
101
- type("SwaggerJSONRenderer", (OpenAPIV3RendererMixin, SwaggerJSONRenderer), {}),
102
- )
103
-
108
+ Return a list of security requirements for this operation.
104
109
 
105
- class SchemaMixin:
106
- """
107
- Always serve the v3 version, which is kept in version control.
110
+ `OpenApiAuthenticationExtension` can't be used here since it's tightly coupled
111
+ with DRF authentication classes
112
+ """
113
+ permissions = self.view.get_permissions()
114
+ scope_permissions = [
115
+ perm for perm in permissions if isinstance(perm, BaseAuthRequired)
116
+ ]
108
117
 
109
- .. warn:: there is a risk of the generated schema not being in sync with
110
- the code. Unfortunately, that's the tradeoff we have. We could set up
111
- CI to check for outdated schemas.
112
- """
118
+ if not scope_permissions:
119
+ return super().get_auth()
113
120
 
114
- schema_path = None
121
+ scopes = get_required_scopes(self.view.request, self.view)
122
+ if not scopes:
123
+ return []
115
124
 
116
- @property
117
- def _is_openapi_v2(self) -> bool:
118
- default = "3" if "format" in self.kwargs else "2"
119
- version = self.request.GET.get("v", default)
120
- return version.startswith("2")
125
+ return [{settings.SECURITY_DEFINITION_NAME: [str(scopes)]}]
121
126
 
122
- def get_renderers(self):
123
- if self._is_openapi_v2:
124
- return super().get_renderers()
125
- return [renderer() for renderer in SPEC_RENDERERS]
127
+ def get_operation_id(self):
128
+ """
129
+ Use view basename as a base for operation_id
130
+ """
131
+ if hasattr(self.view, "basename"):
132
+ basename = self.view.basename
133
+ action = "head" if self.method == "HEAD" else self.view.action
134
+ # make compatible with old OAS
135
+ if action == "destroy":
136
+ action = "delete"
137
+ elif action == "retrieve":
138
+ action = "read"
139
+
140
+ return f"{basename}_{action}"
141
+ return super().get_operation_id()
142
+
143
+ def get_error_responses(self) -> Dict[int, Type[serializers.Serializer]]:
144
+ """
145
+ return dictionary of error codes and correspondent error serializers
146
+ - define status codes based on exceptions for each endpoint
147
+ - define error serializers based on status code
148
+ """
126
149
 
127
- def get_schema_path(self) -> str:
128
- return self.schema_path or os.path.join(
129
- settings.BASE_DIR, "src", "openapi.yaml"
150
+ # only supports viewsets
151
+ action = getattr(self.view, "action", None)
152
+ if not action:
153
+ return {}
154
+
155
+ # define status codes for the action based on potential exceptions
156
+ # general errors
157
+ general_klasses = DEFAULT_ACTION_ERRORS.get(action)
158
+ if general_klasses is None:
159
+ logger.debug("Unknown action %s, no default error responses added")
160
+ return {}
161
+
162
+ exception_klasses = general_klasses[:]
163
+ # add geo and validation errors
164
+ has_validation_errors = action == "list" or any(
165
+ issubclass(klass, exceptions.ValidationError) for klass in exception_klasses
130
166
  )
131
-
132
- def get(self, request, version="", *args, **kwargs):
133
- if self._is_openapi_v2:
134
- version = request.version or version or ""
135
- if isinstance(request.accepted_renderer, _SpecRenderer):
136
- generator = self.generator_class(
137
- getattr(self, "info", swagger_settings.DEFAULT_INFO),
138
- version,
139
- None,
140
- None,
141
- None,
142
- )
167
+ if has_validation_errors:
168
+ exception_klasses.append(exceptions.ValidationError)
169
+
170
+ if isinstance(self.view, GeoMixin):
171
+ exception_klasses.append(PreconditionFailed)
172
+
173
+ status_codes = sorted({e.status_code for e in exception_klasses})
174
+
175
+ # choose serializer based on the status code
176
+ responses = {}
177
+ for status_code in status_codes:
178
+ error_serializer = (
179
+ ValidatieFoutSerializer
180
+ if status_code == exceptions.ValidationError.status_code
181
+ else FoutSerializer
182
+ )
183
+ responses[status_code] = error_serializer
184
+
185
+ return responses
186
+
187
+ def get_response_serializers(
188
+ self,
189
+ ) -> Dict[int, Optional[Type[serializers.Serializer]]]:
190
+ """append error serializers"""
191
+ response_serializers = super().get_response_serializers()
192
+
193
+ if self.method == "HEAD":
194
+ return {200: None}
195
+
196
+ if self.method == "DELETE":
197
+ status_code = 204
198
+ serializer = None
199
+ elif self._is_create_operation():
200
+ status_code = 201
201
+ serializer = response_serializers
202
+ else:
203
+ status_code = 200
204
+ serializer = response_serializers
205
+
206
+ responses = {
207
+ status_code: serializer,
208
+ **self.get_error_responses(),
209
+ }
210
+ return responses
211
+
212
+ def _get_response_for_code(
213
+ self, serializer, status_code, media_types=None, direction="response"
214
+ ):
215
+ """
216
+ choose media types and set descriptions
217
+ add custom response for expand
218
+ """
219
+ if not media_types:
220
+ if int(status_code) >= 400:
221
+ media_types = [ERROR_CONTENT_TYPE]
143
222
  else:
144
- generator = self.generator_class(
145
- getattr(self, "info", swagger_settings.DEFAULT_INFO),
146
- version,
147
- None,
148
- patterns=[],
149
- )
150
-
151
- schema = generator.get_schema(request, self.public)
152
- if schema is None:
153
- raise exceptions.PermissionDenied() # pragma: no cover
154
- return Response(schema)
223
+ media_types = ["application/json"]
155
224
 
156
- # serve the staticically included V3 schema
157
- SCHEMA_PATH = self.get_schema_path()
158
- with open(SCHEMA_PATH, "r") as infile:
159
- schema = yaml_sane_load(infile)
160
-
161
- # fix the servers
162
- for server in schema["servers"]:
163
- split_url = urlsplit(server["url"])
164
- if split_url.netloc:
165
- continue
166
-
167
- prefix = get_script_prefix()
168
- if prefix.endswith("/"):
169
- prefix = prefix[:-1]
170
- server_path = f"{prefix}{server['url']}"
171
- server["url"] = request.build_absolute_uri(server_path)
172
-
173
- return Response(data=schema, headers={"X-OAS-Version": schema["openapi"]})
225
+ response = super()._get_response_for_code(
226
+ serializer, status_code, media_types, direction
227
+ )
174
228
 
229
+ # add description based on the status code
230
+ if not response.get("description"):
231
+ response["description"] = HTTP_STATUS_CODE_TITLES.get(int(status_code), "")
232
+ return response
233
+
234
+ def get_override_parameters(self):
235
+ """Add request and response headers"""
236
+ version_headers = self.get_version_headers()
237
+ content_type_headers = self.get_content_type_headers()
238
+ cache_headers = self.get_cache_headers()
239
+ log_headers = self.get_log_headers()
240
+ location_headers = self.get_location_headers()
241
+ geo_headers = self.get_geo_headers()
242
+ return (
243
+ version_headers
244
+ + content_type_headers
245
+ + cache_headers
246
+ + log_headers
247
+ + location_headers
248
+ + geo_headers
249
+ )
175
250
 
176
- class SchemaView(SchemaMixin, DefaultSchemaView):
177
- pass
251
+ def get_version_headers(self) -> List[OpenApiParameter]:
252
+ return [
253
+ OpenApiParameter(
254
+ name=VERSION_HEADER,
255
+ type=str,
256
+ location=OpenApiParameter.HEADER,
257
+ description=_(
258
+ "Geeft een specifieke API-versie aan in de context van "
259
+ "een specifieke aanroep. Voorbeeld: 1.2.1."
260
+ ),
261
+ response=True,
262
+ )
263
+ ]
264
+
265
+ def get_content_type_headers(self) -> List[OpenApiParameter]:
266
+ if self.method not in ["POST", "PUT", "PATCH"]:
267
+ return []
268
+
269
+ mime_type_enum = [
270
+ cls.media_type
271
+ for cls in self.view.parser_classes
272
+ if hasattr(cls, "media_type")
273
+ ]
274
+
275
+ return [
276
+ OpenApiParameter(
277
+ name="Content-Type",
278
+ type=str,
279
+ location=OpenApiParameter.HEADER,
280
+ description=_("Content type of the request body."),
281
+ enum=mime_type_enum,
282
+ required=True,
283
+ )
284
+ ]
285
+
286
+ def get_cache_headers(self) -> List[OpenApiParameter]:
287
+ """
288
+ support ETag headers
289
+ """
290
+ if not has_cache_header(self.view):
291
+ return []
292
+
293
+ return [
294
+ OpenApiParameter(
295
+ name="If-None-Match",
296
+ type=str,
297
+ location=OpenApiParameter.HEADER,
298
+ required=False,
299
+ description=_(
300
+ "Perform conditional requests. This header should contain one or "
301
+ "multiple ETag values of resources the client has cached. If the "
302
+ "current resource ETag value is in this set, then an HTTP 304 "
303
+ "empty body will be returned. See "
304
+ "[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) "
305
+ "for details."
306
+ ),
307
+ examples=[
308
+ OpenApiExample(
309
+ name="oneValue",
310
+ summary=_("One ETag value"),
311
+ value='"79054025255fb1a26e4bc422aef54eb4"',
312
+ ),
313
+ OpenApiExample(
314
+ name="multipleValues",
315
+ summary=_("Multiple ETag values"),
316
+ value='"79054025255fb1a26e4bc422aef54eb4", "e4d909c290d0fb1ca068ffaddf22cbd0"',
317
+ ),
318
+ ],
319
+ ),
320
+ OpenApiParameter(
321
+ name="ETag",
322
+ type=str,
323
+ location=OpenApiParameter.HEADER,
324
+ response=[200],
325
+ description=_(
326
+ "De ETag berekend op de response body JSON. "
327
+ "Indien twee resources exact dezelfde ETag hebben, dan zijn "
328
+ "deze resources identiek aan elkaar. Je kan de ETag gebruiken "
329
+ "om caching te implementeren."
330
+ ),
331
+ ),
332
+ ]
333
+
334
+ def get_location_headers(self) -> List[OpenApiParameter]:
335
+ return [
336
+ OpenApiParameter(
337
+ name="Location",
338
+ type=OpenApiTypes.URI,
339
+ location=OpenApiParameter.HEADER,
340
+ description=_("URL waar de resource leeft."),
341
+ response=[201],
342
+ ),
343
+ ]
344
+
345
+ def get_geo_headers(self) -> List[OpenApiParameter]:
346
+ if not isinstance(self.view, GeoMixin):
347
+ return []
348
+
349
+ request_headers = []
350
+ if self.method != "DELETE":
351
+ request_headers.append(
352
+ OpenApiParameter(
353
+ name=HEADER_ACCEPT,
354
+ type=str,
355
+ location=OpenApiParameter.HEADER,
356
+ required=False,
357
+ description=_(
358
+ "The desired 'Coordinate Reference System' (CRS) of the response data. "
359
+ "According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 "
360
+ "is the same as WGS84)."
361
+ ),
362
+ enum=[DEFAULT_CRS],
363
+ )
364
+ )
365
+
366
+ if self.method in ("POST", "PUT", "PATCH"):
367
+ request_headers.append(
368
+ OpenApiParameter(
369
+ name=HEADER_CONTENT,
370
+ type=str,
371
+ location=OpenApiParameter.HEADER,
372
+ required=True,
373
+ description=_(
374
+ "The 'Coordinate Reference System' (CRS) of the request data. "
375
+ "According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 "
376
+ "is the same as WGS84)."
377
+ ),
378
+ enum=[DEFAULT_CRS],
379
+ ),
380
+ )
381
+
382
+ response_headers = [
383
+ OpenApiParameter(
384
+ name=HEADER_CONTENT,
385
+ type=str,
386
+ location=OpenApiParameter.HEADER,
387
+ required=True,
388
+ description=_(
389
+ "The 'Coordinate Reference System' (CRS) of the request data. "
390
+ "According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 "
391
+ "is the same as WGS84)."
392
+ ),
393
+ enum=[DEFAULT_CRS],
394
+ response=[200, 201],
395
+ )
396
+ ]
397
+
398
+ return request_headers + response_headers
399
+
400
+ def get_log_headers(self) -> List[OpenApiParameter]:
401
+ if not _view_supports_audittrail(self.view):
402
+ return []
403
+
404
+ return [
405
+ OpenApiParameter(
406
+ name=HEADER_LOGRECORD_ID,
407
+ type=str,
408
+ location=OpenApiParameter.HEADER,
409
+ required=False,
410
+ description=_(
411
+ "Identifier of the request, traceable throughout the network"
412
+ ),
413
+ ),
414
+ OpenApiParameter(
415
+ name=HEADER_AUDIT,
416
+ type=str,
417
+ location=OpenApiParameter.HEADER,
418
+ required=False,
419
+ description=_("Explanation why the request is done"),
420
+ ),
421
+ ]
422
+
423
+ def get_summary(self):
424
+ if self.method == "HEAD":
425
+ return _("De headers voor een specifiek(e) %(model)s opvragen ") % {
426
+ "model": self.view.queryset.model._meta.verbose_name.upper()
427
+ }
428
+ return super().get_summary()
429
+
430
+ def get_description(self):
431
+ if self.method == "HEAD":
432
+ return _("Vraag de headers op die je bij een GET request zou krijgen.")
433
+ return super().get_description()
vng_api_common/views.py CHANGED
@@ -15,7 +15,7 @@ from rest_framework import exceptions as drf_exceptions, status
15
15
  from rest_framework.response import Response
16
16
  from rest_framework.views import exception_handler as drf_exception_handler
17
17
 
18
- from vng_api_common.client import Client, ClientError
18
+ from vng_api_common.client import Client
19
19
 
20
20
  from . import exceptions
21
21
  from .compat import sentry_client
@@ -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