From 9f88fca5528b76f3493a0a5b5e5d14e030b2dcf8 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 22 May 2026 09:54:12 +0200 Subject: ✨ improvements for templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add fields for document - defaut inititialization (prevent crash when indexing a list) - add a hierarchy variable for document source types --- archaeological_warehouse/models.py | 16 ++++ ishtar_common/models.py | 162 ++++++++++++++++++++++++++++++++----- ishtar_common/models_common.py | 12 ++- 3 files changed, 168 insertions(+), 22 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/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 -- cgit v1.2.3