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/bodiffusion.ptl

974 lines
35 KiB
Plaintext

# w.c.s. (asec) - w.c.s. extension for poll & survey service
# Copyright (C) 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
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 htmltext
from qommon.backoffice.menu import html_top
from qommon.admin.emails import EmailsDirectory
from qommon.admin.menu import command_icon
from qommon.admin.cfg import cfg_submit
from qommon.form import *
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 participanttokens import ParticipantToken
from boresults import FormResultDirectory
access_mode_labels = {
'disabled': N_('Disabled'),
'public': N_('Public'),
'private': N_('Private'),
'unknown': N_('Unknown')
}
def get_form_access_mode(formdef):
if formdef.disabled:
return 'disabled'
elif formdef.private:
return 'private'
else:
return 'public'
def get_form_url(formdef):
if formdef.private:
req = get_request()
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
urllib.quote(req.environ.get('SCRIPT_NAME')))
private_url = '%s/p/%s/' % (base_url, formdef.private_urlname)
return private_url
else:
return formdef.get_url()
class AccessCodeMailer:
def __init__(self, objectdef, mail_from, mail_subject, mail_body, behaviour):
self.objectdef = objectdef
self.mail_from = mail_from
self.mail_subject = mail_subject
self.mail_body = mail_body
self.behaviour = behaviour
self.questionnaire_url = get_form_url(objectdef)
def __call__(self, job=None):
count = 0
for user in User.select():
for role_id in self.objectdef.roles:
if role_id in (user.roles or []):
break
else:
continue
if ParticipantToken.has_key('%s-%s' % (self.objectdef.id, user.id)):
if self.behaviour == 'skip':
continue
p = ParticipantToken(self.objectdef.id, user.id)
token = p.generate_token()
token = re.sub(r'(....)', r'\1-', token).strip('-')
p.store()
data = {
'user_name': user.display_name,
'user_email': user.email,
'access_code': token,
'questionnaire_name': self.objectdef.name,
'questionnaire_url': self.questionnaire_url,
}
emails.ezt_email(self.mail_subject, self.mail_body, data,
email_rcpt=user.email, fire_and_forget=False,
want_html=False, email_from=self.mail_from)
count += 1
get_logger().info('form %s - done mailing access codes (%s)' % (
self.objectdef.id, count))
class ParticipantsMailer:
def __init__(self, objectdef, mail_from, mail_subject, mail_body, behaviour):
self.objectdef = objectdef
self.mail_from = mail_from
self.mail_subject = mail_subject
self.mail_body = mail_body
self.behaviour = behaviour
self.questionnaire_url = get_form_url(objectdef)
def __call__(self, job=None):
count = 0
if self.behaviour == 'skip':
# it's not possible to use the index file, as it's disabled
# in asec, for the anonymous vote support, so, no:
# self.objectdef.data_class().get_with_indexed_value('user_id', user.id)
# and something not performant at all; but well, it runs in an
# after job so it's not that important...
#
user_ids = {}
if str(self.objectdef.workflow_id).endswith(str('+anonymous')):
import anonymity
for user_id in anonymity.get_voters(self.objectdef):
user_ids[user_id] = True
else:
for object in self.objectdef.data_class().select():
user_ids[object.user_id] = True
for user in User.select():
for role_id in self.objectdef.roles:
if role_id in (user.roles or []):
break
else:
continue
if self.behaviour == 'skip' and user.id in user_ids:
continue
data = {
'user_name': user.display_name,
'user_email': user.email,
'questionnaire_name': self.objectdef.name,
'questionnaire_url': self.questionnaire_url,
}
emails.ezt_email(self.mail_subject, self.mail_body, data,
email_rcpt=user.email, fire_and_forget=False,
want_html=False, email_from=self.mail_from)
count += 1
get_logger().info('form %s - done mailing participants (%s)' % (
self.objectdef.id, count))
class DiffusionDirectory(Directory):
_q_exports = ['', 'add', 'remove', ('import', 'p_import'),
('import-disabled', 'import_disabled'),
('mail-access-codes', 'mail_access_codes'),
('mail-participants', 'mail_participants'),
('access-disabled', 'access_disabled'),
('access-public', 'access_public'),
('access-private', 'access_private'),
'enable', 'disable', 'emailfrom', 'emailnotify']
def __init__(self, objectdef):
self.objectdef = objectdef
def disable(self):
self.objectdef.roles = []
self.objectdef.store()
return redirect('.')
def enable(self):
if not self.objectdef.roles:
self.set_role()
return redirect('.')
def access_disabled(self):
self.objectdef.disabled = True
self.objectdef.store()
return redirect('.')
def access_public(self):
self.objectdef.disabled = False
self.objectdef.private = False
self.objectdef.store()
return redirect('.')
def access_private(self):
self.objectdef.disabled = False
self.objectdef.private = True
self.objectdef.store()
return redirect('.')
def emailfrom [html] (self):
emails_cfg = get_cfg('emails', {})
form = Form(enctype='multipart/form-data')
form.add(EmailWidget, 'from', title=_('Sender Address'),
value=emails_cfg.get('from', ''), size=30)
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( ('emailfrom', _('Sender Address')) )
html_top('config', title = _('Sender Address'))
'<h2>%s</h2>' % _('Sender Address')
form.render()
else:
cfg_submit(form, 'emails', ['from'])
redirect('.')
def emailnotify [html] (self):
emails_cfg = get_cfg('emails', {})
form = Form(enctype='multipart/form-data')
if not self.objectdef.workflow_options:
self.objectdef.workflow_options = {}
form.add(EmailWidget, 'notify', title=_('Address for notifications'),
value=self.objectdef.workflow_options.get('done*mail-on-filled*to'),
hint=_('Leave blank to disable notifications'),
size=30)
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( ('emailfrom', _('Sender Address')) )
html_top('config', title = _('Sender Address'))
'<h2>%s</h2>' % _('Sender Address')
form.render()
else:
v = form.get_widget('notify').parse()
if v:
v = str(v)
else:
v = None
self.objectdef.workflow_options[str('done*mail-on-filled*to')] = v
self.objectdef.store()
redirect('.')
def set_role(self):
try:
role = Role.get('participants-for-%s' % self.objectdef.id)
except KeyError:
role = Role(_('Participants for "%s"') % self.objectdef.name)
role.id = 'participants-for-%s' % self.objectdef.id
role.store()
self.objectdef.roles = [role.id]
self.objectdef.store()
def html_top(self, section, *args, **kwargs):
html_top('forms/%s/diffusion' % self.objectdef.id, *args, **kwargs)
def _q_index [html] (self):
# XXX: it would be nice to be manage groups, to select an existing group
self.html_top(_('Diffusion'))
'<ul id="main-actions">'
'<li><a href="add" rel="popup">%s</a></li>' % _('Add a participant')
'</ul>'
'<h2>%s</h2>' % _('Diffusion')
'<ul>'
' <li>%s ' % _('Participants:')
if self.objectdef.roles:
'<a href="disable">%s</a>' % _('Anybody')
' - '
'<strong>%s</strong>' % _('List of persons')
else:
'<strong>%s</strong>' % _('Anybody')
' - '
' <a href="enable">%s</a>' % _('List of persons')
'</li>'
access_mode = get_form_access_mode(self.objectdef)
' <li>%s ' % _('Questionnaire Address:')
if access_mode == 'public':
'<strong>%s</strong>' % _('Visible')
else:
' <a href="access-public">%s</a>' % _('Visible')
' - '
if access_mode == 'private':
'<strong>%s</strong>' % _('Hidden')
else:
' <a href="access-private">%s</a>' % _('Hidden')
'<ul><li>'
url = get_form_url(self.objectdef)
'<a href="%s">%s</a>' % (url, url)
'</li></ul>'
'</li>'
emails_cfg = get_cfg('emails', {})
'<li>%s' % _('Sender Address: ')
if emails_cfg.get('from', ''):
emails_cfg.get('from')
' (<a href="emailfrom" rel="popup">%s</a>)' % _('change address')
else:
_('None set')
' (<a href="emailfrom" rel="popup">%s</a>)' % _('set address')
'</li>'
'<li>%s' % _('Notification when a questionnaire is filled: ')
if not self.objectdef.workflow_options:
self.objectdef.workflow_options = {}
if self.objectdef.workflow_options.get('done*mail-on-filled*to'):
self.objectdef.workflow_options.get('done*mail-on-filled*to')
' (<a href="emailnotify" rel="popup">%s</a>)' % _('change address')
else:
_('None set')
' (<a href="emailnotify" rel="popup">%s</a>)' % _('set address')
'</li>'
'</ul>'
get_response().filter['sidebar'] = self.get_sidebar()
get_session().display_message()
if self.objectdef.roles:
self.display_participants()
def display_participants [html] (self):
users = []
many_users = False
search_result = False
user_ids = Set()
for role_id in self.objectdef.roles:
try:
user_ids_with_role = User.get_ids_with_indexed_value(
'roles', role_id, auto_fallback=False)
except:
# this should be limited to StorageIndexException but it
# somehow failed to catch them.
keys = User.keys()
if len(keys) < 500:
user_ids_with_role = User.get_ids_with_indexed_value(
'roles', role_id, auto_fallback=True)
else:
# failed to get a reliable list of participants, note
# it so
user_ids = None
break
user_ids.update(user_ids_with_role)
search_form = Form(enctype='multipart/form-data', use_tokens=False)
search_form.add(StringWidget, 'email', title=_('Email'))
if search_form.is_submitted() and search_form.get_widget('email').parse():
keys = []
term = search_form.get_widget('email').parse()
if term:
term = term.lower()
for k in User.keys():
if term in k:
keys.append(k)
search_result = True
if user_ids is None:
if len(keys) < 1000:
# we do not have a precise list of participants but
# the result set is relatively small, so we iterate
# over all of them, and we'll get back to the
# precise list situation.
user_ids = Set()
for user_key in keys:
user = User.get(user_key)
for role_id in self.objectdef.roles:
if role_id in (user.roles or []):
break
else:
continue
user_ids.add(user_key)
else:
# we reduce the current set
user_ids.intersection_update(keys)
# XXX: and now, convert from user_ids to User objects?
if user_ids:
# XXX: paragraph of explanation, and option to get a list of access
# codes instead of sending them out
'<p>'
_('%s participants.') % len(user_ids)
if search_result:
' <a href=".">%s</a>' % _('Back to full listing')
'</p>'
if len(user_ids) < 500:
# reasonable number, load all of them, so it's possible to
# display and sort on user names
all_users = [User.get(x) for x in user_ids]
all_users.sort(key=lambda x: getattr(x, str('display_name')))
user_are_objects = True
else:
all_users = sorted(user_ids)
user_are_objects = False
'<div class="splitcontent-left">'
'<ul class="biglist">'
rightcol = False
for i, user in enumerate(all_users):
if user_are_objects:
user_id = user.id
'<li><strong>%s</strong> ' % user.display_name
else:
user_id = user
'<li><strong>%s</strong> ' % user_id
'(<a href="remove?id=%s">%s</a>)' % (user_id, _('remove'))
'</li>'
if not rightcol and (i+1) >= len(user_ids)/2.0:
rightcol = True
'</ul>'
'</div>'
'<div class="splitcontent-right">'
'<ul class="biglist">'
'</ul>'
'</div>'
# XXX: add pagination
elif user_ids is None: # unknown set
'<p>'
_('Unable to get a list of participants.')
' '
_('System overloaded?')
# XXX: + use search form on the right.
'</p>'
else:
'<p>'
if search_result:
_('There is currently no participants matching your query.')
else:
_('There is currently no participants defined.')
'</p>'
def get_sidebar [html] (self):
if not self.objectdef.roles:
return ''
'<ul>'
'<li><a href="import" rel="popup">%s</a></li>' % _('Import list of participants')
'<li><a href="import-disabled" rel="popup">%s</a></li>' % _(
'Import list of participants to disable')
'<li><a href="mail-access-codes">%s</a></li>' % _('Mail access codes')
'<li><a href="mail-participants">%s</a></li>' % _('Mail participants')
'</ul>'
'<h4>%s</h4>' % _('Search a participant')
search_form = Form(enctype='multipart/form-data', use_tokens=False)
search_form.add(StringWidget, 'email', title=_('Email'))
search_form.add_submit('submit', _('Search'))
search_form.render()
def remove(self):
user_id = get_request().form.get('id')
if not user_id:
return redirect('.')
user = User.get(user_id)
for role_id in self.objectdef.roles:
if role_id in user.roles:
user.roles.remove(role_id)
if not user.roles:
user.remove_self()
else:
user.store()
return redirect('.')
def add [html] (self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'name', title=_('Name'), size=40, required=False)
form.add(EmailWidget, 'email', title=_('Email'), required=True)
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.add_submit(form)
except ValueError:
pass
get_response().breadcrumb.append( ('add', _('Add')) )
self.html_top(_('Add a Participant'))
form.render()
def add_submit(self, form):
name = form.get_widget('name').parse()
email = form.get_widget('email').parse()
try:
user = User.get(email)
except KeyError:
# this user doesn't exist
user = User()
user.id = email
user.email = email
get_session().message = ('info', _('New partipant has been created.'))
else:
get_session().message = ('info', _('Participant already existed, it has been updated.'))
user.name = name
for role_id in self.objectdef.roles:
if not role_id in user.roles:
user.roles.append(role_id)
user.store()
return redirect('.')
def p_import [html] (self):
if get_request().form.get('job'):
return self.participants_importing()
form = Form(enctype='multipart/form-data')
form.add(FileWidget, 'file', title=_('File'), required=True)
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.import_submit(form)
except ValueError:
pass
get_response().breadcrumb.append( ('import', _('Import')) )
self.html_top(_('Import a List of Participants'))
'<h2>%s</h2>' % _('Importing a List of Participants')
'<p>'
_('The file should have email addresses in the first column, and, '\
'optionnaly, names in the second column.')
'</p>'
form.render()
def import_submit(self, form):
class ParticipantsImporter:
def __init__(self, csvfile, objectdef):
self.csvfile = csvfile
self.objectdef = objectdef
def __call__(self, job=None):
self.csvfile.seek(0)
try:
dialect = csv.Sniffer().sniff(self.csvfile.read(1024),
delimiters=',; \t')
except csv.Error:
# perhaps this is just a list of emails, and it raised
# "Could not determine delimiter", so we check the first
# line
self.csvfile.seek(0)
first_line = self.csvfile.readline().strip()
if first_line.count('@') != 1:
# XXX: no way to pass errors from after job to user.
raise ValueError()
dialect = 'excel'
self.csvfile.seek(0)
reader = csv.reader(self.csvfile, dialect)
count = 0
for line in reader:
email = line[0]
try:
user = User.get(email)
except KeyError:
# this user doesn't exist
user = User()
user.id = email
user.email = email
if len(line) == 2:
user.name = line[1]
for role_id in self.objectdef.roles:
if not role_id in user.roles:
user.roles.append(role_id)
user.store()
count += 1
self.csvfile.close()
get_logger().info('form %s - done importing participants (%s)' % (
self.objectdef.id, count))
tmpfile = tempfile.NamedTemporaryFile()
fp = form.get_widget('file').parse().fp
while True:
s = fp.read(1024*1024)
tmpfile.write(s)
if not s:
break
get_logger().info('form %s - importing participants' % self.objectdef.id)
job = get_response().add_after_job(
str(N_('Importing list of participants')),
ParticipantsImporter(tmpfile, self.objectdef))
return redirect('import?job=%s' % job.id)
def participants_importing [html] (self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('.')
self.html_top( _('Importing Participants'))
get_response().add_javascript(['jquery.js', 'interface.js', 'afterjob.js'])
'<dl class="job-status">'
'<dt>'
_(job.label)
'</dt>'
'<dd>'
'<span class="afterjob" id="%s">' % job.id
_(job.status)
'</span>'
'</dd>'
'</dl>'
'<div class="done">'
'<a href="./">%s</a>' % _('Back')
'</div>'
def mail_access_codes [html] (self):
if get_request().form.get('job'):
return self.access_code_mailing()
emails_cfg = get_cfg('emails', {})
default_subject = EmailsDirectory.get_subject('asec-voting-instructions')
default_body = EmailsDirectory.get_body('asec-voting-instructions')
form = Form(enctype='multipart/form-data')
form.add(EmailWidget, 'mail_from', title=_('From'), size=40, required=False,
value=emails_cfg.get('from', ''), size=30)
form.add(StringWidget, 'mail_subject', title=_('Subject'), size=40, required=True,
value=default_subject)
form.add(TextWidget, 'mail_body', title=_('Body'), cols=70, rows=20, required=True,
value=default_body)
form.add(SingleSelectWidget, 'behaviour',
title=_('Behaviour for participants who already have been given an access code'),
options=[(str('skip'), _('Skip')),
(str('new'), _('Generate new access code'))])
#XXX: send another type of email...
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():
mail_from = form.get_widget('mail_from').parse()
mail_subject = form.get_widget('mail_subject').parse()
mail_body = form.get_widget('mail_body').parse()
behaviour = form.get_widget('behaviour').parse()
EmailsDirectory.set_subject('asec-voting-instructions', mail_subject)
EmailsDirectory.set_body('asec-voting-instructions', mail_body)
access_code_mailer = AccessCodeMailer(self.objectdef, mail_from,
mail_subject, mail_body, behaviour)
get_logger().info('form %s - mailing access codes (behaviour: %s)' % (
self.objectdef.id, behaviour))
job = get_response().add_after_job(
str(N_('Mailing access codes')),
access_code_mailer)
return redirect('mail-access-codes?job=%s' % job.id)
self.html_top(_('Mailing access codes'))
'<h2>%s</h2>' % _('Mailing access codes')
form.render()
def access_code_mailing [html] (self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('.')
self.html_top(_('Mailing access codes'))
get_response().add_javascript(['jquery.js', 'interface.js', 'afterjob.js'])
'<dl class="job-status">'
'<dt>'
_(job.label)
'</dt>'
'<dd>'
'<span class="afterjob" id="%s">' % job.id
_(job.status)
'</span>'
'</dd>'
'</dl>'
'<div class="done">'
'<a href="./">%s</a>' % _('Back')
'</div>'
def import_disabled [html] (self):
if get_request().form.get('job'):
return self.participants_disabling()
form = Form(enctype='multipart/form-data')
form.add(FileWidget, 'file', title=_('File'), required=True)
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.import_disabled_submit(form)
except ValueError:
pass
get_response().breadcrumb.append( ('import', _('Import')) )
self.html_top(_('Import a List of Participants to Disable'))
'<h2>%s</h2>' % _('Importing a List of Participants to Disable')
'<p>'
_('The file should consist of email addresses, one per line.')
'</p>'
form.render()
def import_disabled_submit(self, form):
class ParticipantsDisabler:
def __init__(self, csvfile, objectdef):
self.csvfile = csvfile
self.objectdef = objectdef
def __call__(self, job=None):
self.csvfile.seek(0)
try:
dialect = csv.Sniffer().sniff(self.csvfile.read(1024),
delimiters=',; \t')
except csv.Error:
# perhaps this is just a list of emails, and it raised
# "Could not determine delimiter", so we check the first
# line
self.csvfile.seek(0)
first_line = self.csvfile.readline().strip()
if first_line.count('@') != 1:
# XXX: no way to pass errors from after job to user.
raise ValueError()
dialect = 'excel'
self.csvfile.seek(0)
reader = csv.reader(self.csvfile, dialect)
for line in reader:
email = line[0]
try:
user = User.get(email)
except KeyError:
# this user doesn't exist
continue
touched = False
for role_id in self.objectdef.roles:
if role_id in user.roles:
user.roles.remove(role_id)
touched = True
if touched:
if not user.roles:
user.remove_self()
else:
user.store()
self.csvfile.close()
tmpfile = tempfile.NamedTemporaryFile()
fp = form.get_widget('file').parse().fp
while True:
s = fp.read(1024*1024)
tmpfile.write(s)
if not s:
break
job = get_response().add_after_job(
str(N_('Disabling list of participants')),
ParticipantsDisabler(tmpfile, self.objectdef))
return redirect('import-disabled?job=%s' % job.id)
def participants_disabling [html] (self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('.')
self.html_top(_('Disabling Participants'))
get_response().add_javascript(['jquery.js', 'interface.js', 'afterjob.js'])
'<dl class="job-status">'
'<dt>'
_(job.label)
'</dt>'
'<dd>'
'<span class="afterjob" id="%s">' % job.id
_(job.status)
'</span>'
'</dd>'
'</dl>'
'<div class="done">'
'<a href="./">%s</a>' % _('Back')
'</div>'
def mail_participants [html] (self):
if get_request().form.get('job'):
return self.participants_mailing()
emails_cfg = get_cfg('emails', {})
default_subject = EmailsDirectory.get_subject('asec-skeleton-participant-mail')
default_body = EmailsDirectory.get_body('asec-skeleton-participant-mail')
form = Form(enctype='multipart/form-data')
form.add(EmailWidget, 'mail_from', title=_('From'), size=40, required=False,
value=emails_cfg.get('from', ''), size=30)
form.add(StringWidget, 'mail_subject', title=_('Subject'), size=40, required=True,
value=default_subject)
form.add(TextWidget, 'mail_body', title=_('Body'), cols=70, rows=20, required=True,
value=default_body)
form.add(SingleSelectWidget, 'behaviour',
title=_('Behaviour for participants who already have completed a questionnaire'),
options=[(str('skip'), _('Skip')),
(str('send'), _('Send'))])
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():
mail_from = form.get_widget('mail_from').parse()
mail_subject = form.get_widget('mail_subject').parse()
mail_body = form.get_widget('mail_body').parse()
behaviour = form.get_widget('behaviour').parse()
EmailsDirectory.set_subject('asec-skeleton-participant-mail', mail_subject)
EmailsDirectory.set_body('asec-skeleton-participant-mail', mail_body)
participants_mailer = ParticipantsMailer(self.objectdef,
mail_from, mail_subject, mail_body, behaviour)
get_logger().info('form %s - mailing participants (behaviour: %s)' % (
self.objectdef.id, behaviour))
job = get_response().add_after_job(
str(N_('Mailing participants')),
participants_mailer)
return redirect('mail-participants?job=%s' % job.id)
self.html_top(_('Mailing participants'))
'<h2>%s</h2>' % _('Mailing participants')
form.render()
def participants_mailing [html] (self):
try:
job = AfterJob.get(get_request().form.get('job'))
except KeyError:
return redirect('.')
self.html_top(_('Mailing participants'))
get_response().add_javascript(['jquery.js', 'interface.js', 'afterjob.js'])
'<dl class="job-status">'
'<dt>'
_(job.label)
'</dt>'
'<dd>'
'<span class="afterjob" id="%s">' % job.id
_(job.status)
'</span>'
'</dd>'
'</dl>'
'<div class="done">'
'<a href="./">%s</a>' % _('Back')
'</div>'
EmailsDirectory.register('asec-voting-instructions',
N_('Voting instructions and access codes'),
N_('Available variables: user_name, user_email, access_code, questionnaire_name, questionnaire_url'),
default_subject=N_('[questionnaire_name] - voting instructions'),
default_body=N_('''\
Dear [user_name]
[questionnaire_name] is now open.
To vote, please go to [questionnaire_url]
and follow the instructions there.
When instructed to do so, enter the following details:
E-Mail: [user_email]
Access Code: [access_code]
The election has 4 steps:
First, you must identify yourself using the access code above.
Then fill the questionnaire with your choices.
A third step will show you your choice, and ask you to confirm or return
to the previous step.
Finally, after confirming your choice, a unique identifier will be given
to you which will allow you to verify after the election that your vote
was counted correctly. To ensure anonymity, no link will be kept between
this token and your identifiers, so please keep this token safe. Once you
have voted, you will not be able to vote again.
Thank you for your vote!
Regards,
'''))
EmailsDirectory.register('asec-skeleton-participant-mail',
N_('Skeleton for "mail to participants"'),
N_('Available variables: user_name, user_email, access_code, questionnaire_name, questionnaire_url'),
default_subject=N_('[questionnaire_name]'),
default_body=N_('''\
Dear [user_name]
[questionnaire_name] is now open.
To vote, please go to [questionnaire_url]
and follow the instructions there.
When instructed to do so, enter the following details:
E-Mail: [user_email]
Access Code: [access_code]
The election has 4 steps:
First, you must identify yourself using the access code above.
Then fill the questionnaire with your choices.
A third step will show you your choice, and ask you to confirm or return
to the previous step.
Finally, after confirming your choice, a unique identifier will be given
to you which will allow you to verify after the election that your vote
was counted correctly. To ensure anonymity, no link will be kept between
this token and your identifiers, so please keep this token safe. Once you
have voted, you will not be able to vote again.
Thank you for your vote!
Regards,
'''))