#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010-2017 É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. from collections import OrderedDict import datetime from django import forms from django.db.models import Max from django.conf import settings from django.forms.formsets import formset_factory from ishtar_common.utils import ugettext_lazy as _ from ishtar_common.models import Person, valid_id, Town, \ SpatialReferenceSystem, Organization, valid_ids, person_type_pks_lazy from archaeological_operations.models import ArchaeologicalSite from archaeological_context_records.models import ContextRecord from archaeological_finds.models import TreatmentType, FindBasket, \ MaterialType, ObjectType, IntegrityType, RemarkabilityType, \ ConservatoryState, AlterationType, AlterationCauseType, \ TreatmentEmergencyType from . import models from ishtar_common import widgets from archaeological_operations.widgets import OAWidget from bootstrap_datepicker.widgets import DatePicker from ishtar_common.forms import name_validator, reverse_lazy, \ get_form_selection, ManageOldType, FinalForm, FormSet, \ CustomForm, FieldType, HistorySelect, FormHeader, TableSelect, \ CustomFormSearch, MultiSearchForm, LockForm from ishtar_common.forms_common import get_town_field from archaeological_finds.forms import FindMultipleFormSelection, \ SelectFindBasketForm def get_warehouse_field(label=_(u"Warehouse"), required=True): # !FIXME hard_link, reverse_lazy doen't seem to work with formsets url = "/" + settings.URL_PATH + 'autocomplete-warehouse' widget = widgets.JQueryAutoComplete(url, associated_model=models.Warehouse) return forms.IntegerField(widget=widget, label=label, required=required, validators=[valid_id(models.Warehouse)]) class SelectedDivisionForm(ManageOldType, forms.Form): form_label = _(u"Division") base_model = 'associated_division' associated_models = {'division': models.WarehouseDivision, 'associated_division': models.WarehouseDivisionLink} division = forms.ChoiceField( label=_(u"Division"), choices=(), validators=[valid_id(models.WarehouseDivision)]) order = forms.IntegerField(label=_(u"Order"), min_value=0, required=False) def __init__(self, *args, **kwargs): super(SelectedDivisionForm, self).__init__(*args, **kwargs) self.fields['division'].choices = \ models.WarehouseDivision.get_types( initial=self.init_data.get('division') ) class DivisionFormSet(FormSet): def clean(self): """Checks that no divisions are duplicated.""" self.check_duplicate(('division',), _("There are identical divisions.")) self.check_duplicate(('order',), _(u"Order fields must be different."), check_null=True) SelectedDivisionFormset = formset_factory( SelectedDivisionForm, can_delete=True, formset=DivisionFormSet) SelectedDivisionFormset.form_label = _(u"Divisions") SelectedDivisionFormset.form_admin_name = _(u"Warehouse - 020 - Divisions") SelectedDivisionFormset.form_slug = "warehouse-020-divisions" class WarehouseSelect(CustomForm, TableSelect): _model = models.Warehouse form_admin_name = _(u"Warehouse - 001 - Search") form_slug = "warehouse-001-search" search_vector = forms.CharField( label=_(u"Full text search"), widget=widgets.SearchWidget( 'archaeological-warehouse', 'warehouse' )) name = forms.CharField(label=_("Name")) warehouse_type = forms.ChoiceField(label=_("Warehouse type"), choices=[]) town = get_town_field(label=_("Town")) def __init__(self, *args, **kwargs): super(WarehouseSelect, self).__init__(*args, **kwargs) self.fields['warehouse_type'].choices = \ models.WarehouseType.get_types() self.fields['warehouse_type'].help_text = \ models.WarehouseType.get_help() class WarehouseFormSelection(LockForm, CustomFormSearch): SEARCH_AND_SELECT = True form_label = _("Warehouse search") associated_models = {'pk': models.Warehouse} currents = {'pk': models.Warehouse} pk = forms.IntegerField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-warehouse'), WarehouseSelect, models.Warehouse, gallery=True, map=True), validators=[valid_id(models.Warehouse)]) class WarehouseFormMultiSelection(LockForm, MultiSearchForm): form_label = _("Warehouse search") associated_models = {'pks': models.Warehouse} pk = forms.CharField( label="", required=False, widget=widgets.DataTable( reverse_lazy('get-warehouse'), WarehouseSelect, models.Warehouse, gallery=True, map=True, multiple_select=True ), validators=[valid_ids(models.Warehouse)]) class WarehouseForm(CustomForm, ManageOldType, forms.Form): HEADERS = {} form_label = _(u"Warehouse") form_admin_name = _(u"Warehouse - 010 - General") form_slug = "warehouse-010-general" extra_form_modals = ["organization", "person"] associated_models = { 'warehouse_type': models.WarehouseType, 'person_in_charge': Person, 'organization': Organization, 'precise_town': Town, 'spatial_reference_system': SpatialReferenceSystem } name = forms.CharField(label=_(u"Name"), max_length=200, validators=[name_validator]) warehouse_type = forms.ChoiceField(label=_(u"Warehouse type"), choices=[]) organization = forms.IntegerField( label=_("Organization"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-organization'), associated_model=Organization, new=True), validators=[valid_id(Organization)], required=False) person_in_charge = forms.IntegerField( label=_("Person in charge"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person'), associated_model=Person, new=True), validators=[valid_id(Person)], required=False) create_organization = forms.BooleanField( label=_("Create a new organization from this warehouse"), required=False ) comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea, required=False) HEADERS['address'] = FormHeader( _(u"Address"), collapse=True, help_message=_( "Only fill the following fields if no organization is provided or " "if the address of the warehouse is different from the one of the " "organization. If a new organization is created from this " "warehouse, the following fields are used for the organization.")) address = forms.CharField(label=_(u"Address"), widget=forms.Textarea, required=False) address_complement = forms.CharField(label=_(u"Address complement"), widget=forms.Textarea, required=False) postal_code = forms.CharField(label=_(u"Postal code"), max_length=10, required=False) town = forms.CharField(label=_(u"Town (freeform)"), max_length=150, required=False) precise_town = get_town_field(required=False) country = forms.CharField(label=_(u"Country"), max_length=30, required=False) phone = forms.CharField(label=_(u"Phone"), max_length=18, required=False) mobile_phone = forms.CharField(label=_(u"Mobile phone"), max_length=18, required=False) HEADERS['x'] = FormHeader(_("Coordinates")) x = forms.FloatField(label=_("X"), required=False) y = forms.FloatField(label=_("Y"), required=False) spatial_reference_system = forms.ChoiceField( label=_("Spatial Reference System"), required=False, choices=[]) TYPES = [ FieldType('warehouse_type', models.WarehouseType), FieldType('spatial_reference_system', SpatialReferenceSystem) ] def __init__(self, *args, **kwargs): if 'limits' in kwargs: kwargs.pop('limits') super(WarehouseForm, self).__init__(*args, **kwargs) def clean(self): if self.cleaned_data.get("organization", None) and \ self.cleaned_data.get("create_organization", None): raise forms.ValidationError( _("A new organization is not created if an organization is " "selected.")) return self.cleaned_data def save(self, user): dct = self.cleaned_data dct['history_modifier'] = user dct['warehouse_type'] = models.WarehouseType.objects.get( pk=dct['warehouse_type']) if 'person_in_charge' in dct and dct['person_in_charge']: dct['person_in_charge'] = Person.objects.get( pk=dct['person_in_charge']) if 'organization' in dct and dct['organization']: dct['organization'] = Organization.objects.get( pk=dct['organization']) if not dct.get("spatial_reference_system", None): dct.pop("spatial_reference_system") create_orga = dct.pop("create_organization") new_item = models.Warehouse(**dct) new_item.save() if not create_orga: return new_item new_item.create_attached_organization() return new_item class WarehouseModifyForm(WarehouseForm): def __init__(self, *args, **kwargs): super(WarehouseModifyForm, self).__init__(*args, **kwargs) self.fields.pop("create_organization") class WarehouseDeletionForm(FinalForm): confirm_msg = _(u"Would you like to delete this warehouse?") confirm_end_msg = _(u"Would you like to delete this warehouse?") class ContainerForm(CustomForm, ManageOldType, forms.Form): form_label = _(u"Container") form_admin_name = _(u"Container - 010 - General") form_slug = "container-010-general" file_upload = True extra_form_modals = ["warehouse", "organization", "person"] associated_models = {'container_type': models.ContainerType, 'location': models.Warehouse, 'responsible': models.Warehouse} reference = forms.CharField(label=_(u"Ref."), max_length=200) old_reference = forms.CharField(label=_(u"Old reference"), required=False, max_length=200) container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) responsible = forms.IntegerField( label=_(u"Responsible warehouse"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-warehouse'), associated_model=models.Warehouse, new=True), validators=[valid_id(models.Warehouse)]) location = forms.IntegerField( label=_(u"Current location (warehouse)"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-warehouse'), associated_model=models.Warehouse, new=True), validators=[valid_id(models.Warehouse)]) comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea, required=False) TYPES = [ FieldType('container_type', models.ContainerType), ] class Media: js = ('forms/container.js',) def __init__(self, *args, **kwargs): if 'limits' in kwargs: kwargs.pop('limits') super(ContainerForm, self).__init__(*args, **kwargs) def clean(self): cleaned_data = self.cleaned_data warehouse = cleaned_data.get("location") q = models.Container.objects.filter( reference=cleaned_data.get("reference"), location__pk=warehouse) if 'pk' in cleaned_data and cleaned_data['pk']: q = q.exclude(pk=int(cleaned_data['pk'])) if q.count(): raise forms.ValidationError(_(u"This reference already exists for " u"this warehouse.")) return cleaned_data def save(self, user): dct = self.cleaned_data dct['history_modifier'] = user dct['container_type'] = models.ContainerType.objects.get( pk=dct['container_type']) dct['location'] = models.Warehouse.objects.get(pk=dct['location']) dct['responsible'] = models.Warehouse.objects.get(pk=dct['responsible']) new_item = models.Container(**dct) new_item.save() return new_item class ContainerModifyForm(ContainerForm): pk = forms.IntegerField(required=False, widget=forms.HiddenInput) index = forms.IntegerField(label=_(u"ID"), required=False) def __init__(self, *args, **kwargs): super(ContainerModifyForm, self).__init__(*args, **kwargs) fields = OrderedDict() idx = self.fields.pop('index') for key, value in self.fields.items(): fields[key] = value if key == 'location': fields['index'] = idx self.fields = fields def clean(self): # manage unique ID cleaned_data = super(ContainerModifyForm, self).clean() index = cleaned_data.get("index", None) warehouse = cleaned_data.get("responsible") if not index: q = models.Container.objects.filter(responsible__pk=warehouse) if not q.count(): cleaned_data["index"] = 1 else: cleaned_data["index"] = int(q.aggregate( Max("index"))["index__max"]) + 1 else: q = models.Container.objects.filter( index=index, location__pk=warehouse) if 'pk' in cleaned_data and cleaned_data['pk']: q = q.exclude(pk=int(cleaned_data['pk'])) if q.count(): raise forms.ValidationError(_(u"This ID already exists for " u"this warehouse.")) return cleaned_data class ContainerSelect(HistorySelect): _model = models.Container form_admin_name = _(u"Container - 001 - Search") form_slug = "container-001-search" search_vector = forms.CharField( label=_(u"Full text search"), widget=widgets.SearchWidget( 'archaeological-warehouse', 'container' )) location_name = get_warehouse_field( label=_(u"Current location (warehouse)")) responsible_name = get_warehouse_field(label=_(u"Responsible warehouse")) container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) reference = forms.CharField(label=_(u"Ref.")) old_reference = forms.CharField(label=_(u"Old reference")) comment = forms.CharField(label=_(u"Comment")) no_finds = forms.NullBooleanField(label=_(u"No associated finds")) empty = forms.NullBooleanField(label=_(u"Currently empty")) archaeological_sites = forms.IntegerField( label=_("Archaeological site (attached to the operation)"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-archaeologicalsite'), associated_model=ArchaeologicalSite), validators=[valid_id(ArchaeologicalSite)]) archaeological_sites_name = forms.CharField( label=_(u"Archaeological site name (attached to the operation)") ) archaeological_sites_context_record = forms.IntegerField( label=_("Archaeological site (attached to the context record)"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-archaeologicalsite'), associated_model=ArchaeologicalSite), validators=[valid_id(ArchaeologicalSite)]) archaeological_sites_context_record_name = forms.CharField( label=_(u"Archaeological site name (attached to the context record)") ) code_patriarche = forms.IntegerField(label=_("Operation - Code PATRIARCHE"), widget=OAWidget) operation_town = get_town_field(label=_("Operation - town")) operation_scientist = forms.IntegerField( widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person-permissive'), associated_model=Person), label=_("Operation - Scientist")) context_record = forms.IntegerField( label=_("Context record"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-contextrecord'), associated_model=ContextRecord), validators=[valid_id(ContextRecord)]) find_label = forms.CharField(label=_("Find - Label")) find_denomination = forms.CharField(label=_("Find - Denomination")) description = forms.CharField(label=_(u"Find - Description")) material_types = forms.IntegerField( label=_(u"Material type"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-materialtype'), associated_model=MaterialType), ) object_types = forms.IntegerField( label=_(u"Object type"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-objecttype'), associated_model=ObjectType), ) integrities = forms.ChoiceField(label=_(u"Integrity / interest"), choices=[]) remarkabilities = forms.ChoiceField(label=_(u"Remarkability"), choices=[]) conservatory_state = forms.ChoiceField(label=_(u"Conservatory state"), choices=[]) alterations = forms.ChoiceField( label=_(u"Alteration"), choices=[]) alteration_causes = forms.ChoiceField( label=_(u"Alteration cause"), choices=[]) preservation_to_considers = forms.ChoiceField( choices=[], label=_(u"Preservation type")) treatment_emergency = forms.ChoiceField( choices=[], label=_(u"Treatment emergency") ) TYPES = [ FieldType('integrities', IntegrityType), FieldType('remarkabilities', RemarkabilityType), FieldType('conservatory_state', ConservatoryState), FieldType('alterations', AlterationType), FieldType('alteration_causes', AlterationCauseType), FieldType('preservation_to_considers', TreatmentType), FieldType('treatment_emergency', TreatmentEmergencyType), FieldType('container_type', models.ContainerType) ] SITE_KEYS = { "archaeological_sites": "attached-to-operation", "archaeological_sites_name": "name-attached-to-operation", "archaeological_sites_context_record": "attached-to-cr", "archaeological_sites_context_record_name": "name-attached-to-cr", } ContainerFormSelection = get_form_selection( 'ContainerFormSelection', _(u"Container search"), 'container', models.Container, ContainerSelect, 'get-container', _(u"You should select a container."), new=True, new_message=_(u"Add a new container"), base_form_select=(LockForm, CustomFormSearch) ) MainContainerFormSelection = get_form_selection( 'ContainerFormSelection', _(u"Container search"), 'pk', models.Container, ContainerSelect, 'get-container', _(u"You should select a container."), gallery=True, map=True, base_form_select=CustomFormSearch ) MainContainerFormMultiSelection = get_form_selection( 'ContainerFormSelection', _(u"Container search"), 'pks', models.Container, ContainerSelect, 'get-container', _(u"You should select a container."), gallery=True, map=True, alt_pk_field="pk", multi=True, base_form_select=(LockForm, MultiSearchForm) ) class BasePackagingForm(SelectFindBasketForm): form_label = _(u"Packaging") associated_models = {'treatment_type': TreatmentType, 'person': Person, 'location': models.Warehouse, 'basket': FindBasket} person = forms.IntegerField( label=_(u"Packager"), widget=widgets.JQueryAutoComplete( reverse_lazy('autocomplete-person'), associated_model=Person, new=True), validators=[valid_id(Person)]) start_date = forms.DateField( label=_(u"Date"), required=False, widget=DatePicker, initial=datetime.date.today ) class FindPackagingFormSelection(FindMultipleFormSelection): form_label = _(u"Packaged finds") class LocalisationForm(CustomForm, forms.Form): form_admin_name = _(u"Container - 020 - Localisation") form_slug = "container-020-localisation" form_label = _(u"Localisation") def __init__(self, *args, **kwargs): self.container, self.warehouse = None, None if 'warehouse' in kwargs: self.warehouse = kwargs.pop('warehouse') if 'container' in kwargs: self.container = kwargs.pop('container') super(LocalisationForm, self).__init__(*args, **kwargs) if not self.warehouse: return for divlink in self.warehouse.divisions.order_by('order').all(): initial = u"-" if self.container: q = models.ContainerLocalisation.objects.filter( division__division=divlink.division, container=self.container) if q.count(): initial = q.all()[0].reference self.fields['division_{}'.format(divlink.pk)] = forms.CharField( label=str(divlink.division), max_length=200, initial=initial, ) class ContainerDeletionForm(FinalForm): confirm_msg = _(u"Would you like to delete this container?") confirm_end_msg = _(u"Would you like to delete this container?")