diff options
Diffstat (limited to 'archaeological_context_records')
8 files changed, 200 insertions, 21 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),  ] | 
