Code

Ticket #14389: future_url-1.diff

File future_url-1.diff, 7.4 KB (added by seanbrant, 4 years ago)
Line 
1Index: django/templatetags/future_url.py
2===================================================================
3--- django/templatetags/future_url.py   (revision 0)
4+++ django/templatetags/future_url.py   (revision 0)
5@@ -0,0 +1,117 @@
6+from django.conf import settings
7+from django.template import Library, Node, TemplateSyntaxError
8+from django.template.defaulttags import kwarg_re
9+
10+
11+register = Library()
12+
13+
14+class URLNode(Node):
15+
16+    def __init__(self, view_name, args, kwargs, asvar):
17+        self.view_name = view_name
18+        self.args = args
19+        self.kwargs = kwargs
20+        self.asvar = asvar
21+
22+    def render(self, context):
23+        from django.core.urlresolvers import reverse, NoReverseMatch
24+        args = [arg.resolve(context) for arg in self.args]
25+        kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context))
26+                       for k, v in self.kwargs.items()])
27+
28+        view_name = self.view_name.resolve(context)
29+
30+        # Try to look up the URL twice: once given the view name, and again
31+        # relative to what we guess is the "main" app. If they both fail,
32+        # re-raise the NoReverseMatch unless we're using the
33+        # {% url ... as var %} construct in which cause return nothing.
34+        url = ''
35+        try:
36+            url = reverse(view_name, args=args, kwargs=kwargs,
37+                current_app=context.current_app)
38+        except NoReverseMatch, e:
39+            if settings.SETTINGS_MODULE:
40+                project_name = settings.SETTINGS_MODULE.split('.')[0]
41+                try:
42+                    url = reverse(project_name + '.' + view_name,
43+                              args=args, kwargs=kwargs,
44+                              current_app=context.current_app)
45+                except NoReverseMatch:
46+                    if self.asvar is None:
47+                        # Re-raise the original exception, not the one with
48+                        # the path relative to the project. This makes a
49+                        # better error message.
50+                        raise e
51+            else:
52+                if self.asvar is None:
53+                    raise e
54+
55+        if self.asvar:
56+            context[self.asvar] = url
57+            return ''
58+        else:
59+            return url
60+
61+
62+def url(parser, token):
63+    """
64+    Returns an absolute URL matching given view with its parameters.
65+
66+    This is a way to define links that aren't tied to a particular URL
67+    configuration::
68+
69+        {% url "path.to.some_view" arg1 arg2 %}
70+
71+        or
72+
73+        {% url "path.to.some_view" name1=value1 name2=value2 %}
74+
75+    The first argument is a path to a view. It can be an absolute python path
76+    or just ``app_name.view_name`` without the project name if the view is
77+    located inside the project.  Other arguments are comma-separated values
78+    that will be filled in place of positional and keyword arguments in the
79+    URL. All arguments for the URL should be present.
80+
81+    For example if you have a view ``app_name.client`` taking client's id and
82+    the corresponding line in a URLconf looks like this::
83+
84+        ('^client/(\d+)/$', 'app_name.client')
85+
86+    and this app's URLconf is included into the project's URLconf under some
87+    path::
88+
89+        ('^clients/', include('project_name.app_name.urls'))
90+
91+    then in a template you can create a link for a certain client like this::
92+
93+        {% url "app_name.client" client.id %}
94+
95+    The URL will look like ``/clients/client/123/``.
96+    """
97+    bits = token.split_contents()
98+    if len(bits) < 2:
99+        raise TemplateSyntaxError("'%s' takes at least one argument"
100+                                  " (path to a view)" % bits[0])
101+    viewname = parser.compile_filter(bits[1])
102+    args = []
103+    kwargs = {}
104+    asvar = None
105+    bits = bits[2:]
106+    if len(bits) >= 2 and bits[-2] == 'as':
107+        asvar = bits[-1]
108+        bits = bits[:-2]
109+
110+    if len(bits):
111+        for bit in bits:
112+            match = kwarg_re.match(bit)
113+            if not match:
114+                raise TemplateSyntaxError("Malformed arguments to url tag")
115+            name, value = match.groups()
116+            if name:
117+                kwargs[name] = parser.compile_filter(value)
118+            else:
119+                args.append(parser.compile_filter(value))
120+
121+    return URLNode(viewname, args, kwargs, asvar)
122+url = register.tag(url)
123Index: django/template/defaulttags.py
124===================================================================
125--- django/template/defaulttags.py      (revision 13979)
126+++ django/template/defaulttags.py      (working copy)
127@@ -1111,6 +1111,12 @@
128 
129     The URL will look like ``/clients/client/123/``.
130     """
131+
132+    import warnings
133+    warnings.warn('The old style url tag is being deprecated. '
134+                  'You can start using the new style url (link to docs).',
135+                  category=PendingDeprecationWarning)
136+
137     bits = token.split_contents()
138     if len(bits) < 2:
139         raise TemplateSyntaxError("'%s' takes at least one argument"
140Index: tests/regressiontests/templates/tests.py
141===================================================================
142--- tests/regressiontests/templates/tests.py    (revision 13979)
143+++ tests/regressiontests/templates/tests.py    (working copy)
144@@ -1274,6 +1274,13 @@
145             'url-asvar02': ('{% url regressiontests.templates.views.index as url %}{{ url }}', {}, '/url_tag/'),
146             'url-asvar03': ('{% url no_such_view as url %}{{ url }}', {}, ''),
147 
148+            # Future url tag
149+            'url-future01': ('{% load future_url %}{% url "regressiontests.templates.views.client" client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
150+            'url-future02': ('{% load future_url %}{% url url_from_var client.id %}', {'client': {'id': 1}, 'url_from_var': 'regressiontests.templates.views.client'}, '/url_tag/client/1/'),
151+
152+            # Future url failures
153+            'url-future-fail01': ('{% load future_url %}{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, urlresolvers.NoReverseMatch),
154+
155             ### CACHE TAG ######################################################
156             'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'),
157             'cache02': ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'),
158Index: docs/ref/templates/builtins.txt
159===================================================================
160--- docs/ref/templates/builtins.txt     (revision 13979)
161+++ docs/ref/templates/builtins.txt     (working copy)
162@@ -1005,6 +1005,32 @@
163 signs. Did we mention you shouldn't use this syntax in any new
164 projects?
165 
166+.. versionchanged:: 1.3
167+
168+If you find yourself needing to use a context variable as the path
169+to view, you can now do that. To do so you must load a new url tag
170+that overwrites the previous tag.
171+
172+For example::
173+
174+    {% load future_url %}
175+    {% url path_to_view_var arg1 arg2 %}
176+
177+Now if you have ``path_to_view_var`` defined in your template context
178+it will use that value. Please note that old paths or patterns that are
179+not context variables need to be quoted using the new url tag.
180+
181+For example::
182+
183+    {% url "path.to.view" arg1 arg2 %}
184+
185+The new url tag also drops support for the old style comma separated
186+arguments.
187+
188+In version 1.3 the old url tag throws a ``PendingDeprecationWarning``,
189+then is version 1.4 this will move to a ``DeprecationWarning`` and finally
190+in version 1.5 the old tag will be removed and replaced by the new tag.
191+
192 .. templatetag:: widthratio
193 
194 widthratio