98 lines
3.9 KiB
Python
98 lines
3.9 KiB
Python
from decimal import Decimal
|
|
from itertools import chain
|
|
from operator import attrgetter
|
|
|
|
from django.core.validators import MinValueValidator
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
from django.utils.functional import cached_property
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from shapely.ops import cascaded_union
|
|
|
|
from c3nav.mapdata.models.locations import SpecificLocation
|
|
|
|
|
|
class Level(SpecificLocation, models.Model):
|
|
"""
|
|
A map level
|
|
"""
|
|
base_altitude = models.DecimalField(_('base altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
|
|
default_height = models.DecimalField(_('default space height'), max_digits=6, decimal_places=2, default=3.0,
|
|
validators=[MinValueValidator(Decimal('0'))])
|
|
door_height = models.DecimalField(_('door height'), max_digits=6, decimal_places=2, default=2.0,
|
|
validators=[MinValueValidator(Decimal('0'))])
|
|
on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE,
|
|
related_name='levels_on_top', verbose_name=_('on top of'))
|
|
short_label = models.SlugField(max_length=20, verbose_name=_('short label'), unique=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('Level')
|
|
verbose_name_plural = _('Levels')
|
|
default_related_name = 'levels'
|
|
ordering = ['base_altitude']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def lower(self, level_model=None):
|
|
if self.on_top_of_id is not None:
|
|
raise TypeError
|
|
if level_model is None:
|
|
level_model = Level
|
|
return level_model.objects.filter(base_altitude__lt=self.base_altitude,
|
|
on_top_of__isnull=True).order_by('-base_altitude')
|
|
|
|
def higher(self, level_model=None):
|
|
if self.on_top_of_id is not None:
|
|
raise TypeError
|
|
if level_model is None:
|
|
level_model = Level
|
|
return level_model.objects.filter(base_altitude__gt=self.base_altitude,
|
|
on_top_of__isnull=True).order_by('base_altitude')
|
|
|
|
@property
|
|
def sublevels(self):
|
|
if self.on_top_of is not None:
|
|
raise TypeError
|
|
return chain((self, ), self.levels_on_top.all())
|
|
|
|
@property
|
|
def sublevel_title(self):
|
|
return '-' if self.on_top_of_id is None else self.title
|
|
|
|
@property
|
|
def primary_level(self):
|
|
return self if self.on_top_of_id is None else self.on_top_of
|
|
|
|
@property
|
|
def primary_level_pk(self):
|
|
return self.pk if self.on_top_of_id is None else self.on_top_of_id
|
|
|
|
def _serialize(self, level=True, **kwargs):
|
|
result = super()._serialize(**kwargs)
|
|
result['short_label'] = self.short_label
|
|
result['on_top_of'] = self.on_top_of_id
|
|
result['base_altitude'] = float(str(self.base_altitude))
|
|
result['default_height'] = float(str(self.default_height))
|
|
result['door_height'] = float(str(self.door_height))
|
|
return result
|
|
|
|
def details_display(self, editor_url=True, **kwargs):
|
|
result = super().details_display(**kwargs)
|
|
result['display'].insert(3, (_('short label'), self.short_label))
|
|
result['display'].extend([
|
|
(_('outside only'), self.base_altitude),
|
|
(_('default height'), self.default_height),
|
|
])
|
|
if editor_url:
|
|
result['editor_url'] = reverse('editor.levels.detail', kwargs={'pk': self.pk})
|
|
return result
|
|
|
|
@cached_property
|
|
def min_altitude(self):
|
|
return min(self.altitudeareas.all(), key=attrgetter('altitude'), default=self.base_altitude).altitude
|
|
|
|
@cached_property
|
|
def bounds(self):
|
|
return cascaded_union(tuple(item.geometry.buffer(0)
|
|
for item in chain(self.altitudeareas.all(), self.buildings.all()))).bounds
|