diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/c3nav/api/urls.py b/src/c3nav/api/urls.py index 81f228f4..153e3712 100644 --- a/src/c3nav/api/urls.py +++ b/src/c3nav/api/urls.py @@ -8,14 +8,19 @@ from rest_framework.response import Response from rest_framework.routers import SimpleRouter from c3nav.editor.api import HosterViewSet, SubmitTaskViewSet -from c3nav.mapdata.api import FeatureTypeViewSet, FeatureViewSet, LevelViewSet, PackageViewSet, SourceViewSet +from c3nav.mapdata.api.features import FeatureTypeViewSet, FeatureViewSet, InsideViewSet, RoomViewSet +from c3nav.mapdata.api.main import LevelViewSet, 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='feature') +router.register(r'features', FeatureViewSet, base_name='features') +router.register(r'insides', InsideViewSet) +router.register(r'rooms', RoomViewSet) + router.register(r'hosters', HosterViewSet, base_name='hoster') router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask') diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index 2fbb19e3..ce740156 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -186,41 +186,43 @@ editor = { features: {}, get_features: function () { - $.getJSON('/api/features/', function(features) { - var feature_type; - for (var level in editor.levels) { - for (var j = 0; j < editor.feature_types_order.length; j++) { - feature_type = editor.feature_types_order[j]; + $.getJSON('/api/features/', function(all_features) { + $('.feature_level_list li').remove(); + var feature_type, features, feature, layergroup; + for (var j = 0; j < editor.feature_types_order.length; j++) { + feature_type = editor.feature_types_order[j]; + for (var level in editor.levels) { editor.level_feature_layers[level][feature_type].clearLayers(); } - } - $('.feature_level_list li').remove(); - var feature, layergroup; - for (var i=0; i < features.length; i++) { - feature = features[i]; - layergroup = L.geoJSON({ - type: 'Feature', - geometry: feature.geometry, - properties: { - name: feature.name, - feature_type: feature.feature_type - } - }, { - style: editor._get_feature_style - }).on('mouseover', editor._hover_feature_layer) - .on('mouseout', editor._unhover_feature_layer) - .on('click', editor._click_feature_layer) - .addTo(editor.level_feature_layers[feature.level][feature.feature_type]); - feature.layer = layergroup.getLayers()[0]; - editor.features[feature.name] = feature; + features = all_features[editor.feature_types[feature_type].endpoint] - $('.feature_list[name='+feature.feature_type+'] > [data-level='+feature.level+']').append( - $('
  • ').attr('name', feature.name).append( - $('

    ').text(feature.title).append(' ').append( - $('').text(feature.name) + for (var i = 0; i < features.length; i++) { + feature = features[i]; + console.log(feature); + layergroup = L.geoJSON({ + type: 'Feature', + geometry: feature.geometry, + properties: { + name: feature.name, + feature_type: feature_type + } + }, { + style: editor._get_feature_style + }).on('mouseover', editor._hover_feature_layer) + .on('mouseout', editor._unhover_feature_layer) + .on('click', editor._click_feature_layer) + .addTo(editor.level_feature_layers[feature.level][feature_type]); + feature.layer = layergroup.getLayers()[0]; + editor.features[feature.name] = feature; + + $('.feature_list[name=' + feature_type + '] > [data-level=' + feature.level + ']').append( + $('

  • ').attr('name', feature.name).append( + $('

    ').text(feature.title).append(' ').append( + $('').text(feature.name) + ) ) - ) - ); + ); + } } $('.start-drawing').show(); $('#mapeditcontrols').addClass('list'); @@ -319,7 +321,8 @@ editor = { editor.map.fitBounds(editor._editing.getBounds()); $('.leaflet-drawbar').hide(); - var path = '/editor/features/' + editor._creating + '/add/'; + var endpoint = editor.feature_types[editor._creating].endpoint; + var path = '/editor/' + endpoint + '/add/'; $('#mapeditcontrols').removeClass('list'); $('body').addClass('controls'); $('#mapeditdetail').load(path, editor.edit_form_loaded); @@ -330,7 +333,8 @@ editor = { if (editor._creating !== null || editor._editing !== null) return; editor._highlight_layer.clearLayers(); editor._editing = editor.features[name].layer; - var path = '/editor/features/edit/' + name + '/'; + var endpoint = editor.feature_types[editor._editing.feature.properties.feature_type].endpoint; + var path = '/editor/'+endpoint+'/edit/' + name + '/'; $('#mapeditcontrols').removeClass('list'); $('#mapeditdetail').load(path, editor.edit_form_loaded); $('body').addClass('controls'); diff --git a/src/c3nav/mapdata/api/__init__.py b/src/c3nav/mapdata/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/c3nav/mapdata/api/features.py b/src/c3nav/mapdata/api/features.py new file mode 100644 index 00000000..caffdbc7 --- /dev/null +++ b/src/c3nav/mapdata/api/features.py @@ -0,0 +1,60 @@ +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 Inside, Room +from c3nav.mapdata.serializers.features import FeatureTypeSerializer, InsideSerializer, RoomSerializer + + +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 InsideViewSet(ReadOnlyModelViewSet): + """ + List and retrieve Inside Areas + """ + queryset = Inside.objects.all() + serializer_class = InsideSerializer + lookup_field = 'name' + lookup_value_regex = '[^/]+' + + +class RoomViewSet(ReadOnlyModelViewSet): + """ + List and retrieve Rooms + """ + queryset = Room.objects.all() + serializer_class = RoomSerializer + lookup_field = 'name' + lookup_value_regex = '[^/]+' diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api/main.py similarity index 55% rename from src/c3nav/mapdata/api.py rename to src/c3nav/mapdata/api/main.py index a688c614..45c13cfe 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api/main.py @@ -1,19 +1,15 @@ import mimetypes import os -from itertools import chain 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 rest_framework.viewsets import ReadOnlyModelViewSet -from c3nav.mapdata.models import FEATURE_TYPES, Level, Package, Source -from c3nav.mapdata.models.features import Feature +from c3nav.mapdata.models import Level, Package, Source from c3nav.mapdata.permissions import filter_source_queryset -from c3nav.mapdata.serializers import (FeatureSerializer, FeatureTypeSerializer, LevelSerializer, PackageSerializer, - SourceSerializer) +from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer class LevelViewSet(ReadOnlyModelViewSet): @@ -68,38 +64,3 @@ class SourceViewSet(ReadOnlyModelViewSet): for chunk in File(open(image_path, 'rb')).chunks(): response.write(chunk) return response - - -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(ReadOnlyModelViewSet): - """ - List and retrieve map features you have access to - """ - model = Feature - base_name = 'feature' - serializer_class = FeatureSerializer - lookup_field = 'name' - lookup_value_regex = '[^/]+' - - def get_queryset(self): - querysets = [] - for name, model in FEATURE_TYPES.items(): - querysets.append(model.objects.all()) - return chain(*querysets) - diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index 53be2bbd..305856c2 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -30,4 +30,3 @@ class MapdataModel(models.Model): class Meta: abstract = True unique_together = ('package', 'name') - diff --git a/src/c3nav/mapdata/models/features.py b/src/c3nav/mapdata/models/features.py index 7cf563c2..1e9bc5eb 100644 --- a/src/c3nav/mapdata/models/features.py +++ b/src/c3nav/mapdata/models/features.py @@ -1,17 +1,16 @@ -import os from collections import OrderedDict from django.db import models from django.utils.translation import ugettext_lazy as _ -from shapely.geometry.geo import shape, mapping +from shapely.geometry.geo import mapping, shape from c3nav.mapdata.fields import GeometryField from c3nav.mapdata.models.base import MapdataModel from c3nav.mapdata.utils import format_geojson - FEATURE_TYPES = OrderedDict() + def register_featuretype(cls): FEATURE_TYPES[cls.__name__.lower()] = cls return cls @@ -77,5 +76,3 @@ class Room(Feature): verbose_name = _('Room') verbose_name_plural = _('Rooms') default_related_name = 'rooms' - - diff --git a/src/c3nav/mapdata/serializers.py b/src/c3nav/mapdata/serializers.py deleted file mode 100644 index 291a693f..00000000 --- a/src/c3nav/mapdata/serializers.py +++ /dev/null @@ -1,94 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers -from rest_framework.exceptions import ValidationError -from shapely.geometry import mapping, shape - -from c3nav.editor.hosters import get_hoster_for_package -from c3nav.mapdata.models import Feature, Level, Package, Source -from c3nav.mapdata.utils import format_geojson - - -class GeometryField(serializers.DictField): - """ - shapely geometry objects serialized using GeoJSON - """ - default_error_messages = { - 'invalid': _('Invalid GeoJSON.') - } - - def to_representation(self, obj): - geojson = format_geojson(mapping(obj), round=False) - return super().to_representation(geojson) - - def to_internal_value(self, data): - geojson = super().to_internal_value(data) - try: - return shape(geojson) - except: - raise ValidationError(_('Invalid GeoJSON.')) - - -class PackageSerializer(serializers.ModelSerializer): - hoster = serializers.SerializerMethodField() - depends = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - - class Meta: - model = Package - fields = ('name', 'home_repo', 'commit_id', 'depends', 'bounds', 'public', 'hoster') - - def get_depends(self, obj): - return self.recursive_value(PackageSerializer, obj.depends, many=True) - - def get_hoster(self, obj): - return get_hoster_for_package(obj).name - - -class LevelSerializer(serializers.ModelSerializer): - package = serializers.SlugRelatedField(slug_field='name', read_only=True) - - class Meta: - model = Level - fields = ('name', 'altitude', 'package') - - -class SourceSerializer(serializers.ModelSerializer): - package = serializers.SlugRelatedField(slug_field='name', read_only=True) - - class Meta: - model = Source - fields = ('name', 'package', 'bounds') - - -class FeatureTypeSerializer(serializers.Serializer): - name = serializers.SerializerMethodField() - title = serializers.SerializerMethodField() - title_plural = 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) - - -class FeatureSerializer(serializers.Serializer): - name = serializers.CharField() - feature_type = serializers.SerializerMethodField() - level = serializers.SerializerMethodField() - package = serializers.SerializerMethodField() - geometry = GeometryField() - - def get_feature_type(self, obj): - return obj.__class__.__name__.lower() - - def get_level(self, obj): - return obj.level.name - - def get_package(self, obj): - return obj.package.name - diff --git a/src/c3nav/mapdata/serializers/__init__.py b/src/c3nav/mapdata/serializers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/c3nav/mapdata/serializers/features.py b/src/c3nav/mapdata/serializers/features.py new file mode 100644 index 00000000..161b41fa --- /dev/null +++ b/src/c3nav/mapdata/serializers/features.py @@ -0,0 +1,45 @@ +from rest_framework import serializers + +from c3nav.mapdata.models.features import Inside, Room +from c3nav.mapdata.serializers.fields import GeometryField + + +class FeatureTypeSerializer(serializers.Serializer): + name = serializers.SerializerMethodField() + title = serializers.SerializerMethodField() + title_plural = serializers.SerializerMethodField() + endpoint = 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 + + +class InsideSerializer(serializers.ModelSerializer): + level = serializers.SlugRelatedField(slug_field='name', read_only=True) + package = serializers.SlugRelatedField(slug_field='name', read_only=True) + geometry = GeometryField() + + class Meta: + model = Inside + fields = ('name', 'level', 'package', 'geometry') + + +class RoomSerializer(serializers.ModelSerializer): + level = serializers.SlugRelatedField(slug_field='name', read_only=True) + package = serializers.SlugRelatedField(slug_field='name', read_only=True) + geometry = GeometryField() + + class Meta: + model = Room + fields = ('name', 'level', 'package', 'geometry') diff --git a/src/c3nav/mapdata/serializers/fields.py b/src/c3nav/mapdata/serializers/fields.py new file mode 100644 index 00000000..9e49c631 --- /dev/null +++ b/src/c3nav/mapdata/serializers/fields.py @@ -0,0 +1,26 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from rest_framework.exceptions import ValidationError +from shapely.geometry import mapping, shape + +from c3nav.mapdata.utils import format_geojson + + +class GeometryField(serializers.DictField): + """ + shapely geometry objects serialized using GeoJSON + """ + default_error_messages = { + 'invalid': _('Invalid GeoJSON.') + } + + def to_representation(self, obj): + geojson = format_geojson(mapping(obj), round=False) + return super().to_representation(geojson) + + def to_internal_value(self, data): + geojson = super().to_internal_value(data) + try: + return shape(geojson) + except: + raise ValidationError(_('Invalid GeoJSON.')) diff --git a/src/c3nav/mapdata/serializers/main.py b/src/c3nav/mapdata/serializers/main.py new file mode 100644 index 00000000..ae5f8025 --- /dev/null +++ b/src/c3nav/mapdata/serializers/main.py @@ -0,0 +1,35 @@ +from rest_framework import serializers + +from c3nav.editor.hosters import get_hoster_for_package +from c3nav.mapdata.models import Level, Package, Source + + +class PackageSerializer(serializers.ModelSerializer): + hoster = serializers.SerializerMethodField() + depends = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) + + class Meta: + model = Package + fields = ('name', 'home_repo', 'commit_id', 'depends', 'bounds', 'public', 'hoster') + + def get_depends(self, obj): + return self.recursive_value(PackageSerializer, obj.depends, many=True) + + def get_hoster(self, obj): + return get_hoster_for_package(obj).name + + +class LevelSerializer(serializers.ModelSerializer): + package = serializers.SlugRelatedField(slug_field='name', read_only=True) + + class Meta: + model = Level + fields = ('name', 'altitude', 'package') + + +class SourceSerializer(serializers.ModelSerializer): + package = serializers.SlugRelatedField(slug_field='name', read_only=True) + + class Meta: + model = Source + fields = ('name', 'package', 'bounds')