Skip to content

Commit de1117e

Browse files
committed
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.
1 parent 647dca4 commit de1117e

File tree

2 files changed

+93
-15
lines changed

2 files changed

+93
-15
lines changed

django/core/management/commands/shell.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from importlib import import_module
77

88
from django.apps import apps
9+
from django.core.exceptions import AppRegistryNotReady
910
from django.core.management import BaseCommand, CommandError
1011
from django.utils.datastructures import OrderedSet
1112
from django.utils.module_loading import import_string as import_dotted_path
@@ -150,6 +151,22 @@ def get_namespace(self, **options):
150151
if options and options.get("no_imports"):
151152
return {}
152153

154+
verbosity = options["verbosity"] if options else 0
155+
156+
try:
157+
apps.check_models_ready()
158+
except AppRegistryNotReady:
159+
if verbosity > 0:
160+
settings_env_var = os.getenv("DJANGO_SETTINGS_MODULE")
161+
self.stdout.write(
162+
"Automatic imports are disabled since settings are not configured."
163+
f"\nDJANGO_SETTINGS_MODULE value is {settings_env_var!r}.\n"
164+
"HINT: Ensure that the settings module is configured and set.",
165+
self.style.ERROR,
166+
ending="\n\n",
167+
)
168+
return {}
169+
153170
path_imports = self.get_auto_imports()
154171
if path_imports is None:
155172
return {}
@@ -175,7 +192,6 @@ def get_namespace(self, **options):
175192
name: obj for items in auto_imports.values() for name, obj in items
176193
}
177194

178-
verbosity = options["verbosity"] if options else 0
179195
if verbosity < 1:
180196
return namespace
181197

@@ -228,7 +244,7 @@ def get_namespace(self, **options):
228244
def handle(self, **options):
229245
# Execute the command and exit.
230246
if options["command"]:
231-
exec(options["command"], {**globals(), **self.get_namespace()})
247+
exec(options["command"], {**globals(), **self.get_namespace(**options)})
232248
return
233249

234250
# Execute stdin if it has anything to read and exit.
@@ -238,7 +254,7 @@ def handle(self, **options):
238254
and not sys.stdin.isatty()
239255
and select.select([sys.stdin], [], [], 0)[0]
240256
):
241-
exec(sys.stdin.read(), {**globals(), **self.get_namespace()})
257+
exec(sys.stdin.read(), {**globals(), **self.get_namespace(**options)})
242258
return
243259

244260
available_shells = (

tests/shell/tests.py

+74-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
import subprocess
13
import sys
24
import unittest
35
from unittest import mock
@@ -23,25 +25,85 @@ class ShellCommandTestCase(SimpleTestCase):
2325

2426
def test_command_option(self):
2527
with self.assertLogs("test", "INFO") as cm:
26-
call_command(
27-
"shell",
28-
command=(
29-
"import django; from logging import getLogger; "
30-
'getLogger("test").info(django.__version__)'
31-
),
32-
)
28+
with captured_stdout():
29+
call_command(
30+
"shell",
31+
command=(
32+
"import django; from logging import getLogger; "
33+
'getLogger("test").info(django.__version__)'
34+
),
35+
)
3336
self.assertEqual(cm.records[0].getMessage(), __version__)
3437

3538
def test_command_option_globals(self):
3639
with captured_stdout() as stdout:
37-
call_command("shell", command=self.script_globals)
40+
call_command("shell", command=self.script_globals, verbosity=0)
3841
self.assertEqual(stdout.getvalue().strip(), "True")
3942

4043
def test_command_option_inline_function_call(self):
4144
with captured_stdout() as stdout:
42-
call_command("shell", command=self.script_with_inline_function)
45+
call_command("shell", command=self.script_with_inline_function, verbosity=0)
4346
self.assertEqual(stdout.getvalue().strip(), __version__)
4447

48+
@override_settings(INSTALLED_APPS=["shell"])
49+
def test_no_settings(self):
50+
test_environ = os.environ.copy()
51+
if "DJANGO_SETTINGS_MODULE" in test_environ:
52+
del test_environ["DJANGO_SETTINGS_MODULE"]
53+
error = (
54+
"Automatic imports are disabled since settings are not configured.\n"
55+
"DJANGO_SETTINGS_MODULE value is None.\n"
56+
"HINT: Ensure that the settings module is configured and set.\n\n"
57+
)
58+
for verbosity, assertError in [
59+
("0", self.assertNotIn),
60+
("1", self.assertIn),
61+
("2", self.assertIn),
62+
]:
63+
with self.subTest(verbosity=verbosity, get_auto_imports="models"):
64+
p = subprocess.run(
65+
[
66+
sys.executable,
67+
"-m",
68+
"django",
69+
"shell",
70+
"-c",
71+
"print(globals())",
72+
"-v",
73+
verbosity,
74+
],
75+
capture_output=True,
76+
env=test_environ,
77+
text=True,
78+
umask=-1,
79+
)
80+
assertError(error, p.stdout)
81+
self.assertNotIn("Marker", p.stdout)
82+
83+
with self.subTest(verbosity=verbosity, get_auto_imports="without-models"):
84+
with mock.patch(
85+
"django.core.management.commands.shell.Command.get_auto_imports",
86+
return_value=["django.urls.resolve"],
87+
):
88+
p = subprocess.run(
89+
[
90+
sys.executable,
91+
"-m",
92+
"django",
93+
"shell",
94+
"-c",
95+
"print(globals())",
96+
"-v",
97+
verbosity,
98+
],
99+
capture_output=True,
100+
env=test_environ,
101+
text=True,
102+
umask=-1,
103+
)
104+
assertError(error, p.stdout)
105+
self.assertNotIn("resolve", p.stdout)
106+
45107
@unittest.skipIf(
46108
sys.platform == "win32", "Windows select() doesn't support file descriptors."
47109
)
@@ -50,7 +112,7 @@ def test_stdin_read(self, select):
50112
with captured_stdin() as stdin, captured_stdout() as stdout:
51113
stdin.write("print(100)\n")
52114
stdin.seek(0)
53-
call_command("shell")
115+
call_command("shell", verbosity=0)
54116
self.assertEqual(stdout.getvalue().strip(), "100")
55117

56118
@unittest.skipIf(
@@ -62,7 +124,7 @@ def test_stdin_read_globals(self, select):
62124
with captured_stdin() as stdin, captured_stdout() as stdout:
63125
stdin.write(self.script_globals)
64126
stdin.seek(0)
65-
call_command("shell")
127+
call_command("shell", verbosity=0)
66128
self.assertEqual(stdout.getvalue().strip(), "True")
67129

68130
@unittest.skipIf(
@@ -74,7 +136,7 @@ def test_stdin_read_inline_function_call(self, select):
74136
with captured_stdin() as stdin, captured_stdout() as stdout:
75137
stdin.write(self.script_with_inline_function)
76138
stdin.seek(0)
77-
call_command("shell")
139+
call_command("shell", verbosity=0)
78140
self.assertEqual(stdout.getvalue().strip(), __version__)
79141

80142
def test_ipython(self):

0 commit comments

Comments
 (0)