mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #27262 -- Moved URL checks to resolver and pattern classes.
Thanks Sjoerd Job Postmus for the report and review.
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user