Skip to content

Commit 658064b

Browse files
authored
[FEATURE] Improve registration update mails (#786)
* Send email to user being promoted from waiting list * Make it possible to count/test send emails + fix local/extranet tests * Allow providing reason for rejection/unregistration. Send mail when confirmed from wairting list * Test mails sent on unresgistration * Add update_attendance test * Linting * Add registration deletion test * Add test for waiting list promotion for upcoming event
1 parent d135ff2 commit 658064b

File tree

14 files changed

+374
-113
lines changed

14 files changed

+374
-113
lines changed

collectives/configuration.yaml

+22-25
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,13 @@ Template mail:
277277
description: Email subject for user self unregister
278278
type: ShortString
279279

280-
SELF_UNREGISTER_MESSAGE:
280+
SELF_UNREGISTER_MESSAGE_v2:
281281
content: |
282282
Bonjour,
283283
284284
'{user_name}' vient de se désinscrire de l'événement '{event_title}' que vous encadrez.
285+
{reason}
286+
285287
Lien vers l'événement :
286288
{link}
287289
@@ -400,11 +402,13 @@ Template mail:
400402
description: Email subject for rejected registration to an event
401403
type: ShortString
402404

403-
REJECTED_REGISTRATION_MESSAGE:
405+
REJECTED_REGISTRATION_MESSAGE_v2:
404406
content: |
405407
Bonjour,
406408
407-
{rejector_name} vient de refuser votre inscription à la collective {event_title} débutant le {event_date}.
409+
{rejector_name} vient de refuser votre inscription à l'événement {event_title} débutant le {event_date}.
410+
{reason}
411+
408412
Lien vers l'événement :
409413
{link}
410414
@@ -437,42 +441,35 @@ Template mail:
437441
description: Sujet de l'email pour l'utilisateur lorsque que son inscription est activée.
438442
type: ShortString
439443

440-
ACTIVATED_REGISTRATION_MESSAGE:
444+
ACTIVATED_REGISTRATION_MESSAGE_v2:
441445
content: |
442446
Bonjour,
443447
444-
Une place vient de se libérer pour la collective {event_title} débutant le {event_date}.
445-
Votre place dans la liste d'attente a donc été validée et vous êtes à présent inscrit(e)
446-
à la collective, ou en attente de paiement si la collective en nécessite un.
447-
448-
Si vous ne pouvez plus participer, merci de vous désincrire au plus tôt afin de ne pas
449-
perturber l'organisation de la collective.
448+
Votre inscription vient d'être validée pour l'événement {event_title} débutant le {event_date}
449+
pour lequel vous étiez jusqu'alors en liste d'attente.
450450
451-
Merci de vérifier si la collective nécessite un paiement de votre part:
451+
Merci de vérifier si l'événement nécessite un paiement de votre part et de procéder à celui-ci le cas échéant:
452452
{link}
453453
454+
{unregistration_text}
455+
454456
Vous recevez cet e-mail en tant qu'adhérent inscrit à cette collective.
455457
Cet e-mail est envoyé par un automate, répondre à cet e-mail sera sans effet.
456458
description: Template d'email pour l'utilisateur lorsque que son inscription est activée.
457459
type: LongString
458-
459-
ACTIVATED_REGISTRATION_UPCOMING_EVENT_MESSAGE:
460+
461+
ACTIVATED_REGISTRATION_STANDARD_EVENT_MESSAGE:
460462
content: |
461-
Bonjour,
462-
463-
Une place vient de se libérer pour la collective {event_title} débutant le {event_date}.
464-
Votre place dans la liste d'attente a donc été validée et vous êtes à présent inscrit(e)
465-
à la collective, ou en attente de paiement si la collective en nécessite un.
463+
Si vous ne pouvez plus participer, merci de vous désincrire au plus tôt afin de ne pas
464+
perturber l'organisation de la collective.
465+
description: Template d'email pour l'utilisateur lorsque que son inscription est activée et que l'événement commence bientôt.
466+
type: LongString
466467

467-
Cet événement commence bientôt ; si vous ne pouvez plus participer, merci de vous désincrire
468+
ACTIVATED_REGISTRATION_UPCOMING_EVENT_WARNING:
469+
content: |
470+
Attention : cet événement commence bientôt ! Si vous ne pouvez plus participer, merci de vous désincrire
468471
dans les prochaines {grace_period} heures afin de ne pas perturber l'organisation et que
469472
votre désinscription ne soit pas considérée comme tardive.
470-
471-
Merci de vérifier si la collective nécessite un paiement de votre part:
472-
{link}
473-
474-
Vous recevez cet e-mail en tant qu'adhérent inscrit à cette collective.
475-
Cet e-mail est envoyé par un automate, répondre à cet e-mail sera sans effet.
476473
description: Template d'email pour l'utilisateur lorsque que son inscription est activée et que l'événement commence bientôt.
477474
type: LongString
478475

collectives/email_templates.py

+47-40
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
Registration,
1515
ConfirmationToken,
1616
BadgeIds,
17+
Event,
18+
User,
1719
)
1820
from collectives.models.auth import ConfirmationTokenType, TokenEmailStatus
1921
from collectives.utils import mail
@@ -51,19 +53,24 @@ def send_new_event_notification(event):
5153
current_app.logger.error(f"Mailer error: {err}")
5254

5355

54-
def send_unregister_notification(event, user):
56+
def send_unregister_notification(event: Event, user: User, reason: str):
5557
"""Send a notification to leaders when a user unregisters from an event
5658
5759
:param event: Event on which user unregisters.
58-
:type event: :py:class:`collectives.modes.event.Event`
5960
:param user: User who unregisters.
60-
:type user: :py:class:`collectives.models.user.User`
61+
:param reason: Use-provided reason for unregistering
6162
"""
6263
try:
6364
leader_emails = [l.mail for l in event.leaders]
64-
message = Configuration.SELF_UNREGISTER_MESSAGE.format(
65+
reason = (
66+
f"Justification fournie pour la désinscription : \n {reason}"
67+
if reason
68+
else ""
69+
)
70+
message = Configuration.SELF_UNREGISTER_MESSAGE_v2.format(
6571
user_name=user.full_name(),
6672
event_title=event.title,
73+
reason=reason,
6774
link=url_for(
6875
"event.view_event",
6976
event_id=event.id,
@@ -139,18 +146,16 @@ def has_succeed():
139146
db.session.add(token_copy)
140147
db.session.commit()
141148

142-
# Check if local dev, so that email is not sent
143-
# and token validation link is displayed in flash popup
144-
config = current_app.config
145-
if not config["EXTRANET_DISABLE"]:
146-
mail.send_mail(
147-
email=email,
148-
subject=f"{reason.capitalize()} de compte Collectives",
149-
message=message,
150-
error_action=has_failed,
151-
success_action=has_succeed,
152-
)
153-
else:
149+
mail.send_mail(
150+
email=email,
151+
subject=f"{reason.capitalize()} de compte Collectives",
152+
message=message,
153+
error_action=has_failed,
154+
success_action=has_succeed,
155+
)
156+
157+
# Check if local dev, then token validation link is displayed in flash popup
158+
if current_app.config["EXTRANET_DISABLE"]:
154159
has_succeed()
155160
url = url_for(
156161
"auth.process_confirmation", token_uuid=token.uuid, _external=True
@@ -161,19 +166,23 @@ def has_succeed():
161166
flash(Markup(line))
162167

163168

164-
def send_reject_subscription_notification(rejector_name, event, rejected_user_email):
169+
def send_reject_subscription_notification(
170+
rejector_name: str, event: Event, rejected_user_email: str, reason: str
171+
):
165172
"""Send a notification to user whom registration has been rejected
166173
167-
:param string rejector_name: User name who rejects the subscription.
174+
:param rejector_name: User name who rejects the subscription.
168175
:param event: Event the registraton is rejected on.
169-
:type event: :py:class:`collectives.modes.event.Event`
170-
:param string rejected_user_email: User email for who registraton is rejected.
176+
:param rejected_user_email: User email for who registraton is rejected.
177+
:param reason: Reason for the rejection
171178
"""
172179
try:
173-
message = Configuration.REJECTED_REGISTRATION_MESSAGE.format(
180+
reason = f"Justification du refus : \n {reason}" if reason else ""
181+
message = Configuration.REJECTED_REGISTRATION_MESSAGE_v2.format(
174182
rejector_name=rejector_name,
175183
event_title=event.title,
176184
event_date=format_date(event.start),
185+
reason=reason,
177186
link=url_for(
178187
"event.view_event",
179188
event_id=event.id,
@@ -242,32 +251,30 @@ def send_update_waiting_list_notification(
242251
end_of_grace = current_time() + timedelta(
243252
hours=Configuration.UNREGISTRATION_GRACE_PERIOD + 1
244253
)
254+
245255
if registration.is_in_late_unregistration_period(end_of_grace):
246-
message = (
247-
Configuration.ACTIVATED_REGISTRATION_UPCOMING_EVENT_MESSAGE.format(
248-
event_title=registration.event.title,
249-
event_date=format_date(registration.event.start),
256+
unregistration_text = (
257+
Configuration.ACTIVATED_REGISTRATION_UPCOMING_EVENT_WARNING.format(
250258
grace_period=Configuration.UNREGISTRATION_GRACE_PERIOD,
251-
link=url_for(
252-
"event.view_event",
253-
event_id=registration.event.id,
254-
name=slugify(registration.event.title),
255-
_external=True,
256-
),
257259
)
258260
)
259261
else:
260-
message = Configuration.ACTIVATED_REGISTRATION_MESSAGE.format(
261-
event_title=registration.event.title,
262-
event_date=format_date(registration.event.start),
263-
link=url_for(
264-
"event.view_event",
265-
event_id=registration.event.id,
266-
name=slugify(registration.event.title),
267-
_external=True,
268-
),
262+
unregistration_text = (
263+
Configuration.ACTIVATED_REGISTRATION_STANDARD_EVENT_MESSAGE
269264
)
270265

266+
message = Configuration.ACTIVATED_REGISTRATION_MESSAGE_v2.format(
267+
event_title=registration.event.title,
268+
event_date=format_date(registration.event.start),
269+
unregistration_text=unregistration_text,
270+
link=url_for(
271+
"event.view_event",
272+
event_id=registration.event.id,
273+
name=slugify(registration.event.title),
274+
_external=True,
275+
),
276+
)
277+
271278
if deleted_registrations:
272279
event_titles = "\n".join(
273280
f" - {reg.event.title}" for reg in deleted_registrations

collectives/routes/event.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,8 @@ def self_unregister(event_id):
958958

959959
# Send notification e-mail to leaders only if definitive subscription
960960
if was_holding_slot:
961-
send_unregister_notification(event, current_user)
961+
reason = request.form.get("reason")
962+
send_unregister_notification(event, current_user, reason)
962963

963964
return redirect(url_for("event.view_event", event_id=event_id))
964965

@@ -1160,6 +1161,11 @@ def update_attendance(event_id):
11601161
RegistrationStatus.Waiting,
11611162
RegistrationStatus.Rejected,
11621163
):
1164+
if previous_status == RegistrationStatus.Waiting:
1165+
send_update_waiting_list_notification(
1166+
registration, deleted_registrations=[]
1167+
)
1168+
11631169
# Reset registration time to restart grace period
11641170
registration.registration_time = current_time()
11651171

@@ -1171,6 +1177,7 @@ def update_attendance(event_id):
11711177
current_user.full_name(),
11721178
registration.event,
11731179
registration.user.mail,
1180+
reason=request.form.get("rejection-reason"),
11741181
)
11751182

11761183
if Configuration.ENABLE_SANCTIONS and event.event_type.requires_activity:

collectives/static/css/legacy/event/event-details.scss

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
#attendancelistform td select{
7373
width: 100%;
7474
}
75+
#attendancelistform textarea{
76+
}
7577

7678
.select {
7779
height: 40px;

collectives/static/js/event/event.js

+14
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,17 @@ function attendanceSelectAll(value){
3434
}
3535
});
3636
}
37+
38+
function attendanceUpdate(select, origValue){
39+
if(select.value != origValue) {
40+
select.style.color='#eb691c';
41+
select.style.fontWeight='bold';
42+
} else {
43+
select.style.color=null;
44+
select.style.fontWeight=null;
45+
}
46+
47+
if(select.value == EnumRegistrationStatusKeys["Rejected"]) {
48+
document.getElementById('attendance-rejection-explanation').style.display = null;
49+
}
50+
}

collectives/templates/event/partials/admin.html

+6-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ <h5 class="heading-5 collective-display--administration-title"> Inscrire un adh
135135
</form>
136136

137137
<h5 class="heading-5 collective-display--administration-title"> Liste des présences</h5>
138-
<form action="{{url_for('event.update_attendance', event_id=event.id)}}" method="post" id="attendancelistform" autocomplete="off">
138+
<form class="form" action="{{url_for('event.update_attendance', event_id=event.id)}}" method="post" id="attendancelistform" autocomplete="off">
139139
<table id="attendancelist">
140140
<tbody>
141141
{% set ns = namespace(license_to_renew=False) %}
@@ -151,7 +151,7 @@ <h5 class="heading-5 collective-display--administration-title"> Liste des prése
151151
</a>
152152
</td>
153153
<td style="text-align:center">
154-
<select name="reg_{{ registration.id }}" onchange="this.style.color='#eb691c'; this.style.fontWeight='bold';">
154+
<select name="reg_{{ registration.id }}" onchange="attendanceUpdate(this, {{registration.status.value}});">
155155
{% for status in registration.valid_transitions() %}
156156
<option
157157
value="{{status.value}}"
@@ -183,6 +183,10 @@ <h5 class="heading-5 collective-display--administration-title"> Liste des prése
183183
= Licence à renouveler <br/>
184184
{% endif %}
185185

186+
<textarea id="attendance-rejection-explanation" name="rejection-reason" class="input-50" style="min-width: 22em;" rows="5"
187+
placeholder="Explication optionelle pour les personnes nouvellement refusées..."
188+
></textarea>
189+
186190
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/><br/>
187191
<input type="submit" value="Sauvegarder" class="button button-primary">
188192
</form>

collectives/templates/partials/event/self_unregister.html

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<form
44
action="{{url_for('event.self_unregister', event_id=event.id)}}"
55
method="post"
6-
6+
class="form"
77
>
88
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
99

@@ -36,6 +36,9 @@ <h4 class="centeralign">Confirmez-vous votre désinscription ?<br/>
3636
<input type="submit" id="submitSelfUnregister" value="Confirmer" class="button button-danger">
3737
<input type="button" id="cancelSelfUnregister" value="Annuler" class="button button-secondary" onclick="this.closest('.messages').style.display='none'">
3838
</h4>
39+
<h4 class="centeralign">
40+
<textarea name="reason" class="input-75" placeholder="Justification pour les encadrants..." rows="5" style="min-width:20em; max-width:50em;"></textarea>
41+
</h4>
3942
</div>
4043
<div class="veil" onclick= "this.parentNode.style.display='none'"></div>
4144
</div>

0 commit comments

Comments
 (0)