diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 34bca8ae80..424886e68b 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -206,20 +206,20 @@ class RegexURLResolver(object): else: parent = normalize(pattern.regex.pattern) for name in pattern.reverse_dict: - for matches, pat in pattern.reverse_dict.getlist(name): + for matches, pat, defaults in pattern.reverse_dict.getlist(name): new_matches = [] for piece, p_args in parent: new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches]) - lookups.appendlist(name, (new_matches, p_pattern + pat)) + lookups.appendlist(name, (new_matches, p_pattern + pat, dict(defaults, **pattern.default_kwargs))) for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items(): namespaces[namespace] = (p_pattern + prefix, sub_pattern) for app_name, namespace_list in pattern.app_dict.items(): apps.setdefault(app_name, []).extend(namespace_list) else: bits = normalize(p_pattern) - lookups.appendlist(pattern.callback, (bits, p_pattern)) + lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args)) if pattern.name is not None: - lookups.appendlist(pattern.name, (bits, p_pattern)) + lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args)) self._reverse_dict = lookups self._namespace_dict = namespaces self._app_dict = apps @@ -310,7 +310,7 @@ class RegexURLResolver(object): except (ImportError, AttributeError), e: raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e)) possibilities = self.reverse_dict.getlist(lookup_view) - for possibility, pattern in possibilities: + for possibility, pattern, defaults in possibilities: for result, params in possibility: if args: if len(args) != len(params): @@ -318,7 +318,14 @@ class RegexURLResolver(object): unicode_args = [force_unicode(val) for val in args] candidate = result % dict(zip(params, unicode_args)) else: - if set(kwargs.keys()) != set(params): + if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys()): + continue + matches = True + for k, v in defaults.items(): + if kwargs.get(k, v) != v: + matches = False + break + if not matches: continue unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) candidate = result % unicode_kwargs diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 4f391d9653..31a928caa2 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -127,8 +127,13 @@ test_data = ( ('kwargs_view', '/arg_view/10/', [], {'arg1':10}), ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}), ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}), - ('non_path_include', '/includes/non_path_include/', [], {}) + ('non_path_include', '/includes/non_path_include/', [], {}), + # Tests for #13154 + ('defaults', '/defaults_view1/3/', [], {'arg1': 3, 'arg2': 1}), + ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}), + ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}), + ('defaults', NoReverseMatch, [], {'arg2': 1}), ) class NoURLPatternsTests(TestCase): diff --git a/tests/regressiontests/urlpatterns_reverse/urls.py b/tests/regressiontests/urlpatterns_reverse/urls.py index c603c0235a..ba59cf8f9a 100644 --- a/tests/regressiontests/urlpatterns_reverse/urls.py +++ b/tests/regressiontests/urlpatterns_reverse/urls.py @@ -19,7 +19,7 @@ urlpatterns = patterns('', url(r'^people/(?:name/)', empty_view, name="people2"), url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"), url(r'^optional/(?P.*)/(?:.+/)?', empty_view, name="optional"), - url(r'^hardcoded/$', 'hardcoded/', empty_view, name="hardcoded"), + url(r'^hardcoded/$', empty_view, name="hardcoded"), url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"), url(r'^people/(?P\w\w)/(?P\w+)/$', empty_view, name="people3"), url(r'^people/(?P\w\w)/(?P\d)/$', empty_view, name="people4"), @@ -55,7 +55,11 @@ urlpatterns = patterns('', url(r'arg_view/(?P\d+)/$', 'kwargs_view'), url(r'absolute_arg_view/(?P\d+)/$', absolute_kwargs_view), url(r'absolute_arg_view/$', absolute_kwargs_view), - + + # Tests for #13154. Mixed syntax to test both ways of defining URLs. + url(r'defaults_view1/(?P\d+)/', 'defaults_view', {'arg2': 1}, name='defaults'), + (r'defaults_view2/(?P\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'), + url('^includes/', include(other_patterns)), ) diff --git a/tests/regressiontests/urlpatterns_reverse/views.py b/tests/regressiontests/urlpatterns_reverse/views.py index b04bf33ff1..538c6231a2 100644 --- a/tests/regressiontests/urlpatterns_reverse/views.py +++ b/tests/regressiontests/urlpatterns_reverse/views.py @@ -13,6 +13,9 @@ def kwargs_view(request, arg1=1, arg2=2): def absolute_kwargs_view(request, arg1=1, arg2=2): return HttpResponse('') +def defaults_view(request, arg1, arg2): + pass + class ViewClass(object): def __call__(self, request, *args, **kwargs): return HttpResponse('')