202 lines
6.9 KiB
Python
202 lines
6.9 KiB
Python
from typing import Annotated, Any, ClassVar, Optional, Union
|
|
|
|
from ninja import Schema
|
|
from pydantic import Field as APIField
|
|
from pydantic import PositiveInt, model_validator
|
|
from pydantic.functional_validators import ModelWrapValidatorHandler
|
|
from pydantic_core.core_schema import ValidationInfo
|
|
|
|
from c3nav.api.schema import LineStringSchema, PointSchema, PolygonSchema
|
|
from c3nav.api.utils import NonEmptyStr
|
|
|
|
|
|
class SerializableSchema(Schema):
|
|
@model_validator(mode="wrap") # noqa
|
|
@classmethod
|
|
def _run_root_validator(cls, values: Any, handler: ModelWrapValidatorHandler[Schema], info: ValidationInfo) -> Any:
|
|
""" overwriting this, we need to call serialize to get the correct data """
|
|
if not isinstance(values, dict):
|
|
values = values.serialize()
|
|
return handler(values)
|
|
|
|
|
|
class DjangoModelSchema(SerializableSchema):
|
|
id: PositiveInt = APIField(
|
|
title="ID",
|
|
)
|
|
|
|
|
|
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.",
|
|
)
|
|
|
|
|
|
class WithAccessRestrictionSchema(Schema):
|
|
access_restriction: Optional[PositiveInt] = APIField(
|
|
default=None,
|
|
title="access restriction ID",
|
|
)
|
|
|
|
|
|
class TitledSchema(Schema):
|
|
titles: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
|
title="title (all languages)",
|
|
description="property names are the ISO-language code. languages may be missing.",
|
|
example={
|
|
"en": "Title",
|
|
"de": "Titel",
|
|
}
|
|
)
|
|
title: NonEmptyStr = APIField(
|
|
title="title (preferred language)",
|
|
description="preferred language based on the Accept-Language header."
|
|
)
|
|
|
|
|
|
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."
|
|
)
|
|
icon: Optional[NonEmptyStr] = APIField(
|
|
default=None,
|
|
title="icon name",
|
|
description="any material design icon name"
|
|
)
|
|
can_search: bool = APIField(
|
|
title="can be searched",
|
|
)
|
|
can_describe: bool = APIField(
|
|
title="can describe locations",
|
|
)
|
|
# todo: add_search
|
|
|
|
|
|
class LabelSettingsSchema(DjangoModelSchema): # todo: add titles back in here
|
|
"""
|
|
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.
|
|
"""
|
|
min_zoom: Optional[float] = APIField(
|
|
title="min zoom",
|
|
)
|
|
max_zoom: Optional[float] = APIField(
|
|
title="max zoom",
|
|
)
|
|
font_size: PositiveInt = APIField(
|
|
title="font size",
|
|
)
|
|
|
|
|
|
class SpecificLocationSchema(LocationSchema):
|
|
grid_square: Optional[NonEmptyStr] = APIField(
|
|
default=None,
|
|
title="grid square",
|
|
description="if a grid is defined and this location is within it",
|
|
)
|
|
groups: dict[NonEmptyStr, list[PositiveInt] | Optional[PositiveInt]] = 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."
|
|
"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],
|
|
}
|
|
)
|
|
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,
|
|
title="label override (preferred language)",
|
|
description="preferred language based on the Accept-Language header."
|
|
)
|
|
|
|
|
|
class WithPolygonGeometrySchema(Schema):
|
|
geometry: Optional[PolygonSchema] = APIField(
|
|
None,
|
|
title="geometry",
|
|
description="can be null if not available or excluded from endpoint",
|
|
)
|
|
|
|
|
|
class WithLineStringGeometrySchema(Schema):
|
|
geometry: Optional[LineStringSchema] = APIField(
|
|
None,
|
|
title="geometry",
|
|
description="can be null if not available or excluded from endpoint",
|
|
)
|
|
|
|
|
|
class WithPointGeometrySchema(Schema):
|
|
geometry: Optional[PointSchema] = APIField(
|
|
None,
|
|
title="geometry",
|
|
description="can be null if not available or excluded from endpoint",
|
|
)
|
|
|
|
|
|
class WithLevelSchema(SerializableSchema):
|
|
level: PositiveInt = APIField(
|
|
title="level",
|
|
description="level id this object belongs to.",
|
|
)
|
|
|
|
|
|
class WithSpaceSchema(SerializableSchema):
|
|
space: PositiveInt = APIField(
|
|
title="space",
|
|
description="space id this object belongs to.",
|
|
)
|
|
|
|
|
|
class SimpleGeometryBoundsSchema(Schema):
|
|
bounds: tuple[tuple[float, float], tuple[float, float]] = APIField(
|
|
description="location bounding box from (x, y) to (x, y)",
|
|
example=((-10, -20), (20, 30)),
|
|
)
|
|
|
|
|
|
class SimpleGeometryBoundsAndPointSchema(SimpleGeometryBoundsSchema):
|
|
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)
|
|
)
|
|
|
|
|
|
class SimpleGeometryLocationsSchema(Schema):
|
|
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),
|
|
)
|
|
|
|
|
|
LocationID = Union[
|
|
Annotated[int, APIField(title="location ID",
|
|
description="numeric ID of any lcation")],
|
|
Annotated[str, APIField(title="custom location ID",
|
|
pattern=r"c:[a-z0-9-_]+:(-?\d+(\.\d+)?):(-?\d+(\.\d+)?)$",
|
|
description="level short_name and x/y coordinates form the ID of a custom location")],
|
|
Annotated[str, APIField(title="position ID",
|
|
pattern=r"p:[a-z0-9]+$",
|
|
description="the ID of a user-defined tracked position is made up of its secret")],
|
|
]
|