diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 489faa243e..8182cd4d8c 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -351,22 +351,40 @@ class TemplateTagNode(Node): return self.mapping.get(self.tagtype, '') class URLNode(Node): - def __init__(self, view_name, args, kwargs): + def __init__(self, view_name, args, kwargs, asvar): self.view_name = view_name self.args = args self.kwargs = kwargs + self.asvar = asvar def render(self, context): from django.core.urlresolvers import reverse, NoReverseMatch args = [arg.resolve(context) for arg in self.args] kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) for k, v in self.kwargs.items()]) + + + # Try to look up the URL twice: once given the view name, and again + # relative to what we guess is the "main" app. If they both fail, + # re-raise the NoReverseMatch unless we're using the + # {% url ... as var %} construct in which cause return nothing. + url = '' try: - return reverse(self.view_name, args=args, kwargs=kwargs) + url = reverse(self.view_name, args=args, kwargs=kwargs) except NoReverseMatch: project_name = settings.SETTINGS_MODULE.split('.')[0] - return reverse(project_name + '.' + self.view_name, - args=args, kwargs=kwargs) + try: + url = reverse(project_name + '.' + self.view_name, + args=args, kwargs=kwargs) + except NoReverseMatch: + if self.asvar is None: + raise + + if self.asvar: + context[self.asvar] = url + return '' + else: + return url class WidthRatioNode(Node): def __init__(self, val_expr, max_expr, max_width): @@ -1041,21 +1059,30 @@ def url(parser, token): The URL will look like ``/clients/client/123/``. """ - bits = token.contents.split(' ', 2) + bits = token.contents.split(' ') if len(bits) < 2: raise TemplateSyntaxError("'%s' takes at least one argument" " (path to a view)" % bits[0]) + viewname = bits[1] args = [] kwargs = {} + asvar = None + if len(bits) > 2: - for arg in bits[2].split(','): - if '=' in arg: - k, v = arg.split('=', 1) - k = k.strip() - kwargs[k] = parser.compile_filter(v) + bits = iter(bits[2:]) + for bit in bits: + if bit == 'as': + asvar = bits.next() + break else: - args.append(parser.compile_filter(arg)) - return URLNode(bits[1], args, kwargs) + for arg in bit.split(","): + if '=' in arg: + k, v = arg.split('=', 1) + k = k.strip() + kwargs[k] = parser.compile_filter(v) + elif arg: + args.append(parser.compile_filter(arg)) + return URLNode(viewname, args, kwargs, asvar) url = register.tag(url) #@register.tag diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index d3c8355241..2e143a3967 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -675,6 +675,29 @@ The template tag will output the string ``/clients/client/123/``. `, you can refer to the name of the pattern in the ``url`` tag instead of using the path to the view. +Note that if the URL you're reversing doesn't exist, you'll get an +:exc:`NoReverseMatch` exception raised, which will cause your site to display an +error page. + +**New in development verson:** If you'd like to retrieve a URL without displaying it, +you can use a slightly different call: + +.. code-block:: html+django + + {% url path.to.view arg, arg2 as the_url %} + + I'm linking to {{ the_url }} + +This ``{% url ... as var %}`` syntax will *not* cause an error if the view is +missing. In practice you'll use this to link to views that are optional: + +.. code-block:: html+django + + {% url path.to.view as the_url %} + {% if the_url %} + Link to optional stuff + {% endif %} + .. templatetag:: widthratio widthratio diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 0eb5a2a586..f04bf0c33d 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -896,6 +896,11 @@ class Templates(unittest.TestCase): 'url-fail02': ('{% url no_such_view %}', {}, urlresolvers.NoReverseMatch), 'url-fail03': ('{% url regressiontests.templates.views.client %}', {}, urlresolvers.NoReverseMatch), + # {% url ... as var %} + 'url-asvar01': ('{% url regressiontests.templates.views.index as url %}', {}, ''), + 'url-asvar02': ('{% url regressiontests.templates.views.index as url %}{{ url }}', {}, '/url_tag/'), + 'url-asvar03': ('{% url no_such_view as url %}{{ url }}', {}, ''), + ### CACHE TAG ###################################################### 'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'), 'cache02': ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'),