kopia lustrzana https://github.com/wagtail/wagtail
Patch number formatting functions during tests to flag up potential USE_THOUSAND_SEPARATOR issues
rodzic
85c0047268
commit
ff7e016eb5
|
@ -39,6 +39,9 @@ rules:
|
||||||
- metavariable-regex:
|
- metavariable-regex:
|
||||||
metavariable: $STRING_ID
|
metavariable: $STRING_ID
|
||||||
regex: ".*%\\w.*"
|
regex: ".*%\\w.*"
|
||||||
|
paths:
|
||||||
|
exclude:
|
||||||
|
- 'wagtail/test/numberformat.py'
|
||||||
message: >
|
message: >
|
||||||
Do not use anonymous placeholders for translations.
|
Do not use anonymous placeholders for translations.
|
||||||
Use printf style formatting with named placeholders instead.
|
Use printf style formatting with named placeholders instead.
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -3,6 +3,10 @@ import os
|
||||||
from django.contrib.messages import constants as message_constants
|
from django.contrib.messages import constants as message_constants
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from wagtail.test.numberformat import patch_number_formats
|
||||||
|
|
||||||
|
patch_number_formats()
|
||||||
|
|
||||||
DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"
|
DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"
|
||||||
WAGTAIL_ROOT = os.path.dirname(os.path.dirname(__file__))
|
WAGTAIL_ROOT = os.path.dirname(os.path.dirname(__file__))
|
||||||
WAGTAILADMIN_BASE_URL = "http://testserver"
|
WAGTAILADMIN_BASE_URL = "http://testserver"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
|
from django.template import Context, Template
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from wagtail.admin.tests.test_contentstate import content_state_equal
|
from wagtail.admin.tests.test_contentstate import content_state_equal
|
||||||
|
@ -460,3 +462,21 @@ class TestDummyExternalStorage(WagtailTestUtils, TestCase):
|
||||||
"Content file pointer should be at 0 - got 70 instead",
|
"Content file pointer should be at 0 - got 70 instead",
|
||||||
):
|
):
|
||||||
DummyExternalStorage().save("test.png", simple_png)
|
DummyExternalStorage().save("test.png", simple_png)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPatchedNumberFormat(TestCase):
|
||||||
|
def test_outputting_number_directly_is_disallowed(self):
|
||||||
|
context = Context({"num": 42})
|
||||||
|
template = Template("the answer is {{ num }}")
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
template.render(context)
|
||||||
|
|
||||||
|
def test_outputting_number_via_intcomma(self):
|
||||||
|
context = Context({"num": 9000})
|
||||||
|
template = Template("{% load wagtailadmin_tags %}It's over {{ num|intcomma }}!")
|
||||||
|
self.assertEqual(template.render(context), "It's over 9,000!")
|
||||||
|
|
||||||
|
def test_outputting_number_via_unlocalize(self):
|
||||||
|
context = Context({"num": 9000})
|
||||||
|
template = Template("{% load l10n %}It's over {{ num|unlocalize }}!")
|
||||||
|
self.assertEqual(template.render(context), "It's over 9000!")
|
||||||
|
|
Ładowanie…
Reference in New Issue