From 5183f7c287a9a5d61ca1103b55166cda52d9c647 Mon Sep 17 00:00:00 2001 From: haileyajohnson Date: Tue, 11 Mar 2025 00:02:27 -0700 Subject: [PATCH] Fixed #35816 -- Handled parsing of scientific notation in DTL. (#19213) * Refs #35816 -- Improved test coverage of FilterExpression. * Fixed #35816 -- Made FilterExpression parse scientific numbers. --------- Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> --- django/template/base.py | 7 ++- tests/template_tests/test_parser.py | 68 +++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index eaca428b10..e586a27991 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -633,19 +633,18 @@ constant_string = constant_string.replace("\n", "") filter_raw_string = r""" ^(?P%(constant)s)| -^(?P[%(var_chars)s]+|%(num)s)| +^(?P[%(var_chars)s]+)| (?:\s*%(filter_sep)s\s* (?P\w+) (?:%(arg_sep)s (?: (?P%(constant)s)| - (?P[%(var_chars)s]+|%(num)s) + (?P[%(var_chars)s]+) ) )? )""" % { "constant": constant_string, - "num": r"[-+.]?\d[\d.e]*", - "var_chars": r"\w\.", + "var_chars": r"\w\.\+-", "filter_sep": re.escape(FILTER_SEPARATOR), "arg_sep": re.escape(FILTER_ARGUMENT_SEPARATOR), } diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index eb3bb49113..c54f481f36 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -11,6 +11,7 @@ from django.template.base import ( Token, TokenType, Variable, + VariableDoesNotExist, ) from django.template.defaultfilters import register as filter_library from django.test import SimpleTestCase @@ -72,6 +73,27 @@ class ParserTests(SimpleTestCase): with self.assertRaisesMessage(TemplateSyntaxError, msg): FilterExpression("article._hidden|upper", p) + def test_cannot_parse_characters(self): + p = Parser("", builtins=[filter_library]) + for filter_expression, characters in [ + ('<>|default:"Default"|upper', '|<>||default:"Default"|upper'), + ("test|<>|upper", "test||<>||upper"), + ]: + with self.subTest(filter_expression=filter_expression): + with self.assertRaisesMessage( + TemplateSyntaxError, + f"Could not parse some characters: {characters}", + ): + FilterExpression(filter_expression, p) + + def test_cannot_find_variable(self): + p = Parser("", builtins=[filter_library]) + with self.assertRaisesMessage( + TemplateSyntaxError, + 'Could not find variable at start of |default:"Default"', + ): + FilterExpression('|default:"Default"', p) + def test_variable_parsing(self): c = {"article": {"section": "News"}} self.assertEqual(Variable("article.section").resolve(c), "News") @@ -148,3 +170,49 @@ class ParserTests(SimpleTestCase): '1|two_one_opt_arg:"1"', ): FilterExpression(expr, parser) + + def test_filter_numeric_argument_parsing(self): + p = Parser("", builtins=[filter_library]) + + cases = { + "5": 5, + "-5": -5, + "5.2": 5.2, + ".4": 0.4, + "5.2e3": 5200.0, # 5.2 × 10³ = 5200.0. + "5.2E3": 5200.0, # Case-insensitive. + "5.2e-3": 0.0052, # Negative exponent. + "-1.5E4": -15000.0, + "+3.0e2": 300.0, + ".5e2": 50.0, # 0.5 × 10² = 50.0 + } + for num, expected in cases.items(): + with self.subTest(num=num): + self.assertEqual(FilterExpression(num, p).resolve({}), expected) + self.assertEqual( + FilterExpression(f"0|default:{num}", p).resolve({}), expected + ) + + invalid_numbers = [ + "abc123", + "123abc", + "foo", + "error", + "1e", + "e400", + "1e.2", + "1e2.", + "1e2.0", + "1e2a", + "1e2e3", + "1e-", + "1e-a", + ] + + for num in invalid_numbers: + with self.subTest(num=num): + self.assertIsNone( + FilterExpression(num, p).resolve({}, ignore_failures=True) + ) + with self.assertRaises(VariableDoesNotExist): + FilterExpression(f"0|default:{num}", p).resolve({})