Skip to content

Commit

Permalink
Added the ability to name URL patterns. Helps with disambiguity rever…
Browse files Browse the repository at this point in the history
…se matches.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4901 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
malcolmt committed Apr 1, 2007
1 parent a071609 commit d882656
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 17 deletions.
22 changes: 14 additions & 8 deletions 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)

8 changes: 5 additions & 3 deletions django/core/urlresolvers.py
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
62 changes: 59 additions & 3 deletions docs/url_dispatch.txt
Expand Up @@ -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
----------
Expand Down Expand Up @@ -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*.

7 changes: 4 additions & 3 deletions tests/regressiontests/templates/tests.py
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions tests/regressiontests/templates/urls.py
Expand Up @@ -7,4 +7,5 @@
(r'^$', views.index),
(r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
url(r'^named-client/(\d+)/$', views.client, name="named-client"),
)

0 comments on commit d882656

Please sign in to comment.