First things to implement a event change log

This commit is contained in:
2020-09-29 15:28:48 +02:00
parent 6ddef0c736
commit 9addc237bd
6 changed files with 166 additions and 14 deletions

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-09-29 10:11
from __future__ import unicode_literals
import dav_events.models.eventchange
import dav_events.roles
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('dav_events', '0033_auto_20200925_1543'),
]
operations = [
migrations.CreateModel(
name='EventChange',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
('operation', models.CharField(choices=[('update', 'update')], max_length=20)),
('content', models.TextField()),
('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)),
],
options={
'ordering': ['event', 'timestamp'],
},
),
]

View File

@@ -1,5 +1,6 @@
from ..roles import get_system_user, get_ghost_user from ..roles import get_system_user, get_ghost_user
from .event import Event from .event import Event
from .eventchange import EventChange
from .eventflag import EventFlag from .eventflag import EventFlag
from .eventstatus import EventStatus from .eventstatus import EventStatus
from .oneclickaction import OneClickAction from .oneclickaction import OneClickAction

View File

@@ -2,6 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import difflib import difflib
import json
import logging import logging
import os import os
import re import re
@@ -16,12 +17,12 @@ from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import get_language, ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django_countries.fields import CountryField from django_countries.fields import CountryField
from . import get_ghost_user
from .. import choices from .. import choices
from .. import config from .. import config
from .. import signals from .. import signals
from ..workflow import DefaultWorkflow from ..workflow import DefaultWorkflow
from . import get_ghost_user
from .eventchange import EventChange
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -292,9 +293,8 @@ class Event(models.Model):
logger.warning('Event is not created by its owner (Current user: %s, Owner: %s)!', self.editor, owner) logger.warning('Event is not created by its owner (Current user: %s, Owner: %s)!', self.editor, owner)
self.owner = owner self.owner = owner
creating = True creating = True
elif not implicit_update: else:
original = Event.objects.get(id=self.id) original = Event.objects.get(id=self.id)
original_text = original.render_as_text(show_internal_fields=True)
if not self.editor or not self.editor.is_authenticated: if not self.editor or not self.editor.is_authenticated:
self.editor = self.owner self.editor = self.owner
@@ -305,13 +305,40 @@ class Event(models.Model):
logger.info('Event created: %s', self) logger.info('Event created: %s', self)
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)
elif not implicit_update: else:
modified_text = self.render_as_text(show_internal_fields=True) change = EventChange(event=self, user=self.editor, operation='update', content=self.diff(original))
o_lines = original_text.split('\n') change.save()
m_lines = modified_text.split('\n') if not implicit_update:
diff_lines = list(difflib.unified_diff(o_lines, m_lines, n=len(m_lines), lineterm='')) 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, diff=diff_lines, user=self.editor)
def diff(self, event, fmt='json'):
if fmt == 'human_readable':
from_text = event.render_as_text(show_internal_fields=True)
to_text = self.render_as_text(show_internal_fields=True)
from_lines = from_text.split('\n')
to_lines = to_text.split('\n')
diff_lines = list(difflib.unified_diff(from_lines, to_lines, n=len(from_lines), lineterm=''))
diff_text = '\n'.join(diff_lines[3:])
elif fmt == 'json':
fields = self._meta.get_fields()
changes = []
for field in fields:
field_name = field.name
from_value = getattr(event, field_name)
to_value = getattr(self, field_name)
if from_value != to_value:
change = {
'field': field_name,
'refer': str(from_value),
'current': str(to_value),
}
changes.append(change)
diff_text = json.dumps(changes)
else:
raise ValueError("Event.diff(): Unsupported format: {}".format(fmt))
return diff_text
def is_deadline_expired(self): def is_deadline_expired(self):
today = datetime.date.today() today = datetime.date.today()

View File

@@ -0,0 +1,36 @@
from __future__ import unicode_literals
from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from . import get_ghost_user, get_system_user
CHANGE_OPERATIONS = (
('update', 'update'),
)
def get_system_user_id():
return get_system_user().id
@python_2_unicode_compatible
class EventChange(models.Model):
event = models.ForeignKey('dav_events.Event', related_name='changes')
timestamp = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
default=get_system_user_id,
on_delete=models.SET(get_ghost_user),
related_name='+')
operation = models.CharField(max_length=20, choices=CHANGE_OPERATIONS)
content = models.TextField()
class Meta:
ordering = ['event', 'timestamp']
def __str__(self):
s = '{timestamp} - {user} - {operation}'
return s.format(operation=self.operation, timestamp=self.timestamp.strftime('%d.%m.%Y %H:%M:%S %Z'),
user=self.user)

View File

@@ -0,0 +1,54 @@
from __future__ import unicode_literals
import datetime
import json
from django.test import TestCase
from .generic import EventMixin
TEST_EVENT_DATA = {
'title': 'Täst',
'description': 'Teßt',
'mode': 'joint',
'sport': 'W',
'level': 'beginner',
'first_day': datetime.date(2019, 3, 1),
'country': 'DE',
'trainer_firstname': 'Übungsleiter',
'trainer_familyname': 'Weißalles',
'trainer_email': 'trainer@localhost',
}
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):
data = TEST_EVENT_DATA
event = self.create_event_by_model(data)
event.alt_first_day = event.first_day + datetime.timedelta(1)
event.sport = 'M'
event.ski_lift = True
event.save()
event.country = 'FR'
event.max_participants = 8
event.save()
changes = event.changes
self.assertEqual(changes.count(), 2)
subchanges = json.loads(changes.first().content)
self.assertEqual(len(subchanges), 3)
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': 'ski_lift', 'refer': 'False', 'current': 'True'}, subchanges)
subchanges = json.loads(changes.last().content)
self.assertEqual(len(subchanges), 2)
self.assertIn({'field': 'country', 'refer': 'DE', 'current': 'FR'}, subchanges)
self.assertIn({'field': 'max_participants', 'refer': '0', 'current': '8'}, subchanges)

View File

@@ -312,10 +312,9 @@ class BasicWorkflow(object):
if not app_config.settings.enable_email_on_update: if not app_config.settings.enable_email_on_update:
return return
if len(diff) < 1: if not diff:
logger.debug('send_emails_on_update(): No diff data -> Skip sending mails.') logger.debug('send_emails_on_update(): No diff data -> Skip sending mails.')
return return
diff_text = '\n'.join(diff[3:])
# Who should be informed about the update? # Who should be informed about the update?
recipients = [event.owner] recipients = [event.owner]
@@ -329,7 +328,7 @@ class BasicWorkflow(object):
for recipient in recipients: for recipient in recipients:
if recipient.email and recipient.email != updater.email: if recipient.email and recipient.email != updater.email:
email = emails.EventUpdatedMail(recipient=recipient, event=event, editor=updater, diff=diff_text) email = emails.EventUpdatedMail(recipient=recipient, event=event, editor=updater, diff=diff)
email.send() email.send()
def send_emails_on_status_update(self, flag): def send_emails_on_status_update(self, flag):