add slim and full location list endpoint, geometry optional

This commit is contained in:
Laura Klünder 2023-11-23 18:10:31 +01:00
parent 8b47a89865
commit 87e5e56ea1
6 changed files with 148 additions and 31 deletions

View file

@ -82,7 +82,7 @@ class GeometryMixin(SerializableMixin):
def _serialize(self, geometry=True, simple_geometry=False, **kwargs): def _serialize(self, geometry=True, simple_geometry=False, **kwargs):
result = super()._serialize(simple_geometry=simple_geometry, **kwargs) result = super()._serialize(simple_geometry=simple_geometry, **kwargs)
if geometry: if geometry and "geometry" not in self.get_deferred_fields():
result['geometry'] = format_geojson(smart_mapping(self.geometry), rounded=False) result['geometry'] = format_geojson(smart_mapping(self.geometry), rounded=False)
if simple_geometry: if simple_geometry:
result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0]) result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0])

View file

@ -113,7 +113,8 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
result = super().serialize(detailed=detailed, **kwargs) result = super().serialize(detailed=detailed, **kwargs)
if not detailed: if not detailed:
fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square', fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square',
'locations', 'on_top_of', 'label_settings', 'label_override', 'add_search', 'dynamic') 'locations', 'on_top_of', 'label_settings', 'label_override', 'add_search', 'dynamic',
'locationtype', 'geometry')
result = {name: result[name] for name in fields if name in result} result = {name: result[name] for name in fields if name in result}
return result return result

View file

@ -7,7 +7,8 @@ from pydantic import Field as APIField
from c3nav.api.newauth import auth_responses from c3nav.api.newauth import auth_responses
from c3nav.mapdata.models import Source from c3nav.mapdata.models import Source
from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.access import AccessPermission
from c3nav.mapdata.schemas.models import AnyLocationSchema from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter
from c3nav.mapdata.schemas.models import FullLocationSchema, SlimLocationSchema
from c3nav.mapdata.schemas.responses import BoundsSchema from c3nav.mapdata.schemas.responses import BoundsSchema
from c3nav.mapdata.utils.locations import searchable_locations_for_request, visible_locations_for_request from c3nav.mapdata.utils.locations import searchable_locations_for_request, visible_locations_for_request
@ -34,29 +35,39 @@ def can_access_geometry(request):
return True # todo: implementFd return True # todo: implementFd
@map_api_router.get('/locations/', response={200: list[AnyLocationSchema], **auth_responses}, class LocationListFilters(BySearchableFilter, RemoveGeometryFilter):
summary="Get map boundaries") pass
def location_list(request, params: Query[LocationEndpointParameters]):
# todo: cache, visibility, etc…
searchable = params.searchable
detailed = True # todo: configurable?
geometry = True # todo: configurable
def _location_list(request, detailed: bool, filters: LocationListFilters):
# todo: cache, visibility, etc…
cache_key = 'mapdata:api:location:list:%d:%s:%d' % ( cache_key = 'mapdata:api:location:list:%d:%s:%d' % (
searchable + detailed*2 + geometry*4, filters.searchable + detailed*2 + filters.geometry*4,
AccessPermission.cache_key_for_request(request), AccessPermission.cache_key_for_request(request),
request.user_permissions.can_access_base_mapdata request.user_permissions.can_access_base_mapdata
) )
result = cache.get(cache_key, None) result = cache.get(cache_key, None)
if result is None: if result is None:
if searchable: if filters.searchable:
locations = searchable_locations_for_request(request) locations = searchable_locations_for_request(request)
else: else:
locations = visible_locations_for_request(request).values() locations = visible_locations_for_request(request).values()
result = tuple(obj.serialize(detailed=detailed, search=searchable, result = tuple(obj.serialize(detailed=detailed, search=filters.searchable,
geometry=geometry and can_access_geometry(request), geometry=filters.geometry and can_access_geometry(request),
simple_geometry=True) simple_geometry=True)
for obj in locations) for obj in locations)
cache.set(cache_key, result, 300) cache.set(cache_key, result, 300)
return result return result
@map_api_router.get('/locations/', response={200: list[SlimLocationSchema], **auth_responses},
summary="Get locations (with most important attributes)")
def location_list(request, filters: Query[LocationListFilters]):
return _location_list(request, detailed=False, filters=filters)
@map_api_router.get('/locations/full/', response={200: list[FullLocationSchema], **auth_responses},
summary="Get locations (with all attributes)")
def location_list_full(request, filters: Query[LocationListFilters]):
return _location_list(request, detailed=True, filters=filters)

