add LevelConnector

This commit is contained in:
Laura Klünder 2016-12-01 12:25:02 +01:00
parent 8164cc1a40
commit b309b6f6cd
10 changed files with 164 additions and 28 deletions

View file

@ -6,6 +6,7 @@ from django.conf import settings
from django.forms import CharField, ModelForm, ValidationError
from django.forms.models import ModelChoiceField
from django.forms.widgets import HiddenInput
from django.utils.translation import ugettext_lazy as _
from shapely.geometry.geo import mapping
from c3nav.mapdata.models import Package
@ -50,6 +51,10 @@ class MapitemFormMixin(ModelForm):
if not creating:
self.initial['level'] = self.instance.level.name
if 'levels' in self.fields:
# set field_name
self.fields['levels'].to_field_name = 'name'
if 'geometry' in self.fields:
# hide geometry widget
self.fields['geometry'].widget = HiddenInput()
@ -73,6 +78,12 @@ class MapitemFormMixin(ModelForm):
initial=titles[language].strip(), max_length=50)
self.titles = titles
def clean_levels(self):
levels = self.cleaned_data.get('levels')
if len(levels) < 2:
raise ValidationError(_('Please select at least two levels.'))
return levels
def clean(self):
if 'geometry' in self.fields:
if not self.cleaned_data.get('geometry'):
@ -80,7 +91,7 @@ class MapitemFormMixin(ModelForm):
def create_editor_form(mapitemtype):
possible_fields = ['name', 'package', 'level', 'geometry', 'height', 'elevator', 'button']
possible_fields = ['name', 'package', 'level', 'levels', 'geometry', 'height', 'elevator', 'button']
existing_fields = [field for field in possible_fields if hasattr(mapitemtype, field)]
class EditorForm(MapitemFormMixin, ModelForm):

View file

@ -160,6 +160,7 @@ editor = {
'door': '#FF00FF',
'hole': '#66CC66',
'elevatorlevel': '#9EF8FB',
'levelconnector': '#FFFF00'
},
_get_geometry_style: function (feature) {
// style callback for GeoJSON loader
@ -281,6 +282,7 @@ editor = {
}
editor._creating = true;
$('#id_level').val(editor._level);
$('#id_levels').find('option[value='+editor._level+']').prop('selected', true);
}
} else if (editor._get_geometries_next_time) {
editor.get_geometries();

View file

@ -15,17 +15,21 @@ from c3nav.mapdata.permissions import can_access_package, filter_queryset_by_pac
def list_mapitemtypes(request, level):
def get_item_count(mapitemtype):
if not hasattr(mapitemtype, 'level'):
return 0
if hasattr(mapitemtype, 'level'):
return filter_queryset_by_package_access(request, mapitemtype.objects.filter(level__name=level)).count()
if hasattr(mapitemtype, 'levels'):
return filter_queryset_by_package_access(request, mapitemtype.objects.filter(levels__name=level)).count()
return 0
return render(request, 'editor/mapitemtypes.html', {
'level': level,
'mapitemtypes': [
{
'name': name,
'title': mapitemtype._meta.verbose_name_plural,
'has_level': hasattr(mapitemtype, 'level'),
'has_level': hasattr(mapitemtype, 'level') or hasattr(mapitemtype, 'levels'),
'count': get_item_count(mapitemtype),
} for name, mapitemtype in MAPITEM_TYPES.items()
],
@ -37,14 +41,18 @@ def list_mapitems(request, mapitem_type, level=None):
if mapitemtype is None:
raise Http404('Unknown mapitemtype.')
if hasattr(mapitemtype, 'level') and level is None:
has_level = hasattr(mapitemtype, 'level') or hasattr(mapitemtype, 'levels')
if has_level and level is None:
raise Http404('Missing level.')
elif not hasattr(mapitemtype, 'level') and level is not None:
elif not has_level and level is not None:
return redirect('editor.mapitems', mapitem_type=mapitem_type)
queryset = mapitemtype.objects.all()
if level is not None:
if hasattr(mapitemtype, 'level'):
queryset = queryset.filter(level__name=level)
elif hasattr(mapitemtype, 'levels'):
queryset = queryset.filter(levels__name=level)
return render(request, 'editor/mapitems.html', {
'mapitem_type': mapitem_type,
@ -105,7 +113,7 @@ def edit_mapitem(request, mapitem_type, name=None):
# Update/create mapitem
commit_type = 'Created' if mapitem is None else 'Updated'
action = 'create' if mapitem is None else 'edit'
mapitem = form.instance
mapitem = form.save(commit=False)
if form.titles is not None:
mapitem.titles = {}
@ -130,6 +138,7 @@ def edit_mapitem(request, mapitem_type, name=None):
})
mapitem.save()
form.save_m2m()
return render(request, 'editor/mapitem_success.html', {
'mapitem_type': mapitem_type

View file

@ -38,7 +38,12 @@ class GeometryViewSet(ViewSet):
if packages:
queryset = queryset.filter(package__name__in=packages)
if levels:
if hasattr(mapitemtype, 'level'):
queryset = queryset.filter(level__name__in=levels)
elif hasattr(mapitemtype, 'levels'):
queryset = queryset.filter(levels__name__in=levels)
else:
queryset = queryset.none()
if names:
queryset = queryset.filter(name__in=names)
queryset = filter_queryset_by_package_access(request, queryset)

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-01 10:48
from __future__ import unicode_literals
import c3nav.mapdata.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0008_hole'),
]
operations = [
migrations.CreateModel(
name='LevelConnector',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.SlugField(unique=True, verbose_name='Name')),
('geometry', c3nav.mapdata.fields.GeometryField()),
('levels', models.ManyToManyField(related_name='levelconnectors', to='mapdata.Level', verbose_name='levels')),
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='levelconnectors', to='mapdata.Package', verbose_name='map package')),
],
options={
'default_related_name': 'levelconnectors',
'verbose_name_plural': 'Level Connectors',
'verbose_name': 'Level Connector',
},
),
]

