mirror of
https://github.com/django/django.git
synced 2025-08-21 01:09:13 +00:00
Fixed #35680 -- Added automatic imports of common utilies to shell management command.
This commit is contained in:
parent
8499fba0e1
commit
a5cd84ad20
@ -124,13 +124,19 @@ class Command(BaseCommand):
|
|||||||
def get_auto_imports(self):
|
def get_auto_imports(self):
|
||||||
"""Return a sequence of import paths for objects to be auto-imported.
|
"""Return a sequence of import paths for objects to be auto-imported.
|
||||||
|
|
||||||
By default, import paths for models in INSTALLED_APPS are included,
|
By default, import paths for models in INSTALLED_APPS and some common
|
||||||
with models from earlier apps taking precedence in case of a name
|
utilities are included, with models from earlier apps taking precedence
|
||||||
collision.
|
in case of a name collision.
|
||||||
|
|
||||||
For example, for an unchanged INSTALLED_APPS, this method returns:
|
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.sessions.models.Session",
|
||||||
"django.contrib.contenttypes.models.ContentType",
|
"django.contrib.contenttypes.models.ContentType",
|
||||||
"django.contrib.auth.models.User",
|
"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__}"
|
f"{model.__module__}.{model.__name__}"
|
||||||
for model in reversed(apps.get_models())
|
for model in reversed(apps.get_models())
|
||||||
if model.__module__
|
if model.__module__
|
||||||
|
@ -39,20 +39,29 @@ For example:
|
|||||||
|
|
||||||
The customization above adds :func:`~django.urls.resolve` and
|
The customization above adds :func:`~django.urls.resolve` and
|
||||||
:func:`~django.urls.reverse` to the default namespace, which already includes
|
:func:`~django.urls.reverse` to the default namespace, which already includes
|
||||||
all models from the apps listed in :setting:`INSTALLED_APPS`. These objects
|
all models from the apps listed in :setting:`INSTALLED_APPS` plus what is
|
||||||
will be available in the ``shell`` without requiring a manual import.
|
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:
|
Running this customized ``shell`` command with ``verbosity=2`` would show:
|
||||||
|
|
||||||
.. console::
|
.. 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.admin.models import LogEntry
|
||||||
from django.contrib.auth.models import Group, Permission, User
|
from django.contrib.auth.models import Group, Permission, User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
from django.urls import resolve, reverse
|
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,
|
If an overridden ``shell`` command includes paths that cannot be imported,
|
||||||
these errors are shown when ``verbosity`` is set to ``1`` or higher. Duplicate
|
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
|
All models from installed apps are automatically imported into the shell
|
||||||
environment. Models from apps listed earlier in :setting:`INSTALLED_APPS` take
|
environment. Models from apps listed earlier in :setting:`INSTALLED_APPS` take
|
||||||
precedence. For a ``--verbosity`` of 2 or higher, the automatically imported
|
precedence. The following common utilities are also imported::
|
||||||
objects will be listed. To disable automatic importing entirely, use the
|
|
||||||
``--no-imports`` flag.
|
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
|
See the guide on :ref:`customizing this behavior
|
||||||
<customizing-shell-auto-imports>` to add or remove automatic imports.
|
<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.
|
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}
|
.. 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
|
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
|
* The :djadmin:`startproject` and :djadmin:`startapp` commands now create the
|
||||||
custom target directory if it doesn't exist.
|
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
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -5,14 +5,17 @@ import unittest
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django import __version__
|
from django import __version__
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Group, Permission, User
|
from django.contrib.auth.models import Group, Permission, User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.management import CommandError, call_command
|
from django.core.management import CommandError, call_command
|
||||||
from django.core.management.commands import shell
|
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 import SimpleTestCase
|
||||||
from django.test.utils import captured_stdin, captured_stdout, override_settings
|
from django.test.utils import captured_stdin, captured_stdout, override_settings
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from .models import Marker, Phone
|
from .models import Marker, Phone
|
||||||
|
|
||||||
@ -79,6 +82,8 @@ class ShellCommandTestCase(SimpleTestCase):
|
|||||||
)
|
)
|
||||||
assertError(error, p.stdout)
|
assertError(error, p.stdout)
|
||||||
self.assertNotIn("Marker", 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 self.subTest(verbosity=verbosity, get_auto_imports="without-models"):
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
@ -214,6 +219,12 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
namespace,
|
namespace,
|
||||||
{
|
{
|
||||||
|
"settings": settings,
|
||||||
|
"connection": connection,
|
||||||
|
"reset_queries": reset_queries,
|
||||||
|
"models": models,
|
||||||
|
"functions": functions,
|
||||||
|
"timezone": timezone,
|
||||||
"Marker": Marker,
|
"Marker": Marker,
|
||||||
"Phone": Phone,
|
"Phone": Phone,
|
||||||
"ContentType": ContentType,
|
"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(
|
@override_settings(
|
||||||
INSTALLED_APPS=["model_forms", "contenttypes_tests", "forms_tests"]
|
INSTALLED_APPS=["model_forms", "contenttypes_tests", "forms_tests"]
|
||||||
)
|
)
|
||||||
@ -243,7 +270,6 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
return super().get_auto_imports() + [
|
return super().get_auto_imports() + [
|
||||||
"django.urls.reverse",
|
"django.urls.reverse",
|
||||||
"django.urls.resolve",
|
"django.urls.resolve",
|
||||||
"django.db.connection",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
namespace = TestCommand().get_namespace()
|
namespace = TestCommand().get_namespace()
|
||||||
@ -251,9 +277,14 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
namespace,
|
namespace,
|
||||||
{
|
{
|
||||||
"connection": connection,
|
|
||||||
"resolve": resolve,
|
"resolve": resolve,
|
||||||
"reverse": reverse,
|
"reverse": reverse,
|
||||||
|
"settings": settings,
|
||||||
|
"connection": connection,
|
||||||
|
"reset_queries": reset_queries,
|
||||||
|
"models": models,
|
||||||
|
"functions": functions,
|
||||||
|
"timezone": timezone,
|
||||||
"Marker": Marker,
|
"Marker": Marker,
|
||||||
"Phone": Phone,
|
"Phone": Phone,
|
||||||
"ContentType": ContentType,
|
"ContentType": ContentType,
|
||||||
@ -295,7 +326,7 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
self.assertEqual(len(namespace), len(cmd.get_auto_imports()))
|
self.assertEqual(len(namespace), len(cmd.get_auto_imports()))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
stdout.getvalue().strip(),
|
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"])
|
@override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
|
||||||
@ -320,9 +351,13 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
stdout.getvalue().strip(),
|
stdout.getvalue().strip(),
|
||||||
"7 objects imported automatically:\n\n"
|
"13 objects imported automatically:\n\n"
|
||||||
" import shell\n"
|
" import shell\n"
|
||||||
" import django\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 django.contrib.contenttypes.models import ContentType\n"
|
||||||
" from shell.models import Phone, Marker\n"
|
" from shell.models import Phone, Marker\n"
|
||||||
" from django.urls import reverse, resolve",
|
" from django.urls import reverse, resolve",
|
||||||
@ -350,13 +385,36 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
TestCommand().get_namespace(verbosity=verbosity)
|
TestCommand().get_namespace(verbosity=verbosity)
|
||||||
self.assertEqual(stdout.getvalue().strip(), expected)
|
self.assertEqual(stdout.getvalue().strip(), expected)
|
||||||
|
|
||||||
@override_settings(INSTALLED_APPS=[])
|
def test_message_with_stdout_zero_object(self):
|
||||||
def test_message_with_stdout_no_installed_apps(self):
|
class TestCommand(shell.Command):
|
||||||
|
def get_auto_imports(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
TestCommand().get_namespace(verbosity=2)
|
||||||
|
|
||||||
cases = {
|
cases = {
|
||||||
0: "",
|
0: "",
|
||||||
1: "0 objects imported automatically.",
|
1: "0 objects imported automatically.",
|
||||||
2: "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():
|
for verbosity, expected in cases.items():
|
||||||
with self.subTest(verbosity=verbosity):
|
with self.subTest(verbosity=verbosity):
|
||||||
with captured_stdout() as stdout:
|
with captured_stdout() as stdout:
|
||||||
@ -379,7 +437,11 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
def test_message_with_stdout_listing_objects_with_isort(self):
|
def test_message_with_stdout_listing_objects_with_isort(self):
|
||||||
sorted_imports = (
|
sorted_imports = (
|
||||||
" from shell.models import Marker, Phone\n\n"
|
" 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))
|
mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
|
||||||
|
|
||||||
@ -399,7 +461,7 @@ class ShellCommandAutoImportsTestCase(SimpleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
stdout.getvalue().strip(),
|
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):
|
def test_override_get_auto_imports(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user