Fixed #27612 -- Added a check for duplicate URL instance namespaces.

This commit is contained in:
Andrew Nester 2016-12-22 00:54:15 +03:00 committed by Tim Graham
parent 3188b49ee2
commit 24fa728a47
5 changed files with 88 additions and 2 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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, [])

View 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'))
]

View 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'))
]