View file

@ -2,4 +2,4 @@ from .level import Level # noqa
from .package import Package # noqa
from .source import Source # noqa
from .collections import Elevator # noqa
from .geometry import GeometryMapItem, GEOMETRY_MAPITEM_TYPES # noqa
from .geometry import GeometryMapItemWithLevel, GEOMETRY_MAPITEM_TYPES # noqa

View file

@ -24,7 +24,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
"""
A map feature
"""
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'))
geometry = GeometryField()
geomtype = None
@ -47,10 +46,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
except:
raise ValueError(_('Invalid GeoJSON.'))
if 'level' not in data:
raise ValueError('missing level.')
kwargs['level'] = data['level']
return kwargs
@classmethod
@ -64,7 +59,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
('type', self.__class__.__name__.lower()),
('name', self.name),
('package', self.package.name),
('level', self.level.name),
))
def to_geojson(self):
@ -76,12 +70,81 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
def tofile(self):
result = super().tofile()
result['level'] = self.level.name
result['geometry'] = format_geojson(mapping(self.geometry))
return result
class Building(GeometryMapItem):
class GeometryMapItemWithLevel(GeometryMapItem):
"""
A map feature
"""
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'))
class Meta:
abstract = True
@classmethod
def fromfile(cls, data, file_path):
kwargs = super().fromfile(data, file_path)
if 'level' not in data:
raise ValueError('missing level.')
kwargs['level'] = data['level']
return kwargs
def get_geojson_properties(self):
result = super().get_geojson_properties()
result['level'] = float(self.level.name)
return result
def tofile(self):
result = super().tofile()
result['level'] = self.level.name
result.move_to_end('geometry')
return result
class LevelConnector(GeometryMapItem):
"""
A connector connecting levels
"""
geomtype = 'polygon'
levels = models.ManyToManyField('mapdata.Level', verbose_name=_('levels'))
class Meta:
verbose_name = _('Level Connector')
verbose_name_plural = _('Level Connectors')
default_related_name = 'levelconnectors'
@classmethod
def fromfile(cls, data, file_path):
kwargs = super().fromfile(data, file_path)
if 'levels' not in data:
raise ValueError('missing levels.')
levels = data.get('levels', None)
if not isinstance(levels, list):
raise TypeError('levels has to be a list')
if len(levels) < 2:
raise ValueError('a level connector needs at least two levels')
kwargs['levels'] = levels
return kwargs
def get_geojson_properties(self):
result = super().get_geojson_properties()
result['levels'] = tuple(self.levels.all().order_by('name').values_list('name', flat=True))
return result
def tofile(self):
result = super().tofile()
result['levels'] = tuple(self.levels.all().order_by('name').values_list('name', flat=True))
result.move_to_end('geometry')
return result
class Building(GeometryMapItemWithLevel):
"""
The outline of a building on a specific level
"""
@ -93,7 +156,7 @@ class Building(GeometryMapItem):
default_related_name = 'buildings'
class Room(GeometryMapItem):
class Room(GeometryMapItemWithLevel):
"""
An accessible area like a room. Can overlap.
"""
@ -105,7 +168,7 @@ class Room(GeometryMapItem):
default_related_name = 'rooms'
class Outside(GeometryMapItem):
class Outside(GeometryMapItemWithLevel):
"""
An accessible outdoor area like a court. Can overlap.
"""
@ -117,7 +180,7 @@ class Outside(GeometryMapItem):
default_related_name = 'outsides'
class Obstacle(GeometryMapItem):
class Obstacle(GeometryMapItemWithLevel):
"""
An obstacle
"""
@ -153,7 +216,7 @@ class Obstacle(GeometryMapItem):
return result
class Door(GeometryMapItem):
class Door(GeometryMapItemWithLevel):
"""
A connection between two rooms
"""
@ -165,7 +228,7 @@ class Door(GeometryMapItem):
default_related_name = 'doors'
class Hole(GeometryMapItem):
class Hole(GeometryMapItemWithLevel):
"""
A hole in the ground of a room, e.g. for stairs.
"""
@ -177,11 +240,11 @@ class Hole(GeometryMapItem):
default_related_name = 'holes'
class ElevatorLevel(GeometryMapItem):
class ElevatorLevel(GeometryMapItemWithLevel):
"""
An elevator Level
"""
elevator = models.ForeignKey(Elevator, on_delete=models.PROTECT, related_name='levels')
elevator = models.ForeignKey(Elevator, on_delete=models.PROTECT)
button = models.SlugField(_('Button label'), max_length=10)
geomtype = 'polygon'

