diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 424886e..2625309 100644
a
|
b
|
class RegexURLResolver(object):
|
303 | 303 | return self._resolve_special('500') |
304 | 304 | |
305 | 305 | def reverse(self, lookup_view, *args, **kwargs): |
| 306 | return self._reverse_with_prefix(lookup_view, '', *args, **kwargs) |
| 307 | |
| 308 | def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): |
306 | 309 | if args and kwargs: |
307 | 310 | raise ValueError("Don't mix *args and **kwargs in call to reverse()!") |
308 | 311 | try: |
… |
… |
class RegexURLResolver(object):
|
310 | 313 | except (ImportError, AttributeError), e: |
311 | 314 | raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e)) |
312 | 315 | possibilities = self.reverse_dict.getlist(lookup_view) |
| 316 | prefix_norm, prefix_args = normalize(_prefix)[0] |
313 | 317 | for possibility, pattern, defaults in possibilities: |
314 | 318 | for result, params in possibility: |
315 | 319 | if args: |
316 | | if len(args) != len(params): |
| 320 | if len(args) != len(params) + len(prefix_args): |
317 | 321 | continue |
318 | 322 | unicode_args = [force_unicode(val) for val in args] |
319 | | candidate = result % dict(zip(params, unicode_args)) |
| 323 | candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args)) |
320 | 324 | else: |
321 | | if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys()): |
| 325 | if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args): |
322 | 326 | continue |
323 | 327 | matches = True |
324 | 328 | for k, v in defaults.items(): |
… |
… |
class RegexURLResolver(object):
|
328 | 332 | if not matches: |
329 | 333 | continue |
330 | 334 | unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) |
331 | | candidate = result % unicode_kwargs |
332 | | if re.search(u'^%s' % pattern, candidate, re.UNICODE): |
| 335 | candidate = (prefix_norm + result) % unicode_kwargs |
| 336 | if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE): |
333 | 337 | return candidate |
334 | 338 | # lookup_view can be URL label, or dotted path, or callable, Any of |
335 | 339 | # these can be passed in at the top, but callables are not friendly in |
… |
… |
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
394 | 398 | else: |
395 | 399 | raise NoReverseMatch("%s is not a registered namespace" % key) |
396 | 400 | |
397 | | return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view, |
398 | | *args, **kwargs))) |
| 401 | return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)) |
399 | 402 | |
400 | 403 | reverse_lazy = lazy(reverse, str) |
401 | 404 | |
diff --git a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
index 3d34049..0544e75 100644
a
|
b
|
urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
37 | 37 | (r'^default/', include(default_testobj.urls)), |
38 | 38 | |
39 | 39 | (r'^other1/', include(otherobj1.urls)), |
40 | | (r'^other2/', include(otherobj2.urls)), |
| 40 | (r'^other[246]/', include(otherobj2.urls)), |
41 | 41 | |
42 | | (r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), |
| 42 | (r'^ns-included[135]/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), |
43 | 43 | (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), |
44 | 44 | |
45 | 45 | (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')), |
| 46 | (r'^inc(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns5')), |
46 | 47 | |
47 | 48 | ) |
diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
index 31a928c..f62934b 100644
a
|
b
|
resolve_test_data = (
|
49 | 49 | # Nested namespaces |
50 | 50 | ('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), |
51 | 51 | ('/ns-included1/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:inc-ns4:inc-ns2:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), |
| 52 | |
| 53 | # Namespaces capturing variables |
| 54 | ('/inc70/', 'inner-nothing', None, 'inc-ns5', views.empty_view, tuple(), {'outer': '70'}), |
| 55 | ('/inc78/extra/foobar/', 'inner-extra', None, 'inc-ns5', views.empty_view, tuple(), {'outer':'78', 'extra':'foobar'}), |
52 | 56 | ) |
53 | 57 | |
54 | 58 | test_data = ( |
… |
… |
test_data = (
|
134 | 138 | ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}), |
135 | 139 | ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}), |
136 | 140 | ('defaults', NoReverseMatch, [], {'arg2': 1}), |
| 141 | |
137 | 142 | ) |
138 | 143 | |
139 | 144 | class NoURLPatternsTests(TestCase): |
… |
… |
class NamespaceTests(TestCase):
|
362 | 367 | self.assertEqual('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1')) |
363 | 368 | self.assertEqual('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1')) |
364 | 369 | |
| 370 | def test_namespaces_with_variables(self): |
| 371 | "Namespace prefixes can capture variables: see #15900" |
| 372 | self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', kwargs={'outer': '70'})) |
| 373 | self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', kwargs={'outer':'78', 'extra':'foobar'})) |
| 374 | |
| 375 | self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', args=['70'])) |
| 376 | self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', args=['78','foobar'])) |
| 377 | |
| 378 | |
365 | 379 | class RequestURLconfTests(TestCase): |
366 | 380 | def setUp(self): |
367 | 381 | self.root_urlconf = settings.ROOT_URLCONF |