From d882656ea34119b2c371cc4adbe0aac180651cde Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 1 Apr 2007 07:25:20 +0000 Subject: [PATCH] Added the ability to name URL patterns. Helps with disambiguity reverse matches. git-svn-id: http://code.djangoproject.com/svn/django/trunk@4901 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/urls/defaults.py | 22 ++++++--- django/core/urlresolvers.py | 8 +-- docs/url_dispatch.txt | 62 ++++++++++++++++++++++-- tests/regressiontests/templates/tests.py | 7 +-- tests/regressiontests/templates/urls.py | 1 + 5 files changed, 83 insertions(+), 17 deletions(-) diff --git a/django/conf/urls/defaults.py b/django/conf/urls/defaults.py index 17fe603d96cb0..49bc176ef3431 100644 --- a/django/conf/urls/defaults.py +++ b/django/conf/urls/defaults.py @@ -1,19 +1,25 @@ from django.core.urlresolvers import RegexURLPattern, RegexURLResolver -__all__ = ['handler404', 'handler500', 'include', 'patterns'] +__all__ = ['handler404', 'handler500', 'include', 'patterns', 'url'] handler404 = 'django.views.defaults.page_not_found' handler500 = 'django.views.defaults.server_error' include = lambda urlconf_module: [urlconf_module] -def patterns(prefix, *tuples): +def patterns(prefix, *args): pattern_list = [] - for t in tuples: - regex, view_or_include = t[:2] - default_kwargs = t[2:] - if type(view_or_include) == list: - pattern_list.append(RegexURLResolver(regex, view_or_include[0], *default_kwargs)) + for t in args: + if isinstance(t, (list, tuple)): + pattern_list.append(url(prefix=prefix, *t)) else: - pattern_list.append(RegexURLPattern(regex, prefix and (prefix + '.' + view_or_include) or view_or_include, *default_kwargs)) + pattern_list.append(t) return pattern_list + +def url(regex, view, kwargs=None, name=None, prefix=''): + if type(view) == list: + # For include(...) processing. + return RegexURLResolver(regex, view[0], kwargs) + else: + return RegexURLPattern(regex, prefix and (prefix + '.' + view) or view, kwargs, name) + diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 3f1004c4fbf55..c4cbccabcfc04 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -88,7 +88,7 @@ def __call__(self, match_obj): return str(value) # TODO: Unicode? class RegexURLPattern(object): - def __init__(self, regex, callback, default_args=None): + def __init__(self, regex, callback, default_args=None, name=None): # regex is a string representing a regular expression. # callback is either a string like 'foo.views.news.stories.story_detail' # which represents the path to a module and a view function name, or a @@ -100,6 +100,7 @@ def __init__(self, regex, callback, default_args=None): self._callback = None self._callback_str = callback self.default_args = default_args or {} + self.name = name def resolve(self, path): match = self.regex.search(path) @@ -205,14 +206,15 @@ def reverse(self, lookup_view, *args, **kwargs): try: lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name) except (ImportError, AttributeError): - raise NoReverseMatch + if func_name != '': + raise NoReverseMatch for pattern in self.urlconf_module.urlpatterns: if isinstance(pattern, RegexURLResolver): try: return pattern.reverse_helper(lookup_view, *args, **kwargs) except NoReverseMatch: continue - elif pattern.callback == lookup_view: + elif pattern.callback == lookup_view or pattern.name == lookup_view: try: return pattern.reverse_helper(*args, **kwargs) except NoReverseMatch: diff --git a/docs/url_dispatch.txt b/docs/url_dispatch.txt index 85c87de680eed..cc34e9f88953c 100644 --- a/docs/url_dispatch.txt +++ b/docs/url_dispatch.txt @@ -185,10 +185,25 @@ The first argument to ``patterns()`` is a string ``prefix``. See The remaining arguments should be tuples in this format:: - (regular expression, Python callback function [, optional dictionary]) + (regular expression, Python callback function [, optional dictionary [, optional name]]) -...where ``optional dictionary`` is optional. (See -_`Passing extra options to view functions` below.) +...where ``optional dictionary`` and ``optional name`` are optional. (See +`Passing extra options to view functions`_ below.) + +url +--- +**New in development version** + +The ``url()`` function can be used instead of a tuple as an argument to +``patterns()``. This is convenient if you wish to specify a name without the +optional extra arguments dictionary. For example:: + + urlpatterns = patterns('', + url(r'/index/$', index_view, name="main-view"), + ... + ) + +See `Naming URL patterns`_ for why then ``name`` parameter is useful. handler404 ---------- @@ -479,3 +494,44 @@ The style you use is up to you. Note that if you use this technique -- passing objects rather than strings -- the view prefix (as explained in "The view prefix" above) will have no effect. + +Naming URL patterns +=================== + +**New in development version** + +It is fairly common to use the same view function in multiple URL patterns in +your URLConf. This leads to problems when you come to do reverse URL matching, +because the ``permalink()`` decorator and ``{% url %}`` template tag use the +name of the view function to find a match. + +To solve this problem, you can give a name to each of your URL patterns in +order to distinguish them from other patterns using the same views and +parameters. You can then use this name wherever you would otherwise use the +name of the view function. For example, if you URLConf contains:: + + urlpatterns = patterns('', + url(r'/archive/(\d{4})/$', archive, name="full-archive"), + url(r'/archive-summary/(\d{4})/$', archive, {'summary': True}, "arch-summary"), + ) + +...you could refer to either the summary archive view in a template as:: + + {% url arch-summary 1945 %} + +Even though both URL patterns refer to the ``archive`` view here, using the +``name`` parameter to ``url()`` allows you to tell them apart in templates. + +The string used for the URL name can contain any characters you like. You are +not restricted to valid Python names. + +.. note:: + + Make sure that when you name your URLs, you use names that are unlikely to + clash with any other application's choice of names. If you call your URL + pattern *comment* and another application does the same thing, there is no + guarantee which URL will be inserted into your template when you use this + name. Putting a prefix on your URL names, perhaps derived from + the application name, will decrease the chances of collision. Something + like *myapp-comment* is recommended over simply *comment*. + diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index e60e778d0b652..b544207be875a 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -692,11 +692,12 @@ def test_templates(self): 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), 'url02' : ('{% url regressiontests.templates.views.client_action client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), + 'url04' : ('{% url named-client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), # Failures - 'url04' : ('{% url %}', {}, template.TemplateSyntaxError), - 'url05' : ('{% url no_such_view %}', {}, ''), - 'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), + 'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError), + 'url-fail02' : ('{% url no_such_view %}', {}, ''), + 'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), } # Register our custom template loader. diff --git a/tests/regressiontests/templates/urls.py b/tests/regressiontests/templates/urls.py index dc5b36b08b147..eaa9fd5d9f289 100644 --- a/tests/regressiontests/templates/urls.py +++ b/tests/regressiontests/templates/urls.py @@ -7,4 +7,5 @@ (r'^$', views.index), (r'^client/(\d+)/$', views.client), (r'^client/(\d+)/(?P[^/]+)/$', views.client_action), + url(r'^named-client/(\d+)/$', views.client, name="named-client"), )