mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #13922 -- Updated resolve() to support namespaces. Thanks to Nowell Strite for the report and patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13479 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -30,6 +30,35 @@ _prefixes = {} | ||||
| # Overridden URLconfs for each thread are stored here. | ||||
| _urlconfs = {} | ||||
|  | ||||
| class ResolverMatch(object): | ||||
|     def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None): | ||||
|         self.func = func | ||||
|         self.args = args | ||||
|         self.kwargs = kwargs | ||||
|         self.app_name = app_name | ||||
|         if namespaces: | ||||
|             self.namespaces = [x for x in namespaces if x] | ||||
|         else: | ||||
|             self.namespaces = [] | ||||
|         if not url_name: | ||||
|             url_name = '.'.join([ func.__module__, func.__name__ ]) | ||||
|         self.url_name = url_name | ||||
|  | ||||
|     def namespace(self): | ||||
|         return ':'.join(self.namespaces) | ||||
|     namespace = property(namespace) | ||||
|  | ||||
|     def view_name(self): | ||||
|         return ':'.join([ x for x in [ self.namespace, self.url_name ]  if x ]) | ||||
|     view_name = property(view_name) | ||||
|  | ||||
|     def __getitem__(self, index): | ||||
|         return (self.func, self.args, self.kwargs)[index] | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % ( | ||||
|             self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace) | ||||
|  | ||||
| class Resolver404(Http404): | ||||
|     pass | ||||
|  | ||||
| @@ -120,7 +149,7 @@ class RegexURLPattern(object): | ||||
|             # In both cases, pass any extra_kwargs as **kwargs. | ||||
|             kwargs.update(self.default_args) | ||||
|  | ||||
|             return self.callback, args, kwargs | ||||
|             return ResolverMatch(self.callback, args, kwargs, self.name) | ||||
|  | ||||
|     def _get_callback(self): | ||||
|         if self._callback is not None: | ||||
| @@ -224,9 +253,9 @@ class RegexURLResolver(object): | ||||
|                     if sub_match: | ||||
|                         sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()]) | ||||
|                         sub_match_dict.update(self.default_kwargs) | ||||
|                         for k, v in sub_match[2].iteritems(): | ||||
|                         for k, v in sub_match.kwargs.iteritems(): | ||||
|                             sub_match_dict[smart_str(k)] = v | ||||
|                         return sub_match[0], sub_match[1], sub_match_dict | ||||
|                         return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces) | ||||
|                     tried.append(pattern.regex.pattern) | ||||
|             raise Resolver404({'tried': tried, 'path': new_path}) | ||||
|         raise Resolver404({'path' : path}) | ||||
|   | ||||
| @@ -827,17 +827,80 @@ namespaces into URLs on specific application instances, according to the | ||||
| resolve() | ||||
| --------- | ||||
|  | ||||
| The :func:`django.core.urlresolvers.resolve` function can be used for resolving | ||||
| URL paths to the corresponding view functions. It has the following signature: | ||||
| The :func:`django.core.urlresolvers.resolve` function can be used for | ||||
| resolving URL paths to the corresponding view functions. It has the | ||||
| following signature: | ||||
|  | ||||
| .. function:: resolve(path, urlconf=None) | ||||
|  | ||||
| ``path`` is the URL path you want to resolve. As with ``reverse()`` above, you | ||||
| don't need to worry about the ``urlconf`` parameter. The function returns the | ||||
| triple (view function, arguments, keyword arguments). | ||||
| ``path`` is the URL path you want to resolve. As with | ||||
| :func:`~django.core.urlresolvers.reverse`, you don't need to | ||||
| worry about the ``urlconf`` parameter. The function returns a | ||||
| :class:`django.core.urlresolvers.ResolverMatch` object that allows you | ||||
| to access various meta-data about the resolved URL. | ||||
|  | ||||
| For example, it can be used for testing if a view would raise a ``Http404`` | ||||
| error before redirecting to it:: | ||||
| .. class:: ResolverMatch() | ||||
|  | ||||
|     .. attribute:: ResolverMatch.func | ||||
|  | ||||
|         The view function that would be used to serve the URL | ||||
|  | ||||
|     .. attribute:: ResolverMatch.args | ||||
|  | ||||
|         The arguments that would be passed to the view function, as | ||||
|         parsed from the URL. | ||||
|  | ||||
|     .. attribute:: ResolverMatch.kwargs | ||||
|  | ||||
|         The keyword arguments that would be passed to the view | ||||
|         function, as parsed from the URL. | ||||
|  | ||||
|     .. attribute:: ResolverMatch.url_name | ||||
|  | ||||
|         The name of the URL pattern that matches the URL. | ||||
|  | ||||
|     .. attribute:: ResolverMatch.app_name | ||||
|  | ||||
|         The application namespace for the URL pattern that matches the | ||||
|         URL. | ||||
|  | ||||
|     .. attribute:: ResolverMatch.namespace | ||||
|  | ||||
|         The instance namespace for the URL pattern that matches the | ||||
|         URL. | ||||
|  | ||||
|     .. attribute:: ResolverMatch.namespaces | ||||
|  | ||||
|         The list of individual namespace components in the full | ||||
|         instance namespace for the URL pattern that matches the URL. | ||||
|         i.e., if the namespace is ``foo:bar``, then namespaces will be | ||||
|         ``[`foo`, `bar`]``. | ||||
|  | ||||
| A :class:`~django.core.urlresolvers.ResolverMatch` object can then be | ||||
| interrogated to provide information about the URL pattern that matches | ||||
| a URL:: | ||||
|  | ||||
|     # Resolve a URL | ||||
|     match = resolve('/some/path/') | ||||
|     # Print the URL pattern that matches the URL | ||||
|     print match.url_name | ||||
|  | ||||
| A :class:`~django.core.urlresolvers.ResolverMatch` object can also be | ||||
| assigned to a triple:: | ||||
|  | ||||
|     func, args, kwargs = resolve('/some/path/') | ||||
|  | ||||
| .. versionchanged:: 1.3 | ||||
|     Triple-assignment exists for backwards-compatibility. Prior to | ||||
|     Django 1.3, :func:`~django.core.urlresolvers.resolve` returned a | ||||
|     triple containing (view function, arguments, keyword arguments); | ||||
|     the :class:`~django.core.urlresolvers.ResolverMatch` object (as | ||||
|     well as the namespace and pattern information it provides) is not | ||||
|     available in earlier Django releases. | ||||
|  | ||||
| One possible use of :func:`~django.core.urlresolvers.resolve` would be | ||||
| to testing if a view would raise a ``Http404`` error before | ||||
| redirecting to it:: | ||||
|  | ||||
|     from urlparse import urlparse | ||||
|     from django.core.urlresolvers import resolve | ||||
| @@ -858,6 +921,7 @@ error before redirecting to it:: | ||||
|             return HttpResponseRedirect('/') | ||||
|         return response | ||||
|  | ||||
|  | ||||
| permalink() | ||||
| ----------- | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,11 @@ urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', | ||||
|     url(r'^normal/$', 'empty_view', name='inc-normal-view'), | ||||
|     url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-normal-view'), | ||||
|  | ||||
|     url(r'^mixed_args/(\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-mixed-args'), | ||||
|     url(r'^no_kwargs/(\d+)/(\d+)/$', 'empty_view', name='inc-no-kwargs'), | ||||
|  | ||||
|     (r'^test3/', include(testobj3.urls)), | ||||
|     (r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')), | ||||
|     (r'^ns-included4/', include('regressiontests.urlpatterns_reverse.namespace_urls', namespace='inc-ns4')), | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,9 @@ urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', | ||||
|     url(r'^normal/$', 'empty_view', name='normal-view'), | ||||
|     url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'), | ||||
|  | ||||
|     url(r'^mixed_args/(\d+)/(?P<arg2>\d+)/$', 'empty_view', name='mixed-args'), | ||||
|     url(r'^no_kwargs/(\d+)/(\d+)/$', 'empty_view', name='no-kwargs'), | ||||
|  | ||||
|     (r'^test1/', include(testobj1.urls)), | ||||
|     (r'^test2/', include(testobj2.urls)), | ||||
|     (r'^default/', include(default_testobj.urls)), | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import unittest | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404 | ||||
| from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404, ResolverMatch | ||||
| from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect | ||||
| from django.shortcuts import redirect | ||||
| from django.test import TestCase | ||||
| @@ -26,6 +26,35 @@ from django.test import TestCase | ||||
| import urlconf_outer | ||||
| import urlconf_inner | ||||
| import middleware | ||||
| import views | ||||
|  | ||||
| resolve_test_data = ( | ||||
|     # These entries are in the format: (path, url_name, app_name, namespace, view_func, args, kwargs) | ||||
|     # Simple case | ||||
|     ('/normal/42/37/', 'normal-view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/included/normal/42/37/', 'inc-normal-view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|  | ||||
|     # Unnamed args are dropped if you have *any* kwargs in a pattern | ||||
|     ('/mixed_args/42/37/', 'mixed-args', None, '', views.empty_view, tuple(), {'arg2': '37'}), | ||||
|     ('/included/mixed_args/42/37/', 'inc-mixed-args', None, '', views.empty_view, tuple(), {'arg2': '37'}), | ||||
|  | ||||
|     # If you have no kwargs, you get an args list. | ||||
|     ('/no_kwargs/42/37/', 'no-kwargs', None, '', views.empty_view, ('42','37'), {}), | ||||
|     ('/included/no_kwargs/42/37/', 'inc-no-kwargs', None, '', views.empty_view, ('42','37'), {}), | ||||
|  | ||||
|     # Namespaces | ||||
|     ('/test1/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns1', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/ns-included1/normal/42/37/', 'inc-normal-view', None, 'inc-ns1', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/default/inner/42/37/', 'urlobject-view', 'testapp', 'testapp', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/other2/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns2', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/other1/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns1', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|  | ||||
|     # Nested namespaces | ||||
|     ('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}), | ||||
|     ('/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'}), | ||||
| ) | ||||
|  | ||||
| test_data = ( | ||||
|     ('places', '/places/3/', [3], {}), | ||||
| @@ -229,6 +258,12 @@ class NamespaceTests(TestCase): | ||||
|         self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42])) | ||||
|         self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37})) | ||||
|  | ||||
|     def test_nested_namespace_pattern(self): | ||||
|         "Namespaces can be nested" | ||||
|         self.assertEquals('/ns-included1/ns-included4/ns-included1/test3/inner/', reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view')) | ||||
|         self.assertEquals('/ns-included1/ns-included4/ns-included1/test3/inner/37/42/', reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', args=[37,42])) | ||||
|         self.assertEquals('/ns-included1/ns-included4/ns-included1/test3/inner/42/37/', reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37})) | ||||
|  | ||||
|     def test_app_lookup_object(self): | ||||
|         "A default application namespace can be used for lookup" | ||||
|         self.assertEquals('/default/inner/', reverse('testapp:urlobject-view')) | ||||
| @@ -317,3 +352,29 @@ class NoRootUrlConfTests(TestCase): | ||||
|  | ||||
|     def test_no_handler_exception(self): | ||||
|         self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/') | ||||
|  | ||||
| class ResolverMatchTests(TestCase): | ||||
|     urls = 'regressiontests.urlpatterns_reverse.namespace_urls' | ||||
|  | ||||
|     def test_urlpattern_resolve(self): | ||||
|         for path, name, app_name, namespace, func, args, kwargs in resolve_test_data: | ||||
|             # Test legacy support for extracting "function, args, kwargs" | ||||
|             match_func, match_args, match_kwargs = resolve(path) | ||||
|             self.assertEqual(match_func, func) | ||||
|             self.assertEqual(match_args, args) | ||||
|             self.assertEqual(match_kwargs, kwargs) | ||||
|  | ||||
|             # Test ResolverMatch capabilities. | ||||
|             match = resolve(path) | ||||
|             self.assertEqual(match.__class__, ResolverMatch) | ||||
|             self.assertEqual(match.url_name, name) | ||||
|             self.assertEqual(match.args, args) | ||||
|             self.assertEqual(match.kwargs, kwargs) | ||||
|             self.assertEqual(match.app_name, app_name) | ||||
|             self.assertEqual(match.namespace, namespace) | ||||
|             self.assertEqual(match.func, func) | ||||
|  | ||||
|             # ... and for legacy purposes: | ||||
|             self.assertEquals(match[0], func) | ||||
|             self.assertEquals(match[1], args) | ||||
|             self.assertEquals(match[2], kwargs) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user