diff --git a/docs/docs/report/helpers.md b/docs/docs/report/helpers.md index aa56d74230..c8811f561c 100644 --- a/docs/docs/report/helpers.md +++ b/docs/docs/report/helpers.md @@ -173,8 +173,144 @@ Generate a list of all active customers: More advanced database filtering should be achieved using a [report plugin](../plugins/mixins/report.md), and adding custom context data to the report template. +## List Helpers + +The following helper functions are available for working with list (or list-like) data structures: + +### length + +Return the length of a list (or list-like) data structure. Note that this will also work for other data structures which support the `len()` function, such as strings, dictionaries or querysets: + +::: report.templatetags.report.length + options: + show_docstring_description: false + show_source: False + +### first + +Return the first element of a list (or list-like) data structure: + +::: report.templatetags.report.first + options: + show_docstring_description: false + show_source: False + + +### last + +Return the last element of a list (or list-like) data structure: + +::: report.templatetags.report.last + options: + show_docstring_description: false + show_source: False + +### reverse + +Return a list (or list-like) data structure in reverse order: + +::: report.templatetags.report.reverse + options: + show_docstring_description: false + show_source: False + +### truncate + +Return a truncated version of a list (or list-like) data structure, containing only the first N elements: + +::: report.templatetags.report.truncate + options: + show_docstring_description: false + show_source: False + +## String Formatting + +### strip + +Return a string with leading and trailing whitespace removed: + +::: report.templatetags.report.strip + options: + show_docstring_description: false + show_source: False + + +### lstrip + +Return a string with leading whitespace removed: + +::: report.templatetags.report.lstrip + options: + show_docstring_description: false + show_source: False + +### rstrip + +Return a string with trailing whitespace removed: + +::: report.templatetags.report.rstrip + options: + show_docstring_description: false + show_source: False + +### split + +Return a list of substrings by splitting a string based on a specified separator: + +::: report.templatetags.report.split + options: + show_docstring_description: false + show_source: False + +### join + +Return a string by joining a list of strings into a single string, using a specified separator: + +::: report.templatetags.report.join + options: + show_docstring_description: false + show_source: False + +### replace + +Return a string where occurrences of a specified substring are replaced with another substring: + +::: report.templatetags.report.replace + options: + show_docstring_description: false + show_source: False + +### lowercase + +Return a string with all characters converted to lowercase: + +::: report.templatetags.report.lowercase + options: + show_docstring_description: false + show_source: False + +### uppercase + +Return a string with all characters converted to uppercase: + +::: report.templatetags.report.uppercase + options: + show_docstring_description: false + show_source: False + +### titlecase + +Return a string with the first character of each word converted to uppercase and the remaining characters converted to lowercase: + +::: report.templatetags.report.titlecase + options: + show_docstring_description: false + show_source: False + ## Number Formatting +A number of helper functions are available for formatting numbers in a particular way. These can be used to format numbers according to a particular number of decimal places, or to add leading zeros, for example. + ### format_number The helper function `format_number` allows for some common number formatting options. It takes a number (or a number-like string) as an input, as well as some formatting arguments. It returns a *string* containing the formatted number: diff --git a/src/backend/InvenTree/report/templatetags/report.py b/src/backend/InvenTree/report/templatetags/report.py index f923aa2551..330aa1980f 100644 --- a/src/backend/InvenTree/report/templatetags/report.py +++ b/src/backend/InvenTree/report/templatetags/report.py @@ -1036,3 +1036,196 @@ def include_icon_fonts(ttf: bool = False, woff: bool = False): """ return mark_safe(icon_class + '\n'.join(fonts)) + + +@register.simple_tag() +def lowercase(value: str) -> str: + """Convert a string to lowercase. + + Arguments: + value: The string to be converted + """ + if not value: + return '' + return str(value).lower() + + +@register.simple_tag() +def uppercase(value: str) -> str: + """Convert a string to uppercase. + + Arguments: + value: The string to be converted + """ + if not value: + return '' + return str(value).upper() + + +@register.simple_tag() +def titlecase(value: str) -> str: + """Convert a string to title case. + + Arguments: + value: The string to be converted + """ + if not value: + return '' + return str(value).title() + + +@register.simple_tag() +def strip(value: str, chars: Optional[str] = ' ') -> str: + """Strip leading and trailing characters from a string. + + Arguments: + value: The string to be stripped + chars: The set of characters to strip from the string (default = whitespace) + """ + if not value: + return '' + return str(value).strip(chars) + + +@register.simple_tag() +def lstrip(value: str, chars: Optional[str] = ' ') -> str: + """Strip leading characters from a string. + + Arguments: + value: The string to be stripped + chars: The set of characters to strip from the string (default = whitespace) + """ + if not value: + return '' + return str(value).lstrip(chars) + + +@register.simple_tag() +def rstrip(value: str, chars: Optional[str] = ' ') -> str: + """Strip trailing characters from a string. + + Arguments: + value: The string to be stripped + chars: The set of characters to strip from the string (default = whitespace) + """ + if not value: + return '' + return str(value).rstrip(chars) + + +@register.simple_tag() +def split(value: str, separator: str = ',') -> list: + """Split a string into a list, using the provided separator (default = ','). + + Arguments: + value: The string to be split + separator: The character to use as a separator (default = ',') + """ + if not value: + return [] + return [v.strip() for v in str(value).split(separator)] + + +@register.simple_tag() +def join(value: list, separator: str = ',') -> str: + """Join a list of items into a string, using the provided separator (default = ','). + + Arguments: + value: The list of items to be joined + separator: The character to use as a separator (default = ',') + """ + if not value: + return '' + return separator.join(str(v) for v in value) + + +@register.simple_tag() +def length(value: Any) -> int: + """Return the length of a list or string. + + Arguments: + value: The value to be measured (e.g. a list or string) + """ + if value is None: + return 0 + try: + return len(value) + except TypeError: + return 0 + + +@register.simple_tag() +def replace(value: str, old: str, new: str = '') -> str: + """Replace occurrences of a substring within a string with a new value. + + Arguments: + value: The original string + old: The substring to be replaced + new: The value to replace the old substring with (default = "") + """ + if not value: + return '' + return str(value).replace(old, new) + + +@register.simple_tag() +def first(value: list, default: Any = None) -> Any: + """Return the first item in a list, or a default value if the list is empty. + + Arguments: + value: The list from which to retrieve the first item + default: The value to return if the list is empty (default = None) + """ + if not value: + return default + try: + return value[0] + except (IndexError, TypeError): + return default + + +@register.simple_tag() +def last(value: list, default: Any = None) -> Any: + """Return the last item in a list, or a default value if the list is empty. + + Arguments: + value: The list from which to retrieve the last item + default: The value to return if the list is empty (default = None) + """ + if not value: + return default + try: + return value[-1] + except (IndexError, TypeError): + return default + + +@register.simple_tag() +def reverse(value: list) -> list: + """Return a reversed version of the provided list. + + Arguments: + value: The list to be reversed + """ + if not value: + return [] + try: + return value[::-1] + except TypeError: + return [] + + +@register.simple_tag() +def truncate(value: list, length: int) -> list: + """Return a truncated version of the provided list. + + Arguments: + value: The list to be truncated + length: The maximum length of the returned list + """ + if not value: + return [] + try: + return value[:length] + except TypeError: + return [] diff --git a/src/backend/InvenTree/report/test_tags.py b/src/backend/InvenTree/report/test_tags.py index abee2b09c8..d8c4df23aa 100644 --- a/src/backend/InvenTree/report/test_tags.py +++ b/src/backend/InvenTree/report/test_tags.py @@ -231,6 +231,47 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): logo = report_tags.logo_image() self.assertIn('inventree.png', logo) + def test_string_tags(self): + """Simple tests for the string manipulation tags.""" + self.assertEqual(report_tags.uppercase('hello world'), 'HELLO WORLD') + self.assertEqual(report_tags.lowercase('HELLO WORLD'), 'hello world') + self.assertEqual(report_tags.titlecase('hello world'), 'Hello World') + + self.assertEqual(report_tags.strip(' hello world '), 'hello world') # noqa: B005 + self.assertEqual(report_tags.strip(' hello world ', chars=' hd'), 'ello worl') # noqa: B005 + self.assertEqual(report_tags.strip('xxhelloxx', chars='xy'), 'hello') # noqa: B005 + + self.assertEqual(report_tags.lstrip(' hello world '), 'hello world ') # noqa: B005 + self.assertEqual(report_tags.rstrip(' world ', chars=' dl'), ' wor') # noqa: B005 + + self.assertEqual(report_tags.split('a,b,c', separator=','), ['a', 'b', 'c']) + self.assertEqual(report_tags.split('a,|,b|c', separator='|'), ['a,', ',b', 'c']) + + self.assertEqual(report_tags.join(['a', 'b', 'c'], separator=','), 'a,b,c') + self.assertEqual(report_tags.join(['a', 'b', 'c'], separator='|'), 'a|b|c') + self.assertEqual(report_tags.join(None, separator=','), '') + + self.assertEqual(report_tags.replace('hello world', 'world'), 'hello ') + self.assertEqual( + report_tags.replace('hello world', 'world', 'there'), 'hello there' + ) + + self.assertEqual(report_tags.first('hello world'), 'h') + self.assertEqual(report_tags.last('hello world'), 'd') + self.assertEqual(report_tags.length('hello world'), 11) + self.assertEqual(report_tags.truncate('hello world', length=3), 'hel') + self.assertEqual(report_tags.reverse('hello world'), 'dlrow olleh') + + def test_list_tags(self): + """Simple tests for list manipulation tags.""" + self.assertEqual(report_tags.first([1, 2, 3]), 1) + self.assertEqual(report_tags.last([1, 2, 3]), 3) + self.assertEqual(report_tags.length([1, 2, 3]), 3) + self.assertEqual(report_tags.reverse([1, 2, 3]), [3, 2, 1]) + self.assertEqual(report_tags.truncate([1, 2, 3], 2), [1, 2]) + self.assertEqual(report_tags.join([1, 2, 3], separator=','), '1,2,3') + self.assertEqual(report_tags.join(None, separator=','), '') + def test_maths_tags(self): """Simple tests for mathematical operator tags.""" self.assertEqual(report_tags.add(1, 2), 3)