1577 lines
65 KiB
Python
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)
|