Added a model for event state (including data migration, signal based
notifications, etc.)
This commit is contained in:
@@ -24,7 +24,7 @@ INSTALLATION
|
||||
The creation of a separated python environment is very easy with the
|
||||
virtualenv tool (a python package).
|
||||
|
||||
If you decide to not use virtualenv, proceed with step 2.
|
||||
If you decide to not use virtualenv, confirm_status with step 2.
|
||||
|
||||
- Create the python environment in a directory called ./env/python:
|
||||
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Event, OneClickAction
|
||||
from .models import EventStatus, EventFlag, Event, OneClickAction
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
@admin.register(EventStatus)
|
||||
class EventStatusAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class EventFlagInline(admin.TabularInline):
|
||||
model = EventFlag
|
||||
extra = 1
|
||||
|
||||
|
||||
@admin.register(Event)
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
inlines = [EventFlagInline]
|
||||
|
||||
|
||||
@admin.register(OneClickAction)
|
||||
class OneClickActionAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(OneClickAction, OneClickActionAdmin)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from . import signals
|
||||
from .config import AppConfig as _AppConfig, DefaultSetting
|
||||
|
||||
DEFAULT_SETTINGS = (
|
||||
@@ -28,3 +29,9 @@ class AppConfig(_AppConfig):
|
||||
name = 'dav_events'
|
||||
verbose_name = u'DAV Veranstaltungen'
|
||||
default_settings = DEFAULT_SETTINGS
|
||||
|
||||
def ready(self):
|
||||
signals.event_submitted.connect(signals.notify_submitted_event)
|
||||
signals.event_submitted.connect(signals.notify_to_accept_event)
|
||||
signals.event_accepted.connect(signals.notify_accepted_event)
|
||||
signals.event_accepted.connect(signals.notify_to_publish_event)
|
||||
|
||||
@@ -5,38 +5,20 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.mail import EmailMessage
|
||||
from django.template.loader import get_template
|
||||
|
||||
from .utils import get_users_by_role
|
||||
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_recipients(task, sport=None):
|
||||
users = []
|
||||
if task == 'accept':
|
||||
role = 'manage_all'
|
||||
users += get_users_by_role(role)
|
||||
|
||||
if sport:
|
||||
role = 'manage_{}'.format(sport.lower())
|
||||
users += get_users_by_role(role)
|
||||
|
||||
elif task == 'publish':
|
||||
role = 'incremental_publisher'
|
||||
users += get_users_by_role(role)
|
||||
else:
|
||||
raise ValueError('utils.get_recipients(): invalid value for task')
|
||||
|
||||
return [u'{name} <{addr}>'.format(name=u.get_full_name(), addr=u.email) for u in users]
|
||||
|
||||
|
||||
class AbstractMail(object):
|
||||
_sender = app_config.settings.email_sender
|
||||
_subject = u''
|
||||
_template_name = None
|
||||
|
||||
def _get_sender(self):
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
return app_config.settings.email_sender
|
||||
|
||||
def _get_subject(self, **kwargs):
|
||||
s = self._subject
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
if app_config.settings.email_subject_prefix:
|
||||
s = u'%s %s' % (app_config.settings.email_subject_prefix, s)
|
||||
s.format(**kwargs)
|
||||
@@ -48,6 +30,7 @@ class AbstractMail(object):
|
||||
return get_template(self._template_name)
|
||||
|
||||
def _get_context_data(self, extra_context=None):
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
context = {
|
||||
'base_url': app_config.settings.email_base_url,
|
||||
}
|
||||
@@ -66,7 +49,7 @@ class AbstractMail(object):
|
||||
def send(self):
|
||||
subject = self._get_subject()
|
||||
body = self._get_body()
|
||||
sender = self._sender
|
||||
sender = self._get_sender()
|
||||
recipients = self._get_recipients()
|
||||
|
||||
emo = EmailMessage(subject=subject, body=body, from_email=sender, to=recipients)
|
||||
@@ -92,11 +75,6 @@ class AbstractEventMail(AbstractMail):
|
||||
context.update(self._event.get_template_context())
|
||||
return context
|
||||
|
||||
def send(self):
|
||||
if not app_config.settings.enable_email_notifications:
|
||||
return None
|
||||
return super(AbstractEventMail, self).send()
|
||||
|
||||
|
||||
class NewEventMail(AbstractEventMail):
|
||||
_template_name = 'dav_events/emails/new_event.txt'
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
from django import forms
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.models.manager import Manager
|
||||
|
||||
from .. import converters
|
||||
|
||||
@@ -165,8 +166,11 @@ class ModelMixin(object):
|
||||
data = {}
|
||||
for field in instance._meta.get_fields():
|
||||
v = getattr(instance, field.name)
|
||||
if v is not None:
|
||||
data[field.name] = getattr(instance, field.name)
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, Manager):
|
||||
continue
|
||||
data[field.name] = getattr(instance, field.name)
|
||||
self.is_bound = True
|
||||
self.data = data
|
||||
return data
|
||||
|
||||
@@ -79,7 +79,7 @@ class Migration(migrations.Migration):
|
||||
('trainer_3_phone', models.CharField(blank=True, max_length=250)),
|
||||
('charge', models.FloatField(default=0)),
|
||||
('additional_costs', models.CharField(blank=True, max_length=250)),
|
||||
('owner', models.ForeignKey(null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='events', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='events', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['first_day'],
|
||||
|
||||
@@ -19,7 +19,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='accepted_by',
|
||||
field=models.ForeignKey(null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
field=models.ForeignKey(null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
|
||||
@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='accepted_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
|
||||
@@ -28,6 +28,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='published_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='accepted_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Freigegeben durch'),
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Freigegeben durch'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
@@ -239,7 +239,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='owner',
|
||||
field=models.ForeignKey(null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='events', to=settings.AUTH_USER_MODEL, verbose_name='Ersteller'),
|
||||
field=models.ForeignKey(null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='events', to=settings.AUTH_USER_MODEL, verbose_name='Ersteller'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
@@ -269,7 +269,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='publication_confirmed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.models.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Ver\xf6ffentlichung best\xe4tigt durch'),
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Ver\xf6ffentlichung best\xe4tigt durch'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
|
||||
57
dav_events/migrations/0020_auto_20180704_1202.py
Normal file
57
dav_events/migrations/0020_auto_20180704_1202.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-07-04 12:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dav_events.models.event
|
||||
import dav_events.utils
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
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', '0019_auto_20180306_2101'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventFlag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flags', to='dav_events.Event')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['event', 'status', 'timestamp'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventStatus',
|
||||
fields=[
|
||||
('code', models.CharField(max_length=254, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(b'^[0-9a-z]*$', b'Only characters a-z and digits 0-9 are allowed.')])),
|
||||
('severity', models.IntegerField(unique=True)),
|
||||
('label', models.CharField(max_length=254, unique=True)),
|
||||
('bootstrap_context', models.CharField(blank=True, choices=[(b'default', b'default'), (b'primary', b'primary'), (b'success', b'success'), (b'info', b'info'), (b'warning', b'warning'), (b'danger', b'danger')], max_length=20)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['severity'],
|
||||
'verbose_name': 'Veranstaltungsstatus',
|
||||
'verbose_name_plural': 'Veranstaltungsstati',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventflag',
|
||||
name='status',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dav_events.EventStatus'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventflag',
|
||||
name='user',
|
||||
field=models.ForeignKey(default=dav_events.models.event.get_system_user_id, on_delete=models.SET(dav_events.utils.get_ghost_user), related_name='+', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
61
dav_events/migrations/0021_create_flags.py
Normal file
61
dav_events/migrations/0021_create_flags.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from dav_events.models.eventstatus import get_event_status
|
||||
|
||||
|
||||
def create_stati(apps, schema_editor):
|
||||
l = ('draft', 'submitted', 'accepted', 'publishing', 'published', 'expired')
|
||||
for c in l:
|
||||
get_event_status(c)
|
||||
|
||||
|
||||
def create_flags(apps, schema_editor):
|
||||
EventStatus = apps.get_model('dav_events', 'EventStatus')
|
||||
EventFlag = apps.get_model('dav_events', 'EventFlag')
|
||||
Event = apps.get_model('dav_events', 'Event')
|
||||
for event in Event.objects.all():
|
||||
if not len(event.flags.filter(status__code='draft')):
|
||||
status = EventStatus.objects.get(code='draft')
|
||||
flag = EventFlag(event=event, status=status, timestamp=event.created_at, user=event.owner)
|
||||
flag.save()
|
||||
|
||||
if not len(event.flags.filter(status__code='submitted')):
|
||||
status = EventStatus.objects.get(code='submitted')
|
||||
flag = EventFlag(event=event, status=status, timestamp=event.created_at, user=event.owner)
|
||||
flag.save()
|
||||
|
||||
if event.accepted and not len(event.flags.filter(status__code='accepted')):
|
||||
status = EventStatus.objects.get(code='accepted')
|
||||
flag = EventFlag(event=event, status=status, timestamp=event.accepted_at, user=event.accepted_by)
|
||||
flag.save()
|
||||
|
||||
if event.publication_confirmed:
|
||||
if event.planned_publication_date:
|
||||
if not len(event.flags.filter(status__code='publishing')):
|
||||
status = EventStatus.objects.get(code='publishing')
|
||||
flag = EventFlag(event=event, status=status,
|
||||
timestamp=event.publication_confirmed_at,
|
||||
user=event.publication_confirmed_by)
|
||||
flag.save()
|
||||
else:
|
||||
if not len(event.flags.filter(status__code='published')):
|
||||
status = EventStatus.objects.get(code='published')
|
||||
flag = EventFlag(event=event, status=status,
|
||||
timestamp=event.publication_confirmed_at,
|
||||
user=event.publication_confirmed_by)
|
||||
flag.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dav_events', '0020_auto_20180704_1202'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_stati),
|
||||
migrations.RunPython(create_flags),
|
||||
]
|
||||
3
dav_events/models/__init__.py
Normal file
3
dav_events/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .event import Event, EventFlag
|
||||
from .eventstatus import EventStatus
|
||||
from .oneclickaction import OneClickAction
|
||||
@@ -4,7 +4,6 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import unicodedata
|
||||
import uuid
|
||||
from babel.dates import format_date
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
@@ -12,19 +11,22 @@ from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.template.loader import get_template
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import get_language, ugettext, ugettext_lazy as _
|
||||
from django.utils.translation import get_language, ugettext_lazy as _
|
||||
from django_countries.fields import CountryField
|
||||
|
||||
from . import choices
|
||||
from . import config
|
||||
from . import emails
|
||||
from .utils import get_users_by_role
|
||||
from .. import choices
|
||||
from .. import config
|
||||
from .. import signals
|
||||
from ..utils import get_ghost_user, get_system_user
|
||||
|
||||
from .eventstatus import EventStatus, get_event_status
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_ghost_user():
|
||||
return get_user_model().objects.get_or_create(username='-deleted-')[0]
|
||||
def get_system_user_id():
|
||||
return get_system_user().id
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
@@ -294,90 +296,115 @@ class Event(models.Model):
|
||||
super(Event, self).save(**kwargs)
|
||||
|
||||
if creating:
|
||||
self.confirm_status('draft', self.owner)
|
||||
logger.info('Event created: %s', self)
|
||||
|
||||
managers = get_users_by_role('manage_all')
|
||||
managers += get_users_by_role('manage_{}'.format(self.sport.lower()))
|
||||
for user in managers:
|
||||
if user.email:
|
||||
action = OneClickAction(command='EA')
|
||||
action.parameters = '{event},{user}'.format(event=self.id, user=user.id)
|
||||
action.save()
|
||||
email = emails.EventToAcceptMail(recipient=user, event=self, accept_action=action)
|
||||
email.send()
|
||||
def update_flags(self):
|
||||
if not self.id:
|
||||
return
|
||||
|
||||
if self.owner.email:
|
||||
email = emails.NewEventMail(recipient=self.owner, event=self)
|
||||
email.send()
|
||||
today = datetime.date.today()
|
||||
midnight = datetime.time(00, 00, 00)
|
||||
|
||||
def accept(self, user=None):
|
||||
if not self.accepted:
|
||||
if not self.number:
|
||||
self.number = self.get_next_number()
|
||||
self.accepted = True
|
||||
self.accepted_at = timezone.now()
|
||||
if user:
|
||||
self.accepted_by = user
|
||||
else:
|
||||
logger.warning('Event.accept(): no user given! (Event: %s)', self.event)
|
||||
self.save()
|
||||
logger.info('Event is accepted: %s', self)
|
||||
if not self.flags.filter(status__code='draft').exists():
|
||||
new_flag = EventFlag(event=self, status=get_event_status('draft'), timestamp=self.created_at)
|
||||
new_flag.save()
|
||||
logger.info('Detected draft state of Event %s', self)
|
||||
|
||||
publishers = get_users_by_role('publish_incremental')
|
||||
for user in publishers:
|
||||
if user.email:
|
||||
action = OneClickAction(command='EP')
|
||||
action.parameters = '{event},{user}'.format(event=self.id, user=user.id)
|
||||
action.save()
|
||||
email = emails.EventToPublishMail(recipient=user, event=self, confirm_publication_action=action)
|
||||
email.send()
|
||||
if (self.flags.filter(status__code='publishing').exists() and
|
||||
not self.flags.filter(status__code='published').exists()):
|
||||
if not self.planned_publication_date:
|
||||
flag = self.flags.filter(status__code='publishing').last()
|
||||
new_status = get_event_status('published')
|
||||
new_flag = EventFlag(event=self, status=new_status, timestamp=flag.timestamp)
|
||||
new_flag.save()
|
||||
logger.info('Detected published state of Event %s', self)
|
||||
elif self.planned_publication_date <= today:
|
||||
new_status = get_event_status('published')
|
||||
new_timestamp = timezone.make_aware(datetime.datetime.combine(self.planned_publication_date, midnight))
|
||||
new_flag = EventFlag(event=self, status=new_status, timestamp=new_timestamp)
|
||||
new_flag.save()
|
||||
logger.info('Detected published state of Event %s', self)
|
||||
|
||||
if self.owner.email:
|
||||
email = emails.EventAcceptedMail(recipient=self.owner, event=self)
|
||||
email.send()
|
||||
if not self.flags.filter(status__code='expired').exists():
|
||||
expired_at = None
|
||||
|
||||
return self.number
|
||||
if self.alt_last_day:
|
||||
if self.alt_last_day < today:
|
||||
expired_at = self.alt_last_day
|
||||
elif self.last_day:
|
||||
if self.last_day < today:
|
||||
expired_at = self.last_day
|
||||
elif self.alt_first_day:
|
||||
if self.alt_first_day < today:
|
||||
expired_at = self.alt_first_day
|
||||
elif self.first_day and self.first_day < today:
|
||||
expired_at = self.first_day
|
||||
|
||||
if expired_at:
|
||||
new_timestamp = timezone.make_aware(datetime.datetime.combine(expired_at, midnight))
|
||||
new_flag = EventFlag(event=self, status=get_event_status('expired'), timestamp=new_timestamp)
|
||||
new_flag.save()
|
||||
logger.info('Detected expired state of Event %s', self)
|
||||
|
||||
def is_flagged(self, status):
|
||||
self.update_flags()
|
||||
if isinstance(status, EventStatus):
|
||||
code = status.code
|
||||
else:
|
||||
return None
|
||||
|
||||
def confirm_publication(self, user=None):
|
||||
if not self.accepted:
|
||||
logger.warning('Event.confirm_publication(): event is not accepted yet! (Event: %s)', self.event)
|
||||
|
||||
if not self.publication_confirmed:
|
||||
self.publication_confirmed = True
|
||||
self.publication_confirmed_at = timezone.now()
|
||||
if user:
|
||||
self.publication_confirmed_by = user
|
||||
else:
|
||||
logger.warning('Event.confirm_publication(): no user given! (Event: %s)', self.event)
|
||||
self.save()
|
||||
logger.info('Event is published: %s', self)
|
||||
code = status
|
||||
if self.flags.filter(status__code=code).exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_status(self):
|
||||
today = datetime.date.today()
|
||||
if self.alt_last_day:
|
||||
if self.alt_last_day < today:
|
||||
return 'expired'
|
||||
elif self.last_day:
|
||||
if self.last_day < today:
|
||||
return 'expired'
|
||||
elif self.alt_first_day:
|
||||
if self.alt_first_day < today:
|
||||
return 'expired'
|
||||
elif self.first_day and self.first_day < today:
|
||||
return 'expired'
|
||||
self.update_flags()
|
||||
last_flag = self.flags.last()
|
||||
if last_flag:
|
||||
return last_flag.status
|
||||
return get_event_status('void')
|
||||
|
||||
if self.publication_confirmed and self.planned_publication_date and self.planned_publication_date > today:
|
||||
return 'publishing'
|
||||
elif self.publication_confirmed:
|
||||
return 'published'
|
||||
elif self.accepted:
|
||||
return 'accepted'
|
||||
elif self.owner:
|
||||
return 'submitted'
|
||||
def get_status_codes(self):
|
||||
self.update_flags()
|
||||
return [flag.status.code for flag in self.flags.all()]
|
||||
|
||||
return 'draft'
|
||||
def confirm_status(self, status, user):
|
||||
if isinstance(status, EventStatus):
|
||||
code = status.code
|
||||
else:
|
||||
code = status
|
||||
|
||||
flag = self.flags.filter(status__code=code).last()
|
||||
if flag:
|
||||
return flag
|
||||
|
||||
if code == 'accepted':
|
||||
if not self.is_flagged('submitted'):
|
||||
logger.warning('Event.confirm_status(): yet not submitted event got accepted! (Event: %s)', self)
|
||||
if not self.number:
|
||||
self.number = self.get_next_number()
|
||||
self.save()
|
||||
elif code == 'publishing' or code == 'published':
|
||||
if not self.is_flagged('accepted'):
|
||||
logger.warning('Event.confirm_status(): yet not accepted event got published! (Event: %s)', self)
|
||||
|
||||
status_obj = get_event_status(code)
|
||||
flag = EventFlag(event=self, status=status_obj, user=user)
|
||||
flag.save()
|
||||
logger.info('Flagging status \'%s\' for %s', code, self)
|
||||
|
||||
if code == 'submitted':
|
||||
signals.event_submitted.send(sender=self.__class__, event=self)
|
||||
elif code == 'accepted':
|
||||
signals.event_accepted.send(sender=self.__class__, event=self)
|
||||
elif code == 'publishing':
|
||||
signals.event_publishing.send(sender=self.__class__, event=self)
|
||||
elif code == 'published':
|
||||
signals.event_published.send(sender=self.__class__, event=self)
|
||||
elif code == 'expired':
|
||||
signals.event_expired.send(sender=self.__class__, event=self)
|
||||
|
||||
return flag
|
||||
|
||||
def get_next_number(self):
|
||||
counter = 0
|
||||
@@ -576,134 +603,23 @@ class Event(models.Model):
|
||||
return template.render(self.get_template_context())
|
||||
|
||||
|
||||
class OneClickAction(models.Model):
|
||||
COMMANDS = (
|
||||
('EA', 'accept event'),
|
||||
('EP', 'confirm publication of an event'),
|
||||
('EL', 'login and go to event list')
|
||||
)
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
allow_repeat = models.BooleanField(default=False)
|
||||
|
||||
done = models.BooleanField(default=False)
|
||||
done_at = models.DateTimeField(blank=True,
|
||||
null=True)
|
||||
|
||||
command = models.CharField(max_length=2, choices=COMMANDS)
|
||||
|
||||
parameters = models.TextField(blank=True)
|
||||
class EventFlag(models.Model):
|
||||
event = models.ForeignKey(Event, related_name='flags')
|
||||
status = models.ForeignKey(EventStatus,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+')
|
||||
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='+')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _(u'One-Click-Action')
|
||||
ordering = ['event', 'status', 'timestamp']
|
||||
|
||||
def __unicode__(self):
|
||||
s = u'{command}({parameters}) - {description}'.format(description=self.get_command_display(),
|
||||
command=self.command,
|
||||
parameters=self.parameters)
|
||||
if self.done and not self.allow_repeat:
|
||||
s += u' - done'
|
||||
return s
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dav_events:action_run', kwargs={'pk': self.pk})
|
||||
|
||||
def run(self):
|
||||
result = {}
|
||||
if self.done and not self.allow_repeat:
|
||||
result['context'] = {
|
||||
'status': 'warning',
|
||||
'message': ugettext(u'Diese Aktion hast du bereits ausgeführt.'),
|
||||
}
|
||||
return result
|
||||
|
||||
logger.info('OneClickAction.run(): %s(%s)', self.command, self.parameters)
|
||||
if self.command == 'EA':
|
||||
text = u''
|
||||
try:
|
||||
event_id, user_id = self.parameters.split(',')
|
||||
event = Event.objects.get(id=event_id)
|
||||
user = get_user_model().objects.get(id=user_id)
|
||||
number = event.accept(user)
|
||||
if number:
|
||||
status = 'success'
|
||||
message = ugettext(u'Veranstaltung freigegeben.')
|
||||
text = unicode(event)
|
||||
else:
|
||||
status = 'info'
|
||||
message = (ugettext(u'Veranstaltung wurde bereits von %(fullname)s freigegeben.') %
|
||||
{'fullname': event.accepted_by.get_full_name()})
|
||||
text = unicode(event)
|
||||
text += u'\n'
|
||||
text += (ugettext(u'Freigegeben am: %(date)s') %
|
||||
{'date': event.accepted_at.strftime('%d.%m.%Y %H:%M:%S')})
|
||||
|
||||
self.done = True
|
||||
self.done_at = timezone.now()
|
||||
self.save()
|
||||
except Exception as e:
|
||||
status = 'danger'
|
||||
message = str(e)
|
||||
logger.error('OneClickAction.run(): %s(%s): %s', self.command, self.parameters, message)
|
||||
|
||||
result['context'] = {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'text': text,
|
||||
}
|
||||
elif self.command == 'EP':
|
||||
text = u''
|
||||
try:
|
||||
event_id, user_id = self.parameters.split(',')
|
||||
event = Event.objects.get(id=event_id)
|
||||
user = get_user_model().objects.get(id=user_id)
|
||||
if event.publication_confirmed:
|
||||
status = 'info'
|
||||
message = (ugettext(u'Veröffentlichung wurde bereits von %(fullname)s bestätigt.') %
|
||||
{'fullname': event.publication_confirmed_by.get_full_name()})
|
||||
text = unicode(event)
|
||||
text += u'\n'
|
||||
text += (ugettext(u'Bestätigt am: %(date)s') %
|
||||
{'date': event.publication_confirmed_at.strftime('%d.%m.%Y %H:%M:%S')})
|
||||
else:
|
||||
event.confirm_publication(user)
|
||||
status = 'success'
|
||||
message = ugettext(u'Veröffentlichung registriert.')
|
||||
text = unicode(event)
|
||||
|
||||
self.done = True
|
||||
self.done_at = timezone.now()
|
||||
self.save()
|
||||
except Exception as e:
|
||||
status = 'danger'
|
||||
message = str(e)
|
||||
logger.error('OneClickAction.run(): %s(%s): %s', self.command, self.parameters, message)
|
||||
|
||||
result['context'] = {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'text': text,
|
||||
}
|
||||
elif self.command == 'EL':
|
||||
try:
|
||||
user_id = self.parameters
|
||||
user = get_user_model().objects.get(id=user_id)
|
||||
url = reverse('dav_events:event_list')
|
||||
result['location'] = url
|
||||
result['login'] = user
|
||||
except Exception as e:
|
||||
message = str(e)
|
||||
logger.error('OneClickAction.run(): %s(%s): %s', self.command, self.parameters, message)
|
||||
result['context'] = {
|
||||
'status': 'danger',
|
||||
'message': message,
|
||||
}
|
||||
else:
|
||||
result['context'] = {
|
||||
'status': 'danger',
|
||||
'message': ugettext(u'Invalid Command. Code on fire!'),
|
||||
}
|
||||
|
||||
return result
|
||||
s = u'{status} - {timestamp}'
|
||||
if self.user:
|
||||
s += u' by user {user}'
|
||||
return s.format(status=self.status, timestamp=self.timestamp.strftime('%d.%m.%Y %H:%M:%S'),
|
||||
user=self.user)
|
||||
60
dav_events/models/eventstatus.py
Normal file
60
dav_events/models/eventstatus.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..validators import LowerAlphanumericValidator
|
||||
|
||||
BOOTSTRAP_CONTEXT_CHOICES = (
|
||||
('default', 'default'),
|
||||
('primary', 'primary'),
|
||||
('success', 'success'),
|
||||
('info', 'info'),
|
||||
('warning', 'warning'),
|
||||
('danger', 'danger'),
|
||||
)
|
||||
DEFAULT_EVENT_STATI = {
|
||||
'void': (0, _(u'Ungültig'), None),
|
||||
'draft': (10, _(u'Entwurf'), 'info'),
|
||||
'submitted': (30, _(u'Eingereicht'), 'danger'),
|
||||
'accepted': (50, _(u'Freigegeben'), 'warning'),
|
||||
'publishing': (70, _(u'Veröffentlichung'), 'warning'),
|
||||
'published': (80, _(u'Veröffentlicht'), 'success'),
|
||||
'expired': (100, _(u'Ausgelaufen'), None),
|
||||
}
|
||||
|
||||
|
||||
def get_event_status(code):
|
||||
try:
|
||||
obj = EventStatus.objects.get(code=code)
|
||||
except EventStatus.DoesNotExist as e:
|
||||
if code not in DEFAULT_EVENT_STATI:
|
||||
raise e
|
||||
severity = DEFAULT_EVENT_STATI[code][0]
|
||||
label = DEFAULT_EVENT_STATI[code][1]
|
||||
obj = EventStatus(code=code, severity=severity, label=label)
|
||||
if DEFAULT_EVENT_STATI[code][2]:
|
||||
obj.bootstrap_context = DEFAULT_EVENT_STATI[code][2]
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
class EventStatus(models.Model):
|
||||
code = models.CharField(primary_key=True, max_length=254, validators=[LowerAlphanumericValidator])
|
||||
severity = models.IntegerField(unique=True)
|
||||
label = models.CharField(unique=True, max_length=254)
|
||||
bootstrap_context = models.CharField(max_length=20, blank=True, choices=BOOTSTRAP_CONTEXT_CHOICES)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _(u'Veranstaltungsstatus')
|
||||
verbose_name_plural = _(u'Veranstaltungsstati')
|
||||
ordering = ['severity']
|
||||
|
||||
def __unicode__(self):
|
||||
return u'{label} ({severity} {code})'.format(code=self.code,
|
||||
severity=self.severity,
|
||||
label=self.label)
|
||||
|
||||
def get_bootstrap_label(self):
|
||||
context = self.bootstrap_context or 'default'
|
||||
return u'<span class="label label-{context}">{label}</span>'.format(context=context,
|
||||
label=self.label)
|
||||
145
dav_events/models/oneclickaction.py
Normal file
145
dav_events/models/oneclickaction.py
Normal file
@@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import uuid
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from .event import Event
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OneClickAction(models.Model):
|
||||
COMMANDS = (
|
||||
('EA', 'accept event'),
|
||||
('EP', 'confirm publication of an event'),
|
||||
('EL', 'login and go to event list')
|
||||
)
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
allow_repeat = models.BooleanField(default=False)
|
||||
|
||||
done = models.BooleanField(default=False)
|
||||
done_at = models.DateTimeField(blank=True,
|
||||
null=True)
|
||||
|
||||
command = models.CharField(max_length=2, choices=COMMANDS)
|
||||
|
||||
parameters = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _(u'One-Click-Action')
|
||||
|
||||
def __unicode__(self):
|
||||
s = u'{command}({parameters}) - {description}'.format(description=self.get_command_display(),
|
||||
command=self.command,
|
||||
parameters=self.parameters)
|
||||
if self.done and not self.allow_repeat:
|
||||
s += u' - done'
|
||||
return s
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dav_events:action_run', kwargs={'pk': self.pk})
|
||||
|
||||
def run(self):
|
||||
result = {}
|
||||
if self.done and not self.allow_repeat:
|
||||
result['context'] = {
|
||||
'status': 'warning',
|
||||
'message': ugettext(u'Diese Aktion hast du bereits ausgeführt.'),
|
||||
}
|
||||
return result
|
||||
|
||||
logger.info('OneClickAction.run(): %s(%s)', self.command, self.parameters)
|
||||
if self.command == 'EA':
|
||||
text = u''
|
||||
try:
|
||||
event_id, user_id = self.parameters.split(',')
|
||||
event = Event.objects.get(id=event_id)
|
||||
user = get_user_model().objects.get(id=user_id)
|
||||
number = event.accept(user)
|
||||
if number:
|
||||
status = 'success'
|
||||
message = ugettext(u'Veranstaltung freigegeben.')
|
||||
text = unicode(event)
|
||||
else:
|
||||
status = 'info'
|
||||
message = (ugettext(u'Veranstaltung wurde bereits von %(fullname)s freigegeben.') %
|
||||
{'fullname': event.accepted_by.get_full_name()})
|
||||
text = unicode(event)
|
||||
text += u'\n'
|
||||
text += (ugettext(u'Freigegeben am: %(date)s') %
|
||||
{'date': event.accepted_at.strftime('%d.%m.%Y %H:%M:%S')})
|
||||
|
||||
self.done = True
|
||||
self.done_at = timezone.now()
|
||||
self.save()
|
||||
except Exception as e:
|
||||
status = 'danger'
|
||||
message = str(e)
|
||||
logger.error('OneClickAction.run(): %s(%s): %s', self.command, self.parameters, message)
|
||||
|
||||
result['context'] = {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'text': text,
|
||||
}
|
||||
elif self.command == 'EP':
|
||||
text = u''
|
||||
try:
|
||||
event_id, user_id = self.parameters.split(',')
|
||||
event = Event.objects.get(id=event_id)
|
||||
user = get_user_model().objects.get(id=user_id)
|
||||
if event.publication_confirmed:
|
||||
status = 'info'
|
||||
message = (ugettext(u'Veröffentlichung wurde bereits von %(fullname)s bestätigt.') %
|
||||
{'fullname': event.publication_confirmed_by.get_full_name()})
|
||||
text = unicode(event)
|
||||
text += u'\n'
|
||||
text += (ugettext(u'Bestätigt am: %(date)s') %
|
||||
{'date': event.publication_confirmed_at.strftime('%d.%m.%Y %H:%M:%S')})
|
||||
else:
|
||||
event.confirm_publication(user)
|
||||
status = 'success'
|
||||
message = ugettext(u'Veröffentlichung registriert.')
|
||||
text = unicode(event)
|
||||
|
||||
self.done = True
|
||||
self.done_at = timezone.now()
|
||||
self.save()
|
||||
except Exception as e:
|
||||
status = 'danger'
|
||||
message = str(e)
|
||||
logger.error('OneClickAction.run(): %s(%s): %s', self.command, self.parameters, message)
|
||||
|
||||
result['context'] = {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'text': text,
|
||||
}
|
||||
elif self.command == 'EL':
|
||||
try:
|
||||
user_id = self.parameters
|
||||
user = get_user_model().objects.get(id=user_id)
|
||||
url = reverse('dav_events:event_list')
|
||||
result['location'] = url
|
||||
result['login'] = user
|
||||
except Exception as e:
|
||||
message = str(e)
|
||||
logger.error('OneClickAction.run(): %s(%s): %s', self.command, self.parameters, message)
|
||||
result['context'] = {
|
||||
'status': 'danger',
|
||||
'message': message,
|
||||
}
|
||||
else:
|
||||
result['context'] = {
|
||||
'status': 'danger',
|
||||
'message': ugettext(u'Invalid Command. Code on fire!'),
|
||||
}
|
||||
|
||||
return result
|
||||
61
dav_events/signals.py
Normal file
61
dav_events/signals.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from django.apps import apps
|
||||
from django.dispatch import Signal
|
||||
|
||||
from . import emails
|
||||
|
||||
event_submitted = Signal(providing_args=['event'])
|
||||
event_accepted = Signal(providing_args=['event'])
|
||||
event_publishing = Signal(providing_args=['event'])
|
||||
event_published = Signal(providing_args=['event'])
|
||||
event_expired = Signal(providing_args=['event'])
|
||||
|
||||
|
||||
def notify_submitted_event(sender, **kwargs):
|
||||
event = kwargs.get('event')
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
if app_config.settings.enable_email_notifications:
|
||||
if event.owner.email:
|
||||
email = emails.NewEventMail(recipient=event.owner, event=event)
|
||||
email.send()
|
||||
|
||||
|
||||
def notify_accepted_event(sender, **kwargs):
|
||||
event = kwargs.get('event')
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
if app_config.settings.enable_email_notifications:
|
||||
if event.owner.email:
|
||||
email = emails.EventAcceptedMail(recipient=event.owner, event=event)
|
||||
email.send()
|
||||
|
||||
|
||||
def notify_to_accept_event(sender, **kwargs):
|
||||
event = kwargs.get('event')
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
if app_config.settings.enable_email_notifications:
|
||||
from .utils import get_users_by_role
|
||||
managers = get_users_by_role('manage_all')
|
||||
managers += get_users_by_role('manage_{}'.format(event.sport.lower()))
|
||||
OneClickAction = app_config.get_model('OneClickAction')
|
||||
for user in managers:
|
||||
if user.email:
|
||||
action = OneClickAction(command='EA')
|
||||
action.parameters = '{event},{user}'.format(event=event.id, user=user.id)
|
||||
action.save()
|
||||
email = emails.EventToAcceptMail(recipient=user, event=event, accept_action=action)
|
||||
email.send()
|
||||
|
||||
|
||||
def notify_to_publish_event(sender, **kwargs):
|
||||
event = kwargs.get('event')
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
if app_config.settings.enable_email_notifications:
|
||||
from .utils import get_users_by_role
|
||||
publishers = get_users_by_role('publish_incremental')
|
||||
OneClickAction = app_config.get_model('OneClickAction')
|
||||
for user in publishers:
|
||||
if user.email:
|
||||
action = OneClickAction(command='EP')
|
||||
action.parameters = '{event},{user}'.format(event=event.id, user=user.id)
|
||||
action.save()
|
||||
email = emails.EventToPublishMail(recipient=user, event=event, confirm_publication_action=action)
|
||||
email.send()
|
||||
@@ -63,6 +63,11 @@ thead input {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
#page-body h5 {
|
||||
margin-top: 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#page-footer {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
|
||||
@@ -3,16 +3,11 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="pull-right">
|
||||
{% if status == 'expired' %}
|
||||
<span class="label label-info">{% trans 'Ausgelaufen' %}</span>
|
||||
{% elif status == 'published' %}
|
||||
<span class="label label-success">{% trans 'Veröffentlicht' %}</span>
|
||||
{% elif status == 'publishing' %}
|
||||
<span class="label label-warning">{% trans 'Veröffentlichung' %}: {{ event.planned_publication_date|date:'d.m.Y' }}</span>
|
||||
{% elif status == 'accepted' %}
|
||||
<span class="label label-warning">{% trans 'Freigegeben' %}</span>
|
||||
{% elif status == 'submitted' %}
|
||||
<span class="label label-danger">{% trans 'Eingereicht' %}</span>
|
||||
{% if status.code != 'void' %}
|
||||
{{ status.get_bootstrap_label|safe }}
|
||||
{% if status.code == 'publishing' %}
|
||||
<span class="label label-{{ status.bootstrap_context|default:'default' }}">{{ planned_publication_date|date:'d.m.Y' }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="panel-title">{{ number }} - {{ title }}</span>
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
{% endblock form-fields-visible %}
|
||||
{% block form-buttons %}
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-success">
|
||||
<button type="submit" name="submit" class="btn btn-success">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Einsenden' %}
|
||||
{% trans 'Speichern und Einsenden' %}
|
||||
</button>
|
||||
<a class="btn btn-warning" href="?back">
|
||||
{% bootstrap_icon 'repeat' %} 
|
||||
@@ -40,5 +40,9 @@
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</a>
|
||||
<button type="submit" name="save" class="btn btn-info">
|
||||
{% bootstrap_icon 'hdd' %} 
|
||||
{% trans 'Als Entwurf speichern' %}
|
||||
</button>
|
||||
{% endbuttons %}
|
||||
{% endblock form-buttons %}
|
||||
|
||||
@@ -5,6 +5,36 @@
|
||||
{% block head-title %}{{ event }} - {{ block.super }}{% endblock head-title %}
|
||||
|
||||
{% block modals %}
|
||||
<div id="modal-submit-dialog" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{% trans 'Veranstaltung einreichen?' %}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-center">
|
||||
<strong>{{ event }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
Die Veranstaltungsdaten werden an die Tourenreferenten und zuständigen Fachbereichsleiter
|
||||
zur Freigabe weitergeleitet.<br />
|
||||
Nach dem Einreichen können die Veranstaltungsdaten nicht mehr von dir geändert werden.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:event_confirmstatus' event.pk 'submitted' %}">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Ja, alles klar!' %}
|
||||
</a>
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-accept-dialog" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
@@ -16,11 +46,14 @@
|
||||
<p class="text-center">
|
||||
<strong>{{ event }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
Die Veranstaltungsdaten werden an die Redaktion zur Veröffentlichung weitergeleitet.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:event_accept' event.pk %}">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:event_confirmstatus' event.pk 'accepted' %}">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Ja, mach schon!' %}
|
||||
{% trans 'Ja, passt schon!' %}
|
||||
</a>
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
@@ -41,9 +74,17 @@
|
||||
<p class="text-center">
|
||||
<strong>{{ event }}</strong>
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<strong>{% trans 'Zeitpunkt der Veröffentlichung' %}:</strong>
|
||||
{% if event.planned_publication_date %}
|
||||
{{ event.planned_publication_date|date:'l, d. F Y' }}
|
||||
{% else %}
|
||||
{% trans 'Unverzüglich' %}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:event_confirmpublication' event.pk %}">
|
||||
<a class="btn btn-success" href="{% if event.planned_publication_date %}{% url 'dav_events:event_confirmstatus' event.pk 'publishing' %}{% else %}{% url 'dav_events:event_confirmstatus' event.pk 'published' %}{% endif %}">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Ja' %}
|
||||
</a>
|
||||
@@ -60,10 +101,21 @@
|
||||
{% block page-container-fluid %}
|
||||
<div class="action-tabs">
|
||||
<div class="pull-right">
|
||||
{% if has_permission_submit %}
|
||||
<a class="btn {% if event.get_status.code == 'draft' %}btn-success{% else %}btn-default disabled{% endif %}"
|
||||
data-toggle="modal" data-target="#modal-submit-dialog">
|
||||
{% if 'submitted' in event.get_status_codes %}
|
||||
{% bootstrap_icon 'check' %} 
|
||||
{% else %}
|
||||
{% bootstrap_icon 'unchecked' %} 
|
||||
{% endif %}
|
||||
{% trans 'Einreichen' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if has_permission_accept %}
|
||||
<a class="btn {% if event.get_status == 'submitted' %}btn-success{% else %}btn-default disabled{% endif %}"
|
||||
<a class="btn {% if event.get_status.code == 'submitted' %}btn-success{% else %}btn-default disabled{% endif %}"
|
||||
data-toggle="modal" data-target="#modal-accept-dialog">
|
||||
{% if event.accepted %}
|
||||
{% if 'accepted' in event.get_status_codes %}
|
||||
{% bootstrap_icon 'check' %} 
|
||||
{% else %}
|
||||
{% bootstrap_icon 'unchecked' %} 
|
||||
@@ -72,9 +124,9 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if has_permission_publish %}
|
||||
<a class="btn {% if event.get_status == 'accepted' %}btn-success{% else %}btn-default disabled{% endif %}"
|
||||
<a class="btn {% if event.get_status.code == 'accepted' %}btn-success{% else %}btn-default disabled{% endif %}"
|
||||
data-toggle="modal" data-target="#modal-confirmpublication-dialog">
|
||||
{% if event.publication_confirmed %}
|
||||
{% if 'publishing' in event.get_status_codes or 'published' in event.get_status_codes %}
|
||||
{% bootstrap_icon 'check' %} 
|
||||
{% else %}
|
||||
{% bootstrap_icon 'unchecked' %} 
|
||||
@@ -112,55 +164,22 @@
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
<h5>Status-Log</h5>
|
||||
{% for flag in event.flags.all %}
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<span class="text-danger">{% bootstrap_icon 'check' %}</span>
|
||||
 <strong>{% trans 'Eingereicht' %}:</strong>
|
||||
<div class="col-sm-5">
|
||||
<span class="text-{{ flag.status.bootstrap_context|default:'default' }}">{% bootstrap_icon 'check' %}</span>
|
||||
<strong>{{ flag.status.label }}:</strong>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
{{ event.created_at|date:'l, d. F Y, H:i' }} {% trans 'Uhr' %}<br />
|
||||
{% trans 'von' %} {{ event.owner.get_full_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
{% if event.accepted %}
|
||||
<span class="text-warning">{% bootstrap_icon 'check' %}</span>
|
||||
{% else %}
|
||||
{% bootstrap_icon 'unchecked' %}
|
||||
{% endif %}
|
||||
 <strong>{% trans 'Freigegeben' %}:</strong>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
{% if event.accepted %}
|
||||
{{ event.accepted_at|date:'l, d. F Y, H:i' }} {% trans 'Uhr' %}<br />
|
||||
{% trans 'von' %} {{ event.accepted_by.get_full_name }}
|
||||
{% else %}
|
||||
<br /><br />
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
{% if event.publication_confirmed %}
|
||||
<span class="text-success">{% bootstrap_icon 'check' %}</span>
|
||||
{% else %}
|
||||
{% bootstrap_icon 'unchecked' %}
|
||||
{% endif %}
|
||||
 <strong>{% trans 'Veröffentlicht' %}:</strong>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
{% if event.publication_confirmed %}
|
||||
{{ event.publication_confirmed_at|date:'l, d. F Y, H:i' }} {% trans 'Uhr' %}<br />
|
||||
{% trans 'von' %} {{ event.publication_confirmed_by.get_full_name }}
|
||||
{% else %}
|
||||
<br /><br />
|
||||
{% endif %}
|
||||
<div class="col-sm-7">
|
||||
{{ flag.timestamp|date:'l, d. F Y, H:i' }} {% trans 'Uhr' %}<br />
|
||||
{% trans 'von' %} {{ flag.user.get_full_name|default:flag.user }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans 'Veröffentlichen' %}</strong><br />
|
||||
<h5>{% trans 'Veröffentlichen' %}</h5>
|
||||
{% if event.planned_publication_date %}
|
||||
{{ event.planned_publication_date|date:'l, d. F Y' }}
|
||||
{% else %}
|
||||
@@ -169,7 +188,7 @@
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% if event.internal_note %}
|
||||
<strong>{% trans 'Bearbeitungshinweis' %}</strong>
|
||||
<h5>{% trans 'Bearbeitungshinweis' %}</h5>
|
||||
<div class="well well-sm"><small>{{ event.internal_note|linebreaksbr }}</small></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -64,18 +64,9 @@
|
||||
{{ event.get_numeric_date }}
|
||||
</td>
|
||||
<td>
|
||||
{% if event.get_status == 'expired' %}
|
||||
<span class="label label-info">{% trans 'Ausgelaufen' %}</span>
|
||||
{% elif event.get_status == 'published' %}
|
||||
<span class="label label-success">{% trans 'Veröffentlicht' %}</span>
|
||||
{% elif event.get_status == 'publishing' %}
|
||||
<span class="label label-warning">{% trans 'Veröffentlichung' %}: {{ event.planned_publication_date|date:'d.m.Y' }}</span>
|
||||
{% elif event.get_status == 'accepted' %}
|
||||
<span class="label label-warning">{% trans 'Freigegeben' %}</span>
|
||||
{% elif event.get_status == 'submitted' %}
|
||||
<span class="label label-danger">{% trans 'Eingereicht' %}</span>
|
||||
{% else %}
|
||||
<span class="label label-default">{% trans 'Kaputt' %} ({{ event.get_status }})</span>
|
||||
{{ event.get_status.get_bootstrap_label|safe }}
|
||||
{% if event.get_status.code == 'publishing' %}
|
||||
<span class="label label-{{ event.get_status.bootstrap_context|default:'default' }}">{{ event.planned_publication_date|date:'d.m.Y' }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -21,18 +21,18 @@
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if event.get_status == 'expired' %}
|
||||
{% if 'expired' in event.get_status_codes %}
|
||||
<div class="alert alert-info alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
{% trans 'Diese Veranstaltung ist bereits ausgelaufen.' %}
|
||||
</div>
|
||||
{% elif event.get_status == 'published' or event.get_status == 'publishing' %}
|
||||
{% elif 'publishing' in event.get_status_codes or 'published' in event.get_status_codes %}
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<strong>{% trans 'Achtung!' %}</strong> {% trans 'Diese Veranstaltung wird/wurde bereits veröffentlicht.' %}
|
||||
{% trans 'Änderungen müssen mit allen Beteiligten abgesprochen werden.' %}
|
||||
</div>
|
||||
{% elif event.get_status == 'accepted' %}
|
||||
{% elif 'accepted' in event.get_status_codes %}
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<strong>{% trans 'Achtung!' %}</strong> {% trans 'Diese Veranstaltung wurde bereits freigegeben.' %}
|
||||
|
||||
@@ -11,9 +11,8 @@ urlpatterns = [
|
||||
url(r'^events$', views.events.EventListView.as_view(), name='event_list'),
|
||||
url(r'^events/export$', views.events.EventListExportView.as_view(), name='event_list_export'),
|
||||
url(r'^events/create$', views.events.EventCreateView.as_view(), name='event_create'),
|
||||
url(r'^events/(?P<pk>\d+)/confirmpublication',
|
||||
views.events.EventConfirmPublicationView.as_view(), name='event_confirmpublication'),
|
||||
url(r'^events/(?P<pk>\d+)/accept', views.events.EventAcceptView.as_view(), name='event_accept'),
|
||||
url(r'^events/(?P<pk>\d+)/confirm/(?P<status>[a-z0-9][a-z0-9]*)',
|
||||
views.events.EventConfirmStatusView.as_view(), name='event_confirmstatus'),
|
||||
url(r'^events/(?P<pk>\d+)/edit', views.events.EventUpdateView.as_view(), name='event_update'),
|
||||
url(r'^events/(?P<pk>\d+)/', views.events.EventDetailView.as_view(), name='event_detail'),
|
||||
url(r'^action/(?P<pk>[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12})/',
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import logging
|
||||
from django.apps import apps
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
app_config = apps.get_containing_app_config(__package__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_system_user():
|
||||
return get_user_model().objects.get_or_create(username='-system-')[0]
|
||||
|
||||
|
||||
def get_ghost_user():
|
||||
return get_user_model().objects.get_or_create(username='-deleted-')[0]
|
||||
|
||||
|
||||
def get_group_members(group_name, ignore_missing=False):
|
||||
users = []
|
||||
try:
|
||||
|
||||
5
dav_events/validators.py
Normal file
5
dav_events/validators.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.core.validators import RegexValidator
|
||||
|
||||
|
||||
AlphanumericValidator = RegexValidator(r'^[0-9a-zA-Z]*$', 'Only characters A-Z, a-z and digits 0-9 are allowed.')
|
||||
LowerAlphanumericValidator = RegexValidator(r'^[0-9a-z]*$', 'Only characters a-z and digits 0-9 are allowed.')
|
||||
@@ -45,7 +45,7 @@ class EventListView(generic.ListView):
|
||||
filter |= Q(sport__in=user_sports_list)
|
||||
|
||||
if has_role(user, 'publish') or has_role(user, 'publish_incremental'):
|
||||
filter |= Q(accepted=True)
|
||||
filter |= Q(flags__status__code='accepted')
|
||||
|
||||
qs = self.model.objects.filter(filter)
|
||||
|
||||
@@ -89,7 +89,7 @@ class EventListExportView(generic.FormView):
|
||||
txt = u''
|
||||
event_qs = models.Event.objects.filter(**filter_kwargs).order_by('sport', 'first_day')
|
||||
for event in event_qs:
|
||||
if exclude_expired and event.get_status() == 'expired':
|
||||
if exclude_expired and event.is_flagged('expired'):
|
||||
continue
|
||||
txt += event.render_as_text(format='ka-alpin')
|
||||
if event.internal_note:
|
||||
@@ -120,30 +120,38 @@ class EventPermissionMixin(object):
|
||||
return True
|
||||
|
||||
if permission == 'view':
|
||||
if obj.owner == user:
|
||||
if user == obj.owner:
|
||||
return True
|
||||
if has_role(user, 'manage_all'):
|
||||
return True
|
||||
if has_role(user, 'manage_{}'.format(obj.sport.lower())):
|
||||
return True
|
||||
if obj.accepted and (has_role(user, 'publish') or has_role(user, 'publish_incremental')):
|
||||
if has_role(user, 'publish_incremental') and obj.is_flagged('accepted'):
|
||||
return True
|
||||
if has_role(user, 'publish') and obj.is_flagged('accepted'):
|
||||
return True
|
||||
elif permission == 'submit':
|
||||
if user == obj.owner:
|
||||
return True
|
||||
elif permission == 'accept':
|
||||
if has_role(user, 'manage_all'):
|
||||
return True
|
||||
if has_role(user, 'manage_{}'.format(obj.sport.lower())):
|
||||
return True
|
||||
elif permission == 'publish':
|
||||
if has_role(user, 'publish') or has_role(user, 'publish_incremental'):
|
||||
return True
|
||||
elif permission == 'update':
|
||||
if not obj.accepted and not obj.publication_confirmed:
|
||||
if not obj.is_flagged('submitted'):
|
||||
if user == obj.owner:
|
||||
return True
|
||||
elif not obj.is_flagged('accepted'):
|
||||
if has_role(user, 'manage_all'):
|
||||
return True
|
||||
if has_role(user, 'manage_{}'.format(obj.sport.lower())):
|
||||
return True
|
||||
elif has_role(user, 'publish') or has_role(user, 'publish_incremental'):
|
||||
return True
|
||||
elif permission == 'publish':
|
||||
if has_role(user, 'publish') or has_role(user, 'publish_incremental'):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -164,9 +172,10 @@ class EventDetailView(EventPermissionMixin, generic.DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventDetailView, self).get_context_data(**kwargs)
|
||||
obj = context.get('event')
|
||||
context['has_permission_submit'] = self.has_permission('submit', obj)
|
||||
context['has_permission_accept'] = self.has_permission('accept', obj)
|
||||
context['has_permission_update'] = self.has_permission('update', obj)
|
||||
context['has_permission_publish'] = self.has_permission('publish', obj)
|
||||
context['has_permission_update'] = self.has_permission('update', obj)
|
||||
return context
|
||||
|
||||
@method_decorator(login_required)
|
||||
@@ -174,26 +183,43 @@ class EventDetailView(EventPermissionMixin, generic.DetailView):
|
||||
return super(EventDetailView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class EventAcceptView(EventDetailView):
|
||||
permission = 'accept'
|
||||
class EventConfirmStatusView(EventPermissionMixin, generic.DetailView):
|
||||
model = models.Event
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
status = kwargs.get('status')
|
||||
event = self.get_object()
|
||||
event.accept(request.user)
|
||||
messages.success(request, _(u'Veranstaltung freigegeben.'))
|
||||
return HttpResponseRedirect(event.get_absolute_url())
|
||||
|
||||
if status == 'submitted':
|
||||
if not self.has_permission('submit', event):
|
||||
raise PermissionDenied(status)
|
||||
elif status == 'accepted':
|
||||
if not self.has_permission('accept', event):
|
||||
raise PermissionDenied(status)
|
||||
if not event.is_flagged('submitted'):
|
||||
messages.error(request, _(u'Veranstaltung ist noch nicht eingereicht.'))
|
||||
return HttpResponseRedirect(event.get_absolute_url())
|
||||
elif status == 'publishing' or status == 'published':
|
||||
if not self.has_permission('publish', event):
|
||||
raise PermissionDenied(status)
|
||||
if not event.is_flagged('accepted'):
|
||||
messages.error(request, _(u'Veranstaltung ist noch nicht freigegeben.'))
|
||||
return HttpResponseRedirect(event.get_absolute_url())
|
||||
else:
|
||||
if not self.has_permission('update', event):
|
||||
raise PermissionDenied(status)
|
||||
|
||||
class EventConfirmPublicationView(EventDetailView):
|
||||
permission = 'publish'
|
||||
event.confirm_status(status, request.user)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
event = self.get_object()
|
||||
if event.accepted:
|
||||
event.confirm_publication(request.user)
|
||||
if status == 'submitted':
|
||||
messages.success(request, _(u'Veranstaltung eingereicht.'))
|
||||
elif status == 'accepted':
|
||||
messages.success(request, _(u'Veranstaltung freigegeben.'))
|
||||
elif status == 'publishing' or status == 'published':
|
||||
messages.success(request, _(u'Veröffentlichung registriert.'))
|
||||
else:
|
||||
messages.error(request, _(u'Veranstaltung ist noch nicht freigegeben.'))
|
||||
messages.success(request, _(u'Veranstaltungsstatus registriert.'))
|
||||
|
||||
return HttpResponseRedirect(event.get_absolute_url())
|
||||
|
||||
|
||||
@@ -273,8 +299,12 @@ class EventCreateView(EventPermissionMixin, generic.FormView):
|
||||
return self.render_to_response(self.get_context_data(form=next_form, event=event))
|
||||
else:
|
||||
event.save()
|
||||
if 'submit' in form.data:
|
||||
event.confirm_status('submitted', event.owner)
|
||||
messages.success(self.request, _(u'Veranstaltung eingereicht.'))
|
||||
else:
|
||||
messages.success(self.request, _(u'Veranstaltung angelegt.'))
|
||||
form.flush_session_data()
|
||||
messages.success(self.request, _(u'Veranstaltung angelegt.'))
|
||||
owner = event.owner
|
||||
self.clean_session_data()
|
||||
if self.request.user.is_authenticated:
|
||||
|
||||
Reference in New Issue
Block a user