#!/usr/bin/env python # -*- 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. """ Operations forms definitions """ import datetime from collections import OrderedDict from itertools import groupby from django import forms from django.conf import settings from django.core import validators from django.db.models import Max from django.forms.formsets import formset_factory, DELETION_FIELD_NAME, \ TOTAL_FORM_COUNT from django.utils.functional import lazy from django.utils.safestring import mark_safe from ishtar_common.utils import ugettext_lazy as _, pgettext_lazy from . import models from archaeological_files.models import File from archaeological_operations.utils import parse_parcels from bootstrap_datepicker.widgets import DateField from ishtar_common import widgets from ishtar_common.forms import FinalForm, FormSet, get_now, \ reverse_lazy, TableSelect, get_data_from_formset, QAForm, CustomFormSearch,\ ManageOldType, IshtarForm, CustomForm, FieldType, FormHeader, \ DocumentItemSelect, LockForm, MultiSearchForm from ishtar_common.forms_common import TownFormSet, get_town_field, TownForm from ishtar_common.models import valid_id, valid_ids, Person, Town, \ DocumentTemplate, Organization, get_current_profile, \ person_type_pks_lazy, person_type_pk_lazy, organization_type_pks_lazy, \ organization_type_pk_lazy, SpatialReferenceSystem, Area, \ get_sra_agent_label, get_sra_agent_head_scientist_label, get_operator_label from ishtar_common.wizards import MultiValueDict from .widgets import ParcelWidget, SelectParcelWidget, OAWidget class ParcelField(forms.MultiValueField): def __init__(self, *args, **kwargs): if 'widget' not in kwargs: self.widget = ParcelWidget() super(ParcelField, self).__init__(*args, **kwargs) def compress(self, data_list): return "-".join(data_list) class ParcelForm(IshtarForm): form_label = _("Parcels") base_model = 'parcel' associated_models = {'parcel': models.Parcel, 'town': models.Town, } pk = forms.IntegerField(required=False, widget=forms.HiddenInput) town = forms.ChoiceField(label=_("Town"), choices=(), required=False, validators=[valid_id(models.Town)]) year = forms.IntegerField(label=_("Year"), required=False, validators=[validators.MinValueValidator(1000), validators.MaxValueValidator(2100)]) section = forms.CharField(label=_("Section"), required=False, validators=[validators.MaxLengthValidator(4)]) parcel_number = forms.CharField( label=_("Parcel number"), required=False, validators=[validators.MaxLengthValidator(6)]) public_domain = forms.BooleanField(label=_("Public domain"), initial=False, required=False) def __init__(self, *args, **kwargs): towns = None if 'data' in kwargs and 'TOWNS' in kwargs['data']: towns = kwargs['data']['TOWNS'] # clean data if not "real" data prefix_value = kwargs['prefix'] + '-town' if not [k for k in kwargs['data'].keys() if k.startswith(prefix_value) and kwargs['data'][k]]: kwargs['data'] = {} if 'files' in kwargs: kwargs.pop('files') super(ParcelForm, self).__init__(*args, **kwargs) if towns: self.fields['town'].choices = towns def count_valid_fields(self, data): if not data: return 0 data = get_data_from_formset(data) nb = len(data) # remove last non relevant fields for idx, vals in enumerate(reversed(data[:])): if 'public_domain' in vals: break if 'section' in vals and 'parcel_number' in vals: break nb -= 1 return nb def clean(self): """Check required fields""" if any(self.errors): return if not self.cleaned_data or (DELETION_FIELD_NAME in self.cleaned_data and self.cleaned_data[DELETION_FIELD_NAME]): return if (not self.cleaned_data.get('parcel_number') or not self.cleaned_data.get('section')) and \ not self.cleaned_data.get('public_domain'): return {} if not self.cleaned_data.get('town'): raise forms.ValidationError(_("Town section is required.")) return self.cleaned_data @classmethod def get_formated_datas(cls, cleaned_datas): result, current, deleted = [], [], [] towns = {} for data in cleaned_datas: if not data: continue town = data.get('town') or '' if town: if town in towns: town = towns[town] else: try: towns[town] = str(Town.objects.get(pk=town)) town = towns[town] except (Town.DoesNotExist, ValueError): town = '' parcel_number = data.get('parcel_number') or '' c_number = 0 if parcel_number: parcel_nb = list(reversed(list(parcel_number))) c_number = '' while parcel_nb: c = parcel_nb.pop() try: c_number += str(int(c)) except ValueError: break if not c_number: c_number = 0 values = [ town, str(data.get('year')) if data.get('year') else '', str(data.get('section')) if data.get('section') else '', int(c_number), str(_("public domain")) if data.get('public_domain') else "" ] if data.get('DELETE'): deleted.append(values) else: current.append(values) if current: result.append((_("Current parcels"), cls._format_parcels(current))) if deleted: result.append((_("Deleted parcels"), cls._format_parcels(deleted))) return result @classmethod def _format_parcels(cls, parcels): sortkeyfn = lambda s: (s[0], s[1], s[2]) parcels = sorted(parcels, key=sortkeyfn) grouped = [] for keys, parcel_grp in groupby(parcels, key=sortkeyfn): keys = list(keys) keys.append([' '.join([str(gp[-2]), str(gp[-1])]) for gp in parcel_grp]) grouped.append(keys) res = '' c_town, c_section = '', '' for idx, parcel in enumerate(grouped): town, year, section, parcel_numbers = parcel if c_town != town: c_town = town c_section = '' if idx: res += " ; " res += town + ' : ' if c_section: res += " / " c_section = section res += section + ' ' res += ", ".join(parcel_numbers) if year: res += " (%s)" % str(year) return res class ParcelSelectionForm(IshtarForm): _town = forms.ChoiceField(label=_("Town"), choices=(), required=False, validators=[valid_id(models.Town)]) _parcel_selection = forms.CharField( label=_("Full text input"), widget=SelectParcelWidget(attrs={'class': 'parcel-select'}), help_text=_("example: \"2013: XD:1 to 13,24,33 to 39, YD:24\" or " "\"AB:24,AC:42\""), max_length=100, required=False) class ParcelFormSet(FormSet): SELECTION_FORM = ParcelSelectionForm def __init__(self, *args, **kwargs): if 'data' in kwargs and kwargs['data']: kwargs['data'] = self.rearrange_parcels(kwargs['data']) super(FormSet, self).__init__(*args, **kwargs) self.extra_form = None if self.forms[0].__class__.__name__ == 'ParcelForm': self.selection_form = ParcelSelectionForm() self.extra_form = self.selection_form # copy town choices town_choices = self.forms[0].fields['town'].choices[:] if town_choices and not town_choices[0][0]: # remove empty town_choices = town_choices[1:] if town_choices: self.selection_form.fields['_town'].choices = town_choices def rearrange_parcels(self, parcels): """ Simple database ordering is not possible as a numeric ordering of parcel number have to be made but with parcel number not strictly numeric Very complicated for a simple thing :( """ prefix, ordering_keys, values = '', {}, {} new_values = MultiValueDict() for k in parcels: value = parcels[k] splitted = k.split('-') if len(splitted) < 4: new_values[k] = value continue if not prefix: prefix = "-".join(splitted[:-2]) field = splitted[-1] number = splitted[-2] if number not in values: values[number] = {} values[number][field] = value if field == 'parcel': if not value: continue try: parcel = models.Parcel.objects.get(pk=int(value)) except (models.Parcel.DoesNotExist, ValueError): continue ordering_keys[number] = [ parcel.public_domain, parcel.town, parcel.year, parcel.section, parcel.parcel_number] continue if number not in ordering_keys: ordering_keys[number] = ['', '', '', '', ''] if field == 'public_domain': ordering_keys[number][0] = value elif field == 'town': ordering_keys[number][1] = value elif field == 'year': ordering_keys[number][2] = value elif field == 'section': ordering_keys[number][3] = value elif field == 'parcel_number': ordering_keys[number][4] = value reverse_ordering_keys = {} for number in ordering_keys: reverse_ordering_keys[tuple(ordering_keys[number])] = number for new_idx, keys in enumerate(sorted(reverse_ordering_keys.keys(), key=self._parcel_sorting)): number = reverse_ordering_keys[keys] prefx = '%s-%d-' % (prefix, new_idx) for field in values[number]: new_key = prefx + field new_values[new_key] = values[number][field] return new_values def _parcel_sorting(self, key): public_domain, town, year, section, parcel = key # deal with parcel_number such as '34p' and convert to int parcel_number = '' for p in parcel: try: parcel_number += str(int(p)) except ValueError: break parcel_number = int(parcel_number) if parcel_number else 0 # empty must be at the end if not year and not section and not parcel_number \ and not public_domain: default = 'ZZZZZ' return default, default, default, default, default return (town.name if hasattr(town, "name") else "", year or '', section or "", parcel_number or "", public_domain) def as_table(self): # add dynamic widget render = self.selection_form.as_table() render += super(FormSet, self).as_table() return mark_safe(render) def as_p(self): # add dynamic widget render = self.selection_form.as_p() render += super(FormSet, self).as_p() return mark_safe(render) def as_ul(self): # add dynamic widget render = self.selection_form.as_ul() render += super(FormSet, self).as_ul() return mark_safe(render) def add_fields(self, form, index): super(FormSet, self).add_fields(form, index) def clean(self): # manage parcel selection if self.data.get('_parcel_selection'): parcels = parse_parcels(self.data['_parcel_selection']) selected_town = self.data.get('_town') for idx, parcel in enumerate(parcels): parcel['town'] = selected_town parcel['DELETE'] = False parcels[idx] = parcel c_max = self.total_form_count() # pop the last extra form extra_form = self.forms.pop() for idx, parcel in enumerate(parcels): form = self._construct_form(idx + c_max) for k in parcel: self.data[form.prefix + '-' + k] = parcel[k] # reconstruct with correct binded data form = self._construct_form(idx + c_max) form.cleaned_data = parcel self.forms.append(form) self._errors.append(None) self.forms.append(extra_form) self.data[self.prefix + '-' + TOTAL_FORM_COUNT] = c_max + \ len(parcels) self.management_form.data = self.data self.management_form.is_valid() # Checks that no parcels are duplicated. self.check_duplicate(('town', 'section', 'parcel_number', 'year'), _("There are identical parcels.")) if hasattr(self, 'cleaned_data') and self.cleaned_data: return self.cleaned_data class RecordRelationsForm(ManageOldType): base_model = 'right_relation' current_model = models.RelationType current_related_model = models.Operation associated_models = {'right_record': models.Operation, 'relation_type': models.RelationType} relation_type = forms.ChoiceField(label=_("Relation type"), choices=[], required=False) right_record = forms.IntegerField( label=_("Operation"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-operation'), associated_model=models.Operation), validators=[valid_id(models.Operation)], required=False) def __init__(self, *args, **kwargs): self.left_record = None if 'left_record' in kwargs: self.left_record = kwargs.pop('left_record') super(RecordRelationsForm, self).__init__(*args, **kwargs) self.fields['relation_type'].choices = \ models.RelationType.get_types( initial=self.init_data.get('relation_type')) @classmethod def _format_lst(cls, current): nc = [] for rel, ope in sorted(current): if not nc or nc[-1][0] != rel: nc.append([rel, []]) nc[-1][1].append(ope) rendered = ";".join( ["{}{} {}".format(rel, _(":"), " ; ".join(opes)) for rel, opes in nc]) return rendered def clean(self): cleaned_data = self.cleaned_data if (cleaned_data.get('relation_type', None) and not cleaned_data.get('right_record', None)): raise forms.ValidationError(_("You should select an operation.")) if (not cleaned_data.get('relation_type', None) and cleaned_data.get('right_record', None)): raise forms.ValidationError( _("You should select a relation type.")) if self.left_record and \ str(cleaned_data.get('right_record', None)) == str( self.left_record.pk): raise forms.ValidationError( _("An operation cannot be related to herself.")) return cleaned_data @classmethod def get_formated_datas(cls, cleaned_datas): result, current, deleted = [], [], [] for data in cleaned_datas: if not data: continue try: relation_type = cls.current_model.objects.get( pk=data.get('relation_type')) except cls.current_model.DoesNotExist: continue try: right_record = cls.current_related_model.objects.get( pk=data.get('right_record')) except cls.current_related_model.DoesNotExist: continue values = [str(relation_type), right_record.reference] if data.get('DELETE'): deleted.append(values) else: current.append(values) if current: nc = [] for rel, ope in sorted(current): if not nc or nc[-1][0] != rel: nc.append([rel, []]) nc[-1][1].append(ope) result.append((_("Current relations"), cls._format_lst(current))) if deleted: result.append((_("Deleted relations"), " ; ".join(deleted))) return result class RecordRelationsFormSetBase(FormSet): # passing left_record should be nicely done with form_kwargs with Django 1.9 # with no need of all these complications def __init__(self, *args, **kwargs): self.left_record = None if 'left_record' in kwargs: self.left_record = kwargs.pop('left_record') super(RecordRelationsFormSetBase, self).__init__(*args, **kwargs) def _construct_forms(self): # instantiate all the forms and put them in self.forms self.forms = [] for i in range(self.total_form_count()): self.forms.append(self._construct_form( i, left_record=self.left_record)) RecordRelationsFormSet = formset_factory( RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase) RecordRelationsFormSet.form_label = _("Relations") RecordRelationsFormSet.form_admin_name = _("Operation - 080 - Relations") RecordRelationsFormSet.form_slug = "operation-080-relations" class OperationSelect(DocumentItemSelect): _model = models.Operation form_admin_name = _("Operation - 001 - Search") form_slug = "operation-001-search" search_vector = forms.CharField( label=_("Full text search"), widget=widgets.SearchWidget( 'archaeological-operations', 'operation')) year = forms.IntegerField(label=_("Year")) operation_code = forms.IntegerField(label=_("Numeric reference")) code_patriarche = forms.CharField( max_length=500, widget=OAWidget, label="Code PATRIARCHE") drassm_code = forms.CharField( label=_("DRASSM code"), required=False, max_length=100) towns = get_town_field() towns__areas = forms.ChoiceField(label=_("Areas"), choices=[]) parcel = forms.CharField(label=_("Parcel")) if settings.ISHTAR_DPTS: towns__numero_insee__startswith = forms.ChoiceField( label=_("Department"), choices=[]) common_name = forms.CharField(label=_("Name"), max_length=30) address = forms.CharField(label=_("Address / Locality"), max_length=100) operation_type = forms.ChoiceField(label=_("Operation type"), choices=[]) end_date = forms.NullBooleanField(label=_("Is open?")) in_charge = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy( 'autocomplete-person-permissive', args=[person_type_pks_lazy(['sra_agent'])] ), associated_model=Person), label=_("In charge")) scientist = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy( 'autocomplete-person-permissive', args=[person_type_pks_lazy(['sra_agent', 'head_scientist'])]), associated_model=Person), label=_("Scientist in charge")) operator = forms.IntegerField( label=_("Operator"), widget=widgets.JQueryAutoComplete( reverse_lazy( 'autocomplete-organization', args=[organization_type_pks_lazy(['operator'])]), associated_model=Organization), validators=[valid_id(Organization)]) # operator_reference = forms.CharField(label=_("Operator reference"), # max_length=20) remains = forms.ChoiceField(label=_("Remains"), choices=[]) periods = forms.ChoiceField(label=_("Periods"), choices=[]) start_before = DateField(label=_("Started before")) start_after = DateField(label=_("Started after")) end_before = DateField(label=_("Ended before")) end_after = DateField(label=_("Ended after")) relation_types = forms.ChoiceField( label=_("Search within relations"), choices=[]) comment = forms.CharField(label=_("Comment"), max_length=500) abstract = forms.CharField(label=_("Abstract (full text search)")) scientific_documentation_comment = forms.CharField( label=_("Comment about scientific documentation")) record_quality_type = forms.ChoiceField(label=_("Record quality")) report_processing = forms.ChoiceField(label=_("Report processing"), choices=[]) virtual_operation = forms.NullBooleanField(label=_("Virtual operation")) archaeological_sites = forms.IntegerField( label=_("Archaeological site"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-archaeologicalsite'), associated_model=models.ArchaeologicalSite), validators=[valid_id(models.ArchaeologicalSite)]) history_creator = forms.IntegerField( label=_("Created by"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person', args=['0', 'user']), associated_model=Person), validators=[valid_id(Person)]) history_modifier = forms.IntegerField( label=_("Modified by"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person', args=['0', 'user']), associated_model=Person), validators=[valid_id(Person)]) documentation_received = forms.NullBooleanField( label=_("Documentation received")) documentation_deadline_before = DateField( label=_("Documentation deadline before")) documentation_deadline_after = DateField( label=_("Documentation deadline after")) has_finds = forms.NullBooleanField(label=_("Has finds")) finds_received = forms.NullBooleanField( label=_("Finds received")) finds_deadline_before = DateField( label=_("Finds deadline before")) finds_deadline_after = DateField( label=_("Finds deadline after")) TYPES = [ FieldType('operation_type', models.OperationType), FieldType('report_processing', models.ReportState), FieldType('remains', models.RemainType), FieldType('periods', models.Period), FieldType('record_quality_type', models.RecordQualityType), FieldType('relation_types', models.RelationType), FieldType('towns__areas', Area), ] SITE_KEYS = {"archaeological_sites": None} def __init__(self, *args, **kwargs): super(OperationSelect, self).__init__(*args, **kwargs) profile = get_current_profile() if not profile.warehouse: self.fields.pop('documentation_deadline_before') self.fields.pop('documentation_deadline_after') self.fields.pop('documentation_received') self.fields.pop('finds_deadline_before') self.fields.pop('finds_deadline_after') self.fields.pop('finds_received') if not profile.underwater: self.fields.pop('drassm_code') if settings.ISHTAR_DPTS: k = 'towns__numero_insee__startswith' self.fields[k].choices = [ ('', '--')] + list(settings.ISHTAR_DPTS) class OperationFormSelection(LockForm, CustomFormSearch): SEARCH_AND_SELECT = True form_label = _("Operation search") associated_models = {'pk': models.Operation} currents = {'pk': models.Operation} pk = forms.IntegerField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-operation'), OperationSelect, models.Operation, gallery=True, map=True, source_full=reverse_lazy('get-operation-full')), validators=[valid_id(models.Operation)]) class OperationFormMultiSelection(LockForm, MultiSearchForm): form_label = _("Operation search") associated_models = {'pks': models.Operation} pk_key = 'pks' pk = forms.CharField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-operation'), OperationSelect, models.Operation, gallery=True, map=True, multiple_select=True, source_full=reverse_lazy('get-operation-full')), validators=[valid_ids(models.Operation)]) class OperationCodeInput(forms.TextInput): """Manage auto complete when changing year in form""" def render(self, *args, **kwargs): name, value = args base_name = '-'.join(name.split('-')[:-1]) rendered = super(OperationCodeInput, self).render(*args, **kwargs) js = """\n \n""" % { 'base_name': base_name, 'name': name, 'url': reverse_lazy('get_available_operation_code')} return mark_safe(rendered + js) class OperationFormFileChoice(IshtarForm): form_label = _("Associated file") associated_models = {'associated_file': File, } currents = {'associated_file': File} associated_file = forms.IntegerField( label=_("Archaeological file"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-file'), associated_model=File), validators=[valid_id(File)], required=False) class OperationFormAbstract(CustomForm, IshtarForm): form_label = _("Abstract") form_admin_name = _("Operation - 090 - Abstract") form_slug = "operation-090-abstract" abstract = forms.CharField( label=_("Abstract"), widget=forms.Textarea(attrs={'class': 'xlarge'}), required=False) SLICING = (("month", _("months")), ('year', _("years")),) DATE_SOURCE = (('creation', _("Creation date")), ("start", _("Start of field work"))) PREVENTIVE_RESARCH = (('all', _('All')), ('preventive', _("Preventive")), ('research', _("Research")),) class DashboardForm(IshtarForm): slicing = forms.ChoiceField(label=_("Slicing"), choices=SLICING, required=False) department_detail = forms.BooleanField( label=_("Department detail"), required=False) date_source = forms.ChoiceField( label=_("Date get from"), choices=DATE_SOURCE, required=False) preventive_research = forms.ChoiceField( label=_("Preventive/Research"), choices=PREVENTIVE_RESARCH, required=False) operation_type = forms.ChoiceField(label=_("Operation type"), choices=[], required=False) operator = forms.ChoiceField(label=_("Operator"), choices=[], required=False) after = DateField(label=_("Date after"), required=False) before = DateField(label=_("Date before"), required=False) with_report = forms.BooleanField(label=_("With reports"), required=False) with_finds = forms.BooleanField(label=_("With finds"), required=False) def __init__(self, *args, **kwargs): if 'prefix' not in kwargs: kwargs['prefix'] = 'operations' super(DashboardForm, self).__init__(*args, **kwargs) self.fields['operation_type'].choices = \ models.OperationType.get_types() self.fields['operator'].choices = [('', '--')] self.fields['operator'].choices += [ (orga.pk, orga.name) for orga in Organization.objects.filter(operator__isnull=False) .order_by('name').distinct().all()] def get_show_detail(self): return hasattr(self, 'cleaned_data') and \ self.cleaned_data.get('department_detail') def get_date_source(self): date_source = 'creation' if hasattr(self, 'cleaned_data') and \ self.cleaned_data.get('date_source'): date_source = self.cleaned_data['date_source'] return date_source def get_filter(self): if not hasattr(self, 'cleaned_data') or not self.cleaned_data: return {} date_source = self.get_date_source() fltr = {} if self.cleaned_data.get('preventive_research'): preventive_research = self.cleaned_data['preventive_research'] if preventive_research == 'preventive': fltr['operation_type__preventive'] = True elif preventive_research == 'research': fltr['operation_type__preventive'] = False if self.cleaned_data.get('operation_type'): fltr['operation_type_id'] = self.cleaned_data['operation_type'] if self.cleaned_data.get('operator'): fltr['operator_id'] = self.cleaned_data['operator'] if self.cleaned_data.get('after'): fltr[date_source + '_date__gte'] = self.cleaned_data['after'] if self.cleaned_data.get('before'): fltr[date_source + '_date__lte'] = self.cleaned_data['before'] if self.cleaned_data.get('with_report'): fltr['report_delivery_date__isnull'] = False if self.cleaned_data.get('with_finds'): fltr['context_record__base_finds__isnull'] = False return fltr class OperationFormGeneral(CustomForm, ManageOldType): HEADERS = {} form_label = _("General") form_admin_name = _("Operation - 010 - General") form_slug = "operation-010-general" file_upload = True associated_models = { 'scientist': Person, 'in_charge': Person, 'cira_rapporteur': Person, 'operator': Organization, 'operation_type': models.OperationType, 'record_quality_type': models.RecordQualityType, 'report_processing': models.ReportState, 'spatial_reference_system': SpatialReferenceSystem, } pk = forms.IntegerField(required=False, widget=forms.HiddenInput) code_patriarche = forms.CharField(label="Code PATRIARCHE", max_length=500, widget=OAWidget, required=False) drassm_code = forms.CharField( label=_("DRASSM code"), required=False, max_length=100) operation_type = forms.ChoiceField(label=_("Operation type"), choices=[]) common_name = forms.CharField(label=_("Generic name"), required=False, max_length=500, widget=forms.Textarea) address = forms.CharField(label=_("Address / Locality"), required=False, max_length=500, widget=forms.Textarea) year = forms.IntegerField(label=_("Year"), initial=lambda: datetime.datetime.now().year, validators=[validators.MinValueValidator(1000), validators.MaxValueValidator(2100)]) old_code = forms.CharField( label=_("Old code"), required=False, validators=[validators.MaxLengthValidator(200)]) scientist = forms.IntegerField( label=_("Head scientist"), widget=widgets.JQueryAutoComplete( reverse_lazy( 'autocomplete-person', args=[person_type_pks_lazy(['head_scientist', 'sra_agent'])]), associated_model=Person, tips=lazy(get_sra_agent_head_scientist_label), limit={ 'person_types': (person_type_pk_lazy('head_scientist'), person_type_pk_lazy('sra_agent'))}, new=True), validators=[valid_id(Person)], required=False) operator = forms.IntegerField( label=_("Operator"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-organization', args=[organization_type_pk_lazy('operator')]), limit={'organization_type': organization_type_pk_lazy('operator')}, tips=lazy(get_operator_label), associated_model=Organization, new=True), validators=[valid_id(Organization)], required=False) operator_reference = forms.CharField(label=_("Operator reference"), required=False, max_length=20) in_charge = forms.IntegerField( label=_("In charge"), widget=widgets.JQueryAutoComplete( reverse_lazy( 'autocomplete-person', args=[person_type_pks_lazy(['sra_agent'])]), associated_model=Person, tips=lazy(get_sra_agent_label), limit={'person_types': [person_type_pk_lazy('sra_agent')]}, new=True), validators=[valid_id(Person)], required=False) surface = forms.FloatField( required=False, widget=widgets.AreaWidget, label=_("Total surface (m2)"), validators=[validators.MinValueValidator(0), validators.MaxValueValidator(999999999)]) start_date = DateField(label=_("Start date"), required=False) excavation_end_date = DateField(label=_("Excavation end date"), required=False) report_delivery_date = DateField(label=_("Report delivery date"), required=False) report_processing = forms.ChoiceField(label=_("Report processing"), choices=[], required=False) if settings.COUNTRY == 'fr': cira_date = DateField(label="Date avis CTRA/CIRA", required=False) negative_result = forms.NullBooleanField( required=False, label="Résultat considéré comme négatif") cira_rapporteur = forms.IntegerField( label="Rapporteur CTRA/CIRA", widget=widgets.JQueryAutoComplete( reverse_lazy( 'autocomplete-person', args=[person_type_pks_lazy(['head_scientist', 'sra_agent'])]), limit={'person_types': [ person_type_pk_lazy('sra_agent'), person_type_pk_lazy('head_scientist')]}, tips=lazy(get_sra_agent_head_scientist_label), associated_model=Person, new=True), validators=[valid_id(Person)], required=False) documentation_deadline = DateField( label=_("Deadline for submission of the documentation"), required=False) documentation_received = forms.NullBooleanField( required=False, label=_("Documentation received")) finds_deadline = DateField( label=_("Deadline for submission of the finds"), required=False, ) finds_received = forms.NullBooleanField( required=False, label=_("Finds received")) comment = forms.CharField(label=_("Comment"), widget=forms.Textarea, required=False) scientific_documentation_comment = forms.CharField( label=_("Comment about scientific documentation"), widget=forms.Textarea, required=False) record_quality_type = forms.ChoiceField(label=_("Record quality"), required=False) virtual_operation = forms.BooleanField(required=False, label=_("Virtual operation")) 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=[]) FILE_FIELDS = [ 'report_delivery_date', 'report_processing', 'cira_rapporteur', 'cira_date', 'negative_result' ] PROFILE_FILTER = { 'mapping': [ 'x', 'y', 'z', 'estimated_error_x', 'estimated_error_y', 'estimated_error_z', 'spatial_reference_system' ], } WAREHOUSE_FIELDS = [ 'documentation_deadline', 'documentation_received', 'finds_deadline', 'finds_received', ] TYPES = [ FieldType('operation_type', models.OperationType), FieldType('record_quality_type', models.RecordQualityType), FieldType('report_processing', models.ReportState), FieldType('spatial_reference_system', SpatialReferenceSystem), ] def __init__(self, *args, **kwargs): super(OperationFormGeneral, self).__init__(*args, **kwargs) profile = get_current_profile() if not profile.files: for key in self.FILE_FIELDS: self.remove_field(key) if not profile.warehouse: for key in self.WAREHOUSE_FIELDS: self.remove_field(key) if not profile.underwater: self.fields.pop('drassm_code') def clean(self): cleaned_data = self.cleaned_data # verify the logic between start date and excavation end date if self.are_available(['excavation_end_date', 'start_date']) \ and cleaned_data.get('excavation_end_date', None): if not cleaned_data.get('start_date', None): raise forms.ValidationError( _("If you want to set an excavation end date you " "have to provide a start date.")) if cleaned_data['excavation_end_date'] \ < cleaned_data['start_date']: raise forms.ValidationError( _("The excavation end date cannot be before the start " "date.")) # verify patriarche code_p = self.cleaned_data.get('code_patriarche', None) if code_p: ops = models.Operation.objects.filter(code_patriarche=code_p) if 'pk' in cleaned_data and cleaned_data['pk']: ops = ops.exclude(pk=cleaned_data['pk']) if ops.count(): msg = "Ce code OA a déjà été affecté à une "\ "autre opération" raise forms.ValidationError(msg) # manage unique operation ID year = self.cleaned_data.get("year") operation_code = cleaned_data.get("operation_code", None) if not operation_code: return self.cleaned_data ops = models.Operation.objects.filter(year=year, operation_code=operation_code) if 'pk' in cleaned_data and cleaned_data['pk']: ops = ops.exclude(pk=cleaned_data['pk']) if ops.count(): max_val = models.Operation.objects.filter(year=year).aggregate( Max('operation_code'))["operation_code__max"] if year and max_val: msg = _( "Operation code already exists for year: %(year)d - use a " "value bigger than %(last_val)d") % { 'year': year, 'last_val': max_val} else: msg = _("Bad operation code") raise forms.ValidationError(msg) return self.cleaned_data class OperationFormModifGeneral(OperationFormGeneral): operation_code = forms.IntegerField(label=_("Operation code"), required=False) currents = {'associated_file': File} associated_file = forms.IntegerField( label=_("Archaeological file"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-file'), associated_model=File), validators=[valid_id(File)], required=False) def __init__(self, *args, **kwargs): super(OperationFormModifGeneral, self).__init__(*args, **kwargs) if not get_current_profile().files: self.fields.pop('associated_file') fields = OrderedDict() for idx, field in enumerate(list(self.fields.items())): key, value = field if 'associated_file' in self.fields and ( key == 'in_charge' or idx > 6): fields['associated_file'] = self.fields.pop('associated_file') fields[key] = value if 'operation_code' in self.fields and ( key == 'year' or idx > 5): fields['operation_code'] = self.fields.pop('operation_code') self.fields = fields OperationFormModifGeneral.associated_models = \ OperationFormGeneral.associated_models.copy() OperationFormModifGeneral.associated_models['associated_file'] = File class CourtOrderedSeizureForm(CustomForm, IshtarForm): form_label = _("Court-ordered seizure") form_admin_name = _("Operation - 015 - Court-ordered seizure") form_slug = "operation-015-court-ordered-seizure" associated_models = { 'protagonist': Person, 'applicant_authority': Organization, 'minutes_writer': Person, } seizure_name = forms.CharField( label=_("Seizure name"), required=False, ) official_report_number = forms.CharField( label=_("Official report number"), required=False, ) protagonist = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person-permissive'), associated_model=Person, new=True), label=_("Protagonist"), required=False, ) applicant_authority = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-organization'), associated_model=Organization, new=True), label=_("Applicant authority"), required=False, ) minutes_writer = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person-permissive'), associated_model=Person, new=True), label=_("Writer of the minutes"), required=False, ) class CollaboratorForm(CustomForm, IshtarForm): form_label = _("Collaborators") form_admin_name = _("Operation - 020 - Collaborators") form_slug = "operation-020-collaborators" base_models = ['collaborator'] associated_models = {'collaborator': Person, } collaborator = widgets.Select2MultipleField( model=Person, label=_("Collaborators"), required=False, remote=True) def __init__(self, *args, **kwargs): super(CollaboratorForm, self).__init__(*args, **kwargs) if 'collaborator' in self.fields: self.fields['collaborator'].widget.attrs['full-width'] = True class OperationFormPreventive(CustomForm, IshtarForm): form_label = _("Preventive informations - excavation") form_admin_name = _("Operation - 033 - Preventive - Excavation") form_slug = "operation-033-preventive-excavation" cost = forms.IntegerField(label=_("Cost (euros)"), required=False) scheduled_man_days = forms.IntegerField(label=_("Scheduled man-days"), required=False) optional_man_days = forms.IntegerField(label=_("Optional man-days"), required=False) effective_man_days = forms.IntegerField(label=_("Effective man-days"), required=False) if settings.COUNTRY == 'fr': fnap_financing = forms.FloatField( required=False, label="Pourcentage de financement FNAP", validators=[validators.MinValueValidator(0), validators.MaxValueValidator(100)]) class OperationFormPreventiveDiag(CustomForm, IshtarForm): form_label = _("Preventive informations - diagnostic") form_admin_name = _("Operation - 037 - Preventive - Diagnostic") form_slug = "operation-037-preventive-diagnostic" if settings.COUNTRY == 'fr': zoning_prescription = forms.NullBooleanField( required=False, label=_("Prescription on zoning")) large_area_prescription = forms.NullBooleanField( required=False, label=_("Prescription on large area")) geoarchaeological_context_prescription = forms.NullBooleanField( required=False, label=_("Prescription on geoarchaeological context")) class SelectedTownForm(IshtarForm): form_label = _("Towns") associated_models = {'town': Town} town = forms.ChoiceField(label=_("Town"), choices=(), validators=[valid_id(Town)]) def __init__(self, *args, **kwargs): towns = None if 'data' in kwargs and 'TOWNS' in kwargs['data']: towns = kwargs['data']['TOWNS'] super(SelectedTownForm, self).__init__(*args, **kwargs) if towns and towns != -1: self.fields['town'].choices = [('', '--')] + towns SelectedTownFormset = formset_factory(SelectedTownForm, can_delete=True, formset=TownFormSet) SelectedTownFormset.form_label = _("Towns") SelectedTownFormset.form_admin_name = _("Operation - 040 - Towns") SelectedTownFormset.form_slug = "operation-040-towns" TownFormset = formset_factory(TownForm, can_delete=True, formset=TownFormSet) TownFormset.form_label = _("Towns") TownFormset.form_admin_name = _("Operation - 040 - Towns (2)") TownFormset.form_slug = "operation-040-towns-2" class SelectedParcelForm(IshtarForm): form_label = _("Parcels") associated_models = {'parcel': models.Parcel} parcel = forms.ChoiceField( label=_("Parcel"), choices=(), validators=[valid_id(models.Parcel)]) def __init__(self, *args, **kwargs): parcels = None if 'data' in kwargs and 'PARCELS' in kwargs['data']: parcels = kwargs['data']['PARCELS'] super(SelectedParcelForm, self).__init__(*args, **kwargs) if parcels: self.fields['parcel'].choices = [('', '--')] + parcels SelectedParcelFormSet = formset_factory(SelectedParcelForm, can_delete=True, formset=ParcelFormSet) SelectedParcelFormSet.form_label = _("Parcels") SelectedParcelFormSet.form_admin_name = _("Operation - 050 - Parcels") SelectedParcelFormSet.form_slug = "operation-050-parcels" SelectedParcelGeneralFormSet = formset_factory(ParcelForm, can_delete=True, formset=ParcelFormSet) SelectedParcelGeneralFormSet.form_label = _("Parcels") SelectedParcelGeneralFormSet.form_admin_name = _( "Operation - 050 - Parcels (2)") SelectedParcelGeneralFormSet.form_slug = "operation-050-parcels-2" """ class SelectedParcelFormSet(forms.Form): form_label = _("Parcels") base_model = 'parcel' associated_models = {'parcel': models.Parcel} parcel = forms.MultipleChoiceField( label=_("Parcel"), required=False, choices=[], widget=forms.CheckboxSelectMultiple) def __init__(self, *args, **kwargs): parcels = None if 'data' in kwargs and 'PARCELS' in kwargs['data']: parcels = kwargs['data']['PARCELS'] # clean data if not "real" data prefix_value = kwargs['prefix'] + '-parcel' if not [k for k in kwargs['data'].keys() if k.startswith(prefix_value) and kwargs['data'][k]]: kwargs['data'] = None if 'files' in kwargs: kwargs.pop('files') super(SelectedParcelFormSet, self).__init__(*args, **kwargs) if parcels: self.fields['parcel'].choices = [('', '--')] + parcels """ class RemainForm(CustomForm, ManageOldType, forms.Form): form_label = _("Remain types") form_admin_name = _("Operation - 060 - Remains") form_slug = "operation-060-remains" base_model = 'remain' associated_models = {'remain': models.RemainType} remain = widgets.Select2MultipleField( label=_("Remain type"), required=False ) TYPES = [ FieldType('remain', models.RemainType, True), ] class PeriodForm(CustomForm, ManageOldType, forms.Form): form_label = _("Periods") form_admin_name = _("Operation - 070 - Periods") form_slug = "operation-070-periods" base_model = 'period' associated_models = {'period': models.Period} period = widgets.Select2MultipleField( label=_("Period"), required=False ) TYPES = [ FieldType('period', models.Period, True), ] class ArchaeologicalSiteForm(ManageOldType): associated_models = {'period': models.Period, 'remain': models.RemainType, 'cultural_attribution': models.CulturalAttributionType, 'spatial_reference_system': SpatialReferenceSystem} HEADERS = {} reference = forms.CharField(label=_("Reference"), max_length=200) name = forms.CharField(label=_("Name"), max_length=200, required=False) other_reference = forms.CharField(label=_("Other reference"), required=False) periods = forms.MultipleChoiceField( label=_("Periods"), choices=[], widget=widgets.Select2Multiple, required=False) remains = forms.MultipleChoiceField( label=_("Remains"), choices=[], widget=widgets.Select2Multiple, required=False) cultural_attributions = forms.MultipleChoiceField( label=_("Cultural attributions"), choices=[], widget=widgets.Select2Multiple, required=False) 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=[]) PROFILE_FILTER = { 'mapping': [ 'x', 'y', 'z', 'estimated_error_x', 'estimated_error_y', 'estimated_error_z', 'spatial_reference_system' ], } TYPES = [ FieldType('periods', models.Period, True), FieldType('remains', models.RemainType, True), FieldType('cultural_attributions', models.CulturalAttributionType, True), FieldType('spatial_reference_system', SpatialReferenceSystem), ] def __init__(self, *args, **kwargs): self.limits = {} if 'limits' in kwargs: kwargs.pop('limits') super(ArchaeologicalSiteForm, self).__init__(*args, **kwargs) def clean_reference(self): reference = self.cleaned_data['reference'] if models.ArchaeologicalSite.objects\ .filter(reference=reference).count(): raise forms.ValidationError(_("This reference already exists.")) return reference def save(self, user): dct = self.cleaned_data dct['history_modifier'] = user for typ in self.TYPES: if not dct[typ.key]: dct[typ.key] = None if typ.is_multiple: dct[typ.key] = [] continue if typ.is_multiple: value = [] for v in dct[typ.key]: try: value.append(typ.model.objects.get(pk=v, available=True)) except typ.model.DoesNotExist: continue else: try: value = typ.model.objects.get(pk=dct[typ.key], available=True) except typ.model.DoesNotExist: value = None dct[typ.key] = value periods = dct.pop('periods') remains = dct.pop('remains') cultural_attributions = dct.pop('cultural_attributions') item = models.ArchaeologicalSite.objects.create(**dct) for period in periods: item.periods.add(period) for remain in remains: item.remains.add(remain) for cultural_attribution in cultural_attributions: item.cultural_attributions.add(cultural_attribution) return item class ArchaeologicalSiteBasicForm(widgets.Select2Media, IshtarForm): form_label = _("Archaeological site") base_model = 'archaeological_site' associated_models = {'archaeological_site': models.ArchaeologicalSite} archaeological_site = forms.IntegerField( label=_("Archaeological site"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-archaeologicalsite'), associated_model=models.ArchaeologicalSite, new=True), validators=[valid_id(models.ArchaeologicalSite)], required=False) SITE_KEYS = {"archaeological_site": None} ArchaeologicalSiteFormSet = formset_factory( ArchaeologicalSiteBasicForm, can_delete=True, formset=FormSet) ArchaeologicalSiteFormSet.form_label = _("Archaeological sites") ArchaeologicalSiteFormSet.form_admin_name = _( "Operation - 030 - Archaeological sites") ArchaeologicalSiteFormSet.form_slug = "operation-030-archaeological-sites" ArchaeologicalSiteFormSet.extra_form_modals = ["archaeologicalsite"] class ArchaeologicalSiteSelectionForm(IshtarForm): # TODO: used? form_label = _("Associated archaeological sites") archaeological_sites = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-archaeologicalsite'), associated_model=models.ArchaeologicalSite, new=True, multiple=True), label=_("Search")) class FinalOperationClosingForm(FinalForm): confirm_msg = " " confirm_end_msg = _("Would you like to close this operation?") class OperationDeletionForm(FinalForm): confirm_msg = " " confirm_end_msg = _("Would you like to delete this operation?") ######### # Sites # ######### class SiteSelect(DocumentItemSelect): _model = models.ArchaeologicalSite form_admin_name = _("Archaeological site - 001 - Search") form_slug = "archaeological_site-001-search" search_vector = forms.CharField( label=_("Full text search"), widget=widgets.SearchWidget( 'archaeological-operations', 'site')) reference = forms.CharField(label=_("Reference"), max_length=200, required=False) name = forms.CharField(label=_("Name"), max_length=200, required=False) other_reference = forms.CharField(label=_("Other reference"), max_length=200, required=False) periods = forms.ChoiceField(label=_("Periods"), choices=[], required=False) remains = forms.ChoiceField(label=_("Remains"), choices=[], required=False) cultural_attributions = forms.ChoiceField( label=_("Cultural attribution"), choices=[], required=False) towns = get_town_field() towns__areas = forms.ChoiceField(label=_("Areas"), choices=[]) comment = forms.CharField(label=_("Comment"), max_length=200, required=False) top_operation = forms.IntegerField( label=_("Top operation"), required=False, widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-operation'), associated_model=models.Operation), validators=[valid_id(models.Operation)]) operation = forms.IntegerField( label=_("Operation"), required=False, widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-operation'), associated_model=models.Operation), validators=[valid_id(models.Operation)]) locality_ngi = forms.CharField( label=_("National Geographic Institute locality"), max_length=200, required=False) locality_cadastral = forms.CharField( label=_("Cadastral locality"), max_length=200, required=False) affmar_number = forms.CharField( label=_("AffMar number"), required=False, max_length=100) drassm_number = forms.CharField( label=_("DRASSM number"), required=False, max_length=100) shipwreck_name = forms.CharField( label=_("Shipwreck name"), max_length=200, required=False) oceanographic_service_localisation = forms.CharField( label=_("Oceanographic service localisation"), max_length=200, required=False) shipwreck_code = forms.CharField( label=_("Shipwreck code"), max_length=200, required=False) sinking_date = DateField(label=_("Sinking date"), required=False) discovery_area = forms.CharField( label=_("Discovery area"), max_length=200, required=False) TYPES = [ FieldType('periods', models.Period), FieldType('remains', models.RemainType), FieldType('cultural_attributions', models.CulturalAttributionType), FieldType('towns__areas', Area), ] def __init__(self, *args, **kwargs): super(SiteSelect, self).__init__(*args, **kwargs) if not get_current_profile().underwater: self.fields.pop('shipwreck_name') self.fields.pop('oceanographic_service_localisation') self.fields.pop('shipwreck_code') self.fields.pop('sinking_date') self.fields.pop('discovery_area') self.fields.pop('affmar_number') self.fields.pop('drassm_number') class SiteFormSelection(LockForm, CustomFormSearch): SEARCH_AND_SELECT = True associated_models = {'pk': models.ArchaeologicalSite} currents = {'pk': models.ArchaeologicalSite} pk = forms.IntegerField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-site'), SiteSelect, models.ArchaeologicalSite, gallery=True, map=True, source_full=reverse_lazy('get-site-full')), validators=[valid_id(models.ArchaeologicalSite)]) @classmethod def form_label(cls): return get_current_profile().get_site_label('search') class SiteFormMultiSelection(LockForm, MultiSearchForm): associated_models = {'pks': models.ArchaeologicalSite} pk = forms.CharField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-site'), SiteSelect, models.ArchaeologicalSite, gallery=True, map=True, multiple_select=True, source_full=reverse_lazy('get-site-full')), validators=[valid_ids(models.ArchaeologicalSite)]) @classmethod def form_label(cls): return get_current_profile().get_site_label('search') class SiteForm(CustomForm, ManageOldType): HEADERS = {} form_label = _("General") form_admin_name = _("Archaeological site - 010 - General") form_slug = "archaeological_site-010-general" associated_models = {'period': models.Period, 'remain': models.RemainType, 'spatial_reference_system': SpatialReferenceSystem, 'cultural_attribution': models.CulturalAttributionType, 'collaborator': Person} base_models = ["period", "remain", "collaborator", "cultural_attribution"] pk = forms.IntegerField(required=False, widget=forms.HiddenInput) reference = forms.CharField(label=_("Reference"), max_length=200) name = forms.CharField(label=_("Name"), max_length=200, required=False) other_reference = forms.CharField(label=_("Other reference"), required=False) period = forms.MultipleChoiceField( label=_("Periods"), choices=[], widget=widgets.Select2Multiple, required=False) remain = forms.MultipleChoiceField( label=_("Remains"), choices=[], widget=widgets.Select2Multiple, required=False) cultural_attribution = forms.MultipleChoiceField( label=_("Cultural attributions"), choices=[], widget=widgets.Select2Multiple, required=False) collaborator = widgets.Select2MultipleField( model=Person, label=_("Collaborators"), required=False, remote=True) comment = forms.CharField(label=_("Comment"), widget=forms.Textarea, required=False) locality_ngi = forms.CharField( label=_("National Geographic Institute locality"), widget=forms.Textarea, required=False ) locality_cadastral = forms.CharField( label=_("Cadastral locality"), widget=forms.Textarea, required=False ) 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=[]) PROFILE_FILTER = { 'mapping': [ 'x', 'y', 'z', 'estimated_error_x', 'estimated_error_y', 'estimated_error_z', 'spatial_reference_system' ], } TYPES = [ FieldType('period', models.Period, True), FieldType('remain', models.RemainType, True), FieldType('cultural_attribution', models.CulturalAttributionType, True), FieldType('spatial_reference_system', SpatialReferenceSystem), ] def __init__(self, *args, **kwargs): super(SiteForm, self).__init__(*args, **kwargs) if 'collaborator' in self.fields: self.fields['collaborator'].widget.attrs['full-width'] = True def clean_reference(self): reference = self.cleaned_data['reference'] q = models.ArchaeologicalSite.objects.filter(reference=reference) if 'pk' in self.cleaned_data and self.cleaned_data['pk']: q = q.exclude(pk=self.cleaned_data['pk']) if q.count(): raise forms.ValidationError(_("This reference already exists.")) return reference SiteTownFormset = formset_factory(TownForm, can_delete=True, formset=TownFormSet) SiteTownFormset.form_label = _("Towns") SiteTownFormset.form_admin_name = _("Archaeological site - 020 - Towns") SiteTownFormset.form_slug = "archaeological_site-020-towns" def check_underwater_module(self): return get_current_profile().underwater class SiteUnderwaterForm(CustomForm, ManageOldType): form_label = _("Underwater") form_admin_name = _("Archaeological site - 030 - Underwater") form_slug = "archaeological_site-030-underwater" affmar_number = forms.CharField( label=_("AffMar number"), required=False, max_length=100) drassm_number = forms.CharField( label=_("DRASSM number"), required=False, max_length=100) shipwreck_name = forms.CharField( label=_("Shipwreck name"), required=False) shipwreck_code = forms.CharField( label=_("Shipwreck code"), required=False) sinking_date = DateField( label=_("Sinking date"), required=False) discovery_area = forms.CharField( label=_("Discovery area"), widget=forms.Textarea, required=False) oceanographic_service_localisation = forms.CharField( label=_("Oceanographic service localisation"), widget=forms.Textarea, required=False ) ################################################ # Administrative act management for operations # ################################################ class AdministrativeActOpeSelect(TableSelect): _model = models.AdministrativeAct search_vector = forms.CharField( label=_("Full text search"), widget=widgets.SearchWidget( 'archaeological-operations', 'administrativeact', 'administrativeactop', )) year = forms.IntegerField(label=_("Year")) index = forms.IntegerField(label=_("Index")) if settings.COUNTRY == 'fr': ref_sra = forms.CharField(label="Autre référence", max_length=15) operation__code_patriarche = forms.CharField( max_length=500, widget=OAWidget, label="Code PATRIARCHE") act_type = forms.ChoiceField(label=_("Act type"), choices=[]) indexed = forms.NullBooleanField(label=_("Indexed?")) operation__towns = get_town_field() parcel = forms.CharField(label=_("Parcel")) if settings.ISHTAR_DPTS: operation__towns__numero_insee__startswith = forms.ChoiceField( label=_("Department"), choices=[]) act_object = forms.CharField(label=_("Object"), max_length=300) history_creator = forms.IntegerField( label=_("Created by"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person', args=['0', 'user']), associated_model=Person), validators=[valid_id(Person)]) history_modifier = forms.IntegerField( label=_("Modified by"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person', args=['0', 'user']), associated_model=Person), validators=[valid_id(Person)]) def __init__(self, *args, **kwargs): super(AdministrativeActOpeSelect, self).__init__(*args, **kwargs) self.fields['act_type'].choices = models.ActType.get_types( dct={'intented_to': 'O'}) self.fields['act_type'].help_text = models.ActType.get_help( dct={'intented_to': 'O'}) if settings.ISHTAR_DPTS: k = 'operation__towns__numero_insee__startswith' self.fields[k].choices = [ ('', '--')] + list(settings.ISHTAR_DPTS) class AdministrativeActOpeFormSelection(IshtarForm): SEARCH_AND_SELECT = True form_label = _("Administrative act search") associated_models = {'pk': models.AdministrativeAct} currents = {'pk': models.AdministrativeAct} pk = forms.IntegerField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-administrativeactop'), AdministrativeActOpeSelect, models.AdministrativeAct, table_cols='TABLE_COLS_OPE'), validators=[valid_id(models.AdministrativeAct)]) def clean(self): cleaned_data = self.cleaned_data if 'pk' not in cleaned_data or not cleaned_data['pk']: raise forms.ValidationError( _("You should select an administrative act.")) return cleaned_data class AdministrativeActForm(CustomForm, ManageOldType): form_label = _("General") associated_models = {'act_type': models.ActType, 'signatory': Person} act_type = forms.ChoiceField(label=_("Act type"), choices=[]) signatory = forms.IntegerField( label=_("Signatory"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person'), associated_model=Person, new=True), validators=[valid_id(Person)], required=False) act_object = forms.CharField(label=_("Object"), max_length=300, widget=forms.Textarea, required=False) signature_date = DateField(label=_("Signature date"), required=False) if settings.COUNTRY == 'fr': ref_sra = forms.CharField(label="Autre référence", max_length=15, required=False) TYPES = [ FieldType('act_type', models.ActType, extra_args={"dct": {'intented_to': 'O'}}), ] class AdministrativeActOpeForm(AdministrativeActForm): form_admin_name = _("Operation - Administrative act - General") form_slug = "operation-adminact-general" class AdministrativeActModifForm(object): def __init__(self, *args, **kwargs): super(AdministrativeActModifForm, self).__init__(*args, **kwargs) fields = OrderedDict() idx = self.fields.pop('index') for key, value in self.fields.items(): fields[key] = value if key == 'signature_date': fields['index'] = idx self.fields = fields def clean(self): # manage unique act ID year = self.cleaned_data.get("signature_date", None) if not year or not hasattr(year, 'year'): return self.cleaned_data year = year.year index = self.cleaned_data.get("index", None) if not index: return self.cleaned_data items = models.AdministrativeAct.objects.filter( year=year, index=index) if 'pk' in self.cleaned_data and self.cleaned_data['pk']: items = items.exclude(pk=self.cleaned_data['pk']) if items.count(): max_val = models.AdministrativeAct.objects.filter( year=year).aggregate(Max('index'))["index__max"] msg = '' if year and max_val: msg = _( "This index already exists for year: %(year)d - use a " "value bigger than %(last_val)d") % { 'year': year, 'last_val': max_val} else: msg = _("Bad index") raise forms.ValidationError(msg) return self.cleaned_data class AdministrativeActOpeModifForm(AdministrativeActModifForm, AdministrativeActOpeForm): pk = forms.IntegerField(required=False, widget=forms.HiddenInput) index = forms.IntegerField(label=_("Index"), required=False) class FinalAdministrativeActDeleteForm(FinalForm): confirm_msg = " " confirm_end_msg = _("Would you like to delete this administrative act?") class DocumentGenerationAdminActForm(IshtarForm): _associated_model = models.AdministrativeAct document_template = forms.ChoiceField(label=_("Template"), choices=[]) def __init__(self, *args, **kwargs): self.document_type = 'O' if 'document_type' in kwargs: self.document_type = kwargs.pop('document_type') self.obj = None if 'obj' in kwargs: self.obj = kwargs.pop('obj') super(DocumentGenerationAdminActForm, self).__init__(*args, **kwargs) self.fields['document_template'].choices = DocumentTemplate.get_tuples( dct={'associated_model__klass': 'archaeological_operations.models.AdministrativeAct', 'acttypes__intented_to': self.document_type}) def clean(self): if not self.obj: raise forms.ValidationError( _("You should select an administrative act.")) cleaned_data = self.cleaned_data try: dt = DocumentTemplate.objects.get( pk=self.cleaned_data['document_template']) except DocumentTemplate.DoesNotExist: raise forms.ValidationError(_("This document is not intended for " "this type of act.")) if self.obj.act_type.pk not in [ act_type.pk for act_type in dt.acttypes.all()]: raise forms.ValidationError(_("This document is not intended for " "this type of act.")) return cleaned_data def save(self, object_pk): try: c_object = self._associated_model.objects.get(pk=object_pk) except self._associated_model.DoesNotExist: return try: template = DocumentTemplate.objects.get( pk=self.cleaned_data.get('document_template')) except DocumentTemplate.DoesNotExist: return return template.publish(c_object) class GenerateDocForm(IshtarForm): form_label = _("Doc generation") doc_generation = forms.ChoiceField( required=False, choices=[], label=_("Generate the associated doc?")) def __init__(self, *args, **kwargs): choices = [] if 'choices' in kwargs: choices = kwargs.pop('choices') super(GenerateDocForm, self).__init__(*args, **kwargs) self.fields['doc_generation'].choices = [('', '-' * 9)] + \ [(choice.pk, str(choice)) for choice in choices] class AdministrativeActRegisterSelect(AdministrativeActOpeSelect): indexed = forms.NullBooleanField(label=_("Indexed?")) def __init__(self, *args, **kwargs): super(AdministrativeActRegisterSelect, self).__init__(*args, **kwargs) self.fields['act_type'].choices = models.ActType.get_types() self.fields['act_type'].help_text = models.ActType.get_help() class AdministrativeActRegisterFormSelection(IshtarForm): form_label = pgettext_lazy('admin act register', "Register") associated_models = {'pk': models.AdministrativeAct} currents = {'pk': models.AdministrativeAct} pk = forms.IntegerField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-administrativeact'), AdministrativeActRegisterSelect, models.AdministrativeAct, table_cols='TABLE_COLS', source_full=reverse_lazy('get-administrativeact-full')), validators=[valid_id(models.AdministrativeAct)]) def clean(self): cleaned_data = self.cleaned_data if 'pk' not in cleaned_data or not cleaned_data['pk']: raise forms.ValidationError( _("You should select an administrative act.")) return cleaned_data class QAOperationFormMulti(QAForm): form_admin_name = _("Operation - Quick action - Modify") form_slug = "operation-quickaction-modify" base_models = ['qa_operation_type'] associated_models = { 'qa_operation_type': models.OperationType, 'qa_towns': Town, 'qa_operator': Organization, } MULTI = True REPLACE_FIELDS = [ 'qa_operation_type', 'qa_operator', 'qa_documentation_received', 'qa_finds_received', ] qa_operation_type = forms.ChoiceField( label=_("Operation type"), required=False ) qa_towns = get_town_field(required=False) qa_operator = forms.IntegerField( label=_("Operator"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-organization', args=[organization_type_pk_lazy('operator')]), limit={'organization_type': organization_type_pk_lazy('operator')}, associated_model=Organization, new=True), validators=[valid_id(Organization)], required=False) qa_documentation_received = forms.ChoiceField( label=_("Documentation received"), required=False, choices=QAForm.NULL_BOOL_CHOICES ) qa_finds_received = forms.ChoiceField( label=_("Finds received"), required=False, choices=QAForm.NULL_BOOL_CHOICES ) TYPES = [ FieldType('qa_operation_type', models.OperationType), ] def _get_qa_towns(self, value): try: value = Town.objects.get(pk=value).cached_label except Town.DoesNotExist: return "" return value def _get_qa_operator(self, value): try: value = Organization.objects.get(pk=value).cached_label except Organization.DoesNotExist: return "" return value def _get_qa_finds_received(self, value): return self._get_null_boolean_field(value) def _get_qa_documentation_received(self, value): return self._get_null_boolean_field(value) def _set_qa_finds_received(self, item, __): return self._set_null_boolean_field(item, "qa_finds_received") def _set_qa_documentation_received(self, item, __): return self._set_null_boolean_field(item, "qa_documentation_received") class QAOperationDuplicateForm(IshtarForm): qa_code_patriarche = forms.CharField( max_length=500, widget=OAWidget, label=_("Code PATRIARCHE"), required=False) qa_year = forms.IntegerField(label=_("Year"), required=False, validators=[validators.MinValueValidator(1000), validators.MaxValueValidator(2100)]) qa_common_name = forms.CharField(label=_("Generic name"), required=False, max_length=500, widget=forms.Textarea) qa_operation_type = forms.ChoiceField(label=_("Operation type"), choices=[]) TYPES = [ FieldType('qa_operation_type', models.OperationType), ] 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.operation = kwargs.pop('items')[0] super(QAOperationDuplicateForm, self).__init__(*args, **kwargs) self.fields['qa_year'].initial = self.operation.year self.fields['qa_common_name'].initial = self.operation.common_name self.fields["qa_operation_type"].initial = \ self.operation.operation_type.pk def clean_qa_code_patriarche(self): code = self.cleaned_data['qa_code_patriarche'] if models.Operation.objects \ .filter(code_patriarche=code).count(): raise forms.ValidationError(_("This code already exists.")) return code def save(self): data = {"operation_code": None} for k in ("code_patriarche", "common_name", "year"): data[k] = self.cleaned_data.get("qa_" + k, None) try: data["operation_type"] = models.OperationType.objects.get( pk=self.cleaned_data["qa_operation_type"], available=True ) except models.OperationType.DoesNotExist: return operation = self.operation.duplicate(self.user, data=data) # clear associated sites operation.archaeological_sites.clear() operation.skip_history_when_saving = True operation._cached_label_checked = False operation._search_updated = False operation._no_move = True operation.save() # regen of labels return operation class QAArchaeologicalSiteDuplicateForm(IshtarForm): qa_reference = forms.CharField(label=_("Reference"), max_length=200) qa_name = forms.CharField(label=_("Name"), max_length=200, required=False) 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.site = kwargs.pop('items')[0] super(QAArchaeologicalSiteDuplicateForm, self).__init__(*args, **kwargs) self.fields['qa_reference'].initial = ( self.site.reference or "") + str(_(" - duplicate")) self.fields['qa_name'].initial = self.site.name def clean_qa_reference(self): reference = self.cleaned_data['qa_reference'] if models.ArchaeologicalSite.objects \ .filter(reference=reference).count(): raise forms.ValidationError(_("This reference already exists.")) return reference def save(self): data = {} for k in ("name", "reference"): data[k] = self.cleaned_data.get("qa_" + k, None) return self.site.duplicate(self.user, data=data) class QAArchaeologicalSiteFormMulti(QAForm): form_admin_name = _("Archaeological files - Quick action - Modify") form_slug = "archaeological_site-quickaction-modify" associated_models = { 'qa_towns': Town, } MULTI = True qa_towns = get_town_field(required=False) def _get_qa_towns(self, value): try: value = Town.objects.get(pk=value).cached_label except Town.DoesNotExist: return "" return value