Mailing: Add support for a from field
Add support to the mailing profiles for specifying a "from" address. Closes GitLab issue #522. This commit adds a new backend class property "class_fields" which differs from the normal "fields" property. The "class_fields" property specifies which of the backend fields will be used to initialize a backend's driver class. This is to avoid passing fields that the driver doesn't expect and getting an error. When sending emails, the "send" method will attempt to get a "from" key from the backend data and use that when sending emails. If no "from" key is found a None is passes. Django's behavior in this situation dictates that the "from" value will then be taken from the DEFAULT_FROM_EMAIL setting. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -64,6 +64,9 @@
|
||||
* Use FilteredSelectionForm for IndexTemplateFilteredForm.
|
||||
* Use FilteredSelectionForm for DocumentVersionSignatureCreateForm.
|
||||
* Improve document signatures tests.
|
||||
* Add docstrings to most models.
|
||||
* Add support to the mailing profiles for specifying a from
|
||||
address. Closes GitLab issue #522.
|
||||
|
||||
3.1.11 (2019-04-XX)
|
||||
===================
|
||||
|
||||
@@ -96,6 +96,9 @@ Other changes
|
||||
* Use FilteredSelectionForm for IndexTemplateFilteredForm.
|
||||
* Use FilteredSelectionForm for DocumentVersionSignatureCreateForm.
|
||||
* Improve document signatures tests.
|
||||
* Add docstrings to most models.
|
||||
* Add support to the mailing profiles for specifying a from
|
||||
address. Closes GitLab issue #522.
|
||||
|
||||
Removals
|
||||
--------
|
||||
@@ -166,6 +169,7 @@ Bugs fixed or issues closed
|
||||
---------------------------
|
||||
|
||||
* :gitlab-issue:`498` Can't scan subdirectories
|
||||
* :gitlab-issue:`522` Office 365 SMTP
|
||||
* :gitlab-issue:`563` Recursive Watch Folder
|
||||
|
||||
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -69,21 +69,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):
|
||||
@@ -104,10 +132,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,
|
||||
)
|
||||
|
||||
@@ -152,13 +182,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):
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
TEST_BODY_HTML = '<strong>test body</strong>'
|
||||
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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,8 +5,9 @@ from django.core import mail
|
||||
from mayan.apps.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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -92,6 +92,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):
|
||||
@@ -198,6 +200,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):
|
||||
@@ -211,6 +214,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
|
||||
)
|
||||
@@ -226,6 +230,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
|
||||
)
|
||||
@@ -241,6 +246,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
|
||||
)
|
||||
@@ -255,6 +261,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
|
||||
)
|
||||
@@ -269,6 +276,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
|
||||
)
|
||||
@@ -283,6 +291,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
|
||||
)
|
||||
@@ -297,6 +306,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
|
||||
)
|
||||
@@ -311,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_MIXED_RESULT
|
||||
)
|
||||
@@ -325,6 +336,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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user