Repository: wagtail/wagtail
File: wagtail/test/numberformat.py (Python)

Code before:
# 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.).
    """

Review comment (suggestion):
I ended up adding a context manager instead so we can specify the templates to ignore in each test. Not sure if that's overengineering, though. Let me know if you'd rather us have a global list of templates to ignore.

Code after:
# 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 contextlib import contextmanager
from decimal import Decimal

import django.contrib.humanize.templatetags.humanize
import django.template.base
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
OriginalVariableNode = django.template.base.VariableNode


@contextmanager
def ignore_numberformat(template_names):
    class VariableNode(OriginalVariableNode):
        def render(self, context):
            template_name = context.render_context.template.origin.template_name
            # If the template being rendered is in the list of templates to ignore,
            # restore the original numberformat function so the template can render
            # normally, otherwise reinforce the patch as there might be multiple
            # templates being rendered in the lifespan of the contextmanager.
            if template_name in template_names:
                django.utils.numberformat.format = original_numberformat
            else:
                django.utils.numberformat.format = patched_numberformat

            return super().render(context)

    # Within the contextmanager, use a VariableNode class that conditionally
    # enforces the numberformat patch
    django.template.base.VariableNode = VariableNode

    try:
        yield
    finally:
        # Restore VariableNode to always enforce the numberformat patch
        django.template.base.VariableNode = OriginalVariableNode


def patched_numberformat(*args, use_l10n=None, **kwargs):