From 9104d69dd738845d8de99a258e844434f1f159d3 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Mon, 3 Jun 2019 17:10:52 +0200 Subject: [PATCH] UPD: more registration staff on dav_events. --- dav_events/admin.py | 13 +- dav_events/forms/__init__.py | 1 + dav_events/forms/participant.py | 15 ++ .../migrations/0030_auto_20190603_1014.py | 47 ++++++ dav_events/models/__init__.py | 1 + dav_events/models/participant.py | 79 ++++++++++ .../dav_events/event_registrations.html | 109 +++++++++++++- dav_events/views/events.py | 142 +++++++++++++----- dav_registration/forms.py | 2 +- .../migrations/0003_auto_20190603_1425.py | 25 +++ dav_registration/models.py | 23 ++- 11 files changed, 411 insertions(+), 46 deletions(-) create mode 100644 dav_events/forms/participant.py create mode 100644 dav_events/migrations/0030_auto_20190603_1014.py create mode 100644 dav_events/models/participant.py create mode 100644 dav_registration/migrations/0003_auto_20190603_1425.py diff --git a/dav_events/admin.py b/dav_events/admin.py index eb98b30..7a434ab 100644 --- a/dav_events/admin.py +++ b/dav_events/admin.py @@ -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 diff --git a/dav_events/forms/__init__.py b/dav_events/forms/__init__.py index f995b85..a13db64 100644 --- a/dav_events/forms/__init__.py +++ b/dav_events/forms/__init__.py @@ -1,2 +1,3 @@ from . import generic from . import events +from . import participant diff --git a/dav_events/forms/participant.py b/dav_events/forms/participant.py new file mode 100644 index 0000000..92dd23c --- /dev/null +++ b/dav_events/forms/participant.py @@ -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}), + } diff --git a/dav_events/migrations/0030_auto_20190603_1014.py b/dav_events/migrations/0030_auto_20190603_1014.py new file mode 100644 index 0000000..c157c0c --- /dev/null +++ b/dav_events/migrations/0030_auto_20190603_1014.py @@ -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')]), + ), + ] diff --git a/dav_events/models/__init__.py b/dav_events/models/__init__.py index e8f44d2..9645797 100644 --- a/dav_events/models/__init__.py +++ b/dav_events/models/__init__.py @@ -2,3 +2,4 @@ from .event import Event from .eventflag import EventFlag from .eventstatus import EventStatus from .oneclickaction import OneClickAction +from .participant import Participant diff --git a/dav_events/models/participant.py b/dav_events/models/participant.py new file mode 100644 index 0000000..aa8283b --- /dev/null +++ b/dav_events/models/participant.py @@ -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)) diff --git a/dav_events/templates/dav_events/event_registrations.html b/dav_events/templates/dav_events/event_registrations.html index 9a572b2..ff9b036 100644 --- a/dav_events/templates/dav_events/event_registrations.html +++ b/dav_events/templates/dav_events/event_registrations.html @@ -220,22 +220,117 @@

Teilnehmer (Designstudie - Das funktioniert alles noch nicht!)

+
+ +
+
+ {% for registration in registrations %} +
+ {% csrf_token %} + + {% if registration.answered %} + + {% endif %} + + + {{ registration.get_full_name }} + ({{ registration.email_address }}, + {{ registration.phone_number }}) +   + + {% bootstrap_icon 'time' %} + {{ registration.created_at|date:'d. F Y, G:i' }} + +   + + {% bootstrap_icon 'info-sign' %} + + {% if registration.answered %} + + {% endif %} +
+ {% empty %} + Keine unbestätigten Anmeldungen vorhanden + {% endfor %} +
+
+
-
- Formular -
-
@@ -245,7 +340,7 @@
- {{ participant.position }}. {{ participant.get_full_name }} +   {{ participant.position }}. {{ participant.get_full_name }}
diff --git a/dav_events/views/events.py b/dav_events/views/events.py index 6f4c785..fda013c 100644 --- a/dav_events/views/events.py +++ b/dav_events/views/events.py @@ -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) diff --git a/dav_registration/forms.py b/dav_registration/forms.py index 202e02c..504874b 100644 --- a/dav_registration/forms.py +++ b/dav_registration/forms.py @@ -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}), diff --git a/dav_registration/migrations/0003_auto_20190603_1425.py b/dav_registration/migrations/0003_auto_20190603_1425.py new file mode 100644 index 0000000..e36f545 --- /dev/null +++ b/dav_registration/migrations/0003_auto_20190603_1425.py @@ -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'), + ), + ] diff --git a/dav_registration/models.py b/dav_registration/models.py index 18bad94..c0842ca 100644 --- a/dav_registration/models.py +++ b/dav_registration/models.py @@ -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: