diff options
Diffstat (limited to 'archaeological_finds/models_treatments.py')
| -rw-r--r-- | archaeological_finds/models_treatments.py | 329 |
1 files changed, 327 insertions, 2 deletions
diff --git a/archaeological_finds/models_treatments.py b/archaeological_finds/models_treatments.py index 845da66ac..cd82ab57e 100644 --- a/archaeological_finds/models_treatments.py +++ b/archaeological_finds/models_treatments.py @@ -51,6 +51,7 @@ from ishtar_common.models import ( HistoryModel, ImageModel, MainItem, + OrderedHierarchicalType, OrderedType, Organization, OwnPerms, @@ -59,7 +60,11 @@ from ishtar_common.models import ( SearchVectorConfig, ValueGetter, ) -from ishtar_common.models_common import CompleteIdentifierItem, HistoricalRecords +from ishtar_common.models_common import ( + CompleteIdentifierItem, + IdentifierItem, + HistoricalRecords +) from ishtar_common.utils import ( cached_label_changed, get_current_year, @@ -385,6 +390,13 @@ class Treatment( null=True, on_delete=models.SET_NULL, ) + statement_condition = models.ForeignKey( + "StatementCondition", + verbose_name=_("Statement condition"), + blank=True, + null=True, + on_delete=models.SET_NULL, + ) treatment_status = models.ForeignKey( TreatmentStatus, verbose_name=_("Treatment status"), @@ -1642,10 +1654,323 @@ class TreatmentFile( m2m_changed.connect(document_attached_changed, sender=TreatmentFile.documents.through) - post_save.connect(cached_label_changed, sender=TreatmentFile) +class StatementConditionType(OrderedHierarchicalType): + class Meta: + verbose_name = _("Statement condition type") + verbose_name_plural = _("Statement condition types") + ordering = ( + "order", + "parent__label", + "label", + ) + ADMIN_SECTION = _("Treatments") + + +post_save.connect(post_save_cache, sender=StatementConditionType) +post_delete.connect(post_save_cache, sender=StatementConditionType) + + +class FollowUpActionType(OrderedHierarchicalType): + class Meta: + verbose_name = _("Follow-up action type") + verbose_name_plural = _("Follow-up action types") + ordering = ( + "order", + "parent__label", + "label", + ) + ADMIN_SECTION = _("Treatments") + + +post_save.connect(post_save_cache, sender=FollowUpActionType) +post_delete.connect(post_save_cache, sender=FollowUpActionType) + + +class StatementCondition( + DocumentItem, + BaseHistorizedItem, + IdentifierItem, + ValueGetter, +): + SLUG = "statementcondition" + APP = "archaeological-finds" + MODEL = SLUG + SHOW_URL = "show-statementcondition" + + # changing theses fields on the statement condition change them in + # associated find + OVERLOADED_FIELDS = ( + "description", + "conservatory_comment", + "length", + "width", + "height", + "volume", + "weight", + "diameter", + "circumference", + "thickness", + "clutter_long_side", + "clutter_short_side", + "clutter_height", + "dimensions_comment", + "treatment_emergency_id", + "find_number", + "museum_observed_quantity", + ) + OVERLOADED_M2M_FIELDS = ( + "integrities", + "conservatory_states", + "recommended_treatments", + "alterations", + "alteration_causes", + "museum_marking_type", + "museum_inventory_marking_presence" + ) + APPLIED_CHOICES = ( + ("D", _("Draft")), + ("V", _("Validated")), + ("T", _("Validated with treatment")), + ) + + date = models.DateField(_("Date")) + applied = models.CharField(_("Input status"), default="D", choices=APPLIED_CHOICES) + initial = models.BooleanField(_("Initial"), default=False) + last = models.BooleanField(_("Last"), default=True) + find = models.ForeignKey(Find, verbose_name=_("Find"), on_delete=models.CASCADE, + related_name="statement_conditions") + campaign_number = models.TextField(_("Campaign/observation number"), default="", + blank=True) + report_number = models.TextField(_("Report number"), default="", blank=True) + verification_officer = models.ForeignKey(Person, verbose_name=_("Verification officer"), null=True, + blank=True, on_delete=models.SET_NULL) + statement_condition_type = models.ForeignKey( + StatementConditionType, verbose_name=_("Type"), on_delete=models.PROTECT) + follow_up_actions = models.ManyToManyField( + FollowUpActionType, verbose_name=_("Follow-up actions"), blank=True + ) + observations = models.TextField(_("Observations"), blank=True, default="") + + # find field + integrities = models.ManyToManyField( + "IntegrityType", + verbose_name=_("Integrity"), + blank=True, + ) + conservatory_states = models.ManyToManyField( + "ConservatoryState", + verbose_name=_("Conservatory states"), + blank=True, + ) + recommended_treatments = models.ManyToManyField( + "RecommendedTreatmentType", + verbose_name=_("Recommended treatments"), + blank=True, + ) + treatment_emergency = models.ForeignKey( + "TreatmentEmergencyType", + verbose_name=_("Treatment emergency"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + conservatory_comment = models.TextField( + _("Conservatory comment"), blank=True, default="" + ) + alterations = models.ManyToManyField( + "AlterationType", verbose_name=_("Alteration"), blank=True + ) + alteration_causes = models.ManyToManyField( + "AlterationCauseType", verbose_name=_("Alteration cause"), blank=True, + ) + description = models.TextField(_("Description"), blank=True, default="") + # find fields - museum + museum_inventory_marking_presence = models.ManyToManyField( + "InventoryMarkingPresence", blank=True, + related_name="statement_conditions", + verbose_name=_("Presence of inventory marking"), + ) + museum_marking_type = models.ManyToManyField( + "MarkingType", + verbose_name=_("Type of marking"), + blank=True, + related_name="statement_conditions", + ) + # find field - dimensions + find_number = models.IntegerField(_("Number of remains"), blank=True, null=True) + museum_observed_quantity = models.PositiveSmallIntegerField( + _("Observed quantity"), blank=True, null=True + ) + length = models.FloatField(_("Length (cm)"), blank=True, null=True) + width = models.FloatField(_("Width (cm)"), blank=True, null=True) + height = models.FloatField(_("Height (cm)"), blank=True, null=True) + volume = models.FloatField(_("Volume (l)"), blank=True, null=True) + weight = models.FloatField(_("Weight"), blank=True, null=True) + diameter = models.FloatField(_("Diameter (cm)"), blank=True, null=True) + circumference = models.FloatField(_("Circumference (cm)"), blank=True, null=True) + thickness = models.FloatField(_("Thickness (cm)"), blank=True, null=True) + clutter_long_side = models.FloatField( + _("Clutter - long side (cm)"), blank=True, null=True + ) + clutter_short_side = models.FloatField( + _("Clutter - short side (cm)"), blank=True, null=True + ) + clutter_height = models.FloatField( + _("Clutter - height (cm)"), blank=True, null=True + ) + dimensions_comment = models.TextField( + _("Dimensions comment"), blank=True, default="" + ) + + class Meta: + verbose_name = _("Statement of condition") + verbose_name_plural = _("Statements of condition") + ordering = ("find", "-date", "cached_label") + indexes = [ + GinIndex(fields=["data"]), + ] + ADMIN_SECTION = _("Treatments") + + @property + def is_last(self): + return not self.__class__.objects.filter(date__gt=self.date).exists() + + @property + def applied_label(self): + dct = dict(self.APPLIED_CHOICES) + if self.applied not in dct: + return "-" + return dct[self.applied] + + @classmethod + def get_initial_from_find(cls, find, prefix="qa_"): + initial = {} + base_attrs = list(cls.OVERLOADED_FIELDS) + for attr in base_attrs: + initial[f"{prefix}{attr}"] = getattr(find, attr) + m2m_attrs = cls.OVERLOADED_M2M_FIELDS + for attr in m2m_attrs: + initial[f"{prefix}{attr}"] = list( + getattr(find, attr).all().values_list("id", flat=True) + ) + return initial + + def get_initial(self, prefix="qa_"): + initial = {} + base_attrs = ["pk", "date", "find_id", "statement_condition_type_id", "verification_officer_id", + "campaign_number", "report_number", "observations"] + for attr in base_attrs: + initial[attr] = getattr(self, attr) + for attr in self.OVERLOADED_FIELDS: + initial[f"{prefix}{attr}"] = getattr(self, attr) + m2m_attrs = list(self.OVERLOADED_M2M_FIELDS) + ["follow_up_actions"] + for attr in m2m_attrs: + initial[f"{prefix}{attr}"] = list( + getattr(self, attr).all().values_list("id", flat=True) + ) + return initial + + def _check_apply_validation(self): + if not self.pk: + # no previous state so apply immediatly + return True + # check previous applied is draft + return list( + self.__class__.objects.filter(pk=self.pk).values_list( + "applied", flat=True) + )[0] == "D" + + def _create_associated_treatment(self, treatment_fields): + pass + + def _create_initial_statementcondition(self): + """ + Create a reference statement condition in order to get the diff + """ + obj = self.__class__.objects.get(pk=self.pk) + obj.pk = None # duplicate + obj.applied = "V" + obj.initial = True + obj.last = False + obj.verification_officer = None + for k in ("campaign_number", "report_number", "observations"): + setattr(obj, k, "") + # reinit with find fields + for attr in self.OVERLOADED_FIELDS: + setattr(obj, attr, getattr(self.find, attr)) + obj.save() + obj.follow_up_actions.clear() + + for attr in self.OVERLOADED_M2M_FIELDS: + new_m2m = list( + sorted( + list(getattr(self.find, attr).values_list("id", flat=True)) + ) + ) + m2m = list( + sorted( + list(getattr(obj, attr).values_list("id", flat=True)) + ) + ) + if m2m == new_m2m: + continue + getattr(obj, attr).clear() + getattr(obj, attr).add(*new_m2m) + + def apply_validation(self, treatment_fields=None): + """ + Copy statement condition fields to the associated find + """ + # create a reference condition if not available + if not self.__class__.objects.filter(initial=True, find=self.find).exists(): + self._create_initial_statementcondition() + + # treatment creation + if self.applied == "T": + find = self._create_associated_treatment(treatment_fields) + if find.pk != self.find_id: # new find is created after treatment + self.find, self.find_id = find, find.pk + self.__class__.objects.filter(pk=self.pk).update(find_id=find.pk) + + # update find fields + for attr in self.OVERLOADED_FIELDS: + setattr(self.find, attr, getattr(self, attr)) + for attr in self.OVERLOADED_M2M_FIELDS: + m2m = list( + sorted( + list(getattr(self.find, attr).values_list("id", flat=True)) + ) + ) + new_m2m = list( + sorted( + list(getattr(self, attr).values_list("id", flat=True)) + ) + ) + if m2m == new_m2m: + continue + getattr(self.find, attr).clear() + getattr(self.find, attr).add(*new_m2m) + # TODO: verify find history + self.find.save() + + # update last field + self.__class__.objects.filter(find=self.find).exclude(pk=self.pk).update( + last=False) + # set last field and applied state + self.__class__.objects.filter(pk=self.pk).update(last=True, applied=self.applied) + + def save(self, *args, **kwargs): + apply_validation = False + if self.applied in ("V", "T"): + apply_validation = self._check_apply_validation() + super().save(*args, **kwargs) + if apply_validation: + self.apply_validation() + + class ExhibitionType(GeneralType): treatment_file_type = models.ForeignKey( TreatmentFileType, |
