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):
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)
if simple_geometry:
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)
if not detailed:
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}
return result

View file

@ -7,7 +7,8 @@ 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.filters import BySearchableFilter, RemoveGeometryFilter
from c3nav.mapdata.schemas.models import FullLocationSchema, SlimLocationSchema
from c3nav.mapdata.schemas.responses import BoundsSchema
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
@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
class LocationListFilters(BySearchableFilter, RemoveGeometryFilter):
pass
def _location_list(request, detailed: bool, filters: LocationListFilters):
# todo: cache, visibility, etc…
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),
request.user_permissions.can_access_base_mapdata
)
result = cache.get(cache_key, None)
if result is None:
if searchable:
if filters.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),
result = tuple(obj.serialize(detailed=detailed, search=filters.searchable,
geometry=filters.geometry and can_access_geometry(request),
simple_geometry=True)
for obj in locations)
cache.set(cache_key, result, 300)
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:
qs = qs.filter(on_top_of_id=None if self.on_top_of == "null" else self.on_top_of)
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 pydantic import Field as APIField
@ -127,20 +127,26 @@ class SpecificLocationSchema(LocationSchema):
class WithPolygonGeometrySchema(Schema):
geometry: PolygonSchema = APIField(
geometry: Optional[PolygonSchema] = APIField(
None,
title="geometry",
description="can be null if not available or excluded from endpoint",
)
class WithLineStringGeometrySchema(Schema):
geometry: LineStringSchema = APIField(
geometry: Optional[LineStringSchema] = APIField(
None,
title="geometry",
description="can be null if not available or excluded from endpoint",
)
class WithPointGeometrySchema(Schema):
geometry: PointSchema = APIField(
geometry: Optional[PointSchema] = APIField(
None,
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",
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 Field as APIField
from pydantic import GetJsonSchemaHandler, NonNegativeFloat, PositiveFloat, PositiveInt
@ -315,7 +316,7 @@ class AccessRestrictionGroupSchema(WithAccessRestrictionSchema, DjangoModelSchem
pass
class TypedLevelSchema(LevelSchema):
class FullLevelLocationSchema(LevelSchema):
"""
A level for the location API.
See Level schema for details.
@ -323,7 +324,7 @@ class TypedLevelSchema(LevelSchema):
locationtype: Literal["level"]
class TypedSpaceSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema):
class FullSpaceLocationSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema):
"""
A space with some additional information for the location API.
See Space schema for details.
@ -331,7 +332,7 @@ class TypedSpaceSchema(SimpleGeometryBoundsAndPointSchema, SpaceSchema):
locationtype: Literal["space"]
class TypedAreaSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema):
class FullAreaLocationSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema):
"""
An area with some additional information for the location API.
See Area schema for details.
@ -339,7 +340,7 @@ class TypedAreaSchema(SimpleGeometryBoundsAndPointSchema, AreaSchema):
locationtype: Literal["area"]
class TypedPOISchema(SimpleGeometryBoundsSchema, POISchema):
class FullPOILocationSchema(SimpleGeometryBoundsSchema, POISchema):
"""
A point of interest with some additional information for the location API.
See POI schema for details.
@ -347,21 +348,92 @@ class TypedPOISchema(SimpleGeometryBoundsSchema, POISchema):
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.
"""
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[
TypedLevelSchema,
TypedSpaceSchema,
TypedAreaSchema,
TypedPOISchema,
TypedLocationGroupSchema,
FullLevelLocationSchema,
FullSpaceLocationSchema,
FullAreaLocationSchema,
FullPOILocationSchema,
FullLocationGroupLocationSchema,
],
Discriminator("locationtype"),
]
SlimLocationSchema = Annotated[
Union[
SlimLevelLocationSchema,
SlimSpaceLocationSchema,
SlimAreaLocationSchema,
SlimPOILocationSchema,
SlimLocationGroupLocationSchema,
],
Discriminator("locationtype"),
]