🐛(domains) use a dedicated mail to invite user to manage domain

- modify models to allow to specify path to mail template
- rename team invitation template
- fix logo and text used for domain invitation email
This commit is contained in:
Sabrina Demagny
2025-03-10 10:04:00 +01:00
parent 185b87da40
commit ebc2b02d22
12 changed files with 156 additions and 24 deletions

View File

@@ -16,6 +16,7 @@ and this project adheres to
### Fixed ### Fixed
- 🐛(domains) use a dedicated mail to invite user to manage domain
- 🐛(mailbox) fix mailbox creation email language - 🐛(mailbox) fix mailbox creation email language
## [1.13.1] - 2025-03-04 ## [1.13.1] - 2025-03-04

View File

@@ -908,6 +908,9 @@ class BaseInvitation(BaseModel):
related_name="invitations", related_name="invitations",
) )
MAIL_TEMPLATE_HTML = None
MAIL_TEMPLATE_TXT = None
class Meta: class Meta:
abstract = True abstract = True
@@ -938,17 +941,24 @@ class BaseInvitation(BaseModel):
validity_duration = timedelta(seconds=settings.INVITATION_VALIDITY_DURATION) validity_duration = timedelta(seconds=settings.INVITATION_VALIDITY_DURATION)
return timezone.now() > (self.created_at + validity_duration) return timezone.now() > (self.created_at + validity_duration)
def _get_mail_subject(self):
"""Get the subject of the invitation."""
return gettext("Invitation to join La Régie!")
def _get_mail_context(self):
"""Get the template variables for the invitation."""
return {
"site": Site.objects.get_current(),
}
def email_invitation(self): def email_invitation(self):
"""Email invitation to the user.""" """Email invitation to the user."""
try: try:
with override(self.issuer.language): with override(self.issuer.language):
subject = gettext("Invitation to join La Régie!") subject = self._get_mail_subject()
template_vars = { context = self._get_mail_context()
"title": subject, msg_html = render_to_string(self.MAIL_TEMPLATE_HTML, context)
"site": Site.objects.get_current(), msg_plain = render_to_string(self.MAIL_TEMPLATE_TXT, context)
}
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( mail.send_mail(
subject, subject,
msg_plain, msg_plain,
@@ -974,6 +984,9 @@ class Invitation(BaseInvitation):
max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER
) )
MAIL_TEMPLATE_HTML = "mail/html/team_invitation.html"
MAIL_TEMPLATE_TXT = "mail/text/team_invitation.txt"
class Meta: class Meta:
db_table = "people_invitation" db_table = "people_invitation"
verbose_name = _("Team invitation") verbose_name = _("Team invitation")

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -262,7 +262,7 @@ def test_models_team_invitations_email():
assert email.subject == "Invitation à rejoindre La Régie !" assert email.subject == "Invitation à rejoindre La Régie !"
email_content = " ".join(email.body.split()) email_content = " ".join(email.body.split())
assert "Invitation à rejoindre La Régie !" in email_content assert "Nous sommes ravis de vous accueillir" in email_content
assert "[//example.com]" in email_content assert "[//example.com]" in email_content

View File