View file

@ -134,3 +134,29 @@ class ByOnTopOfFilter(FilterSchema):
if self.on_top_of is not None: if self.on_top_of is not None:
qs = qs.filter(on_top_of_id=None if self.on_top_of == "null" else self.on_top_of) qs = qs.filter(on_top_of_id=None if self.on_top_of == "null" else self.on_top_of)
return super().filter_qs(qs) return super().filter_qs(qs)
class BySearchableFilter(FilterSchema):
searchable: bool = APIField(
False,
title='searchable locations only',
description='only show locations that should show up in search'
)
def filter_qs(self, qs: QuerySet) -> QuerySet:
if self.searchable is not None:
qs = qs.filter(can_search=True)
return super().filter_qs(qs)
class RemoveGeometryFilter(FilterSchema):
geometry: bool = APIField(
False, # todo: should be false
title='include geometry',
description='by default, geometry will be ommited. set to true to include it (if available)'
)
def filter_qs(self, qs: QuerySet) -> QuerySet:
if not self.geometry:
qs = qs.defer('geometry')
return super().filter_qs(qs)

View file

@ -1,4 +1,4 @@
from typing import Any, Optional from typing import Any, ClassVar, Optional
from ninja import Schema from ninja import Schema
from pydantic import Field as APIField from pydantic import Field as APIField
@ -127,20 +127,26 @@ class SpecificLocationSchema(LocationSchema):
class WithPolygonGeometrySchema(Schema): class WithPolygonGeometrySchema(Schema):
geometry: PolygonSchema = APIField( geometry: Optional[PolygonSchema] = APIField(
None,
title="geometry", title="geometry",
description="can be null if not available or excluded from endpoint",
) )
class WithLineStringGeometrySchema(Schema): class WithLineStringGeometrySchema(Schema):
geometry: LineStringSchema = APIField( geometry: Optional[LineStringSchema] = APIField(
None,
title="geometry", title="geometry",
description="can be null if not available or excluded from endpoint",
) )
class WithPointGeometrySchema(Schema): class WithPointGeometrySchema(Schema):
geometry: PointSchema = APIField( geometry: Optional[PointSchema] = APIField(
None,
title="geometry", title="geometry",
description="can be null if not available or excluded from endpoint",
) )
@ -177,3 +183,4 @@ class SimpleGeometryLocationsSchema(Schema):
description="IDs of all locations that belong to this grouo", description="IDs of all locations that belong to this grouo",
example=(1, 2, 3), example=(1, 2, 3),
) )

View file

