summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--archaeological_context_records/admin.py1
-rw-r--r--archaeological_context_records/forms.py23
-rw-r--r--archaeological_context_records/migrations/0124_contextrecord_periods.py19
-rw-r--r--archaeological_context_records/migrations/0125_datings_refactoring.py62
-rw-r--r--archaeological_context_records/migrations/0126_migrate_periods_and_datings.py19
-rw-r--r--archaeological_context_records/models.py86
-rw-r--r--archaeological_context_records/templates/ishtar/sheet_contextrecord.html9
-rw-r--r--archaeological_context_records/views.py2
-rw-r--r--archaeological_finds/admin.py4
-rw-r--r--archaeological_finds/forms.py22
-rw-r--r--archaeological_finds/migrations/0143_find_periods.py19
-rw-r--r--archaeological_finds/migrations/0144_datings_refactoring.py50
-rw-r--r--archaeological_finds/migrations/0145_migrate_periods_and_datings.py19
-rw-r--r--archaeological_finds/models_finds.py44
-rw-r--r--archaeological_finds/templates/ishtar/sheet_find.html7
-rw-r--r--archaeological_finds/views.py2
-rw-r--r--changelog/en/changelog_2022-06-15.md10
-rw-r--r--changelog/fr/changelog_2025-06-19.md10
-rw-r--r--ishtar_common/migrations/0270_gis_import_key_init.py11
-rw-r--r--ishtar_common/migrations/0272_ishtarsiteprofile_dating_external_id.py18
-rw-r--r--ishtar_common/models.py11
-rw-r--r--ishtar_common/templates/ishtar/blocks/sheet_dating_list.html6
-rw-r--r--ishtar_common/utils_migrations.py39
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)
+