Skip to content

Commit

Permalink
♻️(backend) change email invitation content
Browse files Browse the repository at this point in the history
Change the email invitation content. More
document related variables are added.
To benefit of the document inheritance, we moved
the function email_invitation to the document model.
  • Loading branch information
AntoLC committed Sep 24, 2024
1 parent 2775a74 commit a4487ce
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 154 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ and this project adheres to

## [Unreleased]

## Changed

- ♻️(backend) Change email content #283

## Fixed

- 🐛 (backend) gitlab oicd userinfo endpoint #232
- 🐛(backend) gitlab oicd userinfo endpoint #232


## [1.4.0] - 2024-09-17
Expand Down
10 changes: 6 additions & 4 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
)

from core import models
from core.utils import email_invitation

from . import permissions, serializers, utils

Expand Down Expand Up @@ -567,9 +566,10 @@ class DocumentAccessViewSet(
def perform_create(self, serializer):
"""Add a new access to the document and send an email to the new added user."""
access = serializer.save()

language = self.request.headers.get("Content-Language", "en-us")
email_invitation(language, access.user.email, access.document.id)
access.document.email_invitation(
language, access.user.email, access.role, self.request.user.email
)


class TemplateViewSet(
Expand Down Expand Up @@ -769,4 +769,6 @@ def perform_create(self, serializer):
invitation = serializer.save()

language = self.request.headers.get("Content-Language", "en-us")
email_invitation(language, invitation.email, invitation.document.id)
invitation.document.email_invitation(
language, invitation.email, invitation.role, self.request.user.email
)
36 changes: 36 additions & 0 deletions src/backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import hashlib
import smtplib
import tempfile
import textwrap
import uuid
Expand All @@ -13,16 +14,20 @@
from django.conf import settings
from django.contrib.auth import models as auth_models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.sites.models import Site
from django.core import exceptions, mail, validators
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.core.mail import send_mail
from django.db import models
from django.http import FileResponse
from django.template.base import Template as DjangoTemplate
from django.template.context import Context
from django.template.loader import render_to_string
from django.utils import html, timezone
from django.utils.functional import cached_property, lazy
from django.utils.translation import gettext_lazy as _
from django.utils.translation import override

import frontmatter
import markdown
Expand Down Expand Up @@ -522,6 +527,37 @@ def get_abilities(self, user):
"versions_retrieve": can_get_versions,
}

def email_invitation(self, language, email, role, username_sender):
"""Send email invitation."""

domain = Site.objects.get_current().domain

try:
with override(language):
title = _("Document shared with you: %(document)s") % {
"document": self.title
}
template_vars = {
"domain": domain,
"document": self,
"link": f"{domain}/docs/{self.id}/",
"username": username_sender,
"role": RoleChoices(role).label,
}
msg_html = render_to_string("mail/html/invitation.html", template_vars)
msg_plain = render_to_string("mail/text/invitation.txt", template_vars)
send_mail(
title,
msg_plain,
settings.EMAIL_FROM,
[email],
html_message=msg_html,
fail_silently=False,
)

except smtplib.SMTPException as exception:
logger.error("invitation to %s was not sent: %s", email, exception)


class LinkTrace(BaseModel):
"""
Expand Down
92 changes: 92 additions & 0 deletions src/backend/core/tests/test_models_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
Unit tests for the Document model
"""

import smtplib
from logging import Logger
from unittest import mock

from django.contrib.auth.models import AnonymousUser
from django.core import mail
from django.core.exceptions import ValidationError
from django.core.files.storage import default_storage

Expand Down Expand Up @@ -322,3 +327,90 @@ def test_models_documents_version_duplicate():
Bucket=default_storage.bucket_name, Prefix=file_key
)
assert len(response["Versions"]) == 2


def test_models_documents__email_invitation__success():
"""
The email invitation is sent successfully.
"""
document = factories.DocumentFactory()

# pylint: disable-next=no-member
assert len(mail.outbox) == 0

document.email_invitation(
"en", "[email protected]", models.RoleChoices.EDITOR, "[email protected]"
)

# pylint: disable-next=no-member
assert len(mail.outbox) == 1

# pylint: disable-next=no-member
email = mail.outbox[0]

assert email.to == ["[email protected]"]
email_content = " ".join(email.body.split())

assert (
"[email protected] has granted you access to the document as: Editor"
in email_content
)
assert f"docs/{document.id}/" in email_content


def test_models_documents__email_invitation__success_fr():
"""
The email invitation is sent successfully in french.
"""
document = factories.DocumentFactory()

# pylint: disable-next=no-member
assert len(mail.outbox) == 0

