change mapitems API to geometry API.

This commit is contained in:
Laura Klünder 2016-11-27 14:03:39 +01:00
parent a3d0f6dac3
commit 769343f78a
9 changed files with 88 additions and 158 deletions

View file

@ -8,20 +8,15 @@ from rest_framework.response import Response
from rest_framework.routers import SimpleRouter from rest_framework.routers import SimpleRouter
from c3nav.editor.api import HosterViewSet, SubmitTaskViewSet from c3nav.editor.api import HosterViewSet, SubmitTaskViewSet
from c3nav.mapdata.api import (AreaViewSet, BuildingViewSet, DoorViewSet, LevelViewSet, MapItemTypeViewSet, from c3nav.mapdata.api import GeometryStyleViewSet, GeometryViewSet, LevelViewSet, PackageViewSet, SourceViewSet
MapItemViewSet, ObstacleViewSet, PackageViewSet, SourceViewSet)
router = SimpleRouter() router = SimpleRouter()
router.register(r'levels', LevelViewSet)
router.register(r'packages', PackageViewSet) router.register(r'packages', PackageViewSet)
router.register(r'levels', LevelViewSet)
router.register(r'sources', SourceViewSet) router.register(r'sources', SourceViewSet)
router.register(r'mapitemtypes', MapItemTypeViewSet, base_name='mapitemtype') router.register(r'geometrystyles', GeometryStyleViewSet, base_name='geometrystyle')
router.register(r'mapitems', MapItemViewSet, base_name='mapitem') router.register(r'geometries', GeometryViewSet, base_name='geometry')
router.register(r'buildings', BuildingViewSet)
router.register(r'rooms', AreaViewSet)
router.register(r'obstacles', ObstacleViewSet)
router.register(r'doors', DoorViewSet)
router.register(r'hosters', HosterViewSet, base_name='hoster') router.register(r'hosters', HosterViewSet, base_name='hoster')
router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask') router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask')

View file

@ -23,7 +23,7 @@ class FeatureFormMixin(ModelForm):
if not creating and not settings.DIRECT_EDITING: if not creating and not settings.DIRECT_EDITING:
self.fields['name'].disabled = True self.fields['name'].disabled = True
if creating and self._meta.model in (Door, ): if creating and self._meta.model in (Door, Obstacle, ):
self.fields['name'].initial = uuid.uuid4() self.fields['name'].initial = uuid.uuid4()
# restrict package choices and field_name # restrict package choices and field_name

View file

@ -7,14 +7,14 @@ from django.shortcuts import get_object_or_404, render
from django.utils import translation from django.utils import translation
from c3nav.editor.hosters import get_hoster_for_package, hosters from c3nav.editor.hosters import get_hoster_for_package, hosters
from c3nav.mapdata.models import MAPITEM_TYPES from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES
from c3nav.mapdata.models.package import Package from c3nav.mapdata.models.package import Package
from c3nav.mapdata.packageio.write import json_encode from c3nav.mapdata.packageio.write import json_encode
from c3nav.mapdata.permissions import can_access_package from c3nav.mapdata.permissions import can_access_package
def edit_feature(request, feature_type, name=None): def edit_feature(request, feature_type, name=None):
model = MAPITEM_TYPES.get(feature_type) model = GEOMETRY_MAPITEM_TYPES.get(feature_type)
if model is None: if model is None:
raise Http404() raise Http404()

View file

