diff options
| -rw-r--r-- | archaeological_warehouse/models.py | 16 | ||||
| -rw-r--r-- | changelog/en/changelog_2022-06-15.md | 29 | ||||
| -rw-r--r-- | changelog/fr/changelog_2025-06-19.md | 29 | ||||
| -rw-r--r-- | docs/fr/source/annexe-tech-4-jinja-filters.rst | 9 | ||||
| -rw-r--r-- | ishtar_common/data_importer.py | 27 | ||||
| -rw-r--r-- | ishtar_common/jinja_filters.py | 13 | ||||
| -rw-r--r-- | ishtar_common/models.py | 162 | ||||
| -rw-r--r-- | ishtar_common/models_common.py | 12 | ||||
| -rw-r--r-- | ishtar_common/utils.py | 3 | ||||
| -rw-r--r-- | ishtar_common/utils_secretary.py | 3 | ||||
| -rw-r--r-- | ishtar_common/version.py | 4 |
11 files changed, 275 insertions, 32 deletions
diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index 35ef88147..093d56656 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -1322,6 +1322,22 @@ class Container( def natural_key(self): return (self.uuid,) + @property + def location_lbl(self): + return str(self.location or "") + + @property + def responsability_lbl(self): + return str(self.responsibility or "") + + def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): + if "simple" in kwargs: + keys = ["reference", "container_type", "code", "cached_location", + "cached_location", "index", "custom_index", "complete_identifier", + "responsability_lbl", "location_lbl"] + return self._get_values(keys, prefix=prefix, filtr=filtr, **kwargs) + return super().get_values(prefix, no_values, filtr, **kwargs) + @classmethod @pre_importer_action def import_get_location(cls, context, value): diff --git a/changelog/en/changelog_2022-06-15.md b/changelog/en/changelog_2022-06-15.md index 25c0cc9a4..d69fdb6c3 100644 --- a/changelog/en/changelog_2022-06-15.md +++ b/changelog/en/changelog_2022-06-15.md @@ -1,3 +1,32 @@ +v4.99.36 - 2026-05-22 +--------------------- + +### Bug fixes ### +- imports: fix update + + +v4.99.35 - 2026-05-22 +--------------------- + +### Technical ### +- templates: + - year filter for dates + - improve and add fields for documents + + +v4.99.34 - 2026-05-20 +--------------------- + +### Features/improvements ### +- UI: dark/light mode switcher + +### Bug fixes ### +- map: fix bad display of OpenStreetMap tiles (force referrerPolicy) + +### Technical ### +- GIS API: fix import on some configuration + + v4.99.33 - 2026-05-06 --------------------- diff --git a/changelog/fr/changelog_2025-06-19.md b/changelog/fr/changelog_2025-06-19.md index cc0164c4e..f013ec912 100644 --- a/changelog/fr/changelog_2025-06-19.md +++ b/changelog/fr/changelog_2025-06-19.md @@ -1,3 +1,32 @@ +v4.99.36 - 2026-05-22 +--------------------- + +### Correction de dysfonctionnements ### +- imports : corrections sur les mises à jour + + +v4.99.35 - 2026-05-22 +--------------------- + +### Technique ### +- gabarits : + - filtre d'année pour les dates + - amélioration et ajout de champs pour les documents + + +v4.99.34 - 2026-05-20 +--------------------- + +### Fonctionnalités/améliorations ### +- interface : bouton de transition interface claire/sombre + +### Correction de dysfonctionnements ### +- carte : correction de l'affichage de certains tuiles OpenStreetMap (utilistation de la referrerPolicy) + +### Technique ### +- API SIG : corretion de l'import pour certaines configurations + + v4.99.33 - 2026-05-06 --------------------- diff --git a/docs/fr/source/annexe-tech-4-jinja-filters.rst b/docs/fr/source/annexe-tech-4-jinja-filters.rst index d112249bc..0b2e5032b 100644 --- a/docs/fr/source/annexe-tech-4-jinja-filters.rst +++ b/docs/fr/source/annexe-tech-4-jinja-filters.rst @@ -7,7 +7,7 @@ Annexe technique 4 - Filtres et variables pour les patrons de documents ======================================================================= :Auteurs: Étienne Loks, Valérie-Emma Leroux -:Date: 2024-10-08 +:Date: 2026-05-21 :Copyright: CC-BY 3.0 ---------------------------------- @@ -66,6 +66,13 @@ Ce filtre permet d'afficher une date avec le format JJ/MM/AAAA. - `{{\"2020-03-28\"|short_date}}` -> `28/03/2020` + +- **year** + +Ce filtre permet d'afficher l'année d'une date. + + - `{{\"2020-03-03\"|year}}` -> `2020` + - **int** Pour afficher un nombre sans décimales. diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py index 33fa2f295..398ca6e14 100644 --- a/ishtar_common/data_importer.py +++ b/ishtar_common/data_importer.py @@ -1951,19 +1951,21 @@ class Importer: else: self.new_objects.append((path, cls, new_dct)) else: - # manage UNICITY_KEYS - only level 1 + new_values = {} + # manage UNICITY_KEYS - only level 1 -> no path if not path and self.UNICITY_KEYS: get_by_unicity_key = True for k in list(dct.keys()): if k not in self.UNICITY_KEYS and k != "defaults": if dct[k]: - defaults[k] = dct.pop(k) + new_values[k] = dct.pop(k) else: dct.pop(k) if "get_default" in dct and dct["get_default"]: dct.pop("get_default") new_dct = defaults.copy() new_dct.update(dct) + new_dct.update(new_values) dct = new_dct if self.simulate: @@ -1996,7 +1998,7 @@ class Importer: self.updated_objects.append([path, q.all()[0], dct, {}]) dct["defaults"] = defaults.copy() else: - if not dct and not defaults: + if not dct and not defaults and not new_values: obj = None else: if ( @@ -2009,6 +2011,7 @@ class Importer: else: created = True new_dct = dct.copy() + new_dct.update(new_values) for k in defaults: if k not in dct: new_dct[k] = defaults[k] @@ -2045,7 +2048,23 @@ class Importer: if k in self.concat_str: sep = self.concat_str[k] updated_dct[k] = val + sep + new_val - else: # TODO: manage conservative + for k in new_values: + new_val = new_values[k] + if new_val is None or new_val == "": + continue + val = getattr(obj, k) + if obj and k == "data": + updated_dct[k] = update_data(obj.data, new_val) + elif ( + k in self.concats + and type(val) == str + and type(new_val) == str + ): + sep = "" + if k in self.concat_str: + sep = self.concat_str[k] + updated_dct[k] = val + sep + new_val + else: updated_dct[k] = new_val if updated_dct: if self.simulate: diff --git a/ishtar_common/jinja_filters.py b/ishtar_common/jinja_filters.py index 64d22bf10..0c00401a8 100644 --- a/ishtar_common/jinja_filters.py +++ b/ishtar_common/jinja_filters.py @@ -131,11 +131,22 @@ def capitalize_filter(*args): @environmentfilter +def year_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): + return "" + set_locale() + return value.year + + +@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 __: + except (ValueError, TypeError): return "" set_locale() return value.strftime(settings.DATE_FORMAT) diff --git a/ishtar_common/models.py b/ishtar_common/models.py index af78961a3..dbd4ba72a 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -285,6 +285,7 @@ class ValueGetter: "point", "history_m2m", ] + GET_VALUES_DEFAULT = [] # empty values initialized GET_VALUES_M2M = [] def _get_values_documents(self, prefix="", filtr=None): @@ -314,11 +315,34 @@ class ValueGetter: return return [k[len(prefix):] for k in filtr if k.startswith(prefix)] + def _get_values(self, keys, prefix="", filtr=None, **kwargs): + """ + Generic get values from a list of attributes. + """ + values = {} + kw = copy.deepcopy(kwargs) + kw["simple"] = True + for key in keys: + if filtr and prefix + key not in filtr: + continue + value = getattr(self, key) + if hasattr(value, "get_values"): + value = value.get_values(prefix=prefix, no_values=True, + filtr=filtr, **kw) + elif hasattr(value, "all"): + value = [i.get_values( + prefix=prefix, no_values=True, filtr=filtr, **kw) + if hasattr(i, "get_values") else str(i) for i in value.all()] + else: + value = str(value) if value is not None else "" + values[prefix + key] = value + return values + def get_extra_values(self, prefix="", no_values=False, filtr=None, **kwargs): return {} def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): - if not prefix: + if not prefix and "simple" not in kwargs: prefix = self._prefix if not filtr: filtr = [] @@ -335,6 +359,8 @@ class ValueGetter: return values exclude = kwargs.get("exclude", []) values = {} + for key in self.GET_VALUES_DEFAULT: + values[key] = "" if ( # qrcode for basefind is not relevant and interfere with find qrcode getattr(self, "SLUG", "") != "basefind" @@ -372,7 +398,11 @@ class ValueGetter: new_exclude.append(s) new_kwargs = kwargs.copy() new_kwargs["exclude"] = new_exclude - values.update(value.get_values(new_prefix, filtr=filtr, **kwargs)) + new_value = value.get_values(new_prefix, filtr=filtr, **kwargs) + if isinstance(new_value, dict): + values.update(new_value) + else: + values[prefix + field_name] = new_value if hasattr(self, "get_values_for_" + field_name): values[prefix + field_name] = getattr( self, "get_values_for_" + field_name @@ -3151,6 +3181,23 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): rights=["ishtar_common.change_person"], ) QUICK_ACTIONS = [QA_EDIT] + GET_VALUES_EXCLUDE_FIELDS = [ + "adminact_operation_in_charge", "adminact_scientist", "archived", + "author", "biographical_notes", "cached_profiles", + "context_record_excavation", "country", "created", "editors", + "exhibitions", "file_responsability", "gdpr_person", + "general_contractor_files", "history_creator", "history_creator_id", + "history_modifier", "history_modifier_id", "imports", "imports_updated", + "ishtar_users", "ishtaruser", "last_modified", "lock_user", "lock_user_id", + "locked", "manage_treatments", "merge_candidate", "merge_exclusion", + "merge_key", "minutes_writer", "need_update", "old_title", + "operation_collaborator", "operation_monitoring", "operation_protagonist", + "operation_scientist_responsability", "parcel_owner", "precise_town_id", + "profiles", "properties", "responsible_town_planning_service_files", + "salutation", "timestamp_geo", "timestamp_label", + "treatmentfile_applicant", "treatmentfile_responsability", "treatments", + "uuid", "warehouse_in_charge" + ] objects = UUIDModelManager() @@ -4659,7 +4706,7 @@ post_save.connect(post_save_cache, sender=AuthorType) post_delete.connect(post_save_cache, sender=AuthorType) -class Author(FullSearch): +class Author(FullSearch, ValueGetter): SLUG = "author" PARENT_SEARCH_VECTORS = ["person"] @@ -4709,6 +4756,10 @@ class Author(FullSearch): + list(self.contextrecordsource_related.all()) ) + def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): + keys = ["author_type", "person"] + return self._get_values(keys, prefix, filtr, **kwargs) + def public_representation(self): return {"type": str(self.author_type), "person": str(self.person)} @@ -4838,6 +4889,7 @@ class SourceType(OrderedHierarchicalType): help_text=_("Setting a language for this type of document is relevant"), ) code = models.CharField(_("Code"), blank=True, default="", max_length=100) + GET_VALUES_EXTRA = ["code"] class Meta: verbose_name = _("Document type") @@ -4845,6 +4897,19 @@ class SourceType(OrderedHierarchicalType): ordering = ("parent__order", "parent__label", "order", "label",) ADMIN_SECTION = _("Documents") + def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): + values = super().get_values(prefix, no_values, filtr, **kwargs) + if "no_hierarchy" in kwargs or not isinstance(values, dict) or \ + "source_type_parent" not in values: + return values + result = [] + v = copy.deepcopy(values) + while v.get("source_type_parent", None): + result.append(v["source_type_parent"]["source_type"]) + v = v["source_type_parent"] + values["source_type_hierarchy"] = list(reversed(result)) + return values + post_save.connect(post_save_cache, sender=SourceType) post_delete.connect(post_save_cache, sender=SourceType) @@ -5014,6 +5079,8 @@ class Document( LINK_SPLIT = "<||>" GET_VALUES_EXCLUDE_FIELDS = ValueGetter.GET_VALUES_EXCLUDE_FIELDS + [ + "container_id", + "container_ref_id", "warehouses", "operations", "treatments", @@ -5554,7 +5621,7 @@ class Document( class Meta: verbose_name = _("Document") verbose_name_plural = _("Documents") - ordering = ("title",) + ordering = ("source_type__code", "custom_index", "title",) permissions = ( ("view_own_document", gettext("Can view own Document")), ("change_own_document", gettext("Can change own Document")), @@ -5588,6 +5655,36 @@ class Document( ) ) + @property + def operations_full_lbl(self): + operations = list(self.operations.values_list("cached_label", flat=True).all()) + return " ; ".join(sorted(operations)) + + @property + def context_records_full_lbl(self): + crs = list(self.context_records.values_list("cached_label", flat=True).all()) + return " ; ".join(sorted(crs)) + + @property + def finds_full_lbl(self): + finds = list(self.finds.values_list("cached_label", flat=True).all()) + return " ; ".join(sorted(finds)) + + @property + def operations_lbl(self): + operations = list(self.operations.values_list("code_patriarche", flat=True).all()) + return " ; ".join(sorted(operations)) + + @property + def context_records_lbl(self): + crs = list(self.context_records.values_list("label", flat=True).all()) + return " ; ".join(sorted(crs)) + + @property + def finds_lbl(self): + finds = list(self.finds.values_list("label", flat=True).all()) + return " ; ".join(sorted(finds)) + @post_importer_action def set_main_image(self, __, value): """ @@ -5666,25 +5763,28 @@ class Document( Context({"document": self}) ) - @property - def container(self): - if not self.container_id: + def _get_container(self, attr): + cache_attr = f"_{attr}" + if hasattr(self, cache_attr): + return getattr(self, cache_attr) + id_attr = f"{attr}_id" + if not getattr(self, id_attr): + setattr(self, cache_attr, None) return Container = apps.get_model("archaeological_warehouse", "Container") try: - return Container.objects.get(pk=self.container_id) + setattr(self, cache_attr, Container.objects.get(pk=getattr(self, id_attr))) + return getattr(self, cache_attr) except Container.DoesNotExist: return @property + def container(self): + return self._get_container("container") + + @property def container_ref(self): - if not self.container_ref_id: - return - Container = apps.get_model("archaeological_warehouse", "Container") - try: - return Container.objects.get(pk=self.container_ref_id) - except Container.DoesNotExist: - return + return self._get_container("container_ref") @property def pdf_attached(self): @@ -5810,14 +5910,36 @@ class Document( return "" return self.image.path + GET_VALUES_DEFAULT = ["source_type_hierarchy"] + def get_extra_values(self, prefix="", no_values=False, filtr=None, **kwargs): - values = {} - if not filtr or prefix + "image_path" in filtr: - values[prefix + "image_path"] = self.image_path - if not filtr or prefix + "thumbnail_path" in filtr: - values[prefix + "thumbnail_path"] = self.thumbnail_path + keys = ["image_path", "thumbnail_path", "authors", "operations_lbl", + "context_records_lbl", "finds_lbl", "operations_full_lbl", + "context_records_full_lbl", "finds_full_lbl"] + values = self._get_values(keys, prefix, filtr, **kwargs) + for attr in ("container", "container_ref"): + if filtr and attr not in filtr: + continue + values[attr] = {} + container = getattr(self, attr) + if container: + values[attr] = container.get_values(simple=True) return values + get_extra_values.full_doc = _(""" +* `image_path` +* `thumbnail_path` +* `authors` +* `container` +* `container_ref` +* `operations_lbl` +* `operations_full_lbl` +* `context_records_lbl` +* `context_records_full_lbl` +* `finds_lbl` +* `finds_full_lbl` +""") + @property def images_without_main_image(self): return [] diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index 573d4f8bf..de1de3a91 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -193,6 +193,7 @@ class GeneralType(Cached, models.Model): available = models.BooleanField(_("Available"), default=True) HELP_TEXT = "" objects = TypeManager() + GET_VALUES_EXTRA = [] class Meta: abstract = True @@ -207,10 +208,15 @@ class GeneralType(Cached, models.Model): return self.txt_idx def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): + if "simple" in kwargs: + return str(self) dct = {} if "parent_level" in kwargs and kwargs["parent_level"] > 5: return dct - if not prefix: # prefix is mandatory + for key in self.GET_VALUES_EXTRA: + p = (prefix[:]) if prefix else "" + dct[p + key] = str(getattr(self, key)) + if not prefix or "simple" in kwargs: # prefix is mandatory return dct else: dct[prefix[:-1]] = str(self) @@ -220,8 +226,10 @@ class GeneralType(Cached, models.Model): kwargs["parent_level"] += 1 else: kwargs["parent_level"] = 1 + kw = copy.deepcopy(kwargs) + kw["no_hierarchy"] = True dct[prefix + "parent"] = self.parent.get_values( - prefix=prefix + "parent_", no_values=no_values, filtr=filtr, **kwargs) + prefix=prefix, no_values=no_values, filtr=filtr, **kw) return dct @classmethod diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index cb6511d9d..30bf4b7db 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -79,7 +79,7 @@ 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, short_date_filter, splitpart + number_to_words, replace_line_breaks, short_date_filter, splitpart, year_filter if settings.USE_TRANSLATION_OVERLOAD: @@ -2868,6 +2868,7 @@ EXTRA_JINJA_FILTERS = { "euro_format": euro_format, "number_to_words": number_to_words, "replace_line_breaks": replace_line_breaks, + "year": year_filter } diff --git a/ishtar_common/utils_secretary.py b/ishtar_common/utils_secretary.py index 77a2cbc9b..c3da79ea1 100644 --- a/ishtar_common/utils_secretary.py +++ b/ishtar_common/utils_secretary.py @@ -12,7 +12,7 @@ from django.conf import settings from .jinja_filters import capfirst_filter, capitalize_filter, \ euro_format, float_format, human_date_filter, lowerfirst_filter, \ - number_to_words, replace_line_breaks, short_date_filter, splitpart + number_to_words, replace_line_breaks, short_date_filter, splitpart, year_filter RE_UNITS = re.compile("([.0-9]+)([a-z]+)") @@ -66,6 +66,7 @@ class IshtarSecretaryRenderer(Renderer): self.environment.filters["add"] = add_filter self.environment.filters["sub"] = sub_filter self.environment.filters["short_date"] = short_date_filter + self.environment.filters["year"] = year_filter def ishtar_media_loader(self, media, *args, **kwargs): res = self.fs_loader(media, *args, **kwargs) diff --git a/ishtar_common/version.py b/ishtar_common/version.py index 0b65315f1..925e3b5d9 100644 --- a/ishtar_common/version.py +++ b/ishtar_common/version.py @@ -1,5 +1,5 @@ -# 4.99.33 -VERSION = (4, 99, 33) +# 4.99.36 +VERSION = (4, 99, 36) def get_version(): |
