Code

Ticket #14181: localize-template-tag-2.diff

File localize-template-tag-2.diff, 13.1 KB (added by piquadrat, 3 years ago)

Changes proposed by russellm on django-developers

Line 
1diff --git a/django/contrib/gis/templates/gis/google/google-map.js b/django/contrib/gis/templates/gis/google/google-map.js
2index 06f11e3..a8bcba5 100644
3--- a/django/contrib/gis/templates/gis/google/google-map.js
4+++ b/django/contrib/gis/templates/gis/google/google-map.js
5@@ -1,4 +1,5 @@
6 {% autoescape off %}
7+{% localize off %}
8 {% block vars %}var geodjango = {};{% for icon in icons %}
9 var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON);
10 {% if icon.image %}{{ icon.varname }}.image = "{{ icon.image }}";{% endif %}
11@@ -32,4 +33,4 @@ var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON);
12     alert("Sorry, the Google Maps API is not compatible with this browser.");
13   }
14 }
15-{% endblock load %}{% endblock functions %}{% endautoescape %}
16+{% endblock load %}{% endblock functions %}{% endlocalize %}{% endautoescape %}
17diff --git a/django/template/__init__.py b/django/template/__init__.py
18index c316786..ac8f117 100644
19--- a/django/template/__init__.py
20+++ b/django/template/__init__.py
21@@ -789,11 +789,11 @@ class NodeList(list):
22     # extend_nodelist().
23     contains_nontext = False
24 
25-    def render(self, context):
26+    def render(self, context, localization=None):
27         bits = []
28         for node in self:
29             if isinstance(node, Node):
30-                bits.append(self.render_node(node, context))
31+                bits.append(self.render_node(node, context, localization))
32             else:
33                 bits.append(node)
34         return mark_safe(''.join([force_unicode(b) for b in bits]))
35@@ -805,7 +805,9 @@ class NodeList(list):
36             nodes.extend(node.get_nodes_by_type(nodetype))
37         return nodes
38 
39-    def render_node(self, node, context):
40+    def render_node(self, node, context, localization=None):
41+        if isinstance(node, VariableNode):
42+            return node.render(context, localization)
43         return node.render(context)
44 
45 class TextNode(Node):
46@@ -819,13 +821,13 @@ class TextNode(Node):
47     def render(self, context):
48         return self.s
49 
50-def _render_value_in_context(value, context):
51+def _render_value_in_context(value, context, localization=None):
52     """
53     Converts any value to a string to become part of a rendered template. This
54     means escaping, if required, and conversion to a unicode object. If value
55     is a string, it is expected to have already been translated.
56     """
57-    value = localize(value)
58+    value = localize(value, localization)
59     value = force_unicode(value)
60     if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
61         return escape(value)
62@@ -839,14 +841,14 @@ class VariableNode(Node):
63     def __repr__(self):
64         return "<Variable Node: %s>" % self.filter_expression
65 
66-    def render(self, context):
67+    def render(self, context, localization):
68         try:
69             output = self.filter_expression.resolve(context)
70         except UnicodeDecodeError:
71             # Unicode conversion can fail sometimes for reasons out of our
72             # control (e.g. exception rendering). In that case, we fail quietly.
73             return ''
74-        return _render_value_in_context(output, context)
75+        return _render_value_in_context(output, context, localization)
76 
77 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
78     "Returns a template.Node subclass."
79diff --git a/django/template/debug.py b/django/template/debug.py
80index c21fb50..be2f3e4 100644
81--- a/django/template/debug.py
82+++ b/django/template/debug.py
83@@ -84,10 +84,10 @@ class DebugNodeList(NodeList):
84         return result
85 
86 class DebugVariableNode(VariableNode):
87-    def render(self, context):
88+    def render(self, context, localization=None):
89         try:
90             output = self.filter_expression.resolve(context)
91-            output = localize(output)
92+            output = localize(value, context, localization)
93             output = force_unicode(output)
94         except TemplateSyntaxError, e:
95             if not hasattr(e, 'source'):
96diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
97index 1500a05..44e71b7 100644
98--- a/django/template/defaultfilters.py
99+++ b/django/template/defaultfilters.py
100@@ -434,6 +434,17 @@ linebreaksbr.is_safe = True
101 linebreaksbr.needs_autoescape = True
102 linebreaksbr = stringfilter(linebreaksbr)
103 
104+def localize(value, localization='on'):
105+    """
106+    Localizes (or prevents localization) of a value, not respecting
107+    ``settings.USE_L10N``.
108+    """
109+    if localization not in ('on', 'off'):
110+        return value
111+    localization = localization == 'on'
112+    return force_unicode(formats.localize(value, localization))
113+localize.is_safe = False
114+
115 def safe(value):
116     """
117     Marks the value as a string that should not be auto-escaped.
118@@ -922,6 +933,7 @@ register.filter(linebreaks)
119 register.filter(linebreaksbr)
120 register.filter(linenumbers)
121 register.filter(ljust)
122+register.filter(localize)
123 register.filter(lower)
124 register.filter(make_list)
125 register.filter(phone2numeric)
126diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
127index 1b07413..d0f654d 100644
128--- a/django/template/defaulttags.py
129+++ b/django/template/defaulttags.py
130@@ -433,6 +433,17 @@ class WithNode(Node):
131         context.pop()
132         return output
133 
134+class LocalizeNode(Node):
135+    def __init__(self, nodelist, localization):
136+        self.nodelist = nodelist
137+        self.localization = localization
138+
139+    def __repr__(self):
140+        return "<LocalizeNode>"
141+
142+    def render(self, context):
143+        return self.nodelist.render(context, self.localization)
144+
145 #@register.tag
146 def autoescape(parser, token):
147     """
148@@ -929,6 +940,30 @@ def load(parser, token):
149     return LoadNode()
150 load = register.tag(load)
151 
152+@register.tag
153+def localize(parser, token):
154+    """
155+    Forces or prevents localization without respect to `settings.USE_L10N`.
156+   
157+    Sample usage::
158+   
159+        {% localize off %}
160+            var pi = {{ 3.1415 }};
161+        {% endlocalize %}
162+       
163+    """
164+    localization = None
165+    bits = list(token.split_contents())
166+    if len(bits) == 1:
167+        localization = True
168+    elif len(bits) > 2 or bits[1] not in ('on', 'off'):
169+        raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0])
170+    else:
171+        localization = bits[1] == 'on'
172+    nodelist = parser.parse(('endlocalize',))
173+    parser.delete_first_token()
174+    return LocalizeNode(nodelist, localization)
175+
176 #@register.tag
177 def now(parser, token):
178     """
179diff --git a/django/utils/formats.py b/django/utils/formats.py
180index e64cc4e..9ace738 100644
181--- a/django/utils/formats.py
182+++ b/django/utils/formats.py
183@@ -41,14 +41,14 @@ def get_format_modules(reverse=False):
184         modules.reverse()
185     return modules
186 
187-def get_format(format_type, lang=None):
188+def get_format(format_type, lang=None, localization=None):
189     """
190     For a specific format type, returns the format for the current
191     language (locale), defaults to the format in the settings.
192     format_type is the name of the format, e.g. 'DATE_FORMAT'
193     """
194     format_type = smart_str(format_type)
195-    if settings.USE_L10N:
196+    if (localization is None and settings.USE_L10N) or localization:
197         if lang is None:
198             lang = get_language()
199         cache_key = (format_type, lang)
200@@ -65,48 +65,52 @@ def get_format(format_type, lang=None):
201             _format_cache[cache_key] = None
202     return getattr(settings, format_type)
203 
204-def date_format(value, format=None):
205+def date_format(value, format=None, localization=None):
206     """
207     Formats a datetime.date or datetime.datetime object using a
208     localizable format
209     """
210-    return dateformat.format(value, get_format(format or 'DATE_FORMAT'))
211+    return dateformat.format(value, get_format(format or 'DATE_FORMAT', localization=localization))
212 
213-def time_format(value, format=None):
214+def time_format(value, format=None, localization=None):
215     """
216     Formats a datetime.time object using a localizable format
217     """
218-    return dateformat.time_format(value, get_format(format or 'TIME_FORMAT'))
219+    return dateformat.time_format(value, get_format(format or 'TIME_FORMAT', localization=localization))
220 
221-def number_format(value, decimal_pos=None):
222+def number_format(value, decimal_pos=None, localization=None):
223     """
224     Formats a numeric value using localization settings
225     """
226-    if settings.USE_L10N:
227+    if (localization is None and settings.USE_L10N) or localization:
228         lang = get_language()
229     else:
230         lang = None
231     return numberformat.format(
232         value,
233-        get_format('DECIMAL_SEPARATOR', lang),
234+        get_format('DECIMAL_SEPARATOR', lang, localization),
235         decimal_pos,
236-        get_format('NUMBER_GROUPING', lang),
237-        get_format('THOUSAND_SEPARATOR', lang),
238+        get_format('NUMBER_GROUPING', lang, localization),
239+        get_format('THOUSAND_SEPARATOR', lang, localization),
240     )
241 
242-def localize(value):
243+def localize(value, localization=None):
244     """
245     Checks if value is a localizable type (date, number...) and returns it
246-    formatted as a string using current locale format
247+    formatted as a string using current locale format.
248+    If the value of `localization` is True or False, localization is enabled or
249+    disabled without respect to `settings.USE_L10N`.
250     """
251+    if localization is False: # can't use truth value, None has special meaning
252+        return value
253     if isinstance(value, (decimal.Decimal, float, int, long)):
254-        return number_format(value)
255+        return number_format(value, localization=localization)
256     elif isinstance(value, datetime.datetime):
257-        return date_format(value, 'DATETIME_FORMAT')
258+        return date_format(value, 'DATETIME_FORMAT', localization)
259     elif isinstance(value, datetime.date):
260-        return date_format(value)
261+        return date_format(value, localization=localization)
262     elif isinstance(value, datetime.time):
263-        return time_format(value, 'TIME_FORMAT')
264+        return time_format(value, 'TIME_FORMAT', localization)
265     else:
266         return value
267 
268diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
269index 70f0ffd..9e86ee3 100644
270--- a/docs/ref/templates/builtins.txt
271+++ b/docs/ref/templates/builtins.txt
272@@ -639,6 +639,35 @@ Load a custom template tag set.
273 
274 See :doc:`Custom tag and filter libraries </howto/custom-template-tags>` for more information.
275 
276+.. templatetag:: localize
277+
278+localize
279+~~~~~~~~
280+
281+.. versionadded:: 1.3
282+
283+Enables or disables localization for contained block.
284+
285+This tag allows a more fine grained control of localization than
286+:setting:`USE_L10N`.
287+
288+To activate or deactivate localization for a template block, use::
289+
290+    {% localize on %}
291+        {{ value }}
292+    {% endlocalize %}
293+   
294+    {% localize off %}
295+        {{ value }}
296+    {% endlocalize %}
297+
298+.. note::
299+
300+    The value of :setting:`USE_L10N` is not respected inside of a
301+    `{% localize %}` block.   
302+
303+See :tfilter:`localize` for the analog template filter.
304+
305 .. templatetag:: now
306 
307 now
308@@ -1561,6 +1590,31 @@ For example::
309 
310 If ``value`` is ``Django``, the output will be ``"Django    "``.
311 
312+.. templatefilter:: localize
313+
314+localize
315+~~~~~~~~
316+
317+.. versionadded:: 1.3
318+
319+Localizes (or prevents localization) of a value.
320+
321+**Argument:** "on" or "off"
322+
323+To prevent a value to be localized when :setting:`USE_L10N` is ``True``::
324+
325+    {{ value|localize:"off" }}
326+   
327+To force localization of a value::
328+
329+    {{ value|localize:"on" }}
330+   
331+or a shorthand::
332+
333+    {{ value|localize }}
334+   
335+See :ttag:`localize` for the analog template tag.
336+
337 .. templatefilter:: lower
338 
339 lower
340diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
341index 4aa52b6..6f8c3de 100644
342--- a/tests/regressiontests/i18n/tests.py
343+++ b/tests/regressiontests/i18n/tests.py
344@@ -453,6 +453,33 @@ class FormattingTests(TestCase):
345             settings.FORMAT_MODULE_PATH = old_format_module_path
346             deactivate()
347 
348+    def test_localize_templatetag_and_filter(self):
349+        """
350+        Tests the {% localize %} templatetag
351+        """
352+        context = Context({'value': 3.14 })
353+        template1 = Template("{% localize %}{{ value }}{% endlocalize %};{% localize on %}{{ value }}{% endlocalize %}")
354+        template2 = Template("{{ value }};{% localize off %}{{ value }};{% endlocalize %}{{ value }}")
355+        template3 = Template('{{ value }};{{ value|localize:"off" }}')
356+        template4 = Template('{{ value }};{{ value|localize }};{{ value|localize:"on" }}')
357+        output1 = '3,14;3,14'
358+        output2 = '3,14;3.14;3,14'
359+        output3 = '3,14;3.14'
360+        output4 = '3.14;3,14;3,14'
361+        old_localize = settings.USE_L10N
362+        try:
363+            activate('de')
364+            settings.USE_L10N = False
365+            self.assertEqual(template1.render(context), output1)
366+            self.assertEqual(template4.render(context), output4)
367+            settings.USE_L10N = True
368+            self.assertEqual(template1.render(context), output1)
369+            self.assertEqual(template2.render(context), output2)
370+            self.assertEqual(template3.render(context), output3)
371+        finally:
372+            deactivate()
373+            settings.USE_L10N = old_localize
374+
375 class MiscTests(TestCase):
376 
377     def test_parse_spec_http_header(self):