Add trashbin for registrations and participants #28
@@ -114,7 +114,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<h4>{% trans 'Teilnehmer' %}</h4>
|
<h4>{% trans 'Teilnehmer' %}</h4>
|
||||||
<div class="panel-group" id="form-accordion-participants" role="tablist" aria-multiselectable="true">
|
<div class="panel-group" id="form-accordion-participants" role="tablist" aria-multiselectable="true">
|
||||||
{% if registrations_support %}
|
{% if registrations_support %}
|
||||||
@@ -131,22 +133,11 @@
|
|||||||
<div id="collapseRegistrations" class="panel-collapse collapse"
|
<div id="collapseRegistrations" class="panel-collapse collapse"
|
||||||
role="tabpanel" aria-labelledby="headingRegistrations">
|
role="tabpanel" aria-labelledby="headingRegistrations">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% for registration in registrations_all %}
|
{% for registration in registrations_pending %}
|
||||||
<form action="" method="post" class="form-inline">
|
<form action="" method="post" class="form-inline">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="registration" value="{{ registration.id }}">
|
<input type="hidden" name="registration" value="{{ registration.id }}">
|
||||||
{% if registration.answered %}
|
{% if has_permission_update_participants %}
|
||||||
<button disabled="disabled"
|
|
||||||
class="btn btn-link no-padding" title="Anmeldung wurde bereits bearbeitet">
|
|
||||||
<span class="text-muted">{% bootstrap_icon 'plus-sign' %}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button disabled="disabled"
|
|
||||||
class="btn btn-link no-padding" title="Anmeldung wurde bereits bearbeitet">
|
|
||||||
<span class="text-muted">{% bootstrap_icon 'minus-sign' %}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{% elif has_permission_update_participants %}
|
|
||||||
<button type="submit" name="action" value="accept_registration"
|
<button type="submit" name="action" value="accept_registration"
|
||||||
class="btn btn-link no-padding" title="zur Teilnehmerliste hinzufügen">
|
class="btn btn-link no-padding" title="zur Teilnehmerliste hinzufügen">
|
||||||
<span class="text-success">{% bootstrap_icon 'plus-sign' %}</span>
|
<span class="text-success">{% bootstrap_icon 'plus-sign' %}</span>
|
||||||
@@ -158,9 +149,6 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if registration.answered %}
|
|
||||||
<s class="text-muted">
|
|
||||||
{% endif %}
|
|
||||||
{{ registration.get_full_name }}
|
{{ registration.get_full_name }}
|
||||||
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
|
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
|
||||||
{{ registration.phone_number }})
|
{{ registration.phone_number }})
|
||||||
@@ -173,10 +161,6 @@
|
|||||||
<span title="{{ registration.get_info }}">
|
<span title="{{ registration.get_info }}">
|
||||||
{% bootstrap_icon 'info-sign' %}
|
{% bootstrap_icon 'info-sign' %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if registration.answered %}
|
|
||||||
</s>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{% trans 'Keine Anmeldungen vorhanden' %}
|
{% trans 'Keine Anmeldungen vorhanden' %}
|
||||||
@@ -191,13 +175,16 @@
|
|||||||
{% with position=participant.position %}
|
{% with position=participant.position %}
|
||||||
<div class="panel {% if event.max_participants and position > event.max_participants %}panel-warning{% else %}panel-default{% endif %}">
|
<div class="panel {% if event.max_participants and position > event.max_participants %}panel-warning{% else %}panel-default{% endif %}">
|
||||||
<div id="headingParticipant_{{ participant.id }}" class="panel-heading" role="tab">
|
<div id="headingParticipant_{{ participant.id }}" class="panel-heading" role="tab">
|
||||||
<h5 class="panel-title">
|
<div>
|
||||||
|
<strong><span class="panel-title">
|
||||||
<a role="button" href="#collapseParticipant_{{ participant.id }}"
|
<a role="button" href="#collapseParticipant_{{ participant.id }}"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
aria-expanded="true" aria-controls="collapseParticipant_{{ participant.id }}">
|
aria-expanded="true" aria-controls="collapseParticipant_{{ participant.id }}">
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
{{ position }}. {{ participant.get_full_name }}
|
{{ position }}. {{ participant.get_full_name }}
|
||||||
</a>
|
</a>
|
||||||
|
</span></strong>
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
(<a href="mailto:{{ participant.email_address }}">{{ participant.email_address }}</a>, {{ participant.phone_number }})
|
(<a href="mailto:{{ participant.email_address }}">{{ participant.email_address }}</a>, {{ participant.phone_number }})
|
||||||
</small>
|
</small>
|
||||||
@@ -207,6 +194,12 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Nicht Mitglied' %}
|
{% trans 'Nicht Mitglied' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="text-info"
|
||||||
|
title="{{ participant.get_info }}
|
||||||
|
{% trans 'Zeitpunkt der automatischen Löschung' %}: {{ participant.purge_at|date:'d. F Y' }}">
|
||||||
|
{% bootstrap_icon 'info-sign' %}
|
||||||
|
</span>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<form action="" method="post" class="form-inline">
|
<form action="" method="post" class="form-inline">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -222,31 +215,33 @@
|
|||||||
class="btn btn-link no-padding {% if forloop.last %}invisible{% endif %}">
|
class="btn btn-link no-padding {% if forloop.last %}invisible{% endif %}">
|
||||||
<span class="text-info">{% bootstrap_icon 'triangle-bottom' %}</span>
|
<span class="text-info">{% bootstrap_icon 'triangle-bottom' %}</span>
|
||||||
</button>
|
</button>
|
||||||
<button name="action" value="remove_participant"
|
<button name="action" value="trash_participant"
|
||||||
title="{% trans 'Teilnehmer jetzt löschen' %}
|
title="{% trans 'Eintrag in Papierkorb verschieben' %}"
|
||||||
({% trans 'erfolgt automatisch am' %} {{ participant.purge_at|date:'d. F Y' }})"
|
|
||||||
class="btn btn-link no-padding">
|
class="btn btn-link no-padding">
|
||||||
<span class="text-danger">{% bootstrap_icon 'remove-circle' %}</span>
|
<span class="text-danger">{% bootstrap_icon 'trash' %}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.charge and participant.paid and has_permission_payment %}
|
{% if event.charge and participant.paid and has_permission_payment %}
|
||||||
|
|
||||||
<button name="action" value="revoke_payment"
|
<button name="action" value="revoke_payment"
|
||||||
title="{% trans 'Geldeingang wurde bestätigt' %} - {% trans 'Bestätigung des Geldeingangs zurückziehen' %}"
|
title="{% trans 'Geldeingang wurde bestätigt' %} - {% trans 'Bestätigung des Geldeingangs zurückziehen' %}"
|
||||||
class="btn btn-link no-padding">
|
class="btn btn-link no-padding">
|
||||||
<span class="text-success">{% bootstrap_icon 'piggy-bank' %}</span>
|
<span class="text-success">{% bootstrap_icon 'piggy-bank' %}</span>
|
||||||
</button>
|
</button>
|
||||||
{% elif event.charge and participant.paid %}
|
{% elif event.charge and participant.paid %}
|
||||||
|
|
||||||
<span class="text-success" title="{% trans 'Geldeingang bestätigt' %}">
|
<span class="text-success" title="{% trans 'Geldeingang bestätigt' %}">
|
||||||
{% bootstrap_icon 'piggy-bank' %}
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
</span>
|
</span>
|
||||||
{% elif event.charge and has_permission_payment %}
|
{% elif event.charge and has_permission_payment %}
|
||||||
|
|
||||||
<button name="action" value="confirm_payment"
|
<button name="action" value="confirm_payment"
|
||||||
title="{% trans 'Geldeingang bestätigen' %}"
|
title="{% trans 'Geldeingang bestätigen' %}"
|
||||||
class="btn btn-link no-padding">
|
class="btn btn-link no-padding">
|
||||||
<span class="text-danger">{% bootstrap_icon 'piggy-bank' %}</span>
|
<span class="text-danger">{% bootstrap_icon 'piggy-bank' %}</span>
|
||||||
</button>
|
</button>
|
||||||
{% elif event.charge %}
|
{% elif event.charge %}
|
||||||
|
|
||||||
<span class="text-danger" title="{% trans 'Geldeingang unbestätigt' %}">
|
<span class="text-danger" title="{% trans 'Geldeingang unbestätigt' %}">
|
||||||
{% bootstrap_icon 'piggy-bank' %}
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
</span>
|
</span>
|
||||||
@@ -257,7 +252,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseParticipant_{{ participant.id }}"
|
<div id="collapseParticipant_{{ participant.id }}"
|
||||||
class="panel-collapse collapse {% if form.errors %}in{% endif %}"
|
class="panel-collapse collapse {% if form.errors %}in{% endif %}"
|
||||||
@@ -297,6 +292,110 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h4>{% trans 'Papierkorb' %}</h4>
|
||||||
|
<div class="panel-group" id="form-accordion-trash" role="tablist" aria-multiselectable="true">
|
||||||
|
{% if registrations_support and registrations_answered %}
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div id="headingAnsweredRegistrations" class="panel-heading" role="tab">
|
||||||
|
<h5 class="panel-title">
|
||||||
|
<a role="button" href="#collapseAnsweredRegistrations"
|
||||||
|
data-toggle="collapse"
|
||||||
|
aria-expanded="true" aria-controls="collapseAnsweredRegistrations">
|
||||||
|
<span class="caret"></span> {% trans 'Bearbeitete Anmeldungen' %}
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div id="collapseAnsweredRegistrations" class="panel-collapse collapse"
|
||||||
|
role="tabpanel" aria-labelledby="headingAnsweredRegistrations">
|
||||||
|
<div class="panel-body">
|
||||||
|
{% for registration in registrations_answered %}
|
||||||
|
<div>
|
||||||
|
<button disabled="disabled"
|
||||||
|
class="btn btn-link no-padding" title="Anmeldung wurde bereits bearbeitet">
|
||||||
|
<span class="{% if registration.status.accepted %}text-success{% else %}text-muted{% endif %}">{% bootstrap_icon 'plus-sign' %}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button disabled="disabled"
|
||||||
|
class="btn btn-link no-padding" title="Anmeldung wurde bereits bearbeitet">
|
||||||
|
<span class="{% if not registration.status.accepted %}text-danger{% else %}text-muted{% endif %}">{% bootstrap_icon 'minus-sign' %}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="text-muted">
|
||||||
|
{{ registration.get_full_name }}
|
||||||
|
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
|
||||||
|
{{ registration.phone_number }})
|
||||||
|
|
||||||
|
<span title="Anmeldezeitpunkt">
|
||||||
|
{% bootstrap_icon 'time' %}
|
||||||
|
{{ registration.created_at|date:'d. F Y, G:i' }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span title="{{ registration.get_info }}">
|
||||||
|
{% bootstrap_icon 'info-sign' %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if participants_trash %}
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div id="headingTrashedParticipants" class="panel-heading" role="tab">
|
||||||
|
<h5 class="panel-title">
|
||||||
|
<a role="button" href="#collapseTrashedParticipants"
|
||||||
|
data-toggle="collapse"
|
||||||
|
aria-expanded="true" aria-controls="collapseTrashedParticipants">
|
||||||
|
<span class="caret"></span> {% trans 'Gelöschte Teilnehmer' %}
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div id="collapseTrashedParticipants" class="panel-collapse collapse"
|
||||||
|
role="tabpanel" aria-labelledby="headingTrashedParticipants">
|
||||||
|
<div class="panel-body">
|
||||||
|
{% for participant in participants_trash %}
|
||||||
|
<div>
|
||||||
|
<span class="text-muted">
|
||||||
|
{{ participant.get_full_name }}
|
||||||
|
(<a href="mailto:{{ participant.email_address }}">{{ participant.email_address }}</a>,
|
||||||
|
{{ participant.phone_number }})
|
||||||
|
|
||||||
|
{% if participant.dav_member %}
|
||||||
|
{{ participant.dav_number|default:'Fehler! heinzel Bescheid geben!' }}
|
||||||
|
{% else %}
|
||||||
|
{% trans 'Nicht Mitglied' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span title="{{ participant.get_info }}
|
||||||
|
{% trans 'Zeitpunkt der automatischen Löschung' %}: {{ participant.purge_at|date:'d. F Y' }}">
|
||||||
|
{% bootstrap_icon 'info-sign' %}
|
||||||
|
</span>
|
||||||
|
{% if event.charge and participant.paid %}
|
||||||
|
|
||||||
|
<span class="text-success" title="{% trans 'Geldeingang bestätigt' %}">
|
||||||
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
|
</span>
|
||||||
|
{% elif event.charge %}
|
||||||
|
|
||||||
|
<span class="text-danger" title="{% trans 'Geldeingang unbestätigt' %}">
|
||||||
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if not registrations_answered and not participants_trash %}
|
||||||
|
<span class="text-muted small">{% trans 'Der Papierkorb ist leer.' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page-container-fluid %}
|
{% endblock page-container-fluid %}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import EventStatus, EventFlag, Event, OneClickAction, Participant
|
from .models import EventStatus, EventFlag, Event, OneClickAction, Participant, TrashedParticipant
|
||||||
|
|
||||||
|
|
||||||
@admin.register(EventStatus)
|
@admin.register(EventStatus)
|
||||||
@@ -31,3 +31,8 @@ class OneClickActionAdmin(admin.ModelAdmin):
|
|||||||
@admin.register(Participant)
|
@admin.register(Participant)
|
||||||
class ParticipantAdmin(admin.ModelAdmin):
|
class ParticipantAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TrashedParticipant)
|
||||||
|
class TrashedParticipantAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|||||||
46
dav_events/migrations/0036_trashedparticipant.py
Normal file
46
dav_events/migrations/0036_trashedparticipant.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.29 on 2020-12-03 10:44
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dav_events', '0035_merge_20201103_1112'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TrashedParticipant',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('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_member', models.BooleanField(default=True, help_text='In Ausnahmefällen nehmen wir auch Nichtmitglieder mit.', verbose_name='DAV Mitglied')),
|
||||||
|
('dav_number', models.CharField(blank=True, max_length=62, validators=[django.core.validators.RegexValidator('^([0-9]{1,10}/[0-9]{2,10}/)?[0-9]{1,10}(\\*[0-9]{1,10})?(\\*[0-9]{4}\\*[0-9]{4})?([* ][0-9]{8})?$', 'Ungültiges Format.')], verbose_name='DAV Mitgliedsnummer')),
|
||||||
|
('emergency_contact', models.TextField(blank=True, help_text='Kann frei gelassen werden.', verbose_name='Notfall-Kontakt')),
|
||||||
|
('experience', models.TextField(blank=True, help_text='Kann frei gelassen werden.', verbose_name='Erfahrung')),
|
||||||
|
('note', models.TextField(blank=True, help_text='Kann frei gelassen werden.', verbose_name='Anmerkung')),
|
||||||
|
('paid', models.BooleanField(default=False, verbose_name='Teilnehmerbeitrag bezahlt')),
|
||||||
|
('purge_at', models.DateTimeField()),
|
||||||
|
('created_at', models.DateTimeField()),
|
||||||
|
('trashed_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('position', models.IntegerField(verbose_name='Listennummer')),
|
||||||
|
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trashed_participants', to='dav_events.Event')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gelöschter Teilnehmer (Papierkorb)',
|
||||||
|
'verbose_name_plural': 'Gelöschte Teilnehmer (Papierkorb)',
|
||||||
|
'ordering': ['event', 'trashed_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -5,3 +5,4 @@ from .eventflag import EventFlag
|
|||||||
from .eventstatus import EventStatus
|
from .eventstatus import EventStatus
|
||||||
from .oneclickaction import OneClickAction
|
from .oneclickaction import OneClickAction
|
||||||
from .participant import Participant
|
from .participant import Participant
|
||||||
|
from .trash import TrashedParticipant
|
||||||
|
|||||||
@@ -13,12 +13,7 @@ midnight = datetime.time(00, 00, 00)
|
|||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Participant(models.Model):
|
class AbstractParticipant(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,
|
personal_names = models.CharField(max_length=1024,
|
||||||
verbose_name=_('Vorname(n)'))
|
verbose_name=_('Vorname(n)'))
|
||||||
family_names = models.CharField(max_length=1024,
|
family_names = models.CharField(max_length=1024,
|
||||||
@@ -54,17 +49,10 @@ class Participant(models.Model):
|
|||||||
purge_at = models.DateTimeField()
|
purge_at = models.DateTimeField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (('event', 'position'), )
|
abstract = True
|
||||||
verbose_name = _('Teilnehmer')
|
|
||||||
verbose_name_plural = _('Teilnehmer')
|
|
||||||
ordering = ['event', 'position']
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{eventnumber} - {position}. {name}'.format(
|
return self.get_full_name()
|
||||||
eventnumber=self.event.get_number(),
|
|
||||||
position=self.position,
|
|
||||||
name=self.get_full_name(),
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -96,6 +84,12 @@ class Participant(models.Model):
|
|||||||
note=self.note,
|
note=self.note,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_data_dict(self):
|
||||||
|
data = {}
|
||||||
|
for field in self._meta.fields:
|
||||||
|
data[field.name] = getattr(self, field.name)
|
||||||
|
return data
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.dav_member and not self.dav_number:
|
if self.dav_member and not self.dav_number:
|
||||||
raise ValidationError({'dav_number': _('Bei DAV Mitgliedern brauchen wir die Mitgliedsnummer.')})
|
raise ValidationError({'dav_number': _('Bei DAV Mitgliedern brauchen wir die Mitgliedsnummer.')})
|
||||||
@@ -105,7 +99,7 @@ class Participant(models.Model):
|
|||||||
self.purge_at = self.__class__.calc_purge_at(self.event)
|
self.purge_at = self.__class__.calc_purge_at(self.event)
|
||||||
|
|
||||||
self.full_clean()
|
self.full_clean()
|
||||||
super(Participant, self).save(**kwargs)
|
super(AbstractParticipant, self).save(**kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calc_purge_at(event):
|
def calc_purge_at(event):
|
||||||
@@ -132,3 +126,23 @@ class Participant(models.Model):
|
|||||||
purge_date = july_nextyear
|
purge_date = july_nextyear
|
||||||
|
|
||||||
return timezone.make_aware(datetime.datetime.combine(purge_date, midnight))
|
return timezone.make_aware(datetime.datetime.combine(purge_date, midnight))
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class Participant(AbstractParticipant):
|
||||||
|
event = models.ForeignKey('Event', related_name='participants')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
position = models.IntegerField(verbose_name='Listennummer')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Teilnehmer')
|
||||||
|
verbose_name_plural = _('Teilnehmer')
|
||||||
|
unique_together = (('event', 'position'), )
|
||||||
|
ordering = ['event', 'position']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{eventnumber} - {position}. {name}'.format(
|
||||||
|
eventnumber=self.event.get_number(),
|
||||||
|
position=self.position,
|
||||||
|
name=self.get_full_name(),
|
||||||
|
)
|
||||||
|
|||||||
1
dav_events/models/trash/__init__.py
Normal file
1
dav_events/models/trash/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .trashed_participant import TrashedParticipant
|
||||||
26
dav_events/models/trash/trashed_participant.py
Normal file
26
dav_events/models/trash/trashed_participant.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from ..participant import AbstractParticipant
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class TrashedParticipant(AbstractParticipant):
|
||||||
|
event = models.ForeignKey('Event', related_name='trashed_participants')
|
||||||
|
created_at = models.DateTimeField()
|
||||||
|
trashed_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
position = models.IntegerField(verbose_name='Listennummer')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Gelöschter Teilnehmer (Papierkorb)')
|
||||||
|
verbose_name_plural = _('Gelöschte Teilnehmer (Papierkorb)')
|
||||||
|
ordering = ['event', 'trashed_at']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{eventnumber} - {name}'.format(
|
||||||
|
eventnumber=self.event.get_number(),
|
||||||
|
name=self.get_full_name(),
|
||||||
|
)
|
||||||
@@ -222,6 +222,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="pull-right text-info" style="margin-right: 1em;"
|
<div class="pull-right text-info" style="margin-right: 1em;"
|
||||||
title="Sobald du im Kasten 'Anmeldungen' bei einzelnen Personen auf das Plus-Symbol geklickt hast, oder jemanden mit dem Formular unter 'weiteren Teilnehmer hinzufügen' eingetragen hast, erscheinen diese Personen weiter unten als Teilnehmer (graue Kästen).
|
title="Sobald du im Kasten 'Anmeldungen' bei einzelnen Personen auf das Plus-Symbol geklickt hast, oder jemanden mit dem Formular unter 'weiteren Teilnehmer hinzufügen' eingetragen hast, erscheinen diese Personen weiter unten als Teilnehmer (graue Kästen).
|
||||||
@@ -249,7 +250,7 @@ Die Person wird dann automatisch auf die Teilnehmerliste übernommen.
|
|||||||
|
|
||||||
- Wenn du jemanden nicht mitnehmen möchtest, schicke ihm per E-Mail eine Absage und klicke dann auf das Minus-Symbol.
|
- Wenn du jemanden nicht mitnehmen möchtest, schicke ihm per E-Mail eine Absage und klicke dann auf das Minus-Symbol.
|
||||||
|
|
||||||
Nach einem Klick auf Plus oder Minus werden die entsprechenden Zeilen ausgegraut.
|
Nach einem Klick auf Plus oder Minus werden die entsprechenden Zeilen in den Papierkorb verschoben.
|
||||||
Wichtig: das System verschickt keine Zu- oder Absagen an die Teilnehmer!
|
Wichtig: das System verschickt keine Zu- oder Absagen an die Teilnehmer!
|
||||||
Das musst du selbst (per E-Mail oder telefonisch) machen.
|
Das musst du selbst (per E-Mail oder telefonisch) machen.
|
||||||
">
|
">
|
||||||
@@ -266,22 +267,11 @@ Das musst du selbst (per E-Mail oder telefonisch) machen.
|
|||||||
<div id="collapseRegistrations" class="panel-collapse collapse {% if registrations_pending %}in{% endif %}"
|
<div id="collapseRegistrations" class="panel-collapse collapse {% if registrations_pending %}in{% endif %}"
|
||||||
role="tabpanel" aria-labelledby="headingRegistrations">
|
role="tabpanel" aria-labelledby="headingRegistrations">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% for registration in registrations_all %}
|
{% for registration in registrations_pending %}
|
||||||
<form action="" method="post" class="form-inline">
|
<form action="" method="post" class="form-inline">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="registration" value="{{ registration.id }}">
|
<input type="hidden" name="registration" value="{{ registration.id }}">
|
||||||
{% if registration.answered %}
|
{% if has_permission_update_participants %}
|
||||||
<button disabled="disabled"
|
|
||||||
class="btn btn-link no-padding" title="Anmeldung wurde bereits bearbeitet">
|
|
||||||
<span class="text-muted">{% bootstrap_icon 'plus-sign' %}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button disabled="disabled"
|
|
||||||
class="btn btn-link no-padding" title="Anmeldung wurde bereits bearbeitet">
|
|
||||||
<span class="text-muted">{% bootstrap_icon 'minus-sign' %}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{% elif has_permission_update_participants %}
|
|
||||||
<button type="submit" name="action" value="accept_registration"
|
<button type="submit" name="action" value="accept_registration"
|
||||||
class="btn btn-link no-padding" title="zur Teilnehmerliste hinzufügen">
|
class="btn btn-link no-padding" title="zur Teilnehmerliste hinzufügen">
|
||||||
<span class="text-success">{% bootstrap_icon 'plus-sign' %}</span>
|
<span class="text-success">{% bootstrap_icon 'plus-sign' %}</span>
|
||||||
@@ -293,10 +283,6 @@ Das musst du selbst (per E-Mail oder telefonisch) machen.
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if registration.answered %}
|
|
||||||
<s>
|
|
||||||
<span class="text-muted">
|
|
||||||
{% endif %}
|
|
||||||
{{ registration.get_full_name }}
|
{{ registration.get_full_name }}
|
||||||
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
|
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
|
||||||
{{ registration.phone_number }})
|
{{ registration.phone_number }})
|
||||||
@@ -309,19 +295,9 @@ Das musst du selbst (per E-Mail oder telefonisch) machen.
|
|||||||
<span title="{{ registration.get_info }}">
|
<span title="{{ registration.get_info }}">
|
||||||
{% bootstrap_icon 'info-sign' %}
|
{% bootstrap_icon 'info-sign' %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if registration.answered %}
|
|
||||||
</span>
|
|
||||||
</s>
|
|
||||||
|
|
||||||
<span class="text-info" title="Bei dieser Anmeldung hast du bereits auf Plus oder Minus geklickt.
|
|
||||||
Leider speichert das System hier nicht, welchen der beiden Knöpfe du gewählt hast, aber bei Plus sollte die Person ja weiter unten als Teilnehmer gelistet sein.">
|
|
||||||
{% bootstrap_icon 'question-sign' %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{% trans 'Keine Anmeldungen vorhanden' %}
|
{% trans 'Keine unbearbeiteten Anmeldungen vorhanden' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,16 +344,25 @@ Wichtig: das System verschickt keine Bestätigung an dich oder den neuen Teilneh
|
|||||||
{% with position=participant.position %}
|
{% with position=participant.position %}
|
||||||
<div class="panel {% if event.max_participants and position > event.max_participants %}panel-warning{% else %}panel-default{% endif %}">
|
<div class="panel {% if event.max_participants and position > event.max_participants %}panel-warning{% else %}panel-default{% endif %}">
|
||||||
<div id="headingParticipant_{{ participant.id }}" class="panel-heading" role="tab">
|
<div id="headingParticipant_{{ participant.id }}" class="panel-heading" role="tab">
|
||||||
<h5 class="panel-title">
|
<div>
|
||||||
|
<strong><span class="panel-title">
|
||||||
<a role="button" href="#collapseParticipant_{{ participant.id }}"
|
<a role="button" href="#collapseParticipant_{{ participant.id }}"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
aria-expanded="true" aria-controls="collapseParticipant_{{ participant.id }}">
|
aria-expanded="true" aria-controls="collapseParticipant_{{ participant.id }}">
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
{{ position }}. {{ participant.get_full_name }}
|
{{ position }}. {{ participant.get_full_name }}
|
||||||
</a>
|
</a>
|
||||||
|
</span></strong>
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
(<a href="mailto:{{ participant.email_address }}">{{ participant.email_address }}</a>, {{ participant.phone_number }})
|
(<a href="mailto:{{ participant.email_address }}">{{ participant.email_address }}</a>, {{ participant.phone_number }})
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
<span class="text-info"
|
||||||
|
title="{{ participant.get_info }}
|
||||||
|
{% trans 'Zeitpunkt der automatischen Löschung' %}: {{ participant.purge_at|date:'d. F Y' }}">
|
||||||
|
{% bootstrap_icon 'info-sign' %}
|
||||||
|
</span>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<form action="" method="post" class="form-inline">
|
<form action="" method="post" class="form-inline">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -393,31 +378,33 @@ Wichtig: das System verschickt keine Bestätigung an dich oder den neuen Teilneh
|
|||||||
class="btn btn-link no-padding {% if forloop.last %}invisible{% endif %}">
|
class="btn btn-link no-padding {% if forloop.last %}invisible{% endif %}">
|
||||||
<span class="text-info">{% bootstrap_icon 'triangle-bottom' %}</span>
|
<span class="text-info">{% bootstrap_icon 'triangle-bottom' %}</span>
|
||||||
</button>
|
</button>
|
||||||
<button name="action" value="remove_participant"
|
<button name="action" value="trash_participant"
|
||||||
title="{% trans 'Teilnehmer jetzt löschen' %}
|
title="{% trans 'Eintrag in Papierkorb verschieben' %}"
|
||||||
({% trans 'erfolgt automatisch am' %} {{ participant.purge_at|date:'d. F Y'}})"
|
|
||||||
class="btn btn-link no-padding">
|
class="btn btn-link no-padding">
|
||||||
<span class="text-danger">{% bootstrap_icon 'remove-circle' %}</span>
|
<span class="text-danger">{% bootstrap_icon 'trash' %}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.charge and participant.paid and has_permission_payment %}
|
{% if event.charge and participant.paid and has_permission_payment %}
|
||||||
|
|
||||||
<button name="action" value="revoke_payment"
|
<button name="action" value="revoke_payment"
|
||||||
title="{% trans 'Geldeingang wurde bestätigt' %} - {% trans 'Bestätigung des Geldeingangs zurückziehen' %}"
|
title="{% trans 'Geldeingang wurde bestätigt' %} - {% trans 'Bestätigung des Geldeingangs zurückziehen' %}"
|
||||||
class="btn btn-link no-padding">
|
class="btn btn-link no-padding">
|
||||||
<span class="text-success">{% bootstrap_icon 'piggy-bank' %}</span>
|
<span class="text-success">{% bootstrap_icon 'piggy-bank' %}</span>
|
||||||
</button>
|
</button>
|
||||||
{% elif event.charge and participant.paid %}
|
{% elif event.charge and participant.paid %}
|
||||||
|
|
||||||
<span class="text-success" title="{% trans 'Geldeingang bestätigt' %}">
|
<span class="text-success" title="{% trans 'Geldeingang bestätigt' %}">
|
||||||
{% bootstrap_icon 'piggy-bank' %}
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
</span>
|
</span>
|
||||||
{% elif event.charge and has_permission_payment %}
|
{% elif event.charge and has_permission_payment %}
|
||||||
|
|
||||||
<button name="action" value="confirm_payment"
|
<button name="action" value="confirm_payment"
|
||||||
title="{% trans 'Geldeingang bestätigen' %}"
|
title="{% trans 'Geldeingang bestätigen' %}"
|
||||||
class="btn btn-link no-padding">
|
class="btn btn-link no-padding">
|
||||||
<span class="text-danger">{% bootstrap_icon 'piggy-bank' %}</span>
|
<span class="text-danger">{% bootstrap_icon 'piggy-bank' %}</span>
|
||||||
</button>
|
</button>
|
||||||
{% elif event.charge %}
|
{% elif event.charge %}
|
||||||
|
|
||||||
<span class="text-danger" title="{% trans 'Geldeingang unbestätigt' %}">
|
<span class="text-danger" title="{% trans 'Geldeingang unbestätigt' %}">
|
||||||
{% bootstrap_icon 'piggy-bank' %}
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
</span>
|
</span>
|
||||||
@@ -428,7 +415,7 @@ Wichtig: das System verschickt keine Bestätigung an dich oder den neuen Teilneh
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseParticipant_{{ participant.id }}"
|
<div id="collapseParticipant_{{ participant.id }}"
|
||||||
class="panel-collapse collapse {% if form.errors %}in{% endif %}"
|
class="panel-collapse collapse {% if form.errors %}in{% endif %}"
|
||||||
@@ -472,6 +459,157 @@ Wichtig: das System verschickt keine Bestätigung an dich oder den neuen Teilneh
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<div class="pull-right text-info" style="margin-right: 1em;"
|
||||||
|
title="Wenn du Anmeldungen mit einem Klick auf das Plus- oder Minus-Symbol 'bearbeitest', oder Teilnehmer aus der Teilnehmerliste entfernst, dann sind diese Einträge danach noch im Papierkorb zu finden.
|
||||||
|
">
|
||||||
|
{% bootstrap_icon 'question-sign' %}
|
||||||
|
</div>
|
||||||
|
<h4>{% trans 'Papierkorb' %}</h4>
|
||||||
|
<div class="panel-group" id="form-accordion-trash" role="tablist" aria-multiselectable="true">
|
||||||
|
{% if registrations_support and registrations_answered %}
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div id="headingAnsweredRegistrations" class="panel-heading" role="tab">
|
||||||
|
<div class="pull-right text-info" title="Wenn du unter 'Anmeldungen' auf das Plus- oder Minus-Symbol geklickt hast, dann sind diese Einträge hier zu sehen.
|
||||||
|
">
|
||||||
|
{% bootstrap_icon 'question-sign' %}
|
||||||
|
</div>
|
||||||
|
<h5 class="panel-title">
|
||||||
|
<a role="button" href="#collapseAnsweredRegistrations"
|
||||||
|
data-toggle="collapse"
|
||||||
|
aria-expanded="true" aria-controls="collapseAnsweredRegistrations">
|
||||||
|
<span class="caret"></span> {% trans 'Bearbeitete Anmeldungen' %}
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div id="collapseAnsweredRegistrations" class="panel-collapse collapse"
|
||||||
|
role="tabpanel" aria-labelledby="headingAnsweredRegistrations">
|
||||||
|
<div class="panel-body">
|
||||||
|
{% for registration in registrations_answered %}
|
||||||
|
<div>
|
||||||
|
<button disabled="disabled"
|
||||||
|
class="btn btn-link no-padding" title="Bei dieser Anmeldung hast du bereits
|
||||||
|
am {{ registration.status.updated_at|date:'d. F Y, G:i' }}
|
||||||
|
auf {% if registration.status.accepted %}Plus{% else %}Minus{% endif %} geklickt.
|
||||||
|
">
|
||||||
|
<span class="{% if registration.status.accepted %}text-success{% else %}text-muted{% endif %}">{% bootstrap_icon 'plus-sign' %}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button disabled="disabled"
|
||||||
|
class="btn btn-link no-padding" title="Bei dieser Anmeldung hast du bereits
|
||||||
|
am {{ registration.status.updated_at|date:'d. F Y, G:i' }}
|
||||||
|
auf {% if registration.status.accepted %}Plus{% else %}Minus{% endif %} geklickt.
|
||||||
|
">
|
||||||
|
<span class="{% if not registration.status.accepted %}text-danger{% else %}text-muted{% endif %}">{% bootstrap_icon 'minus-sign' %}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="text-muted">
|
||||||
|
{{ registration.get_full_name }}
|
||||||
|
(<a href="mailto:{{ registration.email_address }}">{{ registration.email_address }}</a>,
|
||||||
|
{{ registration.phone_number }})
|
||||||
|
|
||||||
|
<span title="Anmeldezeitpunkt">
|
||||||
|
{% bootstrap_icon 'time' %}
|
||||||
|
{{ registration.created_at|date:'d. F Y, G:i' }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span title="{{ registration.get_info }}">
|
||||||
|
{% bootstrap_icon 'info-sign' %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if has_permission_update_registration %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<form action="" method="post" class="form-inline">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="registration" value="{{ registration.id }}">
|
||||||
|
<button type="submit" name="action" value="untrash_registration"
|
||||||
|
class="btn btn-link no-padding"
|
||||||
|
title="{% trans 'Eintrag in Anmeldungen zurückholen' %}">
|
||||||
|
<span class="text-danger">{% bootstrap_icon 'repeat' %}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if participants_trash %}
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div id="headingTrashedParticipants" class="panel-heading" role="tab">
|
||||||
|
<div class="pull-right text-info" title="Wenn du Teilnehmer deiner Teilnehmerliste hinzugefügt hast und sie später wieder entfernt hast, dann tauchen diese Einträge hier auf.">
|
||||||
|
{% bootstrap_icon 'question-sign' %}
|
||||||
|
</div>
|
||||||
|
<h5 class="panel-title">
|
||||||
|
<a role="button" href="#collapseTrashedParticipants"
|
||||||
|
data-toggle="collapse"
|
||||||
|
aria-expanded="true" aria-controls="collapseTrashedParticipants">
|
||||||
|
<span class="caret"></span> {% trans 'Gelöschte Teilnehmer' %}
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div id="collapseTrashedParticipants" class="panel-collapse collapse"
|
||||||
|
role="tabpanel" aria-labelledby="headingTrashedParticipants">
|
||||||
|
<div class="panel-body">
|
||||||
|
{% for participant in participants_trash %}
|
||||||
|
<div>
|
||||||
|
<button disabled="disabled"
|
||||||
|
class="btn btn-link no-padding" title="Diesen Teilnehmer hast du
|
||||||
|
am {{ participant.trashed_at|date:'d. F Y, G:i' }}
|
||||||
|
von Position {{ participant.position }} der Teilnehmerliste entfernt.
|
||||||
|
">
|
||||||
|
<span class="text-danger">{% bootstrap_icon 'trash' %}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="text-muted">
|
||||||
|
{{ participant.get_full_name }}
|
||||||
|
(<a href="mailto:{{ participant.email_address }}">{{ participant.email_address }}</a>,
|
||||||
|
{{ participant.phone_number }})
|
||||||
|
|
||||||
|
<span title="{{ participant.get_info }}
|
||||||
|
{% trans 'Zeitpunkt der automatischen Löschung' %}: {{ participant.purge_at|date:'d. F Y' }}">
|
||||||
|
{% bootstrap_icon 'info-sign' %}
|
||||||
|
</span>
|
||||||
|
{% if event.charge and participant.paid %}
|
||||||
|
|
||||||
|
<span class="text-success" title="{% trans 'Geldeingang bestätigt' %}">
|
||||||
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
|
</span>
|
||||||
|
{% elif event.charge %}
|
||||||
|
|
||||||
|
<span class="text-danger" title="{% trans 'Geldeingang unbestätigt' %}">
|
||||||
|
{% bootstrap_icon 'piggy-bank' %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if has_permission_update_participants %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<form action="" method="post" class="form-inline">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="id" value="{{ participant.id }}">
|
||||||
|
<button name="action" value="untrash_participant"
|
||||||
|
title="{% trans 'Eintrag in Teilnehmerliste zurückholen' %}"
|
||||||
|
class="btn btn-link no-padding">
|
||||||
|
<span class="text-danger">{% bootstrap_icon 'repeat' %}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if not registrations_answered and not participants_trash %}
|
||||||
|
<span class="text-muted small">{% trans 'Der Papierkorb ist leer.' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page-container-fluid %}
|
{% endblock page-container-fluid %}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from .models import Participant
|
from .models import Participant, TrashedParticipant
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -11,3 +11,6 @@ def purge_participants():
|
|||||||
for p in Participant.objects.filter(purge_at__lte=now):
|
for p in Participant.objects.filter(purge_at__lte=now):
|
||||||
logger.info('Purge participant \'%s\'', p)
|
logger.info('Purge participant \'%s\'', p)
|
||||||
p.delete()
|
p.delete()
|
||||||
|
for p in TrashedParticipant.objects.filter(purge_at__lte=now):
|
||||||
|
logger.info('Purge participant from trash \'%s\'', p)
|
||||||
|
p.delete()
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
|
|||||||
|
|
||||||
participants = event.participants.all()
|
participants = event.participants.all()
|
||||||
context['participants'] = participants
|
context['participants'] = participants
|
||||||
|
participants_trash = event.trashed_participants.all()
|
||||||
|
context['participants_trash'] = participants_trash
|
||||||
|
|
||||||
if participants.count() > 1:
|
if participants.count() > 1:
|
||||||
email_list = [u'"{}" <{}>'.format(p.get_full_name(), p.email_address) for p in participants]
|
email_list = [u'"{}" <{}>'.format(p.get_full_name(), p.email_address) for p in participants]
|
||||||
@@ -231,9 +233,11 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
|
|||||||
context['registrations_support'] = registrations_support
|
context['registrations_support'] = registrations_support
|
||||||
if registrations_support:
|
if registrations_support:
|
||||||
registrations_all = event.registrations.all()
|
registrations_all = event.registrations.all()
|
||||||
registrations_pending = registrations_all.filter(answered=False)
|
registrations_pending = registrations_all.filter(~Q(status__answered=True))
|
||||||
context['registrations_pending'] = registrations_pending
|
registrations_answered = registrations_all.filter(status__answered=True)
|
||||||
context['registrations_all'] = registrations_all
|
context['registrations_all'] = registrations_all
|
||||||
|
context['registrations_pending'] = registrations_pending
|
||||||
|
context['registrations_answered'] = registrations_answered
|
||||||
context['registrations'] = registrations_all
|
context['registrations'] = registrations_all
|
||||||
|
|
||||||
return context
|
return context
|
||||||
@@ -289,13 +293,16 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
|
|||||||
'purge_at': registration.purge_at,
|
'purge_at': registration.purge_at,
|
||||||
}
|
}
|
||||||
participant = models.Participant.objects.create(**data)
|
participant = models.Participant.objects.create(**data)
|
||||||
registration.answered = True
|
registration.accepted()
|
||||||
registration.save()
|
|
||||||
messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name())))
|
messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name())))
|
||||||
|
|
||||||
def _reject_registration(self, registration):
|
def _reject_registration(self, registration):
|
||||||
registration.answered = True
|
registration.rejected()
|
||||||
registration.save()
|
|
||||||
|
def _reset_registration(self, registration):
|
||||||
|
registration.status.accepted = None
|
||||||
|
registration.status.answered = False
|
||||||
|
registration.status.save()
|
||||||
|
|
||||||
def _swap_participants_position(self, participant1, participant2):
|
def _swap_participants_position(self, participant1, participant2):
|
||||||
event = participant1.event
|
event = participant1.event
|
||||||
@@ -345,6 +352,14 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
|
|||||||
self._reject_registration(registration)
|
self._reject_registration(registration)
|
||||||
else:
|
else:
|
||||||
raise FieldDoesNotExist('Event has no registrations')
|
raise FieldDoesNotExist('Event has no registrations')
|
||||||
|
elif action == 'untrash_registration':
|
||||||
|
self.enforce_permission(event, permission='update-registration')
|
||||||
|
if hasattr(event, 'registrations'):
|
||||||
|
registration_id = request.POST.get('registration')
|
||||||
|
registration = event.registrations.get(id=registration_id)
|
||||||
|
self._reset_registration(registration)
|
||||||
|
else:
|
||||||
|
raise FieldDoesNotExist('Event has no registrations')
|
||||||
elif action == 'confirm_payment':
|
elif action == 'confirm_payment':
|
||||||
self.enforce_permission(event, permission='payment')
|
self.enforce_permission(event, permission='payment')
|
||||||
participant_id = request.POST.get('id')
|
participant_id = request.POST.get('id')
|
||||||
@@ -357,18 +372,33 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView):
|
|||||||
participant = event.participants.get(id=participant_id)
|
participant = event.participants.get(id=participant_id)
|
||||||
participant.paid = False
|
participant.paid = False
|
||||||
participant.save()
|
participant.save()
|
||||||
elif action == 'remove_participant':
|
elif action == 'trash_participant':
|
||||||
self.enforce_permission(event, permission='update-participants')
|
self.enforce_permission(event, permission='update-participants')
|
||||||
participant_id = request.POST.get('id')
|
participant_id = request.POST.get('id')
|
||||||
participant = event.participants.get(id=participant_id)
|
participant = event.participants.get(id=participant_id)
|
||||||
full_name = participant.get_full_name()
|
participants_below = event.participants.filter(position__gt=participant.position)
|
||||||
position = participant.position
|
|
||||||
|
data = participant.get_data_dict()
|
||||||
|
trashed = models.TrashedParticipant.objects.create(**data)
|
||||||
participant.delete()
|
participant.delete()
|
||||||
qs = event.participants.filter(position__gt=position)
|
|
||||||
for participant in qs:
|
for participant in participants_below:
|
||||||
participant.position -= 1
|
participant.position -= 1
|
||||||
participant.save()
|
participant.save()
|
||||||
messages.success(request, _(u'Teilnehmer gelöscht: {}'.format(full_name)))
|
|
||||||
|
messages.success(request, _(u'Teilnehmer in den Papierkorb verschoben: {}'.format(trashed.get_full_name())))
|
||||||
|
elif action == 'untrash_participant':
|
||||||
|
self.enforce_permission(event, permission='update-participants')
|
||||||
|
trashed_id = request.POST.get('id')
|
||||||
|
trashed = event.trashed_participants.get(id=trashed_id)
|
||||||
|
trashed.position = event.participants.count() + 1
|
||||||
|
|
||||||
|
data = trashed.get_data_dict()
|
||||||
|
del data['trashed_at']
|
||||||
|
participant = models.Participant.objects.create(**data)
|
||||||
|
trashed.delete()
|
||||||
|
|
||||||
|
messages.success(request, _(u'Teilnehmer zurückgeholt: {}'.format(participant.get_full_name())))
|
||||||
elif action == 'moveup_participant':
|
elif action == 'moveup_participant':
|
||||||
self.enforce_permission(event, permission='update-participants')
|
self.enforce_permission(event, permission='update-participants')
|
||||||
participant_id = request.POST.get('id')
|
participant_id = request.POST.get('id')
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Registration
|
from .models import Registration, RegistrationStatus
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationStatusInline(admin.StackedInline):
|
||||||
|
model = RegistrationStatus
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Registration)
|
@admin.register(Registration)
|
||||||
class RegistrationAdmin(admin.ModelAdmin):
|
class RegistrationAdmin(admin.ModelAdmin):
|
||||||
pass
|
inlines = [RegistrationStatusInline]
|
||||||
|
|||||||
39
dav_registration/migrations/0006_auto_20201203_1144.py
Normal file
39
dav_registration/migrations/0006_auto_20201203_1144.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.29 on 2020-12-03 10:44
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dav_registration', '0005_auto_20201015_1738'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationStatus',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('answered', models.BooleanField(default=False, verbose_name='Durch Tourleitung beantwortet')),
|
||||||
|
('accepted', models.NullBooleanField(verbose_name='Zusage erteilt')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Anmeldungsstatus',
|
||||||
|
'verbose_name_plural': 'Anmeldungsstati',
|
||||||
|
'ordering': ['updated_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='registration',
|
||||||
|
name='answered',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='registrationstatus',
|
||||||
|
name='registration',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='status', to='dav_registration.Registration'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -72,8 +72,6 @@ class Registration(models.Model):
|
|||||||
verbose_name=_('Einwilligung zur Datenspeicherung'))
|
verbose_name=_('Einwilligung zur Datenspeicherung'))
|
||||||
purge_at = models.DateTimeField(_('Zeitpunkt der Datenlöschung'))
|
purge_at = models.DateTimeField(_('Zeitpunkt der Datenlöschung'))
|
||||||
|
|
||||||
answered = models.BooleanField(_('Durch Tourleitung beantwortet'), default=False)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pk2hexstr(pk):
|
def pk2hexstr(pk):
|
||||||
return hex(pk * 113)[2:] # 113 has no meaning, but it produce nice looking hex codes.
|
return hex(pk * 113)[2:] # 113 has no meaning, but it produce nice looking hex codes.
|
||||||
@@ -151,9 +149,29 @@ Anmerkung:
|
|||||||
super(Registration, self).save(**kwargs)
|
super(Registration, self).save(**kwargs)
|
||||||
|
|
||||||
if creating:
|
if creating:
|
||||||
|
status = RegistrationStatus(registration=self)
|
||||||
|
status.save()
|
||||||
logger.info('Registration stored: %s', self)
|
logger.info('Registration stored: %s', self)
|
||||||
signals.registration_created.send(sender=self.__class__, registration=self)
|
signals.registration_created.send(sender=self.__class__, registration=self)
|
||||||
|
|
||||||
|
def answered(self, accepted):
|
||||||
|
if accepted is not True and accepted is not False:
|
||||||
|
raise ValueError('boolean parameter expected')
|
||||||
|
if hasattr(self, 'status'):
|
||||||
|
status = self.status
|
||||||
|
else:
|
||||||
|
status = RegistrationStatus(registration=self)
|
||||||
|
|
||||||
|
status.accepted = accepted
|
||||||
|
status.answered = True
|
||||||
|
status.save()
|
||||||
|
|
||||||
|
def accepted(self):
|
||||||
|
return self.answered(accepted=True)
|
||||||
|
|
||||||
|
def rejected(self):
|
||||||
|
return self.answered(accepted=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def calc_purge_at(cls, event):
|
def calc_purge_at(cls, event):
|
||||||
if event.alt_last_day:
|
if event.alt_last_day:
|
||||||
@@ -179,3 +197,29 @@ Anmerkung:
|
|||||||
purge_date = july_nextyear
|
purge_date = july_nextyear
|
||||||
|
|
||||||
return timezone.make_aware(datetime.datetime.combine(purge_date, midnight))
|
return timezone.make_aware(datetime.datetime.combine(purge_date, midnight))
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class RegistrationStatus(models.Model):
|
||||||
|
registration = models.OneToOneField(Registration, on_delete=models.CASCADE, related_name='status')
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
answered = models.BooleanField(_('Durch Tourleitung beantwortet'), default=False)
|
||||||
|
accepted = models.NullBooleanField(_('Zusage erteilt'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Anmeldungsstatus')
|
||||||
|
verbose_name_plural = _('Anmeldungsstati')
|
||||||
|
ordering = ['updated_at']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} (Updated: {})'.format(self.registration, self.updated_at.strftime('%d.%m.%Y %H:%M:%S'))
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.answered and self.accepted is None:
|
||||||
|
raise ValidationError({'accepted': 'if answered is true, accepted must not be none'})
|
||||||
|
elif not self.answered and self.accepted is not None:
|
||||||
|
raise ValidationError({'answered': 'if answered is false, accepted must be none'})
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
self.full_clean()
|
||||||
|
super(RegistrationStatus, self).save(**kwargs)
|
||||||
|
|||||||
Reference in New Issue
Block a user