new REST API

This commit is contained in:
Laura Klünder 2017-05-11 19:36:49 +02:00
parent ac29041a3c
commit 6bbac4a7ba
12 changed files with 265 additions and 205 deletions

View file

@ -7,16 +7,25 @@ from rest_framework.generics import GenericAPIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.routers import SimpleRouter 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 = SimpleRouter()
router.register(r'sections', SectionViewSet) 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'sources', SourceViewSet)
router.register(r'geometrytypes', GeometryTypeViewSet, base_name='geometrytype') router.register(r'locations', LocationViewSet)
router.register(r'geometries', GeometryViewSet, base_name='geometry') router.register(r'locationgroups', LocationGroupViewSet)
router.register(r'locations', LocationViewSet, base_name='location')
class APIRoot(GenericAPIView): class APIRoot(GenericAPIView):
@ -32,7 +41,7 @@ class APIRoot(GenericAPIView):
urls = OrderedDict() urls = OrderedDict()
for urlpattern in router.urls: for urlpattern in router.urls:
name = urlpattern.name 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] base = url.split('/', 1)[0]
if '-' in name: if '-' in name:
urls.setdefault(base, OrderedDict())[name.split('-', 1)[1]] = url urls.setdefault(base, OrderedDict())[name.split('-', 1)[1]] = url

View file