@ -4,48 +4,59 @@ from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.core.files import File from django.core.files import File
from django.http import Http404, HttpResponse from django.http import HttpResponse
from rest_framework.decorators import detail_route from rest_framework.decorators import detail_route
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, ViewSet
from c3nav.mapdata.models import MAPITEM_TYPES, Level, Package, Source from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES, Level, Package, Source
from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle from c3nav.mapdata.permissions import filter_queryset_by_package_access
from c3nav.mapdata.permissions import PackageAccessMixin, filter_source_queryset
from c3nav.mapdata.serializers.features import (AreaSerializer, BuildingSerializer, DoorSerializer,
MapItemTypeSerializer, ObstacleSerializer)
from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
class MapItemTypeViewSet(ViewSet): class GeometryStyleViewSet(ViewSet):
""" """
List and retrieve feature types List all geometry styles.
""" """
lookup_field = 'name'
def list(self, request): def list(self, request):
serializer = MapItemTypeSerializer(MAPITEM_TYPES.values(), many=True, context={'request': request}) styles = OrderedDict()
return Response(serializer.data) for mapitemtype in GEOMETRY_MAPITEM_TYPES.values():
styles.update(mapitemtype.get_styles())
def retrieve(self, request, pk=None): return Response(styles)
if pk not in MAPITEM_TYPES:
raise Http404
serializer = MapItemTypeSerializer(MAPITEM_TYPES[pk], context={'request': request})
return Response(serializer.data)
class MapItemViewSet(ViewSet): class GeometryViewSet(ViewSet):
""" """
List all features. List all geometries.
This endpoint combines the list endpoints for all feature types. You can filter by adding one or more level, package, type or name GET parameters.
""" """
def list(self, request): def list(self, request):
result = OrderedDict() types = request.GET.getlist('type')
for name, model in MAPITEM_TYPES.items(): valid_types = list(GEOMETRY_MAPITEM_TYPES.keys())
endpoint = model._meta.default_related_name if not types:
result[endpoint] = eval(model.__name__+'ViewSet').as_view({'get': 'list'})(request).data types = valid_types
return Response(result) else:
types = [t for t in types if t in valid_types]
levels = request.GET.getlist('levels')
packages = request.GET.getlist('package')
names = request.GET.getlist('name')
results = []
for t in types:
mapitemtype = GEOMETRY_MAPITEM_TYPES[t]
queryset = mapitemtype.objects.all()
if packages:
queryset = queryset.filter(package__name__in=packages)
if levels:
queryset = queryset.filter(level__name__in=levels)
if names:
queryset = queryset.filter(name__in=names)
queryset = filter_queryset_by_package_access(request, queryset)
queryset.prefetch_related('package', 'level').order_by('name')
results.extend(sum((obj.to_geojson() for obj in queryset), []))
return Response(results)
class PackageViewSet(ReadOnlyModelViewSet): class PackageViewSet(ReadOnlyModelViewSet):
@ -90,7 +101,7 @@ class SourceViewSet(ReadOnlyModelViewSet):
search_fields = ('name',) search_fields = ('name',)
def get_queryset(self): def get_queryset(self):
return filter_source_queryset(self.request, super().get_queryset()) return filter_queryset_by_package_access(self.request, super().get_queryset())
@detail_route(methods=['get']) @detail_route(methods=['get'])
def image(self, request, name=None): def image(self, request, name=None):
@ -100,43 +111,3 @@ class SourceViewSet(ReadOnlyModelViewSet):
for chunk in File(open(image_path, 'rb')).chunks(): for chunk in File(open(image_path, 'rb')).chunks():
response.write(chunk) response.write(chunk)
return response return response
class BuildingViewSet(PackageAccessMixin, ReadOnlyModelViewSet):
"""
List and retrieve Inside Areas
"""
queryset = Building.objects.all()
serializer_class = BuildingSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
class AreaViewSet(PackageAccessMixin, ReadOnlyModelViewSet):
"""
List and retrieve Areas
"""
queryset = Area.objects.all()
serializer_class = AreaSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
class ObstacleViewSet(PackageAccessMixin, ReadOnlyModelViewSet):
"""
List and retrieve Obstcales
"""
queryset = Obstacle.objects.all()
serializer_class = ObstacleSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
class DoorViewSet(PackageAccessMixin, ReadOnlyModelViewSet):
"""
List and retrieve Doors
"""
queryset = Door.objects.all()
serializer_class = DoorSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'

View file

@ -1,4 +1,4 @@
from .base import MAPITEM_TYPES # noqa from c3nav.mapdata.models.geometry import GEOMETRY_MAPITEM_TYPES # noqa
from .level import Level # noqa from .level import Level # noqa
from .package import Package # noqa from .package import Package # noqa
from .source import Source # noqa from .source import Source # noqa

View file

@ -1,21 +1,10 @@
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
from django.db.models.base import ModelBase
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
MAPITEM_TYPES = OrderedDict()
class MapItem(models.Model):
class MapItemMeta(ModelBase):
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
if not cls._meta.abstract:
MAPITEM_TYPES[name.lower()] = cls
return cls
class MapItem(models.Model, metaclass=MapItemMeta):
name = models.SlugField(_('Name'), unique=True, max_length=50) name = models.SlugField(_('Name'), unique=True, max_length=50)
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, verbose_name=_('map package')) package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, verbose_name=_('map package'))

View file

