# 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 . 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('') r += htmltext('
') r += htmltext('

%s

') % last_page had_page = True last_page = None if last_title: r += htmltext('

%s

') % last_title last_title = None r += t if had_page: r += htmltext('
') 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('
') r += htmltext('

%s

') % _('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(' %s') % _('complete listing') r += htmltext('
') 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('

') r += _('No ballot has been found with such a verification code.') r += htmltext('

') 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('

') r += _('The following ballot has been recorded:') r += htmltext('

') r += form_status.receipt(show_status=False, show_signature=False, form_url=None) break else: r += htmltext('

') r += _('No ballot has been found with such a verification code.') r += htmltext('

') 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('') 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('
') r += TextsDirectory.get_html_text('asec-soon-available') r += htmltext('
') 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('
') r += get_session().display_message() r += htmltext('
') 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('
') r += htmltext('
') r += htmltext('

%s

') % _('Lost or forgotten access code?') r += htmltext('

') r += _('Please enter your e-mail address to get a new access code sent to you.') r += htmltext('

') r += form_reminder.render() r += htmltext('
') r += htmltext('
') 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_('''

It appears you already recorded a response.

''')) TextsDirectory.register('asec-please-identify', N_('Message for identification'), default=N_('''

Please login using the informations that were sent to you by e-mail.

''')) TextsDirectory.register('asec-soon-available', N_('Message when the questionnaire is still in preparation'), default=N_('''

This questionnaire will soon be available.

''')) 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_('''

This site has expired. This questionnaire is no longer available.

'''))