diff options
| author | Étienne Loks <etienne.loks@iggdrasil.net> | 2026-03-25 19:11:01 +0100 |
|---|---|---|
| committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2026-03-25 19:12:27 +0100 |
| commit | 28ae853d43338ca9816cdbe1b2887ff48f94d1ca (patch) | |
| tree | 23e03147817726bfa26fe05597993585a773fdb3 /ishtar_common | |
| parent | 357b31ed1526b0627e1fae90ae261a46ee28e322 (diff) | |
| download | Ishtar-28ae853d43338ca9816cdbe1b2887ff48f94d1ca.tar.bz2 Ishtar-28ae853d43338ca9816cdbe1b2887ff48f94d1ca.zip | |
♻️ move BaseDating to ishtar_common
Diffstat (limited to 'ishtar_common')
| -rw-r--r-- | ishtar_common/models.py | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 90e8a8d8d..c8a2f4f45 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -180,6 +180,7 @@ from ishtar_common.models_common import ( post_save_cache, QuickAction, SearchVectorConfig, + SerializeItem, SpatialReferenceSystem, TemplateItem, ShortMenuItem, @@ -6248,6 +6249,250 @@ post_save.connect(post_save_cache, sender=OperationType) post_delete.connect(post_save_cache, sender=OperationType) +class BaseDating(models.Model, SerializeItem): + SLUG = "dating" + SERIALIZE_EXCLUDE = ["find", "context_record", "archaeological_site"] + CURRENT_MODEL = None + CURRENT_MODEL_ATTR = None + uuid = models.UUIDField(default=uuid.uuid4) + reference = models.TextField(_("Reference"), blank=True, default="") + external_id = models.TextField(_("External ID"), blank=True, default="") + period = models.ForeignKey( + "archaeological_operations.Period", verbose_name=_("Chronological period"), + on_delete=models.PROTECT, + blank=True, + null=True, + ) + start_date = models.IntegerField(_("Start date"), blank=True, null=True) + end_date = models.IntegerField(_("End date"), blank=True, null=True) + dating_type = models.ForeignKey( + "archaeological_context_records.DatingType", + verbose_name=_("Dating type"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + quality = models.ForeignKey( + "archaeological_context_records.DatingQuality", + verbose_name=_("Quality"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + precise_dating = models.TextField(_("Precise on this dating"), blank=True, default="") + objects = UUIDModelManager() + ASSOCIATED_ALT_NAMES = { + "datings__period": SearchAltName( + pgettext_lazy("key for text search", "datings-period"), + "datings__period__label__iexact", + ), + "datings__precise_dating": SearchAltName( + pgettext_lazy("key for text search", "datings-precise"), + "datings__precise_dating__iexact", + ), + "datings__start_date": SearchAltName( + pgettext_lazy("key for text search", "datings-start"), + "datings__start_date", + ), + "datings__end_date": SearchAltName( + pgettext_lazy("key for text search", "datings-end"), + "datings__end_date", + ), + "datings__dating_type": SearchAltName( + pgettext_lazy("key for text search", "datings-type"), + "datings__dating_type__label__iexact", + ), + "datings__quality": SearchAltName( + pgettext_lazy("key for text search", "datings-quality"), + "datings__quality__label__iexact", + ), + } + + class Meta: + abstract = True + + def __str__(self): + if self.precise_dating and self.precise_dating.strip(): + return self.precise_dating.strip() + start_date = self.start_date and str(self.start_date) or "" + end_date = self.end_date and str(self.end_date) or "" + if not start_date and not end_date: + return str(self.period) + return "%s (%s-%s)" % (self.period, start_date, end_date) + + def natural_key(self): + return (self.uuid,) + + def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): + values = {} + if not filtr or prefix + "reference" in filtr: + values[prefix + "reference"] = self.reference + if not filtr or prefix + "period" in filtr: + values[prefix + "period"] = str(self.period) + if not filtr or prefix + "start_date" in filtr: + values[prefix + "start_date"] = self.start_date or "" + if not filtr or prefix + "end_date" in filtr: + values[prefix + "end_date"] = self.end_date or "" + if not filtr or prefix + "dating_type" in filtr: + values[prefix + "dating_type"] = ( + str(self.dating_type) if self.dating_type else "" + ) + if not filtr or prefix + "quality" in filtr: + values[prefix + "quality"] = str(self.quality) if self.quality else "" + if not filtr or prefix + "precise_dating" in filtr: + values[prefix + "precise_dating"] = self.precise_dating + return values + + HISTORY_ATTR = [ + "reference", + "period", + "start_date", + "end_date", + "dating_type", + "quality", + "precise_dating", + ] + + def history_compress(self): + values = {} + attrs = self.HISTORY_ATTR + [self.CURRENT_MODEL_ATTR + "_id"] + for attr in attrs: + val = getattr(self, attr) + if hasattr(val, "history_compress"): + val = val.history_compress() + elif hasattr(val, "isoformat"): + val = val.isoformat() + elif val is None: + val = "" + else: + val = str(val) + values[attr] = val + return values + + @classmethod + def history_decompress(cls, full_value, create=False): + if not full_value: + return [] + full_res = [] + for value in full_value: + res = {} + for key in value: + val = value[key] + if val == "" and key != "precise_dating": + val = None + elif key in ("period", "dating_type", "quality"): + field = cls._meta.get_field(key) + q = field.related_model.objects.filter(txt_idx=val) + if q.count(): + val = q.all()[0] + else: # do not exist anymore in db + val = None + elif key in ("start_date", "end_date"): + val = int(val) + res[key] = val + if create: + res = cls.objects.create(**res) + full_res.append(res) + return full_res + + @classmethod + def is_identical(cls, dating_1, dating_2): + """ + Compare two dating attribute by attribute and return True if all + attribute is identical + """ + for attr in [ + "reference", + "period", + "start_date", + "end_date", + "dating_type", + "quality", + "precise_dating", + ]: + value1 = getattr(dating_1, attr) + value2 = getattr(dating_2, attr) + if attr == "precise_dating": + if value1: + value1 = value1.strip() + if value2: + value2 = value2.strip() + if value1 != value2: + return False + return True + + def context_records_lbl(self): + return " - ".join(cr.cached_label for cr in self.context_records.all()) + + context_records_lbl.short_description = _("Context record") + context_records_lbl.admin_order_field = "context_records__cached_label" + + def finds_lbl(self): + return " - ".join(f.cached_label for f in self.find.all()) + + finds_lbl.short_description = _("Find") + finds_lbl.admin_order_field = "find__cached_label" + + @classmethod + def fix_dating_association(cls, obj): + """ + Fix redundant m2m dating association (usually after imports) + """ + current_datings = [] + for dating in obj.datings.order_by("pk").all(): + key = ( + dating.period.pk, + dating.reference, + dating.start_date, + dating.end_date, + dating.dating_type, + dating.quality, + dating.precise_dating, + ) + if key not in current_datings: + current_datings.append(key) + continue + dating.delete() + + @property + def q_parent(self): + if not self.pk or not self.CURRENT_MODEL: + return + if getattr(self, "__q_parent", None): + return self.__q_parent + q = self.CURRENT_MODEL.objects.filter(datings__pk=self.pk) + if q.count(): + self.__q_parent = q + return q + + @property + def parent_external_id(self): + if not self.pk or not self.q_parent: + return "" + return self.q_parent.all()[0].external_id + + @property + def auto_id(self): + if not self.pk or not self.q_parent: + return 0 + parent_pk = self.q_parent.all()[0].pk + attr = self.CURRENT_MODEL_ATTR + for idx, dating_pk in enumerate( + self.__class__.objects.filter(**{attr: parent_pk}).values_list( + "pk", flat=True).all()).all(): + if dating_pk == self.pk: + return idx + 1 + return 0 + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if not self.pk: + return + external_id = get_generated_id("dating_external_id", self) + if external_id != self.external_id: + self.__class__.objects.filter(pk=self.pk).update(external_id=external_id) + + class AdministrationScript(models.Model): path = models.CharField(_("Filename"), max_length=30) name = models.TextField(_("Name"), blank=True, default="") |
