team-3/src/c3nav/editor/views/changes.py

427 lines
19 KiB
Python
Raw Normal View History

from itertools import chain
2017-06-20 15:39:22 +02:00
from operator import itemgetter
2017-06-20 14:02:30 +02:00
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User
from django.core.cache import cache
2017-06-29 18:01:58 +02:00
from django.http import Http404
2017-06-20 14:02:30 +02:00
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import NoReverseMatch, reverse
from django.utils.text import format_lazy
2017-07-13 13:47:00 +02:00
from django.utils.translation import get_language_info
from django.utils.translation import ugettext_lazy as _
2017-06-20 14:02:30 +02:00
2017-07-05 19:40:35 +02:00
from c3nav.editor.forms import ChangeSetForm, RejectForm
2017-06-20 14:02:30 +02:00
from c3nav.editor.models import ChangeSet
from c3nav.editor.views.base import sidebar_view
from c3nav.editor.wrappers import is_created_pk
from c3nav.mapdata.models.locations import LocationRedirect, LocationSlug
2017-06-20 14:02:30 +02:00
@sidebar_view(select_related=('last_update', 'last_state_update', 'last_change', 'author'))
def changeset_detail(request, pk):
2017-06-20 15:39:22 +02:00
changeset = request.changeset
active = True
2017-06-20 14:02:30 +02:00
if str(pk) != str(request.changeset.pk):
active = False
qs = ChangeSet.qs_for_request(request).select_related('last_update', 'last_state_update',
'last_change', 'author')
changeset = get_object_or_404(qs, pk=pk)
2017-06-29 17:40:33 +02:00
if not changeset.can_see(request):
raise Http404
can_edit = changeset.can_edit(request)
2017-06-29 17:48:02 +02:00
can_delete = changeset.can_delete(request)
2017-06-20 15:39:22 +02:00
2017-06-29 18:27:43 +02:00
if request.method == 'POST':
restore = request.POST.get('restore')
2017-06-25 18:29:30 +02:00
if restore and restore.isdigit():
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
if changeset.can_edit(request):
try:
changed_object = changeset.changed_objects_set.get(pk=restore)
2017-10-25 00:47:40 +02:00
except Exception:
pass
else:
try:
changed_object.restore()
messages.success(request, _('Object has been successfully restored.'))
2017-07-27 14:29:45 +02:00
except PermissionError:
messages.error(request, _('You cannot restore this object, because it depends on '
'a deleted object or it would violate a unique contraint.'))
2017-07-07 15:32:41 +02:00
else:
messages.error(request, _('You can not edit changes on this change set.'))
2017-07-04 22:44:21 +02:00
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
elif request.POST.get('activate') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
if changeset.can_activate(request):
2017-07-04 22:44:21 +02:00
changeset.activate(request)
messages.success(request, _('You activated this change set.'))
else:
messages.error(request, _('You can not activate this change set.'))
2017-06-29 18:01:58 +02:00
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
elif request.POST.get('propose') == '1':
if not request.user.is_authenticated:
messages.info(request, _('You need to log in to propose changes.'))
2017-07-04 22:53:03 +02:00
return redirect(reverse('editor.login')+'?r='+request.path)
2017-06-29 18:01:58 +02:00
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
if not changeset.title or not changeset.description:
messages.warning(request, _('You need to add a title an a description to propose this change set.'))
return redirect(reverse('editor.changesets.edit', kwargs={'pk': changeset.pk}))
if changeset.can_propose(request):
changeset.propose(request.user)
messages.success(request, _('You proposed your changes.'))
else:
2017-07-04 22:44:21 +02:00
messages.error(request, _('You cannot propose this change set.'))
2017-06-29 18:01:58 +02:00
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
elif request.POST.get('unpropose') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
if changeset.can_unpropose(request):
changeset.unpropose(request.user)
messages.success(request, _('You unproposed your changes.'))
else:
2017-07-04 22:44:21 +02:00
messages.error(request, _('You cannot unpropose this change set.'))
2017-06-29 16:22:41 +02:00
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
2017-07-05 19:40:35 +02:00
elif request.POST.get('review') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
2017-07-05 19:40:35 +02:00
if changeset.can_start_review(request):
changeset.start_review(request.user)
2017-12-19 18:52:55 +01:00
messages.success(request, _('You are now reviewing these changes.'))
2017-07-05 19:40:35 +02:00
else:
messages.error(request, _('You cannot review these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
elif request.POST.get('reject') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
2017-07-05 19:40:35 +02:00
if not changeset.can_end_review(request):
messages.error(request, _('You cannot reject these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
if request.POST.get('reject_confirm') == '1':
form = RejectForm(data=request.POST)
if form.is_valid():
changeset.reject(request.user, form.cleaned_data['comment'], form.cleaned_data['final'])
messages.success(request, _('You rejected these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
else:
form = RejectForm()
return render(request, 'editor/changeset_reject.html', {
'changeset': changeset,
'form': form,
})
2017-07-05 20:32:43 +02:00
elif request.POST.get('unreject') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
2017-07-05 20:32:43 +02:00
if not changeset.can_unreject(request):
messages.error(request, _('You cannot unreject these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
changeset.unreject(request.user)
messages.success(request, _('You unrejected these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
elif request.POST.get('apply') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_end_review(request):
messages.error(request, _('You cannot accept and apply these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
if request.POST.get('apply_confirm') == '1':
changeset.apply(request.user)
messages.success(request, _('You accepted and applied these changes.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
return render(request, 'editor/changeset_apply.html', {})
elif request.POST.get('delete') == '1':
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
2017-07-05 20:54:04 +02:00
if not changeset.can_delete(request):
messages.error(request, _('You cannot delete this change set.'))
2017-06-29 17:48:02 +02:00
2017-07-05 20:54:04 +02:00
if request.POST.get('delete_confirm') == '1':
changeset.delete()
messages.success(request, _('You deleted this change set.'))
if request.user.is_authenticated:
return redirect(reverse('editor.users.detail', kwargs={'pk': request.user.pk}))
else:
return redirect(reverse('editor.index'))
2017-06-20 15:39:22 +02:00
2017-07-05 20:54:04 +02:00
return render(request, 'editor/delete.html', {
'model_title': ChangeSet._meta.verbose_name,
'obj_title': changeset.title,
})
2017-06-25 18:27:44 +02:00
changeset.fill_changes_cache()
ctx = {
'changeset': changeset,
'can_edit': can_edit,
'can_delete': can_delete,
'can_propose': changeset.can_propose(request),
'can_unpropose': changeset.can_unpropose(request),
'can_start_review': changeset.can_start_review(request),
'can_end_review': changeset.can_end_review(request),
'can_unreject': changeset.can_unreject(request),
'active': active,
}
2017-12-19 18:43:56 +01:00
cache_key = '%s:%s:%s:view_data' % (changeset.cache_key_by_changes,
2017-12-19 18:49:42 +01:00
changeset.last_update_id,
2017-12-19 18:43:56 +01:00
int(can_edit))
changed_objects_data = cache.get(cache_key)
2017-12-19 18:43:56 +01:00
if changed_objects_data:
ctx['changed_objects'] = changed_objects_data
return render(request, 'editor/changeset.html', ctx)
2017-06-21 13:48:13 +02:00
objects = changeset.get_objects()
2017-06-20 14:02:30 +02:00
changed_objects_data = []
added_redirects = {}
removed_redirects = {}
for changed_object in changeset.changed_objects.get(LocationRedirect, {}).values():
if changed_object.is_created == changed_object.deleted:
continue
obj = objects[LocationRedirect][changed_object.obj_pk]
redirect_list = (removed_redirects if changed_object.deleted else added_redirects)
redirect_list.setdefault(obj.target_id, []).append(obj.slug)
redirect_changed_objects = []
for pk in set(added_redirects.keys()) | set(removed_redirects.keys()):
obj = objects[LocationSlug][pk]
model = obj.__class__
try:
changeset.changed_objects[model][pk]
except KeyError:
redirect_changed_objects.append((model, {pk: changeset.get_changed_object(obj)}))
for model, changed_objects in chain(changeset.changed_objects.items(), redirect_changed_objects):
if model == LocationRedirect:
continue
for pk, changed_object in changed_objects.items():
obj = objects[model][pk]
2017-06-20 14:02:30 +02:00
obj_desc = format_lazy(_('{model} #{id}'), model=obj.__class__._meta.verbose_name, id=pk)
2017-06-20 14:02:30 +02:00
if is_created_pk(pk):
2017-06-29 16:22:41 +02:00
obj_still_exists = pk in changeset.created_objects.get(obj.__class__, ())
2017-06-20 14:02:30 +02:00
else:
2017-06-20 21:21:13 +02:00
obj_still_exists = pk not in changeset.deleted_existing.get(obj.__class__, ())
2017-06-20 14:02:30 +02:00
edit_url = None
2017-07-06 00:47:02 +02:00
if obj_still_exists and can_edit and not isinstance(obj, LocationRedirect):
2017-06-20 14:02:30 +02:00
reverse_kwargs = {'pk': obj.pk}
2017-12-19 17:39:46 +01:00
if hasattr(obj, 'space_id'):
2017-06-20 14:02:30 +02:00
reverse_kwargs['space'] = obj.space_id
2017-12-19 17:39:46 +01:00
elif hasattr(obj, 'level_id'):
reverse_kwargs['level'] = obj.level_id
try:
edit_url = reverse('editor.' + obj.__class__._meta.default_related_name + '.edit',
kwargs=reverse_kwargs)
except NoReverseMatch:
pass
2017-06-20 14:02:30 +02:00
changes = []
missing_dependencies = changed_object.get_missing_dependencies()
unique_collisions = changed_object.get_unique_collisions()
changed_object_data = {
2017-06-20 14:02:30 +02:00
'model': obj.__class__,
'model_title': obj.__class__._meta.verbose_name,
2017-06-29 16:22:41 +02:00
'pk': changed_object.pk,
2017-06-27 03:25:05 +02:00
'desc': obj_desc,
'title': obj.title if getattr(obj, 'titles', None) else None,
2017-06-20 14:02:30 +02:00
'changes': changes,
'edit_url': edit_url,
2017-06-29 16:22:41 +02:00
'deleted': changed_object.deleted,
'missing_dependencies': missing_dependencies,
'unique_collisions': unique_collisions,
'order': (changed_object.deleted and changed_object.is_created, not changed_object.is_created),
2017-06-20 15:39:22 +02:00
}
changed_objects_data.append(changed_object_data)
2017-06-20 15:39:22 +02:00
form_fields = changeset.wrap_model(type(obj)).EditorForm._meta.fields
2017-06-20 14:02:30 +02:00
if changed_object.is_created:
changes.append({
'icon': 'plus',
'class': 'success',
2017-12-21 03:09:25 +01:00
'empty': True,
'title': _('created'),
})
update_changes = []
for name, value in changed_object.updated_fields.items():
change_data = {
'icon': 'option-vertical',
'class': 'muted',
}
if name == 'geometry':
2017-06-20 15:39:22 +02:00
change_data.update({
'icon': 'map-marker',
'class': 'info',
2017-12-21 03:09:25 +01:00
'empty': True,
'title': _('created geometry') if changed_object.is_created else _('edited geometry'),
'order': (8,),
2017-06-20 15:39:22 +02:00
})
elif name == 'data':
change_data.update({
'icon': 'signal',
'class': 'info',
2017-12-21 03:09:25 +01:00
'empty': True,
'title': _('created scan data') if changed_object.is_created else _('edited scan data'),
'order': (9,),
})
2017-06-20 14:02:30 +02:00
else:
2017-11-30 13:42:28 +01:00
if '__i18n__' in name:
orig_name, i18n, lang = name.split('__')
2017-07-13 13:47:00 +02:00
lang_info = get_language_info(lang)
2017-11-30 13:42:28 +01:00
field = model._meta.get_field(orig_name)
field_title = format_lazy(_('{field_name} ({lang})'),
field_name=field.verbose_name,
lang=lang_info['name_translated'])
field_value = str(value)
if field_value:
2017-11-30 13:42:28 +01:00
getattr(obj, field.attname)[lang] = field_value
else:
2017-11-30 13:42:28 +01:00
getattr(obj, field.attname).pop(lang, None)
change_data.update({
'order': (4, tuple(code for code, title in settings.LANGUAGES).index(lang)),
})
else:
field = model._meta.get_field(name)
field_title = field.verbose_name
field_value = field.to_python(value)
if field.related_model is not None:
if issubclass(field.related_model, User):
field_value = objects[field.related_model][field_value].username
else:
field_value = objects[field.related_model][field_value].title
change_data.update({
'missing_dependency': field.name in missing_dependencies,
})
if name in unique_collisions:
change_data.update({
'unique_collision': field.name in unique_collisions,
})
order = 5
if name == 'slug':
order = 1
if name not in form_fields:
order = 0
change_data.update({
'order': (order, form_fields.index(name) if order else 1),
})
if field_value == '' or field_value is None:
change_data.update({
'empty': True,
'title': format_lazy(_('remove {field_title}'), field_title=field_title),
})
else:
change_data.update({
'title': field_title,
'value': field_value,
})
update_changes.append(change_data)
changes.extend(sorted(update_changes, key=itemgetter('order')))
for m2m_mode in ('m2m_added', 'm2m_removed'):
m2m_list = getattr(changed_object, m2m_mode).items()
2017-06-27 16:10:28 +02:00
for name, values in sorted(m2m_list, key=lambda nv: form_fields.index(nv[0])):
field = model._meta.get_field(name)
for value in values:
2017-06-27 16:10:28 +02:00
changes.append({
'icon': 'chevron-right' if m2m_mode == 'm2m_added' else 'chevron-left',
'class': 'info',
'title': field.verbose_name,
'value': objects[field.related_model][value].title,
})
if isinstance(obj, LocationSlug):
for slug in added_redirects.get(obj.pk, ()):
changes.append({
'icon': 'chevron-right',
'class': 'info',
'title': _('Redirect slugs'),
'value': slug,
})
for slug in removed_redirects.get(obj.pk, ()):
changes.append({
'icon': 'chevron-left',
'class': 'info',
'title': _('Redirect slugs'),
'value': slug,
})
if changed_object.deleted:
changes.append({
'icon': 'minus',
'class': 'danger',
2017-12-21 03:09:25 +01:00
'empty': True,
'title': _('deleted'),
'order': (9,),
2017-06-20 14:02:30 +02:00
})
changed_objects_data = sorted(changed_objects_data, key=itemgetter('order'))
cache.set(cache_key, changed_objects_data, 300)
ctx['changed_objects'] = changed_objects_data
return render(request, 'editor/changeset.html', ctx)
@sidebar_view
def changeset_edit(request, pk):
changeset = request.changeset
if str(pk) != str(request.changeset.pk):
changeset = get_object_or_404(ChangeSet.qs_for_request(request), pk=pk)
2017-07-07 15:32:41 +02:00
with changeset.lock_to_edit(request) as changeset:
if not changeset.can_edit(request):
messages.error(request, _('You cannot edit this change set.'))
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
if request.method == 'POST':
form = ChangeSetForm(instance=changeset, data=request.POST)
if form.is_valid():
changeset = form.instance
update = changeset.updates.create(user=request.user,
title=changeset.title, description=changeset.description)
changeset.last_update = update
changeset.save()
return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
else:
form = ChangeSetForm(instance=changeset)
return render(request, 'editor/changeset_edit.html', {
'changeset': changeset,
'form': form,
})
2018-12-17 04:12:18 +01:00
@sidebar_view
def changeset_redirect(request):
changeset = request.changeset
changeset_url = changeset.get_absolute_url()
if not changeset_url:
raise Http404
return redirect(changeset_url)