1088 lines
43 KiB
Plaintext
1088 lines
43 KiB
Plaintext
# w.c.s. - web application for online forms
|
|
# Copyright (C) 2005-2010 Entr'ouvert
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
try:
|
|
import elementtree.ElementTree as ET
|
|
except ImportError:
|
|
try:
|
|
import xml.etree.ElementTree as ET
|
|
except ImportError:
|
|
ET = None
|
|
|
|
try:
|
|
import M2Crypto
|
|
except ImportError:
|
|
M2Crypto = None
|
|
|
|
import tarfile
|
|
import time
|
|
from cStringIO import StringIO
|
|
|
|
from quixote import get_response, redirect
|
|
from quixote.directory import Directory
|
|
|
|
from qommon import misc
|
|
from qommon.errors import *
|
|
from qommon.form import *
|
|
from qommon.admin.menu import html_top, command_icon, error_page
|
|
from qommon import get_logger
|
|
|
|
from qommon import tokens
|
|
from qommon.afterjobs import AfterJob
|
|
from qommon import emails
|
|
|
|
from wcs.formdata import FormData, Evolution
|
|
from wcs.formdef import FormDef, FormField
|
|
from wcs.categories import Category
|
|
from wcs.roles import Role, logged_users_role, get_user_roles
|
|
from wcs.workflows import Workflow
|
|
|
|
from fields import FieldWidget, FieldDefPage, FieldsDirectory
|
|
|
|
def indent(elem, level=0):
|
|
# in-place prettyprint formatter
|
|
# http://effbot.org/zone/element-lib.htm#prettyprint
|
|
i = "\n" + level*" "
|
|
if len(elem):
|
|
if not elem.text or not elem.text.strip():
|
|
elem.text = i + " "
|
|
for elem in elem:
|
|
indent(elem, level+1)
|
|
if not elem.tail or not elem.tail.strip():
|
|
elem.tail = i
|
|
else:
|
|
if level and (not elem.tail or not elem.tail.strip()):
|
|
elem.tail = i
|
|
|
|
|
|
|
|
def get_categories():
|
|
t = sorted([(misc.simplify(x.name), x.id, x.name) for x in Category.select()])
|
|
return [x[1:] for x in t]
|
|
|
|
def get_workflows(condition=lambda x: True):
|
|
t = sorted([(misc.simplify(x.name), x.id, x.name) for x in Workflow.select() if condition(x)])
|
|
return [x[1:] for x in t]
|
|
|
|
|
|
class FormDefUI:
|
|
def __init__(self, formdef):
|
|
self.formdef = formdef
|
|
|
|
def new_form_ui(self):
|
|
form = Form(enctype='multipart/form-data')
|
|
if self.formdef:
|
|
formdef = self.formdef
|
|
else:
|
|
formdef = FormDef()
|
|
form.add(StringWidget, 'name', title = _('Form Title'), required=True, size=40,
|
|
value = formdef.name)
|
|
categories = get_categories()
|
|
if categories:
|
|
form.add(SingleSelectWidget, 'category_id', title = _('Category'),
|
|
value = formdef.category_id,
|
|
options = [(None, '---')] + categories)
|
|
workflows = get_workflows()
|
|
if workflows:
|
|
form.add(SingleSelectWidget, 'workflow_id', title = _('Workflow'),
|
|
value = formdef.workflow_id,
|
|
options = [(None, _('Default Workflow'))] + workflows)
|
|
form.add(SingleSelectWidget, 'receiver_id', title = _('Recipient Role'), required = True,
|
|
value = formdef.receiver_id,
|
|
options = get_user_roles())
|
|
form.add(WidgetList, 'roles', title = _('Sender Roles'), element_type = SingleSelectWidget,
|
|
hint = _('Only show this form to the given roles.'),
|
|
value = formdef.roles,
|
|
add_element_label = _('Add Role'),
|
|
element_kwargs = {'render_br': False,
|
|
'options': [('', '---'),
|
|
(logged_users_role().id, logged_users_role().name)] + get_user_roles()})
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
return form
|
|
|
|
def submit_form(self, form):
|
|
if self.formdef:
|
|
formdef = self.formdef
|
|
else:
|
|
formdef = FormDef()
|
|
|
|
name = form.get_widget('name').parse()
|
|
formdefs_name = [x.name for x in FormDef.select(ignore_errors=True) if x.id != formdef.id]
|
|
if name in formdefs_name:
|
|
form.get_widget('name').set_error(_('This name is already used'))
|
|
raise ValueError()
|
|
|
|
for f in ('name', 'confirmation', 'signing', 'acl_read',
|
|
'only_allow_one', 'receiver_id', 'category_id', 'disabled',
|
|
'allow_drafts', 'workflow_id', 'private_status_and_history',
|
|
'disabled_redirection', 'always_advertise',
|
|
'publication_date', 'expiration_date'):
|
|
widget = form.get_widget(f)
|
|
if widget:
|
|
setattr(formdef, f, widget.parse())
|
|
|
|
roles = form.get_widget('roles').parse()
|
|
if roles:
|
|
formdef.roles = [x for x in roles if x]
|
|
else:
|
|
formdef.roles = []
|
|
|
|
def lax_int(s):
|
|
try:
|
|
return int(s)
|
|
except ValueError:
|
|
return -1
|
|
|
|
if not formdef.fields:
|
|
formdef.fields = []
|
|
if form.get_widget('fields'):
|
|
new_fields = []
|
|
for field in form.get_widget('fields').parse():
|
|
if not field['label']:
|
|
if field['id']:
|
|
formdef.fields = [x for x in formdef.fields if x.id != field['id']]
|
|
continue
|
|
if field['id']:
|
|
form_field = [x for x in formdef.fields if x.id == field['id']][0]
|
|
else:
|
|
form_field = FormField()
|
|
if formdef.fields or new_fields:
|
|
form_field.id = str(max([
|
|
lax_int(x.id) for x in formdef.fields + new_fields]) + 1)
|
|
else:
|
|
form_field.id = '1'
|
|
form_field.label = field['label']
|
|
form_field.type = field['type']
|
|
new_fields.append(form_field)
|
|
|
|
formdef.fields = new_fields
|
|
|
|
formdef.store()
|
|
|
|
return formdef
|
|
|
|
|
|
class FieldDefPage(FieldDefPage):
|
|
section = 'forms'
|
|
wsf_support = True
|
|
|
|
|
|
class FieldsDirectory(FieldsDirectory):
|
|
section = 'forms'
|
|
field_def_page_class = FieldDefPage
|
|
|
|
def index_bottom [html] (self):
|
|
if hasattr(self.objectdef, str('disabled')) and self.objectdef.disabled:
|
|
'<p>'
|
|
_('This form is currently disabled.')
|
|
if hasattr(self.objectdef, str('disabled_redirection')) \
|
|
and self.objectdef.disabled_redirection:
|
|
' (<a href="%s">' % self.objectdef.disabled_redirection
|
|
_('redirection')
|
|
'</a>)'
|
|
' <a href="../enable?back=fields">%s</a>' % _('Enable')
|
|
'</p>'
|
|
|
|
class FormDefPage(Directory):
|
|
_q_exports = ['', 'fields', 'delete', 'duplicate', 'export',
|
|
'archive', 'invite', 'enable', 'workflow', 'category',
|
|
'recipient', ('workflow-options', 'workflow_options'),
|
|
('workflow-status-remapping', 'workflow_status_remapping'),
|
|
'roles', 'title', 'options', ('acl-read', 'acl_read')]
|
|
|
|
def __init__(self, component):
|
|
try:
|
|
self.formdef = FormDef.get(component)
|
|
except KeyError:
|
|
raise TraversalError()
|
|
self.formdefui = FormDefUI(self.formdef)
|
|
get_response().breadcrumb.append((component + '/', self.formdef.name))
|
|
self.fields = FieldsDirectory(self.formdef)
|
|
|
|
def _q_index [html] (self):
|
|
html_top('forms', title = self.formdef.name)
|
|
get_response().filter['sidebar'] = self.get_sidebar()
|
|
get_response().add_javascript(['jquery.js', 'widget_list.js'])
|
|
DateWidget.prepare_javascript()
|
|
|
|
'<h2>%s - ' % _('Form')
|
|
self.formdef.name
|
|
' <span class="change">(<a rel="popup" href="title">%s</a>)</span>' % _('change title')
|
|
'</h2>'
|
|
|
|
'<div class="splitcontent-left">'
|
|
'<div class="bo-block">'
|
|
'<h3>%s</h3>' % _('Access')
|
|
'<ul>'
|
|
|
|
categories = get_categories()
|
|
if categories:
|
|
'<li>%s ' % _('Category:')
|
|
if self.formdef.category:
|
|
self.formdef.category.name
|
|
else:
|
|
'-'
|
|
' '
|
|
'(<a href="category" rel="popup">%s</a>)' % _('change')
|
|
'</li>'
|
|
|
|
workflows = get_workflows()
|
|
if workflows:
|
|
'<li>%s ' % _('Workflow:')
|
|
if self.formdef.workflow:
|
|
self.formdef.workflow.name
|
|
else:
|
|
'-'
|
|
if self.formdef.workflow_id:
|
|
pristine_workflow = Workflow.get(self.formdef.workflow_id)
|
|
if pristine_workflow.has_options():
|
|
' (<a href="workflow-options">%s</a>)' % _('options')
|
|
' '
|
|
'(<a href="workflow" rel="popup">%s</a>)' % _('change')
|
|
'</li>'
|
|
|
|
'<li>%s ' % _('Recipient Role:')
|
|
if self.formdef.receiver:
|
|
'<a href="../../roles/%s/">%s</a>' % (
|
|
self.formdef.receiver.id, self.formdef.receiver.name)
|
|
else:
|
|
'-'
|
|
' '
|
|
'(<a href="recipient" rel="popup">%s</a>)' % _('change')
|
|
'</li>'
|
|
|
|
'<li>%s ' % _('Sender Roles:')
|
|
if self.formdef.roles:
|
|
roles = []
|
|
for x in self.formdef.roles:
|
|
if x == logged_users_role().id:
|
|
roles.append(logged_users_role().name)
|
|
else:
|
|
roles.append('<a href="../../roles/%s/">%s</a>' % (x, Role.get(x).name))
|
|
'%s' % ', '.join(roles)
|
|
if self.formdef.always_advertise:
|
|
' (%s)' % _('Always advertise')
|
|
else:
|
|
'-'
|
|
' '
|
|
'(<a href="roles" rel="popup">%s</a>)' % _('change')
|
|
'</li>'
|
|
|
|
'<li>%s ' % _('Read Access:')
|
|
'%s' % {'none': _('None'),
|
|
'owner': _('Owner'),
|
|
'roles': _('Roles'),
|
|
'all': _('Everybody')}.get(self.formdef.acl_read, 'none')
|
|
' '
|
|
'(<a href="acl-read" rel="popup">%s</a>)' % _('change')
|
|
'</ul>'
|
|
'</div>'
|
|
'</div>'
|
|
|
|
'<div class="splitcontent-right">'
|
|
'<div class="bo-block">'
|
|
'<h3>%s' % _('Options')
|
|
' <span class="change">(<a rel="popup" href="options">%s</a>)</span></h3>' % _('change')
|
|
'<ul>'
|
|
if self.formdef.confirmation:
|
|
'<li>%s</li>' % _('Include confirmation page')
|
|
if self.formdef.signing == 'optional':
|
|
'<li>%s</li>' % _('Optional Signing')
|
|
if self.formdef.signing == 'compulsory':
|
|
'<li>%s</li>' % _('Compulsary Signing')
|
|
if self.formdef.private_status_and_history:
|
|
'<li>%s</li>' % _('Keep workflow status and history private')
|
|
if self.formdef.only_allow_one:
|
|
'<li>%s</li>' % _('Only allow one form per user')
|
|
if self.formdef.allow_drafts:
|
|
'<li>%s</li>' % _('Allow user to keep drafts')
|
|
if self.formdef.disabled:
|
|
'<li>%s ' % _('Disabled')
|
|
if self.formdef.disabled_redirection:
|
|
'(<a href="%s">' % self.formdef.disabled_redirection
|
|
_('redirection')
|
|
'</a>) '
|
|
' (<a href="enable">%s</a>)</li>' % _('enable')
|
|
if self.formdef.publication_date:
|
|
'<li>%s</li>' % _('Publication Date: %s') % self.formdef.publication_date
|
|
if self.formdef.expiration_date:
|
|
'<li>%s</li>' % _('Expiration Date: %s') % self.formdef.expiration_date
|
|
'</ul>'
|
|
'</div>'
|
|
'</div>'
|
|
|
|
'<div class="bo-block clear">'
|
|
'<h3 class="clear">%s <span class="change">(<a href="fields/">%s</a>)</span></h3>' % (
|
|
_('Fields'), _('edit'))
|
|
self.get_preview()
|
|
'</div>'
|
|
|
|
def get_sidebar [html] (self):
|
|
'<ul>'
|
|
'<li><a href="delete" rel="popup">%s</a></li>' % _('Delete')
|
|
'<li><a href="duplicate">%s</a></li>' % _('Duplicate')
|
|
if ET:
|
|
'<li><a href="export">%s</a></li>' % _('Export')
|
|
'<li><a href="archive">%s</a></li>' % _('Archive')
|
|
if self.formdef.roles:
|
|
'<li><a href="invite">%s</a></li>' % _('Invites')
|
|
'</ul>'
|
|
|
|
def category [html] (self):
|
|
categories = get_categories()
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(SingleSelectWidget, 'category_id',
|
|
value=self.formdef.category_id,
|
|
options=[(None, '---')] + categories)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append( ('category', _('Category')) )
|
|
html_top('forms', title = self.formdef.name)
|
|
'<p>%s</p>' % _('Select a category for this form')
|
|
form.render()
|
|
else:
|
|
self.formdef.category_id = form.get_widget('category_id').parse()
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
def recipient [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(SingleSelectWidget, 'receiver_id',
|
|
value=self.formdef.receiver_id,
|
|
options = get_user_roles())
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append( ('recipient', _('Recipient Role')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<p>%s</p>' % _('Select the role that will handle those forms.')
|
|
form.render()
|
|
else:
|
|
self.formdef.receiver_id = form.get_widget('receiver_id').parse()
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
def roles [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(WidgetList, 'roles', title=_('Sender Roles'), element_type=SingleSelectWidget,
|
|
value=self.formdef.roles,
|
|
add_element_label = _('Add Role'),
|
|
element_kwargs = {str('render_br'): False,
|
|
str('options'): [(None, str('---')),
|
|
(logged_users_role().id, logged_users_role().name)] + get_user_roles()})
|
|
form.add(CheckboxWidget, 'always_advertise', title=_('Always advertise'),
|
|
value=self.formdef.always_advertise)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append( ('recipient', _('Recipient Role')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<p>%s</p>' % _('Select the roles that can access this form.')
|
|
form.render()
|
|
else:
|
|
self.formdef.always_advertise = form.get_widget('always_advertise').parse()
|
|
roles = form.get_widget('roles').parse()
|
|
if roles:
|
|
self.formdef.roles = [x for x in roles if x]
|
|
else:
|
|
self.formdef.roles = []
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
def title [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(StringWidget, 'name', title=_('Form Title'), required=True,
|
|
size=40, value=self.formdef.name)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if form.is_submitted() and not form.has_errors():
|
|
new_name = form.get_widget('name').parse()
|
|
formdefs_name = [x.name for x in FormDef.select(ignore_errors=True)
|
|
if x.id != self.formdef.id]
|
|
if new_name in formdefs_name:
|
|
form.get_widget('name').set_error(_('This name is already used'))
|
|
else:
|
|
self.formdef.name = new_name
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
get_response().breadcrumb.append( ('title', _('Title')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<p>%s</p>' % _('Choose a title for this form')
|
|
form.render()
|
|
|
|
def acl_read [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(SingleSelectWidget, 'acl_read', title=_('Read Access'),
|
|
options=[
|
|
(str('none'), _('None')),
|
|
(str('owner'), _('Owner')),
|
|
(str('roles'), _('Roles')),
|
|
(str('all'), _('Everybody'))],
|
|
value=self.formdef.acl_read)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if form.is_submitted() and not form.has_errors():
|
|
self.formdef.acl_read = form.get_widget('acl_read').parse()
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
get_response().breadcrumb.append( ('acl-read', _('Read Access')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<p>%s</p>' % _('Select who is granted a read access.')
|
|
form.render()
|
|
|
|
def options [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(CheckboxWidget, 'confirmation', title=_('Include confirmation page'),
|
|
value=self.formdef.confirmation)
|
|
if M2Crypto:
|
|
form.add(SingleSelectWidget, 'signing', title=_('Signature'),
|
|
value=self.formdef.signing,
|
|
options = [
|
|
(None, _('None')),
|
|
('optional', _('Optional')),
|
|
('compulsory', _('Compulsary'))])
|
|
form.add(CheckboxWidget, 'private_status_and_history',
|
|
title=_('Keep workflow status and history private'),
|
|
hint=_('Restrict the possibility to see status and history to the recipients'),
|
|
value=self.formdef.private_status_and_history)
|
|
form.add(CheckboxWidget, 'only_allow_one',
|
|
title=_('Only allow one form per user'),
|
|
value=self.formdef.only_allow_one)
|
|
form.add(CheckboxWidget, 'allow_drafts',
|
|
title=_('Allow user to keep drafts'),
|
|
value=self.formdef.allow_drafts)
|
|
form.add(CheckboxWidget, 'disabled',
|
|
title=_('Disable access to form'),
|
|
value=self.formdef.disabled)
|
|
form.add(StringWidget, 'disabled_redirection',
|
|
title=_('If disabled, redirect to this URL'), size=40,
|
|
hint=_('Redirection will only be performed if the form is disabled and a URL is given. '\
|
|
'Common substitution variables are available with the [variable] syntax.'),
|
|
value=self.formdef.disabled_redirection)
|
|
form.add(DateWidget, 'publication_date',
|
|
title=_('Publication Date'),
|
|
value=self.formdef.publication_date)
|
|
form.add(DateWidget, 'expiration_date',
|
|
title=_('Expiration Date'),
|
|
value=self.formdef.expiration_date)
|
|
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if form.is_submitted() and not form.has_errors():
|
|
for f in ('confirmation', 'signing', 'only_allow_one', 'disabled',
|
|
'allow_drafts', 'private_status_and_history',
|
|
'disabled_redirection', 'publication_date', 'expiration_date'):
|
|
widget = form.get_widget(f)
|
|
if widget:
|
|
setattr(self.formdef, str(f), widget.parse())
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
get_response().breadcrumb.append( ('options', _('Options')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<h2>%s</h2>' % _('Options')
|
|
form.render()
|
|
|
|
def workflow [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
workflows = get_workflows(condition=lambda x: x.possible_status)
|
|
form.add(SingleSelectWidget, 'workflow_id',
|
|
value=self.formdef.workflow_id,
|
|
options = [(None, _('Default Workflow'))] + workflows)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append( ('workflow', _('Workflow')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<p>%s</p>' % _('Select the workflow that will handle those forms.')
|
|
form.render()
|
|
else:
|
|
workflow_id = form.get_widget('workflow_id').parse()
|
|
if self.formdef.data_class().keys():
|
|
# there are existing formdata, status will have to be mapped
|
|
if workflow_id is None:
|
|
workflow_id = '_default'
|
|
return redirect('workflow-status-remapping?new=%s' % workflow_id)
|
|
self.formdef.workflow_id = workflow_id
|
|
self.formdef.store()
|
|
redirect('.')
|
|
|
|
def workflow_status_remapping [html] (self):
|
|
new_workflow = Workflow.get(get_request().form.get('new'))
|
|
if get_request().get_method() == 'GET':
|
|
get_request().form = None # do not be considered submitted already
|
|
new_workflow_status = [(x.id, x.name) for x in new_workflow.get_waitpoint_status()]
|
|
form = Form(enctype='multipart/form-data')
|
|
for status in self.formdef.workflow.possible_status:
|
|
default = status.id
|
|
if not default in [x.id for x in new_workflow.possible_status]:
|
|
default = new_workflow_status[0]
|
|
form.add(SingleSelectWidget, 'mapping-%s' % status.id,
|
|
title=status.name,
|
|
value=default, options=new_workflow_status)
|
|
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append( ('workflow-status-remapping', _('Workflow Status Remapping')) )
|
|
html_top('forms', title=self.formdef.name)
|
|
'<p>'
|
|
_('From %(here)s to %(there)s') % {'here': self.formdef.workflow.name,
|
|
'there': new_workflow.name}
|
|
'</p>'
|
|
form.render()
|
|
else:
|
|
get_logger().info('admin - form "%s", workflow is now "%s" (was "%s")' % (
|
|
self.formdef.name, new_workflow.name, self.formdef.workflow.name))
|
|
self.workflow_status_remapping_submit(form)
|
|
if new_workflow.id == '_default':
|
|
self.formdef.workflow_id = None
|
|
else:
|
|
self.formdef.workflow_id = new_workflow.id
|
|
self.formdef.store()
|
|
return redirect('.')
|
|
|
|
def workflow_status_remapping_submit(self, form):
|
|
status_mapping = {}
|
|
for status in self.formdef.workflow.possible_status:
|
|
status_mapping['wf-%s' % status.id] = 'wf-%s' % \
|
|
form.get_widget('mapping-%s' % status.id).parse()
|
|
for item in self.formdef.data_class().select():
|
|
item.status = status_mapping.get(item.status)
|
|
if item.evolution:
|
|
for evo in item.evolution:
|
|
evo.status = status_mapping.get(evo.status)
|
|
item.store()
|
|
|
|
def get_preview [html] (self):
|
|
form = Form()
|
|
on_page = 0
|
|
for i, field in enumerate(self.formdef.fields):
|
|
field.id = i
|
|
if hasattr(field, str('add_to_form')):
|
|
field.add_to_form(form)
|
|
else:
|
|
if field.key == 'page':
|
|
if on_page:
|
|
form.widgets.append(HtmlWidget('</fieldset>'))
|
|
form.widgets.append(HtmlWidget('<fieldset class="formpage">'))
|
|
on_page += 1
|
|
form.widgets.append(HtmlWidget(
|
|
'<legend>%s</legend>' % _('Page %s') % on_page))
|
|
elif field.key == 'title':
|
|
form.widgets.append(HtmlWidget('<h3>%s</h3>' % field.label))
|
|
elif field.key == 'subtitle':
|
|
form.widgets.append(HtmlWidget('<h4>%s</h4>' % field.label))
|
|
elif field.key == 'comment':
|
|
form.widgets.append(HtmlWidget('<p>%s</p>' % field.label))
|
|
|
|
if on_page:
|
|
form.widgets.append(HtmlWidget('</fieldset>'))
|
|
|
|
'<div class="form-preview">'
|
|
form.render()
|
|
'</div>'
|
|
|
|
def duplicate [html] (self):
|
|
self.formdefui.formdef.id = None
|
|
original_name = self.formdefui.formdef.name
|
|
self.formdefui.formdef.name = self.formdefui.formdef.name + _(' (copy)')
|
|
formdef_names = [x.name for x in FormDef.select()]
|
|
no = 2
|
|
while self.formdefui.formdef.name in formdef_names:
|
|
self.formdefui.formdef.name = _('%(name)s (copy %(no)d)') % {
|
|
'name': original_name, 'no': no}
|
|
no += 1
|
|
self.formdefui.formdef.url_name = None
|
|
self.formdefui.formdef.disabled = True
|
|
self.formdefui.formdef.store()
|
|
return redirect('../%s/' % self.formdefui.formdef.id)
|
|
|
|
def delete [html] (self):
|
|
form = Form(enctype='multipart/form-data')
|
|
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
|
|
'You are about to irrevocably delete this form.')))
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('..')
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append(('delete', _('Delete')))
|
|
html_top('forms', title = _('Delete Form'))
|
|
'<h2>%s %s</h2>' % (_('Deleting Form:'), self.formdef.name)
|
|
form.render()
|
|
else:
|
|
# XXX: remove form data
|
|
#for formdata in FormData.select(FormData.q.formdefID == self.formdef.id):
|
|
# formdata.destroySelf()
|
|
self.formdef.remove_self()
|
|
return redirect('..')
|
|
|
|
def export(self):
|
|
x = self.formdef.export_to_xml()
|
|
indent(x)
|
|
response = get_response()
|
|
response.set_content_type('application/x-wcs-form')
|
|
response.set_header('content-disposition',
|
|
'attachment; filename=%s.wcs' % self.formdef.url_name)
|
|
return '<?xml version="1.0" encoding="iso-8859-15"?>\n' + ET.tostring(x)
|
|
|
|
def archive [html] (self):
|
|
if get_request().form.get('job'):
|
|
return self.archive_processing()
|
|
if get_request().form.get('download'):
|
|
return self.archive_download()
|
|
|
|
form = Form(enctype='multipart/form-data')
|
|
|
|
form.add(DateWidget, 'date', title = _('Archive forms handled before'))
|
|
form.add(CheckboxWidget, 'not-done', title = _('Include forms that have not been handled'),
|
|
value = False)
|
|
form.add(CheckboxWidget, 'keep', title = _('Do not remove forms'),
|
|
value = False)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if not form.is_submitted() or form.has_errors():
|
|
get_response().breadcrumb.append(('archive', _('Archive')))
|
|
html_top('forms', title = _('Archive Forms'))
|
|
'<h2>%s</h2>' % _('Archive Forms')
|
|
form.render()
|
|
else:
|
|
return self.archive_submit(form)
|
|
|
|
def archive_submit(self, form):
|
|
|
|
class Archiver:
|
|
def __init__(self, formdef):
|
|
self.formdef = formdef
|
|
|
|
def archive(self, job=None):
|
|
all_forms = self.formdef.data_class().select()
|
|
|
|
if form.get_widget('not-done').parse() is False:
|
|
not_endpoint_status = self.formdef.workflow.get_not_endpoint_status()
|
|
not_endpoint_status_ids = ['wf-%s' % x.id for x in not_endpoint_status]
|
|
all_forms = [x for x in all_forms if x.status not in not_endpoint_status_ids]
|
|
|
|
if form.get_widget('date').parse():
|
|
date = form.get_widget('date').parse()
|
|
date = time.strptime(date, misc.date_format())
|
|
all_forms = [x for x in all_forms if (
|
|
x.evolution and x.evolution[-1].time < date) or (
|
|
x.receipt_time < date)]
|
|
|
|
self.fd = StringIO()
|
|
t = tarfile.open('wcs.tar.gz', 'w:gz', fileobj=self.fd)
|
|
t.add(self.formdef.get_object_filename(), 'formdef')
|
|
for formdata in all_forms:
|
|
t.add(formdata.get_object_filename(), '%s/%s' % (self.formdef.url_name, str(formdata.id)))
|
|
t.close()
|
|
|
|
if form.get_widget('keep').parse() is False:
|
|
for f in all_forms:
|
|
f.remove_self()
|
|
|
|
if job:
|
|
job.file_content = self.fd.getvalue()
|
|
job.store()
|
|
|
|
count = self.formdef.data_class().count()
|
|
archiver = Archiver(self.formdef)
|
|
if count > 100: # Arbitrary threshold
|
|
job = get_response().add_after_job(
|
|
str(N_('Archiving forms')),
|
|
archiver.archive)
|
|
return redirect('archive?job=%s' % job.id)
|
|
else:
|
|
archiver.archive()
|
|
|
|
response = get_response()
|
|
response.set_content_type('application/x-wcs-archive')
|
|
response.set_header('content-disposition',
|
|
'attachment; filename=%s-archive.wcs' % self.formdef.url_name)
|
|
return archiver.fd.getvalue()
|
|
|
|
def archive_processing [html] (self):
|
|
try:
|
|
job = AfterJob.get(get_request().form.get('job'))
|
|
except KeyError:
|
|
return redirect('.')
|
|
|
|
html_top('forms', title=_('Archiving'))
|
|
get_session().display_message()
|
|
get_response().add_javascript(['jquery.js', 'interface.js', 'afterjob.js'])
|
|
'<dl class="job-status">'
|
|
'<dt>'
|
|
_(job.label)
|
|
'</dt>'
|
|
'<dd>'
|
|
'<span class="afterjob" id="%s">' % job.id
|
|
_(job.status)
|
|
'</span>'
|
|
'</dd>'
|
|
'</dl>'
|
|
|
|
'<div class="done">'
|
|
'<a href="archive?download=%s">%s</a>' % (job.id, _('Download Archive'))
|
|
'</div>'
|
|
|
|
def archive_download(self):
|
|
try:
|
|
job = AfterJob.get(get_request().form.get('download'))
|
|
except KeyError:
|
|
return redirect('.')
|
|
|
|
if not job.status == 'completed':
|
|
raise TraversalError()
|
|
response = get_response()
|
|
response.set_content_type('application/x-wcs-archive')
|
|
response.set_header('content-disposition',
|
|
'attachment; filename=%s-archive.wcs' % self.formdef.url_name)
|
|
return job.file_content
|
|
|
|
def invite [html] (self):
|
|
if not self.formdef.roles:
|
|
return template.error_page(
|
|
_('''Sending invites is limited to forms restricted
|
|
to a set of users.'''))
|
|
|
|
if get_request().form.get('job'):
|
|
return self.sending_invitations()
|
|
|
|
form = Form(enctype='multipart/form-data')
|
|
form.add(IntWidget, 'duration', title=_('Validity Duration (in days)'),
|
|
value=10, required=True)
|
|
form.add(StringWidget, 'invite_subject', title=_('Invite Subject'),
|
|
required=True, value=_('Invitation to %s') % self.formdef.name)
|
|
form.add(TextWidget, 'invite_body', title=_('Invite Text'),
|
|
cols=80, rows=10, required=True,
|
|
value=_('''Hello, connect to [url] and fill the form!'''))
|
|
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
users = [x for x in get_publisher().user_class.select() if x.email]
|
|
if logged_users_role().id in self.formdef.roles:
|
|
allowed_users = users
|
|
else:
|
|
allowed_users = []
|
|
for user in users:
|
|
for q in user.roles or []:
|
|
if q in self.formdef.roles:
|
|
allowed_users.append(user)
|
|
break
|
|
|
|
if self.formdef.only_allow_one:
|
|
# if the form is only authorised once per user, we have to remove
|
|
# users who have already submitted it from the list.
|
|
formdef_data_class = self.formdef.data_class()
|
|
real_allowed_users = []
|
|
for user in allowed_users:
|
|
if not formdef_data_class.get_with_indexed_value('user_id', user.id):
|
|
real_allowed_users.append(user)
|
|
allowed_users = real_allowed_users
|
|
|
|
if form.is_submitted() and not form.has_errors():
|
|
return self.invite_submit(form, allowed_users)
|
|
|
|
get_response().breadcrumb.append(('invite', _('Invite')))
|
|
html_top('forms', title = _('Invitation to Form'))
|
|
'<h2>%s</h2>' % _('Invitation to "%s" Form') % self.formdef.name
|
|
'<p>'
|
|
_('''This action allows you to send an invitation to all users
|
|
concerned by this form.''')
|
|
'</p>'
|
|
if len(allowed_users) == 0:
|
|
'<p>'
|
|
_('There is nobody to receive an invite.')
|
|
'</p>'
|
|
else:
|
|
'<p>'
|
|
_('The invitation will be sent to %d persons.') % len(allowed_users)
|
|
'</p>'
|
|
form.render()
|
|
|
|
def invite_submit(self, form, allowed_users):
|
|
|
|
class InvitationSender:
|
|
def __init__(self, formdef):
|
|
self.formdef = formdef
|
|
self.formdef_url = self.formdef.get_url()
|
|
|
|
def send(self, job=None):
|
|
duration_validity = form.get_widget('duration').parse()
|
|
subject = form.get_widget('invite_subject').parse()
|
|
body = form.get_widget('invite_body').parse()
|
|
|
|
for user in allowed_users:
|
|
token = tokens.Token(duration_validity * 86400)
|
|
token.type = 'form-invite'
|
|
token.formdef_id = self.formdef.id
|
|
token.user_id = user.id
|
|
token.store()
|
|
data = {
|
|
'url': self.formdef_url + 'tokens/%s/' % token.id,
|
|
'time': misc.localstrftime(time.localtime(token.expiration)),
|
|
}
|
|
|
|
emails.ezt_email(subject, body, data,
|
|
email_rcpt=user.email, fire_and_forget=False)
|
|
|
|
invitation_sender = InvitationSender(self.formdef)
|
|
|
|
job = get_response().add_after_job(
|
|
str(N_('Sending invitation emails')),
|
|
invitation_sender.send)
|
|
return redirect('invite?job=%s' % job.id)
|
|
|
|
def sending_invitations [html] (self):
|
|
try:
|
|
job = AfterJob.get(get_request().form.get('job'))
|
|
except KeyError:
|
|
return redirect('..')
|
|
html_top('forms', title = _('Invitation to Form'))
|
|
get_session().display_message()
|
|
get_response().add_javascript(['jquery.js', 'interface.js', 'afterjob.js'])
|
|
'<dl class="job-status">'
|
|
'<dt>'
|
|
_(job.label)
|
|
'</dt>'
|
|
'<dd>'
|
|
'<span class="afterjob" id="%s">' % job.id
|
|
_(job.status)
|
|
'</span>'
|
|
'</dd>'
|
|
'</dl>'
|
|
|
|
'<div class="done">'
|
|
'<a href="./">%s</a>' % _('Back')
|
|
'</div>'
|
|
|
|
def enable(self):
|
|
self.formdef.disabled = False
|
|
self.formdef.store()
|
|
if get_request().form.get('back') == 'fields':
|
|
return redirect('fields/')
|
|
return redirect('.')
|
|
|
|
def workflow_options [html] (self):
|
|
request = get_request()
|
|
if request.get_method() == 'GET' and request.form.get('file'):
|
|
value = self.formdef.workflow_options.get(request.form.get('file'))
|
|
if value:
|
|
return value.build_response()
|
|
|
|
html_top('forms', title = _('Workflow Options'))
|
|
'<h2>%s</h2>' % _('Workflow Options')
|
|
form = Form(enctype='multipart/form-data')
|
|
pristine_workflow = Workflow.get(self.formdef.workflow_id)
|
|
for status in self.formdef.workflow.possible_status:
|
|
had_options = False
|
|
for item in status.items:
|
|
prefix = '%s*%s*' % (status.id, item.id)
|
|
pristine_item = pristine_workflow.get_status(status.id).get_item(item.id)
|
|
parameters = [x for x in item.get_parameters() if not \
|
|
getattr(pristine_item, x)]
|
|
if not parameters:
|
|
continue
|
|
if not had_options:
|
|
form.widgets.append(HtmlWidget('<h3>%s</h3>' % status.name))
|
|
had_options = True
|
|
label = getattr(item, str('label'), None) or _(item.description)
|
|
form.widgets.append(HtmlWidget('<h4>%s</h4>' % label))
|
|
item.add_parameters_widgets(form, parameters, prefix=prefix, formdef=self.formdef)
|
|
form.add_submit('submit', _('Submit'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
form.render()
|
|
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
|
|
if form.is_submitted() and not form.has_errors():
|
|
self.workflow_options_submit(form)
|
|
return redirect('.')
|
|
|
|
def workflow_options_submit(self, form):
|
|
self.formdef.workflow_options = {}
|
|
for widget in form.get_all_widgets():
|
|
if widget in form.get_submit_widgets():
|
|
continue
|
|
if widget.name.startswith('_'):
|
|
continue
|
|
self.formdef.workflow_options[widget.name] = widget.parse()
|
|
self.formdef.store()
|
|
|
|
|
|
class FormsDirectory(Directory):
|
|
_q_exports = ['', 'new', ('import', 'p_import')]
|
|
|
|
def _q_index [html] (self):
|
|
get_response().breadcrumb.append( ('forms/', _('Forms')) )
|
|
html_top('forms', title = _('Forms'))
|
|
get_response().add_javascript(['jquery.js', 'widget_list.js'])
|
|
|
|
if Role.count():
|
|
'<ul id="main-actions">'
|
|
' <li><a class="new-item" href="new" rel="popup">%s</a></li>' % _('New Form')
|
|
' <li><a href="import" rel="popup">%s</a></li>' % _('Import')
|
|
'</ul>'
|
|
else:
|
|
'<p>%s</p>' % _('You first have to define roles.')
|
|
|
|
|
|
cats = Category.select()
|
|
Category.sort_by_position(cats)
|
|
one = False
|
|
formdefs = FormDef.select(order_by='name', ignore_errors=True)
|
|
for c in cats:
|
|
l2 = [x for x in formdefs if x.category_id == c.id]
|
|
l2 = [x for x in l2 if not x.disabled or (x.disabled and x.disabled_redirection)] \
|
|
+ [x for x in l2 if x.disabled and not x.disabled_redirection]
|
|
if l2:
|
|
self.form_list(l2, title = c.name)
|
|
one = True
|
|
|
|
l2 = [x for x in formdefs if not x.category]
|
|
if l2:
|
|
if one:
|
|
title = _('Misc')
|
|
else:
|
|
title = None
|
|
l2 = [x for x in l2 if not x.disabled or (x.disabled and x.disabled_redirection)] \
|
|
+ [x for x in l2 if x.disabled and not x.disabled_redirection]
|
|
self.form_list(l2, title = title)
|
|
|
|
|
|
def form_list [html] (self, formdefs, title):
|
|
'<div class="bo-block">'
|
|
if title:
|
|
'<h2>%s</h2>' % title
|
|
|
|
'<ul class="biglist">'
|
|
for formdef in formdefs:
|
|
if formdef.disabled:
|
|
'<li class="disabled">'
|
|
else:
|
|
'<li>'
|
|
'<strong class="label"><a href="%s/">%s</a></strong>' % (formdef.id, formdef.name)
|
|
'<p class="details">'
|
|
if formdef.disabled and formdef.disabled_redirection:
|
|
'(<a href="%s">' % formdef.disabled_redirection
|
|
_('redirection')
|
|
'</a>) '
|
|
if formdef.receiver:
|
|
misc.ellipsize(formdef.receiver.name, 50)
|
|
'</p>'
|
|
'</li>'
|
|
'</ul>'
|
|
'</div>'
|
|
|
|
def new [html] (self):
|
|
get_response().breadcrumb.append( ('forms/', _('Forms')) )
|
|
get_response().breadcrumb.append( ('new', _('New')) )
|
|
if Role.count() == 0:
|
|
return error_page('forms', _('You first have to define roles.'))
|
|
formdefui = FormDefUI(None)
|
|
form = formdefui.new_form_ui()
|
|
if form.get_widget('cancel').parse():
|
|
return redirect('.')
|
|
redo = False
|
|
if form.get_widget('fields') and \
|
|
form.get_widget('fields').get_widget('add_element').parse():
|
|
form.clear_errors()
|
|
redo = True
|
|
|
|
if form.is_submitted() and not form.has_errors() and not redo:
|
|
try:
|
|
formdef = formdefui.submit_form(form)
|
|
formdef.disabled = True
|
|
formdef.store()
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
return redirect(str(formdef.id) + '/fields')
|
|
|
|
html_top('forms', title = _('New Form'))
|
|
'<h2>%s</h2>' % _('New Form')
|
|
form.render()
|
|
|
|
def _q_lookup(self, component):
|
|
get_response().breadcrumb.append( ('forms/', _('Forms')) )
|
|
return FormDefPage(component)
|
|
|
|
def p_import [html] (self):
|
|
form = Form(enctype = 'multipart/form-data')
|
|
|
|
form.add(FileWidget, 'file', title = _('File'), required = True)
|
|
form.add_submit('submit', _('Import Form'))
|
|
form.add_submit('cancel', _('Cancel'))
|
|
|
|
if form.get_submit() == 'cancel':
|
|
return redirect('.')
|
|
|
|
if form.is_submitted() and not form.has_errors():
|
|
try:
|
|
return self.import_submit(form)
|
|
except ValueError:
|
|
form.get_widget('file').set_error(_('Invalid File'))
|
|
|
|
get_response().breadcrumb.append( ('import', _('Import')) )
|
|
html_top('forms', title = _('Import Form'))
|
|
'<h2>%s</h2>' % _('Import Form')
|
|
form.render()
|
|
|
|
def import_submit(self, form):
|
|
formdef = FormDef.import_from_xml(form.get_widget('file').parse().fp)
|
|
formdef.disabled = True
|
|
formdef.store()
|
|
return redirect('%s' % formdef.id)
|
|
|