team-3/src/c3nav/mapdata/models/locations.py

194 lines
6.4 KiB
Python
Raw Normal View History

2017-05-08 21:55:45 +02:00
import numpy as np
2016-12-19 16:54:11 +01:00
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 _
2017-05-11 09:06:19 +02:00
from django.utils.translation import get_language, ungettext_lazy
2017-05-11 19:36:49 +02:00
from c3nav.mapdata.fields import JSONField
2016-12-19 16:54:11 +01:00
from c3nav.mapdata.lastupdate import get_last_mapdata_update
2017-05-08 16:40:22 +02:00
from c3nav.mapdata.models.base import EditorFormMixin
2017-05-11 19:36:49 +02:00
LOCATION_MODELS = []
2017-05-10 18:03:57 +02:00
class LocationSlug(models.Model):
slug = models.SlugField(_('name'), unique=True, null=True, max_length=50)
2017-05-11 19:36:49 +02:00
def get_child(self):
# todo: cache this
for model in LOCATION_MODELS:
try:
return getattr(self, model._meta.default_related_name)
except AttributeError:
pass
return None
2017-05-10 18:03:57 +02:00
class Meta:
verbose_name = _('Slug for Location')
verbose_name_plural = _('Slugs für Locations')
default_related_name = 'locationslugs'
class LocationModelMixin:
pass
2017-05-11 19:36:49 +02:00
class Location(LocationSlug, EditorFormMixin, models.Model):
2017-05-10 18:03:57 +02:00
titles = JSONField(default={})
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'))
color = models.CharField(null=True, blank=True, max_length=16, verbose_name=_('background color'),
help_text=_('if set, has to be a valid color for svg images'))
public = models.BooleanField(verbose_name=_('public'), default=True)
class Meta:
abstract = True
2017-05-11 19:36:49 +02:00
def _serialize(self, **kwargs):
result = super()._serialize(**kwargs)
result['slug'] = self.slug
result['titles'] = self.titles
result['can_search'] = self.can_search
result['can_describe'] = self.can_search
result['color'] = self.color
result['public'] = self.public
2017-05-10 18:03:57 +02:00
return result
2017-05-11 09:06:19 +02:00
@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
2017-05-10 18:03:57 +02:00
@property
def subtitle(self):
return self._meta.verbose_name
class SpecificLocation(Location, models.Model):
groups = models.ManyToManyField('mapdata.LocationGroup', verbose_name=_('Location Groups'), blank=True)
class Meta:
abstract = True
2017-05-11 19:36:49 +02:00
def _serialize(self, **kwargs):
result = super()._serialize(**kwargs)
result['groups'] = list(self.groups.values_list('id', flat=True))
return result
2017-05-10 18:03:57 +02:00
class LocationGroup(Location, EditorFormMixin, models.Model):
2016-12-24 21:41:57 +01:00
compiled_room = models.BooleanField(default=False, verbose_name=_('is a compiled room'))
2017-05-10 21:24:31 +02:00
compiled_area = models.BooleanField(default=False, verbose_name=_('is a compiled area'))
class Meta:
verbose_name = _('Location Group')
verbose_name_plural = _('Location Groups')
default_related_name = 'locationgroups'
@cached_property
def location_id(self):
return 'g:'+self.slug
def get_in_levels(self):
last_update = get_last_mapdata_update()
if last_update is None:
return self._get_in_levels()
cache_key = 'c3nav__mapdata__locationgroup__in_levels__'+last_update.isoformat()+'__'+str(self.id),
in_levels = cache.get(cache_key)
if not in_levels:
in_levels = self._get_in_levels()
cache.set(cache_key, in_levels, 900)
return in_levels
def _get_in_levels(self):
level_ids = set()
in_levels = []
for arealocation in self.arealocations.all():
for area in arealocation.get_in_areas():
if area.location_type == 'level' and area.id not in level_ids:
level_ids.add(area.id)
in_levels.append(area)
2017-05-07 12:06:13 +02:00
in_levels = sorted(in_levels, key=lambda area: area.section.altitude)
return in_levels
2016-12-22 03:33:53 +01:00
@property
def subtitle(self):
if self.compiled_room:
return ', '.join(area.title for area in self.get_in_levels())
2016-12-22 03:33:53 +01:00
return ungettext_lazy('%d location', '%d locations') % self.arealocations.count()
2016-12-19 16:54:11 +01:00
def __str__(self):
return self.title
2017-05-11 19:36:49 +02:00
def _serialize(self, **kwargs):
result = super()._serialize(**kwargs)
result['compiled_room'] = self.compiled_room
result['compiled_area'] = self.compiled_area
return result
2017-05-11 19:36:49 +02:00
class PointLocation:
2017-05-10 18:03:57 +02:00
def __init__(self, section: 'Section', x: int, y: int, request):
2017-05-07 12:06:13 +02:00
self.section = section
self.x = x
self.y = y
self.request = request
@cached_property
def location_id(self):
2017-05-07 12:06:13 +02:00
return 'c:%d:%d:%d' % (self.section.id, self.x * 100, self.y * 100)
2016-12-21 01:59:08 +01:00
@cached_property
def xy(self):
return np.array((self.x, self.y))
@cached_property
def description(self):
from c3nav.routing.graph import Graph
graph = Graph.load()
2017-05-07 12:06:13 +02:00
point = graph.get_nearest_point(self.section, self.x, self.y)
2016-12-24 02:46:05 +01:00
if point is None or (':nonpublic' in point.arealocations and not self.request.c3nav_full_access and
not len(set(self.request.c3nav_access_list) & set(point.arealocations))):
return _('Unreachable Coordinates'), ''
2017-05-11 19:36:49 +02:00
AreaLocation = None
locations = sorted(AreaLocation.objects.filter(name__in=point.arealocations, can_describe=True),
key=AreaLocation.get_sort_key, reverse=True)
if not locations:
return _('Coordinates'), ''
location = locations[0]
if location.contains(self.x, self.y):
return (_('Coordinates in %(location)s') % {'location': location.title}), location.subtitle_without_type
else:
return (_('Coordinates near %(location)s') % {'location': location.title}), location.subtitle_without_type
@property
def title(self) -> str:
return self.description[0]
@property
def subtitle(self) -> str:
add_subtitle = self.description[1]
2017-05-07 12:06:13 +02:00
subtitle = '%s:%d:%d' % (self.section.name, self.x * 100, self.y * 100)
if add_subtitle:
subtitle += ' - '+add_subtitle
return subtitle
2016-12-27 23:39:14 +01:00
def to_location_json(self):
result = super().to_location_json()
2017-05-07 12:06:13 +02:00
result['section'] = self.section.id
result['x'] = self.x
result['y'] = self.y
return result