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

View File

@@ -1,2 +1,3 @@
from . import generic from . import generic
from . import events 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 .eventflag import EventFlag
from .eventstatus import EventStatus from .eventstatus import EventStatus
from .oneclickaction import OneClickAction 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> </div>
<hr /> <hr />
<h4>Teilnehmer (Designstudie - Das funktioniert alles noch nicht!)</h4> <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 class="panel panel-default">
<div id="headingAddParticipant" class="panel-heading" role="tab"> <div id="headingAddParticipant" class="panel-heading" role="tab">
<h5 class="panel-title"> <h5 class="panel-title">
<a role="button" href="#collapseAddParticipant" data-toggle="collapse" data-parent="#accordion" <a role="button" href="#collapseAddParticipant" data-toggle="collapse" data-parent="#accordion"
aria-expanded="true" aria-controls="collapseAddParticipant"> aria-expanded="true" aria-controls="collapseAddParticipant">
Teilnehmer hinzufügen <span class="caret"></span>&nbsp;&nbsp;weiteren Teilnehmer eintragen
</a> </a>
</h5> </h5>
</div> </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"> role="tabpanel" aria-labelledby="headingAddParticipant">
<div class="panel-body"> <div class="panel-body">
Formular {% bootstrap_form_errors create_participant_form %}
</div> <form action="" method="post">
<div class="panel-footer"> {% csrf_token %}
<button class="btn btn-success">Speichern</button> <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> </div>
</div> </div>
@@ -245,7 +340,7 @@
<h5 class="panel-title"> <h5 class="panel-title">
<a role="button" href="#collapseParticipant_{{ participant.id }}" data-toggle="collapse" data-parent="#accordion" <a role="button" href="#collapseParticipant_{{ participant.id }}" data-toggle="collapse" data-parent="#accordion"
aria-expanded="true" aria-controls="collapseParticipant_{{ participant.id }}"> 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> </a>
</h5> </h5>
</div> </div>

View File

@@ -5,7 +5,7 @@ import os
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import login from django.contrib.auth import login
from django.contrib.auth.decorators import login_required 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.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@@ -169,58 +169,130 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
self.enforce_permission(obj) self.enforce_permission(obj)
return 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): def get_context_data(self, **kwargs):
context = super(EventRegistrationsView, self).get_context_data(**kwargs) context = super(EventRegistrationsView, self).get_context_data(**kwargs)
obj = context.get('event') event = context.get('event')
context['has_permission_update'] = self.has_permission('update', obj) context['has_permission_update'] = self.has_permission('update', event)
context['is_published_any'] = obj.workflow.has_reached_status('published*') context['is_published_any'] = event.workflow.has_reached_status('published*')
context['is_done'] = obj.workflow.has_reached_status('expired') context['is_done'] = event.workflow.has_reached_status('expired')
class MockParticipant(object): participants = event.participants.all()
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')]
context['participants'] = participants 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 return context
def _send_mails(self, event, request): def _notify_publisher(self, event, editor):
editor = request.user
recipients = get_users_by_role('publisher') recipients = get_users_by_role('publisher')
for recipient in recipients: for recipient in recipients:
if recipient.email: if recipient.email:
email = emails.EventRegistrationClosedMail(recipient=recipient, event=event, editor=editor) email = emails.EventRegistrationClosedMail(recipient=recipient, event=event, editor=editor)
email.send() 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): def post(self, request, *args, **kwargs):
event = self.get_object() event = self.get_object()
self.object = event
action = request.POST.get('action') action = request.POST.get('action')
if action == 'close-registration': if action == 'close-registration':
logger.info('Close registration: %s', event) self._close_registration(request, event)
event.registration_closed = True
event.save(implicit_update=True)
self._send_mails(event, request)
messages.success(request, _(u'Die Anmeldung wurde geschlossen'))
elif action == 'open-registration': elif action == 'open-registration':
logger.info('Reopen registration: %s', event) self._reopen_registration(request, event)
event.registration_closed = False
event.save(implicit_update=True)
messages.success(request, _(u'Die Anmeldung wurde geöffnet'))
elif action == 'kill-deadline': elif action == 'kill-deadline':
logger.info('Delete deadline: %s', event) self._kill_deadline(request, event)
event.deadline = None elif action == 'accept_registration':
event.registration_closed = False if hasattr(event, 'registrations'):
event.save(implicit_update=True) registration_id = request.POST.get('registration')
messages.success(request, _(u'Der Anmeldeschluss wurde gelöscht')) 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})) return HttpResponseRedirect(reverse('dav_events:registrations', kwargs={'pk': event.pk}))
@method_decorator(login_required) @method_decorator(login_required)

View File

@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
class RegistrationForm(forms.ModelForm): class RegistrationForm(forms.ModelForm):
class Meta: class Meta:
model = Registration model = Registration
exclude = ['event', 'created_at', 'privacy_policy', 'purge_at'] exclude = ['event', 'created_at', 'privacy_policy', 'purge_at', 'answered']
widgets = { widgets = {
'emergency_contact': forms.Textarea(attrs={'rows': 4}), 'emergency_contact': forms.Textarea(attrs={'rows': 4}),
'experience': forms.Textarea(attrs={'rows': 5}), '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')) verbose_name=_('Erklärung zur Datenspeicherung'))
privacy_policy_accepted = models.BooleanField(default=False, privacy_policy_accepted = models.BooleanField(default=False,
verbose_name=_('Einwilligung zur Datenspeicherung')) 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 @staticmethod
def pk2hexstr(pk): def pk2hexstr(pk):
@@ -105,6 +107,25 @@ class Registration(models.Model):
def get_full_name(self): def get_full_name(self):
return '{} {}'.format(self.personal_names, self.family_names) 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): def save(self, **kwargs):
creating = False creating = False
if not self.id: if not self.id: