integrate data overlay api into new MapdataAPIBuilder

This commit is contained in:
Laura Klünder 2024-12-03 09:59:51 +01:00
parent be76f3a8db
commit 707c81f159
5 changed files with 38 additions and 41 deletions

View file

@ -17,7 +17,7 @@ from c3nav.mapdata.models.geometry.space import (POI, Column, CrossDescription,
Obstacle, Ramp) Obstacle, Ramp)
from c3nav.mapdata.models.locations import DynamicLocation from c3nav.mapdata.models.locations import DynamicLocation
from c3nav.mapdata.schemas.filters import (ByCategoryFilter, ByGroupFilter, ByOnTopOfFilter, FilterSchema, from c3nav.mapdata.schemas.filters import (ByCategoryFilter, ByGroupFilter, ByOnTopOfFilter, FilterSchema,
LevelGeometryFilter, SpaceGeometryFilter, BySpaceFilter) LevelGeometryFilter, SpaceGeometryFilter, BySpaceFilter, ByOverlayFilter)
from c3nav.mapdata.schemas.model_base import schema_description from c3nav.mapdata.schemas.model_base import schema_description
from c3nav.mapdata.schemas.models import (AccessRestrictionGroupSchema, AccessRestrictionSchema, AreaSchema, from c3nav.mapdata.schemas.models import (AccessRestrictionGroupSchema, AccessRestrictionSchema, AreaSchema,
BuildingSchema, ColumnSchema, CrossDescriptionSchema, DoorSchema, BuildingSchema, ColumnSchema, CrossDescriptionSchema, DoorSchema,
@ -143,6 +143,7 @@ class MapdataAPIBuilder:
call_func=mapdata_retrieve_endpoint, call_func=mapdata_retrieve_endpoint,
add_call_params={"model": endpoint.model.__name__, "pk": id_field} add_call_params={"model": endpoint.model.__name__, "pk": id_field}
) )
list_func.__name__ = f"{endpoint.model_name}_by_id"
self.router.get(f'/{endpoint.model_name_plural}/{{{id_field}}}/', summary=f"{endpoint.model_name} by ID", self.router.get(f'/{endpoint.model_name_plural}/{{{id_field}}}/', summary=f"{endpoint.model_name} by ID",
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema), tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
@ -195,6 +196,15 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = {
model=DynamicLocation, model=DynamicLocation,
schema=DynamicLocationSchema, schema=DynamicLocationSchema,
), ),
MapdataEndpoint(
model=DataOverlay,
schema=DataOverlaySchema,
),
MapdataEndpoint(
model=DataOverlayFeature,
schema=DataOverlayFeatureSchema,
filters=ByOverlayFilter,
),
], ],
"level": [ "level": [
MapdataEndpoint( MapdataEndpoint(
@ -269,35 +279,3 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = {
MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints) MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints)
"""
Data overlays
"""
# todo: this wants to move into a MapDataEndpoint
@mapdata_api_router.get('/overlays/', summary="data overlay list",
tags=["mapdata-root"], description=schema_description(DynamicLocationSchema),
response={200: list[DataOverlaySchema], **auth_responses})
@api_etag()
def dataoverlay_list(request):
return mapdata_list_endpoint(request, model=DataOverlay)
# todo: this wants to move into a MapDataEndpoint
@mapdata_api_router.get('/overlays/{overlay_id}/', summary="features for overlay by overlay ID",
tags=["mapdata-root"], description=schema_description(DynamicLocationSchema),
response={200: list[DataOverlayFeatureSchema], **API404.dict(), **auth_responses})
# @api_etag()
def dataoverlay_by_id(request, overlay_id: int):
qs = optimize_query(
DataOverlayFeature.qs_for_request(request)
)
qs = qs.filter(overlay_id=overlay_id)
# order_by
qs = qs.order_by('pk')
return qs

View file

@ -21,11 +21,11 @@ class DataOverlay(TitledMixin, models.Model):
verbose_name=_('headers for pull http request (JSON object)')) verbose_name=_('headers for pull http request (JSON object)'))
pull_interval = models.DurationField(blank=True, null=True, verbose_name=_('pull interval')) pull_interval = models.DurationField(blank=True, null=True, verbose_name=_('pull interval'))
class Meta: class Meta:
verbose_name = _('Data Overlay') verbose_name = _('Data Overlay')
verbose_name_plural = _('Data Overlays') verbose_name_plural = _('Data Overlays')
default_related_name = 'data_overlays' default_related_name = 'dataoverlays'
class DataOverlayFeature(TitledMixin, GeometryMixin, models.Model): class DataOverlayFeature(TitledMixin, GeometryMixin, models.Model):
overlay = models.ForeignKey('mapdata.DataOverlay', on_delete=models.CASCADE, verbose_name=_('Overlay'), related_name='features') overlay = models.ForeignKey('mapdata.DataOverlay', on_delete=models.CASCADE, verbose_name=_('Overlay'), related_name='features')
@ -45,7 +45,6 @@ class DataOverlayFeature(TitledMixin, GeometryMixin, models.Model):
extra_data: Optional[dict[str, str]] = SchemaField(schema=dict[str, str], blank=True, null=True, default=None, extra_data: Optional[dict[str, str]] = SchemaField(schema=dict[str, str], blank=True, null=True, default=None,
verbose_name=_('extra data (JSON object)')) verbose_name=_('extra data (JSON object)'))
def to_geojson(self, instance=None) -> dict: def to_geojson(self, instance=None) -> dict:
result = { result = {
'type': 'Feature', 'type': 'Feature',
@ -65,7 +64,6 @@ class DataOverlayFeature(TitledMixin, GeometryMixin, models.Model):
def get_geojson_key(self): def get_geojson_key(self):
return 'dataoverlayfeature', self.id return 'dataoverlayfeature', self.id
def _serialize(self, **kwargs): def _serialize(self, **kwargs):
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
result.update({ result.update({
@ -84,4 +82,4 @@ class DataOverlayFeature(TitledMixin, GeometryMixin, models.Model):
return result return result
class Meta: class Meta:
default_related_name = "overlayfeatures" default_related_name = "dataoverlayfeatures"

View file

@ -136,6 +136,23 @@ class ByOnTopOfFilter(FilterSchema):
return super().filter_qs(qs) return super().filter_qs(qs)
class ByOverlayFilter(FilterSchema):
overlay: int = APIField(
title='filter by data overlay',
description='only show overlay features belonging to this overlay'
)
def validate(self, request):
super().validate(request)
if self.overlay is not None:
assert_valid_value(request, Level, "pk", {self.overlay})
def filter_qs(self, qs: QuerySet) -> QuerySet:
if self.overlay is not None:
qs = qs.filter(overlay=self.overlay)
return super().filter_qs(qs)
class BySearchableFilter(FilterSchema): class BySearchableFilter(FilterSchema):
searchable: bool = APIField( searchable: bool = APIField(
False, False,

View file

@ -319,11 +319,16 @@ class DynamicLocationSchema(SpecificLocationSchema, DjangoModelSchema):
class DataOverlaySchema(TitledSchema, DjangoModelSchema): class DataOverlaySchema(TitledSchema, DjangoModelSchema):
# TODO """
Represents a collection of geometries to be displayed as an optional overlay to the map.
"""
pass pass
class DataOverlayFeatureSchema(TitledSchema, DjangoModelSchema): class DataOverlayFeatureSchema(TitledSchema, DjangoModelSchema):
"""
A feature (any kind of geometry) to be displayed as part of a data overlay.
"""
geometry: AnyGeometrySchema geometry: AnyGeometrySchema
level_id: PositiveInt level_id: PositiveInt
stroke_color: Optional[str] stroke_color: Optional[str]
@ -336,7 +341,6 @@ class DataOverlayFeatureSchema(TitledSchema, DjangoModelSchema):
external_url: Optional[str] external_url: Optional[str]
extra_data: Optional[dict[str, str]] extra_data: Optional[dict[str, str]]
# TODO # TODO
pass
class SourceSchema(WithAccessRestrictionSchema, DjangoModelSchema): class SourceSchema(WithAccessRestrictionSchema, DjangoModelSchema):

View file

@ -2841,7 +2841,7 @@ class DataOverlay {
} }
async create() { async create() {
const features = await c3nav_api.get(`mapdata/overlays/${this.id}/`); const features = await c3nav_api.get(`mapdata/dataoverlayfeatures/?overlay=${this.id}`);
const levels = {}; const levels = {};
for (const feature of features) { for (const feature of features) {