diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index d06be651..987deb88 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -82,6 +82,7 @@ class LocationSlug(SerializableMixin, models.Model): def _serialize(self, **kwargs): result = super()._serialize(**kwargs) + result["locationtype"] = self.__class__.__name__.lower() result['slug'] = self.get_slug() return result diff --git a/src/c3nav/mapdata/newapi/map.py b/src/c3nav/mapdata/newapi/map.py index 9dc51347..a9d32ad7 100644 --- a/src/c3nav/mapdata/newapi/map.py +++ b/src/c3nav/mapdata/newapi/map.py @@ -1,8 +1,15 @@ +from django.core.cache import cache +from ninja import Query from ninja import Router as APIRouter +from ninja import Schema +from pydantic import Field as APIField from c3nav.api.newauth import auth_responses from c3nav.mapdata.models import Source +from c3nav.mapdata.models.access import AccessPermission +from c3nav.mapdata.schemas.models import AnyLocationSchema from c3nav.mapdata.schemas.responses import BoundsSchema +from c3nav.mapdata.utils.locations import searchable_locations_for_request, visible_locations_for_request map_api_router = APIRouter(tags=["map"]) @@ -13,3 +20,43 @@ def bounds(request): return { "bounds": Source.max_bounds(), } + + +class LocationEndpointParameters(Schema): + searchable: bool = APIField( + False, + title='only list searchable locations', + description='if set, only searchable locations will be listed' + ) + + +def can_access_geometry(request): + return True # todo: implementFd + + +@map_api_router.get('/locations/', response={200: list[AnyLocationSchema], **auth_responses}, + summary="Get map boundaries") +def location_list(request, params: Query[LocationEndpointParameters]): + # todo: cache, visibility, etc… + searchable = params.searchable + detailed = True # todo: configurable? + geometry = True # todo: configurable + + cache_key = 'mapdata:api:location:list:%d:%s:%d' % ( + searchable + detailed*2 + geometry*4, + AccessPermission.cache_key_for_request(request), + request.user_permissions.can_access_base_mapdata + ) + result = cache.get(cache_key, None) + if result is None: + if searchable: + locations = searchable_locations_for_request(request) + else: + locations = visible_locations_for_request(request).values() + + result = tuple(obj.serialize(detailed=detailed, search=searchable, + geometry=geometry and can_access_geometry(request), + simple_geometry=True) + for obj in locations) + cache.set(cache_key, result, 300) + return result diff --git a/src/c3nav/mapdata/schemas/model_base.py b/src/c3nav/mapdata/schemas/model_base.py index 9b216ddb..c2d94be0 100644 --- a/src/c3nav/mapdata/schemas/model_base.py +++ b/src/c3nav/mapdata/schemas/model_base.py @@ -15,7 +15,8 @@ class SerializableSchema(Schema): @classmethod def _run_root_validator(cls, values: Any, handler: ModelWrapValidatorHandler[Schema], info: ValidationInfo) -> Any: """ overwriting this, we need to call serialize to get the correct data """ - values = values.serialize() + if not isinstance(values, dict): + values = values.serialize() return handler(values) @@ -155,3 +156,24 @@ class WithSpaceSchema(SerializableSchema): title="space", description="space id this object belongs to.", ) + + +class SimpleGeometryBoundsSchema(Schema): + bounds: tuple[tuple[float, float], tuple[float, float]] = APIField( + description="location bounding box from (x, y) to (x, y)", + example=((-10, -20), (20, 30)), + ) + + +class SimpleGeometryBoundsAndPointSchema(SimpleGeometryBoundsSchema): + point: tuple[PositiveInt, float, float] = APIField( + title="point representation of the location", + description="made from the level ID, X and Y in this order", + ) + + +class SimpleGeometryLocationsSchema(Schema): + locations: list[PositiveInt] = APIField( # todo: this should be a set… but json serialization? + description="IDs of all locations that belong to this grouo", + example=(1, 2, 3), + ) diff --git a/src/c3nav/mapdata/schemas/models.py b/src/c3nav/mapdata/schemas/models.py index 078d7e04..e6b3999b 100644 --- a/src/c3nav/mapdata/schemas/models.py +++ b/src/c3nav/mapdata/schemas/models.py @@ -1,10 +1,15 @@ -from typing import Optional +from typing import Annotated, Literal, Optional, Union +from pydantic import Discriminator from pydantic import Field as APIField -from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt +from pydantic import GetJsonSchemaHandler, NonNegativeFloat, PositiveFloat, PositiveInt +from pydantic.json_schema import JsonSchemaValue +from pydantic_core import CoreSchema from c3nav.api.utils import NonEmptyStr from c3nav.mapdata.schemas.model_base import (DjangoModelSchema, LabelSettingsSchema, LocationSchema, + LocationSlugSchema, SimpleGeometryBoundsAndPointSchema, + SimpleGeometryBoundsSchema, SimpleGeometryLocationsSchema, SpecificLocationSchema, TitledSchema, WithAccessRestrictionSchema, WithLevelSchema, WithLineStringGeometrySchema, WithPointGeometrySchema, WithPolygonGeometrySchema, WithSpaceSchema) @@ -308,3 +313,57 @@ class AccessRestrictionGroupSchema(WithAccessRestrictionSchema, DjangoModelSchem For simplicity's sake, access restrictions can belong to groups, and you can grant permissions for the entire group. """ pass + + +class TypedLevelSchema(LevelSchema): + """ + A level for the location API. + See Level schema for details. + """ + locationtype: Literal["level"] + + +class TypedSpaceSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema): + """ + A space with some additional information for the location API. + See Space schema for details. + """ + locationtype: Literal["space"] + + +class TypedAreaSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema): + """ + An area with some additional information for the location API. + See Area schema for details. + """ + locationtype: Literal["area"] + + +class TypedPOISchema(SimpleGeometryBoundsSchema, POISchema): + """ + A point of interest with some additional information for the location API. + See POI schema for details. + """ + locationtype: Literal["poi"] + + +class TypedLocationGroupSchema(SimpleGeometryLocationsSchema, LocationGroupSchema): + """ + A locagroun group with some additional information for the location API. + See LocationGroup schema for details. + """ + locationtype: Literal["locationgroup"] + + +AnyLocationSchema = Annotated[ + Union[ + TypedLevelSchema, + TypedSpaceSchema, + TypedAreaSchema, + TypedPOISchema, + TypedLocationGroupSchema, + ], + Discriminator("locationtype"), +] + +