diff options
| author | Chris Rose <offline@offby1.net> | 2026-05-18 10:49:34 -0700 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-06-01 15:24:49 -0400 |
| commit | 9383fae0d55a553be3bda620db87fa9ee8a81478 (patch) | |
| tree | 7c46277248d4ed0fc38eb2193d95c844fc146726 /tests/admin_scripts | |
| parent | 22d25eff1510eb157be3ef02984869b1309ec15d (diff) | |
Fixed #28800 -- Added a listurls management command.
Thanks JaeHyuck Sa, Jacob Walls, and Tim McCurrach for reviews.
Co-authored-by: Ülgen Sarıkavak <ulgensrkvk@gmail.com>
Diffstat (limited to 'tests/admin_scripts')
| -rw-r--r-- | tests/admin_scripts/app_with_urls/__init__.py | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_urls/root_urls.py | 25 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_urls/urls_cbv.py | 17 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_urls/urls_namespaced.py | 17 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_urls/urls_nons.py | 17 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_urls/views.py | 21 | ||||
| -rw-r--r-- | tests/admin_scripts/tests.py | 289 |
7 files changed, 386 insertions, 0 deletions
diff --git a/tests/admin_scripts/app_with_urls/__init__.py b/tests/admin_scripts/app_with_urls/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/admin_scripts/app_with_urls/__init__.py diff --git a/tests/admin_scripts/app_with_urls/root_urls.py b/tests/admin_scripts/app_with_urls/root_urls.py new file mode 100644 index 0000000000..df14128138 --- /dev/null +++ b/tests/admin_scripts/app_with_urls/root_urls.py @@ -0,0 +1,25 @@ +from django.urls import include, path, re_path + + +def dummy_view(request): ... + + +urlpatterns = [ + path( + route="nons/", + view=include("admin_scripts.app_with_urls.urls_nons"), + ), + path( + route="namespaced/", + view=include("admin_scripts.app_with_urls.urls_namespaced", namespace="ns"), + ), + path( + route="cbv/", + view=include("admin_scripts.app_with_urls.urls_cbv"), + ), + re_path( + r"^\.well-known/openid-configuration/?$", + dummy_view, + name="oidc-connect-discovery-info", + ), +] diff --git a/tests/admin_scripts/app_with_urls/urls_cbv.py b/tests/admin_scripts/app_with_urls/urls_cbv.py new file mode 100644 index 0000000000..ec4740f5b9 --- /dev/null +++ b/tests/admin_scripts/app_with_urls/urls_cbv.py @@ -0,0 +1,17 @@ +from django.urls import path + +from . import views + +app_name = "app_with_urls_cbv" + +urlpatterns = [ + path( + route="unnamed", + view=views.CBV.as_view(), + ), + path( + route="named", + view=views.CBV.as_view(), + name="named", + ), +] diff --git a/tests/admin_scripts/app_with_urls/urls_namespaced.py b/tests/admin_scripts/app_with_urls/urls_namespaced.py new file mode 100644 index 0000000000..a993271c79 --- /dev/null +++ b/tests/admin_scripts/app_with_urls/urls_namespaced.py @@ -0,0 +1,17 @@ +from django.urls import path + +from . import views + +app_name = "app_with_urls" + +urlpatterns = [ + path( + route="unnamed", + view=views.view_func_namespaced_unnamed, + ), + path( + route="named", + view=views.view_func_namespaced_named, + name="named", + ), +] diff --git a/tests/admin_scripts/app_with_urls/urls_nons.py b/tests/admin_scripts/app_with_urls/urls_nons.py new file mode 100644 index 0000000000..e957d09fa2 --- /dev/null +++ b/tests/admin_scripts/app_with_urls/urls_nons.py @@ -0,0 +1,17 @@ +from django.urls import path + +from . import views + +app_name = "app_with_urls" + +urlpatterns = [ + path( + route="unnamed", + view=views.view_func_nons_unnamed, + ), + path( + route="named", + view=views.view_func_nons_named, + name="named", + ), +] diff --git a/tests/admin_scripts/app_with_urls/views.py b/tests/admin_scripts/app_with_urls/views.py new file mode 100644 index 0000000000..64035868d4 --- /dev/null +++ b/tests/admin_scripts/app_with_urls/views.py @@ -0,0 +1,21 @@ +from django.views.generic import ListView + + +def view_func_namespaced_unnamed(request): + pass + + +def view_func_namespaced_named(request): + pass + + +def view_func_nons_unnamed(request): + pass + + +def view_func_nons_named(request): + pass + + +class CBV(ListView): + pass 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.""" |
