summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ishtar_common/jinja_filters.py139
-rw-r--r--ishtar_common/utils.py27
-rw-r--r--ishtar_common/utils_secretary.py104
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: