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() for key, val in values.items()
} }
if isinstance(values, (list, tuple, set, frozenset)): 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) return type(values)(make_serializable(val) for val in values)
if isinstance(values, Promise): if isinstance(values, Promise):
return str(values) return str(values)

View file

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

View file

@ -47,9 +47,13 @@ def mapdata_list_endpoint(request,
# order_by # order_by
qs = qs.order_by(*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): 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): class POI(SpaceGeometryMixin, SpecificLocation, models.Model):
""" """
An point of interest A point of interest
""" """
new_serialize = True
geometry = GeometryField('point') geometry = GeometryField('point')
class Meta: class Meta:

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import math import math
import re import re
from typing import Annotated, Optional, Union from typing import Annotated, Optional, Union, Literal
from pydantic import Field as APIField from pydantic import Field as APIField
from pydantic import PositiveInt from pydantic import PositiveInt
@ -119,6 +119,12 @@ class LocationSchema(WithAccessRestrictionSchema, TitledSchema, LocationSlugSche
example="more search terms", 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 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): class SpecificLocationSchema(LocationSchema):
grid_square: Union[ grid_square: Union[
Annotated[NonEmptyStr, APIField(title="grid square", description="grid square(s) that this location is in")], 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")], Annotated[None, APIField(title="null", description="no grid defined or outside of grid")],
] = APIField( ] = APIField(
default=None, default=None,
@ -235,7 +242,10 @@ class WithGeometrySchema(BaseSchema):
minx, miny, maxx, maxy = value.geometry.bounds minx, miny, maxx, maxy = value.geometry.bounds
return { return {
**super().get_overrides(value), **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]), "point": (value.level_id,) + tuple(round(i, 2) for i in value.point.coords[0]),
"bounds": ((int(math.floor(minx)), int(math.floor(miny))), "bounds": ((int(math.floor(minx)), int(math.floor(miny))),
(int(math.ceil(maxx)), int(math.ceil(maxy)))) (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 Field as APIField
from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt from pydantic import NonNegativeFloat, PositiveFloat, PositiveInt
@ -415,6 +416,7 @@ class CustomLocationSchema(BaseSchema):
) )
grid_square: Union[ grid_square: Union[
Annotated[NonEmptyStr, APIField(title="grid square", description="grid square(s) that this location is in")], 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")], Annotated[None, APIField(title="null", description="no grid defined or outside of grid")],
] = APIField( ] = APIField(
default=None, default=None,
@ -569,6 +571,8 @@ class SlimLocationMixin(BaseSchema):
can_describe: ClassVar[None] can_describe: ClassVar[None]
groups: ClassVar[None] groups: ClassVar[None]
groups_by_category: ClassVar[None] groups_by_category: ClassVar[None]
geometry: ClassVar[None]
point: ClassVar[None]
class SlimLevelLocationSchema(SlimLocationMixin, FullLevelLocationSchema): class SlimLevelLocationSchema(SlimLocationMixin, FullLevelLocationSchema):
@ -628,46 +632,62 @@ class SlimDynamicLocationLocationSchema(SlimLocationMixin, FullDynamicLocationLo
pass pass
def get_locationtype(v: Any):
if isinstance(v, Model):
return v._meta.model_name
return v["locationtype"]
FullListableLocationSchema = Annotated[ FullListableLocationSchema = Annotated[
Union[ Union[
FullLevelLocationSchema, Annotated[FullLevelLocationSchema, Tag("level")],
FullSpaceLocationSchema, Annotated[FullSpaceLocationSchema, Tag("space")],
FullAreaLocationSchema, Annotated[FullAreaLocationSchema, Tag("area")],
FullPOILocationSchema, Annotated[FullPOILocationSchema, Tag("poi")],
FullLocationGroupLocationSchema, Annotated[FullLocationGroupLocationSchema, Tag("locationgroup")],
FullDynamicLocationLocationSchema, Annotated[FullDynamicLocationLocationSchema, Tag("dynamiclocation")],
], ],
Discriminator("locationtype"), Discriminator(get_locationtype),
] ]
FullLocationSchema = Annotated[ FullLocationSchema = Annotated[
Union[ Union[
FullListableLocationSchema, Annotated[FullLevelLocationSchema, Tag("level")],
CustomLocationLocationSchema, Annotated[FullSpaceLocationSchema, Tag("space")],
TrackablePositionLocationSchema, 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[ SlimListableLocationSchema = Annotated[
Union[ Union[
SlimLevelLocationSchema, Annotated[SlimLevelLocationSchema, Tag("level")],
SlimSpaceLocationSchema, Annotated[SlimSpaceLocationSchema, Tag("space")],
SlimAreaLocationSchema, Annotated[SlimAreaLocationSchema, Tag("area")],
SlimPOILocationSchema, Annotated[SlimPOILocationSchema, Tag("poi")],
SlimLocationGroupLocationSchema, Annotated[SlimLocationGroupLocationSchema, Tag("locationgroup")],
SlimDynamicLocationLocationSchema, Annotated[SlimDynamicLocationLocationSchema, Tag("dynamiclocation")],
], ],
Discriminator("locationtype"), Discriminator(get_locationtype),
] ]
SlimLocationSchema = Annotated[ SlimLocationSchema = Annotated[
Union[ Union[
SlimListableLocationSchema, Annotated[SlimLevelLocationSchema, Tag("level")],
CustomLocationLocationSchema, Annotated[SlimSpaceLocationSchema, Tag("space")],
TrackablePositionLocationSchema, 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( listable_location_definitions = schema_definitions(

View file

@ -5,7 +5,7 @@ from collections import OrderedDict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import reduce from functools import reduce
from itertools import chain 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.apps import apps
from django.conf import settings from django.conf import settings
@ -275,6 +275,8 @@ def get_custom_location_for_request(slug: str, request):
@dataclass @dataclass
class CustomLocation: class CustomLocation:
locationtype: ClassVar = "customlocation"
can_search = True can_search = True
can_describe = True can_describe = True
access_restriction_id = None access_restriction_id = None