From 9767f29e10eace04b837daa80043280156513d10 Mon Sep 17 00:00:00 2001 From: Alejandro MG Date: Wed, 14 Aug 2024 11:03:19 +0200 Subject: [PATCH] Adds new templates and email sending for user actions --- api/tests/test_company.py | 64 +++++++++++++++++++++++++--- api/tests/test_solicitation.py | 33 +++++++++++++- api/views/solicitation.py | 43 +++++++++++-------- data/models/solicitation.py | 78 ++++++++++++++++++++++++---------- 4 files changed, 171 insertions(+), 47 deletions(-) diff --git a/api/tests/test_company.py b/api/tests/test_company.py index 892b6fdc..68cab0c2 100644 --- a/api/tests/test_company.py +++ b/api/tests/test_company.py @@ -1,3 +1,7 @@ +from unittest import mock + +from django.test.utils import override_settings + from rest_framework import status from data.factories import ( @@ -242,10 +246,17 @@ def setUp(self): self.recipient_email = "jean@example.com" self.payload = dict(recipient_email=self.recipient_email, roles=["DeclarantRole"]) - def test_add_collaborator_without_account_but_invitation_already_sent_ko(self): - # L'invité n'existe pas en base, mais une invitation non traitée a déjà été envoyée. + @override_settings(ANYMAIL={"SENDINBLUE_API_KEY": "fake-api-key"}) + @override_settings(CONTACT_EMAIL="contact@example.com") + @override_settings(SECURE=True) + @override_settings(HOSTNAME="hostname") + @mock.patch("config.email.send_sib_template") + def test_add_collaborator_without_account_but_invitation_already_sent_ko(self, mocked_brevo): self.login(self.adder) CollaborationInvitationFactory(recipient_email=self.recipient_email, company=self.company) + mocked_brevo.reset_mock() + + # L'invité n'existe pas en base, mais une invitation non traitée a déjà été envoyée. response = self.post(self.url(pk=self.company.pk), self.payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -255,21 +266,52 @@ def test_add_collaborator_without_account_but_invitation_already_sent_ko(self): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_add_collaborator_without_account_ok(self): + mocked_brevo.assert_not_called() + + @override_settings(ANYMAIL={"SENDINBLUE_API_KEY": "fake-api-key"}) + @override_settings(CONTACT_EMAIL="contact@example.com") + @override_settings(SECURE=True) + @override_settings(HOSTNAME="hostname") + @mock.patch("config.email.send_sib_template") + def test_add_collaborator_without_account_ok(self, mocked_brevo): # L'invité n'existe pas en base, et aucune invitation n'a été envoyée self.login(self.adder) response = self.post(self.url(pk=self.company.pk), self.payload) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("message", response.data) - def test_add_collaborator_already_a_collaborator_ko(self): + template_number = 16 + mocked_brevo.assert_called_once_with( + template_number, + { + "COMPANY_NAME": self.company.social_name, + "SENDER_NAME": self.adder.get_full_name(), + "SIGNUP_LINK": f"https://hostname/inscription?email={self.recipient_email}", + }, + self.recipient_email, + self.recipient_email, + ) + + @override_settings(ANYMAIL={"SENDINBLUE_API_KEY": "fake-api-key"}) + @override_settings(CONTACT_EMAIL="contact@example.com") + @override_settings(SECURE=True) + @override_settings(HOSTNAME="hostname") + @mock.patch("config.email.send_sib_template") + def test_add_collaborator_already_a_collaborator_ko(self, mocked_brevo): # L'invité existe en base et fait déjà partie des collaborateurs de l'entreprise. self.login(self.adder) DeclarantRoleFactory(user=UserFactory(email=self.recipient_email), company=self.company) response = self.post(self.url(pk=self.company.pk), self.payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_add_collaborator_with_account_ok(self): + mocked_brevo.assert_not_called() + + @override_settings(ANYMAIL={"SENDINBLUE_API_KEY": "fake-api-key"}) + @override_settings(CONTACT_EMAIL="contact@example.com") + @override_settings(SECURE=True) + @override_settings(HOSTNAME="hostname") + @mock.patch("config.email.send_sib_template") + def test_add_collaborator_with_account_ok(self, mocked_brevo): # L'invité existe en base mais n'est pas encore collaborateur de l'entreprise. self.login(self.adder) recipient = UserFactory() @@ -280,6 +322,18 @@ def test_add_collaborator_with_account_ok(self): self.assertIn("message", response.data) self.assertIn(recipient, self.company.collaborators) + template_number = 18 + mocked_brevo.assert_called_once_with( + template_number, + { + "SENDER_NAME": self.adder.get_full_name(), + "COMPANY_NAME": self.company.social_name, + "DASHBOARD_LINK": f"https://hostname/tableau-de-bord?company={self.company.id}", + }, + recipient.email, + recipient.get_full_name(), + ) + def test_add_collaborator_not_logged_ko(self): response = self.post(self.url(pk=self.company.pk), self.payload) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/api/tests/test_solicitation.py b/api/tests/test_solicitation.py index 7185fa15..a094b392 100644 --- a/api/tests/test_solicitation.py +++ b/api/tests/test_solicitation.py @@ -3,6 +3,7 @@ from django.test.utils import override_settings from rest_framework import status +from rest_framework.test import APITestCase from data.factories import ( CollaborationInvitationFactory, @@ -18,7 +19,8 @@ class TestListCollaborationInvitations(ProjectAPITestCase): viewname = "list_collaboration_invitation" - def setUp(self): + @mock.patch("config.email.send_sib_template") + def setUp(self, _): self.user = UserFactory() self.company_1 = CompanyFactory() self.company_2 = CompanyFactory() @@ -55,6 +57,35 @@ def test_get_collaboration_invitations_unauthenticated(self): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) +class TestCollaborationInvitationEmail(APITestCase): + @override_settings(ANYMAIL={"SENDINBLUE_API_KEY": "fake-api-key"}) + @override_settings(CONTACT_EMAIL="contact@example.com") + @override_settings(SECURE=True) + @override_settings(HOSTNAME="hostname") + @mock.patch("config.email.send_sib_template") + def test_account_created_sends_email(self, mocked_brevo): + email = "test@example.com" + company = CompanyFactory() + sender = SupervisorRoleFactory(user=UserFactory(), company=company) + + invitation = CollaborationInvitationFactory(recipient_email=email, company=company, sender=sender.user) + mocked_brevo.reset_mock() + recipient = UserFactory(email=email) + invitation.account_created(processor=recipient) + + template_number = 17 + mocked_brevo.assert_called_once_with( + template_number, + { + "COMPANY_NAME": company.social_name, + "NEW_COLLABORATOR": recipient.get_full_name(), + "MEMBERS_LINK": f"https://hostname/gestion-des-collaborateurs/{company.id}", + }, + sender.user.email, + sender.user.get_full_name(), + ) + + class TestListCompanyAccessClaims(ProjectAPITestCase): viewname = "list_company_access_claim" diff --git a/api/views/solicitation.py b/api/views/solicitation.py index 8e50dc13..96abbf20 100644 --- a/api/views/solicitation.py +++ b/api/views/solicitation.py @@ -1,7 +1,7 @@ +import logging + from django.apps import apps -from django.conf import settings from django.contrib.auth import get_user_model -from django.core.mail import send_mail from django.db import transaction from django.shortcuts import get_object_or_404 @@ -11,6 +11,8 @@ from rest_framework.response import Response from rest_framework.views import APIView +from api.utils.urls import get_base_url +from config import email from data.models import CollaborationInvitation, Company, CompanyAccessClaim from ..exception_handling import ProjectAPIException @@ -18,6 +20,7 @@ from ..serializers import AddNewCollaboratorSerializer, CollaborationInvitationSerializer, CompanyAccessClaimSerializer User = get_user_model() +logger = logging.getLogger(__name__) class CollaborationInvitationListView(ListAPIView): @@ -79,9 +82,7 @@ def post(self, request, pk: int, *args, **kwargs): company=company, recipient_email=recipient_email, processed_at__isnull=True ).exists(): raise ProjectAPIException( - field_errors={ - "recipient_email": "Une invitation a déjà été envoyée à cette adresse e-mail (peut-être par quelqu'un d'autre)." - } + field_errors={"recipient_email": "Une invitation a déjà été envoyée à cette adresse e-mail."} ) # Cas B : l'invité n'existe pas en base, et aucune invitation n'a été envoyée CollaborationInvitation.objects.create( @@ -98,16 +99,22 @@ def post(self, request, pk: int, *args, **kwargs): else: for role_name in roles: role_class = apps.get_model("data", role_name) - role_class.objects.get_or_create( - company=company, user=recipient - ) # le get évite une potentielle erreur - - send_mail( - subject=f"[Compl'Alim] {sender.name} vous a ajouté en tant que collaborateur.", - message=f"{sender.name} vous a ajouté en tant que collaborateur de l'entreprise {company.social_name}.", - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=[recipient.email], - ) - return Response( - {"message": f"{recipient.name} a été ajouté à vos collaborateurs. Un e-mail lui a été envoyé."} - ) + role_class.objects.get_or_create(company=company, user=recipient) + + try: + brevo_template_id = 18 + email.send_sib_template( + brevo_template_id, + { + "SENDER_NAME": sender.get_full_name(), + "COMPANY_NAME": company.social_name, + "DASHBOARD_LINK": f"{get_base_url()}tableau-de-bord?company={company.id}", + }, + recipient.email, + recipient.get_full_name(), + ) + except Exception as e: + logger.error(f"Email not sent on AddNewCollaboratorView for recipient {recipient.id}") + logger.exception(e) + + return Response({"message": f"{recipient.name} a été ajouté à vos collaborateurs."}) diff --git a/data/models/solicitation.py b/data/models/solicitation.py index d9c6a3b8..253c7f85 100644 --- a/data/models/solicitation.py +++ b/data/models/solicitation.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from urllib.parse import urljoin from django.apps import apps from django.conf import settings @@ -109,16 +108,29 @@ def description(self): def create_hook(self): recipients = User.objects.filter(is_staff=True) - self.recipients.set(recipients) - send_mail( - subject="[Compl'Alim] Nouvelle demande de gestion d'une entreprise", - message=f"{self.description} {self.personal_message_for_mail}", - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=self.recipients.values_list("email", flat=True), - ) + self.recipients.set(recipients) # TODO: Je ne pense pas que le self.recipients sert à quelque chose + brevo_template_id = 15 + for recipient in recipients: + try: + email.send_sib_template( + brevo_template_id, + { + "REQUESTER_NAME": self.sender.get_full_name(), + "COMPANY_NAME": self.company.social_name, + "COMPANY_ID": self.company.id, + "ADMIN_LINK": f"{get_base_url()}admin/", + }, + recipient.email, + recipient.get_full_name(), + ) + except Exception as e: + logger.error(f"Email not sent on SupervisionClaim creation with id {self.id}") + logger.exception(e) @processable_action def accept(self, processor): + # TODO : cette action n'est jamais appelé de nulle part. Lors qu'elle le sera il faudra + # créer un template Brevo self.company.supervisors.add(self.sender) send_mail( subject="[Compl'Alim] Votre demande de gestion a été acceptée", @@ -129,6 +141,8 @@ def accept(self, processor): @processable_action def refuse(self, processor): + # TODO : cette action n'est jamais appelé de nulle part. Lors qu'elle le sera il faudra + # créer un template Brevo send_mail( subject="[Compl'Alim] Votre demande de gestion a été refusée", message=f"L'équipe Compl'Alim a refusé que vous deveniez gestionnaire de {self.company.social_name}. N'hésitez pas à nous contacter pour en savoir plus.", @@ -172,7 +186,7 @@ def create_hook(self): { "REQUESTER_NAME": self.sender.get_full_name(), "COMPANY_NAME": self.company.social_name, - "REQUEST_LINK": f"{'https' if settings.SECURE else 'http'}://{settings.HOSTNAME}/gestion-des-collaborateurs/{self.company.id}", + "REQUEST_LINK": f"{get_base_url()}gestion-des-collaborateurs/{self.company.id}", }, recipient.email, recipient.get_full_name(), @@ -195,7 +209,7 @@ def accept(self, processor): { "REQUESTER_NAME": self.sender.get_full_name(), "COMPANY_NAME": self.company.social_name, - "DASHBOARD_LINK": f"{'https' if settings.SECURE else 'http'}://{settings.HOSTNAME}/tableau-de-bord?company={self.company.id}", + "DASHBOARD_LINK": f"{get_base_url()}tableau-de-bord?company={self.company.id}", }, self.sender.email, self.sender.get_full_name(), @@ -246,13 +260,21 @@ def description(self): return f"{self.sender.name} vous invite à créer un compte Compl'Alim et rejoindre l'entreprise {self.company.social_name}." def create_hook(self): - create_account_page_url = urljoin(get_base_url(), "inscription") + f"?email={self.recipient_email}" - send_mail( - subject="[Compl'Alim] Invitation à créer votre compte", - message=f"{self.description} {self.personal_message_for_mail} Veuillez vous rendre sur la plateforme Compl'Alim pour créer votre compte.", - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=[self.recipient_email], - ) + brevo_template_id = 16 + try: + email.send_sib_template( + brevo_template_id, + { + "COMPANY_NAME": self.company.social_name, + "SENDER_NAME": self.sender.name, + "SIGNUP_LINK": f"{get_base_url()}inscription?email={self.recipient_email}", + }, + self.recipient_email, + self.recipient_email, + ) + except Exception as e: + logger.error(f"Email not sent on CollaborationInvitation creation with id {self.id}") + logger.exception(e) @processable_action def account_created(self, processor): @@ -260,9 +282,19 @@ def account_created(self, processor): for role_classname in self.roles: role_class = apps.get_model("data", role_classname) role_class.objects.get_or_create(company=self.company, user=processor) - send_mail( - subject=f"[Compl'Alim] {processor.name} vous a rejoint en tant que collaborateur.", - message=f"Suite à votre invitation, {processor.name} est devenu colloborateur de votre entreprise {self.company.social_name}.", - from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=[self.sender.email], - ) + + try: + brevo_template_id = 17 + email.send_sib_template( + brevo_template_id, + { + "COMPANY_NAME": self.company.social_name, + "NEW_COLLABORATOR": processor.get_full_name(), + "MEMBERS_LINK": f"{get_base_url()}gestion-des-collaborateurs/{self.company.id}", + }, + self.sender.email, + self.sender.get_full_name(), + ) + except Exception as e: + logger.error(f"Email not sent on CollaborationInvitation account_created with id {self.id}") + logger.exception(e)