This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
asec/extra/modules/boforms.py

418 lines
16 KiB
Python

# w.c.s. (asec) - w.c.s. extension for poll & survey service
# Copyright (C) 2010-2011 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib
import csv
import re
from sets import Set
from quixote import get_publisher, get_request, get_response, get_session, redirect
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext
from qommon.backoffice.menu import html_top
from qommon.admin.menu import command_icon
from qommon.form import *
from qommon.tokens import Token
from qommon import errors, get_cfg, get_logger
from qommon.afterjobs import AfterJob
import qommon.storage
from qommon import emails
from wcs.formdef import FormDef, FormField
from wcs.roles import Role
from wcs.users import User
from qommon.ident.password import make_password
from qommon.ident.password_accounts import PasswordAccount
from wcs.workflows import Workflow
from wcs.admin.fields import FieldWidget, FieldDefPage, FieldsDirectory
from bodiffusion import DiffusionDirectory
from boresults import FormResultDirectory
import quota
class CustomFieldDefPage(FieldDefPage):
section = 'forms/'
wsf_support = False
def html_top(self, *args, **kwargs):
html_top('forms/%s' % self.objectdef.id, *args, **kwargs)
def duplicate(self):
if not quota.may_add_a_new_field(self.objectdef):
raise quota.QuotaExceeded()
t = FieldDefPage.duplicate(self)
if get_response().status_code == 302:
return redirect('..')
return t
def delete(self):
t = FieldDefPage.delete(self)
if get_response().status_code == 302:
return redirect('..')
return t
class FormDirectory(FieldsDirectory):
_q_exports = ['', 'update_order', 'new', 'title', 'delete', 'options',
'clean',
('anonymity-on', 'anonymity_on'), ('anonymity-off', 'anonymity_off'),
('status-soonavailable', 'status_soonavailable'),
('status-running', 'status_running'),
('status-closed', 'status_closed'),
]
section = 'forms'
field_def_page_class = CustomFieldDefPage
support_import = False
blacklisted_types = ['file']
def html_top(self, *args, **kwargs):
html_top('forms/%s' % self.objectdef.id, *args, **kwargs)
def __init__(self, objectdef):
self.objectdef = objectdef
get_cfg('misc', {})['sitename'] = '%s - %s' % (get_cfg('misc', {}).get('sitename'), self.objectdef.name)
get_response().filter['objectdef'] = self.objectdef
if not hasattr(self.objectdef, 'asec_status'):
self.objectdef.asec_status = 'running'
def title(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'name', title=_('Title'), size=40,
required=True, value=self.objectdef.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():
try:
return self.title_submit(form)
except ValueError:
pass
get_response().breadcrumb.append( ('forms/', None) )
get_response().breadcrumb.append( ('new', _('New Questionnaire')) )
html_top('forms', _('Change Title'))
return form.render()
def title_submit(self, form):
self.objectdef.name = form.get_widget('name').parse()
self.objectdef.store()
return redirect('.')
def delete(self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably delete this questionnaire.')))
# XXX: add a checkbox to ask about the removal of submitted data
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('forms', title = _('Delete Questionnaire'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Questionnaire:'), self.objectdef.name)
r += form.render()
return r.getvalue()
else:
# XXX: should also remove participants role, and participants to no
# other forms
self.objectdef.remove_self()
return redirect('..')
def _q_index(self):
if self.objectdef.asec_status == 'soon-available':
t = super(FormDirectory, self)._q_index()
self.html_top(self.objectdef.name)
return t
return self._q_index_view()
def _q_index_view(self):
self.html_top(self.objectdef.name)
get_response().add_javascript(['jquery.js', 'interface.js', 'biglist.js'])
r = TemplateIO(html=True)
r += self.index_top()
form = self.get_preview_form()
r += htmltext('<div class="form-preview bo-block">')
r += form.render()
r += htmltext('</div>')
get_response().filter['sidebar'] = self.get_results_sidebar()
return r.getvalue()
def get_results_sidebar(self):
r = TemplateIO(html=True)
if self.objectdef.asec_status == 'running':
r += htmltext('<h3>%s</h3>') % _('Visualisation & export of preliminary results')
else:
r += htmltext('<h3>%s</h3>') % _('Visualisation & export of results')
r += htmltext('<ul>')
r += htmltext(' <li><a href="results/">%s</a></li>') % _('Statistics')
r += htmltext(' <li><a href="results/csv">%s</a></li>') % _('CSV Export')
if str(self.objectdef.workflow_id).endswith(str('+anonymous')):
r += htmltext(' <li><a href="results/participation">%s</a>') % _('Participation')
r += htmltext('</ul>')
return r.getvalue()
def get_preview_form(self):
form = Form()
on_page = 0
for i, field in enumerate(self.objectdef.fields):
field.id = i
if hasattr(field, 'add_to_form'):
field.add_to_form(form)
else:
if field.key == 'page':
if on_page:
form.widgets.append(HtmlWidget(htmltext('</fieldset>')))
form.widgets.append(HtmlWidget(htmltext('<fieldset class="formpage">')))
on_page += 1
form.widgets.append(HtmlWidget(
htmltext('<legend>%s</legend>') % _('Page %s') % on_page))
elif field.key == 'title':
form.widgets.append(HtmlWidget(
htmltext('<h3>%s</h3>') % field.label))
elif field.key == 'subtitle':
form.widgets.append(HtmlWidget(
htmltext('<h4>%s</h4>') % field.label))
elif field.key == 'comment':
form.widgets.append(HtmlWidget(
htmltext('<p>%s</>') % field.label))
if on_page:
form.widgets.append(HtmlWidget(htmltext('</fieldset')))
return form
def new(self):
if quota.may_add_a_new_field(self.objectdef):
return super(FormDirectory, self).new()
else:
raise quota.QuotaExceeded()
def index_top(self):
r = TemplateIO(html=True)
r += htmltext('<ul id="main-actions">')
if self.objectdef.asec_status == 'soon-available':
r += htmltext('<li><a href="title" rel="popup">%s</a></li>') % _('Change Title')
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
r += htmltext('</ul>')
r += htmltext('<h2>%s</h2>') % _('Design')
r += get_session().display_message()
r += htmltext('<ul>')
if self.objectdef.roles:
r += htmltext('<li>%s ') % _('Anonymity:')
if str(self.objectdef.workflow_id).endswith(str('+anonymous')):
r += htmltext('<strong>%s</strong>') % _('Enabled')
r += ' - '
r += htmltext('<a href="anonymity-off">%s</a>') % _('Disabled')
else:
r += htmltext('<a href="anonymity-on">%s</a>') % _('Enabled')
r += ' - '
r += htmltext('<strong>%s</strong>') % _('Disabled')
r += htmltext('</li>')
if not self.objectdef.disabled:
r += htmltext(' <li>%s ') % _('Current Status:')
if self.objectdef.asec_status == 'soon-available':
r += htmltext('<strong>%s</strong>') % _('Soon Available')
else:
r += htmltext('<a href="status-soonavailable">%s</a> ') % _('Soon Available')
r += ' - '
if self.objectdef.asec_status == 'running':
r += htmltext('<strong>%s</strong>') % _('Running')
else:
if self.objectdef.fields:
r += htmltext('<a href="status-running">%s</a> ') % _('Running')
else:
r += _('Running')
r += ' - '
if self.objectdef.asec_status == 'closed':
r += htmltext('<strong>%s</strong>') % _('Closed')
else:
if self.objectdef.fields:
r += htmltext('<a href="status-closed">%s</a> ') % _('Closed')
else:
r += _('Closed')
r += htmltext('</li>')
r += htmltext('</ul>')
if not self.objectdef.fields:
r += htmltext('<p>')
r += _('There are not yet any fields for this questionnaire.')
r += htmltext('</p>')
r += htmltext('<p>')
r += _('You should use the controls at the right of the page to add fields.')
r += htmltext('</p>')
r += htmltext('<h3>%s</h3>') % _('Contents of your questionnaire')
return r.getvalue()
def get_new_field_form(self, page_no):
r = TemplateIO(html=True)
if not self.objectdef.disabled:
if not hasattr(self.objectdef, str('asec_status')):
self.objectdef.asec_status = 'running'
if self.objectdef.asec_status == 'soon-available':
if self.objectdef.data_class().keys():
r += htmltext('<p>')
r += htmltext('<strong>%s</strong> ') % _('Warning:')
r += _('This form already contains submitted items.')
r += ' '
r += htmltext('<a href="clean" rel="popup">%s</a>') % _('Remove Them')
r += htmltext('</p>')
if quota.may_add_a_new_field(self.objectdef):
r += super(FormDirectory, self).get_new_field_form(page_no)
else:
r += htmltext('<p>')
r += _('This questionnaire has reached its number of questions quota.')
r += htmltext('</p>')
return r.getvalue()
def clean(self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably remove all submitted questionnaires.')))
form.add_submit('submit', _('Remove'))
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.clean_submit()
except ValueError:
pass
html_top('forms', _('Remove submitted questionnaires'))
return form.render()
def clean_submit(self):
get_logger().info('form %s - wiped submitted form' % self.objectdef.id)
self.objectdef.data_class().wipe()
return redirect('.')
def anonymity_on(self):
if self.objectdef.workflow_id.startswith('asec-'):
self.objectdef.workflow_id += '+anonymous'
else:
self.objectdef.workflow_id = 'asec-default+anonymous'
self.objectdef.store()
return redirect('.')
def anonymity_off(self):
self.objectdef.workflow_id = self.objectdef.workflow_id.replace('+anonymous', '')
self.objectdef.store()
return redirect('.')
def _q_traverse(self, path):
get_response().breadcrumb.append(('%s/' % self.objectdef.id, self.objectdef.name))
return Directory._q_traverse(self, path)
def status_soonavailable(self):
self.objectdef.asec_status = 'soon-available'
self.objectdef.store()
get_logger().info('form %s - set status to %s' % (
self.objectdef.id, self.objectdef.asec_status))
return redirect('.')
def status_running(self):
self.objectdef.asec_status = 'running'
self.objectdef.store()
get_logger().info('form %s - set status to %s' % (
self.objectdef.id, self.objectdef.asec_status))
return redirect('.')
def status_closed(self):
self.objectdef.asec_status = 'closed'
self.objectdef.store()
get_logger().info('form %s - set status to %s' % (
self.objectdef.id, self.objectdef.asec_status))
return redirect('.')
def options(self):
form = Form(enctype='multipart/form-data')
if self.objectdef.roles: # list of participants -> access codes
form.add(CheckboxWidget, 'access_as_password',
title=_('Access code entry as password'),
value=self.objectdef.access_as_password)
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.options_submit(form)
except ValueError:
pass
get_response().breadcrumb.append( ('forms/', None) )
get_response().breadcrumb.append( ('options', _('Options')) )
html_top('forms', _('Options'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Options')
r += form.render()
return r.getvalue()
def options_submit(self, form):
for widget in ('access_as_password',):
if not form.get_widget(widget):
continue
setattr(self.objectdef, widget, form.get_widget(widget).parse())
self.objectdef.store()
return redirect('.')
def _q_lookup(self, component):
if component == 'diffusion':
get_response().breadcrumb.append( ('diffusion/', _('Diffusion')) )
return DiffusionDirectory(self.objectdef)
if component == 'results':
get_response().breadcrumb.append( ('results/', _('Results')) )
return FormResultDirectory(self.objectdef)
return FieldsDirectory._q_lookup(self, component)
class FormsDirectory(Directory):
_q_exports = ['']
def _q_index(self):
return redirect('..')
def _q_lookup(self, component):
try:
formdef = FormDef.get(component)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append( ('forms/', None) )
return FormDirectory(formdef)