From 9383fae0d55a553be3bda620db87fa9ee8a81478 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Mon, 18 May 2026 10:49:34 -0700 Subject: Fixed #28800 -- Added a listurls management command. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks JaeHyuck Sa, Jacob Walls, and Tim McCurrach for reviews. Co-authored-by: Ülgen Sarıkavak --- tests/admin_scripts/tests.py | 289 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) (limited to 'tests/admin_scripts/tests.py') diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 914f54720c..819ba931d6 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -4,6 +4,7 @@ advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODULE and default settings.py files. """ +import json import os import re import shutil @@ -30,6 +31,7 @@ from django.core.management import ( execute_from_command_line, ) from django.core.management.base import LabelCommand, SystemCheckError +from django.core.management.commands.listurls import Command as ListurlsCommand from django.core.management.commands.loaddata import Command as LoaddataCommand from django.core.management.commands.runserver import Command as RunserverCommand from django.core.management.commands.testserver import Command as TestserverCommand @@ -3313,6 +3315,293 @@ class Dumpdata(AdminScriptTestCase): self.assertNoOutput(out) +@override_settings(ROOT_URLCONF="admin_scripts.app_with_urls.root_urls") +class Listurls(AdminScriptTestCase): + def test_default(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls"] + out, err = self.run_manage(args) + + self.assertNoOutput(err) + + # Check route, view and (if defined) name for each URL. + self.assertOutput(out, "/namespaced/named") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_namespaced_named", + ) + self.assertOutput(out, "ns:named") + + self.assertOutput(out, "/namespaced/unnamed") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_namespaced_unnamed", + ) + + self.assertOutput(out, "/nons/named") + self.assertOutput(out, "admin_scripts.app_with_urls.views.view_func_nons_named") + self.assertOutput(out, "app_with_urls:named") + + self.assertOutput(out, "/nons/unnamed") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_nons_unnamed", + ) + + def test_urls_with_metachars(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls", "--prefix", "/.well-known"] + out, err = self.run_manage(args) + + self.assertOutput(out, "/.well-known/openid-configuration/") + self.assertNoOutput(err) + + def test_cbv_formatting(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls", "--prefix", "/cbv"] + out, err = self.run_manage(args) + + self.assertOutput(out, "/cbv/named") + self.assertOutput(out, "admin_scripts.app_with_urls.views.CBV") + self.assertOutput(out, "app_with_urls_cbv:named") + self.assertNoOutput(err) + + def test_tabular(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls", "-f", "tabular"] + out, err = self.run_manage(args) + self.assertNoOutput(err) + + # Check route, view and (if defined) name for each URL. + self.assertOutput(out, "/namespaced/named") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_namespaced_named", + ) + self.assertOutput(out, "ns:named") + + self.assertOutput(out, "/namespaced/unnamed") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_namespaced_unnamed", + ) + + self.assertOutput(out, "/nons/named") + self.assertOutput(out, "admin_scripts.app_with_urls.views.view_func_nons_named") + self.assertOutput(out, "app_with_urls:named") + + self.assertOutput(out, "/nons/unnamed") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_nons_unnamed", + ) + + def test_stacked(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls", "-f", "stacked"] + out, err = self.run_manage(args) + self.assertNoOutput(err) + + self.assertOutput(out, "Route:") + self.assertOutput(out, "View:") + self.assertOutput(out, "Name:") + self.assertOutput(out, "-" * 20) + + self.assertOutput(out, "/namespaced/named") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_namespaced_named", + ) + self.assertOutput(out, "ns:named") + + self.assertOutput(out, "/namespaced/unnamed") + self.assertOutput( + out, "admin_scripts.app_with_urls.views.view_func_namespaced_unnamed" + ) + + self.assertOutput(out, "/nons/named") + self.assertOutput(out, "app_with_urls:named") + self.assertOutput(out, "admin_scripts.app_with_urls.views.view_func_nons_named") + + self.assertOutput(out, "/nons/unnamed") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_nons_unnamed", + ) + + def test_json(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls", "-f", "json"] + out, err = self.run_manage(args) + self.assertNoOutput(err) + + json.loads(out) + + self.assertOutput(out, '"route": "/namespaced/named"') + self.assertOutput( + out, + '"view": "admin_scripts.app_with_urls.views.view_func_namespaced_named"', + ) + self.assertOutput(out, '"name": "ns:named"') + + self.assertOutput(out, '"route": "/namespaced/unnamed"') + self.assertOutput( + out, + '"view": "admin_scripts.app_with_urls.views.view_func_namespaced_unnamed"', + ) + + self.assertOutput(out, '"route": "/nons/named"') + self.assertOutput(out, "admin_scripts.app_with_urls.views.view_func_nons_named") + self.assertOutput(out, "app_with_urls:named") + + self.assertOutput(out, "/nons/unnamed") + self.assertOutput( + out, + "admin_scripts.app_with_urls.views.view_func_nons_unnamed", + ) + + def test_unsorted(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + # JSON format is the easiest to parse and test. + args = ["listurls", "-f", "json", "--unsorted"] + out, err = self.run_manage(args) + url_patterns = json.loads(out) + + self.assertNotEqual( + url_patterns, + sorted(url_patterns, key=lambda u: u["route"]), + ) + + def test_tabular_with_color_enabled(self): + out = StringIO() + err = StringIO() + + with mock.patch( + "django.core.management.color.supports_color", lambda *args: True + ): + command = ListurlsCommand(stdout=out, stderr=err) + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + call_command(command, format="tabular") + + self.assertIn(command.style.COMMAND_DATA("/namespaced/named"), out.getvalue()) + self.assertIn( + command.style.COMMAND_HIGHLIGHT("view_func_namespaced_named"), + out.getvalue(), + ) + self.assertIn(command.style.COMMAND_HIGHLIGHT("named"), out.getvalue()) + + def test_tabular_with_color_suppressed(self): + out = StringIO() + err = StringIO() + + with mock.patch( + "django.core.management.color.supports_color", lambda *args: True + ): + command = ListurlsCommand(stdout=out, stderr=err) + call_command(command, format="tabular", no_color=True) + + self.assertIn("/namespaced/named", out.getvalue()) + + # There should be no escape codes in the output. + self.assertNotIn("\x1b", out.getvalue()) + + def test_stacked_with_color_enabled(self): + out = StringIO() + err = StringIO() + + with mock.patch( + "django.core.management.color.supports_color", lambda *args: True + ): + command = ListurlsCommand(stdout=out, stderr=err) + call_command(command, format="stacked") + + self.assertIn(command.style.COMMAND_DATA("/namespaced/named"), out.getvalue()) + self.assertIn( + command.style.COMMAND_HIGHLIGHT("view_func_namespaced_named"), + out.getvalue(), + ) + self.assertIn(command.style.COMMAND_HIGHLIGHT("named"), out.getvalue()) + for header in ["Route", "View", "Name"]: + with self.subTest(header=header): + self.assertIn( + command.style.COMMAND_HEADER(f"{header}: "), out.getvalue() + ) + + def test_stacked_with_color_suppressed(self): + out = StringIO() + err = StringIO() + + with mock.patch( + "django.core.management.color.supports_color", lambda *args: True + ): + command = ListurlsCommand(stdout=out, stderr=err) + call_command(command, format="stacked", no_color=True) + + self.assertIn("/namespaced/named", out.getvalue()) + + # There should be no escape codes in the output. + self.assertNotIn("\x1b", out.getvalue()) + + @override_settings(ROOT_URLCONF="urls") + def test_no_urls(self): + self.write_settings("settings.py") + + args = ["listurls"] + out, err = self.run_manage(args) + + self.assertOutput(err, "There are no URL patterns that match given prefixes") + self.assertNoOutput(out) + + def test_prefixes(self): + self.write_settings( + "settings.py", + apps=["admin_scripts.app_with_urls"], + ) + + args = ["listurls", "-p", "/namespaced"] + out, err = self.run_manage(args) + self.assertNoOutput(err) + + self.assertOutput(out, "/namespaced/named") + self.assertOutput(out, "ns:named") + self.assertOutput(out, "/namespaced/unnamed") + + self.assertNotInOutput(out, "/nons/named") + self.assertNotInOutput(out, "app_with_urls:named") + self.assertNotInOutput(out, "/nons/unnamed") + + class MainModule(AdminScriptTestCase): """python -m django works like django-admin.""" -- cgit v1.3