new serializer for all locations

This commit is contained in:
Laura Klünder 2024-12-03 18:42:33 +01:00
parent 38b4fbe1f0
commit b47e97bb81
9 changed files with 87 additions and 45 deletions

View file

@ -27,6 +27,8 @@ def make_serializable(values: Any):
for key, val in values.items()
}
if isinstance(values, (list, tuple, set, frozenset)):
if values and isinstance(next(iter(values)), Model):
return type(values)(val.pk for val in values)
return type(values)(make_serializable(val) for val in values)
if isinstance(values, Promise):
return str(values)

View file

@ -77,17 +77,17 @@ class LocationListFilters(BySearchableFilter, RemoveGeometryFilter):
pass
def _location_list(request, detailed: bool, filters: LocationListFilters):
def _location_list(request, filters: LocationListFilters):
if filters.searchable:
locations = searchable_locations_for_request(request)
else:
locations = visible_locations_for_request(request).values()
result = [obj.serialize(detailed=detailed, search=filters.searchable,
geometry=filters.geometry and can_access_geometry(request, obj),
simple_geometry=True)
for obj in locations]
return result
for location in locations:
if not filters.geometry or not can_access_geometry(request, location):
location._hide_geometry = True
return locations
@map_api_router.get('/locations/', summary="list locations (slim)",
@ -96,7 +96,7 @@ def _location_list(request, detailed: bool, filters: LocationListFilters):
response={200: list[SlimListableLocationSchema], **validate_responses, **auth_responses})
@api_etag(base_mapdata=True)
def location_list(request, filters: Query[LocationListFilters]):
return _location_list(request, detailed=False, filters=filters)
return _location_list(request, filters=filters)
@map_api_router.get('/locations/full/', summary="list locations (full)",
@ -105,7 +105,7 @@ def location_list(request, filters: Query[LocationListFilters]):
response={200: list[FullListableLocationSchema], **validate_responses, **auth_responses})
@api_etag(base_mapdata=True)
def location_list_full(request, filters: Query[LocationListFilters]):
return _location_list(request, detailed=True, filters=filters)
return _location_list(request, filters=filters)
def _location_retrieve(request, location, detailed: bool, geometry: bool, show_redirects: bool):
@ -120,11 +120,10 @@ def _location_retrieve(request, location, detailed: bool, geometry: bool, show_r
request._target_etag = None
request._target_cache_key = None
return location.serialize(
detailed=detailed,
geometry=geometry and can_access_geometry(request, location),
simple_geometry=True
)
if not geometry or not can_access_geometry(request, location):
location._hide_geometry = True
return location
def _location_display(request, location):

View file

@ -47,9 +47,13 @@ def mapdata_list_endpoint(request,
# order_by
qs = qs.order_by(*order_by)
# todo: can access geometry… using defer?
result = list(qs)
return qs
for obj in result:
if can_access_geometry(request, obj):
obj._hide_geometry = True
return result
def mapdata_retrieve_endpoint(request, model: Type[Model], **lookups):

View file

@ -309,8 +309,10 @@ class LineObstacle(SpaceGeometryMixin, models.Model):
class POI(SpaceGeometryMixin, SpecificLocation, models.Model):
"""
An point of interest
A point of interest
"""
new_serialize = True
geometry = GeometryField('point')
class Meta:

View file

@ -385,6 +385,8 @@ class LocationGroup(Location, models.Model):
self.orig_category_id = self.category_id
self.orig_color = self.color
locations = []
def details_display(self, editor_url=True, **kwargs):
result = super().details_display(**kwargs)
result['display'].insert(3, (_('Category'), self.category.title))
@ -522,6 +524,8 @@ class CustomLocationProxyMixin:
class DynamicLocation(CustomLocationProxyMixin, SpecificLocation, models.Model):
new_serialize = True
position_secret = models.CharField(_('position secret'), max_length=32, null=True, blank=True)
class Meta:

View file

@ -176,8 +176,7 @@ class RemoveGeometryFilter(FilterSchema):
# todo: validated true as invalid if not avaiilable for this model
def filter_qs(self, request, qs: QuerySet) -> QuerySet:
if ((qs.model in (Building, Space, Door) and not request.user_permissions.can_access_base_mapdata)
or not self.geometry):
if not self.geometry:
qs = qs.defer('geometry')
return super().filter_qs(request, qs)

View file

@ -1,6 +1,6 @@
import math
import re
from typing import Annotated, Optional, Union
from typing import Annotated, Optional, Union, Literal
from pydantic import Field as APIField
from pydantic import PositiveInt
@ -119,6 +119,12 @@ class LocationSchema(WithAccessRestrictionSchema, TitledSchema, LocationSlugSche
example="more search terms",
)
@classmethod
def get_overrides(cls, value) -> dict:
return {
"locationtype": value._meta.model_name,
}
class LabelSettingsSchema(DjangoModelSchema): # todo: add titles back in here
"""
@ -145,6 +151,7 @@ class LabelSettingsSchema(DjangoModelSchema): # todo: add titles back in here
class SpecificLocationSchema(LocationSchema):
grid_square: Union[
Annotated[NonEmptyStr, APIField(title="grid square", description="grid square(s) that this location is in")],
Annotated[Literal[""], APIField(title="grid square", description="outside of grid")],
Annotated[None, APIField(title="null", description="no grid defined or outside of grid")],
] = APIField(
default=None,
@ -235,7 +242,10 @@ class WithGeometrySchema(BaseSchema):
minx, miny, maxx, maxy = value.geometry.bounds
return {
**super().get_overrides(value),
"geometry": format_geojson(smart_mapping(value.geometry), rounded=False),
"geometry": (
format_geojson(smart_mapping(value.geometry), rounded=False)
if not getattr(value, '_hide_geometry', False) else None
),
"point": (value.level_id,) + tuple(round(i, 2) for i in value.point.coords[0]),
"bounds": ((int(math.floor(minx)), int(math.floor(miny))),
(int(math.ceil(maxx)), int(math.ceil(maxy))))

View file

@ -1,6 +1,7 @@
from typing import Annotated, ClassVar, Literal, Optional, Union
from typing import Annotated, ClassVar, Literal, Optional, Union, Any
from pydantic import Discriminator
from django.db.models import Model
from pydantic import Discriminator, Tag
from pydantic import Field as APIField
from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt
@ -415,6 +416,7 @@ class CustomLocationSchema(BaseSchema):
)
grid_square: Union[
Annotated[NonEmptyStr, APIField(title="grid square", description="grid square(s) that this location is in")],
Annotated[Literal[""], APIField(title="grid square", description="outside of grid")],
Annotated[None, APIField(title="null", description="no grid defined or outside of grid")],
] = APIField(
default=None,
@ -569,6 +571,8 @@ class SlimLocationMixin(BaseSchema):
can_describe: ClassVar[None]
groups: ClassVar[None]
groups_by_category: ClassVar[None]
geometry: ClassVar[None]
point: ClassVar[None]
class SlimLevelLocationSchema(SlimLocationMixin, FullLevelLocationSchema):
@ -628,46 +632,62 @@ class SlimDynamicLocationLocationSchema(SlimLocationMixin, FullDynamicLocationLo
pass
def get_locationtype(v: Any):
if isinstance(v, Model):
return v._meta.model_name
return v["locationtype"]
FullListableLocationSchema = Annotated[
Union[
FullLevelLocationSchema,
FullSpaceLocationSchema,
FullAreaLocationSchema,
FullPOILocationSchema,
FullLocationGroupLocationSchema,
FullDynamicLocationLocationSchema,
Annotated[FullLevelLocationSchema, Tag("level")],
Annotated[FullSpaceLocationSchema, Tag("space")],
Annotated[FullAreaLocationSchema, Tag("area")],
Annotated[FullPOILocationSchema, Tag("poi")],
Annotated[FullLocationGroupLocationSchema, Tag("locationgroup")],
Annotated[FullDynamicLocationLocationSchema, Tag("dynamiclocation")],
],
Discriminator("locationtype"),
Discriminator(get_locationtype),
]
FullLocationSchema = Annotated[
Union[
FullListableLocationSchema,
CustomLocationLocationSchema,
TrackablePositionLocationSchema,
Annotated[FullLevelLocationSchema, Tag("level")],
Annotated[FullSpaceLocationSchema, Tag("space")],
Annotated[FullAreaLocationSchema, Tag("area")],
Annotated[FullPOILocationSchema, Tag("poi")],
Annotated[FullLocationGroupLocationSchema, Tag("locationgroup")],
Annotated[FullDynamicLocationLocationSchema, Tag("dynamiclocation")],
Annotated[CustomLocationLocationSchema, Tag("customlocation")],
Annotated[TrackablePositionLocationSchema, Tag("position")],
],
Discriminator("locationtype"),
Discriminator(get_locationtype),
]
SlimListableLocationSchema = Annotated[
Union[
SlimLevelLocationSchema,
SlimSpaceLocationSchema,
SlimAreaLocationSchema,
SlimPOILocationSchema,
SlimLocationGroupLocationSchema,
SlimDynamicLocationLocationSchema,
Annotated[SlimLevelLocationSchema, Tag("level")],
Annotated[SlimSpaceLocationSchema, Tag("space")],
Annotated[SlimAreaLocationSchema, Tag("area")],
Annotated[SlimPOILocationSchema, Tag("poi")],
Annotated[SlimLocationGroupLocationSchema, Tag("locationgroup")],
Annotated[SlimDynamicLocationLocationSchema, Tag("dynamiclocation")],
],
Discriminator("locationtype"),
Discriminator(get_locationtype),
]
SlimLocationSchema = Annotated[
Union[
SlimListableLocationSchema,
CustomLocationLocationSchema,
TrackablePositionLocationSchema,
Annotated[SlimLevelLocationSchema, Tag("level")],
Annotated[SlimSpaceLocationSchema, Tag("space")],
Annotated[SlimAreaLocationSchema, Tag("area")],
Annotated[SlimPOILocationSchema, Tag("poi")],
Annotated[SlimLocationGroupLocationSchema, Tag("locationgroup")],
Annotated[SlimDynamicLocationLocationSchema, Tag("dynamiclocation")],
Annotated[CustomLocationLocationSchema, Tag("customlocation")],
Annotated[TrackablePositionLocationSchema, Tag("position")],
],
Discriminator("locationtype"),
Discriminator(get_locationtype),
]
listable_location_definitions = schema_definitions(

View file

@ -5,7 +5,7 @@ from collections import OrderedDict
from dataclasses import dataclass, field
from functools import reduce
from itertools import chain
from typing import Any, List, Mapping, Optional, Union
from typing import Any, List, Mapping, Optional, Union, ClassVar
from django.apps import apps
from django.conf import settings
@ -275,6 +275,8 @@ def get_custom_location_for_request(slug: str, request):
@dataclass
class CustomLocation:
locationtype: ClassVar = "customlocation"
can_search = True
can_describe = True
access_restriction_id = None