summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2017-11-24 19:39:06 +0100
committerÉtienne Loks <etienne.loks@iggdrasil.net>2017-11-24 19:39:06 +0100
commit651524bf93ee64f5f28605884f6b60256db43d1f (patch)
tree3eab6d3ab5f8da3f4cb7c803b2237928b27b9d74
parent17bf67ec257d0d10decedc44a88d0ab5197c2ba8 (diff)
parent9384f39f8fd63f1e004d3abe75cb737c6651e3ed (diff)
downloadIshtar-651524bf93ee64f5f28605884f6b60256db43d1f.tar.bz2
Ishtar-651524bf93ee64f5f28605884f6b60256db43d1f.zip
Merge branch 'develop' into develop-bootstrap
-rw-r--r--archaeological_finds/forms.py12
-rw-r--r--archaeological_operations/forms.py166
-rw-r--r--archaeological_operations/tests.py105
-rw-r--r--archaeological_operations/views.py13
-rw-r--r--archaeological_operations/wizards.py6
-rw-r--r--ishtar_common/admin.py127
-rw-r--r--ishtar_common/forms.py126
-rw-r--r--ishtar_common/forms_common.py15
-rw-r--r--ishtar_common/migrations/0022_customform.py32
-rw-r--r--ishtar_common/migrations/0023_excludedfield.py28
-rw-r--r--ishtar_common/migrations/0024_custom_form_enabled.py24
-rw-r--r--ishtar_common/models.py74
-rw-r--r--ishtar_common/tests.py72
-rw-r--r--ishtar_common/utils.py25
-rw-r--r--ishtar_common/wizards.py55
15 files changed, 730 insertions, 150 deletions
diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py
index b88ee164e..2a895064f 100644
--- a/archaeological_finds/forms.py
+++ b/archaeological_finds/forms.py
@@ -41,7 +41,7 @@ import models
from ishtar_common.forms import FormSet, FloatField, \
get_form_selection, reverse_lazy, TableSelect, get_now, FinalForm, \
- ManageOldType
+ ManageOldType, FieldType
from ishtar_common.forms_common import get_town_field, SourceSelect
from ishtar_common.utils import convert_coordinates_to_point
@@ -316,11 +316,11 @@ class PreservationForm(ManageOldType, forms.Form):
widget=forms.Textarea)
TYPES = [
- ('conservatory_state', models.ConservatoryState, False),
- ('treatment_emergency', models.TreatmentEmergencyType, False),
- ('preservation_to_consider', models.TreatmentType, True),
- ('alteration', models.AlterationType, True),
- ('alteration_cause', models.AlterationCauseType, True),
+ FieldType('conservatory_state', models.ConservatoryState),
+ FieldType('treatment_emergency', models.TreatmentEmergencyType),
+ FieldType('preservation_to_consider', models.TreatmentType, True),
+ FieldType('alteration', models.AlterationType, True),
+ FieldType('alteration_cause', models.AlterationCauseType, True),
]
def __init__(self, *args, **kwargs):
diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py
index f456eae86..c61b75f7c 100644
--- a/archaeological_operations/forms.py
+++ b/archaeological_operations/forms.py
@@ -49,7 +49,7 @@ from ishtar_common import widgets
from ishtar_common.forms import FinalForm, FormSet, get_now, \
reverse_lazy, get_form_selection, TableSelect, get_data_from_formset, \
- ManageOldType, IshtarForm
+ ManageOldType, IshtarForm, CustomForm, FieldType
from ishtar_common.forms_common import TownFormSet, SourceForm, SourceSelect, \
get_town_field
@@ -477,6 +477,8 @@ class RecordRelationsFormSetBase(FormSet):
RecordRelationsFormSet = formset_factory(
RecordRelationsForm, can_delete=True, formset=RecordRelationsFormSetBase)
RecordRelationsFormSet.form_label = _(u"Relations")
+RecordRelationsFormSet.form_admin_name = _("Operations - Relations")
+RecordRelationsFormSet.form_slug = "operation-relations"
class OperationSelect(TableSelect):
@@ -675,12 +677,15 @@ class OperationFormFileChoice(IshtarForm):
validators=[valid_id(File)], required=False)
-class OperationFormAbstract(IshtarForm):
+class OperationFormAbstract(CustomForm, IshtarForm):
form_label = _(u"Abstract")
+ form_admin_name = _("Operations - Abstract")
+ form_slug = "operation-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 +766,11 @@ class DashboardForm(forms.Form):
return fltr
-class OperationFormGeneral(ManageOldType):
+class OperationFormGeneral(CustomForm, ManageOldType):
form_label = _(u"General")
+ form_admin_name = _(u"Operation - General")
+ form_slug = "operation-general"
+
file_upload = True
associated_models = {'scientist': Person,
'in_charge': Person,
@@ -882,33 +890,36 @@ class OperationFormGeneral(ManageOldType):
'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 +931,20 @@ class OperationFormGeneral(ManageOldType):
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 +956,13 @@ class OperationFormGeneral(ManageOldType):
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 +1005,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(IshtarForm):
+class CollaboratorForm(CustomForm, IshtarForm):
form_label = _(u"Collaborators")
+ form_admin_name = _(u"Operation - Collaborators")
+ form_slug = "operation-collaborators"
+
base_models = ['collaborator']
associated_models = {'collaborator': Person, }
collaborator = widgets.Select2MultipleField(
@@ -1004,11 +1024,15 @@ class CollaboratorForm(IshtarForm):
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(IshtarForm):
+class OperationFormPreventive(CustomForm, IshtarForm):
form_label = _(u"Preventive informations - excavation")
+ form_admin_name = _(u"Operation - Preventive - Excavation")
+ form_slug = "operation-preventive-excavation"
+
cost = forms.IntegerField(label=_(u"Cost (euros)"), required=False)
scheduled_man_days = forms.IntegerField(label=_(u"Scheduled man-days"),
required=False)
@@ -1023,8 +1047,11 @@ class OperationFormPreventive(IshtarForm):
validators.MaxValueValidator(100)])
-class OperationFormPreventiveDiag(IshtarForm):
+class OperationFormPreventiveDiag(CustomForm, IshtarForm):
form_label = _("Preventive informations - diagnostic")
+ form_admin_name = _(u"Operation - Preventive - Diagnostic")
+ form_slug = "operation-preventive-diagnostic"
+
if settings.COUNTRY == 'fr':
zoning_prescription = forms.NullBooleanField(
required=False, label=_(u"Prescription on zoning"))
@@ -1049,9 +1076,11 @@ class SelectedTownForm(IshtarForm):
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_slug = "towns"
class SelectedParcelForm(IshtarForm):
@@ -1068,13 +1097,17 @@ class SelectedParcelForm(IshtarForm):
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"Operations - Parcels")
+SelectedParcelFormSet.form_slug = "operation-parcels"
SelectedParcelGeneralFormSet = formset_factory(ParcelForm, can_delete=True,
formset=ParcelFormSet)
-SelectedParcelGeneralFormSet.form_label = _("Parcels")
+SelectedParcelGeneralFormSet.form_admin_name = _("Parcels")
+SelectedParcelGeneralFormSet.form_slug = "operation-parcels"
"""
class SelectedParcelFormSet(forms.Form):
@@ -1102,36 +1135,36 @@ class SelectedParcelFormSet(forms.Form):
"""
-class RemainForm(ManageOldType):
+class RemainForm(CustomForm, ManageOldType):
form_label = _("Remain types")
+ form_admin_name = _("Operations - Remains")
+ form_slug = "operation-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):
+class PeriodForm(CustomForm, ManageOldType):
form_label = _("Periods")
+ form_admin_name = _("Operations - Periods")
+ form_slug = "operation-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):
@@ -1144,21 +1177,16 @@ class ArchaeologicalSiteForm(ManageOldType):
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']
@@ -1197,6 +1225,9 @@ class ArchaeologicalSiteBasicForm(IshtarForm):
ArchaeologicalSiteFormSet = formset_factory(ArchaeologicalSiteBasicForm,
can_delete=True, formset=FormSet)
ArchaeologicalSiteFormSet.form_label = _("Archaeological sites")
+ArchaeologicalSiteFormSet.form_admin_name = _("Operation - Archaeological "
+ "sites")
+ArchaeologicalSiteFormSet.form_slug = "operation-archaeological-sites"
class ArchaeologicalSiteSelectionForm(IshtarForm):
@@ -1224,6 +1255,9 @@ class OperationDeletionForm(FinalForm):
class OperationSourceForm(SourceForm):
+ form_admin_name = _("Operation Sources - Main")
+ form_slug = "operation-source-relations"
+
pk = forms.IntegerField(required=False, widget=forms.HiddenInput)
index = forms.IntegerField(label=_(u"Index"))
hidden_operation_id = forms.IntegerField(label="",
@@ -1371,8 +1405,11 @@ class AdministrativeActOpeFormSelection(IshtarForm):
return cleaned_data
-class AdministrativeActOpeForm(ManageOldType):
+class AdministrativeActOpeForm(CustomForm, ManageOldType):
form_label = _("General")
+ form_admin_name = _("Operations - 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 +1425,10 @@ class AdministrativeActOpeForm(ManageOldType):
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 +1444,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/tests.py b/archaeological_operations/tests.py
index ec7ae44c5..af6199774 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-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-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-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-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 1fecce9cd..f295e0f9d 100644
--- a/archaeological_operations/views.py
+++ b/archaeological_operations/views.py
@@ -36,7 +36,7 @@ from forms import *
import models
-def autocomplete_patriarche(request, non_closed=True):
+def autocomplete_patriarche(request):
if (not request.user.has_perm('ishtar_common.view_operation',
models.Operation)
and not request.user.has_perm('ishtar_common.view_own_operation',
@@ -50,8 +50,6 @@ def autocomplete_patriarche(request, non_closed=True):
query = Q()
for q in q.split(' '):
query &= Q(code_patriarche__startswith=q)
- if non_closed:
- query &= Q(end_date__isnull=True)
limit = 15
operations = models.Operation.objects\
.filter(query).order_by('code_patriarche')[:limit]
@@ -84,11 +82,12 @@ def autocomplete_archaeologicalsite(request):
for site in sites])
return HttpResponse(data, content_type='text/plain')
+
new_archaeologicalsite = new_item(models.ArchaeologicalSite,
ArchaeologicalSiteForm, many=True)
-def autocomplete_operation(request, non_closed=True):
+def autocomplete_operation(request):
# person_types = request.user.ishtaruser.person.person_type
if (not request.user.has_perm('ishtar_common.view_operation',
models.Operation)
@@ -117,8 +116,6 @@ def autocomplete_operation(request, non_closed=True):
except ValueError:
pass
query = query & extra
- if non_closed:
- query &= Q(end_date__isnull=True)
limit = 15
operations = models.Operation.objects.filter(query)[:limit]
data = json.dumps([{'id': operation.pk, 'value': unicode(operation)}
@@ -136,6 +133,7 @@ def get_available_operation_code(request, year=None):
models.Operation.get_available_operation_code(year)})
return HttpResponse(data, content_type='text/plain')
+
get_operation = get_item(models.Operation, 'get_operation', 'operation')
show_operation = show_item(models.Operation, 'operation')
@@ -162,11 +160,13 @@ def dashboard_operation(request, *args, **kwargs):
dct = {'dashboard': models.OperationDashboard()}
return render(request, 'ishtar/dashboards/dashboard_operation.html', dct)
+
operation_search_wizard = SearchWizard.as_view(
[('general-operation_search', OperationFormSelection)],
label=_(u"Operation search"),
url_name='operation_search',)
+
wizard_steps = [
('filechoice-operation_creation', OperationFormFileChoice),
('general-operation_creation', OperationFormGeneral),
@@ -195,6 +195,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/ishtar_common/admin.py b/ishtar_common/admin.py
index c3ad21710..189a02c05 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,22 @@ 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_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_forms, operation_forms,
+ context_record_forms, find_forms, treatment_forms,
+ warehouse_forms]
class ImportGenericForm(forms.Form):
@@ -76,6 +92,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 +168,7 @@ class IshtarSiteProfileAdmin(admin.ModelAdmin):
'find', 'warehouse', 'mapping', 'preservation')
model = models.IshtarSiteProfile
+
admin_site.register(models.IshtarSiteProfile, IshtarSiteProfileAdmin)
@@ -158,6 +176,7 @@ class DepartmentAdmin(admin.ModelAdmin):
list_display = ('number', 'label',)
model = models.Department
+
admin_site.register(models.Department, DepartmentAdmin)
@@ -168,6 +187,7 @@ class OrganizationAdmin(HistorizedObjectAdmin):
exclude = ('merge_key', 'merge_exclusion', 'merge_candidate', )
model = models.Organization
+
admin_site.register(models.Organization, OrganizationAdmin)
@@ -179,6 +199,7 @@ class PersonAdmin(HistorizedObjectAdmin):
form = make_ajax_form(models.Person, {'attached_to': 'organization'})
model = models.Person
+
admin_site.register(models.Person, PersonAdmin)
@@ -236,6 +257,7 @@ class AuthorAdmin(admin.ModelAdmin):
model = models.Author
form = make_ajax_form(models.Author, {'person': 'person'})
+
admin_site.register(models.Author, AuthorAdmin)
@@ -244,11 +266,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 +300,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 +343,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 +352,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 +367,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 +376,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 +386,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 +395,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 +465,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']
@@ -451,6 +575,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 985d2c3cf..36b0ef413 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:
@@ -261,8 +346,31 @@ class IshtarForm(forms.Form):
self.fields[k].widget.attrs['class'] = 'form-control'
+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(IshtarForm):
- TYPES = [] # (field_name, model, is_multiple) list
+ TYPES = [] # FieldType list
def __init__(self, *args, **kwargs):
"""
@@ -301,12 +409,12 @@ class ManageOldType(IshtarForm):
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..32c91b683 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):
@@ -733,6 +733,8 @@ class TownFormSet(FormSet):
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,7 +862,7 @@ class MergeOrganizationForm(MergeForm):
######################
# Sources management #
######################
-class SourceForm(ManageOldType, forms.Form):
+class SourceForm(CustomForm, ManageOldType, forms.Form):
form_label = _(u"Documentation informations")
file_upload = True
associated_models = {'source_type': models.SourceType}
@@ -899,10 +901,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 +987,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/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 08276fa9f..b0751f661 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -1631,6 +1631,80 @@ 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 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 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"),
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 db7a3640a..4ecddfc92 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())