add feature views to api and editor.js

This commit is contained in:
Laura Klünder 2016-10-13 13:55:02 +02:00
parent 0036b27057
commit 4b74eb0cf5
13 changed files with 216 additions and 178 deletions

0
src/__init__.py Normal file
View file

View file

@ -8,14 +8,19 @@ 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 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 = SimpleRouter()
router.register(r'levels', LevelViewSet) router.register(r'levels', LevelViewSet)
router.register(r'packages', PackageViewSet) router.register(r'packages', PackageViewSet)
router.register(r'sources', SourceViewSet) router.register(r'sources', SourceViewSet)
router.register(r'featuretypes', FeatureTypeViewSet, base_name='featuretype') 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'hosters', HosterViewSet, base_name='hoster')
router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask') router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask')

View file

@ -186,35 +186,36 @@ editor = {
features: {}, features: {},
get_features: function () { get_features: function () {
$.getJSON('/api/features/', function(features) { $.getJSON('/api/features/', function(all_features) {
var feature_type; $('.feature_level_list li').remove();
for (var level in editor.levels) { var feature_type, features, feature, layergroup;
for (var j = 0; j < editor.feature_types_order.length; j++) { for (var j = 0; j < editor.feature_types_order.length; j++) {
feature_type = editor.feature_types_order[j]; feature_type = editor.feature_types_order[j];
for (var level in editor.levels) {
editor.level_feature_layers[level][feature_type].clearLayers(); editor.level_feature_layers[level][feature_type].clearLayers();
} }
} features = all_features[editor.feature_types[feature_type].endpoint]
$('.feature_level_list li').remove();
var feature, layergroup; for (var i = 0; i < features.length; i++) {
for (var i=0; i < features.length; i++) {
feature = features[i]; feature = features[i];
console.log(feature);
layergroup = L.geoJSON({ layergroup = L.geoJSON({
type: 'Feature', type: 'Feature',
geometry: feature.geometry, geometry: feature.geometry,
properties: { properties: {
name: feature.name, name: feature.name,
feature_type: feature.feature_type feature_type: feature_type
} }
}, { }, {
style: editor._get_feature_style style: editor._get_feature_style
}).on('mouseover', editor._hover_feature_layer) }).on('mouseover', editor._hover_feature_layer)
.on('mouseout', editor._unhover_feature_layer) .on('mouseout', editor._unhover_feature_layer)
.on('click', editor._click_feature_layer) .on('click', editor._click_feature_layer)
.addTo(editor.level_feature_layers[feature.level][feature.feature_type]); .addTo(editor.level_feature_layers[feature.level][feature_type]);
feature.layer = layergroup.getLayers()[0]; feature.layer = layergroup.getLayers()[0];
editor.features[feature.name] = feature; editor.features[feature.name] = feature;
$('.feature_list[name='+feature.feature_type+'] > [data-level='+feature.level+']').append( $('.feature_list[name=' + feature_type + '] > [data-level=' + feature.level + ']').append(
$('<li>').attr('name', feature.name).append( $('<li>').attr('name', feature.name).append(
$('<p>').text(feature.title).append(' ').append( $('<p>').text(feature.title).append(' ').append(
$('<em>').text(feature.name) $('<em>').text(feature.name)
@ -222,6 +223,7 @@ editor = {
) )
); );
} }
}
$('.start-drawing').show(); $('.start-drawing').show();
$('#mapeditcontrols').addClass('list'); $('#mapeditcontrols').addClass('list');
editor.set_current_level(editor._level); editor.set_current_level(editor._level);
@ -319,7 +321,8 @@ editor = {
editor.map.fitBounds(editor._editing.getBounds()); editor.map.fitBounds(editor._editing.getBounds());
$('.leaflet-drawbar').hide(); $('.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'); $('#mapeditcontrols').removeClass('list');
$('body').addClass('controls'); $('body').addClass('controls');
$('#mapeditdetail').load(path, editor.edit_form_loaded); $('#mapeditdetail').load(path, editor.edit_form_loaded);
@ -330,7 +333,8 @@ editor = {
if (editor._creating !== null || editor._editing !== null) return; if (editor._creating !== null || editor._editing !== null) return;
editor._highlight_layer.clearLayers(); editor._highlight_layer.clearLayers();
editor._editing = editor.features[name].layer; 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'); $('#mapeditcontrols').removeClass('list');
$('#mapeditdetail').load(path, editor.edit_form_loaded); $('#mapeditdetail').load(path, editor.edit_form_loaded);
$('body').addClass('controls'); $('body').addClass('controls');

View file

View file

@ -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 = '[^/]+'

View file

@ -1,19 +1,15 @@
import mimetypes import mimetypes
import os import os
from itertools import chain
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.viewsets import ReadOnlyModelViewSet
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from c3nav.mapdata.models import FEATURE_TYPES, Level, Package, Source from c3nav.mapdata.models import Level, Package, Source
from c3nav.mapdata.models.features import Feature
from c3nav.mapdata.permissions import filter_source_queryset from c3nav.mapdata.permissions import filter_source_queryset
from c3nav.mapdata.serializers import (FeatureSerializer, FeatureTypeSerializer, LevelSerializer, PackageSerializer, from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
SourceSerializer)
class LevelViewSet(ReadOnlyModelViewSet): class LevelViewSet(ReadOnlyModelViewSet):
@ -68,38 +64,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 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)

View file

@ -30,4 +30,3 @@ class MapdataModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
unique_together = ('package', 'name') unique_together = ('package', 'name')

View file

@ -1,17 +1,16 @@
import os
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ 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.fields import GeometryField
from c3nav.mapdata.models.base import MapdataModel from c3nav.mapdata.models.base import MapdataModel
from c3nav.mapdata.utils import format_geojson from c3nav.mapdata.utils import format_geojson
FEATURE_TYPES = OrderedDict() FEATURE_TYPES = OrderedDict()
def register_featuretype(cls): def register_featuretype(cls):
FEATURE_TYPES[cls.__name__.lower()] = cls FEATURE_TYPES[cls.__name__.lower()] = cls
return cls return cls
@ -77,5 +76,3 @@ class Room(Feature):
verbose_name = _('Room') verbose_name = _('Room')
verbose_name_plural = _('Rooms') verbose_name_plural = _('Rooms')
default_related_name = 'rooms' default_related_name = 'rooms'

View file

@ -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

View file

@ -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')

View file

@ -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.'))

View file

@ -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')