Fixed #27262 -- Moved URL checks to resolver and pattern classes.
Thanks Sjoerd Job Postmus for the report and review.
This commit is contained in:
parent
d5c0eb7d68
commit
e7fa89fb58
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
from django.conf import settings
|
||||
from django.utils import six
|
||||
|
||||
from . import Error, Tags, Warning, register
|
||||
from . import Error, Tags, register
|
||||
|
||||
|
||||
@register(Tags.urls)
|
||||
|
@ -19,23 +19,13 @@ def check_resolver(resolver):
|
|||
"""
|
||||
Recursively check the resolver.
|
||||
"""
|
||||
from django.urls import RegexURLPattern, RegexURLResolver
|
||||
warnings = []
|
||||
for pattern in resolver.url_patterns:
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
warnings.extend(check_include_trailing_dollar(pattern))
|
||||
# Check resolver recursively
|
||||
warnings.extend(check_resolver(pattern))
|
||||
elif isinstance(pattern, RegexURLPattern):
|
||||
warnings.extend(check_pattern_name(pattern))
|
||||
else:
|
||||
# This is not a url() instance
|
||||
warnings.extend(get_warning_for_invalid_pattern(pattern))
|
||||
|
||||
if not warnings:
|
||||
warnings.extend(check_pattern_startswith_slash(pattern))
|
||||
|
||||
return warnings
|
||||
check_method = getattr(resolver, 'check', None)
|
||||
if check_method is not None:
|
||||
return check_method()
|
||||
elif not hasattr(resolver, 'resolve'):
|
||||
return get_warning_for_invalid_pattern(resolver)
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def get_warning_for_invalid_pattern(pattern):
|
||||
|
@ -61,68 +51,3 @@ def get_warning_for_invalid_pattern(pattern):
|
|||
hint=hint,
|
||||
id="urls.E004",
|
||||
)]
|
||||
|
||||
|
||||
def describe_pattern(pattern):
|
||||
"""
|
||||
Format the URL pattern for display in warning messages.
|
||||
"""
|
||||
description = "'{}'".format(pattern.regex.pattern)
|
||||
if getattr(pattern, 'name', False):
|
||||
description += " [name='{}']".format(pattern.name)
|
||||
return description
|
||||
|
||||
|
||||
def check_include_trailing_dollar(pattern):
|
||||
"""
|
||||
Check that include is not used with a regex ending with a dollar.
|
||||
"""
|
||||
regex_pattern = pattern.regex.pattern
|
||||
if regex_pattern.endswith('$') and not regex_pattern.endswith(r'\$'):
|
||||
warning = Warning(
|
||||
"Your URL pattern {} uses include with a regex ending with a '$'. "
|
||||
"Remove the dollar from the regex to avoid problems including "
|
||||
"URLs.".format(describe_pattern(pattern)),
|
||||
id="urls.W001",
|
||||
)
|
||||
return [warning]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def check_pattern_startswith_slash(pattern):
|
||||
"""
|
||||
Check that the pattern does not begin with a forward slash.
|
||||
"""
|
||||
regex_pattern = pattern.regex.pattern
|
||||
if not settings.APPEND_SLASH:
|
||||
# Skip check as it can be useful to start a URL pattern with a slash
|
||||
# when APPEND_SLASH=False.
|
||||
return []
|
||||
if regex_pattern.startswith('/') or regex_pattern.startswith('^/'):
|
||||
warning = Warning(
|
||||
"Your URL pattern {} has a regex beginning with a '/'. Remove this "
|
||||
"slash as it is unnecessary. If this pattern is targeted in an "
|
||||
"include(), ensure the include() pattern has a trailing '/'.".format(
|
||||
describe_pattern(pattern)
|
||||
),
|
||||
id="urls.W002",
|
||||
)
|
||||
return [warning]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def check_pattern_name(pattern):
|
||||
"""
|
||||
Check that the pattern name does not contain a colon.
|
||||
"""
|
||||
if pattern.name is not None and ":" in pattern.name:
|
||||
warning = Warning(
|
||||
"Your URL pattern {} has a name including a ':'. Remove the colon, to "
|
||||
"avoid ambiguous namespace references.".format(describe_pattern(pattern)),
|
||||
id="urls.W003",
|
||||
)
|
||||
return [warning]
|
||||
else:
|
||||
return []
|
||||
|
|
|
@ -13,6 +13,8 @@ import threading
|
|||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.checks import Warning
|
||||
from django.core.checks.urls import check_resolver
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils import lru_cache, six
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
|
@ -107,6 +109,37 @@ class LocaleRegexProvider(object):
|
|||
self._regex_dict[language_code] = compiled_regex
|
||||
return self._regex_dict[language_code]
|
||||
|
||||
def describe(self):
|
||||
"""
|
||||
Format the URL pattern for display in warning messages.
|
||||
"""
|
||||
description = "'{}'".format(self.regex.pattern)
|
||||
if getattr(self, 'name', False):
|
||||
description += " [name='{}']".format(self.name)
|
||||
return description
|
||||
|
||||
def _check_pattern_startswith_slash(self):
|
||||
"""
|
||||
Check that the pattern does not begin with a forward slash.
|
||||
"""
|
||||
regex_pattern = self.regex.pattern
|
||||
if not settings.APPEND_SLASH:
|
||||
# Skip check as it can be useful to start a URL pattern with a slash
|
||||
# when APPEND_SLASH=False.
|
||||
return []
|
||||
if (regex_pattern.startswith('/') or regex_pattern.startswith('^/')) and not regex_pattern.endswith('/'):
|
||||
warning = Warning(
|
||||
"Your URL pattern {} has a regex beginning with a '/'. Remove this "
|
||||
"slash as it is unnecessary. If this pattern is targeted in an "
|
||||
"include(), ensure the include() pattern has a trailing '/'.".format(
|
||||
self.describe()
|
||||
),
|
||||
id="urls.W002",
|
||||
)
|
||||
return [warning]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
class RegexURLPattern(LocaleRegexProvider):
|
||||
def __init__(self, regex, callback, default_args=None, name=None):
|
||||
|
@ -118,6 +151,26 @@ class RegexURLPattern(LocaleRegexProvider):
|
|||
def __repr__(self):
|
||||
return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
|
||||
|
||||
def check(self):
|
||||
warnings = self._check_pattern_name()
|
||||
if not warnings:
|
||||
warnings = self._check_pattern_startswith_slash()
|
||||
return warnings
|
||||
|
||||
def _check_pattern_name(self):
|
||||
"""
|
||||
Check that the pattern name does not contain a colon.
|
||||
"""
|
||||
if self.name is not None and ":" in self.name:
|
||||
warning = Warning(
|
||||
"Your URL pattern {} has a name including a ':'. Remove the colon, to "
|
||||
"avoid ambiguous namespace references.".format(self.describe()),
|
||||
id="urls.W003",
|
||||
)
|
||||
return [warning]
|
||||
else:
|
||||
return []
|
||||
|
||||
def resolve(self, path):
|
||||
match = self.regex.search(path)
|
||||
if match:
|
||||
|
@ -181,6 +234,30 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
self.namespace, self.regex.pattern,
|
||||
)
|
||||
|
||||
def check(self):
|
||||
warnings = self._check_include_trailing_dollar()
|
||||
for pattern in self.url_patterns:
|
||||
warnings.extend(check_resolver(pattern))
|
||||
if not warnings:
|
||||
warnings = self._check_pattern_startswith_slash()
|
||||
return warnings
|
||||
|
||||
def _check_include_trailing_dollar(self):
|
||||
"""
|
||||
Check that include is not used with a regex ending with a dollar.
|
||||
"""
|
||||
regex_pattern = self.regex.pattern
|
||||
if regex_pattern.endswith('$') and not regex_pattern.endswith(r'\$'):
|
||||
warning = Warning(
|
||||
"Your URL pattern {} uses include with a regex ending with a '$'. "
|
||||
"Remove the dollar from the regex to avoid problems including "
|
||||
"URLs.".format(self.describe()),
|
||||
id="urls.W001",
|
||||
)
|
||||
return [warning]
|
||||
else:
|
||||
return []
|
||||
|
||||
def _populate(self):
|
||||
# Short-circuit if called recursively in this thread to prevent
|
||||
# infinite recursion. Concurrent threads may call this at the same
|
||||
|
|
Loading…
Reference in New Issue