From 0fabbf8ce849dd744d085bae073add038d574e2f Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Tue, 13 Feb 2007 04:24:58 +0000 Subject: [PATCH] Fixed #2606 -- Added tag for working out the URL of a particular view function. All work done by Ivan Sagalaev. git-svn-id: http://code.djangoproject.com/svn/django/trunk@4494 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaulttags.py | 63 ++++++++++++++++++++++++ docs/templates.txt | 34 +++++++++++++ tests/regressiontests/templates/tests.py | 11 +++++ tests/regressiontests/templates/urls.py | 10 ++++ tests/regressiontests/templates/views.py | 10 ++++ tests/urls.py | 3 ++ 6 files changed, 131 insertions(+) create mode 100644 tests/regressiontests/templates/urls.py create mode 100644 tests/regressiontests/templates/views.py diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 3f3f4bda56..6aa53cfd8b 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -315,6 +315,25 @@ class TemplateTagNode(Node): def render(self, context): return self.mapping.get(self.tagtype, '') +class URLNode(Node): + def __init__(self, view_name, args, kwargs): + self.view_name = view_name + self.args = args + self.kwargs = kwargs + + def render(self, context): + from django.core.urlresolvers import reverse, NoReverseMatch + args = [arg.resolve(context) for arg in self.args] + kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()]) + try: + return reverse(self.view_name, args=args, kwargs=kwargs) + except NoReverseMatch: + try: + project_name = settings.SETTINGS_MODULE.split('.')[0] + return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs) + except NoReverseMatch: + return '' + class WidthRatioNode(Node): def __init__(self, val_expr, max_expr, max_width): self.val_expr = val_expr @@ -868,6 +887,50 @@ def templatetag(parser, token): return TemplateTagNode(tag) templatetag = register.tag(templatetag) +def url(parser, token): + """ + Returns an absolute URL matching given view with its parameters. This is a + way to define links that aren't tied to a particular url configuration: + + {% url path.to.some_view arg1,arg2,name1=value1 %} + + The first argument is a path to a view. It can be an absolute python path + or just ``app_name.view_name`` without the project name if the view is + located inside the project. Other arguments are comma-separated values + that will be filled in place of positional and keyword arguments in the + URL. All arguments for the URL should be present. + + For example if you have a view ``app_name.client`` taking client's id and + the corresponding line in a urlconf looks like this: + + ('^client/(\d+)/$', 'app_name.client') + + and this app's urlconf is included into the project's urlconf under some + path: + + ('^clients/', include('project_name.app_name.urls')) + + then in a template you can create a link for a certain client like this: + + {% url app_name.client client.id %} + + The URL will look like ``/clients/client/123/``. + """ + bits = token.contents.split(' ', 2) + if len(bits) < 2: + raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0] + args = [] + kwargs = {} + if len(bits) > 2: + for arg in bits[2].split(','): + if '=' in arg: + k, v = arg.split('=', 1) + kwargs[k] = parser.compile_filter(v) + else: + args.append(parser.compile_filter(arg)) + return URLNode(bits[1], args, kwargs) +url = register.tag(url) + #@register.tag def widthratio(parser, token): """ diff --git a/docs/templates.txt b/docs/templates.txt index 651866e52e..51c3404513 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -829,6 +829,40 @@ The argument tells which template bit to output: Note: ``opencomment`` and ``closecomment`` are new in the Django development version. +url +~~~ + +Returns an absolute URL matching a given view function. This is a way to +define links that aren't tied to a particular url configuration. + +:: + + {% url path.to.some_view arg1,arg2,name1=value1 %} + +The first argument is a path to a view function. It can be an absolute python +path or just ``app_name.view_name`` without the project name if the view is +located inside the project. Other arguments are comma-separated values that +will be use as positional and keyword arguments in the URL. All arguments +needed by the URL resolver should be present. + +For example, suppose you have a view ``app_name.client`` taking client's id +and the corresponding line in a urlconf looks like this:: + + ('^client/(\d+)/$', 'app_name.client') + +If this app's urlconf is included into the project's urlconf under a path +such as + +:: + + ('^clients/', include('project_name.app_name.urls')) + +then, in a template, you can create a link to this view like this:: + + {% url app_name.client client.id %} + +The URL rendered in the template will then look like ``/clients/client/123/``. + widthratio ~~~~~~~~~~ diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index cb0fe7eb9a..3bae6a2609 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -645,6 +645,17 @@ class Templates(unittest.TestCase): # Compare to a given parameter 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'), 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'), + + ### URL TAG ######################################################## + # Successes + '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/'), + + # Failures + 'url04' : ('{% url %}', {}, template.TemplateSyntaxError), + 'url05' : ('{% url no_such_view %}', {}, ''), + 'url06' : ('{% 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 new file mode 100644 index 0000000000..dc5b36b08b --- /dev/null +++ b/tests/regressiontests/templates/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls.defaults import * +from regressiontests.templates import views + +urlpatterns = patterns('', + + # Test urls for testing reverse lookups + (r'^$', views.index), + (r'^client/(\d+)/$', views.client), + (r'^client/(\d+)/(?P[^/]+)/$', views.client_action), +) diff --git a/tests/regressiontests/templates/views.py b/tests/regressiontests/templates/views.py new file mode 100644 index 0000000000..b68809944a --- /dev/null +++ b/tests/regressiontests/templates/views.py @@ -0,0 +1,10 @@ +# Fake views for testing url reverse lookup + +def index(request): + pass + +def client(request, id): + pass + +def client_action(request, id, action): + pass diff --git a/tests/urls.py b/tests/urls.py index 39d5aaee6b..9fefd93624 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -7,4 +7,7 @@ urlpatterns = patterns('', # Always provide the auth system login and logout views (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), (r'^accounts/logout/$', 'django.contrib.auth.views.login'), + + # test urlconf for {% url %} template tag + (r'^url_tag/', include('regressiontests.templates.urls')), )