move location stuff into the corresponding models (now renamed to Locations)

This commit is contained in:
Laura Klünder 2016-12-16 11:03:40 +01:00
parent 7528686e87
commit ed0f168a77
11 changed files with 236 additions and 233 deletions

View file

@ -9,9 +9,9 @@ from rest_framework.decorators import detail_route
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from c3nav.mapdata.locations import AreaOfInterestLocation, GroupOfInterestLocation, get_location
from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES, AreaOfInterest, GroupOfInterest, Level, Package, Source
from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES, AreaLocation, Level, LocationGroup, Package, Source
from c3nav.mapdata.models.geometry import DirectedLineGeometryMapItemWithLevel
from c3nav.mapdata.models.locations import get_location
from c3nav.mapdata.permissions import filter_queryset_by_package_access, get_unlocked_packages_names
from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
from c3nav.mapdata.utils.cache import (CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_levels_cached,
@ -161,13 +161,9 @@ class LocationViewSet(CachedReadOnlyViewSetMixin, ViewSet):
def list(self, request, **kwargs):
locations = []
for area in filter_queryset_by_package_access(request, AreaOfInterest.objects.all()):
locations.append(AreaOfInterestLocation.from_cache(area))
for group in filter_queryset_by_package_access(request, GroupOfInterest.objects.all()):
locations.append(GroupOfInterestLocation.from_cache(group))
return Response([location.to_json() for location in locations])
locations += list(filter_queryset_by_package_access(request, AreaLocation.objects.all()))
locations += list(filter_queryset_by_package_access(request, LocationGroup.objects.all()))
return Response([location.to_location_json() for location in locations])
def retrieve(self, request, name=None, **kwargs):
location = get_location(request, name)

View file

@ -1,129 +0,0 @@
import re
from abc import ABC, abstractmethod
from collections import OrderedDict
from django.core.cache import cache
from c3nav.mapdata.lastupdate import get_last_mapdata_update
from c3nav.mapdata.models import AreaOfInterest, GroupOfInterest, Level
from c3nav.mapdata.permissions import filter_queryset_by_package_access
from c3nav.mapdata.utils.cache import get_levels_cached
def get_location(request, name):
match = re.match('^c:(?P<level>[a-z0-9-_]+):(?P<x>[0-9]+):(?P<y>[0-9]+)$', name)
if match:
levels = get_levels_cached()
level = levels.get(match.group('level'))
if level is None:
return None
return PointLocation.from_cache(level=level, x=int(match.group('x')), y=int(match.group('y')))
if name.startswith('g:'):
group = filter_queryset_by_package_access(request, GroupOfInterest.objects.filter(name=name[2:])).first()
if group is None:
return None
return GroupOfInterestLocation(group)
area = filter_queryset_by_package_access(request, AreaOfInterest.objects.filter(name=name)).first()
if area is None:
return None
return AreaOfInterestLocation(area)
class Location(ABC):
@classmethod
def _from_cache(cls, cache_key, *args, **kwargs):
last_update = get_last_mapdata_update()
if last_update is None:
return cls(*args, **kwargs)
cache_key = 'c3nav__locations__%s__%s__%s' % (last_update.isoformat(), cls.__name__, cache_key)
obj = cache.get(cache_key)
if not obj:
obj = cls(*args, **kwargs)
cache.set(cache_key, obj, 300)
return obj
def __init__(self, name):
self.name = name
@property
@abstractmethod
def title(self) -> str:
pass
@property
@abstractmethod
def subtitle(self) -> str:
pass
def to_json(self):
return OrderedDict((
('name', self.name),
('title', self.title),
('subtitle', self.subtitle),
))
class AreaOfInterestLocation(Location):
@classmethod
def from_cache(cls, area: AreaOfInterest):
return cls._from_cache(area.name, area)
def __init__(self, area: AreaOfInterest):
super().__init__(name=area.name)
self.area = area
@property
def title(self) -> str:
return self.area.title
@property
def subtitle(self) -> str:
return 'Location Group'
class GroupOfInterestLocation(Location):
@classmethod
def from_cache(cls, group: GroupOfInterest):
return cls._from_cache(group.name, group)
def __init__(self, group: GroupOfInterest):
super().__init__(name=group.name)
self.group = group
@property
def title(self) -> str:
return self.group.title
@property
def subtitle(self) -> str:
return 'Location'
class PointLocation(Location):
@classmethod
def from_cache(cls, level: Level, x: int, y: int):
return cls._from_cache('%s:%d:%d' % (level.name, x, y), level, x, y)
def __init__(self, level: Level, x: int, y: int):
super().__init__(name='c:%s:%d:%d' % (level.name, x, y))
self.level = level
self.x = x
self.y = y
@property
def title(self) -> str:
return 'Custom location'
@property
def subtitle(self) -> str:
return 'Coordinates'
def to_json(self):
result = super().to_json()
result['level'] = self.level.name
result['x'] = self.x
result['y'] = self.y
return result

View file

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import c3nav.mapdata.fields
import c3nav.mapdata.models.interest
import c3nav.mapdata.models.locations
from django.db import migrations, models
import django.db.models.deletion
@ -28,7 +28,7 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Areas of Interest',
'verbose_name': 'Area of Interest',
},
bases=(models.Model, c3nav.mapdata.models.interest.MapItemOfInterestMixin),
bases=(models.Model, c3nav.mapdata.models.locations.LocationModelMixin),
),
migrations.CreateModel(
name='GroupOfInterest',
@ -43,7 +43,7 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Groups of Interest',
'verbose_name': 'Group of Interest',
},
bases=(models.Model, c3nav.mapdata.models.interest.MapItemOfInterestMixin),
bases=(models.Model, c3nav.mapdata.models.locations.LocationModelMixin),
),
migrations.AddField(
model_name='areaofinterest',

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-16 09:23
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0018_auto_20161212_1205'),
]
operations = [
migrations.RenameModel('AreaOfInterest', 'AreaLocation'),
migrations.RenameModel('GroupOfInterest', 'LocationGroup'),
]

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-16 09:34
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0019_auto_20161216_0923'),
]
operations = [
migrations.AlterModelOptions(
name='arealocation',
options={'verbose_name': 'Area Location', 'verbose_name_plural': 'Area Locations'},
),
migrations.AlterModelOptions(
name='locationgroup',
options={'verbose_name': 'Location Group', 'verbose_name_plural': 'Location Groups'},
),
migrations.AlterField(
model_name='arealocation',
name='groups',
field=models.ManyToManyField(blank=True, related_name='arealocations', to='mapdata.LocationGroup', verbose_name='Location Groups'),
),
migrations.AlterField(
model_name='arealocation',
name='level',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arealocations', to='mapdata.Level', verbose_name='level'),
),
migrations.AlterField(
model_name='arealocation',
name='package',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arealocations', to='mapdata.Package', verbose_name='map package'),
),
migrations.AlterField(
model_name='locationgroup',
name='package',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locationgroups', to='mapdata.Package', verbose_name='map package'),
),
]

