1
0
mirror of https://github.com/django/django.git synced 2025-06-05 11:39:13 +00:00

Fixed #32915 -- Refactored runserver command to restore traceback

The traceback for `runserver --nostatic` with broken/missing
    settings was hidden because the custom runserver command which
    added `--nostatic` to the command args was only instantiated if
    the settings had no errors.
This commit is contained in:
Paul Schilling 2024-06-08 18:40:25 +02:00
parent 3556f63c4c
commit fab52fc571
5 changed files with 57 additions and 62 deletions

View File

@ -1,36 +0,0 @@
from django.conf import settings
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.core.management.commands.runserver import Command as RunserverCommand
class Command(RunserverCommand):
help = (
"Starts a lightweight web server for development and also serves static files."
)
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument(
"--nostatic",
action="store_false",
dest="use_static_handler",
help="Tells Django to NOT automatically serve static files at STATIC_URL.",
)
parser.add_argument(
"--insecure",
action="store_true",
dest="insecure_serving",
help="Allows serving static files even if DEBUG is False.",
)
def get_handler(self, *args, **options):
"""
Return the static files serving handler wrapping the default handler,
if static files should be served. Otherwise return the default handler.
"""
handler = super().get_handler(*args, **options)
use_static_handler = options["use_static_handler"]
insecure_serving = options["insecure_serving"]
if use_static_handler and (settings.DEBUG or insecure_serving):
return StaticFilesHandler(handler)
return handler

View File

@ -6,6 +6,7 @@ import sys
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core.servers.basehttp import WSGIServer, get_internal_wsgi_application, run from django.core.servers.basehttp import WSGIServer, get_internal_wsgi_application, run
from django.db import connections from django.db import connections
@ -65,6 +66,18 @@ class Command(BaseCommand):
action="store_true", action="store_true",
help="Skip system checks.", help="Skip system checks.",
) )
parser.add_argument(
"--nostatic",
action="store_false",
dest="use_static_handler",
help="Tells Django to NOT automatically serve static files at STATIC_URL.",
)
parser.add_argument(
"--insecure",
action="store_true",
dest="insecure_serving",
help="Allows serving static files even if DEBUG is False.",
)
def execute(self, *args, **options): def execute(self, *args, **options):
if options["no_color"]: if options["no_color"]:
@ -75,8 +88,18 @@ class Command(BaseCommand):
super().execute(*args, **options) super().execute(*args, **options)
def get_handler(self, *args, **options): def get_handler(self, *args, **options):
"""Return the default WSGI handler for the runner.""" """
return get_internal_wsgi_application() Return the default WSGI handler for the runner, or `StaticFilesHandler` if
either of --nostatic/--insecure is passed
"""
handler = get_internal_wsgi_application()
use_static_handler = options["use_static_handler"]
insecure_serving = options["insecure_serving"]
if use_static_handler and (settings.DEBUG or insecure_serving):
handler = StaticFilesHandler(handler)
return handler
def handle(self, *args, **options): def handle(self, *args, **options):
if not settings.DEBUG and not settings.ALLOWED_HOSTS: if not settings.DEBUG and not settings.ALLOWED_HOSTS:

View File

@ -30,7 +30,13 @@ from django.core.management.commands.runserver import Command as RunserverComman
from django.core.management.commands.testserver import Command as TestserverCommand from django.core.management.commands.testserver import Command as TestserverCommand
from django.db import ConnectionHandler, connection from django.db import ConnectionHandler, connection
from django.db.migrations.recorder import MigrationRecorder from django.db.migrations.recorder import MigrationRecorder
from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings from django.test import (
LiveServerTestCase,
RequestFactory,
SimpleTestCase,
TestCase,
override_settings,
)
from django.test.utils import captured_stderr, captured_stdout from django.test.utils import captured_stderr, captured_stdout
from django.urls import path from django.urls import path
from django.utils.version import PY313 from django.utils.version import PY313
@ -1695,6 +1701,23 @@ class ManageRunserver(SimpleTestCase):
self.assertIn("Performing system checks...", self.output.getvalue()) self.assertIn("Performing system checks...", self.output.getvalue())
mocked_check.assert_called() mocked_check.assert_called()
@override_settings(MIDDLEWARE=["django.middleware.common.CommonMiddleware"])
def test_middleware_loaded_only_once(self):
with mock.patch("django.middleware.common.CommonMiddleware") as mocked:
self.cmd.get_handler(use_static_handler=True, insecure_serving=True)
self.assertEqual(mocked.call_count, 1)
def test_404_response(self):
handler = self.cmd.get_handler(use_static_handler=True, insecure_serving=True)
missing_static_file = os.path.join(settings.STATIC_URL, "unknown.css")
req = RequestFactory().get(missing_static_file)
with override_settings(DEBUG=False):
response = handler.get_response(req)
self.assertEqual(response.status_code, 404)
with override_settings(DEBUG=True):
response = handler.get_response(req)
self.assertEqual(response.status_code, 404)
class ManageRunserverMigrationWarning(TestCase): class ManageRunserverMigrationWarning(TestCase):
def setUp(self): def setUp(self):

View File

@ -338,6 +338,12 @@ class SettingsTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, "Incorrect timezone setting: test"): with self.assertRaisesMessage(ValueError, "Incorrect timezone setting: test"):
settings._setup() settings._setup()
def test_import_non_existing_module(self):
exc = ModuleNotFoundError("No module named 'fake_module'", name="fake_module")
with mock.patch("importlib.import_module", side_effect=exc):
with self.assertRaisesMessage(ImportError, "No module named 'fake_module'"):
Settings("fake_settings_module")
class TestComplexSettingOverride(SimpleTestCase): class TestComplexSettingOverride(SimpleTestCase):
def setUp(self): def setUp(self):

View File

@ -11,11 +11,11 @@ from admin_scripts.tests import AdminScriptTestCase
from django.conf import STATICFILES_STORAGE_ALIAS, settings from django.conf import STATICFILES_STORAGE_ALIAS, settings
from django.contrib.staticfiles import storage from django.contrib.staticfiles import storage
from django.contrib.staticfiles.management.commands import collectstatic, runserver from django.contrib.staticfiles.management.commands import collectstatic
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.management import CommandError, call_command from django.core.management import CommandError, call_command
from django.core.management.base import SystemCheckError from django.core.management.base import SystemCheckError
from django.test import RequestFactory, override_settings from django.test import override_settings
from django.test.utils import extend_sys_path from django.test.utils import extend_sys_path
from django.utils._os import symlinks_supported from django.utils._os import symlinks_supported
from django.utils.functional import empty from django.utils.functional import empty
@ -33,27 +33,6 @@ class TestNoFilesCreated:
self.assertEqual(os.listdir(settings.STATIC_ROOT), []) self.assertEqual(os.listdir(settings.STATIC_ROOT), [])
class TestRunserver(StaticFilesTestCase):
@override_settings(MIDDLEWARE=["django.middleware.common.CommonMiddleware"])
def test_middleware_loaded_only_once(self):
command = runserver.Command()
with mock.patch("django.middleware.common.CommonMiddleware") as mocked:
command.get_handler(use_static_handler=True, insecure_serving=True)
self.assertEqual(mocked.call_count, 1)
def test_404_response(self):
command = runserver.Command()
handler = command.get_handler(use_static_handler=True, insecure_serving=True)
missing_static_file = os.path.join(settings.STATIC_URL, "unknown.css")
req = RequestFactory().get(missing_static_file)
with override_settings(DEBUG=False):
response = handler.get_response(req)
self.assertEqual(response.status_code, 404)
with override_settings(DEBUG=True):
response = handler.get_response(req)
self.assertEqual(response.status_code, 404)
class TestFindStatic(TestDefaults, CollectionTestCase): class TestFindStatic(TestDefaults, CollectionTestCase):
""" """
Test ``findstatic`` management command. Test ``findstatic`` management command.