@ -1,5 +1,6 @@
from typing import Annotated, Literal, Optional, Union from typing import Annotated, ClassVar, Literal, Optional, Union
from ninja import Schema
from pydantic import Discriminator from pydantic import Discriminator
from pydantic import Field as APIField from pydantic import Field as APIField
from pydantic import GetJsonSchemaHandler, NonNegativeFloat, PositiveFloat, PositiveInt from pydantic import GetJsonSchemaHandler, NonNegativeFloat, PositiveFloat, PositiveInt
@ -315,7 +316,7 @@ class AccessRestrictionGroupSchema(WithAccessRestrictionSchema, DjangoModelSchem
pass pass
class TypedLevelSchema(LevelSchema): class FullLevelLocationSchema(LevelSchema):
""" """
A level for the location API. A level for the location API.
See Level schema for details. See Level schema for details.
@ -323,7 +324,7 @@ class TypedLevelSchema(LevelSchema):
locationtype: Literal["level"] locationtype: Literal["level"]
class TypedSpaceSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema): class FullSpaceLocationSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema):
""" """
A space with some additional information for the location API. A space with some additional information for the location API.
See Space schema for details. See Space schema for details.
@ -331,7 +332,7 @@ class TypedSpaceSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema):
locationtype: Literal["space"] locationtype: Literal["space"]
class TypedAreaSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema): class FullAreaLocationSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema):
""" """
An area with some additional information for the location API. An area with some additional information for the location API.
See Area schema for details. See Area schema for details.
@ -339,7 +340,7 @@ class TypedAreaSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema):
locationtype: Literal["area"] locationtype: Literal["area"]
class TypedPOISchema(SimpleGeometryBoundsSchema, POISchema): class FullPOILocationSchema(SimpleGeometryBoundsSchema, POISchema):
""" """
A point of interest with some additional information for the location API. A point of interest with some additional information for the location API.
See POI schema for details. See POI schema for details.
@ -347,21 +348,92 @@ class TypedPOISchema(SimpleGeometryBoundsSchema, POISchema):
locationtype: Literal["poi"] locationtype: Literal["poi"]
class TypedLocationGroupSchema(SimpleGeometryLocationsSchema, LocationGroupSchema): class FullLocationGroupLocationSchema(SimpleGeometryLocationsSchema, LocationGroupSchema):
""" """
A locagroun group with some additional information for the location API. A location group with some additional information for the location API.
See LocationGroup schema for details. See LocationGroup schema for details.
""" """
locationtype: Literal["locationgroup"] locationtype: Literal["locationgroup"]
AnyLocationSchema = Annotated[ class SlimLocationMixin(Schema):
level: ClassVar[None]
space: ClassVar[None]
titles: ClassVar[None]
access_restriction: ClassVar[None]
can_search: ClassVar[None]
can_describe: ClassVar[None]
groups: ClassVar[None]
class SlimLevelLocationSchema(SlimLocationMixin, FullLevelLocationSchema):
"""
A level for the location API with some rarely needed fields removed.
See Level schema for details.
"""
short_label: ClassVar[None]
on_top_of: ClassVar[None]
base_altitude: ClassVar[None]
default_height: ClassVar[None]
door_height: ClassVar[None]
class SlimSpaceLocationSchema(SlimLocationMixin, FullSpaceLocationSchema):
"""
A space with some rarely needed fields removed and some additional information for the location API.
See Space schema for details.
"""
outside: ClassVar[None]
height: ClassVar[None]
class SlimAreaLocationSchema(SlimLocationMixin, FullAreaLocationSchema):
"""
An area with some rarely needed fields removed and some additional information for the location API.
See Area schema for details.
"""
slow_down_factor: ClassVar[None]
class SlimPOILocationSchema(SlimLocationMixin, FullPOILocationSchema):
"""
A point of interest with some rarely needed fields removed and some additional information for the location API.
See POI schema for details.
"""
pass
class SlimLocationGroupLocationSchema(SlimLocationMixin, FullLocationGroupLocationSchema):
"""
A locagroun group with some rarely needed fields removed and some additional information for the location API.
See LocationGroup schema for details.
"""
category: ClassVar[None]
priority: ClassVar[None]
hierarchy: ClassVar[None]
color: ClassVar[None]
can_report_missing: ClassVar[None]
FullLocationSchema = Annotated[
Union[ Union[
TypedLevelSchema, FullLevelLocationSchema,
TypedSpaceSchema, FullSpaceLocationSchema,
TypedAreaSchema, FullAreaLocationSchema,
TypedPOISchema, FullPOILocationSchema,
TypedLocationGroupSchema, FullLocationGroupLocationSchema,
],
Discriminator("locationtype"),
]
SlimLocationSchema = Annotated[
Union[
SlimLevelLocationSchema,
SlimSpaceLocationSchema,
SlimAreaLocationSchema,
SlimPOILocationSchema,
SlimLocationGroupLocationSchema,
], ],
Discriminator("locationtype"), Discriminator("locationtype"),
] ]