summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSalvo Polizzi <salvopolizzi03@gmail.com>2025-02-14 08:17:25 +0100
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-07-17 12:51:43 +0200
commita5cd84ad2002f9a43363ab9fd9d9f6e9dfa48c60 (patch)
treee2307cc155727fe1ecd7042a215689ef1df507b5
parent8499fba0e18826a77fe32cbc13a3d951d9ca8924 (diff)
Fixed #35680 -- Added automatic imports of common utilies to shell management command.
-rw-r--r--django/core/management/commands/shell.py22
-rw-r--r--docs/howto/custom-shell.txt17
-rw-r--r--docs/ref/django-admin.txt17
-rw-r--r--docs/releases/6.0.txt3
-rw-r--r--tests/shell/tests.py80
5 files changed, 119 insertions, 20 deletions
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index 132e7f89d2..25edc80f7c 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -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__
diff --git a/docs/howto/custom-shell.txt b/docs/howto/custom-shell.txt
index c1632849b5..cf55a02400 100644
--- a/docs/howto/custom-shell.txt
+++ b/docs/howto/custom-shell.txt
@@ -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
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index a1603c9e84..22a7c0a10c 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -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
diff --git a/docs/releases/6.0.txt b/docs/releases/6.0.txt
index 6a819ecc3f..a7306ba755 100644
--- a/docs/releases/6.0.txt
+++ b/docs/releases/6.0.txt
@@ -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
~~~~~~~~~~
diff --git a/tests/shell/tests.py b/tests/shell/tests.py
index ff4db089f7..8ffbc3fa4f 100644
--- a/tests/shell/tests.py
+++ b/tests/shell/tests.py
@@ -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,8 +385,14 @@ 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.",
@@ -360,6 +401,23 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
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:
shell.Command().get_namespace(verbosity=verbosity)
self.assertEqual(stdout.getvalue().strip(), expected)
@@ -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):