add changeset_history
This commit is contained in:
parent
37ff5e54a9
commit
cf538fa6a5
3 changed files with 120 additions and 35 deletions
|
@ -33,6 +33,7 @@ class ChangeSet(models.Model):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.default_author = None
|
self.default_author = None
|
||||||
self.parsed = False
|
self.parsed = False
|
||||||
|
self.changes_qs = None
|
||||||
self.ever_created_objects = {}
|
self.ever_created_objects = {}
|
||||||
self.created_objects = {}
|
self.created_objects = {}
|
||||||
self.updated_existing = {}
|
self.updated_existing = {}
|
||||||
|
@ -41,12 +42,21 @@ class ChangeSet(models.Model):
|
||||||
self.m2m_removed = {}
|
self.m2m_removed = {}
|
||||||
self._last_change_pk = 0
|
self._last_change_pk = 0
|
||||||
|
|
||||||
def parse_changes(self):
|
@property
|
||||||
if self.pk is None or self.parsed:
|
def relevant_changes(self):
|
||||||
|
return self.changes.filter(discarded_by__isnull=True).exclude(action='restore')
|
||||||
|
|
||||||
|
def parse_changes(self, get_history=False):
|
||||||
|
if self.pk is None or self.changes_qs is not None:
|
||||||
return
|
return
|
||||||
for change in self.changes.all():
|
|
||||||
|
if get_history:
|
||||||
|
self.changes_qs = self.changes.all()
|
||||||
|
else:
|
||||||
|
self.changes_qs = self.relevant_changes
|
||||||
|
|
||||||
|
for change in self.changes_qs:
|
||||||
self._parse_change(change)
|
self._parse_change(change)
|
||||||
self.parsed = True
|
|
||||||
|
|
||||||
def _parse_change(self, change):
|
def _parse_change(self, change):
|
||||||
self._last_change_pk = change.pk
|
self._last_change_pk = change.pk
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from c3nav.editor.views.changes import changeset_detail
|
from c3nav.editor.views.changes import changeset_detail, changeset_history
|
||||||
from c3nav.editor.views.edit import edit, level_detail, list_objects, main_index, space_detail
|
from c3nav.editor.views.edit import edit, level_detail, list_objects, main_index, space_detail
|
||||||
from c3nav.editor.views.login import login_view, logout_view
|
from c3nav.editor.views.login import login_view, logout_view
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ urlpatterns = [
|
||||||
url(r'^levels/(?P<on_top_of>c?[0-9]+)/levels_on_top/create$', edit, name='editor.levels_on_top.create',
|
url(r'^levels/(?P<on_top_of>c?[0-9]+)/levels_on_top/create$', edit, name='editor.levels_on_top.create',
|
||||||
kwargs={'model': 'Level'}),
|
kwargs={'model': 'Level'}),
|
||||||
url(r'^changesets/(?P<pk>[0-9]+)/$', changeset_detail, name='editor.changesets.detail'),
|
url(r'^changesets/(?P<pk>[0-9]+)/$', changeset_detail, name='editor.changesets.detail'),
|
||||||
|
url(r'^changesets/(?P<pk>[0-9]+)/history$', changeset_history, name='editor.changesets.history'),
|
||||||
url(r'^login$', login_view, name='editor.login'),
|
url(r'^login$', login_view, name='editor.login'),
|
||||||
url(r'^logout$', logout_view, name='editor.logout'),
|
url(r'^logout$', logout_view, name='editor.logout'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
@ -14,14 +15,50 @@ from c3nav.mapdata.models.locations import LocationRedirect, LocationSlug
|
||||||
|
|
||||||
@sidebar_view
|
@sidebar_view
|
||||||
def changeset_detail(request, pk):
|
def changeset_detail(request, pk):
|
||||||
|
can_edit = True
|
||||||
|
changeset = request.changeset
|
||||||
if str(pk) != str(request.changeset.pk):
|
if str(pk) != str(request.changeset.pk):
|
||||||
|
can_edit = False
|
||||||
changeset = get_object_or_404(ChangeSet.qs_for_request(request), pk=pk)
|
changeset = get_object_or_404(ChangeSet.qs_for_request(request), pk=pk)
|
||||||
else:
|
|
||||||
changeset = request.changeset
|
ctx = group_changes(changeset, can_edit=can_edit, show_history=False)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if request.POST.get('delete') == '1':
|
||||||
|
if request.POST.get('delete_confirm') == '1':
|
||||||
|
changeset.delete()
|
||||||
|
return redirect(reverse('editor.index'))
|
||||||
|
|
||||||
|
ctx.update({
|
||||||
|
'model_title': ChangeSet._meta.verbose_name,
|
||||||
|
'obj_title': changeset.title,
|
||||||
|
})
|
||||||
|
return render(request, 'editor/delete.html', ctx)
|
||||||
|
|
||||||
|
return render(request, 'editor/changeset.html', ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@sidebar_view
|
||||||
|
def changeset_history(request, pk):
|
||||||
|
can_edit = True
|
||||||
|
changeset = request.changeset
|
||||||
|
if str(pk) != str(request.changeset.pk):
|
||||||
|
can_edit = False
|
||||||
|
changeset = get_object_or_404(ChangeSet.qs_for_request(request), pk=pk)
|
||||||
|
|
||||||
|
ctx = group_changes(changeset, can_edit=can_edit, show_history=True)
|
||||||
|
|
||||||
|
return render(request, 'editor/changeset.html', ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def group_changes(changeset, can_edit=False, show_history=False):
|
||||||
|
changeset.parse_changes(get_history=show_history)
|
||||||
|
|
||||||
|
changes_list = changeset.changes_qs
|
||||||
|
|
||||||
# collect pks of relevant objects
|
# collect pks of relevant objects
|
||||||
object_pks = {}
|
object_pks = {}
|
||||||
for change in changeset.changes.all():
|
for change in changes_list:
|
||||||
object_pks.setdefault(change.model_class, set()).add(change.obj_pk)
|
object_pks.setdefault(change.model_class, set()).add(change.obj_pk)
|
||||||
model = None
|
model = None
|
||||||
if change.action == 'update':
|
if change.action == 'update':
|
||||||
|
@ -41,7 +78,7 @@ def changeset_detail(request, pk):
|
||||||
objects = {}
|
objects = {}
|
||||||
for model, pks in object_pks.items():
|
for model, pks in object_pks.items():
|
||||||
created_pks = set(pk for pk in pks if is_created_pk(pk))
|
created_pks = set(pk for pk in pks if is_created_pk(pk))
|
||||||
existing_pks = pks-created_pks
|
existing_pks = pks - created_pks
|
||||||
model_objects = {}
|
model_objects = {}
|
||||||
if existing_pks:
|
if existing_pks:
|
||||||
for obj in model.objects.filter(pk__in=existing_pks):
|
for obj in model.objects.filter(pk__in=existing_pks):
|
||||||
|
@ -51,13 +88,14 @@ def changeset_detail(request, pk):
|
||||||
if created_pks:
|
if created_pks:
|
||||||
for pk in created_pks:
|
for pk in created_pks:
|
||||||
model_objects[pk] = changeset.get_created_object(model, pk, allow_deleted=True)._obj
|
model_objects[pk] = changeset.get_created_object(model, pk, allow_deleted=True)._obj
|
||||||
model_objects[pk].titles = {}
|
if show_history:
|
||||||
|
model_objects[pk].titles = {}
|
||||||
objects[model] = model_objects
|
objects[model] = model_objects
|
||||||
|
|
||||||
grouped_changes = []
|
grouped_changes = [] if show_history else {}
|
||||||
changes = []
|
changes = []
|
||||||
last_obj = None
|
last_obj = None
|
||||||
for change in changeset.changes.all():
|
for change in changes_list:
|
||||||
pk = change.obj_pk
|
pk = change.obj_pk
|
||||||
obj = objects[change.model_class][pk]
|
obj = objects[change.model_class][pk]
|
||||||
if change.model_class == LocationRedirect:
|
if change.model_class == LocationRedirect:
|
||||||
|
@ -69,7 +107,10 @@ def changeset_detail(request, pk):
|
||||||
pk = obj.target_id
|
pk = obj.target_id
|
||||||
obj = objects[LocationSlug][pk]
|
obj = objects[LocationSlug][pk]
|
||||||
|
|
||||||
if obj != last_obj:
|
if obj != last_obj and not show_history and pk in grouped_changes:
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
changes = grouped_changes[pk]['changes']
|
||||||
|
elif obj != last_obj:
|
||||||
changes = []
|
changes = []
|
||||||
obj_desc = _('%(model)s #%(id)s') % {'model': obj.__class__._meta.verbose_name, 'id': pk}
|
obj_desc = _('%(model)s #%(id)s') % {'model': obj.__class__._meta.verbose_name, 'id': pk}
|
||||||
if is_created_pk(pk):
|
if is_created_pk(pk):
|
||||||
|
@ -79,22 +120,29 @@ def changeset_detail(request, pk):
|
||||||
obj_still_exists = pk not in changeset.deleted_existing[obj.__class__]
|
obj_still_exists = pk not in changeset.deleted_existing[obj.__class__]
|
||||||
|
|
||||||
edit_url = None
|
edit_url = None
|
||||||
if obj_still_exists and changeset == request.changeset:
|
if obj_still_exists and can_edit:
|
||||||
reverse_kwargs = {'pk': obj.pk}
|
reverse_kwargs = {'pk': obj.pk}
|
||||||
if hasattr(obj, 'level'):
|
if hasattr(obj, 'level'):
|
||||||
reverse_kwargs['level'] = obj.level_id
|
reverse_kwargs['level'] = obj.level_id
|
||||||
elif hasattr(obj, 'space'):
|
elif hasattr(obj, 'space'):
|
||||||
reverse_kwargs['space'] = obj.space_id
|
reverse_kwargs['space'] = obj.space_id
|
||||||
edit_url = reverse('editor.'+obj.__class__._meta.default_related_name+'.edit', kwargs=reverse_kwargs)
|
edit_url = reverse('editor.' + obj.__class__._meta.default_related_name + '.edit',
|
||||||
|
kwargs=reverse_kwargs)
|
||||||
|
|
||||||
grouped_changes.append({
|
change_group = {
|
||||||
'model': obj.__class__,
|
'model': obj.__class__,
|
||||||
'model_title': obj.__class__._meta.verbose_name,
|
'model_title': obj.__class__._meta.verbose_name,
|
||||||
'obj': obj_desc,
|
'obj': obj_desc,
|
||||||
'obj_title': obj.title if obj.titles else None,
|
'obj_title': obj.title if obj.titles else None,
|
||||||
'changes': changes,
|
'changes': changes,
|
||||||
'edit_url': edit_url,
|
'edit_url': edit_url,
|
||||||
})
|
}
|
||||||
|
if show_history:
|
||||||
|
grouped_changes.append(change_group)
|
||||||
|
else:
|
||||||
|
change_group['order'] = (0, int(pk[1:])) if is_created_pk(pk) else (1, int(pk))
|
||||||
|
grouped_changes[pk] = change_group
|
||||||
|
|
||||||
last_obj = obj
|
last_obj = obj
|
||||||
|
|
||||||
change_data = {
|
change_data = {
|
||||||
|
@ -108,12 +156,14 @@ def changeset_detail(request, pk):
|
||||||
'icon': 'plus',
|
'icon': 'plus',
|
||||||
'class': 'success',
|
'class': 'success',
|
||||||
'title': _('created'),
|
'title': _('created'),
|
||||||
|
'order': (0, ),
|
||||||
})
|
})
|
||||||
elif change.action == 'delete':
|
elif change.action == 'delete':
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'icon': 'minus',
|
'icon': 'minus',
|
||||||
'class': 'danger',
|
'class': 'danger',
|
||||||
'title': _('deleted')
|
'title': _('deleted'),
|
||||||
|
'order': (9, ),
|
||||||
})
|
})
|
||||||
elif change.action == 'update':
|
elif change.action == 'update':
|
||||||
change_data.update({
|
change_data.update({
|
||||||
|
@ -125,6 +175,7 @@ def changeset_detail(request, pk):
|
||||||
'icon': 'map-marker',
|
'icon': 'map-marker',
|
||||||
'class': 'info',
|
'class': 'info',
|
||||||
'title': _('edited geometry'),
|
'title': _('edited geometry'),
|
||||||
|
'order': (8, ),
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
if change.field_name.startswith('title_'):
|
if change.field_name.startswith('title_'):
|
||||||
|
@ -135,6 +186,9 @@ def changeset_detail(request, pk):
|
||||||
obj.titles[lang] = field_value
|
obj.titles[lang] = field_value
|
||||||
else:
|
else:
|
||||||
obj.titles.pop(lang, None)
|
obj.titles.pop(lang, None)
|
||||||
|
change_data.update({
|
||||||
|
'order': (4, tuple(code for code, title in settings.LANGUAGES).index(lang)),
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
field = obj.__class__._meta.get_field(change.field_name)
|
field = obj.__class__._meta.get_field(change.field_name)
|
||||||
field_title = field.verbose_name
|
field_title = field.verbose_name
|
||||||
|
@ -142,15 +196,40 @@ def changeset_detail(request, pk):
|
||||||
model = getattr(field, 'related_model', None)
|
model = getattr(field, 'related_model', None)
|
||||||
if model is not None:
|
if model is not None:
|
||||||
field_value = objects[model][field_value].title
|
field_value = objects[model][field_value].title
|
||||||
|
order = 5
|
||||||
|
if change.field_name == 'slug':
|
||||||
|
order = 1
|
||||||
|
if change.field_name not in obj.__class__.EditorForm._meta.fields:
|
||||||
|
order = 0
|
||||||
|
change_data.update({
|
||||||
|
'order': (order,
|
||||||
|
obj.__class__.EditorForm._meta.fields.index(change.field_name) if order else 1),
|
||||||
|
})
|
||||||
if not field_value:
|
if not field_value:
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'title': _('unset %(field_title)s') % {'field_title': field_title},
|
'title': _('remove %(field_title)s') % {'field_title': field_title},
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'title': field_title,
|
'title': field_title,
|
||||||
'value': field_value,
|
'value': field_value,
|
||||||
})
|
})
|
||||||
|
elif change.action == 'revert':
|
||||||
|
change_data.update({
|
||||||
|
'icon': 'share-alt',
|
||||||
|
'class': 'muted',
|
||||||
|
})
|
||||||
|
if change.field_name == 'geometry':
|
||||||
|
change_data.update({
|
||||||
|
'icon': 'map-marker',
|
||||||
|
'title': _('reverted geometry'),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
field = obj.__class__._meta.get_field(change.field_name)
|
||||||
|
field_title = field.verbose_name
|
||||||
|
change_data.update({
|
||||||
|
'title': _('reverted %(field_title)s') % {'field_title': field_title},
|
||||||
|
})
|
||||||
elif change.action in ('m2m_add', 'm2m_remove'):
|
elif change.action in ('m2m_add', 'm2m_remove'):
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'icon': 'chevron-right' if change.action == 'm2m_add' else 'chevron-left',
|
'icon': 'chevron-right' if change.action == 'm2m_add' else 'chevron-left',
|
||||||
|
@ -160,40 +239,35 @@ def changeset_detail(request, pk):
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'title': _('Redirecting slugs'),
|
'title': _('Redirecting slugs'),
|
||||||
'value': change.field_value,
|
'value': change.field_value,
|
||||||
|
'order': (6, -1, (change.action == 'm2m_remove')),
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
field = obj.__class__._meta.get_field(change.field_name)
|
field = obj.__class__._meta.get_field(change.field_name)
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'title': field.verbose_name,
|
'title': field.verbose_name,
|
||||||
'value': objects[field.related_model][json.loads(change.field_value)].title,
|
'value': objects[field.related_model][json.loads(change.field_value)].title,
|
||||||
|
'order': (6, obj.__class__.EditorForm._meta.fields.index(change.field_name),
|
||||||
|
(change.action == 'm2m_remove')),
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
change_data.update({
|
change_data.update({
|
||||||
'title': '???',
|
'title': '???',
|
||||||
|
'order': (10, )
|
||||||
})
|
})
|
||||||
|
|
||||||
if changeset.author:
|
if changeset.author:
|
||||||
desc = _('created at %(datetime)s by') % {'datetime': date_format(changeset.created, 'DATETIME_FORMAT')}
|
desc = _('created at %(datetime)s by') % {'datetime': date_format(changeset.created, 'DATETIME_FORMAT')}
|
||||||
else:
|
else:
|
||||||
desc = _('created at %(datetime)s') % {'datetime': date_format(changeset.created, 'DATETIME_FORMAT')}
|
desc = _('created at %(datetime)s') % {'datetime': date_format(changeset.created, 'DATETIME_FORMAT')}
|
||||||
|
|
||||||
|
if not show_history:
|
||||||
|
grouped_changes = sorted(grouped_changes.values(), key=itemgetter('order'))
|
||||||
|
for group in grouped_changes:
|
||||||
|
group['changes'] = sorted(group['changes'], key=itemgetter('order'))
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'pk': pk,
|
'pk': changeset.pk,
|
||||||
'changeset': changeset,
|
'changeset': changeset,
|
||||||
'desc': desc,
|
'desc': desc,
|
||||||
'grouped_changes': grouped_changes,
|
'grouped_changes': grouped_changes,
|
||||||
}
|
}
|
||||||
|
return ctx
|
||||||
if request.method == 'POST':
|
|
||||||
if request.POST.get('delete') == '1':
|
|
||||||
if request.POST.get('delete_confirm') == '1':
|
|
||||||
changeset.delete()
|
|
||||||
return redirect(reverse('editor.index'))
|
|
||||||
|
|
||||||
ctx.update({
|
|
||||||
'model_title': ChangeSet._meta.verbose_name,
|
|
||||||
'obj_title': changeset.title,
|
|
||||||
})
|
|
||||||
return render(request, 'editor/delete.html', ctx)
|
|
||||||
|
|
||||||
return render(request, 'editor/changeset.html', ctx)
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue