2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-21 00:17:39 +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 ## 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 ### add
@@ -293,6 +297,13 @@ Simple mathematical operators are available, as demonstrated in the example temp
show_docstring_description: false show_docstring_description: false
show_source: False show_source: False
### modulo
::: report.templatetags.report.modulo
options:
show_docstring_description: false
show_source: False
### Example ### Example
```html ```html

View File

@@ -103,7 +103,7 @@ def filter_db_model(model_name: str, **kwargs) -> Optional[QuerySet]:
def getindex(container: list, index: int) -> Any: def getindex(container: list, index: int) -> Any:
"""Return the value contained at the specified index of the list. """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: Arguments:
container: A python list object container: A python list object
@@ -413,28 +413,54 @@ def internal_link(link, text) -> str:
return mark_safe(f'<a href="{url}">{text}</a>') return mark_safe(f'<a href="{url}">{text}</a>')
@register.simple_tag() def destringify(value: Any) -> Any:
def add(x: float, y: float, *args, **kwargs) -> float: """Convert a string value into a float.
"""Add two numbers together."""
return float(x) + float(y) - 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() @register.simple_tag()
def subtract(x: float, y: float) -> float: def add(x: Any, y: Any) -> Any:
"""Subtract one number from another.""" """Add two numbers (or number like values) together."""
return float(x) - float(y) return destringify(x) + destringify(y)
@register.simple_tag() @register.simple_tag()
def multiply(x: float, y: float) -> float: def subtract(x: Any, y: Any) -> Any:
"""Multiply two numbers together.""" """Subtract one number (or number-like value) from another."""
return float(x) * float(y) return destringify(x) - destringify(y)
@register.simple_tag() @register.simple_tag()
def divide(x: float, y: float) -> float: def multiply(x: Any, y: Any) -> Any:
"""Divide one number by another.""" """Multiply two numbers (or number-like values) together."""
return float(x) / float(y) 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 @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.multiply('-2', 4), -8.0)
self.assertEqual(report_tags.divide(100, 5), 20) 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): with self.assertRaises(ZeroDivisionError):
report_tags.divide(100, 0) 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): def test_number_tags(self):
"""Simple tests for number formatting tags.""" """Simple tests for number formatting tags."""
fn = report_tags.format_number fn = report_tags.format_number