2010-05-08 21:38:14 +02:00
|
|
|
# w.c.s. (asec) - w.c.s. extension for poll & survey service
|
2011-05-02 10:08:59 +02:00
|
|
|
# Copyright (C) 2010-2011 Entr'ouvert
|
2010-05-08 21:38:14 +02:00
|
|
|
#
|
2011-05-02 10:08:59 +02:00
|
|
|
# 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.
|
2010-05-08 21:38:14 +02:00
|
|
|
#
|
2011-05-02 10:08:59 +02:00
|
|
|
# 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.
|
2010-05-08 21:38:14 +02:00
|
|
|
#
|
2011-05-02 10:08:59 +02:00
|
|
|
# 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/>.
|
2010-05-08 21:38:14 +02:00
|
|
|
|
2010-09-18 15:44:34 +02:00
|
|
|
from quixote import get_request, redirect
|
2010-11-29 15:24:00 +01:00
|
|
|
from quixote.directory import Directory
|
2015-01-08 20:04:42 +01:00
|
|
|
from quixote.html import TemplateIO, htmltext
|
2010-09-19 15:45:44 +02:00
|
|
|
from qommon.admin.texts import TextsDirectory
|
2010-10-27 13:25:53 +02:00
|
|
|
from qommon.admin.emails import EmailsDirectory
|
2010-09-18 15:44:34 +02:00
|
|
|
from qommon import errors
|
2010-10-27 13:25:53 +02:00
|
|
|
from qommon import emails
|
2010-12-13 17:01:14 +01:00
|
|
|
from qommon import get_logger
|
2010-09-18 15:44:34 +02:00
|
|
|
from qommon.form import *
|
2010-05-08 21:38:14 +02:00
|
|
|
import wcs.forms.root
|
2010-10-27 13:25:53 +02:00
|
|
|
from wcs.users import User
|
2010-05-08 21:38:14 +02:00
|
|
|
|
2010-09-18 15:44:34 +02:00
|
|
|
from participanttokens import ParticipantToken
|
2010-05-08 21:38:14 +02:00
|
|
|
|
2011-04-21 15:16:29 +02:00
|
|
|
import quota
|
|
|
|
|
2010-09-18 17:43:17 +02:00
|
|
|
class AlreadyVotedError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2010-11-29 15:24:00 +01:00
|
|
|
class ResultsDirectory(Directory):
|
|
|
|
_q_exports = ['', 'check', 'listing']
|
|
|
|
|
|
|
|
def __init__(self, formdef):
|
|
|
|
self.formdef = formdef
|
|
|
|
|
|
|
|
def _q_access(self):
|
|
|
|
if self.formdef.asec_status != 'closed':
|
|
|
|
raise errors.AccessForbiddenError()
|
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def stats_fields(self, values):
|
|
|
|
r = TemplateIO(html=True)
|
2010-11-29 15:24:00 +01:00
|
|
|
had_page = False
|
|
|
|
last_page = None
|
|
|
|
last_title = None
|
|
|
|
for f in self.formdef.fields:
|
|
|
|
if f.type == 'page':
|
|
|
|
last_page = f.label
|
|
|
|
last_title = None
|
|
|
|
continue
|
|
|
|
if f.type == 'title':
|
|
|
|
last_title = f.label
|
|
|
|
continue
|
|
|
|
if not f.stats:
|
|
|
|
continue
|
|
|
|
t = f.stats(values)
|
|
|
|
if not t:
|
|
|
|
continue
|
|
|
|
if last_page:
|
|
|
|
if had_page:
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('</div>')
|
|
|
|
r += htmltext('<div class="page">')
|
|
|
|
r += htmltext('<h3>%s</h3>') % last_page
|
2010-11-29 15:24:00 +01:00
|
|
|
had_page = True
|
|
|
|
last_page = None
|
|
|
|
if last_title:
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<h3>%s</h3>') % last_title
|
2010-11-29 15:24:00 +01:00
|
|
|
last_title = None
|
2015-01-08 20:04:42 +01:00
|
|
|
r += t
|
2010-11-29 15:24:00 +01:00
|
|
|
|
|
|
|
if had_page:
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('</div>')
|
|
|
|
return r.getvalue()
|
2010-11-29 15:24:00 +01:00
|
|
|
|
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def _q_index(self):
|
2010-11-29 15:24:00 +01:00
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
2015-01-08 20:04:42 +01:00
|
|
|
r = TemplateIO(html=True)
|
2010-11-29 15:24:00 +01:00
|
|
|
|
|
|
|
# XXX: need an option to decide if we want results to be displayed
|
|
|
|
#values = self.formdef.data_class().select()
|
|
|
|
#self.stats_fields(values)
|
|
|
|
|
|
|
|
if str(self.formdef.workflow_id).endswith(str('+anonymous')):
|
|
|
|
# Verification Code
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<div class="verification-code">')
|
|
|
|
r += htmltext('<h3>%s</h3>') % _('Anonymous Verification Code Check')
|
2010-11-29 15:24:00 +01:00
|
|
|
form = Form(action='check')
|
|
|
|
form.add(StringWidget, 'code', title=('Verification Code'), required=True)
|
2015-01-08 20:04:42 +01:00
|
|
|
r += _('Enter your verification code to check it against the recorded ballot.')
|
2010-11-29 15:24:00 +01:00
|
|
|
form.add_submit('submit', _('Submit'))
|
2015-01-08 20:04:42 +01:00
|
|
|
r += form.render()
|
|
|
|
r += _('Or search for it in the list of all the ballots:')
|
|
|
|
r += htmltext(' <a href="listing">%s</a>') % _('complete listing')
|
|
|
|
r += htmltext('</div>')
|
|
|
|
return r.getvalue()
|
2010-11-29 15:24:00 +01:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def check(self):
|
2010-11-29 15:24:00 +01:00
|
|
|
if not str(self.formdef.workflow_id).endswith(str('+anonymous')):
|
|
|
|
raise errors.TraversalError()
|
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
|
|
|
form = Form(action='check')
|
|
|
|
form.add(StringWidget, 'code', title=('Verification Code'), required=True)
|
|
|
|
form.add_submit('submit', _('Submit'))
|
|
|
|
if not 'code' in get_request().form:
|
|
|
|
return redirect('.')
|
|
|
|
|
|
|
|
code = get_request().form.get('code')
|
2015-01-08 20:04:42 +01:00
|
|
|
r = TemplateIO(html=True)
|
2010-11-29 15:24:00 +01:00
|
|
|
|
|
|
|
import anonymity
|
|
|
|
if code not in anonymity.get_verification_codes(self.formdef):
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<p>')
|
|
|
|
r += _('No ballot has been found with such a verification code.')
|
|
|
|
r += htmltext('</p>')
|
2010-11-29 15:24:00 +01:00
|
|
|
else:
|
|
|
|
for formdata in self.formdef.data_class().select():
|
2014-11-02 16:17:57 +01:00
|
|
|
if hasattr(formdata, str('verification_code')) and formdata.verification_code == code:
|
2010-11-29 15:24:00 +01:00
|
|
|
form_status = wcs.forms.root.PublicFormStatusPage(self.formdef, formdata)
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<p>')
|
|
|
|
r += _('The following ballot has been recorded:')
|
|
|
|
r += htmltext('</p>')
|
|
|
|
r += form_status.receipt(show_status=False, show_signature=False, form_url=None)
|
2010-11-29 15:24:00 +01:00
|
|
|
break
|
|
|
|
else:
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<p>')
|
|
|
|
r += _('No ballot has been found with such a verification code.')
|
|
|
|
r += htmltext('</p>')
|
|
|
|
return r.getvalue()
|
2010-11-29 15:24:00 +01:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def listing(self):
|
2010-11-29 15:24:00 +01:00
|
|
|
if not str(self.formdef.workflow_id).endswith(str('+anonymous')):
|
|
|
|
raise errors.TraversalError()
|
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
|
|
|
import anonymity
|
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<ul>')
|
2010-11-29 15:24:00 +01:00
|
|
|
for code in anonymity.get_verification_codes(self.formdef):
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<li><a href="check?code=%s">%s</a></li>') % (code, code)
|
|
|
|
r += htmltext('</ul>')
|
|
|
|
return r.getvalue()
|
2010-11-29 15:24:00 +01:00
|
|
|
|
|
|
|
|
2010-05-08 21:38:14 +02:00
|
|
|
class FormPage(wcs.forms.root.FormPage):
|
2010-11-29 15:24:00 +01:00
|
|
|
_q_exports = wcs.forms.root.FormPage._q_exports + ['reminder', 'results']
|
2010-10-27 13:25:53 +02:00
|
|
|
|
2010-05-08 21:38:14 +02:00
|
|
|
def __init__(self, formdef):
|
|
|
|
self.formdef = formdef
|
2010-11-29 15:24:00 +01:00
|
|
|
if not hasattr(self.formdef, str('asec_status')):
|
|
|
|
self.formdef.asec_status = 'running'
|
|
|
|
if self.formdef.asec_status == 'closed':
|
|
|
|
self.results = ResultsDirectory(self.formdef)
|
2010-05-08 21:38:14 +02:00
|
|
|
self.tokens = wcs.forms.root.TokensDirectory(self.formdef)
|
|
|
|
self.page_number = len([x for x in self.formdef.fields[1:] if x.type == 'page']) + 1
|
|
|
|
self.user = get_request().user
|
|
|
|
|
2010-11-29 15:24:00 +01:00
|
|
|
def results(self):
|
|
|
|
raise errors.TraversalError()
|
|
|
|
|
2010-09-18 15:44:34 +02:00
|
|
|
def check_role(self):
|
2010-10-01 11:23:37 +02:00
|
|
|
if get_session().user and str(self.formdef.workflow_id).endswith('+anonymous'):
|
2010-09-18 17:43:17 +02:00
|
|
|
import anonymity
|
|
|
|
if anonymity.has_voted(self.formdef, get_session().user):
|
|
|
|
raise AlreadyVotedError()
|
|
|
|
|
|
|
|
wcs.forms.root.FormPage.check_role(self)
|
2010-09-18 15:44:34 +02:00
|
|
|
|
|
|
|
def _q_index(self, *args, **kwargs):
|
2010-11-29 15:24:00 +01:00
|
|
|
if not self.formdef.disabled:
|
|
|
|
if self.formdef.asec_status == 'closed':
|
|
|
|
return redirect('results/')
|
|
|
|
|
2011-04-21 15:16:29 +02:00
|
|
|
if quota.is_expired():
|
|
|
|
return self.expired()
|
|
|
|
|
2010-11-29 18:03:26 +01:00
|
|
|
if self.formdef.asec_status == 'soon-available':
|
2010-11-29 15:24:00 +01:00
|
|
|
if self.formdef.roles:
|
|
|
|
return self.participant_ask_token()
|
|
|
|
else:
|
2010-11-29 18:03:26 +01:00
|
|
|
return self.soon_available()
|
2010-11-29 15:24:00 +01:00
|
|
|
|
2011-04-21 15:16:29 +02:00
|
|
|
if quota.is_expired():
|
|
|
|
return self.expired()
|
|
|
|
|
2010-09-18 15:44:34 +02:00
|
|
|
try:
|
|
|
|
return wcs.forms.root.FormPage._q_index(self, *args, **kwargs)
|
|
|
|
except (errors.AccessUnauthorizedError, errors.AccessForbiddenError):
|
|
|
|
return self.participant_ask_token()
|
2010-09-18 17:43:17 +02:00
|
|
|
except AlreadyVotedError:
|
|
|
|
return self.already_voted_error()
|
2010-09-18 15:44:34 +02:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def expired(self):
|
2011-04-21 15:16:29 +02:00
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
2015-01-08 20:04:42 +01:00
|
|
|
return TextsDirectory.get_html_text('asec-expired-site-questionnaire')
|
2011-04-21 15:16:29 +02:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def soon_available(self):
|
2010-11-29 15:24:00 +01:00
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
2015-01-08 20:04:42 +01:00
|
|
|
r = TemplateIO(html=True)
|
|
|
|
r += htmltext('<div id="access-asec-identify">')
|
|
|
|
r += TextsDirectory.get_html_text('asec-soon-available')
|
|
|
|
r += htmltext('</div>')
|
|
|
|
return r.getvalue()
|
2010-11-29 15:24:00 +01:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def participant_ask_token(self):
|
2010-09-18 15:44:34 +02:00
|
|
|
form = Form(enctype='multipart/form-data')
|
|
|
|
form.add(EmailWidget, 'email', title=_('Email'), required=True)
|
2011-01-14 15:36:25 +01:00
|
|
|
if self.formdef.access_as_password:
|
|
|
|
form.add(PasswordWidget, 'token', title=_('Access Code'), required=True)
|
|
|
|
else:
|
|
|
|
form.add(StringWidget, 'token', title=_('Access Code'), required=True)
|
2010-09-18 15:44:34 +02:00
|
|
|
|
|
|
|
form.add_submit('submit', _('Submit'))
|
|
|
|
|
2010-11-29 15:24:00 +01:00
|
|
|
if form.is_submitted() and not form.has_errors() and \
|
|
|
|
self.formdef.asec_status == 'running':
|
2010-09-18 15:44:34 +02:00
|
|
|
try:
|
|
|
|
return self.participant_ask_token_submit(form)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
2010-10-27 13:25:53 +02:00
|
|
|
form_reminder = Form(enctype='multipart/form-data', action='reminder')
|
|
|
|
form_reminder.add(EmailWidget, 'email', title=_('Email'), required=True)
|
|
|
|
form_reminder.add_submit('submit', _('Send'))
|
|
|
|
form_reminder.clear_errors()
|
|
|
|
|
2010-09-18 15:44:34 +02:00
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
2015-01-08 20:04:42 +01:00
|
|
|
r = TemplateIO(html=True)
|
2010-10-27 13:25:53 +02:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<div id="access-asec">')
|
|
|
|
r += get_session().display_message()
|
2010-10-27 13:25:53 +02:00
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
r += htmltext('<div id="access-asec-identify">')
|
2010-11-29 18:04:52 +01:00
|
|
|
if self.formdef.asec_status == 'soon-available':
|
2015-01-08 20:04:42 +01:00
|
|
|
r += TextsDirectory.get_html_text('asec-soon-available')
|
2010-11-29 15:24:00 +01:00
|
|
|
else:
|
2015-01-08 20:04:42 +01:00
|
|
|
r += TextsDirectory.get_html_text('asec-please-identify')
|
|
|
|
r += form.render()
|
|
|
|
r += htmltext('</div>')
|
|
|
|
|
|
|
|
r += htmltext('<div id="access-asec-reminder">')
|
|
|
|
r += htmltext('<h3>%s</h3>') % _('Lost or forgotten access code?')
|
|
|
|
r += htmltext('<p>')
|
|
|
|
r += _('Please enter your e-mail address to get a new access code sent to you.')
|
|
|
|
r += htmltext('</p>')
|
|
|
|
r += form_reminder.render()
|
|
|
|
r += htmltext('</div>')
|
|
|
|
r += htmltext('</div> <!-- #access-asec -->')
|
|
|
|
return r.getvalue()
|
2010-09-18 15:44:34 +02:00
|
|
|
|
|
|
|
def participant_ask_token_submit(self, form):
|
|
|
|
email = form.get_widget('email').parse()
|
|
|
|
token = form.get_widget('token').parse()
|
|
|
|
try:
|
|
|
|
participant_token = ParticipantToken.get('%s-%s' % (self.formdef.id, email))
|
|
|
|
except KeyError:
|
|
|
|
form.set_error('email', _('Wrong Credentials'))
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
if participant_token.check(token):
|
|
|
|
get_session().set_user(participant_token.user_id)
|
|
|
|
return redirect('.')
|
|
|
|
else:
|
|
|
|
form.set_error('email', _('Wrong Credentials'))
|
|
|
|
raise ValueError()
|
2010-09-18 17:43:17 +02:00
|
|
|
|
2010-10-27 13:25:53 +02:00
|
|
|
def reminder(self):
|
|
|
|
form_reminder = Form(enctype='multipart/form-data', action='reminder')
|
|
|
|
form_reminder.add(EmailWidget, 'email', title=_('Email'), required=True)
|
|
|
|
email = form_reminder.get_widget('email').parse()
|
|
|
|
|
|
|
|
# We know we are lying, but we do not want to help external persons
|
|
|
|
# check if a given person is registered for the vote, so we use a
|
|
|
|
# unique message in all situations.
|
|
|
|
get_session().message = ('info', _(
|
|
|
|
'An email has been sent. If you do not see it in your '\
|
|
|
|
'mailbox within five minutes you should check the address '\
|
|
|
|
'you used is the address registered for this site. '\
|
|
|
|
'Do not forget to check your Spam folder.'))
|
|
|
|
try:
|
|
|
|
participant_token = ParticipantToken.get('%s-%s' % (self.formdef.id, email))
|
|
|
|
except KeyError:
|
2010-12-13 17:01:14 +01:00
|
|
|
get_logger().info('Access code request for unknown participant (%s)' % email)
|
2010-10-27 13:25:53 +02:00
|
|
|
return redirect('.')
|
|
|
|
|
|
|
|
try:
|
|
|
|
user = User.get(participant_token.user_id)
|
|
|
|
except KeyError:
|
2010-12-13 17:01:14 +01:00
|
|
|
get_logger().info('Access code request for unknown user (%s)' % email)
|
2010-10-27 13:25:53 +02:00
|
|
|
return redirect('.')
|
|
|
|
|
|
|
|
token = participant_token.generate_token()
|
|
|
|
participant_token.store()
|
|
|
|
|
|
|
|
data = {
|
|
|
|
'user_name': user.display_name,
|
|
|
|
'user_email': user.email,
|
2011-04-30 13:37:52 +02:00
|
|
|
'access_code': token,
|
2011-04-17 08:40:17 +02:00
|
|
|
'questionnaire_name': self.formdef.name,
|
|
|
|
'questionnaire_url': self.formdef.get_url(),
|
2010-10-27 13:25:53 +02:00
|
|
|
}
|
|
|
|
|
2010-12-13 17:01:14 +01:00
|
|
|
get_logger().info('Access code request for user (%s)' % email)
|
2010-10-27 13:25:53 +02:00
|
|
|
emails.custom_ezt_email('asec-new-access-code', data,
|
|
|
|
email_rcpt=user.email, fire_and_forget=False,
|
|
|
|
want_html=False)
|
|
|
|
|
|
|
|
return redirect('.')
|
|
|
|
|
2015-01-08 20:04:42 +01:00
|
|
|
def already_voted_error(self):
|
2010-09-18 17:43:17 +02:00
|
|
|
wcs.forms.root.html_top(self.formdef.name)
|
2015-01-08 20:04:42 +01:00
|
|
|
return TextsDirectory.get_html_text('asec-already-voted')
|
2010-09-19 15:45:44 +02:00
|
|
|
|
2010-11-29 15:24:00 +01:00
|
|
|
|
2010-09-19 15:45:44 +02:00
|
|
|
TextsDirectory.register('asec-already-voted',
|
2011-04-30 13:37:52 +02:00
|
|
|
N_('Message when a response was already recorded for the user.'),
|
2010-09-19 15:45:44 +02:00
|
|
|
default=N_('''<p>
|
2011-04-30 13:37:52 +02:00
|
|
|
It appears you already recorded a response.
|
2010-09-19 15:45:44 +02:00
|
|
|
</p>'''))
|
|
|
|
|
2010-09-19 22:31:07 +02:00
|
|
|
TextsDirectory.register('asec-please-identify',
|
|
|
|
N_('Message for identification'),
|
|
|
|
default=N_('''<p>
|
|
|
|
Please login using the informations that were sent to you by e-mail.
|
|
|
|
</p>'''))
|
2010-10-27 13:25:53 +02:00
|
|
|
|
2010-11-29 18:03:26 +01:00
|
|
|
TextsDirectory.register('asec-soon-available',
|
2011-03-14 12:38:53 +01:00
|
|
|
N_('Message when the questionnaire is still in preparation'),
|
2010-11-29 15:24:00 +01:00
|
|
|
default=N_('''<p>
|
2011-03-14 12:38:53 +01:00
|
|
|
This questionnaire will soon be available.
|
2010-11-29 15:24:00 +01:00
|
|
|
</p>'''))
|
|
|
|
|
2010-10-27 13:25:53 +02:00
|
|
|
EmailsDirectory.register('asec-new-access-code',
|
|
|
|
N_('Request for a new access code'),
|
2011-04-30 13:37:52 +02:00
|
|
|
N_('Available variables: user_name, user_email, access_code, questionnaire_name, questionnaire_url'),
|
2011-04-17 08:40:17 +02:00
|
|
|
default_subject=N_('Access code for [questionnaire_name]'),
|
2010-10-27 13:25:53 +02:00
|
|
|
default_body=N_('''\
|
|
|
|
Dear [user_name]
|
|
|
|
|
2011-04-17 08:40:17 +02:00
|
|
|
You requested a new access code for [questionnaire_name]
|
2010-10-27 13:25:53 +02:00
|
|
|
|
|
|
|
E-Mail: [user_email]
|
2011-04-30 13:37:52 +02:00
|
|
|
Access Code: [access_code]
|
2010-10-27 13:25:53 +02:00
|
|
|
|
2011-04-30 13:37:52 +02:00
|
|
|
You can now go back to [questionnaire_url] and give your answer.
|
2010-10-27 13:25:53 +02:00
|
|
|
|
2011-04-30 13:37:52 +02:00
|
|
|
Thank you for your participation!
|
2010-10-27 13:25:53 +02:00
|
|
|
|
|
|
|
Regards,
|
|
|
|
'''))
|
2011-04-21 15:16:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
TextsDirectory.register('asec-expired-site-questionnaire',
|
|
|
|
N_('Text displayed on a questionnaire when the site has expired'),
|
|
|
|
default=N_('''<p>
|
|
|
|
This site has expired. This questionnaire is no longer available.
|
|
|
|
</p>
|
|
|
|
'''))
|