1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Made changes requested following the forum discussion:

- Sorted the imports using isort.
- Removed modules from imports.
- Changed informative output.
- Added no-imports flag.
This commit is contained in:
Salvo Polizzi 2024-11-28 09:10:39 +01:00 committed by Salvo Polizzi
parent 1f7e972485
commit aedd8ae6c1
3 changed files with 93 additions and 70 deletions

View File

@ -27,6 +27,11 @@ class Command(BaseCommand):
"variable and ~/.pythonrc.py script."
),
)
parser.add_argument(
"--no-imports",
action="store_true",
help="Disable automatic imports of models.",
)
parser.add_argument(
"-i",
"--interface",
@ -50,19 +55,25 @@ class Command(BaseCommand):
start_ipython(
argv=[],
user_ns=self.get_and_report_namespace(options["verbosity"]),
user_ns=self.get_and_report_namespace(
options["verbosity"], options["no_imports"]
),
)
def bpython(self, options):
import bpython
bpython.embed(self.get_and_report_namespace(options["verbosity"]))
bpython.embed(
self.get_and_report_namespace(options["verbosity"], options["no_imports"])
)
def python(self, options):
import code
# Set up a dictionary to serve as the environment for the shell.
imported_objects = self.get_and_report_namespace(options["verbosity"])
imported_objects = self.get_and_report_namespace(
options["verbosity"], options["no_imports"]
)
# We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system
# conventions and get $PYTHONSTARTUP first then .pythonrc.py.
@ -115,17 +126,17 @@ class Command(BaseCommand):
# Start the interactive interpreter.
code.interact(local=imported_objects)
def get_and_report_namespace(self, verbosity):
namespace = self.get_namespace()
def get_and_report_namespace(self, verbosity, no_imports=False):
namespace = {} if no_imports else self.get_namespace()
if verbosity >= 1:
self.stdout.write(
f"{len(namespace)} objects imported automatically",
f"Automatic imports: {len(namespace)} objects imported"
" (use -v 2 for details)",
self.style.SUCCESS,
)
if verbosity >= 2:
imports_by_module = {}
imports_by_alias = {}
for obj_name, obj in namespace.items():
if hasattr(obj, "__module__") and (
(hasattr(obj, "__qualname__") and obj.__qualname__.find(".") == -1)
@ -141,34 +152,30 @@ class Command(BaseCommand):
module = ".".join(tokens)
collected_imports = imports_by_module.get(module, [])
imports_by_module[module] = collected_imports + [obj_name]
else:
module = ".".join(tokens)
imports_by_alias[module] = obj_name
import_string = ""
for module, imported_objects in imports_by_module.items():
self.stdout.write(
f"from {module} import {', '.join(imported_objects)}",
self.style.SUCCESS,
import_string += (
f"\tfrom {module} import {', '.join(imported_objects)}\n"
)
for module, alias in imports_by_alias.items():
self.stdout.write(f"import {module} as {alias}", self.style.SUCCESS)
try:
import isort
import_string = isort.code(import_string)
except ImportError:
pass
self.stdout.write(import_string, self.style.SUCCESS)
return namespace
def get_namespace(self):
apps_models = apps.get_models()
apps_models_modules = [
(app.models_module, app.label)
for app in apps.get_app_configs()
if app.models_module is not None
]
namespace = {}
for model in reversed(apps_models):
if model.__module__:
namespace[model.__name__] = model
for app_models_module, app_label in apps_models_modules:
if f"{app_label}_models" not in namespace:
namespace[f"{app_label}_models"] = app_models_module
return namespace
def handle(self, **options):

View File

@ -1070,11 +1070,9 @@ All models from all installed apps are automatically imported. See the guide on
remove imports.
Models from apps listed earlier in :setting:`INSTALLED_APPS` take precedence.
You can access overridden models from later, lower-precedence apps through its
models module, which is made available as ``<app_label>_models``, for example
``biblio_models.Book`` to access a model called ``Book`` in an app called
``biblio``. For a ``--verbosity`` of 2 and above, all imported functions and
classes are shown.
For a ``--verbosity`` of 2 and above, all imported functions and classes
are shown. If you don't want to import any model use the flag
``--no-imports``.
.. versionchanged:: 5.2

View File

@ -3,9 +3,7 @@ import unittest
from unittest import mock
from django import __version__
from django.contrib.auth import models as auth_model_module
from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes import models as contenttypes_model_module
from django.contrib.contenttypes.models import ContentType
from django.core.management import CommandError, call_command
from django.core.management.commands import shell
@ -89,7 +87,7 @@ class ShellCommandTestCase(SimpleTestCase):
mock_ipython = mock.Mock(start_ipython=mock.MagicMock())
with mock.patch.dict(sys.modules, {"IPython": mock_ipython}):
cmd.ipython({"verbosity": 0})
cmd.ipython({"verbosity": 0, "no_imports": False})
self.assertEqual(
mock_ipython.start_ipython.mock_calls,
@ -110,7 +108,7 @@ class ShellCommandTestCase(SimpleTestCase):
mock_bpython = mock.Mock(embed=mock.MagicMock())
with mock.patch.dict(sys.modules, {"bpython": mock_bpython}):
cmd.bpython({"verbosity": 0})
cmd.bpython({"verbosity": 0, "no_imports": False})
self.assertEqual(
mock_bpython.embed.mock_calls, [mock.call(cmd.get_and_report_namespace(0))]
@ -130,7 +128,7 @@ class ShellCommandTestCase(SimpleTestCase):
mock_code = mock.Mock(interact=mock.MagicMock())
with mock.patch.dict(sys.modules, {"code": mock_code}):
cmd.python({"verbosity": 0, "no_startup": True})
cmd.python({"verbosity": 0, "no_startup": True, "no_imports": False})
self.assertEqual(
mock_code.interact.mock_calls,
@ -160,9 +158,6 @@ class ShellCommandTestCase(SimpleTestCase):
"Group": Group,
"Permission": Permission,
"User": User,
"auth_models": auth_model_module,
"contenttypes_models": contenttypes_model_module,
"shell_models": shell_models,
},
)
@ -216,12 +211,29 @@ class ShellCommandTestCase(SimpleTestCase):
"Group": Group,
"Permission": Permission,
"User": User,
"auth_models": auth_model_module,
"contenttypes_models": contenttypes_model_module,
"shell_models": shell_models,
},
)
@override_settings(
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
)
def test_no_imports_namespace(self):
cmd = shell.Command()
namespace = cmd.get_and_report_namespace(verbosity=0, no_imports=True)
self.assertEqual(namespace, {})
@override_settings(
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
)
def test_message_with_no_imports(self):
with captured_stdout() as stdout:
cmd = shell.Command()
cmd.get_and_report_namespace(verbosity=1, no_imports=True)
self.assertEqual(
stdout.getvalue().strip(),
"Automatic imports: 0 objects imported (use -v 2 for details)",
)
@override_settings(
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
)
@ -229,7 +241,10 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdout() as stdout:
cmd = shell.Command()
cmd.get_and_report_namespace(verbosity=1)
self.assertEqual(stdout.getvalue().strip(), "9 objects imported automatically")
self.assertEqual(
stdout.getvalue().strip(),
"Automatic imports: 6 objects imported (use -v 2 for details)",
)
@override_settings(
INSTALLED_APPS=["shell", "django.contrib.auth", "django.contrib.contenttypes"]
@ -241,7 +256,8 @@ class ShellCommandTestCase(SimpleTestCase):
self.assertEqual(stdout.getvalue().strip(), "")
@override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
def test_message_with_stdout_listing_objects(self):
@mock.patch.dict(sys.modules, {"isort": None})
def test_message_with_stdout_listing_objects_with_isort_not_installed(self):
class Command(shell.Command):
def get_namespace(self):
class MyClass:
@ -261,38 +277,40 @@ class ShellCommandTestCase(SimpleTestCase):
self.assertEqual(
stdout.getvalue().strip(),
"7 objects imported automatically\n"
"from django.contrib.contenttypes.models import ContentType\n"
"from shell.models import Phone, Marker\n"
"import shell.models as shell_models\n"
"import django.contrib.contenttypes.models as contenttypes_models",
"Automatic imports: 5 objects imported (use -v 2 for details)\n"
"\tfrom django.contrib.contenttypes.models import ContentType\n"
"\tfrom shell.models import Phone, Marker",
)
@override_settings(
INSTALLED_APPS=[
"shell",
"django.contrib.auth",
"django.contrib.messages",
"django.contrib.staticfiles",
]
)
def test_shell_has_no_empty_models_objects(self):
cmd = shell.Command()
namespace = cmd.get_namespace()
@override_settings(INSTALLED_APPS=["shell", "django.contrib.contenttypes"])
def test_message_with_stdout_listing_objects_with_isort(self):
sorted_imports = (
"\tfrom shell.models import Marker, Phone\n\n"
"\tfrom django.contrib.contenttypes.models import ContentType"
)
mock_isort_code = mock.Mock(code=mock.MagicMock(return_value=sorted_imports))
self.assertNotIn("messages_models", namespace)
self.assertNotIn("staticfiles_models", namespace)
self.assertIsNotNone(namespace["auth_models"])
self.assertIsNotNone(namespace["shell_models"])
class Command(shell.Command):
def get_namespace(self):
class MyClass:
pass
@override_settings(INSTALLED_APPS=["shell"])
@isolate_apps("shell", kwarg_name="apps")
def test_app_has_model_with_name_equal_module_name(self, apps):
class shell_models(models.Model):
pass
constant = "constant"
cmd = shell.Command()
return {
**super().get_namespace(),
"MyClass": MyClass,
"constant": constant,
}
with mock.patch("django.apps.apps.get_models", return_value=apps.get_models()):
namespace = cmd.get_namespace()
self.assertEqual(namespace["shell_models"], shell_models)
with captured_stdout() as stdout:
cmd = Command()
with mock.patch.dict(sys.modules, {"isort": mock_isort_code}):
cmd.get_and_report_namespace(verbosity=2)
self.assertEqual(
stdout.getvalue().strip(),
"Automatic imports: 5 objects imported (use -v 2 for details)\n"
"\tfrom shell.models import Marker, Phone\n\n"
"\tfrom django.contrib.contenttypes.models import ContentType",
)