add slim and full location list endpoint, geometry optional
This commit is contained in:
parent
8b47a89865
commit
87e5e56ea1
6 changed files with 148 additions and 31 deletions
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue