From 4a43335d300da942602fbf216cd0a53b60827ab4 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 8 Sep 2021 08:37:27 +0200 Subject: [PATCH] Fixed #30086, Refs #32873 -- Made floatformat template filter independent of USE_L10N. --- django/template/defaultfilters.py | 40 ++++++++++++++----- docs/ref/templates/builtins.txt | 19 +++++++++ docs/releases/4.0.txt | 7 +++- tests/i18n/tests.py | 18 ++++----- .../filter_tests/test_floatformat.py | 16 +++++++- 5 files changed, 79 insertions(+), 21 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 1c844580c6..5ccef38048 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -126,13 +126,29 @@ def floatformat(text, arg=-1): * {{ 6666.6666|floatformat:"2g" }} displays "6,666.67" * {{ 10000|floatformat:"g" }} displays "10,000" + If arg has the 'u' suffix, force the result to be unlocalized. When the + active locale is pl (Polish): + + * {{ 66666.6666|floatformat:"2" }} displays "66666,67" + * {{ 66666.6666|floatformat:"2u" }} displays "66666.67" + If the input float is infinity or NaN, display the string representation of that value. """ force_grouping = False - if isinstance(arg, str) and arg.endswith('g'): - force_grouping = True - arg = arg[:-1] or -1 + use_l10n = True + if isinstance(arg, str): + last_char = arg[-1] + if arg[-2:] in {'gu', 'ug'}: + force_grouping = True + use_l10n = False + arg = arg[:-2] or -1 + elif last_char == 'g': + force_grouping = True + arg = arg[:-1] or -1 + elif last_char == 'u': + use_l10n = False + arg = arg[:-1] or -1 try: input_val = repr(text) d = Decimal(input_val) @@ -152,9 +168,12 @@ def floatformat(text, arg=-1): return input_val if not m and p < 0: - return mark_safe( - formats.number_format('%d' % (int(d)), 0, force_grouping=force_grouping), - ) + return mark_safe(formats.number_format( + '%d' % (int(d)), + 0, + use_l10n=use_l10n, + force_grouping=force_grouping, + )) exp = Decimal(1).scaleb(-abs(p)) # Set the precision high enough to avoid an exception (#15789). @@ -174,9 +193,12 @@ def floatformat(text, arg=-1): if sign and rounded_d: digits.append('-') number = ''.join(reversed(digits)) - return mark_safe( - formats.number_format(number, abs(p), force_grouping=force_grouping), - ) + return mark_safe(formats.number_format( + number, + abs(p), + use_l10n=use_l10n, + force_grouping=force_grouping, + )) @register.filter(is_safe=True) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 936c80cdb5..baf0608a40 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1736,6 +1736,18 @@ example, when the active locale is ``en`` (English): ``34232.00`` ``{{ value|floatformat:"-3g" }}`` ``34,232`` ============ ================================= ============= +Output is always localized (independently of the :ttag:`{% localize off %} +` tag) unless the argument passed to ``floatformat`` has the ``u`` +suffix, which will force disabling localization. For example, when the active +locale is ``pl`` (Polish): + +============ ================================= ============= +``value`` Template Output +============ ================================= ============= +``34.23234`` ``{{ value|floatformat:"3" }}`` ``34,232`` +``34.23234`` ``{{ value|floatformat:"3u" }}`` ``34.232`` +============ ================================= ============= + Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with an argument of ``-1``. @@ -1743,6 +1755,13 @@ with an argument of ``-1``. The ``g`` suffix to force grouping by thousand separators was added. +.. versionchanged:: 4.0 + + ``floatformat`` template filter no longer depends on the + :setting:`USE_L10N` setting and always returns localized output. + + The ``u`` suffix to force disabling localization was added. + .. templatefilter:: force_escape ``force_escape`` diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 88b3c8ac37..7ae566d43a 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -355,7 +355,8 @@ Signals Templates ~~~~~~~~~ -* ... +* :tfilter:`floatformat` template filter now allows using the ``u`` suffix to + force disabling localization. Tests ~~~~~ @@ -574,6 +575,10 @@ Miscellaneous ` with the appropriate template from Django 3.2. +* The :tfilter:`floatformat` template filter no longer depends on the + :setting:`USE_L10N` setting and always returns localized output. Use the + ``u`` suffix to disable localization. + .. _deprecated-features-4.0: Features deprecated in 4.0 diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index eba4a8595c..104d986a2c 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -523,15 +523,15 @@ class FormattingTests(SimpleTestCase): self.assertEqual('99999.999', Template('{{ f }}').render(self.ctxt)) self.assertEqual('Des. 31, 2009', Template('{{ d }}').render(self.ctxt)) self.assertEqual('Des. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt)) - self.assertEqual('66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) - self.assertEqual('100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual('66666.67', Template('{{ n|floatformat:"2u" }}').render(self.ctxt)) + self.assertEqual('100000.0', Template('{{ f|floatformat:"u" }}').render(self.ctxt)) self.assertEqual( '66666.67', - Template('{{ n|floatformat:"2g" }}').render(self.ctxt), + Template('{{ n|floatformat:"2gu" }}').render(self.ctxt), ) self.assertEqual( '100000.0', - Template('{{ f|floatformat:"g" }}').render(self.ctxt), + Template('{{ f|floatformat:"ug" }}').render(self.ctxt), ) self.assertEqual('10:15 a.m.', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) self.assertEqual('12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) @@ -628,12 +628,12 @@ class FormattingTests(SimpleTestCase): ) # We shouldn't change the behavior of the floatformat filter re: - # thousand separator and grouping when USE_L10N is False even - # if the USE_THOUSAND_SEPARATOR, NUMBER_GROUPING and - # THOUSAND_SEPARATOR settings are specified + # thousand separator and grouping when localization is disabled + # even if the USE_THOUSAND_SEPARATOR, NUMBER_GROUPING and + # THOUSAND_SEPARATOR settings are specified. with self.settings(USE_THOUSAND_SEPARATOR=True, NUMBER_GROUPING=1, THOUSAND_SEPARATOR='!'): - self.assertEqual('66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) - self.assertEqual('100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual('66666.67', Template('{{ n|floatformat:"2u" }}').render(self.ctxt)) + self.assertEqual('100000.0', Template('{{ f|floatformat:"u" }}').render(self.ctxt)) def test_false_like_locale_formats(self): """ diff --git a/tests/template_tests/filter_tests/test_floatformat.py b/tests/template_tests/filter_tests/test_floatformat.py index 64d65f7dc4..1035fb96da 100644 --- a/tests/template_tests/filter_tests/test_floatformat.py +++ b/tests/template_tests/filter_tests/test_floatformat.py @@ -2,7 +2,6 @@ from decimal import Decimal, localcontext from django.template.defaultfilters import floatformat from django.test import SimpleTestCase -from django.test.utils import override_settings from django.utils import translation from django.utils.safestring import mark_safe @@ -60,7 +59,6 @@ class FunctionTests(SimpleTestCase): self.assertEqual(floatformat(1.5e-15, -20), '0.00000000000000150000') self.assertEqual(floatformat(1.00000000000000015, 16), '1.0000000000000002') - @override_settings(USE_L10N=True) def test_force_grouping(self): with translation.override('en'): self.assertEqual(floatformat(10000, 'g'), '10,000') @@ -73,6 +71,20 @@ class FunctionTests(SimpleTestCase): # Invalid suffix. self.assertEqual(floatformat(10000, 'g2'), '10000') + def test_unlocalize(self): + with translation.override('de', deactivate=True): + self.assertEqual(floatformat(66666.666, '2'), '66666,67') + self.assertEqual(floatformat(66666.666, '2u'), '66666.67') + with self.settings( + USE_THOUSAND_SEPARATOR=True, + NUMBER_GROUPING=3, + THOUSAND_SEPARATOR='!', + ): + self.assertEqual(floatformat(66666.666, '2gu'), '66!666.67') + self.assertEqual(floatformat(66666.666, '2ug'), '66!666.67') + # Invalid suffix. + self.assertEqual(floatformat(66666.666, 'u2'), '66666.666') + def test_zero_values(self): self.assertEqual(floatformat(0, 6), '0.000000') self.assertEqual(floatformat(0, 7), '0.0000000')