move location stuff into the corresponding models (now renamed to Locations)
This commit is contained in:
parent
7528686e87
commit
ed0f168a77
11 changed files with 236 additions and 233 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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',
|
||||
|
|
17
src/c3nav/mapdata/migrations/0019_auto_20161216_0923.py
Normal file
17
src/c3nav/mapdata/migrations/0019_auto_20161216_0923.py
Normal 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'),
|
||||
]
|
44
src/c3nav/mapdata/migrations/0020_auto_20161216_0934.py
Normal file
44
src/c3nav/mapdata/migrations/0020_auto_20161216_0934.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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
|
160
src/c3nav/mapdata/models/locations.py
Normal file
160
src/c3nav/mapdata/models/locations.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue