Skip to content

Commit 03f1c86

Browse files
authored
Merge branch 'master' into event_management_helpers
2 parents 6342e65 + 658064b commit 03f1c86

File tree

21 files changed

+464
-199
lines changed

21 files changed

+464
-199
lines changed

collectives/api/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
1111
"""
1212

13-
from collectives.api.autocomplete_user import find_users_by_fuzzy_name
1413
from collectives.api.autocomplete_reservation import find_equipment_types
1514

15+
import collectives.api.autocomplete_user
1616
import collectives.api.admin
1717
import collectives.api.event
1818
import collectives.api.equipment

collectives/api/autocomplete_user.py

+18-49
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from flask import request, abort
1010
from flask_login import current_user
11-
from sqlalchemy.sql import text
11+
from sqlalchemy.orm import Query
1212
from sqlalchemy import func, and_
1313
from marshmallow import fields
1414

@@ -31,39 +31,18 @@ class Meta:
3131
fields = (
3232
"id",
3333
"full_name",
34-
"license_expiry_date",
34+
"is_active",
3535
)
3636

3737

38-
def find_users_by_fuzzy_name(pattern, limit=8):
39-
"""Find user for autocomplete from a part of their full name.
38+
def _make_autocomplete_query(pattern: str) -> Query:
39+
"""Builds the autocomplete query for the provided pattern"""
4040

41-
Comparison are case insensitive.
41+
query = db.session.query(User)
42+
query = query.filter(func.lower(User.full_name()).like(f"%{pattern}%"))
43+
query = query.order_by(User.is_active.desc(), User.full_name(), User.id)
4244

43-
:param string pattern: Part of the name that will be searched.
44-
:param int limit: Maximum number of response.
45-
:return: List of users corresponding to ``pattern``
46-
:rtype: list(:py:class:`collectives.models.user.User`)
47-
"""
48-
if "sqlite" in db.engine.name:
49-
# SQLlite does not follow SQL standard
50-
concat_clause = "(first_name || ' ' || last_name)"
51-
else:
52-
concat_clause = "CONCAT(first_name, ' ', last_name)"
53-
54-
sql = (
55-
f"SELECT id, first_name, last_name from users WHERE "
56-
f"LOWER({concat_clause}) LIKE :pattern LIMIT :limit"
57-
)
58-
59-
sql_pattern = f"%{pattern.lower()}%"
60-
found_users = (
61-
db.session.query(User)
62-
.from_statement(text(sql))
63-
.params(pattern=sql_pattern, limit=limit)
64-
)
65-
66-
return found_users
45+
return query
6746

6847

6948
@blueprint.route("/users/autocomplete/create_rental")
@@ -90,8 +69,8 @@ def autocomplete_users_create_rental():
9069
if pattern is None or (len(pattern) < 2):
9170
found_users = []
9271
else:
93-
limit = request.args.get("l", type=int) or 8
94-
found_users = find_users_by_fuzzy_name(pattern, limit)
72+
limit = request.args.get("l", default=8, type=int)
73+
found_users = _make_autocomplete_query(pattern).limit(limit)
9574

9675
content = json.dumps(AutocompleteUserSchema(many=True).dump(found_users))
9776
return content, 200, {"content-type": "application/json"}
@@ -121,8 +100,8 @@ def autocomplete_users():
121100
if pattern is None or (len(pattern) < 2):
122101
found_users = []
123102
else:
124-
limit = request.args.get("l", type=int) or 8
125-
found_users = find_users_by_fuzzy_name(pattern, limit)
103+
limit = request.args.get("l", default=8, type=int)
104+
found_users = _make_autocomplete_query(pattern).limit(limit)
126105

127106
content = json.dumps(AutocompleteUserSchema(many=True).dump(found_users))
128107
return content, 200, {"content-type": "application/json"}
@@ -148,16 +127,11 @@ def autocomplete_leaders():
148127
if len(pattern) < 2:
149128
found_users = []
150129
else:
151-
limit = request.args.get("l", type=int) or 8
130+
limit = request.args.get("l", default=8, type=int)
152131

153-
query = db.session.query(User)
154-
condition = func.lower(User.first_name + " " + User.last_name).like(
155-
f"%{pattern}%"
156-
)
157-
query = query.filter(condition)
132+
query = _make_autocomplete_query(pattern)
158133
query = query.filter(User.led_events)
159-
found_users = query.order_by(User.id).all()
160-
found_users = found_users[0:limit]
134+
found_users = query.limit(limit)
161135

162136
content = json.dumps(AutocompleteUserSchema(many=True).dump(found_users))
163137
return content, 200, {"content-type": "application/json"}
@@ -188,15 +162,15 @@ def autocomplete_available_leaders():
188162
if pattern is None or (len(pattern) < 2):
189163
found_users = []
190164
else:
191-
limit = request.args.get("l", type=int) or 8
165+
limit = request.args.get("l", default=8, type=int)
166+
167+
query = _make_autocomplete_query(pattern)
192168
event_type = db.session.get(
193169
EventType, request.args.get("etype", type=int, default=0)
194170
)
195171
activity_ids = request.args.getlist("aid", type=int)
196172
existing_ids = request.args.getlist("eid", type=int)
197173

198-
query = db.session.query(User)
199-
200174
if event_type and event_type.requires_activity:
201175
ok_roles = RoleIds.all_activity_leader_roles()
202176
else:
@@ -206,12 +180,7 @@ def autocomplete_available_leaders():
206180
role_condition = and_(role_condition, Role.activity_id.in_(activity_ids))
207181
query = query.filter(User.roles.any(role_condition))
208182
query = query.filter(~User.id.in_(existing_ids))
209-
condition = func.lower(User.first_name + " " + User.last_name).ilike(
210-
f"%{pattern}%"
211-
)
212-
query = query.filter(condition)
213183

214-
query = query.order_by(User.first_name, User.last_name, User.id)
215184
found_users = query.limit(limit)
216185

217186
content = json.dumps(AutocompleteUserSchema(many=True).dump(found_users))

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

0 commit comments

Comments
 (0)