diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index b634b56..84cb111 100644
|
|
def get_resolver(urlconf):
|
118 | 118 | return RegexURLResolver(r'^/', urlconf) |
119 | 119 | get_resolver = memoize(get_resolver, _resolver_cache, 1) |
120 | 120 | |
121 | | def get_ns_resolver(ns_pattern, resolver): |
| 121 | def get_ns_resolver(resolvers): |
122 | 122 | # Build a namespaced resolver for the given parent urlconf pattern. |
123 | 123 | # This makes it possible to have captured parameters in the parent |
124 | 124 | # urlconf pattern. |
125 | | ns_resolver = RegexURLResolver(ns_pattern, |
126 | | resolver.url_patterns) |
127 | | return RegexURLResolver(r'^/', [ns_resolver]) |
| 125 | ns_resolvers = [] |
| 126 | for ns_pattern, resolver in resolvers: |
| 127 | ns_resolvers.append(RegexURLResolver(ns_pattern, |
| 128 | resolver.url_patterns)) |
| 129 | return RegexURLResolver(r'^/', ns_resolvers) |
128 | 130 | get_ns_resolver = memoize(get_ns_resolver, _ns_resolver_cache, 2) |
129 | 131 | |
130 | 132 | def get_mod_func(callback): |
… |
… |
class RegexURLResolver(LocaleRegexProvider):
|
245 | 247 | p_pattern = p_pattern[1:] |
246 | 248 | if isinstance(pattern, RegexURLResolver): |
247 | 249 | if pattern.namespace: |
248 | | namespaces[pattern.namespace] = (p_pattern, pattern) |
| 250 | namespaces.setdefault(pattern.namespace, []).append((p_pattern, pattern)) |
249 | 251 | if pattern.app_name: |
250 | 252 | apps.setdefault(pattern.app_name, []).append(pattern.namespace) |
251 | 253 | else: |
… |
… |
class RegexURLResolver(LocaleRegexProvider):
|
256 | 258 | for piece, p_args in parent: |
257 | 259 | new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches]) |
258 | 260 | lookups.appendlist(name, (new_matches, p_pattern + pat, dict(defaults, **pattern.default_kwargs))) |
259 | | for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items(): |
260 | | namespaces[namespace] = (p_pattern + prefix, sub_pattern) |
| 261 | for namespace, resolvers in pattern.namespace_dict.items(): |
| 262 | for prefix, sub_pattern in resolvers: |
| 263 | namespaces.setdefault(namespace, []).append((p_pattern + prefix, sub_pattern)) |
261 | 264 | for app_name, namespace_list in pattern.app_dict.items(): |
262 | 265 | apps.setdefault(app_name, []).extend(namespace_list) |
263 | 266 | else: |
… |
… |
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
437 | 440 | view = parts[0] |
438 | 441 | path = parts[1:] |
439 | 442 | |
| 443 | resolvers = [] |
440 | 444 | resolved_path = [] |
441 | | ns_pattern = '' |
| 445 | ns_patterns = [''] |
442 | 446 | while path: |
443 | 447 | ns = path.pop() |
444 | 448 | |
… |
… |
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
459 | 463 | pass |
460 | 464 | |
461 | 465 | try: |
462 | | extra, resolver = resolver.namespace_dict[ns] |
| 466 | _resolvers = resolver.namespace_dict[ns] |
463 | 467 | resolved_path.append(ns) |
464 | | ns_pattern = ns_pattern + extra |
| 468 | _ns_patterns = [] |
| 469 | for extra, resolver in _resolvers: |
| 470 | for ns_pattern in ns_patterns: |
| 471 | ns_pattern += extra |
| 472 | _ns_patterns.append(ns_pattern) |
| 473 | resolvers.append((ns_pattern, resolver)) |
465 | 474 | except KeyError, key: |
466 | 475 | if resolved_path: |
467 | 476 | raise NoReverseMatch( |
… |
… |
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
470 | 479 | else: |
471 | 480 | raise NoReverseMatch("%s is not a registered namespace" % |
472 | 481 | key) |
473 | | if ns_pattern: |
474 | | resolver = get_ns_resolver(ns_pattern, resolver) |
| 482 | |
| 483 | if resolvers and resolvers[0]: |
| 484 | resolver = get_ns_resolver(tuple(resolvers)) |
475 | 485 | |
476 | 486 | return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)) |
477 | 487 | |
diff --git a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
index fa892a4..2208d38 100644
|
|
from .views import view_class_instance
|
6 | 6 | |
7 | 7 | |
8 | 8 | class URLObject(object): |
9 | | def __init__(self, app_name, namespace): |
| 9 | def __init__(self, app_name, namespace, _patterns=None): |
| 10 | if _patterns is None: |
| 11 | _patterns = patterns('', |
| 12 | url(r'^inner/$', 'empty_view', name='urlobject-view'), |
| 13 | url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'), |
| 14 | url(r'^inner/\+\\\$\*/$', 'empty_view', name='urlobject-special-view'), |
| 15 | ) |
| 16 | self.patterns = _patterns |
10 | 17 | self.app_name = app_name |
11 | 18 | self.namespace = namespace |
12 | 19 | |
13 | 20 | def urls(self): |
14 | | return patterns('', |
15 | | url(r'^inner/$', 'empty_view', name='urlobject-view'), |
16 | | url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'), |
17 | | url(r'^inner/\+\\\$\*/$', 'empty_view', name='urlobject-special-view'), |
18 | | ), self.app_name, self.namespace |
| 21 | return self.patterns, self.app_name, self.namespace |
19 | 22 | urls = property(urls) |
20 | 23 | |
21 | 24 | testobj1 = URLObject('testapp', 'test-ns1') |
… |
… |
default_testobj = URLObject('testapp', 'testapp')
|
25 | 28 | otherobj1 = URLObject('nodefault', 'other-ns1') |
26 | 29 | otherobj2 = URLObject('nodefault', 'other-ns2') |
27 | 30 | |
| 31 | first_patterns = patterns('', |
| 32 | url(r'^$', 'empty_view', name='first'), |
| 33 | ) |
| 34 | first = URLObject('first', 'inc-some', first_patterns) |
| 35 | second_patterns = patterns('', |
| 36 | url(r'^$', 'empty_view', name='second'), |
| 37 | ) |
| 38 | second = URLObject('second', 'inc-some', second_patterns) |
| 39 | |
28 | 40 | urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', |
29 | 41 | url(r'^normal/$', 'empty_view', name='normal-view'), |
30 | 42 | url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'), |
… |
… |
urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
55 | 67 | (r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')), |
56 | 68 | |
57 | 69 | (r'^\+\\\$\*/', include('regressiontests.urlpatterns_reverse.namespace_urls', namespace='special')), |
| 70 | |
| 71 | url(r'^first/', include(first.urls)), |
| 72 | url(r'^second/', include(second.urls)), |
58 | 73 | ) |
diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
index a1c9244..aef7214 100644
|
|
class NamespaceTests(TestCase):
|
392 | 392 | self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', args=['70'])) |
393 | 393 | self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', args=['78','foobar'])) |
394 | 394 | |
| 395 | def test_namespaces_in_different_includes(self): |
| 396 | "Namespaces declared in different places: see #17551" |
| 397 | self.assertEqual('/first/', reverse('inc-some:first')) |
| 398 | self.assertEqual('/second/', reverse('inc-some:second')) |
| 399 | |
395 | 400 | class RequestURLconfTests(TestCase): |
396 | 401 | def setUp(self): |
397 | 402 | self.root_urlconf = settings.ROOT_URLCONF |