From ac6c4260074de43a978e5c6553ef89441e1d6748 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 11 Oct 2020 23:34:32 -0400 Subject: [PATCH] Fixed #20601 -- Allowed forcing format with thousand separators in floatformat filter. Thanks Claude Paroz and Nick Pope for reviews. --- django/template/defaultfilters.py | 19 +++++++++++++-- docs/ref/templates/builtins.txt | 16 +++++++++++++ docs/releases/3.2.txt | 3 ++- tests/i18n/tests.py | 24 +++++++++++++++++++ .../filter_tests/test_floatformat.py | 15 ++++++++++++ 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 8bd865ad80..1c844580c6 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -119,9 +119,20 @@ def floatformat(text, arg=-1): * {{ num2|floatformat:"-3" }} displays "34" * {{ num3|floatformat:"-3" }} displays "34.260" + If arg has the 'g' suffix, force the result to be grouped by the + THOUSAND_SEPARATOR for the active locale. When the active locale is + en (English): + + * {{ 6666.6666|floatformat:"2g" }} displays "6,666.67" + * {{ 10000|floatformat:"g" }} displays "10,000" + 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 try: input_val = repr(text) d = Decimal(input_val) @@ -141,7 +152,9 @@ def floatformat(text, arg=-1): return input_val if not m and p < 0: - return mark_safe(formats.number_format('%d' % (int(d)), 0)) + return mark_safe( + formats.number_format('%d' % (int(d)), 0, force_grouping=force_grouping), + ) exp = Decimal(1).scaleb(-abs(p)) # Set the precision high enough to avoid an exception (#15789). @@ -161,7 +174,9 @@ 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))) + return mark_safe( + formats.number_format(number, abs(p), force_grouping=force_grouping), + ) @register.filter(is_safe=True) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 54258155bd..7fd44b01d7 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1732,6 +1732,18 @@ displayed. For example: ``34.26000`` ``{{ value|floatformat:"-3" }}`` ``34.260`` ============ ================================ ========== +If the argument passed to ``floatformat`` has the ``g`` suffix, it will force +grouping by the :setting:`THOUSAND_SEPARATOR` for the active locale. For +example, when the active locale is ``en`` (English): + +============ ================================= ============= +``value`` Template Output +============ ================================= ============= +``34232.34`` ``{{ value|floatformat:"2g" }}`` ``34,232.34`` +``34232.06`` ``{{ value|floatformat:"g" }}`` ``34,232.1`` +``34232.00`` ``{{ value|floatformat:"-3g" }}`` ``34,232`` +============ ================================= ============= + Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with an argument of ``-1``. @@ -1740,6 +1752,10 @@ with an argument of ``-1``. In older versions, a negative zero ``-0`` was returned for negative numbers which round to zero. +.. versionchanged:: 3.2 + + The ``g`` suffix to force grouping by thousand separators was added. + .. templatefilter:: force_escape ``force_escape`` diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 5ab05a1dba..f4ef77e435 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -367,7 +367,8 @@ Signals Templates ~~~~~~~~~ -* ... +* :tfilter:`floatformat` template filter now allows using the ``g`` suffix to + force grouping by the :setting:`THOUSAND_SEPARATOR` for the active locale. Tests ~~~~~ diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index affda21fb4..99e6febb28 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -560,6 +560,14 @@ class FormattingTests(SimpleTestCase): 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:"2g" }}').render(self.ctxt), + ) + self.assertEqual( + '100000.0', + Template('{{ f|floatformat:"g" }}').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)) self.assertEqual( @@ -734,6 +742,14 @@ class FormattingTests(SimpleTestCase): self.assertEqual('31 de desembre de 2009 a les 20:50', 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( + '66.666,67', + Template('{{ n|floatformat:"2g" }}').render(self.ctxt), + ) + self.assertEqual( + '100.000,0', + Template('{{ f|floatformat:"g" }}').render(self.ctxt), + ) self.assertEqual('10:15', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) self.assertEqual('31/12/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) self.assertEqual( @@ -935,6 +951,14 @@ class FormattingTests(SimpleTestCase): self.assertEqual('Dec. 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( + '66,666.67', + Template('{{ n|floatformat:"2g" }}').render(self.ctxt), + ) + self.assertEqual( + '100,000.0', + Template('{{ f|floatformat:"g" }}').render(self.ctxt), + ) self.assertEqual('12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) self.assertEqual( '12/31/2009 8:50 p.m.', diff --git a/tests/template_tests/filter_tests/test_floatformat.py b/tests/template_tests/filter_tests/test_floatformat.py index 8ad7322af2..64d65f7dc4 100644 --- a/tests/template_tests/filter_tests/test_floatformat.py +++ b/tests/template_tests/filter_tests/test_floatformat.py @@ -2,6 +2,8 @@ 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 from ..utils import setup @@ -58,6 +60,19 @@ 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') + self.assertEqual(floatformat(66666.666, '1g'), '66,666.7') + # Invalid suffix. + self.assertEqual(floatformat(10000, 'g2'), '10000') + with translation.override('de', deactivate=True): + self.assertEqual(floatformat(10000, 'g'), '10.000') + self.assertEqual(floatformat(66666.666, '1g'), '66.666,7') + # Invalid suffix. + self.assertEqual(floatformat(10000, 'g2'), '10000') + def test_zero_values(self): self.assertEqual(floatformat(0, 6), '0.000000') self.assertEqual(floatformat(0, 7), '0.0000000')