diff options
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/admin.py | 127 | ||||
-rw-r--r-- | ishtar_common/forms.py | 126 | ||||
-rw-r--r-- | ishtar_common/forms_common.py | 15 | ||||
-rw-r--r-- | ishtar_common/migrations/0022_customform.py | 32 | ||||
-rw-r--r-- | ishtar_common/migrations/0023_excludedfield.py | 28 | ||||
-rw-r--r-- | ishtar_common/migrations/0024_custom_form_enabled.py | 24 | ||||
-rw-r--r-- | ishtar_common/models.py | 74 | ||||
-rw-r--r-- | ishtar_common/tests.py | 72 | ||||
-rw-r--r-- | ishtar_common/utils.py | 25 | ||||
-rw-r--r-- | ishtar_common/wizards.py | 55 |
10 files changed, 508 insertions, 70 deletions
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()) |