add more mapdata API endpoints
This commit is contained in:
parent
f43d458fc4
commit
846101ea37
6 changed files with 300 additions and 17 deletions
|
@ -326,7 +326,7 @@ class LeaveDescription(SerializableMixin):
|
|||
result = super()._serialize(**kwargs)
|
||||
result['space'] = self.space_id
|
||||
result['target_space'] = self.target_space_id
|
||||
result['description_i18n'] = self.description_i18n
|
||||
result['descriptions'] = self.description_i18n
|
||||
result['description'] = self.description
|
||||
return result
|
||||
|
||||
|
@ -366,7 +366,7 @@ class CrossDescription(SerializableMixin):
|
|||
result['space'] = self.space_id
|
||||
result['origin_space'] = self.origin_space_id
|
||||
result['target_space'] = self.target_space_id
|
||||
result['description_i18n'] = self.description_i18n
|
||||
result['descriptions'] = self.description_i18n
|
||||
result['description'] = self.description
|
||||
return result
|
||||
|
||||
|
|
|
@ -293,9 +293,21 @@ class LocationGroupCategory(SerializableMixin, models.Model):
|
|||
def _serialize(self, detailed=True, **kwargs):
|
||||
result = super()._serialize(detailed=detailed, **kwargs)
|
||||
result['name'] = self.name
|
||||
result['single'] = self.single
|
||||
if detailed:
|
||||
result['titles'] = self.titles
|
||||
result['title'] = self.title
|
||||
result['titles_plural'] = self.titles_plural
|
||||
result['help_texts'] = self.help_texts
|
||||
result['title'] = str(self.title)
|
||||
result['title_plural'] = str(self.title_plural)
|
||||
result['help_text'] = str(self.help_text)
|
||||
result['allow_levels'] = self.allow_levels
|
||||
result['allow_spaces'] = self.allow_spaces
|
||||
result['allow_areas'] = self.allow_areas
|
||||
result['allow_pois'] = self.allow_pois
|
||||
result['allow_dynamic_locations'] = self.allow_dynamic_locations
|
||||
result['priority'] = self.priority
|
||||
|
||||
return result
|
||||
|
||||
def register_changed_geometries(self):
|
||||
|
@ -352,6 +364,9 @@ class LocationGroup(Location, models.Model):
|
|||
def _serialize(self, simple_geometry=False, **kwargs):
|
||||
result = super()._serialize(simple_geometry=simple_geometry, **kwargs)
|
||||
result['category'] = self.category_id
|
||||
result['priority'] = self.priority
|
||||
result['hierarchy'] = self.hierarchy
|
||||
result['can_report_missing'] = self.can_report_missing
|
||||
result['color'] = self.color
|
||||
if simple_geometry:
|
||||
result['locations'] = tuple(obj.pk for obj in getattr(self, 'locations', ()))
|
||||
|
|
|
@ -7,13 +7,17 @@ from ninja.pagination import paginate
|
|||
|
||||
from c3nav.api.exceptions import API404
|
||||
from c3nav.mapdata.api import optimize_query
|
||||
from c3nav.mapdata.models import Area, Building, Door, Hole, Level, Space, Stair
|
||||
from c3nav.mapdata.models import (Area, Building, Door, Hole, Level, LocationGroup, LocationGroupCategory, Source,
|
||||
Space, Stair)
|
||||
from c3nav.mapdata.models.access import AccessPermission
|
||||
from c3nav.mapdata.models.geometry.space import POI, Column, LineObstacle, Obstacle, Ramp
|
||||
from c3nav.mapdata.schemas.filters import ByGroupFilter, ByLevelFilter, ByOnTopOfFilter, BySpaceFilter, FilterSchema
|
||||
from c3nav.mapdata.schemas.models import (AreaSchema, BuildingSchema, ColumnSchema, DoorSchema, HoleSchema, LevelSchema,
|
||||
LineObstacleSchema, ObstacleSchema, POISchema, RampSchema, SpaceSchema,
|
||||
StairSchema)
|
||||
from c3nav.mapdata.models.geometry.space import (POI, Column, CrossDescription, LeaveDescription, LineObstacle,
|
||||
Obstacle, Ramp)
|
||||
from c3nav.mapdata.schemas.filters import (ByCategoryFilter, ByGroupFilter, ByLevelFilter, ByOnTopOfFilter,
|
||||
BySpaceFilter, FilterSchema)
|
||||
from c3nav.mapdata.schemas.models import (AreaSchema, BuildingSchema, ColumnSchema, CrossDescriptionSchema, DoorSchema,
|
||||
HoleSchema, LeaveDescriptionSchema, LevelSchema, LineObstacleSchema,
|
||||
LocationGroupCategorySchema, LocationGroupSchema, ObstacleSchema, POISchema,
|
||||
RampSchema, SourceSchema, SpaceSchema, StairSchema)
|
||||
|
||||
mapdata_api_router = APIRouter(tags=["mapdata"])
|
||||
|
||||
|
@ -316,3 +320,103 @@ def poi_list(request, filters: Query[BySpaceFilter]):
|
|||
def poi_detail(request, poi_id: int):
|
||||
# todo: access, caching, filtering, etc
|
||||
return mapdata_retrieve_endpoint(request, POI, pk=poi_id)
|
||||
|
||||
|
||||
"""
|
||||
LeaveDescriptions
|
||||
"""
|
||||
|
||||
|
||||
@mapdata_api_router.get('/leavedescriptions/', response=list[LeaveDescriptionSchema],
|
||||
summary="Get leave description list")
|
||||
@paginate
|
||||
def leavedescription_list(request, filters: Query[BySpaceFilter]):
|
||||
# todo cache?
|
||||
return mapdata_list_endpoint(request, model=LeaveDescription, filters=filters)
|
||||
|
||||
|
||||
@mapdata_api_router.get('/leavedescriptions/{leavedescription_id}/', response=LeaveDescriptionSchema,
|
||||
summary="Get leave description by ID")
|
||||
def leavedescription_detail(request, leavedescription_id: int):
|
||||
# todo: access, caching, filtering, etc
|
||||
return mapdata_retrieve_endpoint(request, LeaveDescription, pk=leavedescription_id)
|
||||
|
||||
|
||||
"""
|
||||
CrossDescriptions
|
||||
"""
|
||||
|
||||
|
||||
@mapdata_api_router.get('/crossdescriptions/', response=list[CrossDescriptionSchema],
|
||||
summary="Get cross description list")
|
||||
@paginate
|
||||
def crossdescription_list(request, filters: Query[BySpaceFilter]):
|
||||
# todo cache?
|
||||
return mapdata_list_endpoint(request, model=CrossDescription, filters=filters)
|
||||
|
||||
|
||||
@mapdata_api_router.get('/crossdescriptions/{crossdescription_id}/', response=CrossDescriptionSchema,
|
||||
summary="Get cross description by ID")
|
||||
def crossdescription_detail(request, crossdescription_id: int):
|
||||
# todo: access, caching, filtering, etc
|
||||
return mapdata_retrieve_endpoint(request, CrossDescription, pk=crossdescription_id)
|
||||
|
||||
|
||||
"""
|
||||
LocationGroup
|
||||
"""
|
||||
|
||||
|
||||
@mapdata_api_router.get('/locationgroups/', response=list[LocationGroupSchema],
|
||||
summary="Get location group list")
|
||||
@paginate
|
||||
def locationgroup_list(request, filters: Query[ByCategoryFilter]):
|
||||
# todo cache?
|
||||
return mapdata_list_endpoint(request, model=LocationGroup, filters=filters)
|
||||
|
||||
|
||||
@mapdata_api_router.get('/locationgroups/{locationgroup_id}/', response=LocationGroupSchema,
|
||||
summary="Get location group by ID")
|
||||
def locationgroup_detail(request, locationgroup_id: int):
|
||||
# todo: access, caching, filtering, etc
|
||||
return mapdata_retrieve_endpoint(request, LocationGroup, pk=locationgroup_id)
|
||||
|
||||
|
||||
"""
|
||||
LocationGroupCategories
|
||||
"""
|
||||
|
||||
|
||||
@mapdata_api_router.get('/locationgroupcategories/', response=list[LocationGroupCategorySchema],
|
||||
summary="Get location group category list")
|
||||
@paginate
|
||||
def locationgroupcategory_list(request):
|
||||
# todo cache?
|
||||
return mapdata_list_endpoint(request, model=LocationGroupCategory)
|
||||
|
||||
|
||||
@mapdata_api_router.get('/locationgroupcategories/{category_id}/', response=LocationGroupCategorySchema,
|
||||
summary="Get location group category by ID")
|
||||
def locationgroupcategory_detail(request, category_id: int):
|
||||
# todo: access, caching, filtering, etc
|
||||
return mapdata_retrieve_endpoint(request, LocationGroupCategory, pk=category_id)
|
||||
|
||||
|
||||
"""
|
||||
Sources
|
||||
"""
|
||||
|
||||
|
||||
@mapdata_api_router.get('/sources/', response=list[SourceSchema],
|
||||
summary="Get source list")
|
||||
@paginate
|
||||
def source_list(request):
|
||||
# todo cache?
|
||||
return mapdata_list_endpoint(request, model=Source)
|
||||
|
||||
|
||||
@mapdata_api_router.get('/sources/{source_id}/', response=SourceSchema,
|
||||
summary="Get source by ID")
|
||||
def source_detail(request, source_id: int):
|
||||
# todo: access, caching, filtering, etc
|
||||
return mapdata_retrieve_endpoint(request, Source, pk=source_id)
|
||||
|
|
|
@ -6,7 +6,7 @@ from ninja import Schema
|
|||
from pydantic import Field as APIField
|
||||
|
||||
from c3nav.api.exceptions import APIRequestValidationFailed
|
||||
from c3nav.mapdata.models import Level, LocationGroup, MapUpdate, Space
|
||||
from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space
|
||||
from c3nav.mapdata.models.access import AccessPermission
|
||||
|
||||
|
||||
|
@ -82,6 +82,24 @@ class BySpaceFilter(FilterSchema):
|
|||
return super().filter_qs(qs)
|
||||
|
||||
|
||||
class ByCategoryFilter(FilterSchema):
|
||||
category: Optional[int] = APIField(
|
||||
None,
|
||||
title="filter by location group category",
|
||||
description="if set, only groups belonging to the location group category with this ID will be shown"
|
||||
)
|
||||
|
||||
def validate(self, request):
|
||||
super().validate(request)
|
||||
if self.category is not None:
|
||||
assert_valid_value(request, LocationGroupCategory, "pk", {self.category})
|
||||
|
||||
def filter_qs(self, qs: QuerySet) -> QuerySet:
|
||||
if self.category is not None:
|
||||
qs = qs.filter(category=self.category)
|
||||
return super().filter_qs(qs)
|
||||
|
||||
|
||||
class ByGroupFilter(FilterSchema):
|
||||
group: Optional[int] = APIField(
|
||||
None,
|
||||
|
|
|
@ -116,6 +116,7 @@ class SpecificLocationSchema(LocationSchema):
|
|||
label_settings: Optional[LabelSettingsSchema] = APIField(
|
||||
default=None,
|
||||
title="label settings",
|
||||
description="if not set, it may be taken from location groups"
|
||||
)
|
||||
label_override: Optional[NonEmptyStr] = APIField(
|
||||
default=None,
|
||||
|
|
|
@ -2,10 +2,10 @@ from typing import Optional
|
|||
|
||||
from pydantic import Field as APIField
|
||||
from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt
|
||||
from pydantic.color import Color
|
||||
|
||||
from c3nav.api.utils import NonEmptyStr
|
||||
from c3nav.mapdata.schemas.model_base import (AccessRestrictionSchema, DjangoModelSchema, SpecificLocationSchema,
|
||||
from c3nav.mapdata.schemas.model_base import (AccessRestrictionSchema, DjangoModelSchema, LabelSettingsSchema,
|
||||
LocationSchema, SerializableSchema, SpecificLocationSchema, TitledSchema,
|
||||
WithLevelSchema, WithLineStringGeometrySchema, WithPointGeometrySchema,
|
||||
WithPolygonGeometrySchema, WithSpaceSchema)
|
||||
|
||||
|
@ -14,7 +14,7 @@ class LevelSchema(SpecificLocationSchema, DjangoModelSchema):
|
|||
"""
|
||||
A physical level of the map, containing building, spaces, doors…
|
||||
|
||||
A level is a specific location, and can therefor be routed to and from, as well as belong to location groups.
|
||||
A level is a specific location, and can therefore be routed to and from, as well as belong to location groups.
|
||||
"""
|
||||
short_label: NonEmptyStr = APIField(
|
||||
title="short label (for level selector)",
|
||||
|
@ -46,7 +46,7 @@ class SpaceSchema(WithPolygonGeometrySchema, SpecificLocationSchema, WithLevelSc
|
|||
"""
|
||||
An accessible area on a level. It can be outside-only or inside-only.
|
||||
|
||||
A space is a specific location, and can therefor be routed to and from, as well as belong to location groups.
|
||||
A space is a specific location, and can therefore be routed to and from, as well as belong to location groups.
|
||||
"""
|
||||
outside: bool = APIField(
|
||||
title="outside only",
|
||||
|
@ -76,7 +76,7 @@ class AreaSchema(WithPolygonGeometrySchema, SpecificLocationSchema, WithSpaceSch
|
|||
"""
|
||||
An area inside a space.
|
||||
|
||||
An area is a specific location, and can therefor be routed to and from, as well as belong to location groups.
|
||||
An area is a specific location, and can therefore be routed to and from, as well as belong to location groups.
|
||||
"""
|
||||
slow_down_factor: PositiveFloat = APIField(
|
||||
title="slow-down factor",
|
||||
|
@ -107,7 +107,7 @@ class BaseObstacleSchema(WithSpaceSchema, DjangoModelSchema):
|
|||
title="altitude above ground",
|
||||
description="altitude above ground"
|
||||
)
|
||||
color: Optional[Color] = APIField(
|
||||
color: Optional[NonEmptyStr] = APIField(
|
||||
title="color",
|
||||
description="an optional color for this obstacle"
|
||||
)
|
||||
|
@ -141,6 +141,151 @@ class POISchema(WithPointGeometrySchema, SpecificLocationSchema, WithSpaceSchema
|
|||
"""
|
||||
A point of interest inside a space.
|
||||
|
||||
A POI is a specific location, and can therefor be routed to and from, as well as belong to location groups.
|
||||
A POI is a specific location, and can therefore be routed to and from, as well as belong to location groups.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class LeaveDescriptionSchema(WithSpaceSchema, DjangoModelSchema):
|
||||
"""
|
||||
A description for leaving a space to enter another space.
|
||||
"""
|
||||
target_space: PositiveInt = APIField(
|
||||
title="target space",
|
||||
description="the space that is being entered",
|
||||
)
|
||||
descriptions: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
||||
title="description (all languages)",
|
||||
description="property names are the ISO-language code. languages may be missing.",
|
||||
example={
|
||||
"en": "Stanley walked through the red door.",
|
||||
"de": "Stanley ging durch die rote Tür.",
|
||||
}
|
||||
)
|
||||
description: NonEmptyStr = APIField(
|
||||
title="description (preferred language)",
|
||||
description="preferred language based on the Accept-Language header."
|
||||
)
|
||||
|
||||
|
||||
class CrossDescriptionSchema(WithSpaceSchema, DjangoModelSchema):
|
||||
"""
|
||||
A description for crossing through a space from one space to another.
|
||||
"""
|
||||
origin_space: PositiveInt = APIField(
|
||||
title="origin space",
|
||||
description="the space from which the main space is being entered",
|
||||
)
|
||||
target_space: PositiveInt = APIField(
|
||||
title="target space",
|
||||
description="the space that is being entered from the main space",
|
||||
)
|
||||
descriptions: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
||||
title="description (all languages)",
|
||||
description="property names are the ISO-language code. languages may be missing.",
|
||||
example={
|
||||
"en": "Go straight ahead through the big glass doors.",
|
||||
"de": "gehe geradeaus durch die Glastüren.",
|
||||
}
|
||||
)
|
||||
description: NonEmptyStr = APIField(
|
||||
title="description (preferred language)",
|
||||
description="preferred language based on the Accept-Language header."
|
||||
)
|
||||
|
||||
|
||||
class LocationGroupSchema(LocationSchema, DjangoModelSchema):
|
||||
"""
|
||||
A location group, always belonging to a location group category.
|
||||
|
||||
A location group is a (non-specific) location, which means it can be routed to and from.
|
||||
"""
|
||||
category: PositiveInt = APIField(
|
||||
title="category",
|
||||
description="location group category that this location group belongs to",
|
||||
)
|
||||
priority: int = APIField() # todo: ???
|
||||
hierarchy: int = APIField() # todo: ???
|
||||
label_settings: Optional[LabelSettingsSchema] = APIField(
|
||||
default=None,
|
||||
title="label settings",
|
||||
description="for locations with this group, can be overwritten by specific locations"
|
||||
)
|
||||
can_report_missing: bool = APIField(
|
||||
title="report missing locations",
|
||||
description="can be used in form for reporting missing locations",
|
||||
)
|
||||
color: Optional[NonEmptyStr] = APIField(
|
||||
title="color",
|
||||
description="an optional color for spaces and areas with this group"
|
||||
)
|
||||
|
||||
|
||||
class LocationGroupCategorySchema(TitledSchema, SerializableSchema, DjangoModelSchema):
|
||||
"""
|
||||
A location group category can hold either one or multiple location groups.
|
||||
|
||||
It is used to allow for having different kind of groups for different means.
|
||||
"""
|
||||
name: NonEmptyStr = APIField(
|
||||
title="name/slug",
|
||||
description="name/slug of this location group category",
|
||||
)
|
||||
single: bool = APIField(
|
||||
title="single choice",
|
||||
description="if true, every location can only have one group from this category, not a list"
|
||||
)
|
||||
titles_plural: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
||||
title="plural title (all languages)",
|
||||
description="property names are the ISO-language code. languages may be missing.",
|
||||
example={
|
||||
"en": "Title",
|
||||
"de": "Titel",
|
||||
}
|
||||
)
|
||||
title_plural: NonEmptyStr = APIField(
|
||||
title="plural title (preferred language)",
|
||||
description="preferred language based on the Accept-Language header."
|
||||
)
|
||||
help_texts: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
||||
title="help text (all languages)",
|
||||
description="property names are the ISO-language code. languages may be missing.",
|
||||
example={
|
||||
"en": "Title",
|
||||
"de": "Titel",
|
||||
}
|
||||
)
|
||||
help_text: str = APIField(
|
||||
title="help text (preferred language)",
|
||||
description="preferred language based on the Accept-Language header."
|
||||
)
|
||||
allow_levels: bool = APIField(
|
||||
description="whether groups with this category can be assigned to levels"
|
||||
)
|
||||
allow_spaces: bool = APIField(
|
||||
description="whether groups with this category can be assigned to spaces"
|
||||
)
|
||||
allow_areas: bool = APIField(
|
||||
description="whether groups with this category can be assigned to areas"
|
||||
)
|
||||
allow_pois: bool = APIField(
|
||||
description="whether groups with this category can be assigned to POIs"
|
||||
)
|
||||
allow_dynamic_locations: bool = APIField(
|
||||
description="whether groups with this category can be assigned to dynamic locations"
|
||||
)
|
||||
priority: int = APIField() # todo: ???
|
||||
|
||||
|
||||
class SourceSchema(AccessRestrictionSchema, DjangoModelSchema):
|
||||
"""
|
||||
A source image that can be traced in the editor.
|
||||
"""
|
||||
name: NonEmptyStr = APIField(
|
||||
title="name",
|
||||
description="name/filename of the source",
|
||||
)
|
||||
bottom: float
|
||||
left: float
|
||||
top: float
|
||||
right: float
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue