From ddcbfb661def13467815da18d90ace9a3c30197a Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 17 Nov 2017 12:36:22 +0100 Subject: Custom forms: model --- ishtar_common/migrations/0022_customform.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 ishtar_common/migrations/0022_customform.py (limited to 'ishtar_common/migrations') 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', + }, + ), + ] -- cgit v1.2.3 From 986725aacb88131471ffe61a3a2e32c57408a05b Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 17 Nov 2017 17:50:44 +0100 Subject: Custom forms: exclude field model --- ishtar_common/migrations/0023_excludedfield.py | 28 ++++++++++++++++++++++++++ ishtar_common/models.py | 9 +++++++++ 2 files changed, 37 insertions(+) create mode 100644 ishtar_common/migrations/0023_excludedfield.py (limited to 'ishtar_common/migrations') 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/models.py b/ishtar_common/models.py index 1562280ef..a5a3d96c1 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1648,6 +1648,15 @@ class CustomForm(models.Model): ordering = ['name', '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"Custom form - excluded field") + verbose_name_plural = _(u"Custom form - excluded fields") + + class GlobalVar(models.Model, Cached): slug = models.SlugField(_(u"Variable name"), unique=True) description = models.TextField(_(u"Description of the variable"), -- cgit v1.2.3 From c410be9b3c3ba193ee8c233cc6a50d065d4090fd Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Tue, 21 Nov 2017 10:46:58 +0100 Subject: Custom forms: disable completly a form --- archaeological_operations/forms.py | 7 ++- archaeological_operations/tests.py | 23 +++++++++ archaeological_operations/wizards.py | 6 ++- ishtar_common/admin.py | 4 +- ishtar_common/forms.py | 21 +++++++-- .../migrations/0024_custom_form_enabled.py | 24 ++++++++++ ishtar_common/models.py | 4 ++ ishtar_common/utils.py | 19 ++++++++ ishtar_common/wizards.py | 55 +++++++++++++--------- 9 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 ishtar_common/migrations/0024_custom_form_enabled.py (limited to 'ishtar_common/migrations') diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py index 61be371d1..47fe746f5 100644 --- a/archaeological_operations/forms.py +++ b/archaeological_operations/forms.py @@ -997,8 +997,10 @@ OperationFormModifGeneral.associated_models = \ OperationFormModifGeneral.associated_models['associated_file'] = File -class CollaboratorForm(forms.Form): +class CollaboratorForm(CustomForm, forms.Form): form_label = _(u"Collaborators") + form_admin_name = _(u"Operation - Collaborators") + form_slug = "operation-collaborators" base_models = ['collaborator'] associated_models = {'collaborator': Person, } collaborator = widgets.Select2MultipleField( @@ -1006,7 +1008,8 @@ 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): diff --git a/archaeological_operations/tests.py b/archaeological_operations/tests.py index 9f07aff45..af6199774 100644 --- a/archaeological_operations/tests.py +++ b/archaeological_operations/tests.py @@ -1221,6 +1221,29 @@ class CustomFormTest(TestCase, OperationInitTest): 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/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 7c21fa4be..189a02c05 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -519,9 +519,9 @@ class ExcludeFieldInline(admin.TabularInline): class CustomFormAdmin(admin.ModelAdmin): - list_display = ['name', 'form', 'available', 'apply_to_all', + list_display = ['name', 'form', 'available', 'enabled', 'apply_to_all', 'users_lbl', 'user_types_lbl'] - fields = ('name', 'form', 'available', 'apply_to_all', 'users', + fields = ('name', 'form', 'available', 'enabled', 'apply_to_all', 'users', 'user_types') form = CustomFormForm inlines = [ExcludeFieldInline] diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 683726e67..da6a1c051 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 @@ -311,7 +311,17 @@ class CustomForm(object): except AttributeError: pass super(CustomForm, self).__init__(*args, **kwargs) - base_q = {"form": self.form_slug, 'available': True} + available, excluded = self.check_availability_and_excluded_fields( + current_user) + for exc in excluded: + if exc in self.fields: + self.fields.pop(exc) + + @classmethod + def check_availability_and_excluded_fields(cls, current_user): + if not current_user: + return True, [] + base_q = {"form": cls.form_slug, 'available': True} # order is important : try for user, user type then all query_dicts = [] if current_user: @@ -325,17 +335,20 @@ class CustomForm(object): 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 - if excluded.field in self.fields: - self.fields.pop(excluded.field) + excluded_lst.append(excluded.field) break + return True, excluded_lst @classmethod def get_custom_fields(cls): 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 c888e87fd..b0751f661 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1635,6 +1635,10 @@ 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 " diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index ae178a752..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,6 +48,24 @@ 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 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()) -- cgit v1.2.3