more map api documentation improvements
This commit is contained in:
parent
4d57b81ad1
commit
964574e535
8 changed files with 335 additions and 121 deletions
|
@ -22,8 +22,7 @@ class SwaggerAndRedoc(DocsBase):
|
|||
redoc_config = Redoc(settings={
|
||||
"hideOneOfDescription": True,
|
||||
"expandSingleSchemaField": True,
|
||||
"jsonSampleExpandLevel": 4,
|
||||
"showObjectSchemaExamples": True,
|
||||
"jsonSampleExpandLevel": 5,
|
||||
"expandResponses": "200",
|
||||
"hideSingleRequestSampleTab": True,
|
||||
"nativeScrollbars": True,
|
||||
|
|
|
@ -25,6 +25,9 @@ class PolygonSchema(Schema):
|
|||
example=[[[1.5, 1.5], [1.5, 2.5], [2.5, 2.5], [2.5, 2.5]]]
|
||||
)
|
||||
|
||||
class Config(Schema.Config):
|
||||
title = "GeoJSON Polygon"
|
||||
|
||||
|
||||
class LineStringSchema(Schema):
|
||||
"""
|
||||
|
@ -35,6 +38,9 @@ class LineStringSchema(Schema):
|
|||
example=[[1.5, 1.5], [2.5, 2.5], [5, 8.7]]
|
||||
)
|
||||
|
||||
class Config(Schema.Config):
|
||||
title = "GeoJSON LineString"
|
||||
|
||||
|
||||
class LineSchema(Schema):
|
||||
"""
|
||||
|
@ -45,6 +51,9 @@ class LineSchema(Schema):
|
|||
example=[[1.5, 1.5], [5, 8.7]]
|
||||
)
|
||||
|
||||
class Config(Schema.Config):
|
||||
title = "GeoJSON LineString (only two points)"
|
||||
|
||||
|
||||
class PointSchema(Schema):
|
||||
"""
|
||||
|
@ -55,6 +64,9 @@ class PointSchema(Schema):
|
|||
example=[1, 2.5]
|
||||
)
|
||||
|
||||
class Config(Schema.Config):
|
||||
title = "GeoJSON Point"
|
||||
|
||||
|
||||
GeometrySchema = Annotated[
|
||||
Union[
|
||||
|
|
|
@ -10,14 +10,14 @@ from c3nav.editor.api.schemas import EditorGeometriesElemSchema, EditorID, Geome
|
|||
from c3nav.editor.views.base import editor_etag_func
|
||||
from c3nav.mapdata.api.base import api_etag
|
||||
from c3nav.mapdata.models import Source
|
||||
from c3nav.mapdata.schemas.responses import BoundsSchema
|
||||
from c3nav.mapdata.schemas.responses import WithBoundsSchema
|
||||
|
||||
editor_api_router = APIRouter(tags=["editor"], auth=APITokenAuth(permissions={"editor_access"}))
|
||||
|
||||
|
||||
@editor_api_router.get('/bounds/', summary="boundaries",
|
||||
description="get maximum boundaries of everything on the map",
|
||||
response={200: BoundsSchema, **auth_permission_responses},
|
||||
response={200: WithBoundsSchema, **auth_permission_responses},
|
||||
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
||||
@api_etag()
|
||||
def bounds(request):
|
||||
|
|
|
@ -17,10 +17,11 @@ from c3nav.mapdata.api.base import api_etag, api_stats
|
|||
from c3nav.mapdata.models import Source
|
||||
from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Position
|
||||
from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter
|
||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID
|
||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID, schema_definition
|
||||
from c3nav.mapdata.schemas.models import (AnyPositionStatusSchema, FullListableLocationSchema, FullLocationSchema,
|
||||
LocationDisplay, SlimListableLocationSchema, SlimLocationSchema)
|
||||
from c3nav.mapdata.schemas.responses import BoundsSchema, LocationGeometry
|
||||
LevelSchema, LocationDisplay, SlimListableLocationSchema, SlimLocationSchema,
|
||||
all_location_definitions, listable_location_definitions)
|
||||
from c3nav.mapdata.schemas.responses import LocationGeometry, WithBoundsSchema
|
||||
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request,
|
||||
searchable_locations_for_request, visible_locations_for_request)
|
||||
from c3nav.mapdata.utils.user import can_access_editor
|
||||
|
@ -30,7 +31,7 @@ map_api_router = APIRouter(tags=["map"])
|
|||
|
||||
@map_api_router.get('/bounds/', summary="get boundaries",
|
||||
description="get maximum boundaries of everything on the map",
|
||||
response={200: BoundsSchema, **auth_responses})
|
||||
response={200: WithBoundsSchema, **auth_responses})
|
||||
@api_etag(permissions=False)
|
||||
def bounds(request):
|
||||
return {
|
||||
|
@ -69,7 +70,8 @@ def _location_list(request, detailed: bool, filters: LocationListFilters):
|
|||
|
||||
|
||||
@map_api_router.get('/locations/', summary="list locations (slim)",
|
||||
description="Get locations (with most important attributes set)",
|
||||
description=("Get locations (with most important attributes set)\n\n"
|
||||
"Possible location types:\n"+listable_location_definitions),
|
||||
response={200: list[SlimListableLocationSchema], **validate_responses, **auth_responses})
|
||||
@api_etag(base_mapdata=True)
|
||||
def location_list(request, filters: Query[LocationListFilters]):
|
||||
|
@ -77,7 +79,8 @@ def location_list(request, filters: Query[LocationListFilters]):
|
|||
|
||||
|
||||
@map_api_router.get('/locations/full/', summary="list locations (full)",
|
||||
description="Get locations (with all attributes set)",
|
||||
description=("Get locations (with all attributes set)\n\n"
|
||||
"Possible location types:\n"+listable_location_definitions),
|
||||
response={200: list[FullListableLocationSchema], **validate_responses, **auth_responses})
|
||||
@api_etag(base_mapdata=True)
|
||||
def location_list_full(request, filters: Query[LocationListFilters]):
|
||||
|
@ -150,7 +153,8 @@ class ShowRedirects(Schema):
|
|||
|
||||
|
||||
@map_api_router.get('/locations/{location_id}/', summary="location by ID (slim)",
|
||||
description="Get locations by ID (with all attributes set)",
|
||||
description=("Get locations by ID (with all attributes set)\n\n"
|
||||
"Possible location types:\n"+all_location_definitions),
|
||||
response={200: SlimLocationSchema, **API404.dict(), **validate_responses, **auth_responses})
|
||||
@api_stats('location_get')
|
||||
@api_etag(base_mapdata=True)
|
||||
|
@ -164,7 +168,8 @@ def location_by_id(request, location_id: AnyLocationID, filters: Query[RemoveGeo
|
|||
|
||||
|
||||
@map_api_router.get('/locations/{location_id}/full/', summary="location by ID (full)",
|
||||
description="Get location by ID (with all attributes set)",
|
||||
description=("Get location by ID (with all attributes set)\n\n"
|
||||
"Possible location types:\n"+all_location_definitions),
|
||||
response={200: FullLocationSchema, **API404.dict(), **validate_responses, **auth_responses})
|
||||
@api_stats('location_get')
|
||||
@api_etag(base_mapdata=True)
|
||||
|
@ -202,7 +207,8 @@ def location_by_id_geometry(request, location_id: AnyLocationID):
|
|||
|
||||
|
||||
@map_api_router.get('/locations/by-slug/{location_slug}/', summary="location by slug (slim)",
|
||||
description="Get location by slug (with most important attributes set)",
|
||||
description=("Get location by slug (with most important attributes set)\n\n"
|
||||
"Possible location types:\n"+all_location_definitions),
|
||||
response={200: SlimLocationSchema, **API404.dict(), **validate_responses, **auth_responses})
|
||||
@api_stats('location_get')
|
||||
@api_etag(base_mapdata=True)
|
||||
|
@ -216,7 +222,8 @@ def location_by_slug(request, location_slug: NonEmptyStr, filters: Query[RemoveG
|
|||
|
||||
|
||||
@map_api_router.get('/locations/by-slug/{location_slug}/full/', summary="location by slug (full)",
|
||||
description="Get location by slug (with all attributes set)",
|
||||
description=("Get location by slug (with all attributes set)\n\n"
|
||||
"Possible location types:\n"+all_location_definitions),
|
||||
response={200: FullLocationSchema, **API404.dict(), **validate_responses, **auth_responses})
|
||||
@api_stats('location_get')
|
||||
@api_etag(base_mapdata=True)
|
||||
|
|
|
@ -15,6 +15,7 @@ from c3nav.mapdata.models.geometry.space import (POI, Column, CrossDescription,
|
|||
from c3nav.mapdata.models.locations import DynamicLocation
|
||||
from c3nav.mapdata.schemas.filters import (ByCategoryFilter, ByGroupFilter, ByOnTopOfFilter, FilterSchema,
|
||||
LevelGeometryFilter, SpaceGeometryFilter)
|
||||
from c3nav.mapdata.schemas.model_base import schema_description
|
||||
from c3nav.mapdata.schemas.models import (AccessRestrictionGroupSchema, AccessRestrictionSchema, AreaSchema,
|
||||
BuildingSchema, ColumnSchema, CrossDescriptionSchema, DoorSchema,
|
||||
DynamicLocationSchema, HoleSchema, LeaveDescriptionSchema, LevelSchema,
|
||||
|
@ -58,10 +59,6 @@ def mapdata_retrieve_endpoint(request, model: Type[Model], **lookups):
|
|||
raise API404("%s not found" % model.__name__.lower())
|
||||
|
||||
|
||||
def schema_description(schema):
|
||||
return schema.__doc__.replace("\n ", "\n")
|
||||
|
||||
|
||||
"""
|
||||
Levels
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Annotated, Any, Optional, Union
|
||||
import re
|
||||
from typing import Annotated, Any, Union
|
||||
|
||||
from ninja import Schema
|
||||
from pydantic import Field as APIField
|
||||
|
@ -10,6 +11,31 @@ from c3nav.api.schema import LineStringSchema, PointSchema, PolygonSchema
|
|||
from c3nav.api.utils import NonEmptyStr
|
||||
|
||||
|
||||
def schema_description(schema):
|
||||
return schema.__doc__.replace("\n ", "\n").strip()
|
||||
|
||||
|
||||
def schema_definition(schema):
|
||||
return ("- **"+re.sub(r"([a-z])([A-Z])", r"\1 \2", schema.__name__.removesuffix("Schema")) +"**: " +
|
||||
schema_description(schema).split("\n")[0].strip())
|
||||
|
||||
|
||||
def schema_definitions(schemas):
|
||||
return "\n".join(schema_definition(schema) for schema in schemas)
|
||||
|
||||
|
||||
BoundsSchema = tuple[
|
||||
Annotated[tuple[
|
||||
Annotated[float, APIField(title="left", description="lowest X coordindate")],
|
||||
Annotated[float, APIField(title="bottom", description="lowest Y coordindate")]
|
||||
], APIField(title="(left, bottom)", description="lowest coordinates", example=(-10, -20))],
|
||||
Annotated[tuple[
|
||||
Annotated[float, APIField(title="right", description="highest X coordindate")],
|
||||
Annotated[float, APIField(title="top", description="highest Y coordindate")]
|
||||
], APIField(title="(right, top)", description="highest coordinates", example=(20, 30))]
|
||||
]
|
||||
|
||||
|
||||
class SerializableSchema(Schema):
|
||||
@model_validator(mode="wrap") # noqa
|
||||
@classmethod
|
||||
|
@ -23,62 +49,79 @@ class SerializableSchema(Schema):
|
|||
class DjangoModelSchema(SerializableSchema):
|
||||
id: PositiveInt = APIField(
|
||||
title="ID",
|
||||
example=1,
|
||||
)
|
||||
|
||||
|
||||
class LocationSlugSchema(Schema):
|
||||
slug: NonEmptyStr = APIField(
|
||||
title="location slug",
|
||||
description="a slug is a unique way to refer to a location across all location types. "
|
||||
"locations can have a human-readable slug. "
|
||||
"if it doesn't, this field holds a slug generated based from the location type and ID. "
|
||||
"this slug will work even if a human-readable slug is defined later. "
|
||||
"even dynamic locations like coordinates have a slug.",
|
||||
description="a slug is a unique way to refer to a location. while locations have a shared ID space, slugs"
|
||||
"are meants to be human-readable and easy to remember.\n\n"
|
||||
"if a location doesn't have a slug defined, this field holds a slug generated from the "
|
||||
"location type and ID, which will work even if a human-readable slug is defined later.\n\n"
|
||||
"even dynamic locations like coordinates have an (auto-generated) slug.",
|
||||
example="entrance",
|
||||
)
|
||||
|
||||
|
||||
class WithAccessRestrictionSchema(Schema):
|
||||
access_restriction: Optional[PositiveInt] = APIField(
|
||||
access_restriction: Union[
|
||||
Annotated[PositiveInt, APIField(title="access restriction ID")],
|
||||
Annotated[None, APIField(title="null", description="no access restriction")],
|
||||
] = APIField(
|
||||
default=None,
|
||||
title="access restriction ID",
|
||||
description="access restriction that this object belongs to",
|
||||
)
|
||||
|
||||
|
||||
class TitledSchema(Schema):
|
||||
titles: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
||||
title="title (all languages)",
|
||||
description="property names are the ISO-language code. languages may be missing.",
|
||||
description="title in all available languages. property names are the ISO-language code. "
|
||||
"languages may be missing.",
|
||||
example={
|
||||
"en": "Title",
|
||||
"de": "Titel",
|
||||
"en": "Entrance",
|
||||
"de": "Eingang",
|
||||
}
|
||||
)
|
||||
title: NonEmptyStr = APIField(
|
||||
title="title (preferred language)",
|
||||
description="preferred language based on the Accept-Language header."
|
||||
description="title in the preferred language based on the Accept-Language header.",
|
||||
example="Entrance",
|
||||
)
|
||||
|
||||
|
||||
class LocationSchema(WithAccessRestrictionSchema, TitledSchema, LocationSlugSchema):
|
||||
subtitle: NonEmptyStr = APIField(
|
||||
title="subtitle (preferred language)",
|
||||
description="an automatically generated short description for this location. "
|
||||
"preferred language based on the Accept-Language header."
|
||||
description="an automatically generated short description for this location in the "
|
||||
"preferred language based on the Accept-Language header.",
|
||||
example="near Area 51",
|
||||
)
|
||||
icon: Optional[NonEmptyStr] = APIField(
|
||||
default=None,
|
||||
icon: NonEmptyStr = APIField(
|
||||
title="icon name",
|
||||
description="any material design icon name"
|
||||
description="any material design icon name",
|
||||
example="pin_drop",
|
||||
)
|
||||
can_search: bool = APIField(
|
||||
title="can be searched",
|
||||
description="if `true`, this object can show up in search results",
|
||||
)
|
||||
can_describe: bool = APIField(
|
||||
title="can describe locations",
|
||||
description="if `true`, this object can be used to describe other locations (e.g. in their subtitle)",
|
||||
)
|
||||
add_search: Optional[str] = APIField(
|
||||
add_search: Union[
|
||||
Annotated[str, APIField(title="search terms", description="set when looking for searchable locations")],
|
||||
Annotated[None, APIField(title="null", description="when not looking for searchable locations")],
|
||||
] = APIField(
|
||||
None,
|
||||
title="more data for search index, only set when looking for searchable locations"
|
||||
title="additional search terms",
|
||||
description="more data for the search index separated by spaces, "
|
||||
"only set when looking for searchable locations",
|
||||
example="more search terms",
|
||||
)
|
||||
|
||||
|
||||
|
@ -90,49 +133,91 @@ class LabelSettingsSchema(DjangoModelSchema): # todo: add titles back in here
|
|||
min_zoom: float = APIField(
|
||||
-10,
|
||||
title="min zoom",
|
||||
description="minimum zoom to display the label at",
|
||||
)
|
||||
max_zoom: float = APIField(
|
||||
10,
|
||||
title="max zoom",
|
||||
description="maximum, zoom to display the label at",
|
||||
)
|
||||
font_size: PositiveInt = APIField(
|
||||
title="font size",
|
||||
description="font size of the label",
|
||||
example=12,
|
||||
)
|
||||
|
||||
|
||||
class SpecificLocationSchema(LocationSchema):
|
||||
grid_square: Optional[NonEmptyStr] = APIField(
|
||||
grid_square: Union[
|
||||
Annotated[NonEmptyStr, APIField(title="grid square", description="grid square(s) that this location is in")],
|
||||
Annotated[None, APIField(title="null", description="no grid defined or outside of grid")],
|
||||
] = APIField(
|
||||
default=None,
|
||||
title="grid square",
|
||||
description="if a grid is defined and this location is within it",
|
||||
description="grid cell(s) that this location is in, if a grid is defined and the location is within it",
|
||||
example="C3",
|
||||
)
|
||||
groups: dict[NonEmptyStr, list[PositiveInt] | Optional[PositiveInt]] = APIField(
|
||||
groups: dict[
|
||||
Annotated[NonEmptyStr, APIField(title="location group category name")],
|
||||
Union[
|
||||
Annotated[list[PositiveInt], APIField(
|
||||
title="array of location IDs",
|
||||
description="for categories that have `single` set to `false`. can be an empty array.",
|
||||
example=[1,4,5],
|
||||
)],
|
||||
Annotated[PositiveInt, APIField(
|
||||
title="one location ID",
|
||||
description="for categories that have `single` set to `true`.",
|
||||
example=1,
|
||||
)],
|
||||
Annotated[None, APIField(
|
||||
title="null",
|
||||
description="for categories that have `single` set to `true`."
|
||||
)],
|
||||
]
|
||||
] = APIField(
|
||||
title="location groups",
|
||||
description="grouped by location group categories. "
|
||||
"property names are the names of location groupes. "
|
||||
"property values are integer, None or a list of integers, see example."
|
||||
"see location group category endpoint for currently available possibilities."
|
||||
description="location group(s) that this specific location belongs to, grouped by categories.\n\n"
|
||||
"keys are location group category names. see location group category endpoint for details.\n\n"
|
||||
"categories may be missing if no groups apply.",
|
||||
example={
|
||||
"category_with_single_true": 5,
|
||||
"other_category_with_single_true": None,
|
||||
"categoryother_category_with_single_false": [1, 3, 7],
|
||||
"category_with_single_false": [1, 3, 7],
|
||||
}
|
||||
)
|
||||
label_settings: Optional[LabelSettingsSchema] = APIField(
|
||||
label_settings: Union[
|
||||
Annotated[LabelSettingsSchema, APIField(
|
||||
title="label settings",
|
||||
description="label settings to use",
|
||||
)],
|
||||
Annotated[None, APIField(
|
||||
title="null",
|
||||
description="label settings from location group will be used, if available"
|
||||
)],
|
||||
] = APIField(
|
||||
default=None,
|
||||
title="label settings",
|
||||
description="if not set, it may be taken from location groups"
|
||||
description=(
|
||||
schema_description(LabelSettingsSchema) +
|
||||
"\n\nif not set, label settings of location groups might be used"
|
||||
)
|
||||
label_override: Optional[NonEmptyStr] = APIField(
|
||||
)
|
||||
label_override: Union[
|
||||
Annotated[NonEmptyStr, APIField(title="label override", description="text to use for label")],
|
||||
Annotated[None, APIField(title="null", description="title will be used")],
|
||||
] = APIField(
|
||||
default=None,
|
||||
title="label override (preferred language)",
|
||||
description="preferred language based on the Accept-Language header."
|
||||
description="text to use for the label. by default (null), the title would be used."
|
||||
)
|
||||
|
||||
|
||||
class WithPolygonGeometrySchema(Schema):
|
||||
geometry: Optional[PolygonSchema] = APIField(
|
||||
geometry: Union[
|
||||
PolygonSchema,
|
||||
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
||||
] = APIField(
|
||||
None,
|
||||
title="geometry",
|
||||
description="can be null if not available or excluded from endpoint",
|
||||
|
@ -140,7 +225,10 @@ class WithPolygonGeometrySchema(Schema):
|
|||
|
||||
|
||||
class WithLineStringGeometrySchema(Schema):
|
||||
geometry: Optional[LineStringSchema] = APIField(
|
||||
geometry: Union[
|
||||
LineStringSchema,
|
||||
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
||||
] = APIField(
|
||||
None,
|
||||
title="geometry",
|
||||
description="can be null if not available or excluded from endpoint",
|
||||
|
@ -148,7 +236,10 @@ class WithLineStringGeometrySchema(Schema):
|
|||
|
||||
|
||||
class WithPointGeometrySchema(Schema):
|
||||
geometry: Optional[PointSchema] = APIField(
|
||||
geometry: Union[
|
||||
PointSchema,
|
||||
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
||||
] = APIField(
|
||||
None,
|
||||
title="geometry",
|
||||
description="can be null if not available or excluded from endpoint",
|
||||
|
@ -159,6 +250,7 @@ class WithLevelSchema(SerializableSchema):
|
|||
level: PositiveInt = APIField(
|
||||
title="level",
|
||||
description="level id this object belongs to.",
|
||||
example=1,
|
||||
)
|
||||
|
||||
|
||||
|
@ -166,6 +258,7 @@ class WithSpaceSchema(SerializableSchema):
|
|||
space: PositiveInt = APIField(
|
||||
title="space",
|
||||
description="space id this object belongs to.",
|
||||
example=1,
|
||||
)
|
||||
|
||||
|
||||
|
@ -182,8 +275,8 @@ class SimpleGeometryPointSchema(Schema):
|
|||
|
||||
|
||||
class SimpleGeometryPointAndBoundsSchema(SimpleGeometryPointSchema):
|
||||
bounds: tuple[tuple[float, float], tuple[float, float]] = APIField(
|
||||
description="location bounding box from (x, y) to (x, y)",
|
||||
bounds: BoundsSchema = APIField(
|
||||
description="location bounding box",
|
||||
example=((-10, -20), (20, 30)),
|
||||
)
|
||||
|
||||
|
@ -212,7 +305,7 @@ Coordinates3D = tuple[float, float, float]
|
|||
AnyLocationID = Union[
|
||||
Annotated[PositiveInt, APIField(
|
||||
title="location ID",
|
||||
description="numeric ID of any lcation"
|
||||
description="numeric ID of any lcation – all locations have a shared ID space"
|
||||
)],
|
||||
CustomLocationID,
|
||||
PositionID,
|
||||
|
|
|
@ -13,7 +13,8 @@ from c3nav.mapdata.schemas.model_base import (AnyLocationID, AnyPositionID, Cust
|
|||
SimpleGeometryPointSchema, SpecificLocationSchema, TitledSchema,
|
||||
WithAccessRestrictionSchema, WithLevelSchema,
|
||||
WithLineStringGeometrySchema, WithPointGeometrySchema,
|
||||
WithPolygonGeometrySchema, WithSpaceSchema)
|
||||
WithPolygonGeometrySchema, WithSpaceSchema, schema_definitions,
|
||||
schema_description)
|
||||
|
||||
|
||||
class LevelSchema(SpecificLocationSchema, DjangoModelSchema):
|
||||
|
@ -26,18 +27,26 @@ class LevelSchema(SpecificLocationSchema, DjangoModelSchema):
|
|||
title="short label (for level selector)",
|
||||
description="unique among levels",
|
||||
)
|
||||
on_top_of: Optional[PositiveInt] = APIField(
|
||||
on_top_of: Union[
|
||||
Annotated[PositiveInt, APIField(title="level ID", description="level this level is on top of", example=1)],
|
||||
Annotated[None, APIField(title="null", description="this is a main level, not on top of any other")]
|
||||
] = APIField(
|
||||
title="on top of level ID",
|
||||
description="if set, this is not a main level, but it's on top of this other level"
|
||||
)
|
||||
base_altitude: float = APIField(
|
||||
title="base/default altitude",
|
||||
description="default ground altitude for this level, if it can't be determined using altitude markers.",
|
||||
)
|
||||
default_height: PositiveFloat = APIField(
|
||||
title="default ceiling height",
|
||||
description="default ceiling height for all spaces that don't set their own",
|
||||
example=2.5
|
||||
)
|
||||
door_height: PositiveFloat = APIField(
|
||||
title="door height",
|
||||
description="height for all doors on this level",
|
||||
example="2.0",
|
||||
)
|
||||
|
||||
|
||||
|
@ -86,7 +95,8 @@ class AreaSchema(WithPolygonGeometrySchema, SpecificLocationSchema, WithSpaceSch
|
|||
"""
|
||||
slow_down_factor: PositiveFloat = APIField(
|
||||
title="slow-down factor",
|
||||
description="how much walking in this area is slowed down, overlapping areas are multiplied"
|
||||
description="how much walking in this area is slowed down, overlapping areas are multiplied",
|
||||
example=1.0,
|
||||
)
|
||||
|
||||
|
||||
|
@ -212,16 +222,31 @@ class LocationGroupSchema(LocationSchema, DjangoModelSchema):
|
|||
)
|
||||
priority: int = APIField() # todo: ???
|
||||
hierarchy: int = APIField() # todo: ???
|
||||
label_settings: Optional[LabelSettingsSchema] = APIField(
|
||||
label_settings: Union[
|
||||
Annotated[LabelSettingsSchema, APIField(
|
||||
title="label settings",
|
||||
description="label settings to use for gruop members that don't have their own set",
|
||||
)],
|
||||
Annotated[None, APIField(
|
||||
title="null",
|
||||
description="no label settings set"
|
||||
)],
|
||||
] = APIField(
|
||||
default=None,
|
||||
title="label settings",
|
||||
description="for locations with this group, can be overwritten by specific locations"
|
||||
description=(
|
||||
schema_description(LabelSettingsSchema) +
|
||||
"\n\nlocations can override this setting"
|
||||
)
|
||||
)
|
||||
can_report_missing: bool = APIField(
|
||||
title="report missing locations",
|
||||
description="can be used in form for reporting missing locations",
|
||||
)
|
||||
color: Optional[NonEmptyStr] = APIField(
|
||||
color: Union[
|
||||
Annotated[NonEmptyStr, APIField(title="color", description="a valid CSS color expression")],
|
||||
Annotated[None, APIField(title="null", description="default/no color will be used")],
|
||||
] = APIField(
|
||||
title="color",
|
||||
description="an optional color for spaces and areas with this group"
|
||||
)
|
||||
|
@ -285,7 +310,7 @@ class LocationGroupCategorySchema(TitledSchema, DjangoModelSchema):
|
|||
|
||||
class DynamicLocationSchema(SpecificLocationSchema, DjangoModelSchema):
|
||||
"""
|
||||
A dynamic location represents a moving object. Its position has to be separately queries through the position API.
|
||||
Represents a moving object. Its position has to be separately queried through the position API.
|
||||
|
||||
A dynamic location is a specific location, and can therefore be routed to and from,
|
||||
as well as belong to location groups.
|
||||
|
@ -344,10 +369,10 @@ class CustomLocationSchema(SerializableSchema):
|
|||
slug: CustomLocationID = APIField(
|
||||
description="slug, identical to ID"
|
||||
)
|
||||
icon: Optional[NonEmptyStr] = APIField(
|
||||
default=None,
|
||||
icon: NonEmptyStr = APIField(
|
||||
title="icon name",
|
||||
description="any material design icon name"
|
||||
description="any material design icon name",
|
||||
example="pin_drop",
|
||||
)
|
||||
title: NonEmptyStr = APIField(
|
||||
title="title (preferred language)",
|
||||
|
@ -359,32 +384,56 @@ class CustomLocationSchema(SerializableSchema):
|
|||
"preferred language based on the Accept-Language header."
|
||||
)
|
||||
level: PositiveInt = APIField(
|
||||
description="level ID this custom location is located on"
|
||||
description="level ID this custom location is located on",
|
||||
example=1,
|
||||
)
|
||||
space: Optional[PositiveInt] = APIField(
|
||||
description="space ID this custom location is located in, if applicable"
|
||||
space: Union[
|
||||
Annotated[PositiveInt, APIField(title="space ID", example=1)],
|
||||
Annotated[None, APIField(title="null", description="the location is not inside a space")],
|
||||
] = APIField(
|
||||
default=None,
|
||||
description="space ID this custom location is located in"
|
||||
)
|
||||
areas: list[PositiveInt] = APIField(
|
||||
description="IDs of areas this custom location is located in"
|
||||
)
|
||||
grid_square: Optional[NonEmptyStr] = APIField(
|
||||
grid_square: Union[
|
||||
Annotated[NonEmptyStr, APIField(title="grid square", description="grid square(s) that this location is in")],
|
||||
Annotated[None, APIField(title="null", description="no grid defined or outside of grid")],
|
||||
] = APIField(
|
||||
default=None,
|
||||
title="grid square",
|
||||
description="if a grid is defined and this custom location is within it",
|
||||
description="grid cell(s) that this location is in, if a grid is defined and the location is within it",
|
||||
example="C3",
|
||||
)
|
||||
near_area: Optional[PositiveInt] = APIField(
|
||||
description="the ID of an area near this custom location, if there is one"
|
||||
near_area: Union[
|
||||
Annotated[PositiveInt, APIField(title="area ID", example=1)],
|
||||
Annotated[None, APIField(title="null", description="the location is not near any areas")],
|
||||
] = APIField(
|
||||
near="nearby area",
|
||||
description="the ID of an area near this custom location"
|
||||
)
|
||||
near_poi: Optional[PositiveInt] = APIField(
|
||||
description="the ID of a POI near this custom location, if there is one"
|
||||
near_poi: Union[
|
||||
Annotated[PositiveInt, APIField(title="POI ID", example=1)],
|
||||
Annotated[None, APIField(title="null", description="the location is not near any POIs")],
|
||||
] = APIField(
|
||||
title="nearby POI",
|
||||
description="the ID of a POI near this custom location"
|
||||
)
|
||||
nearby: list[PositiveInt] = APIField(
|
||||
description="list of IDs of nearby locations"
|
||||
)
|
||||
altitude: Optional[float] = APIField(
|
||||
description="ground altitude (in the map-wide coordinate system), if it can be determined"
|
||||
altitude: Union[
|
||||
Annotated[float, APIField(title="ground altitude", example=1)],
|
||||
Annotated[None, APIField(title="null", description="could not be determined (outside of space?)")],
|
||||
] = APIField(
|
||||
title="ground altitude",
|
||||
description="ground altitude (in the map-wide coordinate system)"
|
||||
)
|
||||
geometry: Optional[PointSchema] = APIField(
|
||||
geometry: Union[
|
||||
PointSchema,
|
||||
Annotated[None, APIField(title="null", description="geometry excluded from endpoint")]
|
||||
] = APIField(
|
||||
None,
|
||||
description="point geometry for this custom location",
|
||||
)
|
||||
|
@ -392,37 +441,43 @@ class CustomLocationSchema(SerializableSchema):
|
|||
|
||||
class TrackablePositionSchema(Schema):
|
||||
"""
|
||||
A trackable position. It's position can be set or reset.
|
||||
A trackable position. Its position can be set or reset.
|
||||
"""
|
||||
id: PositionID = APIField(
|
||||
description="ID representing the position"
|
||||
description="ID representing the position",
|
||||
example="p:adskjfalskdj",
|
||||
)
|
||||
slug: PositionID = APIField(
|
||||
description="slug representing the position"
|
||||
description="slug representing the position",
|
||||
example="p:adskjfalskdj",
|
||||
)
|
||||
icon: Optional[NonEmptyStr] = APIField(
|
||||
default=None,
|
||||
icon: NonEmptyStr = APIField(
|
||||
title="icon name",
|
||||
description="any material design icon name"
|
||||
description="any material design icon name",
|
||||
example="pin_drop",
|
||||
)
|
||||
title: NonEmptyStr = APIField(
|
||||
title="title of the position",
|
||||
example="My position"
|
||||
)
|
||||
subtitle: NonEmptyStr = APIField(
|
||||
title="subtitle (preferred language)",
|
||||
description="an automatically generated short description, which might change when the position changes. "
|
||||
"preferred language based on the Accept-Language header."
|
||||
"preferred language based on the Accept-Language header.",
|
||||
example="Near Bällebad"
|
||||
)
|
||||
|
||||
|
||||
def put_locationtype_first(schema):
|
||||
fields = schema.__fields__.copy()
|
||||
schema.__fields__ = {"locationtype": fields.pop("locationtype"), **fields}
|
||||
return schema
|
||||
|
||||
|
||||
class LocationTypeSchema(Schema):
|
||||
locationtype: str
|
||||
locationtype: str = APIField(title="location type",
|
||||
description="indicates what kind of location is included. "
|
||||
"different location types have different fields.")
|
||||
|
||||
|
||||
def LocationTypeAPIField():
|
||||
return APIField(title="location type",
|
||||
description="indicates what kind of location is included. "
|
||||
"different location types have different fields.")
|
||||
|
||||
|
||||
class FullLevelLocationSchema(LevelSchema, LocationTypeSchema):
|
||||
|
@ -430,7 +485,7 @@ class FullLevelLocationSchema(LevelSchema, LocationTypeSchema):
|
|||
A level for the location API.
|
||||
See Level schema for details.
|
||||
"""
|
||||
locationtype: Literal["level"]
|
||||
locationtype: Literal["level"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class FullSpaceLocationSchema(SimpleGeometryPointAndBoundsSchema, SpaceSchema, LocationTypeSchema):
|
||||
|
@ -438,7 +493,7 @@ class FullSpaceLocationSchema(SimpleGeometryPointAndBoundsSchema, SpaceSchema, L
|
|||
A space with some additional information for the location API.
|
||||
See Space schema for details.
|
||||
"""
|
||||
locationtype: Literal["space"]
|
||||
locationtype: Literal["space"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class FullAreaLocationSchema(SimpleGeometryPointAndBoundsSchema, AreaSchema, LocationTypeSchema):
|
||||
|
@ -446,7 +501,7 @@ class FullAreaLocationSchema(SimpleGeometryPointAndBoundsSchema, AreaSchema, Loc
|
|||
An area with some additional information for the location API.
|
||||
See Area schema for details.
|
||||
"""
|
||||
locationtype: Literal["area"]
|
||||
locationtype: Literal["area"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class FullPOILocationSchema(SimpleGeometryPointSchema, POISchema, LocationTypeSchema):
|
||||
|
@ -454,7 +509,7 @@ class FullPOILocationSchema(SimpleGeometryPointSchema, POISchema, LocationTypeSc
|
|||
A point of interest with some additional information for the location API.
|
||||
See POI schema for details.
|
||||
"""
|
||||
locationtype: Literal["poi"]
|
||||
locationtype: Literal["poi"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class FullLocationGroupLocationSchema(SimpleGeometryLocationsSchema, LocationGroupSchema, LocationTypeSchema):
|
||||
|
@ -462,7 +517,7 @@ class FullLocationGroupLocationSchema(SimpleGeometryLocationsSchema, LocationGro
|
|||
A location group with some additional information for the location API.
|
||||
See LocationGroup schema for details.
|
||||
"""
|
||||
locationtype: Literal["locationgroup"]
|
||||
locationtype: Literal["locationgroup"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class FullDynamicLocationLocationSchema(DynamicLocationSchema, LocationTypeSchema):
|
||||
|
@ -470,7 +525,7 @@ class FullDynamicLocationLocationSchema(DynamicLocationSchema, LocationTypeSchem
|
|||
A dynamic location for the location API.
|
||||
See DynamicLocation schema for details.
|
||||
"""
|
||||
locationtype: Literal["dynamiclocation"]
|
||||
locationtype: Literal["dynamiclocation"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class CustomLocationLocationSchema(SimpleGeometryPointAndBoundsSchema, CustomLocationSchema, LocationTypeSchema):
|
||||
|
@ -478,7 +533,7 @@ class CustomLocationLocationSchema(SimpleGeometryPointAndBoundsSchema, CustomLoc
|
|||
A custom location for the location API.
|
||||
See CustomLocation schema for details.
|
||||
"""
|
||||
locationtype: Literal["customlocation"]
|
||||
locationtype: Literal["customlocation"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class TrackablePositionLocationSchema(TrackablePositionSchema, LocationTypeSchema):
|
||||
|
@ -486,7 +541,7 @@ class TrackablePositionLocationSchema(TrackablePositionSchema, LocationTypeSchem
|
|||
A trackable position for the location API.
|
||||
See TrackablePosition schema for details.
|
||||
"""
|
||||
locationtype: Literal["position"]
|
||||
locationtype: Literal["position"] = LocationTypeAPIField()
|
||||
|
||||
|
||||
class SlimLocationMixin(Schema):
|
||||
|
@ -599,6 +654,14 @@ SlimLocationSchema = Annotated[
|
|||
]
|
||||
|
||||
|
||||
listable_location_definitions = schema_definitions(
|
||||
(LevelSchema, SpaceSchema, AreaSchema, POISchema, DynamicLocationSchema, LocationGroupSchema)
|
||||
)
|
||||
all_location_definitions = listable_location_definitions + "\n" + schema_definitions(
|
||||
(CustomLocationSchema, TrackablePositionSchema)
|
||||
)
|
||||
|
||||
|
||||
class DisplayLink(Schema):
|
||||
"""
|
||||
A link for the location display
|
||||
|
@ -612,14 +675,24 @@ class DisplayLink(Schema):
|
|||
class LocationDisplay(SerializableSchema):
|
||||
id: AnyLocationID = APIField(
|
||||
description="a numeric ID for a map location or a string ID for generated locations",
|
||||
example=1,
|
||||
)
|
||||
level: Optional[PositiveInt] = APIField(
|
||||
|
||||
level: Union[
|
||||
Annotated[PositiveInt, APIField(title="level ID", description="ID of relevant level")],
|
||||
Annotated[None, APIField(title="null", description="no relevant level")],
|
||||
] = APIField(
|
||||
None,
|
||||
description="level ID, if applicable"
|
||||
title="level",
|
||||
example=2,
|
||||
)
|
||||
space: Optional[PositiveInt] = APIField(
|
||||
space: Union[
|
||||
Annotated[PositiveInt, APIField(title="level ID", description="ID of relevant level")],
|
||||
Annotated[None, APIField(title="null", description="no relevant level")],
|
||||
] = APIField(
|
||||
None,
|
||||
description="space ID, if applicable"
|
||||
description="space",
|
||||
example=3,
|
||||
)
|
||||
display: list[
|
||||
tuple[
|
||||
|
@ -631,12 +704,48 @@ class LocationDisplay(SerializableSchema):
|
|||
Annotated[None, APIField(title="no value")]
|
||||
], APIField(title="field value", union_mode='left_to_right')]
|
||||
]
|
||||
] = APIField(description="a list of human-readable display values")
|
||||
geometry: Optional[GeometrySchema] = APIField(
|
||||
] = APIField(
|
||||
title="display fields",
|
||||
description="a list of human-readable display values",
|
||||
example=[
|
||||
("Title", "Awesome location"),
|
||||
("Access Restriction", None),
|
||||
("Level", {
|
||||
"id": 2,
|
||||
"slug": "level0",
|
||||
"title": "Ground Floor",
|
||||
"can_search": True,
|
||||
}),
|
||||
("Groups", [
|
||||
{
|
||||
"id": 10,
|
||||
"slug": "entrances",
|
||||
"title": "Entrances",
|
||||
"can_search": True,
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"slug": "startswithe",
|
||||
"title": "Locations that Start with E",
|
||||
"can_search": False,
|
||||
}
|
||||
])
|
||||
]
|
||||
)
|
||||
geometry: Union[
|
||||
GeometrySchema,
|
||||
Annotated[None, APIField(title="null", description="no geometry available")]
|
||||
] = APIField(
|
||||
None, description="representative geometry, if available"
|
||||
)
|
||||
editor_url: Optional[NonEmptyStr] = APIField(
|
||||
None, description="path to edit this object in the editor, if the user has access to it",
|
||||
editor_url: Union[
|
||||
Annotated[NonEmptyStr, APIField(title="path to editor")],
|
||||
Annotated[None, APIField(title="null", description="no editor access or object is not editable")],
|
||||
] = APIField(
|
||||
None,
|
||||
title="editor URL",
|
||||
description="path to edit this object in the editor",
|
||||
example="/editor/spaces/2/pois/1/"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated, Union
|
||||
|
||||
from ninja import Schema
|
||||
from pydantic import Field as APIField
|
||||
from pydantic import PositiveInt
|
||||
|
||||
from c3nav.api.schema import GeometrySchema
|
||||
from c3nav.mapdata.schemas.model_base import AnyLocationID
|
||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, BoundsSchema
|
||||
|
||||
|
||||
class BoundsSchema(Schema):
|
||||
class WithBoundsSchema(Schema):
|
||||
"""
|
||||
Describing a bounding box
|
||||
"""
|
||||
bounds: tuple[
|
||||
Annotated[tuple[
|
||||
Annotated[float, APIField(title="left", description="lowest X coordindate")],
|
||||
Annotated[float, APIField(title="bottom", description="lowest Y coordindate")]
|
||||
], APIField(title="(left, bottom)", description="lowest coordinates", example=(-10, -20))],
|
||||
Annotated[tuple[
|
||||
Annotated[float, APIField(title="right", description="highest X coordindate")],
|
||||
Annotated[float, APIField(title="top", description="highest Y coordindate")]
|
||||
], APIField(title="(right, top)", description="highest coordinates", example=(20, 30))]
|
||||
] = APIField(
|
||||
bounds: BoundsSchema = APIField(
|
||||
title="boundaries",
|
||||
description="(left, bottom) to (top, right)",
|
||||
)
|
||||
|
@ -31,9 +22,15 @@ class LocationGeometry(Schema):
|
|||
id: AnyLocationID = APIField(
|
||||
description="ID of the location that the geometry is being queried for",
|
||||
)
|
||||
level: Optional[PositiveInt] = APIField(
|
||||
level: Union[
|
||||
Annotated[PositiveInt, APIField(title="level ID")],
|
||||
Annotated[None, APIField(title="null", description="geometry is not on any level")], # todo: possible?
|
||||
] = APIField(
|
||||
description="ID of the level the geometry is on",
|
||||
)
|
||||
geometry: Optional[GeometrySchema] = APIField(
|
||||
geometry: Union[
|
||||
GeometrySchema,
|
||||
Annotated[None, APIField(title="null", description="no geometry available")]
|
||||
] = APIField(
|
||||
description="geometry, if available"
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue