Refactoring Mapdata Models: MapItem and GeometryMapItem

This commit is contained in:
Laura Klünder 2016-11-14 21:15:20 +01:00
parent 91d898f240
commit bc0b5521ce
14 changed files with 186 additions and 192 deletions

View file

@ -8,18 +8,17 @@ from rest_framework.response import Response
from rest_framework.routers import SimpleRouter
from c3nav.editor.api import HosterViewSet, SubmitTaskViewSet
from c3nav.mapdata.api.features import (AreaViewSet, BuildingViewSet, DoorViewSet, FeatureTypeViewSet, FeatureViewSet,
ObstacleViewSet)
from c3nav.mapdata.api.main import LevelViewSet, PackageViewSet, SourceViewSet
from c3nav.mapdata.api import (AreaViewSet, BuildingViewSet, DoorViewSet, LevelViewSet, MapItemTypeViewSet,
MapItemViewSet, ObstacleViewSet, PackageViewSet, SourceViewSet)
router = SimpleRouter()
router.register(r'levels', LevelViewSet)
router.register(r'packages', PackageViewSet)
router.register(r'sources', SourceViewSet)
router.register(r'featuretypes', FeatureTypeViewSet, base_name='featuretype')
router.register(r'features', FeatureViewSet, base_name='features')
router.register(r'insides', BuildingViewSet)
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)

View file

@ -9,7 +9,7 @@ from django.forms.widgets import HiddenInput
from shapely.geometry.geo import mapping
from c3nav.mapdata.models import Package
from c3nav.mapdata.models.features import Area, Building, Door, Obstacle
from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle
from c3nav.mapdata.permissions import get_unlocked_packages

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.features import FEATURE_TYPES
from c3nav.mapdata.models import 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 = FEATURE_TYPES.get(feature_type)
model = MAPITEM_TYPES.get(feature_type)
if model is None:
raise Http404()

142
src/c3nav/mapdata/api.py Normal file
View file

@ -0,0 +1,142 @@
import mimetypes
import os
from collections import OrderedDict
from django.conf import settings
from django.core.files import File
from django.http import Http404, 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.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
class MapItemTypeViewSet(ViewSet):
"""
List and retrieve feature types
"""
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)
class MapItemViewSet(ViewSet):
"""
List all features.
This endpoint combines the list endpoints for all feature types.
"""
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)
class PackageViewSet(ReadOnlyModelViewSet):
"""
Retrieve packages the map consists of.
"""
queryset = Package.objects.all()
serializer_class = PackageSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
filter_fields = ('name', 'depends')
ordering_fields = ('name',)
ordering = ('name',)
search_fields = ('name',)
class LevelViewSet(ReadOnlyModelViewSet):
"""
List and retrieve levels.
"""
queryset = Level.objects.all()
serializer_class = LevelSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
filter_fields = ('altitude', 'package')
ordering_fields = ('altitude', 'package')
ordering = ('altitude',)
search_fields = ('name',)
class SourceViewSet(ReadOnlyModelViewSet):
"""
List and retrieve source images (to use as a drafts).
"""
queryset = Source.objects.all()
serializer_class = SourceSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
filter_fields = ('package',)
ordering_fields = ('name', 'package')
ordering = ('name',)
search_fields = ('name',)
def get_queryset(self):
return filter_source_queryset(self.request, super().get_queryset())
@detail_route(methods=['get'])
def image(self, request, name=None):
source = self.get_object()
response = HttpResponse(content_type=mimetypes.guess_type(source.name)[0])
image_path = os.path.join(settings.MAP_ROOT, source.package.directory, 'sources', source.name)
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,82 +0,0 @@
from collections import OrderedDict
from django.http import Http404
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from c3nav.mapdata.models import FEATURE_TYPES
from c3nav.mapdata.models.features import Area, Building, Door, Obstacle
from c3nav.mapdata.permissions import PackageAccessMixin
from c3nav.mapdata.serializers.features import (AreaSerializer, BuildingSerializer, DoorSerializer,
FeatureTypeSerializer, ObstacleSerializer)
class FeatureTypeViewSet(ViewSet):
"""
List and retrieve feature types
"""
lookup_field = 'name'
def list(self, request):
serializer = FeatureTypeSerializer(FEATURE_TYPES.values(), many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None):
if pk not in FEATURE_TYPES:
raise Http404
serializer = FeatureTypeSerializer(FEATURE_TYPES[pk], context={'request': request})
return Response(serializer.data)
class FeatureViewSet(ViewSet):
"""
List all features.
This endpoint combines the list endpoints for all feature types.
"""
def list(self, request):
result = OrderedDict()
for name, model in FEATURE_TYPES.items():
endpoint = model._meta.default_related_name
result[endpoint] = eval(model.__name__+'ViewSet').as_view({'get': 'list'})(request).data
return Response(result)
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,66 +0,0 @@
import mimetypes
import os
from django.conf import settings
from django.core.files import File
from django.http import HttpResponse
from rest_framework.decorators import detail_route
from rest_framework.viewsets import ReadOnlyModelViewSet
from c3nav.mapdata.models import Level, Package, Source
from c3nav.mapdata.permissions import filter_source_queryset
from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
class LevelViewSet(ReadOnlyModelViewSet):
"""
List and retrieve levels.
"""
queryset = Level.objects.all()
serializer_class = LevelSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
filter_fields = ('altitude', 'package')
ordering_fields = ('altitude', 'package')
ordering = ('altitude',)
search_fields = ('name',)
class PackageViewSet(ReadOnlyModelViewSet):
"""
Retrieve packages the map consists of.
"""
queryset = Package.objects.all()
serializer_class = PackageSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
filter_fields = ('name', 'depends')
ordering_fields = ('name',)
ordering = ('name',)
search_fields = ('name',)
class SourceViewSet(ReadOnlyModelViewSet):
"""
List and retrieve source images (to use as a drafts).
"""
queryset = Source.objects.all()
serializer_class = SourceSerializer
lookup_field = 'name'
lookup_value_regex = '[^/]+'
filter_fields = ('package',)
ordering_fields = ('name', 'package')
ordering = ('name',)
search_fields = ('name',)
def get_queryset(self):
return filter_source_queryset(self.request, super().get_queryset())
@detail_route(methods=['get'])
def image(self, request, name=None):
source = self.get_object()
response = HttpResponse(content_type=mimetypes.guess_type(source.name)[0])
image_path = os.path.join(settings.MAP_ROOT, source.package.directory, 'sources', source.name)
for chunk in File(open(image_path, 'rb')).chunks():
response.write(chunk)
return response

View file

@ -1,4 +1,5 @@
from .features import Feature, FEATURE_TYPES # noqa
from .base import MAPITEM_TYPES # noqa
from .level import Level # noqa
from .package import Package # noqa
from .source import Source # noqa
from .geometry import GeometryMapItem # noqa

View file

@ -1,13 +1,28 @@
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 MapdataModel(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)
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, verbose_name=_('map package'))
EditorForm = None
geomtype = None
color = None
@classmethod
def get_path_prefix(cls):
return cls._meta.default_related_name + '/'

View file

@ -1,30 +1,19 @@
from collections import OrderedDict
from django.db import models
from django.utils.translation import ugettext_lazy as _
from shapely.geometry.geo import mapping, shape
from c3nav.mapdata.fields import GeometryField
from c3nav.mapdata.models.base import MapdataModel
from c3nav.mapdata.models.base import MapItem
from c3nav.mapdata.utils import format_geojson
FEATURE_TYPES = OrderedDict()
def register_featuretype(cls):
FEATURE_TYPES[cls.__name__.lower()] = cls
return cls
class Feature(MapdataModel):
class GeometryMapItem(MapItem):
"""
A map feature
"""
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'))
geometry = GeometryField()
EditorForm = None
class Meta:
abstract = True
@ -56,8 +45,7 @@ class Feature(MapdataModel):
return result
@register_featuretype
class Building(Feature):
class Building(GeometryMapItem):
"""
The outline of a building on a specific level
"""
@ -70,8 +58,7 @@ class Building(Feature):
default_related_name = 'buildings'
@register_featuretype
class Area(Feature):
class Area(GeometryMapItem):
"""
An accessible area like a room. Can also be outside. Can overlap.
"""
@ -84,8 +71,7 @@ class Area(Feature):
default_related_name = 'areas'
@register_featuretype
class Obstacle(Feature):
class Obstacle(GeometryMapItem):
"""
An obstacle
"""
@ -117,8 +103,7 @@ class Obstacle(Feature):
return result
@register_featuretype
class Door(Feature):
class Door(GeometryMapItem):
"""
A connection between two rooms
"""

View file

@ -1,10 +1,10 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from c3nav.mapdata.models.base import MapdataModel
from c3nav.mapdata.models.base import MapItem
class Level(MapdataModel):
class Level(MapItem):
"""
A map level (-1, 0, 1, 2)
"""

View file

@ -1,10 +1,10 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from c3nav.mapdata.models.base import MapdataModel
from c3nav.mapdata.models.base import MapItem
class Source(MapdataModel):
class Source(MapItem):
"""
A map source, images of levels that can be useful as backgrounds for the map editor
"""

View file

@ -1,4 +1,4 @@
from c3nav.mapdata.models import Level, Package, Source
from c3nav.mapdata.models.features import Area, Building
from c3nav.mapdata.models.geometry import Area, Building
ordered_models = (Package, Level, Source, Building, Area)

View file

@ -1,10 +1,10 @@
from rest_framework import serializers
from c3nav.mapdata.models.features import Area, Building, Door, Obstacle
from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle
from c3nav.mapdata.serializers.fields import GeometryField
class FeatureTypeSerializer(serializers.Serializer):
class MapItemTypeSerializer(serializers.Serializer):
name = serializers.SerializerMethodField()
title = serializers.SerializerMethodField()
title_plural = serializers.SerializerMethodField()
@ -29,31 +29,31 @@ class FeatureTypeSerializer(serializers.Serializer):
return str(obj.__doc__.strip())
class FeatureSerializer(serializers.ModelSerializer):
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(FeatureSerializer):
class BuildingSerializer(MapItemSerializer):
class Meta:
model = Building
fields = ('name', 'level', 'package', 'geometry')
class AreaSerializer(FeatureSerializer):
class AreaSerializer(MapItemSerializer):
class Meta:
model = Area
fields = ('name', 'level', 'package', 'geometry')
class ObstacleSerializer(FeatureSerializer):
class ObstacleSerializer(MapItemSerializer):
class Meta:
model = Obstacle
fields = ('name', 'level', 'package', 'geometry', 'height')
class DoorSerializer(FeatureSerializer):
class DoorSerializer(MapItemSerializer):
class Meta:
model = Door
fields = ('name', 'level', 'package', 'geometry')