View file

@ -46,6 +46,9 @@ class Level(MapItem):
result['altitude'] = float(self.altitude)
return result
def __str__(self):
return self.name
class LevelGeometries():
def __init__(self, level):

View file

@ -1,6 +1,6 @@
from c3nav.mapdata.models import Level, Package, Source
from c3nav.mapdata.models.collections import Elevator
from c3nav.mapdata.models.geometry import Building, Door, ElevatorLevel, Hole, Obstacle, Outside, Room
from c3nav.mapdata.models.geometry import Building, Door, ElevatorLevel, Hole, LevelConnector, Obstacle, Outside, Room
ordered_models = (Package, Level, Source, Building, Room, Outside, Door, Obstacle, Hole)
ordered_models = (Package, Level, LevelConnector, Source, Building, Room, Outside, Door, Obstacle, Hole)
ordered_models += (Elevator, ElevatorLevel)

View file

@ -8,6 +8,7 @@ from django.conf import settings
from django.core.management import CommandError
from c3nav.mapdata.models import Elevator, Level, Package
from c3nav.mapdata.models.geometry import LevelConnector
from c3nav.mapdata.packageio.const import ordered_models
@ -158,6 +159,11 @@ class ReaderItem:
depends = [self.reader.saved_items[Package][name].obj.pk for name in self.data['depends']]
self.data.pop('depends')
levels = []
if self.model == LevelConnector:
levels = [self.reader.saved_items[Level][name].obj.pk for name in self.data['levels']]
self.data.pop('levels')
# Change name references to the referenced object
for name, model in self.relations.items():
if name in self.data:
@ -174,3 +180,8 @@ class ReaderItem:
self.obj.depends.clear()
for dependency in depends:
self.obj.depends.add(dependency)
if levels:
self.obj.levels.clear()
for level in levels:
self.obj.levels.add(level)