diff options
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 |
commit | 1f49ac758d42cd924dd1df39af58835b80f1f77e (patch) | |
tree | b631fd7d258640517b986708b1db9f35239d95ce | |
parent | c410be9b3c3ba193ee8c233cc6a50d065d4090fd (diff) | |
download | Ishtar-1f49ac758d42cd924dd1df39af58835b80f1f77e.tar.bz2 Ishtar-1f49ac758d42cd924dd1df39af58835b80f1f77e.zip |
Custom forms: manage formsets
-rw-r--r-- | archaeological_finds/forms.py | 12 | ||||
-rw-r--r-- | archaeological_operations/forms.py | 84 | ||||
-rw-r--r-- | archaeological_operations/views.py | 5 | ||||
-rw-r--r-- | ishtar_common/forms.py | 180 | ||||
-rw-r--r-- | ishtar_common/tests.py | 2 |
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 |