summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-03-03 17:19:24 +0100
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-03-07 15:36:23 +0100
commit4c7b737b309ebcc1d2264572e1fe835798acd18a (patch)
tree22d932ac964ef6ca1fd0fcd28ccfce3cb74d6fb1
parent2bfec6c84b6401dc6f18ccd1d9628e8a1e1ba2c6 (diff)
[5.2.x] Fixed #36224 -- Fixed shell imports when settings not configured.
Thank you Raffaella for the report. Thank you Tim Schilling and Natalia Bidart for the reviews. Backport of de1117ea8eabe0ee0aa048e5a4e249eab7c4245e from main.
-rw-r--r--django/core/management/commands/shell.py22
-rw-r--r--tests/shell/tests.py86
2 files changed, 93 insertions, 15 deletions
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index eb3cf04e81..89d12ce6b8 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -6,6 +6,7 @@ from collections import defaultdict
from importlib import import_module
from django.apps import apps
+from django.core.exceptions import AppRegistryNotReady
from django.core.management import BaseCommand, CommandError
from django.utils.datastructures import OrderedSet
from django.utils.module_loading import import_string as import_dotted_path
@@ -150,6 +151,22 @@ class Command(BaseCommand):
if options and options.get("no_imports"):
return {}
+ verbosity = options["verbosity"] if options else 0
+
+ try:
+ apps.check_models_ready()
+ except AppRegistryNotReady:
+ if verbosity > 0:
+ settings_env_var = os.getenv("DJANGO_SETTINGS_MODULE")
+ self.stdout.write(
+ "Automatic imports are disabled since settings are not configured."
+ f"\nDJANGO_SETTINGS_MODULE value is {settings_env_var!r}.\n"
+ "HINT: Ensure that the settings module is configured and set.",
+ self.style.ERROR,
+ ending="\n\n",
+ )
+ return {}
+
path_imports = self.get_auto_imports()
if path_imports is None:
return {}
@@ -175,7 +192,6 @@ class Command(BaseCommand):
name: obj for items in auto_imports.values() for name, obj in items
}
- verbosity = options["verbosity"] if options else 0
if verbosity < 1:
return namespace
@@ -228,7 +244,7 @@ class Command(BaseCommand):
def handle(self, **options):
# Execute the command and exit.
if options["command"]:
- exec(options["command"], {**globals(), **self.get_namespace()})
+ exec(options["command"], {**globals(), **self.get_namespace(**options)})
return
# Execute stdin if it has anything to read and exit.
@@ -238,7 +254,7 @@ class Command(BaseCommand):
and not sys.stdin.isatty()
and select.select([sys.stdin], [], [], 0)[0]
):
- exec(sys.stdin.read(), {**globals(), **self.get_namespace()})
+ exec(sys.stdin.read(), {**globals(), **self.get_namespace(**options)})
return
available_shells = (
diff --git a/tests/shell/tests.py b/tests/shell/tests.py
index d8c708075d..49c85ecbe3 100644
--- a/tests/shell/tests.py
+++ b/tests/shell/tests.py
@@ -1,3 +1,5 @@
+import os
+import subprocess
import sys
import unittest
from unittest import mock
@@ -23,25 +25,85 @@ class ShellCommandTestCase(SimpleTestCase):
def test_command_option(self):
with self.assertLogs("test", "INFO") as cm:
- call_command(
- "shell",
- command=(
- "import django; from logging import getLogger; "
- 'getLogger("test").info(django.__version__)'
- ),
- )
+ with captured_stdout():
+ call_command(
+ "shell",
+ command=(
+ "import django; from logging import getLogger; "
+ 'getLogger("test").info(django.__version__)'
+ ),
+ )
self.assertEqual(cm.records[0].getMessage(), __version__)
def test_command_option_globals(self):
with captured_stdout() as stdout:
- call_command("shell", command=self.script_globals)
+ call_command("shell", command=self.script_globals, verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "True")
def test_command_option_inline_function_call(self):
with captured_stdout() as stdout:
- call_command("shell", command=self.script_with_inline_function)
+ call_command("shell", command=self.script_with_inline_function, verbosity=0)
self.assertEqual(stdout.getvalue().strip(), __version__)
+ @override_settings(INSTALLED_APPS=["shell"])
+ def test_no_settings(self):
+ test_environ = os.environ.copy()
+ if "DJANGO_SETTINGS_MODULE" in test_environ:
+ del test_environ["DJANGO_SETTINGS_MODULE"]
+ error = (
+ "Automatic imports are disabled since settings are not configured.\n"
+ "DJANGO_SETTINGS_MODULE value is None.\n"
+ "HINT: Ensure that the settings module is configured and set.\n\n"
+ )
+ for verbosity, assertError in [
+ ("0", self.assertNotIn),
+ ("1", self.assertIn),
+ ("2", self.assertIn),
+ ]:
+ with self.subTest(verbosity=verbosity, get_auto_imports="models"):
+ p = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ "django",
+ "shell",
+ "-c",
+ "print(globals())",
+ "-v",
+ verbosity,
+ ],
+ capture_output=True,
+ env=test_environ,
+ text=True,
+ umask=-1,
+ )
+ assertError(error, p.stdout)
+ self.assertNotIn("Marker", p.stdout)
+
+ with self.subTest(verbosity=verbosity, get_auto_imports="without-models"):
+ with mock.patch(
+ "django.core.management.commands.shell.Command.get_auto_imports",
+ return_value=["django.urls.resolve"],
+ ):
+ p = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ "django",
+ "shell",
+ "-c",
+ "print(globals())",
+ "-v",
+ verbosity,
+ ],
+ capture_output=True,
+ env=test_environ,
+ text=True,
+ umask=-1,
+ )
+ assertError(error, p.stdout)
+ self.assertNotIn("resolve", p.stdout)
+
@unittest.skipIf(
sys.platform == "win32", "Windows select() doesn't support file descriptors."
)
@@ -50,7 +112,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write("print(100)\n")
stdin.seek(0)
- call_command("shell")
+ call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "100")
@unittest.skipIf(
@@ -62,7 +124,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write(self.script_globals)
stdin.seek(0)
- call_command("shell")
+ call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), "True")
@unittest.skipIf(
@@ -74,7 +136,7 @@ class ShellCommandTestCase(SimpleTestCase):
with captured_stdin() as stdin, captured_stdout() as stdout:
stdin.write(self.script_with_inline_function)
stdin.seek(0)
- call_command("shell")
+ call_command("shell", verbosity=0)
self.assertEqual(stdout.getvalue().strip(), __version__)
def test_ipython(self):