This repository has been archived by the owner on Oct 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into multiple-departments-management
- Loading branch information
Showing
13 changed files
with
353 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
from . import invitations # noqa | ||
from . import structures # noqa | ||
from . import users # noqa | ||
|
||
# imports utilisés pour l'activation des tâches de notification (effet de bord) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import uuid | ||
|
||
import pytest | ||
from dateutil.relativedelta import relativedelta | ||
from django.core import mail | ||
from django.utils import timezone | ||
from freezegun import freeze_time | ||
|
||
from dora.core.test_utils import make_structure, make_user | ||
from dora.notifications.models import Notification | ||
from dora.users.models import User | ||
|
||
from ..core import Task | ||
from ..users import UsersWithoutStructureTask | ||
|
||
|
||
@pytest.fixture | ||
def user_without_structure_task(): | ||
return UsersWithoutStructureTask() | ||
|
||
|
||
def test_user_without_structure_task_is_registered(): | ||
assert UsersWithoutStructureTask in Task.registered_tasks() | ||
|
||
|
||
def test_user_without_structure_task_candidates(user_without_structure_task): | ||
# pas d'id IC | ||
nok_user = make_user() | ||
assert nok_user not in user_without_structure_task.candidates() | ||
|
||
# adresse e-mail non validée | ||
nok_user = make_user(is_valid=False) | ||
assert nok_user not in user_without_structure_task.candidates() | ||
|
||
# utilisateurs incrits depuis plus de 4 mois exclus | ||
nok_user = make_user( | ||
ic_id=uuid.uuid4(), date_joined=timezone.now() - relativedelta(months=4, days=1) | ||
) | ||
assert nok_user not in user_without_structure_task.candidates() | ||
|
||
# membres de structure exclus | ||
nok_user = make_user(structure=make_structure(), ic_id=uuid.uuid4()) | ||
assert nok_user not in user_without_structure_task.candidates() | ||
|
||
# utilisateurs invités exclus | ||
nok_user = make_user(ic_id=uuid.uuid4()) | ||
make_structure(putative_member=nok_user) | ||
assert nok_user not in user_without_structure_task.candidates() | ||
|
||
# candidat potentiel | ||
ok_user = make_user(ic_id=uuid.uuid4()) | ||
assert ok_user in user_without_structure_task.candidates() | ||
|
||
|
||
def test_user_without_structure_task_should_trigger(user_without_structure_task): | ||
user = make_user(ic_id=uuid.uuid4()) | ||
|
||
# première notification à +1j | ||
ok, _, _ = user_without_structure_task.run() | ||
assert not ok | ||
|
||
notification = Notification.objects.first() | ||
assert notification.is_pending | ||
|
||
now = timezone.now() | ||
|
||
for cnt, day in enumerate((1, 5, 10, 15), 1): | ||
with freeze_time(now + relativedelta(days=day)): | ||
ok, _, _ = user_without_structure_task.run() | ||
assert ok | ||
|
||
notification.refresh_from_db() | ||
assert notification.counter == cnt | ||
assert notification.is_pending | ||
|
||
# on vérifie qu'un e-mail est bien envoyé | ||
# testé plus en détails dans la partie e-mail | ||
assert len(mail.outbox) == cnt | ||
assert mail.outbox[cnt - 1].to == [user.email] | ||
|
||
# le contenu de l'e-mail est différent pour la dernière notification | ||
match cnt: | ||
case 4: | ||
assert ( | ||
mail.outbox[cnt - 1].subject | ||
== "Dernier rappel avant suppression" | ||
) | ||
case _: | ||
assert ( | ||
mail.outbox[cnt - 1].subject | ||
== "Rappel : Identifiez votre structure sur DORA" | ||
) | ||
|
||
with freeze_time(now + relativedelta(days=day + 1)): | ||
ok, _, _ = user_without_structure_task.run() | ||
assert not ok | ||
|
||
notification.refresh_from_db() | ||
assert notification.counter == cnt | ||
assert notification.is_pending | ||
|
||
notification.refresh_from_db() | ||
assert notification.counter == 4 | ||
assert notification.is_pending | ||
|
||
# on teste la dernière iteration de la notification (+4 mois) | ||
with freeze_time(now + relativedelta(months=4)): | ||
ok, _, _ = user_without_structure_task.run() | ||
assert ok | ||
|
||
# la notification ne doit plus exister (l'utilisateur propriétaire a été supprimé) | ||
with pytest.raises(Notification.DoesNotExist): | ||
notification.refresh_from_db() | ||
|
||
# le compte utilisateur doit avoir été supprimé | ||
with pytest.raises(User.DoesNotExist): | ||
user.refresh_from_db() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
from dateutil.relativedelta import relativedelta | ||
from django.utils import timezone | ||
|
||
from dora.notifications.enums import TaskType | ||
from dora.notifications.models import Notification | ||
from dora.users.emails import send_user_without_structure_notification | ||
from dora.users.models import User | ||
|
||
from .core import Task, TaskError | ||
|
||
""" | ||
Users : | ||
Notifications ayant pour candidats des utilisateurs "en direct", | ||
c.a.d. sans passer par l'intermédiaire d'un objet tiers, | ||
comme une invitation ou une structure. | ||
""" | ||
|
||
|
||
class UsersWithoutStructureTask(Task): | ||
@classmethod | ||
def task_type(cls): | ||
return TaskType.USERS_WITHOUT_STRUCTURE | ||
|
||
@classmethod | ||
def candidates(cls): | ||
return User.objects.exclude(ic_id=None).filter( | ||
is_valid=True, | ||
membership=None, | ||
putative_membership=None, | ||
date_joined__gt=timezone.now() - relativedelta(months=4, days=1), | ||
) | ||
|
||
@classmethod | ||
def should_trigger(cls, notification: Notification) -> bool: | ||
now = timezone.now() | ||
match notification.counter: | ||
case 0: | ||
return notification.created_at + relativedelta(days=1) <= now | ||
case 1: | ||
return notification.created_at + relativedelta(days=5) <= now | ||
case 2: | ||
return notification.created_at + relativedelta(days=10) <= now | ||
case 3: | ||
return notification.created_at + relativedelta(days=15) <= now | ||
case 4: | ||
return notification.created_at + relativedelta(months=4) <= now | ||
case _: | ||
return False | ||
|
||
@classmethod | ||
def process(cls, notification: Notification): | ||
match notification.counter: | ||
case 0 | 1 | 2 | 3: | ||
try: | ||
send_user_without_structure_notification( | ||
notification.owner_user, deletion=notification.counter == 3 | ||
) | ||
except Exception as ex: | ||
raise TaskError( | ||
f"Erreur d'envoi du mail pour un utilisateur sans structure ({notification}) : {ex}" | ||
) from ex | ||
case 4: | ||
notification.complete() | ||
# action : voir post_process | ||
case _: | ||
raise TaskError(f"État du compteur incohérent ({notification})") | ||
|
||
@classmethod | ||
def post_process(cls, notification: Notification): | ||
print("PP") | ||
if notification.is_complete: | ||
print("PP:deleting") | ||
user = notification.owner_user | ||
# suppression du compte utilisateur associé si : | ||
# - aucune autre invitation | ||
# - non membre d'une structure | ||
if not user.putative_membership.count() and not user.membership.count(): | ||
user.delete() | ||
# à ce point, la notification doit aussi être détruite (CASCADE)... | ||
|
||
|
||
Task.register(UsersWithoutStructureTask) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Generated by Django 4.2.10 on 2024-02-29 16:04 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("services", "0103_savedsearch_location_kinds"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="service", | ||
name="appointment_link", | ||
field=models.URLField( | ||
blank=True, verbose_name="Lien de prise de rendez-vous" | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
dora/users/templates/notification_user_without_structure.mjml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{% extends "email-base.mjml" %} | ||
|
||
{% block preview %}N'oubliez pas d'identifier et de rejoindre votre structure{% endblock %} | ||
|
||
{% block content %} | ||
<p> | ||
<strong>Bonjour {{ user.last_name }},</strong> | ||
</p> | ||
|
||
<p>Pour tirer le meilleur parti de notre plateforme, il est temps d’identifier et rejoindre votre structure dès maintenant, pour bénéficier de toutes les fonctionnalités de DORA :</p> | ||
{% endblock %} | ||
|
||
|
||
{% block post_cta %} | ||
<mj-text> | ||
<p> | ||
N'oubliez pas que l'identification de votre structure est essentielle pour : | ||
<ul> | ||
<li>mettre en avant les informations concernant votre structure</li> | ||
<li>inviter vos collaborateurs</li> | ||
<li>référencer son offre de services</li> | ||
<li>accéder aux coordonner de tous vos partenaires</li> | ||
<li>envoyer vos demandes d’orientation via DORA à vos partenaires</li> | ||
</ul> | ||
</p> | ||
|
||
<p>Ne manquez pas cette opportunité de bénéficier pleinement de DORA !</p> | ||
</mj-text> | ||
{% endblock %} | ||
|
||
{% block cta %} | ||
<mj-button mj-class="cta" href="{{ cta_link }}">Identifiez et rejoignez votre structure</mj-button> | ||
<mj-spacer height="24px"/> | ||
{% endblock %} |
28 changes: 28 additions & 0 deletions
28
dora/users/templates/notification_user_without_structure_deletion.mjml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{% extends "email-base.mjml" %} | ||
|
||
{% block preview %}Activez votre compte DORA avant qu'il ne soit supprimé{% endblock %} | ||
|
||
{% block content %} | ||
<p> | ||
<strong>Bonjour {{ user.last_name }},</strong> | ||
</p> | ||
|
||
<p>Nous avons remarqué que vous n'avez toujours pas identifié et rejoint votre structure sur DORA.</p> | ||
|
||
<p>Veuillez noter que sans action de votre part, votre compte sera supprimé dans <strong>4 mois</strong>.</p> | ||
|
||
<p>Pour éviter cela, activez votre compte dès maintenant :</p> | ||
{% endblock %} | ||
|
||
{% block post_cta %} | ||
<mj-text> | ||
<p> | ||
Nous vous remercions pour votre intérêt pour DORA et espérons vous voir bientôt parmi nos utilisateurs actifs. | ||
</p> | ||
</mj-text> | ||
{% endblock %} | ||
|
||
{% block cta %} | ||
<mj-button mj-class="cta" href="{{ cta_link }}">Identifiez et rejoignez votre structure</mj-button> | ||
<mj-spacer height="24px"/> | ||
{% endblock %} |
Oops, something went wrong.