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: | 
