|
@@ -0,0 +1,104 @@
|
|
|
+# Patch Django's number formatting functions during tests so that outputting a number onto a
|
|
|
+# template without explicitly passing it through one of |intcomma, |localize, |unlocalize or
|
|
|
+# |filesizeformat will raise an exception. This helps to catch bugs where
|
|
|
+# USE_THOUSAND_SEPARATOR = True incorrectly reformats numbers that are not intended to be
|
|
|
+# human-readable (such as image dimensions, or IDs within data attributes).
|
|
|
+
|
|
|
+from decimal import Decimal
|
|
|
+
|
|
|
+import django.contrib.humanize.templatetags.humanize
|
|
|
+import django.template.defaultfilters
|
|
|
+import django.templatetags.l10n
|
|
|
+import django.utils.numberformat
|
|
|
+from django.core.exceptions import ImproperlyConfigured
|
|
|
+from django.utils import formats
|
|
|
+from django.utils.html import avoid_wrapping
|
|
|
+from django.utils.translation import gettext, ngettext
|
|
|
+
|
|
|
+original_numberformat = django.utils.numberformat.format
|
|
|
+original_intcomma = django.contrib.humanize.templatetags.humanize.intcomma
|
|
|
+
|
|
|
+
|
|
|
+def patched_numberformat(*args, use_l10n=None, **kwargs):
|
|
|
+ if use_l10n is False or use_l10n == "explicit":
|
|
|
+ return original_numberformat(*args, use_l10n=use_l10n, **kwargs)
|
|
|
+
|
|
|
+ raise ImproperlyConfigured(
|
|
|
+ "A number was used directly on a template. "
|
|
|
+ "Numbers output on templates should be passed through one of |intcomma, |localize, "
|
|
|
+ "|unlocalize or |filesizeformat to avoid issues with USE_THOUSAND_SEPARATOR."
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def patched_intcomma(value, use_l10n=True):
|
|
|
+ if use_l10n:
|
|
|
+ try:
|
|
|
+ if not isinstance(value, (float, Decimal)):
|
|
|
+ value = int(value)
|
|
|
+ except (TypeError, ValueError):
|
|
|
+ return original_intcomma(value, False)
|
|
|
+ else:
|
|
|
+ return formats.number_format(
|
|
|
+ value, use_l10n="explicit", force_grouping=True
|
|
|
+ )
|
|
|
+
|
|
|
+ return original_intcomma(value, use_l10n=use_l10n)
|
|
|
+
|
|
|
+
|
|
|
+def patched_filesizeformat(bytes_):
|
|
|
+ """
|
|
|
+ Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
|
|
|
+ 102 bytes, etc.).
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ bytes_ = int(bytes_)
|
|
|
+ except (TypeError, ValueError, UnicodeDecodeError):
|
|
|
+ value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {"size": 0}
|
|
|
+ return avoid_wrapping(value)
|
|
|
+
|
|
|
+ def filesize_number_format(value):
|
|
|
+ return formats.number_format(round(value, 1), 1, use_l10n="explicit")
|
|
|
+
|
|
|
+ KB = 1 << 10
|
|
|
+ MB = 1 << 20
|
|
|
+ GB = 1 << 30
|
|
|
+ TB = 1 << 40
|
|
|
+ PB = 1 << 50
|
|
|
+
|
|
|
+ negative = bytes_ < 0
|
|
|
+ if negative:
|
|
|
+ bytes_ = -bytes_ # Allow formatting of negative numbers.
|
|
|
+
|
|
|
+ if bytes_ < KB:
|
|
|
+ value = ngettext("%(size)d byte", "%(size)d bytes", bytes_) % {"size": bytes_}
|
|
|
+ elif bytes_ < MB:
|
|
|
+ value = gettext("%s KB") % filesize_number_format(bytes_ / KB)
|
|
|
+ elif bytes_ < GB:
|
|
|
+ value = gettext("%s MB") % filesize_number_format(bytes_ / MB)
|
|
|
+ elif bytes_ < TB:
|
|
|
+ value = gettext("%s GB") % filesize_number_format(bytes_ / GB)
|
|
|
+ elif bytes_ < PB:
|
|
|
+ value = gettext("%s TB") % filesize_number_format(bytes_ / TB)
|
|
|
+ else:
|
|
|
+ value = gettext("%s PB") % filesize_number_format(bytes_ / PB)
|
|
|
+
|
|
|
+ if negative:
|
|
|
+ value = "-%s" % value
|
|
|
+ return avoid_wrapping(value)
|
|
|
+
|
|
|
+
|
|
|
+def patched_localize(value):
|
|
|
+ return str(formats.localize(value, use_l10n="explicit"))
|
|
|
+
|
|
|
+
|
|
|
+def patch_number_formats():
|
|
|
+ django.utils.numberformat.format = patched_numberformat
|
|
|
+ django.contrib.humanize.templatetags.humanize.intcomma = patched_intcomma
|
|
|
+ django.template.defaultfilters.filesizeformat = patched_filesizeformat
|
|
|
+ django.template.defaultfilters.register.filter(
|
|
|
+ "filesizeformat", patched_filesizeformat, is_safe=True
|
|
|
+ )
|
|
|
+ django.templatetags.l10n.localize = patched_localize
|
|
|
+ django.templatetags.l10n.register.filter(
|
|
|
+ "localize", patched_localize, is_safe=False
|
|
|
+ )
|