First things to implement a event change log
This commit is contained in:
35
dav_events/migrations/0034_eventchange.py
Normal file
35
dav_events/migrations/0034_eventchange.py
Normal 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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from ..roles import get_system_user, get_ghost_user
|
||||
from .event import Event
|
||||
from .eventchange import EventChange
|
||||
from .eventflag import EventFlag
|
||||
from .eventstatus import EventStatus
|
||||
from .oneclickaction import OneClickAction
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import datetime
|
||||
import difflib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
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_countries.fields import CountryField
|
||||
|
||||
from . import get_ghost_user
|
||||
from .. import choices
|
||||
from .. import config
|
||||
from .. import signals
|
||||
from ..workflow import DefaultWorkflow
|
||||
|
||||
from . import get_ghost_user
|
||||
from .eventchange import EventChange
|
||||
|
||||
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)
|
||||
self.owner = owner
|
||||
creating = True
|
||||
elif not implicit_update:
|
||||
else:
|
||||
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:
|
||||
self.editor = self.owner
|
||||
@@ -305,13 +305,40 @@ class Event(models.Model):
|
||||
logger.info('Event created: %s', self)
|
||||
signals.event_created.send(sender=self.__class__, event=self)
|
||||
self.workflow.update_status('draft', self.editor)
|
||||
elif not implicit_update:
|
||||
modified_text = self.render_as_text(show_internal_fields=True)
|
||||
o_lines = original_text.split('\n')
|
||||
m_lines = modified_text.split('\n')
|
||||
diff_lines = list(difflib.unified_diff(o_lines, m_lines, n=len(m_lines), lineterm=''))
|
||||
logger.info('Event updated: %s', self)
|
||||
signals.event_updated.send(sender=self.__class__, event=self, diff=diff_lines, user=self.editor)
|
||||
else:
|
||||
change = EventChange(event=self, user=self.editor, operation='update', content=self.diff(original))
|
||||
change.save()
|
||||
if not implicit_update:
|
||||
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)
|
||||
|
||||
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):
|
||||
today = datetime.date.today()
|
||||
|
||||
36
dav_events/models/eventchange.py
Normal file
36
dav_events/models/eventchange.py
Normal 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)
|
||||
54
dav_events/tests/test_models.py
Normal file
54
dav_events/tests/test_models.py
Normal 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)
|
||||
@@ -312,10 +312,9 @@ class BasicWorkflow(object):
|
||||
if not app_config.settings.enable_email_on_update:
|
||||
return
|
||||
|
||||
if len(diff) < 1:
|
||||
if not diff:
|
||||
logger.debug('send_emails_on_update(): No diff data -> Skip sending mails.')
|
||||
return
|
||||
diff_text = '\n'.join(diff[3:])
|
||||
|
||||
# Who should be informed about the update?
|
||||
recipients = [event.owner]
|
||||
@@ -329,7 +328,7 @@ class BasicWorkflow(object):
|
||||
|
||||
for recipient in recipients:
|
||||
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()
|
||||
|
||||
def send_emails_on_status_update(self, flag):
|
||||
|
||||
Reference in New Issue
Block a user