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 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' handler404 = 'django.views.defaults.page_not_found'
handler500 = 'django.views.defaults.server_error' handler500 = 'django.views.defaults.server_error'
include = lambda urlconf_module: [urlconf_module] include = lambda urlconf_module: [urlconf_module]
def patterns(prefix, *tuples): def patterns(prefix, *args):
pattern_list = [] pattern_list = []
for t in tuples: for t in args:
regex, view_or_include = t[:2] if isinstance(t, (list, tuple)):
default_kwargs = t[2:] pattern_list.append(url(prefix=prefix, *t))
if type(view_or_include) == list:
pattern_list.append(RegexURLResolver(regex, view_or_include[0], *default_kwargs))
else: 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 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? return str(value) # TODO: Unicode?
class RegexURLPattern(object): 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. # regex is a string representing a regular expression.
# callback is either a string like 'foo.views.news.stories.story_detail' # 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 # 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 = None
self._callback_str = callback self._callback_str = callback
self.default_args = default_args or {} self.default_args = default_args or {}
self.name = name
def resolve(self, path): def resolve(self, path):
match = self.regex.search(path) match = self.regex.search(path)
@ -205,14 +206,15 @@ class RegexURLResolver(object):
try: try:
lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name) lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
except (ImportError, AttributeError): except (ImportError, AttributeError):
raise NoReverseMatch if func_name != '':
raise NoReverseMatch
for pattern in self.urlconf_module.urlpatterns: for pattern in self.urlconf_module.urlpatterns:
if isinstance(pattern, RegexURLResolver): if isinstance(pattern, RegexURLResolver):
try: try:
return pattern.reverse_helper(lookup_view, *args, **kwargs) return pattern.reverse_helper(lookup_view, *args, **kwargs)
except NoReverseMatch: except NoReverseMatch:
continue continue
elif pattern.callback == lookup_view: elif pattern.callback == lookup_view or pattern.name == lookup_view:
try: try:
return pattern.reverse_helper(*args, **kwargs) return pattern.reverse_helper(*args, **kwargs)
except NoReverseMatch: 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:: 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 ...where ``optional dictionary`` and ``optional name`` are optional. (See
_`Passing extra options to view functions` below.) `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 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 -- 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. 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/'), '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/'), '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/'), 'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
'url04' : ('{% url named-client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
# Failures # Failures
'url04' : ('{% url %}', {}, template.TemplateSyntaxError), 'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError),
'url05' : ('{% url no_such_view %}', {}, ''), 'url-fail02' : ('{% url no_such_view %}', {}, ''),
'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), 'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
} }
# Register our custom template loader. # Register our custom template loader.

View File

@ -7,4 +7,5 @@ urlpatterns = patterns('',
(r'^$', views.index), (r'^$', views.index),
(r'^client/(\d+)/$', views.client), (r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action), (r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
url(r'^named-client/(\d+)/$', views.client, name="named-client"),
) )