diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 093cf14b..78fc0242 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -36,6 +36,7 @@ from rest_framework.throttling import UserRateThrottle from core import authentication, enums, models from core.services.ai_services import AIService from core.services.collaboration_services import CollaborationService +from core.tasks.mail import send_ask_for_access_mail from core.utils import extract_attachments, filter_descendants from . import permissions, serializers, utils @@ -1829,12 +1830,14 @@ class DocumentAskForAccessViewSet( status=drf.status.HTTP_400_BAD_REQUEST, ) - models.DocumentAskForAccess.objects.create( + ask_for_access = models.DocumentAskForAccess.objects.create( document=document, user=request.user, role=serializer.validated_data["role"], ) + send_ask_for_access_mail.delay(ask_for_access.id) + return drf.response.Response(status=drf.status.HTTP_201_CREATED) @drf.decorators.action(detail=True, methods=["post"]) diff --git a/src/backend/core/models.py b/src/backend/core/models.py index f137802d..fb3443ce 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -876,8 +876,8 @@ class Document(MP_Node, BaseModel): ) with override(language): - msg_html = render_to_string("mail/html/invitation.html", context) - msg_plain = render_to_string("mail/text/invitation.txt", context) + msg_html = render_to_string("mail/html/template.html", context) + msg_plain = render_to_string("mail/text/template.txt", context) subject = str(subject) # Force translation try: @@ -1221,6 +1221,39 @@ class DocumentAskForAccess(BaseModel): ) self.delete() + def send_ask_for_access_email(self, email, language=None): + """ + Method allowing a user to send an email notification when asking for access to a document. + """ + + language = language or get_language() + sender = self.user + sender_name = sender.full_name or sender.email + sender_name_email = ( + f"{sender.full_name:s} ({sender.email})" + if sender.full_name + else sender.email + ) + + with override(language): + context = { + "title": _("{name} would like access to a document!").format( + name=sender_name + ), + "message": _( + "{name} would like access to the following document:" + ).format(name=sender_name_email), + } + subject = ( + context["title"] + if not self.document.title + else _("{name} is asking for access to the document: {title}").format( + name=sender_name, title=self.document.title + ) + ) + + self.document.send_email(subject, [email], context, language) + class Template(BaseModel): """HTML and CSS code used for formatting the print around the MarkDown body.""" diff --git a/src/backend/core/tasks/__init__.py b/src/backend/core/tasks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/backend/core/tasks/mail.py b/src/backend/core/tasks/mail.py new file mode 100644 index 00000000..483c9614 --- /dev/null +++ b/src/backend/core/tasks/mail.py @@ -0,0 +1,24 @@ +"""Send mail using celery task.""" + +from django.conf import settings + +from core import models + +from impress.celery_app import app + + +@app.task +def send_ask_for_access_mail(ask_for_access_id): + """Send mail using celery task.""" + # Send email to document owners/admins + ask_for_access = models.DocumentAskForAccess.objects.get(id=ask_for_access_id) + owner_admin_accesses = models.DocumentAccess.objects.filter( + document=ask_for_access.document, role__in=models.PRIVILEGED_ROLES + ).select_related("user") + + for access in owner_admin_accesses: + if access.user and access.user.email: + ask_for_access.send_ask_for_access_email( + access.user.email, + access.user.language or settings.LANGUAGE_CODE, + ) diff --git a/src/backend/core/tests/documents/test_api_documents_ask_for_access.py b/src/backend/core/tests/documents/test_api_documents_ask_for_access.py index f8f03189..2fcdf167 100644 --- a/src/backend/core/tests/documents/test_api_documents_ask_for_access.py +++ b/src/backend/core/tests/documents/test_api_documents_ask_for_access.py @@ -2,6 +2,8 @@ import uuid +from django.core import mail + import pytest from rest_framework.test import APIClient @@ -41,13 +43,26 @@ def test_api_documents_ask_for_access_create_invalid_document_id(): def test_api_documents_ask_for_access_create_authenticated(): - """Authenticated users should be able to create a document ask for access.""" - document = DocumentFactory() + """ + Authenticated users should be able to create a document ask for access. + An email should be sent to document owners and admins to notify them. + """ + owner_user = UserFactory(language="en-us") + admin_user = UserFactory(language="fr-fr") + document = DocumentFactory( + users=[ + (owner_user, RoleChoices.OWNER), + (admin_user, RoleChoices.ADMIN), + ] + ) + user = UserFactory() client = APIClient() client.force_login(user) + assert len(mail.outbox) == 0 + response = client.post(f"/api/v1.0/documents/{document.id}/ask-for-access/") assert response.status_code == 201 @@ -57,6 +72,30 @@ def test_api_documents_ask_for_access_create_authenticated(): role=RoleChoices.READER, ).exists() + # Verify emails were sent to both owner and admin + assert len(mail.outbox) == 2 + + # Check that emails were sent to the right recipients + email_recipients = [email.to[0] for email in mail.outbox] + assert owner_user.email in email_recipients + assert admin_user.email in email_recipients + + # Check email content for both users + for email in mail.outbox: + email_content = " ".join(email.body.split()) + email_subject = " ".join(email.subject.split()) + + # Check that the requesting user's name is in the email + user_name = user.full_name or user.email + assert user_name.lower() in email_content.lower() + + # Check that the subject mentions access request + assert "access" in email_subject.lower() + + # Check that the document title is mentioned if it exists + if document.title: + assert document.title.lower() in email_subject.lower() + def test_api_documents_ask_for_access_create_authenticated_specific_role(): """ diff --git a/src/mail/mjml/invitation.mjml b/src/mail/mjml/template.mjml similarity index 100% rename from src/mail/mjml/invitation.mjml rename to src/mail/mjml/template.mjml