Merge pull request 'Add a simple changelog to the events' (#26) from changelog into master
All checks were successful
buildbot/tox Build done.
All checks were successful
buildbot/tox Build done.
Reviewed-on: #26
This commit was merged in pull request #26.
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 20:15
|
||||||
|
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'), ('set_flag', 'Raise Flag'), ('unset_flag', 'Lower Flag')], 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'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
16
dav_events/migrations/0035_merge_20201103_1112.py
Normal file
16
dav_events/migrations/0035_merge_20201103_1112.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.29 on 2020-11-03 10:12
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dav_events', '0034_auto_20201015_1738'),
|
||||||
|
('dav_events', '0034_eventchange'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -14,14 +15,14 @@ from django.db import models
|
|||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
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 Country, 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,50 @@ 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=EventChange.UPDATE,
|
||||||
o_lines = original_text.split('\n')
|
content=self.diff(original))
|
||||||
m_lines = modified_text.split('\n')
|
change.save()
|
||||||
diff_lines = list(difflib.unified_diff(o_lines, m_lines, n=len(m_lines), lineterm=''))
|
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=diff_lines, 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'):
|
||||||
|
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)
|
||||||
|
try:
|
||||||
|
json.dumps(from_value)
|
||||||
|
except TypeError:
|
||||||
|
from_value = str(from_value)
|
||||||
|
to_value = getattr(self, field_name)
|
||||||
|
try:
|
||||||
|
json.dumps(to_value)
|
||||||
|
except TypeError:
|
||||||
|
to_value = str(to_value)
|
||||||
|
if from_value != to_value:
|
||||||
|
change = {
|
||||||
|
'field': field_name,
|
||||||
|
'refer': from_value,
|
||||||
|
'current': 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()
|
||||||
@@ -468,6 +505,7 @@ class Event(models.Model):
|
|||||||
'course_goal_6': self.course_goal_6,
|
'course_goal_6': self.course_goal_6,
|
||||||
'planned_publication_date': self.planned_publication_date,
|
'planned_publication_date': self.planned_publication_date,
|
||||||
'internal_note': self.internal_note,
|
'internal_note': self.internal_note,
|
||||||
|
'registration_closed': self.registration_closed,
|
||||||
}
|
}
|
||||||
if context is not None:
|
if context is not None:
|
||||||
r.update(context)
|
r.update(context)
|
||||||
|
|||||||
42
dav_events/models/eventchange.py
Normal file
42
dav_events/models/eventchange.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_user_id():
|
||||||
|
return get_system_user().id
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
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')
|
||||||
|
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=OPERATION_CHOICES)
|
||||||
|
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,6 +54,7 @@
|
|||||||
{% trans 'Schwierigkeitsnivau' %}: {{ event.get_level_display }}
|
{% trans 'Schwierigkeitsnivau' %}: {{ event.get_level_display }}
|
||||||
{% if event.sport == 'S' %}{% trans 'Skiliftbenutzung' %}: {% if event.ski_list %}{% trans 'Ja' %}{% else %}{% trans 'Nein' %}{% endif %}
|
{% if event.sport == 'S' %}{% trans 'Skiliftbenutzung' %}: {% if event.ski_list %}{% trans 'Ja' %}{% else %}{% trans 'Nein' %}{% endif %}
|
||||||
{% endif %}{% trans 'Gelände' %}: {{ event.get_terrain_display }}
|
{% endif %}{% trans 'Gelände' %}: {{ event.get_terrain_display }}
|
||||||
|
{% trans 'Anmeldung' %}: {% if registration_required %}{% if registration_closed %}{% trans 'Geschlossen' %}{% else %}{% trans 'Erforderlich' %}{% endif %}{% else %}{% trans 'Nicht erforderlich' %}{% endif %}
|
||||||
{% trans 'Anreise des Kurs-/Tourenleiters am Vortag' %}: {% if event.arrival_previous_day %}{% trans 'Ja' %}{% else %}{% trans 'Nein' %}{% endif %}
|
{% trans 'Anreise des Kurs-/Tourenleiters am Vortag' %}: {% if event.arrival_previous_day %}{% trans 'Ja' %}{% else %}{% trans 'Nein' %}{% endif %}
|
||||||
{% trans 'Veröffentlichung' %}: {% if planned_publication_date %}{{ planned_publication_date|date:'l, d. F Y' }}{% else %}{% trans 'sofort' %}{% endif %}
|
{% trans 'Veröffentlichung' %}: {% if planned_publication_date %}{{ planned_publication_date|date:'l, d. F Y' }}{% else %}{% trans 'sofort' %}{% endif %}
|
||||||
{% if internal_note %}
|
{% if internal_note %}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{% extends 'dav_events/base.html' %}
|
{% extends 'dav_events/base.html' %}
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load dav_events %}
|
||||||
|
|
||||||
{% block head-title %}{{ event }} - {{ block.super }}{% endblock head-title %}
|
{% block head-title %}{{ event }} - {{ block.super }}{% endblock head-title %}
|
||||||
|
|
||||||
@@ -188,35 +189,77 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
{{ event.render_as_html }}
|
{{ event.render_as_html }}
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
<h5>Status-Log</h5>
|
<div class="panel-group" id="log-accordion" role="tablist" aria-multiselectable="true">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div id="headingStatusLog" class="panel-heading" role="tab">
|
||||||
|
<h5 class="panel-title">
|
||||||
|
<a role="button" href="#collapseStatusLog" data-toggle="collapse"
|
||||||
|
data-parent="#log-accordion" aria-expanded="true"
|
||||||
|
aria-controls="collapseStatusLog">
|
||||||
|
Status-Flags
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div id="collapseStatusLog" class="panel-collapse collapse in"
|
||||||
|
role="tabpanel" aria-labelledby="headingStatusLog">
|
||||||
|
<div class="panel-body">
|
||||||
{% for flag in event.flags.all %}
|
{% for flag in event.flags.all %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-5">
|
<div class="col-sm-4">
|
||||||
<span class="text-{{ flag.status.bootstrap_context|default:'default' }}">{% bootstrap_icon 'check' %}</span>
|
<span class="text-{{ flag.status.bootstrap_context|default:'default' }}">{% bootstrap_icon 'check' %}</span>
|
||||||
<strong>{{ flag.status.label }}:</strong>
|
<strong>{{ flag.status.label }}:</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-8">
|
||||||
{{ flag.timestamp|date:'l, d. F Y, H:i' }} {% trans 'Uhr' %}<br />
|
{{ flag.timestamp|date:'l, d. F Y, H:i' }} {% trans 'Uhr' %}<br />
|
||||||
{% trans 'von' %} {{ flag.user.get_full_name|default:flag.user }}
|
{% trans 'von' %} {{ flag.user.get_full_name|default:flag.user }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div id="headingChangeLog" class="panel-heading" role="tab">
|
||||||
|
<h5 class="panel-title">
|
||||||
|
<a role="button" href="#collapseChangeLog" data-toggle="collapse"
|
||||||
|
data-parent="#log-accordion" aria-expanded="true"
|
||||||
|
aria-controls="collapseChangeLog">
|
||||||
|
Change-Log
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div id="collapseChangeLog" class="panel-collapse collapse"
|
||||||
|
role="tabpanel" aria-labelledby="headingChangeLog">
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_event_changelog event %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-sm-5">
|
<div class="col-sm-5">
|
||||||
<h5>{% trans 'Veröffentlichung' %}</h5>
|
{% if event.internal_note %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h5 class="panel-title">{% trans 'Bearbeitungshinweis' %}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div><small>{{ event.internal_note|linebreaksbr }}</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h5 class="panel-title">{% trans 'Veröffentlichung' %}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
{% if event.planned_publication_date %}
|
{% if event.planned_publication_date %}
|
||||||
{{ event.planned_publication_date|date:'l, d. F Y' }}
|
{{ event.planned_publication_date|date:'l, d. F Y' }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Unverzüglich' %}
|
{% trans 'Unverzüglich' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.internal_note %}
|
|
||||||
<h5 style="margin-top: 1em;">{% trans 'Bearbeitungshinweis' %}</h5>
|
|
||||||
<div class="well well-sm"><small>{{ event.internal_note|linebreaksbr }}</small></div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import json
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils import timezone
|
||||||
|
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()
|
||||||
@@ -30,3 +35,86 @@ def render_event_status(event, show_void=True):
|
|||||||
context=context)
|
context=context)
|
||||||
|
|
||||||
return mark_safe(html)
|
return mark_safe(html)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def render_event_changelog(event):
|
||||||
|
change_templ = u'<li class="list-group-item">\n' \
|
||||||
|
u'\t<p class="list-group-item-heading">' \
|
||||||
|
u'<span class="glyphicon glyphicon-{icon}"></span>' \
|
||||||
|
u' {timestamp}' \
|
||||||
|
u' - ' \
|
||||||
|
u' {user}</p>\n' \
|
||||||
|
u'\t{content}\n' \
|
||||||
|
u'</li>\n'
|
||||||
|
update_sub_templ = u'<li class="list-group-item">\n' \
|
||||||
|
u'\t{field}:{separator1}\n' \
|
||||||
|
u'\t<span style="background-color: #ffe0e0;">{refer}</span>\n' \
|
||||||
|
u'\t{separator2}\n' \
|
||||||
|
u'\t<span style="background-color: #e0ffe0;">{current}</span>\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():
|
||||||
|
html = u'<ul class="list-group">\n'
|
||||||
|
|
||||||
|
for change in event.changes.all():
|
||||||
|
|
||||||
|
username = change.user.get_full_name()
|
||||||
|
if not username:
|
||||||
|
username = change.user
|
||||||
|
|
||||||
|
if change.operation == EventChange.UPDATE:
|
||||||
|
icon = u'pencil'
|
||||||
|
content_html = u'<ul class="list-group">'
|
||||||
|
subchanges = json.loads(change.content)
|
||||||
|
for subchange in subchanges:
|
||||||
|
field_label = event._meta.get_field(subchange['field']).verbose_name
|
||||||
|
try:
|
||||||
|
is_long_strings = (len(subchange['refer']) + len(subchange['current'])) > 20
|
||||||
|
except TypeError:
|
||||||
|
is_long_strings = False
|
||||||
|
if is_long_strings:
|
||||||
|
separator1 = u'<br />'
|
||||||
|
separator2 = u'<br />'
|
||||||
|
else:
|
||||||
|
separator1 = u' '
|
||||||
|
separator2 = u' -> '
|
||||||
|
content_html += format_html(update_sub_templ,
|
||||||
|
field=field_label,
|
||||||
|
separator1=mark_safe(separator1),
|
||||||
|
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,
|
||||||
|
icon=icon,
|
||||||
|
timestamp=timezone.localtime(change.timestamp).strftime('%Y-%m-%d %H:%M:%S %Z'),
|
||||||
|
user=username,
|
||||||
|
content=mark_safe(content_html))
|
||||||
|
|
||||||
|
html += u'</ul>\n'
|
||||||
|
else:
|
||||||
|
html = _(u'Keine Einträge')
|
||||||
|
return mark_safe(html)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Link zur Veranstaltung:
|
|||||||
Veranstaltungsart: gemeinschaftliche Tour
|
Veranstaltungsart: gemeinschaftliche Tour
|
||||||
Schwierigkeitsnivau: Anfänger
|
Schwierigkeitsnivau: Anfänger
|
||||||
Gelände: Kletterhalle
|
Gelände: Kletterhalle
|
||||||
|
Anmeldung: Nicht erforderlich
|
||||||
Anreise des Kurs-/Tourenleiters am Vortag: Nein
|
Anreise des Kurs-/Tourenleiters am Vortag: Nein
|
||||||
Veröffentlichung: sofort
|
Veröffentlichung: sofort
|
||||||
"""
|
"""
|
||||||
|
|||||||
67
dav_events/tests/test_models.py
Normal file
67
dav_events/tests/test_models.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from ..models.eventchange import EventChange
|
||||||
|
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_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.save()
|
||||||
|
|
||||||
|
event.trainer_familyname += '-Ömlaut'
|
||||||
|
event.max_participants = 8
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
changes = event.changes
|
||||||
|
self.assertEqual(changes.count(), 4)
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
|
||||||
|
change = changes.get(pk=3)
|
||||||
|
self.assertEqual(change.operation, EventChange.UPDATE)
|
||||||
|
subchanges = json.loads(change.content)
|
||||||
|
self.assertEqual(len(subchanges), 1)
|
||||||
|
self.assertIn({'field': 'country', 'refer': 'DE', 'current': 'FR'}, subchanges)
|
||||||
|
|
||||||
|
change = changes.get(pk=4)
|
||||||
|
self.assertEqual(change.operation, EventChange.UPDATE)
|
||||||
|
subchanges = json.loads(change.content)
|
||||||
|
self.assertEqual(len(subchanges), 2)
|
||||||
|
self.assertIn({'field': 'trainer_familyname', 'refer': 'Weißalles', 'current': 'Weißalles-Ömlaut'}, 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
|
||||||
|
|
||||||
@@ -312,10 +315,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 +331,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):
|
||||||
|
|||||||
Reference in New Issue
Block a user