summaryrefslogtreecommitdiff
path: root/django/core/management/commands/shell.py
diff options
context:
space:
mode:
authorSalvo Polizzi <101474753+salvo-polizzi@users.noreply.github.com>2025-01-09 17:00:29 +0100
committerGitHub <noreply@github.com>2025-01-09 13:00:29 -0300
commitfc28550fe4e0582952993976edc62971bd5345a8 (patch)
treecaf9428df753d1266dec97cffb3d4571cceacab7 /django/core/management/commands/shell.py
parent8c118c0e00846091c261b97dbed9a5b89ceb79bf (diff)
Fixed #35515 -- Added automatic model imports to shell management command.
Thanks to Bhuvnesh Sharma and Adam Johnson for mentoring this Google Summer of Code 2024 project. Thanks to Sarah Boyce, David Smith, Jacob Walls and Natalia Bidart for reviews.
Diffstat (limited to 'django/core/management/commands/shell.py')
-rw-r--r--django/core/management/commands/shell.py84
1 files changed, 79 insertions, 5 deletions
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index f55b346406..1619561fea 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -2,7 +2,9 @@ import os
import select
import sys
import traceback
+from collections import defaultdict
+from django.apps import apps
from django.core.management import BaseCommand, CommandError
from django.utils.datastructures import OrderedSet
@@ -27,6 +29,11 @@ class Command(BaseCommand):
),
)
parser.add_argument(
+ "--no-imports",
+ action="store_true",
+ help="Disable automatic imports of models.",
+ )
+ parser.add_argument(
"-i",
"--interface",
choices=self.shells,
@@ -47,18 +54,27 @@ class Command(BaseCommand):
def ipython(self, options):
from IPython import start_ipython
- start_ipython(argv=[])
+ start_ipython(
+ argv=[],
+ user_ns=self.get_and_report_namespace(
+ options["verbosity"], options["no_imports"]
+ ),
+ )
def bpython(self, options):
import bpython
- bpython.embed()
+ 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 = {}
+ 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.
@@ -111,10 +127,68 @@ class Command(BaseCommand):
# Start the interactive interpreter.
code.interact(local=imported_objects)
+ def get_and_report_namespace(self, verbosity, no_imports=False):
+ if no_imports:
+ return {}
+
+ namespace = self.get_namespace()
+
+ if verbosity < 1:
+ return namespace
+
+ amount = len(namespace)
+ msg = f"{amount} objects imported automatically"
+
+ if verbosity < 2:
+ self.stdout.write(f"{msg} (use -v 2 for details).", self.style.SUCCESS)
+ return namespace
+
+ imports_by_module = defaultdict(list)
+ for obj_name, obj in namespace.items():
+ if hasattr(obj, "__module__") and (
+ (hasattr(obj, "__qualname__") and obj.__qualname__.find(".") == -1)
+ or not hasattr(obj, "__qualname__")
+ ):
+ imports_by_module[obj.__module__].append(obj_name)
+ if not hasattr(obj, "__module__") and hasattr(obj, "__name__"):
+ tokens = obj.__name__.split(".")
+ if obj_name in tokens:
+ module = ".".join(t for t in tokens if t != obj_name)
+ imports_by_module[module].append(obj_name)
+
+ import_string = "\n".join(
+ [
+ f" from {module} import {objects}"
+ for module, imported_objects in imports_by_module.items()
+ if (objects := ", ".join(imported_objects))
+ ]
+ )
+
+ try:
+ import isort
+ except ImportError:
+ pass
+ else:
+ import_string = isort.code(import_string)
+
+ self.stdout.write(
+ f"{msg}, including:\n\n{import_string}", self.style.SUCCESS, ending="\n\n"
+ )
+
+ return namespace
+
+ def get_namespace(self):
+ apps_models = apps.get_models()
+ namespace = {}
+ for model in reversed(apps_models):
+ if model.__module__:
+ namespace[model.__name__] = model
+ return namespace
+
def handle(self, **options):
# Execute the command and exit.
if options["command"]:
- exec(options["command"], globals())
+ exec(options["command"], {**globals(), **self.get_namespace()})
return
# Execute stdin if it has anything to read and exit.
@@ -124,7 +198,7 @@ class Command(BaseCommand):
and not sys.stdin.isatty()
and select.select([sys.stdin], [], [], 0)[0]
):
- exec(sys.stdin.read(), globals())
+ exec(sys.stdin.read(), {**globals(), **self.get_namespace()})
return
available_shells = (