document.email_invitation(
"fr-fr", "[email protected]", models.RoleChoices.OWNER, "[email protected]"
)

# pylint: disable-next=no-member
assert len(mail.outbox) == 1

# pylint: disable-next=no-member
email = mail.outbox[0]

assert email.to == ["[email protected]"]
email_content = " ".join(email.body.split())
assert "Invitation à rejoindre Docs !" in email_content
assert f"docs/{document.id}/" in email_content


@mock.patch(
"core.models.send_mail",
side_effect=smtplib.SMTPException("Error SMTPException"),
)
@mock.patch.object(Logger, "error")
def test_models_documents__email_invitation__failed(mock_logger, _mock_send_mail):
"""Check mail behavior when an SMTP error occurs when sent an email invitation."""
document = factories.DocumentFactory()

# pylint: disable-next=no-member
assert len(mail.outbox) == 0

document.email_invitation(
"en", "[email protected]", models.RoleChoices.ADMIN, "[email protected]"
)

# 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 == "[email protected]"
assert isinstance(exception, smtplib.SMTPException)
87 changes: 0 additions & 87 deletions src/backend/core/tests/test_utils.py

This file was deleted.

40 changes: 0 additions & 40 deletions src/backend/core/utils.py

This file was deleted.

31 changes: 9 additions & 22 deletions src/mail/mjml/invitation.mjml
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,27 @@

<mj-body mj-class="bg--blue-100">
<mj-wrapper css-class="wrapper" padding="0 25px 0px 25px">
<mj-section background-url="{{site.domain}}/assets/mail-header-background.png" background-size="cover" background-repeat="no-repeat" background-position="0 -30px">
<mj-section background-url="{{domain}}/assets/mail-header-background.png" background-size="cover" background-repeat="no-repeat" background-position="0 -30px">
<mj-column>
<mj-image align="center" src="{{site.domain}}/assets/logo-suite-numerique.png" width="250px" align="left" alt="{%trans 'La Suite Numérique' %}" />
<mj-image align="center" src="{{domain}}/assets/logo-suite-numerique.png" width="250px" align="left" alt="{%trans 'La Suite Numérique' %}" />
</mj-column>
</mj-section>
<mj-section mj-class="bg--white-100" padding="30px 20px 60px 20px">
<mj-column>
<mj-text font-size="16px">
<p>{% trans "Invitation to join a document !" %}</p>
</mj-text>

<!-- Welcome Message -->
<mj-text>
<h1> {% blocktrans %}Welcome to <strong>Docs!</strong>{% endblocktrans %}</h1>
<h1> {% blocktrans %}{{username}} shared a document with you{% endblocktrans %}</h1>
</mj-text>
<mj-divider border-width="1px" border-style="solid" border-color="#DDDDDD" width="30%" align="left"/>
<!-- Main Message -->
<mj-text>{% trans "We are delighted to welcome you to our community on Docs, your new companion to collaborate on documents efficiently, intuitively, and securely." %}</mj-text>
<mj-text>{% trans "Our application is designed to help you organize, collaborate, and manage permissions." %}</mj-text>
<mj-text>{% blocktrans %}{{username}} has granted you access to the document {{document.title}} as: {{role}}{% endblocktrans %}</mj-text>
<mj-text>
{% trans "With Docs, you will be able to:" %}
<ul>
<li>{% trans "Create documents."%}</li>
<li>{% trans "Work offline."%}</li>
<li>{% trans "Invite members of your community to your document in just a few clicks."%}</li>
</ul>
<a mj-class="color-text" href="{{link}}">{{document.title}}</a>
</mj-text>
<mj-button href="{{site.domain}}/docs/{{document_id}}/" background-color="#000091" color="white" padding-bottom="30px">
{% trans "Visit Docs"%}
<mj-button href="{{link}}" background-color="#000091" color="white" padding-bottom="30px">
{% trans "Open the document"%}
</mj-button>
<mj-text>{% trans "We are confident that Docs will help you increase efficiency and productivity while strengthening the bond among members." %}</mj-text>
<mj-text>{% 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." %}</mj-text>
<mj-text>{% trans "Once again, welcome aboard! We are eager to accompany you on your collaboration adventure." %}</mj-text>

<mj-divider border-width="1px" border-style="solid" border-color="#DDDDDD" width="30%" align="left"/>
<mj-text>{% trans "Docs, your new essential tool for organizing, sharing and managing your documents as a team." %}</mj-text>
<!-- Signature -->
<mj-text>
<p>{% trans "Sincerely," %}</p>
Expand Down

0 comments on commit a4487ce

Please sign in to comment.