mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #35680 -- Added automatic imports of common utilies to shell management command.
This commit is contained in:
		
				
					committed by
					
						 Sarah Boyce
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						parent
						
							8499fba0e1
						
					
				
				
					commit
					a5cd84ad20
				
			| @@ -124,13 +124,19 @@ class Command(BaseCommand): | ||||
|     def get_auto_imports(self): | ||||
|         """Return a sequence of import paths for objects to be auto-imported. | ||||
|  | ||||
|         By default, import paths for models in INSTALLED_APPS are included, | ||||
|         with models from earlier apps taking precedence in case of a name | ||||
|         collision. | ||||
|         By default, import paths for models in INSTALLED_APPS and some common | ||||
|         utilities are included, with models from earlier apps taking precedence | ||||
|         in case of a name collision. | ||||
|  | ||||
|         For example, for an unchanged INSTALLED_APPS, this method returns: | ||||
|  | ||||
|         [ | ||||
|             "django.conf.settings", | ||||
|             "django.db.connection", | ||||
|             "django.db.reset_queries", | ||||
|             "django.db.models", | ||||
|             "django.db.models.functions", | ||||
|             "django.utils.timezone", | ||||
|             "django.contrib.sessions.models.Session", | ||||
|             "django.contrib.contenttypes.models.ContentType", | ||||
|             "django.contrib.auth.models.User", | ||||
| @@ -140,7 +146,15 @@ class Command(BaseCommand): | ||||
|         ] | ||||
|  | ||||
|         """ | ||||
|         app_models_imports = [ | ||||
|         default_imports = [ | ||||
|             "django.conf.settings", | ||||
|             "django.db.connection", | ||||
|             "django.db.reset_queries", | ||||
|             "django.db.models", | ||||
|             "django.db.models.functions", | ||||
|             "django.utils.timezone", | ||||
|         ] | ||||
|         app_models_imports = default_imports + [ | ||||
|             f"{model.__module__}.{model.__name__}" | ||||
|             for model in reversed(apps.get_models()) | ||||
|             if model.__module__ | ||||
|   | ||||
| @@ -39,20 +39,29 @@ For example: | ||||
|  | ||||
| The customization above adds :func:`~django.urls.resolve` and | ||||
| :func:`~django.urls.reverse` to the default namespace, which already includes | ||||
| all models from the apps listed in :setting:`INSTALLED_APPS`. These objects | ||||
| will be available in the ``shell`` without requiring a manual import. | ||||
| all models from the apps listed in :setting:`INSTALLED_APPS` plus what is  | ||||
| imported by default. These objects will be available in the ``shell`` without  | ||||
| requiring a manual import. | ||||
|  | ||||
| Running this customized ``shell`` command with ``verbosity=2`` would show: | ||||
|  | ||||
| .. console:: | ||||
|  | ||||
|     8 objects imported automatically: | ||||
|  | ||||
|     13 objects imported automatically: | ||||
| 	 | ||||
|       from django.db import connection, reset_queries, models | ||||
|       from django.conf import settings | ||||
|       from django.contrib.admin.models import LogEntry | ||||
|       from django.contrib.auth.models import Group, Permission, User | ||||
|       from django.contrib.contenttypes.models import ContentType | ||||
|       from django.contrib.sessions.models import Session | ||||
|       from django.urls import resolve, reverse | ||||
|       from django.utils import timezone | ||||
|  | ||||
| .. versionchanged:: 6.0 | ||||
|  | ||||
|     Automatic imports of common utilities, such as ``django.conf.settings``, | ||||
|     were added. | ||||
|  | ||||
| If an overridden ``shell`` command includes paths that cannot be imported, | ||||
| these errors are shown when ``verbosity`` is set to ``1`` or higher. Duplicate | ||||
|   | ||||
| @@ -1067,9 +1067,15 @@ Starts the Python interactive interpreter. | ||||
|  | ||||
| All models from installed apps are automatically imported into the shell | ||||
| environment. Models from apps listed earlier in :setting:`INSTALLED_APPS` take | ||||
| precedence. For a ``--verbosity`` of 2 or higher, the automatically imported | ||||
| objects will be listed. To disable automatic importing entirely, use the | ||||
| ``--no-imports`` flag. | ||||
| precedence. The following common utilities are also imported:: | ||||
|  | ||||
|     from django.db import connection, reset_queries, models | ||||
|     from django.conf import settings | ||||
|     from django.utils import timezone | ||||
|  | ||||
| For a ``--verbosity`` of 2 or higher, the automatically imported objects will | ||||
| be listed. To disable automatic importing entirely, use the ``--no-imports`` | ||||
| flag. | ||||
|  | ||||
| See the guide on :ref:`customizing this behavior | ||||
| <customizing-shell-auto-imports>` to add or remove automatic imports. | ||||
| @@ -1078,6 +1084,11 @@ See the guide on :ref:`customizing this behavior | ||||
|  | ||||
|     Automatic models import was added. | ||||
|  | ||||
| .. versionchanged:: 6.0 | ||||
|  | ||||
|     Automatic imports of common utilities, such as ``django.conf.settings``, | ||||
|     were added. | ||||
|  | ||||
| .. django-admin-option:: --interface {ipython,bpython,python}, -i {ipython,bpython,python} | ||||
|  | ||||
| Specifies the shell to use. By default, Django will use IPython_ or bpython_ if | ||||
|   | ||||
| @@ -224,6 +224,9 @@ Management Commands | ||||
| * The :djadmin:`startproject` and :djadmin:`startapp` commands now create the | ||||
|   custom target directory if it doesn't exist. | ||||
|  | ||||
| * Common utilities, such as ``django.conf.settings``, are now automatically | ||||
|   imported to the :djadmin:`shell` by default. | ||||
|  | ||||
| Migrations | ||||
| ~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -5,14 +5,17 @@ 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 | ||||
| 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 | ||||
|  | ||||
| @@ -79,6 +82,8 @@ class ShellCommandTestCase(SimpleTestCase): | ||||
|                 ) | ||||
|                 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( | ||||
| @@ -214,6 +219,12 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|         self.assertEqual( | ||||
|             namespace, | ||||
|             { | ||||
|                 "settings": settings, | ||||
|                 "connection": connection, | ||||
|                 "reset_queries": reset_queries, | ||||
|                 "models": models, | ||||
|                 "functions": functions, | ||||
|                 "timezone": timezone, | ||||
|                 "Marker": Marker, | ||||
|                 "Phone": Phone, | ||||
|                 "ContentType": ContentType, | ||||
| @@ -223,6 +234,22 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @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"] | ||||
|     ) | ||||
| @@ -243,7 +270,6 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|                 return super().get_auto_imports() + [ | ||||
|                     "django.urls.reverse", | ||||
|                     "django.urls.resolve", | ||||
|                     "django.db.connection", | ||||
|                 ] | ||||
|  | ||||
|         namespace = TestCommand().get_namespace() | ||||
| @@ -251,9 +277,14 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|         self.assertEqual( | ||||
|             namespace, | ||||
|             { | ||||
|                 "connection": connection, | ||||
|                 "resolve": resolve, | ||||
|                 "reverse": reverse, | ||||
|                 "settings": settings, | ||||
|                 "connection": connection, | ||||
|                 "reset_queries": reset_queries, | ||||
|                 "models": models, | ||||
|                 "functions": functions, | ||||
|                 "timezone": timezone, | ||||
|                 "Marker": Marker, | ||||
|                 "Phone": Phone, | ||||
|                 "ContentType": ContentType, | ||||
| @@ -295,7 +326,7 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|         self.assertEqual(len(namespace), len(cmd.get_auto_imports())) | ||||
|         self.assertEqual( | ||||
|             stdout.getvalue().strip(), | ||||
|             "6 objects imported automatically (use -v 2 for details).", | ||||
|             "12 objects imported automatically (use -v 2 for details).", | ||||
|         ) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"]) | ||||
| @@ -320,9 +351,13 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|  | ||||
|         self.assertEqual( | ||||
|             stdout.getvalue().strip(), | ||||
|             "7 objects imported automatically:\n\n" | ||||
|             "13 objects imported automatically:\n\n" | ||||
|             "  import shell\n" | ||||
|             "  import django\n" | ||||
|             "  from django.conf import settings\n" | ||||
|             "  from django.db import connection, reset_queries, models\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", | ||||
| @@ -350,13 +385,36 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|                     TestCommand().get_namespace(verbosity=verbosity) | ||||
|                     self.assertEqual(stdout.getvalue().strip(), expected) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=[]) | ||||
|     def test_message_with_stdout_no_installed_apps(self): | ||||
|     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, reset_queries, models\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: | ||||
| @@ -379,7 +437,11 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|     def test_message_with_stdout_listing_objects_with_isort(self): | ||||
|         sorted_imports = ( | ||||
|             "  from shell.models import Marker, Phone\n\n" | ||||
|             "  from django.contrib.contenttypes.models import ContentType" | ||||
|             "  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)) | ||||
|  | ||||
| @@ -399,7 +461,7 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase): | ||||
|  | ||||
|         self.assertEqual( | ||||
|             stdout.getvalue().strip(), | ||||
|             "6 objects imported automatically:\n\n" + sorted_imports, | ||||
|             "12 objects imported automatically:\n\n" + sorted_imports, | ||||
|         ) | ||||
|  | ||||
|     def test_override_get_auto_imports(self): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user