new map edit control panel
This commit is contained in:
parent
d0492a7d88
commit
52d7b5b121
8 changed files with 171 additions and 186 deletions
|
@ -34,6 +34,14 @@ class SourceSerializer(serializers.ModelSerializer):
|
||||||
fields = ('name', 'package', 'bounds')
|
fields = ('name', 'package', 'bounds')
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureTypeSerializer(serializers.Serializer):
|
||||||
|
name = serializers.CharField()
|
||||||
|
title = serializers.CharField()
|
||||||
|
title_plural = serializers.CharField()
|
||||||
|
geomtype = serializers.CharField()
|
||||||
|
color = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class HosterSerializer(serializers.Serializer):
|
class HosterSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
base_url = serializers.CharField()
|
base_url = serializers.CharField()
|
||||||
|
|
|
@ -8,6 +8,7 @@ router = DefaultRouter()
|
||||||
router.register(r'levels', mapdata_views.LevelViewSet)
|
router.register(r'levels', mapdata_views.LevelViewSet)
|
||||||
router.register(r'packages', mapdata_views.PackageViewSet)
|
router.register(r'packages', mapdata_views.PackageViewSet)
|
||||||
router.register(r'sources', mapdata_views.SourceViewSet)
|
router.register(r'sources', mapdata_views.SourceViewSet)
|
||||||
|
router.register(r'featuretypes', mapdata_views.FeatureTypeViewSet, base_name='featuretype')
|
||||||
router.register(r'hosters', editor_views.HosterViewSet, base_name='hoster')
|
router.register(r'hosters', editor_views.HosterViewSet, base_name='hoster')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,14 @@ import os
|
||||||
|
|
||||||
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 HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||||
|
|
||||||
from ...mapdata.models import Level, Package, Source
|
from ...mapdata.models import FEATURE_TYPES, Level, Package, Source
|
||||||
from ..permissions import filter_source_queryset
|
from ..permissions import filter_source_queryset
|
||||||
from ..serializers import LevelSerializer, PackageSerializer, SourceSerializer
|
from ..serializers import FeatureTypeSerializer, LevelSerializer, PackageSerializer, SourceSerializer
|
||||||
from .cache import AccessCachedViewSetMixin, CachedViewSetMixin
|
from .cache import AccessCachedViewSetMixin, CachedViewSetMixin
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,3 +64,18 @@ class SourceViewSet(AccessCachedViewSetMixin, 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):
|
||||||
|
"""
|
||||||
|
Get Feature types
|
||||||
|
"""
|
||||||
|
def list(self, request, version=None):
|
||||||
|
serializer = FeatureTypeSerializer(FEATURE_TYPES.values(), many=True, context={'request': request})
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def retrieve(self, request, pk=None, version=None):
|
||||||
|
if pk not in FEATURE_TYPES:
|
||||||
|
raise Http404
|
||||||
|
serializer = FeatureTypeSerializer(FEATURE_TYPES[pk], context={'request': request})
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#mapeditor {
|
#map {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
top:54px;
|
top:54px;
|
||||||
bottom:0;
|
bottom:0;
|
||||||
left:0;
|
left:0;
|
||||||
right:0;
|
right:350px;
|
||||||
}
|
}
|
||||||
.leaflet-control-layers-overlays label {
|
.leaflet-control-layers-overlays label {
|
||||||
margin-bottom:0;
|
margin-bottom:0;
|
||||||
|
@ -22,30 +22,6 @@
|
||||||
padding:0 6px;
|
padding:0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-editbar a, .leaflet-editbar a:hover {
|
|
||||||
font-size: 13px;
|
|
||||||
width:auto;
|
|
||||||
min-width:50px;
|
|
||||||
text-align:left;
|
|
||||||
padding:0 6px;
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
.leaflet-editbar a.current {
|
|
||||||
display:block;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
.leaflet-editbar.usable:hover a {
|
|
||||||
width:80px;
|
|
||||||
display:block;
|
|
||||||
}
|
|
||||||
.leaflet-editbar:not(.usable) a.current, .leaflet-editbar:not(:hover) a.current, .leaflet-drawbar a {
|
|
||||||
border-radius:4px;
|
|
||||||
border:0;
|
|
||||||
}
|
|
||||||
.leaflet-editbar:not(.usable) a.current:hover {
|
|
||||||
background-color:#FFFFFF;
|
|
||||||
cursor:default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-levels a {
|
.leaflet-levels a {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
|
@ -55,3 +31,17 @@
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
color:#000000;
|
color:#000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mapeditcontrols {
|
||||||
|
position:absolute;
|
||||||
|
top:54px;
|
||||||
|
bottom:0;
|
||||||
|
padding:8px;
|
||||||
|
width:350px;
|
||||||
|
right:0;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
#mapeditcontrols legend .btn {
|
||||||
|
padding-left:5.5px;
|
||||||
|
padding-right:5.5px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,38 +1,9 @@
|
||||||
editor = {
|
editor = {
|
||||||
feature_types: {
|
feature_types: {},
|
||||||
bounds: {
|
|
||||||
type: 'polygon',
|
|
||||||
color: '#FFFFFF',
|
|
||||||
},
|
|
||||||
building: {
|
|
||||||
type: 'polygon',
|
|
||||||
color: '#CCCCCC',
|
|
||||||
},
|
|
||||||
wall: {
|
|
||||||
type: 'polygon',
|
|
||||||
color: '#333333',
|
|
||||||
},
|
|
||||||
obstacle: {
|
|
||||||
type: 'polygon',
|
|
||||||
color: '#999999',
|
|
||||||
},
|
|
||||||
door: {
|
|
||||||
type: 'polygon',
|
|
||||||
color: '#FF00FF',
|
|
||||||
},
|
|
||||||
step: {
|
|
||||||
type: 'polyline',
|
|
||||||
color: '#FF0000',
|
|
||||||
},
|
|
||||||
elevator: {
|
|
||||||
type: 'polygon',
|
|
||||||
color: '#99CC00',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
// Init Map
|
// Init Map
|
||||||
editor.map = L.map('mapeditor', {
|
editor.map = L.map('map', {
|
||||||
zoom: 2,
|
zoom: 2,
|
||||||
maxZoom: 10,
|
maxZoom: 10,
|
||||||
minZoom: 1,
|
minZoom: 1,
|
||||||
|
@ -43,154 +14,64 @@ editor = {
|
||||||
|
|
||||||
L.control.scale({imperial: false}).addTo(editor.map);
|
L.control.scale({imperial: false}).addTo(editor.map);
|
||||||
|
|
||||||
|
editor.get_feature_types();
|
||||||
editor.get_packages();
|
editor.get_packages();
|
||||||
editor.init_features();
|
editor.get_sources();
|
||||||
|
editor.get_levels();
|
||||||
},
|
},
|
||||||
|
|
||||||
init_features: function() {
|
get_feature_types: function() {
|
||||||
editor._feature_type = null;
|
$.getJSON('/api/v1/featuretypes/', function(feature_types) {
|
||||||
L.FeatureLayerControl = L.Control.extend({
|
var feature_type;
|
||||||
options: {
|
var editcontrols = $('#mapeditcontrols');
|
||||||
position: 'topleft'
|
for(var i=0;i<feature_types.length;i++) {
|
||||||
},
|
feature_type = feature_types[i];
|
||||||
onAdd: function (map) {
|
editor.feature_types[feature_type.name] = feature_type;
|
||||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-editbar usable');
|
editcontrols.append(
|
||||||
$('<a href="#" class="current">').appendTo(container).html('').attr({
|
$('<fieldset>').attr('name', feature_type.name).append(
|
||||||
href: '#',
|
$('<legend>').text(feature_type.title_plural).append(
|
||||||
title: 'disable editing',
|
$('<button class="btn btn-primary btn-xs pull-right"><i class="glyphicon glyphicon-plus"></i></button>')
|
||||||
name: 'null'
|
)
|
||||||
}).on('click', function() {
|
)
|
||||||
editor.set_feature_type(null);
|
);
|
||||||
});
|
|
||||||
var plural;
|
|
||||||
for (var feature_type in editor.feature_types) {
|
|
||||||
$('<a href="#">').appendTo(container).text(feature_type+((feature_type.substr(-1) != 's')?'s':'')).attr({
|
|
||||||
href: '#',
|
|
||||||
title: 'edit '+feature_type+' layer',
|
|
||||||
name: feature_type
|
|
||||||
}).on('click', editor._layer_button_click);
|
|
||||||
}
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
editor.map.addControl(new L.FeatureLayerControl());
|
|
||||||
|
|
||||||
// Add drawing new features
|
|
||||||
editor._drawing = null;
|
|
||||||
editor._adding = null;
|
|
||||||
|
|
||||||
L.DrawControl = L.Control.extend({
|
|
||||||
options: {
|
|
||||||
position: 'topleft'
|
|
||||||
},
|
|
||||||
onAdd: function (map) {
|
|
||||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-drawbar');
|
|
||||||
$('<a href="#" id="drawstart">').appendTo(container).text('start drawing').attr({
|
|
||||||
href: '#',
|
|
||||||
title: 'start drawing',
|
|
||||||
name: ''
|
|
||||||
}).on('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
editor.start_drawing();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('<a href="#" id="drawcancel">').appendTo(container).text('cancel').attr({
|
|
||||||
href: '#',
|
|
||||||
title: 'cancel drawing',
|
|
||||||
name: ''
|
|
||||||
}).on('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
editor.cancel_drawing();
|
|
||||||
});
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
editor.map.addControl(new L.DrawControl());
|
|
||||||
$('#drawcancel').hide();
|
|
||||||
|
|
||||||
editor.map.on('editable:drawing:commit', function (e) {
|
|
||||||
editor._drawing = null;
|
|
||||||
editor._adding = e.layer;
|
|
||||||
|
|
||||||
e.layer.disableEdit();
|
|
||||||
L.popup({
|
|
||||||
closeButton: false,
|
|
||||||
autoClose: false,
|
|
||||||
}).setContent('<img src="/static/img/loader.gif">').setLatLng(e.layer.getCenter()).openOn(editor.map);
|
|
||||||
console.log(e.layer.toGeoJSON());
|
|
||||||
}).on('editable:drawing:cancel', function (e) {
|
|
||||||
if (editor._drawing !== null && editor._adding === null) {
|
|
||||||
e.layer.remove();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_layer_button_click: function(e) {
|
packages: {},
|
||||||
e.preventDefault();
|
|
||||||
editor.set_feature_type($(this).attr('name'));
|
|
||||||
},
|
|
||||||
|
|
||||||
set_feature_type: function(feature_type) {
|
|
||||||
if (editor._drawing !== null || editor._adding !== null) return;
|
|
||||||
|
|
||||||
$('.leaflet-editbar .current').removeClass('current');
|
|
||||||
$('.leaflet-editbar [name='+feature_type+']').addClass('current');
|
|
||||||
editor._feature_type = feature_type;
|
|
||||||
|
|
||||||
$('.leaflet-drawbar').toggle(feature_type !== null);
|
|
||||||
$('#drawstart').text('add '+feature_type);
|
|
||||||
},
|
|
||||||
start_drawing: function() {
|
|
||||||
if (editor._feature_type === null) return;
|
|
||||||
|
|
||||||
editor._drawing = editor._feature_type;
|
|
||||||
var options = editor.feature_types[editor._drawing];
|
|
||||||
if (options.type == 'polygon') {
|
|
||||||
editor.map.editTools.startPolygon(null, options);
|
|
||||||
} else if (options.type == 'polyline') {
|
|
||||||
editor.map.editTools.startPolyline(null, options);
|
|
||||||
}
|
|
||||||
$('.leaflet-editbar').toggleClass('usable', false);
|
|
||||||
$('#drawstart').hide();
|
|
||||||
$('#drawcancel').show();
|
|
||||||
},
|
|
||||||
cancel_drawing: function() {
|
|
||||||
if (editor._drawing === null || editor._adding !== null) return;
|
|
||||||
editor.map.editTools.stopDrawing();
|
|
||||||
editor._drawing = null;
|
|
||||||
$('.leaflet-editbar').toggleClass('usable', true);
|
|
||||||
$('#drawcancel').hide();
|
|
||||||
$('#drawstart').show();
|
|
||||||
},
|
|
||||||
|
|
||||||
get_packages: function() {
|
get_packages: function() {
|
||||||
$.getJSON('/api/v1/packages/', function(packages) {
|
$.getJSON('/api/v1/packages/', function(packages) {
|
||||||
var bounds = [[0, 0], [0, 0]];
|
var bounds = [[0, 0], [0, 0]];
|
||||||
var pkg;
|
var pkg;
|
||||||
for(var i=0;i<packages.length;i++) {
|
for(var i=0;i<packages.length;i++) {
|
||||||
pkg = packages[i];
|
pkg = packages[i];
|
||||||
|
editor.packages[pkg.name] = pkg;
|
||||||
if (pkg.bounds === null) continue;
|
if (pkg.bounds === null) continue;
|
||||||
bounds = [[Math.min(bounds[0][0], pkg.bounds[0][0]), Math.min(bounds[0][1], pkg.bounds[0][1])],
|
bounds = [[Math.min(bounds[0][0], pkg.bounds[0][0]), Math.min(bounds[0][1], pkg.bounds[0][1])],
|
||||||
[Math.max(bounds[1][0], pkg.bounds[1][0]), Math.max(bounds[1][1], pkg.bounds[1][1])]];
|
[Math.max(bounds[1][0], pkg.bounds[1][0]), Math.max(bounds[1][1], pkg.bounds[1][1])]];
|
||||||
}
|
}
|
||||||
editor.map.setMaxBounds(bounds);
|
editor.map.setMaxBounds(bounds);
|
||||||
editor.map.fitBounds(bounds, {padding: [30, 50]});
|
editor.map.fitBounds(bounds, {padding: [30, 50]});
|
||||||
|
|
||||||
editor.get_sources();
|
|
||||||
editor.get_levels();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sources: {},
|
||||||
get_sources: function() {
|
get_sources: function() {
|
||||||
$.getJSON('/api/v1/sources/', function(sources) {
|
$.getJSON('/api/v1/sources/', function(sources) {
|
||||||
var layers = {};
|
var layers = {};
|
||||||
var source;
|
var source;
|
||||||
for(var i=0;i<sources.length;i++) {
|
for(var i=0;i<sources.length;i++) {
|
||||||
source = sources[i];
|
source = sources[i];
|
||||||
layers[source.name] = L.imageOverlay('/api/v1/sources/'+source.name+'/image/', source.bounds);
|
editor.sources[source.name] = source;
|
||||||
|
source.layer = L.imageOverlay('/api/v1/sources/'+source.name+'/image/', source.bounds);
|
||||||
|
layers[source.name] = source.layer;
|
||||||
}
|
}
|
||||||
L.control.layers([], layers).addTo(editor.map);
|
L.control.layers([], layers).addTo(editor.map);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
level_layers: {},
|
||||||
|
levels: {},
|
||||||
get_levels: function() {
|
get_levels: function() {
|
||||||
$.getJSON('/api/v1/levels/?ordering=-altitude', function(levels) {
|
$.getJSON('/api/v1/levels/?ordering=-altitude', function(levels) {
|
||||||
L.LevelControl = L.Control.extend({
|
L.LevelControl = L.Control.extend({
|
||||||
|
@ -209,11 +90,87 @@ editor = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
editor.map.addControl(new L.LevelControl());
|
editor.map.addControl(new L.LevelControl());
|
||||||
|
|
||||||
|
var level_layer, feature_layer, level;
|
||||||
|
for(var i=0;i<levels.length;i++) {
|
||||||
|
level = levels[i];
|
||||||
|
editor.levels[level.name] = level;
|
||||||
|
level_layer = L.layerGroup().addTo(editor.map);
|
||||||
|
editor.level_layers[level.name] = level_layer;
|
||||||
|
}
|
||||||
|
editor.init_drawing();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
init_drawing: function() {
|
||||||
|
// Add drawing new features
|
||||||
|
editor._drawing = null;
|
||||||
|
editor._adding = null;
|
||||||
|
|
||||||
|
L.DrawControl = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft'
|
||||||
|
},
|
||||||
|
onAdd: function (map) {
|
||||||
|
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-drawbar');
|
||||||
|
$('<a href="#" id="drawcancel">').appendTo(container).text('cancel').attr({
|
||||||
|
href: '#',
|
||||||
|
title: 'cancel drawing',
|
||||||
|
name: ''
|
||||||
|
}).on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
editor.cancel_drawing();
|
||||||
|
});
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
editor.map.addControl(new L.DrawControl());
|
||||||
|
|
||||||
|
$('#mapeditcontrols').on('click', 'fieldset legend .btn', function() {
|
||||||
|
console.log($(this).closest('fieldset'));
|
||||||
|
editor.start_drawing($(this).closest('fieldset').attr('name'));
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.map.on('editable:drawing:commit', function (e) {
|
||||||
|
editor._drawing = null;
|
||||||
|
editor._adding = e.layer;
|
||||||
|
|
||||||
|
e.layer.disableEdit();
|
||||||
|
L.popup({
|
||||||
|
closeButton: false,
|
||||||
|
autoClose: false,
|
||||||
|
}).setContent('<img src="/static/img/loader.gif">').setLatLng(e.layer.getCenter()).openOn(editor.map);
|
||||||
|
console.log(e.layer.toGeoJSON());
|
||||||
|
}).on('editable:drawing:cancel', function (e) {
|
||||||
|
if (editor._drawing !== null && editor._adding === null) {
|
||||||
|
e.layer.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
start_drawing: function(feature_type) {
|
||||||
|
console.log(feature_type);
|
||||||
|
editor._drawing = feature_type;
|
||||||
|
var options = editor.feature_types[feature_type];
|
||||||
|
if (options.geomtype == 'polygon') {
|
||||||
|
editor.map.editTools.startPolygon(null, options);
|
||||||
|
} else if (options.geomtype == 'polyline') {
|
||||||
|
editor.map.editTools.startPolyline(null, options);
|
||||||
|
}
|
||||||
|
$('.leaflet-editbar').toggleClass('usable', false);
|
||||||
|
$('#drawstart').hide();
|
||||||
|
$('#drawcancel').show();
|
||||||
|
},
|
||||||
|
cancel_drawing: function() {
|
||||||
|
if (editor._drawing === null || editor._adding !== null) return;
|
||||||
|
editor.map.editTools.stopDrawing();
|
||||||
|
editor._drawing = null;
|
||||||
|
$('#drawcancel').hide();
|
||||||
|
$('#drawstart').show();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if ($('#mapeditor').length) {
|
if ($('#mapeditcontrols').length) {
|
||||||
editor.init();
|
editor.init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends 'editor/base.html' %}
|
{% extends 'editor/base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="mapeditor"></div>
|
<div id="map"></div>
|
||||||
|
<div id="mapeditcontrols"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .feature import Feature # noqa
|
from .feature import Feature, FEATURE_TYPES # noqa
|
||||||
from .level import Level # noqa
|
from .level import Level # noqa
|
||||||
from .package import Package # noqa
|
from .package import Package # noqa
|
||||||
from .source import Source # noqa
|
from .source import Source # noqa
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
|
from collections import OrderedDict, namedtuple
|
||||||
|
|
||||||
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 _
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureType(namedtuple('FeatureType', ('name', 'title', 'title_plural', 'geomtype', 'color'))):
|
||||||
|
def __init__(self, *args, **kwartgs):
|
||||||
|
FEATURE_TYPES[self.name] = self
|
||||||
|
|
||||||
|
FEATURE_TYPES = OrderedDict()
|
||||||
|
FeatureType('building', _('Building'), _('Buildings'), 'polygon', '#333333')
|
||||||
|
FeatureType('room', _('Room'), _('Rooms'), 'polygon', '#CCCCCC')
|
||||||
|
FeatureType('outside', _('Outside Area'), _('Outside Areas'), 'polygon', '#EEEEEE')
|
||||||
|
FeatureType('obstacle', _('Obstacle'), _('Obstacles'), 'polygon', '#999999')
|
||||||
|
# FeatureType('door', _('Door'), 'polygon', '#FF00FF')
|
||||||
|
# FeatureType('step', _('Step'), 'polyline', '#FF0000')
|
||||||
|
# FeatureType('elevator', _('Elevator'), 'polygon', '#99CC00')
|
||||||
|
|
||||||
|
|
||||||
class Feature(models.Model):
|
class Feature(models.Model):
|
||||||
"""
|
"""
|
||||||
A map feature
|
A map feature
|
||||||
"""
|
"""
|
||||||
TYPES = (
|
TYPES = tuple((name, t.title) for name, t in FEATURE_TYPES.items())
|
||||||
('building', _('Building')),
|
|
||||||
('room', _('Room')),
|
|
||||||
('obstacle', _('Obstacle')),
|
|
||||||
)
|
|
||||||
|
|
||||||
name = models.SlugField(_('feature identifier'), primary_key=True, max_length=50, help_text=_('e.g. noc'))
|
name = models.SlugField(_('feature identifier'), primary_key=True, max_length=50, help_text=_('e.g. noc'))
|
||||||
package = models.ForeignKey('Package', on_delete=models.CASCADE, related_name='features',
|
package = models.ForeignKey('Package', on_delete=models.CASCADE, related_name='features',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue