summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--django/urls/resolvers.py33
-rw-r--r--docs/ref/checks.txt2
-rw-r--r--tests/check_framework/test_urls.py41
-rw-r--r--tests/check_framework/urls/path_compatibility/matched_angle_brackets.py5
-rw-r--r--tests/check_framework/urls/path_compatibility/unmatched_angle_brackets.py8
6 files changed, 89 insertions, 1 deletions
diff --git a/AUTHORS b/AUTHORS
index 40df3589c0..6cafad023a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -62,6 +62,7 @@ answer newbie questions, and generally made Django that much better:
Aljaž Košir <aljazkosir5@gmail.com>
Aljosa Mohorovic <aljosa.mohorovic@gmail.com>
Alokik Vijay <alokik.roe@gmail.com>
+ Amir Karimi <amk9978@gmail.com>
Amit Chakradeo <https://amit.chakradeo.net/>
Amit Ramon <amit.ramon@gmail.com>
Amit Upadhyay <http://www.amitu.com/blog/>
diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py
index b021673772..432bf50076 100644
--- a/django/urls/resolvers.py
+++ b/django/urls/resolvers.py
@@ -318,7 +318,10 @@ class RoutePattern(CheckURLMixin):
return None
def check(self):
- warnings = self._check_pattern_startswith_slash()
+ warnings = [
+ *self._check_pattern_startswith_slash(),
+ *self._check_pattern_unmatched_angle_brackets(),
+ ]
route = self._route
if "(?P<" in route or route.startswith("^") or route.endswith("$"):
warnings.append(
@@ -331,6 +334,34 @@ class RoutePattern(CheckURLMixin):
)
return warnings
+ def _check_pattern_unmatched_angle_brackets(self):
+ warnings = []
+ segments = self._route.split("/")
+ for segment in segments:
+ open_bracket_counter = 0
+ unmatched_angle_bracket = None
+ for char in segment:
+ if char == "<":
+ open_bracket_counter += 1
+ elif char == ">":
+ open_bracket_counter -= 1
+ if open_bracket_counter < 0:
+ unmatched_angle_bracket = ">"
+ break
+ else:
+ if open_bracket_counter > 0:
+ unmatched_angle_bracket = "<"
+
+ if unmatched_angle_bracket is not None:
+ warnings.append(
+ Warning(
+ "Your URL pattern %s has an unmatched '%s' bracket."
+ % (self.describe(), unmatched_angle_bracket),
+ id="urls.W010",
+ )
+ )
+ return warnings
+
def _compile(self, route):
return re.compile(_route_to_regex(route, self._is_endpoint)[0])
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index de7b0dc382..49bc9cb0a2 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -609,6 +609,8 @@ The following checks are performed on your URL configuration:
imported.
* **urls.E009**: Your URL pattern ``<pattern>`` has an invalid view, pass
``<view>.as_view()`` instead of ``<view>``.
+* **urls.W010**: Your URL pattern ``<pattern>` has an unmatched
+ ``<angle bracket>``.
``contrib`` app checks
======================
diff --git a/tests/check_framework/test_urls.py b/tests/check_framework/test_urls.py
index a9038c6f13..4b6a4a6f3e 100644
--- a/tests/check_framework/test_urls.py
+++ b/tests/check_framework/test_urls.py
@@ -161,6 +161,47 @@ class CheckUrlConfigTests(SimpleTestCase):
],
)
+ @override_settings(
+ ROOT_URLCONF="check_framework.urls.path_compatibility.matched_angle_brackets"
+ )
+ def test_no_warnings_matched_angle_brackets(self):
+ self.assertEqual(check_url_config(None), [])
+
+ @override_settings(
+ ROOT_URLCONF="check_framework.urls.path_compatibility.unmatched_angle_brackets"
+ )
+ def test_warning_unmatched_angle_brackets(self):
+ self.assertEqual(
+ check_url_config(None),
+ [
+ Warning(
+ "Your URL pattern 'beginning-with/<angle_bracket' has an unmatched "
+ "'<' bracket.",
+ id="urls.W010",
+ ),
+ Warning(
+ "Your URL pattern 'ending-with/angle_bracket>' has an unmatched "
+ "'>' bracket.",
+ id="urls.W010",
+ ),
+ Warning(
+ "Your URL pattern 'closed_angle>/x/<opened_angle' has an unmatched "
+ "'>' bracket.",
+ id="urls.W010",
+ ),
+ Warning(
+ "Your URL pattern 'closed_angle>/x/<opened_angle' has an unmatched "
+ "'<' bracket.",
+ id="urls.W010",
+ ),
+ Warning(
+ "Your URL pattern '<mixed>angle_bracket>' has an unmatched '>' "
+ "bracket.",
+ id="urls.W010",
+ ),
+ ],
+ )
+
class UpdatedToPathTests(SimpleTestCase):
@override_settings(
diff --git a/tests/check_framework/urls/path_compatibility/matched_angle_brackets.py b/tests/check_framework/urls/path_compatibility/matched_angle_brackets.py
new file mode 100644
index 0000000000..de3076942d
--- /dev/null
+++ b/tests/check_framework/urls/path_compatibility/matched_angle_brackets.py
@@ -0,0 +1,5 @@
+from django.urls import path
+
+urlpatterns = [
+ path("<int:angle_bracket>", lambda x: x),
+]
diff --git a/tests/check_framework/urls/path_compatibility/unmatched_angle_brackets.py b/tests/check_framework/urls/path_compatibility/unmatched_angle_brackets.py
new file mode 100644
index 0000000000..e53685ec7a
--- /dev/null
+++ b/tests/check_framework/urls/path_compatibility/unmatched_angle_brackets.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+urlpatterns = [
+ path("beginning-with/<angle_bracket", lambda x: x),
+ path("ending-with/angle_bracket>", lambda x: x),
+ path("closed_angle>/x/<opened_angle", lambda x: x),
+ path("<mixed>angle_bracket>", lambda x: x),
+]