editor geometries now available in new API
This commit is contained in:
parent
70e9e1fb9f
commit
5c203a7a2b
12 changed files with 411 additions and 45 deletions
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
from ninja import Router as APIRouter
|
||||
from ninja import Schema
|
||||
|
||||
from c3nav.api.newauth import auth_responses, APIAuthMethod
|
||||
from c3nav.api.newauth import APIAuthMethod, auth_responses
|
||||
from c3nav.api.utils import NonEmptyStr
|
||||
from c3nav.control.models import UserPermissions
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from ninja import NinjaAPI, Swagger
|
||||
from ninja import NinjaAPI, Redoc, Swagger
|
||||
from ninja.operation import Operation
|
||||
from ninja.schema import NinjaGenerateJsonSchema
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from c3nav.api.utils import get_api_post_data
|
|||
from c3nav.editor.forms import ChangeSetForm, RejectForm
|
||||
from c3nav.editor.models import ChangeSet
|
||||
from c3nav.editor.utils import LevelChildEditUtils, SpaceChildEditUtils
|
||||
from c3nav.editor.views.base import etag_func
|
||||
from c3nav.editor.views.base import editor_etag_func
|
||||
from c3nav.mapdata.api import api_etag
|
||||
from c3nav.mapdata.models import Area, MapUpdate, Source
|
||||
from c3nav.mapdata.models.geometry.space import POI
|
||||
|
@ -136,7 +136,7 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
|
|||
|
||||
# noinspection PyPep8Naming
|
||||
@action(detail=False, methods=['get'])
|
||||
@api_etag_with_update_cache_key(etag_func=etag_func, cache_parameters={'level': str, 'space': str})
|
||||
@api_etag_with_update_cache_key(etag_func=editor_etag_func, cache_parameters={'level': str, 'space': str})
|
||||
def geometries(self, request, update_cache_key, update_cache_key_match, *args, **kwargs):
|
||||
Level = request.changeset.wrap_model('Level')
|
||||
Space = request.changeset.wrap_model('Space')
|
||||
|
@ -378,7 +378,7 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
|
|||
})
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
@api_etag(etag_func=etag_func, cache_parameters={})
|
||||
@api_etag(etag_func=editor_etag_func, cache_parameters={})
|
||||
def bounds(self, request, *args, **kwargs):
|
||||
return Response({
|
||||
'bounds': Source.max_bounds(),
|
||||
|
|
30
src/c3nav/editor/newapi/base.py
Normal file
30
src/c3nav/editor/newapi/base.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from functools import wraps
|
||||
|
||||
from c3nav.editor.models import ChangeSet
|
||||
from c3nav.mapdata.models.access import AccessPermission
|
||||
from c3nav.mapdata.newapi.base import newapi_etag
|
||||
|
||||
|
||||
def newapi_etag_with_update_cache_key(permissions=True, etag_func=AccessPermission.etag_func, base_mapdata=False):
|
||||
|
||||
def inner_wrapper(func):
|
||||
func = newapi_etag(permissions=permissions, etag_func=etag_func, base_mapdata=base_mapdata)(func)
|
||||
@wraps(func)
|
||||
def inner_wrapped_func(request, *args, **kwargs):
|
||||
try:
|
||||
changeset = request.changeset
|
||||
except AttributeError:
|
||||
changeset = ChangeSet.get_for_request(request)
|
||||
request.changeset = changeset
|
||||
|
||||
request_update_cache_key = kwargs.get("update_cache_key", None)
|
||||
actual_update_cache_key = changeset.raw_cache_key_without_changes
|
||||
|
||||
kwargs.update({
|
||||
"update_cache_key": actual_update_cache_key,
|
||||
"update_cache_key_match": request_update_cache_key == actual_update_cache_key,
|
||||
})
|
||||
return func(request, *args, **kwargs)
|
||||
|
||||
return inner_wrapped_func
|
||||
return inner_wrapper
|
|
@ -1,26 +1,15 @@
|
|||
from datetime import datetime
|
||||
from typing import Annotated, Optional, Literal, Union
|
||||
|
||||
from django.db import IntegrityError, transaction
|
||||
from ninja import Field as APIField
|
||||
from ninja import Query
|
||||
from ninja import Router as APIRouter
|
||||
from ninja import Schema, UploadedFile
|
||||
from ninja.pagination import paginate
|
||||
from pydantic import PositiveInt, field_validator
|
||||
|
||||
from c3nav.api.exceptions import API404, APIConflict, APIRequestValidationFailed
|
||||
from c3nav.api.newauth import APITokenAuth, auth_permission_responses, auth_responses, validate_responses
|
||||
from c3nav.api.schema import GeometrySchema, LineSchema
|
||||
from c3nav.api.utils import NonEmptyStr
|
||||
from c3nav.editor.newapi.schemas import GeometryStylesSchema, EditorID, EditorSpaceGeometriesElemSchema, \
|
||||
EditorLevelGeometriesElemSchema, UpdateCacheKey
|
||||
from c3nav.api.exceptions import API404
|
||||
from c3nav.api.newauth import APITokenAuth, auth_permission_responses
|
||||
from c3nav.editor.newapi.base import newapi_etag_with_update_cache_key
|
||||
from c3nav.editor.newapi.geometries import get_level_geometries_result, get_space_geometries_result
|
||||
from c3nav.editor.newapi.schemas import (EditorID, EditorLevelGeometriesElemSchema, EditorSpaceGeometriesElemSchema,
|
||||
GeometryStylesSchema, UpdateCacheKey)
|
||||
from c3nav.editor.views.base import editor_etag_func
|
||||
from c3nav.mapdata.models import Source
|
||||
from c3nav.mapdata.newapi.base import newapi_etag
|
||||
from c3nav.mapdata.schemas.responses import BoundsSchema
|
||||
from c3nav.mesh.dataformats import BoardType, ChipType, FirmwareImage
|
||||
from c3nav.mesh.messages import MeshMessageType
|
||||
from c3nav.mesh.models import FirmwareBuild, FirmwareVersion, NodeMessage
|
||||
|
||||
editor_api_router = APIRouter(tags=["editor"], auth=APITokenAuth(permissions={"editor_access"}))
|
||||
|
||||
|
@ -65,12 +54,22 @@ def geometrystyles():
|
|||
response={200: list[EditorSpaceGeometriesElemSchema], **API404.dict(),
|
||||
**auth_permission_responses},
|
||||
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
||||
@newapi_etag() # todo: correct?
|
||||
def space_geometries(space_id: EditorID, update_cache_key: UpdateCacheKey = None):
|
||||
@newapi_etag_with_update_cache_key(etag_func=editor_etag_func) # todo: correct?
|
||||
def space_geometries(request, space_id: EditorID, update_cache_key: UpdateCacheKey = None, **kwargs):
|
||||
"""
|
||||
look. this is a complex mess. there will hopefully be more documentation soon. or a better endpoint.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
# newapi_etag_with_update_cache_key does the following, don't let it confuse you:
|
||||
# - update_cache_key becomes the actual update_cache_key, not the one supplied be the user
|
||||
# - kwargs has "update_cache_key_match", which is true if update_cache_key matches the one supplied be the user
|
||||
# this is done so the api etag is correctly generated, as it takes the function arguments into account
|
||||
return get_space_geometries_result(
|
||||
request,
|
||||
space_id=space_id,
|
||||
update_cache_key=update_cache_key,
|
||||
update_cache_key_match=kwargs["update_cache_key_match"]
|
||||
)
|
||||
# todo: test the heck out of this
|
||||
|
||||
|
||||
@editor_api_router.get('/geometries/level/{level_id}/', summary="get the geometries to display for a level",
|
||||
|
@ -78,11 +77,21 @@ def space_geometries(space_id: EditorID, update_cache_key: UpdateCacheKey = None
|
|||
**auth_permission_responses},
|
||||
openapi_extra={"security": [{"APITokenAuth": ["editor_access"]}]})
|
||||
@newapi_etag() # todo: correct?
|
||||
def level_geometries(level_id: EditorID, update_cache_key: UpdateCacheKey = None):
|
||||
def level_geometries(request, level_id: EditorID, update_cache_key: UpdateCacheKey = None, **kwargs):
|
||||
"""
|
||||
look. this is a complex mess. there will hopefully be more documentation soon. or a better endpoint.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
# newapi_etag_with_update_cache_key does the following, don't let it confuse you:
|
||||
# - update_cache_key becomes the actual update_cache_key, not the one supplied be the user
|
||||
# - kwargs has "update_cache_key_match", which is true if update_cache_key matches the one supplied be the user
|
||||
# this is done so the api etag is correctly generated, as it takes the function arguments into account
|
||||
return get_level_geometries_result(
|
||||
request,
|
||||
level_id=level_id,
|
||||
update_cache_key=update_cache_key,
|
||||
update_cache_key_match=kwargs["update_cache_key_match"]
|
||||
)
|
||||
# todo: test the heck out of this
|
||||
|
||||
|
||||
# todo: need a way to pass the changeset if it's not a session API key
|
||||
|
|
323
src/c3nav/editor/newapi/geometries.py
Normal file
323
src/c3nav/editor/newapi/geometries.py
Normal file
|
@ -0,0 +1,323 @@
|
|||
from dataclasses import dataclass
|
||||
from itertools import chain
|
||||
from typing import Sequence
|
||||
|
||||
from django.db.models import Prefetch, Q
|
||||
from shapely import prepared
|
||||
from shapely.lib import unary_union
|
||||
|
||||
from c3nav.api.exceptions import API404, APIPermissionDenied
|
||||
from c3nav.editor.utils import LevelChildEditUtils, SpaceChildEditUtils
|
||||
from c3nav.mapdata.utils.geometry import unwrap_geom
|
||||
|
||||
|
||||
def space_sorting_func(space):
|
||||
groups = tuple(space.groups.all())
|
||||
if not groups:
|
||||
return (0, 0, 0)
|
||||
return (1, groups[0].category.priority, groups[0].hierarchy, groups[0].priority)
|
||||
|
||||
|
||||
def _get_geometries_for_one_level(level):
|
||||
buildings = level.buildings.all()
|
||||
buildings_geom = unary_union([unwrap_geom(building.geometry) for building in buildings])
|
||||
spaces = {space.pk: space for space in level.spaces.all()}
|
||||
holes_geom = []
|
||||
for space in spaces.values():
|
||||
if space.outside:
|
||||
space.geometry = space.geometry.difference(buildings_geom)
|
||||
columns = [column.geometry for column in space.columns.all()]
|
||||
if columns:
|
||||
columns_geom = unary_union([unwrap_geom(column.geometry) for column in space.columns.all()])
|
||||
space.geometry = space.geometry.difference(columns_geom)
|
||||
holes = [unwrap_geom(hole.geometry) for hole in space.holes.all()]
|
||||
if holes:
|
||||
space_holes_geom = unary_union(holes)
|
||||
holes_geom.append(space_holes_geom.intersection(unwrap_geom(space.geometry)))
|
||||
space.geometry = space.geometry.difference(space_holes_geom)
|
||||
|
||||
for building in buildings:
|
||||
building.original_geometry = building.geometry
|
||||
|
||||
if holes_geom:
|
||||
holes_geom = unary_union(holes_geom)
|
||||
holes_geom_prep = prepared.prep(holes_geom)
|
||||
for obj in buildings:
|
||||
if holes_geom_prep.intersects(unwrap_geom(obj.geometry)):
|
||||
obj.geometry = obj.geometry.difference(holes_geom)
|
||||
|
||||
results = []
|
||||
results.extend(buildings)
|
||||
for door in level.doors.all():
|
||||
results.append(door)
|
||||
|
||||
results.extend(sorted(spaces.values(), key=space_sorting_func))
|
||||
return results
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LevelsForLevel:
|
||||
levels: Sequence[int] # IDs of all levels to render for this level, in order, including the level itself
|
||||
levels_on_top: Sequence[int] # IDs of levels that are on top of this level (on_top_of field)
|
||||
levels_under: Sequence[int] # IDs of the level below this level plus levels on top of it (on_top_of field)
|
||||
|
||||
@classmethod
|
||||
def for_level(cls, request, level: "Level", special_if_on_top=False): # add typing
|
||||
# noinspection PyPep8Naming
|
||||
Level = request.changeset.wrap_model('Level')
|
||||
levels_under = ()
|
||||
levels_on_top = ()
|
||||
lower_level = level.lower(Level).first()
|
||||
primary_levels = (level,) + ((lower_level,) if lower_level else ())
|
||||
secondary_levels = Level.objects.filter(on_top_of__in=primary_levels).values_list('pk', 'on_top_of')
|
||||
if lower_level:
|
||||
levels_under = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == lower_level.pk)
|
||||
if True:
|
||||
levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk)
|
||||
|
||||
levels = tuple(chain([level.pk], levels_under, levels_on_top))
|
||||
|
||||
if special_if_on_top and level.on_top_of_id is not None:
|
||||
levels = tuple(chain([level.pk], levels_on_top))
|
||||
levels_under = (level.on_top_of_id, )
|
||||
levels_on_top = ()
|
||||
|
||||
return cls(
|
||||
levels=levels,
|
||||
levels_under=levels_under,
|
||||
levels_on_top=levels_on_top,
|
||||
)
|
||||
|
||||
|
||||
def area_sorting_func(area):
|
||||
groups = tuple(area.groups.all())
|
||||
if not groups:
|
||||
return (0, 0, 0)
|
||||
return (1, groups[0].category.priority, groups[0].hierarchy, groups[0].priority)
|
||||
|
||||
|
||||
def conditional_geojson(obj, update_cache_key_match):
|
||||
if update_cache_key_match and not obj._affected_by_changeset:
|
||||
return obj.get_geojson_key()
|
||||
|
||||
result = obj.to_geojson(instance=obj)
|
||||
result['properties']['changed'] = obj._affected_by_changeset
|
||||
return result
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
def get_level_geometries_result(request, level_id: int, update_cache_key: str, update_cache_key_match: True):
|
||||
Level = request.changeset.wrap_model('Level')
|
||||
Space = request.changeset.wrap_model('Space')
|
||||
Column = request.changeset.wrap_model('Column')
|
||||
Hole = request.changeset.wrap_model('Hole')
|
||||
AltitudeMarker = request.changeset.wrap_model('AltitudeMarker')
|
||||
Building = request.changeset.wrap_model('Building')
|
||||
Door = request.changeset.wrap_model('Door')
|
||||
LocationGroup = request.changeset.wrap_model('LocationGroup')
|
||||
WifiMeasurement = request.changeset.wrap_model('WifiMeasurement')
|
||||
RangingBeacon = request.changeset.wrap_model('RangingBeacon')
|
||||
|
||||
try:
|
||||
level = Level.objects.filter(Level.q_for_request(request)).get(pk=level_id)
|
||||
except Level.DoesNotExist:
|
||||
raise API404('Level not found')
|
||||
|
||||
edit_utils = LevelChildEditUtils(level, request) # todo: what's happening here?
|
||||
if not edit_utils.can_access_child_base_mapdata:
|
||||
raise APIPermissionDenied()
|
||||
|
||||
levels_for_level = LevelsForLevel.for_level(request, level)
|
||||
# don't prefetch groups for now as changesets do not yet work with m2m-prefetches
|
||||
levels = Level.objects.filter(pk__in=levels_for_level.levels).filter(Level.q_for_request(request))
|
||||
# graphnodes_qs = request.changeset.wrap_model('GraphNode').objects.all()
|
||||
levels = levels.prefetch_related(
|
||||
Prefetch('spaces', Space.objects.filter(Space.q_for_request(request)).only(
|
||||
'geometry', 'level', 'outside'
|
||||
)),
|
||||
Prefetch('doors', Door.objects.filter(Door.q_for_request(request)).only('geometry', 'level')),
|
||||
Prefetch('spaces__columns', Column.objects.filter(
|
||||
Q(access_restriction__isnull=True) | ~Column.q_for_request(request)
|
||||
).only('geometry', 'space')),
|
||||
Prefetch('spaces__groups', LocationGroup.objects.only(
|
||||
'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_spaces'
|
||||
)),
|
||||
Prefetch('buildings', Building.objects.only('geometry', 'level')),
|
||||
Prefetch('spaces__holes', Hole.objects.only('geometry', 'space')),
|
||||
Prefetch('spaces__altitudemarkers', AltitudeMarker.objects.only('geometry', 'space')),
|
||||
Prefetch('spaces__wifi_measurements', WifiMeasurement.objects.only('geometry', 'space')),
|
||||
Prefetch('spaces__ranging_beacons', RangingBeacon.objects.only('geometry', 'space')),
|
||||
# Prefetch('spaces__graphnodes', graphnodes_qs)
|
||||
)
|
||||
|
||||
levels = {s.pk: s for s in levels}
|
||||
|
||||
level = levels[level.pk]
|
||||
levels_under = [levels[pk] for pk in levels_for_level.levels_under]
|
||||
levels_on_top = [levels[pk] for pk in levels_for_level.levels_on_top]
|
||||
|
||||
# todo: permissions
|
||||
# graphnodes = tuple(chain(*(space.graphnodes.all()
|
||||
# for space in chain(*(level.spaces.all() for level in levels.values())))))
|
||||
# graphnodes_lookup = {node.pk: node for node in graphnodes}
|
||||
|
||||
# graphedges = request.changeset.wrap_model('GraphEdge').objects.all()
|
||||
# graphedges = graphedges.filter(Q(from_node__in=graphnodes) | Q(to_node__in=graphnodes))
|
||||
# graphedges = graphedges.select_related('waytype')
|
||||
|
||||
# this is faster because we only deserialize graphnode geometries once
|
||||
# missing_graphnodes = graphnodes_qs.filter(pk__in=set(chain(*((edge.from_node_id, edge.to_node_id)
|
||||
# for edge in graphedges))))
|
||||
# graphnodes_lookup.update({node.pk: node for node in missing_graphnodes})
|
||||
# for edge in graphedges:
|
||||
# edge._from_node_cache = graphnodes_lookup[edge.from_node_id]
|
||||
# edge._to_node_cache = graphnodes_lookup[edge.to_node_id]
|
||||
|
||||
# graphedges = [edge for edge in graphedges if edge.from_node.space_id != edge.to_node.space_id]
|
||||
|
||||
results = chain(
|
||||
*(_get_geometries_for_one_level(level) for level in levels_under),
|
||||
_get_geometries_for_one_level(level),
|
||||
*(_get_geometries_for_one_level(level) for level in levels_on_top),
|
||||
*(space.altitudemarkers.all() for space in level.spaces.all()),
|
||||
*(space.wifi_measurements.all() for space in level.spaces.all()),
|
||||
*(space.ranging_beacons.all() for space in level.spaces.all()),
|
||||
# graphedges,
|
||||
# graphnodes,
|
||||
)
|
||||
|
||||
return list(chain(
|
||||
[('update_cache_key', update_cache_key)],
|
||||
(conditional_geojson(obj, update_cache_key_match) for obj in results)
|
||||
))
|
||||
|
||||
|
||||
def get_space_geometries_result(request, space_id: int, update_cache_key: str, update_cache_key_match: bool):
|
||||
Space = request.changeset.wrap_model('Space')
|
||||
Area = request.changeset.wrap_model('Area')
|
||||
POI = request.changeset.wrap_model('POI')
|
||||
Door = request.changeset.wrap_model('Door')
|
||||
LocationGroup = request.changeset.wrap_model('LocationGroup')
|
||||
|
||||
space_q_for_request = Space.q_for_request(request)
|
||||
qs = Space.objects.filter(space_q_for_request)
|
||||
|
||||
try:
|
||||
space = qs.select_related('level', 'level__on_top_of').get(pk=space_id)
|
||||
except Space.DoesNotExist:
|
||||
raise API404('space not found')
|
||||
|
||||
level = space.level
|
||||
|
||||
edit_utils = SpaceChildEditUtils(space, request)
|
||||
if not edit_utils.can_access_child_base_mapdata:
|
||||
raise APIPermissionDenied
|
||||
|
||||
if request.user_permissions.can_access_base_mapdata:
|
||||
doors = [door for door in level.doors.filter(Door.q_for_request(request)).all()
|
||||
if door.geometry.wrapped_geom.intersects(space.geometry.wrapped_geom)]
|
||||
doors_space_geom = unary_union(
|
||||
[unwrap_geom(door.geometry) for door in doors] +
|
||||
[unwrap_geom(space.geometry)]
|
||||
)
|
||||
|
||||
levels_for_level = LevelsForLevel.for_level(request, level.primary_level, special_if_on_top=True)
|
||||
other_spaces = Space.objects.filter(space_q_for_request, level__pk__in=levels_for_level.levels).only(
|
||||
'geometry', 'level'
|
||||
).prefetch_related(
|
||||
Prefetch('groups', LocationGroup.objects.only(
|
||||
'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_spaces'
|
||||
).filter(color__isnull=False))
|
||||
)
|
||||
|
||||
space = next(s for s in other_spaces if s.pk == space.pk)
|
||||
other_spaces = [s for s in other_spaces
|
||||
if s.geometry.intersects(doors_space_geom) and s.pk != space.pk]
|
||||
all_other_spaces = other_spaces
|
||||
|
||||
other_spaces_lower = [s for s in other_spaces if s.level_id in levels_for_level.levels_under]
|
||||
other_spaces_upper = [s for s in other_spaces if s.level_id in levels_for_level.levels_on_top]
|
||||
other_spaces = [s for s in other_spaces if s.level_id == level.pk]
|
||||
|
||||
space.bounds = True
|
||||
|
||||
# deactivated for performance reasons
|
||||
buildings = level.buildings.all()
|
||||
# buildings_geom = unary_union([building.geometry for building in buildings])
|
||||
# for other_space in other_spaces:
|
||||
# if other_space.outside:
|
||||
# other_space.geometry = other_space.geometry.difference(buildings_geom)
|
||||
for other_space in chain(other_spaces, other_spaces_lower, other_spaces_upper):
|
||||
other_space.opacity = 0.4
|
||||
other_space.color = '#ffffff'
|
||||
for building in buildings:
|
||||
building.opacity = 0.5
|
||||
else:
|
||||
buildings = []
|
||||
doors = []
|
||||
other_spaces = []
|
||||
other_spaces_lower = []
|
||||
other_spaces_upper = []
|
||||
all_other_spaces = []
|
||||
|
||||
# todo: permissions
|
||||
if request.user_permissions.can_access_base_mapdata:
|
||||
graph_nodes = request.changeset.wrap_model('GraphNode').objects.all()
|
||||
graph_nodes = graph_nodes.filter((Q(space__in=all_other_spaces)) | Q(space__pk=space.pk))
|
||||
|
||||
space_graph_nodes = tuple(node for node in graph_nodes if node.space_id == space.pk)
|
||||
|
||||
graph_edges = request.changeset.wrap_model('GraphEdge').objects.all()
|
||||
space_graphnodes_ids = tuple(node.pk for node in space_graph_nodes)
|
||||
graph_edges = graph_edges.filter(Q(from_node__pk__in=space_graphnodes_ids) |
|
||||
Q(to_node__pk__in=space_graphnodes_ids))
|
||||
graph_edges = graph_edges.select_related('from_node', 'to_node', 'waytype').only(
|
||||
'from_node__geometry', 'to_node__geometry', 'waytype__color'
|
||||
)
|
||||
else:
|
||||
graph_nodes = []
|
||||
graph_edges = []
|
||||
|
||||
areas = space.areas.filter(Area.q_for_request(request)).only(
|
||||
'geometry', 'space'
|
||||
).prefetch_related(
|
||||
Prefetch('groups', LocationGroup.objects.order_by(
|
||||
'-category__priority', '-hierarchy', '-priority'
|
||||
).only(
|
||||
'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_areas'
|
||||
))
|
||||
)
|
||||
for area in areas:
|
||||
area.opacity = 0.5
|
||||
areas = sorted(areas, key=area_sorting_func)
|
||||
|
||||
results = chain(
|
||||
buildings,
|
||||
other_spaces_lower,
|
||||
doors,
|
||||
other_spaces,
|
||||
[space],
|
||||
areas,
|
||||
space.holes.all().only('geometry', 'space'),
|
||||
space.stairs.all().only('geometry', 'space'),
|
||||
space.ramps.all().only('geometry', 'space'),
|
||||
space.obstacles.all().only('geometry', 'space', 'color'),
|
||||
space.lineobstacles.all().only('geometry', 'width', 'space', 'color'),
|
||||
space.columns.all().only('geometry', 'space'),
|
||||
space.altitudemarkers.all().only('geometry', 'space'),
|
||||
space.wifi_measurements.all().only('geometry', 'space'),
|
||||
space.ranging_beacons.all().only('geometry', 'space'),
|
||||
space.pois.filter(POI.q_for_request(request)).only('geometry', 'space').prefetch_related(
|
||||
Prefetch('groups', LocationGroup.objects.only(
|
||||
'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_pois'
|
||||
).filter(color__isnull=False))
|
||||
),
|
||||
other_spaces_upper,
|
||||
graph_edges,
|
||||
graph_nodes
|
||||
)
|
||||
|
||||
return list(chain(
|
||||
[('update_cache_key', update_cache_key)],
|
||||
(conditional_geojson(obj, update_cache_key_match) for obj in results)
|
||||
))
|
|
@ -1,9 +1,10 @@
|
|||
from typing import Annotated, Union, Literal, Optional
|
||||
from typing import Annotated, Literal, Optional, Union
|
||||
|
||||
from ninja import Schema
|
||||
from pydantic import Field as APIField, PositiveInt
|
||||
from pydantic import Field as APIField
|
||||
from pydantic import PositiveInt
|
||||
|
||||
from c3nav.api.schema import LineSchema, GeometrySchema
|
||||
from c3nav.api.schema import GeometrySchema, LineSchema
|
||||
from c3nav.api.utils import NonEmptyStr
|
||||
|
||||
GeometryStylesSchema = Annotated[
|
||||
|
|
|
@ -289,7 +289,7 @@ def call_api_hybrid_view_for_html(func, request, *args, **kwargs):
|
|||
raise NoAPIHybridResponse
|
||||
|
||||
|
||||
def etag_func(request, *args, **kwargs):
|
||||
def editor_etag_func(request, *args, **kwargs):
|
||||
try:
|
||||
changeset = request.changeset
|
||||
except AttributeError:
|
||||
|
|
|
@ -17,8 +17,8 @@ from django.views.decorators.http import etag
|
|||
from c3nav.editor.forms import GraphEdgeSettingsForm, GraphEditorActionForm
|
||||
from c3nav.editor.utils import DefaultEditUtils, LevelChildEditUtils, SpaceChildEditUtils
|
||||
from c3nav.editor.views.base import (APIHybridError, APIHybridFormTemplateResponse, APIHybridLoginRequiredResponse,
|
||||
APIHybridMessageRedirectResponse, APIHybridTemplateContextResponse, etag_func,
|
||||
sidebar_view)
|
||||
APIHybridMessageRedirectResponse, APIHybridTemplateContextResponse,
|
||||
editor_etag_func, sidebar_view)
|
||||
from c3nav.mapdata.models.access import AccessPermission
|
||||
from c3nav.mapdata.utils.user import can_access_editor
|
||||
|
||||
|
@ -40,7 +40,7 @@ def child_model(request, model: typing.Union[str, models.Model], kwargs=None, pa
|
|||
}
|
||||
|
||||
|
||||
@etag(etag_func)
|
||||
@etag(editor_etag_func)
|
||||
@sidebar_view(api_hybrid=True)
|
||||
def main_index(request):
|
||||
Level = request.changeset.wrap_model('Level')
|
||||
|
@ -61,7 +61,7 @@ def main_index(request):
|
|||
}, fields=('can_create_level', 'child_models'))
|
||||
|
||||
|
||||
@etag(etag_func)
|
||||
@etag(editor_etag_func)
|
||||
@sidebar_view(api_hybrid=True)
|
||||
def level_detail(request, pk):
|
||||
Level = request.changeset.wrap_model('Level')
|
||||
|
@ -90,7 +90,7 @@ def level_detail(request, pk):
|
|||
}, fields=('level', 'can_edit_graph', 'can_create_level', 'child_models', 'levels_on_top'))
|
||||
|
||||
|
||||
@etag(etag_func)
|
||||
@etag(editor_etag_func)
|
||||
@sidebar_view(api_hybrid=True)
|
||||
def space_detail(request, level, pk):
|
||||
Level = request.changeset.wrap_model('Level')
|
||||
|
@ -126,7 +126,7 @@ def get_changeset_exceeded(request):
|
|||
return request.user_permissions.max_changeset_changes <= request.changeset.changed_objects_count
|
||||
|
||||
|
||||
@etag(etag_func)
|
||||
@etag(editor_etag_func)
|
||||
@sidebar_view(api_hybrid=True)
|
||||
def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, explicit_edit=False):
|
||||
changeset_exceeded = get_changeset_exceeded(request)
|
||||
|
@ -412,7 +412,7 @@ def get_visible_spaces_kwargs(model, request):
|
|||
return kwargs
|
||||
|
||||
|
||||
@etag(etag_func)
|
||||
@etag(editor_etag_func)
|
||||
@sidebar_view(api_hybrid=True)
|
||||
def list_objects(request, model=None, level=None, space=None, explicit_edit=False):
|
||||
resolver_match = getattr(request, 'sub_resolver_match', request.resolver_match)
|
||||
|
@ -568,7 +568,7 @@ def connect_nodes(request, active_node, clicked_node, edge_settings_form):
|
|||
messages.success(request, _('Reverse edge overwritten.') if is_reverse else _('Edge overwritten.'))
|
||||
|
||||
|
||||
@etag(etag_func)
|
||||
@etag(editor_etag_func)
|
||||
@sidebar_view
|
||||
def graph_edit(request, level=None, space=None):
|
||||
if not request.user_permissions.can_access_base_mapdata:
|
||||
|
|
|
@ -2,7 +2,7 @@ from dataclasses import fields
|
|||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from c3nav.mesh.baseformats import normalize_name, StructType
|
||||
from c3nav.mesh.baseformats import StructType, normalize_name
|
||||
from c3nav.mesh.messages import MeshMessage
|
||||
from c3nav.mesh.utils import indent_c
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Annotated, Optional
|
||||
|
||||
from ninja import Field as APIField
|
||||
from ninja import Router as APIRouter, Schema
|
||||
from ninja import Router as APIRouter
|
||||
from ninja import Schema
|
||||
from pydantic import NegativeInt
|
||||
|
||||
from c3nav.api.newauth import auth_responses
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
from enum import StrEnum
|
||||
from typing import Annotated, Union, Optional
|
||||
from typing import Annotated, Optional, Union
|
||||
|
||||
from ninja import Router as APIRouter, Schema, Field as APIField
|
||||
from ninja import Field as APIField
|
||||
from ninja import Router as APIRouter
|
||||
from ninja import Schema
|
||||
from pydantic import PositiveInt
|
||||
|
||||
from c3nav.api.newauth import auth_responses, validate_responses
|
||||
from c3nav.api.utils import NonEmptyStr
|
||||
from c3nav.mapdata.models import Source
|
||||
from pydantic import PositiveInt
|
||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, Coordinates3D
|
||||
from c3nav.mapdata.schemas.responses import BoundsSchema
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue