mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			526 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			526 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import os
 | |
| import subprocess
 | |
| import sys
 | |
| import unittest
 | |
| from unittest import mock
 | |
| 
 | |
| from django import __version__
 | |
| from django.conf import settings
 | |
| from django.contrib.auth.models import Group, Permission, User
 | |
| from django.contrib.contenttypes.models import ContentType
 | |
| from django.core.management import CommandError, call_command
 | |
| from django.core.management.commands import shell
 | |
| from django.db import connection, models, reset_queries
 | |
| from django.db.models import functions
 | |
| from django.test import SimpleTestCase
 | |
| from django.test.utils import captured_stdin, captured_stdout, override_settings
 | |
| from django.urls import resolve, reverse
 | |
| from django.utils import timezone
 | |
| 
 | |
| from .models import Marker, Phone
 | |
| 
 | |
| 
 | |
| class ShellCommandTestCase(SimpleTestCase):
 | |
|     script_globals = 'print("__name__" in globals() and "Phone" in globals())'
 | |
|     script_with_inline_function = (
 | |
|         "import django\ndef f():\n    print(django.__version__)\nf()"
 | |
|     )
 | |
| 
 | |
|     def test_command_option(self):
 | |
|         with self.assertLogs("test", "INFO") as cm:
 | |
|             with captured_stdout():
 | |
|                 call_command(
 | |
|                     "shell",
 | |
|                     command=(
 | |
|                         "import django; from logging import getLogger; "
 | |
|                         'getLogger("test").info(django.__version__)'
 | |
|                     ),
 | |
|                 )
 | |
|         self.assertEqual(cm.records[0].getMessage(), __version__)
 | |
| 
 | |
|     def test_command_option_globals(self):
 | |
|         with captured_stdout() as stdout:
 | |
|             call_command("shell", command=self.script_globals, verbosity=0)
 | |
|         self.assertEqual(stdout.getvalue().strip(), "True")
 | |
| 
 | |
|     def test_command_option_inline_function_call(self):
 | |
|         with captured_stdout() as stdout:
 | |
|             call_command("shell", command=self.script_with_inline_function, verbosity=0)
 | |
|         self.assertEqual(stdout.getvalue().strip(), __version__)
 | |
| 
 | |
|     @override_settings(INSTALLED_APPS=["shell"])
 | |
|     def test_no_settings(self):
 | |
|         test_environ = os.environ.copy()
 | |
|         if "DJANGO_SETTINGS_MODULE" in test_environ:
 | |
|             del test_environ["DJANGO_SETTINGS_MODULE"]
 | |
|         error = (
 | |
|             "Automatic imports are disabled since settings are not configured.\n"
 | |
|             "DJANGO_SETTINGS_MODULE value is None.\n"
 | |
|             "HINT: Ensure that the settings module is configured and set.\n\n"
 | |
|         )
 | |
|         for verbosity, assertError in [
 | |
|             ("0", self.assertNotIn),
 | |
|             ("1", self.assertIn),
 | |
|             ("2", self.assertIn),
 | |
|         ]:
 | |
|             with self.subTest(verbosity=verbosity, get_auto_imports="models"):
 | |
|                 p = subprocess.run(
 | |
|                     [
 | |
|                         sys.executable,
 | |
|                         "-m",
 | |
|                         "django",
 | |
|                         "shell",
 | |
|                         "-c",
 | |
|                         "print(globals())",
 | |
|                         "-v",
 | |
|                         verbosity,
 | |
|                     ],
 | |
|                     capture_output=True,
 | |
|                     env=test_environ,
 | |
|                     text=True,
 | |
|                     umask=-1,
 | |
|                 )
 | |
|                 assertError(error, p.stdout)
 | |
|                 self.assertNotIn("Marker", p.stdout)
 | |
|                 self.assertNotIn("reset_queries", p.stdout)
 | |
|                 self.assertNotIn("imported automatically", p.stdout)
 | |
| 
 | |
|             with self.subTest(verbosity=verbosity, get_auto_imports="without-models"):
 | |
|                 with mock.patch(
 | |
|                     "django.core.management.commands.shell.Command.get_auto_imports",
 | |
|                     return_value=["django.urls.resolve"],
 | |
|                 ):
 | |
|                     p = subprocess.run(
 | |
|                         [
 | |
|                             sys.executable,
 | |
|                             "-m",
 | |
|                             "django",
 | |
|                             "shell",
 | |
|                             "-c",
 | |
|                             "print(globals())",
 | |
|                             "-v",
 | |
|                             verbosity,
 | |
|                         ],
 | |
|                         capture_output=True,
 | |
|                         env=test_environ,
 | |
|                         text=True,
 | |
|                         umask=-1,
 | |
|                     )
 | |
|                     assertError(error, p.stdout)
 | |
|                     self.assertNotIn("resolve", p.stdout)
 | |
| 
 | |
|     @unittest.skipIf(
 | |
|         sys.platform == "win32", "Windows select() doesn't support file descriptors."
 | |
|     )
 | |
|     @mock.patch("django.core.management.commands.shell.select")
 | |
|     def test_stdin_read(self, select):
 | |
|         with captured_stdin() as stdin, captured_stdout() as stdout:
 | |
|             stdin.write("print(100)\n")
 | |
|             stdin.seek(0)
 | |
|             call_command("shell", verbosity=0)
 | |
|         self.assertEqual(stdout.getvalue().strip(), "100")
 | |
| 
 | |
|     @unittest.skipIf(
 | |
|         sys.platform == "win32",
 | |
|         "Windows select() doesn't support file descriptors.",
 | |
|     )
 | |
|     @mock.patch("django.core.management.commands.shell.select")  # [1]
 | |
|     def test_stdin_read_globals(self, select):
 | |
|         with captured_stdin() as stdin, captured_stdout() as stdout:
 | |
|             stdin.write(self.script_globals)
 | |
|             stdin.seek(0)
 | |
|             call_command("shell", verbosity=0)
 | |
|         self.assertEqual(stdout.getvalue().strip(), "True")
 | |
| 
 | |
|     @unittest.skipIf(
 | |
|         sys.platform == "win32",
 | |
|         "Windows select() doesn't support file descriptors.",
 | |
|     )
 | |
|     @mock.patch("django.core.management.commands.shell.select")  # [1]
 | |
|     def test_stdin_read_inline_function_call(self, select):
 | |
|         with captured_stdin() as stdin, captured_stdout() as stdout:
 | |
|             stdin.write(self.script_with_inline_function)
 | |
|             stdin.seek(0)
 | |
|             call_command("shell", verbosity=0)
 | |
|         self.assertEqual(stdout.getvalue().strip(), __version__)
 | |
| 
 | |
|     def test_ipython(self):
 | |
|         cmd = shell.Command()
 | |
|         mock_ipython = mock.Mock(start_ipython=mock.MagicMock())
 | |
|         options = {"verbosity": 0, "no_imports": False}
 | |
| 
 | |
|         with mock.patch.dict(sys.modules, {"IPython": mock_ipython}):
 | |
|             cmd.ipython(options)
 | |
| 
 | |
|         self.assertEqual(
 | |
|             mock_ipython.start_ipython.mock_calls,
 | |
|             [mock.call(argv=[], user_ns=cmd.get_namespace(**options))],
 | |
|         )
 | |
| 
 | |
|     @mock.patch("django.core.management.commands.shell.select.select")  # [1]
 | |
|     @mock.patch.dict("sys.modules", {"IPython": None})
 | |
|     def test_shell_with_ipython_not_installed(self, select):
 | |
|         select.return_value = ([], [], [])
 | |
|         with self.assertRaisesMessage(
 | |
|             CommandError, "Couldn't import ipython interface."
 | |
|         ):
 | |
|             call_command("shell", interface="ipython")
 | |
| 
 | |
|     def test_bpython(self):
 | |
|         cmd = shell.Command()
 | |
|         mock_bpython = mock.Mock(embed=mock.MagicMock())
 | |
|         options = {"verbosity": 0, "no_imports": False}
 | |
| 
 | |
|         with mock.patch.dict(sys.modules, {"bpython": mock_bpython}):
 | |
|             cmd.bpython(options)
 | |
| 
 | |
|         self.assertEqual(
 | |
|             mock_bpython.embed.mock_calls, [mock.call(cmd.get_namespace(**options))]
 | |
|         )
 | |
| 
 | |
|     @mock.patch("django.core.management.commands.shell.select.select")  # [1]
 | |
|     @mock.patch.dict("sys.modules", {"bpython": None})
 | |
|     def test_shell_with_bpython_not_installed(self, select):
 | |
|         select.return_value = ([], [], [])
 | |
|         with self.assertRaisesMessage(
 | |
|             CommandError, "Couldn't import bpython interface."
 | |
|         ):
 | |
|             call_command("shell", interface="bpython")
 | |
| 
 | |
|     def test_python(self):
 | |
|         cmd = shell.Command()
 | |
|         mock_code = mock.Mock(interact=mock.MagicMock())
 | |
