Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/services api documentation #371

Merged
merged 20 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a5e836a
Merge branch 'feature/filter-administrative-divisions-by-address' int…
juuso-j Jul 2, 2024
2108637
Merge branch 'feature/unit-contract-type-replace-with-displayed-servi…
juuso-j Jul 2, 2024
5d774e9
Document address API parameters
juuso-j Jul 2, 2024
b9d9321
Document administrative division API parameters
juuso-j Jul 2, 2024
7f26477
Document service node API parameters
juuso-j Jul 2, 2024
ef9a21d
Document service API parameters
juuso-j Jul 2, 2024
cf6330f
Document unit API parameters
juuso-j Jul 2, 2024
bed635a
Reduce OpenApi parameter duplication
juuso-j Jul 2, 2024
37e36af
Add services endpoints to DOC_ENDPOINTS
juuso-j Jul 2, 2024
c66f56f
Use only departments with unit organization-filter
juuso-j Jul 3, 2024
ee8415b
Fix translated fields in OpenAPI docs
juuso-j Jul 3, 2024
448f4a8
Merge branch 'feature/unit-contract-type-replace-with-displayed-servi…
juuso-j Jul 4, 2024
61382ba
Merge branch 'feature/filter-administrative-divisions-by-address' int…
juuso-j Jul 4, 2024
73801a4
Merge branch 'feature/services-api-documentation' into feature/unit-a…
juuso-j Jul 4, 2024
c2f5bbd
Merge branch 'feature/use-only-departments-with-unit-organization-fil…
juuso-j Jul 4, 2024
41e5d05
Add missing unit API parameters
juuso-j Jul 4, 2024
ec87323
Merge branch 'feature/services-api-documentation' into feature/unit-a…
juuso-j Jul 4, 2024
17a5a23
Add field geometry_3d
juuso-j Jul 4, 2024
63f2007
Format
juuso-j Jul 4, 2024
bbff1d2
Merge branch 'develop' into feature/services-api-documentation
juuso-j Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 137 additions & 20 deletions services/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +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,
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
Expand All @@ -41,6 +46,32 @@
UnitServiceDetails,
)
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,
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,
UNIT_GEOMETRY_3D_PARAMETER,
UNIT_GEOMETRY_PARAMETER,
)
from services.utils import check_valid_concrete_field
from services.utils.geocode_address import geocode_address

Expand Down Expand Up @@ -68,6 +99,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)
Expand Down Expand Up @@ -186,6 +228,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(
Expand Down Expand Up @@ -250,7 +299,7 @@ def to_representation(self, obj):


class DepartmentSerializer(
TranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer
ServicesTranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer
):
id = serializers.SerializerMethodField("get_uuid")
parent = serializers.SerializerMethodField()
Expand All @@ -272,7 +321,7 @@ def get_parent(self, obj):


class ServiceNodeSerializer(
TranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer
ServicesTranslatedModelSerializer, MPTTModelSerializer, JSONAPISerializer
):
children = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

Expand Down Expand Up @@ -326,7 +375,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": {}}
Expand Down Expand Up @@ -368,7 +417,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
Expand Down Expand Up @@ -472,7 +521,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:
Expand All @@ -491,7 +542,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:
Expand All @@ -502,7 +555,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

Expand Down Expand Up @@ -532,6 +589,7 @@ class Meta:
exclude = ["unit", "id"]


@extend_schema(parameters=[ID_PARAMETER, ANCESTOR_ID_PARAMETER])
class ServiceNodeViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet):
queryset = ServiceNode.objects.all()
serializer_class = ServiceNodeSerializer
Expand Down Expand Up @@ -559,6 +617,7 @@ def get_queryset(self):
register_view(ServiceNodeViewSet, "service_node")


@extend_schema(parameters=[ID_PARAMETER])
class ServiceViewSet(JSONAPIViewSet, viewsets.ReadOnlyModelViewSet):
queryset = Service.objects.all()
serializer_class = ServiceSerializer
Expand Down Expand Up @@ -587,7 +646,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)
Expand Down Expand Up @@ -749,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
Expand Down Expand Up @@ -812,6 +878,20 @@ def render(self, data, media_type=None, renderer_context=None):
return render_to_string("kml.xml", resp)


@extend_schema(
parameters=[
ACCESSIBILITY_DESCRIPTION_PARAMETER,
ID_PARAMETER,
OCD_MUNICIPALITY_PARAMETER,
ORGANIZATION_PARAMETER,
CITY_AS_DEPARTMENT_PARAMETER,
PROVIDER_TYPE_PARAMETER,
PROVIDER_TYPE_NOT_PARAMETER,
LEVEL_PARAMETER,
UNIT_GEOMETRY_PARAMETER,
UNIT_GEOMETRY_3D_PARAMETER,
]
)
class UnitViewSet(
munigeo_api.GeoModelAPIView, JSONAPIViewSet, viewsets.ReadOnlyModelViewSet
):
Expand Down Expand Up @@ -846,6 +926,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:
Expand All @@ -866,8 +952,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(",")
Expand All @@ -877,16 +973,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:
Expand Down Expand Up @@ -1127,6 +1219,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)
Expand Down Expand Up @@ -1171,6 +1264,19 @@ def to_representation(self, obj):
return ret


@extend_schema(
parameters=[
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):
serializer_class = AdministrativeDivisionSerializer

Expand All @@ -1196,6 +1302,17 @@ def get_queryset(self):
register_view(AdministrativeDivisionViewSet, "administrative_division")


@extend_schema(
parameters=[
STREET_PARAMETER,
OCD_MUNICIPALITY_PARAMETER,
BUILDING_NUMBER_PARAMETER,
LATITUDE_PARAMETER,
LONGITUDE_PARAMETER,
DISTANCE_PARAMETER,
BBOX_PARAMETER,
],
)
class AddressViewSet(munigeo_api.AddressViewSet):
serializer_class = munigeo_api.AddressSerializer

Expand All @@ -1210,7 +1327,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"]
Expand All @@ -1224,7 +1341,7 @@ class AnnouncementViewSet(viewsets.ReadOnlyModelViewSet):
register_view(AnnouncementViewSet, "announcement")


class ErrorMessageSerializer(TranslatedModelSerializer, JSONAPISerializer):
class ErrorMessageSerializer(ServicesTranslatedModelSerializer, JSONAPISerializer):
class Meta:
model = ErrorMessage
exclude = ["id", "active"]
Expand Down
21 changes: 21 additions & 0 deletions services/migrations/0103_unit_geometry_3d.py
Original file line number Diff line number Diff line change
@@ -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
),
),
]
2 changes: 2 additions & 0 deletions services/models/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading