UPD: more registration staff on dav_events.

This commit is contained in:
2019-06-03 17:10:52 +02:00
parent 2dbab0032f
commit 9104d69dd7
11 changed files with 411 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import EventStatus, EventFlag, Event, OneClickAction
from .models import EventStatus, EventFlag, Event, OneClickAction, Participant
@admin.register(EventStatus)
@@ -13,11 +13,20 @@ class EventFlagInline(admin.TabularInline):
extra = 1
class EventParticipantInline(admin.TabularInline):
model = Participant
extra = 1
@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
inlines = [EventFlagInline]
inlines = [EventFlagInline, EventParticipantInline]
@admin.register(OneClickAction)
class OneClickActionAdmin(admin.ModelAdmin):
pass
@admin.register(Participant)
class ParticipantAdmin(admin.ModelAdmin):
pass

View File

@@ -1,2 +1,3 @@
from . import generic
from . import events
from . import participant

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from django import forms
from ..models import Participant
class ParticipantForm(forms.ModelForm):
class Meta:
model = Participant
exclude = ['event', 'created_at', 'position', 'purge_at']
widgets = {
'emergency_contact': forms.Textarea(attrs={'rows': 4}),
'experience': forms.Textarea(attrs={'rows': 5}),
'note': forms.Textarea(attrs={'rows': 5}),
}

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-06-03 10:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dav_events', '0029_event_registration_closed'),
]
operations = [
migrations.CreateModel(
name='Participant',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('position', models.IntegerField(verbose_name='Listennummer')),
('personal_names', models.CharField(max_length=1024, verbose_name='Vorname(n)')),
('family_names', models.CharField(max_length=1024, verbose_name='Familienname')),
('address', models.CharField(help_text='Straße, Hausnummer', max_length=1024, verbose_name='Anschrift')),
('postal_code', models.CharField(max_length=254, verbose_name='Postleitzahl')),
('city', models.CharField(max_length=1024, verbose_name='Ort')),
('email_address', models.EmailField(max_length=254, verbose_name='E-Mail-Adresse')),
('phone_number', models.CharField(max_length=254, verbose_name='Telefonnummer')),
('dav_number', models.CharField(max_length=62, verbose_name='DAV Mitgliednummer')),
('emergency_contact', models.TextField(blank=True, verbose_name='Notfall-Kontakt')),
('experience', models.TextField(blank=True, verbose_name='Erfahrung')),
('note', models.TextField(blank=True, verbose_name='Anmerkung')),
('paid', models.BooleanField(default=False, verbose_name='Teilnehmerbeitrag bezahlt')),
('purge_at', models.DateTimeField()),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='dav_events.Event')),
],
options={
'verbose_name': 'Teilnehmer',
'verbose_name_plural': 'Teilnehmer',
'ordering': ['event', 'position'],
},
),
migrations.AlterUniqueTogether(
name='participant',
unique_together=set([('event', 'position')]),
),
]

View File

@@ -2,3 +2,4 @@ from .event import Event
from .eventflag import EventFlag
from .eventstatus import EventStatus
from .oneclickaction import OneClickAction
from .participant import Participant

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
midnight = datetime.time(00, 00, 00)
one_day = datetime.timedelta(1)
@python_2_unicode_compatible
class Participant(models.Model):
event = models.ForeignKey('Event', related_name='participants')
created_at = models.DateTimeField(auto_now_add=True)
position = models.IntegerField(verbose_name='Listennummer')
personal_names = models.CharField(max_length=1024,
verbose_name=_('Vorname(n)'))
family_names = models.CharField(max_length=1024,
verbose_name=_('Familienname'))
address = models.CharField(max_length=1024,
verbose_name=_('Anschrift'),
help_text=_('Straße, Hausnummer'))
postal_code = models.CharField(max_length=254,
verbose_name=_('Postleitzahl'))
city = models.CharField(max_length=1024,
verbose_name=_('Ort'))
email_address = models.EmailField(verbose_name=_('E-Mail-Adresse'))
phone_number = models.CharField(max_length=254,
verbose_name=_('Telefonnummer'))
dav_number = models.CharField(max_length=62,
#validators=[DAVNumberValidator],
verbose_name=_('DAV Mitgliednummer'))
emergency_contact = models.TextField(blank=True,
verbose_name=_('Notfall-Kontakt'))
experience = models.TextField(blank=True,
verbose_name=_('Erfahrung'))
note = models.TextField(blank=True,
verbose_name=_('Anmerkung'))
paid = models.BooleanField('Teilnehmerbeitrag bezahlt', default=False)
purge_at = models.DateTimeField()
class Meta:
unique_together = (('event', 'position'), )
verbose_name = _('Teilnehmer')
verbose_name_plural = _('Teilnehmer')
ordering = ['event', 'position']
def __str__(self):
return '{position}. {name}'.format(
position=self.position,
name=self.get_full_name(),
)
def get_full_name(self):
return '{} {}'.format(self.personal_names, self.family_names)
def save(self, **kwargs):
if not self.purge_at and self.event:
self.purge_at = self.__class__.calc_purge_at(self.event)
super(Participant, self).save(**kwargs)
@staticmethod
def calc_purge_at(event):
if event.alt_last_day:
last_day = event.alt_last_day
elif event.last_day:
last_day = event.last_day
elif event.alt_first_day:
last_day = event.alt_first_day
else:
last_day = event.first_day
return timezone.make_aware(datetime.datetime.combine(last_day + one_day * 7, midnight))

View File

@@ -220,22 +220,117 @@
</div>
<hr />
<h4>Teilnehmer (Designstudie - Das funktioniert alles noch nicht!)</h4>
<div class="panel panel-default">
<div id="headingRegistrations" class="panel-heading" role="tab">
<h5 class="panel-title">
<a role="button" href="#collapseRegistrations" data-toggle="collapse" data-parent="#accordion"
aria-expanded="true" aria-controls="collapseRegistrations">
<span class="caret"></span>&nbsp;&nbsp;Anmeldungen
</a>
</h5>
</div>
<div id="collapseRegistrations" class="panel-collapse collapse"
role="tabpanel" aria-labelledby="headingRegistrations">
<div class="panel-body">
{% for registration in registrations %}
<form action="" method="post" class="form-inline">
{% csrf_token %}
<input type="hidden" name="registration" value="{{ registration.id }}">
{% if registration.answered %}
<span class="text-muted disabled">
{% endif %}
<button type="submit" name="action" value="accept_registration"
class="btn btn-link" title="Teilnehmer hinzufügen">
<span class="text-success">{% bootstrap_icon 'plus-sign' %}</span>
</button>
<button type="submit" name="action" value="reject_registration"
class="btn btn-link" title="Anmeldung löschen">
<span class="text-danger">{% bootstrap_icon 'minus-sign' %}</span>
</button>
{{ registration.get_full_name }}
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
{{ registration.phone_number }})
&nbsp;
<span title="Anmeldezeitpunkt">
{% bootstrap_icon 'time' %}
{{ registration.created_at|date:'d. F Y, G:i' }}
</span>
&nbsp;
<span title="{{ registration.get_info }}">
{% bootstrap_icon 'info-sign' %}
</span>
{% if registration.answered %}
</span>
{% endif %}
</form>
{% empty %}
Keine unbestätigten Anmeldungen vorhanden
{% endfor %}
</div>
</div>
</div>
<div class="panel panel-default">
<div id="headingAddParticipant" class="panel-heading" role="tab">
<h5 class="panel-title">
<a role="button" href="#collapseAddParticipant" data-toggle="collapse" data-parent="#accordion"
aria-expanded="true" aria-controls="collapseAddParticipant">
Teilnehmer hinzufügen
<span class="caret"></span>&nbsp;&nbsp;weiteren Teilnehmer eintragen
</a>
</h5>
</div>
<div id="collapseAddParticipant" class="panel-collapse collapse in"
<div id="collapseAddParticipant" class="panel-collapse collapse {% if create_participant_form.errors %}in{% endif %}"
role="tabpanel" aria-labelledby="headingAddParticipant">
<div class="panel-body">
Formular
</div>
<div class="panel-footer">
<button class="btn btn-success">Speichern</button>
{% bootstrap_form_errors create_participant_form %}
<form action="" method="post">
{% csrf_token %}
<div class="row">
<div class="col-sm-6">
{% bootstrap_field create_participant_form.personal_names %}
</div>
<div class="col-sm-6">
{% bootstrap_field create_participant_form.family_names %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
{% bootstrap_field create_participant_form.address %}
</div>
</div>
<div class="row">
<div class="col-sm-4">
{% bootstrap_field create_participant_form.postal_code %}
</div>
<div class="col-sm-8">
{% bootstrap_field create_participant_form.city %}
</div>
</div>
<div class="row">
<div class="col-sm-6">
{% bootstrap_field create_participant_form.email_address %}
</div>
<div class="col-sm-6">
{% bootstrap_field create_participant_form.phone_number %}
</div>
</div>
<div class="row">
<div class="col-sm-6">
{% bootstrap_field create_participant_form.dav_number %}
</div>
<div class="col-sm-6">
{% bootstrap_field create_participant_form.emergency_contact %}
</div>
</div>
<div class="row">
<div class="col-sm-6">
{% bootstrap_field create_participant_form.experience %}
</div>
<div class="col-sm-6">
{% bootstrap_field create_participant_form.note %}
</div>
</div>
<button class="btn btn-success">{% bootstrap_icon 'plus-sign' %} {% trans 'Teilnehmer hinzufügen' %}</button>
</form>
</div>
</div>
</div>
@@ -245,7 +340,7 @@
<h5 class="panel-title">
<a role="button" href="#collapseParticipant_{{ participant.id }}" data-toggle="collapse" data-parent="#accordion"
aria-expanded="true" aria-controls="collapseParticipant_{{ participant.id }}">
{{ participant.position }}. {{ participant.get_full_name }}
<span class="caret"></span>&nbsp;&nbsp;{{ participant.position }}. {{ participant.get_full_name }}
</a>
</h5>
</div>

View File

@@ -5,7 +5,7 @@ import os
from django.contrib import messages
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.exceptions import PermissionDenied, SuspiciousOperation, FieldDoesNotExist
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
@@ -169,58 +169,130 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
self.enforce_permission(obj)
return obj
def get_form_kwargs(self):
kwargs = {}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_create_participant_form(self):
event = self.get_object()
form = forms.participant.ParticipantForm(**self.get_form_kwargs())
form.instance.event = event
form.instance.position = event.participants.count() + 1
return form
def get_context_data(self, **kwargs):
context = super(EventRegistrationsView, self).get_context_data(**kwargs)
obj = context.get('event')
context['has_permission_update'] = self.has_permission('update', obj)
context['is_published_any'] = obj.workflow.has_reached_status('published*')
context['is_done'] = obj.workflow.has_reached_status('expired')
event = context.get('event')
context['has_permission_update'] = self.has_permission('update', event)
context['is_published_any'] = event.workflow.has_reached_status('published*')
context['is_done'] = event.workflow.has_reached_status('expired')
class MockParticipant(object):
def __init__(self, id, name):
self.id = id
self.name = name
@property
def position(self):
return self.id
def get_full_name(self):
return self.name
participants = [MockParticipant(1, 'Erika Musterfrau'), MockParticipant(2, 'Max Mustermann')]
participants = event.participants.all()
context['participants'] = participants
if hasattr(event, 'registrations'):
# registrations = event.registrations.filter(answered=False)
registrations = event.registrations.all()
context['registrations'] = registrations
if 'create_participant_form' not in context:
context['create_participant_form'] = self.get_create_participant_form()
return context
def _send_mails(self, event, request):
editor = request.user
def _notify_publisher(self, event, editor):
recipients = get_users_by_role('publisher')
for recipient in recipients:
if recipient.email:
email = emails.EventRegistrationClosedMail(recipient=recipient, event=event, editor=editor)
email.send()
def _close_registration(self, request, event):
logger.info('Close registration: %s', event)
event.registration_closed = True
event.save(implicit_update=True)
self._notify_publisher(event, request.user)
messages.success(request, _(u'Die Anmeldung wurde geschlossen'))
def _reopen_registration(self, request, event):
logger.info('Reopen registration: %s', event)
event.registration_closed = False
event.save(implicit_update=True)
messages.success(request, _(u'Die Anmeldung wurde geöffnet'))
def _kill_deadline(self, request, event):
logger.info('Delete deadline: %s', event)
event.deadline = None
event.registration_closed = False
event.save(implicit_update=True)
messages.success(request, _(u'Der Anmeldeschluss wurde gelöscht'))
def _accept_registration(self, request, registration):
event = registration.event
position = event.participants.count() + 1
data = {
'event': event,
'position': position,
'personal_names': registration.personal_names,
'family_names': registration.family_names,
'address': registration.address,
'postal_code': registration.postal_code,
'city': registration.city,
'email_address': registration.email_address,
'phone_number': registration.phone_number,
'dav_number': registration.dav_number,
'emergency_contact': registration.emergency_contact,
'experience': registration.experience,
'note': registration.note,
'purge_at': registration.purge_at,
}
participant = models.Participant.objects.create(**data)
registration.answered = True
registration.save()
messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name())))
def _reject_registration(self, registration):
registration.answered = True
registration.save()
def post(self, request, *args, **kwargs):
event = self.get_object()
self.object = event
action = request.POST.get('action')
if action == 'close-registration':
logger.info('Close registration: %s', event)
event.registration_closed = True
event.save(implicit_update=True)
self._send_mails(event, request)
messages.success(request, _(u'Die Anmeldung wurde geschlossen'))
self._close_registration(request, event)
elif action == 'open-registration':
logger.info('Reopen registration: %s', event)
event.registration_closed = False
event.save(implicit_update=True)
messages.success(request, _(u'Die Anmeldung wurde geöffnet'))
self._reopen_registration(request, event)
elif action == 'kill-deadline':
logger.info('Delete deadline: %s', event)
event.deadline = None
event.registration_closed = False
event.save(implicit_update=True)
messages.success(request, _(u'Der Anmeldeschluss wurde gelöscht'))
self._kill_deadline(request, event)
elif action == 'accept_registration':
if hasattr(event, 'registrations'):
registration_id = request.POST.get('registration')
registration = event.registrations.get(id=registration_id)
self._accept_registration(request, registration)
else:
raise FieldDoesNotExist('Event has no registrations')
elif action == 'reject_registration':
if hasattr(event, 'registrations'):
registration_id = request.POST.get('registration')
registration = event.registrations.get(id=registration_id)
self._reject_registration(registration)
else:
raise FieldDoesNotExist('Event has no registrations')
else:
form = self.get_create_participant_form()
if form.is_valid():
form.save()
participant = form.instance
messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name())))
else:
messages.error(request, _(u'irgendwas ging schief.'))
return self.render_to_response(self.get_context_data(create_participant_form=form))
return HttpResponseRedirect(reverse('dav_events:registrations', kwargs={'pk': event.pk}))
@method_decorator(login_required)

View File

@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
class RegistrationForm(forms.ModelForm):
class Meta:
model = Registration
exclude = ['event', 'created_at', 'privacy_policy', 'purge_at']
exclude = ['event', 'created_at', 'privacy_policy', 'purge_at', 'answered']
widgets = {
'emergency_contact': forms.Textarea(attrs={'rows': 4}),
'experience': forms.Textarea(attrs={'rows': 5}),

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-06-03 14:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dav_registration', '0002_auto_20190401_1310'),
]
operations = [
migrations.AddField(
model_name='registration',
name='answered',
field=models.BooleanField(default=False, verbose_name='Durch Tourleitung beantwortet'),
),
migrations.AlterField(
model_name='registration',
name='purge_at',
field=models.DateTimeField(verbose_name='Zeitpunkt der Datenlöschung'),
),
]

View File

@@ -69,7 +69,9 @@ class Registration(models.Model):
verbose_name=_('Erklärung zur Datenspeicherung'))
privacy_policy_accepted = models.BooleanField(default=False,
verbose_name=_('Einwilligung zur Datenspeicherung'))
purge_at = models.DateTimeField()
purge_at = models.DateTimeField(_('Zeitpunkt der Datenlöschung'))
answered = models.BooleanField(_('Durch Tourleitung beantwortet'), default=False)
@staticmethod
def pk2hexstr(pk):
@@ -105,6 +107,25 @@ class Registration(models.Model):
def get_full_name(self):
return '{} {}'.format(self.personal_names, self.family_names)
def get_info(self):
text = """{fullname}
{address}, {postal_code} {city}
Erfahrung:
{experience}
Anmerkung:
{note}
"""
return text.format(
fullname=self.get_full_name(),
address=self.address,
postal_code=self.postal_code,
city=self.city,
experience=self.experience,
note=self.note,
)
def save(self, **kwargs):
creating = False
if not self.id: