summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@peacefrogs.net>2012-10-21 01:31:49 +0200
committerÉtienne Loks <etienne.loks@peacefrogs.net>2012-10-21 01:31:49 +0200
commitea73bf44c5d527f407c89b35b22b21abf2f32617 (patch)
treeb0ce9167008a2ef608f522680f3d11a0459663e4
parent8d594a01220d5cbbe9d6e856d1aef04bbb8daf3e (diff)
downloadIshtar-ea73bf44c5d527f407c89b35b22b21abf2f32617.tar.bz2
Ishtar-ea73bf44c5d527f407c89b35b22b21abf2f32617.zip
Djangoization - Major refactoring (step 7)
Work on archaeological_files wizards
-rw-r--r--archaeological_context_records/models.py7
-rw-r--r--archaeological_context_records/wizards.py3
-rw-r--r--archaeological_files/models.py2
-rw-r--r--archaeological_files/views.py4
-rw-r--r--archaeological_files/wizards.py51
-rw-r--r--archaeological_finds/models.py8
-rw-r--r--archaeological_operations/forms.py1
-rw-r--r--archaeological_operations/models.py10
-rw-r--r--archaeological_operations/wizards.py12
-rw-r--r--ishtar_common/models.py15
-rw-r--r--ishtar_common/templates/ishtar/wizard/search.html (renamed from ishtar_common/templates/search.html)13
-rw-r--r--ishtar_common/urls.py2
-rw-r--r--ishtar_common/wizards.py878
13 files changed, 946 insertions, 60 deletions
diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py
index 75653e78e..64772627b 100644
--- a/archaeological_context_records/models.py
+++ b/archaeological_context_records/models.py
@@ -143,7 +143,7 @@ class ContextRecord(BaseHistorizedItem, OwnPerms):
return self.short_label()
def short_label(self):
- return JOINT.join([unicode(item) for item in [self.parcel,
+ return settings.JOINT.join([unicode(item) for item in [self.parcel,
self.label] if item])
def full_label(self):
@@ -154,13 +154,14 @@ class ContextRecord(BaseHistorizedItem, OwnPerms):
def _real_label(self):
if not self.parcel.operation.code_patriarche:
return
- return JOINT.join((self.parcel.operation.code_patriarche,
+ return settings.JOINT.join((self.parcel.operation.code_patriarche,
self.label))
def _temp_label(self):
if self.parcel.operation.code_patriarche:
return
- return JOINT.join([unicode(lbl) for lbl in [self.parcel.operation.year,
+ return settings.JOINT.join([unicode(lbl) for lbl in [
+ self.parcel.operation.year,
self.parcel.operation.operation_code,
self.label] if lbl])
diff --git a/archaeological_context_records/wizards.py b/archaeological_context_records/wizards.py
index b14272e16..1fd657bcc 100644
--- a/archaeological_context_records/wizards.py
+++ b/archaeological_context_records/wizards.py
@@ -73,7 +73,8 @@ class RecordWizard(Wizard):
else:
data = {}
if not step:
- step = self.determine_step(request, storage)
+ step = self.steps.current
+ #step = self.determine_step(request, storage)
form = self.get_form_list(request, storage)[step]
general_form_key = 'general-' + self.url_name
diff --git a/archaeological_files/models.py b/archaeological_files/models.py
index c3d950d3e..0bc9a9566 100644
--- a/archaeological_files/models.py
+++ b/archaeological_files/models.py
@@ -131,7 +131,7 @@ class File(BaseHistorizedItem, OwnPerms):
unicode(self.numeric_reference))))
items += [unicode(getattr(self, k))[:36]
for k in ['internal_reference',] if getattr(self, k)]
- return JOINT.join(items)
+ return settings.JOINT.join(items)
@classmethod
def get_query_owns(cls, user):
diff --git a/archaeological_files/views.py b/archaeological_files/views.py
index 0f71b24d8..84a42b95a 100644
--- a/archaeological_files/views.py
+++ b/archaeological_files/views.py
@@ -78,8 +78,8 @@ def dashboard_file(request, dct, obj_id=None, *args, **kwargs):
context_instance=RequestContext(request))
file_search_wizard = SearchWizard.as_view(
- [('general-file_search', FileFormSelection)],
- url_name='file_search',)
+ [('general-file_search', FileFormSelection)],
+ url_name='file_search',)
file_creation_wizard = FileWizard.as_view([
('general-file_creation', FileFormGeneral),
('address-file_creation', FileFormAddress),
diff --git a/archaeological_files/wizards.py b/archaeological_files/wizards.py
index b161deae0..e98248965 100644
--- a/archaeological_files/wizards.py
+++ b/archaeological_files/wizards.py
@@ -19,6 +19,7 @@
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
+from django.db.models import Max
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _
@@ -26,14 +27,15 @@ from django.utils.translation import ugettext_lazy as _
from ishtar_common.wizards import Wizard, ClosingWizard
from archaeological_operations.wizards import OperationAdministrativeActWizard,\
AdministrativeActDeletionWizard
-from archaeological_operations.models import AdministrativeAct
+from ishtar_common.models import Town
+from archaeological_operations.models import AdministrativeAct, Parcel
import models
class FileWizard(Wizard):
model = models.File
object_parcel_type = 'associated_file'
- def get_form(self, request, storage, step=None, data=None, files=None):
+ def get_form(self, step=None, data=None, files=None):
"""
Manage towns
"""
@@ -43,40 +45,40 @@ class FileWizard(Wizard):
data = {}
# manage the dynamic choice of towns
if not step:
- step = self.determine_step(request, storage)
- form = self.get_form_list(request, storage)[step]
+ # step = self.determine_step(request, storage)
+ step = self.steps.current
+ form = self.get_form_list()[step]
town_form_key = 'towns-' + self.url_name
if step.startswith('parcels-') and hasattr(form, 'management_form') \
- and self.session_has_key(request, storage, town_form_key):
+ and self.session_has_key(self.request, self.storage, town_form_key):
towns = []
- qdict = request.session[storage.prefix]['step_data'][town_form_key]
+ qdict = self.request.session[self.storage.prefix]['step_data']\
+ [town_form_key]
for k in qdict.keys():
if k.endswith("town") and qdict[k]:
try:
- town = Town.objects.get(pk=int(qdict[k]))
+ town = Town.objects.get(pk=int(qdict[k][0]))
towns.append((town.pk, unicode(town)))
except (ObjectDoesNotExist, ValueError):
pass
data['TOWNS'] = sorted(towns, key=lambda x:x[1])
- form = super(FileWizard, self).get_form(request, storage, step, data,
- files)
+ form = super(FileWizard, self).get_form(step, data, files)
return form
- def get_extra_model(self, dct, request, storage, form_list):
- dct = super(FileWizard, self).get_extra_model(dct, request, storage,
- form_list)
+ def get_extra_model(self, dct, form_list):
+ dct = super(FileWizard, self).get_extra_model(dct, form_list)
if not dct['numeric_reference']:
current_ref = models.File.objects.filter(year=dct['year']
).aggregate(Max('numeric_reference'))["numeric_reference__max"]
dct['numeric_reference'] = current_ref and current_ref + 1 or 1
return dct
- def done(self, request, storage, form_list, **kwargs):
+ def done(self, form_list, **kwargs):
'''
Save parcels
'''
- r = super(FileWizard, self).done(request, storage, form_list,
- return_object=True, **kwargs)
+ r = super(FileWizard, self).done(form_list, return_object=True,
+ **kwargs)
if type(r) not in (list, tuple) or len(r) != 2:
return r
obj, res = r
@@ -92,7 +94,7 @@ class FileWizard(Wizard):
dct = frm.cleaned_data.copy()
if 'parcel' in dct:
try:
- parcel = models.Parcel.objects.get(pk=dct['parcel'])
+ parcel = Parcel.objects.get(pk=dct['parcel'])
setattr(parcel, self.object_parcel_type, obj)
parcel.save()
except (ValueError, ObjectDoesNotExist):
@@ -106,10 +108,10 @@ class FileWizard(Wizard):
dct[self.object_parcel_type] = obj
if 'DELETE' in dct:
dct.pop('DELETE')
- parcel = models.Parcel.objects.filter(**dct).count()
+ parcel = Parcel.objects.filter(**dct).count()
if not parcel:
- dct['history_modifier'] = request.user
- parcel = models.Parcel(**dct)
+ dct['history_modifier'] = self.request.user
+ parcel = Parcel(**dct)
parcel.save()
return res
@@ -125,6 +127,7 @@ class FileClosingWizard(ClosingWizard):
if settings.COUNTRY == 'fr':
fields += ['saisine_type', 'reference_number']
fields += ['towns']
+
class FileDeletionWizard(FileClosingWizard):
def get_formated_datas(self, forms):
datas = super(FileDeletionWizard, self).get_formated_datas(forms)
@@ -135,14 +138,14 @@ class FileDeletionWizard(FileClosingWizard):
datas[-1][1].append(('', unicode(operation)))
return datas
- def done(self, request, storage, form_list, **kwargs):
- obj = self.get_current_object(request, storage)
+ def done(self, form_list, **kwargs):
+ obj = self.get_current_object()
for operation in models.Operation.objects.filter(
associated_file=obj).all():
operation.delete()
obj.delete()
return render_to_response('wizard_done.html', {},
- context_instance=RequestContext(request))
+ context_instance=RequestContext(self.request))
class FileAdministrativeActWizard(OperationAdministrativeActWizard):
@@ -151,5 +154,5 @@ class FileAdministrativeActWizard(OperationAdministrativeActWizard):
class FileEditAdministrativeActWizard(FileAdministrativeActWizard):
model = AdministrativeAct
edit = True
- def get_associated_item(self, request, storage, dct):
- return self.get_current_object(request, storage).associated_file
+ def get_associated_item(self, dct):
+ return self.get_current_object().associated_file
diff --git a/archaeological_finds/models.py b/archaeological_finds/models.py
index eeb293934..cb305a93a 100644
--- a/archaeological_finds/models.py
+++ b/archaeological_finds/models.py
@@ -79,14 +79,14 @@ class BaseFind(BaseHistorizedItem, OwnPerms):
finds += [ope.code_patriarche or \
(unicode(ope.year) + "-" + unicode(ope.operation_code))]
finds += [self.context_record.label, unicode(self.material_index)]
- return JOINT.join(finds)
+ return settings.JOINT.join(finds)
def _real_label(self):
if not self.context_record.parcel.operation.code_patriarche:
return
find = self.get_last_find()
lbl = find.label or self.label
- return JOINT.join([unicode(it) for it in (
+ return settings.JOINT.join([unicode(it) for it in (
self.context_record.parcel.operation.code_patriarche,
self.context_record.label,
lbl) if it])
@@ -96,7 +96,7 @@ class BaseFind(BaseHistorizedItem, OwnPerms):
return
find = self.get_last_find()
lbl = find.label or self.label
- return JOINT.join([unicode(it) for it in (
+ return settings.JOINT.join([unicode(it) for it in (
self.context_record.parcel.year,
self.index,
self.context_record.label,
@@ -278,5 +278,5 @@ class Property(LightHistorizedItem):
verbose_name_plural = _(u"Properties")
def __unicode__(self):
- return self.person + JOINT + self.find
+ return self.person + settings.JOINT + self.find
diff --git a/archaeological_operations/forms.py b/archaeological_operations/forms.py
index d87e72423..dc342f477 100644
--- a/archaeological_operations/forms.py
+++ b/archaeological_operations/forms.py
@@ -29,6 +29,7 @@ from django.template import RequestContext
from django.core import validators
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Max
+from django.forms.formsets import DELETION_FIELD_NAME
from django.utils.translation import ugettext_lazy as _
from ishtar_common.models import valid_id, PersonType, Person, Town
diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py
index 2c008ef9b..60de936d2 100644
--- a/archaeological_operations/models.py
+++ b/archaeological_operations/models.py
@@ -129,7 +129,7 @@ class Operation(BaseHistorizedItem, OwnPerms):
items[0] = unicode(self.towns.all()[0])
items.append("-".join((unicode(self.year),
unicode(self.operation_code))))
- return JOINT.join(items)
+ return settings.JOINT.join(items)
@classmethod
def get_available_operation_code(cls, year=None):
@@ -301,7 +301,7 @@ related_name='+', verbose_name=_(u"Person in charge of the scientific part"))
)
def __unicode__(self):
- return JOINT.join([unicode(item)
+ return settings.JOINT.join([unicode(item)
for item in [self.operation, self.associated_file, self.act_object]
if item])
@@ -322,7 +322,7 @@ class Parcel(LightHistorizedItem):
verbose_name_plural = _(u"Parcels")
def short_label(self):
- return JOINT.join([unicode(item) for item in [self.section,
+ return settings.JOINT.join([unicode(item) for item in [self.section,
self.parcel_number] if item])
def __unicode__(self):
@@ -332,7 +332,7 @@ class Parcel(LightHistorizedItem):
items = [unicode(self.operation or self.associated_file)]
items += [unicode(item) for item in [self.section, self.parcel_number]
if item]
- return JOINT.join(items)
+ return settings.JOINT.join(items)
class ParcelOwner(LightHistorizedItem):
owner = models.ForeignKey(Person, verbose_name=_(u"Owner"))
@@ -345,7 +345,7 @@ class ParcelOwner(LightHistorizedItem):
verbose_name_plural = _(u"Parcel owners")
def __unicode__(self):
- return self.owner + JOINT + self.parcel
+ return self.owner + settings.JOINT + self.parcel
class OperationDashboard:
def __init__(self):
diff --git a/archaeological_operations/wizards.py b/archaeological_operations/wizards.py
index df785fe6e..542d0118b 100644
--- a/archaeological_operations/wizards.py
+++ b/archaeological_operations/wizards.py
@@ -45,7 +45,8 @@ class OperationWizard(Wizard):
"""
context = super(OperationWizard, self).get_extra_context(request,
storage)
- step = self.determine_step(request, storage)
+ #step = self.determine_step(request, storage)
+ step = self.steps.current
if not step.startswith('towns-'):
return context
context['TOWNS'] = self.get_towns(request, storage)
@@ -79,7 +80,8 @@ class OperationWizard(Wizard):
else:
data = {}
if not step:
- step = self.determine_step(request, storage)
+ #step = self.determine_step(request, storage)
+ step = self.steps.current
form = self.get_form_list(request, storage)[step]
general_form_key = 'general-' + self.url_name
# manage the dynamic choice of towns
@@ -229,7 +231,9 @@ class AdministrativeActDeletionWizard(ClosingWizard):
context_instance=RequestContext(request))
def is_preventive(form_name, model, type_key='operation_type', key=''):
- def func(self, request, storage):
+ def func(self):
+ request = self.request
+ storage = self.storage
if storage.prefix not in request.session or \
'step_data' not in request.session[storage.prefix] or \
form_name not in request.session[storage.prefix]['step_data'] or\
@@ -238,7 +242,7 @@ def is_preventive(form_name, model, type_key='operation_type', key=''):
return False
try:
typ = int(request.session[storage.prefix]['step_data']\
- [form_name][form_name+'-'+type_key])
+ [form_name][form_name+'-'+type_key][0])
return model.is_preventive(typ, key)
except ValueError:
return False
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index 9b6f94907..3ca830aa3 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -38,8 +38,6 @@ from django.contrib import admin
from simple_history.models import HistoricalRecords as BaseHistoricalRecords
-JOINT = u" - "
-
def post_save_user(sender, **kwargs):
user = kwargs['instance']
ishtaruser = None
@@ -499,7 +497,7 @@ class Department(models.Model):
ordering = ['number']
def __unicode__(self):
- return unicode(self.number) + JOINT + self.label
+ return unicode(self.number) + settings.JOINT + self.label
class Address(BaseHistorizedItem):
address = models.TextField(_(u"Address"), null=True, blank=True)
@@ -574,11 +572,10 @@ class Person(Address, OwnPerms) :
def __unicode__(self):
lbl = u"%s %s" % (self.name, self.surname)
- lbl += JOINT
if self.attached_to:
- lbl += unicode(self.attached_to)
+ lbl += settings.JOINT + unicode(self.attached_to)
elif self.email:
- lbl += self.email
+ lbl += settings.JOINT + self.email
return lbl
def full_label(self):
@@ -624,7 +621,7 @@ class Author(models.Model):
verbose_name_plural = _(u"Authors")
def __unicode__(self):
- return unicode(self.person) + JOINT + unicode(self.author_type)
+ return unicode(self.person) + settings.JOINT + unicode(self.author_type)
class SourceType(GeneralType):
class Meta:
@@ -655,14 +652,14 @@ if settings.COUNTRY == 'fr':
department = models.ForeignKey(Department, verbose_name=u"Département")
def __unicode__(self):
- return JOINT.join((self.name, unicode(self.department)))
+ return settings.JOINT.join((self.name, unicode(self.department)))
class Canton(models.Model):
name = models.CharField(u"Nom", max_length=30)
arrondissement = models.ForeignKey(Arrondissement,
verbose_name=u"Arrondissement")
def __unicode__(self):
- return JOINT.join((self.name, unicode(self.arrondissement)))
+ return settings.JOINT.join((self.name, unicode(self.arrondissement)))
class Town(models.Model):
name = models.CharField(_(u"Name"), max_length=100)
diff --git a/ishtar_common/templates/search.html b/ishtar_common/templates/ishtar/wizard/search.html
index 1b5829820..7ed214a48 100644
--- a/ishtar_common/templates/search.html
+++ b/ishtar_common/templates/ishtar/wizard/search.html
@@ -2,26 +2,27 @@
{% load i18n %}
{% load range %}
{% block extra_head %}
-{{form.media}}
+{{wizard.form.media}}
{% endblock %}
{% block content %}
+<h2>{{wizard_label}}</h2>
<ul id='form_path'>
<li class='current'>&raquo;&nbsp;<a href='#'>{{form.form_label}}</a></li>
</ul>
<div class='form'>
-{% if form.forms %}
+{% if wizard.form.forms %}
<div class='top_button'><input type="submit" id="submit_form" value="{% trans "Validate" %}"/></div>
<table class='formset'>
- {%if form.non_form_errors%}<tr class='error'><th colspan='2'>{{form.non_form_errors}}</th></tr>{%endif%}
- {{ form.management_form }}
- {% for formsetform in form.forms %}
+ {%if wizard.form.non_form_errors%}<tr class='error'><th colspan='2'>{{wizard.form.non_form_errors}}</th></tr>{%endif%}
+ {{ wizard.form.management_form }}
+ {% for formsetform in wizard.form.forms %}
{{ formsetform.as_table }}
{% endfor %}
<tr class='modify'><td colspan="2"><button name="formset_modify" value="{{form_step}}">{% trans "Add/Modify" %}</button></td></tr></li>
</table>
{% else %}
<table>
- {{ form.as_table }}
+ {{ wizard.form.as_table }}
</table>
{% endif %}
</div>
diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py
index 40ef4a1d2..7f82ade2f 100644
--- a/ishtar_common/urls.py
+++ b/ishtar_common/urls.py
@@ -57,7 +57,7 @@ urlpatterns += patterns('ishtar_common.views',
'new_person', name='new-person'),
url(r'autocomplete-person/([0-9_]+)?$', 'autocomplete_person',
name='autocomplete-person'),
- url(r'autocomplete-town/$', 'autocomplete_town',
+ url(r'autocomplete-town/?$', 'autocomplete_town',
name='autocomplete-town'),
url(r'new-author/(?P<parent_name>.+)?/$',
'new_author', name='new-author'),
diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py
new file mode 100644
index 000000000..7895d98e3
--- /dev/null
+++ b/ishtar_common/wizards.py
@@ -0,0 +1,878 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010-2012 É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
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+import datetime
+
+from django.conf import settings
+from django.contrib.formtools.wizard.views import NamedUrlWizardView
+from django.core.exceptions import ObjectDoesNotExist
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
+
+import models
+
+class Wizard(NamedUrlWizardView):
+ model = None
+ label = ''
+ modification = None # True when the wizard modify an item
+ storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+ @staticmethod
+ def _check_right(step, condition=True):
+ '''Return a method to check the right for a specific step'''
+ """
+ def check_right(self, request, storage):
+ cond = condition
+ if callable(condition):
+ cond = condition(self, request, storage)
+ if not cond:
+ return False
+ person_type = request.user.ishtaruser.person.person_type
+ if person_type.txt_idx == 'administrator':
+ return True
+ if person_type.rights.filter(url_name=step).count():
+ return True"""
+ def check_right(self):
+ cond = condition
+ if callable(condition):
+ cond = condition(self)
+ if not cond:
+ return False
+ if not hasattr(self.request.user, 'ishtaruser'):
+ return False
+ person_type = self.request.user.ishtaruser.person.person_type
+ if person_type.txt_idx == 'administrator':
+ return True
+ if person_type.rights.filter(url_name=step).count():
+ return True
+ return check_right
+
+ def __init__(self, *args, **kwargs):
+ """Check right for each step of the wizard"""
+ print "2"
+ super(Wizard, self).__init__(*args, **kwargs)
+ for form_key in self.form_list.keys()[:-1]:
+ condition = True
+ if form_key in self.condition_dict:
+ condition = self.condition_dict.get(form_key, True)
+ cond = self._check_right(form_key, condition)
+ self.condition_dict[form_key] = cond
+ """
+ for form_key in self.form_list.keys()[:-1]:
+ condition = True
+ if form_key in self.condition_list:
+ condition = self.condition_list.get(form_key, True)
+ cond = self._check_right(form_key, condition)
+ self.condition_list[form_key] = cond"""
+
+ def get_prefix(self, *args, **kwargs):
+ """As the class name can interfere when reused prefix with the url_name
+ """
+ print "3"
+ return self.url_name + super(Wizard, self).get_prefix(*args,
+ **kwargs)
+
+ def get_wizard_name(self):
+ """As the class name can interfere when reused, use the url_name"""
+ print "4"
+ return self.url_name
+
+ def get_template_names(self):
+ print "5"
+ templates = ['ishtar/wizard/default_wizard.html']
+ current_step = self.steps.current
+ if current_step == self.steps.last:
+ templates = ['ishtar/wizard/confirm_wizard.html'] + templates
+ return templates
+
+ def get_context_data(self, form, **kwargs):
+ """Add previous, next and current steps to manage the wizard path"""
+ print "6"
+ context = super(Wizard, self).get_context_data(form)
+ self.request.session['CURRENT_ACTION'] = self.get_wizard_name()
+ step = self.steps.first
+ current_step = self.steps.current
+ context.update({'current_step':self.form_list[current_step],
+ 'wizard_label':self.label})
+ if step == current_step:
+ return context
+ previous_steps, next_steps, previous_step_counter = [], [], 0
+ while step:
+ if step == current_step \
+ or (previous_steps and
+ previous_steps[-1] == self.form_list[step]):
+ break
+ previous_steps.append(self.form_list[step])
+ previous_step_counter += 1
+ if previous_step_counter >= len(self.steps):
+ break
+ step = self.steps.all[previous_step_counter]
+ context.update({'previous_steps':previous_steps,
+ 'previous_step_counter':previous_step_counter})
+ storage = self.storage
+ # if modification: show the next steps
+ if self.modification:
+ next_step = step
+ while next_step:
+ # check if the form is initialized otherwise initialize it
+ if not storage.get_step_data(next_step):
+ values = self.get_form_initial(next_step)
+ prefixed_values = {}
+ if not isinstance(values, list):
+ for key in values:
+ form_key = next_step + '-' + key
+ prefixed_values[form_key] = values[key]
+ else:
+ for formset_idx, v in enumerate(values):
+ prefix = u"-%d-" % formset_idx
+ for key in v:
+ form_key = next_step + prefix + key
+ prefixed_values[form_key] = v[key]
+ storage.set_step_data(next_step, prefixed_values)
+ if step != next_step: # if not current step
+ next_steps.append(self.form_list[next_step])
+ next_step = self.get_next_step(next_step)
+ context.update({'next_steps':next_steps})
+ # not last step: validation
+ if current_step != self.steps.last:
+ return context
+ final_form_list = []
+ for form_key in self.get_form_list().keys():
+ form_obj = self.get_form(step=form_key,
+ data=self.storage.get_step_data(form_key),
+ files=self.storage.get_step_files(form_key))
+ form_obj.is_valid()
+ final_form_list.append(form_obj)
+ last_form = final_form_list[-1]
+ context.update({'datas':self.get_formated_datas(final_form_list)})
+ if hasattr(last_form, 'confirm_msg'):
+ context.update({'confirm_msg':last_form.confirm_msg})
+ if hasattr(last_form, 'confirm_end_msg'):
+ context.update({'confirm_end_msg':last_form.confirm_end_msg})
+ return context
+
+ def get_formated_datas(self, forms):
+ """Get the data to present in the last page"""
+ print "7"
+ datas = []
+ for form in forms:
+ form_datas = []
+ base_form = hasattr(form, 'forms') and form.forms[0] or form
+ associated_models = hasattr(base_form, 'associated_models') and \
+ base_form.associated_models or {}
+ if not hasattr(form, 'cleaned_data') and hasattr(form, 'forms'):
+ cleaned_datas = [frm.cleaned_data for frm in form.forms
+ if frm.is_valid()]
+ if not cleaned_datas:
+ continue
+ elif not hasattr(form, 'cleaned_data'):
+ continue
+ else:
+ cleaned_datas = type(form.cleaned_data) == list and \
+ form.cleaned_data \
+ or [form.cleaned_data]
+ for cleaned_data in cleaned_datas:
+ if not cleaned_data:
+ continue
+ if form_datas:
+ form_datas.append(("", "", "spacer"))
+ items = hasattr(base_form, 'fields') and \
+ base_form.fields.keyOrder or cleaned_data.keys()
+ for key in items:
+ lbl = None
+ if key.startswith('hidden_'):
+ continue
+ if hasattr(base_form, 'fields') and key in base_form.fields:
+ lbl = base_form.fields[key].label
+ if hasattr(base_form, 'associated_labels') \
+ and key in base_form.associated_labels:
+ lbl = base_form.associated_labels[key]
+ if not lbl:
+ continue
+ value = cleaned_data[key]
+ if not value and value != False:
+ continue
+ if type(value) == bool:
+ if value == True:
+ value = _(u"Yes")
+ elif value == False:
+ value = _(u"No")
+ elif key in associated_models:
+ values = []
+ if "," in unicode(value):
+ values = unicode(value).split(",")
+ else:
+ values = [value]
+ rendered_values = []
+ for val in values:
+ item = associated_models[key].objects.get(pk=val)
+ if hasattr(item, 'short_label'):
+ value = item.short_label()
+ else:
+ value = unicode(item)
+ rendered_values.append(value)
+ value = u" ; ".join(rendered_values)
+ form_datas.append((lbl, value, ''))
+ if form_datas:
+ datas.append((form.form_label, form_datas))
+ return datas
+
+ def get_extra_model(self, dct, form_list):
+ print "8"
+ dct['history_modifier'] = self.request.user
+ return dct
+
+ def done(self, form_list, return_object=False, **kwargs):
+ """Save to the model"""
+ print "9"
+ dct, m2m, whole_associated_models = {}, [], []
+ for form in form_list:
+ if not form.is_valid():
+ return self.render(form)
+ base_form = hasattr(form, 'forms') and form.forms[0] or form
+ associated_models = hasattr(base_form, 'associated_models') and \
+ base_form.associated_models or {}
+ if hasattr(form, 'forms'):
+ multi = False
+ if form.forms:
+ frm = form.forms[0]
+ if hasattr(frm, 'base_model') and frm.base_model:
+ whole_associated_models.append(frm.base_model)
+ else:
+ whole_associated_models += associated_models.keys()
+ fields = frm.fields.copy()
+ if 'DELETE' in fields:
+ fields.pop('DELETE')
+ multi = len(fields) > 1
+ if multi:
+ assert hasattr(frm, 'base_model'), \
+ u"Must define a base_model for " + unicode(frm.__class__)
+ for frm in form.forms:
+ if not frm.is_valid():
+ continue
+ vals = {}
+ if "DELETE" in frm.cleaned_data:
+ if frm.cleaned_data["DELETE"]:
+ continue
+ frm.cleaned_data.pop('DELETE')
+ for key in frm.cleaned_data:
+ value = frm.cleaned_data[key]
+ if not value and value != False:
+ continue
+ if key in associated_models:
+ value = associated_models[key].objects.get(pk=value)
+ if multi:
+ vals[key] = value
+ else:
+ m2m.append((key, value))
+ if multi and vals:
+ m2m.append((frm.base_model, vals))
+ elif type(form.cleaned_data) == dict:
+ for key in form.cleaned_data:
+ if key.startswith('hidden_'):
+ continue
+ value = form.cleaned_data[key]
+ if key in associated_models:
+ if value:
+ model = associated_models[key]
+ if isinstance(value, unicode) \
+ or isinstance(value, str) and "," in value:
+ value = value.split(",")
+ if isinstance(value, list) \
+ or isinstance(value, tuple):
+ value = [model.objects.get(pk=val)
+ for val in value if val]
+ if len(value) == 1:
+ value = value[0]
+ else:
+ value = model.objects.get(pk=value)
+ else:
+ value = None
+ dct[key] = value
+ return self.save_model(dct, m2m, whole_associated_models, form_list,
+ return_object)
+
+ def get_saved_model(self):
+ """Permit a distinguo when saved model is not the base selected model"""
+ print "10"
+ return self.model
+
+ def get_current_saved_object(self):
+ """Permit a distinguo when saved model is not the base selected model"""
+ print "11"
+ return self.get_current_object()
+
+ def save_model(self, dct, m2m, whole_associated_models, form_list,
+ return_object):
+ print "12"
+ dct = self.get_extra_model(dct, form_list)
+ obj = self.get_current_saved_object()
+ # manage dependant items
+ other_objs = {}
+ for k in dct.keys():
+ if '__' not in k:
+ continue
+ vals = k.split('__')
+ assert len(vals) == 2, "Only one level of dependant item is managed"
+ dependant_item, key = vals
+ if dependant_item not in other_objs:
+ other_objs[dependant_item] = {}
+ other_objs[dependant_item][key] = dct.pop(k)
+ if obj:
+ for k in dct:
+ if k.startswith('pk'):
+ continue
+ setattr(obj, k, dct[k])
+ try:
+ obj.full_clean()
+ except forms.ValidationError, msg:
+ return self.render(form_list[-1])
+ for dependant_item in other_objs:
+ c_item = getattr(obj, dependant_item)
+ # manage ManyToMany if only one associated
+ if hasattr(c_item, "all"):
+ c_items = c_item.all()
+ if len(c_items) != 1:
+ continue
+ c_item = c_items[0]
+ if c_item:
+ # to check #
+ for k in other_objs[dependant_item]:
+ setattr(c_item, k, other_objs[dependant_item][k])
+ c_item.save()
+ else:
+ m = getattr(self.model, dependant_item)
+ if hasattr(m, 'related'):
+ c_item = m.related.model(**other_objs[dependant_item])
+ setattr(obj, dependant_item, c_item)
+ obj.save()
+ obj.save()
+ else:
+ adds = {}
+ for dependant_item in other_objs:
+ m = getattr(self.model, dependant_item)
+ model = m.field.rel.to
+ c_dct = other_objs[dependant_item].copy()
+ if issubclass(model, models.BaseHistorizedItem):
+ c_dct['history_modifier'] = self.request.user
+ c_item = model(**c_dct)
+ c_item.save()
+ if hasattr(m, 'through'):
+ adds[dependant_item] = c_item
+ elif hasattr(m, 'field'):
+ dct[dependant_item] = c_item
+ if 'pk' in dct:
+ dct.pop('pk')
+ obj = self.get_saved_model()(**dct)
+ try:
+ obj.full_clean()
+ except forms.ValidationError, msg:
+ return self.render(form_list[-1])
+ obj.save()
+ for k in adds:
+ getattr(obj, k).add(adds[k])
+ # necessary to manage interaction between models like
+ # material_index management for baseitems
+ obj.save()
+ m2m_items = {}
+ for model in whole_associated_models:
+ getattr(obj, model+'s').clear()
+ for key, value in m2m:
+ if key not in m2m_items:
+ if type(key) == dict:
+ vals = []
+ for item in getattr(obj, key+'s').all():
+ v = {}
+ for k in value.keys():
+ v[k] = getattr(item, k)
+ vals.append(v)
+ m2m_items[key] = vals
+ else:
+ m2m_items[key] = getattr(obj, key+'s').all()
+ if value not in m2m_items[key]:
+ if type(value) == dict:
+ model = getattr(obj, key+'s').model
+ if issubclass(model, models.BaseHistorizedItem):
+ value['history_modifier'] = self.request.user
+ value = model.objects.create(**value)
+ value.save()
+ getattr(obj, key+'s').add(value)
+ # necessary to manage interaction between models like
+ # material_index management for baseitems
+ obj.save()
+ res = render_to_response('wizard_done.html', {},
+ context_instance=RequestContext(self.request))
+ return return_object and (obj, res) or res
+
+ def get_deleted(self, keys):
+ """Get the deleted and non-deleted items in formsets"""
+ print "13"
+ not_to_delete, to_delete = set(), set()
+ for key in keys:
+ items = key.split('-')
+ if len(items) < 2 or items[-2] in to_delete:
+ continue
+ idx = items[-2]
+ try:
+ int(idx)
+ except:
+ continue
+ if items[-1] == u'DELETE':
+ to_delete.add(idx)
+ if idx in not_to_delete:
+ not_to_delete.remove(idx)
+ elif idx not in not_to_delete:
+ not_to_delete.add(idx)
+ return (to_delete, not_to_delete)
+
+ def get_form(self, step=None, data=None, files=None):
+ """Manage formset"""
+ print "14"
+ request = self.request
+ storage = self.storage
+ if data:
+ data = data.copy()
+ if not step:
+ step = self.steps.current
+ form = self.get_form_list()[step]
+ if hasattr(form, 'management_form'):
+ # manage deletion
+ to_delete, not_to_delete = self.get_deleted(data.keys())
+ # raz deleted fields
+ for key in data.keys():
+ items = key.split('-')
+ if len(items) < 2 or items[-2] not in to_delete:
+ continue
+ data.pop(key)
+ if to_delete:
+ # reorganize
+ for idx, number in enumerate(sorted(not_to_delete)):
+ idx = unicode(idx)
+ if idx == number:
+ continue
+ for key in data.keys():
+ items = key.split('-')
+ if len(items) > 2 and number == items[-2]:
+ items[-2] = unicode(idx)
+ k = u'-'.join(items)
+ data[k] = data.pop(key)[0]
+ # get a form key
+ base_key = form.form.base_fields.keys()[0]
+ init = self.get_form_initial(step)
+ total_field = len([key for key in data.keys()
+ if base_key in key.split('-')
+ and data[key]])
+ if init and not to_delete:
+ total_field = max((total_field, len(init)))
+ data[step + u'-INITIAL_FORMS'] = unicode(total_field)
+ data[step + u'-TOTAL_FORMS'] = unicode(total_field + 1)
+ data = data or None
+ form = super(Wizard, self).get_form(step, data, files)
+ return form
+
+ def render_next_step(self, form, **kwargs):
+ """
+ Manage:
+ - modify or delete button in formset: next step = current step
+ - validate and end: nextstep = last step
+ """
+ print "15"
+ request = self.request
+ if request.POST.has_key('formset_modify') \
+ and request.POST['formset_modify'] \
+ or [key for key in request.POST.keys()
+ if key.endswith('DELETE') and request.POST[key]]:
+ return self.render(form)
+ elif request.POST.has_key('validate_and_end') \
+ and request.POST['validate_and_end']:
+ last_step = self.steps.last
+ new_form = self.get_form(last_step,
+ data=self.storage.get_step_data(last_step),
+ files=self.storage.get_step_files(last_step))
+ self.storage.current_step = last_step
+ return self.render(new_form)
+ return super(Wizard, self).render_next_step(form, **kwargs)
+
+ def post(self, *args, **kwargs):
+ """Convert numerical step number to step name"""
+ print "16"
+ request = self.request
+ post_data = request.POST.copy()
+ if request.POST.has_key('form_prev_step'):
+ try:
+ step_number = int(request.POST['form_prev_step'])
+ post_data['wizard_goto_step'] = self.get_form_list().keys(
+ )[step_number]
+ except ValueError:
+ pass
+ request.POST = post_data
+ return super(Wizard, self).post(*args, **kwargs)
+
+ @classmethod
+ def session_has_key(cls, request, storage, form_key, key=None, multi=None):
+ """Check if the session has value of a specific form and (if provided)
+ of a key
+ """
+ print "17"
+ test = storage.prefix in request.session \
+ and 'step_data' in request.session[storage.prefix] \
+ and form_key in request.session[storage.prefix]['step_data']
+ if not key or not test:
+ return test
+ key = key.startswith(form_key) and key or \
+ not multi and form_key + '-' + key or \
+ form_key + '-0-' + key #only check if the first field is available
+ return key in request.session[storage.prefix]['step_data'][form_key]
+
+ @classmethod
+ def session_get_value(cls, request, storage, form_key, key, multi=False):
+ """Get the value of a specific form"""
+ print "18"
+ if not cls.session_has_key(request, storage, form_key, key, multi):
+ return
+ if not multi:
+ key = key.startswith(form_key) and key or form_key + '-' + key
+ return request.session[storage.prefix]['step_data'][form_key][key]
+ vals = []
+ for k in request.session[storage.prefix]['step_data'][form_key]:
+ if k.startswith(form_key) and k.endswith(key) and \
+ request.session[storage.prefix]['step_data'][form_key][k]:
+ vals.append(request.session[storage.prefix]['step_data']\
+ [form_key][k])
+ return vals
+
+ def get_current_object(self):
+ """Get the current object for an instancied wizard"""
+ print "19"
+ current_obj = None
+ main_form_key = 'selec-' + self.url_name
+ try:
+ idx = self.session_get_value(self.request, self.storage,
+ main_form_key, 'pk')
+ if type(idx) in (tuple, list):
+ idx = idx[0]
+ idx = int(idx)
+ current_obj = self.model.objects.get(pk=idx)
+ except(TypeError, ValueError, ObjectDoesNotExist):
+ pass
+ return current_obj
+
+ def get_form_initial(self, step):
+ print "20"
+ current_obj = self.get_current_object()
+ current_step = self.steps.current
+ request = self.request
+ if step.startswith('selec-') and step in self.form_list \
+ and 'pk' in self.form_list[step].associated_models:
+ model_name = self.form_list[step].associated_models['pk'
+ ].__name__.lower()
+ if step == current_step:
+ #self.reset_wizard(request, storage)
+ self.storage.reset()
+ val = model_name in request.session and request.session[model_name]
+ if val:
+ return {'pk':val}
+ elif current_obj:
+ return self.get_instanced_init(current_obj, step)
+ current_form = self.form_list[current_step]
+ if hasattr(current_form, 'currents'):
+ initial = {}
+ for key in current_form.currents:
+ model_name = current_form.currents[key].__name__.lower()
+ val = model_name in request.session and \
+ request.session[model_name]
+ if val:
+ initial[key] = val
+ if initial:
+ return initial
+ return super(Wizard, self).get_form_initial(step)
+
+ def get_instanced_init(self, obj, step=None):
+ """Get initial data from an init"""
+ print "21"
+ current_step = step or self.steps.current
+ c_form = self.form_list[current_step]
+ # make the current object the default item for the session
+ obj_name = obj.__class__.__name__.lower()
+ # prefer a specialized name if available
+ prefixes = self.storage.prefix.split('_')
+ if len(prefixes) > 1 and prefixes[-2].startswith(obj_name):
+ obj_name = prefixes[-2]
+ self.request.session[obj_name] = unicode(obj.pk)
+ initial = {}
+ if self.request.POST or \
+ (step in self.request.session[self.storage.prefix] and\
+ self.request.session[self.storage.prefix]['step_data'][step]):
+ return {}
+ if hasattr(c_form, 'base_fields'):
+ for base_field in c_form.base_fields.keys():
+ fields = base_field.split('__')
+ value = obj
+ for field in fields:
+ if not hasattr(value, field) or \
+ getattr(value, field) == None:
+ value = obj
+ break
+ value = getattr(value, field)
+ if value == obj:
+ continue
+ if hasattr(value, 'pk'):
+ value = value.pk
+ if value in (True, False):
+ initial[base_field] = value
+ elif value != None:
+ initial[base_field] = unicode(value)
+ elif hasattr(c_form, 'management_form'):
+ initial = []
+ if hasattr(c_form.form, 'base_model'):
+ key = c_form.form.base_model + 's'
+ else:
+ key = current_step.split('-')[0]
+ if not hasattr(obj, key):
+ return initial
+ keys = c_form.form.base_fields.keys()
+ for child_obj in getattr(obj, key).order_by('pk').all():
+ if not keys:
+ break
+ vals = {}
+ if len(keys) == 1:
+ # only one field: must be the id of the object
+ vals[keys[0]] = unicode(child_obj.pk)
+ else:
+ for field in keys:
+ if hasattr(child_obj, field):
+ value = getattr(child_obj, field)
+ if hasattr(value, 'pk'):
+ value = value.pk
+ if value != None:
+ vals[field] = unicode(value)
+ if vals:
+ initial.append(vals)
+ return initial
+
+class SearchWizard(NamedUrlWizardView):
+ model = None
+ label = ''
+ modification = None # True when the wizard modify an item
+ storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+ def get_wizard_name(self):
+ """
+ As the class name can interfere when reused, use the url_name
+ """
+ return self.url_name
+
+ def get_prefix(self, *args, **kwargs):
+ """As the class name can interfere when reused prefix with the url_name
+ """
+ return self.url_name + super(SearchWizard, self).get_prefix(*args,
+ **kwargs)
+
+ def get_template_names(self):
+ templates = ['ishtar/wizard/search.html']
+ return templates
+
+class DeletionWizard(Wizard):
+ def get_formated_datas(self, forms):
+ datas = super(DeletionWizard, self).get_formated_datas(forms)
+ self.current_obj = None
+ for form in forms:
+ if not hasattr(form, "cleaned_data"):
+ continue
+ for key in form.cleaned_data:
+ if key == 'pk':
+ model = form.associated_models['pk']
+ self.current_obj = model.objects.get(pk=form.cleaned_data['pk'])
+ if not self.current_obj:
+ return datas
+ res = {}
+ for field in self.model._meta.fields + self.model._meta.many_to_many:
+ if field.name not in self.fields:
+ continue
+ value = getattr(self.current_obj, field.name)
+ if not value:
+ continue
+ if hasattr(value, 'all'):
+ value = ", ".join([unicode(item) for item in value.all()])
+ if not value:
+ continue
+ else:
+ value = unicode(value)
+ res[field.name] = (field.verbose_name, value, '')
+ if not datas and self.fields:
+ datas = [['', []]]
+ for field in self.fields:
+ if field in res:
+ datas[0][1].append(res[field])
+ return datas
+
+ def done(self, request, storage, form_list, **kwargs):
+ obj = self.get_current_object(request, storage)
+ obj.delete()
+ return render_to_response('wizard_delete_done.html', {},
+ context_instance=RequestContext(request))
+
+class ClosingWizard(Wizard):
+ # "close" an item
+ # to be define in the overloaded class
+ model = None
+ fields = []
+
+ def get_formated_datas(self, forms):
+ datas = super(ClosingWizard, self).get_formated_datas(forms)
+ self.current_obj = None
+ for form in forms:
+ if not hasattr(form, "cleaned_data"):
+ continue
+ for key in form.cleaned_data:
+ if key == 'pk':
+ model = form.associated_models['pk']
+ self.current_obj = model.objects.get(
+ pk=form.cleaned_data['pk'])
+ if not self.current_obj:
+ return datas
+ res = {}
+ for field in self.model._meta.fields + self.model._meta.many_to_many:
+ if field.name not in self.fields:
+ continue
+ value = getattr(self.current_obj, field.name)
+ if not value:
+ continue
+ if hasattr(value, 'all'):
+ value = ", ".join([unicode(item) for item in value.all()])
+ if not value:
+ continue
+ else:
+ value = unicode(value)
+ res[field.name] = (field.verbose_name, value, '')
+ if not datas and self.fields:
+ datas = [['', []]]
+ for field in self.fields:
+ if field in res:
+ datas[0][1].append(res[field])
+ return datas
+
+ def done(self, request, storage, form_list, **kwargs):
+ obj = self.get_current_object(request, storage)
+ for form in form_list:
+ if form.is_valid():
+ if 'end_date' in form.cleaned_data and hasattr(obj, 'end_date'):
+ obj.end_date = form.cleaned_data['end_date']
+ obj.save()
+ return render_to_response('wizard_closing_done.html', {},
+ context_instance=RequestContext(request))
+
+class PersonWizard(Wizard):
+ model = models.Person
+
+class PersonModifWizard(PersonWizard):
+ modification = True
+
+class AccountWizard(Wizard):
+ model = models.Person
+ def get_formated_datas(self, forms):
+ datas = super(AccountWizard, self).get_formated_datas(forms)
+ for form in forms:
+ if not hasattr(form, "cleaned_data"):
+ continue
+ for key in form.cleaned_data:
+ if key == 'hidden_password' and form.cleaned_data[key]:
+ datas[-1][1].append((_("New password"), "*"*8))
+ return datas
+
+ def done(self, form_list, **kwargs):
+ """
+ Save the account
+ """
+ dct = {}
+ for form in form_list:
+ if not form.is_valid():
+ return self.render(form)
+ associated_models = hasattr(form, 'associated_models') and \
+ form.associated_models or {}
+ if type(form.cleaned_data) == dict:
+ for key in form.cleaned_data:
+ if key == 'pk':
+ continue
+ value = form.cleaned_data[key]
+ if key in associated_models and value:
+ value = associated_models[key].objects.get(pk=value)
+ dct[key] = value
+ person = self.get_current_object()
+ if not person:
+ return self.render(form)
+ for key in dct.keys():
+ if key.startswith('hidden_password'):
+ dct['password'] = dct.pop(key)
+ try:
+ account = models.IshtarUser.objects.get(person=person)
+ account.username = dct['username']
+ account.email = dct['email']
+ except ObjectDoesNotExist:
+ now = datetime.datetime.now()
+ account = models.IshtarUser(person=person, username=dct['username'],
+ email=dct['email'], first_name=person.surname,
+ last_name=person.name, is_staff=False, is_active=True,
+ is_superuser=False, last_login=now, date_joined=now)
+ if dct['password']:
+ account.set_password(dct['password'])
+ account.save()
+
+ if 'send_password' in dct and dct['send_password'] and \
+ settings.ADMINS:
+ site = Site.objects.get_current()
+
+ app_name = site and ("Ishtar - " + site.name) \
+ or "Ishtar"
+ context = Context({'login':dct['username'],
+ 'password':dct['password'],
+ 'app_name':app_name,
+ 'site': site and site.domain or ""
+ })
+ t = loader.get_template('account_activation_email.txt')
+ msg = t.render(context)
+ subject = _(u"[%(app_name)s] Account creation/modification") % {
+ "app_name":app_name}
+ send_mail(subject, msg, settings.ADMINS[0][1],
+ [dct['email']], fail_silently=True)
+ res = render_to_response('wizard_done.html', {},
+ context_instance=RequestContext(self.request))
+ return res
+
+ def get_form(self, step=None, data=None, files=None):
+ """
+ Display the "Send email" field if necessary
+ """
+ form = super(AccountWizard, self).get_form(step, data, files)
+ if not hasattr(form, 'is_hidden'):
+ return form
+ if self.session_get_value(self.request, self.storage,
+ 'account-account_management', 'hidden_password'):
+ form.is_hidden = False
+ return form
+
+class SourceWizard(Wizard):
+ model = None
+ def get_extra_model(self, dct, request, storage, form_list):
+ dct = super(SourceWizard, self).get_extra_model(dct, request, storage,
+ form_list)
+ if 'history_modifier' in dct:
+ dct.pop('history_modifier')
+ return dct