members: rejection of invitations

pull/73/head
Christian Merten 1 year ago
parent d512d2b14c
commit e2d660ed1e
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -27,20 +27,37 @@ INVITE_TEXT = """Hallo {name},
wir haben gute Neuigkeiten für dich. Es ist ein Platz in der Jugendgruppe {group_name} {group_link}freigeworden.
Wir treffen uns jeden {weekday} von {start_time} bis {end_time} Uhr.
Wir brauchen jetzt noch ein paar Informationen von dir und deine Anmeldebestätigung. Das kannst du alles über folgenden
Link erledigen:
Wir brauchen jetzt noch ein paar Informationen von dir und deine Anmeldebestätigung. Die lädst du hier herunter,
lässt es von deinen Eltern ausfüllen, unterschreiben und lädst ein Foto davon in unserem Anmeldeformular hoch.
Das kannst du alles über folgenden Link erledigen:
{link}
Du siehst dort auch die Daten, die du bei deiner Eintragung auf die Warteliste angegeben hast. Bitte
überprüfe, ob die Daten noch stimmen und ändere sie bei Bedarf ab.
Falls du zu dem obigen Termin keine Zeit hast oder dich ganz von der Warteliste abmelden möchtest,
lehne bitte diese Einladung unter folgendem Link ab:
{invitation_reject_link}
Bei Fragen, wende dich gerne an %(RESPONSIBLE_MAIL)s.
Viele Grüße
Deine JDAV %(SEKTION)s""" % { 'SEKTION': SEKTION, 'RESPONSIBLE_MAIL': RESPONSIBLE_MAIL }
LEAVE_WAITINGLIST_TEXT = """Hallo {name},
du hast dich erfolgreich von der Warteliste abgemeldet. Falls du zu einem späteren
Zeitpunkt wieder der Warteliste beitreten möchtest, kannst du das über unsere Webseite machen.
Falls du dich nicht selbst abgemeldet hast, wende dich bitte umgehend an %(RESPONSIBLE_MAIL)s.
Viele Grüße
Deine JDAV %(SEKTION)s""" % { 'SEKTION': SEKTION, 'RESPONSIBLE_MAIL': RESPONSIBLE_MAIL }
WAIT_CONFIRMATION_TEXT = """Hallo {name},
leider können wir dir zur Zeit noch keinen Platz in einer Jugendgruppe anbieten. Da wir

@ -64,11 +64,14 @@ def get_echo_link(member):
return prepend_base_url("/members/echo?key={}".format(key))
def get_registration_link(waiter):
key = waiter.generate_registration_key()
def get_registration_link(key):
return prepend_base_url("/members/registration?key={}".format(key))
def get_invitation_reject_link(key):
return prepend_base_url("/members/waitinglist/invitation/reject?key={}".format(key))
def get_wait_confirmation_link(waiter):
key = waiter.generate_wait_confirmation_key()
return prepend_base_url("/members/waitinglist/confirm?key={}".format(key))

@ -36,7 +36,8 @@ from .models import (Member, Group, Freizeit, MemberNoteList, NewMemberOnList, K
MemberWaitingList, LJPProposal, Intervention, PermissionMember,
PermissionGroup, MemberTraining, TrainingCategory,
KlettertreffAttendee, ActivityCategory, EmergencyContact,
annotate_activity_score, RegistrationPassword, MemberUnconfirmedProxy)
annotate_activity_score, RegistrationPassword, MemberUnconfirmedProxy,
InvitationToGroup)
from finance.models import Statement, BillOnExcursionProxy
from mailer.mailutils import send as send_mail, get_echo_link
from django.conf import settings
@ -414,16 +415,28 @@ class WaiterInviteForm(forms.Form):
label=_('Group'))
class InvitationToGroupAdmin(CommonAdminInlineMixin, admin.TabularInline):
model = InvitationToGroup
fields = ['group', 'date', 'status']
readonly_fields = ['group', 'date', 'status']
extra = 0
can_delete = False
def has_add_permission(self, request, obj=None):
return False
class MemberWaitingListAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['prename', 'lastname', 'email', 'birth_date', 'gender', 'application_text',
'application_date', 'comments', 'invited_for_group',
'application_date', 'comments',
'sent_reminders']
list_display = ('name', 'birth_date', 'age', 'application_date', 'confirmed_mail',
'waiting_confirmed', 'sent_reminders')
search_fields = ('prename', 'lastname', 'email')
list_filter = ('confirmed_mail',)
actions = ['ask_for_registration', 'ask_for_wait_confirmation']
readonly_fields= ['invited_for_group', 'application_date', 'sent_reminders']
inlines = [InvitationToGroupAdmin]
readonly_fields= ['application_date', 'sent_reminders']
def has_add_permission(self, request, obj=None):
return False

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@
# Generated by Django 4.0.1 on 2024-11-17 21:32
from django.db import migrations, models
import django.db.models.deletion
import members.models
class Migration(migrations.Migration):
dependencies = [
('members', '0020_alter_freizeit_options_freizeit_description_and_more'),
]
operations = [
migrations.CreateModel(
name='InvitationToGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(auto_now=True, verbose_name='Invitation date')),
('rejected', models.BooleanField(default=False, verbose_name='Invitation rejected')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.group', verbose_name='Group')),
('waiter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.memberwaitinglist', verbose_name='Waiter')),
('key', models.CharField(default=members.models.gen_key, max_length=32)),
],
options={
'verbose_name': 'Invitation to group',
'verbose_name_plural': 'Invitations to groups',
},
),
migrations.RemoveField(
model_name='memberwaitinglist',
name='invited_for_group',
),
]

@ -14,7 +14,8 @@ from django.contrib.contenttypes.models import ContentType
from utils import RestrictedFileField
import os
from mailer.mailutils import send as send_mail, get_mail_confirmation_link,\
prepend_base_url, get_registration_link, get_wait_confirmation_link
prepend_base_url, get_registration_link, get_wait_confirmation_link,\
get_invitation_reject_link
from django.contrib.auth.models import User
from django.conf import settings
from django.core.validators import MinValueValidator
@ -689,6 +690,35 @@ class MemberUnconfirmedProxy(Member):
return self.name
def gen_key():
return uuid.uuid4().hex
class InvitationToGroup(models.Model):
"""An invitation of a waiter to a group."""
waiter = models.ForeignKey('MemberWaitingList', verbose_name=_('Waiter'), on_delete=models.CASCADE)
group = models.ForeignKey(Group, verbose_name=_('Group'), on_delete=models.CASCADE)
date = models.DateField(auto_now=True, verbose_name=_('Invitation date'))
rejected = models.BooleanField(verbose_name=_('Invitation rejected'), default=False)
key = models.CharField(max_length=32, default=gen_key)
class Meta:
verbose_name = _('Invitation to group')
verbose_name_plural = _('Invitations to groups')
def is_expired(self):
return self.date < (timezone.now() - timezone.timedelta(days=30)).date()
def status(self):
if self.rejected:
return _('Rejected')
elif self.is_expired():
return _('Expired')
else:
return _('Undecided')
status.short_description = _('Status')
class MemberWaitingList(Person):
"""A participant on the waiting list"""
@ -710,12 +740,6 @@ class MemberWaitingList(Person):
registration_key = models.CharField(max_length=32, default="")
registration_expire = models.DateTimeField(default=timezone.now)
invited_for_group = models.ForeignKey(Group,
null=True,
blank=True,
default=None,
verbose_name=_('Invited for group'),
on_delete=models.SET_NULL)
class Meta(CommonModel.Meta):
verbose_name = _('Waiter')
verbose_name_plural = _('Waiters')
@ -783,14 +807,13 @@ class MemberWaitingList(Person):
self.save()
return self.wait_confirmation_key
def generate_registration_key(self):
self.registration_key = uuid.uuid4().hex
self.registration_expire = timezone.now() + timezone.timedelta(days=30)
self.save()
return self.registration_key
def may_register(self, key):
return self.registration_key == key and timezone.now() < self.registration_expire
print("may_register", key)
try:
invitation = InvitationToGroup.objects.get(key=key)
return self.pk == invitation.waiter.pk and timezone.now() < invitation.date + timezone.timedelta(days=30)
except InvitationToGroup.DoesNotExist:
return False
def invite_to_group(self, group):
if group.show_website:
@ -801,6 +824,8 @@ class MemberWaitingList(Person):
weekday = WEEKDAYS[group.weekday][1] if group.weekday != None else WEEKDAYS[0][1]
start_time = group.start_time.strftime('%H:%M') if group.start_time != None else "14:00"
end_time = group.end_time.strftime('%H:%M') if group.end_time != None else "16:00"
invitation = InvitationToGroup(group=group, waiter=self)
invitation.save()
self.send_mail(_("Invitation to trial group meeting"),
settings.INVITE_TEXT.format(name=self.prename,
weekday=weekday,
@ -808,7 +833,14 @@ class MemberWaitingList(Person):
end_time=end_time,
group_name=group.name,
group_link=group_link,
link=get_registration_link(self)))
link=get_registration_link(invitation.key),
invitation_reject_link=get_invitation_reject_link(invitation.key)))
def unregister(self):
"""Delete the waiter and inform them about the deletion via email."""
self.send_mail(_("Unregistered from waiting list"),
settings.LEAVE_WAITINGLIST_TEXT.format(name=self.prename))
self.delete()
class NewMemberOnList(CommonModel):

@ -0,0 +1,15 @@
{% extends "members/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}
{% trans "Reject invitation" %}
{% endblock %}
{% block content %}
<h1>{% trans "Reject invitation" %}</h1>
<p>{% trans "This invitation is invalid or expired." %}</p>
{% endblock %}

@ -0,0 +1,31 @@
{% extends "members/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}
{% trans "Reject invitation" %}
{% endblock %}
{% block content %}
<h1>{% trans "Reject invitation" %}</h1>
<p>{% blocktrans %}You were invited to a trial group meeting of the group {{ groupname }}. On this
page you can reject this invitation.{% endblocktrans %}
</p>
<p>{% blocktrans %}You may either reject this specific invitation, because
the time of the group does not fit your calendear, or unregister from the mailing list altogether.{% endblocktrans %}
</p>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="key" value="{{invitation.key}}">
<p>
<input type="submit" name="reject_invitation"
value="{% trans "Reject this invitation and stay on the waitinglist" %}"/>
<input type="submit" name="leave_waitinglist" value="{% trans "Leave the waitinglist" %}"/>
</p>
</form>
{% endblock %}

@ -0,0 +1,25 @@
{% extends "members/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}
{% trans "Invitation rejected" %}
{% endblock %}
{% block content %}
<h1>{% trans "Invitation rejected" %}</h1>
<p>
{% if leave_waitinglist %}
{% blocktrans %}You successfully unregistered from the waitinglist. If you want to rejoin the waitinglist
at a later time, please do so on our website.
{% endblocktrans %}
{% else %}
{% blocktrans %}You successfully rejected the invitation to a trial group meeting of the group
{{ groupname }}. If there is a slot in a different group available, you will receive a new invitation.
{% endblocktrans %}
{% endif %}
</p>
{% endblock %}

@ -8,6 +8,7 @@ urlpatterns = [
re_path(r'^registration', views.invited_registration , name='registration'),
re_path(r'^register', views.register , name='register'),
re_path(r'^waitinglist/confirm', views.confirm_waiting , name='confirm_waiting'),
re_path(r'^waitinglist/invitation/reject', views.reject_invitation , name='reject_invitation'),
re_path(r'^waitinglist', views.register_waiting_list , name='register_waiting_list'),
re_path(r'^mail/confirm', views.confirm_mail , name='confirm_mail'),
]

@ -4,7 +4,7 @@ from django.http import HttpResponseRedirect
from django.forms import ModelForm, TextInput, DateInput, BaseInlineFormSet,\
inlineformset_factory, HiddenInput, FileInput
from members.models import Member, RegistrationPassword, MemberUnconfirmedProxy, MemberWaitingList, Group,\
confirm_mail_by_key, EmergencyContact
confirm_mail_by_key, EmergencyContact, InvitationToGroup
from django.urls import reverse
from django.utils import timezone
from django.conf import settings
@ -229,9 +229,10 @@ def register(request):
return render_register_wrong_password(request)
elif waiter_key:
try:
waiter = MemberWaitingList.objects.get(registration_key=waiter_key)
group = waiter.invited_for_group
except MemberWaitingList.DoesNotExist:
invitation = InvitationToGroup.objects.get(key=waiter_key)
waiter = invitation.waiter
group = invitation.group
except InvitationToGroup.DoesNotExist:
return render_register_failed(request)
# group must not be None
@ -323,14 +324,13 @@ def invited_registration(request):
if request.method == 'GET' and 'key' in request.GET:
try:
key = request.GET['key']
waiter = MemberWaitingList.objects.get(registration_key=key)
if not waiter.may_register(key):
raise KeyError
if not waiter.invited_for_group:
invitation = InvitationToGroup.objects.get(key=key)
waiter = invitation.waiter
if invitation.is_expired() or invitation.rejected:
raise KeyError
form = MemberRegistrationForm(instance=waiter)
return render_register(request, group=waiter.invited_for_group, form=form, waiter_key=key)
except MemberWaitingList.DoesNotExist:
return render_register(request, group=invitation.group, form=form, waiter_key=key)
except InvitationToGroup.DoesNotExist:
return render_invited_registration_failed(request, _("invalid"))
except KeyError:
return render_invited_registration_failed(request, _("expired"))
@ -346,6 +346,50 @@ def render_invited_registration_failed(request, reason=""):
return render(request, 'members/invited_registration_failed.html', context)
def render_reject_invitation(request, invitation):
return render(request, 'members/reject_invitation.html',
{'invitation': invitation,
'groupname': invitation.group.name})
def render_reject_invalid(request):
return render(request, 'members/reject_invalid.html')
def render_reject_success(request, invitation, leave_waitinglist=False):
return render(request, 'members/reject_success.html',
{'invitation': invitation,
'leave_waitinglist': leave_waitinglist,
'groupname': invitation.group.name})
def reject_invitation(request):
if request.method == 'GET' and 'key' in request.GET:
key = request.GET['key']
try:
invitation = InvitationToGroup.objects.get(key=key)
if invitation.rejected or invitation.is_expired():
raise ValueError
return render_reject_invitation(request, invitation)
except (ValueError, InvitationToGroup.DoesNotExist):
return render_reject_invalid(request)
if request.method != 'POST' or 'key' not in request.POST:
return render_reject_invalid(request)
key = request.POST['key']
try:
invitation = InvitationToGroup.objects.get(key=key)
except InvitationToGroup.DoesNotExist:
return render_reject_invalid(request)
if 'reject_invitation' in request.POST:
invitation.rejected = True
invitation.save()
return render_reject_success(request, invitation)
elif 'leave_waitinglist' in request.POST:
invitation.waiter.unregister()
return render_reject_success(request, invitation, leave_waitinglist=True)
return render_reject_invalid(request)
def confirm_waiting(request):
if request.method == 'GET' and 'key' in request.GET:
key = request.GET['key']

Loading…
Cancel
Save