2023-12-04 18:58:49 +01:00
|
|
|
|
import re
|
2023-12-11 20:49:50 +01:00
|
|
|
|
from typing import Annotated, Optional, Union
|
2023-11-18 21:29:35 +01:00
|
|
|
|
|
|
|
|
|
from pydantic import Field as APIField
|
2023-12-11 20:49:50 +01:00
|
|
|
|
from pydantic import PositiveInt
|
2023-11-18 21:29:35 +01:00
|
|
|
|
|
2023-12-24 16:04:33 +01:00
|
|
|
|
from c3nav.api.schema import BaseSchema, LineStringSchema, PointSchema, PolygonSchema
|
2023-11-18 21:29:35 +01:00
|
|
|
|
from c3nav.api.utils import NonEmptyStr
|
|
|
|
|
|
|
|
|
|
|
2023-12-04 18:58:49 +01:00
|
|
|
|
def schema_description(schema):
|
|
|
|
|
return schema.__doc__.replace("\n ", "\n").strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def schema_definition(schema):
|
2023-12-11 19:02:19 +01:00
|
|
|
|
return ("- **"+re.sub(r"([a-z])([A-Z])", r"\1 \2", schema.__name__.removesuffix("Schema")) + "**: " +
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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))]
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class DjangoModelSchema(BaseSchema):
|
2023-11-19 16:48:40 +01:00
|
|
|
|
id: PositiveInt = APIField(
|
|
|
|
|
title="ID",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
example=1,
|
2023-11-19 16:48:40 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class LocationSlugSchema(BaseSchema):
|
2023-11-18 21:29:35 +01:00
|
|
|
|
slug: NonEmptyStr = APIField(
|
|
|
|
|
title="location slug",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class WithAccessRestrictionSchema(BaseSchema):
|
2023-12-04 18:58:49 +01:00
|
|
|
|
access_restriction: Union[
|
|
|
|
|
Annotated[PositiveInt, APIField(title="access restriction ID")],
|
|
|
|
|
Annotated[None, APIField(title="null", description="no access restriction")],
|
|
|
|
|
] = APIField(
|
2023-11-18 21:29:35 +01:00
|
|
|
|
default=None,
|
|
|
|
|
title="access restriction ID",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="access restriction that this object belongs to",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class TitledSchema(BaseSchema):
|
2023-11-18 21:29:35 +01:00
|
|
|
|
titles: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
|
|
|
|
title="title (all languages)",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="title in all available languages. property names are the ISO-language code. "
|
|
|
|
|
"languages may be missing.",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
example={
|
2023-12-04 18:58:49 +01:00
|
|
|
|
"en": "Entrance",
|
|
|
|
|
"de": "Eingang",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
title: NonEmptyStr = APIField(
|
|
|
|
|
title="title (preferred language)",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="title in the preferred language based on the Accept-Language header.",
|
|
|
|
|
example="Entrance",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-11-19 16:48:40 +01:00
|
|
|
|
class LocationSchema(WithAccessRestrictionSchema, TitledSchema, LocationSlugSchema):
|
2023-11-18 21:29:35 +01:00
|
|
|
|
subtitle: NonEmptyStr = APIField(
|
|
|
|
|
title="subtitle (preferred language)",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="an automatically generated short description for this location in the "
|
|
|
|
|
"preferred language based on the Accept-Language header.",
|
|
|
|
|
example="near Area 51",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
2023-12-04 23:07:30 +01:00
|
|
|
|
icon: Optional[NonEmptyStr] = APIField( # todo: not optional?
|
2023-11-18 21:29:35 +01:00
|
|
|
|
title="icon name",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="any material design icon name",
|
|
|
|
|
example="pin_drop",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
can_search: bool = APIField(
|
|
|
|
|
title="can be searched",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="if `true`, this object can show up in search results",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
can_describe: bool = APIField(
|
|
|
|
|
title="can describe locations",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="if `true`, this object can be used to describe other locations (e.g. in their subtitle)",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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(
|
2023-11-24 16:53:56 +01:00
|
|
|
|
None,
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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",
|
2023-11-24 16:53:56 +01:00
|
|
|
|
)
|
2023-11-24 17:24:07 +01:00
|
|
|
|
|
2023-11-18 21:29:35 +01:00
|
|
|
|
|
2023-11-23 21:20:14 +01:00
|
|
|
|
class LabelSettingsSchema(DjangoModelSchema): # todo: add titles back in here
|
2023-11-19 00:12:10 +01:00
|
|
|
|
"""
|
|
|
|
|
Settings preset for how and when to display a label. Reusable between objects.
|
|
|
|
|
The title describes the title of this preset, not the displayed label.
|
|
|
|
|
"""
|
2023-11-23 21:21:55 +01:00
|
|
|
|
min_zoom: float = APIField(
|
|
|
|
|
-10,
|
2023-11-18 21:29:35 +01:00
|
|
|
|
title="min zoom",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="minimum zoom to display the label at",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
2023-11-23 21:21:55 +01:00
|
|
|
|
max_zoom: float = APIField(
|
|
|
|
|
10,
|
2023-11-18 21:29:35 +01:00
|
|
|
|
title="max zoom",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="maximum, zoom to display the label at",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
font_size: PositiveInt = APIField(
|
|
|
|
|
title="font size",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="font size of the label",
|
|
|
|
|
example=12,
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-11-19 15:34:08 +01:00
|
|
|
|
class SpecificLocationSchema(LocationSchema):
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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(
|
2023-11-18 21:29:35 +01:00
|
|
|
|
default=None,
|
|
|
|
|
title="grid square",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="grid cell(s) that this location is in, if a grid is defined and the location is within it",
|
|
|
|
|
example="C3",
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
2024-12-03 14:18:16 +01:00
|
|
|
|
groups: list[PositiveInt] = APIField(
|
|
|
|
|
title="location groups",
|
|
|
|
|
description="location group(s) that this specific location belongs to.",
|
|
|
|
|
example=[5, 1, 3, 7],
|
|
|
|
|
)
|
|
|
|
|
groups_by_category: dict[
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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.",
|
2023-12-11 19:02:19 +01:00
|
|
|
|
example=[1, 4, 5],
|
2023-12-04 18:58:49 +01:00
|
|
|
|
)],
|
|
|
|
|
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(
|
2024-12-03 14:18:16 +01:00
|
|
|
|
title="location groups by category",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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"
|
2023-11-18 21:29:35 +01:00
|
|
|
|
"categories may be missing if no groups apply.",
|
|
|
|
|
example={
|
|
|
|
|
"category_with_single_true": 5,
|
|
|
|
|
"other_category_with_single_true": None,
|
2023-12-04 18:58:49 +01:00
|
|
|
|
"category_with_single_false": [1, 3, 7],
|
2023-11-18 21:29:35 +01:00
|
|
|
|
}
|
|
|
|
|
)
|
2024-12-03 14:18:16 +01:00
|
|
|
|
label_settings: Optional[PositiveInt] = APIField(
|
|
|
|
|
default=None,
|
|
|
|
|
title="label settings",
|
|
|
|
|
description=(
|
|
|
|
|
schema_description(LabelSettingsSchema) +
|
|
|
|
|
"\n\nif not set, label settings of location groups might be used"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
effective_label_settings: Union[
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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(
|
2023-11-18 21:29:35 +01:00
|
|
|
|
default=None,
|
|
|
|
|
title="label settings",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description=(
|
|
|
|
|
schema_description(LabelSettingsSchema) +
|
|
|
|
|
"\n\nif not set, label settings of location groups might be used"
|
|
|
|
|
)
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
2023-12-04 18:58:49 +01:00
|
|
|
|
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(
|
2023-11-18 21:29:35 +01:00
|
|
|
|
default=None,
|
|
|
|
|
title="label override (preferred language)",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="text to use for the label. by default (null), the title would be used."
|
2023-11-18 21:29:35 +01:00
|
|
|
|
)
|
2023-11-19 15:34:08 +01:00
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class WithPolygonGeometrySchema(BaseSchema):
|
2023-12-04 18:58:49 +01:00
|
|
|
|
geometry: Union[
|
|
|
|
|
PolygonSchema,
|
|
|
|
|
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
|
|
|
|
] = APIField(
|
2023-11-23 18:10:31 +01:00
|
|
|
|
None,
|
2023-11-19 15:34:08 +01:00
|
|
|
|
title="geometry",
|
2023-11-23 18:10:31 +01:00
|
|
|
|
description="can be null if not available or excluded from endpoint",
|
2023-11-19 15:34:08 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class WithLineStringGeometrySchema(BaseSchema):
|
2023-12-04 18:58:49 +01:00
|
|
|
|
geometry: Union[
|
|
|
|
|
LineStringSchema,
|
|
|
|
|
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
|
|
|
|
] = APIField(
|
2023-11-23 18:10:31 +01:00
|
|
|
|
None,
|
2023-11-19 15:34:08 +01:00
|
|
|
|
title="geometry",
|
2023-11-23 18:10:31 +01:00
|
|
|
|
description="can be null if not available or excluded from endpoint",
|
2023-11-19 15:34:08 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class WithPointGeometrySchema(BaseSchema):
|
2023-12-04 18:58:49 +01:00
|
|
|
|
geometry: Union[
|
|
|
|
|
PointSchema,
|
|
|
|
|
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
|
|
|
|
] = APIField(
|
2023-11-23 18:10:31 +01:00
|
|
|
|
None,
|
2023-11-19 15:34:08 +01:00
|
|
|
|
title="geometry",
|
2023-11-23 18:10:31 +01:00
|
|
|
|
description="can be null if not available or excluded from endpoint",
|
2023-11-19 15:34:08 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class WithLevelSchema(BaseSchema):
|
2023-11-19 15:34:08 +01:00
|
|
|
|
level: PositiveInt = APIField(
|
|
|
|
|
title="level",
|
|
|
|
|
description="level id this object belongs to.",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
example=1,
|
2023-11-19 15:34:08 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class WithSpaceSchema(BaseSchema):
|
2023-11-19 15:34:08 +01:00
|
|
|
|
space: PositiveInt = APIField(
|
|
|
|
|
title="space",
|
|
|
|
|
description="space id this object belongs to.",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
example=1,
|
2023-11-19 15:34:08 +01:00
|
|
|
|
)
|
2023-11-23 16:37:25 +01:00
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class SimpleGeometryPointSchema(BaseSchema):
|
2023-11-23 18:16:56 +01:00
|
|
|
|
point: tuple[
|
|
|
|
|
Annotated[PositiveInt, APIField(title="level ID")],
|
|
|
|
|
Annotated[float, APIField(title="x coordinate")],
|
|
|
|
|
Annotated[float, APIField(title="y coordinate")]
|
|
|
|
|
] = APIField(
|
|
|
|
|
title="point representation",
|
|
|
|
|
description="representative point for the location",
|
|
|
|
|
example=(1, 4.2, 13.37)
|
2023-11-23 16:37:25 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-11-24 16:31:39 +01:00
|
|
|
|
class SimpleGeometryPointAndBoundsSchema(SimpleGeometryPointSchema):
|
2023-12-04 18:58:49 +01:00
|
|
|
|
bounds: BoundsSchema = APIField(
|
|
|
|
|
description="location bounding box",
|
2023-11-24 16:31:39 +01:00
|
|
|
|
example=((-10, -20), (20, 30)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-12-11 20:49:50 +01:00
|
|
|
|
class SimpleGeometryLocationsSchema(BaseSchema):
|
2023-11-23 16:37:25 +01:00
|
|
|
|
locations: list[PositiveInt] = APIField( # todo: this should be a set… but json serialization?
|
|
|
|
|
description="IDs of all locations that belong to this grouo",
|
|
|
|
|
example=(1, 2, 3),
|
|
|
|
|
)
|
2023-11-23 18:10:31 +01:00
|
|
|
|
|
2023-11-23 21:11:31 +01:00
|
|
|
|
|
2023-11-24 01:05:38 +01:00
|
|
|
|
CustomLocationID = Annotated[NonEmptyStr, APIField(
|
|
|
|
|
title="custom location ID",
|
|
|
|
|
pattern=r"c:[a-z0-9-_]+:(-?\d+(\.\d+)?):(-?\d+(\.\d+)?)$",
|
2023-11-24 15:42:48 +01:00
|
|
|
|
example="c:0:-7.23:12.34",
|
2023-11-24 01:05:38 +01:00
|
|
|
|
description="level short_name and x/y coordinates form the ID of a custom location"
|
|
|
|
|
)]
|
|
|
|
|
PositionID = Annotated[NonEmptyStr, APIField(
|
|
|
|
|
title="position ID",
|
2023-11-24 17:21:57 +01:00
|
|
|
|
pattern=r"p:[A-Za-z0-9]+$",
|
2023-11-24 01:05:38 +01:00
|
|
|
|
description="the ID of a user-defined tracked position is made up of its secret"
|
|
|
|
|
)]
|
2023-12-01 23:49:02 +01:00
|
|
|
|
Coordinates3D = tuple[float, float, float]
|
2023-11-24 01:05:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AnyLocationID = Union[
|
2023-11-23 22:44:09 +01:00
|
|
|
|
Annotated[PositiveInt, APIField(
|
|
|
|
|
title="location ID",
|
2023-12-04 18:58:49 +01:00
|
|
|
|
description="numeric ID of any lcation – all locations have a shared ID space"
|
2023-11-23 22:44:09 +01:00
|
|
|
|
)],
|
2023-11-24 01:05:38 +01:00
|
|
|
|
CustomLocationID,
|
|
|
|
|
PositionID,
|
|
|
|
|
]
|
|
|
|
|
AnyPositionID = Union[
|
|
|
|
|
Annotated[PositiveInt, APIField(
|
|
|
|
|
title="dynamic location ID",
|
|
|
|
|
description="numeric ID of any dynamic lcation"
|
2023-11-23 22:44:09 +01:00
|
|
|
|
)],
|
2023-11-24 01:05:38 +01:00
|
|
|
|
PositionID,
|
2023-11-23 21:11:31 +01:00
|
|
|
|
]
|