From 9e38ed0536c7dc598a6c2c1bb774d0a8db3cdddc Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 9 Jun 2019 12:48:20 -0700 Subject: [PATCH] Fixed #27486 -- Fixed Python 3.7 DeprecationWarning in intword and filesizeformat filters. intword and filesizeformat passed floats to ngettext() which is deprecated in Python 3.7. The rationale for this warning is documented in BPO-28692: https://bugs.python.org/issue28692. For filesizeformat, the filesize value is expected to be an int -- it fills %d string formatting placeholders. It was likely coerced to a float to ensure floating point division on Python 2. Python 3 always does floating point division, so coerce to an int instead of a float to fix the warning. For intword, the number may contain a decimal component. In English, a decimal component makes the noun plural. A helper function, round_away_from_one(), was added to convert the float to an integer that is appropriate for ngettext(). --- .../contrib/humanize/templatetags/humanize.py | 5 +-- django/template/defaultfilters.py | 2 +- django/utils/translation/__init__.py | 5 +++ docs/ref/contrib/humanize.txt | 9 ++++- docs/releases/3.0.txt | 3 ++ tests/i18n/tests.py | 34 +++++++++++++++++-- 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 194c7e8fbb..31880846f0 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -10,7 +10,7 @@ from django.utils.safestring import mark_safe from django.utils.timezone import is_aware, utc from django.utils.translation import ( gettext as _, gettext_lazy, ngettext, ngettext_lazy, npgettext_lazy, - pgettext, + pgettext, round_away_from_one, ) register = template.Library() @@ -158,7 +158,8 @@ def intword(value): large_number = 10 ** exponent if value < large_number * 1000: new_value = value / large_number - return _check_for_i18n(new_value, *converters(new_value)) + rounded_value = round_away_from_one(new_value) + return _check_for_i18n(new_value, *converters(rounded_value)) return value diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index d8fb0a5396..174d2a092d 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -812,7 +812,7 @@ def filesizeformat(bytes_): 102 bytes, etc.). """ try: - bytes_ = float(bytes_) + bytes_ = int(bytes_) except (TypeError, ValueError, UnicodeDecodeError): value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0} return avoid_wrapping(value) diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index 8f3e3396a4..e48c7d245d 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -4,6 +4,7 @@ Internationalization support. import re import warnings from contextlib import ContextDecorator +from decimal import ROUND_UP, Decimal from django.utils.autoreload import autoreload_started, file_changed from django.utils.deprecation import RemovedInDjango40Warning @@ -332,3 +333,7 @@ trim_whitespace_re = re.compile(r'\s*\n\s*') def trim_whitespace(s): return trim_whitespace_re.sub(' ', s.strip()) + + +def round_away_from_one(value): + return int(Decimal(value - 1).quantize(Decimal('0'), rounding=ROUND_UP)) + 1 diff --git a/docs/ref/contrib/humanize.txt b/docs/ref/contrib/humanize.txt index 9a30ce1aa4..edbdb136b0 100644 --- a/docs/ref/contrib/humanize.txt +++ b/docs/ref/contrib/humanize.txt @@ -57,7 +57,9 @@ e.g. with the ``'de'`` language: =========== Converts a large integer (or a string representation of an integer) to a -friendly text representation. Works best for numbers over 1 million. +friendly text representation. Translates ``1.0`` as a singular phrase and all +other numeric values as plural, this may be incorrect for some languages. Works +best for numbers over 1 million. Examples: @@ -74,6 +76,11 @@ e.g. with the ``'de'`` language: * ``1200000`` becomes ``'1,2 Millionen'``. * ``1200000000`` becomes ``'1,2 Milliarden'``. +.. versionchanged:: 3.0 + + All numeric values are now translated as plural, except ``1.0`` which is + translated as a singular phrase. This may be incorrect for some languages. + .. templatefilter:: naturalday ``naturalday`` diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index f6a45ebfc9..e58a18f9da 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -416,6 +416,9 @@ Miscellaneous when ``doseq=False``, rather than iterating them, bringing it into line with the standard library :func:`urllib.parse.urlencode` function. +* ``intword`` template filter now translates ``1.0`` as a singular phrase and + all other numeric values as plural. This may be incorrect for some languages. + .. _deprecated-features-3.0: Features deprecated in 3.0 diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 4302e01fda..8bb284e0c3 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -34,9 +34,9 @@ from django.utils.translation import ( LANGUAGE_SESSION_KEY, activate, check_for_language, deactivate, get_language, get_language_bidi, get_language_from_request, get_language_info, gettext, gettext_lazy, ngettext, ngettext_lazy, - npgettext, npgettext_lazy, pgettext, to_language, to_locale, trans_null, - trans_real, ugettext, ugettext_lazy, ugettext_noop, ungettext, - ungettext_lazy, + npgettext, npgettext_lazy, pgettext, round_away_from_one, to_language, + to_locale, trans_null, trans_real, ugettext, ugettext_lazy, ugettext_noop, + ungettext, ungettext_lazy, ) from django.utils.translation.reloader import ( translation_file_changed, watch_for_translation_changes, @@ -1883,3 +1883,31 @@ class TranslationFileChangedTests(SimpleTestCase): self.assertEqual(trans_real._translations, {}) self.assertIsNone(trans_real._default) self.assertIsInstance(trans_real._active, _thread._local) + + +class UtilsTests(SimpleTestCase): + def test_round_away_from_one(self): + tests = [ + (0, 0), + (0., 0), + (0.25, 0), + (0.5, 0), + (0.75, 0), + (1, 1), + (1., 1), + (1.25, 2), + (1.5, 2), + (1.75, 2), + (-0., 0), + (-0.25, -1), + (-0.5, -1), + (-0.75, -1), + (-1, -1), + (-1., -1), + (-1.25, -2), + (-1.5, -2), + (-1.75, -2), + ] + for value, expected in tests: + with self.subTest(value=value): + self.assertEqual(round_away_from_one(value), expected)