diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 6fd346045..11bac69d5 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -4,22 +4,28 @@ Declare and configure the models for the People core application import json import os +import smtplib import uuid from datetime import timedelta +from logging import getLogger from django.conf import settings from django.contrib.auth import models as auth_models from django.contrib.auth.base_user import AbstractBaseUser from django.core import exceptions, mail, validators from django.db import models +from django.template.loader import render_to_string from django.utils import timezone from django.utils.functional import lazy from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ +from django.utils.translation import override import jsonschema from timezone_field import TimeZoneField +logger = getLogger(__name__) + current_dir = os.path.dirname(os.path.abspath(__file__)) contact_schema_path = os.path.join(current_dir, "jsonschema", "contact_data.json") with open(contact_schema_path, "r", encoding="utf-8") as contact_schema_file: @@ -516,6 +522,8 @@ class Invitation(BaseModel): raise exceptions.PermissionDenied() super().save(*args, **kwargs) + self.email_invitation() + def clean(self): """Validate fields.""" super().clean() @@ -559,3 +567,24 @@ class Invitation(BaseModel): "patch": False, "put": False, } + + def email_invitation(self): + """Email invitation to the user.""" + try: + with override(self.issuer.language): + template_vars = { + "title": _("Invitation to join Desk!"), + } + msg_html = render_to_string("mail/html/invitation.html", template_vars) + msg_plain = render_to_string("mail/text/invitation.txt", template_vars) + mail.send_mail( + _("Invitation to join Desk!"), + msg_plain, + settings.EMAIL_FROM, + [self.email], + html_message=msg_html, + fail_silently=False, + ) + + except smtplib.SMTPException as exception: + logger.error("invitation to %s was not sent: %s", self.email, exception) diff --git a/src/backend/core/static/images/logo-suite-numerique.png b/src/backend/core/static/images/logo-suite-numerique.png new file mode 100644 index 000000000..243c96629 Binary files /dev/null and b/src/backend/core/static/images/logo-suite-numerique.png differ diff --git a/src/backend/core/static/images/logo.png b/src/backend/core/static/images/logo.png new file mode 100644 index 000000000..b36bff2fa Binary files /dev/null and b/src/backend/core/static/images/logo.png differ diff --git a/src/backend/core/static/images/mail-header-background.png b/src/backend/core/static/images/mail-header-background.png new file mode 100644 index 000000000..89d161155 Binary files /dev/null and b/src/backend/core/static/images/mail-header-background.png differ diff --git a/src/backend/core/tests/test_models_invitations.py b/src/backend/core/tests/test_models_invitations.py index 8f487fe4e..7da3a5447 100644 --- a/src/backend/core/tests/test_models_invitations.py +++ b/src/backend/core/tests/test_models_invitations.py @@ -2,11 +2,14 @@ Unit tests for the Invitation model """ +import smtplib import time import uuid +from logging import Logger +from unittest import mock from django.contrib.auth.models import AnonymousUser -from django.core import exceptions +from django.core import exceptions, mail import pytest from faker import Faker @@ -236,3 +239,64 @@ def test_models_team_invitations_get_abilities_member(): "patch": False, "put": False, } + + +def test_models_team_invitations_email(): + """Check email invitation during invitation creation.""" + + member_access = factories.TeamAccessFactory(role="member") + team = member_access.team + + # pylint: disable-next=no-member + assert len(mail.outbox) == 0 + + factories.TeamAccessFactory(team=team) + invitation = factories.InvitationFactory(team=team, email="john@people.com") + + # pylint: disable-next=no-member + assert len(mail.outbox) == 1 + + # pylint: disable-next=no-member + (email,) = mail.outbox + + assert email.to == [invitation.email] + assert email.subject == "Invitation to join Desk!" + + email_content = " ".join(email.body.split()) + assert "Invitation to join Desk!" in email_content + + +@mock.patch( + "django.core.mail.send_mail", + side_effect=smtplib.SMTPException("Error SMTPException"), +) +@mock.patch.object(Logger, "error") +def test_models_team_invitations_email_failed(mock_logger, _mock_send_mail): + """Check invitation behavior when an SMTP error occurs during invitation creation.""" + + member_access = factories.TeamAccessFactory(role="member") + team = member_access.team + + # pylint: disable-next=no-member + assert len(mail.outbox) == 0 + + factories.TeamAccessFactory(team=team) + + # No error should be raised + invitation = factories.InvitationFactory(team=team, email="john@people.com") + + # No email has been sent + # pylint: disable-next=no-member + assert len(mail.outbox) == 0 + + # Logger should be called + mock_logger.assert_called_once() + + ( + _, + email, + exception, + ) = mock_logger.call_args.args + + assert email == invitation.email + assert isinstance(exception, smtplib.SMTPException) diff --git a/src/backend/debug/urls.py b/src/backend/debug/urls.py index 2a470fbf0..f9e29b45b 100644 --- a/src/backend/debug/urls.py +++ b/src/backend/debug/urls.py @@ -9,13 +9,13 @@ from .views import ( urlpatterns = [ path( - "__debug__/mail/hello_html", + "__debug__/mail/invitation_html", DebugViewHtml.as_view(), - name="debug.mail.hello_html", + name="debug.mail.invitation_html", ), path( - "__debug__/mail/hello_txt", + "__debug__/mail/invitation_txt", DebugViewTxt.as_view(), - name="debug.mail.hello_txt", + name="debug.mail.invitation_txt", ), ] diff --git a/src/backend/debug/views.py b/src/backend/debug/views.py index bb0076790..29e52779c 100644 --- a/src/backend/debug/views.py +++ b/src/backend/debug/views.py @@ -11,8 +11,6 @@ class DebugBaseView(TemplateView): context = super().get_context_data(**kwargs) context["title"] = "Development email preview" - context["email"] = "random@gmail.com" - context["fullname"] = "robert" return context @@ -20,10 +18,10 @@ class DebugBaseView(TemplateView): class DebugViewHtml(DebugBaseView): """Debug View for HTML Email Layout""" - template_name = "mail/html/hello.html" + template_name = "mail/html/invitation.html" class DebugViewTxt(DebugBaseView): """Debug View for Text Email Layout""" - template_name = "mail/text/hello.txt" + template_name = "mail/text/invitation.txt" diff --git a/src/mail/mjml/hello.mjml b/src/mail/mjml/hello.mjml deleted file mode 100644 index 276315493..000000000 --- a/src/mail/mjml/hello.mjml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - -

- {%if fullname%} - {% blocktranslate with name=fullname %}Hello {{ name }}{% endblocktranslate %} - {% else %} - {%trans "Hello" %} - {% endif %}
- {%trans "Thank you very much for your visit!"%} -

-
-
-
-
- -
-
- diff --git a/src/mail/mjml/invitation.mjml b/src/mail/mjml/invitation.mjml new file mode 100644 index 000000000..125246018 --- /dev/null +++ b/src/mail/mjml/invitation.mjml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + +

{% trans "Invitation to join a team" %}

+
+ + + +

{% trans "Welcome to" %} Equipes

+
+ + + + + + {% trans "We are delighted to welcome you to our community on Equipes, your new companion to simplify the management of your groups efficiently, intuitively, and securely." %} + {% trans "Our application is designed to help you organize, collaborate, and manage permissions." %} + + {% trans "With Equipes, you will be able to:" %} +
    +
  • {% trans "Create customized groups according to your specific needs."%}
  • +
  • {% trans "Invite members of your team or community in just a few clicks."%}
  • +
  • {% trans "Plan events, meetings, or activities effortlessly with our integrated calendar."%}
  • +
  • {% trans "Share documents, photos, and important information securely."%}
  • +
  • {% trans "Facilitate exchanges and communication with our messaging and group discussion tools."%}
  • +
+
+ + {% trans "Visit Equipes"%} + + {% trans "We are confident that Equipes will help you increase efficiency and productivity while strengthening the bond among members." %} + {% trans "Feel free to explore all the features of the application and share your feedback and suggestions with us. Your feedback is valuable to us and will enable us to continually improve our service." %} + {% trans "Once again, welcome aboard! We are eager to accompany you on this group management adventure." %} + + + +

{% trans "Sincerely," %}

+

{% trans "The La Suite Numérique Team" %}

+
+
+
+
+
+ + +
+ diff --git a/src/mail/mjml/partial/header.mjml b/src/mail/mjml/partial/header.mjml index 665a3c026..37bc590fc 100644 --- a/src/mail/mjml/partial/header.mjml +++ b/src/mail/mjml/partial/header.mjml @@ -14,10 +14,8 @@ font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" font-size="16px" line-height="1.5em" - color="#031963" + color="#3A3A3A" /> - - /* Reset */ @@ -33,7 +31,7 @@ /* Global styles */ h1 { - color: #055FD2; + color: #161616; font-size: 2rem; line-height: 1em; font-weight: 700;