🐛(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:
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
BIN
src/backend/core/static/images/arrow.png
Normal file
BIN
src/backend/core/static/images/arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/backend/core/static/images/logo-footer-mail.png
Normal file
BIN
src/backend/core/static/images/logo-footer-mail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
src/backend/core/static/images/logo-laregie.png
Normal file
BIN
src/backend/core/static/images/logo-laregie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"""
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
58
src/mail/mjml/maildomain_invitation.mjml
Normal file
58
src/mail/mjml/maildomain_invitation.mjml
Normal 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>
|
||||||
|
|
||||||
Reference in New Issue
Block a user