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 | ff60c200ddc406250c335de3b59a56b9327a16ec (patch) | |
| tree | b631fd7d258640517b986708b1db9f35239d95ce | |
| parent | 6a3c8c3ff340110960bf64dbfc31e526243f6616 (diff) | |
| download | Ishtar-ff60c200ddc406250c335de3b59a56b9327a16ec.tar.bz2 Ishtar-ff60c200ddc406250c335de3b59a56b9327a16ec.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  | 
