diff options
30 files changed, 1134 insertions, 327 deletions
diff --git a/archaeological_context_records/forms.py b/archaeological_context_records/forms.py index c310e98fa..7cc94bb9a 100644 --- a/archaeological_context_records/forms.py +++ b/archaeological_context_records/forms.py @@ -37,14 +37,17 @@ import models from ishtar_common import widgets from archaeological_operations.widgets import OAWidget from ishtar_common.forms import FinalForm, FormSet, \ - reverse_lazy, get_form_selection, TableSelect, ManageOldType + reverse_lazy, get_form_selection, TableSelect, ManageOldType, CustomForm,\ + FieldType from ishtar_common.forms_common import get_town_field, SourceSelect from archaeological_operations.forms import OperationSelect, ParcelField,\ RecordRelationsForm as OpeRecordRelationsForm, RecordRelationsFormSetBase -class OperationFormSelection(forms.Form): +class OperationFormSelection(CustomForm, forms.Form): form_label = _("Operation") + form_admin_name = _(u"Context record - 010 - Operation choice") + form_slug = "contextrecord-010-operationchoice" associated_models = {'operation': Operation} currents = {'operation': Operation} operation = forms.IntegerField( @@ -129,8 +132,10 @@ class RecordFormSelection(forms.Form): return cleaned_data -class RecordFormGeneral(ManageOldType, forms.Form): +class RecordFormGeneral(CustomForm, ManageOldType, forms.Form): form_label = _("General") + form_admin_name = _(u"Context record - 020 - General") + form_slug = "contextrecord-020-general" file_upload = True base_models = ["documentation"] associated_models = { @@ -175,6 +180,12 @@ class RecordFormGeneral(ManageOldType, forms.Form): 'height': settings.IMAGE_MAX_SIZE[1]}), max_length=255, required=False, widget=widgets.ImageFileInput()) + TYPES = [ + FieldType('unit', models.Unit), + FieldType('excavation_technic', models.ExcavationTechnicType), + FieldType('documentation', models.DocumentationType, is_multiple=True) + ] + def __init__(self, *args, **kwargs): # TODO: simplify operation = None @@ -222,19 +233,6 @@ class RecordFormGeneral(ManageOldType, forms.Form): (" - ".join([k for k in key if k]), [(parcel.pk, parcel.short_label) for parcel in gparcels]) ) - self.fields['unit'].choices = models.Unit.get_types( - initial=self.init_data.get('unit')) - self.fields['unit'].help_text = models.Unit.get_help() - self.fields['excavation_technic'].choices = \ - models.ExcavationTechnicType.get_types( - initial=self.init_data.get('excavation_technic')) - self.fields['excavation_technic'].help_text = \ - models.ExcavationTechnicType.get_help() - self.fields['documentation'].choices = \ - models.DocumentationType.get_types(empty_first=False, - initial=self.init_data.get('documentation')) - self.fields['documentation'].help_text = \ - models.DocumentationType.get_help() def clean(self): # manage unique context record ID @@ -264,22 +262,18 @@ class DatingForm(ManageOldType, forms.Form): dating_type = forms.ChoiceField(label=_("Dating type"), required=False, choices=[]) - def __init__(self, *args, **kwargs): - super(DatingForm, self).__init__(*args, **kwargs) - self.fields['dating_type'].choices = models.DatingType.get_types( - initial=self.init_data.get('dating_type')) - self.fields['dating_type'].help_text = models.DatingType.get_help() - self.fields['quality'].choices = models.DatingQuality.get_types( - initial=self.init_data.get('quality')) - self.fields['quality'].help_text = models.DatingQuality.get_help() - self.fields['period'].choices = Period.get_types( - initial=self.init_data.get('period')) - self.fields['period'].help_text = Period.get_help() + TYPES = [ + FieldType('dating_type', models.DatingType), + FieldType('quality', models.DatingQuality), + FieldType('period', models.Period) + ] DatingFormSet = formset_factory(DatingForm, can_delete=True, formset=FormSet) DatingFormSet.form_label = _("Dating") +DatingFormSet.form_admin_name = _(u"Context record - 030 - Dating") +DatingFormSet.form_slug = "contextrecord-030-datings" class RecordRelationsForm(OpeRecordRelationsForm): @@ -301,13 +295,19 @@ class RecordRelationsForm(OpeRecordRelationsForm): if crs: self.fields['right_record'].choices = [('', '-' * 2)] + crs + RecordRelationsFormSet = formset_factory( RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase) RecordRelationsFormSet.form_label = _(u"Relations") +RecordRelationsFormSet.form_admin_name = _(u"Context record - 050 - Relations") +RecordRelationsFormSet.form_slug = "contextrecord-050-recordrelations" -class RecordFormInterpretation(ManageOldType, forms.Form): +class RecordFormInterpretation(CustomForm, ManageOldType, forms.Form): form_label = _("Interpretation") + form_admin_name = "Context record - 040 - Interpretation" + form_slug = "contextrecord-040-interpretation" + associated_models = {'activity': models.ActivityType, 'identification': models.IdentificationType} datings_comment = forms.CharField( @@ -328,16 +328,11 @@ class RecordFormInterpretation(ManageOldType, forms.Form): tpq_estimated = forms.IntegerField(label=_(u"Estimated TPQ"), required=False) - def __init__(self, *args, **kwargs): - super(RecordFormInterpretation, self).__init__(*args, **kwargs) - self.fields['activity'].choices = models.ActivityType.get_types( - initial=self.init_data.get('activity')) - self.fields['activity'].help_text = models.ActivityType.get_help() - self.fields['identification'].choices = \ - models.IdentificationType.get_types( - initial=self.init_data.get('identification')) - self.fields['identification'].help_text = \ - models.IdentificationType.get_help() + TYPES = [ + FieldType('activity', models.ActivityType), + FieldType('identification', models.IdentificationType), + ] + OperationRecordFormSelection = get_form_selection( 'OperationRecordFormSelection', _(u"Operation search"), 'operation_id', @@ -353,6 +348,7 @@ class RecordDeletionForm(FinalForm): # Source management for context records # ######################################### + SourceRecordFormSelection = get_form_selection( 'SourceRecordFormSelection', _(u"Context record search"), 'context_record', models.ContextRecord, RecordSelect, 'get-contextrecord', diff --git a/archaeological_context_records/migrations/0014_contextrecord_images.py b/archaeological_context_records/migrations/0014_contextrecord_images.py new file mode 100644 index 000000000..641309c97 --- /dev/null +++ b/archaeological_context_records/migrations/0014_contextrecord_images.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-10 17:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0021_auto_20171110_1717'), + ('archaeological_context_records', '0013_auto_20171026_1827'), + ] + + operations = [ + migrations.AddField( + model_name='contextrecord', + name='images', + field=models.ManyToManyField(blank=True, to='ishtar_common.IshtarImage', verbose_name='Images'), + ), + ] diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index 925a48597..dde661ee7 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -33,7 +33,7 @@ from ishtar_common.utils import cached_label_changed from ishtar_common.models import GeneralType, BaseHistorizedItem, \ HistoricalRecords, OwnPerms, ShortMenuItem, Source, GeneralRelationType,\ GeneralRecordRelations, post_delete_record_relation, \ - ImageModel, post_save_cache, ValueGetter, BulkUpdatedItem + ImageModel, post_save_cache, ValueGetter, BulkUpdatedItem, IshtarImage from archaeological_operations.models import Operation, Period, Parcel @@ -302,6 +302,8 @@ class ContextRecord(BulkUpdatedItem, BaseHistorizedItem, point_2d = models.PointField(_(u"Point (2D)"), blank=True, null=True) point = models.PointField(_(u"Point (3D)"), blank=True, null=True, dim=3) polygon = models.PolygonField(_(u"Polygon"), blank=True, null=True) + images = models.ManyToManyField(IshtarImage, verbose_name=_(u"Images"), + blank=True) cached_label = models.TextField(_(u"Cached name"), null=True, blank=True, db_index=True) PARENT_SEARCH_VECTORS = ['operation'] diff --git a/archaeological_files/forms.py b/archaeological_files/forms.py index afb62ae4f..cf8b8ea4b 100644 --- a/archaeological_files/forms.py +++ b/archaeological_files/forms.py @@ -25,6 +25,7 @@ import datetime from django import forms from django.conf import settings from django.core import validators +from django.forms.formsets import formset_factory from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe @@ -34,12 +35,13 @@ from ishtar_common.models import Person, Organization, \ from archaeological_operations.models import ActType, AdministrativeAct, \ OperationType import models + from ishtar_common.forms import FinalForm, get_now, reverse_lazy, TableSelect, \ - ManageOldType + ManageOldType, CustomForm, FieldType from ishtar_common.forms_common import get_town_field from archaeological_operations.forms import AdministrativeActOpeForm, \ AdministrativeActOpeFormSelection, \ - ParcelField, SLICING, AdministrativeActModifForm + ParcelField, SLICING, AdministrativeActModifForm, ParcelForm, ParcelFormSet from ishtar_common import widgets @@ -271,6 +273,13 @@ class FileFormGeneralRO(FileFormGeneral): return cleaned_data +ParcelFormset = formset_factory(ParcelForm, can_delete=True, + formset=ParcelFormSet) +ParcelFormset.form_label = _(u"Parcels") +ParcelFormset.form_admin_name = _("Archaeological file - 020 - Parcel") +ParcelFormset.form_slug = "source-general" + + class FileFormPreventive(ManageOldType, forms.Form): form_label = _(u"Preventive informations") associated_models = {'general_contractor': Person, @@ -329,8 +338,10 @@ class FileFormPreventive(ManageOldType, forms.Form): self.fields['permit_type'].help_text = models.PermitType.get_help() -class FileFormResearch(ManageOldType, forms.Form): +class FileFormResearch(CustomForm, ManageOldType, forms.Form): form_label = _("Research archaeology") + form_admin_name = _("Archaeological file - 045 - Research - General") + form_slug = "file-045-research-general" base_model = 'department' associated_models = {'scientist': Person, 'requested_operation_type': OperationType, @@ -528,15 +539,14 @@ class AdministrativeActFileModifyFormSelection( class AdministrativeActFileForm(AdministrativeActOpeForm): + form_admin_name = _(u"Archaeological file - Administrative act - General") + form_slug = "file-adminact-general" act_type = forms.ChoiceField(label=_(u"Act type"), choices=[]) - def __init__(self, *args, **kwargs): - super(AdministrativeActFileForm, self).__init__(*args, **kwargs) - self.fields['act_type'].choices = ActType.get_types( - initial=self.init_data.get('act_type'), - dct={'intented_to': 'F'}) - self.fields['act_type'].help_text = ActType.get_help( - dct={'intented_to': 'F'}) + TYPES = [ + FieldType('act_type', ActType, + extra_args={"dct": {'intented_to': 'F'}}), + ] class AdministrativeActFileModifForm(AdministrativeActModifForm, diff --git a/archaeological_files/views.py b/archaeological_files/views.py index 0c0dac3f3..86fa83ee4 100644 --- a/archaeological_files/views.py +++ b/archaeological_files/views.py @@ -37,8 +37,7 @@ from archaeological_operations.wizards import AdministrativeActDeletionWizard, \ from wizards import * from ishtar_common.forms_common import TownFormset -from archaeological_operations.forms import ParcelFormSet, \ - FinalAdministrativeActDeleteForm +from archaeological_operations.forms import FinalAdministrativeActDeleteForm from ishtar_common.forms import ClosingDateFormSelection from forms import * @@ -108,7 +107,7 @@ file_search_wizard = SearchWizard.as_view( file_creation_wizard = FileWizard.as_view( [('general-file_creation', FileFormGeneral), ('towns-file_creation', TownFormset), - ('parcels-file_creation', ParcelFormSet), + ('parcels-file_creation', ParcelFormset), ('preventive-file_creation', FileFormPreventive), ('research-file_creation', FileFormResearch), ('final-file_creation', FinalForm)], @@ -127,7 +126,7 @@ file_modification_wizard = FileModificationWizard.as_view( [('selec-file_modification', FileFormSelection), ('general-file_modification', FileFormGeneralRO), ('towns-file_modification', TownFormset), - ('parcels-file_modification', ParcelFormSet), + ('parcels-file_modification', ParcelFormset), ('preventive-file_modification', FileFormPreventive), ('research-file_modification', FileFormResearch), ('final-file_modification', FinalForm)], diff --git a/archaeological_files_pdl/forms.py b/archaeological_files_pdl/forms.py index e5fbb4a96..e0a8f119b 100644 --- a/archaeological_files_pdl/forms.py +++ b/archaeological_files_pdl/forms.py @@ -29,13 +29,16 @@ from ishtar_common.models import Person, Town, Department, valid_id, \ organization_type_pk_lazy from archaeological_files import models -from ishtar_common.forms import get_now, reverse_lazy, ManageOldType +from ishtar_common.forms import get_now, reverse_lazy, ManageOldType, \ + CustomForm, FieldType from ishtar_common import widgets -class FileFormGeneral(ManageOldType, forms.Form): +class FileFormGeneral(CustomForm, ManageOldType, forms.Form): form_label = _("General") + form_admin_name = _(u"Archaeological file - 010 - General") + form_slug = "file-010-general" associated_models = {'file_type': models.FileType} file_type = forms.ChoiceField(label=_("File type"), choices=[]) year = forms.IntegerField(label=_("Year"), @@ -47,11 +50,9 @@ class FileFormGeneral(ManageOldType, forms.Form): reception_date = forms.DateField( label=_(u"Reception date"), initial=get_now, widget=widgets.JQueryDate) - def __init__(self, *args, **kwargs): - super(FileFormGeneral, self).__init__(*args, **kwargs) - self.fields['file_type'].choices = models.FileType.get_types( - initial=self.init_data.get('file_type')) - self.fields['file_type'].help_text = models.FileType.get_help() + TYPES = [ + FieldType('file_type', models.FileType), + ] def clean_reception_date(self): value = self.cleaned_data.get('reception_date', None) @@ -61,27 +62,31 @@ class FileFormGeneral(ManageOldType, forms.Form): return value -class FileFormPreventiveType(ManageOldType, forms.Form): +class FileFormPreventiveType(CustomForm, ManageOldType, forms.Form): form_label = u"Saisine" + form_admin_name = _(u"Archaeological file - 013 - Preventive - Saisine") + form_slug = "file-013-preventivesaisine" associated_models = {'saisine_type': models.SaisineType, 'permit_type': models.PermitType} permit_type = forms.ChoiceField(label=_(u"Permit type"), required=False, choices=[]) saisine_type = forms.ChoiceField(label=_(u"Saisine type"), choices=[]) + TYPES = [ + FieldType('saisine_type', models.SaisineType), + ] def __init__(self, *args, **kwargs): super(FileFormPreventiveType, self).__init__(*args, **kwargs) - self.fields['saisine_type'].choices = models.SaisineType.get_types( - initial=self.init_data.get('saisine_type')) - self.fields['saisine_type'].help_text = models.SaisineType.get_help() self.fields['permit_type'].choices = models.PermitType.get_types( default='NP', initial=self.init_data.get('permit_type')) self.fields['permit_type'].help_text = models.PermitType.get_help() -class FileFormPlanning(forms.Form): +class FileFormPlanning(CustomForm, forms.Form): form_label = _(u"Planning") + form_admin_name = _(u"Archaeological file - 017 - Preventive - Planning") + form_slug = "file-017-preventiveplanning" base_models = ['town', 'department'] associated_models = {'town': Town, 'department': Department} name = forms.CharField(label=_(u"Planning name"), required=False, @@ -112,8 +117,10 @@ class FileFormPlanning(forms.Form): validators.MaxValueValidator(999999999)]) -class FileFormResearchAddress(forms.Form): +class FileFormResearchAddress(CustomForm, forms.Form): form_label = _(u"Address") + form_admin_name = _(u"Archaeological file - 015 - Research - Address") + form_slug = "file-015-researchplanning" base_models = ['town', 'department'] associated_models = {'town': Town, 'department': Department} name = forms.CharField(label=_(u"Project name"), required=False, @@ -228,8 +235,11 @@ class PersonOrgaForm(forms.Form): validators=[valid_id(models.Organization)]) -class FileFormGeneralContractor(PersonOrgaForm): +class FileFormGeneralContractor(CustomForm, PersonOrgaForm): form_label = _(u"General contractor") + form_admin_name = _("Archaeological file - 030 - General contrator") + form_slug = "file-030-generalcontractor" + associated_models = {'general_contractor': models.Person, 'corporation_general_contractor': models.Organization} corporation_general_contractor = forms.IntegerField( @@ -323,6 +333,10 @@ class FileFormGeneralContractor(PersonOrgaForm): if status not in ('natural', 'corporation'): status = DEFAULT_STATUS + user = None + if 'user' in kwargs: + user = kwargs.pop('user') + super(PersonOrgaForm, self).__init__(*args, **kwargs) # distinct widget for natural and corporation @@ -344,8 +358,10 @@ class FileFormGeneralContractor(PersonOrgaForm): self.fields.pop(self.ORGA_FIELD) -class FileFormPlanningService(forms.Form): +class FileFormPlanningService(CustomForm, forms.Form): form_label = _(u"Planning service") + form_admin_name = _("Archaeological file - 040 - Planning service") + form_slug = "file-040-planningservice" associated_models = {'responsible_town_planning_service': models.Person, 'planning_service': models.Organization} @@ -387,8 +403,10 @@ class FileFormPlanningService(forms.Form): ) -class FileFormInstruction(forms.Form): +class FileFormInstruction(CustomForm, forms.Form): form_label = u"Instruction SRA" + form_admin_name = _("Archaeological file - 050 - Instruction") + form_slug = "file-050-instruction" associated_models = {'in_charge': models.Person, 'related_file': models.File} in_charge = forms.IntegerField( diff --git a/archaeological_files_pdl/views.py b/archaeological_files_pdl/views.py index d03fd958d..cf412efb6 100644 --- a/archaeological_files_pdl/views.py +++ b/archaeological_files_pdl/views.py @@ -27,7 +27,6 @@ from ishtar_common.views import OrganizationPersonCreate, \ from archaeological_files_pdl import forms from archaeological_files import forms as ref_forms -from archaeological_operations.forms import ParcelFormSet from archaeological_files import models @@ -41,7 +40,7 @@ file_creation_wizard = FileWizard.as_view([ ('preventivetype-file_creation', forms.FileFormPreventiveType), ('preventiveplanning-file_creation', forms.FileFormPlanning), ('researchaddress-file_creation', forms.FileFormResearchAddress), - ('parcelspdl-file_creation', ParcelFormSet), + ('parcelspdl-file_creation', ref_forms.ParcelFormset), ('generalcontractor-file_creation', forms.FileFormGeneralContractor), ('planningservice-file_creation', forms.FileFormPlanningService), ('research-file_creation', ref_forms.FileFormResearch), @@ -68,7 +67,7 @@ file_modification_wizard = FileModificationWizard.as_view([ ('preventivetype-file_modification', forms.FileFormPreventiveType), ('preventiveplanning-file_modification', forms.FileFormPlanning), ('researchaddress-file_modification', forms.FileFormResearchAddress), - ('parcelspdl-file_modification', ParcelFormSet), + ('parcelspdl-file_modification', ref_forms.ParcelFormset), ('generalcontractor-file_modification', forms.FileFormGeneralContractor), ('planningservice-file_modification', forms.FileFormPlanningService), ('research-file_modification', ref_forms.FileFormResearch), diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py index b88ee164e..eca12f6e9 100644 --- a/archaeological_finds/forms.py +++ b/archaeological_finds/forms.py @@ -41,9 +41,9 @@ 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.forms_common import get_town_field, SourceSelect, CustomForm from ishtar_common.utils import convert_coordinates_to_point from ishtar_common import widgets from archaeological_operations.widgets import OAWidget @@ -92,8 +92,10 @@ __all__ = [ logger = logging.getLogger(__name__) -class RecordFormSelection(forms.Form): +class RecordFormSelection(CustomForm, forms.Form): form_label = _("Context record") + form_admin_name = _(u"Find - 010 - Context record choice") + form_slug = "find-010-contextrecord" base_models = ['get_first_base_find'] associated_models = {'get_first_base_find__context_record': ContextRecord} get_first_base_find__context_record = forms.IntegerField( @@ -124,9 +126,11 @@ class RecordFormSelection(forms.Form): cr.operation.pk) -class FindForm(ManageOldType, forms.Form): +class FindForm(CustomForm, ManageOldType, forms.Form): file_upload = True form_label = _("Find") + form_admin_name = _(u"Find - 020 - General") + form_slug = "find-020-general" base_models = ['get_first_base_find', 'object_type', 'material_type', 'integritie', 'remarkabilitie'] associated_models = {'material_type': models.MaterialType, @@ -206,6 +210,14 @@ class FindForm(ManageOldType, forms.Form): 'height': settings.IMAGE_MAX_SIZE[1]}), max_length=255, required=False, widget=widgets.ImageFileInput()) + TYPES = [ + FieldType('material_type', models.MaterialType, is_multiple=True), + FieldType('object_type', models.ObjectType, is_multiple=True), + FieldType('get_first_base_find__batch', models.BatchType), + FieldType('integritie', models.IntegrityType, is_multiple=True), + FieldType('remarkabilitie', models.RemarkabilityType, is_multiple=True) + ] + def __init__(self, *args, **kwargs): super(FindForm, self).__init__(*args, **kwargs) if not get_current_profile().mapping: @@ -224,35 +236,6 @@ class FindForm(ManageOldType, forms.Form): self.fields[srs].help_text = \ SpatialReferenceSystem.get_help() self.fields['checked'].choices = models.CHECK_CHOICES - self.fields['material_type'].choices = models.MaterialType.get_types( - initial=self.init_data.get('material_type'), - empty_first=False - ) - self.fields['material_type'].help_text = models.MaterialType.get_help() - - self.fields['object_type'].choices = models.ObjectType.get_types( - initial=self.init_data.get('object_type'), - empty_first=False - ) - self.fields['object_type'].help_text = models.ObjectType.get_help() - - self.fields['get_first_base_find__batch'].choices = \ - models.BatchType.get_types( - initial=self.init_data.get('get_first_base_find__batch')) - self.fields['get_first_base_find__batch'].help_text = \ - models.BatchType.get_help() - self.fields['integritie'].choices = \ - models.IntegrityType.get_types( - empty_first=False, - initial=self.init_data.get('integritie')) - self.fields['integritie'].help_text = \ - models.IntegrityType.get_help() - self.fields['remarkabilitie'].choices = \ - models.RemarkabilityType.get_types( - empty_first=False, - initial=self.init_data.get('remarkabilitie')) - self.fields['remarkabilitie'].help_text = \ - models.RemarkabilityType.get_help() self.fields['estimated_value'].label = u"{} ({})".format( unicode(self.fields['estimated_value'].label), get_current_profile().currency) @@ -285,8 +268,10 @@ class FindForm(ManageOldType, forms.Form): return self.cleaned_data -class PreservationForm(ManageOldType, forms.Form): +class PreservationForm(CustomForm, ManageOldType, forms.Form): form_label = _("Preservation") + form_admin_name = _(u"Find - 030 - Preservation") + form_slug = "find-030-preservation" base_models = ['alteration', 'alteration_cause', 'preservation_to_consider'] associated_models = {'alteration': models.AlterationType, @@ -316,11 +301,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): @@ -346,23 +331,18 @@ class DateForm(ManageOldType, forms.Form): required=False, choices=[]) precise_dating = forms.CharField(label=_("Precise dating"), required=False) - - def __init__(self, *args, **kwargs): - super(DateForm, self).__init__(*args, **kwargs) - self.fields['dating_type'].choices = DatingType.get_types( - initial=self.init_data.get('dating_type')) - self.fields['dating_type'].help_text = DatingType.get_help() - self.fields['period'].choices = Period.get_types( - initial=self.init_data.get('period')) - self.fields['period'].help_text = Period.get_help() - self.fields['quality'].choices = DatingQuality.get_types( - initial=self.init_data.get('quality')) - self.fields['quality'].help_text = DatingQuality.get_help() + TYPES = [ + FieldType('dating_type', DatingType), + FieldType('period', Period), + FieldType('quality', DatingQuality), + ] DatingFormSet = formset_factory(DateForm, can_delete=True, formset=FormSet) DatingFormSet.form_label = _("Dating") +DatingFormSet.form_admin_name = _(u"Find - 040 - Dating") +DatingFormSet.form_slug = "find-040-dating" class FindSelect(TableSelect): @@ -660,11 +640,10 @@ class ResultFindForm(ManageOldType, forms.Form): weight = forms.IntegerField(label=_(u"Weight (g)")) find_number = forms.IntegerField(label=_(u"Find number")) - def __init__(self, *args, **kwargs): - super(ResultFindForm, self).__init__(*args, **kwargs) - self.fields['material_type'].choices = models.MaterialType.get_types( - initial=self.init_data.get('material_type')) - self.fields['material_type'].help_text = models.MaterialType.get_help() + TYPES = [ + FieldType('material_type', models.MaterialType) + ] + ResultFindFormSet = formset_factory(ResultFindForm, can_delete=True, formset=FormSet) diff --git a/archaeological_finds/forms_treatments.py b/archaeological_finds/forms_treatments.py index 99813ea31..4e5994ca9 100644 --- a/archaeological_finds/forms_treatments.py +++ b/archaeological_finds/forms_treatments.py @@ -37,7 +37,7 @@ from archaeological_operations.forms import AdministrativeActOpeForm, \ AdministrativeActOpeFormSelection, AdministrativeActModifForm from ishtar_common.forms import reverse_lazy, TableSelect, FinalForm, \ - ManageOldType, get_form_selection + ManageOldType, get_form_selection, CustomForm, FieldType from ishtar_common.forms_common import SourceSelect from ishtar_common import widgets @@ -75,8 +75,10 @@ class TreatmentFormSelection(forms.Form): validators=[valid_id(models.Treatment)]) -class BaseTreatmentForm(ManageOldType, forms.Form): +class BaseTreatmentForm(CustomForm, ManageOldType, forms.Form): form_label = _(u"Base treatment") + form_admin_name = _(u"Treatment - 020 - General") + form_slug = "treatment-020-general" base_models = ['treatment_type'] associated_models = {'treatment_type': models.TreatmentType, 'person': Person, @@ -154,6 +156,13 @@ class BaseTreatmentForm(ManageOldType, forms.Form): 'height': settings.IMAGE_MAX_SIZE[1]}), max_length=255, required=False, widget=widgets.ImageFileInput()) + TYPES = [ + FieldType('treatment_state', models.TreatmentState), + FieldType('treatment_type', models.TreatmentType, is_multiple=True, + extra_args={'dct': {'upstream_is_many': False, + 'downstream_is_many': False}}) + ] + def __init__(self, *args, **kwargs): user = kwargs.pop('user') super(BaseTreatmentForm, self).__init__(*args, **kwargs) @@ -172,20 +181,6 @@ class BaseTreatmentForm(ManageOldType, forms.Form): self.fields['organization'].initial = person.attached_to.pk self.fields['target_is_basket'].widget.choices = \ ((False, _(u"Single find")), (True, _(u"Basket"))) - self.fields['treatment_type'].choices = models.TreatmentType.get_types( - initial=self.init_data.get('treatment_type'), - dct={'upstream_is_many': False, 'downstream_is_many': False}, - empty_first=False - ) - self.fields['treatment_type'].help_text = \ - models.TreatmentType.get_help( - dct={'upstream_is_many': False, 'downstream_is_many': False}) - self.fields['treatment_state'].choices = \ - models.TreatmentState.get_types( - initial=self.init_data.get('treatment_state'), - ) - self.fields['treatment_state'].help_text = \ - models.TreatmentState.get_help() # TODO """ self.fields['basket'].required = False @@ -266,8 +261,10 @@ class TreatmentModifyForm(BaseTreatmentForm): return cleaned_data -class TreatmentFormFileChoice(forms.Form): +class TreatmentFormFileChoice(CustomForm, forms.Form): form_label = _(u"Associated request") + form_admin_name = _(u"Treatment - 010 - Request choice") + form_slug = "treatment-010-requestchoice" associated_models = {'file': models.TreatmentFile, } currents = {'file': models.TreatmentFile} file = forms.IntegerField( @@ -383,15 +380,14 @@ class AdministrativeActTreatmentFormSelection( class AdministrativeActTreatmentForm(AdministrativeActOpeForm): + form_admin_name = _(u"Treatment - Administrative act - General") + form_slug = "treatment-adminact-general" act_type = forms.ChoiceField(label=_(u"Act type"), choices=[]) - def __init__(self, *args, **kwargs): - super(AdministrativeActTreatmentForm, self).__init__(*args, **kwargs) - self.fields['act_type'].choices = ActType.get_types( - initial=self.init_data.get('act_type'), - dct={'intented_to': 'T'}) - self.fields['act_type'].help_text = ActType.get_help( - dct={'intented_to': 'T'}) + TYPES = [ + FieldType('act_type', ActType, + extra_args={"dct": {'intented_to': 'T'}}), + ] class AdministrativeActTreatmentModifForm( @@ -640,16 +636,14 @@ class AdministrativeActTreatmentFileFormSelection( class AdministrativeActTreatmentFileForm(AdministrativeActOpeForm): + form_admin_name = _(u"Treatment request - Administrative act - General") + form_slug = "treatmentfile-adminact-general" act_type = forms.ChoiceField(label=_(u"Act type"), choices=[]) - def __init__(self, *args, **kwargs): - super(AdministrativeActTreatmentFileForm, self).__init__(*args, - **kwargs) - self.fields['act_type'].choices = ActType.get_types( - initial=self.init_data.get('act_type'), - dct={'intented_to': 'TF'}) - self.fields['act_type'].help_text = ActType.get_help( - dct={'intented_to': 'TF'}) + TYPES = [ + FieldType('act_type', ActType, + extra_args={"dct": {'intented_to': 'TF'}}), + ] class AdministrativeActTreatmentFileModifForm( diff --git a/archaeological_finds/migrations/0014_auto_20171110_1717.py b/archaeological_finds/migrations/0014_auto_20171110_1717.py new file mode 100644 index 000000000..63458bd9b --- /dev/null +++ b/archaeological_finds/migrations/0014_auto_20171110_1717.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-10 17:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0021_auto_20171110_1717'), + ('archaeological_finds', '0013_auto_20171026_1828'), + ] + + operations = [ + migrations.AddField( + model_name='find', + name='images', + field=models.ManyToManyField(blank=True, to='ishtar_common.IshtarImage', verbose_name='Images'), + ), + migrations.AddField( + model_name='treatment', + name='images', + field=models.ManyToManyField(blank=True, to='ishtar_common.IshtarImage', verbose_name='Images'), + ), + ] diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index 8be440181..2b782c614 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -34,7 +34,7 @@ from ishtar_common.utils import cached_label_changed, post_save_point from ishtar_common.models import GeneralType, HierarchicalType, ImageModel, \ BaseHistorizedItem, ShortMenuItem, LightHistorizedItem, \ HistoricalRecords, OwnPerms, Source, Person, Basket, post_save_cache, \ - ValueGetter, get_current_profile + ValueGetter, get_current_profile, IshtarImage from archaeological_operations.models import AdministrativeAct from archaeological_context_records.models import ContextRecord, Dating @@ -750,7 +750,8 @@ class Find(BulkUpdatedItem, ValueGetter, BaseHistorizedItem, ImageModel, null=True) appraisal_date = models.DateField(_(u"Appraisal date"), blank=True, null=True) - + images = models.ManyToManyField(IshtarImage, verbose_name=_(u"Images"), + blank=True) cached_label = models.TextField(_(u"Cached name"), null=True, blank=True, db_index=True) history = HistoricalRecords() diff --git a/archaeological_finds/models_treatments.py b/archaeological_finds/models_treatments.py index 03eeed452..6c173959a 100644 --- a/archaeological_finds/models_treatments.py +++ b/archaeological_finds/models_treatments.py @@ -30,7 +30,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext from ishtar_common.utils import cached_label_changed, get_current_year from ishtar_common.models import GeneralType, ImageModel, BaseHistorizedItem, \ OwnPerms, HistoricalRecords, Person, Organization, Source, \ - ValueGetter, post_save_cache, ShortMenuItem, DashboardFormItem + ValueGetter, post_save_cache, ShortMenuItem, DashboardFormItem, IshtarImage from archaeological_warehouse.models import Warehouse, Container from archaeological_finds.models_finds import Find, FindBasket, TreatmentType from archaeological_operations.models import ClosedItem, Operation @@ -115,6 +115,8 @@ class Treatment(DashboardFormItem, ValueGetter, BaseHistorizedItem, blank=True, null=True) target_is_basket = models.BooleanField(_(u"Target a basket"), default=False) + images = models.ManyToManyField(IshtarImage, verbose_name=_(u"Images"), + blank=True) cached_label = models.TextField(_(u"Cached name"), null=True, blank=True, db_index=True) history = HistoricalRecords() diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py index 841131da6..6ba343508 100644 --- a/archaeological_operations/forms.py +++ b/archaeological_operations/forms.py @@ -49,9 +49,9 @@ 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 + ManageOldType, CustomForm, FieldType from ishtar_common.forms_common import TownFormSet, SourceForm, SourceSelect, \ - get_town_field + get_town_field, TownForm from archaeological_operations.utils import parse_parcels @@ -366,10 +366,6 @@ class ParcelFormSet(FormSet): if hasattr(self, 'cleaned_data') and self.cleaned_data: return self.cleaned_data -ParcelFormSet = formset_factory(ParcelForm, can_delete=True, - formset=ParcelFormSet) -ParcelFormSet.form_label = _(u"Parcels") - class RecordRelationsForm(ManageOldType, forms.Form): base_model = 'right_relation' @@ -477,6 +473,8 @@ class RecordRelationsFormSetBase(FormSet): RecordRelationsFormSet = formset_factory( RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase) RecordRelationsFormSet.form_label = _(u"Relations") +RecordRelationsFormSet.form_admin_name = _(u"Operation - 080 - Relations") +RecordRelationsFormSet.form_slug = "operation-080-relations" class OperationSelect(TableSelect): @@ -675,12 +673,15 @@ class OperationFormFileChoice(forms.Form): validators=[valid_id(File)], required=False) -class OperationFormAbstract(forms.Form): +class OperationFormAbstract(CustomForm, forms.Form): form_label = _(u"Abstract") + form_admin_name = _(u"Operation - 090 - Abstract") + form_slug = "operation-090-abstract" abstract = forms.CharField( label=_(u"Abstract"), widget=forms.Textarea(attrs={'class': 'xlarge'}), required=False) + SLICING = (("month", _(u"months")), ('year', _(u"years")),) DATE_SOURCE = (('creation', _(u"Creation date")), @@ -761,8 +762,11 @@ class DashboardForm(forms.Form): return fltr -class OperationFormGeneral(ManageOldType, forms.Form): +class OperationFormGeneral(ManageOldType, CustomForm, forms.Form): form_label = _(u"General") + form_admin_name = _(u"Operation - 010 - General") + form_slug = "operation-010-general" + file_upload = True associated_models = {'scientist': Person, 'in_charge': Person, @@ -882,33 +886,36 @@ class OperationFormGeneral(ManageOldType, 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') @@ -920,17 +927,20 @@ class OperationFormGeneral(ManageOldType, 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) @@ -942,11 +952,13 @@ class OperationFormGeneral(ManageOldType, 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']: @@ -989,14 +1001,18 @@ class OperationFormModifGeneral(OperationFormGeneral): fields[key] = value self.fields = fields + OperationFormModifGeneral.associated_models = \ OperationFormGeneral.associated_models.copy() OperationFormModifGeneral.associated_models['associated_file'] = File -class CollaboratorForm(forms.Form): +class CollaboratorForm(CustomForm, forms.Form): form_label = _(u"Collaborators") + form_admin_name = _(u"Operation - 020 - Collaborators") + form_slug = "operation-020-collaborators" + base_models = ['collaborator'] associated_models = {'collaborator': Person, } collaborator = widgets.Select2MultipleField( @@ -1004,11 +1020,15 @@ class CollaboratorForm(forms.Form): def __init__(self, *args, **kwargs): super(CollaboratorForm, self).__init__(*args, **kwargs) - self.fields['collaborator'].widget.attrs['full-width'] = True + if 'collaborator' in self.fields: + 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 - 033 - Preventive - Excavation") + form_slug = "operation-033-preventive-excavation" + cost = forms.IntegerField(label=_(u"Cost (euros)"), required=False) scheduled_man_days = forms.IntegerField(label=_(u"Scheduled man-days"), required=False) @@ -1023,8 +1043,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 - 037 - Preventive - Diagnostic") + form_slug = "operation-037-preventive-diagnostic" + if settings.COUNTRY == 'fr': zoning_prescription = forms.NullBooleanField( required=False, label=_(u"Prescription on zoning")) @@ -1049,9 +1072,17 @@ 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") +SelectedTownFormset.form_admin_name = _(u"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 = _(u"Operation - 040 - Towns (2)") +TownFormset.form_slug = "operation-040-towns-2" class SelectedParcelForm(forms.Form): @@ -1068,13 +1099,19 @@ class SelectedParcelForm(forms.Form): if parcels: self.fields['parcel'].choices = [('', '--')] + parcels + SelectedParcelFormSet = formset_factory(SelectedParcelForm, can_delete=True, formset=ParcelFormSet) SelectedParcelFormSet.form_label = _("Parcels") +SelectedParcelFormSet.form_admin_name = _(u"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 = _( + u"Operation - 050 - Parcels (2)") +SelectedParcelGeneralFormSet.form_slug = "operation-050-parcels-2" """ class SelectedParcelFormSet(forms.Form): @@ -1102,36 +1139,36 @@ class SelectedParcelFormSet(forms.Form): """ -class RemainForm(ManageOldType, forms.Form): - form_label = _("Remain types") +class RemainForm(CustomForm, ManageOldType, forms.Form): + form_label = _(u"Remain types") + form_admin_name = _(u"Operation - 060 - Remains") + form_slug = "operation-060-remains" + base_model = 'remain' associated_models = {'remain': models.RemainType} remain = forms.MultipleChoiceField( label=_("Remain type"), required=False, choices=[], widget=forms.CheckboxSelectMultiple) - def __init__(self, *args, **kwargs): - super(RemainForm, self).__init__(*args, **kwargs) - self.fields['remain'].choices = models.RemainType.get_types( - initial=self.init_data.get('remain'), - empty_first=False) - self.fields['remain'].help_text = models.RemainType.get_help() + TYPES = [ + FieldType('remain', models.RemainType, True), + ] -class PeriodForm(ManageOldType, forms.Form): - form_label = _("Periods") +class PeriodForm(CustomForm, ManageOldType, forms.Form): + form_label = _(u"Periods") + form_admin_name = _(u"Operation - 070 - Periods") + form_slug = "operation-070-periods" + base_model = 'period' associated_models = {'period': models.Period} period = forms.MultipleChoiceField( label=_("Period"), required=False, choices=[], widget=forms.CheckboxSelectMultiple) - def __init__(self, *args, **kwargs): - super(PeriodForm, self).__init__(*args, **kwargs) - self.fields['period'].choices = models.Period.get_types( - initial=self.init_data.get('period'), - empty_first=False) - self.fields['period'].help_text = models.Period.get_help() + TYPES = [ + FieldType('period', models.Period, True), + ] class ArchaeologicalSiteForm(ManageOldType, forms.Form): @@ -1144,21 +1181,16 @@ class ArchaeologicalSiteForm(ManageOldType, forms.Form): label=_("Remains"), choices=[], widget=widgets.Select2Multiple, required=False) + TYPES = [ + FieldType('periods', models.Period, True), + FieldType('remains', models.RemainType, True), + ] + def __init__(self, *args, **kwargs): self.limits = {} if 'limits' in kwargs: kwargs.pop('limits') super(ArchaeologicalSiteForm, self).__init__(*args, **kwargs) - self.fields['periods'].choices = \ - models.Period.get_types( - empty_first=False, - initial=self.init_data.get('periods')) - self.fields['periods'].help_text = models.Period.get_help() - self.fields['remains'].choices = \ - models.RemainType.get_types( - initial=self.init_data.get('remains'), - empty_first=False) - self.fields['remains'].help_text = models.RemainType.get_help() def clean_reference(self): reference = self.cleaned_data['reference'] @@ -1194,9 +1226,12 @@ class ArchaeologicalSiteBasicForm(forms.Form): required=False) -ArchaeologicalSiteFormSet = formset_factory(ArchaeologicalSiteBasicForm, - can_delete=True, formset=FormSet) -ArchaeologicalSiteFormSet.form_label = _("Archaeological sites") +ArchaeologicalSiteFormSet = formset_factory( + ArchaeologicalSiteBasicForm, can_delete=True, formset=FormSet) +ArchaeologicalSiteFormSet.form_label = _(u"Archaeological sites") +ArchaeologicalSiteFormSet.form_admin_name = _( + u"Operation - 030 - Archaeological sites") +ArchaeologicalSiteFormSet.form_slug = "operation-030-archaeological-sites" class ArchaeologicalSiteSelectionForm(forms.Form): @@ -1371,8 +1406,11 @@ class AdministrativeActOpeFormSelection(forms.Form): return cleaned_data -class AdministrativeActOpeForm(ManageOldType, forms.Form): +class AdministrativeActOpeForm(CustomForm, ManageOldType, forms.Form): form_label = _("General") + form_admin_name = _(u"Operation - Administrative act - General") + form_slug = "operation-adminact-general" + associated_models = {'act_type': models.ActType, } # 'signatory':Person} act_type = forms.ChoiceField(label=_("Act type"), choices=[]) @@ -1388,13 +1426,10 @@ class AdministrativeActOpeForm(ManageOldType, forms.Form): ref_sra = forms.CharField(label=u"Autre référence", max_length=15, required=False) - def __init__(self, *args, **kwargs): - super(AdministrativeActOpeForm, self).__init__(*args, **kwargs) - self.fields['act_type'].choices = models.ActType.get_types( - initial=self.init_data.get('act_type'), - dct={'intented_to': 'O'}) - self.fields['act_type'].help_text = models.ActType.get_help( - dct={'intented_to': 'O'}) + TYPES = [ + FieldType('act_type', models.ActType, + extra_args={"dct": {'intented_to': 'O'}}), + ] class AdministrativeActModifForm(object): @@ -1410,7 +1445,7 @@ class AdministrativeActModifForm(object): def clean(self): # manage unique act ID - year = self.cleaned_data.get("signature_date") + year = self.cleaned_data.get("signature_date", None) if not year or not hasattr(year, 'year'): return self.cleaned_data year = year.year diff --git a/archaeological_operations/migrations/0013_operation_images.py b/archaeological_operations/migrations/0013_operation_images.py new file mode 100644 index 000000000..e32d9371f --- /dev/null +++ b/archaeological_operations/migrations/0013_operation_images.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-10 17:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0021_auto_20171110_1717'), + ('archaeological_operations', '0012_auto_20171026_1827'), + ] + + operations = [ + migrations.AddField( + model_name='operation', + name='images', + field=models.ManyToManyField(blank=True, to='ishtar_common.IshtarImage', verbose_name='Images'), + ), + ] diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index cf648a43a..2f1ea3ea3 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -38,7 +38,7 @@ from ishtar_common.models import GeneralType, BaseHistorizedItem, \ SourceType, Person, Organization, Town, Dashboard, IshtarUser, ValueGetter,\ DocumentTemplate, ShortMenuItem, DashboardFormItem, GeneralRelationType,\ GeneralRecordRelations, post_delete_record_relation, OperationType, \ - ImageModel, post_save_cache, PersonType + ImageModel, post_save_cache, PersonType, IshtarImage class RemainType(GeneralType): @@ -352,6 +352,8 @@ class Operation(ClosedItem, BaseHistorizedItem, ImageModel, OwnPerms, comment = models.TextField(_(u"General comment"), null=True, blank=True) scientific_documentation_comment = models.TextField( _(u"Comment about scientific documentation"), null=True, blank=True) + images = models.ManyToManyField(IshtarImage, verbose_name=_(u"Images"), + blank=True) cached_label = models.CharField(_(u"Cached name"), max_length=500, null=True, blank=True, db_index=True) archaeological_sites = models.ManyToManyField( diff --git a/archaeological_operations/tests.py b/archaeological_operations/tests.py index ec7ae44c5..21b7da2a0 100644 --- a/archaeological_operations/tests.py +++ b/archaeological_operations/tests.py @@ -40,7 +40,7 @@ from ishtar_common.models import OrganizationType, Organization, ItemKey, \ ImporterType, IshtarUser, TargetKey, ImporterModel, IshtarSiteProfile, \ Town, ImporterColumn, Person, Author, SourceType, AuthorType, \ DocumentTemplate, PersonType, TargetKeyGroup, JsonDataField, \ - JsonDataSection, ImportTarget, FormaterType + JsonDataSection, ImportTarget, FormaterType, CustomForm, ExcludedField from archaeological_files.models import File, FileType from archaeological_context_records.models import Unit @@ -1142,6 +1142,109 @@ class OperationTest(TestCase, OperationInitTest): self.assertNotIn(u"Marmotte".encode('utf-8'), response.content) +class CustomFormTest(TestCase, OperationInitTest): + fixtures = FILE_FIXTURES + + def setUp(self): + IshtarSiteProfile.objects.get_or_create( + slug='default', active=True) + self.username, self.password, self.user = create_superuser() + self.alt_username, self.alt_password, self.alt_user = create_user() + self.alt_user.user_permissions.add(Permission.objects.get( + codename='view_own_operation')) + self.orgas = self.create_orgas(self.user) + self.operations = self.create_operation(self.user, self.orgas[0]) + self.operations += self.create_operation(self.alt_user, self.orgas[0]) + self.item = self.operations[0] + + def test_filters(self): + c = Client() + c.login(username=self.username, password=self.password) + + cls_wiz = OperationWizardModifTest + url = reverse(cls_wiz.url_name) + # first wizard step + step = 'selec-operation_modification' + cls_wiz.wizard_post(c, url, step, {'pk': self.operations[0].pk}) + + step = 'general-operation_modification' + data = { + '{}{}-current_step'.format(cls_wiz.url_name, + cls_wiz.wizard_name): [step], + } + key_in_charge = "in_charge" + response = c.post(url, data) + self.assertIn( + key_in_charge, response.content, + msg="filter all - 'in charge' field not found on the modification " + "wizard") + f = CustomForm.objects.create(name="Test", form="operation-010-general", + available=True, apply_to_all=True) + ExcludedField.objects.create(custom_form=f, field="in_charge") + + response = c.post(url, data) + self.assertNotIn( + key_in_charge, response.content, + msg="filter all - 'in charge' field found on the modification " + "wizard. It should have been filtered.") + + # user type form prevail on "all" + f_scientist = CustomForm.objects.create( + name="Test", form="operation-010-general", available=True) + tpe = PersonType.objects.get(txt_idx='head_scientist') + key_address = "address" + f_scientist.user_types.add(tpe) + self.user.ishtaruser.person.person_types.add(tpe) + ExcludedField.objects.create(custom_form=f_scientist, field="address") + response = c.post(url, data) + self.assertIn( + key_in_charge, response.content, + msg="filter user type - 'in charge' field not found on the " + "modification wizard. It should not have been filtered.") + self.assertNotIn( + key_address, response.content, + msg="filter user type - 'address' field found on the " + "modification wizard. It should have been filtered.") + + # user prevail on "all" and "user_types" + f_user = CustomForm.objects.create( + name="Test", form="operation-010-general", available=True) + f_user.users.add(self.user.ishtaruser) + self.user.ishtaruser.person.person_types.add(tpe) + response = c.post(url, data) + self.assertIn( + key_in_charge, response.content, + msg="filter user - 'in charge' field not found on the modification " + "wizard. It should not have been filtered.") + self.assertIn( + key_address, response.content, + msg="filter user - 'address' field not found on the modification " + "wizard. It should not have been filtered.") + + def test_enabled(self): + c = Client() + c.login(username=self.username, password=self.password) + + cls_wiz = OperationWizardModifTest + url = reverse(cls_wiz.url_name) + # first wizard step + step = 'selec-operation_modification' + cls_wiz.wizard_post(c, url, step, {'pk': self.operations[0].pk}) + + step = 'collaborators-operation_modification' + data = { + '{}{}-current_step'.format(cls_wiz.url_name, + cls_wiz.wizard_name): [step], + } + response = c.post(url, data) + self.assertNotEqual(response.status_code, 404) + CustomForm.objects.create( + name="Test2", form="operation-020-collaborators", available=True, + apply_to_all=True, enabled=False) + response = c.post(url, data) + self.assertEqual(response.status_code, 404) + + class OperationSearchTest(TestCase, OperationInitTest): fixtures = FILE_FIXTURES diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py index 98da31801..a4cb1f673 100644 --- a/archaeological_operations/views.py +++ b/archaeological_operations/views.py @@ -28,8 +28,7 @@ from django.utils.translation import ugettext_lazy as _, pgettext_lazy from ishtar_common.views import get_item, show_item, revert_item, new_item from ishtar_common.wizards import SearchWizard, check_rights_condition from ishtar_common.forms import ClosingDateFormSelection -from ishtar_common.forms_common import AuthorFormset, TownFormset, \ - SourceDeletionForm +from ishtar_common.forms_common import AuthorFormset, SourceDeletionForm from ishtar_common.models import get_current_profile from wizards import * from forms import * @@ -82,6 +81,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) @@ -132,6 +132,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') @@ -158,11 +159,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), @@ -191,6 +194,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/archaeological_operations/wizards.py b/archaeological_operations/wizards.py index fffe34ca7..24c1af45b 100644 --- a/archaeological_operations/wizards.py +++ b/archaeological_operations/wizards.py @@ -23,6 +23,7 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db.models import Max +from django.http import Http404 from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ @@ -149,7 +150,10 @@ class OperationWizard(Wizard): data = {} if not step: step = self.steps.current - form = self.get_form_list()[step] + try: + form = self.get_form_list()[step] + except KeyError: + raise Http404() # manage the dynamic choice of towns if step.startswith('towns') and hasattr(form, 'management_form'): data['TOWNS'] = self.get_towns() diff --git a/archaeological_warehouse/forms.py b/archaeological_warehouse/forms.py index f020864ff..2e672efd4 100644 --- a/archaeological_warehouse/forms.py +++ b/archaeological_warehouse/forms.py @@ -30,7 +30,8 @@ from archaeological_finds.models import TreatmentType, FindBasket import models from ishtar_common import widgets from ishtar_common.forms import name_validator, reverse_lazy, \ - get_form_selection, TableSelect, ManageOldType, FinalForm, FormSet + get_form_selection, TableSelect, ManageOldType, FinalForm, FormSet, \ + CustomForm, FieldType from archaeological_finds.forms import FindMultipleFormSelection, \ SelectFindBasketForm @@ -67,9 +68,12 @@ class DivisionFormSet(FormSet): return self.check_duplicate(('division',), _("There are identical divisions.")) + 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(TableSelect): @@ -97,8 +101,10 @@ class WarehouseFormSelection(forms.Form): validators=[valid_id(models.Warehouse)]) -class WarehouseForm(ManageOldType, forms.Form): +class WarehouseForm(CustomForm, ManageOldType, forms.Form): form_label = _(u"Warehouse") + form_admin_name = _(u"Warehouse - 010 - General") + form_slug = "warehouse-010-general" associated_models = {'warehouse_type': models.WarehouseType, 'person_in_charge': Person} @@ -127,16 +133,14 @@ class WarehouseForm(ManageOldType, forms.Form): phone = forms.CharField(label=_(u"Phone"), max_length=18, required=False) mobile_phone = forms.CharField(label=_(u"Mobile phone"), max_length=18, required=False) + TYPES = [ + FieldType('warehouse_type', models.WarehouseType) + ] def __init__(self, *args, **kwargs): if 'limits' in kwargs: kwargs.pop('limits') super(WarehouseForm, self).__init__(*args, **kwargs) - self.fields['warehouse_type'].choices = \ - models.WarehouseType.get_types( - initial=self.init_data.get('warehouse_type')) - self.fields['warehouse_type'].help_text = \ - models.WarehouseType.get_help() def save(self, user): dct = self.cleaned_data @@ -156,8 +160,10 @@ class WarehouseDeletionForm(FinalForm): confirm_end_msg = _(u"Would you like to delete this warehouse?") -class ContainerForm(ManageOldType, forms.Form): +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 associated_models = {'container_type': models.ContainerType, 'location': models.Warehouse, @@ -185,16 +191,14 @@ class ContainerForm(ManageOldType, forms.Form): max_length=255, required=False, widget=widgets.ImageFileInput()) comment = forms.CharField(label=_(u"Comment"), widget=forms.Textarea, required=False) + TYPES = [ + FieldType('container_type', models.ContainerType), + ] def __init__(self, *args, **kwargs): if 'limits' in kwargs: kwargs.pop('limits') super(ContainerForm, self).__init__(*args, **kwargs) - self.fields['container_type'].choices = \ - models.ContainerType.get_types( - initial=self.init_data.get('container_type')) - self.fields['container_type'].help_text = \ - models.ContainerType.get_help() def save(self, user): dct = self.cleaned_data @@ -282,7 +286,9 @@ class FindPackagingFormSelection(FindMultipleFormSelection): form_label = _(u"Packaged finds") -class LocalisationForm(forms.Form): +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): diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index d3292148f..fa71c4d3f 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -31,6 +31,8 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.sites.admin import SiteAdmin from django.contrib.sites.models import Site from django.contrib.gis.forms import PointField, OSMWidget, MultiPolygonField +from django.core.cache import cache +from django.forms import BaseInlineFormSet from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import render from django.template.defaultfilters import slugify @@ -38,8 +40,23 @@ from django.utils.translation import ugettext_lazy as _ from django import forms -from ishtar_common.apps import admin_site from ishtar_common import models +from ishtar_common.apps import admin_site +from ishtar_common.utils import get_cache + +from ishtar_common import forms as common_forms +from archaeological_files import forms as file_forms +from archaeological_files_pdl import forms as file_pdl_forms +from archaeological_operations import forms as operation_forms +from archaeological_context_records import forms as context_record_forms +from archaeological_finds import forms as find_forms, \ + forms_treatments as treatment_forms +from archaeological_warehouse import forms as warehouse_forms + + +ISHTAR_FORMS = [common_forms, file_pdl_forms, file_forms, operation_forms, + context_record_forms, find_forms, treatment_forms, + warehouse_forms] class ImportGenericForm(forms.Form): @@ -76,6 +93,7 @@ def gen_import_generic(self, request, queryset): request.POST.getlist(admin.ACTION_CHECKBOX_NAME)}) return render(request, 'admin/import_from_csv.html', {'csv_form': form}) + gen_import_generic.short_description = "Import from a CSV file" @@ -151,6 +169,7 @@ class IshtarSiteProfileAdmin(admin.ModelAdmin): 'find', 'warehouse', 'mapping', 'preservation') model = models.IshtarSiteProfile + admin_site.register(models.IshtarSiteProfile, IshtarSiteProfileAdmin) @@ -158,6 +177,7 @@ class DepartmentAdmin(admin.ModelAdmin): list_display = ('number', 'label',) model = models.Department + admin_site.register(models.Department, DepartmentAdmin) @@ -168,6 +188,7 @@ class OrganizationAdmin(HistorizedObjectAdmin): exclude = ('merge_key', 'merge_exclusion', 'merge_candidate', ) model = models.Organization + admin_site.register(models.Organization, OrganizationAdmin) @@ -179,6 +200,7 @@ class PersonAdmin(HistorizedObjectAdmin): form = make_ajax_form(models.Person, {'attached_to': 'organization'}) model = models.Person + admin_site.register(models.Person, PersonAdmin) @@ -236,6 +258,7 @@ class AuthorAdmin(admin.ModelAdmin): model = models.Author form = make_ajax_form(models.Author, {'person': 'person'}) + admin_site.register(models.Author, AuthorAdmin) @@ -244,11 +267,14 @@ class PersonTypeAdmin(admin.ModelAdmin): model = models.PersonType filter_vertical = ('groups',) + admin_site.register(models.PersonType, PersonTypeAdmin) class GlobalVarAdmin(admin.ModelAdmin): list_display = ['slug', 'description', 'value'] + + admin_site.register(models.GlobalVar, GlobalVarAdmin) @@ -275,16 +301,22 @@ class ImporterDefaultAdmin(admin.ModelAdmin): list_display = ('importer_type', 'target') model = models.ImporterDefault inlines = (ImporterDefaultValuesInline,) + + admin_site.register(models.ImporterDefault, ImporterDefaultAdmin) class ImporterTypeAdmin(admin.ModelAdmin): list_display = ('name', 'associated_models', 'available') + + admin_site.register(models.ImporterType, ImporterTypeAdmin) class RegexpAdmin(admin.ModelAdmin): list_display = ('name', 'description', "regexp") + + admin_site.register(models.Regexp, RegexpAdmin) @@ -312,6 +344,8 @@ class ImporterColumnAdmin(admin.ModelAdmin): 'targets_lbl', 'duplicate_fields_lbl', 'required') list_filter = ('importer_type',) inlines = (ImportTargetInline, ImporterDuplicateFieldInline) + + admin_site.register(models.ImporterColumn, ImporterColumnAdmin) @@ -319,11 +353,14 @@ class ImporterModelAdmin(admin.ModelAdmin): list_display = ('name', 'klass') model = models.ImporterModel + admin_site.register(models.ImporterModel, ImporterModelAdmin) class FormaterTypeAdmin(admin.ModelAdmin): list_display = ('formater_type', 'options') + + admin_site.register(models.FormaterType, FormaterTypeAdmin) @@ -331,6 +368,8 @@ class ImportAdmin(admin.ModelAdmin): list_display = ('name', 'importer_type', 'imported_file', 'user', 'state', 'creation_date') form = make_ajax_form(models.Import, {'user': 'ishtaruser'}) + + admin_site.register(models.Import, ImportAdmin) @@ -338,6 +377,8 @@ class TargetKeyGroupAdmin(admin.ModelAdmin): list_display = ('name', 'all_user_can_use', 'all_user_can_modify', 'available') search_fields = ('name',) + + admin_site.register(models.TargetKeyGroup, TargetKeyGroupAdmin) @@ -346,6 +387,8 @@ class TargetKeyAdmin(admin.ModelAdmin): 'value', 'is_set') list_filter = ("is_set", "target__column__importer_type") search_fields = ('target__target', 'value', 'key') + + admin_site.register(models.TargetKey, TargetKeyAdmin) @@ -353,18 +396,23 @@ class OperationTypeAdmin(GeneralTypeAdmin): list_display = GeneralTypeAdmin.list_display + ['order', 'preventive'] model = models.OperationType + admin_site.register(models.OperationType, OperationTypeAdmin) class SpatialReferenceSystemAdmin(GeneralTypeAdmin): list_display = GeneralTypeAdmin.list_display + ['order', 'srid'] model = models.SpatialReferenceSystem + + admin_site.register(models.SpatialReferenceSystem, SpatialReferenceSystemAdmin) class ItemKeyAdmin(admin.ModelAdmin): list_display = ('content_type', 'key', 'content_object', 'importer') search_fields = ('key', ) + + admin_site.register(models.ItemKey, ItemKeyAdmin) @@ -418,6 +466,83 @@ class JsonDataFieldAdmin(admin.ModelAdmin): admin_site.register(models.JsonDataField, JsonDataFieldAdmin) +def get_choices_form(): + cache_key, value = get_cache(models.CustomForm, ['associated-forms']) + if value: + return value + forms = [] + for slug in models.CustomForm.register(): + forms.append((slug, models.CustomForm._register[slug].form_admin_name)) + forms = sorted(forms, key=lambda x: x[1]) + cache.set(cache_key, forms, settings.CACHE_TIMEOUT) + return forms + + +class CustomFormForm(forms.ModelForm): + class Meta: + model = models.CustomForm + exclude = [] + form = forms.ChoiceField(label=_(u"Form"), choices=get_choices_form) + users = AutoCompleteSelectMultipleField('ishtaruser', required=False, + label=_(u"Users")) + + +class ExcludeFieldFormset(BaseInlineFormSet): + def get_form_kwargs(self, index): + kwargs = super(ExcludeFieldFormset, self).get_form_kwargs(index) + if not self.instance or not self.instance.pk: + return kwargs + form = self.instance.get_form_class() + if not form: + kwargs['choices'] = [] + return kwargs + kwargs['choices'] = [('', '--')] + form.get_custom_fields() + return kwargs + + +class ExcludeFieldForm(forms.ModelForm): + class Meta: + model = models.ExcludedField + exclude = [] + field = forms.ChoiceField(label=_(u"Field")) + + def __init__(self, *args, **kwargs): + choices = kwargs.pop('choices') + super(ExcludeFieldForm, self).__init__(*args, **kwargs) + self.fields['field'].choices = choices + + +class ExcludeFieldInline(admin.TabularInline): + model = models.ExcludedField + extra = 2 + form = ExcludeFieldForm + formset = ExcludeFieldFormset + + +class CustomFormAdmin(admin.ModelAdmin): + list_display = ['name', 'form', 'available', 'enabled', 'apply_to_all', + 'users_lbl', 'user_types_lbl'] + fields = ('name', 'form', 'available', 'enabled', 'apply_to_all', 'users', + 'user_types') + form = CustomFormForm + inlines = [ExcludeFieldInline] + + def get_inline_instances(self, request, obj=None): + # no inline on creation + if not obj: + return [] + return super(CustomFormAdmin, self).get_inline_instances(request, + obj=obj) + + def get_readonly_fields(self, request, obj=None): + if obj: + return ('form',) + return [] + + +admin_site.register(models.CustomForm, CustomFormAdmin) + + class AdministrationScriptAdmin(admin.ModelAdmin): list_display = ['name', 'path'] @@ -426,9 +551,19 @@ class AdministrationScriptAdmin(admin.ModelAdmin): return ('path',) return [] + admin_site.register(models.AdministrationScript, AdministrationScriptAdmin) +class ImageAdmin(admin.ModelAdmin): + list_display = ('name', 'image_type', 'reference', 'internal_reference') + list_filter = ('image_type',) + search_fields = ('name', 'reference', 'internal_reference') + + +admin_site.register(models.IshtarImage, ImageAdmin) + + class AdministrationTaskAdmin(admin.ModelAdmin): readonly_fields = ('state', 'creation_date', 'launch_date', 'finished_date', "result", ) @@ -441,6 +576,7 @@ class AdministrationTaskAdmin(admin.ModelAdmin): return ("script", ) + self.readonly_fields return self.readonly_fields + admin_site.register(models.AdministrationTask, AdministrationTaskAdmin) diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 5c3de7b77..eebd912ea 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -34,7 +34,7 @@ from django.utils.translation import ugettext_lazy as _ import models import widgets -from wizards import MultiValueDict +from ishtar_common.utils import MultiValueDict # from formwizard.forms import NamedUrlSessionFormWizard @@ -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,31 @@ def get_data_from_formset(data): return values +class FieldType(object): + def __init__(self, key, model, is_multiple=False, extra_args=None): + self.key = key + self.model = model + self.is_multiple = is_multiple + self.extra_args = extra_args + + def get_choices(self, initial=None): + args = { + 'empty_first': not self.is_multiple, + 'initial': initial + } + if self.extra_args: + args.update(self.extra_args) + return self.model.get_types(**args) + + def get_help(self): + args = {} + if self.extra_args: + args.update(self.extra_args) + return self.model.get_help(**args) + + class ManageOldType(object): - TYPES = [] # (field_name, model, is_multiple) list + TYPES = [] # FieldType list def __init__(self, *args, **kwargs): """ @@ -290,12 +398,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() + for field in self.TYPES: + if field.key not in self.fields: + continue + 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/forms_common.py b/ishtar_common/forms_common.py index 116c8c277..dadeefee0 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -35,7 +35,7 @@ import models import widgets from ishtar_common.templatetags.link_to_window import link_to_window from forms import FinalForm, FormSet, reverse_lazy, name_validator, \ - TableSelect, ManageOldType + TableSelect, ManageOldType, CustomForm, FieldType def get_town_field(label=_(u"Town"), required=True): @@ -731,8 +731,11 @@ class TownFormSet(FormSet): return self.check_duplicate(('town',), _("There are identical towns.")) + TownFormset = formset_factory(TownForm, can_delete=True, formset=TownFormSet) TownFormset.form_label = _("Towns") +TownFormset.form_admin_name = _(u"Towns") +TownFormset.form_slug = "towns" class MergeFormSet(BaseModelFormSet): @@ -860,8 +863,11 @@ class MergeOrganizationForm(MergeForm): ###################### # Sources management # ###################### -class SourceForm(ManageOldType, forms.Form): +class SourceForm(CustomForm, ManageOldType, forms.Form): form_label = _(u"Documentation informations") + form_admin_name = _("Source - General") + form_slug = "source-general" + file_upload = True associated_models = {'source_type': models.SourceType} title = forms.CharField(label=_(u"Title"), @@ -899,10 +905,9 @@ class SourceForm(ManageOldType, forms.Form): 'height': settings.IMAGE_MAX_SIZE[1]}), max_length=255, required=False, widget=widgets.ImageFileInput()) - def __init__(self, *args, **kwargs): - super(SourceForm, self).__init__(*args, **kwargs) - self.fields['source_type'].choices = models.SourceType.get_types( - initial=self.init_data.get('source_type')) + TYPES = [ + FieldType('source_type', models.SourceType), + ] class SourceSelect(TableSelect): @@ -986,3 +991,5 @@ class AuthorFormSet(FormSet): AuthorFormset = formset_factory(AuthorFormSelection, can_delete=True, formset=AuthorFormSet) AuthorFormset.form_label = _("Authors") +AuthorFormset.form_admin_name = _(u"Authors") +AuthorFormset.form_slug = "authors" diff --git a/ishtar_common/migrations/0021_auto_20171110_1717.py b/ishtar_common/migrations/0021_auto_20171110_1717.py new file mode 100644 index 000000000..c95f5e1d8 --- /dev/null +++ b/ishtar_common/migrations/0021_auto_20171110_1717.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-10 17:17 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import ishtar_common.models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0020_auto_20171030_1708'), + ] + + operations = [ + migrations.CreateModel( + name='ImageType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=100, verbose_name='Label')), + ('txt_idx', models.CharField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', max_length=100, unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), + ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ], + options={ + 'ordering': ('label',), + 'verbose_name': 'Image type', + 'verbose_name_plural': 'Image types', + }, + bases=(ishtar_common.models.Cached, models.Model), + ), + migrations.CreateModel( + name='IshtarImage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(blank=True, max_length=255, null=True, upload_to=ishtar_common.models.get_image_path)), + ('thumbnail', models.ImageField(blank=True, max_length=255, null=True, upload_to=ishtar_common.models.get_image_path)), + ('name', models.CharField(max_length=250, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Description')), + ('authors_raw', models.CharField(blank=True, max_length=250, null=True, verbose_name='Authors (raw)')), + ('creation_date', models.DateField(blank=True, null=True, verbose_name='Creation date')), + ('reference', models.CharField(blank=True, max_length=250, null=True, verbose_name='Ref.')), + ('internal_reference', models.CharField(blank=True, max_length=250, null=True, verbose_name='Internal ref.')), + ('authors', models.ManyToManyField(blank=True, to='ishtar_common.Author', verbose_name='Authors')), + ('image_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.ImageType', verbose_name='Type')), + ], + options={ + 'ordering': ('name',), + 'verbose_name': 'Image', + 'verbose_name_plural': 'Images', + }, + ), + migrations.CreateModel( + name='LicenseType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=100, verbose_name='Label')), + ('txt_idx', models.CharField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', max_length=100, unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')], verbose_name='Textual ID')), + ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ], + options={ + 'ordering': ('label',), + 'verbose_name': 'License type', + 'verbose_name_plural': 'License types', + }, + bases=(ishtar_common.models.Cached, models.Model), + ), + migrations.AddField( + model_name='ishtarimage', + name='licenses', + field=models.ManyToManyField(blank=True, to='ishtar_common.LicenseType', verbose_name='License'), + ), + ] diff --git a/ishtar_common/migrations/0022_customform.py b/ishtar_common/migrations/0022_customform.py new file mode 100644 index 000000000..8eaed6d89 --- /dev/null +++ b/ishtar_common/migrations/0022_customform.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-17 12:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0021_auto_20171110_1717'), + ] + + operations = [ + migrations.CreateModel( + name='CustomForm', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=250, verbose_name='Name')), + ('form', models.CharField(max_length=250, verbose_name='Form')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('apply_to_all', models.BooleanField(default=False, help_text='Apply this form to all users. If set to True, selecting user and user type is useless.', verbose_name='Apply to all')), + ('user_types', models.ManyToManyField(blank=True, to='ishtar_common.PersonType')), + ('users', models.ManyToManyField(blank=True, to='ishtar_common.IshtarUser')), + ], + options={ + 'ordering': ['name', 'form'], + 'verbose_name': 'Custom form', + 'verbose_name_plural': 'Custom forms', + }, + ), + ] diff --git a/ishtar_common/migrations/0023_excludedfield.py b/ishtar_common/migrations/0023_excludedfield.py new file mode 100644 index 000000000..2573219ae --- /dev/null +++ b/ishtar_common/migrations/0023_excludedfield.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-17 17:37 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0022_customform'), + ] + + operations = [ + migrations.CreateModel( + name='ExcludedField', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('field', models.CharField(max_length=250, verbose_name='Field')), + ('custom_form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='excluded_fields', to='ishtar_common.CustomForm')), + ], + options={ + 'verbose_name': 'Custom form - excluded field', + 'verbose_name_plural': 'Custom form - excluded fields', + }, + ), + ] diff --git a/ishtar_common/migrations/0024_custom_form_enabled.py b/ishtar_common/migrations/0024_custom_form_enabled.py new file mode 100644 index 000000000..92fd32f6e --- /dev/null +++ b/ishtar_common/migrations/0024_custom_form_enabled.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-11-21 09:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0023_excludedfield'), + ] + + operations = [ + migrations.AlterModelOptions( + name='excludedfield', + options={'verbose_name': 'Excluded field', 'verbose_name_plural': 'Excluded fields'}, + ), + migrations.AddField( + model_name='customform', + name='enabled', + field=models.BooleanField(default=True, help_text='Disable with caution: disabling a form with mandatory fields may lead to database errors.', verbose_name='Enable this form'), + ), + ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 2c8240a72..aab532cfc 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -23,6 +23,7 @@ Models description from cStringIO import StringIO import copy import datetime +import inspect from PIL import Image import logging import os @@ -887,6 +888,7 @@ class HistoryError(Exception): def __str__(self): return repr(self.value) + PRIVATE_FIELDS = ('id', 'history_modifier', 'order') @@ -1447,6 +1449,7 @@ def get_external_id(key, item): dct[initial_key] = filtr(dct[initial_key]) return formula.format(**dct) + CURRENCY = ((u"€", _(u"Euro")), (u"$", _(u"US dollar"))) FIND_INDEX_SOURCE = ((u"O", _(u"Operations")), @@ -1629,6 +1632,84 @@ post_save.connect(cached_site_changed, sender=IshtarSiteProfile) post_delete.connect(cached_site_changed, sender=IshtarSiteProfile) +class CustomForm(models.Model): + name = models.CharField(_(u"Name"), max_length=250) + form = models.CharField(_(u"Form"), max_length=250) + available = models.BooleanField(_(u"Available"), default=True) + enabled = models.BooleanField( + _(u"Enable this form"), default=True, + help_text=_(u"Disable with caution: disabling a form with mandatory " + u"fields may lead to database errors.")) + apply_to_all = models.BooleanField( + _(u"Apply to all"), default=False, + help_text=_(u"Apply this form to all users. If set to True, selecting " + u"user and user type is useless.")) + users = models.ManyToManyField('IshtarUser', blank=True) + user_types = models.ManyToManyField('PersonType', blank=True) + + class Meta: + verbose_name = _(u"Custom form") + verbose_name_plural = _(u"Custom forms") + ordering = ['name', 'form'] + + def __unicode__(self): + return u"{} - {}".format(self.name, self.form) + + def users_lbl(self): + users = [unicode(user) for user in self.users.all()] + return " ; ".join(users) + + users_lbl.short_description = _(u"Users") + + def user_types_lbl(self): + user_types = [unicode(u) for u in self.user_types.all()] + return " ; ".join(user_types) + + user_types_lbl.short_description = _(u"User types") + + @classmethod + def register(cls): + if hasattr(cls, '_register'): + return cls._register + cache_key, value = get_cache(cls.__class__, ['dct-forms'], + app_label='ishtar_common') + if value: + cls._register = value + return cls._register + cls._register = {} + # ideally should be improved but only used in admin + from ishtar_common.admin import ISHTAR_FORMS + from ishtar_common.forms import CustomForm + + for app_form in ISHTAR_FORMS: + for form in dir(app_form): + if 'Form' not in form: + # not very clean... but do not treat inappropriate items + continue + form = getattr(app_form, form) + if not inspect.isclass(form) \ + or not issubclass(form, CustomForm) \ + or not getattr(form, 'form_slug', None): + continue + cls._register[form.form_slug] = form + return cls._register + + def get_form_class(self): + register = self.register() + if self.form not in self._register: + return + return register[self.form] + + +class ExcludedField(models.Model): + custom_form = models.ForeignKey(CustomForm, related_name='excluded_fields') + field = models.CharField(_(u"Field"), max_length=250) + + class Meta: + verbose_name = _(u"Excluded field") + verbose_name_plural = _(u"Excluded fields") + + class GlobalVar(models.Model, Cached): slug = models.SlugField(_(u"Variable name"), unique=True) description = models.TextField(_(u"Description of the variable"), @@ -1651,6 +1732,7 @@ def cached_globalvar_changed(sender, **kwargs): cache_key, value = get_cache(GlobalVar, var.slug) cache.set(cache_key, var.value, settings.CACHE_TIMEOUT) + post_save.connect(cached_globalvar_changed, sender=GlobalVar) @@ -2146,6 +2228,8 @@ class OrganizationType(GeneralType): verbose_name = _(u"Organization type") verbose_name_plural = _(u"Organization types") ordering = ('label',) + + post_save.connect(post_save_cache, sender=OrganizationType) post_delete.connect(post_save_cache, sender=OrganizationType) @@ -2577,6 +2661,8 @@ class AuthorType(GeneralType): verbose_name = _(u"Author type") verbose_name_plural = _(u"Author types") ordering = ['order', 'label'] + + post_save.connect(post_save_cache, sender=AuthorType) post_delete.connect(post_save_cache, sender=AuthorType) @@ -2635,6 +2721,8 @@ class Format(GeneralType): verbose_name = _(u"Format type") verbose_name_plural = _(u"Format types") ordering = ['label'] + + post_save.connect(post_save_cache, sender=Format) post_delete.connect(post_save_cache, sender=Format) @@ -2696,6 +2784,45 @@ class Source(OwnPerms, ImageModel, models.Model): return slugify(u"-".join(values)) +class LicenseType(GeneralType): + class Meta: + verbose_name = _(u"License type") + verbose_name_plural = _(u"License types") + ordering = ('label',) + + +class ImageType(GeneralType): + class Meta: + verbose_name = _(u"Image type") + verbose_name_plural = _(u"Image types") + ordering = ('label',) + + +class IshtarImage(ImageModel): + name = models.CharField(_(u"Name"), max_length=250) + description = models.TextField(_(u"Description"), blank=True, null=True) + licenses = models.ManyToManyField(LicenseType, verbose_name=_(u"License"), + blank=True) + authors = models.ManyToManyField(Author, verbose_name=_(u"Authors"), + blank=True) + authors_raw = models.CharField(verbose_name=_(u"Authors (raw)"), + blank=True, null=True, max_length=250) + + image_type = models.ForeignKey(ImageType, verbose_name=_(u"Type"), + blank=True, null=True) + creation_date = models.DateField(blank=True, null=True, + verbose_name=_(u"Creation date")) + reference = models.CharField(_(u"Ref."), max_length=250, null=True, + blank=True) + internal_reference = models.CharField( + _(u"Internal ref."), max_length=250, null=True, blank=True) + + class Meta: + verbose_name = _(u"Image") + verbose_name_plural = _(u"Images") + ordering = ('name',) + + if settings.COUNTRY == 'fr': class Arrondissement(models.Model): name = models.CharField(u"Nom", max_length=30) @@ -2893,6 +3020,8 @@ class OperationType(GeneralType): if not key: return op_type.preventive return key == op_type.txt_idx + + post_save.connect(post_save_cache, sender=OperationType) post_delete.connect(post_save_cache, sender=OperationType) diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py index 63d80d5ab..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 @@ -288,6 +288,38 @@ class WizardTest(object): raise ValidationError(u"Errors: {} on {}.".format( u" ".join(errors), current_step)) + @classmethod + def wizard_post(cls, client, url, current_step, form_data=None, + follow=True): + if not url: + url = reverse(cls.url_name) + data = { + '{}{}-current_step'.format(cls.url_name, + cls.wizard_name): [current_step], + } + if not form_data: + form_data = [] + + # reconstruct a POST request + if type(form_data) in (list, tuple): # is a formset + for d_idx, item in enumerate(form_data): + for k in item: + data['{}-{}-{}'.format( + current_step, d_idx, k)] = item[k] + else: + for k in form_data: + data['{}-{}'.format(current_step, k)] = form_data[k] + + try: + response = client.post(url, data, follow=follow) + except ValidationError as e: + msg = u"Errors: {} on {}. On \"ManagementForm data is " \ + u"missing or...\" error verify the wizard_name or " \ + u"step name".format(u" - ".join(e.messages), + current_step) + raise ValidationError(msg) + return response + def test_wizard(self): if self.pass_test(): return @@ -301,35 +333,14 @@ class WizardTest(object): current_step, current_form = step if current_step in ignored: continue - data = { - '{}{}-current_step'.format(self.url_name, - self.wizard_name): - [current_step], - } - - # reconstruct a POST request - if current_step in form_data: - d = form_data[current_step] - if type(d) in (list, tuple): # is a formset - for d_idx, item in enumerate(d): - for k in item: - data['{}-{}-{}'.format( - current_step, d_idx, k)] = item[k] - else: - for k in d: - data['{}-{}'.format(current_step, k)] = d[k] - next_form_is_checked = len(self.steps) > idx + 1 and \ - self.steps[idx + 1][0] not in ignored - try: - response = self.client.post( - url, data, follow=not next_form_is_checked) - except ValidationError as e: - msg = u"Errors: {} on {}. On \"ManagementForm data is " \ - u"missing or...\" error verify the wizard_name or " \ - u"step name".format(u" - ".join(e.messages), - current_step) - raise ValidationError(msg) + self.steps[idx + 1][0] not in ignored + data = [] + if current_step in form_data: + data = form_data[current_step] + response = self.wizard_post( + self.client, url, current_step, data, + not next_form_is_checked) self.check_response(response, current_step) if next_form_is_checked: next_form = self.steps[idx + 1][0] @@ -402,7 +413,8 @@ class AdminGenTypeTest(TestCase): gen_models = [ models.OrganizationType, models.PersonType, models.TitleType, models.AuthorType, models.SourceType, models.OperationType, - models.SpatialReferenceSystem, models.Format, models.SupportType] + models.SpatialReferenceSystem, models.Format, models.SupportType, + ] models_with_data = gen_models + [models.ImporterModel] models = models_with_data module_name = 'ishtar_common' diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 5d9e85c60..cc01f23e7 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -28,6 +28,7 @@ from django.conf import settings from django.contrib.gis.geos import GEOSGeometry from django.core.cache import cache from django.core.urlresolvers import reverse +from django.utils.datastructures import MultiValueDict as BaseMultiValueDict from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _, ugettext from django.template.defaultfilters import slugify @@ -47,13 +48,33 @@ class BColors: UNDERLINE = '\033[4m' +class MultiValueDict(BaseMultiValueDict): + def get(self, *args, **kwargs): + v = super(MultiValueDict, self).getlist(*args, **kwargs) + if callable(v): + v = v() + if type(v) in (list, tuple) and len(v) > 1: + v = ",".join(v) + elif type(v) not in (int, unicode): + v = super(MultiValueDict, self).get(*args, **kwargs) + return v + + def getlist(self, *args, **kwargs): + lst = super(MultiValueDict, self).getlist(*args, **kwargs) + if type(lst) not in (tuple, list): + lst = [lst] + return lst + + def get_current_year(): return datetime.datetime.now().year -def get_cache(cls, extra_args=[]): +def get_cache(cls, extra_args=tuple(), app_label=None): + if not app_label: + app_label = cls._meta.app_label cache_key = u"{}-{}-{}".format( - settings.PROJECT_SLUG, cls._meta.app_label, cls.__name__) + settings.PROJECT_SLUG, app_label, cls.__name__) for arg in extra_args: if not arg: cache_key += '-0' diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index f86e03df0..e82b32671 100644 --- a/ishtar_common/wizards.py +++ b/ishtar_common/wizards.py @@ -34,37 +34,19 @@ from django.db.models.fields.files import FileField, ImageFieldFile from django.db.models.fields.related import ManyToManyField from django.db.models.fields import NOT_PROVIDED -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, Http404 from django.forms import ValidationError from django.shortcuts import redirect, render from django.template import loader -from django.utils.datastructures import MultiValueDict as BaseMultiValueDict from django.utils.translation import ugettext_lazy as _ from ishtar_common import models -from ishtar_common.utils import get_all_field_names +from ishtar_common.forms import CustomForm +from ishtar_common.utils import get_all_field_names, MultiValueDict logger = logging.getLogger(__name__) -class MultiValueDict(BaseMultiValueDict): - def get(self, *args, **kwargs): - v = super(MultiValueDict, self).getlist(*args, **kwargs) - if callable(v): - v = v() - if type(v) in (list, tuple) and len(v) > 1: - v = ",".join(v) - elif type(v) not in (int, unicode): - v = super(MultiValueDict, self).get(*args, **kwargs) - return v - - def getlist(self, *args, **kwargs): - lst = super(MultiValueDict, self).getlist(*args, **kwargs) - if type(lst) not in (tuple, list): - lst = [lst] - return lst - - def check_rights(rights=[], redirect_url='/'): """ Decorator that checks the rights to access the view. @@ -125,6 +107,19 @@ def _check_right(step, condition=True): """ +def filter_no_fields_form(form, other_check=None): + def func(self): + if issubclass(form, CustomForm): + enabled, exc = form.check_availability_and_excluded_fields( + self.request.user.ishtaruser) + if not enabled: + return False + if other_check: + return other_check(self) + return True + return func + + class Wizard(NamedUrlWizardView): model = None label = '' @@ -155,6 +150,19 @@ class Wizard(NamedUrlWizardView): self.condition_dict[form_key] = cond ''' + @classmethod + def get_initkwargs(cls, *args, **kwargs): + kwargs = super(Wizard, cls).get_initkwargs(*args, **kwargs) + # remove + for form_key in kwargs['form_list']: + form = kwargs['form_list'][form_key] + other_check = None + if form_key in kwargs['condition_dict']: + other_check = kwargs['condition_dict'][form_key] + kwargs['condition_dict'][form_key] = filter_no_fields_form( + form, other_check) + return kwargs + def dispatch(self, request, *args, **kwargs): self.current_right = kwargs.get('current_right', None) @@ -813,7 +821,10 @@ class Wizard(NamedUrlWizardView): data = data.copy() if not step: step = self.steps.current - form = self.get_form_list()[step] + try: + form = self.get_form_list()[step] + except KeyError: + raise Http404() if hasattr(form, 'management_form'): # manage deletion to_delete, not_to_delete = self.get_deleted(data.keys()) |