mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	Fixed #27612 -- Added a check for duplicate URL instance namespaces.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							3188b49ee2
						
					
				
				
					commit
					24fa728a47
				
			| @@ -1,9 +1,11 @@ | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from collections import Counter | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils import six | ||||
|  | ||||
| from . import Error, Tags, register | ||||
| from . import Error, Tags, Warning, register | ||||
|  | ||||
|  | ||||
| @register(Tags.urls) | ||||
| @@ -28,6 +30,43 @@ def check_resolver(resolver): | ||||
|         return [] | ||||
|  | ||||
|  | ||||
| @register(Tags.urls) | ||||
| def check_url_namespaces_unique(app_configs, **kwargs): | ||||
|     """ | ||||
|     Warn if URL namespaces used in applications aren't unique. | ||||
|     """ | ||||
|     if not getattr(settings, 'ROOT_URLCONF', None): | ||||
|         return [] | ||||
|  | ||||
|     from django.urls import get_resolver | ||||
|     resolver = get_resolver() | ||||
|     all_namespaces = _load_all_namespaces(resolver) | ||||
|     counter = Counter(all_namespaces) | ||||
|     non_unique_namespaces = [n for n, count in counter.items() if count > 1] | ||||
|     errors = [] | ||||
|     for namespace in non_unique_namespaces: | ||||
|         errors.append(Warning( | ||||
|             "URL namespace '{}' isn't unique. You may not be able to reverse " | ||||
|             "all URLs in this namespace".format(namespace), | ||||
|             id="urls.W005", | ||||
|         )) | ||||
|     return errors | ||||
|  | ||||
|  | ||||
| def _load_all_namespaces(resolver): | ||||
|     """ | ||||
|     Recursively load all namespaces from URL patterns. | ||||
|     """ | ||||
|     url_patterns = getattr(resolver, 'url_patterns', []) | ||||
|     namespaces = [ | ||||
|         url.namespace for url in url_patterns | ||||
|         if getattr(url, 'namespace', None) is not None | ||||
|     ] | ||||
|     for pattern in url_patterns: | ||||
|         namespaces.extend(_load_all_namespaces(pattern)) | ||||
|     return namespaces | ||||
|  | ||||
|  | ||||
| def get_warning_for_invalid_pattern(pattern): | ||||
|     """ | ||||
|     Return a list containing a warning that the pattern is invalid. | ||||
|   | ||||
| @@ -673,3 +673,5 @@ The following checks are performed on your URL configuration: | ||||
|   references. | ||||
| * **urls.E004**: Your URL pattern ``<pattern>`` is invalid. Ensure that | ||||
|   ``urlpatterns`` is a list of :func:`~django.conf.urls.url()` instances. | ||||
| * **urls.W005**: URL namespace ``<namespace>`` isn't unique. You may not be | ||||
|   able to reverse all URLs in this namespace. | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from django.conf import settings | ||||
| from django.core.checks.messages import Warning | ||||
| from django.core.checks.urls import ( | ||||
|     check_url_config, get_warning_for_invalid_pattern, | ||||
|     check_url_config, check_url_namespaces_unique, get_warning_for_invalid_pattern, | ||||
| ) | ||||
| from django.test import SimpleTestCase | ||||
| from django.test.utils import override_settings | ||||
| @@ -94,3 +95,23 @@ class CheckUrlsTest(SimpleTestCase): | ||||
|     def test_get_warning_for_invalid_pattern_other(self): | ||||
|         warning = get_warning_for_invalid_pattern(object())[0] | ||||
|         self.assertIsNone(warning.hint) | ||||
|  | ||||
|     @override_settings(ROOT_URLCONF='check_framework.urls.non_unique_namespaces') | ||||
|     def test_check_non_unique_namespaces(self): | ||||
|         result = check_url_namespaces_unique(None) | ||||
|         self.assertEqual(len(result), 2) | ||||
|         non_unique_namespaces = ['app-ns1', 'app-1'] | ||||
|         warning_messages = [ | ||||
|             "URL namespace '{}' isn't unique. You may not be able to reverse " | ||||
|             "all URLs in this namespace".format(namespace) | ||||
|             for namespace in non_unique_namespaces | ||||
|         ] | ||||
|         for warning in result: | ||||
|             self.assertIsInstance(warning, Warning) | ||||
|             self.assertEqual('urls.W005', warning.id) | ||||
|             self.assertIn(warning.msg, warning_messages) | ||||
|  | ||||
|     @override_settings(ROOT_URLCONF='check_framework.urls.unique_namespaces') | ||||
|     def test_check_unique_namespaces(self): | ||||
|         result = check_url_namespaces_unique(None) | ||||
|         self.assertEqual(result, []) | ||||
|   | ||||
							
								
								
									
										13
									
								
								tests/check_framework/urls/non_unique_namespaces.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/check_framework/urls/non_unique_namespaces.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| from django.conf.urls import include, url | ||||
|  | ||||
| common_url_patterns = ([ | ||||
|     url(r'^app-ns1/', include([])), | ||||
|     url(r'^app-url/', include([])), | ||||
| ], 'app-ns1') | ||||
|  | ||||
| urlpatterns = [ | ||||
|     url(r'^app-ns1-0/', include(common_url_patterns)), | ||||
|     url(r'^app-ns1-1/', include(common_url_patterns)), | ||||
|     url(r'^app-some-url/', include(([], 'app'), namespace='app-1')), | ||||
|     url(r'^app-some-url-2/', include(([], 'app'), namespace='app-1')) | ||||
| ] | ||||
							
								
								
									
										11
									
								
								tests/check_framework/urls/unique_namespaces.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/check_framework/urls/unique_namespaces.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| from django.conf.urls import include, url | ||||
|  | ||||
| common_url_patterns = ([ | ||||
|     url(r'^app-ns1/', include([])), | ||||
|     url(r'^app-url/', include([])), | ||||
| ], 'common') | ||||
|  | ||||
| urlpatterns = [ | ||||
|     url(r'^app-ns1-0/', include(common_url_patterns, namespace='app-include-1')), | ||||
|     url(r'^app-ns1-1/', include(common_url_patterns, namespace='app-include-2')) | ||||
| ] | ||||
		Reference in New Issue
	
	Block a user