diff options
23 files changed, 443 insertions, 50 deletions
diff --git a/archaeological_context_records/admin.py b/archaeological_context_records/admin.py index 62f4c12e2..8a0e254ae 100644 --- a/archaeological_context_records/admin.py +++ b/archaeological_context_records/admin.py @@ -54,7 +54,6 @@ class ContextRecordAdmin(HistorizedObjectAdmin, MainGeoDataItem): model = models.ContextRecord readonly_fields = HistorizedObjectAdmin.readonly_fields + [ "cached_label", - "datings", ] exclude = ["documents", "main_image"] diff --git a/archaeological_context_records/forms.py b/archaeological_context_records/forms.py index 4ecb40386..df074c768 100644 --- a/archaeological_context_records/forms.py +++ b/archaeological_context_records/forms.py @@ -179,6 +179,9 @@ class RecordSelect(GeoItemSelect, PeriodSelect): filling = forms.CharField(label=_("Filling")) interpretation = forms.CharField(label=_("Interpretation")) parcel = forms.CharField(label=_("Parcel")) + periods = forms.ChoiceField( + label=_("Periods"), choices=[], required=False + ) has_finds = forms.NullBooleanField(label=_("Has finds")) cr_relation_types = forms.ChoiceField( label=_("Search within relations"), choices=[] @@ -192,6 +195,7 @@ class RecordSelect(GeoItemSelect, PeriodSelect): TYPES = PeriodSelect.TYPES + [ FieldType('area', Area), + FieldType("periods", Period), FieldType('cultural_attributions', models.CulturalAttributionType), FieldType("unit", models.Unit), FieldType("cr_relation_types", models.RelationType), @@ -302,7 +306,10 @@ class RecordFormGeneral(CustomForm, ManageOldType): form_admin_name = _("Context record - 020 - General") form_slug = "contextrecord-020-general" file_upload = True - base_models = ["documentation", "excavation_technic", "structure", "texture", "color", "inclusion"] + base_models = [ + "documentation", "excavation_technic", "structure", "texture", "color", + "inclusion" + ] associated_models = { "archaeological_site": ArchaeologicalSite, "parcel": Parcel, @@ -527,23 +534,18 @@ class DatingForm(ManageOldType, forms.Form): ] -DatingFormSet = formset_factory(DatingForm, can_delete=True, formset=FormSet) -DatingFormSet.form_label = _("Dating") -DatingFormSet.form_admin_name = _("Context record - 030 - Dating") -DatingFormSet.form_slug = "contextrecord-030-datings" - - class RecordFormInterpretation(CustomForm, ManageOldType): HEADERS = {} form_label = _("Interpretation") form_admin_name = _("Context record - 040 - Interpretation") form_slug = "contextrecord-040-interpretation" - base_models = ["cultural_attribution", "identification"] + base_models = ["cultural_attribution", "identification", "period"] associated_models = { "activity": models.ActivityType, "identification": models.IdentificationType, 'cultural_attribution': models.CulturalAttributionType, + "period": Period, } interpretation = forms.CharField( label=_("Interpretation"), widget=forms.Textarea, required=False @@ -559,6 +561,10 @@ class RecordFormInterpretation(CustomForm, ManageOldType): taq_estimated = forms.IntegerField(label=_("Estimated TAQ"), required=False) tpq = forms.IntegerField(label=_("TPQ"), required=False) tpq_estimated = forms.IntegerField(label=_("Estimated TPQ"), required=False) + period = widgets.Select2MultipleField( + label=_("Periods"), + required=False, + ) cultural_attribution = forms.MultipleChoiceField( label=_("Cultural attributions"), choices=[], widget=widgets.Select2Multiple, @@ -573,6 +579,7 @@ class RecordFormInterpretation(CustomForm, ManageOldType): FieldType("identification", models.IdentificationType, True), FieldType('cultural_attribution', models.CulturalAttributionType, True), + FieldType("period", Period, is_multiple=True), ] diff --git a/archaeological_context_records/migrations/0124_contextrecord_periods.py b/archaeological_context_records/migrations/0124_contextrecord_periods.py new file mode 100644 index 000000000..72877aa2b --- /dev/null +++ b/archaeological_context_records/migrations/0124_contextrecord_periods.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.19 on 2025-10-28 15:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_operations', '0123_add_timezone_django_v4'), + ('archaeological_context_records', '0123_add_timezone_django_v4'), + ] + + operations = [ + migrations.AddField( + model_name='contextrecord', + name='periods', + field=models.ManyToManyField(blank=True, to='archaeological_operations.period', verbose_name='Periods'), + ), + ] diff --git a/archaeological_context_records/migrations/0125_datings_refactoring.py b/archaeological_context_records/migrations/0125_datings_refactoring.py new file mode 100644 index 000000000..585939420 --- /dev/null +++ b/archaeological_context_records/migrations/0125_datings_refactoring.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.19 on 2025-10-29 10:53 + +from django.db import migrations, models +import django.db.models.deletion +import ishtar_common.models_common +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_operations', '0123_add_timezone_django_v4'), + ('archaeological_context_records', '0124_contextrecord_periods'), + ] + + operations = [ + migrations.AlterModelOptions( + name='dating', + options={'verbose_name': 'Dating - deprecated', 'verbose_name_plural': 'Datings - deprecated'}, + ), + migrations.RenameField( + model_name='contextrecord', + old_name='datings', + new_name='datings_old', + ), + migrations.AddField( + model_name='dating', + name='external_id', + field=models.TextField(blank=True, default='', verbose_name='External ID'), + ), + migrations.AddField( + model_name='dating', + name='reference', + field=models.TextField(blank=True, default='', verbose_name='Reference'), + ), + migrations.AlterField( + model_name='dating', + name='period', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='archaeological_operations.period', verbose_name='Chronological period'), + ), + migrations.CreateModel( + name='ContextRecordDating', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4)), + ('reference', models.TextField(blank=True, default='', verbose_name='Reference')), + ('external_id', models.TextField(blank=True, default='', verbose_name='External ID')), + ('start_date', models.IntegerField(blank=True, null=True, verbose_name='Start date')), + ('end_date', models.IntegerField(blank=True, null=True, verbose_name='End date')), + ('precise_dating', models.TextField(blank=True, default='', verbose_name='Precise on this dating')), + ('context_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='datings', to='archaeological_context_records.contextrecord', verbose_name='Context record')), + ('dating_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='archaeological_context_records.datingtype', verbose_name='Dating type')), + ('period', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='archaeological_operations.period', verbose_name='Chronological period')), + ('quality', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='archaeological_context_records.datingquality', verbose_name='Quality')), + ], + options={ + 'verbose_name': 'Context record dating', + 'verbose_name_plural': 'Context record datings', + }, + bases=(models.Model, ishtar_common.models_common.SerializeItem), + ), + ] diff --git a/archaeological_context_records/migrations/0126_migrate_periods_and_datings.py b/archaeological_context_records/migrations/0126_migrate_periods_and_datings.py new file mode 100644 index 000000000..3b04c7ad1 --- /dev/null +++ b/archaeological_context_records/migrations/0126_migrate_periods_and_datings.py @@ -0,0 +1,19 @@ +from django.db import migrations +from ishtar_common.utils_migrations import migrate_dating_periods + + +def _migrate_datings_periods(apps, __): + model_dating = apps.get_model("archaeological_context_records", "contextrecorddating") + model = apps.get_model("archaeological_context_records", "contextrecord") + migrate_dating_periods(apps, model_dating, model, "context_record") + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_context_records', '0125_datings_refactoring'), + ] + + operations = [ + migrations.RunPython(_migrate_datings_periods) + ] diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index 29deae5ee..b12782e3a 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -30,7 +30,9 @@ from django.db.models import Q from django.db.models.signals import post_delete, post_save, m2m_changed from django.urls import reverse, reverse_lazy -from ishtar_common.utils import gettext_lazy as _, pgettext_lazy, pgettext +from ishtar_common.utils import get_generated_id, gettext_lazy as _, pgettext_lazy, \ + pgettext + from django.utils.text import slugify from ishtar_common.utils import ( @@ -101,12 +103,17 @@ post_save.connect(post_save_cache, sender=DatingQuality) post_delete.connect(post_save_cache, sender=DatingQuality) -class Dating(models.Model, SerializeItem): +class BaseDating(models.Model, SerializeItem): SLUG = "dating" SERIALIZE_EXCLUDE = ["find", "context_record"] + CURRENT_MODEL = 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( - Period, verbose_name=_("Chronological period"), on_delete=models.PROTECT + 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) @@ -154,8 +161,7 @@ class Dating(models.Model, SerializeItem): } class Meta: - verbose_name = _("Dating") - verbose_name_plural = _("Datings") + abstract = True def __str__(self): if self.precise_dating and self.precise_dating.strip(): @@ -294,6 +300,50 @@ class Dating(models.Model, SerializeItem): 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 = "context_record" if self.CURRENT_MODEL == ContextRecord else "find" + 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 Dating(BaseDating): + class Meta: + verbose_name = _("Dating - deprecated") + verbose_name_plural = _("Datings - deprecated") + class Unit(GeneralType): order = models.IntegerField(_("Order")) @@ -693,6 +743,10 @@ class ContextRecord( pgettext_lazy("key for text search", "excavation-technique"), "excavation_technics__label__iexact", ), + "periods": SearchAltName( + pgettext_lazy("key for text search", "period"), + "periods__label__iexact", + ), "cultural_attributions": SearchAltName( pgettext_lazy("key for text search", "cultural-attribution"), "cultural_attributions__label__iexact", @@ -790,7 +844,8 @@ class ContextRecord( ("site", "archaeological_site__pk"), ("file", "operation__associated_file__pk"), ] - HISTORICAL_M2M = ["datings", "documentations", "excavation_technics", "identifications"] + # HISTORICAL_M2M = ["datings", "documentations", "excavation_technics", "identifications"] + HISTORICAL_M2M = ["documentations", "excavation_technics", "identifications"] CACHED_LABELS = ["cached_label", "cached_periods", "cached_related_context_records"] DOWN_MODEL_UPDATE = ["base_finds"] GET_VALUES_EXTRA = ValueGetter.GET_VALUES_EXTRA + ["context_record"] @@ -917,7 +972,8 @@ class ContextRecord( default="", help_text=_("A short description of the location of the context record"), ) - datings = models.ManyToManyField(Dating, related_name="context_records") + datings_old = models.ManyToManyField(Dating, related_name="context_records") + periods = models.ManyToManyField(Period, verbose_name=_("Periods"), blank=True) documentations = models.ManyToManyField(DocumentationType, blank=True) structures = models.ManyToManyField(StructureType, blank=True) textures = models.ManyToManyField(TextureType, blank=True) @@ -1467,6 +1523,22 @@ for attr in ContextRecord.HISTORICAL_M2M: ) +class ContextRecordDating(BaseDating): + SERIALIZE_EXCLUDE = ["context_record"] + CURRENT_MODEL = ContextRecord + + context_record = models.ForeignKey( + ContextRecord, + verbose_name=_("Context record"), + related_name="datings", + on_delete=models.CASCADE, + ) + + class Meta: + verbose_name = _("Context record dating") + verbose_name_plural = _("Context record datings") + + class RelationType(GeneralRelationType): class Meta: verbose_name = _("Relation type") diff --git a/archaeological_context_records/templates/ishtar/sheet_contextrecord.html b/archaeological_context_records/templates/ishtar/sheet_contextrecord.html index 4bd770fd0..869d6edd5 100644 --- a/archaeological_context_records/templates/ishtar/sheet_contextrecord.html +++ b/archaeological_context_records/templates/ishtar/sheet_contextrecord.html @@ -55,7 +55,7 @@ <a class="nav-link" id="{{window_id}}-datations-tab" data-toggle="tab" href="#{{window_id}}-datations" role="tab" aria-controls="{{window_id}}-datations" aria-selected="false"> - {% trans "Datations" %} + {% trans "Periods / Datings" %} </a> </li> {% endif %} @@ -214,10 +214,12 @@ {% if display_datations %} <div class="tab-pane fade" id="{{window_id}}-datations" role="tabpanel" aria-labelledby="{{window_id}}-datations-tab"> - {% if dating_list %} - <h3>{% trans "Datations" %}</h3> + {% if dating_list or item.periods_count %} + <h3>{% trans "Periods / Datings" %}</h3> {% endif %} + {% field_flex_multiple_obj _("Periods") item 'periods' %} {% include "ishtar/blocks/sheet_dating_list.html" %} + {% if item.cultural_attributions_count or item.taq or item.taq_estimated or item.tpq or item.tpq_estimated or datings_comment %} <h3>{% trans "Dating complements" %}</h3> <div class='row'> {% field_flex_multiple_obj "Cultural attributions" item 'cultural_attributions' %} @@ -227,6 +229,7 @@ {% field_flex "Estimated TPQ" item.tpq_estimated %} {% field_flex_full "Comment on datings" item.datings_comment "<pre>" "</pre>" has_image %} </div> + {% endif %} </div> {% endif %} diff --git a/archaeological_context_records/views.py b/archaeological_context_records/views.py index 437a2824e..69f995932 100644 --- a/archaeological_context_records/views.py +++ b/archaeological_context_records/views.py @@ -115,7 +115,6 @@ record_search_wizard = wizards.RecordSearch.as_view( record_creation_steps = [ ("selec-record_creation", forms.OperationRecordFormSelection), ("general-record_creation", forms.RecordFormGeneral), - ("datings-record_creation", forms.DatingFormSet), ("interpretation-record_creation", forms.RecordFormInterpretation), ("final-record_creation", forms.FinalForm), ] @@ -130,7 +129,6 @@ record_modification_steps = [ ("selec-record_modification", forms.RecordFormSelection), ("operation-record_modification", forms.OperationFormSelection), ("general-record_modification", forms.RecordFormGeneral), - ("datings-record_modification", forms.DatingFormSet), ("interpretation-record_modification", forms.RecordFormInterpretation), ("final-record_modification", forms.FinalForm), ] diff --git a/archaeological_finds/admin.py b/archaeological_finds/admin.py index d6a711187..ffc51a787 100644 --- a/archaeological_finds/admin.py +++ b/archaeological_finds/admin.py @@ -61,7 +61,7 @@ admin_site.register(models.FindBasket, FindBasketAdmin) class FindAdmin(HistorizedObjectAdmin): list_display = ('label', 'operations_lbl', 'context_records_lbl', 'index', 'dating', 'materials') - list_filter = ('datings__period', 'material_types') + list_filter = ('material_types',) search_fields = ('cached_label', "base_finds__cache_complete_id") model = models.Find autocomplete_fields = HistorizedObjectAdmin.autocomplete_fields + [ @@ -76,7 +76,7 @@ class FindAdmin(HistorizedObjectAdmin): 'documents', ] readonly_fields = HistorizedObjectAdmin.readonly_fields + [ - 'datings', 'cached_label' + 'cached_label' ] diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py index 0cabfd53a..9890387b1 100644 --- a/archaeological_finds/forms.py +++ b/archaeological_finds/forms.py @@ -129,7 +129,6 @@ __all__ = [ "FindForm", "SimpleFindForm", "DateForm", - "DatingFormSet", "PreservationForm", "FindBasketFormSelection", "FindBasketForWriteFormSelection", @@ -240,6 +239,7 @@ class BasicFindForm(MuseumForm, CustomForm, ManageOldType): form_slug = "find-020-simplegeneral" base_models = [ "object_type", + "period", "material_type", "communicabilitie", "cultural_attribution", @@ -256,6 +256,7 @@ class BasicFindForm(MuseumForm, CustomForm, ManageOldType): "material_type": models.MaterialType, "cultural_attribution": CulturalAttributionType, "object_type": models.ObjectType, + "period": Period, "functional_area": models.FunctionalArea, "technical_area": models.TechnicalAreaType, "technical_processe": models.TechnicalProcessType, @@ -330,6 +331,7 @@ class BasicFindForm(MuseumForm, CustomForm, ManageOldType): "communicabilitie", "comment", "cultural_attribution", + "period", "dating_comment", "length", "width", @@ -519,6 +521,10 @@ class BasicFindForm(MuseumForm, CustomForm, ManageOldType): label=_("Cultural attribution"), required=False, ) + period = widgets.Select2MultipleField( + label=_("Periods"), + required=False, + ) dating_comment = forms.CharField( label=_("Comment on dating"), required=False, widget=forms.Textarea ) @@ -579,6 +585,7 @@ class BasicFindForm(MuseumForm, CustomForm, ManageOldType): extra_args={"full_hierarchy": True}, ), FieldType("cultural_attribution", CulturalAttributionType, is_multiple=True), + FieldType("period", Period, is_multiple=True), FieldType("material_type_quality", models.MaterialTypeQualityType), FieldType( "object_type", @@ -708,8 +715,9 @@ class FindForm(BasicFindForm): "decoration", "manufacturing_place", "communicabilitie", - "comment", "cultural_attribution", + "comment", + "period", "dating_comment", "length", "width", @@ -1610,12 +1618,6 @@ class DateForm(ManageOldType, forms.Form): ] -DatingFormSet = formset_factory(DateForm, can_delete=True, formset=FormSet) -DatingFormSet.form_label = _("Dating") -DatingFormSet.form_admin_name = _("Find - 040 - Dating") -DatingFormSet.form_slug = "find-040-dating" - - class FindSelect(MuseumForm, GeoItemSelect, PeriodSelect): _model = models.Find form_admin_name = _("Find - 001 - Search") @@ -1907,6 +1909,9 @@ class FindSelect(MuseumForm, GeoItemSelect, PeriodSelect): cultural_attributions = forms.ChoiceField( label=_("Cultural attribution"), choices=[], required=False ) + periods = forms.ChoiceField( + label=_("Periods"), choices=[], required=False + ) dating_comment = forms.CharField(label=_("Comment on dating")) length = FloatField(label=_("Length (cm)"), widget=widgets.CentimeterMeterWidget) @@ -2015,6 +2020,7 @@ class FindSelect(MuseumForm, GeoItemSelect, PeriodSelect): museum_purchase_price = forms.CharField(label=_("Museum - Purchase price")) TYPES = PeriodSelect.TYPES + [ + FieldType("periods", Period), FieldType("conservatory_states", models.ConservatoryState), FieldType("base_finds__batch", models.BatchType), FieldType("recommended_treatments", models.RecommendedTreatmentType), diff --git a/archaeological_finds/migrations/0143_find_periods.py b/archaeological_finds/migrations/0143_find_periods.py new file mode 100644 index 000000000..91a5a7f54 --- /dev/null +++ b/archaeological_finds/migrations/0143_find_periods.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.19 on 2025-10-28 15:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_operations', '0123_add_timezone_django_v4'), + ('archaeological_finds', '0142_add_timezone_django_v4'), + ] + + operations = [ + migrations.AddField( + model_name='find', + name='periods', + field=models.ManyToManyField(blank=True, to='archaeological_operations.period', verbose_name='Periods'), + ), + ] diff --git a/archaeological_finds/migrations/0144_datings_refactoring.py b/archaeological_finds/migrations/0144_datings_refactoring.py new file mode 100644 index 000000000..27131f82a --- /dev/null +++ b/archaeological_finds/migrations/0144_datings_refactoring.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.19 on 2025-10-29 10:54 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import ishtar_common.models_common +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_context_records', '0125_datings_refactoring'), + ('archaeological_operations', '0123_add_timezone_django_v4'), + ('archaeological_finds', '0143_find_periods'), + ] + + operations = [ + migrations.RenameField( + model_name='find', + old_name='datings', + new_name='datings_old', + ), + migrations.AlterField( + model_name='find', + name='preservation_to_considers', + field=models.ManyToManyField(blank=True, related_name='old_finds_recommended', to='archaeological_finds.treatmenttype', verbose_name='Recommended treatments'), + ), + migrations.CreateModel( + name='FindDating', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4)), + ('reference', models.TextField(blank=True, default='', verbose_name='Reference')), + ('external_id', models.TextField(blank=True, default='', verbose_name='External ID')), + ('start_date', models.IntegerField(blank=True, null=True, verbose_name='Start date')), + ('end_date', models.IntegerField(blank=True, null=True, verbose_name='End date')), + ('precise_dating', models.TextField(blank=True, default='', verbose_name='Precise on this dating')), + ('dating_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='archaeological_context_records.datingtype', verbose_name='Dating type')), + ('find', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='datings', to='archaeological_finds.find', verbose_name='Find')), + ('period', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='archaeological_operations.period', verbose_name='Chronological period')), + ('quality', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='archaeological_context_records.datingquality', verbose_name='Quality')), + ], + options={ + 'verbose_name': 'Find dating', + 'verbose_name_plural': 'Find datings', + }, + bases=(models.Model, ishtar_common.models_common.SerializeItem), + ), + ] diff --git a/archaeological_finds/migrations/0145_migrate_periods_and_datings.py b/archaeological_finds/migrations/0145_migrate_periods_and_datings.py new file mode 100644 index 000000000..a693f5b6d --- /dev/null +++ b/archaeological_finds/migrations/0145_migrate_periods_and_datings.py @@ -0,0 +1,19 @@ +from django.db import migrations +from ishtar_common.utils_migrations import migrate_dating_periods + + +def _migrate_datings_periods(apps, __): + model_dating = apps.get_model("archaeological_finds", "finddating") + model = apps.get_model("archaeological_finds", "find") + migrate_dating_periods(apps, model_dating, model, "find") + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_finds', '0144_datings_refactoring'), + ] + + operations = [ + migrations.RunPython(_migrate_datings_periods) + ] diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index 432bd5467..3b6392039 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -71,17 +71,18 @@ from ishtar_common.models import ( SearchVectorConfig, ValueGetter, ) -from ishtar_common.models_common import HistoricalRecords, Imported, SerializeItem, \ +from ishtar_common.models_common import HistoricalRecords, SerializeItem, \ GeoVectorData, geodata_attached_changed from ishtar_common.utils import PRIVATE_FIELDS from archaeological_operations.models import ( AdministrativeAct, - Operation, CulturalAttributionType, + Operation, + Period, ) -from archaeological_context_records.models import ContextRecord, Dating, \ +from archaeological_context_records.models import BaseDating, ContextRecord, Dating, \ GeographicSubTownItem from archaeological_warehouse.models import Warehouse @@ -1285,6 +1286,7 @@ class Find( "container__cached_label": _("Current container"), "container_ref__cached_label": _("Reference container"), "datings__period__label": _("Periods"), + "periods__label": _("Periods"), "cached_periods": _("Periods"), "material_types__label": _("Material types"), "cached_materials": _("Material types"), @@ -1322,7 +1324,7 @@ class Find( "base_finds__context_record__operation__towns__areas__parent__label", _("Extended area"), ), - ("datings__period__label", _("Chronological period")), + ("periods__label", _("Chronological period")), ("material_types__label", _("Material type")), ("object_types__label", _("Object type")), ("recommended_treatments__label", _("Recommended treatments")), @@ -1513,6 +1515,11 @@ class Find( pgettext_lazy("key for text search", "object-type"), "object_types__label__iexact", ), + "periods": SearchAltName( + pgettext_lazy("key for text search", "period"), + "periods__label__iexact", + related_name="periods", + ), "recommended_treatments": SearchAltName( pgettext_lazy("key for text search", "recommended-treatments"), "recommended_treatments__label__iexact", @@ -1958,7 +1965,7 @@ class Find( SearchVectorConfig("museum_inventory_transcript", "local"), ] M2M_SEARCH_VECTORS = [ - SearchVectorConfig("datings__period__label", "local"), + SearchVectorConfig("periods__label", "local"), SearchVectorConfig("integrities__label", "raw"), SearchVectorConfig("material_types__label", "local"), SearchVectorConfig("object_types__label", "raw"), @@ -2064,7 +2071,7 @@ class Find( HISTORICAL_M2M = [ "material_types", "technical_processes", - "datings", + # "datings", "cultural_attributions", "conservatory_states", "object_types", @@ -2207,9 +2214,10 @@ class Find( verbose_name=_("Downstream treatment"), on_delete=models.SET_NULL, ) - datings = models.ManyToManyField( + datings_old = models.ManyToManyField( Dating, verbose_name=_("Dating"), related_name="find" ) + periods = models.ManyToManyField(Period, verbose_name=_("Periods"), blank=True) cultural_attributions = models.ManyToManyField( CulturalAttributionType, verbose_name=_("Cultural attribution"), blank=True ) @@ -2937,6 +2945,10 @@ class Find( return self.documents.count() @property + def periods_count(self): + return self.periods.count() + + @property def operation(self): bf = self.get_first_base_find() if not bf or not bf.context_record or not bf.context_record.operation: @@ -3246,7 +3258,7 @@ class Find( return get_generated_id("museum_complete_identifier", self) or "" def _generate_cached_periods(self): - return " & ".join([dating.period.label for dating in self.datings.all()]) + return " & ".join([period.label for period in self.periods.all()]) def _generate_cached_object_types(self): return " & ".join([str(obj) for obj in self.object_types.all()]) @@ -3820,6 +3832,22 @@ m2m_changed.connect(base_find_find_changed, sender=Find.base_finds.through) m2m_changed.connect(document_attached_changed, sender=Find.documents.through) +class FindDating(BaseDating): + SERIALIZE_EXCLUDE = ["find"] + CURRENT_MODEL = Find + + find = models.ForeignKey( + Find, + verbose_name=_("Find"), + related_name="datings", + on_delete=models.CASCADE, + ) + + class Meta: + verbose_name = _("Find dating") + verbose_name_plural = _("Find datings") + + class FindInsideContainer(models.Model): CREATE_SQL = """ CREATE VIEW find_inside_container AS diff --git a/archaeological_finds/templates/ishtar/sheet_find.html b/archaeological_finds/templates/ishtar/sheet_find.html index 247e4c03b..34c442542 100644 --- a/archaeological_finds/templates/ishtar/sheet_find.html +++ b/archaeological_finds/templates/ishtar/sheet_find.html @@ -252,10 +252,11 @@ {% endif %} {% with dating_list=item|m2m_listing:"datings" %} - {% if dating_list or item.dating_comment or item.cultural_attributions_count %} - <h3>{% trans "Dating" %}</h3> - {% if item.cultural_attributions_count %} + {% if dating_list or item.dating_comment or item.cultural_attributions_count or item.periods_count %} + <h3>{% trans "Periods / Datings" %}</h3> + {% if item.cultural_attributions_count or item.periods_count %} <div class='row'> + {% field_flex_multiple_obj _("Periods") item 'periods' %} {% field_flex_multiple_obj "Cultural attributions" item 'cultural_attributions' %} </div> {% endif %} diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py index 751a6d973..6b612af43 100644 --- a/archaeological_finds/views.py +++ b/archaeological_finds/views.py @@ -378,7 +378,6 @@ find_creation_steps = [ ("selecrecord-find_creation", RecordFormSelectionTable), ("find-find_creation", forms.FindForm), ("preservation-find_creation", forms.PreservationForm), - ("dating-find_creation", forms.DatingFormSet), ("final-find_creation", FinalForm), ] @@ -452,7 +451,6 @@ find_modification_steps = [ ("find-find_modification", forms.FindForm), ("simplefind-find_modification", forms.SimpleFindForm), ("preservation-find_modification", forms.PreservationForm), - ("dating-find_modification", forms.DatingFormSet), ("final-find_modification", FinalForm), ] diff --git a/changelog/en/changelog_2022-06-15.md b/changelog/en/changelog_2022-06-15.md index 1c5a4c475..579a39c03 100644 --- a/changelog/en/changelog_2022-06-15.md +++ b/changelog/en/changelog_2022-06-15.md @@ -1,3 +1,13 @@ +### Bug fixes ### +- find search - fix context record criteria (#6452) +- fix regenerate cached label of childs on container move (#6480) +- person, organization tables: fix and optimize town name and person type list (#6441) +- fix permissions for imports +- fix image orientation display - do not take into account exif information (#6419) +- fix archaeological files forms when instruction deadline is removed (#6484) +- sheet - fix history view with new permissions + + v4.99.2 - 2025-09-11 -------------------- diff --git a/changelog/fr/changelog_2025-06-19.md b/changelog/fr/changelog_2025-06-19.md index e208ef542..b69f20465 100644 --- a/changelog/fr/changelog_2025-06-19.md +++ b/changelog/fr/changelog_2025-06-19.md @@ -1,3 +1,13 @@ +### Correction de dysfonctionnements ### +- recherche mobilier - correction du critère Unité d'enregistrement (#6452) +- correction regénération du libellé en cache du mobilier des contenants enfants lors du déplacement du contenant parent (#6480) +- tables personnes, organisations : correction et optimisation des libellés de communes et des listes de types de personnes (#6441) +- correction des permissions sur les imports +- correction de l'orientation des images (#6419) +- correction des formulaires de dossiers archéologiques quand le champ date d'instruction limite est supprimée (#6484) +- fiche - correction de la visualisation de l'historique + + v4.99.2 - 2025-09-11 -------------------- diff --git a/ishtar_common/migrations/0270_gis_import_key_init.py b/ishtar_common/migrations/0270_gis_import_key_init.py index ed3d8e568..030db3a25 100644 --- a/ishtar_common/migrations/0270_gis_import_key_init.py +++ b/ishtar_common/migrations/0270_gis_import_key_init.py @@ -1,16 +1,17 @@ +import sys from django.db import migrations -from ishtar_common.utils_migrations import update_import_key +from ishtar_common.utils_migrations import update_import_key, print_progress def update_importkey(apps, __): updated = 0 GeoVectorData = apps.get_model("ishtar_common", "geovectordata") - for data in GeoVectorData.objects.all(): + total = GeoVectorData.objects.count() + sys.stdout.write("\n") + for idx, data in enumerate(GeoVectorData.objects.all()): + print_progress(idx, total) updated += 1 if update_import_key(data) else 0 - if updated: - print() - print(f"* {updated} GeoVectorData import_key updated") class Migration(migrations.Migration): diff --git a/ishtar_common/migrations/0272_ishtarsiteprofile_dating_external_id.py b/ishtar_common/migrations/0272_ishtarsiteprofile_dating_external_id.py new file mode 100644 index 000000000..a3c8f278a --- /dev/null +++ b/ishtar_common/migrations/0272_ishtarsiteprofile_dating_external_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.19 on 2025-10-29 10:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0271_import_import_immediatly'), + ] + + operations = [ + migrations.AddField( + model_name='ishtarsiteprofile', + name='dating_external_id', + field=models.TextField(default='{{parent_external_id}}-{% if reference %}{{reference}}{% else %}{{auto_id}}{% endif %}', help_text='Formula to manage dating external ID. Change this with care. With incorrect formula, the application might be unusable and import of external data can be destructive.', verbose_name='Dating external id'), + ), + ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 0ffb24882..92abca547 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1525,6 +1525,17 @@ class IshtarSiteProfile(models.Model, Cached): "Formula to manage cached label. If not set a default formula is used." ), ) + dating_external_id = models.TextField( + _("Dating external id"), + default="{{parent_external_id}}-{% if reference %}{{reference}}" + "{% else %}{{auto_id}}{% endif %}", + help_text=_( + "Formula to manage dating external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) document_external_id = models.TextField( _("Document external id"), default="{index}", diff --git a/ishtar_common/templates/ishtar/blocks/sheet_dating_list.html b/ishtar_common/templates/ishtar/blocks/sheet_dating_list.html index 6135bbf77..e79d9f201 100644 --- a/ishtar_common/templates/ishtar/blocks/sheet_dating_list.html +++ b/ishtar_common/templates/ishtar/blocks/sheet_dating_list.html @@ -1,6 +1,7 @@ {% load i18n %} <table id='{{window_id}}-datings' class="table table-striped"> <tr> + <th>{% trans "Reference" %}</th> <th>{% trans "Chronological period" %}</th> <th>{% trans "Start date" %}</th> <th>{% trans "End date" %}</th> @@ -11,7 +12,10 @@ {% for dating in dating_list %} <tr> <td> - {{dating.period}} + {{dating.reference|default:"-"}} + </td> + <td> + {{dating.period|default:"-"}} </td> <td> {{dating.start_date|default_if_none:"-"}} diff --git a/ishtar_common/utils_migrations.py b/ishtar_common/utils_migrations.py index 6d75ff885..04a6d9827 100644 --- a/ishtar_common/utils_migrations.py +++ b/ishtar_common/utils_migrations.py @@ -10,6 +10,9 @@ from django.core.management import call_command from django.db import connection from django.utils.translation import gettext_lazy +from ishtar_common.utils import BColors + + HOMEPAGE_TITLE = gettext_lazy("Welcome in Ishtar, open source software for management and inventory of archaeological data") @@ -233,3 +236,39 @@ def update_import_key(geovectordata): # 0267_gis_import_key GeoVectorData.objects.filter(pk=geovectordata.id).update( import_key=import_key) return True + + +def print_progress(idx, total): + sys.stdout.write(f"\r {BColors.OKBLUE}→ Migration {idx+1}/{total}{BColors.ENDC}") + + +def migrate_dating_periods(apps, model_dating, model, dating_attr): + if not hasattr(model, "datings_old"): # migration is not relevant anymore + return + q = model.objects.filter(datings_old__pk__isnull=False) + if not q.count(): + return + period_attr = ["start_date", "end_date", "dating_type", "quality", "precise_dating"] + full_period_attr = period_attr + ["period"] + sys.stdout.write("\n") + total = q.count() + for idx, item in enumerate(q.all()): + print_progress(idx, total) + for idx_dating, dating in enumerate(item.datings_old.all()): + if not dating.period: + # should not occur as for old dating period was required + continue + item.periods.add(dating.period) + has_more = False + for attr in period_attr: + if hasattr(dating, attr) and getattr(dating, attr): + has_more = True + break + if not has_more: + # do not recreate a new dating, it is not relevant anymore + continue + new_attrs = dict((k, getattr(dating, k)) for k in full_period_attr) + new_attrs[dating_attr] = item + new_attrs["external_id"] = f"{item.external_id}-{idx_dating + 1}" + model_dating.objects.create(**new_attrs) + |
