enjoy an awesome broken display viewpoint

This commit is contained in:
Laura Klünder 2023-11-23 21:11:31 +01:00
parent 389e9ee52b
commit 505a59091c
4 changed files with 170 additions and 12 deletions

View file

@ -1,6 +1,7 @@
from typing import Literal
from typing import Annotated, Literal, Union
from ninja import Schema
from pydantic import Discriminator
from pydantic import Field as APIField
from c3nav.api.utils import NonEmptyStr
@ -43,3 +44,13 @@ class PointSchema(Schema):
coordinates: tuple[float, float] = APIField(
example=[1, 2.5]
)
GeometrySchema = Annotated[
Union[
PolygonSchema,
LineStringSchema,
PointSchema,],
Discriminator("type"),
]

View file

@ -1,16 +1,27 @@
import json
from typing import Annotated, Union
from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder
from django.shortcuts import redirect
from ninja import Query
from ninja import Router as APIRouter
from ninja import Schema
from pydantic import Field as APIField
from pydantic import PositiveInt
from c3nav.api.newauth import auth_responses
from c3nav.api.exceptions import API404
from c3nav.api.newauth import auth_responses, validate_responses
from c3nav.mapdata.models import Source
from c3nav.mapdata.models.access import AccessPermission
from c3nav.mapdata.models.locations import LocationRedirect
from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter
from c3nav.mapdata.schemas.models import FullLocationSchema, SlimLocationSchema
from c3nav.mapdata.schemas.model_base import LocationID
from c3nav.mapdata.schemas.models import FullLocationSchema, LocationDisplay, SlimLocationSchema
from c3nav.mapdata.schemas.responses import BoundsSchema
from c3nav.mapdata.utils.locations import searchable_locations_for_request, visible_locations_for_request
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, searchable_locations_for_request,
visible_locations_for_request)
from c3nav.mapdata.utils.user import can_access_editor
map_api_router = APIRouter(tags=["map"])
@ -61,13 +72,97 @@ def _location_list(request, detailed: bool, filters: LocationListFilters):
return result
@map_api_router.get('/locations/', response={200: list[SlimLocationSchema], **auth_responses},
@map_api_router.get('/locations/',
response={200: list[SlimLocationSchema], **validate_responses, **auth_responses},
summary="Get locations (with most important attributes)")
def location_list(request, filters: Query[LocationListFilters]):
return _location_list(request, detailed=False, filters=filters)
@map_api_router.get('/locations/full/', response={200: list[FullLocationSchema], **auth_responses},
@map_api_router.get('/locations/full/',
response={200: list[FullLocationSchema], **validate_responses, **auth_responses},
summary="Get locations (with all attributes)")
def location_list_full(request, filters: Query[LocationListFilters]):
return _location_list(request, detailed=True, filters=filters)
def _location_retrieve(request, location, detailed: bool, geometry: bool, show_redirects: bool):
# todo: cache, visibility, etc…
if location is None:
raise API404
if isinstance(location, LocationRedirect):
if not show_redirects:
return redirect('../' + str(location.target.slug)) # todo: use reverse, make pk and slug both work
return location.serialize(
detailed=detailed,
geometry=geometry and can_access_geometry(request),
simple_geometry=True
)
def _location_display(request, location):
# todo: cache, visibility, etc…
if location is None:
raise API404
if isinstance(location, LocationRedirect):
return redirect('../' + str(location.target.slug) + '/details/') # todo: use reverse, make pk+slug work
result = location.details_display(
detailed_geometry=can_access_geometry(request),
editor_url=can_access_editor(request)
)
from pprint import pprint
pprint(result)
return json.loads(json.dumps(result, cls=DjangoJSONEncoder)) # todo: wtf?? well we need to get rid of lazy strings
class ShowRedirects(Schema):
show_redirects: bool = APIField(
False,
name="show redirects",
description="whether to show redirects instead of sending a redirect response",
)
@map_api_router.get('/locations/{location_id}/',
response={200: SlimLocationSchema, **API404.dict(), **validate_responses, **auth_responses},
summary="Get location by ID (with most important attributes)",
description="a numeric ID for a map location or a string ID for generated locations can be used")
def location_by_id(request, location_id: LocationID, filters: Query[RemoveGeometryFilter],
redirects: Query[ShowRedirects]):
return _location_retrieve(
request,
get_location_by_id_for_request(location_id, request),
detailed=False, geometry=filters.geometry, show_redirects=redirects.show_redirects,
)
@map_api_router.get('/locations/{location_id}/full/',
response={200: FullLocationSchema, **API404.dict(), **validate_responses, **auth_responses},
summary="Get location by ID (with all attributes)",
description="a numeric ID for a map location or a string ID for generated locations can be used")
def location_by_id_full(request, location_id: LocationID, filters: Query[RemoveGeometryFilter],
redirects: Query[ShowRedirects]):
return _location_retrieve(
request,
get_location_by_id_for_request(location_id, request),
detailed=True, geometry=filters.geometry, show_redirects=redirects.show_redirects,
)
@map_api_router.get('/locations/{location_id}/display/',
response={200: LocationDisplay, **API404.dict(), **auth_responses},
summary="Get location display data by ID",
description="a numeric ID for a map location or a string ID for generated locations can be used")
def location_by_id_display(request, location_id: LocationID):
return _location_display(
request,
get_location_by_id_for_request(location_id, request),
)

View file

@ -1,4 +1,4 @@
from typing import Annotated, Any, ClassVar, Optional
from typing import Annotated, Any, ClassVar, Optional, Union
from ninja import Schema
from pydantic import Field as APIField
@ -189,3 +189,14 @@ class SimpleGeometryLocationsSchema(Schema):
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")],
]

View file

@ -7,12 +7,14 @@ from pydantic import GetJsonSchemaHandler, NonNegativeFloat, PositiveFloat, Posi
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import CoreSchema
from c3nav.api.schema import GeometrySchema
from c3nav.api.utils import NonEmptyStr
from c3nav.mapdata.schemas.model_base import (DjangoModelSchema, LabelSettingsSchema, LocationSchema,
LocationSlugSchema, SimpleGeometryBoundsAndPointSchema,
SimpleGeometryBoundsSchema, SimpleGeometryLocationsSchema,
SpecificLocationSchema, TitledSchema, WithAccessRestrictionSchema,
WithLevelSchema, WithLineStringGeometrySchema, WithPointGeometrySchema,
from c3nav.mapdata.schemas.model_base import (DjangoModelSchema, LabelSettingsSchema, LocationID, LocationSchema,
LocationSlugSchema, SerializableSchema,
SimpleGeometryBoundsAndPointSchema, SimpleGeometryBoundsSchema,
SimpleGeometryLocationsSchema, SpecificLocationSchema, TitledSchema,
WithAccessRestrictionSchema, WithLevelSchema,
WithLineStringGeometrySchema, WithPointGeometrySchema,
WithPolygonGeometrySchema, WithSpaceSchema)
@ -439,3 +441,42 @@ SlimLocationSchema = Annotated[
]
class DisplayLink(Schema):
"""
A link for the location display
"""
id: PositiveInt
slug: NonEmptyStr
title: NonEmptyStr
can_search: bool
class LocationDisplay(SerializableSchema):
id: LocationID = APIField(
description="a numeric ID for a map location or a string ID for generated locations",
)
level: Optional[PositiveInt] = APIField(
None,
description="level ID, if applicable"
)
space: Optional[PositiveInt] = APIField(
None,
description="space ID, if applicable"
)
display: list[
tuple[
Annotated[NonEmptyStr, APIField(name="field title")],
Annotated[Union[
Annotated[str, APIField(name="a simple string value")],
Annotated[DisplayLink, APIField(namen="a link value")],
Annotated[list[DisplayLink], APIField(name="a list of link values")],
Annotated[Literal[None], APIField(name="no value")]
], APIField(name="field value")]
]
] = APIField(description="a list of human-readable display values")
geometry: Optional[GeometrySchema] = 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",
)