Skip to content

Commit

Permalink
Merge pull request #371 from City-of-Turku/feature/services-api-docum…
Browse files Browse the repository at this point in the history
…entation

Feature/services api documentation
  • Loading branch information
juuso-j committed Aug 26, 2024
2 parents b618f46 + bbff1d2 commit d06fedd
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 20 deletions.
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

0 comments on commit d06fedd

Please sign in to comment.