@@ -3,25 +3,37 @@
from django.urls import path from django.urls import path
from .views import ( from .views import (
DebugViewHtml, DebugViewMaildomainInvitationHtml,
DebugViewMaildomainInvitationTxt,
DebugViewNewMailboxHtml, DebugViewNewMailboxHtml,
DebugViewTxt, DebugViewTeamInvitationHtml,
DebugViewTeamInvitationTxt,
) )
urlpatterns = [ urlpatterns = [
path( path(
"__debug__/mail/invitation_html", "__debug__/mail/team_invitation_html",
DebugViewHtml.as_view(), DebugViewTeamInvitationHtml.as_view(),
name="debug.mail.invitation_html", name="debug.mail.team_invitation_html",
), ),
path( path(
"__debug__/mail/invitation_txt", "__debug__/mail/team_invitation_txt",
DebugViewTxt.as_view(), DebugViewTeamInvitationTxt.as_view(),
name="debug.mail.invitation_txt", name="debug.mail.team_invitation_txt",
), ),
path( path(
"__debug__/mail/new_mailbox_html", "__debug__/mail/new_mailbox_html",
DebugViewNewMailboxHtml.as_view(), DebugViewNewMailboxHtml.as_view(),
name="debug.mail.new_mailbox_html", name="debug.mail.new_mailbox_html",
), ),
path(
"__debug__/mail/maildomain_invitation_txt",
DebugViewMaildomainInvitationTxt.as_view(),
name="debug.mail.maildomain_invitation_txt",
),
path(
"__debug__/mail/maildomain_invitation_html",
DebugViewMaildomainInvitationHtml.as_view(),
name="debug.mail.maildomain_invitation_html",
),
] ]

View File

@@ -8,25 +8,49 @@ class DebugBaseView(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Generates sample datas to have a valid debug email""" """Generates sample datas to have a valid debug email"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["title"] = "Development email preview" context["title"] = "Development email preview"
return context return context
class DebugViewHtml(DebugBaseView): # TEAM INVITATION
"""Debug View for HTML Email Layout""" class DebugViewTeamInvitationHtml(DebugBaseView):
"""Debug view for team invitation html email layout"""
template_name = "mail/html/invitation.html" template_name = "mail/html/team_invitation.html"
class DebugViewTxt(DebugBaseView): class DebugViewTeamInvitationTxt(DebugBaseView):
"""Debug View for Text Email Layout""" """Debug view for team invitation text email layout"""
template_name = "mail/text/invitation.txt" template_name = "mail/text/team_invitation.txt"
# MAIL DOMAIN INVITATION
class DebugViewMaildomainInvitationBase(DebugBaseView):
"""Debug view for mail domain invitation base email layout"""
def get_context_data(self, **kwargs):
"""Add some fake context data for mail domain invitation email layout"""
context = super().get_context_data(**kwargs)
context["domain"] = "example.com"
context["role"] = "owner"
return context
class DebugViewMaildomainInvitationHtml(DebugViewMaildomainInvitationBase):
"""Debug view for mail domain invitation html email layout"""
template_name = "mail/html/maildomain_invitation.html"
class DebugViewMaildomainInvitationTxt(DebugViewMaildomainInvitationBase):
"""Debug view for mail domain invitation text email layout"""
template_name = "mail/text/maildomain_invitation.txt"
# NEW MAILBOX
class DebugViewNewMailboxHtml(DebugBaseView): class DebugViewNewMailboxHtml(DebugBaseView):
"""Debug view for new mailbox email layout""" """Debug view for new mailbox email layout"""

View File

@@ -7,6 +7,7 @@ from django.contrib.auth.base_user import AbstractBaseUser
from django.core import exceptions, validators from django.core import exceptions, validators
from django.db import models from django.db import models
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.models import BaseInvitation, BaseModel, Organization, User from core.models import BaseInvitation, BaseModel, Organization, User
@@ -294,6 +295,9 @@ class MailDomainInvitation(BaseInvitation):
default=MailDomainRoleChoices.VIEWER, default=MailDomainRoleChoices.VIEWER,
) )
MAIL_TEMPLATE_HTML = "mail/html/maildomain_invitation.html"
MAIL_TEMPLATE_TXT = "mail/text/maildomain_invitation.txt"
class Meta: class Meta:
db_table = "people_mail_domain_invitation" db_table = "people_mail_domain_invitation"
verbose_name = _("Mail domain invitation") verbose_name = _("Mail domain invitation")
@@ -307,6 +311,18 @@ class MailDomainInvitation(BaseInvitation):
def __str__(self): def __str__(self):
return f"{self.email} invited to {self.domain}" return f"{self.email} invited to {self.domain}"
def _get_mail_subject(self):
"""Get the subject of the invitation."""
return gettext("[La Suite] You have been invited to join La Régie")
def _get_mail_context(self):
"""Get the template variables for the invitation."""
return {
**super()._get_mail_context(),
"domain": self.domain.name,
"role": self.get_role_display(),
}
def get_abilities(self, user): def get_abilities(self, user):
"""Compute and return abilities for a given user.""" """Compute and return abilities for a given user."""
can_delete = False can_delete = False

