From af2c05a13ce4cce40cdaf0b580d7a042ad764c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Mon, 19 Dec 2016 16:54:11 +0100 Subject: [PATCH] location subtitles --- src/c3nav/editor/forms.py | 3 +- src/c3nav/editor/views.py | 4 + src/c3nav/mapdata/api.py | 12 ++- .../migrations/0025_auto_20161219_1440.py | 36 +++++++ .../migrations/0026_auto_20161219_1501.py | 20 ++++ src/c3nav/mapdata/models/locations.py | 99 +++++++++++++++++++ 6 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/c3nav/mapdata/migrations/0025_auto_20161219_1440.py create mode 100644 src/c3nav/mapdata/migrations/0026_auto_20161219_1501.py diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index f428178f..69875a54 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -108,7 +108,8 @@ class MapitemFormMixin(ModelForm): def create_editor_form(mapitemtype): possible_fields = ['name', 'package', 'altitude', 'level', 'intermediate', 'levels', 'geometry', 'direction', - 'elevator', 'button', 'crop_to_level', 'width', 'groups', 'override_altitude'] + 'elevator', 'button', 'crop_to_level', 'width', 'groups', 'override_altitude', + 'location_type', 'can_search', 'can_describe'] existing_fields = [field.name for field in mapitemtype._meta.get_fields() if field.name in possible_fields] class EditorForm(MapitemFormMixin, ModelForm): diff --git a/src/c3nav/editor/views.py b/src/c3nav/editor/views.py index f3fcbe80..48212d18 100644 --- a/src/c3nav/editor/views.py +++ b/src/c3nav/editor/views.py @@ -7,6 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils import translation from c3nav.editor.hosters import get_hoster_for_package, hosters +from c3nav.mapdata.models import AreaLocation from c3nav.mapdata.models.base import MAPITEM_TYPES from c3nav.mapdata.models.package import Package from c3nav.mapdata.packageio.write import json_encode @@ -54,6 +55,9 @@ def list_mapitems(request, mapitem_type, level=None): elif hasattr(mapitemtype, 'levels'): queryset = queryset.filter(levels__name=level) + if issubclass(mapitemtype, AreaLocation): + queryset = sorted(queryset, key=AreaLocation.get_sort_key) + return render(request, 'editor/mapitems.html', { 'mapitem_type': mapitem_type, 'title': mapitemtype._meta.verbose_name_plural, diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api.py index 3cb177b2..162ffd1e 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api.py @@ -68,6 +68,10 @@ class GeometryViewSet(ViewSet): return self._list(request, types=types, level=level, packages=packages, add_cache_key=cache_key) + @staticmethod + def compare_by_location_type(x: AreaLocation, y: AreaLocation): + return AreaLocation.LOCATION_TYPES.index(x.location_type) - AreaLocation.LOCATION_TYPES.index(y.location_type) + @cache_mapdata_api_response() def _list(self, request, types, level, packages): results = [] @@ -94,6 +98,9 @@ class GeometryViewSet(ViewSet): if hasattr(mapitemtype, field_name): queryset.prefetch_related(field_name) + if issubclass(mapitemtype, AreaLocation): + queryset = sorted(queryset, key=AreaLocation.get_sort_key) + if issubclass(mapitemtype, DirectedLineGeometryMapItemWithLevel): results.extend(obj.to_shadow_geojson() for obj in queryset) @@ -161,8 +168,9 @@ class LocationViewSet(CachedReadOnlyViewSetMixin, ViewSet): def list(self, request, **kwargs): locations = [] - locations += list(filter_queryset_by_package_access(request, AreaLocation.objects.all())) - locations += list(filter_queryset_by_package_access(request, LocationGroup.objects.all())) + locations += sorted(filter_queryset_by_package_access(request, AreaLocation.objects.filter(can_search=True)), + key=AreaLocation.get_sort_key) + locations += list(filter_queryset_by_package_access(request, LocationGroup.objects.filter(can_search=True))) return Response([location.to_location_json() for location in locations]) def retrieve(self, request, name=None, **kwargs): diff --git a/src/c3nav/mapdata/migrations/0025_auto_20161219_1440.py b/src/c3nav/mapdata/migrations/0025_auto_20161219_1440.py new file mode 100644 index 00000000..8f557c00 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0025_auto_20161219_1440.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-19 14:40 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0024_oneway'), + ] + + operations = [ + migrations.AddField( + model_name='arealocation', + name='can_describe', + field=models.BooleanField(default=True, verbose_name='can be used to describe a position'), + ), + migrations.AddField( + model_name='arealocation', + name='can_search', + field=models.BooleanField(default=True, verbose_name='can be searched'), + ), + migrations.AddField( + model_name='arealocation', + name='location_type', + field=models.CharField(choices=[('level', 'Level name'), ('area', 'General Area'), ('room', 'Room'), ('roomsegment', 'Room Segment'), ('poi', 'Point of Interest')], default='room', max_length=20, verbose_name='Location Type'), + preserve_default=False, + ), + migrations.AddField( + model_name='locationgroup', + name='can_search', + field=models.BooleanField(default=True, verbose_name='can be searched'), + ), + ] diff --git a/src/c3nav/mapdata/migrations/0026_auto_20161219_1501.py b/src/c3nav/mapdata/migrations/0026_auto_20161219_1501.py new file mode 100644 index 00000000..4298e459 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0026_auto_20161219_1501.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-19 15:01 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0025_auto_20161219_1440'), + ] + + operations = [ + migrations.AlterField( + model_name='arealocation', + name='location_type', + field=models.CharField(choices=[('level', 'Level'), ('area', 'General Area'), ('room', 'Room'), ('roomsegment', 'Room Segment'), ('poi', 'Point of Interest')], max_length=20, verbose_name='Location Type'), + ), + ] diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 0a5fb7b7..22e93732 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -1,11 +1,13 @@ import re from collections import OrderedDict +from django.core.cache import cache from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from c3nav.mapdata.fields import JSONField +from c3nav.mapdata.lastupdate import get_last_mapdata_update from c3nav.mapdata.models import Level from c3nav.mapdata.models.base import MapItem from c3nav.mapdata.models.geometry import GeometryMapItemWithLevel @@ -67,6 +69,7 @@ class LocationModelMixin(Location): class LocationGroup(LocationModelMixin, MapItem): titles = JSONField() + can_search = models.BooleanField(default=True, verbose_name=_('can be searched')) class Meta: verbose_name = _('Location Group') @@ -77,9 +80,42 @@ class LocationGroup(LocationModelMixin, MapItem): def location_id(self): return 'g:'+self.name + @classmethod + def fromfile(cls, data, file_path): + kwargs = super().fromfile(data, file_path) + + if 'can_search' not in data: + raise ValueError('Missing can_search') + can_search = data['can_search'] + if not isinstance(can_search, bool): + raise ValueError('can_search has to be boolean!') + kwargs['can_search'] = can_search + + return kwargs + + def tofile(self): + result = super().tofile() + result['can_search'] = self.can_search + return result + + def __str__(self): + return self.title + class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel): + LOCATION_TYPES = ( + ('level', _('Level')), + ('area', _('General Area')), + ('room', _('Room')), + ('roomsegment', _('Room Segment')), + ('poi', _('Point of Interest')), + ) + LOCATION_TYPES_ORDER = tuple(name for name, title in LOCATION_TYPES) + + location_type = models.CharField(max_length=20, verbose_name=_('Location Type'), choices=LOCATION_TYPES) titles = JSONField() + can_search = models.BooleanField(default=True, verbose_name=_('can be searched')) + can_describe = models.BooleanField(default=True, verbose_name=_('can be used to describe a position')) groups = models.ManyToManyField(LocationGroup, verbose_name=_('Location Groups'), blank=True) geomtype = 'polygon' @@ -93,6 +129,45 @@ class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel): def location_id(self): return self.name + def get_in_areas(self): + last_update = get_last_mapdata_update() + if last_update is None: + return self._get_in_areas() + + cache_key = 'c3nav__mapdata__location__in__areas__'+last_update.isoformat()+'__'+self.name, + in_areas = cache.get(cache_key) + if not in_areas: + in_areas = self._get_in_areas() + cache.set(cache_key, in_areas, 900) + + return in_areas + + def _get_in_areas(self): + my_area = self.geometry.area + + in_areas = [] + area_location_i = self.get_sort_key(self) + for location_type in reversed(self.LOCATION_TYPES_ORDER[:area_location_i]): + for arealocation in AreaLocation.objects.filter(location_type=location_type, level=self.level): + intersection_area = arealocation.geometry.intersection(self.geometry).area + if intersection_area and my_area / intersection_area > 0.99: + in_areas.append(arealocation) + + return in_areas + + @property + def subtitle(self): + return self.get_subtitle() + + def get_subtitle(self): + items = [self.get_location_type_display()] + items += [area.title for area in self.get_in_areas()] + return ', '.join(items) + + @classmethod + def get_sort_key(cls, arealocation): + return cls.LOCATION_TYPES_ORDER.index(arealocation.location_type) + @classmethod def fromfile(cls, data, file_path): kwargs = super().fromfile(data, file_path) @@ -102,6 +177,27 @@ class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel): raise TypeError('groups has to be a list') kwargs['groups'] = groups + if 'location_type' not in data: + raise ValueError('Missing location type') + location_type = data['location_type'] + if location_type not in dict(cls.LOCATION_TYPES): + raise ValueError('Invalid location type') + kwargs['location_tyoe'] = location_type + + if 'can_search' not in data: + raise ValueError('Missing can_search') + can_search = data['can_search'] + if not isinstance(can_search, bool): + raise ValueError('can_search has to be boolean!') + kwargs['can_search'] = can_search + + if 'can_describe' not in data: + raise ValueError('Missing can_describe') + can_describe = data['can_describe'] + if not isinstance(can_describe, bool): + raise ValueError('can_describe has to be boolean!') + kwargs['can_describe'] = can_describe + return kwargs def get_geojson_properties(self): @@ -112,6 +208,9 @@ class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel): def tofile(self): result = super().tofile() result['groups'] = sorted(self.groups.all().order_by('name').values_list('name', flat=True)) + result['location_type'] = self.location_type + result['can_search'] = self.can_search + result['can_describe'] = self.can_describe result.move_to_end('geometry') return result