feat(members/waitinglist): add confirm link in invitation mail and more notifications

MK/finance_workflow
Christian Merten 8 months ago
parent 0535cce70f
commit d913c8049d
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -40,6 +40,28 @@ aber weiterhin auf der Warteliste.
Viele Grüße Viele Grüße
Dein KOMPASS""") Dein KOMPASS""")
GROUP_INVITATION_CONFIRMED_TEXT = get_text('group_invitation_confirmed',
default="""Hallo {name},
{waiter} hat die Einladung zu einer Schnupperstunde bei der Gruppe {group} angenommen.
Viele Grüße
Dein KOMPASS""")
TRIAL_GROUP_MEETING_CONFIRMED_TEXT = get_text('trial_group_meeting_confirmed',
default="""Hallo {name},
deine Teilnahme an der Schnupperstunde der Gruppe {group} wurde erfolgreich bestätigt.
{timeinfo}
Für alle weiteren Absprachen, kontaktiere bitte die Jugendleiter*innen der Gruppe
unter {contact_email}.
Viele Grüße
Deine JDAV %(SEKTION)s""" % { 'SEKTION': SEKTION })
GROUP_TIME_AVAILABLE_TEXT = get_text('group_time_available', GROUP_TIME_AVAILABLE_TEXT = get_text('group_time_available',
default="""Die Gruppenstunde findet jeden {weekday} von {start_time} bis {end_time} Uhr statt.""") default="""Die Gruppenstunde findet jeden {weekday} von {start_time} bis {end_time} Uhr statt.""")
@ -51,7 +73,11 @@ INVITE_TEXT = get_text('invite', default="""Hallo {{name}},
wir haben gute Neuigkeiten für dich. Es ist ein Platz in der Jugendgruppe {group_name} {group_link}freigeworden. wir haben gute Neuigkeiten für dich. Es ist ein Platz in der Jugendgruppe {group_name} {group_link}freigeworden.
{group_time} {group_time}
Bitte kontaktiere die Gruppenleitung ({contact_email}) für alle weiteren Absprachen. Wenn du an der Schnupperstunde teilnehmen möchtest, bestätige deine Teilnahme bitte unter folgendem Link:
{{invitation_confirm_link}}
Für alle weiteren Absprachen, kontaktiere bitte die Gruppenleitung ({contact_email}).
Wenn du nach der Schnupperstunde beschließt der Gruppe beizutreten, benötigen wir noch ein paar Wenn du nach der Schnupperstunde beschließt der Gruppe beizutreten, benötigen wir noch ein paar
Informationen und eine schriftliche Anmeldebestätigung von dir. Das kannst du alles über folgenden Link erledigen: Informationen und eine schriftliche Anmeldebestätigung von dir. Das kannst du alles über folgenden Link erledigen:

@ -76,6 +76,10 @@ def get_invitation_reject_link(key):
return prepend_base_url("/members/waitinglist/invitation/reject?key={}".format(key)) return prepend_base_url("/members/waitinglist/invitation/reject?key={}".format(key))
def get_invitation_confirm_link(key):
return prepend_base_url("/members/waitinglist/invitation/confirm?key={}".format(key))
def get_wait_confirmation_link(waiter): def get_wait_confirmation_link(waiter):
key = waiter.generate_wait_confirmation_key() key = waiter.generate_wait_confirmation_key()
return prepend_base_url("/members/waitinglist/confirm?key={}".format(key)) return prepend_base_url("/members/waitinglist/confirm?key={}".format(key))

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-06 14:02+0200\n" "POT-Creation-Date: 2025-04-06 15:38+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -805,6 +805,15 @@ msgstr "%(waiter)s hat die Warteliste verlassen"
msgid "Group invitation rejected by %(waiter)s" msgid "Group invitation rejected by %(waiter)s"
msgstr "Einladung zur Schnupperstunde von %(waiter)s abgelehnt" msgstr "Einladung zur Schnupperstunde von %(waiter)s abgelehnt"
#: members/models.py
#, python-format
msgid "Group invitation confirmed by %(waiter)s"
msgstr "Teilnahme an Schnupperstunde von %(waiter)s bestätigt"
#: members/models.py
msgid "Trial group meeting confirmed"
msgstr "Teilnahme an Schnupperstunde bestätigt"
#: members/models.py #: members/models.py
msgid "Do you want to tell us something else?" msgid "Do you want to tell us something else?"
msgstr "Möchtest du uns noch etwas mitteilen?" msgstr "Möchtest du uns noch etwas mitteilen?"
@ -1655,6 +1664,60 @@ msgstr "Fähigkeitsniveau"
msgid "Save and confirm registration" msgid "Save and confirm registration"
msgstr "Speichern und Registrierung bestätigen" msgstr "Speichern und Registrierung bestätigen"
#: members/templates/members/confirm_invalid.html
msgid "Confirm invitation"
msgstr "Teilnahme bestätigen"
#: members/templates/members/confirm_invalid.html
#: members/templates/members/reject_invalid.html members/tests.py
msgid "This invitation is invalid or expired."
msgstr "Diese Einladung ist ungültig oder abgelaufen."
#: members/templates/members/confirm_invitation.html
msgid "Confirm trial group meeting invitation"
msgstr "Teilnahme bestätigen"
#: members/templates/members/confirm_invitation.html
#, python-format
msgid "You were invited to a trial group meeting of the group %(groupname)s."
msgstr ""
"Du wurdest zu einer Schnupperstunde in der Gruppe %(groupname)s eingeladen."
#: members/templates/members/confirm_invitation.html
msgid ""
"Do you want to take part in the trial group meeting? If yes, please confirm "
"your attendance by clicking on the following button."
msgstr ""
"Möchtest du an der Schnupperstunde teilnehmen? Falls ja, bitte bestätige "
"deine Teilnahme durch das Betätigen des folgenden Knopfes."
#: members/templates/members/confirm_invitation.html
msgid "Confirm trial group meeting"
msgstr "Teilnahme bestätigen"
#: members/templates/members/confirm_success.html
msgid "Invitation confirmed"
msgstr "Teilnahme bestätigt"
#: members/templates/members/confirm_success.html
#, python-format
msgid ""
"You successfully confirmed the invitation to the trial group meeting of the "
"group %(groupname)s."
msgstr ""
"Deine Teilnahme an der Schnupperstunde der Gruppe %(groupname)s wurde "
"erfolgreich bestätigt."
#: members/templates/members/confirm_success.html
msgid ""
"We have informed the group leaders about your confirmation. If for some "
"reason you can not make it,\n"
"please contact the group leaders at"
msgstr ""
"Wir haben die Jugendleiter*innen der Jugendgruppe über deine Bestätigung "
"informiert. Falls du doch nicht zur Schnupperstunde kommen kannst, "
"informiere bitte die Jugendleiter*innen unter"
#: members/templates/members/echo.html #: members/templates/members/echo.html
#: members/templates/members/echo_failed.html #: members/templates/members/echo_failed.html
#: members/templates/members/echo_password.html #: members/templates/members/echo_password.html
@ -1942,10 +2005,6 @@ msgstr ""
msgid "Reject invitation" msgid "Reject invitation"
msgstr "Einladung ablehnen" msgstr "Einladung ablehnen"
#: members/templates/members/reject_invalid.html members/tests.py
msgid "This invitation is invalid or expired."
msgstr "Diese Einladung ist ungültig oder abgelaufen."
#: members/templates/members/reject_invitation.html #: members/templates/members/reject_invitation.html
#, python-format #, python-format
msgid "" msgid ""
@ -2240,14 +2299,6 @@ msgstr "Ungültige Notfallkontakte"
#~ msgid "Good conduct certificate presentation needed" #~ msgid "Good conduct certificate presentation needed"
#~ msgstr "Vorlage Führungszeugnis notwendig" #~ msgstr "Vorlage Führungszeugnis notwendig"
#, python-format
#~ msgid ""
#~ "Do you want to reject the invitation to a trial group meeting of the\n"
#~ "group %(invitation.group.name)s?"
#~ msgstr ""
#~ "Möchtest du die Einladung zur Schnupperstunde bei der Gruppe "
#~ "%(invitation.group.name)s wirklich ablehnen?"
#~ msgid "Yes, reject invitation" #~ msgid "Yes, reject invitation"
#~ msgstr "Ja, Einladung ablehnen." #~ msgstr "Ja, Einladung ablehnen."

@ -18,7 +18,8 @@ from utils import RestrictedFileField, normalize_name
import os import os
from mailer.mailutils import send as send_mail, get_mail_confirmation_link,\ 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, get_invite_as_user_key, get_leave_waitinglist_link get_invitation_reject_link, get_invite_as_user_key, get_leave_waitinglist_link,\
get_invitation_confirm_link
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
@ -109,6 +110,14 @@ class Group(models.Model):
# return if the group has all relevant time slot information filled # return if the group has all relevant time slot information filled
return self.weekday and self.start_time and self.end_time return self.weekday and self.start_time and self.end_time
def get_time_info(self):
if self.has_time_info():
return settings.GROUP_TIME_AVAILABLE_TEXT.format(weekday=WEEKDAYS[self.weekday][1],
start_time=self.start_time.strftime('%H:%M'),
end_time=self.end_time.strftime('%H:%M'))
else:
return ""
def get_invitation_text_template(self): def get_invitation_text_template(self):
"""The text template used to invite waiters to this group. This contains """The text template used to invite waiters to this group. This contains
placeholders for the name of the waiter and personalized links.""" placeholders for the name of the waiter and personalized links."""
@ -117,9 +126,7 @@ class Group(models.Model):
else: else:
group_link = '' group_link = ''
if self.has_time_info(): if self.has_time_info():
group_time = settings.GROUP_TIME_AVAILABLE_TEXT.format(weekday=WEEKDAYS[self.weekday][1], group_time = self.get_time_info()
start_time=self.start_time.strftime('%H:%M'),
end_time=self.end_time.strftime('%H:%M'))
else: else:
group_time = settings.GROUP_TIME_UNAVAILABLE_TEXT.format(contact_email=self.contact_email) group_time = settings.GROUP_TIME_UNAVAILABLE_TEXT.format(contact_email=self.contact_email)
return settings.INVITE_TEXT.format(group_time=group_time, return settings.INVITE_TEXT.format(group_time=group_time,
@ -912,6 +919,21 @@ class InvitationToGroup(models.Model):
settings.DEFAULT_SENDING_MAIL, settings.DEFAULT_SENDING_MAIL,
recipient.email) recipient.email)
def send_confirm_notification_to(self, recipient):
send_mail(_('Group invitation confirmed by %(waiter)s') % {'waiter': self.waiter},
settings.GROUP_INVITATION_CONFIRMED_TEXT.format(name=recipient.prename,
waiter=self.waiter,
group=self.group),
settings.DEFAULT_SENDING_MAIL,
recipient.email)
def send_confirm_confirmation(self):
self.waiter.send_mail(_('Trial group meeting confirmed'),
settings.TRIAL_GROUP_MEETING_CONFIRMED_TEXT.format(name=self.waiter.prename,
group=self.group,
contact_email=self.group.contact_email,
timeinfo=self.group.get_time_info()))
def notify_left_waitinglist(self): def notify_left_waitinglist(self):
""" """
Inform youth leaders of the group and the inviter that the waiter left the waitinglist, Inform youth leaders of the group and the inviter that the waiter left the waitinglist,
@ -932,6 +954,18 @@ class InvitationToGroup(models.Model):
for jl in self.group.leiters.all(): for jl in self.group.leiters.all():
self.send_reject_notification_to(jl) self.send_reject_notification_to(jl)
def confirm(self):
"""Confirm this invitation. Informs the youth leaders of the group of the invitation."""
self.rejected = False
self.save()
# confirm the confirmation
self.send_confirm_confirmation()
# send notifications
if self.created_by:
self.send_confirm_notification_to(self.created_by)
for jl in self.group.leiters.all():
self.send_confirm_notification_to(jl)
class MemberWaitingList(Person): class MemberWaitingList(Person):
"""A participant on the waiting list""" """A participant on the waiting list"""
@ -1063,7 +1097,8 @@ class MemberWaitingList(Person):
self.send_mail(_("Invitation to trial group meeting"), self.send_mail(_("Invitation to trial group meeting"),
text_template.format(name=self.prename, text_template.format(name=self.prename,
link=get_registration_link(invitation.key), link=get_registration_link(invitation.key),
invitation_reject_link=get_invitation_reject_link(invitation.key)), invitation_reject_link=get_invitation_reject_link(invitation.key),
invitation_confirm_link=get_invitation_confirm_link(invitation.key)),
cc=group.contact_email.email) cc=group.contact_email.email)
def unregister(self): def unregister(self):

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

@ -0,0 +1,29 @@
{% extends "members/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}
{% trans "Confirm trial group meeting invitation" %}
{% endblock %}
{% block content %}
<h1>{% trans "Confirm trial group meeting invitation" %}</h1>
<p>
{% blocktrans %}You were invited to a trial group meeting of the group {{ groupname }}.{% endblocktrans %}
{{ timeinfo }}
</p>
<p>
{% blocktrans %}Do you want to take part in the trial group meeting? If yes, please confirm your attendance by clicking on the following button.{% endblocktrans %}
</p>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="key" value="{{invitation.key}}">
<input type="submit" name="confirm_invitation"
value="{% trans "Confirm trial group meeting" %}"/>
</form>
{% endblock %}

@ -0,0 +1,23 @@
{% extends "members/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}
{% trans "Invitation confirmed" %}
{% endblock %}
{% block content %}
<h1>{% trans "Invitation confirmed" %}</h1>
<p>
{% blocktrans %}You successfully confirmed the invitation to the trial group meeting of the group {{ groupname }}.{% endblocktrans %}
{{ timeinfo }}
</p>
<p>
{% blocktrans %}We have informed the group leaders about your confirmation. If for some reason you can not make it,
please contact the group leaders at{% endblocktrans %}
<a href="mailto:{{ contact_email }}">{{ contact_email }}</a>.
</p>
{% endblock %}

@ -12,6 +12,7 @@ urlpatterns = [
re_path(r'^waitinglist/confirm', views.confirm_waiting , name='confirm_waiting'), re_path(r'^waitinglist/confirm', views.confirm_waiting , name='confirm_waiting'),
re_path(r'^waitinglist/leave', views.leave_waitinglist , name='leave_waitinglist'), re_path(r'^waitinglist/leave', views.leave_waitinglist , name='leave_waitinglist'),
re_path(r'^waitinglist/invitation/reject', views.reject_invitation , name='reject_invitation'), re_path(r'^waitinglist/invitation/reject', views.reject_invitation , name='reject_invitation'),
re_path(r'^waitinglist/invitation/confirm', views.confirm_invitation , name='confirm_invitation'),
re_path(r'^waitinglist', views.register_waiting_list , name='register_waiting_list'), re_path(r'^waitinglist', views.register_waiting_list , name='register_waiting_list'),
re_path(r'^mail/confirm', views.confirm_mail , name='confirm_mail'), re_path(r'^mail/confirm', views.confirm_mail , name='confirm_mail'),
] ]

@ -479,6 +479,47 @@ def reject_invitation(request):
return render_reject_invalid(request) return render_reject_invalid(request)
def render_confirm_invitation(request, invitation):
return render(request, 'members/confirm_invitation.html',
{'invitation': invitation,
'groupname': invitation.group.name,
'contact_email': invitation.group.contact_email,
'timeinfo': invitation.group.get_time_info()})
def render_confirm_invalid(request):
return render(request, 'members/confirm_invalid.html')
def render_confirm_success(request, invitation):
return render(request, 'members/confirm_success.html',
{'invitation': invitation,
'groupname': invitation.group.name,
'contact_email': invitation.group.contact_email,
'timeinfo': invitation.group.get_time_info()})
def confirm_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_confirm_invitation(request, invitation)
except (ValueError, InvitationToGroup.DoesNotExist):
return render_confirm_invalid(request)
if request.method != 'POST' or 'key' not in request.POST:
return render_confirm_invalid(request)
key = request.POST['key']
try:
invitation = InvitationToGroup.objects.get(key=key)
except InvitationToGroup.DoesNotExist:
return render_confirm_invalid(request)
invitation.confirm()
return render_confirm_success(request, invitation)
def confirm_waiting(request): def confirm_waiting(request):
if request.method == 'GET' and 'key' in request.GET: if request.method == 'GET' and 'key' in request.GET:
key = request.GET['key'] key = request.GET['key']

Loading…
Cancel
Save