@ -1,4 +1,7 @@
from collections import OrderedDict
from django.db import models from django.db import models
from django.db.models.base import ModelBase
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from shapely.geometry.geo import mapping, shape from shapely.geometry.geo import mapping, shape
@ -6,8 +9,18 @@ from c3nav.mapdata.fields import GeometryField
from c3nav.mapdata.models.base import MapItem from c3nav.mapdata.models.base import MapItem
from c3nav.mapdata.utils import format_geojson from c3nav.mapdata.utils import format_geojson
GEOMETRY_MAPITEM_TYPES = OrderedDict()
class GeometryMapItem(MapItem):
class GeometryMapItemMeta(ModelBase):
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
if not cls._meta.abstract:
GEOMETRY_MAPITEM_TYPES[name.lower()] = cls
return cls
class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
""" """
A map feature A map feature
""" """
@ -38,6 +51,27 @@ class GeometryMapItem(MapItem):
return kwargs return kwargs
@classmethod
def get_styles(cls):
return {
cls.__name__.lower(): cls.color
}
def get_geojson_properties(self):
return OrderedDict((
('type', self.__class__.__name__.lower()),
('name', self.name),
('package', self.package.name),
('level', self.level.name),
))
def to_geojson(self):
return [OrderedDict((
('type', 'Feature'),
('properties', self.get_geojson_properties()),
('geometry', format_geojson(mapping(self.geometry), round=False)),
))]
def tofile(self): def tofile(self):
result = super().tofile() result = super().tofile()
result['level'] = self.level.name result['level'] = self.level.name
@ -85,6 +119,11 @@ class Obstacle(GeometryMapItem):
verbose_name_plural = _('Obstacles') verbose_name_plural = _('Obstacles')
default_related_name = 'obstacles' default_related_name = 'obstacles'
def get_geojson_properties(self):
result = super().get_geojson_properties()
result['height'] = float(self.height)
return result
@classmethod @classmethod
def fromfile(cls, data, file_path): def fromfile(cls, data, file_path):
kwargs = super().fromfile(data, file_path) kwargs = super().fromfile(data, file_path)

View file

@ -14,7 +14,7 @@ def can_access_package(request, package):
return settings.DEBUG or package.name in get_unlocked_packages(request) return settings.DEBUG or package.name in get_unlocked_packages(request)
def filter_source_queryset(request, queryset): def filter_queryset_by_package_access(request, queryset):
return queryset if settings.DIRECT_EDITING else queryset.filter(package__name__in=get_unlocked_packages(request)) return queryset if settings.DIRECT_EDITING else queryset.filter(package__name__in=get_unlocked_packages(request))
@ -24,8 +24,3 @@ class LockedMapFeatures(BasePermission):
if not can_access_package(request, obj.package): if not can_access_package(request, obj.package):
raise PermissionDenied(_('This Source belongs to a package you don\'t have access to.')) raise PermissionDenied(_('This Source belongs to a package you don\'t have access to.'))
return True return True
class PackageAccessMixin:
def get_queryset(self):
return filter_source_queryset(self.request, super().get_queryset())

View file

@ -1,59 +0,0 @@
from rest_framework import serializers
from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle
from c3nav.mapdata.serializers.fields import GeometryField
class MapItemTypeSerializer(serializers.Serializer):
name = serializers.SerializerMethodField()
title = serializers.SerializerMethodField()
title_plural = serializers.SerializerMethodField()
endpoint = serializers.SerializerMethodField()
description = serializers.SerializerMethodField()
geomtype = serializers.CharField()
color = serializers.CharField()
def get_name(self, obj):
return obj.__name__.lower()
def get_title(self, obj):
return str(obj._meta.verbose_name)
def get_title_plural(self, obj):
return str(obj._meta.verbose_name_plural)
def get_endpoint(self, obj):
return obj._meta.default_related_name
def get_description(self, obj):
return str(obj.__doc__.strip())
class MapItemSerializer(serializers.ModelSerializer):
level = serializers.SlugRelatedField(slug_field='name', read_only=True)
package = serializers.SlugRelatedField(slug_field='name', read_only=True)
geometry = GeometryField()
class BuildingSerializer(MapItemSerializer):
class Meta:
model = Building
fields = ('name', 'level', 'package', 'geometry')
class AreaSerializer(MapItemSerializer):
class Meta:
model = Area
fields = ('name', 'level', 'package', 'geometry')
class ObstacleSerializer(MapItemSerializer):
class Meta:
model = Obstacle
fields = ('name', 'level', 'package', 'geometry', 'height')
class DoorSerializer(MapItemSerializer):
class Meta:
model = Door
fields = ('name', 'level', 'package', 'geometry')