From cc33e1d259cfbf4b91b1a3921334c5456245547e Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 12 Jul 2017 02:50:29 -0400 Subject: [PATCH] Add support for emailing documents to a recipient list. GitLab #396 Signed-off-by: Roberto Rosario --- HISTORY.rst | 5 +- mayan/apps/mailer/literals.py | 2 + mayan/apps/mailer/models.py | 52 ++++++++---------- mayan/apps/mailer/tasks.py | 34 +++++------- mayan/apps/mailer/tests/literals.py | 6 +++ mayan/apps/mailer/tests/mailers.py | 3 ++ mayan/apps/mailer/tests/test_models.py | 75 ++++++++++++++++++++++++++ mayan/apps/mailer/utils.py | 21 ++++++++ 8 files changed, 145 insertions(+), 53 deletions(-) create mode 100644 mayan/apps/mailer/tests/test_models.py create mode 100644 mayan/apps/mailer/utils.py diff --git a/HISTORY.rst b/HISTORY.rst index 2fd3e277d6..8a3feebb87 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,7 +1,8 @@ 2.5.3 (2017-07-XX) ================== -- Fix HTML mark up in window title. GitLab issue #397. - +- Fix HTML mark up in window title. GitLab #397. +- Add support for emailing documents to a recipient list. GitLab #396 + 2.5.2 (2017-07-08) ================== - Improve new document creation signal handling. diff --git a/mayan/apps/mailer/literals.py b/mayan/apps/mailer/literals.py index d7940ba4ad..b22405cee3 100644 --- a/mayan/apps/mailer/literals.py +++ b/mayan/apps/mailer/literals.py @@ -14,3 +14,5 @@ DEFAULT_LINK_BODY_TEMPLATE = _( '{{ link }}\n\n--------\n ' 'This email has been sent from %(project_title)s (%(project_website)s)' ) + +EMAIL_SEPARATORS = (',', ';') diff --git a/mayan/apps/mailer/models.py b/mayan/apps/mailer/models.py index 9e7972d08d..960b1d5dbc 100644 --- a/mayan/apps/mailer/models.py +++ b/mayan/apps/mailer/models.py @@ -8,6 +8,8 @@ from django.db import models from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ +from .utils import split_recipient_list + logger = logging.getLogger(__name__) @@ -80,36 +82,28 @@ class UserMailer(models.Model): self.backend_data = json.dumps(data) self.save() - def send(self, **kwargs): - """ - https://docs.djangoproject.com/en/1.11/topics/email - #django.core.mail.EmailMessage - subject: The subject line of the email. - body: The body text. This should be a plain text message. - from_email: The sender's address. Both fred@example.com and Fred - forms are legal. If omitted, - the DEFAULT_FROM_EMAIL setting is used. - to: A list or tuple of recipient addresses. - bcc: A list or tuple of addresses used in the "Bcc" header when - sending the email. - connection: An email backend instance. Use this parameter if you want - to use the same connection for multiple messages. If omitted, a new - connection is created when send() is called. - attachments: A list of attachments to put on the message. These can be - either email.MIMEBase.MIMEBase instances, or (filename, content, - mimetype) triples. - headers: A dictionary of extra headers to put on the message. The - keys are the header name, values are the header values. It's up to - the caller to ensure header names and values are in the correct - format for an email message. The corresponding attribute is - extra_headers. - cc: A list or tuple of recipient addresses used in the "Cc" - header when sending the email. - reply_to: A list or tuple of recipient addresses used in the - "Reply-To" header when sending the email. - """ + def send(self, subject='', body='', to=None, document=None, as_attachment=False): + recipient_list = split_recipient_list(recipients=[to]) + with self.get_connection() as connection: - mail.EmailMessage(connection=connection, **kwargs).send() + email_message = mail.EmailMultiAlternatives( + subject=subject, body=body, to=recipient_list, + connection=connection + ) + + if as_attachment: + with document.open() as descriptor: + email_message.attach( + filename=document.label, content=descriptor.read(), + mimetype=document.file_mimetype + ) + + try: + email_message.send() + except Exception as exception: + self.error_log.create(message=exception) + else: + self.error_log.all().delete() def test(self, to): self.send(to=to, subject=_('Test email from Mayan EDMS')) diff --git a/mayan/apps/mailer/tasks.py b/mayan/apps/mailer/tasks.py index 11dce91471..733e7edca1 100644 --- a/mayan/apps/mailer/tasks.py +++ b/mayan/apps/mailer/tasks.py @@ -1,37 +1,27 @@ from __future__ import unicode_literals from django.apps import apps -from django.core.mail import EmailMultiAlternatives -from documents.models import Document from mayan.celery import app @app.task(ignore_result=True) -def task_send_document(subject_text, body_text_content, sender, recipient, document_id, user_mailer_id, as_attachment=False): +def task_send_document(subject_text, body_text_content, sender, recipient, user_mailer_id, as_attachment=False, document_id=None): + Document = apps.get_model( + app_label='documents', model_name='Document' + ) UserMailer = apps.get_model( app_label='mailer', model_name='UserMailer' ) + if document_id: + document = Document.objects.get(pk=document_id) + else: + document = None + user_mailer = UserMailer.objects.get(pk=user_mailer_id) - connection = user_mailer.get_connection() - - email_msg = EmailMultiAlternatives( - subject_text, body_text_content, sender, [recipient], - connection=connection, + user_mailer.send( + subject=subject_text, body=body_text_content, to=recipient, + document=document, as_attachment=as_attachment ) - - if as_attachment: - document = Document.objects.get(pk=document_id) - with document.open() as descriptor: - email_msg.attach( - document.label, descriptor.read(), document.file_mimetype - ) - - try: - email_msg.send() - except Exception as exception: - user_mailer.error_log.create(message=exception) - else: - user_mailer.error_log.all().delete() diff --git a/mayan/apps/mailer/tests/literals.py b/mayan/apps/mailer/tests/literals.py index e93ab2f97c..9c1f0dd8c9 100644 --- a/mayan/apps/mailer/tests/literals.py +++ b/mayan/apps/mailer/tests/literals.py @@ -1,5 +1,11 @@ from __future__ import unicode_literals TEST_EMAIL_ADDRESS = 'test@example.com' +TEST_RECIPIENTS_MULTIPLE_COMMA = 'test@example.com,test2@example.com' +TEST_RECIPIENTS_MULTIPLE_SEMICOLON = 'test@example.com;test2@example.com' +TEST_RECIPIENTS_MULTIPLE_MIXED = 'test@example.com,test2@example.com;test2@example.com' +TEST_RECIPIENTS_MULTIPLE_MIXED_LIST = ( + 'test@example.com', 'test2@example.com', 'test2@example.com', +) TEST_USER_MAILER_LABEL = 'test user mailer label' TEST_USER_MAILER_BACKEND_PATH = 'mailer.tests.mailers.TestBackend' diff --git a/mayan/apps/mailer/tests/mailers.py b/mayan/apps/mailer/tests/mailers.py index b56b3540ae..e01746309f 100644 --- a/mayan/apps/mailer/tests/mailers.py +++ b/mayan/apps/mailer/tests/mailers.py @@ -4,5 +4,8 @@ from ..classes import MailerBackend class TestBackend(MailerBackend): + """ + User mailer backend to use with tests + """ class_path = 'django.core.mail.backends.locmem.EmailBackend' label = 'Django local memory backend' diff --git a/mayan/apps/mailer/tests/test_models.py b/mayan/apps/mailer/tests/test_models.py new file mode 100644 index 0000000000..a8eac0089e --- /dev/null +++ b/mayan/apps/mailer/tests/test_models.py @@ -0,0 +1,75 @@ +from __future__ import absolute_import, unicode_literals + +from django.core import mail + +from documents.tests.test_models import GenericDocumentTestCase + +from ..models import UserMailer + +from .literals import ( + TEST_EMAIL_ADDRESS, TEST_RECIPIENTS_MULTIPLE_COMMA, + TEST_RECIPIENTS_MULTIPLE_SEMICOLON, TEST_RECIPIENTS_MULTIPLE_MIXED, + TEST_RECIPIENTS_MULTIPLE_MIXED_LIST, TEST_USER_MAILER_LABEL, + TEST_USER_MAILER_BACKEND_PATH +) + + +class ModelTestCase(GenericDocumentTestCase): + def _create_user_mailer(self): + self.user_mailer = UserMailer.objects.create( + default=True, + enabled=True, + label=TEST_USER_MAILER_LABEL, + backend_path=TEST_USER_MAILER_BACKEND_PATH, + backend_data='{}' + ) + + def test_send_simple(self): + self._create_user_mailer() + self.user_mailer.send(to=TEST_EMAIL_ADDRESS) + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) + + def test_send_attachment(self): + self._create_user_mailer() + self.user_mailer.send( + to=TEST_EMAIL_ADDRESS, document=self.document, as_attachment=True + ) + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) + with self.document.open() as file_object: + self.assertEqual( + mail.outbox[0].attachments[0], ( + self.document.label, file_object.read(), + self.document.file_mimetype + ) + ) + + def test_send_multiple_recipients_comma(self): + self._create_user_mailer() + self.user_mailer.send(to=TEST_RECIPIENTS_MULTIPLE_COMMA) + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA.split(',') + ) + + def test_send_multiple_recipients_semicolon(self): + self._create_user_mailer() + self.user_mailer.send(to=TEST_RECIPIENTS_MULTIPLE_SEMICOLON) + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON.split(';') + ) + + def test_send_multiple_recipient_mixed(self): + self._create_user_mailer() + self.user_mailer.send(to=TEST_RECIPIENTS_MULTIPLE_MIXED) + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual( + list(mail.outbox[0].to), list(TEST_RECIPIENTS_MULTIPLE_MIXED_LIST) + ) diff --git a/mayan/apps/mailer/utils.py b/mayan/apps/mailer/utils.py new file mode 100644 index 0000000000..d0226762af --- /dev/null +++ b/mayan/apps/mailer/utils.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals + +from .literals import EMAIL_SEPARATORS + + +def split_recipient_list(recipients, separator_list=None, separator_index=0): + separator_list = separator_list or EMAIL_SEPARATORS + + try: + separator = separator_list[separator_index] + except IndexError: + return recipients + else: + result = [] + for recipient in recipients: + result.extend(recipient.split(separator)) + + return split_recipient_list( + recipients=result, separator_list=separator_list, + separator_index=separator_index + 1 + )