diff --git a/HISTORY.rst b/HISTORY.rst index f0fe05e434..d671d6bcfc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -88,6 +88,8 @@ - Two new custom password validators added. One ensures passwords have a minimum number of uppercase letters and the other ensures passwords have a minimum amount of numbers. +- Add support to the mailing profiles for specifying a from + address. Closes GitLab issue #522. 3.1.9 (2018-11-01) ================== diff --git a/docs/releases/3.2.rst b/docs/releases/3.2.rst index adb4525c27..13e2cc00b9 100644 --- a/docs/releases/3.2.rst +++ b/docs/releases/3.2.rst @@ -77,6 +77,8 @@ Backward incompatible changes Bugs fixed or issues closed --------------------------- +* :gitlab-issue:`522` Office 365 SMTP * :gitlab-issue:`539` Setting for default email sender is missing + .. _PyPI: https://pypi.python.org/pypi/mayan-edms/ diff --git a/mayan/apps/mailer/classes.py b/mayan/apps/mailer/classes.py index 4c5d7de4cf..dbfe07da99 100644 --- a/mayan/apps/mailer/classes.py +++ b/mayan/apps/mailer/classes.py @@ -48,6 +48,11 @@ class MailerBackendBase(object): class_path = '' # Dot path to the actual class that will handle the mail fields = {} + @classmethod + def get_class_fields(cls): + backend_field_list = getattr(cls, 'fields', {}).keys() + return getattr(cls, 'class_fields', backend_field_list) + class MailerBackend(six.with_metaclass(MailerBackendMetaclass, MailerBackendBase)): @classmethod diff --git a/mayan/apps/mailer/mailers.py b/mayan/apps/mailer/mailers.py index 5e958e1094..9e4660f2ba 100644 --- a/mayan/apps/mailer/mailers.py +++ b/mayan/apps/mailer/mailers.py @@ -4,13 +4,31 @@ from django.utils.translation import ugettext_lazy as _ from .classes import MailerBackend -__all__ = ('DjangoSMTP', 'DjangoFileBased') +__all__ = ('DjangoFileBased', 'DjangoSMTP') class DjangoSMTP(MailerBackend): + """ + Backend that wraps Django's SMTP backend + """ + class_fields = ( + 'host', 'port', 'use_tls', 'use_ssl', 'user', 'password' + ) class_path = 'django.core.mail.backends.smtp.EmailBackend' + field_order = ( + 'host', 'port', 'use_tls', 'use_ssl', 'user', 'password', 'from' + ) fields = { - 'host': { + 'from': { + 'label': _('From'), + 'class': 'django.forms.CharField', 'default': '', + 'help_text': _( + 'The sender\'s address. Some system will refuse to send ' + 'messages if this value is not set.' + ), 'kwargs': { + 'max_length': 48 + }, 'required': False + }, 'host': { 'label': _('Host'), 'class': 'django.forms.CharField', 'default': 'localhost', 'help_text': _('The host to use for sending email.'), @@ -64,7 +82,7 @@ class DjangoSMTP(MailerBackend): }, 'required': False }, } - field_order = ('host', 'port', 'use_tls', 'use_ssl', 'user', 'password') + label = _('Django SMTP backend') widgets = { 'password': { 'class': 'django.forms.widgets.PasswordInput', @@ -73,17 +91,32 @@ class DjangoSMTP(MailerBackend): } } } - label = _('Django SMTP backend') class DjangoFileBased(MailerBackend): + """ + Mailing backend that wraps Django's file based email backend + """ + class_fields = ('file_path',) class_path = 'django.core.mail.backends.filebased.EmailBackend' + field_order = ( + 'file_path', 'from' + ) fields = { 'file_path': { 'label': _('File path'), 'class': 'django.forms.CharField', 'kwargs': { 'max_length': 48 } - }, + }, 'from': { + 'label': _('From'), + 'class': 'django.forms.CharField', 'default': '', + 'help_text': _( + 'The sender\'s address. Some system will refuse to send ' + 'messages if this value is not set.' + ), 'kwargs': { + 'max_length': 48 + }, 'required': False + } } label = _('Django file based backend') diff --git a/mayan/apps/mailer/models.py b/mayan/apps/mailer/models.py index 605415436e..064b1e15b7 100644 --- a/mayan/apps/mailer/models.py +++ b/mayan/apps/mailer/models.py @@ -70,21 +70,49 @@ class UserMailer(models.Model): return self.label def backend_label(self): + """ + Return the label that the backend itself provides. The backend is + loaded but not initialized. As such the label returned is a class + property. + """ return self.get_backend().label def dumps(self, data): + """ + Serialize the backend configuration data. + """ self.backend_data = json.dumps(data) self.save() + def get_class_data(self): + """ + Return the actual mailing class initialization data + """ + backend = self.get_backend() + return { + key: value for key, value in self.loads().items() if key in backend.get_class_fields() + } + def get_backend(self): + """ + Retrieves the backend by importing the module and the class + """ return import_string(self.backend_path) def get_connection(self): + """ + Establishes a reusable connection to the server by loading the + backend, initializing it, and the using the backend instance to get + a connection. + """ return mail.get_connection( - backend=self.get_backend().class_path, **self.loads() + backend=self.get_backend().class_path, **self.get_class_data() ) def loads(self): + """ + Deserialize the stored backend data. + """ return json.loads(self.backend_data) def natural_key(self): @@ -105,10 +133,12 @@ class UserMailer(models.Model): filename, content, and mimetype. """ recipient_list = split_recipient_list(recipients=[to]) + backend_data = self.loads() with self.get_connection() as connection: email_message = mail.EmailMultiAlternatives( - body=strip_tags(body), connection=connection, subject=subject, + body=strip_tags(body), connection=connection, + from_email=backend_data.get('from'), subject=subject, to=recipient_list, ) @@ -151,13 +181,15 @@ class UserMailer(models.Model): with document.open() as file_object: attachments.append( { - 'filename': document.label, 'content': file_object.read(), + 'content': file_object.read(), + 'filename': document.label, 'mimetype': document.file_mimetype } ) return self.send( - subject=subject_text, body=body_html_content, to=to, attachments=attachments + attachments=attachments, body=body_html_content, + subject=subject_text, to=to, ) def test(self, to): diff --git a/mayan/apps/mailer/tests/literals.py b/mayan/apps/mailer/tests/literals.py index b61fc6103a..ba3f97cd14 100644 --- a/mayan/apps/mailer/tests/literals.py +++ b/mayan/apps/mailer/tests/literals.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals TEST_BODY_HTML = 'test body' TEST_EMAIL_ADDRESS = 'test@example.com' +TEST_EMAIL_FROM_ADDRESS = 'from.test@example.com' TEST_RECIPIENTS_MULTIPLE_COMMA = 'test@example.com,test2@example.com' TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT = [ 'test@example.com', 'test2@example.com' diff --git a/mayan/apps/mailer/tests/mixins.py b/mayan/apps/mailer/tests/mixins.py index c6cd6bc7b3..3cee7df777 100644 --- a/mayan/apps/mailer/tests/mixins.py +++ b/mayan/apps/mailer/tests/mixins.py @@ -1,8 +1,13 @@ from __future__ import unicode_literals +import json + from ..models import UserMailer -from .literals import TEST_USER_MAILER_BACKEND_PATH, TEST_USER_MAILER_LABEL +from .literals import ( + TEST_EMAIL_FROM_ADDRESS, TEST_USER_MAILER_BACKEND_PATH, + TEST_USER_MAILER_LABEL +) class MailerTestMixin(object): @@ -12,5 +17,9 @@ class MailerTestMixin(object): enabled=True, label=TEST_USER_MAILER_LABEL, backend_path=TEST_USER_MAILER_BACKEND_PATH, - backend_data='{}' + backend_data=json.dumps( + { + 'from': TEST_EMAIL_FROM_ADDRESS + } + ) ) diff --git a/mayan/apps/mailer/tests/test_models.py b/mayan/apps/mailer/tests/test_models.py index 56fa8ef9f6..ea0cf0343b 100644 --- a/mayan/apps/mailer/tests/test_models.py +++ b/mayan/apps/mailer/tests/test_models.py @@ -5,8 +5,9 @@ from django.core import mail from documents.tests.test_models import GenericDocumentTestCase from .literals import ( - TEST_BODY_HTML, TEST_EMAIL_ADDRESS, TEST_RECIPIENTS_MULTIPLE_COMMA, - TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT, TEST_RECIPIENTS_MULTIPLE_SEMICOLON, + TEST_BODY_HTML, TEST_EMAIL_ADDRESS, TEST_EMAIL_FROM_ADDRESS, + TEST_RECIPIENTS_MULTIPLE_COMMA, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT, + TEST_RECIPIENTS_MULTIPLE_SEMICOLON, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT, TEST_RECIPIENTS_MULTIPLE_MIXED, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT, ) @@ -19,6 +20,7 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): self.user_mailer.send(to=TEST_EMAIL_ADDRESS) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) def test_send_simple_with_html(self): @@ -26,6 +28,7 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): self.user_mailer.send(to=TEST_EMAIL_ADDRESS, body=TEST_BODY_HTML) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) self.assertEqual(mail.outbox[0].alternatives[0][0], TEST_BODY_HTML) @@ -36,6 +39,7 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): ) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) with self.document.open() as file_object: self.assertEqual( @@ -50,6 +54,7 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): self.user_mailer.send(to=TEST_RECIPIENTS_MULTIPLE_COMMA) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT ) @@ -59,6 +64,7 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): self.user_mailer.send(to=TEST_RECIPIENTS_MULTIPLE_SEMICOLON) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT ) @@ -68,6 +74,7 @@ class ModelTestCase(MailerTestMixin, GenericDocumentTestCase): self.user_mailer.send(to=TEST_RECIPIENTS_MULTIPLE_MIXED) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT ) diff --git a/mayan/apps/mailer/tests/test_views.py b/mayan/apps/mailer/tests/test_views.py index 9bd4dd8ce8..f24bdc454c 100644 --- a/mayan/apps/mailer/tests/test_views.py +++ b/mayan/apps/mailer/tests/test_views.py @@ -12,10 +12,10 @@ from ..permissions import ( ) from .literals import ( - TEST_EMAIL_ADDRESS, TEST_USER_MAILER_BACKEND_PATH, TEST_USER_MAILER_LABEL, - TEST_RECIPIENTS_MULTIPLE_COMMA, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT, - TEST_RECIPIENTS_MULTIPLE_MIXED, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT, - TEST_RECIPIENTS_MULTIPLE_SEMICOLON, + TEST_EMAIL_ADDRESS, TEST_EMAIL_FROM_ADDRESS, TEST_USER_MAILER_BACKEND_PATH, + TEST_USER_MAILER_LABEL, TEST_RECIPIENTS_MULTIPLE_COMMA, + TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT, TEST_RECIPIENTS_MULTIPLE_MIXED, + TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT, TEST_RECIPIENTS_MULTIPLE_SEMICOLON, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT ) from .mailers import TestBackend @@ -90,6 +90,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_link_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) def test_mail_document_view_no_permissions(self): @@ -111,6 +112,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) def test_user_mailer_create_view_no_permissions(self): @@ -206,6 +208,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS]) def test_send_multiple_recipients_comma(self): @@ -220,6 +223,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): response = self._request_user_mailer_test() self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT ) @@ -236,6 +240,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): response = self._request_user_mailer_test() self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT ) @@ -252,6 +257,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): response = self._request_user_mailer_test() self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT ) @@ -267,6 +273,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_link_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT ) @@ -282,6 +289,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_link_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT ) @@ -297,6 +305,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_link_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT ) @@ -312,6 +321,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_COMMA_RESULT ) @@ -327,6 +337,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_MIXED_RESULT ) @@ -342,6 +353,7 @@ class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase): self._request_document_send() self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, TEST_EMAIL_FROM_ADDRESS) self.assertEqual( mail.outbox[0].to, TEST_RECIPIENTS_MULTIPLE_SEMICOLON_RESULT )