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
This commit is contained in:
Malcolm Tredinnick 2007-04-01 07:25:20 +00:00
parent a071609a70
commit d882656ea3
5 changed files with 83 additions and 17 deletions

View File

@ -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)

View File

@ -88,7 +88,7 @@ class MatchChecker(object):
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 @@ class RegexURLPattern(object):
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 @@ class RegexURLResolver(object):
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:

View File

@ -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*.

View File

@ -692,11 +692,12 @@ class Templates(unittest.TestCase):
'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.

View File

@ -7,4 +7,5 @@ urlpatterns = patterns('',
(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"),
)