From 1deaac6aeb3c6eddee3f33146d320f50f4a60950 Mon Sep 17 00:00:00 2001
From: Helge Jung <hej@c3pb.de>
Date: Mon, 18 Dec 2023 18:13:50 +0100
Subject: [PATCH] API: c3nav combined export view

---
 src/api/urls.py               |  1 +
 src/api/views/maps.py         | 51 ++++++++++++++++++++++++++++++++++-
 src/core/models/assemblies.py | 21 ++++++++++-----
 src/core/models/map.py        | 10 ++++---
 4 files changed, 72 insertions(+), 11 deletions(-)

diff --git a/src/api/urls.py b/src/api/urls.py
index 021b15c28..d10c632e9 100644
--- a/src/api/urls.py
+++ b/src/api/urls.py
@@ -41,6 +41,7 @@ urlpatterns = [
     path('event/<uuid:pk>/', events.EventDetail.as_view(), name='event-detail'),
     path('event/<uuid:pk>/schedule', schedule.EventSchedule.as_view(), name='event-schedule'),
     # integration with other components
+    path('integration/c3nav', maps.C3NavExportView.as_view(), name='c3nav'),
     path('integration/is_angel/<str:username>', conferencemember.AngelView.as_view(), name='user-angel'),
 ]
 
diff --git a/src/api/views/maps.py b/src/api/views/maps.py
index 509deb138..6b90e250e 100644
--- a/src/api/views/maps.py
+++ b/src/api/views/maps.py
@@ -1,6 +1,7 @@
 import abc
 import json
 
+from rest_framework.response import Response
 from rest_framework.views import APIView
 
 from django.contrib.gis.gdal import CoordTransform, SpatialReference
@@ -9,6 +10,7 @@ from core.models.assemblies import Assembly
 from core.models.conference import ConferenceExportCache
 from core.models.map import MapPOI
 
+from ..permissions import IsConferenceService, IsSuperUser
 from .mixins import ConferenceSlugMixin
 
 _cts = {}  # cache of CoordTransforms (if needed)
@@ -83,7 +85,7 @@ class AssembliesExportView(ConferenceSlugMixin, APIView, metaclass=abc.ABCMeta):
             'features': features,
         }
 
-        for assembly in self.get_queryset().prefetch_related('parent').all():
+        for assembly in self.get_queryset().select_related('parent').all():
             geometry = self.get_geometry_field(assembly)
             if geometry is None:
                 continue
@@ -124,3 +126,50 @@ class AssembliesPoiExportView(AssembliesExportView):
 
 class AssembliesAreasExportView(AssembliesExportView):
     geometry_field = 'location_boundaries'
+
+
+class C3NavExportView(ConferenceSlugMixin, APIView):
+    permission_classes = [IsConferenceService | IsSuperUser]
+    required_service_classes = ['c3nav']
+
+    def get(self, request, *args, **kwargs):
+        from core.templatetags.hub_absolute import hub_absolute
+
+        data = []
+
+        exportable_states = [*Assembly.PLACED_STATES, Assembly.State.HIDDEN]
+        qs = self.conference.assemblies.filter(state_assembly__in=exportable_states)
+        if request.GET.get('all') != '1':
+            qs = qs.exclude(location_point=None, location_boundaries=None)
+        for assembly in qs:  # type: Assembly
+            data.append(
+                {
+                    'type': 'assembly',
+                    'id': str(assembly.pk),
+                    'slug': assembly.slug,
+                    'name': assembly.name,
+                    'is_official': assembly.is_official,
+                    'description': {'de': assembly.description_de, 'en': assembly.description_en},
+                    'public_url': hub_absolute('plainui:assembly', assembly_slug=assembly.slug),
+                    'parent_id': assembly.parent_id,
+                    'children': assembly.children.filter(state_assembly__in=exportable_states).values_list('slug', flat=True) if assembly.is_cluster else None,
+                    'floor': assembly.get_location_floor_index(),
+                    'location': assembly.get_location_point_xy(),
+                    'polygons': assembly.get_location_boundaries_xy(),
+                }
+            )
+
+        for poi in self.conference.pois.filter(visible=True):  # type: MapPOI
+            data.append(
+                {
+                    'type': 'poi',
+                    'id': str(poi.pk),
+                    'name': {'de': poi.name_de, 'en': poi.name_en},
+                    'is_official': poi.is_official,
+                    'description': {'de': poi.description_de, 'en': poi.description_en},
+                    'floor': poi.get_location_floor_index(),
+                    'location': poi.get_location_point_xy(),
+                }
+            )
+
+        return Response(data)
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index 866511090..529b912d5 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -535,21 +535,28 @@ class Assembly(TaggedItemMixin, models.Model):
     def get_location_floor_index(self):
         return self.location_floor.index if self.location_floor else None
 
-    def get_location_point_as_json(self):
+    def get_location_point_xy(self):
+        """Get the location_point's X/Y coordinate as array of (two) floats, if it's a valid coordinate - return None otherwise."""
         if not self.location_point or not self.location_point.valid:
-            return ''
+            return None
 
-        return json.dumps([self.location_point.x, self.location_point.y])
+        return [self.location_point.x, self.location_point.y]
 
-    def get_location_boundaries_as_json(self):
+    def get_location_point_as_json(self):
+        return json.dumps(self.get_location_point_xy() or '')
+
+    def get_location_boundaries_xy(self):
         if not self.location_boundaries or not self.location_boundaries.valid:
-            return ''
+            return None
 
         polygons = []
         for multipolygon in self.location_boundaries:
             for polygon in multipolygon:
-                polygons.append(polygon.array)
-        return json.dumps(polygons)
+                polygons.append(list([x[0], x[1]] for x in polygon.array))
+        return polygons
+
+    def get_location_boundaries_as_json(self):
+        return json.dumps(self.get_location_boundaries_xy() or '')
 
     def log_activity(self, user: Optional[PlatformUser], kind: str, message: Optional[str] = None, changes: Optional[dict] = None):
         self.logentries.create(user=user, kind=kind, comment=message, changes=changes)
diff --git a/src/core/models/map.py b/src/core/models/map.py
index aa991af12..84c329866 100644
--- a/src/core/models/map.py
+++ b/src/core/models/map.py
@@ -84,8 +84,12 @@ class MapPOI(models.Model):
     def get_location_floor_index(self):
         return self.location_floor.index if self.location_floor else None
 
-    def get_location_point_as_json(self):
+    def get_location_point_xy(self):
+        """Get the location_point's X/Y coordinate as array of (two) floats, if it's a valid coordinate - return None otherwise."""
         if not self.location_point or not self.location_point.valid:
-            return ''
+            return None
+
+        return [self.location_point.x, self.location_point.y]
 
-        return json.dumps([self.location_point.x, self.location_point.y])
+    def get_location_point_as_json(self):
+        return json.dumps(self.get_location_point_xy() or '')
-- 
GitLab