From 0a17666045de6739ae1c2ac695041823d5f827f7 Mon Sep 17 00:00:00 2001 From: Ayush Joshi Date: Fri, 14 Jan 2022 10:03:55 +0530 Subject: [PATCH] Fixed #28135 -- Made simplify_regex() handle non-capturing groups. --- django/contrib/admindocs/utils.py | 18 +++++++++++++++++- django/contrib/admindocs/views.py | 4 +++- tests/admin_docs/test_views.py | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py index d918153146..e956913c78 100644 --- a/django/contrib/admindocs/utils.py +++ b/django/contrib/admindocs/utils.py @@ -137,9 +137,10 @@ if docutils_is_available: for name, urlbase in ROLES.items(): create_reference_role(name, urlbase) -# Match the beginning of a named or unnamed group. +# Match the beginning of a named, unnamed, or non-capturing groups. named_group_matcher = _lazy_re_compile(r'\(\?P(<\w+>)') unnamed_group_matcher = _lazy_re_compile(r'\(') +non_capturing_group_matcher = _lazy_re_compile(r'\(\?\:') def replace_metacharacters(pattern): @@ -210,3 +211,18 @@ def replace_unnamed_groups(pattern): final_pattern += pattern[:start] + '' prev_end = end return final_pattern + pattern[prev_end:] + + +def remove_non_capturing_groups(pattern): + r""" + Find non-capturing groups in the given `pattern` and remove them, e.g. + 1. (?P\w+)/b/(?:\w+)c(?:\w+) => (?P\\w+)/b/c + 2. ^(?:\w+(?:\w+))a => ^a + 3. ^a(?:\w+)/b(?:\w+) => ^a/b + """ + group_start_end_indices = _find_groups(pattern, non_capturing_group_matcher) + final_pattern, prev_end = '', None + for start, end, _ in group_start_end_indices: + final_pattern += pattern[prev_end:start] + prev_end = end + return final_pattern + pattern[prev_end:] diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index ffe4e13fa4..a1ad47626b 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -8,7 +8,8 @@ from django.contrib import admin from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admindocs import utils from django.contrib.admindocs.utils import ( - replace_metacharacters, replace_named_groups, replace_unnamed_groups, + remove_non_capturing_groups, replace_metacharacters, replace_named_groups, + replace_unnamed_groups, ) from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.db import models @@ -410,6 +411,7 @@ def simplify_regex(pattern): example, turn "^(?P\w+)/athletes/(?P\w+)/$" into "//athletes//". """ + pattern = remove_non_capturing_groups(pattern) pattern = replace_named_groups(pattern) pattern = replace_unnamed_groups(pattern) pattern = replace_metacharacters(pattern) diff --git a/tests/admin_docs/test_views.py b/tests/admin_docs/test_views.py index 5355382dfa..b786fb1930 100644 --- a/tests/admin_docs/test_views.py +++ b/tests/admin_docs/test_views.py @@ -397,6 +397,13 @@ class AdminDocViewFunctionsTests(SimpleTestCase): (r'^(?P(x|y))/b/(?P\w+)', '//b/'), (r'^(?P(x|y))/b/(?P\w+)ab', '//b/ab'), (r'^(?P(x|y)(\(|\)))/b/(?P\w+)ab', '//b/ab'), + # Non-capturing groups. + (r'^a(?:\w+)b', '/ab'), + (r'^a(?:(x|y))', '/a'), + (r'^(?:\w+(?:\w+))a', '/a'), + (r'^a(?:\w+)/b(?:\w+)', '/a/b'), + (r'(?P\w+)/b/(?:\w+)c(?:\w+)', '//b/c'), + (r'(?P\w+)/b/(\w+)/(?:\w+)c(?:\w+)', '//b//c'), # Single and repeated metacharacters. (r'^a', '/a'), (r'^^a', '/a'),