convert django lazy string proxies to strings in the base schema validator, rather than in the serialization code of each source model
This commit is contained in:
parent
4c06abd400
commit
4b1ac9f194
21 changed files with 126 additions and 104 deletions
|
@ -1,16 +1,16 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from ninja import Field as APIField
|
from ninja import Field as APIField
|
||||||
from ninja import Router as APIRouter
|
from ninja import Router as APIRouter
|
||||||
from ninja import Schema
|
|
||||||
|
|
||||||
from c3nav.api.auth import APIKeyType, auth_responses
|
from c3nav.api.auth import APIKeyType, auth_responses
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.control.models import UserPermissions
|
from c3nav.control.models import UserPermissions
|
||||||
|
|
||||||
auth_api_router = APIRouter(tags=["auth"])
|
auth_api_router = APIRouter(tags=["auth"])
|
||||||
|
|
||||||
|
|
||||||
class AuthStatusSchema(Schema):
|
class AuthStatusSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
Current auth state and permissions
|
Current auth state and permissions
|
||||||
"""
|
"""
|
||||||
|
@ -44,7 +44,7 @@ def get_status(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIKeySchema(Schema):
|
class APIKeySchema(BaseSchema):
|
||||||
key: NonEmptyStr = APIField(
|
key: NonEmptyStr = APIField(
|
||||||
title="API key",
|
title="API key",
|
||||||
description="API secret to be directly used with `X-API-Key` HTTP header."
|
description="API secret to be directly used with `X-API-Key` HTTP header."
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.utils.functional import Promise
|
||||||
|
|
||||||
from c3nav.api.schema import APIErrorSchema
|
from c3nav.api.schema import APIErrorSchema
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +9,10 @@ class CustomAPIException(Exception):
|
||||||
|
|
||||||
def __init__(self, detail=None):
|
def __init__(self, detail=None):
|
||||||
if detail is not None:
|
if detail is not None:
|
||||||
self.detail = detail
|
if isinstance(detail, Promise):
|
||||||
|
self.detail = str(detail)
|
||||||
|
else:
|
||||||
|
self.detail = detail
|
||||||
|
|
||||||
def get_response(self, api, request):
|
def get_response(self, api, request):
|
||||||
return api.create_response(request, {"detail": self.detail}, status=self.status_code)
|
return api.create_response(request, {"detail": self.detail}, status=self.status_code)
|
||||||
|
|
|
@ -1,13 +1,44 @@
|
||||||
|
from types import NoneType
|
||||||
from typing import Annotated, Any, Literal, Union
|
from typing import Annotated, Any, Literal, Union
|
||||||
|
|
||||||
|
from django.utils.functional import Promise
|
||||||
from ninja import Schema
|
from ninja import Schema
|
||||||
from pydantic import Discriminator
|
from pydantic import Discriminator, model_validator
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
|
from pydantic.functional_validators import ModelWrapValidatorHandler
|
||||||
|
from pydantic_core.core_schema import ValidationInfo
|
||||||
|
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
|
|
||||||
|
|
||||||
class APIErrorSchema(Schema):
|
class BaseSchema(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 """
|
||||||
|
return handler(cls.convert(values))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert(cls, values: Any):
|
||||||
|
if isinstance(values, Schema):
|
||||||
|
return values
|
||||||
|
if isinstance(values, (str, bool, int, float, complex, NoneType)):
|
||||||
|
return values
|
||||||
|
if isinstance(values, dict):
|
||||||
|
return {
|
||||||
|
key: cls.convert(val)
|
||||||
|
for key, val in values.items()
|
||||||
|
}
|
||||||
|
if isinstance(values, (list, tuple, set, frozenset)):
|
||||||
|
return type(values)(cls.convert(val) for val in values)
|
||||||
|
if isinstance(values, Promise):
|
||||||
|
return str(values)
|
||||||
|
if hasattr(values, 'serialize') and callable(values.serialize):
|
||||||
|
return values.serialize()
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class APIErrorSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
An error has occured with this request
|
An error has occured with this request
|
||||||
"""
|
"""
|
||||||
|
@ -16,7 +47,7 @@ class APIErrorSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PolygonSchema(Schema):
|
class PolygonSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A GeoJSON Polygon
|
A GeoJSON Polygon
|
||||||
"""
|
"""
|
||||||
|
@ -29,7 +60,7 @@ class PolygonSchema(Schema):
|
||||||
title = "GeoJSON Polygon"
|
title = "GeoJSON Polygon"
|
||||||
|
|
||||||
|
|
||||||
class LineStringSchema(Schema):
|
class LineStringSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A GeoJSON LineString
|
A GeoJSON LineString
|
||||||
"""
|
"""
|
||||||
|
@ -42,7 +73,7 @@ class LineStringSchema(Schema):
|
||||||
title = "GeoJSON LineString"
|
title = "GeoJSON LineString"
|
||||||
|
|
||||||
|
|
||||||
class LineSchema(Schema):
|
class LineSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A GeoJSON LineString with only two points
|
A GeoJSON LineString with only two points
|
||||||
"""
|
"""
|
||||||
|
@ -55,7 +86,7 @@ class LineSchema(Schema):
|
||||||
title = "GeoJSON LineString (only two points)"
|
title = "GeoJSON LineString (only two points)"
|
||||||
|
|
||||||
|
|
||||||
class PointSchema(Schema):
|
class PointSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A GeoJSON Point
|
A GeoJSON Point
|
||||||
"""
|
"""
|
||||||
|
@ -77,7 +108,7 @@ GeometrySchema = Annotated[
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AnyGeometrySchema(Schema):
|
class AnyGeometrySchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A GeoJSON Geometry
|
A GeoJSON Geometry
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -125,7 +125,7 @@ def get_view_as_api(request, path: str):
|
||||||
resolved = resolve_editor_path_api(request, path)
|
resolved = resolve_editor_path_api(request, path)
|
||||||
|
|
||||||
if not resolved:
|
if not resolved:
|
||||||
raise API404(str(_('No matching editor view endpoint found.')))
|
raise API404(_('No matching editor view endpoint found.'))
|
||||||
|
|
||||||
if not getattr(resolved.func, 'api_hybrid', False):
|
if not getattr(resolved.func, 'api_hybrid', False):
|
||||||
raise API404(_('Matching editor view point does not provide an API.'))
|
raise API404(_('Matching editor view point does not provide an API.'))
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from typing import Annotated, Literal, Optional, Union
|
from typing import Annotated, Literal, Optional, Union
|
||||||
|
|
||||||
from ninja import Schema
|
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
from pydantic import PositiveInt
|
from pydantic import PositiveInt
|
||||||
|
|
||||||
from c3nav.api.schema import AnyGeometrySchema, GeometrySchema, LineSchema
|
from c3nav.api.schema import AnyGeometrySchema, GeometrySchema, LineSchema, BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
|
|
||||||
GeometryStylesSchema = Annotated[
|
GeometryStylesSchema = Annotated[
|
||||||
|
@ -47,7 +46,7 @@ EditorGeometriesCacheReferenceElem = Annotated[
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditorGeometriesPropertiesSchema(Schema):
|
class EditorGeometriesPropertiesSchema(BaseSchema):
|
||||||
id: EditorID
|
id: EditorID
|
||||||
type: NonEmptyStr
|
type: NonEmptyStr
|
||||||
space: Union[
|
space: Union[
|
||||||
|
@ -63,20 +62,20 @@ class EditorGeometriesPropertiesSchema(Schema):
|
||||||
opacity: Optional[float] = None # todo: range
|
opacity: Optional[float] = None # todo: range
|
||||||
|
|
||||||
|
|
||||||
class EditorGeometriesGraphEdgePropertiesSchema(Schema):
|
class EditorGeometriesGraphEdgePropertiesSchema(BaseSchema):
|
||||||
id: EditorID
|
id: EditorID
|
||||||
type: Literal["graphedge"]
|
type: Literal["graphedge"]
|
||||||
from_node: EditorID
|
from_node: EditorID
|
||||||
to_node: EditorID
|
to_node: EditorID
|
||||||
|
|
||||||
|
|
||||||
class EditorGeometriesGraphEdgeElemSchema(Schema):
|
class EditorGeometriesGraphEdgeElemSchema(BaseSchema):
|
||||||
type: Literal["Feature"]
|
type: Literal["Feature"]
|
||||||
properties: EditorGeometriesGraphEdgePropertiesSchema
|
properties: EditorGeometriesGraphEdgePropertiesSchema
|
||||||
geometry: LineSchema
|
geometry: LineSchema
|
||||||
|
|
||||||
|
|
||||||
class EditorGeometriesGeometryElemSchema(Schema):
|
class EditorGeometriesGeometryElemSchema(BaseSchema):
|
||||||
type: Literal["Feature"]
|
type: Literal["Feature"]
|
||||||
geometry: AnyGeometrySchema = APIField(description="geometry, potentially modified for displaying")
|
geometry: AnyGeometrySchema = APIField(description="geometry, potentially modified for displaying")
|
||||||
original_geometry: Optional[GeometrySchema] = APIField(
|
original_geometry: Optional[GeometrySchema] = APIField(
|
||||||
|
|
|
@ -187,7 +187,7 @@ class APIHybridFormTemplateResponse(APIHybridResponse):
|
||||||
def get_api_response(self, request):
|
def get_api_response(self, request):
|
||||||
result = {}
|
result = {}
|
||||||
if self.error:
|
if self.error:
|
||||||
result['error'] = str(self.error.message)
|
result['error'] = self.error.message
|
||||||
self.status_code = self.error.status_code
|
self.status_code = self.error.status_code
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if not self.form.is_valid():
|
if not self.form.is_valid():
|
||||||
|
|
|
@ -6,12 +6,12 @@ from django.shortcuts import redirect
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from ninja import Query
|
from ninja import Query
|
||||||
from ninja import Router as APIRouter
|
from ninja import Router as APIRouter
|
||||||
from ninja import Schema
|
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
from pydantic import PositiveInt
|
from pydantic import PositiveInt
|
||||||
|
|
||||||
from c3nav.api.auth import auth_permission_responses, auth_responses, validate_responses
|
from c3nav.api.auth import auth_permission_responses, auth_responses, validate_responses
|
||||||
from c3nav.api.exceptions import API404, APIPermissionDenied, APIRequestValidationFailed
|
from c3nav.api.exceptions import API404, APIPermissionDenied, APIRequestValidationFailed
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.mapdata.api.base import api_etag, api_stats, can_access_geometry
|
from c3nav.mapdata.api.base import api_etag, api_stats, can_access_geometry
|
||||||
from c3nav.mapdata.models import Source
|
from c3nav.mapdata.models import Source
|
||||||
|
@ -39,7 +39,7 @@ def bounds(request):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LocationEndpointParameters(Schema):
|
class LocationEndpointParameters(BaseSchema):
|
||||||
searchable: bool = APIField(
|
searchable: bool = APIField(
|
||||||
False,
|
False,
|
||||||
title='only list searchable locations',
|
title='only list searchable locations',
|
||||||
|
@ -133,7 +133,7 @@ def _location_geometry(request, location):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ShowRedirects(Schema):
|
class ShowRedirects(BaseSchema):
|
||||||
show_redirects: bool = APIField(
|
show_redirects: bool = APIField(
|
||||||
False,
|
False,
|
||||||
name="show redirects",
|
name="show redirects",
|
||||||
|
@ -268,7 +268,7 @@ def get_position_by_id(request, position_id: AnyPositionID):
|
||||||
return location.serialize_position()
|
return location.serialize_position()
|
||||||
|
|
||||||
|
|
||||||
class UpdatePositionSchema(Schema):
|
class UpdatePositionSchema(BaseSchema):
|
||||||
coordinates_id: Union[
|
coordinates_id: Union[
|
||||||
Annotated[CustomLocationID, APIField(title="set coordinates")],
|
Annotated[CustomLocationID, APIField(title="set coordinates")],
|
||||||
Annotated[None, APIField(title="unset coordinates")],
|
Annotated[None, APIField(title="unset coordinates")],
|
||||||
|
|
|
@ -4,10 +4,10 @@ from urllib.parse import urlparse
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from ninja import Field as APIField
|
from ninja import Field as APIField
|
||||||
from ninja import Router as APIRouter
|
from ninja import Router as APIRouter
|
||||||
from ninja import Schema
|
|
||||||
from pydantic import PositiveInt
|
from pydantic import PositiveInt
|
||||||
|
|
||||||
from c3nav.api.auth import auth_responses
|
from c3nav.api.auth import auth_responses
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.mapdata.models import MapUpdate
|
from c3nav.mapdata.models import MapUpdate
|
||||||
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
||||||
|
@ -17,7 +17,7 @@ from c3nav.mapdata.views import set_tile_access_cookie
|
||||||
updates_api_router = APIRouter(tags=["updates"])
|
updates_api_router = APIRouter(tags=["updates"])
|
||||||
|
|
||||||
|
|
||||||
class UserDataSchema(Schema):
|
class UserDataSchema(BaseSchema):
|
||||||
logged_in: bool = APIField(
|
logged_in: bool = APIField(
|
||||||
title="logged in",
|
title="logged in",
|
||||||
description="whether a user is logged in",
|
description="whether a user is logged in",
|
||||||
|
@ -52,7 +52,7 @@ class UserDataSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FetchUpdatesResponseSchema(Schema):
|
class FetchUpdatesResponseSchema(BaseSchema):
|
||||||
last_site_update: Optional[PositiveInt] = APIField(
|
last_site_update: Optional[PositiveInt] = APIField(
|
||||||
title="ID of the last site update",
|
title="ID of the last site update",
|
||||||
description="If this ID increments, it means a major code change may have occurred. "
|
description="If this ID increments, it means a major code change may have occurred. "
|
||||||
|
|
|
@ -74,7 +74,7 @@ class TitledMixin(SerializableMixin, models.Model):
|
||||||
result = super()._serialize(detailed=detailed, **kwargs)
|
result = super()._serialize(detailed=detailed, **kwargs)
|
||||||
if detailed:
|
if detailed:
|
||||||
result['titles'] = self.titles
|
result['titles'] = self.titles
|
||||||
result['title'] = str(self.title)
|
result['title'] = self.title
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -88,7 +88,7 @@ class LocationSlug(SerializableMixin, models.Model):
|
||||||
|
|
||||||
def details_display(self, **kwargs):
|
def details_display(self, **kwargs):
|
||||||
result = super().details_display(**kwargs)
|
result = super().details_display(**kwargs)
|
||||||
result['display'].insert(2, (_('Slug'), str(self.get_slug())))
|
result['display'].insert(2, (_('Slug'), self.get_slug()))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -120,7 +120,7 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
|
||||||
|
|
||||||
def _serialize(self, search=False, **kwargs):
|
def _serialize(self, search=False, **kwargs):
|
||||||
result = super()._serialize(**kwargs)
|
result = super()._serialize(**kwargs)
|
||||||
result['subtitle'] = str(self.subtitle)
|
result['subtitle'] = self.subtitle
|
||||||
result['icon'] = self.get_icon()
|
result['icon'] = self.get_icon()
|
||||||
result['can_search'] = self.can_search
|
result['can_search'] = self.can_search
|
||||||
result['can_describe'] = self.can_search
|
result['can_describe'] = self.can_search
|
||||||
|
@ -202,7 +202,7 @@ class SpecificLocation(Location, models.Model):
|
||||||
result['label_settings'] = label_settings.serialize(detailed=False)
|
result['label_settings'] = label_settings.serialize(detailed=False)
|
||||||
if self.label_overrides:
|
if self.label_overrides:
|
||||||
# todo: what if only one language is set?
|
# todo: what if only one language is set?
|
||||||
result['label_override'] = str(self.label_override)
|
result['label_override'] = self.label_override
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_label_settings(self):
|
def get_label_settings(self):
|
||||||
|
@ -300,9 +300,9 @@ class LocationGroupCategory(SerializableMixin, models.Model):
|
||||||
result['titles'] = self.titles
|
result['titles'] = self.titles
|
||||||
result['titles_plural'] = self.titles_plural
|
result['titles_plural'] = self.titles_plural
|
||||||
result['help_texts'] = self.help_texts
|
result['help_texts'] = self.help_texts
|
||||||
result['title'] = str(self.title)
|
result['title'] = self.title
|
||||||
result['title_plural'] = str(self.title_plural)
|
result['title_plural'] = self.title_plural
|
||||||
result['help_text'] = str(self.help_text)
|
result['help_text'] = self.help_text
|
||||||
result['allow_levels'] = self.allow_levels
|
result['allow_levels'] = self.allow_levels
|
||||||
result['allow_spaces'] = self.allow_spaces
|
result['allow_spaces'] = self.allow_spaces
|
||||||
result['allow_areas'] = self.allow_areas
|
result['allow_areas'] = self.allow_areas
|
||||||
|
|
|
@ -2,10 +2,10 @@ from typing import Literal, Optional, Type
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Model, QuerySet
|
from django.db.models import Model, QuerySet
|
||||||
from ninja import Schema
|
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
|
|
||||||
from c3nav.api.exceptions import APIRequestValidationFailed
|
from c3nav.api.exceptions import APIRequestValidationFailed
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space
|
from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ def assert_valid_value(request, model: Type[Model], key: str, values: set):
|
||||||
raise APIRequestValidationFailed("Unknown %s: %r" % (model.__name__, remainder))
|
raise APIRequestValidationFailed("Unknown %s: %r" % (model.__name__, remainder))
|
||||||
|
|
||||||
|
|
||||||
class FilterSchema(Schema):
|
class FilterSchema(BaseSchema):
|
||||||
def filter_qs(self, qs: QuerySet) -> QuerySet:
|
def filter_qs(self, qs: QuerySet) -> QuerySet:
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import re
|
import re
|
||||||
from typing import Annotated, Any, Optional, Union
|
from typing import Annotated, Optional, Union
|
||||||
|
|
||||||
from ninja import Schema
|
from ninja import Schema
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
from pydantic import PositiveInt, model_validator
|
from pydantic import PositiveInt
|
||||||
from pydantic.functional_validators import ModelWrapValidatorHandler
|
|
||||||
from pydantic_core.core_schema import ValidationInfo
|
|
||||||
|
|
||||||
from c3nav.api.schema import LineStringSchema, PointSchema, PolygonSchema
|
from c3nav.api.schema import LineStringSchema, PointSchema, PolygonSchema, BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,24 +34,14 @@ BoundsSchema = tuple[
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SerializableSchema(Schema):
|
class DjangoModelSchema(BaseSchema):
|
||||||
@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 hasattr(values, 'serialize') and callable(values.serialize):
|
|
||||||
values = values.serialize()
|
|
||||||
return handler(values)
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoModelSchema(SerializableSchema):
|
|
||||||
id: PositiveInt = APIField(
|
id: PositiveInt = APIField(
|
||||||
title="ID",
|
title="ID",
|
||||||
example=1,
|
example=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocationSlugSchema(Schema):
|
class LocationSlugSchema(BaseSchema):
|
||||||
slug: NonEmptyStr = APIField(
|
slug: NonEmptyStr = APIField(
|
||||||
title="location slug",
|
title="location slug",
|
||||||
description="a slug is a unique way to refer to a location. while locations have a shared ID space, slugs"
|
description="a slug is a unique way to refer to a location. while locations have a shared ID space, slugs"
|
||||||
|
@ -65,7 +53,7 @@ class LocationSlugSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithAccessRestrictionSchema(Schema):
|
class WithAccessRestrictionSchema(BaseSchema):
|
||||||
access_restriction: Union[
|
access_restriction: Union[
|
||||||
Annotated[PositiveInt, APIField(title="access restriction ID")],
|
Annotated[PositiveInt, APIField(title="access restriction ID")],
|
||||||
Annotated[None, APIField(title="null", description="no access restriction")],
|
Annotated[None, APIField(title="null", description="no access restriction")],
|
||||||
|
@ -76,7 +64,7 @@ class WithAccessRestrictionSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TitledSchema(Schema):
|
class TitledSchema(BaseSchema):
|
||||||
titles: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
titles: dict[NonEmptyStr, NonEmptyStr] = APIField(
|
||||||
title="title (all languages)",
|
title="title (all languages)",
|
||||||
description="title in all available languages. property names are the ISO-language code. "
|
description="title in all available languages. property names are the ISO-language code. "
|
||||||
|
@ -213,7 +201,7 @@ class SpecificLocationSchema(LocationSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithPolygonGeometrySchema(Schema):
|
class WithPolygonGeometrySchema(BaseSchema):
|
||||||
geometry: Union[
|
geometry: Union[
|
||||||
PolygonSchema,
|
PolygonSchema,
|
||||||
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
||||||
|
@ -224,7 +212,7 @@ class WithPolygonGeometrySchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithLineStringGeometrySchema(Schema):
|
class WithLineStringGeometrySchema(BaseSchema):
|
||||||
geometry: Union[
|
geometry: Union[
|
||||||
LineStringSchema,
|
LineStringSchema,
|
||||||
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
||||||
|
@ -235,7 +223,7 @@ class WithLineStringGeometrySchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithPointGeometrySchema(Schema):
|
class WithPointGeometrySchema(BaseSchema):
|
||||||
geometry: Union[
|
geometry: Union[
|
||||||
PointSchema,
|
PointSchema,
|
||||||
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
Annotated[None, APIField(title="null", description="geometry not available of excluded from endpoint")]
|
||||||
|
@ -246,7 +234,7 @@ class WithPointGeometrySchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithLevelSchema(SerializableSchema):
|
class WithLevelSchema(BaseSchema):
|
||||||
level: PositiveInt = APIField(
|
level: PositiveInt = APIField(
|
||||||
title="level",
|
title="level",
|
||||||
description="level id this object belongs to.",
|
description="level id this object belongs to.",
|
||||||
|
@ -254,7 +242,7 @@ class WithLevelSchema(SerializableSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WithSpaceSchema(SerializableSchema):
|
class WithSpaceSchema(BaseSchema):
|
||||||
space: PositiveInt = APIField(
|
space: PositiveInt = APIField(
|
||||||
title="space",
|
title="space",
|
||||||
description="space id this object belongs to.",
|
description="space id this object belongs to.",
|
||||||
|
@ -262,7 +250,7 @@ class WithSpaceSchema(SerializableSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SimpleGeometryPointSchema(Schema):
|
class SimpleGeometryPointSchema(BaseSchema):
|
||||||
point: tuple[
|
point: tuple[
|
||||||
Annotated[PositiveInt, APIField(title="level ID")],
|
Annotated[PositiveInt, APIField(title="level ID")],
|
||||||
Annotated[float, APIField(title="x coordinate")],
|
Annotated[float, APIField(title="x coordinate")],
|
||||||
|
@ -281,7 +269,7 @@ class SimpleGeometryPointAndBoundsSchema(SimpleGeometryPointSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SimpleGeometryLocationsSchema(Schema):
|
class SimpleGeometryLocationsSchema(BaseSchema):
|
||||||
locations: list[PositiveInt] = APIField( # todo: this should be a set… but json serialization?
|
locations: list[PositiveInt] = APIField( # todo: this should be a set… but json serialization?
|
||||||
description="IDs of all locations that belong to this grouo",
|
description="IDs of all locations that belong to this grouo",
|
||||||
example=(1, 2, 3),
|
example=(1, 2, 3),
|
||||||
|
|
|
@ -5,10 +5,10 @@ from pydantic import Discriminator
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt
|
from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt
|
||||||
|
|
||||||
from c3nav.api.schema import GeometrySchema, PointSchema
|
from c3nav.api.schema import GeometrySchema, PointSchema, BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.mapdata.schemas.model_base import (AnyLocationID, AnyPositionID, CustomLocationID, DjangoModelSchema,
|
from c3nav.mapdata.schemas.model_base import (AnyLocationID, AnyPositionID, CustomLocationID, DjangoModelSchema,
|
||||||
LabelSettingsSchema, LocationSchema, PositionID, SerializableSchema,
|
LabelSettingsSchema, LocationSchema, PositionID,
|
||||||
SimpleGeometryLocationsSchema, SimpleGeometryPointAndBoundsSchema,
|
SimpleGeometryLocationsSchema, SimpleGeometryPointAndBoundsSchema,
|
||||||
SimpleGeometryPointSchema, SpecificLocationSchema, TitledSchema,
|
SimpleGeometryPointSchema, SpecificLocationSchema, TitledSchema,
|
||||||
WithAccessRestrictionSchema, WithLevelSchema,
|
WithAccessRestrictionSchema, WithLevelSchema,
|
||||||
|
@ -357,7 +357,7 @@ class AccessRestrictionGroupSchema(WithAccessRestrictionSchema, DjangoModelSchem
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CustomLocationSchema(SerializableSchema):
|
class CustomLocationSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A custom location represents coordinates that have been put in or calculated.
|
A custom location represents coordinates that have been put in or calculated.
|
||||||
|
|
||||||
|
@ -439,7 +439,7 @@ class CustomLocationSchema(SerializableSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TrackablePositionSchema(Schema):
|
class TrackablePositionSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A trackable position. Its position can be set or reset.
|
A trackable position. Its position can be set or reset.
|
||||||
"""
|
"""
|
||||||
|
@ -468,7 +468,7 @@ class TrackablePositionSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocationTypeSchema(Schema):
|
class LocationTypeSchema(BaseSchema):
|
||||||
locationtype: str = APIField(title="location type",
|
locationtype: str = APIField(title="location type",
|
||||||
description="indicates what kind of location is included. "
|
description="indicates what kind of location is included. "
|
||||||
"different location types have different fields.")
|
"different location types have different fields.")
|
||||||
|
@ -544,7 +544,7 @@ class TrackablePositionLocationSchema(TrackablePositionSchema, LocationTypeSchem
|
||||||
locationtype: Literal["position"] = LocationTypeAPIField()
|
locationtype: Literal["position"] = LocationTypeAPIField()
|
||||||
|
|
||||||
|
|
||||||
class SlimLocationMixin(Schema):
|
class SlimLocationMixin(BaseSchema):
|
||||||
level: ClassVar[None]
|
level: ClassVar[None]
|
||||||
space: ClassVar[None]
|
space: ClassVar[None]
|
||||||
titles: ClassVar[None]
|
titles: ClassVar[None]
|
||||||
|
@ -662,7 +662,7 @@ all_location_definitions = listable_location_definitions + "\n" + schema_definit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DisplayLink(Schema):
|
class DisplayLink(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A link for the location display
|
A link for the location display
|
||||||
"""
|
"""
|
||||||
|
@ -672,7 +672,7 @@ class DisplayLink(Schema):
|
||||||
can_search: bool
|
can_search: bool
|
||||||
|
|
||||||
|
|
||||||
class LocationDisplay(SerializableSchema):
|
class LocationDisplay(BaseSchema):
|
||||||
id: AnyLocationID = APIField(
|
id: AnyLocationID = APIField(
|
||||||
description="a numeric ID for a map location or a string ID for generated locations",
|
description="a numeric ID for a map location or a string ID for generated locations",
|
||||||
example=1,
|
example=1,
|
||||||
|
@ -749,7 +749,7 @@ class LocationDisplay(SerializableSchema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PositionStatusSchema(Schema):
|
class PositionStatusSchema(BaseSchema):
|
||||||
id: AnyPositionID = APIField(
|
id: AnyPositionID = APIField(
|
||||||
description="the ID of the dynamic position that has been queries",
|
description="the ID of the dynamic position that has been queries",
|
||||||
)
|
)
|
||||||
|
@ -758,7 +758,7 @@ class PositionStatusSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PositionAvailabilitySchema(Schema):
|
class PositionAvailabilitySchema(BaseSchema):
|
||||||
available: str
|
available: str
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
from typing import Annotated, Union
|
from typing import Annotated, Union
|
||||||
|
|
||||||
from ninja import Schema
|
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
from pydantic import PositiveInt
|
from pydantic import PositiveInt
|
||||||
|
|
||||||
from c3nav.api.schema import GeometrySchema
|
from c3nav.api.schema import GeometrySchema, BaseSchema
|
||||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, BoundsSchema
|
from c3nav.mapdata.schemas.model_base import AnyLocationID, BoundsSchema
|
||||||
|
|
||||||
|
|
||||||
class WithBoundsSchema(Schema):
|
class WithBoundsSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
Describing a bounding box
|
Describing a bounding box
|
||||||
"""
|
"""
|
||||||
|
@ -18,7 +17,7 @@ class WithBoundsSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocationGeometry(Schema):
|
class LocationGeometry(BaseSchema):
|
||||||
id: AnyLocationID = APIField(
|
id: AnyLocationID = APIField(
|
||||||
description="ID of the location that the geometry is being queried for",
|
description="ID of the location that the geometry is being queried for",
|
||||||
)
|
)
|
||||||
|
|
|
@ -416,7 +416,7 @@ class CustomLocation:
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def grid_square(self):
|
def grid_square(self):
|
||||||
return grid.get_square_for_point(self.x, self.y) or ''
|
return grid.get_square_for_point(self.x, self.y)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def title_subtitle(self):
|
def title_subtitle(self):
|
||||||
|
|
|
@ -17,13 +17,13 @@ def get_user_data(request):
|
||||||
}
|
}
|
||||||
if permissions:
|
if permissions:
|
||||||
result.update({
|
result.update({
|
||||||
'title': str(_('not logged in')),
|
'title': _('not logged in'),
|
||||||
'subtitle': ngettext_lazy('%d area unlocked', '%d areas unlocked', len(permissions)) % len(permissions),
|
'subtitle': ngettext_lazy('%d area unlocked', '%d areas unlocked', len(permissions)) % len(permissions),
|
||||||
'permissions': tuple(permissions),
|
'permissions': tuple(permissions),
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
result.update({
|
result.update({
|
||||||
'title': str(_('Login')),
|
'title': _('Login'),
|
||||||
'subtitle': None,
|
'subtitle': None,
|
||||||
'permissions': (),
|
'permissions': (),
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,7 @@ from pydantic import PositiveInt, field_validator
|
||||||
|
|
||||||
from c3nav.api.auth import APIKeyAuth, auth_permission_responses, auth_responses, validate_responses
|
from c3nav.api.auth import APIKeyAuth, auth_permission_responses, auth_responses, validate_responses
|
||||||
from c3nav.api.exceptions import API404, APIConflict, APIRequestValidationFailed
|
from c3nav.api.exceptions import API404, APIConflict, APIRequestValidationFailed
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.mesh.dataformats import BoardType, ChipType, FirmwareImage
|
from c3nav.mesh.dataformats import BoardType, ChipType, FirmwareImage
|
||||||
from c3nav.mesh.messages import MeshMessageType
|
from c3nav.mesh.messages import MeshMessageType
|
||||||
from c3nav.mesh.models import FirmwareBuild, FirmwareVersion, NodeMessage
|
from c3nav.mesh.models import FirmwareBuild, FirmwareVersion, NodeMessage
|
||||||
|
@ -18,7 +19,7 @@ from c3nav.mesh.models import FirmwareBuild, FirmwareVersion, NodeMessage
|
||||||
mesh_api_router = APIRouter(tags=["mesh"], auth=APIKeyAuth(permissions={"mesh_control"}))
|
mesh_api_router = APIRouter(tags=["mesh"], auth=APIKeyAuth(permissions={"mesh_control"}))
|
||||||
|
|
||||||
|
|
||||||
class FirmwareBuildSchema(Schema):
|
class FirmwareBuildSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A build belonging to a firmware version.
|
A build belonging to a firmware version.
|
||||||
"""
|
"""
|
||||||
|
@ -53,7 +54,7 @@ class FirmwareBuildSchema(Schema):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FirmwareSchema(Schema):
|
class FirmwareSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A firmware version, usually with multiple build variants.
|
A firmware version, usually with multiple build variants.
|
||||||
"""
|
"""
|
||||||
|
@ -128,7 +129,7 @@ def firmware_project_description(request, firmware_id: int, variant: str):
|
||||||
raise API404("Firmware or firmware build not found")
|
raise API404("Firmware or firmware build not found")
|
||||||
|
|
||||||
|
|
||||||
class UploadFirmwareBuildSchema(Schema):
|
class UploadFirmwareBuildSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A firmware build to upload, with at least one build variant
|
A firmware build to upload, with at least one build variant
|
||||||
"""
|
"""
|
||||||
|
@ -138,7 +139,7 @@ class UploadFirmwareBuildSchema(Schema):
|
||||||
uploaded_filename: str = APIField(..., example="firmware.bin")
|
uploaded_filename: str = APIField(..., example="firmware.bin")
|
||||||
|
|
||||||
|
|
||||||
class UploadFirmwareSchema(Schema):
|
class UploadFirmwareSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
A firmware version to upload, with at least one build variant
|
A firmware version to upload, with at least one build variant
|
||||||
"""
|
"""
|
||||||
|
@ -205,14 +206,14 @@ def firmware_upload(request, firmware_data: UploadFirmwareSchema, binary_files:
|
||||||
NodeAddress = Annotated[str, APIField(pattern=r"^[a-z0-9]{2}(:[a-z0-9]{2}){5}$")]
|
NodeAddress = Annotated[str, APIField(pattern=r"^[a-z0-9]{2}(:[a-z0-9]{2}){5}$")]
|
||||||
|
|
||||||
|
|
||||||
class MessagesFilter(Schema):
|
class MessagesFilter(BaseSchema):
|
||||||
src_node: Optional[NodeAddress] = None
|
src_node: Optional[NodeAddress] = None
|
||||||
msg_type: Optional[MeshMessageType] = None
|
msg_type: Optional[MeshMessageType] = None
|
||||||
time_from: Optional[datetime] = None
|
time_from: Optional[datetime] = None
|
||||||
time_until: Optional[datetime] = None
|
time_until: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
class NodeMessageSchema(Schema):
|
class NodeMessageSchema(BaseSchema):
|
||||||
id: int
|
id: int
|
||||||
src_node: NodeAddress
|
src_node: NodeAddress
|
||||||
message_type: MeshMessageType
|
message_type: MeshMessageType
|
||||||
|
|
|
@ -3,9 +3,9 @@ from typing import Annotated, Union
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from ninja import Field as APIField
|
from ninja import Field as APIField
|
||||||
from ninja import Router as APIRouter
|
from ninja import Router as APIRouter
|
||||||
from ninja import Schema
|
|
||||||
|
|
||||||
from c3nav.api.auth import auth_responses
|
from c3nav.api.auth import auth_responses
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
from c3nav.mapdata.schemas.models import CustomLocationSchema
|
from c3nav.mapdata.schemas.models import CustomLocationSchema
|
||||||
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
from c3nav.mapdata.utils.cache.stats import increment_cache_key
|
||||||
|
@ -15,13 +15,13 @@ from c3nav.routing.schemas import BSSIDSchema, LocateRequestPeerSchema
|
||||||
positioning_api_router = APIRouter(tags=["positioning"])
|
positioning_api_router = APIRouter(tags=["positioning"])
|
||||||
|
|
||||||
|
|
||||||
class LocateRequestSchema(Schema):
|
class LocateRequestSchema(BaseSchema):
|
||||||
peers: list[LocateRequestPeerSchema] = APIField(
|
peers: list[LocateRequestPeerSchema] = APIField(
|
||||||
title="list of visible/measured location beacons",
|
title="list of visible/measured location beacons",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PositioningResult(Schema):
|
class PositioningResult(BaseSchema):
|
||||||
location: Union[
|
location: Union[
|
||||||
Annotated[CustomLocationSchema, APIField(title="location")],
|
Annotated[CustomLocationSchema, APIField(title="location")],
|
||||||
Annotated[None, APIField(title="null", description="position could not be determined")]
|
Annotated[None, APIField(title="null", description="position could not be determined")]
|
||||||
|
|
|
@ -11,6 +11,7 @@ from pydantic import PositiveInt
|
||||||
|
|
||||||
from c3nav.api.auth import APIKeyAuth, auth_responses, validate_responses
|
from c3nav.api.auth import APIKeyAuth, auth_responses, validate_responses
|
||||||
from c3nav.api.exceptions import APIRequestValidationFailed
|
from c3nav.api.exceptions import APIRequestValidationFailed
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
from c3nav.mapdata.api.base import api_stats_clean_location_value
|
from c3nav.mapdata.api.base import api_stats_clean_location_value
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
|
@ -53,7 +54,7 @@ class AltitudeWayTypeChoice(StrEnum):
|
||||||
AVOID = "avoid"
|
AVOID = "avoid"
|
||||||
|
|
||||||
|
|
||||||
class UpdateRouteOptionsSchema(Schema):
|
class UpdateRouteOptionsSchema(BaseSchema):
|
||||||
# todo: default is wrong, this should be optional
|
# todo: default is wrong, this should be optional
|
||||||
mode: Union[
|
mode: Union[
|
||||||
Annotated[RouteMode, APIField(title="route mode", description="routing mode to use")],
|
Annotated[RouteMode, APIField(title="route mode", description="routing mode to use")],
|
||||||
|
@ -81,7 +82,7 @@ class UpdateRouteOptionsSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RouteOptionsSchema(Schema):
|
class RouteOptionsSchema(BaseSchema):
|
||||||
# todo: default is wrong, this should be optional
|
# todo: default is wrong, this should be optional
|
||||||
mode: RouteMode = APIField(name="routing mode")
|
mode: RouteMode = APIField(name="routing mode")
|
||||||
walk_speed: WalkSpeed = APIField(name="walk speed")
|
walk_speed: WalkSpeed = APIField(name="walk speed")
|
||||||
|
@ -96,7 +97,7 @@ class RouteOptionsSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RouteParametersSchema(Schema):
|
class RouteParametersSchema(BaseSchema):
|
||||||
origin: AnyLocationID
|
origin: AnyLocationID
|
||||||
destination: AnyLocationID
|
destination: AnyLocationID
|
||||||
options_override: Optional[UpdateRouteOptionsSchema] = APIField(
|
options_override: Optional[UpdateRouteOptionsSchema] = APIField(
|
||||||
|
@ -105,7 +106,7 @@ class RouteParametersSchema(Schema):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RouteItemSchema(Schema):
|
class RouteItemSchema(BaseSchema):
|
||||||
id: PositiveInt
|
id: PositiveInt
|
||||||
coordinates: Coordinates3D
|
coordinates: Coordinates3D
|
||||||
waytype: Union[
|
waytype: Union[
|
||||||
|
@ -132,7 +133,7 @@ class RouteItemSchema(Schema):
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|
||||||
class RouteSchema(Schema):
|
class RouteSchema(BaseSchema):
|
||||||
origin: dict # todo: improve this
|
origin: dict # todo: improve this
|
||||||
destination: dict # todo: improve this
|
destination: dict # todo: improve this
|
||||||
distance: float
|
distance: float
|
||||||
|
@ -144,7 +145,7 @@ class RouteSchema(Schema):
|
||||||
items: list[RouteItemSchema]
|
items: list[RouteItemSchema]
|
||||||
|
|
||||||
|
|
||||||
class RouteResponse(Schema):
|
class RouteResponse(BaseSchema):
|
||||||
request: RouteParametersSchema
|
request: RouteParametersSchema
|
||||||
options: RouteOptionsSchema
|
options: RouteOptionsSchema
|
||||||
report_issue_url: NonEmptyStr
|
report_issue_url: NonEmptyStr
|
||||||
|
@ -154,7 +155,7 @@ class RouteResponse(Schema):
|
||||||
title = "route found"
|
title = "route found"
|
||||||
|
|
||||||
|
|
||||||
class NoRouteResponse(Schema):
|
class NoRouteResponse(BaseSchema):
|
||||||
request: RouteParametersSchema
|
request: RouteParametersSchema
|
||||||
options: RouteOptionsSchema
|
options: RouteOptionsSchema
|
||||||
error: NonEmptyStr = APIField(
|
error: NonEmptyStr = APIField(
|
||||||
|
@ -197,19 +198,19 @@ def get_route(request, parameters: RouteParametersSchema):
|
||||||
return NoRouteResponse(
|
return NoRouteResponse(
|
||||||
request=parameters,
|
request=parameters,
|
||||||
options=_new_serialize_route_options(options),
|
options=_new_serialize_route_options(options),
|
||||||
error=str(_('Not yet routable, try again shortly.')),
|
error=_('Not yet routable, try again shortly.'),
|
||||||
)
|
)
|
||||||
except LocationUnreachable:
|
except LocationUnreachable:
|
||||||
return NoRouteResponse(
|
return NoRouteResponse(
|
||||||
request=parameters,
|
request=parameters,
|
||||||
options=_new_serialize_route_options(options),
|
options=_new_serialize_route_options(options),
|
||||||
error=str(_('Unreachable location.'))
|
error=_('Unreachable location.')
|
||||||
)
|
)
|
||||||
except NoRouteFound:
|
except NoRouteFound:
|
||||||
return NoRouteResponse(
|
return NoRouteResponse(
|
||||||
request=parameters,
|
request=parameters,
|
||||||
options=_new_serialize_route_options(options),
|
options=_new_serialize_route_options(options),
|
||||||
error=str(_('No route found.'))
|
error=_('No route found.')
|
||||||
)
|
)
|
||||||
|
|
||||||
origin_values = api_stats_clean_location_value(form.cleaned_data['origin'].pk)
|
origin_values = api_stats_clean_location_value(form.cleaned_data['origin'].pk)
|
||||||
|
@ -282,12 +283,12 @@ def set_route_options(request, new_options: UpdateRouteOptionsSchema):
|
||||||
return _new_serialize_route_options(options)
|
return _new_serialize_route_options(options)
|
||||||
|
|
||||||
|
|
||||||
class RouteOptionsFieldChoices(Schema):
|
class RouteOptionsFieldChoices(BaseSchema):
|
||||||
name: NonEmptyStr
|
name: NonEmptyStr
|
||||||
title: NonEmptyStr
|
title: NonEmptyStr
|
||||||
|
|
||||||
|
|
||||||
class RouteOptionsField(Schema):
|
class RouteOptionsField(BaseSchema):
|
||||||
name: NonEmptyStr
|
name: NonEmptyStr
|
||||||
type: NonEmptyStr
|
type: NonEmptyStr
|
||||||
label: NonEmptyStr
|
label: NonEmptyStr
|
||||||
|
|
|
@ -240,9 +240,7 @@ class RouteItem:
|
||||||
if self.new_level:
|
if self.new_level:
|
||||||
result['level'] = describe_location(self.level, locations)
|
result['level'] = describe_location(self.level, locations)
|
||||||
|
|
||||||
# convert all the string proxies to strings
|
result['descriptions'] = [(icon, instruction) for (icon, instruction) in self.descriptions]
|
||||||
# todo: better to let the api response serializer do this somehow?
|
|
||||||
result['descriptions'] = [(icon, str(instruction)) for (icon, instruction) in self.descriptions]
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
from typing import Annotated, Union
|
from typing import Annotated, Union
|
||||||
|
|
||||||
from ninja import Schema
|
|
||||||
from pydantic import Field as APIField
|
from pydantic import Field as APIField
|
||||||
from pydantic import NegativeInt, PositiveInt
|
from pydantic import NegativeInt, PositiveInt
|
||||||
|
|
||||||
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.api.utils import NonEmptyStr
|
from c3nav.api.utils import NonEmptyStr
|
||||||
|
|
||||||
BSSIDSchema = Annotated[str, APIField(pattern=r"^[a-z0-9]{2}(:[a-z0-9]{2}){5}$", title="BSSID")]
|
BSSIDSchema = Annotated[str, APIField(pattern=r"^[a-z0-9]{2}(:[a-z0-9]{2}){5}$", title="BSSID")]
|
||||||
|
|
||||||
|
|
||||||
class LocateRequestPeerSchema(Schema):
|
class LocateRequestPeerSchema(BaseSchema):
|
||||||
bssid: BSSIDSchema = APIField(
|
bssid: BSSIDSchema = APIField(
|
||||||
title="BSSID",
|
title="BSSID",
|
||||||
description="BSSID of the peer",
|
description="BSSID of the peer",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue