diff options
-rw-r--r-- | ishtar_common/jinja_filters.py | 139 | ||||
-rw-r--r-- | ishtar_common/utils.py | 27 | ||||
-rw-r--r-- | ishtar_common/utils_secretary.py | 104 |
3 files changed, 170 insertions, 100 deletions
diff --git a/ishtar_common/jinja_filters.py b/ishtar_common/jinja_filters.py new file mode 100644 index 000000000..0bc81c47f --- /dev/null +++ b/ishtar_common/jinja_filters.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2025 Étienne Loks <etienne.loks at iggdrasil dot net> +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + + +from datetime import datetime +from jinja2.filters import environmentfilter +import locale +from num2words import num2words +import re + +from django.conf import settings + + +def set_locale(): + language_code = settings.LANGUAGE_CODE.split("-") + language_code = language_code[0] + "_" + language_code[1].upper() + for language_suffix in (".utf8", ""): + try: + locale.setlocale(locale.LC_TIME, language_code + language_suffix) + break + except locale.Error: + pass + + +@environmentfilter +def float_format(*args): + """ + 10350.5 -> 10 350,5 + 5 -> 5 + 5.449999 -> 5,45 + """ + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + if value is None or value == "": + return "" + try: + value = float(value) + except ValueError: + return "" + locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8") + if int(value) != value: + value = float(f"{value:.2f}") + return f"{value:n}" + + +@environmentfilter +def euro_format(*args): + """ + 15000 -> 15 000,00 € + 5 -> 5,00 € + """ + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + value = float_format(value) + if not value: + return "" + parts = value.split(",") + if len(parts) < 2: + return value + ",00 €" + elif len(parts[1]) == 1: + return value + "0 €" + return value + " €" + + +@environmentfilter +def number_to_words(*args): + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + if value is None or value == "": + return "" + try: + value = float(value) + except ValueError: + return "" + return num2words(value, lang=settings.LANGUAGE_CODE.split("-")[0]) + + +@environmentfilter +def replace_line_breaks(*args): + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + return (value or "").replace("\r\n", "\n") + + +@environmentfilter +def capfirst_filter(*args): + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + return value[0].upper() + value[1:] if value else value + + +@environmentfilter +def lowerfirst_filter(*args): + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + return value[0].lower() + value[1:] if value else value + + +RE_CAP = re.compile(r"[^-' ]+") +SEP = ("un", "une", "le", "la", "les", "lez", "d", "l", "de", "des", "du", "sur", + "sous", "en") + + +@environmentfilter +def capitalize_filter(*args): + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + if not value: + return "" + value = value.lower() + res = "" + for m in RE_CAP.finditer(value): + start = m.start() + if start: + res += value[start - 1] + v = m.group() + if v not in SEP: + v = v[0].upper() + v[1:] + res += v + return res + + +@environmentfilter +def human_date_filter(*args): + value = args[0] if len(args) == 1 else args[1] # jinja simple filter + try: + value = datetime.strptime(value, "%Y-%m-%d") + except (ValueError, TypeError) as __: + return "" + set_locale() + return value.strftime(settings.DATE_FORMAT) diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index fc58fce4e..17fb62711 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -77,6 +77,10 @@ from django.utils.formats import date_format from django.utils.safestring import mark_safe from django.template.defaultfilters import slugify +from .jinja_filters import capfirst_filter, capitalize_filter, \ + euro_format, float_format, human_date_filter, lowerfirst_filter, \ + number_to_words, replace_line_breaks + if settings.USE_TRANSLATION_OVERLOAD: from overload_translation.utils import ( @@ -2813,6 +2817,18 @@ FORMULA_FILTERS = { } +EXTRA_JINJA_FILTERS = { + "human_date": human_date_filter, + "capfirst": capfirst_filter, + "lowerfirst": lowerfirst_filter, + "capitalize": capitalize_filter, + "float_format": float_format, + "euro_format": euro_format, + "number_to_words": number_to_words, + "replace_line_breaks": replace_line_breaks, +} + + def _update_gen_id_dct(item, dct, initial_key, fkey=None, filters=None): if not fkey: fkey = initial_key[:] @@ -2915,6 +2931,17 @@ def get_generated_id(key, item): return value +def jinja_evaluation(formula, values): + for key in FORMULA_FILTERS: + if key not in FILTERS: + FILTERS[key] = FORMULA_FILTERS[key] + for key in EXTRA_JINJA_FILTERS: + if key not in FILTERS: + FILTERS[key] = EXTRA_JINJA_FILTERS[key] + tpl = Template(formula) + return tpl.render(values) + + PRIVATE_FIELDS = ("id", "history_modifier", "order", "uuid") diff --git a/ishtar_common/utils_secretary.py b/ishtar_common/utils_secretary.py index 917abdeaa..9988fa4c2 100644 --- a/ishtar_common/utils_secretary.py +++ b/ishtar_common/utils_secretary.py @@ -1,28 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import re from secretary import Renderer from lxml import etree from xml.dom.minidom import parseString from xml.parsers.expat import ExpatError, ErrorString - -from datetime import datetime -import locale -from num2words import num2words from PIL import Image -import re from django.conf import settings - -def set_locale(): - language_code = settings.LANGUAGE_CODE.split("-") - language_code = language_code[0] + "_" + language_code[1].upper() - for language_suffix in (".utf8", ""): - try: - locale.setlocale(locale.LC_TIME, language_code + language_suffix) - break - except locale.Error: - pass +from .jinja_filters import capfirst_filter, capitalize_filter, \ + euro_format, float_format, human_date_filter, lowerfirst_filter, \ + number_to_words, replace_line_breaks RE_UNITS = re.compile("([.0-9]+)([a-z]+)") @@ -37,62 +26,6 @@ def parse_value_unit(value): return value, unit -def float_format(value): - """ - 10350.5 -> 10 350,5 - 5 -> 5 - 5.449999 -> 5,45 - """ - if value is None or value == "": - return "" - try: - value = float(value) - except ValueError: - return "" - locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8") - if int(value) != value: - value = float(f"{value:.2f}") - return f"{value:n}" - - -def euro_format(value): - """ - 15000 -> 15 000,00 € - 5 -> 5,00 € - """ - value = float_format(value) - if not value: - return "" - parts = value.split(",") - if len(parts) < 2: - return value + ",00 €" - elif len(parts[1]) == 1: - return value + "0 €" - return value + " €" - - -def number_to_words(value): - if value is None or value == "": - return "" - try: - value = float(value) - except ValueError: - return "" - return num2words(value, lang=settings.LANGUAGE_CODE.split("-")[0]) - - -def replace_line_breaks(value): - return (value or "").replace("\r\n", "\n") - - -def capfirst_filter(value): - return value[0].upper() + value[1:] if value else value - - -def lowerfirst_filter(value): - return value[0].lower() + value[1:] if value else value - - def add_filter(value1, value2): try: return float(value1 or 0) + float(value2 or 0) @@ -114,35 +47,6 @@ def multiply_filter(value1, value2): return 0 -RE_CAP = re.compile(r"[^-' ]+") -SEP = ("un", "une", "le", "la", "les", "lez", "d", "l", "de", "des", "du", "sur", "sous", "en") - - -def capitalize_filter(value): - if not value: - return "" - value = value.lower() - res = "" - for m in RE_CAP.finditer(value): - start = m.start() - if start: - res += value[start - 1] - v = m.group() - if v not in SEP: - v = v[0].upper() + v[1:] - res += v - return res - - -def human_date_filter(value): - try: - value = datetime.strptime(value, "%Y-%m-%d") - except (ValueError, TypeError) as __: - return "" - set_locale() - return value.strftime(settings.DATE_FORMAT) - - def splitpart(value, index, index_end=None, char=",", merge_character=None): if index_end: try: |