wcs/wcs/admin/forms.py

1577 lines
65 KiB
Python

# 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/>.
import xml.etree.ElementTree as ET
import datetime
import difflib
import tarfile
import time
import urllib2
from cStringIO import StringIO
from quixote import get_response, redirect
from quixote.directory import Directory, AccessControlled
from quixote.html import TemplateIO, htmltext
from qommon import misc
from qommon.errors import *
from qommon.form import *
from qommon.backoffice.menu import html_top
from qommon import get_logger
from qommon import tokens
from qommon.afterjobs import AfterJob
from qommon import emails
from wcs.formdef import FormDef, FormdefImportError
from wcs.categories import Category
from wcs.roles import Role, logged_users_role, get_user_roles
from wcs.workflows import Workflow
from wcs.forms.root import qrcode
from fields import FieldDefPage, FieldsDirectory
def get_categories():
t = sorted([(misc.simplify(x.name), x.id, x.name, x.id) 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, x.id) for x in Workflow.select() if condition(x)])
return [x[1:] for x in t]
class FormDefUI(object):
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_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', 'acl_read',
'only_allow_one', 'category_id', 'disabled',
'enable_tracking_codes', '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())
if not formdef.fields:
formdef.fields = []
formdef.store()
return formdef
class FieldDefPage(FieldDefPage):
section = 'forms'
class FieldsDirectory(FieldsDirectory):
field_def_page_class = FieldDefPage
def index_bottom(self):
if hasattr(self.objectdef, str('disabled')) and self.objectdef.disabled:
r = TemplateIO(html=True)
r += htmltext('<p>')
r += _('This form is currently disabled.')
if hasattr(self.objectdef, str('disabled_redirection')) \
and self.objectdef.disabled_redirection:
r += htmltext(' (<a href="%s">') % self.objectdef.disabled_redirection
r += _('redirection')
r += htmltext('</a>)')
r += htmltext(' <a href="../enable?back=fields">%s</a>') % _('Enable')
r += htmltext('</p>')
return r.getvalue()
class WorkflowRoleDirectory(Directory):
def __init__(self, formdef):
self.formdef = formdef
def _q_lookup(self, component):
if not component in self.formdef.workflow.roles:
raise TraversalError()
if not self.formdef.workflow_roles:
self.formdef.workflow_roles = {}
role_id = self.formdef.workflow_roles.get(component)
form = Form(enctype='multipart/form-data')
form.add(SingleSelectWidget, 'role_id',
value=role_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( ('role/%s' % component, _('Workflow Role')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Role')
r += htmltext('<p>%s</p>') % self.formdef.workflow.roles.get(component)
r += form.render()
return r.getvalue()
else:
self.formdef.workflow_roles[component] = form.get_widget('role_id').parse()
self.formdef.store()
# instruct formdef to update its security rules
self.formdef.data_class().rebuild_security()
return redirect('..')
class FormDefPage(Directory):
_q_exports = ['', 'fields', 'delete', 'duplicate', 'export',
'anonymise', 'archive', 'invite', 'enable', 'workflow',
'category', 'role', ('workflow-options', 'workflow_options'),
('workflow-variables', 'workflow_variables'),
('workflow-status-remapping', 'workflow_status_remapping'),
'roles', 'title', 'options', ('acl-read', 'acl_read'),
'overwrite', 'qrcode', 'information',
('public-url', 'public_url')]
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)
self.fields.html_top = self.html_top
self.role = WorkflowRoleDirectory(self.formdef)
self.role.html_top = self.html_top
def html_top(self, title):
return html_top('forms', title)
def _q_index(self):
self.html_top(title = self.formdef.name)
r = TemplateIO(html=True)
get_response().filter['sidebar'] = self.get_sidebar()
get_response().add_javascript(['jquery.js', 'widget_list.js',
'ckeditor/ckeditor.js', 'qommon.wysiwyg.js', 'ckeditor/adapters/jquery.js'])
DateWidget.prepare_javascript()
r += htmltext('<h2>%s - ') % _('Form')
r += self.formdef.name
r += htmltext(' <span class="change">(<a rel="popup" href="title">%s</a>)</span>') % _('change title')
r += htmltext('</h2>')
if self.formdef.last_modification_time:
warning_class = ''
if (time.time() - time.mktime(self.formdef.last_modification_time)) < 600:
if get_request().user and get_request().user.id != self.formdef.last_modification_user_id:
warning_class = 'recent'
r += htmltext('<p class="last-modification %s">') % warning_class
r += _('Last Modification:')
r += ' '
r += misc.localstrftime(self.formdef.last_modification_time)
r += ' '
if self.formdef.last_modification_user_id:
try:
r += _('by %s') % get_publisher().user_class.get(
self.formdef.last_modification_user_id).display_name
except KeyError:
pass
r += htmltext('</p>')
r += get_session().display_message()
r += htmltext('<div class="bo-block">')
r += htmltext('<h3>%s') % _('Information')
if not (self.formdef.description or self.formdef.keywords):
r += htmltext(' <span class="change">(%s)</span>') % _('No description or keywords')
r += htmltext(' <span class="change">(<a rel="popup" href="information">%s</a>)</span></h3>') % _('change')
if self.formdef.description:
r += htmltext(self.formdef.description)
if self.formdef.keywords:
r += htmltext('<p>%s %s</p>') % (
_('Keywords:'), self.formdef.keywords)
r += htmltext('</div>')
r += htmltext('<div class="splitcontent-left">')
r += htmltext('<div class="bo-block">')
r += htmltext('<h3>%s</h3>') % _('Access')
r += htmltext('<ul>')
categories = get_categories()
if categories:
r += htmltext('<li>%s ') % _('Category:')
if self.formdef.category:
r += self.formdef.category.name
else:
r += '-'
r += ' '
r += htmltext('(<a href="category" rel="popup">%s</a>)') % _('change')
r += htmltext('</li>')
workflows = get_workflows()
if workflows:
r += htmltext('<li>%s ') % _('Workflow:')
if self.formdef.workflow:
r += self.formdef.workflow.name
else:
r += '-'
if self.formdef.workflow_id:
pristine_workflow = Workflow.get(self.formdef.workflow_id)
if pristine_workflow.variables_formdef:
r += htmltext(' (<a rel="popup" href="workflow-variables">%s</a>)') % _('options')
elif self.formdef.workflow_options:
# there are no variables defined but there are some values
# in workflow_options, this is probably the legacy stuff.
if any((x for x in self.formdef.workflow_options if '*' in x)):
r += htmltext(' (<a rel="popup" href="workflow-options">%s</a>)') % _('options')
r += ' '
r += htmltext('(<a href="workflow" rel="popup">%s</a>)') % _('change')
r += htmltext('</li>')
if self.formdef.workflow.roles:
r += htmltext('<li>%s ') % _('Workflow Roles:')
r += htmltext('<ul>')
if not self.formdef.workflow_roles:
self.formdef.workflow_roles = {}
for (wf_role_id, wf_role_label) in self.formdef.workflow.roles.items():
r += htmltext('<li>%s ') % _('%s:') % wf_role_label
role_id = self.formdef.workflow_roles.get(wf_role_id)
if role_id:
try:
role = Role.get(role_id)
r += htmltext('<a href="../../roles/%s/">%s</a>') % (role.id, role.name)
except KeyError:
# removed role ?
r += htmltext('<em>')
r += _('Unknown role (%s)') % role_id
r += htmltext('</em>')
else:
r += '-'
r += htmltext(' ')
r += htmltext('(<a href="role/%s" rel="popup">%s</a>)') % (wf_role_id, _('change'))
r += htmltext('</li>')
r += htmltext('</ul>')
r += htmltext('</li>')
r += htmltext('<li>%s ') % _('User 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:
try:
roles.append(htmltext('<a href="../../roles/%s/">%s</a>') % (x, Role.get(x).name))
except KeyError:
# removed role ?
roles.append(htmltext('<em>%s</em>') % _('Unknown role (%s)') % role_id)
r += htmltext(', ').join(roles)
if self.formdef.always_advertise:
r += htmltext(' (%s)') % _('Always advertise')
else:
r += '-'
r += ' '
r += htmltext('(<a href="roles" rel="popup">%s</a>)') % _('change')
r += htmltext('</li>')
r += htmltext('<li>%s ') % _('Read Access:')
r += '%s' % {'none': _('None'),
'owner': _('Owner'),
'roles': _('Roles'),
'all': _('Everybody')}.get(self.formdef.acl_read, 'none')
r += ' '
r += htmltext('(<a href="acl-read" rel="popup">%s</a>)') % _('change')
r += htmltext('</ul>')
r += htmltext('</div>')
r += htmltext('</div>')
r += htmltext('<div class="splitcontent-right">')
r += htmltext('<div class="bo-block">')
r += htmltext('<h3>%s') % _('Options')
r += htmltext(' <span class="change">(<a rel="popup" href="options">%s</a>)</span></h3>') % _('change')
r += htmltext('<ul>')
if self.formdef.confirmation:
r += htmltext('<li>%s</li>') % _('Include confirmation page')
if self.formdef.private_status_and_history:
r += htmltext('<li>%s</li>') % _('Keep workflow status and history private')
if self.formdef.only_allow_one:
r += htmltext('<li>%s</li>') % _('Only allow one form per user')
if self.formdef.enable_tracking_codes:
r += htmltext('<li>%s</li>') % _('Has support for tracking codes')
if self.formdef.disabled:
r += htmltext('<li>%s ') % _('Disabled')
if self.formdef.disabled_redirection:
r += htmltext('(<a href="%s">') % self.formdef.disabled_redirection
r += _('redirection')
r += htmltext('</a>) ')
r += htmltext(' (<a href="enable">%s</a>)</li>') % _('enable')
if self.formdef.publication_date:
r += htmltext('<li>%s</li>') % _('Publication Date: %s') % self.formdef.publication_date
if self.formdef.expiration_date:
r += htmltext('<li>%s</li>') % _('Expiration Date: %s') % self.formdef.expiration_date
r += htmltext('</ul>')
r += htmltext('</div>')
r += htmltext('</div>')
r += htmltext('<div class="bo-block clear">')
r += htmltext('<h3 class="clear">%s <span class="change">(<a href="fields/">%s</a>)</span></h3>') % (
_('Fields'), _('edit'))
r += self.get_preview()
r += htmltext('</div>')
return r.getvalue()
def get_sidebar(self):
r = TemplateIO(html=True)
r += htmltext('<ul id="sidebar-actions">')
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate')
r += htmltext('<li><a rel="popup" href="overwrite">%s</a></li>') % _(
'Overwrite with new import')
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
r += htmltext('<li><a href="anonymise">%s</a></li>') % _('Anonymise forms')
if not get_publisher().is_using_postgresql():
r += htmltext('<li><a href="archive">%s</a></li>') % _('Archive')
if self.formdef.roles:
r += htmltext('<li><a href="invite">%s</a></li>') % _('Invites')
r += htmltext('</ul>')
r += htmltext('<ul>')
if not self.formdef.is_disabled():
r += htmltext('<li><a href="%s">%s</a></li>') % (
self.formdef.get_url(), _('Display Online'))
r += htmltext('<li><a href="public-url" rel="popup">%s</a></li>') % _('Display public URL')
if qrcode is not None:
r += htmltext('<li><a href="qrcode" rel="popup">%s</a></li>') % _('Display QR Code')
r += htmltext('</ul>')
return r.getvalue()
def public_url(self):
self.html_top(title=self.formdef.name)
get_response().breadcrumb.append(('public-url', _('Public URL')))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>' % _('Public URL'))
r += htmltext('<div>')
r += htmltext('<p>%s</p>') % _('The public URL of this form is:')
url = self.formdef.get_url()
r += htmltext('<a href="%s">%s</a>') % (url, url)
r += htmltext('</div>')
return r.getvalue()
def qrcode(self):
self.html_top(title=self.formdef.name)
get_response().breadcrumb.append(('qrcode', _('QR Code')))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>' % _('QR Code'))
r += htmltext('<div id="qrcode">')
r += htmltext('<img width="410px" height="410px" src="%sqrcode" alt=""/>' % self.formdef.get_url())
r += htmltext('<a href="%sqrcode?download">%s</a></p>') % (self.formdef.get_url(), _('Download'))
r += htmltext('</div>')
return r.getvalue()
def category(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')) )
self.html_top(title = self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Category')
r += htmltext('<p>%s</p>') % _('Select a category for this form')
r += form.render()
return r.getvalue()
else:
self.formdef.category_id = form.get_widget('category_id').parse()
self.formdef.store()
return redirect('.')
def roles(self):
form = Form(enctype='multipart/form-data')
form.add(WidgetList, 'roles', title=_('User 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( ('roles', _('User Roles')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Roles')
r += htmltext('<p>%s</p>') % _('Select the roles that can access this form.')
r += form.render()
return r.getvalue()
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()
return redirect('.')
def title(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()
return redirect('.')
get_response().breadcrumb.append( ('title', _('Title')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Title')
r += htmltext('<p>%s</p>') % _('Choose a title for this form')
r += form.render()
return r.getvalue()
def acl_read(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()
return redirect('.')
get_response().breadcrumb.append( ('acl-read', _('Read Access')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Roles')
r += htmltext('<p>%s</p>') % _('Select who is granted a read access.')
r += form.render()
return r.getvalue()
def information(self):
form = Form(enctype='multipart/form-data')
form.add(WysiwygTextWidget, 'description', title=_('Description'),
value=self.formdef.description)
form.add(StringWidget, 'keywords', title=_('Keywords'),
value=self.formdef.keywords, size=50)
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 field_name in ('description', 'keywords'):
widget = form.get_widget(field_name)
if widget:
setattr(self.formdef, field_name, widget.parse())
self.formdef.store()
return redirect('.')
get_response().breadcrumb.append( ('information', _('Information')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Information')
r += form.render()
return r.getvalue()
def options(self):
form = Form(enctype='multipart/form-data')
form.add(CheckboxWidget, 'confirmation', title=_('Include confirmation page'),
value=self.formdef.confirmation)
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, 'enable_tracking_codes',
title=_('Enable support for tracking codes'),
value=self.formdef.enable_tracking_codes)
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(DateTimeWidget, 'publication_date',
title=_('Publication Date'),
value=self.formdef.publication_date)
form.add(DateTimeWidget, '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', 'only_allow_one', 'disabled',
'enable_tracking_codes', '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()
return redirect('.')
get_response().breadcrumb.append( ('options', _('Options')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Options')
r += form.render()
return r.getvalue()
def workflow(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')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Workflow')
r += htmltext('<p>%s</p>') % _('Select the workflow that will handle those forms.')
r += form.render()
return r.getvalue()
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()
# instruct formdef to update its security rules
self.formdef.data_class().rebuild_security()
return redirect('.')
def workflow_status_remapping(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')) )
self.html_top(title=self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<p>')
r += _('From %(here)s to %(there)s') % {'here': self.formdef.workflow.name,
'there': new_workflow.name}
r += htmltext('</p>')
r += form.render()
return r.getvalue()
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 = {'wf-draft': 'wf-draft'}
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(self):
form = Form(action='#', use_tokens=False)
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>'))
r = TemplateIO(html=True)
r += htmltext('<div class="form-preview">')
r += form.render()
r += htmltext('</div>')
return r.getvalue()
def duplicate(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.table_name = None
self.formdefui.formdef.disabled = True
self.formdefui.formdef.store()
return redirect('../%s/' % self.formdefui.formdef.id)
def delete(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('delete', _('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')))
self.html_top(title = _('Delete Form'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Form:'), self.formdef.name)
r += form.render()
return r.getvalue()
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 overwrite(self):
form = Form(enctype='multipart/form-data', use_tokens=False)
form.add(FileWidget, 'file', title=_('File'), required=False)
form.add(UrlWidget, 'url', title=_('Address'), required=False, size=50)
form.add_hidden('new_formdef', required=False)
form.add_hidden('force', required=False)
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():
try:
return self.overwrite_submit(form)
except ValueError:
pass
get_response().breadcrumb.append( ('overwrite', _('Overwrite')) )
self.html_top(title = _('Overwrite'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Overwrite')
r += htmltext('<p>%s</p>') % _(
'You can replace this new form by uploading a file '\
'or by pointing to a form URL.')
r += form.render()
return r.getvalue()
def overwrite_submit(self, form):
if form.get_widget('file').parse():
fp = form.get_widget('file').parse().fp
elif form.get_widget('new_formdef').parse():
fp = StringIO(form.get_widget('new_formdef').parse())
elif form.get_widget('url').parse():
url = form.get_widget('url').parse()
try:
fp = urllib2.urlopen(url)
except urllib2.HTTPError as e:
form.set_error('url', _('Error loading form (%s).') % str(e))
raise ValueError()
except urllib2.URLError as e:
form.set_error('url', _('Error loading form (%s).') % str(e))
raise ValueError()
else:
form.set_error('file', _('You have to enter a file or a URL.'))
raise ValueError()
error, reason = False, None
try:
new_formdef = FormDef.import_from_xml(fp, include_id=True)
except FormdefImportError, e:
error = True
reason = _(e)
except ValueError:
error = True
if error:
if reason:
msg = _('Invalid File (%s)') % reason
else:
msg = _('Invalid File')
if form.get_widget('url').parse():
form.set_error('url', msg)
else:
form.set_error('file', msg)
raise ValueError()
if form.get_widget('new_formdef').parse():
# it's been through the summary page.
if form.get_widget('force').parse():
# doing it!
self.overwrite_by_formdef(new_formdef)
return redirect('.')
# 1. map field id and types
current_fields = {}
new_fields = {}
for field in self.formdef.fields:
current_fields[field.id] = field.type
for field in new_formdef.fields:
new_fields[field.id] = field.type
# 2. compare with current fields
removed_fields = []
different_type_fields = []
for field_id, field_type in current_fields.items():
if field_id not in new_fields:
removed_fields.append(field_id)
elif new_fields.get(field_id) != field_type:
different_type_fields.append(field_id)
if removed_fields or different_type_fields:
return self.overwrite_warning_summary(new_formdef,
removed_fields, different_type_fields)
self.overwrite_by_formdef(new_formdef)
return redirect('.')
def overwrite_by_formdef(self, new_formdef):
current_id = self.formdef.id
self.formdef = new_formdef
self.formdef.id = current_id
self.formdef.store()
def overwrite_warning_summary(self, new_formdef, removed_fields, different_type_fields):
self.html_top(title = _('Overwrite'))
get_response().breadcrumb.append( ('overwrite', _('Overwrite')) )
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Overwrite')
r += htmltext('<h3>%s</h3>') % _('Summary of changes')
r += htmltext('<p>%s</p>') % _(
'The form removes and changes fields, you should review the '
'changes carefully.')
current_fields_list = [str(x.id) for x in self.formdef.fields]
new_fields_list = [str(x.id) for x in new_formdef.fields]
current_fields = {}
new_fields = {}
for field in self.formdef.fields:
current_fields[field.id] = field
for field in new_formdef.fields:
new_fields[field.id] = field
r += htmltext('<div id="form-diff">')
r += htmltext('<div>')
r += htmltext('<table id="table-diff">')
def ellipsize_html(label):
unhtmled_label = re.sub('<.*?>', ' ', label)
return misc.ellipsize(unhtmled_label, 60)
for diffinfo in difflib.ndiff(current_fields_list, new_fields_list):
if diffinfo[0] == '?':
# detail line, ignored
continue
field_id = diffinfo[2:].split()[0]
if diffinfo[0] == ' ':
# unchanged line
label1 = ellipsize_html(current_fields.get(field_id).label)
label2 = ellipsize_html(new_fields.get(field_id).label)
if current_fields.get(field_id) and new_fields.get(field_id) and \
current_fields.get(field_id).type != new_fields.get(field_id).type:
r += htmltext('<tr class="type-change"><td class="indicator">!</td>')
if current_fields.get(field_id) and new_fields.get(field_id) and \
ET.tostring(current_fields.get(field_id).export_to_xml('utf-8')) != \
ET.tostring(new_fields.get(field_id).export_to_xml('utf-8')):
r += htmltext('<tr class="modified-field"><td class="indicator">~</td>')
else:
r += htmltext('<tr><td class="indicator"></td>')
r += htmltext('<td>%s</td> <td>%s</td></tr>') % (label1, label2)
elif diffinfo[0] == '-':
# removed field
label1 = ellipsize_html(current_fields.get(field_id).label)
if current_fields.get(field_id) and new_fields.get(field_id) and \
current_fields.get(field_id).type != new_fields.get(field_id).type:
r += htmltext('<tr class="type-change"><td class="indicator">!</td>')
else:
r += htmltext('<tr class="removed-field"><td class="indicator">-</td>')
r += htmltext('<td>%s</td> <td></td></tr>') % label1
elif diffinfo[0] == '+':
# added field
label2 = ellipsize_html(new_fields.get(field_id).label)
if current_fields.get(field_id) and new_fields.get(field_id) and \
current_fields.get(field_id).type != new_fields.get(field_id).type:
r += htmltext('<tr class="type-change"><td class="indicator">!</td>')
else:
r += htmltext('<tr class="added-field"><td class="indicator">+</td>')
r += htmltext('<td></td> <td>%s</td></tr>') % label2
r += htmltext('</table>')
r += htmltext('</div>')
r += htmltext('<div id="legend">')
r += htmltext('<table>')
r += htmltext('<tr class="added-field"><td class="indicator">+</td><td>%s</td></tr>') % (
_('Added field'))
r += htmltext('</table>')
r += htmltext('<table>')
r += htmltext('<tr class="removed-field"><td class="indicator">-</td><td>%s</td></tr>') % (
_('Removed field'))
r += htmltext('</table>')
r += htmltext('<table>')
r += htmltext('<tr class="modified-field"><td class="indicator">~</td><td>%s</td></tr>') % (
_('Modified field'))
r += htmltext('<table>')
r += htmltext('<tr class="type-change"><td class="indicator">!</td><td>%s</td></tr>') % (
_('Incompatible field'))
r += htmltext('</table>')
r += htmltext('</div>') # .legend
r += htmltext('</div>')
get_request().method = 'GET'
get_request().form = {}
form = Form(enctype='multipart/form-data', use_tokens=False)
if different_type_fields:
form.widgets.append(HtmlWidget('<div class="errornotice"><p>%s</p></div>' % _(
'The form has incompatible fields, it may cause data corruption and bugs.')))
form.add(CheckboxWidget, 'force', title=_('Overwrite nevertheless'))
else:
form.add_hidden('force', 'ok')
form.add_hidden('new_formdef', ET.tostring(new_formdef.export_to_xml(include_id=True)))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
r += form.render()
return r.getvalue()
def export(self):
x = self.formdef.export_to_xml(include_id=True)
misc.indent_xml(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(self):
if get_publisher().is_using_postgresql():
raise TraversalError()
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')))
self.html_top(title = _('Archive Forms'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Archive Forms')
r += form.render()
return r.getvalue()
else:
return self.archive_submit(form)
def archive_submit(self, form):
class Archiver(object):
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(self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('.')
self.html_top(title=_('Archiving'))
r = TemplateIO(html=True)
r += get_session().display_message()
get_response().add_javascript(['jquery.js', 'afterjob.js'])
r += htmltext('<dl class="job-status">')
r += htmltext('<dt>')
r += _(job.label)
r += htmltext('</dt>')
r += htmltext('<dd>')
r += htmltext('<span class="afterjob" id="%s">') % job.id
r += _(job.status)
r += htmltext('</span>')
r += htmltext('</dd>')
r += htmltext('</dl>')
r += htmltext('<div class="done">')
r += htmltext('<a href="archive?download=%s">%s</a>') % (job.id, _('Download Archive'))
r += htmltext('</div>')
return r.getvalue()
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 anonymise(self):
if get_request().form.get('job'):
return self.anonymise_processing()
endpoints = []
for status in self.formdef.workflow.get_endpoint_status():
endpoints.append((status.id, status.name))
form = Form(enctype='multipart/form-data')
form.add(DateWidget, 'before_request_date',
title=_('Forms ended before'),
value=datetime.date.today() - datetime.timedelta(30),
required=True)
form.add(CheckboxesWidget, 'endpoints', title=_('Status of the forms to anonymise'),
value=[endpoint[0] for endpoint in endpoints],
elements=endpoints,
inline=False,
required=True)
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(('anonymise', _('Anonymise')))
html_top('forms', title=_('Anonymise Forms'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Anonymise Forms')
r += htmltext('<p>%s</p>' % _("You are about to irrevocably anonymise forms."))
r += form.render()
return r.getvalue()
else:
return self.anonymise_submit(form)
def anonymise_submit(self, form):
class Anonymiser(object):
def __init__(self, formdef, status_ids, before_date):
self.formdef = formdef
self.status_ids = ["wf-%s" % id for id in status_ids]
self.before_date = before_date
def anonymise(self, job=None):
for formdata in self.formdef.data_class().select():
if formdata.anonymised:
continue
if formdata.status not in self.status_ids:
continue
if (formdata.evolution and formdata.evolution[-1].time >= self.before_date) \
or (formdata.receipt_time >= self.before_date):
continue
formdata.anonymise()
before_date = form.get_widget('before_request_date').parse()
before_date = time.strptime(before_date, misc.date_format())
status_ids = form.get_widget('endpoints').parse()
count = self.formdef.data_class().count()
anonymiser = Anonymiser(self.formdef, status_ids, before_date)
if count > 100: # Arbitrary threshold
job = get_response().add_after_job(
str(N_('Anonymising forms')),
anonymiser.anonymise)
return redirect('anonymise?job=%s' % job.id)
else:
anonymiser.anonymise()
return redirect('.')
def anonymise_processing(self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('.')
html_top('forms', title=_('Anonymising'))
r = TemplateIO(html=True)
r += get_session().display_message()
get_response().add_javascript(['jquery.js', 'afterjob.js'])
r += htmltext('<dl class="job-status">')
r += htmltext('<dt>')
r += _(job.label)
r += htmltext('</dt>')
r += htmltext('<dd>')
r += htmltext('<span class="afterjob" id="%s">') % job.id
r += _(job.status)
r += htmltext('</span>')
r += htmltext('</dd>')
r += htmltext('</dl>')
r += htmltext('<div class="done">')
r += htmltext('<a href="./">%s</a>') % _('Back')
r += htmltext('</div>')
return r.getvalue()
def invite(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')))
self.html_top(title = _('Invitation to Form'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Invitation to "%s" Form') % self.formdef.name
r += htmltext('<p>')
r += _('''This action allows you to send an invitation to all users
concerned by this form.''')
r += htmltext('</p>')
if len(allowed_users) == 0:
r += htmltext('<p>')
r += _('There is nobody to receive an invite.')
r += htmltext('</p>')
else:
r += htmltext('<p>')
r += _('The invitation will be sent to %d persons.') % len(allowed_users)
r += htmltext('</p>')
r += form.render()
return r.getvalue()
def invite_submit(self, form, allowed_users):
class InvitationSender(object):
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(self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('..')
self.html_top(title = _('Invitation to Form'))
r = TemplateIO(html=True)
r += get_session().display_message()
get_response().add_javascript(['jquery.js', 'afterjob.js'])
r += htmltext('<dl class="job-status">')
r += htmltext('<dt>')
r += _(job.label)
r += htmltext('</dt>')
r += htmltext('<dd>')
r += htmltext('<span class="afterjob" id="%s">') % job.id
r += _(job.status)
r += htmltext('</span>')
r += htmltext('</dd>')
r += htmltext('</dl>')
r += htmltext('<div class="done">')
r += htmltext('<a href="./">%s</a>') % _('Back')
r += htmltext('</div>')
return r.getvalue()
def enable(self):
self.formdef.disabled = False
self.formdef.store()
if get_request().form.get('back') == 'fields':
return redirect('fields/')
return redirect('.')
def workflow_variables(self):
self.html_top(title=_('Options'))
form = Form(enctype='multipart/form-data')
self.formdef.workflow.variables_formdef.add_fields_to_form(form,
form_data=self.formdef.get_variable_options_for_form())
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.set_variable_options(form)
self.formdef.store()
return redirect('.')
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Options')
r += form.render()
return r.getvalue()
def workflow_options(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()
self.html_top(title = _('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'))
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
self.workflow_options_submit(form)
return redirect('.')
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Workflow Options')
r += form.render()
return r.getvalue()
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(AccessControlled, Directory):
_q_exports = ['', 'new', ('import', 'p_import')]
formdef_page_class = FormDefPage
def html_top(self, title):
return html_top('forms', title)
def _q_index(self):
get_response().breadcrumb.append( ('forms/', _('Forms')) )
self.html_top(title = _('Forms'))
r = TemplateIO(html=True)
get_response().add_javascript(['jquery.js', 'widget_list.js'])
if Role.count():
get_response().filter['sidebar'] = self.get_sidebar()
else:
r += htmltext('<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 str(x.category_id) == str(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:
r += 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]
r += self.form_list(l2, title = title)
return r.getvalue()
def get_sidebar(self):
r = TemplateIO(html=True)
r += htmltext('<ul id="sidebar-actions">')
r += htmltext(' <li><a class="new-item" href="new" rel="popup">%s</a></li>') % _('New Form')
r += htmltext(' <li><a href="import" rel="popup">%s</a></li>') % _('Import')
r += htmltext('</ul>')
return r.getvalue()
def form_list(self, formdefs, title):
r = TemplateIO(html=True)
r += htmltext('<div class="bo-block">')
if title:
r += htmltext('<h2>%s</h2>') % title
r += htmltext('<ul class="biglist">')
for formdef in formdefs:
if formdef.disabled:
r += htmltext('<li class="disabled">')
else:
r += htmltext('<li>')
r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (formdef.id, formdef.name)
r += htmltext('<p class="details">')
if formdef.disabled and formdef.disabled_redirection:
r += htmltext('(<a href="%s">') % formdef.disabled_redirection
r += _('redirection')
r += htmltext('</a>) ')
r += htmltext('</p>')
r += htmltext('</li>')
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def new(self):
get_response().breadcrumb.append( ('forms/', _('Forms')) )
get_response().breadcrumb.append( ('new', _('New')) )
if Role.count() == 0:
return template.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('.')
if form.is_submitted() and not form.has_errors():
try:
formdef = formdefui.submit_form(form)
formdef.disabled = True
formdef.store()
except ValueError:
pass
else:
return redirect(str(formdef.id) + '/')
self.html_top(title = _('New Form'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('New Form')
r += form.render()
return r.getvalue()
def _q_lookup(self, component):
get_response().breadcrumb.append( ('forms/', _('Forms')) )
return self.formdef_page_class(component)
def p_import(self):
form = Form(enctype = 'multipart/form-data')
form.add(FileWidget, 'file', title=_('File'), required=False)
form.add(UrlWidget, 'url', title=_('Address'), required=False,
size=50)
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:
pass
get_response().breadcrumb.append( ('forms/', _('Forms')) )
get_response().breadcrumb.append( ('import', _('Import')) )
self.html_top(title = _('Import Form'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Import Form')
r += htmltext('<p>%s</p>') % _(
'You can install a new form by uploading a file '\
'or by pointing to the form URL.')
r += form.render()
return r.getvalue()
def import_submit(self, form):
if form.get_widget('file').parse():
fp = form.get_widget('file').parse().fp
elif form.get_widget('url').parse():
url = form.get_widget('url').parse()
try:
fp = urllib2.urlopen(url)
except urllib2.HTTPError as e:
form.set_error('url', _('Error loading form (%s).') % str(e))
raise ValueError()
except urllib2.URLError as e:
form.set_error('url', _('Error loading form (%s).') % str(e))
raise ValueError()
else:
form.set_error('file', _('You have to enter a file or a URL.'))
raise ValueError()
error, reason = False, None
try:
formdef = FormDef.import_from_xml(fp)
except FormdefImportError, e:
error = True
reason = _(e)
except ValueError:
error = True
if error:
if reason:
msg = _('Invalid File (%s)') % reason
else:
msg = _('Invalid File')
if form.get_widget('url').parse():
form.set_error('url', msg)
else:
form.set_error('file', msg)
raise ValueError()
formdef.disabled = True
formdef.store()
get_session().message = ('info',
_('This form has been successfully imported. '\
'Do note it is disabled by default.'))
return redirect('%s/' % formdef.id)