diff --git a/django/core/checks/messages.py b/django/core/checks/messages.py index aacac632eb4..0987c2d1188 100644 --- a/django/core/checks/messages.py +++ b/django/core/checks/messages.py @@ -9,7 +9,8 @@ CRITICAL = 50 class CheckMessage: def __init__(self, level, msg, hint=None, obj=None, id=None): - assert isinstance(level, int), "The first argument should be level." + if not isinstance(level, int): + raise TypeError('The first argument should be level.') self.level = level self.msg = msg self.hint = hint diff --git a/django/core/checks/registry.py b/django/core/checks/registry.py index d54538e3673..d7bfa495486 100644 --- a/django/core/checks/registry.py +++ b/django/core/checks/registry.py @@ -75,9 +75,12 @@ class CheckRegistry: for check in checks: new_errors = check(app_configs=app_configs, databases=databases) - assert is_iterable(new_errors), ( - "The function %r did not return a list. All functions registered " - "with the checks registry must return a list." % check) + if not is_iterable(new_errors): + raise TypeError( + 'The function %r did not return a list. All functions ' + 'registered with the checks registry must return a list.' + % check, + ) errors.extend(new_errors) return errors diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 16f9d4e27b1..29b0e4c9ed6 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -294,7 +294,8 @@ class FileSystemStorage(Storage): return str(name).replace('\\', '/') def delete(self, name): - assert name, "The name argument is not allowed to be empty." + if not name: + raise ValueError('The name must be given to delete().') name = self.path(name) # If the file or directory exists, delete it from the filesystem. try: diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 963542cd623..ccc8a769ea3 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -296,11 +296,15 @@ class EmailMessage: mimetype to DEFAULT_ATTACHMENT_MIME_TYPE and don't decode the content. """ if isinstance(filename, MIMEBase): - assert content is None - assert mimetype is None + if content is not None or mimetype is not None: + raise ValueError( + 'content and mimetype must not be given when a MIMEBase ' + 'instance is provided.' + ) self.attachments.append(filename) + elif content is None: + raise ValueError('content must be provided.') else: - assert content is not None mimetype = mimetype or mimetypes.guess_type(filename)[0] or DEFAULT_ATTACHMENT_MIME_TYPE basetype, subtype = mimetype.split('/', 1) @@ -428,8 +432,8 @@ class EmailMultiAlternatives(EmailMessage): def attach_alternative(self, content, mimetype): """Attach an alternative content representation.""" - assert content is not None - assert mimetype is not None + if content is None or mimetype is None: + raise ValueError('Both content and mimetype must be provided.') self.alternatives.append((content, mimetype)) def _create_message(self, msg): diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 56197115c6a..6f10124c0c5 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -355,6 +355,13 @@ Miscellaneous to ``reorder_tests()``. It now accepts an iterable of tests rather than a test suite, and returns an iterator of tests. +* Calling ``FileSystemStorage.delete()`` with an empty ``name`` now raises + ``ValueError`` instead of ``AssertionError``. + +* Calling ``EmailMultiAlternatives.attach_alternative()`` or + ``EmailMessage.attach()`` with an invalid ``content`` or ``mimetype`` + arguments now raise ``ValueError`` instead of ``AssertionError``. + .. _deprecated-features-4.0: Features deprecated in 4.0 diff --git a/tests/check_framework/tests.py b/tests/check_framework/tests.py index e669b11c2b8..9e461c5040a 100644 --- a/tests/check_framework/tests.py +++ b/tests/check_framework/tests.py @@ -4,6 +4,7 @@ from io import StringIO from django.apps import apps from django.core import checks from django.core.checks import Error, Warning +from django.core.checks.messages import CheckMessage from django.core.checks.registry import CheckRegistry from django.core.management import call_command from django.core.management.base import CommandError @@ -74,6 +75,20 @@ class SystemCheckFrameworkTests(SimpleTestCase): def no_kwargs(app_configs, databases): pass + def test_register_run_checks_non_iterable(self): + registry = CheckRegistry() + + @registry.register + def return_non_iterable(**kwargs): + return Error('Message') + + msg = ( + 'The function %r did not return a list. All functions registered ' + 'with the checks registry must return a list.' % return_non_iterable + ) + with self.assertRaisesMessage(TypeError, msg): + registry.run_checks() + class MessageTests(SimpleTestCase): @@ -132,6 +147,11 @@ class MessageTests(SimpleTestCase): e = Error("Error", obj=DummyObj()) self.assertNotEqual(e, 'a string') + def test_invalid_level(self): + msg = 'The first argument should be level.' + with self.assertRaisesMessage(TypeError, msg): + CheckMessage('ERROR', 'Message') + def simple_system_check(**kwargs): simple_system_check.kwargs = kwargs diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py index 6d17a7118b4..381bc6b7b5c 100644 --- a/tests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -501,7 +501,10 @@ class FileStorageTests(SimpleTestCase): Calling delete with an empty name should not try to remove the base storage directory, but fail loudly (#20660). """ - with self.assertRaises(AssertionError): + msg = 'The name must be given to delete().' + with self.assertRaisesMessage(ValueError, msg): + self.storage.delete(None) + with self.assertRaisesMessage(ValueError, msg): self.storage.delete('') def test_delete_deletes_directories(self): diff --git a/tests/mail/tests.py b/tests/mail/tests.py index 475e204b32b..de8ff159c00 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -529,6 +529,24 @@ class MailTests(HeadersCheckMixin, SimpleTestCase): self.assertEqual(content, b'\xff') self.assertEqual(mimetype, 'application/octet-stream') + def test_attach_mimetext_content_mimetype(self): + email_msg = EmailMessage() + txt = MIMEText('content') + msg = ( + 'content and mimetype must not be given when a MIMEBase instance ' + 'is provided.' + ) + with self.assertRaisesMessage(ValueError, msg): + email_msg.attach(txt, content='content') + with self.assertRaisesMessage(ValueError, msg): + email_msg.attach(txt, mimetype='text/plain') + + def test_attach_content_none(self): + email_msg = EmailMessage() + msg = 'content must be provided.' + with self.assertRaisesMessage(ValueError, msg): + email_msg.attach('file.txt', mimetype="application/pdf") + def test_dummy_backend(self): """ Make sure that dummy backends returns correct number of sent messages @@ -835,6 +853,14 @@ class MailTests(HeadersCheckMixin, SimpleTestCase): with self.assertRaisesMessage(ValueError, msg): sanitize_address(email_address, encoding='utf-8') + def test_email_multi_alternatives_content_mimetype_none(self): + email_msg = EmailMultiAlternatives() + msg = 'Both content and mimetype must be provided.' + with self.assertRaisesMessage(ValueError, msg): + email_msg.attach_alternative(None, 'text/html') + with self.assertRaisesMessage(ValueError, msg): + email_msg.attach_alternative('

content

', None) + @requires_tz_support class MailTimeZoneTests(SimpleTestCase):