├── .bumpversion.cfg
├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── .travis.yml
├── AUTHORS
├── CHANGELOG
├── LICENSE
├── MANIFEST.in
├── README.rst
├── pytest.ini
├── requirements.txt
├── setup.py
├── templated_email
├── __init__.py
├── backends
│ ├── __init__.py
│ └── vanilla_django.py
├── generic_views.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ └── templated_email
│ │ └── saved_email.html
├── urls.py
├── utils.py
└── views.py
├── tests
├── __init__.py
├── backends
│ ├── __init__.py
│ ├── test_vanilla_django_backend.py
│ └── utils.py
├── generic_views
│ ├── __init__.py
│ ├── models.py
│ ├── test_views.py
│ └── views.py
├── settings.py
├── template_fixtures
│ └── templated_email
│ │ ├── html_template.email
│ │ ├── inexistent_base.email
│ │ ├── inheritance_template.email
│ │ ├── inline_image.email
│ │ ├── legacy.html
│ │ ├── legacy.txt
│ │ ├── mixed_template.email
│ │ ├── multi-template.email
│ │ ├── plain_template.email
│ │ ├── plain_template_without_subject.email
│ │ └── welcome.email
├── test_get_connection.py
├── test_get_templated_mail.py
├── test_inline_image.py
├── test_send_templated_mail.py
├── test_urls.py
├── test_utils.py
├── test_views.py
└── utils.py
├── tox-requirements.txt
└── tox.ini
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 3.0.1
3 | commit = True
4 | tag = True
5 | tag_name = {new_version}
6 |
7 | [bumpversion:file:setup.py]
8 | search = VERSION = '{current_version}'
9 | replace = VERSION = '{new_version}'
10 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 |
3 | on:
4 | - push
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Set up Python ${{ matrix.python-version }}
17 | uses: actions/setup-python@v4
18 | with:
19 | python-version: ${{ matrix.python-version }}
20 | - name: Install dependencies
21 | run: |
22 | python -m pip install --upgrade pip
23 | python -m pip install tox tox-gh-actions
24 | - name: Test with tox
25 | run: tox
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | .cache
3 |
4 | # C extensions
5 | *.so
6 |
7 | # Packages
8 | *.egg
9 | *.egg-info
10 | dist
11 | build
12 | eggs
13 | parts
14 | bin
15 | var
16 | sdist
17 | develop-eggs
18 | .installed.cfg
19 | lib
20 | lib64
21 |
22 | # Installer logs
23 | pip-log.txt
24 |
25 | # Unit test / coverage reports
26 | .coverage
27 | .tox
28 | nosetests.xml
29 |
30 | #Translations
31 | *.mo
32 |
33 | #Mr Developer
34 | .mr.developer.cfg
35 |
36 | #pycharm
37 | .idea
38 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - "3.10"
5 | - "3.11"
6 | - "3.12"
7 | - "3.13"
8 |
9 | install:
10 | - pip install tox-travis
11 | - pip install -r requirements.txt
12 | - pip install coveralls
13 |
14 | script:
15 | - tox
16 |
17 | after_script:
18 | coveralls
19 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Anatoly Kudinov Hi {{full_name}}, You just signed up for my website, using:
140 |
141 |
144 |
Thanks, you rock!
147 | {% endblock %} 148 | 149 | The plain part can also be calculated from the HTML using `html2textHi Foo Bar,
You just signed up for my website, ' 36 | u'using:
Thanks, you rock!
') 39 | 40 | INHERITANCE_RESULT = (u'You just signed up for my website, ' 41 | u'using:
vintasoftware
\n join date: Aug. 22, 2016\n' 57 | u'\n Thanks, you rock!\n') 58 | 59 | 60 | ESCAPED_HTML_RESULT = (u'Hi Foo Bar,
You just signed up for my website, ' 61 | u'using:
Thanks, you rock!
') 64 | 65 | NON_ESCAPED_SUBJECT_RESULT = 'My subject forvintasoftware
' 66 | 67 | TXT_FILE = 'test' 68 | 69 | 70 | def decode_b64_msg(msg): 71 | return base64.b64decode(msg).decode("utf-8") 72 | 73 | 74 | class TemplateBackendTestCase(MockedNetworkTestCaseMixin, 75 | TempalteBackendBaseMixin, TestCase): 76 | template_backend_klass = TemplateBackend 77 | 78 | def setUp(self): 79 | self.backend = self.template_backend_klass() 80 | self.context = {'username': 'vintasoftware', 81 | 'joindate': date(2016, 8, 22), 82 | 'full_name': 'Foo Bar'} 83 | 84 | def test_inexistent_base_email(self): 85 | try: 86 | self.backend._render_email('inexistent_base.email', {}) 87 | except TemplateDoesNotExist as e: 88 | self.assertEqual(e.args[0], 'foo') 89 | 90 | def test_inexistent_template_email(self): 91 | try: 92 | self.backend._render_email('foo', {}) 93 | except TemplateDoesNotExist as e: 94 | self.assertEqual(e.args[0], 'templated_email/foo.email') 95 | 96 | def test_render_plain_email(self): 97 | response = self.backend._render_email( 98 | 'plain_template.email', self.context) 99 | self.assertEqual(len(response.keys()), 2) 100 | self.assertEqual(PLAIN_RESULT, response['plain']) 101 | self.assertEqual(SUBJECT_RESULT, response['subject']) 102 | 103 | def test_render_html_email(self): 104 | response = self.backend._render_email( 105 | 'html_template.email', self.context) 106 | self.assertEqual(len(response.keys()), 2) 107 | self.assertHTMLEqual(HTML_RESULT, response['html']) 108 | self.assertEqual(SUBJECT_RESULT, response['subject']) 109 | 110 | def test_render_mixed_email(self): 111 | response = self.backend._render_email( 112 | 'mixed_template.email', self.context) 113 | self.assertEqual(len(response.keys()), 3) 114 | self.assertHTMLEqual(HTML_RESULT, response['html']) 115 | self.assertEqual(PLAIN_RESULT, response['plain']) 116 | self.assertEqual(SUBJECT_RESULT, response['subject']) 117 | 118 | def test_render_inheritance_email(self): 119 | response = self.backend._render_email( 120 | 'inheritance_template.email', self.context) 121 | self.assertEqual(len(response.keys()), 3) 122 | self.assertHTMLEqual(INHERITANCE_RESULT, response['html']) 123 | self.assertEqual(PLAIN_RESULT, response['plain']) 124 | self.assertEqual('Another subject for vintasoftware', response['subject']) 125 | 126 | def test_email_text_escaping(self): 127 | self.context['username'] = 'vintasoftware
' 128 | 129 | response = self.backend._render_email( 130 | 'mixed_template.email', self.context) 131 | self.assertHTMLEqual(ESCAPED_HTML_RESULT, response['html']) 132 | self.assertEqual(NON_ESCAPED_PLAIN_RESULT, response['plain']) 133 | self.assertEqual(NON_ESCAPED_SUBJECT_RESULT, response['subject']) 134 | 135 | @patch.object( 136 | template_backend_klass, '_render_email', 137 | return_value={'plain': PLAIN_RESULT, 'subject': SUBJECT_RESULT} 138 | ) 139 | def test_get_email_message(self, mock): 140 | message = self.backend.get_email_message( 141 | 'foo.email', {}, 142 | from_email='from@example.com', cc=['cc@example.com'], 143 | bcc=['bcc@example.com'], to=['to@example.com']) 144 | self.assertTrue(isinstance(message, EmailMessage)) 145 | self.assertEqual(message.body, PLAIN_RESULT) 146 | self.assertEqual(message.subject, SUBJECT_RESULT) 147 | self.assertEqual(message.to, ['to@example.com']) 148 | self.assertEqual(message.cc, ['cc@example.com']) 149 | self.assertEqual(message.bcc, ['bcc@example.com']) 150 | self.assertEqual(message.from_email, 'from@example.com') 151 | 152 | @patch.object( 153 | template_backend_klass, '_render_email', 154 | return_value={'html': HTML_RESULT, 'plain': PLAIN_RESULT, 155 | 'subject': SUBJECT_RESULT} 156 | ) 157 | def test_get_email_message_with_create_link(self, mocked): 158 | self.backend.get_email_message( 159 | 'foo.email', {}, 160 | from_email='from@example.com', cc=['cc@example.com'], 161 | bcc=['bcc@example.com'], to=['to@example.com'], 162 | create_link=True) 163 | first_call_context = mocked.call_args_list[0][0][1] 164 | uuid = first_call_context['email_uuid'] 165 | self.assertTrue(uuid) 166 | second_call_context = mocked.call_args_list[1][0][1] 167 | self.assertEqual(len(second_call_context), 0) 168 | saved_email = SavedEmail.objects.get( 169 | uuid=uuid) 170 | self.assertEqual(saved_email.content, HTML_RESULT) 171 | 172 | @patch('django.core.files.storage.FileSystemStorage.save') 173 | @patch('django.core.files.storage.FileSystemStorage.url') 174 | @patch.object( 175 | template_backend_klass, '_render_email', 176 | return_value={'html': HTML_RESULT, 'plain': PLAIN_RESULT, 177 | 'subject': SUBJECT_RESULT} 178 | ) 179 | def test_get_email_message_with_inline_image(self, mock_render_email, mock_url, mock_save): 180 | mock_url.return_value = 'media/saved_url' 181 | self.backend.get_email_message( 182 | 'foo.email', {'an_image': InlineImage('file.png', b'foo', 183 | subtype='png')}, 184 | from_email='from@example.com', cc=['cc@example.com'], 185 | bcc=['bcc@example.com'], to=['to@example.com'], 186 | create_link=True) 187 | second_call_context = mock_render_email.call_args_list[1][0][1] 188 | self.assertEqual(second_call_context['an_image'], 'media/saved_url') 189 | 190 | @override_settings(TEMPLATED_EMAIL_EMAIL_MESSAGE_CLASS='anymail.message.AnymailMessage') 191 | @patch.object( 192 | template_backend_klass, '_render_email', 193 | return_value={'plain': PLAIN_RESULT, 'subject': SUBJECT_RESULT} 194 | ) 195 | def test_custom_emailmessage_klass(self, mock): 196 | message = self.backend.get_email_message( 197 | 'foo.email', {}, 198 | from_email='from@example.com', cc=['cc@example.com'], 199 | bcc=['bcc@example.com'], to=['to@example.com']) 200 | self.assertTrue(isinstance(message, AnymailMessage)) 201 | 202 | @override_settings(TEMPLATED_EMAIL_DJANGO_SUBJECTS={'foo.email': 203 | 'foo\r\n'}) 204 | @patch.object( 205 | template_backend_klass, '_render_email', 206 | return_value={'plain': PLAIN_RESULT} 207 | ) 208 | def test_get_email_message_without_subject(self, mock): 209 | message = self.backend.get_email_message( 210 | 'foo.email', {}, 211 | from_email='from@example.com', cc=['cc@example.com'], 212 | bcc=['bcc@example.com'], to=['to@example.com']) 213 | self.assertTrue(isinstance(message, EmailMessage)) 214 | self.assertEqual(message.body, PLAIN_RESULT) 215 | self.assertEqual(message.subject, 'foo') 216 | self.assertEqual(message.to, ['to@example.com']) 217 | self.assertEqual(message.cc, ['cc@example.com']) 218 | self.assertEqual(message.bcc, ['bcc@example.com']) 219 | self.assertEqual(message.from_email, 'from@example.com') 220 | 221 | @override_settings(TEMPLATED_EMAIL_DJANGO_SUBJECTS={'foo.email': 222 | 'foo\r\n'}) 223 | @patch.object( 224 | template_backend_klass, '_render_email', 225 | return_value={'plain': PLAIN_RESULT} 226 | ) 227 | def test_get_email_message_without_subject_multiple_templates(self, mock): 228 | message = self.backend.get_email_message( 229 | ['woo.email', 'foo.email'], {}, 230 | from_email='from@example.com', cc=['cc@example.com'], 231 | bcc=['bcc@example.com'], to=['to@example.com']) 232 | self.assertTrue(isinstance(message, EmailMessage)) 233 | self.assertEqual(message.body, PLAIN_RESULT) 234 | self.assertEqual(message.subject, 'foo') 235 | self.assertEqual(message.to, ['to@example.com']) 236 | self.assertEqual(message.cc, ['cc@example.com']) 237 | self.assertEqual(message.bcc, ['bcc@example.com']) 238 | self.assertEqual(message.from_email, 'from@example.com') 239 | 240 | @patch.object( 241 | template_backend_klass, '_render_email', 242 | return_value={'html': HTML_RESULT, 'subject': SUBJECT_RESULT} 243 | ) 244 | def test_get_email_message_generated_plain_text(self, mock): 245 | message = self.backend.get_email_message( 246 | 'foo.email', {}, 247 | from_email='from@example.com', cc=['cc@example.com'], 248 | bcc=['bcc@example.com'], to=['to@example.com']) 249 | self.assertTrue(isinstance(message, EmailMultiAlternatives)) 250 | self.assertHTMLEqual(message.alternatives[0][0], HTML_RESULT) 251 | self.assertEqual(message.alternatives[0][1], 'text/html') 252 | self.assertEqual(message.body, GENERATED_PLAIN_RESULT) 253 | self.assertEqual(message.subject, SUBJECT_RESULT) 254 | self.assertEqual(message.to, ['to@example.com']) 255 | self.assertEqual(message.cc, ['cc@example.com']) 256 | self.assertEqual(message.bcc, ['bcc@example.com']) 257 | self.assertEqual(message.from_email, 'from@example.com') 258 | 259 | @patch.object( 260 | template_backend_klass, '_render_email', 261 | return_value={'html': HTML_RESULT, 'subject': SUBJECT_RESULT} 262 | ) 263 | @override_settings(TEMPLATED_EMAIL_PLAIN_FUNCTION=lambda x: 'hi') 264 | def test_get_email_message_custom_func_generated_plain_text(self, mock): 265 | message = self.backend.get_email_message('foo.email', {}) 266 | self.assertEqual(message.body, 'hi') 267 | 268 | def test_get_multi_match_last_email_message_generated_plain_text(self): 269 | message = self.backend.get_email_message( 270 | ['multi-template.email', 'foo.email', ], {}, 271 | from_email='from@example.com', cc=['cc@example.com'], 272 | bcc=['bcc@example.com'], to=['to@example.com']) 273 | self.assertEqual(message.body, MULTI_TEMPLATE_PLAIN_RESULT) 274 | self.assertEqual(message.subject, MULTI_TEMPLATE_SUBJECT_RESULT) 275 | self.assertEqual(message.to, ['to@example.com']) 276 | self.assertEqual(message.cc, ['cc@example.com']) 277 | self.assertEqual(message.bcc, ['bcc@example.com']) 278 | self.assertEqual(message.from_email, 'from@example.com') 279 | 280 | def test_get_multi_first_match_email_message_generated_plain_text(self): 281 | message = self.backend.get_email_message( 282 | ['foo.email', 'multi-template.email', ], {}, 283 | from_email='from@example.com', cc=['cc@example.com'], 284 | bcc=['bcc@example.com'], to=['to@example.com']) 285 | self.assertEqual(message.body, MULTI_TEMPLATE_PLAIN_RESULT) 286 | self.assertEqual(message.subject, MULTI_TEMPLATE_SUBJECT_RESULT) 287 | self.assertEqual(message.to, ['to@example.com']) 288 | self.assertEqual(message.cc, ['cc@example.com']) 289 | self.assertEqual(message.bcc, ['bcc@example.com']) 290 | self.assertEqual(message.from_email, 'from@example.com') 291 | 292 | def test_get_multi_options_select_last_plain_only(self): 293 | message = self.backend.get_email_message( 294 | ['non-existing.email', 'also-non-existing.email', 'non-existing-without-suffix', 'foo.email', 'multi-template.email', ], {}, 295 | from_email='from@example.com', cc=['cc@example.com'], 296 | bcc=['bcc@example.com'], to=['to@example.com']) 297 | self.assertEqual(message.body, MULTI_TEMPLATE_PLAIN_RESULT) 298 | self.assertEqual(message.subject, MULTI_TEMPLATE_SUBJECT_RESULT) 299 | self.assertEqual(message.to, ['to@example.com']) 300 | self.assertEqual(message.cc, ['cc@example.com']) 301 | self.assertEqual(message.bcc, ['bcc@example.com']) 302 | self.assertEqual(message.from_email, 'from@example.com') 303 | 304 | @patch.object( 305 | template_backend_klass, '_render_email', 306 | return_value={'html': HTML_RESULT, 'plain': PLAIN_RESULT, 307 | 'subject': SUBJECT_RESULT} 308 | ) 309 | def test_get_email_message_with_plain_and_html(self, mock): 310 | message = self.backend.get_email_message( 311 | 'foo.email', {}, 312 | from_email='from@example.com', cc=['cc@example.com'], 313 | bcc=['bcc@example.com'], to=['to@example.com']) 314 | self.assertTrue(isinstance(message, EmailMultiAlternatives)) 315 | self.assertHTMLEqual(message.alternatives[0][0], HTML_RESULT) 316 | self.assertEqual(message.alternatives[0][1], 'text/html') 317 | self.assertEqual(message.body, PLAIN_RESULT) 318 | self.assertEqual(message.subject, SUBJECT_RESULT) 319 | self.assertEqual(message.to, ['to@example.com']) 320 | self.assertEqual(message.cc, ['cc@example.com']) 321 | self.assertEqual(message.bcc, ['bcc@example.com']) 322 | self.assertEqual(message.from_email, 'from@example.com') 323 | 324 | @patch.object( 325 | template_backend_klass, '_render_email', 326 | return_value={'subject': SUBJECT_RESULT} 327 | ) 328 | def test_get_email_message_with_no_body_parts(self, mock): 329 | with pytest.raises(EmailRenderException): 330 | self.backend.get_email_message( 331 | 'foo.email', {}, 332 | from_email='from@example.com', cc=['cc@example.com'], 333 | bcc=['bcc@example.com'], to=['to@example.com']) 334 | 335 | @override_settings(TEMPLATED_EMAIL_EMAIL_MULTIALTERNATIVES_CLASS='anymail.message.AnymailMessage') 336 | @patch.object( 337 | template_backend_klass, '_render_email', 338 | return_value={'html': HTML_RESULT, 'plain': PLAIN_RESULT, 339 | 'subject': SUBJECT_RESULT} 340 | ) 341 | def test_custom_emailmessage_klass_multipart(self, mock): 342 | message = self.backend.get_email_message( 343 | 'foo.email', {}, 344 | from_email='from@example.com', cc=['cc@example.com'], 345 | bcc=['bcc@example.com'], to=['to@example.com']) 346 | self.assertTrue(isinstance(message, AnymailMessage)) 347 | 348 | @override_settings(TEMPLATED_EMAIL_AUTO_PLAIN=False) 349 | @patch.object( 350 | template_backend_klass, '_render_email', 351 | return_value={'html': HTML_RESULT, 352 | 'subject': SUBJECT_RESULT} 353 | ) 354 | def test_get_email_message_html_only(self, mock): 355 | message = self.backend.get_email_message( 356 | 'foo.email', {}, 357 | from_email='from@example.com', cc=['cc@example.com'], 358 | bcc=['bcc@example.com'], to=['to@example.com']) 359 | self.assertTrue(isinstance(message, EmailMessage)) 360 | self.assertHTMLEqual(message.body, HTML_RESULT) 361 | self.assertEqual(message.content_subtype, 'html') 362 | self.assertEqual(message.subject, SUBJECT_RESULT) 363 | self.assertEqual(message.to, ['to@example.com']) 364 | self.assertEqual(message.cc, ['cc@example.com']) 365 | self.assertEqual(message.bcc, ['bcc@example.com']) 366 | self.assertEqual(message.from_email, 'from@example.com') 367 | 368 | @patch.object( 369 | template_backend_klass, '_render_email', 370 | return_value={'html': HTML_RESULT, 'plain': PLAIN_RESULT, 371 | 'subject': SUBJECT_RESULT} 372 | ) 373 | def test_send(self, render_mock): 374 | ret = self.backend.send('mixed_template', 'from@example.com', 375 | ['to@example.com', 'to2@example.com'], {}, 376 | headers={'Message-Id': 'a_message_id'}) 377 | self.assertEqual(ret, 'a_message_id') 378 | self.assertEqual(len(mail.outbox), 1) 379 | message = mail.outbox[0] 380 | self.assertEqual(ret, message.extra_headers['Message-Id']) 381 | self.assertTrue(isinstance(message, EmailMultiAlternatives)) 382 | self.assertHTMLEqual(message.alternatives[0][0], HTML_RESULT) 383 | self.assertEqual(message.alternatives[0][1], 'text/html') 384 | self.assertEqual(message.body, PLAIN_RESULT) 385 | self.assertEqual(message.subject, SUBJECT_RESULT) 386 | self.assertEqual(message.to, ['to@example.com', 'to2@example.com']) 387 | self.assertEqual(message.from_email, 'from@example.com') 388 | 389 | @patch.object( 390 | template_backend_klass, 'get_email_message' 391 | ) 392 | @patch( 393 | 'templated_email.backends.vanilla_django.get_connection' 394 | ) 395 | def test_all_arguments_passed_forward_from_send( 396 | self, get_connection_mock, get_email_message_mock): 397 | kwargs = { 398 | 'template_name': 'foo', 399 | 'from_email': 'from@example.com', 400 | 'recipient_list': ['to@example.com'], 401 | 'context': {'foo': 'bar'}, 402 | 'cc': ['cc@example.com'], 403 | 'bcc': ['bcc@example.com'], 404 | 'fail_silently': True, 405 | 'headers': {'Message-Id': 'a_message_id'}, 406 | 'template_prefix': 'prefix', 407 | 'template_suffix': 'suffix', 408 | 'template_dir': 'tempdir', 409 | 'file_extension': 'ext', 410 | 'auth_user': 'vintasoftware', 411 | 'auth_password': 'password', 412 | 'create_link': False, 413 | } 414 | 415 | send_mock = get_email_message_mock.return_value.send 416 | self.backend.send(**kwargs) 417 | get_connection_mock.assert_called_with( 418 | username=kwargs['auth_user'], 419 | password=kwargs['auth_password'], 420 | fail_silently=kwargs['fail_silently'] 421 | ) 422 | get_email_message_mock.assert_called_with( 423 | kwargs['template_name'], 424 | kwargs['context'], 425 | from_email=kwargs['from_email'], 426 | to=kwargs['recipient_list'], 427 | cc=kwargs['cc'], 428 | bcc=kwargs['bcc'], 429 | headers=kwargs['headers'], 430 | template_prefix=kwargs['template_prefix'], 431 | template_suffix=kwargs['template_suffix'], 432 | template_dir=kwargs['template_dir'], 433 | file_extension=kwargs['file_extension'], 434 | create_link=kwargs['create_link'], 435 | attachments=None, 436 | ) 437 | send_mock.assert_called_with( 438 | kwargs['fail_silently'] 439 | ) 440 | 441 | @patch.object( 442 | template_backend_klass, '_render_email', 443 | return_value={'plain': PLAIN_RESULT, 444 | 'subject': SUBJECT_RESULT} 445 | ) 446 | def test_send_attachment_mime_base(self, render_mock): 447 | self.backend.send('plain_template', 'from@example.com', 448 | ['to@example.com', 'to2@example.com'], {}, 449 | attachments=[MIMEImage(TXT_FILE, 'text/plain')]) 450 | attachment = mail.outbox[0].attachments[0] 451 | self.assertEqual(decode_b64_msg(attachment.get_payload()), 452 | TXT_FILE) 453 | 454 | @patch.object( 455 | template_backend_klass, '_render_email', 456 | return_value={'plain': PLAIN_RESULT, 457 | 'subject': SUBJECT_RESULT} 458 | ) 459 | def test_send_attachment_tripple(self, render_mock): 460 | self.backend.send('plain_template', 'from@example.com', 461 | ['to@example.com', 'to2@example.com'], {}, 462 | attachments=[('black_pixel.png', TXT_FILE, 'text/plain')]) 463 | attachment = mail.outbox[0].attachments[0] 464 | self.assertEqual(('black_pixel.png', TXT_FILE, 'text/plain'), 465 | attachment) 466 | 467 | @patch.object( 468 | template_backend_klass, '_render_email', 469 | return_value={'plain': PLAIN_RESULT, 'subject': SUBJECT_RESULT} 470 | ) 471 | def test_get_email_message_attachment_mime_base(self, mock): 472 | message = self.backend.get_email_message( 473 | 'foo.email', {}, 474 | from_email='from@example.com', cc=['cc@example.com'], 475 | bcc=['bcc@example.com'], to=['to@example.com'], 476 | attachments=[MIMEImage(TXT_FILE, 'text/plain')]) 477 | attachment = message.attachments[0] 478 | self.assertEqual(decode_b64_msg(attachment.get_payload()), 479 | TXT_FILE) 480 | 481 | @patch.object( 482 | template_backend_klass, '_render_email', 483 | return_value={'plain': PLAIN_RESULT, 'subject': SUBJECT_RESULT} 484 | ) 485 | def test_get_email_message_attachment_tripple(self, mock): 486 | message = self.backend.get_email_message( 487 | 'foo.email', {}, 488 | from_email='from@example.com', cc=['cc@example.com'], 489 | bcc=['bcc@example.com'], to=['to@example.com'], 490 | attachments=[('black_pixel.png', TXT_FILE, 'text/plain')]) 491 | attachment = message.attachments[0] 492 | self.assertEqual(('black_pixel.png', TXT_FILE, 'text/plain'), 493 | attachment) 494 | 495 | def test_removal_of_legacy(self): 496 | try: 497 | self.backend._render_email('legacy', {}) 498 | except TemplateDoesNotExist as e: 499 | self.assertEqual(e.args[0], 'templated_email/legacy.email') 500 | 501 | @patch('django.core.files.storage.FileSystemStorage.url') 502 | @patch('django.core.files.storage.FileSystemStorage.save') 503 | def test_host_inline_image_if_not_exist(self, mock_save, mock_url): 504 | mock_url.return_value = 'media/saved_url' 505 | inline_image = InlineImage('foo.jpg', b'bar') 506 | 507 | filename = self.backend.host_inline_image(inline_image) 508 | self.assertEqual(filename, 'media/saved_url') 509 | mock_save.assert_called_once() 510 | name, content = mock_save.call_args[0] 511 | self.assertEqual( 512 | name, 513 | 'templated_email/37b51d194a7513e45b56f6524f2d51f2foo.jpg') 514 | self.assertTrue(isinstance(content, BytesIO)) 515 | 516 | @patch('django.core.files.storage.FileSystemStorage.exists') 517 | @patch('django.core.files.storage.FileSystemStorage.save') 518 | def test_host_inline_image_if_exist(self, mock_save, mock_exists): 519 | inline_image = InlineImage('foo.jpg', b'bar') 520 | mock_exists.return_value = True 521 | 522 | filename = self.backend.host_inline_image(inline_image) 523 | self.assertEqual( 524 | filename, 525 | '/media/templated_email/37b51d194a7513e45b56f6524f2d51f2foo.jpg') 526 | 527 | mock_save.assert_not_called() 528 | mock_exists.assert_called_once_with( 529 | 'templated_email/37b51d194a7513e45b56f6524f2d51f2foo.jpg') 530 | -------------------------------------------------------------------------------- /tests/backends/utils.py: -------------------------------------------------------------------------------- 1 | from django.test import override_settings 2 | 3 | 4 | class TempalteBackendBaseMixin(object): 5 | 6 | @override_settings(TEMPLATED_EMAIL_TEMPLATE_DIR='test_prefix') 7 | def test_uses_prefix_from_config(self): 8 | backend = self.template_backend_klass() 9 | self.assertEqual(backend.template_prefix, 'test_prefix') 10 | 11 | @override_settings(TEMPLATED_EMAIL_FILE_EXTENSION='test_suffix') 12 | def test_uses_suffix_from_config(self): 13 | backend = self.template_backend_klass() 14 | self.assertEqual(backend.template_suffix, 'test_suffix') 15 | 16 | def test_override_prefix_from_config(self): 17 | backend = self.template_backend_klass(template_prefix='test_prefix') 18 | self.assertEqual(backend.template_prefix, 'test_prefix') 19 | 20 | def test_override_suffix_from_config(self): 21 | backend = self.template_backend_klass(template_suffix='test_suffix') 22 | self.assertEqual(backend.template_suffix, 'test_suffix') 23 | -------------------------------------------------------------------------------- /tests/generic_views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vintasoftware/django-templated-email/f906be91cc0f719d2d7532d86c9c317181b75d62/tests/generic_views/__init__.py -------------------------------------------------------------------------------- /tests/generic_views/models.py: -------------------------------------------------------------------------------- 1 | 2 | from django.db import models 3 | 4 | 5 | class Author(models.Model): 6 | email = models.EmailField() 7 | name = models.CharField(max_length=200) 8 | -------------------------------------------------------------------------------- /tests/generic_views/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase, RequestFactory, override_settings 2 | from django.core.exceptions import ImproperlyConfigured 3 | from django.core import mail 4 | 5 | from unittest.mock import patch 6 | 7 | from templated_email.generic_views import TemplatedEmailFormViewMixin 8 | from tests.generic_views.views import AuthorCreateView 9 | from tests.generic_views.models import Author 10 | from tests.utils import MockedNetworkTestCaseMixin 11 | 12 | 13 | class TemplatedEmailFormViewMixinUnitTestCase(TestCase): 14 | def setUp(self): 15 | self.mixin_object = TemplatedEmailFormViewMixin() 16 | 17 | def test_templated_email_get_template_names_raises_exception(self): 18 | self.assertRaises(ImproperlyConfigured, 19 | self.mixin_object.templated_email_get_template_names, 20 | valid=True) 21 | self.assertRaises(ImproperlyConfigured, 22 | self.mixin_object.templated_email_get_template_names, 23 | valid=False) 24 | 25 | def test_templated_email_get_template_names_with_template_name(self): 26 | self.mixin_object.templated_email_template_name = 'template_name' 27 | self.assertEqual( 28 | self.mixin_object.templated_email_get_template_names(valid=True), 29 | ['template_name'] 30 | ) 31 | self.assertEqual( 32 | self.mixin_object.templated_email_get_template_names(valid=False), 33 | ['template_name'] 34 | ) 35 | 36 | def test_templated_email_get_context_data(self): 37 | context = self.mixin_object.templated_email_get_context_data() 38 | self.assertEqual(context, {}) 39 | context = self.mixin_object.templated_email_get_context_data(foo='bar') 40 | self.assertEqual(context, {'foo': 'bar'}) 41 | 42 | def test_templated_email_get_recipients(self): 43 | self.assertRaises(NotImplementedError, 44 | self.mixin_object.templated_email_get_recipients, 45 | form=None) 46 | 47 | @patch.object(TemplatedEmailFormViewMixin, 48 | 'templated_email_get_template_names', 49 | return_value=['template']) 50 | @patch.object(TemplatedEmailFormViewMixin, 51 | 'templated_email_get_recipients', 52 | return_value=['foo@example.com']) 53 | def test_templated_email_get_send_email_kwargs_valid( 54 | self, 55 | mocked_get_templated_email_recipients, 56 | mocked_get_templated_email_template_names): 57 | class FakeForm(object): 58 | data = 'foo' 59 | form = FakeForm() 60 | kwargs = self.mixin_object.templated_email_get_send_email_kwargs( 61 | valid=True, form=form) 62 | self.assertEqual(len(kwargs), 4) 63 | self.assertEqual(kwargs['template_name'], ['template']) 64 | self.assertEqual(kwargs['from_email'], None) 65 | self.assertEqual(kwargs['recipient_list'], ['foo@example.com']) 66 | self.assertEqual(kwargs['context'], {'form_data': 'foo'}) 67 | 68 | @patch.object(TemplatedEmailFormViewMixin, 69 | 'templated_email_get_template_names', 70 | return_value=['template']) 71 | @patch.object(TemplatedEmailFormViewMixin, 72 | 'templated_email_get_recipients', 73 | return_value=['foo@example.com']) 74 | def test_templated_email_get_send_email_kwargs_not_valid( 75 | self, 76 | mocked_get_templated_email_recipients, 77 | mocked_get_templated_email_template_names): 78 | class FakeForm(object): 79 | errors = 'errors foo' 80 | form = FakeForm() 81 | kwargs = self.mixin_object.templated_email_get_send_email_kwargs( 82 | valid=False, form=form) 83 | self.assertEqual(len(kwargs), 4) 84 | self.assertEqual(kwargs['template_name'], ['template']) 85 | self.assertEqual(kwargs['from_email'], None) 86 | self.assertEqual(kwargs['recipient_list'], ['foo@example.com']) 87 | self.assertEqual(kwargs['context'], {'form_errors': 'errors foo'}) 88 | 89 | 90 | class TemplatedEmailFormViewMixinTestCase(MockedNetworkTestCaseMixin, TestCase): 91 | def setUp(self): 92 | self.factory = RequestFactory() 93 | self.good_request = self.factory.post( 94 | '/doesnt-matter/', 95 | data={'email': 'author@vinta.com.br', 'name': 'Andre'} 96 | ) 97 | self.bad_request = self.factory.post( 98 | '/doesnt-matter/', 99 | data={'email': 'this_is_not_an_email', 'name': 'Andre'} 100 | ) 101 | 102 | def test_form_valid_with_send_on_success(self): 103 | response = AuthorCreateView.as_view()(self.good_request) 104 | self.assertEqual(response.status_code, 302) 105 | self.assertEqual(Author.objects.count(), 1) 106 | self.assertEqual(len(mail.outbox), 1) 107 | self.assertEqual(mail.outbox[0].alternatives[0][0].strip(), 108 | 'Andre - author@vinta.com.br') 109 | 110 | def test_form_valid_with_send_on_success_false(self): 111 | default_value = AuthorCreateView.templated_email_send_on_success 112 | AuthorCreateView.templated_email_send_on_success = False 113 | response = AuthorCreateView.as_view()(self.good_request) 114 | self.assertEqual(response.status_code, 302) 115 | self.assertEqual(Author.objects.count(), 1) 116 | self.assertEqual(len(mail.outbox), 0) 117 | AuthorCreateView.templated_email_send_on_success = default_value 118 | 119 | def test_form_invalid_with_not_send_on_failure(self): 120 | response = AuthorCreateView.as_view()(self.bad_request) 121 | self.assertEqual(response.status_code, 200) 122 | self.assertEqual(Author.objects.count(), 0) 123 | self.assertEqual(len(mail.outbox), 0) 124 | 125 | def test_form_invalid_with_send_on_failure(self): 126 | default_value = AuthorCreateView.templated_email_send_on_failure 127 | AuthorCreateView.templated_email_send_on_failure = True 128 | response = AuthorCreateView.as_view()(self.bad_request) 129 | self.assertEqual(response.status_code, 200) 130 | self.assertEqual(Author.objects.count(), 0) 131 | self.assertEqual(len(mail.outbox), 1) 132 | self.assertEqual(mail.outbox[0].alternatives[0][0].strip(), 133 | '* Enter a valid email address.') 134 | AuthorCreateView.templated_email_send_on_failure = default_value 135 | 136 | @override_settings(TEMPLATED_EMAIL_FROM_EMAIL='from@vinta.com.br') 137 | def test_from_email(self): 138 | AuthorCreateView.as_view()(self.good_request) 139 | self.assertEqual(len(mail.outbox), 1) 140 | self.assertEqual(mail.outbox[0].from_email, 'from@vinta.com.br') 141 | 142 | def test_from_email_with_templated_email_from_email(self): 143 | default_value = AuthorCreateView.templated_email_from_email 144 | AuthorCreateView.templated_email_from_email = 'from2@vinta.com.br' 145 | AuthorCreateView.as_view()(self.good_request) 146 | self.assertEqual(len(mail.outbox), 1) 147 | self.assertEqual(mail.outbox[0].from_email, 'from2@vinta.com.br') 148 | AuthorCreateView.templated_email_from_email = default_value 149 | -------------------------------------------------------------------------------- /tests/generic_views/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic.edit import CreateView 2 | 3 | from templated_email.generic_views import TemplatedEmailFormViewMixin 4 | from tests.generic_views.models import Author 5 | 6 | 7 | # This view send a welcome email to the author 8 | class AuthorCreateView(TemplatedEmailFormViewMixin, CreateView): 9 | model = Author 10 | fields = ['name', 'email'] 11 | templated_email_template_name = 'welcome' 12 | template_name = 'authors/create_author.html' 13 | success_url = '/create_author/' 14 | 15 | def templated_email_get_recipients(self, form): 16 | return [form.data['email']] 17 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | DATABASES = { 4 | 'default': { 5 | 'ENGINE': 'django.db.backends.sqlite3', 6 | 'NAME': ':memory:', 7 | } 8 | } 9 | 10 | INSTALLED_APPS = ( 11 | 'django.contrib.auth', 12 | 'django.contrib.contenttypes', 13 | 'templated_email', 14 | 'tests.generic_views', 15 | ) 16 | 17 | SECRET_KEY = "notimportant" 18 | 19 | TEMPLATES = [ 20 | { 21 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 22 | 'DIRS': [ 23 | 'tests/template_fixtures', 24 | ], 25 | 'APP_DIRS': True, 26 | 'OPTIONS': { 27 | 'context_processors': [ 28 | 'django.contrib.auth.context_processors.auth', 29 | 'django.template.context_processors.debug', 30 | 'django.template.context_processors.i18n', 31 | 'django.template.context_processors.media', 32 | 'django.template.context_processors.static', 33 | 'django.template.context_processors.tz', 34 | 'django.contrib.messages.context_processors.messages', 35 | ], 36 | }, 37 | }, 38 | ] 39 | 40 | ROOT_URLCONF = 'tests.test_urls' 41 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 42 | MEDIA_ROOT = os.path.join(BASE_DIR, "tmp") 43 | MEDIA_URL = '/media/' 44 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/html_template.email: -------------------------------------------------------------------------------- 1 | {% block subject %}My subject for {{ username }}{% endblock %} 2 | {% block html %} 3 |Hi {{full_name}},
4 | 5 |You just signed up for my website, using: 6 |
Thanks, you rock!
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/inexistent_base.email: -------------------------------------------------------------------------------- 1 | {% extends 'foo' %} 2 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/inheritance_template.email: -------------------------------------------------------------------------------- 1 | {% extends 'templated_email/mixed_template.email' %} 2 | {% block subject %}Another subject for {{ username }}{% endblock %} 3 | 4 | {% block hello %} 5 |test
3 | {% endblock %} 4 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/legacy.txt: -------------------------------------------------------------------------------- 1 | {% block test %} 2 | test 3 | {% endblock %} 4 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/mixed_template.email: -------------------------------------------------------------------------------- 1 | {% block subject %}My subject for {{ username }}{% endblock %} 2 | {% block html %} 3 | {% block hello %} 4 |Hi {{full_name}},
5 | {% endblock %} 6 | 7 |You just signed up for my website, using: 8 |
Thanks, you rock!
18 | {% endblock %} 19 | {% endblock %} 20 | {% block plain %} 21 | Hi, 22 | 23 | You just signed up for my website, using: 24 | username: {{ username }} 25 | join date: {{ joindate }} 26 | 27 | Thanks, you rock! 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/multi-template.email: -------------------------------------------------------------------------------- 1 | {% block subject %}A subject{% endblock %} 2 | {% block plain %} 3 | Just to make sure the content is read 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/plain_template.email: -------------------------------------------------------------------------------- 1 | {% block subject %}My subject for {{ username }}{% endblock %} 2 | {% block plain %} 3 | Hi, 4 | 5 | You just signed up for my website, using: 6 | username: {{ username }} 7 | join date: {{ joindate }} 8 | 9 | Thanks, you rock! 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/plain_template_without_subject.email: -------------------------------------------------------------------------------- 1 | {% block plain %} 2 | Hi, 3 | 4 | You just signed up for my website, using: 5 | username: {{ username }} 6 | join date: {{ joindate }} 7 | 8 | Thanks, you rock! 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /tests/template_fixtures/templated_email/welcome.email: -------------------------------------------------------------------------------- 1 | {% block subject %}welcome{% endblock %} 2 | {% block html %} 3 | {% if form_errors %} 4 | {{ form_errors.email.as_text }} 5 | {% else %} 6 | {{ form_data.name }} - {{ form_data.email }} 7 | {% endif %} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /tests/test_get_connection.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from templated_email import get_connection, backends 4 | 5 | 6 | class GetConnectionTestCase(TestCase): 7 | def test_default(self): 8 | connection = get_connection() 9 | 10 | self.assertIsInstance(connection, 11 | backends.vanilla_django.TemplateBackend) 12 | 13 | def test_class_name(self): 14 | klass = 'templated_email.backends.vanilla_django.TemplateBackend' 15 | 16 | connection = get_connection(klass) 17 | 18 | self.assertIsInstance(connection, 19 | backends.vanilla_django.TemplateBackend) 20 | 21 | def test_class_name_omitted(self): 22 | klass = 'templated_email.backends.vanilla_django' 23 | 24 | connection = get_connection(klass) 25 | 26 | self.assertIsInstance(connection, 27 | backends.vanilla_django.TemplateBackend) 28 | 29 | def test_class_instance(self): 30 | klass = backends.vanilla_django.TemplateBackend 31 | 32 | connection = get_connection(klass) 33 | 34 | self.assertIsInstance(connection, klass) 35 | 36 | def test_non_existing_module(self): 37 | klass = 'templated_email.backends.non_existing.NoBackend' 38 | 39 | self.assertRaises(ImportError, get_connection, klass) 40 | 41 | def test_non_existing_class(self): 42 | klass = 'templated_email.backends.vanilla_django.NoBackend' 43 | 44 | self.assertRaises(ImportError, get_connection, klass) 45 | -------------------------------------------------------------------------------- /tests/test_get_templated_mail.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from unittest.mock import patch 4 | 5 | from templated_email import get_templated_mail 6 | 7 | 8 | class GetTemplatedMailTestCase(TestCase): 9 | TEST_ARGS = ['a_template_name', {'context': 'content'}] 10 | TEST_KWARGS = { 11 | 'from_email': 'from@example.com', 12 | 'to': ['to@example.com'], 13 | 'cc': ['cc@example.com'], 14 | 'bcc': ['bcc@example.com'], 15 | 'headers': {'A_HEADER': 'foo'}, 16 | 'template_prefix': 'prefix', 17 | 'template_suffix': 'suffix', 18 | 'template_dir': 'dirp', 19 | 'file_extension': 'ext', 20 | 'create_link': False, 21 | } 22 | 23 | @patch('templated_email.TemplateBackend') 24 | def test_get_templated_mail_returns_response_of_get_email_message( 25 | self, mocked_backend): 26 | ret = get_templated_mail(*self.TEST_ARGS) 27 | self.assertTrue( 28 | ret is mocked_backend.return_value.get_email_message.return_value) 29 | 30 | @patch('templated_email.TemplateBackend') 31 | def test_called_get_email_message_from_vanilla_backend(self, mocked_backend): 32 | get_templated_mail(*self.TEST_ARGS) 33 | mocked_backend.return_value.get_email_message.assert_called_once() 34 | 35 | @patch('templated_email.TemplateBackend') 36 | def test_arguments_get_passsed_to_get_email_message(self, mocked_backend): 37 | get_templated_mail(*self.TEST_ARGS, **self.TEST_KWARGS) 38 | 39 | mocked_backend.assert_called_with(template_prefix='prefix', 40 | template_suffix='suffix') 41 | 42 | get_email_message = mocked_backend.return_value.get_email_message 43 | 44 | kwargs = dict(self.TEST_KWARGS) 45 | del kwargs['template_dir'] 46 | del kwargs['file_extension'] 47 | get_email_message.assert_called_with(*self.TEST_ARGS, **kwargs) 48 | 49 | @patch('templated_email.TemplateBackend') 50 | def test_arguments_get_email_message_fallback(self, mocked_backend): 51 | kwargs = dict(self.TEST_KWARGS) 52 | del kwargs['template_prefix'] 53 | del kwargs['template_suffix'] 54 | 55 | get_templated_mail(*self.TEST_ARGS, **kwargs) 56 | 57 | mocked_backend.assert_called_with(template_prefix=kwargs['template_dir'], 58 | template_suffix=kwargs['file_extension']) 59 | 60 | get_email_message = mocked_backend.return_value.get_email_message 61 | 62 | kwargs['template_prefix'] = kwargs.pop('template_dir') 63 | kwargs['template_suffix'] = kwargs.pop('file_extension') 64 | get_email_message.assert_called_with(*self.TEST_ARGS, **kwargs) 65 | -------------------------------------------------------------------------------- /tests/test_inline_image.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from email.utils import unquote 3 | 4 | from django.test import TestCase 5 | 6 | from templated_email.backends.vanilla_django import TemplateBackend 7 | from templated_email import InlineImage 8 | 9 | from tests.utils import MockedNetworkTestCaseMixin 10 | 11 | imageb64 = ('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1B' 12 | 'MVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvD' 13 | 'MAAAAASUVORK5CYII=') 14 | 15 | 16 | class GetMessageWithInlineMessageTestCase(MockedNetworkTestCaseMixin, TestCase): 17 | def setUp(self): 18 | self.backend = TemplateBackend() 19 | self.inline_image = InlineImage('foo.png', base64.b64decode(imageb64)) 20 | self.message = self.backend.get_email_message( 21 | 'inline_image.email', {'image_file': self.inline_image}, 22 | from_email='from@example.com', cc=['cc@example.com'], 23 | bcc=['bcc@example.com'], to=['to@example.com']) 24 | 25 | def test_cid_in_message(self): 26 | alternative_message = self.message.alternatives[0][0] 27 | self.assertIn('cid:%s' % unquote(self.inline_image._content_id), 28 | alternative_message) 29 | 30 | def test_image_in_attachments(self): 31 | mimage = self.message.attachments[0] 32 | attachment_content = base64.b64encode(mimage.get_payload(decode=True)) 33 | self.assertEqual(attachment_content.decode(), imageb64) 34 | -------------------------------------------------------------------------------- /tests/test_send_templated_mail.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from unittest.mock import patch, Mock 4 | 5 | from templated_email import send_templated_mail 6 | 7 | 8 | class SendTemplatedMailTestCase(TestCase): 9 | TEST_ARGS = ['a_template_name', 'from@example.com', ['to@example.com'], 10 | {'context': 'content'}] 11 | TEST_KWARGS = { 12 | 'cc': ['cc@example.com'], 13 | 'bcc': ['bcc@example.com'], 14 | 'fail_silently': True, 15 | 'headers': {'A_HEADER': 'foo'}, 16 | 'template_prefix': 'prefix', 17 | 'template_suffix': 'suffix', 18 | 'something': 'else', 19 | 'create_link': False, 20 | } 21 | 22 | def test_send_templated_mail_returns_send_response(self): 23 | mocked_connection = Mock() 24 | ret = send_templated_mail(*self.TEST_ARGS, connection=mocked_connection, 25 | **self.TEST_KWARGS) 26 | self.assertTrue(ret is mocked_connection.send.return_value) 27 | 28 | def test_with_connection_in_args(self): 29 | mocked_connection = Mock() 30 | send_templated_mail(*self.TEST_ARGS, connection=mocked_connection, 31 | **self.TEST_KWARGS) 32 | 33 | kwargs = dict(self.TEST_KWARGS) 34 | del kwargs['template_prefix'] 35 | del kwargs['template_suffix'] 36 | mocked_connection.send.assert_called_with(*self.TEST_ARGS, **kwargs) 37 | 38 | @patch('templated_email.get_connection') 39 | def test_without_connection_in_args(self, mocked_get_connection): 40 | send_templated_mail(*self.TEST_ARGS, **self.TEST_KWARGS) 41 | 42 | mocked_get_connection.assert_called_with(template_prefix='prefix', 43 | template_suffix='suffix') 44 | 45 | mocked_connection = mocked_get_connection.return_value 46 | kwargs = dict(self.TEST_KWARGS) 47 | del kwargs['template_prefix'] 48 | del kwargs['template_suffix'] 49 | mocked_connection.send.assert_called_with(*self.TEST_ARGS, **kwargs) 50 | -------------------------------------------------------------------------------- /tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.urls import re_path 3 | 4 | urlpatterns = [ 5 | re_path(r'^', include('templated_email.urls', namespace='templated_email')), 6 | ] 7 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch, Mock 2 | 3 | from django.test import TestCase 4 | 5 | from templated_email import InlineImage 6 | from tests.utils import MockedNetworkTestCaseMixin 7 | 8 | 9 | class InlineMessageTestCase(MockedNetworkTestCaseMixin, TestCase): 10 | def setUp(self): 11 | self.inline_image = InlineImage('foo.png', 'content', 'png') 12 | 13 | def test_needs_two_args(self): 14 | with self.assertRaises(TypeError): 15 | InlineImage() 16 | with self.assertRaises(TypeError): 17 | InlineImage('foo') 18 | 19 | def test_has_no_cid(self): 20 | self.assertIsNone(self.inline_image._content_id) 21 | 22 | def test_generate_cid(self): 23 | str(self.inline_image) 24 | self.assertIsNotNone(self.inline_image._content_id) 25 | 26 | @patch('templated_email.utils.make_msgid', return_value='foo') 27 | def test_str(self, mocked): 28 | self.assertEqual(str(self.inline_image), 'cid:foo') 29 | 30 | @patch('templated_email.utils.make_msgid', return_value='foo') 31 | def test_should_cache_cid(self, mocked): 32 | str(self.inline_image) 33 | str(self.inline_image) 34 | mocked.assert_called_once() 35 | 36 | def test_changing_content_should_generate_new_cid(self): 37 | src_value = str(self.inline_image) 38 | cid = self.inline_image._content_id 39 | self.inline_image.content = 'content2' 40 | cid2 = self.inline_image._content_id 41 | src_value2 = str(self.inline_image) 42 | self.assertNotEqual(src_value, src_value2) 43 | cid3 = self.inline_image._content_id 44 | self.assertNotEqual(cid, cid2) 45 | self.assertNotEqual(cid2, cid3) 46 | 47 | def test_attach_to_message(self): 48 | message = Mock() 49 | self.inline_image.attach_to_message(message) 50 | mimeimage = message.attach.call_args[0][0] 51 | self.assertEqual(mimeimage.get('Content-ID'), 52 | self.inline_image._content_id) 53 | self.assertEqual(mimeimage.get('Content-Disposition'), 54 | 'inline; filename="foo.png"') 55 | -------------------------------------------------------------------------------- /tests/test_views.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.urls import reverse 4 | from django.test import TestCase 5 | from django.test import Client 6 | 7 | from templated_email.models import SavedEmail 8 | 9 | 10 | class ShowEmailViewTestCase(TestCase): 11 | 12 | def setUp(self): 13 | self.uuid = uuid.uuid4() 14 | self.saved_email = SavedEmail.objects.create(uuid=self.uuid, content='foo') 15 | self.url = '/email/%s/' % self.uuid 16 | self.url_hex = '/email/%s/' % self.uuid.hex 17 | self.client = Client() 18 | 19 | def test_get(self): 20 | response = self.client.get(self.url) 21 | self.assertEqual(response.status_code, 200) 22 | 23 | def test_get_hex(self): 24 | response = self.client.get(self.url_hex) 25 | self.assertEqual(response.status_code, 200) 26 | 27 | def test_get_non_valid(self): 28 | response = self.client.get('/email/bar/') 29 | self.assertEqual(response.status_code, 404) 30 | 31 | def test_url(self): 32 | self.assertEqual( 33 | reverse('templated_email:show_email', 34 | kwargs={'uuid': self.saved_email.uuid}), 35 | self.url) 36 | 37 | def test_url_hex(self): 38 | self.assertEqual( 39 | reverse('templated_email:show_email', 40 | kwargs={'uuid': self.saved_email.uuid.hex}), 41 | self.url_hex) 42 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import unittest.mock 2 | 3 | 4 | class MockedNetworkTestCaseMixin(object): 5 | # getfqdn can be too slow, mock it for speed. 6 | # See: https://code.djangoproject.com/ticket/24380 7 | @classmethod 8 | def setUpClass(cls): 9 | cls.getfqdn_patcher = unittest.mock.patch( 10 | 'django.core.mail.utils.socket.getfqdn', 11 | return_value='vinta.local') 12 | cls.getfqdn_patcher.start() 13 | super(MockedNetworkTestCaseMixin, cls).setUpClass() 14 | 15 | @classmethod 16 | def tearDownClass(cls): 17 | cls.getfqdn_patcher.stop() 18 | super(MockedNetworkTestCaseMixin, cls).tearDownClass() 19 | -------------------------------------------------------------------------------- /tox-requirements.txt: -------------------------------------------------------------------------------- 1 | django-anymail 2 | django-pytest 3 | django-render-block 4 | html2text 5 | pytest 6 | pytest-django 7 | pytest-pythonpath 8 | tox 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | linux-py39-django42 4 | linux-py{310,311,312,313}-django{42,50,51} 5 | 6 | [gh-actions] 7 | python = 8 | 3.9: py39 9 | 3.10: py310 10 | 3.11: py311 11 | 3.12: py312 12 | 3.13: py313 13 | 14 | 15 | [gh-actions:env] 16 | OS = 17 | ubuntu-latest: linux 18 | DJANGO = 19 | 4.2: django42 20 | 5.0: django50 21 | 5.1: django51 22 | 23 | [testenv] 24 | commands= 25 | py.test --cov=templated_email tests/ 26 | deps= 27 | -rtox-requirements.txt 28 | pytest-cov 29 | django42: django~=4.2 30 | django50: django~=5.0 31 | django51: django~=5.1 32 | --------------------------------------------------------------------------------