diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-09-29 14:39:56 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-09-29 23:24:09 +0200 |
commit | ccd7055b5d94f60eb6a8974f530b526fa2405e0b (patch) | |
tree | ebf1550ddd540e9478c3e1b570e0a0fee59bf526 | |
parent | 9f44f4dd6cdaeb44addf5cc8f2d15a581f1c3e89 (diff) | |
download | Ishtar-ccd7055b5d94f60eb6a8974f530b526fa2405e0b.tar.bz2 Ishtar-ccd7055b5d94f60eb6a8974f530b526fa2405e0b.zip |
✨ more robust templates (don't crash on missing values) - add fields for file templates - number_to_words and float_format filters for templates
-rw-r--r-- | archaeological_files/models.py | 86 | ||||
-rw-r--r-- | ishtar_common/models.py | 5 | ||||
-rw-r--r-- | ishtar_common/utils_secretary.py | 30 | ||||
-rw-r--r-- | requirements.txt | 2 |
4 files changed, 108 insertions, 15 deletions
diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 04a4ccc2c..9507a5e0b 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -130,6 +130,10 @@ class Job(GeneralType): lbl += " ({})".format(_("fixed-term contract")) return lbl + @property + def has_parents(self): + return bool(self.parents.count()) + @classmethod def get_choices(cls, current_value, price_agreement_id=None): q = cls.objects.filter( @@ -1059,17 +1063,42 @@ class File( values[prefix + key_qt_planned] = 0 key_qt_worked = slug + "_equipment_sum_worked" values[prefix + key_qt_worked] = 0 + key_costs = prefix + slug + "_equipment_costs" + key_costs_dct = {} for cost in q.all(): - values[prefix + key_qt_planned] += (cost.quantity_by_day_planned or 0) - values[prefix + key_qt_worked] += (cost.quantity_by_day_worked or 0) + quantity_by_day_planned = (cost.quantity_by_day_planned or 0) + values[prefix + key_qt_planned] += quantity_by_day_planned + quantity_by_day_worked = (cost.quantity_by_day_worked or 0) + values[prefix + key_qt_worked] += quantity_by_day_worked value = ES_UNITS_DAYS[cost.equipment_service_cost.unit] * ( cost.days_planned or 0) if value > values[prefix + key_time_planned]: values[prefix + key_time_planned] = value + cost_name = cost.equipment_service_cost.equipment_service_type.label + if cost_name not in key_costs_dct: + key_costs_dct[cost_name] = { + "quantity_by_day_planned": 0, + "days_planned": 0, + "cost_planned": 0, + "quantity_by_day_worked": 0, + "days_worked": 0, + "cost_worked": 0 + } + key_costs_dct[cost_name]["quantity_by_day_planned"] += (quantity_by_day_planned or 0) + key_costs_dct[cost_name]["days_planned"] += (cost.days_planned or 0) + key_costs_dct[cost_name]["cost_planned"] += value value = ES_UNITS_DAYS[cost.equipment_service_cost.unit] * ( cost.days_worked or 0) if value > values[prefix + key_time_planned]: values[prefix + key_time_worked] = value + key_costs_dct[cost_name]["quantity_by_day_worked"] += (quantity_by_day_worked or 0) + key_costs_dct[cost_name]["days_worked"] += (cost.days_worked or 0) + key_costs_dct[cost_name]["cost_worked"] += value + values[key_costs] = [] + for cost_name in key_costs_dct: + cost_detail = {"name": cost_name} + cost_detail.update(key_costs_dct[cost_name]) + values[key_costs].append(InlineClass(cost_detail)) return values @property @@ -1094,8 +1123,11 @@ class File( jobs = {} total_ground_cost_planned = 0 total_ground_cost_worked = 0 + max_ground_day_planned = 0 for job in self.ground_jobs.filter(job__permanent_contract=permanent).all(): name = job.job.label + has_parents = job.job.has_parents + max_ground_day_planned = max(max_ground_day_planned, job.days_planned or 0) ground_cost_total = ( (job.man_by_day_planned or 0) * (job.days_planned or 0) * (job.cost_planned or 0) @@ -1115,13 +1147,25 @@ class File( "ground_days_worked": job.days_worked, "ground_cost_worked": job.cost_worked, "ground_cost_total_worked": ground_cost_total_worked or "", + "has_parents": has_parents, + "man_by_day_planned": "", + "days_planned": "", + "cost_planned": "", + "cost_total_planned": "", + "man_by_day_worked": "", + "days_worked": "", + "cost_worked": "", + "cost_total_worked": "", } total_cost_planned = 0 total_cost_worked = 0 + max_day_worked = 0 for job in self.jobs.filter(job__permanent_contract=permanent).all(): name = job.job.label + max_day_worked = max(max_day_worked, job.days_worked or 0) if name not in jobs: - jobs[name] = {} + has_parents = job.job.has_parents + jobs[name] = {"has_parents": has_parents} cost_total_planned = ( (job.man_by_day_planned or 0) * (job.days_planned or 0) * (job.cost_planned or 0) @@ -1147,21 +1191,37 @@ class File( dct = jobs[k] dct["name"] = k result.append(InlineClass(dct)) - return result, total_ground_cost_planned, total_ground_cost_worked, \ - total_cost_planned, total_cost_worked + dct = { + "jobs_list": result, + "ground_cost_planned": total_ground_cost_planned, + "ground_cost_worked": total_ground_cost_worked, + "cost_planned": total_cost_planned, + "cost_worked": total_cost_worked, + "max_day_worked": max_day_worked, + "max_ground_day_worked": max_ground_day_planned, + } + return dct def get_extra_values(self, prefix="", no_values=False, filtr=None, **kwargs): values = get_values_town_related(self, prefix, {}, filtr=filtr) values = self.get_dynamic_values(prefix, values, filtr=filtr) values["montant_subvention_dotation"] = self.montant_subvention_dotation - values["jobs_list_permanent"], values["permanent_ground_cost_planned"], \ - values["permanent_ground_cost_worked"], \ - values["permanent_cost_planned"], \ - values["permanent_cost_worked"] = self.jobs_list_permanent - values["jobs_list_non_permanent"], values["non_permanent_ground_cost_planned"], \ - values["non_permanent_ground_cost_worked"], \ - values["non_permanent_cost_planned"], \ - values["non_permanent_cost_worked"] = self.jobs_list_non_permanent + + dct = self.jobs_list_permanent + values["jobs_list"] = dct["jobs_list"][:] + values["jobs_list_permanent"] = dct.pop("jobs_list") + values["max_day_worked"] = dct.pop("max_day_worked") + values["max_ground_day_worked"] = dct.pop("max_ground_day_worked") + for k in dct.keys(): + values["permanent_" + k] = dct[k] + dct = self.jobs_list_non_permanent + values["jobs_list"] += dct["jobs_list"][:] + values["jobs_list_non_permanent"] = dct.pop("jobs_list") + values["max_day_worked"] = max(values["max_day_worked"], dct.pop("max_day_worked")) + values["max_ground_day_worked"] = max(values["max_ground_day_worked"], dct.pop("max_ground_day_worked")) + for k in dct.keys(): + values["non_permanent_" + k] = dct[k] + values["used_equipments"] = self.used_equipments values["equipments_cost_planned"] = sum([cost for _1, cost, _2, _3, _4 in self.used_equipments]) values["equipments_cost_worked"] = sum([cost for _1, _2, cost, _3, _4 in self.used_equipments]) diff --git a/ishtar_common/models.py b/ishtar_common/models.py index f7512b606..a827fa902 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -2310,6 +2310,11 @@ class DocumentTemplate(models.Model): ensure_ascii=False, separators=("", " : "), ).replace(" " * 4, "\t") + # initialize missing values + if filtr: + for k in filtr: + if k not in values: + values[k] = "" engine = IshtarSecretaryRenderer() try: result = engine.render(self.template, **values) diff --git a/ishtar_common/utils_secretary.py b/ishtar_common/utils_secretary.py index e4e6e8e0b..318f50bdc 100644 --- a/ishtar_common/utils_secretary.py +++ b/ishtar_common/utils_secretary.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- from secretary import Renderer from lxml import etree @@ -7,10 +7,12 @@ 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 +from django.utils import formats RE_UNITS = re.compile("([.0-9]+)([a-z]+)") @@ -25,6 +27,28 @@ def parse_value_unit(value): return value, unit +def float_format(value): + if value is None or value == "": + return "" + try: + value = float(value) + except ValueError: + return "" + if int(value) == value: + return int(value) + return formats.localize(value, use_l10n=True) + + +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") @@ -60,7 +84,7 @@ def capitalize_filter(value): def human_date_filter(value): try: value = datetime.strptime(value, "%Y-%m-%d") - except ValueError: + except (ValueError, TypeError) as __: return "" language_code = settings.LANGUAGE_CODE.split("-") language_code = language_code[0] + "_" + language_code[1].upper() @@ -109,6 +133,8 @@ class IshtarSecretaryRenderer(Renderer): self.environment.filters["capfirst"] = capfirst_filter self.environment.filters["lowerfirst"] = lowerfirst_filter self.environment.filters["capitalize"] = capitalize_filter + self.environment.filters["float_format"] = float_format + self.environment.filters["number_to_words"] = number_to_words self.environment.filters["replace_line_breaks"] = replace_line_breaks self.environment.filters["splitpart"] = splitpart diff --git a/requirements.txt b/requirements.txt index ffc44643e..2e666c9de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -70,3 +70,5 @@ pandas==1.1.5 django-ipware==3.0.0 django-axes==5.4.3 + +num2words==0.5.9
\ No newline at end of file |