team-3/src/c3nav/routing/models.py
2019-12-24 17:29:00 +01:00

199 lines
6.4 KiB
Python

import threading
from collections import OrderedDict
from django import forms
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
from c3nav.mapdata.fields import JSONField
from c3nav.mapdata.models import MapUpdate, WayType
class RouteOptions(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
data = JSONField(default={})
class Meta:
verbose_name = _('Route options')
verbose_name_plural = _('Route options')
default_related_name = 'routeoptions'
fields_cached = None
fields_cache_key = None
fields_cache_lock = threading.Lock()
@classmethod
def build_fields(cls):
fields = OrderedDict()
fields['mode'] = forms.ChoiceField(
label=_('Routing mode'),
choices=(('fastest', _('fastest')), ('shortest', _('shortest'))),
initial='fastest'
)
fields['walk_speed'] = forms.ChoiceField(
label=_('Walk speed'),
choices=(('slow', _('slow')), ('default', _('default')), ('fast', _('fast'))),
initial='default'
)
for waytype in WayType.objects.all():
choices = []
choices.append(('allow', _('allow')))
if waytype.up_separate:
choices.append(('avoid_up', _('avoid upwards')))
choices.append(('avoid_down', _('avoid downwards')))
choices.append(('avoid', _('avoid completely')))
else:
choices.append(('avoid', _('avoid')))
fields['waytype_%d' % waytype.pk] = forms.ChoiceField(
label=waytype.title_plural,
choices=tuple(choices),
initial='allow'
)
return fields
@classmethod
def get_fields(cls):
cache_key = MapUpdate.current_cache_key()
if cls.fields_cache_key != cache_key:
with cls.fields_cache_lock:
cls.fields_cache_key = cache_key
cls.fields_cached = cls.build_fields()
return cls.fields_cached
@staticmethod
def get_cache_key(pk):
return 'routing:options:user:%d' % pk
@classmethod
def get_for_user(cls, user):
cache_key = cls.get_cache_key(user.pk)
result = cache.get(cache_key, None)
if result:
return result
try:
result = user.routeoptions
except AttributeError:
result = None
if result:
cache.set(cache_key, result, 900)
return result
@classmethod
def get_for_request(cls, request):
session_options = request.session.get('route_options', None)
if session_options is not None:
session_options = cls(request=request)
session_options.update(session_options, ignore_errors=True)
user_options = None
if request.user.is_authenticated:
user_options = cls.get_for_user(request.user)
if user_options is not None:
user_options.request = request
user_options.clean_data()
elif session_options:
user_options = session_options
user_options.user = request.user
user_options.save()
request.session.pop('session_options')
return user_options or session_options or cls(request=request)
def clean_data(self):
new_data = OrderedDict()
for name, field in self.get_fields().items():
value = self.data.get(name)
if value is None or value not in dict(field.choices):
value = field.initial
new_data[name] = value
self.data = new_data
def __init__(self, *args, request=None, **kwargs):
super().__init__(*args, **kwargs)
self.clean_data()
self.request = request
def __getitem__(self, key):
try:
return self.data[key]
except AttributeError:
return self.get_fields()[key].initial
def update(self, value_dict, ignore_errors=False, ignore_unknown=False):
if not value_dict:
return
if isinstance(value_dict, RouteOptions):
value_dict = value_dict.data
fields = self.get_fields()
for key, value in value_dict.items():
field = fields.get(key)
if not field:
if ignore_errors or ignore_unknown:
continue
raise ValidationError(_('Unknown route option: %s') % key)
if value is None or value not in dict(field.choices):
if ignore_errors:
continue
raise ValidationError(_('Invalid value for route option %s.') % key)
self.data[key] = value
def __setitem__(self, key, value):
self.update({key: value})
@property
def walk_factor(self):
return {'slow': 0.8, 'default': 1, 'fast': 1.2}[self['walk_speed']]
def get(self, key, default):
try:
return self[key]
except AttributeError:
return default
def serialize(self):
return [
{
'name': name,
'type': field.widget.input_type,
'label': field.label,
'choices': [
{
'name': choice_name,
'title': choice_title,
}
for choice_name, choice_title in field.choices
],
'value': self[name],
'value_display': dict(field.choices)[self[name]],
}
for name, field in self.get_fields().items()
]
def serialize_string(self):
return ','.join('%s=%s' % (key, val) for key, val in self.data.items())
@classmethod
def unserialize_string(cls, data):
return RouteOptions(
data=dict(item.split('=') for item in data.split(','))
)
def save(self, *args, **kwargs):
if self.request is None or self.request.user.is_authenticated:
self.user = self.request.user
return super().save(*args, **kwargs)
self.request.session['route_options'] = self.data
def items(self):
yield from self.data.items()