diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api.py index a6fbecaf..deb9d26e 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api.py @@ -1,17 +1,15 @@ import mimetypes from collections import namedtuple from functools import wraps -from operator import attrgetter from django.core.cache import cache -from django.db.models import FieldDoesNotExist, Model, Prefetch +from django.db.models import Prefetch from django.http import HttpResponse from django.shortcuts import redirect -from django.urls import reverse from django.utils.cache import get_conditional_response from django.utils.http import quote_etag from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import get_language, get_language_info +from django.utils.translation import get_language from rest_framework.decorators import detail_route, list_route from rest_framework.exceptions import NotFound, ValidationError from rest_framework.mixins import RetrieveModelMixin @@ -19,7 +17,7 @@ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet, ViewSet from c3nav.mapdata.models import AccessRestriction, Building, Door, Hole, LocationGroup, MapUpdate, Source, Space -from c3nav.mapdata.models.access import AccessPermission, AccessRestrictionMixin +from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.geometry.level import LevelGeometryMixin from c3nav.mapdata.models.geometry.space import POI, Area, Column, LineObstacle, Obstacle, SpaceGeometryMixin, Stair from c3nav.mapdata.models.level import Level @@ -301,78 +299,7 @@ class LocationViewSet(RetrieveModelMixin, GenericViewSet): if isinstance(location, LocationRedirect): return redirect('../' + location.target.slug + '/display/') - result = location.serialize(geometry=True) - - display = [ - (str(_('Type')), str(location.__class__._meta.verbose_name)), - (str(_('ID')), str(location.pk)), - ] - - for lang, title in sorted(location.titles.items(), key=lambda item: item[0] != get_language()): - language = _('Title ({lang})').format(lang=get_language_info(lang)['name_translated']) - display.append((language, title)) - - display.append((str(_('Slug')), str(location.get_slug()))) - - if isinstance(location, Level): - display.append((str(_('short label')), location.short_label)) - - if isinstance(location, LevelGeometryMixin): - display.append((str(_('Level')), {'slug': location.level.get_slug(), 'title': location.level.title})) - - if isinstance(location, SpaceGeometryMixin): - display.append((str(_('Space')), {'slug': location.space.get_slug(), 'title': location.space.title})) - - if isinstance(location, LocationGroup): - display.append((str(_('Category')), location.category.title)) - - if isinstance(location, AccessRestrictionMixin): - display.append((str(_('Access Restriction')), - location.access_restriction_id and location.access_restriction.title)) - - groupcategories = {} - if isinstance(location, SpecificLocation): - for group in location.groups.all(): - groupcategories.setdefault(group.category, []).append(group) - - for category, groups in sorted(groupcategories.items(), key=lambda item: item[0].priority, reverse=True): - display.append((category.title, tuple( - {'slug': group.get_slug(), 'title': group.title} - for group in sorted(groups, key=attrgetter('priority'), reverse=True) - ))) - - model: Model = location.__class__ - for name in ('can_search', 'can_describe', 'color', 'outside', 'base_altitude', 'height', 'default_height', - 'priority'): - try: - field = model._meta.get_field(name) - except FieldDoesNotExist: - continue - - value = getattr(location, name) - if isinstance(value, bool): - value = _('Ja') if value else _('Nein') - display.append((str(field.verbose_name), value and str(value))) - - editor_url = None - if isinstance(location, Level): - editor_url = reverse('editor.levels.detail', kwargs={'pk': location.pk}) - elif isinstance(location, Space): - editor_url = reverse('editor.spaces.detail', kwargs={'level': location.level_id, 'pk': location.pk}) - elif isinstance(location, Area): - editor_url = reverse('editor.areas.edit', kwargs={'space': location.space_id, 'pk': location.pk}) - elif isinstance(location, POI): - editor_url = reverse('editor.pois.edit', kwargs={'space': location.space_id, 'pk': location.pk}) - elif isinstance(location, LocationGroup): - editor_url = reverse('editor.locationgroups.edit', kwargs={'pk': location.pk}) - - result = { - 'editor_url': editor_url, - 'display': display, - 'geometry': result['geometry'], - } - - return Response(result) + return Response(location.details_display()) @list_route(methods=['get']) @api_etag(permissions=False) diff --git a/src/c3nav/mapdata/models/access.py b/src/c3nav/mapdata/models/access.py index 0cc75400..158395d6 100644 --- a/src/c3nav/mapdata/models/access.py +++ b/src/c3nav/mapdata/models/access.py @@ -89,6 +89,13 @@ class AccessRestrictionMixin(SerializableMixin, models.Model): result['access_restriction'] = self.access_restriction_id return result + def details_display(self): + result = super().details_display() + result['display'].extend([ + (str(_('Access Restriction')), self.access_restriction_id and self.access_restriction.title), + ]) + return result + @classmethod def qs_for_request(cls, request, allow_none=False): return cls.objects.filter(cls.q_for_request(request, allow_none=allow_none)) diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index bcbb2965..fbbfc3d6 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -3,7 +3,7 @@ from collections import OrderedDict from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import get_language +from django.utils.translation import get_language, get_language_info from c3nav.mapdata.fields import JSONField from c3nav.mapdata.models import MapUpdate @@ -32,6 +32,14 @@ class SerializableMixin(models.Model): result['id'] = self.pk return result + def details_display(self): + return { + 'display': [ + (str(_('Type')), str(self.__class__._meta.verbose_name)), + (str(_('ID')), str(self.pk)), + ] + } + @property def title(self): return self._meta.verbose_name + ' ' + str(self.id) @@ -57,6 +65,13 @@ class TitledMixin(SerializableMixin, models.Model): result['title'] = self.title return result + def details_display(self): + result = super().details_display() + for lang, title in sorted(self.titles.items(), key=lambda item: item[0] != get_language()): + language = _('Title ({lang})').format(lang=get_language_info(lang)['name_translated']) + result['display'].append((language, title)) + return result + @property def title(self): lang = get_language() diff --git a/src/c3nav/mapdata/models/geometry/base.py b/src/c3nav/mapdata/models/geometry/base.py index 8056965c..d57519dc 100644 --- a/src/c3nav/mapdata/models/geometry/base.py +++ b/src/c3nav/mapdata/models/geometry/base.py @@ -88,6 +88,11 @@ class GeometryMixin(SerializableMixin): (int(math.ceil(self.maxx)), int(math.ceil(self.maxy)))) return result + def details_display(self): + result = super().details_display() + result['geometry'] = format_geojson(mapping(self.geometry), round=False) + return result + def get_shadow_geojson(self): pass diff --git a/src/c3nav/mapdata/models/geometry/level.py b/src/c3nav/mapdata/models/geometry/level.py index 094e4c1c..fdc8ea74 100644 --- a/src/c3nav/mapdata/models/geometry/level.py +++ b/src/c3nav/mapdata/models/geometry/level.py @@ -4,6 +4,7 @@ from operator import attrgetter, itemgetter import numpy as np from django.db import models from django.db.models import F +from django.urls import reverse from django.utils.text import format_lazy from django.utils.translation import ugettext_lazy as _ from scipy.sparse.csgraph._shortest_path import dijkstra @@ -43,6 +44,11 @@ class LevelGeometryMixin(GeometryMixin): result['level'] = self.level_id return result + def details_display(self): + result = super().details_display() + result['display'].insert(3, (str(_('Level')), {'slug': self.level.get_slug(), 'title': self.level.title})) + return result + @property def subtitle(self): base_subtitle = super().subtitle @@ -96,6 +102,15 @@ class Space(LevelGeometryMixin, SpecificLocation, models.Model): result['height'] = None if self.height is None else float(str(self.height)) return result + def details_display(self): + result = super().details_display() + result['display'].extend([ + (str(_('height')), self.height), + (str(_('outside only')), str(_('yes') if self.outside else _('no'))), + ]) + result['editor_url'] = reverse('editor.spaces.detail', kwargs={'level': self.level_id, 'pk': self.pk}) + return result + class Door(LevelGeometryMixin, AccessRestrictionMixin, models.Model): """ diff --git a/src/c3nav/mapdata/models/geometry/space.py b/src/c3nav/mapdata/models/geometry/space.py index daae57da..bfb54cb3 100644 --- a/src/c3nav/mapdata/models/geometry/space.py +++ b/src/c3nav/mapdata/models/geometry/space.py @@ -1,4 +1,5 @@ from django.db import models +from django.urls import reverse from django.utils.functional import cached_property from django.utils.text import format_lazy from django.utils.translation import ugettext_lazy as _ @@ -52,6 +53,11 @@ class SpaceGeometryMixin(GeometryMixin): self.geometry if force else self.get_changed_geometry() )) + def details_display(self): + result = super().details_display() + result['display'].insert(3, (str(_('Space')), {'slug': self.space.get_slug(), 'title': self.space.title})) + return result + def register_delete(self): space = self.space changed_geometries.register(space.level_id, space.geometry.intersection(self.geometry)) @@ -88,6 +94,11 @@ class Area(SpaceGeometryMixin, SpecificLocation, models.Model): result = super()._serialize(**kwargs) return result + def details_display(self): + result = super().details_display() + result['editor_url'] = reverse('editor.areas.edit', kwargs={'space': self.space_id, 'pk': self.pk}) + return result + class Stair(SpaceGeometryMixin, models.Model): """ @@ -168,6 +179,11 @@ class POI(SpaceGeometryMixin, SpecificLocation, models.Model): verbose_name_plural = _('Points of Interest') default_related_name = 'pois' + def details_display(self): + result = super().details_display() + result['editor_url'] = reverse('editor.pois.edit', kwargs={'space': self.space_id, 'pk': self.pk}) + return result + class Hole(SpaceGeometryMixin, models.Model): """ diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index aebaac95..ba330ad7 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -6,6 +6,7 @@ from operator import attrgetter, itemgetter from django.conf import settings from django.db import models from django.db.models import Prefetch +from django.urls import reverse from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from shapely.geometry import JOIN_STYLE, box @@ -88,6 +89,16 @@ class Level(SpecificLocation, models.Model): result['default_height'] = float(str(self.default_height)) return result + def details_display(self): + result = super().details_display() + result['display'].insert(3, (str(_('short label')), self.short_label)) + result['display'].extend([ + (str(_('outside only')), self.base_altitude), + (str(_('default height')), self.default_height), + ]) + result['editor_url'] = reverse('editor.levels.detail', kwargs={'pk': self.pk}) + return result + def _render_space_ground(self, svg, space): areas_by_color = {} for area in space.areas.all(): diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index f23fed4f..6d8d5942 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -1,8 +1,10 @@ from collections import OrderedDict from contextlib import suppress +from operator import attrgetter from django.db import models from django.db.models import Prefetch +from django.urls import reverse from django.utils.functional import cached_property from django.utils.text import format_lazy from django.utils.translation import ugettext_lazy as _ @@ -57,6 +59,11 @@ class LocationSlug(SerializableMixin, models.Model): result['slug'] = self.get_slug() return result + def details_display(self): + result = super().details_display() + result['display'].insert(2, (str(_('Slug')), str(self.get_slug()))) + return result + @cached_property def order(self): return (-1, 0) @@ -94,6 +101,14 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model): result['can_describe'] = self.can_search return result + def details_display(self): + result = super().details_display() + result['display'].extend([ + (str(_('can be searched')), str(_('yes') if self.can_search else _('no'))), + (str(_('can describe')), str(_('yes') if self.can_describe else _('no'))) + ]) + return result + def get_slug(self): if self.slug is None: code = self.LOCATION_TYPE_CODES.get(self.__class__.__name__) @@ -139,6 +154,21 @@ class SpecificLocation(Location, models.Model): result['groups'] = groups return result + def details_display(self): + result = super().details_display() + + groupcategories = {} + for group in self.groups.all(): + groupcategories.setdefault(group.category, []).append(group) + + for category, groups in sorted(groupcategories.items(), key=lambda item: item[0].priority): + result['display'].insert(3, (category.title, tuple( + {'slug': group.get_slug(), 'title': group.title} + for group in sorted(groups, key=attrgetter('priority'), reverse=True) + ))) + + return result + @property def subtitle(self): groups = tuple(self.groups.all()) @@ -230,6 +260,16 @@ class LocationGroup(Location, models.Model): result['locations'] = tuple(obj.pk for obj in getattr(self, 'locations', ())) return result + def details_display(self): + result = super().details_display() + result['display'].insert(3, (str(_('Category')), self.category.title)) + result['display'].extend([ + (str(_('color')), self.color), + (str(_('priority')), self.priority), + ]) + result['editor_url'] = reverse('editor.locationgroups.edit', kwargs={'pk': self.pk}) + return result + @property def title_for_forms(self): attributes = []