2016-10-13 15:55:15 +02:00
|
|
|
import json
|
2017-07-10 16:00:43 +02:00
|
|
|
import operator
|
2016-09-23 17:02:17 +02:00
|
|
|
from collections import OrderedDict
|
2017-07-10 16:00:43 +02:00
|
|
|
from functools import reduce
|
2016-09-23 17:02:17 +02:00
|
|
|
|
|
|
|
from django.conf import settings
|
2017-07-10 19:03:24 +02:00
|
|
|
from django.core.exceptions import FieldDoesNotExist
|
2017-07-26 13:52:23 +02:00
|
|
|
from django.forms import (BooleanField, CharField, ChoiceField, Form, ModelChoiceField, ModelForm, MultipleChoiceField,
|
|
|
|
ValidationError)
|
2016-09-23 15:23:02 +02:00
|
|
|
from django.forms.widgets import HiddenInput
|
2017-07-13 13:47:11 +02:00
|
|
|
from django.utils.text import format_lazy
|
2016-12-01 12:25:02 +01:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2017-07-13 13:47:11 +02:00
|
|
|
from django.utils.translation import get_language_info
|
2016-10-13 15:55:15 +02:00
|
|
|
from shapely.geometry.geo import mapping
|
2016-09-23 15:23:02 +02:00
|
|
|
|
2017-07-05 19:40:35 +02:00
|
|
|
from c3nav.editor.models import ChangeSet, ChangeSetUpdate
|
2017-07-26 13:20:55 +02:00
|
|
|
from c3nav.mapdata.fields import GeometryField
|
2017-07-26 13:52:23 +02:00
|
|
|
from c3nav.mapdata.models import GraphEdge, GraphNode
|
2017-06-29 15:53:26 +02:00
|
|
|
|
2016-09-23 15:23:02 +02:00
|
|
|
|
2017-07-10 19:03:45 +02:00
|
|
|
class EditorFormBase(ModelForm):
|
2016-10-16 15:17:30 +02:00
|
|
|
def __init__(self, *args, request=None, **kwargs):
|
2016-09-26 17:23:08 +02:00
|
|
|
self.request = request
|
2016-09-23 15:23:02 +02:00
|
|
|
super().__init__(*args, **kwargs)
|
2016-10-13 15:55:15 +02:00
|
|
|
creating = not self.instance.pk
|
2016-09-23 15:23:02 +02:00
|
|
|
|
2017-06-11 14:43:14 +02:00
|
|
|
if 'level' in self.fields:
|
|
|
|
# hide level widget
|
|
|
|
self.fields['level'].widget = HiddenInput()
|
2016-12-01 12:25:02 +01:00
|
|
|
|
2017-05-19 16:55:07 +02:00
|
|
|
if 'space' in self.fields:
|
|
|
|
# hide space widget
|
|
|
|
self.fields['space'].widget = HiddenInput()
|
|
|
|
|
2016-11-28 15:46:58 +01:00
|
|
|
if 'geometry' in self.fields:
|
|
|
|
# hide geometry widget
|
|
|
|
self.fields['geometry'].widget = HiddenInput()
|
|
|
|
if not creating:
|
|
|
|
self.initial['geometry'] = json.dumps(mapping(self.instance.geometry), separators=(',', ':'))
|
2016-10-13 15:55:15 +02:00
|
|
|
|
2017-05-16 14:50:36 +02:00
|
|
|
if 'groups' in self.fields:
|
2017-07-10 16:00:43 +02:00
|
|
|
LocationGroupCategory = self.request.changeset.wrap_model('LocationGroupCategory')
|
|
|
|
|
2017-07-10 19:04:35 +02:00
|
|
|
kwargs = {'allow_'+self._meta.model._meta.default_related_name: True}
|
2017-07-11 18:35:42 +02:00
|
|
|
categories = LocationGroupCategory.objects.filter(**kwargs).prefetch_related('groups').order_by('priority')
|
2017-07-10 19:18:44 +02:00
|
|
|
if self.instance.pk:
|
|
|
|
instance_groups = tuple(self.instance.groups.values_list('pk', flat=True))
|
|
|
|
else:
|
|
|
|
instance_groups = ()
|
2017-07-10 16:00:43 +02:00
|
|
|
|
|
|
|
self.fields.pop('groups')
|
|
|
|
|
|
|
|
for category in categories:
|
|
|
|
choices = tuple((str(group.pk), group.title) for group in category.groups.all())
|
2017-07-10 19:18:44 +02:00
|
|
|
category_groups = set(group.pk for group in category.groups.all())
|
|
|
|
initial = tuple(str(pk) for pk in instance_groups if pk in category_groups)
|
|
|
|
if category.single:
|
|
|
|
name = 'group_'+category.name
|
|
|
|
initial = initial[0] if initial else ''
|
2017-07-11 18:04:22 +02:00
|
|
|
choices = (('', '---'), )+choices
|
2017-07-10 19:18:44 +02:00
|
|
|
field = ChoiceField(label=category.title, required=False, initial=initial, choices=choices)
|
|
|
|
else:
|
|
|
|
name = 'groups_'+category.name
|
|
|
|
field = MultipleChoiceField(label=category.title, required=False, initial=initial, choices=choices)
|
|
|
|
self.fields[name] = field
|
|
|
|
self.fields.move_to_end(name, last=False)
|
2017-05-16 14:50:36 +02:00
|
|
|
|
2017-07-10 14:10:48 +02:00
|
|
|
if 'category' in self.fields:
|
|
|
|
self.fields['category'].label_from_instance = lambda obj: obj.title
|
|
|
|
|
2017-07-13 18:54:49 +02:00
|
|
|
if 'access_restriction' in self.fields:
|
|
|
|
AccessRestriction = self.request.changeset.wrap_model('AccessRestriction')
|
|
|
|
|
|
|
|
self.fields['access_restriction'].label_from_instance = lambda obj: obj.title
|
|
|
|
self.fields['access_restriction'].queryset = AccessRestriction.qs_for_request(self.request)
|
|
|
|
|
2016-10-13 15:55:15 +02:00
|
|
|
# parse titles
|
|
|
|
self.titles = None
|
|
|
|
if hasattr(self.instance, 'titles'):
|
|
|
|
titles = OrderedDict((lang_code, '') for lang_code, language in settings.LANGUAGES)
|
|
|
|
if self.instance is not None and self.instance.pk:
|
|
|
|
titles.update(self.instance.titles)
|
|
|
|
|
2017-05-26 20:40:20 +02:00
|
|
|
for language in reversed(titles.keys()):
|
2016-10-13 15:55:15 +02:00
|
|
|
new_title = self.data.get('title_' + language)
|
|
|
|
if new_title is not None:
|
|
|
|
titles[language] = new_title
|
2017-07-13 13:47:11 +02:00
|
|
|
language_info = get_language_info(language)
|
|
|
|
field_title = format_lazy(_('Title ({lang})'), lang=language_info['name_translated'])
|
|
|
|
self.fields['title_' + language] = CharField(label=field_title,
|
2016-10-13 15:55:15 +02:00
|
|
|
required=False,
|
|
|
|
initial=titles[language].strip(), max_length=50)
|
2017-05-26 20:40:20 +02:00
|
|
|
self.fields.move_to_end('title_' + language, last=False)
|
2016-10-13 15:55:15 +02:00
|
|
|
self.titles = titles
|
|
|
|
|
2017-07-10 14:10:48 +02:00
|
|
|
if 'name' in self.fields:
|
|
|
|
self.fields.move_to_end('name', last=False)
|
|
|
|
|
2017-05-27 18:29:36 +02:00
|
|
|
self.redirect_slugs = None
|
|
|
|
self.add_redirect_slugs = None
|
|
|
|
self.remove_redirect_slugs = None
|
|
|
|
if 'slug' in self.fields:
|
|
|
|
self.redirect_slugs = sorted(self.instance.redirects.values_list('slug', flat=True))
|
|
|
|
self.fields['redirect_slugs'] = CharField(label=_('Redirecting Slugs (comma seperated)'), required=False,
|
|
|
|
initial=','.join(self.redirect_slugs))
|
|
|
|
self.fields.move_to_end('redirect_slugs', last=False)
|
|
|
|
self.fields.move_to_end('slug', last=False)
|
|
|
|
|
2017-07-19 18:02:40 +02:00
|
|
|
if 'from_node' in self.fields:
|
|
|
|
self.fields['from_node'].widget = HiddenInput()
|
|
|
|
|
|
|
|
if 'to_node' in self.fields:
|
|
|
|
self.fields['to_node'].widget = HiddenInput()
|
|
|
|
|
2017-05-27 18:29:36 +02:00
|
|
|
def clean_redirect_slugs(self):
|
|
|
|
old_redirect_slugs = set(self.redirect_slugs)
|
|
|
|
new_redirect_slugs = set(s for s in (s.strip() for s in self.cleaned_data['redirect_slugs'].split(',')) if s)
|
|
|
|
|
|
|
|
self.add_redirect_slugs = new_redirect_slugs - old_redirect_slugs
|
|
|
|
self.remove_redirect_slugs = old_redirect_slugs - new_redirect_slugs
|
|
|
|
|
|
|
|
for slug in self.add_redirect_slugs:
|
|
|
|
self.fields['slug'].run_validators(slug)
|
2017-06-12 18:01:51 +02:00
|
|
|
|
2017-06-27 03:20:50 +02:00
|
|
|
LocationSlug = self.request.changeset.wrap_model('LocationSlug')
|
2017-07-06 12:26:19 +02:00
|
|
|
qs = LocationSlug.objects.filter(slug__in=self.add_redirect_slugs)
|
|
|
|
|
|
|
|
if self.cleaned_data['slug'] in self.add_redirect_slugs:
|
|
|
|
raise ValidationError(
|
|
|
|
_('Can not add redirecting slug “%s”: it\'s the slug of this object.') % self.cleaned_data['slug']
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
qs = qs.exclude(pk=self.instance.pk)
|
|
|
|
|
|
|
|
for slug in qs.values_list('slug', flat=True)[:1]:
|
2017-06-12 18:01:51 +02:00
|
|
|
raise ValidationError(
|
|
|
|
_('Can not add redirecting slug “%s”: it is already used elsewhere.') % slug
|
|
|
|
)
|
2017-05-27 18:29:36 +02:00
|
|
|
|
2016-11-28 15:46:58 +01:00
|
|
|
def clean(self):
|
|
|
|
if 'geometry' in self.fields:
|
|
|
|
if not self.cleaned_data.get('geometry'):
|
|
|
|
raise ValidationError('Missing geometry.')
|
2016-12-12 13:21:09 +01:00
|
|
|
|
2016-12-04 14:16:05 +01:00
|
|
|
super().clean()
|
2016-11-28 15:46:58 +01:00
|
|
|
|
2017-07-10 16:00:43 +02:00
|
|
|
def _save_m2m(self):
|
|
|
|
super()._save_m2m()
|
2017-07-10 19:03:24 +02:00
|
|
|
try:
|
|
|
|
field = self._meta.model._meta.get_field('groups')
|
|
|
|
except FieldDoesNotExist:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if field.many_to_many:
|
|
|
|
groups = reduce(operator.or_, (set(value) for name, value in self.cleaned_data.items()
|
|
|
|
if name.startswith('groups_')), set())
|
2017-07-13 14:11:33 +02:00
|
|
|
groups |= set(value for name, value in self.cleaned_data.items() if name.startswith('group_') and value)
|
2017-07-10 19:03:24 +02:00
|
|
|
groups = tuple((int(val) if val.isdigit() else val) for val in groups)
|
|
|
|
self.instance.groups.set(groups)
|
2017-07-10 16:00:43 +02:00
|
|
|
|
2016-11-28 15:46:58 +01:00
|
|
|
|
2017-05-16 14:10:50 +02:00
|
|
|
def create_editor_form(editor_model):
|
2017-07-19 18:02:40 +02:00
|
|
|
possible_fields = ['slug', 'name', 'altitude', 'category', 'width', 'groups', 'color', 'priority', 'waytype',
|
|
|
|
'access_restriction', 'space_transfer', 'can_search', 'can_describe', 'outside', 'geometry',
|
2017-07-11 18:30:08 +02:00
|
|
|
'single', 'allow_levels', 'allow_spaces', 'allow_areas', 'allow_pois',
|
2017-07-26 13:20:55 +02:00
|
|
|
'left', 'top', 'right', 'bottom']
|
2017-07-10 14:10:48 +02:00
|
|
|
field_names = [field.name for field in editor_model._meta.get_fields() if not field.one_to_many]
|
2017-05-26 20:40:20 +02:00
|
|
|
existing_fields = [name for name in possible_fields if name in field_names]
|
2016-10-13 15:55:15 +02:00
|
|
|
|
2017-07-10 19:03:45 +02:00
|
|
|
class EditorForm(EditorFormBase, ModelForm):
|
2016-10-13 15:55:15 +02:00
|
|
|
class Meta:
|
2017-05-16 14:10:50 +02:00
|
|
|
model = editor_model
|
2016-11-28 15:46:58 +01:00
|
|
|
fields = existing_fields
|
2016-10-13 15:55:15 +02:00
|
|
|
|
2017-06-21 12:47:28 +02:00
|
|
|
EditorForm.__name__ = editor_model.__name__+'EditorForm'
|
|
|
|
return EditorForm
|
2017-06-29 15:53:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ChangeSetForm(ModelForm):
|
|
|
|
class Meta:
|
|
|
|
model = ChangeSet
|
|
|
|
fields = ('title', 'description')
|
2017-07-05 19:40:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
class RejectForm(ModelForm):
|
|
|
|
final = BooleanField(label=_('Final rejection'), required=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ChangeSetUpdate
|
|
|
|
fields = ('comment', )
|
2017-07-19 18:02:40 +02:00
|
|
|
|
|
|
|
|
2017-07-26 13:20:55 +02:00
|
|
|
class GraphNodeSettingsForm(ModelForm):
|
|
|
|
class Meta:
|
|
|
|
model = GraphNode
|
|
|
|
fields = ('space_transfer', )
|
|
|
|
|
|
|
|
|
|
|
|
class GraphEdgeSettingsForm(ModelForm):
|
|
|
|
class Meta:
|
|
|
|
model = GraphEdge
|
|
|
|
fields = ('waytype', 'access_restriction', )
|
|
|
|
|
|
|
|
def __init__(self, *args, request=None, **kwargs):
|
|
|
|
self.request = request
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
AccessRestriction = self.request.changeset.wrap_model('AccessRestriction')
|
|
|
|
self.fields['access_restriction'].label_from_instance = lambda obj: obj.title
|
|
|
|
self.fields['access_restriction'].queryset = AccessRestriction.qs_for_request(self.request)
|
|
|
|
|
|
|
|
|
|
|
|
class GraphEditorActionForm(Form):
|
|
|
|
def __init__(self, *args, request=None, allow_clicked_position=False, **kwargs):
|
|
|
|
self.request = request
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
GraphNode = self.request.changeset.wrap_model('GraphNode')
|
|
|
|
graph_node_qs = GraphNode.objects.all()
|
2017-07-26 13:30:08 +02:00
|
|
|
self.fields['active_node'] = ModelChoiceField(graph_node_qs, widget=HiddenInput(), required=False)
|
2017-07-26 13:20:55 +02:00
|
|
|
self.fields['clicked_node'] = ModelChoiceField(graph_node_qs, widget=HiddenInput(), required=False)
|
|
|
|
|
|
|
|
if allow_clicked_position:
|
|
|
|
self.fields['clicked_position'] = CharField(widget=HiddenInput(), required=False)
|
|
|
|
|
|
|
|
def clean_clicked_position(self):
|
|
|
|
return GeometryField(geomtype='point').to_python(self.cleaned_data['clicked_position'])
|
|
|
|
|
|
|
|
|
2017-07-19 18:02:40 +02:00
|
|
|
class GraphEditorSettingsForm(Form):
|
|
|
|
node_click = ChoiceField(label=_('when clicking on an existing node…'), choices=(
|
|
|
|
('connect_or_toggle', _('connect if possible, otherwise toggle')),
|
|
|
|
('connect', _('connect if possible')),
|
|
|
|
('activate', _('activate')),
|
|
|
|
('deactivate', _('deactivate')),
|
|
|
|
('toggle', _('toggle')),
|
|
|
|
('set_space_transfer', _('set space transfer')),
|
|
|
|
('noop', _('do nothing')),
|
|
|
|
), initial='connect_or_toggle')
|
|
|
|
|
|
|
|
click_anywhere = ChoiceField(label=_('when clicking anywhere…'), choices=(
|
|
|
|
('create_connect_node', _('create node and connect if possible')),
|
|
|
|
('create_node_if_none_active', _('create node if no node is active')),
|
|
|
|
('create_node', _('create node')),
|
|
|
|
('noop', _('do nothing')),
|
|
|
|
), initial='create_connect_node')
|
|
|
|
|
|
|
|
create_edge = ChoiceField(label=_('when connecting two nodes…'), choices=(
|
|
|
|
('bidirectional', _('create edge in both directions')),
|
|
|
|
('unidirectional', _('create edge in one direction')),
|
|
|
|
('unidirectional_force', _('create edge, delete other direction')),
|
|
|
|
('delete_bidirectional', _('delete edges in both directions')),
|
|
|
|
('delete_unidirectional', _('delete edge in one direction')),
|
|
|
|
), initial='bidirectional')
|
|
|
|
|
|
|
|
create_existing_edge = ChoiceField(label=_('when creating an already existing edge…'), choices=(
|
|
|
|
('overwrite_toggle', _('overwrite if not identical, otherwise delete')),
|
|
|
|
('overwrite_always', _('overwrite')),
|
|
|
|
('overwrite_waytype', _('overwrite waytype')),
|
|
|
|
('overwrite_access', _('overwrite access restriction')),
|
|
|
|
('delete', _('delete')),
|
|
|
|
), initial='overwrite_toggle')
|
|
|
|
|
|
|
|
after_create_edge = ChoiceField(label=_('after creating a new edge…'), choices=(
|
|
|
|
('reset', _('deactivate active node')),
|
|
|
|
('keep_first_active', _('keep first node active')),
|
|
|
|
('set_second_active', _('set second node as active')),
|
|
|
|
), initial='reset')
|