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( - $('
').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(
+ $(' ').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')