diff --git a/django/conf/__init__.py b/django/conf/__init__.py index dd0a158dfb9a..16f5d9b575dc 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -147,21 +147,6 @@ def configured(self): """Return True if the settings have already been configured.""" return self._wrapped is not empty - def _show_deprecation_warning(self, message, category): - """Issue a warning when external code uses a deprecated setting. - - Allow Django's own code to use it without emitting the warning. This - function should only be called from within LazySettings methods. - """ - warn_about_external_use( - message, - category, - skip_name_prefixes=( - "django.conf.LazySettings", - "django.utils.functional.LazyObject", - ), - ) - class Settings: def __init__(self, settings_module): @@ -275,4 +260,33 @@ def __repr__(self): } +def _show_settings_deprecation_warning(message, category): + """Issue a warning when external code uses a deprecated setting. + + Allow Django's own code to use the setting without emitting the warning. + This function should only be called from within settings-related code. + """ + warn_about_external_use( + message, + category, + skip_name_prefixes=( + # Include all settings-related code here. (Do not include all of + # "django.conf", which would incorrectly identify any deprecated + # settings usage inside django.conf.urls as external.) + "django.conf.LazySettings", + "django.conf.Settings", + "django.conf.UserSettingsHolder", + "django.utils.functional.LazyObject", # LazySettings superclass. + # override_settings() and similar test utils must be treated as + # settings-related code, else deprecated settings usage in tests + # would be incorrectly identified as internal. + "django.test.utils.override_settings", + "django.test.utils.modify_settings", + "django.test.utils.TestContextDecorator", + "django.test.testcases.SimpleTestCase.settings", + "django.test.testcases.SimpleTestCase.modify_settings", + ), + ) + + settings = LazySettings() diff --git a/tests/mail/test_backends.py b/tests/mail/test_backends.py index ba1f652b6d66..9a3c20d99202 100644 --- a/tests/mail/test_backends.py +++ b/tests/mail/test_backends.py @@ -13,10 +13,9 @@ from django.core import mail from django.core.exceptions import ImproperlyConfigured from django.core.mail import EmailMessage -from django.core.mail.backends import dummy, filebased, locmem, smtp +from django.core.mail.backends import console, dummy, filebased, locmem, smtp from django.core.mail.backends.base import BaseEmailBackend from django.test import SimpleTestCase, override_settings -from django.utils.module_loading import import_string from .tests import MailTestsMixin, message_from_bytes @@ -44,12 +43,19 @@ def test_unknown_kwargs_ignored(self): class SharedEmailBackendTests(MailTestsMixin): """Common test cases run against each EmailBackend.""" - email_backend = None + # Subclasses must set to the EmailBackend class being tested. + backend_class = None - @classmethod - def setUpClass(cls): - cls.enterClassContext(override_settings(EMAIL_BACKEND=cls.email_backend)) - super().setUpClass() + # Create an instance of the backend_class for use in this test context + # (configured for use with get_mailbox_content() and flush_mailbox()). + # Subclasses should override to default kwargs for testing if needed. + def create_backend(self, **kwargs): + if self.backend_class is None: + raise NotImplementedError( + "Subclasses of SharedEmailBackendTests must provide a " + "backend_class attribute." + ) + return self.backend_class(**kwargs) def get_mailbox_content(self): raise NotImplementedError( @@ -77,7 +83,7 @@ def test_send(self): email = EmailMessage( "Subject", "Content\n", "from@example.com", ["to@example.com"] ) - num_sent = mail.get_connection().send_messages([email]) + num_sent = self.create_backend().send_messages([email]) self.assertEqual(num_sent, 1) message = self.get_the_message() self.assertEqual(message["subject"], "Subject") @@ -92,7 +98,7 @@ def test_send_unicode(self): "from@example.com", ["to@example.com"], ) - num_sent = mail.get_connection().send_messages([email]) + num_sent = self.create_backend().send_messages([email]) self.assertEqual(num_sent, 1) message = self.get_the_message() self.assertEqual(message["subject"], "Chère maman") @@ -105,7 +111,7 @@ def test_send_many(self): emails_lists = ([email1, email2], iter((email1, email2))) for emails_list in emails_lists: with self.subTest(emails_list=repr(emails_list)): - num_sent = mail.get_connection().send_messages(emails_list) + num_sent = self.create_backend().send_messages(emails_list) self.assertEqual(num_sent, 2) messages = self.get_mailbox_content() self.assertEqual(len(messages), 2) @@ -114,11 +120,11 @@ def test_send_many(self): self.flush_mailbox() def test_connection_can_be_closed_even_if_not_opened(self): - backend = mail.get_connection() + backend = self.create_backend() backend.close() def test_connection_can_be_used_as_contextmanager(self): - backend = mail.get_connection() + backend = self.create_backend() backend.open = mock.Mock() backend.close = mock.Mock() @@ -130,20 +136,18 @@ def test_connection_can_be_used_as_contextmanager(self): backend.close.assert_called_once() def test_fail_silently_arg_accepted(self): - backend_class = import_string(self.email_backend) for value in [True, False]: with self.subTest(fail_silently=value): - backend = backend_class(fail_silently=value) + backend = self.create_backend(fail_silently=value) self.assertIs(backend.fail_silently, value) def test_unknown_kwargs_ignored(self): - backend_class = import_string(self.email_backend) - backend = backend_class(unknown_kwarg="foo") + backend = self.create_backend(unknown_kwarg="foo") self.assertFalse(hasattr(backend, "unknown_kwarg")) class DummyBackendTests(SharedEmailBackendTests, SimpleTestCase): - email_backend = "django.core.mail.backends.dummy.EmailBackend" + backend_class = dummy.EmailBackend def get_mailbox_content(self): # Shared tests that examine the content of sent messages are not @@ -155,13 +159,13 @@ def flush_mailbox(self): pass def test_send_messages_returns_sent_count(self): - backend = dummy.EmailBackend() + backend = self.create_backend() email = EmailMessage(to=["to@example.com"]) self.assertEqual(backend.send_messages([email, email, email]), 3) class LocmemBackendTests(SharedEmailBackendTests, SimpleTestCase): - email_backend = "django.core.mail.backends.locmem.EmailBackend" + backend_class = locmem.EmailBackend def get_mailbox_content(self): return [m.message() for m in mail.outbox] @@ -177,8 +181,8 @@ def test_locmem_shared_messages(self): """ Make sure that the locmem backend populates the outbox. """ - backend1 = locmem.EmailBackend() - backend2 = locmem.EmailBackend() + backend1 = self.create_backend() + backend2 = self.create_backend() email = EmailMessage(to=["to@example.com"]) backend1.send_messages([email]) backend2.send_messages([email]) @@ -188,7 +192,7 @@ def test_validate_multiline_headers(self): # Headers are validated when using the locmem backend (#18861). # (See also EmailMessageTests.test_header_injection().) email = EmailMessage(subject="Subject\nMultiline", to=["to@example.com"]) - backend = locmem.EmailBackend() + backend = self.create_backend() with self.assertRaises(ValueError): backend.send_messages([email]) @@ -197,7 +201,7 @@ def test_outbox_not_mutated_after_send(self): subject="correct subject", to=["to@example.com"], ) - backend = locmem.EmailBackend() + backend = self.create_backend() backend.send_messages([email]) email.subject = "other subject" email.to.append("other@example.com") @@ -206,14 +210,11 @@ def test_outbox_not_mutated_after_send(self): class FileBackendTests(SharedEmailBackendTests, SimpleTestCase): - email_backend = "django.core.mail.backends.filebased.EmailBackend" + backend_class = filebased.EmailBackend def setUp(self): super().setUp() self.tmp_dir = self.mkdtemp() - _settings_override = override_settings(EMAIL_FILE_PATH=self.tmp_dir) - _settings_override.enable() - self.addCleanup(_settings_override.disable) def mkdtemp(self): tmp_dir = tempfile.mkdtemp() @@ -228,6 +229,10 @@ def get_messages_from_filename(self, filename): messages = fp.read().split(b"\n" + (b"-" * 79) + b"\n") return [message_from_bytes(m) for m in messages if m] + def create_backend(self, **kwargs): + kwargs.setdefault("file_path", self.tmp_dir) + return super().create_backend(**kwargs) + def flush_mailbox(self): for filename in self.get_filenames(): os.unlink(os.path.join(self.tmp_dir, filename)) @@ -255,10 +260,7 @@ def test_email_file_path_override_settings(self): def test_error_if_email_file_path_setting_not_defined(self): msg = "The EMAIL_FILE_PATH setting must be set to use the file EmailBackend." - with ( - self.settings(EMAIL_FILE_PATH=None), - self.assertRaisesMessage(ImproperlyConfigured, msg), - ): + with self.assertRaisesMessage(ImproperlyConfigured, msg): filebased.EmailBackend() def test_error_if_file_path_is_not_directory(self): @@ -271,7 +273,7 @@ def test_error_if_file_path_is_not_directory(self): f"Path for saving email messages exists, but is not a directory: {tmp_file}" ) with self.assertRaisesMessage(ImproperlyConfigured, msg): - filebased.EmailBackend(file_path=tmp_file) + self.create_backend(file_path=tmp_file) @skipIf( sys.platform == "win32", @@ -280,7 +282,7 @@ def test_error_if_file_path_is_not_directory(self): def test_error_if_file_path_cannot_be_created(self): msg = "Could not create directory for saving email messages: /dev/null/foo" with self.assertRaisesMessage(ImproperlyConfigured, msg): - filebased.EmailBackend(file_path="/dev/null/foo") + self.create_backend(file_path="/dev/null/foo") @skipIf( sys.platform == "win32", @@ -291,7 +293,7 @@ def test_error_if_file_path_is_not_writeable(self): self.addCleanup(os.chmod, self.tmp_dir, 0o777) msg = f"Could not write to directory: {self.tmp_dir}" with self.assertRaisesMessage(ImproperlyConfigured, msg): - filebased.EmailBackend(file_path=self.tmp_dir) + self.create_backend(file_path=self.tmp_dir) def test_new_file_per_instance(self): # Documented behavior: "A new file is created for each new session that @@ -299,17 +301,17 @@ def test_new_file_per_instance(self): email = EmailMessage(to=["to@example.com"]) self.assertEqual(len(self.get_filenames()), 0) - backend1 = mail.get_connection() + backend1 = self.create_backend() backend1.send_messages([email]) self.assertEqual(len(self.get_filenames()), 1) - backend2 = mail.get_connection() + backend2 = self.create_backend() backend2.send_messages([email]) self.assertEqual(len(self.get_filenames()), 2) def test_multiple_messages_same_connection_single_file_reused(self): self.assertEqual(len(self.get_filenames()), 0) - backend = mail.get_connection() + backend = self.create_backend() self.assertIs(backend.open(), True) backend.send_messages([EmailMessage(to=["one@example.com"])]) @@ -331,7 +333,7 @@ def test_multiple_messages_same_connection_single_file_reused(self): def test_reopening_connection_uses_same_file(self): self.assertEqual(len(self.get_filenames()), 0) - backend = mail.get_connection() + backend = self.create_backend() self.assertIs(backend.open(), True) backend.send_messages([EmailMessage(to=["one@example.com"])]) backend.close() @@ -360,7 +362,7 @@ def mkdtemp(self): class ConsoleBackendTests(SharedEmailBackendTests, SimpleTestCase): - email_backend = "django.core.mail.backends.console.EmailBackend" + backend_class = console.EmailBackend def setUp(self): super().setUp() @@ -382,9 +384,7 @@ def get_mailbox_content(self): def test_console_stream_kwarg(self): s = StringIO() - backend = mail.get_connection( - "django.core.mail.backends.console.EmailBackend", stream=s - ) + backend = self.create_backend(stream=s) backend.send_messages([EmailMessage(to=["to@example.com"])]) message = s.getvalue().split("\n" + ("-" * 79) + "\n")[0].encode() self.assertMessageHasHeaders( @@ -442,12 +442,6 @@ def setUpClass(cls): hostname="127.0.0.1", port=port, ) - cls._settings_override = override_settings( - EMAIL_HOST=cls.smtp_controller.hostname, - EMAIL_PORT=cls.smtp_controller.port, - ) - cls._settings_override.enable() - cls.addClassCleanup(cls._settings_override.disable) cls.smtp_controller.start() cls.addClassCleanup(cls.stop_smtp) @@ -458,13 +452,18 @@ def stop_smtp(cls): @skipUnless(HAS_AIOSMTPD, "No aiosmtpd library detected.") class SMTPBackendTests(SharedEmailBackendTests, SMTPBackendTestsBase): - email_backend = "django.core.mail.backends.smtp.EmailBackend" + backend_class = smtp.EmailBackend def setUp(self): super().setUp() self.smtp_handler.flush_mailbox() self.addCleanup(self.smtp_handler.flush_mailbox) + def create_backend(self, **kwargs): + kwargs.setdefault("host", self.smtp_controller.hostname) + kwargs.setdefault("port", self.smtp_controller.port) + return super().create_backend(**kwargs) + def flush_mailbox(self): self.smtp_handler.flush_mailbox() @@ -493,7 +492,7 @@ def test_email_host_override_settings(self): self.assertEqual(backend.port, 5322) def test_smtp_connection_uses_host_and_port(self): - backend = smtp.EmailBackend(host="mail.example.com", port=5322) + backend = self.create_backend(host="mail.example.com", port=5322) self.assertEqual(backend.host, "mail.example.com") self.assertEqual(backend.port, 5322) with ( @@ -538,7 +537,7 @@ def test_auth_attempted(self): Opening the backend with non empty username/password tries to authenticate against the SMTP server. """ - backend = smtp.EmailBackend( + backend = self.create_backend( username="not empty username", password="not empty password" ) with mock.patch("smtplib.SMTP.login") as mock_smtp_login, backend: @@ -553,14 +552,14 @@ def test_server_open(self): """ open() returns whether it opened a connection. """ - backend = smtp.EmailBackend() + backend = self.create_backend() self.assertIsNone(backend.connection) opened = backend.open() backend.close() self.assertIs(opened, True) def test_reopen_connection(self): - backend = smtp.EmailBackend() + backend = self.create_backend() # Simulate an already open connection. backend.connection = mock.Mock(spec=object()) self.assertIs(backend.open(), False) @@ -576,7 +575,7 @@ def test_email_tls_override_settings(self): self.assertIs(backend.use_tls, False) def test_email_tls_default_disabled(self): - backend = smtp.EmailBackend() + backend = self.create_backend() self.assertIs(backend.use_tls, False) def test_ssl_tls_mutually_exclusive(self): @@ -585,7 +584,7 @@ def test_ssl_tls_mutually_exclusive(self): "one of those settings to True." ) with self.assertRaisesMessage(ValueError, msg): - smtp.EmailBackend(use_ssl=True, use_tls=True) + self.create_backend(use_ssl=True, use_tls=True) @override_settings(EMAIL_USE_SSL=True) def test_email_ssl_use_settings(self): @@ -598,7 +597,7 @@ def test_email_ssl_override_settings(self): self.assertIs(backend.use_ssl, False) def test_email_ssl_default_disabled(self): - backend = smtp.EmailBackend() + backend = self.create_backend() self.assertIs(backend.use_ssl, False) @override_settings(EMAIL_SSL_CERTFILE="foo") @@ -612,7 +611,7 @@ def test_email_ssl_certfile_override_settings(self): self.assertEqual(backend.ssl_certfile, "bar") def test_email_ssl_certfile_default_disabled(self): - backend = smtp.EmailBackend() + backend = self.create_backend() self.assertIsNone(backend.ssl_certfile) @override_settings(EMAIL_SSL_KEYFILE="foo") @@ -626,11 +625,11 @@ def test_email_ssl_keyfile_override_settings(self): self.assertEqual(backend.ssl_keyfile, "bar") def test_email_ssl_keyfile_default_disabled(self): - backend = smtp.EmailBackend() + backend = self.create_backend() self.assertIsNone(backend.ssl_keyfile) def test_ssl_context_uses_ssl_certfile_and_keyfile(self): - backend = smtp.EmailBackend(ssl_certfile="certfile", ssl_keyfile="keyfile") + backend = self.create_backend(ssl_certfile="certfile", ssl_keyfile="keyfile") with mock.patch( "django.core.mail.backends.smtp.ssl.SSLContext" ) as mock_ssl_context: @@ -639,9 +638,8 @@ def test_ssl_context_uses_ssl_certfile_and_keyfile(self): mock_ssl_context.assert_called_once_with(protocol=ssl.PROTOCOL_TLS_CLIENT) ssl_context.load_cert_chain.assert_called_once_with("certfile", "keyfile") - @override_settings(EMAIL_USE_TLS=True) def test_email_tls_attempts_starttls(self): - backend = smtp.EmailBackend() + backend = self.create_backend(use_tls=True) self.assertIs(backend.use_tls, True) with self.assertRaisesMessage( SMTPException, "STARTTLS extension not supported by server." @@ -649,16 +647,15 @@ def test_email_tls_attempts_starttls(self): with backend: pass - @override_settings(EMAIL_USE_SSL=True) def test_email_ssl_attempts_ssl_connection(self): - backend = smtp.EmailBackend() + backend = self.create_backend(use_ssl=True) self.assertIs(backend.use_ssl, True) with self.assertRaises(SSLError): with backend: pass def test_connection_timeout_default(self): - backend = mail.get_connection("django.core.mail.backends.smtp.EmailBackend") + backend = self.create_backend() self.assertIsNone(backend.timeout) def test_connection_timeout_custom(self): @@ -669,7 +666,9 @@ def __init__(self, *args, **kwargs): kwargs.setdefault("timeout", 42) super().__init__(*args, **kwargs) - myemailbackend = MyEmailBackend() + myemailbackend = MyEmailBackend( + host=self.smtp_controller.hostname, port=self.smtp_controller.port + ) myemailbackend.open() self.assertEqual(myemailbackend.timeout, 42) self.assertEqual(myemailbackend.connection.timeout, 42) @@ -686,12 +685,12 @@ def test_email_timeout_override_settings(self): self.assertEqual(backend.timeout, 15) def test_smtp_connection_uses_timeout(self): - backend = smtp.EmailBackend(timeout=10) + backend = self.create_backend(timeout=10) with backend: self.assertEqual(backend.connection.timeout, 10) def test_serialized_message_uses_crlf_line_ending(self): - backend = mail.get_connection() + backend = self.create_backend() with ( backend, mock.patch.object(backend.connection, "sendmail") as mock_sendmail, @@ -712,7 +711,7 @@ def test_send_messages_after_open_failed(self): send_messages() shouldn't try to send messages if open() raises an exception after initializing the connection. """ - backend = smtp.EmailBackend() + backend = self.create_backend() # Simulate connection initialization success and a subsequent # connection exception. backend.connection = mock.Mock() @@ -721,14 +720,14 @@ def test_send_messages_after_open_failed(self): self.assertEqual(backend.send_messages([email]), 0) def test_send_messages_with_empty_list_does_not_open_connection(self): - backend = smtp.EmailBackend() + backend = self.create_backend() backend.open = mock.Mock() self.assertEqual(backend.send_messages([]), 0) backend.open.assert_not_called() def test_send_messages_zero_sent(self): """A message isn't sent if it doesn't have any recipients.""" - backend = smtp.EmailBackend() + backend = self.create_backend() backend.connection = mock.Mock() email = EmailMessage("Subject", "Content", "from@example.com", to=[]) sent = backend.send_messages([email]) @@ -741,7 +740,7 @@ def test_avoids_sending_to_invalid_addresses(self): EmailMessage.all_recipients() (which is distinct from message header fields). """ - backend = smtp.EmailBackend() + backend = self.create_backend() backend.connection = mock.Mock() for email_address in ( # Invalid address with two @ signs. @@ -780,7 +779,7 @@ def test_encodes_idna_in_smtp_commands(self): "To": "Discussão Django ", }, ) - backend = smtp.EmailBackend() + backend = self.create_backend() backend.send_messages([email]) envelope = self.get_smtp_envelopes()[0] self.assertEqual(envelope["mail_from"], "lists@xn--discusso-xza.example.org") @@ -805,7 +804,7 @@ def test_does_not_reencode_idna(self): cc=['"ශ්‍රී" '], bcc=['"نامه‌ای." '], ) - backend = smtp.EmailBackend() + backend = self.create_backend() backend.send_messages([email]) envelope = self.get_smtp_envelopes()[0] self.assertEqual(envelope["mail_from"], "from@xn--fa-hia.example.com") @@ -823,7 +822,7 @@ def test_rejects_non_ascii_local_part(self): The SMTP EmailBackend does not currently support non-ASCII local-parts. (That would require using the RFC 6532 SMTPUTF8 extension.) #35713. """ - backend = smtp.EmailBackend() + backend = self.create_backend() backend.connection = mock.Mock(spec=object()) email = EmailMessage(to=["nø@example.dk"]) with self.assertRaisesMessage( @@ -835,7 +834,7 @@ def test_rejects_non_ascii_local_part(self): def test_prep_address_without_force_ascii(self): # A subclass implementing SMTPUTF8 could use # prep_address(force_ascii=False). - backend = smtp.EmailBackend() + backend = self.create_backend() for case in ["åh@example.dk", "oh@åh.example.dk", "åh@åh.example.dk"]: with self.subTest(case=case): self.assertEqual(backend.prep_address(case, force_ascii=False), case) @@ -846,7 +845,9 @@ class SMTPBackendStoppedServerTests(SMTPBackendTestsBase): @classmethod def setUpClass(cls): super().setUpClass() - cls.backend = smtp.EmailBackend() + cls.backend = smtp.EmailBackend( + host=cls.smtp_controller.hostname, port=cls.smtp_controller.port + ) cls.smtp_controller.stop() @classmethod