View File

@@ -3,6 +3,8 @@ Tests for MailDomainInvitations API endpoint in People's app mailbox_manager.
Focus on "create" action. Focus on "create" action.
""" """
from django.core import mail
import pytest import pytest
from rest_framework import status from rest_framework import status
from rest_framework.test import APIClient from rest_framework.test import APIClient
@@ -70,12 +72,18 @@ def test_api_domain_invitations__admin_should_create_invites(role):
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
assert len(mail.outbox) == 0
response = client.post( response = client.post(
f"/api/v1.0/mail-domains/{domain.slug}/invitations/", f"/api/v1.0/mail-domains/{domain.slug}/invitations/",
invitation_values, invitation_values,
format="json", format="json",
) )
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert email.to == [invitation_values["email"]]
assert email.subject == "[La Suite] You have been invited to join La Régie"
def test_api_domain_invitations__viewers_should_not_invite_to_manage_domains(): def test_api_domain_invitations__viewers_should_not_invite_to_manage_domains():

View File

@@ -0,0 +1,58 @@
<mjml>
<mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100">
<mj-wrapper css-class="wrapper" padding="10px">
<mj-section>
<mj-column>
<mj-image align="center" src="{% base64_static 'images/logo-laregie.png' %}" width="157px" align="left" alt="{%trans 'La Régie' %}" padding="10px" />
</mj-column>
</mj-section>
<mj-section mj-class="bg--white-100">
<mj-column>
<mj-divider border-width="1px" border-style="solid" border-color="#EEEEEE" width="100%" padding="10px 20px" />
<mj-text>
<strong>{% trans "Welcome to" %} La Régie</strong>
</mj-text>
<!-- Main Message -->
<mj-text>{% trans "Hello," %}</mj-text>
<mj-text>{% blocktranslate with role=role domain=domain trimmed %}
You have been invited to join La Régie to be {{ role }} of the domain {{ domain }}.
{% endblocktranslate %}
</mj-text>
<mj-text>{% trans "To do so, please log in La Régie via ProConnect, by following this link:" %}</mj-text>
<mj-button href="//{{ site.domain }}" background-color="#000091" color="white" padding-bottom="30px">
<img src="{% base64_static 'images/arrow.png' %}" width="25px" align="left" alt="arrow" background-color="red"/>
{% trans "Go to La Régie"%}
</mj-button>
<!-- end Main Message -->
<mj-text>
<strong>{% trans "What is La Régie?" %}</strong>
</mj-text>
<mj-text>
{% trans "La Régie is the administration center of la Suite, where you can manage users, groups and domains. You will be able to:" %}
<ul>
<li>{% trans "create work groups,"%}</li>
<li>{% trans "invite new members,"%}</li>
<li>{% trans "manage mail domains,"%}</li>
<li>{% trans "create new mail accounts,"%}</li>
<li>{% trans "etc."%}</li>
</ul>
</mj-text>
<mj-text>
{% trans "Welcome aboard!" %}
</mj-text>
<!-- Signature -->
<mj-text>
<p>{% trans "Regards," %}</p>
<p>{% trans "La Suite Team" %}</p>
</mj-text>
<mj-divider border-width="1px" border-style="solid" border-color="#EEEEEE" width="100%" />
<mj-image align="left" src="{% base64_static 'images/logo-footer-mail.png' %}" width="160px" align="left" alt="La Suite" />
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>