diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0b0e0ae..b05d908 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,6 @@ name: Run tests on: - push - - pull_request jobs: build: @@ -10,7 +9,7 @@ jobs: strategy: matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 diff --git a/README.rst b/README.rst index d5122aa..bb0cc4e 100644 --- a/README.rst +++ b/README.rst @@ -360,6 +360,24 @@ Methods: a celery's task for example or to handle errors. +Settings +============= + +You can configure Django-Templated-Email by setting the following settings + +.. code-block:: python + + TEMPLATED_EMAIL_FROM_EMAIL = None # String containing the email to send the email from - fallback to DEFAULT_FROM_EMAIL + TEMPLATED_EMAIL_BACKEND = TemplateBackend # The backend class that will send the email, as a string like 'foo.bar.TemplateBackend' or the class reference itself + TEMPLATED_EMAIL_TEMPLATE_DIR = 'templated_email/' # The directory containing the templates, use '' if using the top level + TEMPLATED_EMAIL_FILE_EXTENSION = 'email' # The file extension of the template files + TEMPLATED_EMAIL_AUTO_PLAIN = True # Set to false to disable the behavior of calculating the plain part from the html part of the email when `html2text ` is installed + TEMPLATED_EMAIL_PLAIN_FUNCTION = None # Specify a custom function that converts from HTML to the plain part + + # Specific for anymail integration: + TEMPLATED_EMAIL_EMAIL_MESSAGE_CLASS = 'django.core.mail.EmailMessage' # Replaces django.core.mail.EmailMessage + TEMPLATED_EMAIL_EMAIL_MULTIALTERNATIVES_CLASS = 'django.core.mail.EmailMultiAlternatives' # Replaces django.core.mail.EmailMultiAlternatives + Future Plans ============= diff --git a/tests/backends/test_vanilla_django_backend.py b/tests/backends/test_vanilla_django_backend.py index db4bc7e..bc4e711 100644 --- a/tests/backends/test_vanilla_django_backend.py +++ b/tests/backends/test_vanilla_django_backend.py @@ -5,12 +5,19 @@ from django.test import TestCase, override_settings from django.core.mail import EmailMessage, EmailMultiAlternatives -from django.core.files.storage import get_storage_class +try: + from django.core.files.storage import get_storage_class +except ImportError: + from django.core.files.storage import storages + + def get_storage_class(s): + storages[s] + from django.template import TemplateDoesNotExist from django.core import mail import pytest -from mock import patch, Mock +from unittest.mock import patch, Mock from anymail.message import AnymailMessage from templated_email.backends.vanilla_django import TemplateBackend, EmailRenderException @@ -69,17 +76,11 @@ class TemplateBackendTestCase(MockedNetworkTestCaseMixin, template_backend_klass = TemplateBackend def setUp(self): - self.storage_mock = Mock(wraps=get_storage_class())() - self.storage_mock.save = Mock() self.backend = self.template_backend_klass() self.context = {'username': 'vintasoftware', 'joindate': date(2016, 8, 22), 'full_name': 'Foo Bar'} - def patch_storage(self): - return patch('django.core.files.storage.default_storage._wrapped', - self.storage_mock) - def test_inexistent_base_email(self): try: self.backend._render_email('inexistent_base.email', {}) @@ -168,22 +169,23 @@ def test_get_email_message_with_create_link(self, mocked): uuid=uuid) self.assertEqual(saved_email.content, HTML_RESULT) + @patch('django.core.files.storage.FileSystemStorage.save') + @patch('django.core.files.storage.FileSystemStorage.url') @patch.object( template_backend_klass, '_render_email', return_value={'html': HTML_RESULT, 'plain': PLAIN_RESULT, 'subject': SUBJECT_RESULT} ) - def test_get_email_message_with_inline_image(self, mocked): - self.storage_mock.save = Mock(return_value='saved_url') - with self.patch_storage(): - self.backend.get_email_message( - 'foo.email', {'an_image': InlineImage('file.png', b'foo', - subtype='png')}, - from_email='from@example.com', cc=['cc@example.com'], - bcc=['bcc@example.com'], to=['to@example.com'], - create_link=True) - second_call_context = mocked.call_args_list[1][0][1] - self.assertEqual(second_call_context['an_image'], '/media/saved_url') + def test_get_email_message_with_inline_image(self, mock_render_email, mock_url, mock_save): + mock_url.return_value = 'media/saved_url' + self.backend.get_email_message( + 'foo.email', {'an_image': InlineImage('file.png', b'foo', + subtype='png')}, + from_email='from@example.com', cc=['cc@example.com'], + bcc=['bcc@example.com'], to=['to@example.com'], + create_link=True) + second_call_context = mock_render_email.call_args_list[1][0][1] + self.assertEqual(second_call_context['an_image'], 'media/saved_url') @override_settings(TEMPLATED_EMAIL_EMAIL_MESSAGE_CLASS='anymail.message.AnymailMessage') @patch.object( @@ -496,30 +498,32 @@ def test_removal_of_legacy(self): except TemplateDoesNotExist as e: self.assertEqual(e.args[0], 'templated_email/legacy.email') - def test_host_inline_image_if_not_exist(self): + @patch('django.core.files.storage.FileSystemStorage.url') + @patch('django.core.files.storage.FileSystemStorage.save') + def test_host_inline_image_if_not_exist(self, mock_save, mock_url): + mock_url.return_value = 'media/saved_url' inline_image = InlineImage('foo.jpg', b'bar') - self.storage_mock.save = Mock(return_value='saved_url') - with self.patch_storage(): - filename = self.backend.host_inline_image(inline_image) - self.assertEqual(filename, '/media/saved_url') - self.storage_mock.save.assert_called_once() - name, content = self.storage_mock.save.call_args[0] + filename = self.backend.host_inline_image(inline_image) + self.assertEqual(filename, 'media/saved_url') + mock_save.assert_called_once() + name, content = mock_save.call_args[0] self.assertEqual( name, 'templated_email/37b51d194a7513e45b56f6524f2d51f2foo.jpg') self.assertTrue(isinstance(content, BytesIO)) - def test_host_inline_image_if_exist(self): + @patch('django.core.files.storage.FileSystemStorage.exists') + @patch('django.core.files.storage.FileSystemStorage.save') + def test_host_inline_image_if_exist(self, mock_save, mock_exists): inline_image = InlineImage('foo.jpg', b'bar') - self.storage_mock.exists = Mock(return_value=True) + mock_exists.return_value = True - with self.patch_storage(): - filename = self.backend.host_inline_image(inline_image) + filename = self.backend.host_inline_image(inline_image) self.assertEqual( filename, '/media/templated_email/37b51d194a7513e45b56f6524f2d51f2foo.jpg') - self.storage_mock.save.assert_not_called() - self.storage_mock.exists.assert_called_once_with( + mock_save.assert_not_called() + mock_exists.assert_called_once_with( 'templated_email/37b51d194a7513e45b56f6524f2d51f2foo.jpg') diff --git a/tests/generic_views/test_views.py b/tests/generic_views/test_views.py index 168b8c9..ffb6150 100644 --- a/tests/generic_views/test_views.py +++ b/tests/generic_views/test_views.py @@ -2,7 +2,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core import mail -import mock +from unittest.mock import patch from templated_email.generic_views import TemplatedEmailFormViewMixin from tests.generic_views.views import AuthorCreateView @@ -44,10 +44,10 @@ def test_templated_email_get_recipients(self): self.mixin_object.templated_email_get_recipients, form=None) - @mock.patch.object(TemplatedEmailFormViewMixin, + @patch.object(TemplatedEmailFormViewMixin, 'templated_email_get_template_names', return_value=['template']) - @mock.patch.object(TemplatedEmailFormViewMixin, + @patch.object(TemplatedEmailFormViewMixin, 'templated_email_get_recipients', return_value=['foo@example.com']) def test_templated_email_get_send_email_kwargs_valid( @@ -65,10 +65,10 @@ class FakeForm(object): self.assertEqual(kwargs['recipient_list'], ['foo@example.com']) self.assertEqual(kwargs['context'], {'form_data': 'foo'}) - @mock.patch.object(TemplatedEmailFormViewMixin, + @patch.object(TemplatedEmailFormViewMixin, 'templated_email_get_template_names', return_value=['template']) - @mock.patch.object(TemplatedEmailFormViewMixin, + @patch.object(TemplatedEmailFormViewMixin, 'templated_email_get_recipients', return_value=['foo@example.com']) def test_templated_email_get_send_email_kwargs_not_valid( diff --git a/tests/test_get_templated_mail.py b/tests/test_get_templated_mail.py index 0a2f1a7..96ac3d7 100644 --- a/tests/test_get_templated_mail.py +++ b/tests/test_get_templated_mail.py @@ -1,6 +1,6 @@ from django.test import TestCase -from mock import patch +from unittest.mock import patch from templated_email import get_templated_mail diff --git a/tests/test_send_templated_mail.py b/tests/test_send_templated_mail.py index 0c77b7e..c37ae3f 100644 --- a/tests/test_send_templated_mail.py +++ b/tests/test_send_templated_mail.py @@ -1,6 +1,6 @@ from django.test import TestCase -from mock import patch, Mock +from unittest.mock import patch, Mock from templated_email import send_templated_mail diff --git a/tests/test_utils.py b/tests/test_utils.py index 10d80ae..e74175d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,4 @@ -import mock +from unittest.mock import patch, Mock from django.test import TestCase @@ -23,11 +23,11 @@ def test_generate_cid(self): str(self.inline_image) self.assertIsNotNone(self.inline_image._content_id) - @mock.patch('templated_email.utils.make_msgid', return_value='foo') + @patch('templated_email.utils.make_msgid', return_value='foo') def test_str(self, mocked): self.assertEqual(str(self.inline_image), 'cid:foo') - @mock.patch('templated_email.utils.make_msgid', return_value='foo') + @patch('templated_email.utils.make_msgid', return_value='foo') def test_should_cache_cid(self, mocked): str(self.inline_image) str(self.inline_image) @@ -45,7 +45,7 @@ def test_changing_content_should_generate_new_cid(self): self.assertNotEqual(cid2, cid3) def test_attach_to_message(self): - message = mock.Mock() + message = Mock() self.inline_image.attach_to_message(message) mimeimage = message.attach.call_args[0][0] self.assertEqual(mimeimage.get('Content-ID'), diff --git a/tests/utils.py b/tests/utils.py index 75d7693..0f742fd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,4 @@ -import mock +import unittest.mock class MockedNetworkTestCaseMixin(object): @@ -6,7 +6,7 @@ class MockedNetworkTestCaseMixin(object): # See: https://code.djangoproject.com/ticket/24380 @classmethod def setUpClass(cls): - cls.getfqdn_patcher = mock.patch( + cls.getfqdn_patcher = unittest.mock.patch( 'django.core.mail.utils.socket.getfqdn', return_value='vinta.local') cls.getfqdn_patcher.start() diff --git a/tox-requirements.txt b/tox-requirements.txt index 871945f..5f3db7b 100644 --- a/tox-requirements.txt +++ b/tox-requirements.txt @@ -2,7 +2,6 @@ django-anymail django-pytest django-render-block html2text -mock pytest pytest-django pytest-pythonpath diff --git a/tox.ini b/tox.ini index 6f86fae..6487626 100644 --- a/tox.ini +++ b/tox.ini @@ -1,30 +1,30 @@ [tox] -envlist = - py36-{flake8}, - {py36,py37,py38,py39}-django32, - {py38,py39,py310,py311}-django{40,41,main}, +envlist = {linux}-py{38,39,310,311}-django{32,40,41,42} [gh-actions] python = - 3.6: py36 - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 3.11: py311 + +[gh-actions:env] +OS = + ubuntu-latest: linux +DJANGO = + 3.2: django32 + 4.0: django40 + 4.1: django41 + 4.2: django42 + [testenv] commands= py.test --cov=templated_email tests/ deps= -rtox-requirements.txt pytest-cov - django32: Django==3.2 - django40: Django>=4.0,<4.1 - django41: Django>=4.1,<4.2 - djangomain: https://github.com/django/django/archive/main.tar.gz - -[testenv:py36-flake8] -commands = flake8 templated_email tests --ignore=E501 -deps = - flake8 + django32: django~=3.2 + django40: django~=4.0 + django41: django~=4.1 + django42: django~=4.2