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/forms.py

364 lines
13 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/>.
from quixote import get_request, redirect
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext
from qommon.admin.texts import TextsDirectory
from qommon.admin.emails import EmailsDirectory
from qommon import errors
from qommon import emails
from qommon import get_logger
from qommon.form import *
import wcs.forms.root
from wcs.users import User
from participanttokens import ParticipantToken
import quota
class AlreadyVotedError(Exception):
pass
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()
def stats_fields(self, values):
r = TemplateIO(html=True)
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:
r += htmltext('</div>')
r += htmltext('<div class="page">')
r += htmltext('<h3>%s</h3>') % last_page
had_page = True
last_page = None
if last_title:
r += htmltext('<h3>%s</h3>') % last_title
last_title = None
r += t
if had_page:
r += htmltext('</div>')
return r.getvalue()
def _q_index(self):
wcs.forms.root.html_top(self.formdef.name)
r = TemplateIO(html=True)
# 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
r += htmltext('<div class="verification-code">')
r += htmltext('<h3>%s</h3>') % _('Anonymous Verification Code Check')
form = Form(action='check')
form.add(StringWidget, 'code', title=('Verification Code'), required=True)
r += _('Enter your verification code to check it against the recorded ballot.')
form.add_submit('submit', _('Submit'))
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()
def check(self):
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')
r = TemplateIO(html=True)
import anonymity
if code not in anonymity.get_verification_codes(self.formdef):
r += htmltext('<p>')
r += _('No ballot has been found with such a verification code.')
r += htmltext('</p>')
else:
for formdata in self.formdef.data_class().select():
if hasattr(formdata, str('verification_code')) and formdata.verification_code == code:
form_status = wcs.forms.root.PublicFormStatusPage(self.formdef, formdata)
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)
break
else:
r += htmltext('<p>')
r += _('No ballot has been found with such a verification code.')
r += htmltext('</p>')
return r.getvalue()
def listing(self):
if not str(self.formdef.workflow_id).endswith(str('+anonymous')):
raise errors.TraversalError()
wcs.forms.root.html_top(self.formdef.name)
import anonymity
r += htmltext('<ul>')
for code in anonymity.get_verification_codes(self.formdef):
r += htmltext('<li><a href="check?code=%s">%s</a></li>') % (code, code)
r += htmltext('</ul>')
return r.getvalue()
class FormPage(wcs.forms.root.FormPage):
_q_exports = wcs.forms.root.FormPage._q_exports + ['reminder', 'results']
def __init__(self, formdef):
self.formdef = formdef
if not hasattr(self.formdef, str('asec_status')):
self.formdef.asec_status = 'running'
if self.formdef.asec_status == 'closed':
self.results = ResultsDirectory(self.formdef)
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
def results(self):
raise errors.TraversalError()
def check_role(self):
if get_session().user and str(self.formdef.workflow_id).endswith('+anonymous'):
import anonymity
if anonymity.has_voted(self.formdef, get_session().user):
raise AlreadyVotedError()
wcs.forms.root.FormPage.check_role(self)
def _q_index(self, *args, **kwargs):
if not self.formdef.disabled:
if self.formdef.asec_status == 'closed':
return redirect('results/')
if quota.is_expired():
return self.expired()
if self.formdef.asec_status == 'soon-available':
if self.formdef.roles:
return self.participant_ask_token()
else:
return self.soon_available()
if quota.is_expired():
return self.expired()
try:
return wcs.forms.root.FormPage._q_index(self, *args, **kwargs)
except (errors.AccessUnauthorizedError, errors.AccessForbiddenError):
return self.participant_ask_token()
except AlreadyVotedError:
return self.already_voted_error()
def expired(self):
wcs.forms.root.html_top(self.formdef.name)
return TextsDirectory.get_html_text('asec-expired-site-questionnaire')
def soon_available(self):
wcs.forms.root.html_top(self.formdef.name)
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()
def participant_ask_token(self):
form = Form(enctype='multipart/form-data')
form.add(EmailWidget, 'email', title=_('Email'), required=True)
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)
form.add_submit('submit', _('Submit'))
if form.is_submitted() and not form.has_errors() and \
self.formdef.asec_status == 'running':
try:
return self.participant_ask_token_submit(form)
except ValueError:
pass
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()
wcs.forms.root.html_top(self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<div id="access-asec">')
r += get_session().display_message()
r += htmltext('<div id="access-asec-identify">')
if self.formdef.asec_status == 'soon-available':
r += TextsDirectory.get_html_text('asec-soon-available')
else:
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()
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()
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:
get_logger().info('Access code request for unknown participant (%s)' % email)
return redirect('.')
try:
user = User.get(participant_token.user_id)
except KeyError:
get_logger().info('Access code request for unknown user (%s)' % email)
return redirect('.')
token = participant_token.generate_token()
participant_token.store()
data = {
'user_name': user.display_name,
'user_email': user.email,
'access_code': token,
'questionnaire_name': self.formdef.name,
'questionnaire_url': self.formdef.get_url(),
}
get_logger().info('Access code request for user (%s)' % email)
emails.custom_ezt_email('asec-new-access-code', data,
email_rcpt=user.email, fire_and_forget=False,
want_html=False)
return redirect('.')
def already_voted_error(self):
wcs.forms.root.html_top(self.formdef.name)
return TextsDirectory.get_html_text('asec-already-voted')
TextsDirectory.register('asec-already-voted',
N_('Message when a response was already recorded for the user.'),
default=N_('''<p>
It appears you already recorded a response.
</p>'''))
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>'''))
TextsDirectory.register('asec-soon-available',
N_('Message when the questionnaire is still in preparation'),
default=N_('''<p>
This questionnaire will soon be available.
</p>'''))
EmailsDirectory.register('asec-new-access-code',
N_('Request for a new access code'),
N_('Available variables: user_name, user_email, access_code, questionnaire_name, questionnaire_url'),
default_subject=N_('Access code for [questionnaire_name]'),
default_body=N_('''\
Dear [user_name]
You requested a new access code for [questionnaire_name]
E-Mail: [user_email]
Access Code: [access_code]
You can now go back to [questionnaire_url] and give your answer.
Thank you for your participation!
Regards,
'''))
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>
'''))