summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2017-11-21 15:26:41 +0100
committerÉtienne Loks <etienne.loks@iggdrasil.net>2017-11-21 15:26:41 +0100
commitff60c200ddc406250c335de3b59a56b9327a16ec (patch)
treeb631fd7d258640517b986708b1db9f35239d95ce
parent6a3c8c3ff340110960bf64dbfc31e526243f6616 (diff)
downloadIshtar-ff60c200ddc406250c335de3b59a56b9327a16ec.tar.bz2
Ishtar-ff60c200ddc406250c335de3b59a56b9327a16ec.zip
Custom forms: manage formsets
-rw-r--r--archaeological_finds/forms.py12
-rw-r--r--archaeological_operations/forms.py84
-rw-r--r--archaeological_operations/views.py5
-rw-r--r--ishtar_common/forms.py180
-rw-r--r--ishtar_common/tests.py2
5 files changed, 173 insertions, 110 deletions
diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py
index b88ee164e..2a895064f 100644
--- a/archaeological_finds/forms.py
+++ b/archaeological_finds/forms.py
@@ -41,7 +41,7 @@ import models
from ishtar_common.forms import FormSet, FloatField, \
get_form_selection, reverse_lazy, TableSelect, get_now, FinalForm, \
- ManageOldType
+ ManageOldType, FieldType
from ishtar_common.forms_common import get_town_field, SourceSelect
from ishtar_common.utils import convert_coordinates_to_point
@@ -316,11 +316,11 @@ class PreservationForm(ManageOldType, forms.Form):
widget=forms.Textarea)
TYPES = [
- ('conservatory_state', models.ConservatoryState, False),
- ('treatment_emergency', models.TreatmentEmergencyType, False),
- ('preservation_to_consider', models.TreatmentType, True),
- ('alteration', models.AlterationType, True),
- ('alteration_cause', models.AlterationCauseType, True),
+ FieldType('conservatory_state', models.ConservatoryState),
+ FieldType('treatment_emergency', models.TreatmentEmergencyType),
+ FieldType('preservation_to_consider', models.TreatmentType, True),
+ FieldType('alteration', models.AlterationType, True),
+ FieldType('alteration_cause', models.AlterationCauseType, True),
]
def __init__(self, *args, **kwargs):
diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py
index 47fe746f5..4dd896966 100644
--- a/archaeological_operations/forms.py
+++ b/archaeological_operations/forms.py
@@ -49,7 +49,7 @@ from ishtar_common import widgets
from ishtar_common.forms import FinalForm, FormSet, get_now, \
reverse_lazy, get_form_selection, TableSelect, get_data_from_formset, \
- ManageOldType, CustomForm
+ ManageOldType, CustomForm, FieldType
from ishtar_common.forms_common import TownFormSet, SourceForm, SourceSelect, \
get_town_field
@@ -763,8 +763,9 @@ class DashboardForm(forms.Form):
class OperationFormGeneral(ManageOldType, CustomForm, forms.Form):
form_label = _(u"General")
- form_admin_name = _(u"Operation - General")
- form_slug = "operation-general"
+ form_admin_name = _(u"Operation - General - Creation")
+ form_slug = "operation-general-creation"
+
file_upload = True
associated_models = {'scientist': Person,
'in_charge': Person,
@@ -884,33 +885,36 @@ class OperationFormGeneral(ManageOldType, CustomForm, forms.Form):
'height': settings.IMAGE_MAX_SIZE[1]}),
max_length=255, required=False, widget=widgets.ImageFileInput())
+ FILE_FIELDS = [
+ 'report_delivery_date',
+ 'report_processing',
+ 'cira_rapporteur',
+ 'cira_date',
+ 'negative_result'
+ ]
+ WAREHOUSE_FIELDS = [
+ 'documentation_deadline',
+ 'documentation_received',
+ 'finds_deadline',
+ 'finds_received',
+ ]
+ TYPES = [
+ FieldType('operation_type', models.OperationType),
+ FieldType('report_processing', models.ReportState),
+ ]
+
def __init__(self, *args, **kwargs):
super(OperationFormGeneral, self).__init__(*args, **kwargs)
profile = get_current_profile()
if not profile.files:
- self.fields.pop('report_delivery_date')
- self.fields.pop('report_processing')
- self.fields.pop('cira_rapporteur')
- self.fields.pop('cira_date')
- self.fields.pop('negative_result')
+ for key in self.FILE_FIELDS:
+ self.remove_field(key)
if not profile.warehouse:
- self.fields.pop('documentation_deadline')
- self.fields.pop('documentation_received')
- self.fields.pop('finds_deadline')
- self.fields.pop('finds_received')
- self.fields['operation_type'].choices = \
- models.OperationType.get_types(
- initial=self.init_data.get('operation_type'))
- self.fields['operation_type'].help_text = \
- models.OperationType.get_help()
- if 'report_processing' in self.fields:
- self.fields['report_processing'].choices = \
- models.ReportState.get_types(
- initial=self.init_data.get('report_processing'))
- self.fields['report_processing'].help_text = \
- models.ReportState.get_help()
- self.fields['record_quality'].choices = \
- [('', '--')] + list(models.QUALITY)
+ for key in self.WAREHOUSE_FIELDS:
+ self.remove_field(key)
+ if 'record_quality' in self.fields:
+ self.fields['record_quality'].choices = \
+ [('', '--')] + list(models.QUALITY)
if 'operation_code' in self.fields:
fields = OrderedDict()
ope_code = self.fields.pop('operation_code')
@@ -922,17 +926,20 @@ class OperationFormGeneral(ManageOldType, CustomForm, forms.Form):
def clean(self):
cleaned_data = self.cleaned_data
+
# verify the logic between start date and excavation end date
- if cleaned_data.get('excavation_end_date'):
+ if self.are_available(['excavation_end_date', 'start_date']) \
+ and cleaned_data.get('excavation_end_date'):
if not self.cleaned_data['start_date']:
raise forms.ValidationError(
- _(u"If you want to set an excavation end date you have to "
- u"provide a start date."))
+ _(u"If you want to set an excavation end date you "
+ u"have to provide a start date."))
if cleaned_data['excavation_end_date'] \
< cleaned_data['start_date']:
raise forms.ValidationError(
_(u"The excavation end date cannot be before the start "
u"date."))
+
# verify patriarche
code_p = self.cleaned_data.get('code_patriarche', None)
@@ -944,11 +951,13 @@ class OperationFormGeneral(ManageOldType, CustomForm, forms.Form):
msg = u"Ce code OA a déjà été affecté à une "\
u"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']:
@@ -969,6 +978,9 @@ class OperationFormGeneral(ManageOldType, CustomForm, forms.Form):
class OperationFormModifGeneral(OperationFormGeneral):
+ form_admin_name = _(u"Operation - General - Modification")
+ form_slug = "operation-general-modification"
+
operation_code = forms.IntegerField(label=_(u"Operation code"),
required=False)
currents = {'associated_file': File}
@@ -991,6 +1003,7 @@ class OperationFormModifGeneral(OperationFormGeneral):
fields[key] = value
self.fields = fields
+
OperationFormModifGeneral.associated_models = \
OperationFormGeneral.associated_models.copy()
@@ -1001,6 +1014,7 @@ class CollaboratorForm(CustomForm, forms.Form):
form_label = _(u"Collaborators")
form_admin_name = _(u"Operation - Collaborators")
form_slug = "operation-collaborators"
+
base_models = ['collaborator']
associated_models = {'collaborator': Person, }
collaborator = widgets.Select2MultipleField(
@@ -1012,8 +1026,11 @@ class CollaboratorForm(CustomForm, forms.Form):
self.fields['collaborator'].widget.attrs['full-width'] = True
-class OperationFormPreventive(forms.Form):
+class OperationFormPreventive(CustomForm, forms.Form):
form_label = _(u"Preventive informations - excavation")
+ form_admin_name = _(u"Operation - Preventive - Excavation")
+ form_slug = "operation-preventive-excavation"
+
cost = forms.IntegerField(label=_(u"Cost (euros)"), required=False)
scheduled_man_days = forms.IntegerField(label=_(u"Scheduled man-days"),
required=False)
@@ -1028,8 +1045,11 @@ class OperationFormPreventive(forms.Form):
validators.MaxValueValidator(100)])
-class OperationFormPreventiveDiag(forms.Form):
+class OperationFormPreventiveDiag(CustomForm, forms.Form):
form_label = _("Preventive informations - diagnostic")
+ form_admin_name = _(u"Operation - Preventive - Diagnostic")
+ form_slug = "operation-preventive-diagnostic"
+
if settings.COUNTRY == 'fr':
zoning_prescription = forms.NullBooleanField(
required=False, label=_(u"Prescription on zoning"))
@@ -1054,6 +1074,7 @@ class SelectedTownForm(forms.Form):
if towns and towns != -1:
self.fields['town'].choices = [('', '--')] + towns
+
SelectedTownFormset = formset_factory(SelectedTownForm, can_delete=True,
formset=TownFormSet)
SelectedTownFormset.form_label = _(u"Towns")
@@ -1202,6 +1223,9 @@ class ArchaeologicalSiteBasicForm(forms.Form):
ArchaeologicalSiteFormSet = formset_factory(ArchaeologicalSiteBasicForm,
can_delete=True, formset=FormSet)
ArchaeologicalSiteFormSet.form_label = _("Archaeological sites")
+ArchaeologicalSiteFormSet.form_admin_name = _("Operation - Archaeological "
+ "sites")
+ArchaeologicalSiteFormSet.form_slug = "operation-archaeological-sites"
class ArchaeologicalSiteSelectionForm(forms.Form):
diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py
index 1fecce9cd..c078d910d 100644
--- a/archaeological_operations/views.py
+++ b/archaeological_operations/views.py
@@ -84,6 +84,7 @@ def autocomplete_archaeologicalsite(request):
for site in sites])
return HttpResponse(data, content_type='text/plain')
+
new_archaeologicalsite = new_item(models.ArchaeologicalSite,
ArchaeologicalSiteForm, many=True)
@@ -136,6 +137,7 @@ def get_available_operation_code(request, year=None):
models.Operation.get_available_operation_code(year)})
return HttpResponse(data, content_type='text/plain')
+
get_operation = get_item(models.Operation, 'get_operation', 'operation')
show_operation = show_item(models.Operation, 'operation')
@@ -162,11 +164,13 @@ def dashboard_operation(request, *args, **kwargs):
dct = {'dashboard': models.OperationDashboard()}
return render(request, 'ishtar/dashboards/dashboard_operation.html', dct)
+
operation_search_wizard = SearchWizard.as_view(
[('general-operation_search', OperationFormSelection)],
label=_(u"Operation search"),
url_name='operation_search',)
+
wizard_steps = [
('filechoice-operation_creation', OperationFormFileChoice),
('general-operation_creation', OperationFormGeneral),
@@ -195,6 +199,7 @@ def get_check_files_for_operation(other_check=None):
return other_check(self)
return func
+
check_files_for_operation = get_check_files_for_operation()
diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py
index da6a1c051..8b97cfbab 100644
--- a/ishtar_common/forms.py
+++ b/ishtar_common/forms.py
@@ -105,7 +105,92 @@ def get_readonly_clean(key):
return func
-class FormSet(BaseFormSet):
+class CustomForm(object):
+ form_admin_name = ""
+ form_slug = ""
+ need_user_for_initialization = True
+
+ def __init__(self, *args, **kwargs):
+ current_user = None
+ if 'user' in kwargs:
+ try:
+ current_user = kwargs.pop('user').ishtaruser
+ except AttributeError:
+ pass
+ super(CustomForm, self).__init__(*args, **kwargs)
+ available, excluded = self.check_availability_and_excluded_fields(
+ current_user)
+ for exc in excluded:
+ if hasattr(self, 'fields'):
+ self.remove_field(exc)
+ else:
+ # formset
+ for form in self.forms:
+ if exc in form.fields:
+ form.fields.pop(exc)
+
+ def are_available(self, keys):
+ for k in keys:
+ if k not in self.fields:
+ return False
+ return True
+
+ def remove_field(self, key):
+ if key in self.fields:
+ self.fields.pop(key)
+
+ @classmethod
+ def check_availability_and_excluded_fields(cls, current_user):
+ if not current_user:
+ return True, []
+ base_q = {"form": cls.form_slug, 'available': True}
+ # order is important : try for user, user type then all
+ query_dicts = []
+ if current_user:
+ dct = base_q.copy()
+ dct.update({'users__pk': current_user.pk})
+ query_dicts = [dct]
+ for user_type in current_user.person.person_types.all():
+ dct = base_q.copy()
+ dct.update({'user_types__pk': user_type.pk}),
+ query_dicts.append(dct)
+ dct = base_q.copy()
+ dct.update({'apply_to_all': True})
+ query_dicts.append(dct)
+ excluded_lst = []
+ for query_dict in query_dicts:
+ q = models.CustomForm.objects.filter(**query_dict)
+ if not q.count():
+ continue
+ # todo: prevent multiple result in database
+ form = q.all()[0]
+ if not form.enabled:
+ return False, []
+ for excluded in form.excluded_fields.all():
+ # could have be filtered previously
+ excluded_lst.append(excluded.field)
+ break
+ return True, excluded_lst
+
+ @classmethod
+ def get_custom_fields(cls):
+ if hasattr(cls, 'base_fields'):
+ fields = cls.base_fields
+ else:
+ # formset
+ fields = cls.form.base_fields
+ customs = []
+ for key in fields:
+ field = fields[key]
+ # cannot customize display of required and hidden field
+ # field with no label are also rejected
+ if field.required or field.widget.is_hidden or not field.label:
+ continue
+ customs.append((key, field.label))
+ return sorted(customs, key=lambda x: x[1])
+
+
+class FormSet(CustomForm, BaseFormSet):
def __init__(self, *args, **kwargs):
self.readonly = False
if 'readonly' in kwargs:
@@ -250,8 +335,23 @@ def get_data_from_formset(data):
return values
+class FieldType(object):
+ def __init__(self, key, model, is_multiple=False):
+ self.key = key
+ self.model = model
+ self.is_multiple = is_multiple
+
+ def get_choices(self, initial=None):
+ return self.model.get_types(
+ empty_first=not self.is_multiple,
+ initial=initial)
+
+ def get_help(self):
+ return self.model.get_help()
+
+
class ManageOldType(object):
- TYPES = [] # (field_name, model, is_multiple) list
+ TYPES = [] # FieldType list
def __init__(self, *args, **kwargs):
"""
@@ -290,78 +390,12 @@ class ManageOldType(object):
self.init_data[k].append(val)
self.init_data = MultiValueDict(self.init_data)
super(ManageOldType, self).__init__(*args, **kwargs)
- for field_name, model, is_multiple in self.TYPES:
- self.fields[field_name].choices = \
- model.get_types(
- empty_first=not is_multiple,
- initial=self.init_data.get(field_name))
- self.fields[field_name].help_text = model.get_help()
-
-
-class CustomForm(object):
- form_admin_name = ""
- form_slug = ""
- need_user_for_initialization = True
-
- def __init__(self, *args, **kwargs):
- current_user = None
- if 'user' in kwargs:
- try:
- current_user = kwargs.pop('user').ishtaruser
- except AttributeError:
- pass
- super(CustomForm, self).__init__(*args, **kwargs)
- available, excluded = self.check_availability_and_excluded_fields(
- current_user)
- for exc in excluded:
- if exc in self.fields:
- self.fields.pop(exc)
-
- @classmethod
- def check_availability_and_excluded_fields(cls, current_user):
- if not current_user:
- return True, []
- base_q = {"form": cls.form_slug, 'available': True}
- # order is important : try for user, user type then all
- query_dicts = []
- if current_user:
- dct = base_q.copy()
- dct.update({'users__pk': current_user.pk})
- query_dicts = [dct]
- for user_type in current_user.person.person_types.all():
- dct = base_q.copy()
- dct.update({'user_types__pk': user_type.pk}),
- query_dicts.append(dct)
- dct = base_q.copy()
- dct.update({'apply_to_all': True})
- query_dicts.append(dct)
- excluded_lst = []
- for query_dict in query_dicts:
- q = models.CustomForm.objects.filter(**query_dict)
- if not q.count():
+ for field in self.TYPES:
+ if field.key not in self.fields:
continue
- # todo: prevent multiple result in database
- form = q.all()[0]
- if not form.enabled:
- return False, []
- for excluded in form.excluded_fields.all():
- # could have be filtered previously
- excluded_lst.append(excluded.field)
- break
- return True, excluded_lst
-
- @classmethod
- def get_custom_fields(cls):
- fields = cls.base_fields
- customs = []
- for key in fields:
- field = fields[key]
- # cannot customize display of required and hidden field
- # field with no label are also rejected
- if field.required or field.widget.is_hidden or not field.label:
- continue
- customs.append((key, field.label))
- return sorted(customs, key=lambda x: x[1])
+ self.fields[field.key].choices = field.get_choices(
+ initial=self.init_data.get(field.key))
+ self.fields[field.key].help_text = field.get_help()
class DocumentGenerationForm(forms.Form):
diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py
index 016596772..7c8b2fd5c 100644
--- a/ishtar_common/tests.py
+++ b/ishtar_common/tests.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2015-2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2015-2017 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as