From 0e19ce5dac88e8da1a9a11c92375422e7a1833f0 Mon Sep 17 00:00:00 2001 From: Gwendolyn Date: Thu, 26 Dec 2024 21:49:07 +0100 Subject: [PATCH] overlay feature update api --- src/c3nav/mapdata/api/mapdata.py | 70 ++++++++++++++++++++++++++--- src/c3nav/mapdata/schemas/models.py | 44 ++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/c3nav/mapdata/api/mapdata.py b/src/c3nav/mapdata/api/mapdata.py index 57b2c786..0ca6fcd0 100644 --- a/src/c3nav/mapdata/api/mapdata.py +++ b/src/c3nav/mapdata/api/mapdata.py @@ -1,18 +1,20 @@ from dataclasses import dataclass from typing import Optional, Sequence, Type, Callable, Any -from django.db.models import Model +from django.db import transaction +from django.db.models import Model, F +from django.shortcuts import get_object_or_404 from ninja import Query from ninja import Router as APIRouter from pydantic import PositiveInt -from c3nav.api.auth import auth_responses, validate_responses -from c3nav.api.exceptions import API404 +from c3nav.api.auth import auth_responses, validate_responses, auth_permission_responses +from c3nav.api.exceptions import API404, APIPermissionDenied from c3nav.api.schema import BaseSchema from c3nav.mapdata.api.base import api_etag, optimize_query, can_access_geometry from c3nav.mapdata.models import (Area, Building, Door, Hole, Level, LocationGroup, LocationGroupCategory, Source, Space, Stair, DataOverlay, DataOverlayFeature, WayType) -from c3nav.mapdata.models.access import AccessRestriction, AccessRestrictionGroup +from c3nav.mapdata.models.access import AccessRestriction, AccessRestrictionGroup, AccessPermission from c3nav.mapdata.models.geometry.space import (POI, Column, CrossDescription, LeaveDescription, LineObstacle, Obstacle, Ramp) from c3nav.mapdata.models.locations import DynamicLocation, LabelSettings, LocationRedirect @@ -25,7 +27,8 @@ from c3nav.mapdata.schemas.models import (AccessRestrictionGroupSchema, AccessRe LineObstacleSchema, LocationGroupCategorySchema, LocationGroupSchema, ObstacleSchema, POISchema, RampSchema, SourceSchema, SpaceSchema, StairSchema, DataOverlaySchema, DataOverlayFeatureSchema, LocationRedirectSchema, - WayTypeSchema) + WayTypeSchema, + DataOverlayFeatureUpdateSchema, DataOverlayFeatureBulkUpdateSchema) mapdata_api_router = APIRouter(tags=["mapdata"]) @@ -303,3 +306,60 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = { MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints) + + +@mapdata_api_router.post('/dataoverlayfeatures/{id}', summary="update a data overlay feature (including geometries)", + response={204: None, **API404.dict(), **auth_permission_responses}) +def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeatureUpdateSchema): + """ + update the data overlay feature + """ + + feature = get_object_or_404(DataOverlayFeature, id=id) + + if feature.overlay.edit_access_restriction_id is None or feature.overlay.edit_access_restriction_id not in AccessPermission.get_for_request(request): + raise APIPermissionDenied('You are not allowed to edit this object.') + + updates = parameters.dict(exclude_unset=True) + + for key, value in updates.items(): + setattr(feature, key, value) + + feature.save() + + return 204, None + +@mapdata_api_router.post('/dataoverlayfeatures-bulk', summary="bulk-update data overlays (including geometries)", + response={204: None, **API404.dict(), **auth_permission_responses}) +def update_data_overlay_features_bulk(request, parameters: DataOverlayFeatureBulkUpdateSchema): + + permissions = AccessPermission.get_for_request(request) + + updates = { + u.id: u + for u in parameters.updates + } + + forbidden_object_ids = [] + + with transaction.atomic(): + features = DataOverlayFeature.objects.filter(id__in=updates.keys()).annotate(edit_access_restriction_id=F('overlay__edit_access_restriction_id')) + + for feature in features: + if feature.edit_access_restriction_id is None or feature.edit_access_restriction_id not in permissions: + forbidden_object_ids.append(feature.id) + continue + + changes = updates[feature.id].dict(exclude_unset=True) + + for key, value in changes.items(): + if key == 'id': + continue + setattr(feature, key, value) + feature.save() + + if len(forbidden_object_ids) > 0: + raise APIPermissionDenied('You are not allowed to edit the objects with the following ids: %s.' + % ", ".join([str(x) for x in forbidden_object_ids])) + + return 204, None \ No newline at end of file diff --git a/src/c3nav/mapdata/schemas/models.py b/src/c3nav/mapdata/schemas/models.py index f829f41e..dd4a1563 100644 --- a/src/c3nav/mapdata/schemas/models.py +++ b/src/c3nav/mapdata/schemas/models.py @@ -397,6 +397,50 @@ class DataOverlayFeatureSchema(TitledSchema, WithGeometrySchema, DjangoModelSche external_url: Optional[str] extra_data: Optional[dict[str, str | int | float]] +class DataOverlayFeatureUpdateSchema(BaseSchema): + """ + An update to a data overlay feature. + """ + level_id: Optional[PositiveInt] = None + geometry: Optional[AnyGeometrySchema] = None + stroke_color: Optional[str] = None + stroke_width: Optional[float] = None + stroke_opacity: Optional[float] = None + fill_color: Optional[str] = None + fill_opacity: Optional[float] = None + show_label: Optional[bool] = None + show_geometry: Optional[bool] = None + interactive: Optional[bool] = None + point_icon: Optional[str] = None + external_url: Optional[str] = None + extra_data: Optional[dict[str, str | int | float]] = None + +class DataOverlayFeatureBulkUpdateItemSchema(BaseSchema): + """ + An item of a bulk update to data overlay features. + """ + id: PositiveInt + level_id: Optional[PositiveInt] = None + geometry: Optional[AnyGeometrySchema] = None + stroke_color: Optional[str] = None + stroke_width: Optional[float] = None + stroke_opacity: Optional[float] = None + fill_color: Optional[str] = None + fill_opacity: Optional[float] = None + show_label: Optional[bool] = None + show_geometry: Optional[bool] = None + interactive: Optional[bool] = None + point_icon: Optional[str] = None + external_url: Optional[str] = None + extra_data: Optional[dict[str, str | int | float]] = None + +class DataOverlayFeatureBulkUpdateSchema(BaseSchema): + """ + A bulk update to data overlay features + """ + updates: list[DataOverlayFeatureBulkUpdateItemSchema] + + class WayTypeSchema(TitledSchema, DjangoModelSchema): """