From 5d774e9ba602b8896c7d64846a56a85b4aef0557 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:38:51 +0300 Subject: [PATCH 01/12] Document address API parameters --- services/api.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/services/api.py b/services/api.py index 0b3406e6..0c47c0c1 100644 --- a/services/api.py +++ b/services/api.py @@ -15,6 +15,7 @@ from django.utils import timezone, translation from django.utils.module_loading import import_string from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.utils import extend_schema, OpenApiParameter from modeltranslation.translator import NotRegistered, translator from mptt.utils import drilldown_tree_for_node from munigeo import api as munigeo_api @@ -1196,6 +1197,63 @@ def get_queryset(self): register_view(AdministrativeDivisionViewSet, "administrative_division") +@extend_schema( + parameters=[ + OpenApiParameter( + name="street", + location=OpenApiParameter.QUERY, + description="Filter by street name.", + required=False, + type=str, + ), + OpenApiParameter( + name="municipality", + location=OpenApiParameter.QUERY, + description="Filter by municipality name or OCD ID.", + required=False, + type=str, + ), + OpenApiParameter( + name="number", + location=OpenApiParameter.QUERY, + description="Filter by building number.", + required=False, + type=str, + ), + OpenApiParameter( + name="lat", + location=OpenApiParameter.QUERY, + description="Filter by location. Give latitude in WGS84 system. If this parameter is given also the 'lon' " + "parameter is required.", + required=False, + type=float, + ), + OpenApiParameter( + name="lon", + location=OpenApiParameter.QUERY, + description="Filter by location. Give longitude in WGS84 system. If this parameter is given also the 'lat' " + "parameter is required.", + required=False, + type=float, + ), + OpenApiParameter( + name="distance", + location=OpenApiParameter.QUERY, + description="The maximum distance from the provided location, defined by the lat and lon parameters. " + "If this parameter is given also the 'lat' and 'lon' parameters are required.", + required=False, + type=float, + ), + OpenApiParameter( + name="bbox", + location=OpenApiParameter.QUERY, + description="Bounding box in the format 'left,bottom,right,top'. " + "Values must be floating points or integers.", + required=False, + type=str, + ), + ], +) class AddressViewSet(munigeo_api.AddressViewSet): serializer_class = munigeo_api.AddressSerializer From b9d9321ee2e21dd93fcce70d91a892c30f2048e5 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:41:26 +0300 Subject: [PATCH 02/12] Document administrative division API parameters --- services/api.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/services/api.py b/services/api.py index 0c47c0c1..863c7d26 100644 --- a/services/api.py +++ b/services/api.py @@ -1172,6 +1172,75 @@ def to_representation(self, obj): return ret +@extend_schema( + parameters=[ + OpenApiParameter( + name="type", + location=OpenApiParameter.QUERY, + description="Filter by administrative division type or type ID.", + required=False, + type=str, + ), + OpenApiParameter( + name="lat", + location=OpenApiParameter.QUERY, + description="Filter by location. Give latitude in WGS84 system. If this parameter is given also the 'lon' " + "parameter is required.", + required=False, + type=float, + ), + OpenApiParameter( + name="lon", + location=OpenApiParameter.QUERY, + description="Filter by location. Give longitude in WGS84 system. If this parameter is given also the 'lat' " + "parameter is required.", + required=False, + type=float, + ), + OpenApiParameter( + name="input", + location=OpenApiParameter.QUERY, + description="Filter by partial match of name.", + required=False, + type=str, + ), + OpenApiParameter( + name="ocd_id", + location=OpenApiParameter.QUERY, + description="Filter by OCD ID.", + required=False, + type=str, + ), + OpenApiParameter( + name="geometry", + location=OpenApiParameter.QUERY, + description="Display administrative division boundary.", + required=False, + type=bool, + ), + OpenApiParameter( + name="origin_id", + location=OpenApiParameter.QUERY, + description="Filter by origin ID.", + required=False, + type=str, + ), + OpenApiParameter( + name="municipality", + location=OpenApiParameter.QUERY, + description="Filter by municipality.", + required=False, + type=str, + ), + OpenApiParameter( + name="date", + location=OpenApiParameter.QUERY, + description="Filter divisions based on their validity date. Format: YYYY-MM-DD.", + required=False, + type=str, + ), + ] +) class AdministrativeDivisionViewSet(munigeo_api.AdministrativeDivisionViewSet): serializer_class = AdministrativeDivisionSerializer From 7f26477475d199608a0baea8870a3eec7b8968e6 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:51:27 +0300 Subject: [PATCH 03/12] Document service node API parameters --- services/api.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/services/api.py b/services/api.py index 863c7d26..6a8e3941 100644 --- a/services/api.py +++ b/services/api.py @@ -533,6 +533,24 @@ class Meta: exclude = ["unit", "id"] +@extend_schema( + parameters=[ + OpenApiParameter( + name="id", + location=OpenApiParameter.QUERY, + description="Filter by ID or list of IDs.", + required=False, + type=str, + ), + OpenApiParameter( + name="ancestor", + location=OpenApiParameter.QUERY, + description="Filter by ancestor ID.", + required=False, + type=str, + ), + ] +) class ServiceNodeViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet): queryset = ServiceNode.objects.all() serializer_class = ServiceNodeSerializer From ef9a21d0ec13c7c65b4e7ee294e9e15693ec66a5 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:12:44 +0300 Subject: [PATCH 04/12] Document service API parameters --- services/api.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/api.py b/services/api.py index 6a8e3941..c341e726 100644 --- a/services/api.py +++ b/services/api.py @@ -578,6 +578,17 @@ def get_queryset(self): register_view(ServiceNodeViewSet, "service_node") +@extend_schema( + parameters=[ + OpenApiParameter( + name="id", + location=OpenApiParameter.QUERY, + description="Filter by ID or list of IDs.", + required=False, + type=str, + ), + ] +) class ServiceViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet): queryset = Service.objects.all() serializer_class = ServiceSerializer From cf6330fd928b4dc3259d3cf79ebee70f22622055 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:17:17 +0300 Subject: [PATCH 05/12] Document unit API parameters --- services/api.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/services/api.py b/services/api.py index c341e726..c613ba82 100644 --- a/services/api.py +++ b/services/api.py @@ -842,6 +842,59 @@ def render(self, data, media_type=None, renderer_context=None): return render_to_string("kml.xml", resp) +@extend_schema( + parameters=[ + OpenApiParameter( + name="id", + location=OpenApiParameter.QUERY, + description="Filter by ID or list of IDs.", + required=False, + type=str, + ), + OpenApiParameter( + name="municipality", + location=OpenApiParameter.QUERY, + description="Filter by municipality name or OCD ID.", + required=False, + type=str, + ), + OpenApiParameter( + name="organization", + location=OpenApiParameter.QUERY, + description="Filter by organization UUID.", + required=False, + type=str, + ), + OpenApiParameter( + name="city_as_department", + location=OpenApiParameter.QUERY, + description="Filter by city UUID.", + required=False, + type=str, + ), + OpenApiParameter( + name="provider_type", + location=OpenApiParameter.QUERY, + description="Filter by provider type numeric value.", + required=False, + type=int, + ), + OpenApiParameter( + name="provider_type__not", + location=OpenApiParameter.QUERY, + description="Exclude by provider type numeric value.", + required=False, + type=int, + ), + OpenApiParameter( + name="level", + location=OpenApiParameter.QUERY, + description="Filter by level.", + required=False, + type=str, + ), + ] +) class UnitViewSet( munigeo_api.GeoModelAPIView, JSONAPIViewSet, viewsets.ReadOnlyModelViewSet ): @@ -876,6 +929,12 @@ def get_queryset(self): id_list = filters["id"].split(",") queryset = queryset.filter(id__in=id_list) + for f in filters: + if f.startswith("extra__"): + queryset = queryset.filter( + **{f: int(filters[f]) if filters[f].isnumeric() else filters[f]} + ) + if "municipality" in filters: val = filters["municipality"].lower().strip() if len(val) > 0: From bed635a791e8720e3ba70d8c50ca80ee6d810192 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:51:34 +0300 Subject: [PATCH 06/12] Reduce OpenApi parameter duplication Refactor OpenAPI parameters to constants to reduce duplication. --- services/api.py | 246 +++++++------------------------- services/open_api_parameters.py | 174 ++++++++++++++++++++++ 2 files changed, 223 insertions(+), 197 deletions(-) create mode 100644 services/open_api_parameters.py diff --git a/services/api.py b/services/api.py index c613ba82..e0785557 100644 --- a/services/api.py +++ b/services/api.py @@ -15,7 +15,7 @@ from django.utils import timezone, translation from django.utils.module_loading import import_string from django_filters.rest_framework import DjangoFilterBackend -from drf_spectacular.utils import extend_schema, OpenApiParameter +from drf_spectacular.utils import extend_schema from modeltranslation.translator import NotRegistered, translator from mptt.utils import drilldown_tree_for_node from munigeo import api as munigeo_api @@ -42,6 +42,29 @@ UnitServiceDetails, ) from services.models.unit import ORGANIZER_TYPES, PROVIDER_TYPES +from services.open_api_parameters import ( + ANCESTOR_ID_PARAMETER, + BBOX_PARAMETER, + BUILDING_NUMBER_PARAMETER, + CITY_AS_DEPARTMENT_PARAMETER, + DATE_PARAMETER, + DISTANCE_PARAMETER, + DIVISION_TYPE_PARAMETER, + GEOMETRY_PARAMETER, + ID_PARAMETER, + INPUT_PARAMETER, + LATITUDE_PARAMETER, + LEVEL_PARAMETER, + LONGITUDE_PARAMETER, + MUNICIPALITY_PARAMETER, + OCD_ID_PARAMETER, + OCD_MUNICIPALITY_PARAMETER, + ORGANIZATION_PARAMETER, + ORIGIN_ID_PARAMETER, + PROVIDER_TYPE_NOT_PARAMETER, + PROVIDER_TYPE_PARAMETER, + STREET_PARAMETER, +) from services.utils import check_valid_concrete_field from services.utils.geocode_address import geocode_address @@ -533,24 +556,7 @@ class Meta: exclude = ["unit", "id"] -@extend_schema( - parameters=[ - OpenApiParameter( - name="id", - location=OpenApiParameter.QUERY, - description="Filter by ID or list of IDs.", - required=False, - type=str, - ), - OpenApiParameter( - name="ancestor", - location=OpenApiParameter.QUERY, - description="Filter by ancestor ID.", - required=False, - type=str, - ), - ] -) +@extend_schema(parameters=[ID_PARAMETER, ANCESTOR_ID_PARAMETER]) class ServiceNodeViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet): queryset = ServiceNode.objects.all() serializer_class = ServiceNodeSerializer @@ -578,17 +584,7 @@ def get_queryset(self): register_view(ServiceNodeViewSet, "service_node") -@extend_schema( - parameters=[ - OpenApiParameter( - name="id", - location=OpenApiParameter.QUERY, - description="Filter by ID or list of IDs.", - required=False, - type=str, - ), - ] -) +@extend_schema(parameters=[ID_PARAMETER]) class ServiceViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet): queryset = Service.objects.all() serializer_class = ServiceSerializer @@ -844,55 +840,13 @@ def render(self, data, media_type=None, renderer_context=None): @extend_schema( parameters=[ - OpenApiParameter( - name="id", - location=OpenApiParameter.QUERY, - description="Filter by ID or list of IDs.", - required=False, - type=str, - ), - OpenApiParameter( - name="municipality", - location=OpenApiParameter.QUERY, - description="Filter by municipality name or OCD ID.", - required=False, - type=str, - ), - OpenApiParameter( - name="organization", - location=OpenApiParameter.QUERY, - description="Filter by organization UUID.", - required=False, - type=str, - ), - OpenApiParameter( - name="city_as_department", - location=OpenApiParameter.QUERY, - description="Filter by city UUID.", - required=False, - type=str, - ), - OpenApiParameter( - name="provider_type", - location=OpenApiParameter.QUERY, - description="Filter by provider type numeric value.", - required=False, - type=int, - ), - OpenApiParameter( - name="provider_type__not", - location=OpenApiParameter.QUERY, - description="Exclude by provider type numeric value.", - required=False, - type=int, - ), - OpenApiParameter( - name="level", - location=OpenApiParameter.QUERY, - description="Filter by level.", - required=False, - type=str, - ), + ID_PARAMETER, + OCD_MUNICIPALITY_PARAMETER, + ORGANIZATION_PARAMETER, + CITY_AS_DEPARTMENT_PARAMETER, + PROVIDER_TYPE_PARAMETER, + PROVIDER_TYPE_NOT_PARAMETER, + LEVEL_PARAMETER, ] ) class UnitViewSet( @@ -1262,71 +1216,15 @@ def to_representation(self, obj): @extend_schema( parameters=[ - OpenApiParameter( - name="type", - location=OpenApiParameter.QUERY, - description="Filter by administrative division type or type ID.", - required=False, - type=str, - ), - OpenApiParameter( - name="lat", - location=OpenApiParameter.QUERY, - description="Filter by location. Give latitude in WGS84 system. If this parameter is given also the 'lon' " - "parameter is required.", - required=False, - type=float, - ), - OpenApiParameter( - name="lon", - location=OpenApiParameter.QUERY, - description="Filter by location. Give longitude in WGS84 system. If this parameter is given also the 'lat' " - "parameter is required.", - required=False, - type=float, - ), - OpenApiParameter( - name="input", - location=OpenApiParameter.QUERY, - description="Filter by partial match of name.", - required=False, - type=str, - ), - OpenApiParameter( - name="ocd_id", - location=OpenApiParameter.QUERY, - description="Filter by OCD ID.", - required=False, - type=str, - ), - OpenApiParameter( - name="geometry", - location=OpenApiParameter.QUERY, - description="Display administrative division boundary.", - required=False, - type=bool, - ), - OpenApiParameter( - name="origin_id", - location=OpenApiParameter.QUERY, - description="Filter by origin ID.", - required=False, - type=str, - ), - OpenApiParameter( - name="municipality", - location=OpenApiParameter.QUERY, - description="Filter by municipality.", - required=False, - type=str, - ), - OpenApiParameter( - name="date", - location=OpenApiParameter.QUERY, - description="Filter divisions based on their validity date. Format: YYYY-MM-DD.", - required=False, - type=str, - ), + DIVISION_TYPE_PARAMETER, + LATITUDE_PARAMETER, + LONGITUDE_PARAMETER, + INPUT_PARAMETER, + OCD_ID_PARAMETER, + GEOMETRY_PARAMETER, + ORIGIN_ID_PARAMETER, + MUNICIPALITY_PARAMETER, + DATE_PARAMETER, ] ) class AdministrativeDivisionViewSet(munigeo_api.AdministrativeDivisionViewSet): @@ -1356,59 +1254,13 @@ def get_queryset(self): @extend_schema( parameters=[ - OpenApiParameter( - name="street", - location=OpenApiParameter.QUERY, - description="Filter by street name.", - required=False, - type=str, - ), - OpenApiParameter( - name="municipality", - location=OpenApiParameter.QUERY, - description="Filter by municipality name or OCD ID.", - required=False, - type=str, - ), - OpenApiParameter( - name="number", - location=OpenApiParameter.QUERY, - description="Filter by building number.", - required=False, - type=str, - ), - OpenApiParameter( - name="lat", - location=OpenApiParameter.QUERY, - description="Filter by location. Give latitude in WGS84 system. If this parameter is given also the 'lon' " - "parameter is required.", - required=False, - type=float, - ), - OpenApiParameter( - name="lon", - location=OpenApiParameter.QUERY, - description="Filter by location. Give longitude in WGS84 system. If this parameter is given also the 'lat' " - "parameter is required.", - required=False, - type=float, - ), - OpenApiParameter( - name="distance", - location=OpenApiParameter.QUERY, - description="The maximum distance from the provided location, defined by the lat and lon parameters. " - "If this parameter is given also the 'lat' and 'lon' parameters are required.", - required=False, - type=float, - ), - OpenApiParameter( - name="bbox", - location=OpenApiParameter.QUERY, - description="Bounding box in the format 'left,bottom,right,top'. " - "Values must be floating points or integers.", - required=False, - type=str, - ), + STREET_PARAMETER, + OCD_MUNICIPALITY_PARAMETER, + BUILDING_NUMBER_PARAMETER, + LATITUDE_PARAMETER, + LONGITUDE_PARAMETER, + DISTANCE_PARAMETER, + BBOX_PARAMETER, ], ) class AddressViewSet(munigeo_api.AddressViewSet): diff --git a/services/open_api_parameters.py b/services/open_api_parameters.py new file mode 100644 index 00000000..008b27d4 --- /dev/null +++ b/services/open_api_parameters.py @@ -0,0 +1,174 @@ +from drf_spectacular.utils import OpenApiParameter + +ANCESTOR_ID_PARAMETER = OpenApiParameter( + name="ancestor", + location=OpenApiParameter.QUERY, + description="Filter by ancestor ID.", + required=False, + type=str, +) + +BBOX_PARAMETER = OpenApiParameter( + name="bbox", + location=OpenApiParameter.QUERY, + description="Bounding box in the format 'left,bottom,right,top'. Values must be floating points or integers.", + required=False, + type=str, +) + +BUILDING_NUMBER_PARAMETER = OpenApiParameter( + name="number", + location=OpenApiParameter.QUERY, + description="Filter by building number.", + required=False, + type=str, +) + +CITY_AS_DEPARTMENT_PARAMETER = OpenApiParameter( + name="city_as_department", + location=OpenApiParameter.QUERY, + description="Filter by city UUID.", + required=False, + type=str, +) + +DATE_PARAMETER = ( + OpenApiParameter( + name="date", + location=OpenApiParameter.QUERY, + description="Filter divisions based on their validity date. Format: YYYY-MM-DD.", + required=False, + type=str, + ), +) + +DISTANCE_PARAMETER = OpenApiParameter( + name="distance", + location=OpenApiParameter.QUERY, + description="The maximum distance from the provided location, defined by the lat and lon parameters. If this" + " parameter is given also the 'lat' and 'lon' parameters are required.", + required=False, + type=float, +) + +DIVISION_TYPE_PARAMETER = OpenApiParameter( + name="type", + location=OpenApiParameter.QUERY, + description="Filter by administrative division type or type ID.", + required=False, + type=str, +) + +GEOMETRY_PARAMETER = OpenApiParameter( + name="geometry", + location=OpenApiParameter.QUERY, + description="Display administrative division boundary.", + required=False, + type=bool, +) + +ID_PARAMETER = OpenApiParameter( + name="id", + location=OpenApiParameter.QUERY, + description="Filter by ID or list of IDs.", + required=False, + type=str, +) + +INPUT_PARAMETER = OpenApiParameter( + name="input", + location=OpenApiParameter.QUERY, + description="Filter by partial match of name.", + required=False, + type=str, +) + +LATITUDE_PARAMETER = OpenApiParameter( + name="lat", + location=OpenApiParameter.QUERY, + description="Filter by location. Give latitude in WGS84 system. If this parameter is given also the 'lon' " + "parameter is required.", + required=False, + type=float, +) + +LEVEL_PARAMETER = OpenApiParameter( + name="level", + location=OpenApiParameter.QUERY, + description="Filter by level.", + required=False, + type=str, +) + +LONGITUDE_PARAMETER = OpenApiParameter( + name="lon", + location=OpenApiParameter.QUERY, + description="Filter by location. Give longitude in WGS84 system. If this parameter is given also the 'lat' " + "parameter is required.", + required=False, + type=float, +) + +MUNICIPALITY_PARAMETER = OpenApiParameter( + name="municipality", + location=OpenApiParameter.QUERY, + description="Filter by municipality.", + required=False, + type=str, +) + +OCD_ID_PARAMETER = OpenApiParameter( + name="ocd_id", + location=OpenApiParameter.QUERY, + description="Filter by OCD ID.", + required=False, + type=str, +) + +OCD_MUNICIPALITY_PARAMETER = OpenApiParameter( + name="municipality", + location=OpenApiParameter.QUERY, + description="Filter by municipality name or OCD ID.", + required=False, + type=str, +) + +ORGANIZATION_PARAMETER = OpenApiParameter( + name="organization", + location=OpenApiParameter.QUERY, + description="Filter by organization UUID.", + required=False, + type=str, +) + +ORIGIN_ID_PARAMETER = OpenApiParameter( + name="origin_id", + location=OpenApiParameter.QUERY, + description="Filter by origin ID.", + required=False, + type=str, +) + +PROVIDER_TYPE_NOT_PARAMETER = OpenApiParameter( + name="provider_type__not", + location=OpenApiParameter.QUERY, + description="Exclude by provider type numeric value.", + required=False, + type=int, +) + +PROVIDER_TYPE_PARAMETER = OpenApiParameter( + name="provider_type", + location=OpenApiParameter.QUERY, + description="Filter by provider type numeric value.", + required=False, + type=int, +) + +STREET_PARAMETER = OpenApiParameter( + name="street", + location=OpenApiParameter.QUERY, + description="Filter by street name.", + required=False, + type=str, +) From 37e36afd5f6b27ebbc693cdd395fc0a2e25dcd50 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:52:34 +0300 Subject: [PATCH 07/12] Add services endpoints to DOC_ENDPOINTS --- smbackend/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/smbackend/settings.py b/smbackend/settings.py index 14ada4f3..3fa06f3c 100644 --- a/smbackend/settings.py +++ b/smbackend/settings.py @@ -358,6 +358,11 @@ def gettext(s): "/exceptional_situations/api/v1/situation/", "/exceptional_situations/api/v1/situation_type/", "/api/v2/search", + "/api/v2/address/", + "/api/v2/administrative_division/", + "/api/v2/service_node/", + "/api/v2/service/", + "/api/v2/unit/", ] From c66f56fd10d9aa2b9ff2cb3aee208a331924b5bd Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:13:33 +0300 Subject: [PATCH 08/12] Use only departments with unit organization-filter Filter with unit root_departments and departments with organization-filter. Do not auto-include department's municipalities. --- services/api.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/services/api.py b/services/api.py index 9f57361e..26e85c94 100644 --- a/services/api.py +++ b/services/api.py @@ -864,8 +864,18 @@ def get_queryset(self): queryset = queryset.filter(muni_sq) - if "city_as_department" in filters: - val = filters["city_as_department"].lower().strip() + if "organization" in filters or "city_as_department" in filters: + val = ( + filters["organization"].lower().strip() + if "organization" in filters + else "" + ) + if len(val) == 0: + val = ( + filters["city_as_department"].lower().strip() + if "city_as_department" in filters + else "" + ) if len(val) > 0: deps_uuids = val.split(",") @@ -875,16 +885,12 @@ def get_queryset(self): uuid.UUID(deps_uuid) except ValueError: raise serializers.ValidationError( - "'city_as_department' value must be a valid UUID" + "'organization' value must be a valid UUID" ) - deps = Department.objects.filter(uuid__in=deps_uuids).select_related( - "municipality" - ) - munis = [d.municipality for d in deps] - - queryset = queryset.filter(root_department__in=deps) | queryset.filter( - municipality__in=munis + deps = Department.objects.filter(uuid__in=deps_uuids) + queryset = queryset.filter(department__in=deps) | queryset.filter( + root_department__in=deps ) if "provider_type" in filters: From ee8415b9d8cb001ffe194c23e25b72af407d889e Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:22:21 +0300 Subject: [PATCH 09/12] Fix translated fields in OpenAPI docs --- services/api.py | 53 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/services/api.py b/services/api.py index e0785557..49303bde 100644 --- a/services/api.py +++ b/services/api.py @@ -15,7 +15,11 @@ from django.utils import timezone, translation from django.utils.module_loading import import_string from django_filters.rest_framework import DjangoFilterBackend -from drf_spectacular.utils import extend_schema +from drf_spectacular.utils import ( + extend_schema, + extend_schema_field, + extend_schema_serializer, +) from modeltranslation.translator import NotRegistered, translator from mptt.utils import drilldown_tree_for_node from munigeo import api as munigeo_api @@ -92,6 +96,17 @@ def register_view(klass, name, basename=None): logger = logging.getLogger(__name__) +class TranslationsSerializer(serializers.Serializer): + fi = serializers.CharField(required=False) + sv = serializers.CharField(required=False) + en = serializers.CharField(required=False) + + +@extend_schema_field(TranslationsSerializer) +class TranslationsField(serializers.CharField): + pass + + class MPTTModelSerializer(serializers.ModelSerializer): def __init__(self, *args, **kwargs): super(MPTTModelSerializer, self).__init__(*args, **kwargs) @@ -210,6 +225,13 @@ def to_representation(self, obj): return ret +class ServicesTranslatedModelSerializer(TranslatedModelSerializer): + def __init__(self, *args, **kwargs): + super(ServicesTranslatedModelSerializer, self).__init__(*args, **kwargs) + for field_name in self.translated_fields: + self.fields[field_name] = TranslationsField() + + def root_services(services): tree_ids = set(s.tree_id for s in services) return map( @@ -274,7 +296,7 @@ def to_representation(self, obj): class DepartmentSerializer( - TranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer + ServicesTranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer ): id = serializers.SerializerMethodField("get_uuid") parent = serializers.SerializerMethodField() @@ -296,7 +318,7 @@ def get_parent(self, obj): class ServiceNodeSerializer( - TranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer + ServicesTranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer ): children = serializers.PrimaryKeyRelatedField(many=True, read_only=True) @@ -350,7 +372,7 @@ class Meta: ) -class ServiceSerializer(TranslatedModelSerializer, JSONAPISerializer): +class ServiceSerializer(ServicesTranslatedModelSerializer, JSONAPISerializer): def to_representation(self, obj): ret = super(ServiceSerializer, self).to_representation(obj) ret["unit_count"] = {"municipality": {}} @@ -392,7 +414,7 @@ class Meta: fields = ["name", "root_service_node"] -class ServiceDetailsSerializer(TranslatedModelSerializer, JSONAPISerializer): +class ServiceDetailsSerializer(ServicesTranslatedModelSerializer, JSONAPISerializer): def to_representation(self, obj): ret = super(ServiceDetailsSerializer, self).to_representation(obj) service_data = RelatedServiceSerializer(obj.service).data @@ -496,7 +518,9 @@ def choicefield_string(choices, key, obj): return None -class UnitConnectionSerializer(TranslatedModelSerializer, serializers.ModelSerializer): +class UnitConnectionSerializer( + ServicesTranslatedModelSerializer, serializers.ModelSerializer +): section_type = serializers.SerializerMethodField() class Meta: @@ -515,7 +539,9 @@ class UnitConnectionViewSet(viewsets.ReadOnlyModelViewSet): register_view(UnitConnectionViewSet, "unit_connection") -class UnitEntranceSerializer(TranslatedModelSerializer, munigeo_api.GeoModelSerializer): +class UnitEntranceSerializer( + ServicesTranslatedModelSerializer, munigeo_api.GeoModelSerializer +): location = serializers.SerializerMethodField() class Meta: @@ -526,7 +552,11 @@ def get_location(self, obj): return munigeo_api.geom_to_json(obj.location, self.srs) -class UnitEntranceViewSet(munigeo_api.GeoModelAPIView, viewsets.ReadOnlyModelViewSet): +class UnitEntranceViewSet( + ServicesTranslatedModelSerializer, + munigeo_api.GeoModelAPIView, + viewsets.ReadOnlyModelViewSet, +): queryset = UnitEntrance.objects.all() serializer_class = UnitEntranceSerializer @@ -613,7 +643,7 @@ def get_queryset(self): class UnitSerializer( - TranslatedModelSerializer, munigeo_api.GeoModelSerializer, JSONAPISerializer + ServicesTranslatedModelSerializer, munigeo_api.GeoModelSerializer, JSONAPISerializer ): connections = UnitConnectionSerializer(many=True) entrances = UnitEntranceSerializer(many=True) @@ -1170,6 +1200,7 @@ def list(self, request, *args, **kwargs): ) +@extend_schema_serializer(deprecate_fields=["service_point_id"]) class AdministrativeDivisionSerializer(munigeo_api.AdministrativeDivisionSerializer): def to_representation(self, obj): ret = super(AdministrativeDivisionSerializer, self).to_representation(obj) @@ -1277,7 +1308,7 @@ class PostalCodeAreaViewSet(munigeo_api.PostalCodeAreaViewSet): register_view(PostalCodeAreaViewSet, "postalcodearea") -class AnnouncementSerializer(TranslatedModelSerializer, JSONAPISerializer): +class AnnouncementSerializer(ServicesTranslatedModelSerializer, JSONAPISerializer): class Meta: model = Announcement exclude = ["id", "active"] @@ -1291,7 +1322,7 @@ class AnnouncementViewSet(viewsets.ReadOnlyModelViewSet): register_view(AnnouncementViewSet, "announcement") -class ErrorMessageSerializer(TranslatedModelSerializer, JSONAPISerializer): +class ErrorMessageSerializer(ServicesTranslatedModelSerializer, JSONAPISerializer): class Meta: model = ErrorMessage exclude = ["id", "active"] From 41e5d05193420bc1b61112be4471d2827c81bf05 Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:18:44 +0300 Subject: [PATCH 10/12] Add missing unit API parameters --- services/api.py | 4 ++++ services/open_api_parameters.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/services/api.py b/services/api.py index 49303bde..b43809e0 100644 --- a/services/api.py +++ b/services/api.py @@ -47,6 +47,7 @@ ) from services.models.unit import ORGANIZER_TYPES, PROVIDER_TYPES from services.open_api_parameters import ( + ACCESSIBILITY_DESCRIPTION_PARAMETER, ANCESTOR_ID_PARAMETER, BBOX_PARAMETER, BUILDING_NUMBER_PARAMETER, @@ -68,6 +69,7 @@ PROVIDER_TYPE_NOT_PARAMETER, PROVIDER_TYPE_PARAMETER, STREET_PARAMETER, + UNIT_GEOMETRY_PARAMETER, ) from services.utils import check_valid_concrete_field from services.utils.geocode_address import geocode_address @@ -870,6 +872,7 @@ def render(self, data, media_type=None, renderer_context=None): @extend_schema( parameters=[ + ACCESSIBILITY_DESCRIPTION_PARAMETER, ID_PARAMETER, OCD_MUNICIPALITY_PARAMETER, ORGANIZATION_PARAMETER, @@ -877,6 +880,7 @@ def render(self, data, media_type=None, renderer_context=None): PROVIDER_TYPE_PARAMETER, PROVIDER_TYPE_NOT_PARAMETER, LEVEL_PARAMETER, + UNIT_GEOMETRY_PARAMETER, ] ) class UnitViewSet( diff --git a/services/open_api_parameters.py b/services/open_api_parameters.py index 008b27d4..0238cf2d 100644 --- a/services/open_api_parameters.py +++ b/services/open_api_parameters.py @@ -1,5 +1,13 @@ from drf_spectacular.utils import OpenApiParameter +ACCESSIBILITY_DESCRIPTION_PARAMETER = OpenApiParameter( + name="accessibility_description", + location=OpenApiParameter.QUERY, + description="If given displays the accessibility description of unit.", + required=False, + type=bool, +) + ANCESTOR_ID_PARAMETER = OpenApiParameter( name="ancestor", location=OpenApiParameter.QUERY, @@ -172,3 +180,11 @@ required=False, type=str, ) + +UNIT_GEOMETRY_PARAMETER = OpenApiParameter( + name="geometry", + location=OpenApiParameter.QUERY, + description="If given displays the geometry of unit if it exists.", + required=False, + type=bool, +) From 17a5a23be9965c4ae12f54a56bd40ac7a3c6295c Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:39:43 +0300 Subject: [PATCH 11/12] Add field geometry_3d --- services/api.py | 9 +++++++++ services/migrations/0103_unit_geometry_3d.py | 21 ++++++++++++++++++++ services/models/unit.py | 2 ++ services/open_api_parameters.py | 9 +++++++++ 4 files changed, 41 insertions(+) create mode 100644 services/migrations/0103_unit_geometry_3d.py diff --git a/services/api.py b/services/api.py index 13f520e2..6c4d84be 100644 --- a/services/api.py +++ b/services/api.py @@ -69,6 +69,7 @@ PROVIDER_TYPE_NOT_PARAMETER, PROVIDER_TYPE_PARAMETER, STREET_PARAMETER, + UNIT_GEOMETRY_3D_PARAMETER, UNIT_GEOMETRY_PARAMETER, ) from services.utils import check_valid_concrete_field @@ -807,6 +808,13 @@ def to_representation(self, obj): elif "geometry" in ret: del ret["geometry"] + if qparams.get("geometry_3d", "").lower() in ("true", "1"): + geom = obj.geometry_3d + if geom: + ret["geometry_3d"] = munigeo_api.geom_to_json(geom, self.srs) + elif "geometry_3d" in ret: + del ret["geometry_3d"] + if qparams.get("accessibility_description", "").lower() in ("true", "1"): ret["accessibility_description"] = shortcomings.accessibility_description return ret @@ -881,6 +889,7 @@ def render(self, data, media_type=None, renderer_context=None): PROVIDER_TYPE_NOT_PARAMETER, LEVEL_PARAMETER, UNIT_GEOMETRY_PARAMETER, + UNIT_GEOMETRY_3D_PARAMETER, ] ) class UnitViewSet( diff --git a/services/migrations/0103_unit_geometry_3d.py b/services/migrations/0103_unit_geometry_3d.py new file mode 100644 index 00000000..537fb41e --- /dev/null +++ b/services/migrations/0103_unit_geometry_3d.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.13 on 2024-07-04 06:06 + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("services", "0102_unit_new_contract_type_fields"), + ] + + operations = [ + migrations.AddField( + model_name="unit", + name="geometry_3d", + field=django.contrib.gis.db.models.fields.GeometryField( + dim=3, null=True, srid=3067 + ), + ), + ] diff --git a/services/models/unit.py b/services/models/unit.py index 7100a098..6dad5bee 100644 --- a/services/models/unit.py +++ b/services/models/unit.py @@ -101,6 +101,8 @@ class Unit(SoftDeleteModel): location = models.PointField(null=True, srid=PROJECTION_SRID) # lat, lng? geometry = models.GeometryField(srid=PROJECTION_SRID, null=True) + geometry_3d = models.GeometryField(srid=PROJECTION_SRID, null=True, dim=3) + department = models.ForeignKey(Department, null=True, on_delete=models.CASCADE) root_department = models.ForeignKey( Department, null=True, related_name="descendant_units", on_delete=models.CASCADE diff --git a/services/open_api_parameters.py b/services/open_api_parameters.py index 0238cf2d..5ea2ad53 100644 --- a/services/open_api_parameters.py +++ b/services/open_api_parameters.py @@ -181,6 +181,14 @@ type=str, ) +UNIT_GEOMETRY_3D_PARAMETER = OpenApiParameter( + name="geometry_3d", + location=OpenApiParameter.QUERY, + description="If given displays the 3D geometry of unit if it exists.", + required=False, + type=bool, +) + UNIT_GEOMETRY_PARAMETER = OpenApiParameter( name="geometry", location=OpenApiParameter.QUERY, @@ -188,3 +196,4 @@ required=False, type=bool, ) + From 63f200787a9119c53fa593737133f31066f832dd Mon Sep 17 00:00:00 2001 From: juuso-j <68938778+juuso-j@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:56:31 +0300 Subject: [PATCH 12/12] Format --- services/open_api_parameters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/open_api_parameters.py b/services/open_api_parameters.py index 5ea2ad53..83e57fde 100644 --- a/services/open_api_parameters.py +++ b/services/open_api_parameters.py @@ -196,4 +196,3 @@ required=False, type=bool, ) -