From 6bbac4a7ba38cb2d06ab4d3ddece4455237a1a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 11 May 2017 19:36:49 +0200 Subject: [PATCH] new REST API --- src/c3nav/api/urls.py | 21 +- src/c3nav/mapdata/api.py | 270 ++++++++++--------- src/c3nav/mapdata/apps.py | 9 +- src/c3nav/mapdata/models/__init__.py | 2 +- src/c3nav/mapdata/models/base.py | 10 + src/c3nav/mapdata/models/geometry/base.py | 12 + src/c3nav/mapdata/models/geometry/section.py | 32 ++- src/c3nav/mapdata/models/geometry/space.py | 34 ++- src/c3nav/mapdata/models/locations.py | 59 ++-- src/c3nav/mapdata/models/section.py | 7 +- src/c3nav/mapdata/serializers/main.py | 12 +- src/c3nav/mapdata/utils/json.py | 2 +- 12 files changed, 265 insertions(+), 205 deletions(-) diff --git a/src/c3nav/api/urls.py b/src/c3nav/api/urls.py index 749240d0..c09cf4ed 100644 --- a/src/c3nav/api/urls.py +++ b/src/c3nav/api/urls.py @@ -7,16 +7,25 @@ from rest_framework.generics import GenericAPIView from rest_framework.response import Response from rest_framework.routers import SimpleRouter -from c3nav.mapdata.api import GeometryTypeViewSet, GeometryViewSet, LocationViewSet, SectionViewSet, SourceViewSet +from c3nav.mapdata.api import (AreaViewSet, BuildingViewSet, DoorViewSet, HoleViewSet, LineObstacleViewSet, + LocationGroupViewSet, LocationViewSet, ObstacleViewSet, PointViewSet, SectionViewSet, + SourceViewSet, SpaceViewSet, StairViewSet) router = SimpleRouter() router.register(r'sections', SectionViewSet) +router.register(r'buildings', BuildingViewSet) +router.register(r'spaces', SpaceViewSet) +router.register(r'doors', DoorViewSet) +router.register(r'holes', HoleViewSet) +router.register(r'areas', AreaViewSet) +router.register(r'stairs', StairViewSet) +router.register(r'obstacles', ObstacleViewSet) +router.register(r'lineobstacles', LineObstacleViewSet) +router.register(r'points', PointViewSet) router.register(r'sources', SourceViewSet) -router.register(r'geometrytypes', GeometryTypeViewSet, base_name='geometrytype') -router.register(r'geometries', GeometryViewSet, base_name='geometry') - -router.register(r'locations', LocationViewSet, base_name='location') +router.register(r'locations', LocationViewSet) +router.register(r'locationgroups', LocationGroupViewSet) class APIRoot(GenericAPIView): @@ -32,7 +41,7 @@ class APIRoot(GenericAPIView): urls = OrderedDict() for urlpattern in router.urls: name = urlpattern.name - url = self._format_pattern(urlpattern.regex.pattern) + url = self._format_pattern(urlpattern.regex.pattern).replace('{pk}', '{id}') base = url.split('/', 1)[0] if '-' in name: urls.setdefault(base, OrderedDict())[name.split('-', 1)[1]] = url diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api.py index d7759f32..a585ccca 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api.py @@ -1,165 +1,167 @@ -import hashlib -import json import mimetypes from collections import OrderedDict +from itertools import chain -from django.http import Http404, HttpResponse, HttpResponseNotModified +from django.http import HttpResponse +from django.utils.translation import ugettext_lazy as _ from rest_framework.decorators import detail_route, list_route +from rest_framework.exceptions import NotFound, ValidationError from rest_framework.response import Response -from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet +from rest_framework.viewsets import ReadOnlyModelViewSet -from c3nav.access.apply import filter_arealocations_by_access, filter_queryset_by_access -from c3nav.mapdata.lastupdate import get_last_mapdata_update -from c3nav.mapdata.models import LocationGroup, Source -from c3nav.mapdata.models.geometry.base import GEOMETRY_MODELS -from c3nav.mapdata.models.geometry.space import Stair +from c3nav.access.apply import filter_queryset_by_access +from c3nav.mapdata.models import Building, Door, Hole, LocationGroup, Source, Space +from c3nav.mapdata.models.geometry.section import SECTION_MODELS +from c3nav.mapdata.models.geometry.space import SPACE_MODELS, Area, LineObstacle, Obstacle, Point, Stair +from c3nav.mapdata.models.locations import LocationSlug from c3nav.mapdata.models.section import Section -from c3nav.mapdata.search import get_location -from c3nav.mapdata.serializers.main import SectionSerializer, SourceSerializer -from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_bssid_areas_cached +from c3nav.mapdata.serializers.main import SourceSerializer +from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin -class GeometryTypeViewSet(ViewSet): - """ - Lists all geometry types. - """ - @cache_mapdata_api_response() +class MapdataViewSet(ReadOnlyModelViewSet): def list(self, request): + qs = self.get_queryset() + geometry = ('geometry' in request.GET) + if qs.model in SECTION_MODELS and 'section' in request.GET: + if not request.GET['section'].isdigit(): + raise ValidationError(detail={'detail': _('%s is not an integer.') % 'section'}) + try: + section = Section.objects.get(pk=request.GET['section']) + except Section.DoesNotExist: + raise NotFound(detail=_('section not found.')) + qs = qs.filter(section=section) + if qs.model in SPACE_MODELS and 'space' in request.GET: + if not request.GET['space'].isdigit(): + raise ValidationError(detail={'detail': _('%s is not an integer.') % 'space'}) + try: + space = Space.objects.get(pk=request.GET['space']) + except Space.DoesNotExist: + raise NotFound(detail=_('section not found.')) + qs = qs.filter(space=space) + return Response([obj.serialize(geometry=geometry) for obj in qs.order_by('id')]) + + def retrieve(self, request, pk=None): + return Response(self.get_object().serialize()) + + def list_geometrytypes(self, models_list): return Response([ OrderedDict(( - ('name', name), - ('title', str(mapitemtype._meta.verbose_name)), - ('title_plural', str(mapitemtype._meta.verbose_name_plural)), - )) for name, mapitemtype in GEOMETRY_MODELS.items() + ('name', model.__name__.lower()), + ('name_plural', model._meta.default_related_name), + ('geomtype', model._meta.get_field('geometry').geomtype), + ('title', str(model._meta.verbose_name)), + ('title_plural', str(model._meta.verbose_name_plural)), + )) for model in models_list ]) -class GeometryViewSet(ViewSet): - """ - List all geometries. - """ - def list(self, request): - types = set(request.GET.getlist('type')) - valid_types = list(GEOMETRY_MODELS.keys()) - if not types: - types = valid_types - else: - types = [t for t in valid_types if t in types] - - cache_key = '__'.join(( - ','.join([str(i) for i in types]), - )) - - return self._list(request, types=types, add_cache_key=cache_key) - - @staticmethod - def compare_by_location_type(x, y): - return AreaLocation.LOCATION_TYPES.index(x.location_type) - AreaLocation.LOCATION_TYPES.index(y.location_type) - - @cache_mapdata_api_response() - def _list(self, request, types): - results = [] - for t in types: - mapitemtype = GEOMETRY_MODELS[t] - queryset = mapitemtype.objects.all() - queryset = filter_queryset_by_access(request, queryset) - queryset = queryset.order_by('id') - - if issubclass(mapitemtype, AreaLocation): - queryset = sorted(queryset, key=AreaLocation.get_sort_key) - - if issubclass(mapitemtype, Stair): - results.extend(obj.to_shadow_geojson() for obj in queryset) - - results.extend(obj.to_geojson() for obj in queryset) - - return Response(results) - - -class SectionViewSet(CachedReadOnlyViewSetMixin, ReadOnlyModelViewSet): - """ - List and retrieve sections. - """ +class SectionViewSet(MapdataViewSet): queryset = Section.objects.all() - serializer_class = SectionSerializer - lookup_field = 'id' + + @list_route(methods=['get']) + def geometrytypes(self, request): + return self.list_geometrytypes(SECTION_MODELS) + + @detail_route(methods=['get']) + def geometries(self, requests, pk=None): + section = self.get_object() + results = [] + results.extend(section.buildings.all()) + results.extend(section.holes.all()) + for space in section.spaces.all(): + results.append(space) + for door in section.doors.all(): + results.append(door) + return Response([obj.to_geojson() for obj in results]) + + +class BuildingViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?section= to filter by section. """ + queryset = Building.objects.all() + + +class SpaceViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?section= to filter by section. """ + queryset = Space.objects.all() + + @list_route(methods=['get']) + def geometrytypes(self, request): + return self.list_geometrytypes(SPACE_MODELS) + + @detail_route(methods=['get']) + def geometries(self, requests, pk=None): + space = self.get_object() + results = chain( + space.stairs.all(), + space.areas.all(), + space.obstacles.all(), + space.lineobstacles.all(), + space.points.all(), + ) + return Response([obj.to_geojson() for obj in results]) + + +class DoorViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?section= to filter by section. """ + queryset = Door.objects.all() + + +class HoleViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?section= to filter by section. """ + queryset = Hole.objects.all() + + +class AreaViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?space= to filter by space. """ + queryset = Area.objects.all() + + +class StairViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?space= to filter by space. """ + queryset = Stair.objects.all() + + +class ObstacleViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?space= to filter by space. """ + queryset = Obstacle.objects.all() + + +class LineObstacleViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?space= to filter by space. """ + queryset = LineObstacle.objects.all() + + +class PointViewSet(MapdataViewSet): + """ Add ?geometry=1 to get geometries, add ?space= to filter by space. """ + queryset = Point.objects.all() + + +class LocationGroupViewSet(MapdataViewSet): + queryset = LocationGroup.objects.all() + + +class LocationViewSet(MapdataViewSet): + queryset = LocationSlug.objects.all() + lookup_field = 'slug' + + def retrieve(self, request, slug=None): + return Response(self.get_object().get_child().serialize(include_type=True)) class SourceViewSet(CachedReadOnlyViewSetMixin, ReadOnlyModelViewSet): - """ - List and retrieve source images (to use as a drafts). - """ queryset = Source.objects.all() serializer_class = SourceSerializer - lookup_field = 'id' def get_queryset(self): return filter_queryset_by_access(self.request, super().get_queryset().all()) @detail_route(methods=['get']) - def image(self, request, name=None): - return self._image(request, name=name, add_cache_key=self._get_add_cache_key(request)) + def image(self, request, pk=None): + return self._image(request, pk=pk) - @cache_mapdata_api_response() - def _image(self, request, name=None): + def _image(self, request, pk=None): source = self.get_object() response = HttpResponse(content_type=mimetypes.guess_type(source.name)[0]) response.write(source.image) return response - - -class LocationViewSet(ViewSet): - """ - List and retrieve locations - """ - # We don't cache this, because it depends on access_list - lookup_field = 'location_id' - - @staticmethod - def _filter(queryset): - return queryset.filter(can_search=True).order_by('id') - - def list(self, request, **kwargs): - etag = hashlib.sha256(json.dumps({ - 'full_access': request.c3nav_full_access, - 'access_list': request.c3nav_access_list, - 'last_update': get_last_mapdata_update().isoformat() - }).encode()).hexdigest() - - if_none_match = request.META.get('HTTP_IF_NONE_MATCH') - if if_none_match: - if if_none_match == etag: - return HttpResponseNotModified() - - locations = [] - locations += list(filter_queryset_by_access(request, self._filter(LocationGroup.objects.all()))) - locations += sorted(filter_arealocations_by_access(request, self._filter(AreaLocation.objects.all())), - key=AreaLocation.get_sort_key, reverse=True) - - response = Response([location.to_location_json() for location in locations]) - response['ETag'] = etag - response['Cache-Control'] = 'no-cache' - return response - - def retrieve(self, request, location_id=None, **kwargs): - location = get_location(request, location_id) - if location is None: - raise Http404 - return Response(location.to_location_json()) - - @list_route(methods=['POST']) - def wifilocate(self, request): - stations = json.loads(request.POST['stations'])[:200] - if not stations: - return Response({'location': None}) - - bssids = get_bssid_areas_cached() - stations = sorted(stations, key=lambda l: l['level']) - for station in stations: - area_name = bssids.get(station['bssid'].lower()) - if area_name is not None: - location = get_location(request, area_name) - if location is not None: - return Response({'location': location.to_location_json()}) - - return Response({'location': None}) diff --git a/src/c3nav/mapdata/apps.py b/src/c3nav/mapdata/apps.py index 3fd2157d..d9e06b4d 100644 --- a/src/c3nav/mapdata/apps.py +++ b/src/c3nav/mapdata/apps.py @@ -34,10 +34,11 @@ class MapdataConfig(AppConfig): if geometry is None: raise TypeError(_('Model %s has GeometryMixin as base class but has no geometry field.') % cls) + from c3nav.mapdata.models.locations import Location, LOCATION_MODELS + LOCATION_MODELS.extend(self._get_submodels(Location)) + from c3nav.mapdata.models.geometry.section import SectionGeometryMixin, SECTION_MODELS - for cls in self._get_submodels(SectionGeometryMixin): - SECTION_MODELS[cls.__name__] = cls + SECTION_MODELS.extend(self._get_submodels(SectionGeometryMixin)) from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin, SPACE_MODELS - for cls in self._get_submodels(SpaceGeometryMixin): - SPACE_MODELS[cls.__name__] = cls + SPACE_MODELS.extend(self._get_submodels(SpaceGeometryMixin)) diff --git a/src/c3nav/mapdata/models/__init__.py b/src/c3nav/mapdata/models/__init__.py index cfd6f18a..e1b0d4e1 100644 --- a/src/c3nav/mapdata/models/__init__.py +++ b/src/c3nav/mapdata/models/__init__.py @@ -2,4 +2,4 @@ from .section import Section # noqa from .source import Source # noqa from c3nav.mapdata.models.geometry.section import Building, Space, Hole, Door # noqa from c3nav.mapdata.models.geometry.space import Area, Stair, Obstacle, LineObstacle # noqa -from .locations import Location, LocationGroup # noqa +from .locations import Location, LocationSlug, LocationGroup # noqa diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index e5d584fe..13f76be5 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -10,3 +10,13 @@ class EditorFormMixin(models.Model): class Meta: abstract = True + + def serialize(self, **kwargs): + return self._serialize(**kwargs) + + def _serialize(self, include_type=False, **kwargs): + result = OrderedDict() + if include_type: + result['type'] = self.__class__.__name__.lower() + result['id'] = self.id + return result diff --git a/src/c3nav/mapdata/models/geometry/base.py b/src/c3nav/mapdata/models/geometry/base.py index 7a6a1dbd..37b7ce99 100644 --- a/src/c3nav/mapdata/models/geometry/base.py +++ b/src/c3nav/mapdata/models/geometry/base.py @@ -30,6 +30,18 @@ class GeometryMixin(EditorFormMixin): ('geometry', format_geojson(mapping(self.geometry), round=False)), )) + def serialize(self, geometry=True, **kwargs): + result = super().serialize(geometry=geometry, **kwargs) + if geometry: + result.move_to_end('geometry') + return result + + def _serialize(self, geometry=True, **kwargs): + result = super()._serialize(**kwargs) + if geometry: + result['geometry'] = format_geojson(mapping(self.geometry), round=False) + return result + def get_shadow_geojson(self): pass diff --git a/src/c3nav/mapdata/models/geometry/section.py b/src/c3nav/mapdata/models/geometry/section.py index 454eca5c..b37a8036 100644 --- a/src/c3nav/mapdata/models/geometry/section.py +++ b/src/c3nav/mapdata/models/geometry/section.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -7,7 +5,7 @@ from c3nav.mapdata.fields import GeometryField from c3nav.mapdata.models.geometry.base import GeometryMixin from c3nav.mapdata.models.locations import SpecificLocation -SECTION_MODELS = OrderedDict() +SECTION_MODELS = [] class SectionGeometryMixin(GeometryMixin): @@ -16,9 +14,15 @@ class SectionGeometryMixin(GeometryMixin): class Meta: abstract = True - def get_geojson_properties(self): + def get_geojson_properties(self) -> dict: result = super().get_geojson_properties() - result['section'] = self.section.id + result['layer'] = getattr(self, 'level', 'base') + return result + + def _serialize(self, section=True, **kwargs): + result = super()._serialize(**kwargs) + if section: + result['section'] = self.section.id return result @@ -33,11 +37,6 @@ class LevelSectionGeometryMixin(SectionGeometryMixin): class Meta: abstract = True - def get_geojson_properties(self): - result = super().get_geojson_properties() - result['level'] = self.level - return result - class Building(SectionGeometryMixin, models.Model): """ @@ -55,7 +54,6 @@ class Space(SpecificLocation, LevelSectionGeometryMixin, models.Model): """ An accessible space. Shouldn't overlap with spaces on same secion and level. """ - CATEGORIES = ( ('', _('normal')), ('stairs', _('stairs')), @@ -63,7 +61,6 @@ class Space(SpecificLocation, LevelSectionGeometryMixin, models.Model): ('elevator', _('elevator')), ) geometry = GeometryField('polygon') - public = models.BooleanField(verbose_name=_('public'), default=True) category = models.CharField(verbose_name=_('category'), choices=CATEGORIES, default='', max_length=16) class Meta: @@ -71,11 +68,12 @@ class Space(SpecificLocation, LevelSectionGeometryMixin, models.Model): verbose_name_plural = _('Spaces') default_related_name = 'spaces' - def get_geojson_properties(self): - result = super().get_geojson_properties() - result['category'] = self.category - result['level'] = self.level - result['public'] = self.public + def _serialize(self, space=True, **kwargs): + result = super()._serialize(**kwargs) + if space: + result['category'] = self.category + result['level'] = self.level + result['public'] = self.public return result diff --git a/src/c3nav/mapdata/models/geometry/space.py b/src/c3nav/mapdata/models/geometry/space.py index 113f9ffa..91063a28 100644 --- a/src/c3nav/mapdata/models/geometry/space.py +++ b/src/c3nav/mapdata/models/geometry/space.py @@ -9,7 +9,7 @@ from c3nav.mapdata.models.geometry.base import GeometryMixin from c3nav.mapdata.models.locations import SpecificLocation from c3nav.mapdata.utils.json import format_geojson -SPACE_MODELS = OrderedDict() +SPACE_MODELS = [] class SpaceGeometryMixin(GeometryMixin): @@ -18,9 +18,10 @@ class SpaceGeometryMixin(GeometryMixin): class Meta: abstract = True - def get_geojson_properties(self): - result = super().get_geojson_properties() - result['space'] = self.space.id + def _serialize(self, space=True, **kwargs): + result = super()._serialize(**kwargs) + if space: + result['space'] = self.space.id return result @@ -36,6 +37,11 @@ class Area(SpecificLocation, SpaceGeometryMixin, models.Model): verbose_name_plural = _('Areas') default_related_name = 'areas' + def _serialize(self, **kwargs): + result = super()._serialize(**kwargs) + result['stuffed'] = self.stuffed + return result + class Stair(SpaceGeometryMixin, models.Model): """ @@ -65,7 +71,6 @@ class Stair(SpaceGeometryMixin, models.Model): ('type', 'shadow'), ('original_type', self.__class__.__name__.lower()), ('original_id', self.id), - ('space', self.space.id), ))), ('geometry', format_geojson(mapping(shadow), round=False)), )) @@ -95,6 +100,19 @@ class LineObstacle(SpaceGeometryMixin, models.Model): verbose_name_plural = _('Line Obstacles') default_related_name = 'lineobstacles' + def serialize(self, geometry=True, **kwargs): + result = super().serialize(geometry=geometry, **kwargs) + if geometry: + result.move_to_end('buffered_geometry') + return result + + def _serialize(self, geometry=True, **kwargs): + result = super()._serialize(geometry=geometry, **kwargs) + result['width'] = float(str(self.width)) + if geometry: + result['buffered_geometry'] = format_geojson(mapping(self.buffered_geometry)) + return result + @property def buffered_geometry(self): return self.geometry.buffer(self.width / 2, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat) @@ -106,12 +124,6 @@ class LineObstacle(SpaceGeometryMixin, models.Model): result['original_geometry'] = original_geometry return result - def get_geojson_properties(self): - result = super().get_geojson_properties() - # noinspection PyTypeChecker - result['width'] = float(self.width) - return result - class Point(SpecificLocation, SpaceGeometryMixin, models.Model): """ diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 44f7fede..953110b5 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import numpy as np from django.core.cache import cache from django.db import models @@ -7,14 +5,25 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language, ungettext_lazy -from c3nav.mapdata.fields import GeometryField, JSONField, validate_bssid_lines +from c3nav.mapdata.fields import JSONField from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.models.base import EditorFormMixin +LOCATION_MODELS = [] + class LocationSlug(models.Model): slug = models.SlugField(_('name'), unique=True, null=True, max_length=50) + def get_child(self): + # todo: cache this + for model in LOCATION_MODELS: + try: + return getattr(self, model._meta.default_related_name) + except AttributeError: + pass + return None + class Meta: verbose_name = _('Slug for Location') verbose_name_plural = _('Slugs für Locations') @@ -25,7 +34,7 @@ class LocationModelMixin: pass -class Location(LocationSlug, models.Model): +class Location(LocationSlug, EditorFormMixin, models.Model): titles = JSONField(default={}) can_search = models.BooleanField(default=True, verbose_name=_('can be searched')) can_describe = models.BooleanField(default=True, verbose_name=_('can be used to describe a position')) @@ -36,10 +45,14 @@ class Location(LocationSlug, models.Model): class Meta: abstract = True - def get_geojson_properties(self): - result = super().get_geojson_properties() - result['slug'] = self.slug_ptr.slug - result['titles'] = OrderedDict(sorted(self.titles.items())) + def _serialize(self, **kwargs): + result = super()._serialize(**kwargs) + result['slug'] = self.slug + result['titles'] = self.titles + result['can_search'] = self.can_search + result['can_describe'] = self.can_search + result['color'] = self.color + result['public'] = self.public return result @property @@ -62,23 +75,10 @@ class SpecificLocation(Location, models.Model): class Meta: abstract = True - -class LegacyLocation: - @property - def location_id(self): - raise NotImplementedError - - @property - def subtitle(self): - raise NotImplementedError - - def to_location_json(self): - return OrderedDict(( - ('id', self.id), - ('location_id', self.location_id), - ('title', str(self.title)), - ('subtitle', str(self.subtitle)), - )) + def _serialize(self, **kwargs): + result = super()._serialize(**kwargs) + result['groups'] = list(self.groups.values_list('id', flat=True)) + return result class LocationGroup(Location, EditorFormMixin, models.Model): @@ -128,12 +128,14 @@ class LocationGroup(Location, EditorFormMixin, models.Model): def __str__(self): return self.title - def get_geojson_properties(self): - result = super().get_geojson_properties() + def _serialize(self, **kwargs): + result = super()._serialize(**kwargs) + result['compiled_room'] = self.compiled_room + result['compiled_area'] = self.compiled_area return result -class PointLocation(LegacyLocation): +class PointLocation: def __init__(self, section: 'Section', x: int, y: int, request): self.section = section self.x = x @@ -158,6 +160,7 @@ class PointLocation(LegacyLocation): not len(set(self.request.c3nav_access_list) & set(point.arealocations))): return _('Unreachable Coordinates'), '' + AreaLocation = None locations = sorted(AreaLocation.objects.filter(name__in=point.arealocations, can_describe=True), key=AreaLocation.get_sort_key, reverse=True) diff --git a/src/c3nav/mapdata/models/section.py b/src/c3nav/mapdata/models/section.py index 29eb12dc..cc9ca726 100644 --- a/src/c3nav/mapdata/models/section.py +++ b/src/c3nav/mapdata/models/section.py @@ -39,8 +39,11 @@ class Section(SpecificLocation, EditorFormMixin, models.Model): def higher(self): return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude') - def __str__(self): - return self.name + def _serialize(self, section=True, **kwargs): + result = super()._serialize(**kwargs) + result['name'] = self.name + result['altitude'] = float(str(self.altitude)) + return result class SectionGeometries(): diff --git a/src/c3nav/mapdata/serializers/main.py b/src/c3nav/mapdata/serializers/main.py index ee080adb..c886987f 100644 --- a/src/c3nav/mapdata/serializers/main.py +++ b/src/c3nav/mapdata/serializers/main.py @@ -5,9 +5,19 @@ from c3nav.mapdata.models.source import Source class SectionSerializer(serializers.ModelSerializer): + titles = serializers.DictField() + class Meta: model = Section - fields = ('id', 'name', 'altitude') + fields = ('id', 'name', 'altitude', 'slug', 'public', 'titles', 'can_search', 'can_search', 'color') + + +class LocationSerializer(serializers.ModelSerializer): + titles = serializers.DictField() + + class Meta: + model = Section + fields = ('id', 'name', 'slug', 'public', 'titles', 'can_search', 'can_search', 'color') class SourceSerializer(serializers.ModelSerializer): diff --git a/src/c3nav/mapdata/utils/json.py b/src/c3nav/mapdata/utils/json.py index 518d6a4e..ad121051 100644 --- a/src/c3nav/mapdata/utils/json.py +++ b/src/c3nav/mapdata/utils/json.py @@ -6,7 +6,7 @@ def _preencode(data, magic_marker, in_coords=False): if isinstance(data, dict): data = data.copy() for name, value in tuple(data.items()): - if name in ('bounds', ): + if name in ('bounds', 'groups'): data[name] = magic_marker+json.dumps(value)+magic_marker else: data[name] = _preencode(value, magic_marker, in_coords=(name == 'coordinates'))