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 import CharField, ModelForm, ValidationError
from django.forms.models import ModelChoiceField from django.forms.models import ModelChoiceField
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from django.utils.translation import ugettext_lazy as _
from shapely.geometry.geo import mapping from shapely.geometry.geo import mapping
from c3nav.mapdata.models import Package from c3nav.mapdata.models import Package
@ -50,6 +51,10 @@ class MapitemFormMixin(ModelForm):
if not creating: if not creating:
self.initial['level'] = self.instance.level.name 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: if 'geometry' in self.fields:
# hide geometry widget # hide geometry widget
self.fields['geometry'].widget = HiddenInput() self.fields['geometry'].widget = HiddenInput()
@ -73,6 +78,12 @@ class MapitemFormMixin(ModelForm):
initial=titles[language].strip(), max_length=50) initial=titles[language].strip(), max_length=50)
self.titles = titles 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): def clean(self):
if 'geometry' in self.fields: if 'geometry' in self.fields:
if not self.cleaned_data.get('geometry'): if not self.cleaned_data.get('geometry'):
@ -80,7 +91,7 @@ class MapitemFormMixin(ModelForm):
def create_editor_form(mapitemtype): 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)] existing_fields = [field for field in possible_fields if hasattr(mapitemtype, field)]
class EditorForm(MapitemFormMixin, ModelForm): class EditorForm(MapitemFormMixin, ModelForm):

View file

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

View file

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

View file

@ -38,7 +38,12 @@ class GeometryViewSet(ViewSet):
if packages: if packages:
queryset = queryset.filter(package__name__in=packages) queryset = queryset.filter(package__name__in=packages)
if levels: if levels:
queryset = queryset.filter(level__name__in=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: if names:
queryset = queryset.filter(name__in=names) queryset = queryset.filter(name__in=names)
queryset = filter_queryset_by_package_access(request, queryset) 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 .package import Package # noqa
from .source import Source # noqa from .source import Source # noqa
from .collections import Elevator # 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 A map feature
""" """
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'))
geometry = GeometryField() geometry = GeometryField()
geomtype = None geomtype = None
@ -47,10 +46,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
except: except:
raise ValueError(_('Invalid GeoJSON.')) raise ValueError(_('Invalid GeoJSON.'))
if 'level' not in data:
raise ValueError('missing level.')
kwargs['level'] = data['level']
return kwargs return kwargs
@classmethod @classmethod
@ -64,7 +59,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
('type', self.__class__.__name__.lower()), ('type', self.__class__.__name__.lower()),
('name', self.name), ('name', self.name),
('package', self.package.name), ('package', self.package.name),
('level', self.level.name),
)) ))
def to_geojson(self): def to_geojson(self):
@ -76,12 +70,81 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
def tofile(self): def tofile(self):
result = super().tofile() result = super().tofile()
result['level'] = self.level.name
result['geometry'] = format_geojson(mapping(self.geometry)) result['geometry'] = format_geojson(mapping(self.geometry))
return result 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 The outline of a building on a specific level
""" """
@ -93,7 +156,7 @@ class Building(GeometryMapItem):
default_related_name = 'buildings' default_related_name = 'buildings'
class Room(GeometryMapItem): class Room(GeometryMapItemWithLevel):
""" """
An accessible area like a room. Can overlap. An accessible area like a room. Can overlap.
""" """
@ -105,7 +168,7 @@ class Room(GeometryMapItem):
default_related_name = 'rooms' default_related_name = 'rooms'
class Outside(GeometryMapItem): class Outside(GeometryMapItemWithLevel):
""" """
An accessible outdoor area like a court. Can overlap. An accessible outdoor area like a court. Can overlap.
""" """
@ -117,7 +180,7 @@ class Outside(GeometryMapItem):
default_related_name = 'outsides' default_related_name = 'outsides'
class Obstacle(GeometryMapItem): class Obstacle(GeometryMapItemWithLevel):
""" """
An obstacle An obstacle
""" """
@ -153,7 +216,7 @@ class Obstacle(GeometryMapItem):
return result return result
class Door(GeometryMapItem): class Door(GeometryMapItemWithLevel):
""" """
A connection between two rooms A connection between two rooms
""" """
@ -165,7 +228,7 @@ class Door(GeometryMapItem):
default_related_name = 'doors' default_related_name = 'doors'
class Hole(GeometryMapItem): class Hole(GeometryMapItemWithLevel):
""" """
A hole in the ground of a room, e.g. for stairs. A hole in the ground of a room, e.g. for stairs.
""" """
@ -177,11 +240,11 @@ class Hole(GeometryMapItem):
default_related_name = 'holes' default_related_name = 'holes'
class ElevatorLevel(GeometryMapItem): class ElevatorLevel(GeometryMapItemWithLevel):
""" """
An elevator Level 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) button = models.SlugField(_('Button label'), max_length=10)
geomtype = 'polygon' geomtype = 'polygon'

View file

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

View file

@ -1,6 +1,6 @@
from c3nav.mapdata.models import Level, Package, Source from c3nav.mapdata.models import Level, Package, Source
from c3nav.mapdata.models.collections import Elevator 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) ordered_models += (Elevator, ElevatorLevel)

View file

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