#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2016 É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 itertools import groupby 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 ugettext_lazy as _ from ishtar_common.models import ( valid_id, IshtarSiteProfile, Town, SpatialReferenceSystem, 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, HistorySelect, MultiSearchForm, LockForm, DocumentItemSelect, ) from ishtar_common.forms_common import get_town_field from archaeological_operations.forms import ( OperationSelect, ParcelField, RecordRelationsForm as OpeRecordRelationsForm, RecordRelationsFormSetBase, ) from archaeological_operations.models import ( Period, Parcel, Operation, ArchaeologicalSite, RelationType as OpeRelationType, ) from archaeological_operations.widgets import OAWidget from bootstrap_datepicker.widgets import DatePicker 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__before = forms.IntegerField( label=_("Dating - start date before") ) datings__start_date__after = forms.IntegerField( label=_("Dating - start date after") ) datings__end_date__before = forms.IntegerField(label=_("Dating - end date before")) datings__end_date__after = forms.IntegerField(label=_("Dating - end date after")) 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__before", "datings__start_date__after", "datings__end_date__before", "datings__end_date__after", "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: fields[period_key] = self.fields[period_key] self.fields = fields class RecordSelect(DocumentItemSelect, 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() if settings.COUNTRY == "fr": operation__code_patriarche = forms.CharField( max_length=500, widget=OAWidget, label=_("Code PATRIARCHE") ) operation__year = forms.IntegerField(label=_("Operation's 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=_("Unit type"), choices=[]) parcel = forms.CharField(label=_("Parcel")) has_finds = forms.NullBooleanField(label=_("Has finds")) cr_relation_types = forms.ChoiceField( label=_("Search within relations"), choices=[] ) TYPES = PeriodSelect.TYPES + [ FieldType("unit", models.Unit), FieldType("cr_relation_types", models.RelationType), FieldType("ope_relation_types", OpeRelationType), ] SITE_KEYS = {"archaeological_site": None} def __init__(self, *args, **kwargs): super(RecordSelect, self).__init__(*args, **kwargs) 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"] associated_models = { "archaeological_site": ArchaeologicalSite, "parcel": Parcel, "unit": models.Unit, "town": Town, "documentation": models.DocumentationType, "spatial_reference_system": SpatialReferenceSystem, "excavation_technic": models.ExcavationTechnicType, } 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=[]) description = forms.CharField( label=_("Description"), widget=forms.Textarea, required=False ) comment = forms.CharField( label=_("General comment"), widget=forms.Textarea, required=False ) excavation_technic = forms.ChoiceField( label=_("Excavation technique"), choices=[], required=False ) surface = forms.FloatField( required=False, widget=widgets.AreaWidget, label=_("Total surface (m2)"), validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(999999999), ], ) length = forms.FloatField(label=_("Length (m)"), required=False) width = forms.FloatField(label=_("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 ) opening_date = forms.DateField( label=_("Opening date"), widget=DatePicker, required=False ) closing_date = forms.DateField( label=_("Closing date"), widget=DatePicker, required=False ) documentation = forms.MultipleChoiceField( label=_("Documentation"), choices=[], required=False, widget=widgets.Select2Multiple, ) location = forms.CharField( label=_("Location"), widget=forms.Textarea, required=False, validators=[validators.MaxLengthValidator(200)], ) HEADERS["x"] = FormHeader(_("Coordinates")) x = forms.FloatField(label=_("X"), required=False) estimated_error_x = forms.FloatField( label=_("Estimated error for X"), required=False ) y = forms.FloatField(label=_("Y"), required=False) estimated_error_y = forms.FloatField( label=_("Estimated error for Y"), required=False ) z = forms.FloatField(label=_("Z"), required=False) estimated_error_z = forms.FloatField( label=_("Estimated error for Z"), required=False ) spatial_reference_system = forms.ChoiceField( label=_("Spatial Reference System"), required=False, choices=[] ) TYPES = [ FieldType("unit", models.Unit), FieldType("excavation_technic", models.ExcavationTechnicType), FieldType("documentation", models.DocumentationType, is_multiple=True), FieldType("spatial_reference_system", SpatialReferenceSystem), ] PROFILE_FILTER = { "mapping": [ "x", "y", "z", "estimated_error_x", "estimated_error_y", "estimated_error_z", "spatial_reference_system", ], } 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(RecordFormGeneral, self).__init__(*args, **kwargs) profile = IshtarSiteProfile.get_current_profile() self.fields["parcel"].choices = [("", "--")] if not profile.parcel_mandatory: self.fields["parcel"].required = False self.fields["town"].choices = [("", "--")] else: self.fields.pop("town") site_label = IshtarSiteProfile.get_default_site_label() self.fields["archaeological_site"].label = site_label self.fields["archaeological_site"].choices = [("", "--")] if operation: self.fields["operation_id"].initial = operation.pk get_init_parcel(self, operation) self.fields["archaeological_site"].choices += [ (site.pk, str(site)) for site in operation.archaeological_sites.all() ] 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=_("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 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 RecordRelationsForm(OpeRecordRelationsForm): current_model = models.RelationType current_related_model = models.ContextRecord associated_models = { "right_record": models.ContextRecord, "relation_type": models.RelationType, } right_record = forms.ChoiceField( label=_("Context record"), choices=[], required=False ) def __init__(self, *args, **kwargs): crs = None if "data" in kwargs and "CONTEXT_RECORDS" in kwargs["data"]: crs = kwargs["data"]["CONTEXT_RECORDS"] super(RecordRelationsForm, self).__init__(*args, **kwargs) self.fields["relation_type"].choices = models.RelationType.get_types( initial=self.init_data.get("relation_type") ) if crs: self.fields["right_record"].choices = [("", "-" * 2)] + crs RecordRelationsFormSet = formset_factory( RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase ) RecordRelationsFormSet.form_label = _("Relations") RecordRelationsFormSet.form_admin_name = _("Context record - 050 - Relations") RecordRelationsFormSet.form_slug = "contextrecord-050-recordrelations" class RecordFormInterpretation(CustomForm, ManageOldType, forms.Form): form_label = _("Interpretation") form_admin_name = _("Context record - 040 - Interpretation") form_slug = "contextrecord-040-interpretation" associated_models = { "activity": models.ActivityType, "identification": models.IdentificationType, } datings_comment = forms.CharField( label=_("Comments on dating"), required=False, widget=forms.Textarea ) filling = forms.CharField(label=_("Filling"), widget=forms.Textarea, required=False) interpretation = forms.CharField( label=_("Interpretation"), widget=forms.Textarea, required=False ) activity = forms.ChoiceField(label=_("Activity"), required=False, choices=[]) identification = forms.ChoiceField( label=_("Identification"), required=False, choices=[] ) 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) TYPES = [ FieldType("activity", models.ActivityType), FieldType("identification", models.IdentificationType), ] 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 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.short_label, parcel.town)) 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)