Create change log entry on status updates
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.29 on 2020-09-29 10:11
|
# Generated by Django 1.11.29 on 2020-09-29 20:15
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import dav_events.models.eventchange
|
import dav_events.models.eventchange
|
||||||
@@ -23,7 +23,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
('operation', models.CharField(choices=[('update', 'update')], max_length=20)),
|
('operation', models.CharField(choices=[('update', 'Update'), ('set_flag', 'Raise Flag'), ('unset_flag', 'Lower Flag')], max_length=20)),
|
||||||
('content', models.TextField()),
|
('content', models.TextField()),
|
||||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changes', to='dav_events.Event')),
|
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changes', to='dav_events.Event')),
|
||||||
('user', models.ForeignKey(default=dav_events.models.eventchange.get_system_user_id, on_delete=models.SET(dav_events.roles.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(default=dav_events.models.eventchange.get_system_user_id, on_delete=models.SET(dav_events.roles.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||||
|
|||||||
@@ -306,11 +306,13 @@ class Event(models.Model):
|
|||||||
signals.event_created.send(sender=self.__class__, event=self)
|
signals.event_created.send(sender=self.__class__, event=self)
|
||||||
self.workflow.update_status('draft', self.editor)
|
self.workflow.update_status('draft', self.editor)
|
||||||
else:
|
else:
|
||||||
change = EventChange(event=self, user=self.editor, operation='update', content=self.diff(original))
|
change = EventChange(event=self, user=self.editor, operation=EventChange.UPDATE,
|
||||||
|
content=self.diff(original))
|
||||||
change.save()
|
change.save()
|
||||||
if not implicit_update:
|
if not implicit_update:
|
||||||
logger.info('Event updated: %s', self)
|
logger.info('Event updated: %s', self)
|
||||||
signals.event_updated.send(sender=self.__class__, event=self, diff=self.diff(original, fmt='human_readable'), user=self.editor)
|
signals.event_updated.send(sender=self.__class__, event=self, user=self.editor,
|
||||||
|
diff=self.diff(original, fmt='human_readable'))
|
||||||
|
|
||||||
def diff(self, event, fmt='json'):
|
def diff(self, event, fmt='json'):
|
||||||
if fmt == 'human_readable':
|
if fmt == 'human_readable':
|
||||||
@@ -326,16 +328,14 @@ class Event(models.Model):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
field_name = field.name
|
field_name = field.name
|
||||||
from_value = getattr(event, field_name)
|
from_value = getattr(event, field_name)
|
||||||
if (isinstance(from_value, datetime.datetime) or
|
try:
|
||||||
isinstance(from_value, datetime.date) or
|
json.dumps(from_value)
|
||||||
isinstance(from_value, datetime.time) or
|
except TypeError:
|
||||||
isinstance(from_value, Country)):
|
|
||||||
from_value = str(from_value)
|
from_value = str(from_value)
|
||||||
to_value = getattr(self, field_name)
|
to_value = getattr(self, field_name)
|
||||||
if (isinstance(to_value, datetime.datetime) or
|
try:
|
||||||
isinstance(to_value, datetime.date) or
|
json.dumps(to_value)
|
||||||
isinstance(to_value, datetime.time) or
|
except TypeError:
|
||||||
isinstance(to_value, Country)):
|
|
||||||
to_value = str(to_value)
|
to_value = str(to_value)
|
||||||
if from_value != to_value:
|
if from_value != to_value:
|
||||||
change = {
|
change = {
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ from django.utils.encoding import python_2_unicode_compatible
|
|||||||
|
|
||||||
from . import get_ghost_user, get_system_user
|
from . import get_ghost_user, get_system_user
|
||||||
|
|
||||||
CHANGE_OPERATIONS = (
|
|
||||||
('update', 'update'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_system_user_id():
|
def get_system_user_id():
|
||||||
return get_system_user().id
|
return get_system_user().id
|
||||||
@@ -18,6 +14,15 @@ def get_system_user_id():
|
|||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class EventChange(models.Model):
|
class EventChange(models.Model):
|
||||||
|
UPDATE = 'update'
|
||||||
|
RAISE_FLAG = 'set_flag'
|
||||||
|
LOWER_FLAG = 'unset_flag'
|
||||||
|
OPERATION_CHOICES = (
|
||||||
|
(UPDATE, 'Update'),
|
||||||
|
(RAISE_FLAG, 'Raise Flag'),
|
||||||
|
(LOWER_FLAG, 'Lower Flag'),
|
||||||
|
)
|
||||||
|
|
||||||
event = models.ForeignKey('dav_events.Event', related_name='changes')
|
event = models.ForeignKey('dav_events.Event', related_name='changes')
|
||||||
timestamp = models.DateTimeField(default=timezone.now)
|
timestamp = models.DateTimeField(default=timezone.now)
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||||
@@ -25,7 +30,7 @@ class EventChange(models.Model):
|
|||||||
on_delete=models.SET(get_ghost_user),
|
on_delete=models.SET(get_ghost_user),
|
||||||
related_name='+')
|
related_name='+')
|
||||||
|
|
||||||
operation = models.CharField(max_length=20, choices=CHANGE_OPERATIONS)
|
operation = models.CharField(max_length=20, choices=OPERATION_CHOICES)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
import json
|
import json
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
@@ -5,6 +6,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from ..models.eventchange import EventChange
|
||||||
from ..models.eventstatus import EventStatus, get_or_create_event_status
|
from ..models.eventstatus import EventStatus, get_or_create_event_status
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
@@ -38,50 +40,81 @@ def render_event_status(event, show_void=True):
|
|||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def render_event_changelog(event):
|
def render_event_changelog(event):
|
||||||
change_templ = u'<li class="list-group-item">\n' \
|
change_templ = u'<li class="list-group-item">\n' \
|
||||||
u'\t<p class="list-group-item-heading">{timestamp}' \
|
u'\t<p class="list-group-item-heading">' \
|
||||||
|
u'<span class="glyphicon glyphicon-{icon}"></span>' \
|
||||||
|
u' {timestamp}' \
|
||||||
u' - ' \
|
u' - ' \
|
||||||
u' {user}</p>\n' \
|
u' {user}</p>\n' \
|
||||||
u'\t{content}\n' \
|
u'\t{content}\n' \
|
||||||
u'</li>\n'
|
u'</li>\n'
|
||||||
subchange_templ = u'<li class="list-group-item">\n' \
|
update_sub_templ = u'<li class="list-group-item">\n' \
|
||||||
u'\t{field}:{separator1}\n' \
|
u'\t{field}:{separator1}\n' \
|
||||||
u'\t<span style="background-color: #ffe0e0;">{refer}</span>\n' \
|
u'\t<span style="background-color: #ffe0e0;">{refer}</span>\n' \
|
||||||
u'\t{separator2}\n' \
|
u'\t{separator2}\n' \
|
||||||
u'\t<span style="background-color: #e0ffe0;">{current}</span>\n' \
|
u'\t<span style="background-color: #e0ffe0;">{current}</span>\n' \
|
||||||
u'</li>\n'
|
u'</li>\n'
|
||||||
|
raise_flag_templ = u'<span class="text-success glyphicon glyphicon-plus"></span>' \
|
||||||
|
u' <span class="label label-{bcontext}">{label}</span>' \
|
||||||
|
u' <span class="text-success glyphicon glyphicon-plus"></span>'
|
||||||
|
lower_flag_templ = u'<span class="text-danger glyphicon glyphicon-minus"></span>' \
|
||||||
|
u' <del><span class="label label-{bcontext}">{label}</span></del>' \
|
||||||
|
u' <span class="text-danger glyphicon glyphicon-minus"></span>'
|
||||||
|
|
||||||
if event.changes.exists():
|
if event.changes.exists():
|
||||||
html = u'<ul class="list-group">\n'
|
html = u'<ul class="list-group">\n'
|
||||||
|
|
||||||
for change in event.changes.all():
|
for change in event.changes.all():
|
||||||
|
|
||||||
username = change.user.get_full_name()
|
username = change.user.get_full_name()
|
||||||
if not username:
|
if not username:
|
||||||
username = change.user
|
username = change.user
|
||||||
content_html = u'<ul class="list-group">'
|
|
||||||
subchanges = json.loads(change.content)
|
if change.operation == EventChange.UPDATE:
|
||||||
for subchange in subchanges:
|
icon = u'pencil'
|
||||||
field_label = event._meta.get_field(subchange['field']).verbose_name
|
content_html = u'<ul class="list-group">'
|
||||||
try:
|
subchanges = json.loads(change.content)
|
||||||
is_long_strings = (len(subchange['refer']) + len(subchange['current'])) > 20
|
for subchange in subchanges:
|
||||||
except TypeError:
|
field_label = event._meta.get_field(subchange['field']).verbose_name
|
||||||
is_long_strings = False
|
try:
|
||||||
if is_long_strings:
|
is_long_strings = (len(subchange['refer']) + len(subchange['current'])) > 20
|
||||||
separator1 = u'<br />'
|
except TypeError:
|
||||||
separator2 = u'<br />'
|
is_long_strings = False
|
||||||
else:
|
if is_long_strings:
|
||||||
separator1 = u' '
|
separator1 = u'<br />'
|
||||||
separator2 = u' -> '
|
separator2 = u'<br />'
|
||||||
content_html += format_html(subchange_templ,
|
else:
|
||||||
field=field_label,
|
separator1 = u' '
|
||||||
separator1=mark_safe(separator1),
|
separator2 = u' -> '
|
||||||
refer=subchange['refer'],
|
content_html += format_html(update_sub_templ,
|
||||||
separator2=mark_safe(separator2),
|
field=field_label,
|
||||||
current=subchange['current'])
|
separator1=mark_safe(separator1),
|
||||||
content_html += u'</ul>'
|
refer=subchange['refer'],
|
||||||
|
separator2=mark_safe(separator2),
|
||||||
|
current=subchange['current'])
|
||||||
|
content_html += u'</ul>'
|
||||||
|
elif change.operation == EventChange.RAISE_FLAG:
|
||||||
|
icon = u'flag'
|
||||||
|
status = get_or_create_event_status(change.content)
|
||||||
|
content_html = format_html(raise_flag_templ,
|
||||||
|
bcontext=status.bootstrap_context,
|
||||||
|
label=status.label)
|
||||||
|
elif change.operation == EventChange.LOWER_FLAG:
|
||||||
|
icon = u'flag'
|
||||||
|
status = get_or_create_event_status(change.content)
|
||||||
|
content_html = format_html(lower_flag_templ,
|
||||||
|
bcontext=status.bootstrap_context,
|
||||||
|
label=status.label)
|
||||||
|
else:
|
||||||
|
icon = u'question-sign'
|
||||||
|
content_html = format_html(u'{content}', content=change.content)
|
||||||
|
|
||||||
html += format_html(change_templ,
|
html += format_html(change_templ,
|
||||||
|
icon=icon,
|
||||||
timestamp=timezone.localtime(change.timestamp).strftime('%Y-%m-%d %H:%M:%S %Z'),
|
timestamp=timezone.localtime(change.timestamp).strftime('%Y-%m-%d %H:%M:%S %Z'),
|
||||||
user=username,
|
user=username,
|
||||||
content=mark_safe(content_html))
|
content=mark_safe(content_html))
|
||||||
|
|
||||||
html += u'</ul>\n'
|
html += u'</ul>\n'
|
||||||
else:
|
else:
|
||||||
html = _(u'No entries')
|
html = _(u'Keine Einträge')
|
||||||
return mark_safe(html)
|
return mark_safe(html)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from ..models.eventchange import EventChange
|
||||||
from .generic import EventMixin
|
from .generic import EventMixin
|
||||||
|
|
||||||
TEST_EVENT_DATA = {
|
TEST_EVENT_DATA = {
|
||||||
@@ -21,12 +22,6 @@ TEST_EVENT_DATA = {
|
|||||||
|
|
||||||
|
|
||||||
class EventsTestCase(EventMixin, TestCase):
|
class EventsTestCase(EventMixin, TestCase):
|
||||||
def test_empty_changelog(self):
|
|
||||||
data = TEST_EVENT_DATA
|
|
||||||
event = self.create_event_by_model(data)
|
|
||||||
event.sport = 'M'
|
|
||||||
self.assertFalse(event.changes.exists())
|
|
||||||
|
|
||||||
def test_changelog(self):
|
def test_changelog(self):
|
||||||
data = TEST_EVENT_DATA
|
data = TEST_EVENT_DATA
|
||||||
event = self.create_event_by_model(data)
|
event = self.create_event_by_model(data)
|
||||||
@@ -44,19 +39,29 @@ class EventsTestCase(EventMixin, TestCase):
|
|||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
changes = event.changes
|
changes = event.changes
|
||||||
self.assertEqual(changes.count(), 3)
|
self.assertEqual(changes.count(), 4)
|
||||||
|
|
||||||
subchanges = json.loads(changes.get(pk=1).content)
|
change = changes.get(pk=1)
|
||||||
|
self.assertEqual(change.operation, EventChange.RAISE_FLAG)
|
||||||
|
self.assertEqual(change.content, 'draft')
|
||||||
|
|
||||||
|
change = changes.get(pk=2)
|
||||||
|
self.assertEqual(change.operation, EventChange.UPDATE)
|
||||||
|
subchanges = json.loads(change.content)
|
||||||
self.assertEqual(len(subchanges), 3)
|
self.assertEqual(len(subchanges), 3)
|
||||||
self.assertIn({'field': 'alt_first_day', 'refer': None, 'current': '2019-03-02'}, subchanges)
|
self.assertIn({'field': 'alt_first_day', 'refer': None, 'current': '2019-03-02'}, subchanges)
|
||||||
self.assertIn({'field': 'sport', 'refer': 'W', 'current': 'M'}, subchanges)
|
self.assertIn({'field': 'sport', 'refer': 'W', 'current': 'M'}, subchanges)
|
||||||
self.assertIn({'field': 'ski_lift', 'refer': False, 'current': True}, subchanges)
|
self.assertIn({'field': 'ski_lift', 'refer': False, 'current': True}, subchanges)
|
||||||
|
|
||||||
subchanges = json.loads(changes.get(pk=2).content)
|
change = changes.get(pk=3)
|
||||||
|
self.assertEqual(change.operation, EventChange.UPDATE)
|
||||||
|
subchanges = json.loads(change.content)
|
||||||
self.assertEqual(len(subchanges), 1)
|
self.assertEqual(len(subchanges), 1)
|
||||||
self.assertIn({'field': 'country', 'refer': 'DE', 'current': 'FR'}, subchanges)
|
self.assertIn({'field': 'country', 'refer': 'DE', 'current': 'FR'}, subchanges)
|
||||||
|
|
||||||
subchanges = json.loads(changes.get(pk=3).content)
|
change = changes.get(pk=4)
|
||||||
|
self.assertEqual(change.operation, EventChange.UPDATE)
|
||||||
|
subchanges = json.loads(change.content)
|
||||||
self.assertEqual(len(subchanges), 2)
|
self.assertEqual(len(subchanges), 2)
|
||||||
self.assertIn({'field': 'trainer_familyname', 'refer': 'Weißalles', 'current': 'Weißalles-Ömlaut'}, subchanges)
|
self.assertIn({'field': 'trainer_familyname', 'refer': 'Weißalles', 'current': 'Weißalles-Ömlaut'}, subchanges)
|
||||||
self.assertIn({'field': 'max_participants', 'refer': 0, 'current': 8}, subchanges)
|
self.assertIn({'field': 'max_participants', 'refer': 0, 'current': 8}, subchanges)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from . import emails
|
from . import emails
|
||||||
from . import signals
|
from . import signals
|
||||||
|
from .models.eventchange import EventChange
|
||||||
from .models.eventflag import EventFlag
|
from .models.eventflag import EventFlag
|
||||||
from .models.eventstatus import get_or_create_event_status
|
from .models.eventstatus import get_or_create_event_status
|
||||||
from .roles import get_users_by_role, has_role
|
from .roles import get_users_by_role, has_role
|
||||||
@@ -50,6 +51,8 @@ class BasicWorkflow(object):
|
|||||||
kwargs['status'] = status
|
kwargs['status'] = status
|
||||||
flag = EventFlag(**kwargs)
|
flag = EventFlag(**kwargs)
|
||||||
flag.save()
|
flag.save()
|
||||||
|
change = EventChange(event=event, user=flag.user, operation=EventChange.RAISE_FLAG, content=status.code)
|
||||||
|
change.save()
|
||||||
logger.info('Flagging status \'%s\' for %s', status.code, event)
|
logger.info('Flagging status \'%s\' for %s', status.code, event)
|
||||||
return flag
|
return flag
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user