|         options = {"verbosity": 0, "no_startup": True, "no_imports": False}
 | |
| 
 | |
|         with mock.patch.dict(sys.modules, {"code": mock_code}):
 | |
|             cmd.python(options)
 | |
| 
 | |
|         self.assertEqual(
 | |
|             mock_code.interact.mock_calls,
 | |
|             [mock.call(local=cmd.get_namespace(**options))],
 | |
|         )
 | |
| 
 | |
|     # [1] Patch select to prevent tests failing when the test suite is run
 | |
|     # in parallel mode. The tests are run in a subprocess and the subprocess's
 | |
|     # stdin is closed and replaced by /dev/null. Reading from /dev/null always
 | |
|     # returns EOF and so select always shows that sys.stdin is ready to read.
 | |
|     # This causes problems because of the call to select.select() toward the
 | |
|     # end of shell's handle() method.
 | |
| 
 | |
| 
 | |
| class ShellCommandAutoImportsTestCase(SimpleTestCase):
 | |
| 
 | |
|     @override_settings(
 | |
|         INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
 | |
|     )
 | |
|     def test_get_namespace(self):
 | |
|         namespace = shell.Command().get_namespace()
 | |
| 
 | |
|         self.assertEqual(
 | |
|             namespace,
 | |
|             {
 | |
|                 "settings": settings,
 | |
|                 "connection": connection,
 | |
|                 "reset_queries": reset_queries,
 | |
|                 "models": models,
 | |
|                 "functions": functions,
 | |
|                 "timezone": timezone,
 | |
|                 "Marker": Marker,
 | |
|                 "Phone": Phone,
 | |
|                 "ContentType": ContentType,
 | |
|                 "Group": Group,
 | |
|                 "Permission": Permission,
 | |
|                 "User": User,
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     @override_settings(INSTALLED_APPS=[])
 | |
|     def test_get_namespace_default_imports(self):
 | |
|         namespace = shell.Command().get_namespace()
 | |
| 
 | |
|         self.assertEqual(
 | |
|             namespace,
 | |
|             {
 | |
|                 "settings": settings,
 | |
|                 "connection": connection,
 | |
|                 "reset_queries": reset_queries,
 | |
|                 "models": models,
 | |
|                 "functions": functions,
 | |
|                 "timezone": timezone,
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     @override_settings(
 | |
|         INSTALLED_APPS=["model_forms", "contenttypes_tests", "forms_tests"]
 | |
|     )
 | |
|     def test_get_namespace_precedence(self):
 | |
|         # All of these apps define an `Article` model. The one defined first in
 | |
|         # INSTALLED_APPS, takes precedence.
 | |
|         import model_forms.models
 | |
| 
 | |
|         namespace = shell.Command().get_namespace()
 | |
|         self.assertIs(namespace.get("Article"), model_forms.models.Article)
 | |
| 
 | |
|     @override_settings(
 | |
|         INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
 | |
|     )
 | |
|     def test_get_namespace_overridden(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return super().get_auto_imports() + [
 | |
|                     "django.urls.reverse",
 | |
|                     "django.urls.resolve",
 | |
|                 ]
 | |
| 
 | |
|         namespace = TestCommand().get_namespace()
 | |
| 
 | |
|         self.assertEqual(
 | |
|             namespace,
 | |
|             {
 | |
|                 "resolve": resolve,
 | |
|                 "reverse": reverse,
 | |
|                 "settings": settings,
 | |
|                 "connection": connection,
 | |
|                 "reset_queries": reset_queries,
 | |
|                 "models": models,
 | |
|                 "functions": functions,
 | |
|                 "timezone": timezone,
 | |
|                 "Marker": Marker,
 | |
|                 "Phone": Phone,
 | |
|                 "ContentType": ContentType,
 | |
|                 "Group": Group,
 | |
|                 "Permission": Permission,
 | |
|                 "User": User,
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     @override_settings(
 | |
|         INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
 | |
|     )
 | |
|     def test_no_imports_flag(self):
 | |
|         for verbosity in (0, 1, 2, 3):
 | |
|             with self.subTest(verbosity=verbosity), captured_stdout() as stdout:
 | |
|                 namespace = shell.Command().get_namespace(
 | |
|                     verbosity=verbosity, no_imports=True
 | |
|                 )
 | |
|             self.assertEqual(namespace, {})
 | |
|             self.assertEqual(stdout.getvalue().strip(), "")
 | |
| 
 | |
|     @override_settings(
 | |
|         INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
 | |
|     )
 | |
|     def test_verbosity_zero(self):
 | |
|         with captured_stdout() as stdout:
 | |
|             cmd = shell.Command()
 | |
|             namespace = cmd.get_namespace(verbosity=0)
 | |
|         self.assertEqual(len(namespace), len(cmd.get_auto_imports()))
 | |
|         self.assertEqual(stdout.getvalue().strip(), "")
 | |
| 
 | |
|     @override_settings(
 | |
|         INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
 | |
|     )
 | |
|     def test_verbosity_one(self):
 | |
|         with captured_stdout() as stdout:
 | |
|             cmd = shell.Command()
 | |
|             namespace = cmd.get_namespace(verbosity=1)
 | |
|         self.assertEqual(len(namespace), len(cmd.get_auto_imports()))
 | |
|         self.assertEqual(
 | |
|             stdout.getvalue().strip(),
 | |
|             "12 objects imported automatically (use -v 2 for details).",
 | |
|         )
 | |
| 
 | |
|     @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
 | |
|     @mock.patch.dict(sys.modules, {"isort": None})
 | |
|     def test_message_with_stdout_listing_objects_with_isort_not_installed(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 # Include duplicate import strings to ensure proper handling,
 | |
|                 # independent of isort's deduplication (#36252).
 | |
|                 return super().get_auto_imports() + [
 | |
|                     "django.urls.reverse",
 | |
|                     "django.urls.resolve",
 | |
|                     "shell",
 | |
|                     "django",
 | |
|                     "django.urls.reverse",
 | |
|                     "shell",
 | |
|                     "django",
 | |
|                 ]
 | |
| 
 | |
|         with captured_stdout() as stdout:
 | |
|             TestCommand().get_namespace(verbosity=2)
 | |
| 
 | |
|         self.assertEqual(
 | |
|             stdout.getvalue().strip(),
 | |
|             "13 objects imported automatically:\n\n"
 | |
|             "  import shell\n"
 | |
|             "  import django\n"
 | |
|             "  from django.conf import settings\n"
 | |
|             "  from django.db import connection, models, reset_queries\n"
 | |
|             "  from django.db.models import functions\n"
 | |
|             "  from django.utils import timezone\n"
 | |
|             "  from django.contrib.contenttypes.models import ContentType\n"
 | |
|             "  from shell.models import Phone, Marker\n"
 | |
|             "  from django.urls import reverse, resolve",
 | |
|         )
 | |
| 
 | |
|     def test_message_with_stdout_one_object(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return ["django.db.connection"]
 | |
| 
 | |
|         with captured_stdout() as stdout:
 | |
|             TestCommand().get_namespace(verbosity=2)
 | |
| 
 | |
|         cases = {
 | |
|             0: "",
 | |
|             1: "1 object imported automatically (use -v 2 for details).",
 | |
|             2: (
 | |
|                 "1 object imported automatically:\n\n"
 | |
|                 "  from django.db import connection"
 | |
|             ),
 | |
|         }
 | |
|         for verbosity, expected in cases.items():
 | |
|             with self.subTest(verbosity=verbosity):
 | |
|                 with captured_stdout() as stdout:
 | |
|                     TestCommand().get_namespace(verbosity=verbosity)
 | |
|                     self.assertEqual(stdout.getvalue().strip(), expected)
 | |
| 
 | |
|     def test_message_with_stdout_zero_object(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return []
 | |
| 
 | |
|         with captured_stdout() as stdout:
 | |
|             TestCommand().get_namespace(verbosity=2)
 | |
| 
 | |
|         cases = {
 | |
|             0: "",
 | |
|             1: "0 objects imported automatically.",
 | |
|             2: "0 objects imported automatically.",
 | |
|         }
 | |
|         for verbosity, expected in cases.items():
 | |
|             with self.subTest(verbosity=verbosity):
 | |
|                 with captured_stdout() as stdout:
 | |
|                     TestCommand().get_namespace(verbosity=verbosity)
 | |
|                     self.assertEqual(stdout.getvalue().strip(), expected)
 | |
| 
 | |
|     @override_settings(INSTALLED_APPS=[])
 | |
|     def test_message_with_stdout_no_installed_apps(self):
 | |
|         cases = {
 | |
|             0: "",
 | |
|             1: "6 objects imported automatically (use -v 2 for details).",
 | |
|             2: "6 objects imported automatically:\n\n"
 | |
|             "  from django.conf import settings\n"
 | |
|             "  from django.db import connection, models, reset_queries\n"
 | |
|             "  from django.db.models import functions\n"
 | |
|             "  from django.utils import timezone",
 | |
|         }
 | |
|         for verbosity, expected in cases.items():
 | |
|             with self.subTest(verbosity=verbosity):
 | |
|                 with captured_stdout() as stdout:
 | |
|                     shell.Command().get_namespace(verbosity=verbosity)
 | |
|                     self.assertEqual(stdout.getvalue().strip(), expected)
 | |
| 
 | |
|     def test_message_with_stdout_overriden_none_result(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return None
 | |
| 
 | |
|         for verbosity in [0, 1, 2]:
 | |
|             with self.subTest(verbosity=verbosity):
 | |
|                 with captured_stdout() as stdout:
 | |
|                     result = TestCommand().get_namespace(verbosity=verbosity)
 | |
|                     self.assertEqual(result, {})
 | |
|                     self.assertEqual(stdout.getvalue().strip(), "")
 | |
| 
 | |
|     @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
 | |
|     def test_message_with_stdout_listing_objects_with_isort(self):
 | |
|         sorted_imports = (
 | |
|             "  from shell.models import Marker, Phone\n\n"
 | |
|             "  from django.db import connection, models, reset_queries\n"
 | |
|             "  from django.db.models import functions\n"
 | |
|             "  from django.contrib.contenttypes.models import ContentType\n"
 | |
|             "  from django.conf import settings\n"
 | |
|             "  from django.utils import timezone"
 | |
|         )
 | |
|         mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
 | |
| 
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return super().get_auto_imports() + [
 | |
|                     "django.urls.reverse",
 | |
|                     "django.urls.resolve",
 | |
|                     "django",
 | |
|                 ]
 | |
| 
 | |
|         with (
 | |
|             mock.patch.dict(sys.modules, {"isort": mock_isort_code}),
 | |
|             captured_stdout() as stdout,
 | |
|         ):
 | |
|             TestCommand().get_namespace(verbosity=2)
 | |
| 
 | |
|         self.assertEqual(
 | |
|             stdout.getvalue().strip(),
 | |
|             "12 objects imported automatically:\n\n" + sorted_imports,
 | |
|         )
 | |
| 
 | |
|     def test_override_get_auto_imports(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return [
 | |
|                     "model_forms",
 | |
|                     "shell",
 | |
|                     "does.not.exist",
 | |
|                     "doesntexisteither",
 | |
|                 ]
 | |
| 
 | |
|         with captured_stdout() as stdout:
 | |
|             TestCommand().get_namespace(verbosity=2)
 | |
| 
 | |
|         expected = (
 | |
|             "2 objects could not be automatically imported:\n\n"
 | |
|             "  does.not.exist\n"
 | |
|             "  doesntexisteither\n\n"
 | |
|             "2 objects imported automatically:\n\n"
 | |
|             "  import model_forms\n"
 | |
|             "  import shell\n\n"
 | |
|         )
 | |
|         self.assertEqual(stdout.getvalue(), expected)
 | |
| 
 | |
|     def test_override_get_auto_imports_one_error(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return [
 | |
|                     "foo",
 | |
|                 ]
 | |
| 
 | |
|         expected = (
 | |
|             "1 object could not be automatically imported:\n\n  foo\n\n"
 | |
|             "0 objects imported automatically.\n\n"
 | |
|         )
 | |
|         for verbosity, expected in [(0, ""), (1, expected), (2, expected)]:
 | |
|             with self.subTest(verbosity=verbosity):
 | |
|                 with captured_stdout() as stdout:
 | |
|                     TestCommand().get_namespace(verbosity=verbosity)
 | |
|                     self.assertEqual(stdout.getvalue(), expected)
 | |
| 
 | |
|     def test_override_get_auto_imports_many_errors(self):
 | |
|         class TestCommand(shell.Command):
 | |
|             def get_auto_imports(self):
 | |
|                 return [
 | |
|                     "does.not.exist",
 | |
|                     "doesntexisteither",
 | |
|                 ]
 | |
| 
 | |
|         expected = (
 | |
|             "2 objects could not be automatically imported:\n\n"
 | |
|             "  does.not.exist\n"
 | |
|             "  doesntexisteither\n\n"
 | |
|             "0 objects imported automatically.\n\n"
 | |
|         )
 | |
|         for verbosity, expected in [(0, ""), (1, expected), (2, expected)]:
 | |
|             with self.subTest(verbosity=verbosity):
 | |
|                 with captured_stdout() as stdout:
 | |
|                     TestCommand().get_namespace(verbosity=verbosity)
 | |
|                     self.assertEqual(stdout.getvalue(), expected)
 |