2017-05-21 23:39:26 +02:00
|
|
|
from itertools import chain
|
|
|
|
|
2017-07-27 16:07:00 +02:00
|
|
|
from django.db.models import Prefetch, Q
|
2018-11-21 01:35:31 +01:00
|
|
|
from django.urls import Resolver404, resolve
|
2018-11-21 21:49:49 +01:00
|
|
|
from django.utils.functional import cached_property
|
2018-11-21 01:35:31 +01:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2018-11-23 21:22:48 +01:00
|
|
|
from rest_framework.authentication import SessionAuthentication
|
2018-11-23 18:12:03 +01:00
|
|
|
from rest_framework.decorators import action
|
2018-11-21 01:35:31 +01:00
|
|
|
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
|
2017-05-21 23:39:26 +02:00
|
|
|
from rest_framework.generics import get_object_or_404
|
|
|
|
from rest_framework.response import Response
|
2017-07-06 13:07:05 +02:00
|
|
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
2017-05-21 23:39:26 +02:00
|
|
|
from shapely.ops import cascaded_union
|
|
|
|
|
2017-06-12 23:33:59 +02:00
|
|
|
from c3nav.editor.models import ChangeSet
|
2017-10-27 17:33:50 +02:00
|
|
|
from c3nav.editor.views.base import etag_func
|
2017-11-01 11:12:40 +01:00
|
|
|
from c3nav.mapdata.api import api_etag
|
2017-10-27 17:33:50 +02:00
|
|
|
from c3nav.mapdata.models import Area, Door, MapUpdate, Source
|
2017-07-13 22:22:13 +02:00
|
|
|
from c3nav.mapdata.models.geometry.space import POI
|
2018-09-19 19:08:47 +02:00
|
|
|
from c3nav.mapdata.utils.user import can_access_editor
|
2017-05-21 23:39:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
class EditorViewSet(ViewSet):
|
|
|
|
"""
|
|
|
|
Editor API
|
2017-06-11 14:43:14 +02:00
|
|
|
/geometries/ returns a list of geojson features, you have to specify ?level=<id> or ?space=<id>
|
2017-05-21 23:39:26 +02:00
|
|
|
/geometrystyles/ returns styling information for all geometry types
|
2017-07-14 00:00:53 +02:00
|
|
|
/bounds/ returns the maximum bounds of the map
|
2017-05-21 23:39:26 +02:00
|
|
|
"""
|
2018-11-21 01:35:31 +01:00
|
|
|
lookup_field = 'path'
|
|
|
|
lookup_value_regex = r'.+'
|
|
|
|
|
2017-06-27 17:10:22 +02:00
|
|
|
@staticmethod
|
|
|
|
def _get_level_geometries(level):
|
2017-06-11 14:43:14 +02:00
|
|
|
buildings = level.buildings.all()
|
2017-06-10 14:58:13 +02:00
|
|
|
buildings_geom = cascaded_union([building.geometry for building in buildings])
|
2017-06-18 05:58:24 +02:00
|
|
|
spaces = {space.pk: space for space in level.spaces.all()}
|
2017-06-10 14:58:13 +02:00
|
|
|
holes_geom = []
|
|
|
|
for space in spaces.values():
|
|
|
|
if space.outside:
|
|
|
|
space.geometry = space.geometry.difference(buildings_geom)
|
|
|
|
columns_geom = cascaded_union([column.geometry for column in space.columns.all()])
|
|
|
|
space.geometry = space.geometry.difference(columns_geom)
|
|
|
|
space_holes_geom = cascaded_union([hole.geometry for hole in space.holes.all()])
|
|
|
|
holes_geom.append(space_holes_geom.intersection(space.geometry))
|
|
|
|
space.geometry = space.geometry.difference(space_holes_geom)
|
|
|
|
holes_geom = cascaded_union(holes_geom)
|
|
|
|
|
|
|
|
for building in buildings:
|
|
|
|
building.original_geometry = building.geometry
|
2017-06-25 12:42:17 +02:00
|
|
|
for obj in buildings:
|
2017-06-10 14:58:13 +02:00
|
|
|
obj.geometry = obj.geometry.difference(holes_geom)
|
|
|
|
|
|
|
|
results = []
|
|
|
|
results.extend(buildings)
|
2017-06-11 14:43:14 +02:00
|
|
|
for door in level.doors.all():
|
2017-06-10 14:58:13 +02:00
|
|
|
results.append(door)
|
|
|
|
|
|
|
|
results.extend(spaces.values())
|
|
|
|
return results
|
|
|
|
|
2017-06-27 17:10:22 +02:00
|
|
|
@staticmethod
|
|
|
|
def _get_levels_pk(request, level):
|
|
|
|
# noinspection PyPep8Naming
|
2017-06-27 03:20:50 +02:00
|
|
|
Level = request.changeset.wrap_model('Level')
|
2017-06-11 14:43:14 +02:00
|
|
|
levels_under = ()
|
|
|
|
levels_on_top = ()
|
2017-06-12 23:33:59 +02:00
|
|
|
lower_level = level.lower(Level).first()
|
2017-06-11 14:43:14 +02:00
|
|
|
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)
|
2017-06-11 13:26:35 +02:00
|
|
|
if True:
|
2017-06-11 14:43:14 +02:00
|
|
|
levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk)
|
|
|
|
levels = chain([level.pk], levels_under, levels_on_top)
|
|
|
|
return levels, levels_on_top, levels_under
|
2017-06-11 13:26:35 +02:00
|
|
|
|
2017-06-27 17:10:22 +02:00
|
|
|
# noinspection PyPep8Naming
|
2018-11-23 18:12:03 +01:00
|
|
|
@action(detail=False, methods=['get'])
|
2017-11-30 17:05:56 +01:00
|
|
|
@api_etag(etag_func=etag_func, cache_parameters={'level': str, 'space': str})
|
2017-05-21 23:39:26 +02:00
|
|
|
def geometries(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
2018-11-20 22:54:29 +01:00
|
|
|
raise PermissionDenied
|
|
|
|
|
2017-06-27 03:20:50 +02:00
|
|
|
Level = request.changeset.wrap_model('Level')
|
|
|
|
Space = request.changeset.wrap_model('Space')
|
2017-06-12 23:33:59 +02:00
|
|
|
|
2017-06-11 14:43:14 +02:00
|
|
|
level = request.GET.get('level')
|
2017-05-21 23:39:26 +02:00
|
|
|
space = request.GET.get('space')
|
2017-06-11 14:43:14 +02:00
|
|
|
if level is not None:
|
2017-05-21 23:39:26 +02:00
|
|
|
if space is not None:
|
2017-06-11 14:43:14 +02:00
|
|
|
raise ValidationError('Only level or space can be specified.')
|
2018-11-20 23:41:25 +01:00
|
|
|
|
|
|
|
if not request.user_permissions.can_access_base_mapdata:
|
|
|
|
raise PermissionDenied
|
|
|
|
|
2017-07-13 22:22:13 +02:00
|
|
|
level = get_object_or_404(Level.objects.filter(Level.q_for_request(request)), pk=level)
|
2017-06-10 14:58:13 +02:00
|
|
|
|
2017-06-12 23:33:59 +02:00
|
|
|
levels, levels_on_top, levels_under = self._get_levels_pk(request, level)
|
2017-06-16 22:27:10 +02:00
|
|
|
# don't prefetch groups for now as changesets do not yet work with m2m-prefetches
|
2017-07-13 22:22:13 +02:00
|
|
|
levels = Level.objects.filter(pk__in=levels).filter(Level.q_for_request(request))
|
2017-12-22 01:17:38 +01:00
|
|
|
# graphnodes_qs = request.changeset.wrap_model('GraphNode').objects.all()
|
2017-07-13 22:22:13 +02:00
|
|
|
levels = levels.prefetch_related(
|
|
|
|
Prefetch('spaces', request.changeset.wrap_model('Space').objects.filter(Space.q_for_request(request))),
|
2017-07-13 23:49:00 +02:00
|
|
|
Prefetch('doors', request.changeset.wrap_model('Door').objects.filter(Door.q_for_request(request))),
|
2017-11-18 17:45:01 +01:00
|
|
|
'buildings', 'spaces__holes', 'spaces__groups', 'spaces__columns', 'spaces__altitudemarkers',
|
2017-12-22 01:17:38 +01:00
|
|
|
# Prefetch('spaces__graphnodes', graphnodes_qs)
|
2017-07-13 22:22:13 +02:00
|
|
|
)
|
2017-06-13 18:52:16 +02:00
|
|
|
|
2017-06-11 14:43:14 +02:00
|
|
|
levels = {s.pk: s for s in levels}
|
2017-06-10 14:58:13 +02:00
|
|
|
|
2017-06-11 14:43:14 +02:00
|
|
|
level = levels[level.pk]
|
|
|
|
levels_under = [levels[pk] for pk in levels_under]
|
|
|
|
levels_on_top = [levels[pk] for pk in levels_on_top]
|
2017-06-10 14:58:13 +02:00
|
|
|
|
2017-07-27 19:21:07 +02:00
|
|
|
# todo: permissions
|
2017-12-22 01:13:29 +01:00
|
|
|
# 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}
|
2017-07-27 16:07:00 +02:00
|
|
|
|
2017-12-22 01:13:29 +01:00
|
|
|
# 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')
|
2017-11-25 19:01:23 +01:00
|
|
|
|
|
|
|
# this is faster because we only deserialize graphnode geometries once
|
2017-12-22 01:13:29 +01:00
|
|
|
# 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]
|
2017-11-25 19:01:23 +01:00
|
|
|
|
2017-11-25 11:07:40 +01:00
|
|
|
# graphedges = [edge for edge in graphedges if edge.from_node.space_id != edge.to_node.space_id]
|
2017-07-27 16:07:00 +02:00
|
|
|
|
2017-06-10 14:58:13 +02:00
|
|
|
results = chain(
|
2017-07-27 15:12:30 +02:00
|
|
|
*(self._get_level_geometries(l) for l in levels_under),
|
2017-06-11 14:43:14 +02:00
|
|
|
self._get_level_geometries(level),
|
2017-07-27 16:07:00 +02:00
|
|
|
*(self._get_level_geometries(l) for l in levels_on_top),
|
2017-11-18 17:45:01 +01:00
|
|
|
*(space.altitudemarkers.all() for space in level.spaces.all()),
|
2017-12-22 01:13:29 +01:00
|
|
|
# graphedges,
|
|
|
|
# graphnodes,
|
2017-06-10 14:58:13 +02:00
|
|
|
)
|
|
|
|
|
2017-06-16 18:38:41 +02:00
|
|
|
return Response([obj.to_geojson(instance=obj) for obj in results])
|
2017-05-21 23:39:26 +02:00
|
|
|
elif space is not None:
|
2017-07-13 22:22:13 +02:00
|
|
|
space_q_for_request = Space.q_for_request(request)
|
|
|
|
qs = Space.objects.filter(space_q_for_request)
|
|
|
|
space = get_object_or_404(qs.select_related('level', 'level__on_top_of'), pk=space)
|
2017-06-11 14:43:14 +02:00
|
|
|
level = space.level
|
2017-05-21 23:39:26 +02:00
|
|
|
|
2018-11-20 23:41:25 +01:00
|
|
|
if not request.user_permissions.can_access_base_mapdata and not space.base_mapdata_accessible:
|
|
|
|
raise PermissionDenied
|
|
|
|
|
|
|
|
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.intersects(space.geometry)]
|
|
|
|
doors_space_geom = cascaded_union([door.geometry for door in doors]+[space.geometry])
|
|
|
|
|
|
|
|
levels, levels_on_top, levels_under = self._get_levels_pk(request, level.primary_level)
|
|
|
|
if level.on_top_of_id is not None:
|
|
|
|
levels = chain([level.pk], levels_on_top)
|
|
|
|
other_spaces = Space.objects.filter(space_q_for_request,
|
|
|
|
level__pk__in=levels).prefetch_related('groups')
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
if level.on_top_of_id is None:
|
|
|
|
other_spaces_lower = [s for s in other_spaces if s.level_id in levels_under]
|
|
|
|
other_spaces_upper = [s for s in other_spaces if s.level_id in levels_on_top]
|
|
|
|
else:
|
|
|
|
other_spaces_lower = [s for s in other_spaces if s.level_id == level.on_top_of_id]
|
|
|
|
other_spaces_upper = []
|
|
|
|
other_spaces = [s for s in other_spaces if s.level_id == level.pk]
|
|
|
|
|
|
|
|
space.bounds = True
|
|
|
|
|
|
|
|
buildings = level.buildings.all()
|
|
|
|
buildings_geom = cascaded_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
|
2017-07-08 17:30:10 +02:00
|
|
|
else:
|
2018-11-20 23:41:25 +01:00
|
|
|
buildings = []
|
|
|
|
doors = []
|
|
|
|
other_spaces = []
|
|
|
|
other_spaces_lower = []
|
2017-07-08 17:30:10 +02:00
|
|
|
other_spaces_upper = []
|
2018-11-20 23:41:25 +01:00
|
|
|
all_other_spaces = []
|
2017-06-09 15:40:52 +02:00
|
|
|
|
2017-07-27 19:21:07 +02:00
|
|
|
# todo: permissions
|
2017-07-27 16:07:00 +02:00
|
|
|
graphnodes = request.changeset.wrap_model('GraphNode').objects.all()
|
2017-08-06 16:52:08 +02:00
|
|
|
graphnodes = graphnodes.filter((Q(space__in=all_other_spaces)) | Q(space__pk=space.pk))
|
|
|
|
|
2017-11-25 18:12:28 +01:00
|
|
|
space_graphnodes = tuple(node for node in graphnodes if node.space_id == space.pk)
|
2017-07-27 16:07:00 +02:00
|
|
|
|
|
|
|
graphedges = request.changeset.wrap_model('GraphEdge').objects.all()
|
2017-08-06 16:52:08 +02:00
|
|
|
graphedges = graphedges.filter(Q(from_node__in=space_graphnodes) | Q(to_node__in=space_graphnodes))
|
2017-07-27 16:07:00 +02:00
|
|
|
graphedges = graphedges.select_related('from_node', 'to_node', 'waytype')
|
|
|
|
|
2017-12-14 21:57:08 +01:00
|
|
|
areas = space.areas.filter(Area.q_for_request(request)).prefetch_related('groups')
|
|
|
|
for area in areas:
|
|
|
|
area.opacity = 0.5
|
|
|
|
|
2017-05-21 23:55:12 +02:00
|
|
|
results = chain(
|
2017-06-09 15:40:52 +02:00
|
|
|
buildings,
|
2017-07-08 17:30:10 +02:00
|
|
|
other_spaces_lower,
|
2017-05-21 23:55:12 +02:00
|
|
|
doors,
|
2017-07-08 17:30:10 +02:00
|
|
|
other_spaces,
|
2017-05-21 23:55:12 +02:00
|
|
|
[space],
|
2017-12-14 21:57:08 +01:00
|
|
|
areas,
|
2017-06-08 15:19:12 +02:00
|
|
|
space.holes.all(),
|
2017-05-21 23:39:26 +02:00
|
|
|
space.stairs.all(),
|
2017-11-17 20:31:29 +01:00
|
|
|
space.ramps.all(),
|
2017-05-21 23:39:26 +02:00
|
|
|
space.obstacles.all(),
|
|
|
|
space.lineobstacles.all(),
|
2017-06-09 15:22:30 +02:00
|
|
|
space.columns.all(),
|
2017-08-05 11:56:29 +02:00
|
|
|
space.altitudemarkers.all(),
|
2017-12-21 02:35:36 +01:00
|
|
|
space.wifi_measurements.all(),
|
2017-07-13 22:22:13 +02:00
|
|
|
space.pois.filter(POI.q_for_request(request)).prefetch_related('groups'),
|
2017-07-08 17:30:10 +02:00
|
|
|
other_spaces_upper,
|
2017-07-27 16:07:00 +02:00
|
|
|
graphedges,
|
|
|
|
graphnodes
|
2017-05-21 23:55:12 +02:00
|
|
|
)
|
2017-08-06 17:07:01 +02:00
|
|
|
return Response([obj.to_geojson(instance=obj) for obj in results])
|
2017-05-21 23:39:26 +02:00
|
|
|
else:
|
2017-06-11 14:43:14 +02:00
|
|
|
raise ValidationError('No level or space specified.')
|
2017-05-21 23:39:26 +02:00
|
|
|
|
2018-11-23 18:12:03 +01:00
|
|
|
@action(detail=False, methods=['get'])
|
2017-11-30 17:05:56 +01:00
|
|
|
@api_etag(etag_func=MapUpdate.current_cache_key, cache_parameters={})
|
2017-05-21 23:39:26 +02:00
|
|
|
def geometrystyles(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
2018-11-20 22:54:29 +01:00
|
|
|
raise PermissionDenied
|
2018-09-19 19:08:47 +02:00
|
|
|
|
2017-05-21 23:39:26 +02:00
|
|
|
return Response({
|
2017-09-21 16:22:20 +02:00
|
|
|
'building': '#aaaaaa',
|
2017-09-21 16:16:44 +02:00
|
|
|
'space': '#eeeeee',
|
2017-05-21 23:39:26 +02:00
|
|
|
'hole': 'rgba(255, 0, 0, 0.3)',
|
|
|
|
'door': '#ffffff',
|
2017-12-14 21:57:08 +01:00
|
|
|
'area': '#55aaff',
|
2017-08-06 17:07:01 +02:00
|
|
|
'stair': '#a000a0',
|
2017-11-17 20:31:29 +01:00
|
|
|
'ramp': 'rgba(160, 0, 160, 0.2)',
|
2017-05-21 23:39:26 +02:00
|
|
|
'obstacle': '#999999',
|
|
|
|
'lineobstacle': '#999999',
|
2017-06-09 15:22:30 +02:00
|
|
|
'column': '#888888',
|
2017-07-08 16:29:12 +02:00
|
|
|
'poi': '#4488cc',
|
2017-05-22 00:12:49 +02:00
|
|
|
'shadow': '#000000',
|
2017-08-06 16:53:42 +02:00
|
|
|
'graphnode': '#009900',
|
2017-07-27 16:07:00 +02:00
|
|
|
'graphedge': '#00CC00',
|
2017-11-18 17:38:13 +01:00
|
|
|
'altitudemarker': '#0000FF',
|
2017-12-21 02:35:36 +01:00
|
|
|
'wifimeasurement': '#DDDD00',
|
2017-05-21 23:39:26 +02:00
|
|
|
})
|
2017-06-16 20:27:07 +02:00
|
|
|
|
2018-11-23 18:12:03 +01:00
|
|
|
@action(detail=False, methods=['get'])
|
2017-11-30 17:05:56 +01:00
|
|
|
@api_etag(etag_func=etag_func, cache_parameters={})
|
2017-07-13 23:37:48 +02:00
|
|
|
def bounds(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
|
|
|
return PermissionDenied
|
|
|
|
|
2017-07-13 23:37:48 +02:00
|
|
|
return Response({
|
|
|
|
'bounds': Source.max_bounds(),
|
|
|
|
})
|
|
|
|
|
2018-11-21 21:49:49 +01:00
|
|
|
def __getattr__(self, name):
|
|
|
|
# allow POST and DELETE methods for the editor API
|
2018-11-21 22:44:31 +01:00
|
|
|
|
|
|
|
if getattr(self, 'get', None).__name__ in ('list', 'retrieve'):
|
|
|
|
if name == 'post' and (self.resolved.url_name.endswith('.create') or
|
|
|
|
self.resolved.url_name.endswith('.edit')):
|
2018-11-22 17:33:52 +01:00
|
|
|
return self.post_or_delete
|
2018-11-21 22:44:31 +01:00
|
|
|
if name == 'delete' and self.resolved.url_name.endswith('.edit'):
|
2018-11-22 17:33:52 +01:00
|
|
|
return self.post_or_delete
|
2018-11-21 21:49:49 +01:00
|
|
|
raise AttributeError
|
|
|
|
|
2018-11-22 17:33:52 +01:00
|
|
|
def post_or_delete(self, request, *args, **kwargs):
|
2018-11-23 21:22:48 +01:00
|
|
|
# django-rest-framework doesn't automatically do this for logged out requests
|
|
|
|
SessionAuthentication().enforce_csrf(request)
|
2018-11-22 17:33:52 +01:00
|
|
|
|
|
|
|
return self.retrieve(request, *args, **kwargs)
|
|
|
|
|
2018-11-21 01:35:31 +01:00
|
|
|
def list(self, request, *args, **kwargs):
|
|
|
|
return self.retrieve(request, *args, **kwargs)
|
|
|
|
|
2018-11-21 21:49:49 +01:00
|
|
|
@cached_property
|
|
|
|
def resolved(self):
|
2018-11-21 01:35:31 +01:00
|
|
|
resolved = None
|
|
|
|
path = self.kwargs.get('path', '')
|
|
|
|
if path:
|
|
|
|
try:
|
|
|
|
resolved = resolve('/editor/'+path+'/')
|
|
|
|
except Resolver404:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if not resolved:
|
|
|
|
try:
|
|
|
|
resolved = resolve('/editor/'+path)
|
|
|
|
except Resolver404:
|
2018-11-21 21:49:49 +01:00
|
|
|
pass
|
|
|
|
|
2018-11-21 22:21:44 +01:00
|
|
|
self.request.sub_resolver_match = resolved
|
|
|
|
|
2018-11-21 21:49:49 +01:00
|
|
|
return resolved
|
|
|
|
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
|
|
if not can_access_editor(request):
|
|
|
|
return PermissionDenied
|
|
|
|
|
|
|
|
resolved = self.resolved
|
|
|
|
if not resolved:
|
|
|
|
raise NotFound(_('No matching editor view endpoint found.'))
|
2018-11-21 01:35:31 +01:00
|
|
|
|
|
|
|
if not getattr(resolved.func, 'api_hybrid', False):
|
|
|
|
raise NotFound(_('Matching editor view point does not provide an API.'))
|
|
|
|
|
2018-11-21 21:49:49 +01:00
|
|
|
response = resolved.func(request, api=True, *resolved.args, **resolved.kwargs)
|
|
|
|
return response
|
2018-11-21 01:35:31 +01:00
|
|
|
|
2017-06-16 20:27:07 +02:00
|
|
|
|
2017-07-06 13:07:05 +02:00
|
|
|
class ChangeSetViewSet(ReadOnlyModelViewSet):
|
|
|
|
"""
|
|
|
|
List change sets
|
|
|
|
/current/ returns the current changeset.
|
|
|
|
"""
|
|
|
|
queryset = ChangeSet.objects.all()
|
|
|
|
|
|
|
|
def get_queryset(self):
|
2017-07-06 13:09:33 +02:00
|
|
|
return ChangeSet.qs_for_request(self.request).select_related('last_update', 'last_state_update', 'last_change')
|
2017-07-06 13:07:05 +02:00
|
|
|
|
|
|
|
def list(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
|
|
|
return PermissionDenied
|
2017-07-06 13:07:05 +02:00
|
|
|
return Response([obj.serialize() for obj in self.get_queryset().order_by('id')])
|
|
|
|
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
|
|
|
return PermissionDenied
|
2017-07-06 13:07:05 +02:00
|
|
|
return Response(self.get_object().serialize())
|
|
|
|
|
2018-11-23 18:12:03 +01:00
|
|
|
@action(detail=False, methods=['get'])
|
2017-07-06 13:07:05 +02:00
|
|
|
def current(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
|
|
|
return PermissionDenied
|
2017-07-06 13:07:05 +02:00
|
|
|
changeset = ChangeSet.get_for_request(request)
|
|
|
|
return Response(changeset.serialize())
|
|
|
|
|
2018-11-23 18:12:03 +01:00
|
|
|
@action(detail=True, methods=['get'])
|
2017-07-06 13:07:05 +02:00
|
|
|
def changes(self, request, *args, **kwargs):
|
2018-09-19 19:08:47 +02:00
|
|
|
if not can_access_editor(request):
|
|
|
|
return PermissionDenied
|
2017-07-06 13:07:05 +02:00
|
|
|
changeset = self.get_object()
|
2017-07-07 15:16:35 +02:00
|
|
|
changeset.fill_changes_cache()
|
2017-07-06 15:06:01 +02:00
|
|
|
return Response([obj.serialize() for obj in changeset.iter_changed_objects()])
|