2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-18 23:17:41 +00:00

Report maths tweaks (#10604)

* Maths tags updates

- Allow maths operations on non-float values
- Add tests for Decimal values
- Add tests for Money values
- Convert string values to floating point

* Add modulo tag
This commit is contained in:
Oliver
2025-10-17 20:54:20 +11:00
committed by GitHub
parent 24dfbe815e
commit d534f67c62
3 changed files with 101 additions and 15 deletions

View File

@@ -263,7 +263,11 @@ Total Price: {% render_currency order.total_price currency='NZD' decimal_places=
## Maths Operations
Simple mathematical operators are available, as demonstrated in the example template below:
Simple mathematical operators are available, as demonstrated in the example template below. These operators can be used to perform basic arithmetic operations within the report template.
### Input Types
These mathematical functions accept inputs of various input types, and attempt to perform the operation accordingly. Note that any inputs which are provided as strings will be converted to floating point numbers before the operation is performed.
### add
@@ -293,6 +297,13 @@ Simple mathematical operators are available, as demonstrated in the example temp
show_docstring_description: false
show_source: False
### modulo
::: report.templatetags.report.modulo
options:
show_docstring_description: false
show_source: False
### Example
```html

View File

@@ -103,7 +103,7 @@ def filter_db_model(model_name: str, **kwargs) -> Optional[QuerySet]:
def getindex(container: list, index: int) -> Any:
"""Return the value contained at the specified index of the list.
This function is provideed to get around template rendering limitations.
This function is provided to get around template rendering limitations.
Arguments:
container: A python list object
@@ -413,28 +413,54 @@ def internal_link(link, text) -> str:
return mark_safe(f'<a href="{url}">{text}</a>')
@register.simple_tag()
def add(x: float, y: float, *args, **kwargs) -> float:
"""Add two numbers together."""
return float(x) + float(y)
def destringify(value: Any) -> Any:
"""Convert a string value into a float.
- If the value is a string, attempt to convert it to a float.
- If conversion fails, return the original string.
- If the value is not a string, return it unchanged.
The purpose of this function is to provide "seamless" math operations in templates,
where numeric values may be provided as strings, or converted to strings during template rendering.
"""
if isinstance(value, str):
value = value.strip()
try:
return float(value)
except ValueError:
return value
return value
@register.simple_tag()
def subtract(x: float, y: float) -> float:
"""Subtract one number from another."""
return float(x) - float(y)
def add(x: Any, y: Any) -> Any:
"""Add two numbers (or number like values) together."""
return destringify(x) + destringify(y)
@register.simple_tag()
def multiply(x: float, y: float) -> float:
"""Multiply two numbers together."""
return float(x) * float(y)
def subtract(x: Any, y: Any) -> Any:
"""Subtract one number (or number-like value) from another."""
return destringify(x) - destringify(y)
@register.simple_tag()
def divide(x: float, y: float) -> float:
"""Divide one number by another."""
return float(x) / float(y)
def multiply(x: Any, y: Any) -> Any:
"""Multiply two numbers (or number-like values) together."""
return destringify(x) * destringify(y)
@register.simple_tag()
def divide(x: Any, y: Any) -> Any:
"""Divide one number (or number-like value) by another."""
return destringify(x) / destringify(y)
@register.simple_tag()
def modulo(x: Any, y: Any) -> Any:
"""Calculate the modulo of one number (or number-like value) by another."""
return destringify(x) % destringify(y)
@register.simple_tag

View File

@@ -203,9 +203,58 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase):
self.assertEqual(report_tags.multiply('-2', 4), -8.0)
self.assertEqual(report_tags.divide(100, 5), 20)
self.assertEqual(report_tags.modulo(10, 3), 1)
self.assertEqual(report_tags.modulo('10', '4'), 2)
with self.assertRaises(ZeroDivisionError):
report_tags.divide(100, 0)
def test_maths_tags_with_strings(self):
"""Tests for mathematical operator tags with string inputs."""
self.assertEqual(report_tags.add('10', '20'), 30)
self.assertEqual(report_tags.subtract('50.5', '20.2'), 30.3)
self.assertEqual(report_tags.multiply(3.0000000000000, '7'), 21)
self.assertEqual(report_tags.divide('100.0', '4'), 25.0)
def test_maths_tags_with_decimal(self):
"""Tests for mathematical operator tags with Decimal inputs."""
from decimal import Decimal
self.assertEqual(
report_tags.add(Decimal('1.1'), Decimal('2.2')), Decimal('3.3')
)
self.assertEqual(
report_tags.subtract(Decimal('5.5'), Decimal('2.2')), Decimal('3.3')
)
self.assertEqual(report_tags.multiply(Decimal('3.0'), 4), Decimal('12.0'))
self.assertEqual(
report_tags.divide(Decimal('10.0'), Decimal('2.000')), Decimal('5.0')
)
def test_maths_tags_with_money(self):
"""Tests for mathematical operator tags with Money inputs."""
m1 = Money(100, 'USD')
m2 = Money(50, 'USD')
self.assertEqual(report_tags.add(m1, m2), Money(150, 'USD'))
self.assertEqual(report_tags.subtract(m1, m2), Money(50, 'USD'))
self.assertEqual(report_tags.multiply(m2, 3), Money(150, 'USD'))
self.assertEqual(report_tags.divide(m1, '4'), Money(25, 'USD'))
def test_maths_tags_invalid(self):
"""Tests for mathematical operator tags with invalid inputs."""
with self.assertRaises(TypeError):
report_tags.add('abc', 10)
with self.assertRaises(TypeError):
report_tags.subtract(50, 'xyz')
with self.assertRaises(TypeError):
report_tags.multiply('foo', 'bar')
with self.assertRaises(TypeError):
report_tags.divide('100', 'baz')
def test_number_tags(self):
"""Simple tests for number formatting tags."""
fn = report_tags.format_number