@ -1,165 +1,167 @@
import hashlib
import json
import mimetypes import mimetypes
from collections import OrderedDict 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.decorators import detail_route, list_route
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.response import Response 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.access.apply import filter_queryset_by_access
from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.models import Building, Door, Hole, LocationGroup, Source, Space
from c3nav.mapdata.models import LocationGroup, Source from c3nav.mapdata.models.geometry.section import SECTION_MODELS
from c3nav.mapdata.models.geometry.base import GEOMETRY_MODELS from c3nav.mapdata.models.geometry.space import SPACE_MODELS, Area, LineObstacle, Obstacle, Point, Stair
from c3nav.mapdata.models.geometry.space import Stair from c3nav.mapdata.models.locations import LocationSlug
from c3nav.mapdata.models.section import Section from c3nav.mapdata.models.section import Section
from c3nav.mapdata.search import get_location from c3nav.mapdata.serializers.main import SourceSerializer
from c3nav.mapdata.serializers.main import SectionSerializer, SourceSerializer from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin
from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_bssid_areas_cached
class GeometryTypeViewSet(ViewSet): class MapdataViewSet(ReadOnlyModelViewSet):
"""
Lists all geometry types.
"""
@cache_mapdata_api_response()
def list(self, request): 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([ return Response([
OrderedDict(( OrderedDict((
('name', name), ('name', model.__name__.lower()),
('title', str(mapitemtype._meta.verbose_name)), ('name_plural', model._meta.default_related_name),
('title_plural', str(mapitemtype._meta.verbose_name_plural)), ('geomtype', model._meta.get_field('geometry').geomtype),
)) for name, mapitemtype in GEOMETRY_MODELS.items() ('title', str(model._meta.verbose_name)),
('title_plural', str(model._meta.verbose_name_plural)),
)) for model in models_list
]) ])
class GeometryViewSet(ViewSet): class SectionViewSet(MapdataViewSet):
"""
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.
"""
queryset = Section.objects.all() 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=<id> to filter by section. """
queryset = Building.objects.all()
class SpaceViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?section=<id> 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=<id> to filter by section. """
queryset = Door.objects.all()
class HoleViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?section=<id> to filter by section. """
queryset = Hole.objects.all()
class AreaViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?space=<id> to filter by space. """
queryset = Area.objects.all()
class StairViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?space=<id> to filter by space. """
queryset = Stair.objects.all()
class ObstacleViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?space=<id> to filter by space. """
queryset = Obstacle.objects.all()
class LineObstacleViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?space=<id> to filter by space. """
queryset = LineObstacle.objects.all()
class PointViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?space=<id> 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): class SourceViewSet(CachedReadOnlyViewSetMixin, ReadOnlyModelViewSet):
"""
List and retrieve source images (to use as a drafts).
"""
queryset = Source.objects.all() queryset = Source.objects.all()
serializer_class = SourceSerializer serializer_class = SourceSerializer
lookup_field = 'id'
def get_queryset(self): def get_queryset(self):
return filter_queryset_by_access(self.request, super().get_queryset().all()) return filter_queryset_by_access(self.request, super().get_queryset().all())
@detail_route(methods=['get']) @detail_route(methods=['get'])
def image(self, request, name=None): def image(self, request, pk=None):
return self._image(request, name=name, add_cache_key=self._get_add_cache_key(request)) return self._image(request, pk=pk)
@cache_mapdata_api_response() def _image(self, request, pk=None):
def _image(self, request, name=None):
source = self.get_object() source = self.get_object()
response = HttpResponse(content_type=mimetypes.guess_type(source.name)[0]) response = HttpResponse(content_type=mimetypes.guess_type(source.name)[0])
response.write(source.image) response.write(source.image)
return response 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})

View file

@ -34,10 +34,11 @@ class MapdataConfig(AppConfig):
if geometry is None: if geometry is None:
raise TypeError(_('Model %s has GeometryMixin as base class but has no geometry field.') % cls) 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 from c3nav.mapdata.models.geometry.section import SectionGeometryMixin, SECTION_MODELS
for cls in self._get_submodels(SectionGeometryMixin): SECTION_MODELS.extend(self._get_submodels(SectionGeometryMixin))
SECTION_MODELS[cls.__name__] = cls
from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin, SPACE_MODELS from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin, SPACE_MODELS
for cls in self._get_submodels(SpaceGeometryMixin): SPACE_MODELS.extend(self._get_submodels(SpaceGeometryMixin))
SPACE_MODELS[cls.__name__] = cls

View file

@ -2,4 +2,4 @@ from .section import Section # noqa
from .source import Source # noqa from .source import Source # noqa
from c3nav.mapdata.models.geometry.section import Building, Space, Hole, Door # 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 c3nav.mapdata.models.geometry.space import Area, Stair, Obstacle, LineObstacle # noqa
from .locations import Location, LocationGroup # noqa from .locations import Location, LocationSlug, LocationGroup # noqa

View file

@ -10,3 +10,13 @@ class EditorFormMixin(models.Model):
class Meta: class Meta:
abstract = True 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

View file

@ -30,6 +30,18 @@ class GeometryMixin(EditorFormMixin):
('geometry', format_geojson(mapping(self.geometry), round=False)), ('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): def get_shadow_geojson(self):
pass pass

View file

@ -1,5 +1,3 @@
from collections import OrderedDict
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ 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.geometry.base import GeometryMixin
from c3nav.mapdata.models.locations import SpecificLocation from c3nav.mapdata.models.locations import SpecificLocation
SECTION_MODELS = OrderedDict() SECTION_MODELS = []
class SectionGeometryMixin(GeometryMixin): class SectionGeometryMixin(GeometryMixin):
@ -16,8 +14,14 @@ class SectionGeometryMixin(GeometryMixin):
class Meta: class Meta:
abstract = True abstract = True
def get_geojson_properties(self): def get_geojson_properties(self) -> dict:
result = super().get_geojson_properties() result = super().get_geojson_properties()
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 result['section'] = self.section.id
return result return result
@ -33,11 +37,6 @@ class LevelSectionGeometryMixin(SectionGeometryMixin):
class Meta: class Meta:
abstract = True abstract = True
def get_geojson_properties(self):
result = super().get_geojson_properties()
result['level'] = self.level
return result
class Building(SectionGeometryMixin, models.Model): 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. An accessible space. Shouldn't overlap with spaces on same secion and level.
""" """
CATEGORIES = ( CATEGORIES = (
('', _('normal')), ('', _('normal')),
('stairs', _('stairs')), ('stairs', _('stairs')),
@ -63,7 +61,6 @@ class Space(SpecificLocation, LevelSectionGeometryMixin, models.Model):
('elevator', _('elevator')), ('elevator', _('elevator')),
) )
geometry = GeometryField('polygon') geometry = GeometryField('polygon')
public = models.BooleanField(verbose_name=_('public'), default=True)
category = models.CharField(verbose_name=_('category'), choices=CATEGORIES, default='', max_length=16) category = models.CharField(verbose_name=_('category'), choices=CATEGORIES, default='', max_length=16)
class Meta: class Meta:
@ -71,8 +68,9 @@ class Space(SpecificLocation, LevelSectionGeometryMixin, models.Model):
verbose_name_plural = _('Spaces') verbose_name_plural = _('Spaces')
default_related_name = 'spaces' default_related_name = 'spaces'
def get_geojson_properties(self): def _serialize(self, space=True, **kwargs):
result = super().get_geojson_properties() result = super()._serialize(**kwargs)
if space:
result['category'] = self.category result['category'] = self.category
result['level'] = self.level result['level'] = self.level
result['public'] = self.public result['public'] = self.public

View file

@ -9,7 +9,7 @@ from c3nav.mapdata.models.geometry.base import GeometryMixin
from c3nav.mapdata.models.locations import SpecificLocation from c3nav.mapdata.models.locations import SpecificLocation
from c3nav.mapdata.utils.json import format_geojson from c3nav.mapdata.utils.json import format_geojson
SPACE_MODELS = OrderedDict() SPACE_MODELS = []
class SpaceGeometryMixin(GeometryMixin): class SpaceGeometryMixin(GeometryMixin):
@ -18,8 +18,9 @@ class SpaceGeometryMixin(GeometryMixin):
class Meta: class Meta:
abstract = True abstract = True
def get_geojson_properties(self): def _serialize(self, space=True, **kwargs):
result = super().get_geojson_properties() result = super()._serialize(**kwargs)
if space:
result['space'] = self.space.id result['space'] = self.space.id
return result return result
@ -36,6 +37,11 @@ class Area(SpecificLocation, SpaceGeometryMixin, models.Model):
verbose_name_plural = _('Areas') verbose_name_plural = _('Areas')
default_related_name = 'areas' default_related_name = 'areas'
def _serialize(self, **kwargs):
result = super()._serialize(**kwargs)
result['stuffed'] = self.stuffed
return result
class Stair(SpaceGeometryMixin, models.Model): class Stair(SpaceGeometryMixin, models.Model):
""" """
@ -65,7 +71,6 @@ class Stair(SpaceGeometryMixin, models.Model):
('type', 'shadow'), ('type', 'shadow'),
('original_type', self.__class__.__name__.lower()), ('original_type', self.__class__.__name__.lower()),
('original_id', self.id), ('original_id', self.id),
('space', self.space.id),
))), ))),
('geometry', format_geojson(mapping(shadow), round=False)), ('geometry', format_geojson(mapping(shadow), round=False)),
)) ))
@ -95,6 +100,19 @@ class LineObstacle(SpaceGeometryMixin, models.Model):
verbose_name_plural = _('Line Obstacles') verbose_name_plural = _('Line Obstacles')
default_related_name = 'lineobstacles' 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 @property
def buffered_geometry(self): def buffered_geometry(self):
return self.geometry.buffer(self.width / 2, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat) 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 result['original_geometry'] = original_geometry
return result 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): class Point(SpecificLocation, SpaceGeometryMixin, models.Model):
""" """

View file

@ -1,5 +1,3 @@
from collections import OrderedDict
import numpy as np import numpy as np
from django.core.cache import cache from django.core.cache import cache
from django.db import models 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 ugettext_lazy as _
from django.utils.translation import get_language, ungettext_lazy 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.lastupdate import get_last_mapdata_update
from c3nav.mapdata.models.base import EditorFormMixin from c3nav.mapdata.models.base import EditorFormMixin
LOCATION_MODELS = []
class LocationSlug(models.Model): class LocationSlug(models.Model):
slug = models.SlugField(_('name'), unique=True, null=True, max_length=50) 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: class Meta:
verbose_name = _('Slug for Location') verbose_name = _('Slug for Location')
verbose_name_plural = _('Slugs für Locations') verbose_name_plural = _('Slugs für Locations')
@ -25,7 +34,7 @@ class LocationModelMixin:
pass pass
class Location(LocationSlug, models.Model): class Location(LocationSlug, EditorFormMixin, models.Model):
titles = JSONField(default={}) titles = JSONField(default={})
can_search = models.BooleanField(default=True, verbose_name=_('can be searched')) 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')) 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: class Meta:
abstract = True abstract = True
def get_geojson_properties(self): def _serialize(self, **kwargs):
result = super().get_geojson_properties() result = super()._serialize(**kwargs)
result['slug'] = self.slug_ptr.slug result['slug'] = self.slug
result['titles'] = OrderedDict(sorted(self.titles.items())) 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 return result
@property @property
@ -62,23 +75,10 @@ class SpecificLocation(Location, models.Model):
class Meta: class Meta:
abstract = True abstract = True
def _serialize(self, **kwargs):
class LegacyLocation: result = super()._serialize(**kwargs)
@property result['groups'] = list(self.groups.values_list('id', flat=True))
def location_id(self): return result
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)),
))
class LocationGroup(Location, EditorFormMixin, models.Model): class LocationGroup(Location, EditorFormMixin, models.Model):
@ -128,12 +128,14 @@ class LocationGroup(Location, EditorFormMixin, models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
def get_geojson_properties(self): def _serialize(self, **kwargs):
result = super().get_geojson_properties() result = super()._serialize(**kwargs)
result['compiled_room'] = self.compiled_room
result['compiled_area'] = self.compiled_area
return result return result
class PointLocation(LegacyLocation): class PointLocation:
def __init__(self, section: 'Section', x: int, y: int, request): def __init__(self, section: 'Section', x: int, y: int, request):
self.section = section self.section = section
self.x = x self.x = x
@ -158,6 +160,7 @@ class PointLocation(LegacyLocation):
not len(set(self.request.c3nav_access_list) & set(point.arealocations))): not len(set(self.request.c3nav_access_list) & set(point.arealocations))):
return _('Unreachable Coordinates'), '' return _('Unreachable Coordinates'), ''
AreaLocation = None
locations = sorted(AreaLocation.objects.filter(name__in=point.arealocations, can_describe=True), locations = sorted(AreaLocation.objects.filter(name__in=point.arealocations, can_describe=True),
key=AreaLocation.get_sort_key, reverse=True) key=AreaLocation.get_sort_key, reverse=True)

View file

@ -39,8 +39,11 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
def higher(self): def higher(self):
return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude') return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude')
def __str__(self): def _serialize(self, section=True, **kwargs):
return self.name result = super()._serialize(**kwargs)
result['name'] = self.name
result['altitude'] = float(str(self.altitude))
return result
class SectionGeometries(): class SectionGeometries():

View file

@ -5,9 +5,19 @@ from c3nav.mapdata.models.source import Source
class SectionSerializer(serializers.ModelSerializer): class SectionSerializer(serializers.ModelSerializer):
titles = serializers.DictField()
class Meta: class Meta:
model = Section 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): class SourceSerializer(serializers.ModelSerializer):

View file

@ -6,7 +6,7 @@ def _preencode(data, magic_marker, in_coords=False):
if isinstance(data, dict): if isinstance(data, dict):
data = data.copy() data = data.copy()
for name, value in tuple(data.items()): 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 data[name] = magic_marker+json.dumps(value)+magic_marker
else: else:
data[name] = _preencode(value, magic_marker, in_coords=(name == 'coordinates')) data[name] = _preencode(value, magic_marker, in_coords=(name == 'coordinates'))