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 c3nav.editor.api import HosterViewSet, SubmitTaskViewSet
from c3nav.mapdata.api import (AreaViewSet, BuildingViewSet, DoorViewSet, LevelViewSet, MapItemTypeViewSet,
MapItemViewSet, ObstacleViewSet, PackageViewSet, SourceViewSet)
from c3nav.mapdata.api import GeometryStyleViewSet, GeometryViewSet, LevelViewSet, PackageViewSet, SourceViewSet
router = SimpleRouter()
router.register(r'levels', LevelViewSet)
router.register(r'packages', PackageViewSet)
router.register(r'levels', LevelViewSet)
router.register(r'sources', SourceViewSet)
router.register(r'mapitemtypes', MapItemTypeViewSet, base_name='mapitemtype')
router.register(r'mapitems', MapItemViewSet, base_name='mapitem')
router.register(r'buildings', BuildingViewSet)
router.register(r'rooms', AreaViewSet)
router.register(r'obstacles', ObstacleViewSet)
router.register(r'doors', DoorViewSet)
router.register(r'geometrystyles', GeometryStyleViewSet, base_name='geometrystyle')
router.register(r'geometries', GeometryViewSet, base_name='geometry')
router.register(r'hosters', HosterViewSet, base_name='hoster')
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:
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()
# 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 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.packageio.write import json_encode
from c3nav.mapdata.permissions import can_access_package
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:
raise Http404()

View file

@ -4,48 +4,59 @@ from collections import OrderedDict
from django.conf import settings
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.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from c3nav.mapdata.models import MAPITEM_TYPES, Level, Package, Source
from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle
from c3nav.mapdata.permissions import PackageAccessMixin, filter_source_queryset
from c3nav.mapdata.serializers.features import (AreaSerializer, BuildingSerializer, DoorSerializer,
MapItemTypeSerializer, ObstacleSerializer)
from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES, Level, Package, Source
from c3nav.mapdata.permissions import filter_queryset_by_package_access
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):
serializer = MapItemTypeSerializer(MAPITEM_TYPES.values(), many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None):
if pk not in MAPITEM_TYPES:
raise Http404
serializer = MapItemTypeSerializer(MAPITEM_TYPES[pk], context={'request': request})
return Response(serializer.data)
styles = OrderedDict()
for mapitemtype in GEOMETRY_MAPITEM_TYPES.values():
styles.update(mapitemtype.get_styles())
return Response(styles)
class MapItemViewSet(ViewSet):
class GeometryViewSet(ViewSet):
"""
List all features.
This endpoint combines the list endpoints for all feature types.
List all geometries.
You can filter by adding one or more level, package, type or name GET parameters.
"""
def list(self, request):
result = OrderedDict()
for name, model in MAPITEM_TYPES.items():
endpoint = model._meta.default_related_name
result[endpoint] = eval(model.__name__+'ViewSet').as_view({'get': 'list'})(request).data
return Response(result)
types = request.GET.getlist('type')
valid_types = list(GEOMETRY_MAPITEM_TYPES.keys())
if not types:
types = valid_types
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):
@ -90,7 +101,7 @@ class SourceViewSet(ReadOnlyModelViewSet):
search_fields = ('name',)
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'])
def image(self, request, name=None):
@ -100,43 +111,3 @@ class SourceViewSet(ReadOnlyModelViewSet):
for chunk in File(open(image_path, 'rb')).chunks():
response.write(chunk)
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 .package import Package # noqa
from .source import Source # noqa

View file

@ -1,21 +1,10 @@
from collections import OrderedDict
from django.db import models
from django.db.models.base import ModelBase
from django.utils.translation import ugettext_lazy as _
MAPITEM_TYPES = OrderedDict()
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):
class MapItem(models.Model):
name = models.SlugField(_('Name'), unique=True, max_length=50)
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.models.base import ModelBase
from django.utils.translation import ugettext_lazy as _
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.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
"""
@ -38,6 +51,27 @@ class GeometryMapItem(MapItem):
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):
result = super().tofile()
result['level'] = self.level.name
@ -85,6 +119,11 @@ class Obstacle(GeometryMapItem):
verbose_name_plural = _('Obstacles')
default_related_name = 'obstacles'
def get_geojson_properties(self):
result = super().get_geojson_properties()
result['height'] = float(self.height)
return result
@classmethod
def fromfile(cls, 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)
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))
@ -24,8 +24,3 @@ class LockedMapFeatures(BasePermission):
if not can_access_package(request, obj.package):
raise PermissionDenied(_('This Source belongs to a package you don\'t have access to.'))
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')