diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index bd401582..9e264b17 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -61,6 +61,10 @@ class MapitemFormMixin(ModelForm): # set field_name self.fields['levels'].to_field_name = 'name' + if 'groups' in self.fields: + # set field_name + self.fields['groups'].to_field_name = 'name' + if 'geometry' in self.fields: # hide geometry widget self.fields['geometry'].widget = HiddenInput() @@ -94,12 +98,17 @@ class MapitemFormMixin(ModelForm): if 'geometry' in self.fields: if not self.cleaned_data.get('geometry'): raise ValidationError('Missing geometry.') + + if hasattr(self.instance, 'titles') and not any(self.titles.values()): + raise ValidationError( + _('You have to select a title in at least one language.') + ) super().clean() def create_editor_form(mapitemtype): possible_fields = ['name', 'package', 'altitude', 'level', 'intermediate', 'levels', 'geometry', - 'elevator', 'button', 'crop_to_level', 'width'] + 'elevator', 'button', 'crop_to_level', 'width', 'groups'] existing_fields = [field for field in possible_fields if hasattr(mapitemtype, field)] class EditorForm(MapitemFormMixin, ModelForm): diff --git a/src/c3nav/editor/static/editor/css/editor.css b/src/c3nav/editor/static/editor/css/editor.css index b55d3c83..47f4fba9 100644 --- a/src/c3nav/editor/static/editor/css/editor.css +++ b/src/c3nav/editor/static/editor/css/editor.css @@ -29,7 +29,7 @@ form button.invisiblesubmit { display: block; } -.leaflet-control-layers-toggle { +.leaflet-container .leaflet-control-layers-toggle { color:#000000 !important; font-size:14px; text-align:center; @@ -38,7 +38,7 @@ form button.invisiblesubmit { background-image:none; padding:0 8px; } -.leaflet-control-layers-expanded { +.leaflet-container .leaflet-control-layers-expanded { min-width:75px; } .leaflet-levels { diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index 6e680dfb..808b0299 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -281,7 +281,8 @@ editor = { 'elevatorlevel': '#9EF8FB', 'levelconnector': '#FFFF00', 'shadow': '#000000', - 'stair': '#FF0000' + 'stair': '#FF0000', + 'areaofinterest': '#0099FF' }, _line_draw_geometry_style: function(style) { style.stroke = true; @@ -300,12 +301,19 @@ editor = { }, _get_mapitem_type_style: function (mapitem_type) { // get styles for a specific mapitem - return { + var result = { stroke: false, fillColor: editor._geometry_colors[mapitem_type], - fillOpacity: 0.6, + fillOpacity: (mapitem_type == 'areaofinterest') ? 0.2 : 0.6, smoothFactor: 0 }; + if (mapitem_type == 'areaofinterest') { + result.fillOpacity = 0.02; + result.color = result.fillColor; + result.stroke = true; + result.weight = 1; + } + return result; }, _register_geojson_feature: function (feature, layer) { // onEachFeature callback for GeoJSON loader – register all needed events diff --git a/src/c3nav/editor/templates/editor/mapitems.html b/src/c3nav/editor/templates/editor/mapitems.html index eea13548..999e0ffa 100644 --- a/src/c3nav/editor/templates/editor/mapitems.html +++ b/src/c3nav/editor/templates/editor/mapitems.html @@ -13,7 +13,8 @@ {% endif %} - {{ item.name }} + + {% if item.title != item.name %}{{ item.title }} {{ item.name }}{% else %}{{ item.name }}{% endif %} {% if has_elevator %} {{ item.elevator }} {% endif %} diff --git a/src/c3nav/mapdata/migrations/0018_auto_20161212_1205.py b/src/c3nav/mapdata/migrations/0018_auto_20161212_1205.py new file mode 100644 index 00000000..6343d1f6 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0018_auto_20161212_1205.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-12 12:05 +from __future__ import unicode_literals + +import c3nav.mapdata.fields +import c3nav.mapdata.models.interest +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0017_auto_20161208_2039'), + ] + + operations = [ + migrations.CreateModel( + name='AreaOfInterest', + 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()), + ('titles', c3nav.mapdata.fields.JSONField()), + ], + options={ + 'default_related_name': 'areasofinterest', + 'verbose_name_plural': 'Areas of Interest', + 'verbose_name': 'Area of Interest', + }, + bases=(models.Model, c3nav.mapdata.models.interest.MapItemOfInterestMixin), + ), + migrations.CreateModel( + name='GroupOfInterest', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.SlugField(unique=True, verbose_name='Name')), + ('titles', c3nav.mapdata.fields.JSONField()), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groupsofinterest', to='mapdata.Package', verbose_name='map package')), + ], + options={ + 'default_related_name': 'groupsofinterest', + 'verbose_name_plural': 'Groups of Interest', + 'verbose_name': 'Group of Interest', + }, + bases=(models.Model, c3nav.mapdata.models.interest.MapItemOfInterestMixin), + ), + migrations.AddField( + model_name='areaofinterest', + name='groups', + field=models.ManyToManyField(blank=True, related_name='areasofinterest', to='mapdata.GroupOfInterest', verbose_name='Groups of Interest'), + ), + migrations.AddField( + model_name='areaofinterest', + name='level', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='areasofinterest', to='mapdata.Level', verbose_name='level'), + ), + migrations.AddField( + model_name='areaofinterest', + name='package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='areasofinterest', to='mapdata.Package', verbose_name='map package'), + ), + ] diff --git a/src/c3nav/mapdata/models/__init__.py b/src/c3nav/mapdata/models/__init__.py index 0b53072e..ad8be331 100644 --- a/src/c3nav/mapdata/models/__init__.py +++ b/src/c3nav/mapdata/models/__init__.py @@ -3,3 +3,4 @@ from .package import Package # noqa from .source import Source # noqa from .collections import Elevator # noqa from .geometry import GeometryMapItemWithLevel, GEOMETRY_MAPITEM_TYPES # noqa +from .interest import AreaOfInterest, GroupOfInterest # noqa diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index 7dcc7edd..c6551b33 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -3,6 +3,7 @@ from collections import OrderedDict from django.db import models from django.db.models.base import ModelBase from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import get_language from c3nav.mapdata.lastupdate import set_last_mapdata_update @@ -23,6 +24,15 @@ class MapItem(models.Model, metaclass=MapItemMeta): EditorForm = None + @property + def title(self): + if not hasattr(self, 'titles'): + return self.name + lang = get_language() + if lang in self.titles: + return self.titles[lang] + return next(iter(self.titles.values())) if self.titles else self.name + @classmethod def get_path_prefix(cls): return cls._meta.default_related_name + '/' diff --git a/src/c3nav/mapdata/models/geometry.py b/src/c3nav/mapdata/models/geometry.py index 1cfce748..70c49c5f 100644 --- a/src/c3nav/mapdata/models/geometry.py +++ b/src/c3nav/mapdata/models/geometry.py @@ -32,10 +32,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta): class Meta: abstract = True - @property - def title(self): - return self.name - @classmethod def fromfile(cls, data, file_path): kwargs = super().fromfile(data, file_path) diff --git a/src/c3nav/mapdata/models/interest.py b/src/c3nav/mapdata/models/interest.py new file mode 100644 index 00000000..5a4418c4 --- /dev/null +++ b/src/c3nav/mapdata/models/interest.py @@ -0,0 +1,82 @@ +from collections import OrderedDict + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from c3nav.mapdata.fields import JSONField +from c3nav.mapdata.models.base import MapItem +from c3nav.mapdata.models.geometry import GeometryMapItemWithLevel + + +# noinspection PyUnresolvedReferences +class MapItemOfInterestMixin: + def get_geojson_properties(self): + result = super().get_geojson_properties() + result['titles'] = OrderedDict(sorted(self.titles.items())) + return result + + @classmethod + def fromfile(cls, data, file_path): + kwargs = super().fromfile(data, file_path) + + if 'titles' not in data: + raise ValueError('missing titles.') + titles = data['titles'] + if not isinstance(titles, dict): + raise ValueError('Invalid titles format.') + if any(not isinstance(lang, str) for lang in titles.keys()): + raise ValueError('titles: All languages have to be strings.') + if any(not isinstance(title, str) for title in titles.values()): + raise ValueError('titles: All titles have to be strings.') + if any(not title for title in titles.values()): + raise ValueError('titles: Titles must not be empty strings.') + kwargs['titles'] = titles + return kwargs + + def tofile(self): + result = super().tofile() + result['titles'] = OrderedDict(sorted(self.titles.items())) + return result + + +class GroupOfInterest(MapItem, MapItemOfInterestMixin): + titles = JSONField() + + class Meta: + verbose_name = _('Group of Interest') + verbose_name_plural = _('Groups of Interest') + default_related_name = 'groupsofinterest' + + +class AreaOfInterest(GeometryMapItemWithLevel, MapItemOfInterestMixin): + titles = JSONField() + groups = models.ManyToManyField(GroupOfInterest, verbose_name=_('Groups of Interest'), blank=True) + + geomtype = 'polygon' + + class Meta: + verbose_name = _('Area of Interest') + verbose_name_plural = _('Areas of Interest') + default_related_name = 'areasofinterest' + + @classmethod + def fromfile(cls, data, file_path): + kwargs = super().fromfile(data, file_path) + + groups = data.get('groups', []) + if not isinstance(groups, list): + raise TypeError('groups has to be a list') + kwargs['groups'] = groups + + return kwargs + + def get_geojson_properties(self): + result = super().get_geojson_properties() + result['groups'] = tuple(self.groups.all().order_by('name').values_list('name', flat=True)) + return result + + def tofile(self): + result = super().tofile() + result['groups'] = sorted(self.groups.all().order_by('name').values_list('name', flat=True)) + result.move_to_end('geometry') + return result diff --git a/src/c3nav/mapdata/packageio/const.py b/src/c3nav/mapdata/packageio/const.py index 144a88da..4e68f9c2 100644 --- a/src/c3nav/mapdata/packageio/const.py +++ b/src/c3nav/mapdata/packageio/const.py @@ -1,7 +1,8 @@ -from c3nav.mapdata.models import Level, Package, Source +from c3nav.mapdata.models import AreaOfInterest, GroupOfInterest, Level, Package, Source from c3nav.mapdata.models.collections import Elevator from c3nav.mapdata.models.geometry import (Building, Door, ElevatorLevel, Hole, LevelConnector, LineObstacle, Obstacle, Outside, Room, Stair) ordered_models = (Package, Level, LevelConnector, Source, Building, Room, Outside, Door, Obstacle, Hole) ordered_models += (Elevator, ElevatorLevel, LineObstacle, Stair) +ordered_models += (GroupOfInterest, AreaOfInterest) diff --git a/src/c3nav/mapdata/packageio/read.py b/src/c3nav/mapdata/packageio/read.py index fe6fb5c3..78b6dfbe 100644 --- a/src/c3nav/mapdata/packageio/read.py +++ b/src/c3nav/mapdata/packageio/read.py @@ -7,7 +7,7 @@ from collections import OrderedDict from django.conf import settings from django.core.management import CommandError -from c3nav.mapdata.models import Elevator, Level, Package +from c3nav.mapdata.models import AreaOfInterest, Elevator, GroupOfInterest, Level, Package from c3nav.mapdata.models.geometry import LevelConnector from c3nav.mapdata.packageio.const import ordered_models @@ -165,6 +165,11 @@ class ReaderItem: levels = [self.reader.saved_items[Level][name].obj.pk for name in self.data['levels']] self.data.pop('levels') + groups = [] + if self.model == AreaOfInterest: + groups = [self.reader.saved_items[GroupOfInterest][name].obj.pk for name in self.data['groups']] + self.data.pop('groups') + # Change name references to the referenced object for name, model in self.relations.items(): if name in self.data: @@ -186,3 +191,8 @@ class ReaderItem: self.obj.levels.clear() for level in levels: self.obj.levels.add(level) + + if groups: + self.obj.groups.clear() + for group in groups: + self.obj.groups.add(group)