View file

@ -3,4 +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
from .locations import AreaLocation, LocationGroup # noqa

View file

@ -1,85 +0,0 @@
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(MapItemOfInterestMixin, MapItem):
titles = JSONField()
class Meta:
verbose_name = _('Group of Interest')
verbose_name_plural = _('Groups of Interest')
default_related_name = 'groupsofinterest'
class AreaOfInterest(MapItemOfInterestMixin, GeometryMapItemWithLevel):
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
def __str__(self):
return self.title

View file

@ -0,0 +1,160 @@
import re
from collections import OrderedDict
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.models import Level
from c3nav.mapdata.models.base import MapItem
from c3nav.mapdata.models.geometry import GeometryMapItemWithLevel
from c3nav.mapdata.permissions import filter_queryset_by_package_access
from c3nav.mapdata.utils.cache import get_levels_cached
class Location:
@property
def location_id(self):
raise NotImplementedError
@property
def subtitle(self):
raise NotImplementedError
def to_location_json(self):
return OrderedDict((
('id', self.location_id),
('title', str(self.title)),
('subtitle', str(self.subtitle)),
))
# noinspection PyUnresolvedReferences
class LocationModelMixin(Location):
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
@property
def subtitle(self):
return self._meta.verbose_name
class LocationGroup(LocationModelMixin, MapItem):
titles = JSONField()
class Meta:
verbose_name = _('Location Group')
verbose_name_plural = _('Location Groups')
default_related_name = 'locationgroups'
@cached_property
def location_id(self):
return 'g:'+self.name
class AreaLocation(LocationModelMixin, GeometryMapItemWithLevel):
titles = JSONField()
groups = models.ManyToManyField(LocationGroup, verbose_name=_('Location Groups'), blank=True)
geomtype = 'polygon'
class Meta:
verbose_name = _('Area Location')
verbose_name_plural = _('Area Locations')
default_related_name = 'arealocations'
@cached_property
def location_id(self):
return self.name
@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
def __str__(self):
return self.title
def get_location(request, name):
match = re.match('^c:(?P<level>[a-z0-9-_]+):(?P<x>[0-9]+):(?P<y>[0-9]+)$', name)
if match:
levels = get_levels_cached()
level = levels.get(match.group('level'))
if level is None:
return None
return PointLocation(level=level, x=int(match.group('x')), y=int(match.group('y')))
if name.startswith('g:'):
return filter_queryset_by_package_access(request, LocationGroup.objects.filter(name=name[2:])).first()
return filter_queryset_by_package_access(request, AreaLocation.objects.filter(name=name)).first()
class PointLocation(Location):
def __init__(self, level: Level, x: int, y: int):
self.level = level
self.x = x
self.y = y
@cached_property
def location_id(self):
return 'c:%s:%d:%d' % (self.level.name, self.x, self.y)
@property
def title(self) -> str:
return 'Custom location'
@property
def subtitle(self) -> str:
return 'Coordinates'
def to_location_json(self):
result = super().to_location_json()
result['level'] = self.level.name
result['x'] = self.x
result['y'] = self.y
return result

View file

@ -1,8 +1,8 @@
from c3nav.mapdata.models import AreaOfInterest, GroupOfInterest, Level, Package, Source
from c3nav.mapdata.models import AreaLocation, Level, LocationGroup, 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)
ordered_models += (LocationGroup, AreaLocation)

View file

@ -7,7 +7,7 @@ from collections import OrderedDict
from django.conf import settings
from django.core.management import CommandError
from c3nav.mapdata.models import AreaOfInterest, Elevator, GroupOfInterest, Level, Package
from c3nav.mapdata.models import AreaLocation, Elevator, Level, LocationGroup, Package
from c3nav.mapdata.models.geometry import LevelConnector
from c3nav.mapdata.packageio.const import ordered_models
@ -166,8 +166,8 @@ class ReaderItem:
self.data.pop('levels')
groups = []
if self.model == AreaOfInterest:
groups = [self.reader.saved_items[GroupOfInterest][name].obj.pk for name in self.data['groups']]
if self.model == AreaLocation:
groups = [self.reader.saved_items[LocationGroup][name].obj.pk for name in self.data['groups']]
self.data.pop('groups')
# Change name references to the referenced object

View file

@ -1,6 +1,6 @@
from django.shortcuts import redirect, render
from c3nav.mapdata.locations import get_location
from c3nav.mapdata.models.locations import get_location
def main(request, origin=None, destination=None):