Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions django/core/mail/backends/filebased.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def __init__(self, *args, file_path=None, **kwargs):
self.file_path = file_path
else:
self.file_path = getattr(settings, "EMAIL_FILE_PATH", None)
if self.file_path is None:
raise ImproperlyConfigured(
"The EMAIL_FILE_PATH setting must be set to use the file EmailBackend."
)
self.file_path = os.path.abspath(self.file_path)
try:
os.makedirs(self.file_path, exist_ok=True)
Expand Down
3 changes: 3 additions & 0 deletions tests/auth_tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import urllib.parse
from unittest import mock

from mail.custombackend import FailingEmailBackend

from django import forms
from django.contrib.auth.forms import (
AdminPasswordChangeForm,
Expand Down Expand Up @@ -1382,6 +1384,7 @@ def test_save_html_email_template_name(self):

@override_settings(EMAIL_BACKEND="mail.custombackend.FailingEmailBackend")
def test_save_send_email_exceptions_are_catched_and_logged(self):
self.addCleanup(FailingEmailBackend.reset)
user, username, email = self.create_dummy_user()
form = PasswordResetForm({"email": email})
self.assertTrue(form.is_valid())
Expand Down
6 changes: 0 additions & 6 deletions tests/logging_tests/logconfig.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging

from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend
from django.views.debug import ExceptionReporter


Expand All @@ -11,11 +10,6 @@ def __init__(self):
self.config = settings.LOGGING


class MyEmailBackend(BaseEmailBackend):
def send_messages(self, email_messages):
pass


class CustomExceptionReporter(ExceptionReporter):
def get_traceback_text(self):
return "custom traceback text"
102 changes: 32 additions & 70 deletions tests/logging_tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from unittest import TestCase, mock

from admin_scripts.tests import AdminScriptTestCase
from mail.custombackend import FailingEmailBackend, OptionsCapturingBackend

from django.conf import settings
from django.core import mail
Expand All @@ -26,7 +27,6 @@
from django.views.debug import ExceptionReporter

from . import views
from .logconfig import MyEmailBackend


class LoggingFiltersTest(SimpleTestCase):
Expand Down Expand Up @@ -286,9 +286,22 @@ def get_admin_email_handler(self, logger):
h for h in logger.handlers if h.__class__.__name__ == "AdminEmailHandler"
][0]

def test_fail_silently(self):
admin_email_handler = self.get_admin_email_handler(self.logger)
self.assertTrue(admin_email_handler.connection().fail_silently)
def make_log_record(self, url_path=None, *args, **kwargs):
record = self.logger.makeRecord(
"name", logging.ERROR, "function", "lno", "message", None, None
)
if url_path is not None:
record.request = self.request_factory.get(url_path, *args, **kwargs)
return record

@override_settings(
ADMINS=["admin@example.com"],
EMAIL_BACKEND="mail.custombackend.FailingEmailBackend",
)
def test_sends_using_fail_silently(self):
self.addCleanup(FailingEmailBackend.reset)
self.logger.error("All work and no play makes Jack a dull boy")
self.assertIs(FailingEmailBackend.init_kwargs[0]["fail_silently"], True)

@override_settings(
ADMINS=["admin@example.com"],
Expand Down Expand Up @@ -383,36 +396,16 @@ def test_subject_accepts_newlines(self):
self.assertNotIn("\r", mail.outbox[0].subject)
self.assertEqual(mail.outbox[0].subject, expected_subject)

@override_settings(
ADMINS=["admin@example.com"],
DEBUG=False,
)
@override_settings(ADMINS=["admin@example.com"])
def test_uses_custom_email_backend(self):
"""
Refs #19325
"""
message = "All work and no play makes Jack a dull boy"
admin_email_handler = self.get_admin_email_handler(self.logger)
mail_admins_called = {"called": False}

def my_mail_admins(*args, **kwargs):
connection = kwargs["connection"]
self.assertIsInstance(connection, MyEmailBackend)
mail_admins_called["called"] = True

# Monkeypatches
orig_mail_admins = mail.mail_admins
orig_email_backend = admin_email_handler.email_backend
mail.mail_admins = my_mail_admins
admin_email_handler.email_backend = "logging_tests.logconfig.MyEmailBackend"

try:
self.logger.error(message)
self.assertTrue(mail_admins_called["called"])
finally:
# Revert Monkeypatches
mail.mail_admins = orig_mail_admins
admin_email_handler.email_backend = orig_email_backend
self.addCleanup(OptionsCapturingBackend.reset)
handler = AdminEmailHandler(
email_backend="mail.custombackend.OptionsCapturingBackend"
)
handler.emit(self.make_log_record("/"))
self.assertEqual(len(mail.outbox), 0)
self.assertIs(OptionsCapturingBackend.init_kwargs[0]["fail_silently"], True)
self.assertEqual(len(OptionsCapturingBackend.sent_messages), 1)

@override_settings(
ADMINS=["admin@example.com"],
Expand All @@ -423,12 +416,8 @@ def test_emit_non_ascii(self):
request.
"""
handler = self.get_admin_email_handler(self.logger)
record = self.logger.makeRecord(
"name", logging.ERROR, "function", "lno", "message", None, None
)
url_path = "/º"
record.request = self.request_factory.get(url_path)
handler.emit(record)
handler.emit(self.make_log_record(url_path))
self.assertEqual(len(mail.outbox), 1)
msg = mail.outbox[0]
self.assertEqual(msg.to, ["admin@example.com"])
Expand All @@ -442,16 +431,10 @@ def test_emit_non_ascii(self):
def test_customize_send_mail_method(self):
class ManagerEmailHandler(AdminEmailHandler):
def send_mail(self, subject, message, *args, **kwargs):
mail.mail_managers(
subject, message, *args, connection=self.connection(), **kwargs
)
mail.mail_managers(subject, message, *args, **kwargs)

handler = ManagerEmailHandler()
record = self.logger.makeRecord(
"name", logging.ERROR, "function", "lno", "message", None, None
)
self.assertEqual(len(mail.outbox), 0)
handler.emit(record)
handler.emit(self.make_log_record())
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ["manager@example.com"])

Expand Down Expand Up @@ -480,14 +463,10 @@ def test_default_exception_reporter_class(self):

@override_settings(ADMINS=["admin@example.com"])
def test_custom_exception_reporter_is_used(self):
record = self.logger.makeRecord(
"name", logging.ERROR, "function", "lno", "message", None, None
)
record.request = self.request_factory.get("/")
handler = AdminEmailHandler(
reporter_class="logging_tests.logconfig.CustomExceptionReporter"
)
handler.emit(record)
handler.emit(self.make_log_record("/"))
self.assertEqual(len(mail.outbox), 1)
msg = mail.outbox[0]
self.assertEqual(msg.body, "message\n\ncustom traceback text")
Expand All @@ -496,16 +475,7 @@ def test_custom_exception_reporter_is_used(self):
def test_emit_no_form_tag(self):
"""HTML email doesn't contain forms."""
handler = AdminEmailHandler(include_html=True)
record = self.logger.makeRecord(
"name",
logging.ERROR,
"function",
"lno",
"message",
None,
None,
)
handler.emit(record)
handler.emit(self.make_log_record())
self.assertEqual(len(mail.outbox), 1)
msg = mail.outbox[0]
self.assertEqual(msg.subject, "[Django] ERROR: message")
Expand All @@ -517,15 +487,7 @@ def test_emit_no_form_tag(self):
@override_settings(ADMINS=[])
def test_emit_no_admins(self):
handler = AdminEmailHandler()
record = self.logger.makeRecord(
"name",
logging.ERROR,
"function",
"lno",
"message",
None,
None,
)
record = self.make_log_record()
with mock.patch.object(
handler,
"format_subject",
Expand Down
48 changes: 47 additions & 1 deletion tests/mail/custombackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,53 @@ def send_messages(self, email_messages):
return len(email_messages)


class FailingEmailBackend(BaseEmailBackend):
class OptionsCapturingBackend(BaseEmailBackend):
"""Capture init kwargs and sent messages for use in test assertions.

Test cases using this backend _must_ ensure reset() is called::

def test_something(self):
self.addCleanup(OptionsCapturingBackend.reset)
...

Failing to call reset() will cause unexpected behavior in other tests that
use the OptionsCapturingBackend.
"""

init_kwargs = []
sent_messages = []

@classmethod
def reset(cls):
cls.init_kwargs = []
cls.sent_messages = []

def __init__(self, **kwargs):
self.init_kwargs.append(kwargs.copy())
super().__init__(**kwargs)

def send_messages(self, email_messages):
self.sent_messages.extend(email_messages)
return len(email_messages)


class FailingEmailBackend(OptionsCapturingBackend):
"""Raise on send_messages(), or do nothing if fail_silently is set.

Test cases using this backend _must_ ensure reset() is called::

def test_something(self):
self.addCleanup(FailingEmailBackend.reset)
...

Failing to call reset() will cause unexpected behavior in other tests that
use the FailingEmailBackend.
"""

init_kwargs = []
sent_messages = []

def send_messages(self, email_messages):
if self.fail_silently:
return 0
raise ValueError("FailingEmailBackend is doomed to fail.")
Loading
Loading