#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2025 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # See the file COPYING for details. """ Context records forms definitions """ from collections import OrderedDict from copy import copy from itertools import groupby from bootstrap_datepicker.widgets import DateField from django import forms from django.conf import settings from django.core import validators from django.forms.formsets import formset_factory from ishtar_common.utils import get_current_profile, gettext_lazy as _ from ishtar_common.models import ( Area, IshtarSiteProfile, Town, SpatialReferenceSystem, valid_id, valid_ids, ) from archaeological_context_records import models from ishtar_common.forms import ( FinalForm, FormSet, reverse_lazy, get_form_selection, ManageOldType, CustomForm, FieldType, CustomFormSearch, IshtarForm, FormHeader, MultiSearchForm, LockForm, GeoItemSelect, QAForm, ) from ishtar_common.forms_common import get_town_field from archaeological_operations.forms import ( OperationSelect, RecordRelationsForm as OpeRecordRelationsForm, RecordRelationsFormSetBase, ) from ishtar_common.models import Person from archaeological_operations.models import ( Period, Parcel, Operation, ArchaeologicalSite, RelationType as OpeRelationType, ) from archaeological_operations.widgets import OAWidget from ishtar_common import widgets class OperationFormSelection(CustomForm, forms.Form): form_label = _("Operation") form_admin_name = _("Context record - 010 - Operation choice") form_slug = "contextrecord-010-operationchoice" associated_models = {"operation": Operation} currents = {"operation": Operation} operation = forms.IntegerField( label=_("Operation"), required=False, widget=widgets.JQueryAutoComplete( reverse_lazy("autocomplete-operation"), associated_model=Operation ), validators=[valid_id(Operation)], ) class PeriodSelect(forms.Form): datings__period = forms.ChoiceField(label=_("Dating - period"), choices=[]) datings__precise_dating = forms.CharField(label=_("Dating - precise")) datings__start_date = forms.IntegerField(label=_("Dating - start date")) datings__end_date = forms.IntegerField(label=_("Dating - end date")) datings__dating_type = forms.ChoiceField( label=_("Dating - dating type"), choices=[] ) datings__quality = forms.ChoiceField(label=_("Dating - dating quality"), choices=[]) TYPES = [ FieldType("datings__period", Period), FieldType("datings__dating_type", models.DatingType), FieldType("datings__quality", models.DatingQuality), ] PERIOD_FIELDS = [ "datings__period", "datings__precise_dating", "datings__start_date", "datings__end_date", "datings__dating_type", "datings__quality", ] def _reorder_period_fields(self, insert_period_after): fields = OrderedDict() for key in self.fields: if key in self.PERIOD_FIELDS: continue fields[key] = self.fields[key] if key == insert_period_after: for period_key in self.PERIOD_FIELDS: if period_key not in self.fields: continue fields[period_key] = self.fields[period_key] if period_key in self.fields: fields[period_key] = self.fields[period_key] self.fields = fields class RecordSelect(GeoItemSelect, PeriodSelect): _model = models.ContextRecord form_admin_name = _("Context record - 001 - Search") form_slug = "contextrecord-001-search" search_vector = forms.CharField( label=_("Full text search"), widget=widgets.SearchWidget("archaeological-context-records", "contextrecord"), ) label = forms.CharField(label=_("ID"), max_length=100) town = get_town_field() area = forms.ChoiceField(label=_("Area"), choices=[]) if settings.COUNTRY == "fr": operation__code_patriarche = forms.CharField( max_length=500, widget=OAWidget, label=_("Code PATRIARCHE") ) operation__common_name = forms.CharField(label=_("Operation - name"), max_length=30) operation__year = forms.IntegerField(label=_("Operation - year")) operation__operation_code = forms.IntegerField( label=_("Operation's number (index by year)") ) archaeological_site = forms.IntegerField( label=_("Archaeological site"), widget=widgets.JQueryAutoComplete( reverse_lazy("autocomplete-archaeologicalsite"), associated_model=ArchaeologicalSite, ), validators=[valid_id(ArchaeologicalSite)], ) ope_relation_types = forms.ChoiceField( label=_("Search within related operations"), choices=[] ) unit = forms.ChoiceField(label=_("Type"), choices=[]) excavator = forms.IntegerField( label=_("Excavator"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person'), associated_model=Person), validators=[valid_id(Person)], required=False) activity = forms.ChoiceField(label=_("Activity"), choices=[]) identifications = forms.ChoiceField(label=_("Identification"), choices=[]) cultural_attributions = forms.ChoiceField( label=_("Cultural attribution"), choices=[]) documentations = forms.ChoiceField(label=_("Documentation")) description = forms.CharField(label=_("Description")) comment = forms.CharField(label=_("General comment")) filling = forms.CharField(label=_("Filling")) interpretation = forms.CharField(label=_("Interpretation")) parcel = forms.CharField(label=_("Parcel")) has_finds = forms.NullBooleanField(label=_("Has finds")) cr_relation_types = forms.ChoiceField( label=_("Search within relations"), choices=[] ) excavation_technics = forms.ChoiceField(label=_("Excavation techniques"), choices=[]) structures = forms.ChoiceField(label=_("Structure"), choices=[]) textures = forms.ChoiceField(label=_("Texture"), choices=[]) inclusions = forms.ChoiceField(label=_("Inclusions"), choices=[]) colors = forms.ChoiceField(label=_("Colors"), choices=[]) details_on_color = forms.CharField(label=_("Details on color")) TYPES = PeriodSelect.TYPES + [ FieldType('area', Area), FieldType('cultural_attributions', models.CulturalAttributionType), FieldType("unit", models.Unit), FieldType("cr_relation_types", models.RelationType), FieldType("ope_relation_types", OpeRelationType), FieldType("excavation_technics", models.ExcavationTechnicType), FieldType("activity", models.ActivityType), FieldType("identifications", models.IdentificationType), FieldType("documentations", models.DocumentationType), FieldType("structures", models.StructureType), FieldType("textures", models.TextureType), FieldType("inclusions", models.InclusionType), FieldType("colors", models.ColorType), ] + GeoItemSelect.TYPES SITE_KEYS = {"archaeological_site": None} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not get_current_profile().archaeological_site: self._remove_fields(("archaeological_site",)) self._reorder_period_fields("unit") def get_input_ids(self): ids = super(RecordSelect, self).get_input_ids() if "cr_relation_types" in ids: ids.pop(ids.index("cr_relation_types")) for idx, c in enumerate(self.fields["cr_relation_types"].choices): ids.append("cr_relation_types_{}".format(idx)) if "ope_relation_types" in ids: ids.pop(ids.index("ope_relation_types")) for idx, c in enumerate(self.fields["ope_relation_types"].choices): ids.append("ope_relation_types_{}".format(idx)) return ids class RecordFormSelection(LockForm, CustomFormSearch): SEARCH_AND_SELECT = True form_label = _("Context record search") pk_key = "pk" associated_models = {"pk": models.ContextRecord} currents = {"pk": models.ContextRecord} pk = forms.IntegerField( label="", required=False, widget=widgets.DataTable( reverse_lazy("get-contextrecord"), RecordSelect, models.ContextRecord, gallery=True, map=True, source_full=reverse_lazy("get-contextrecord-full"), ), validators=[valid_id(models.ContextRecord)], ) class RecordFormMultiSelection(LockForm, MultiSearchForm): form_label = _("Context record search") associated_models = {"pks": models.ContextRecord} pk_key = "pks" pk = forms.CharField( label="", required=False, widget=widgets.DataTable( reverse_lazy("get-contextrecord"), RecordSelect, models.ContextRecord, multiple_select=True, gallery=True, map=True, source_full=reverse_lazy("get-contextrecord-full"), ), validators=[valid_ids(models.ContextRecord)], ) def get_init_parcel(form, operation, prefix=""): parcels = operation.parcels.all() sort = lambda x: (x.town.name, x.section) parcels = sorted(parcels, key=sort) for key, gparcels in groupby(parcels, sort): form.fields[prefix + "parcel"].choices.append( ( " - ".join(k for k in key if k), [(parcel.pk, parcel.short_label) for parcel in gparcels], ) ) if ( len(form.fields[prefix + "parcel"].choices) == 1 and (prefix + "town") in form.fields ): # only the empty choice is available form.fields.pop(prefix + "parcel") form.fields[prefix + "town"].required = True if (prefix + "town") in form.fields: if form.fields[prefix + "town"].required: form.fields[prefix + "town"].choices = [] # remove the empty choice form.fields[prefix + "town"].choices += [ (t.pk, str(t)) for t in operation.towns.all() ] class RecordFormGeneral(CustomForm, ManageOldType): HEADERS = {} form_label = _("General") form_admin_name = _("Context record - 020 - General") form_slug = "contextrecord-020-general" file_upload = True base_models = ["documentation", "excavation_technic", "structure", "texture", "color", "inclusion"] associated_models = { "archaeological_site": ArchaeologicalSite, "parcel": Parcel, "unit": models.Unit, "town": Town, "documentation": models.DocumentationType, "spatial_reference_system": SpatialReferenceSystem, "excavation_technic": models.ExcavationTechnicType, "structure": models.StructureType, "texture": models.TextureType, "color": models.ColorType, "inclusion": models.InclusionType, "excavator": Person, } pk = forms.IntegerField(required=False, widget=forms.HiddenInput) operation_id = forms.IntegerField(widget=forms.HiddenInput) parcel = forms.ChoiceField(label=_("Parcel"), choices=[]) town = forms.ChoiceField(label=_("Town"), choices=[], required=False) archaeological_site = forms.ChoiceField( label=" ", choices=[], required=False, help_text=_("Only the items associated to the operation can be selected."), ) label = forms.CharField( label=_("ID"), validators=[validators.MaxLengthValidator(200)] ) unit = forms.ChoiceField(label=_("Context record type"), required=False, choices=[]) comment = forms.CharField( label=_("General comment"), widget=forms.Textarea, required=False ) excavator = forms.IntegerField( label=_("Excavator"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person'), associated_model=Person, new=True), validators=[valid_id(Person)], required=False) HEADERS["excavation_technic"] = FormHeader(_("Description")) excavation_technic = forms.MultipleChoiceField( label=_("Excavation techniques"), choices=[], required=False, widget=widgets.Select2Multiple, ) documentation = forms.MultipleChoiceField( label=_("Documentation"), choices=[], required=False, widget=widgets.Select2Multiple, ) opening_date = DateField(label=_("Opening date"), required=False) closing_date = DateField(label=_("Closing date"), required=False) filling = forms.CharField(label=_("Filling"), widget=forms.Textarea, required=False) location = forms.CharField( label=_("Location"), widget=forms.Textarea, required=False, validators=[validators.MaxLengthValidator(200)], ) description = forms.CharField( label=_("Description"), widget=forms.Textarea, required=False ) structure = forms.MultipleChoiceField( label=_("Structure"), choices=[], required=False, widget=widgets.Select2Multiple, ) texture = forms.MultipleChoiceField( label=_("Texture"), choices=[], required=False, widget=widgets.Select2Multiple, ) inclusion = forms.MultipleChoiceField( label=_("Inclusions"), choices=[], required=False, widget=widgets.Select2Multiple, ) color = forms.MultipleChoiceField( label=_("Colors"), choices=[], required=False, widget=widgets.Select2Multiple, ) details_on_color = forms.CharField(label=_("Details on color"), widget=forms.Textarea, required=False) HEADERS["surface"] = FormHeader(_("Dimensions")) surface = forms.FloatField( required=False, widget=widgets.AreaWidget, label=_("Total surface (m²)"), validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(999999999), ], ) length = forms.FloatField(label=_("Length (m)"), required=False) excavated_length = forms.FloatField(label=_("Excavated length (m)"), required=False) width = forms.FloatField(label=_("Width (m)"), required=False) excavated_width = forms.FloatField(label=_("Excavated width (m)"), required=False) thickness = forms.FloatField(label=_("Thickness (m)"), required=False) diameter = forms.FloatField(label=_("Diameter (m)"), required=False) depth = forms.FloatField(label=_("Depth (m)"), required=False) depth_of_appearance = forms.FloatField( label=_("Depth of appearance (m)"), required=False ) TYPES = [ FieldType("unit", models.Unit), FieldType("excavation_technic", models.ExcavationTechnicType, is_multiple=True), FieldType("documentation", models.DocumentationType, is_multiple=True), FieldType("spatial_reference_system", SpatialReferenceSystem), FieldType("structure", models.StructureType, is_multiple=True), FieldType("texture", models.TextureType, is_multiple=True), FieldType("color", models.ColorType, is_multiple=True), FieldType("inclusion", models.InclusionType, is_multiple=True), ] def __init__(self, *args, **kwargs): # TODO: simplify operation = None if ( "data" in kwargs and kwargs["data"] and ("operation" in kwargs["data"] or "context_record" in kwargs["data"]) ): if "operation" in kwargs["data"]: operation = kwargs["data"].pop("operation") if type(operation) in (list, tuple): operation = operation[0] if not hasattr(operation, "id"): operation_id = operation try: operation = Operation.objects.get(pk=operation_id) except Operation.DoesNotExist: operation = None # force operation modification # if posted if operation and kwargs["data"]: if hasattr(operation, "id"): kwargs["data"][ kwargs["prefix"] + "-operation_id" ] = operation.id else: kwargs["data"][kwargs["prefix"] + "-operation_id"] = operation if "context_record" in kwargs["data"] and kwargs["data"]["context_record"]: operation = kwargs["data"]["context_record"].operation # clean data if not "real" data # prefix_value = kwargs['prefix'] if not [ k for k in kwargs["data"].keys() if k.startswith(kwargs["prefix"]) and kwargs["data"][k] ]: kwargs["data"] = None if "files" in kwargs: kwargs.pop("files") super().__init__(*args, **kwargs) profile = get_current_profile() self.fields["parcel"].choices = [("", "--")] if not profile.parcel_mandatory: self.fields["parcel"].required = False self.fields["town"].choices = [("", "--")] else: self._remove_fields(("town",)) sites = [] if operation: sites = [(site.pk, str(site)) for site in operation.archaeological_sites.all()] if not sites or not profile.archaeological_site: self._remove_fields(("archaeological_site",)) else: site_label = IshtarSiteProfile.get_default_site_label() self.fields["archaeological_site"].label = site_label self.fields["archaeological_site"].choices = [("", "--")] + sites if operation: self.fields["operation_id"].initial = operation.pk get_init_parcel(self, operation) def clean(self): # manage unique context record ID cleaned_data = self.cleaned_data operation_id = cleaned_data.get("operation_id") label = cleaned_data.get("label") cr = models.ContextRecord.objects.filter( label=label, parcel__operation__pk=operation_id ) if "pk" in cleaned_data and cleaned_data["pk"]: cr = cr.exclude(pk=int(cleaned_data["pk"])) if cr.count(): raise forms.ValidationError( _("This ID already exists for " "this operation.") ) if not self.cleaned_data.get("parcel", None) and not self.cleaned_data.get( "town", None ): raise forms.ValidationError(_("You have to choose a town or a parcel.")) return cleaned_data class DatingForm(ManageOldType, forms.Form): form_label = _("Dating") base_model = "dating" associated_models = { "dating_type": models.DatingType, "quality": models.DatingQuality, "period": models.Period, } period = forms.ChoiceField(label=_("Chronological period"), choices=[]) start_date = forms.IntegerField(label=_("Start date"), required=False) end_date = forms.IntegerField(label=_("End date"), required=False) quality = forms.ChoiceField(label=_("Quality"), required=False, choices=[]) dating_type = forms.ChoiceField(label=_("Dating type"), required=False, choices=[]) precise_dating = forms.CharField(label=_("Precise on this dating"), required=False) TYPES = [ FieldType("dating_type", models.DatingType), FieldType("quality", models.DatingQuality), FieldType("period", models.Period), ] 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"] associated_models = { "activity": models.ActivityType, "identification": models.IdentificationType, 'cultural_attribution': models.CulturalAttributionType, } interpretation = forms.CharField( label=_("Interpretation"), widget=forms.Textarea, required=False ) activity = forms.ChoiceField(label=_("Activity"), required=False, choices=[]) identification = forms.MultipleChoiceField( label=_("Identification"), choices=[], widget=widgets.Select2Multiple, required=False ) HEADERS['taq'] = FormHeader(_("Dating complements")) taq = forms.IntegerField(label=_("TAQ"), required=False) 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) cultural_attribution = forms.MultipleChoiceField( label=_("Cultural attributions"), choices=[], widget=widgets.Select2Multiple, required=False ) datings_comment = forms.CharField( label=_("Comments on dating"), required=False, widget=forms.Textarea ) TYPES = [ FieldType("activity", models.ActivityType), FieldType("identification", models.IdentificationType, True), FieldType('cultural_attribution', models.CulturalAttributionType, True), ] OperationRecordFormSelection = get_form_selection( "OperationRecordFormSelection", _("Operation search"), "operation_id", Operation, OperationSelect, "get-operation", _("You should select an operation."), ) class RecordDeletionForm(FinalForm): confirm_msg = " " confirm_end_msg = _("Would you like to delete this context record?") class RecordRelationsForm(OpeRecordRelationsForm): current_model = models.RelationType current_related_model = models.ContextRecord associated_models = { "right_record": models.ContextRecord, "relation_type": models.RelationType, } ERROR_MISSING = _("You should select a context record and a relation type.") ERROR_SAME = _("A context record cannot be related to himself.") right_record = forms.ChoiceField( label=_("Context record"), choices=[], required=False ) def __init__(self, *args, **kwargs): crs = None if "data" in kwargs and "CURRENT_ITEMS" in kwargs["data"]: kwargs["data"] = copy(kwargs["data"]) crs = kwargs["data"].pop("CURRENT_ITEMS") initial = kwargs.get("initial", {}) if initial and initial.get("right_record", None): if initial["right_record"] not in [cr_id for cr_id, cr_lbl in crs]: try: crs.append( (initial["right_record"], str(models.ContextRecord.objects.get(pk=initial["right_record"]))) ) except models.ContextRecord.DoesNotExist: pass super().__init__(*args, **kwargs) if crs: self.fields["right_record"].choices = [("", "-" * 2)] + crs RecordRelationsFormSet = formset_factory( RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase ) RecordRelationsFormSet.form_label = _("Context records - Relations") RecordRelationsFormSet.form_admin_name = _("Context record - Relations") RecordRelationsFormSet.form_slug = "contextrecord-recordrelations" class QAOperationCR(IshtarForm): town = forms.ChoiceField(label=_("Town"), choices=[]) archaeological_site = forms.ChoiceField( label=" ", choices=[], required=False, help_text=_("Only the items associated to the operation can be " "selected."), ) label = forms.CharField( label=_("ID"), validators=[validators.MaxLengthValidator(200)] ) parcel = forms.ChoiceField(label=_("Parcel"), choices=[], required=False) unit = forms.ChoiceField(label=_("Context record type"), required=False, choices=[]) TYPES = [ FieldType("unit", models.Unit), ] def __init__(self, *args, **kwargs): self.items = kwargs.pop("items") super(QAOperationCR, self).__init__(*args, **kwargs) self.profil = IshtarSiteProfile.get_current_profile() site_label = self.profil.get_site_label() self.fields["archaeological_site"].label = site_label self.fields["archaeological_site"].choices = [("", "--")] if self.profil.parcel_mandatory: self.fields["parcel"].required = True self.fields.pop("town") else: self.fields.pop("parcel") if not self.items: return operation = self.items[0] if not self.profil.parcel_mandatory: self.fields["town"].choices = [ (t.pk, str(t)) for t in operation.towns.all() ] self.fields["archaeological_site"].choices += [ (site.pk, str(site)) for site in operation.archaeological_sites.all() ] if self.profil.parcel_mandatory: self.fields["parcel"].choices += [("", "--")] + [ (str(parcel.pk), "{} - {}".format(parcel.town, parcel.short_label)) for parcel in operation.parcels.all() ] def save(self, items): operation = items[0] data = { "operation": operation, "archaeological_site_id": self.cleaned_data["archaeological_site"] or None, "label": self.cleaned_data["label"], "unit_id": self.cleaned_data["unit"] or None, } if self.profil.parcel_mandatory: data["parcel_id"] = self.cleaned_data["parcel"] else: data["town_id"] = self.cleaned_data["town"] models.ContextRecord.objects.create(**data) class QAContextRecordDuplicateForm(IshtarForm): qa_label = forms.CharField(label=_("ID"), max_length=None, required=True) qa_parcel = forms.ChoiceField(label=_("Parcel"), choices=[]) qa_town = forms.ChoiceField(label=_("Town"), choices=[], required=False) qa_unit = forms.ChoiceField( label=_("Context record type"), required=False, choices=[] ) TYPES = [ FieldType("qa_unit", models.Unit), ] def __init__(self, *args, **kwargs): self.user = None if "user" in kwargs: self.user = kwargs.pop("user") if hasattr(self.user, "ishtaruser"): self.user = self.user.ishtaruser self.context_record = kwargs.pop("items")[0] super(QAContextRecordDuplicateForm, self).__init__(*args, **kwargs) profile = IshtarSiteProfile.get_current_profile() self.fields["qa_parcel"].choices = [("", "--")] if not profile.parcel_mandatory: self.fields["qa_parcel"].required = False self.fields["qa_town"].choices = [("", "--")] else: self.fields.pop("qa_town") get_init_parcel(self, self.context_record.operation, prefix="qa_") if "qa_town" in self.fields and self.context_record.town: self.fields["qa_town"].initial = self.context_record.town.pk if "qa_parcel" in self.fields and self.context_record.parcel: self.fields["qa_parcel"].initial = self.context_record.parcel.pk self.fields["qa_label"].initial = (self.context_record.label or "") + str( _(" - duplicate") ) if self.context_record.unit: self.fields["qa_unit"].initial = self.context_record.unit.pk def save(self): data = {"label": self.cleaned_data["qa_label"]} if self.cleaned_data.get("qa_unit", None): try: data["unit"] = models.Unit.objects.get( pk=int(self.cleaned_data["qa_unit"]), available=True ) except models.Unit.DoesNotExist: pass if self.cleaned_data.get("qa_town", None): try: data["town"] = Town.objects.get(pk=int(self.cleaned_data["qa_town"])) except Town.DoesNotExist: pass if self.cleaned_data.get("qa_parcel", None): try: data["parcel"] = Parcel.objects.get( pk=int(self.cleaned_data["qa_parcel"]) ) except Parcel.DoesNotExist: pass return self.context_record.duplicate(self.user, data=data) class QAContextRecordFormMulti(QAForm): form_admin_name = _("Context record - Quick action - Modify") form_slug = "contextrecord-quickaction-modify" base_models = ["qa_relation_type", "qa_related_to", "qa_unit"] associated_models = { "qa_relation_type": models.RelationType, "qa_related_to": models.ContextRecord, "qa_unit": models.Unit, "qa_documentations": models.DocumentationType, "qa_activity": models.ActivityType, "qa_identifications": models.IdentificationType, "qa_excavation_technics": models.ExcavationTechnicType, "qa_town": models.Town, "qa_parcel": models.Parcel, "qa_structures": models.StructureType, "qa_textures": models.TextureType, "qa_colors": models.ColorType, "qa_inclusions": models.InclusionType } REPLACE_FIELDS = [ "qa_unit", "qa_activity", "qa_town", "qa_archaeological_site", "qa_parcel", ] MULTI = True qa_relation_type = forms.ChoiceField( label=_("Relation type"), required=False, ) qa_related_to = forms.IntegerField( label=_("Related to"), widget=widgets.JQueryAutoComplete( reverse_lazy("autocomplete-contextrecord"), associated_model=models.ContextRecord, ), validators=[valid_id(models.ContextRecord)], required=False, ) qa_unit = forms.ChoiceField(label=_("Unit type"), choices=[], required=False) qa_town = forms.IntegerField( label=_("Town"), widget=widgets.JQueryAutoComplete( reverse_lazy("autocomplete-town"), ), required=False, ) qa_documentations = forms.ChoiceField(label=_("Documentation"), required=False) qa_excavation_technics = forms.ChoiceField(label=_("Excavation technic"), required=False) qa_activity = forms.ChoiceField(label=_("Activity"), required=False) qa_identifications = forms.ChoiceField(label=_("Identification"), required=False) qa_structures = forms.ChoiceField(label=_("Structures"), required=False) qa_textures = forms.ChoiceField(label=_("Textures"), required=False) qa_colors = forms.ChoiceField(label=_("Colors"), required=False) qa_inclusions = forms.ChoiceField(label=_("Inclusions"), required=False) qa_archaeological_site = forms.IntegerField( label=_("Archaeological site"), widget=widgets.JQueryAutoComplete( reverse_lazy("autocomplete-archaeologicalsite"), associated_model=ArchaeologicalSite, ), required=False ) qa_parcel = forms.ChoiceField( label=_("Associated parcel"), choices=[], required=False ) TYPES = [ FieldType("qa_relation_type", models.RelationType), FieldType("qa_unit", models.Unit), FieldType("qa_documentations", models.DocumentationType), FieldType("qa_excavation_technics", models.ExcavationTechnicType), FieldType("qa_activity", models.ActivityType), FieldType("qa_identifications", models.IdentificationType), FieldType("qa_structures", models.StructureType), FieldType("qa_textures", models.TextureType), FieldType("qa_colors", models.ColorType), FieldType("qa_inclusions", models.InclusionType), ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.items = kwargs.pop("items") # manage parcel field if "qa_parcel" not in self.fields or not self.items: return op_pk = self.items[0].operation_id if len(self.items) > 1 and any( (1 for cr in self.items[1:] if op_pk != cr.operation_id)): self.fields["qa_parcel"].disabled = True self.fields["qa_parcel"].widget = forms.TextInput( attrs={ "class": 'w-100', "placeholder": _( "Parcel can be modified only if operations are the same" )} ) return choices = [("", "--")] choices += [ (p.pk, "{} - {}".format(p.town, p.short_label)) for p in Parcel.objects.filter(operation_id=op_pk).all() ] self.fields["qa_parcel"].choices = choices def clean(self): cleaned_data = super().clean() r_type = cleaned_data.get("qa_relation_type") r_related = cleaned_data.get("qa_related_to") if (r_type and not r_related) or (not r_type and r_related): raise forms.ValidationError( _( "To add relations, you must fill \"relation type\" and \"related to\" fields." ) ) def _get_qa_archaeological_site(self, value): try: return str(ArchaeologicalSite.objects.get(pk=value)) except ArchaeologicalSite.DoesNotExist: return "" def _get_qa_parcel(self, value): try: return str(Parcel.objects.get(pk=value)) except Parcel.DoesNotExist: return "" def _get_qa_related_to(self, value): try: return str(models.ContextRecord.objects.get(pk=value)) except models.ContextRecord.DoesNotExist: return "" def _set_qa_related_to(self, item, __): r_type = self.cleaned_data.get("qa_relation_type", None) r_related = self.cleaned_data.get("qa_related_to", None) if not r_type or not r_related: return models.RecordRelations.objects.get_or_create( left_record=item, right_record_id=r_related, relation_type_id=r_type ) def _set_qa_relation_type(self, item, __): pass