From 5c715424eee6d3bf602d854fb3acc99f0c8aa46e Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Wed, 14 Sep 2022 12:25:54 +0200 Subject: [PATCH] [#210] Add query param extension --- vng_api_common/inspectors/__init__.py | 1 + vng_api_common/inspectors/query.py | 83 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 vng_api_common/inspectors/query.py diff --git a/vng_api_common/inspectors/__init__.py b/vng_api_common/inspectors/__init__.py index c04ec423..ce12021e 100644 --- a/vng_api_common/inspectors/__init__.py +++ b/vng_api_common/inspectors/__init__.py @@ -5,3 +5,4 @@ ReadOnlyFieldExtension, ) from .polymorphic import PolymorphicSerializerExtension +from .query import FilterExtension diff --git a/vng_api_common/inspectors/query.py b/vng_api_common/inspectors/query.py new file mode 100644 index 00000000..a3bb72e8 --- /dev/null +++ b/vng_api_common/inspectors/query.py @@ -0,0 +1,83 @@ +from django.db import models +from django.utils.encoding import force_text +from django.utils.translation import gettext as _ + +from django_filters.filters import BaseCSVFilter, ChoiceFilter +from drf_spectacular.extensions import OpenApiFilterExtension +from rest_framework.filters import OrderingFilter + +from vng_api_common.filters import URLModelChoiceFilter +from vng_api_common.inspectors.utils import get_target_field +from vng_api_common.oas import TYPE_ARRAY, TYPE_STRING +from vng_api_common.utils import underscore_to_camel + + +class FilterExtension(OpenApiFilterExtension): + target_class = "vng_api_common.filters.Backend" + match_subclasses = True + + def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): + default_parameters = self.target.get_schema_operation_parameters( + auto_schema.view + ) + filterset_class = getattr(auto_schema.view, "filterset_class", None) + + if isinstance(self.target, OrderingFilter) or not filterset_class: + # TODO: OrderingFilter not present in ZRC, test in other components + return default_parameters + + queryset = auto_schema.view.get_queryset() + + for parameter in default_parameters: + filter_field = filterset_class.base_filters[parameter["name"]] + model_field = get_target_field(queryset.model, parameter["name"]) + + parameter_name = underscore_to_camel(parameter["name"]) + + original_description = parameter.get("description") + help_text = filter_field.extra.get( + "help_text", + getattr(model_field, "help_text", "") if model_field else "", + ) + + if isinstance(filter_field, BaseCSVFilter): + schema = { + "type": TYPE_ARRAY, + "items": { + "type": TYPE_STRING, + }, + } + + if "choices" in filter_field.extra: + schema["items"]["enum"] = ( + [choice[0] for choice in filter_field.extra["choices"]], + ) + + parameter["type"] = TYPE_ARRAY + parameter["schema"] = schema + parameter["style"] = "form" + parameter["explode"] = False + elif isinstance(filter_field, URLModelChoiceFilter): + description = _("URL to the related {resource}").format( + resource=parameter_name + ) + parameter["description"] = help_text or description + parameter["format"] = "uri" + elif isinstance(filter_field, ChoiceFilter): + parameter["schema"]["enum"] = [ + choice[0] for choice in filter_field.extra["choices"] + ] + elif model_field and isinstance(model_field, models.URLField): + parameter["format"] = "uri" + + if parameter["description"] == original_description and help_text: + parameter["description"] = force_text(help_text) + + if "max_length" in filter_field.extra: + parameter["max_length"] = filter_field.extra["max_length"] + if "min_length" in filter_field.extra: + parameter["min_length"] = filter_field.extra["min_length"] + + parameter["name"] = parameter_name + + return default_parameters