Skip to content
Snippets Groups Projects
Commit 744f92b0 authored by Ben Adida's avatar Ben Adida
Browse files

fixed emailing to be consistent, with templates

parent 9a2523e4
Branches
Tags
No related merge requests found
...@@ -3,16 +3,6 @@ ...@@ -3,16 +3,6 @@
{% else %} {% else %}
{% for auth_system in enabled_auth_systems %} {% for auth_system in enabled_auth_systems %}
{% ifequal auth_system "password" %} {% ifequal auth_system "password" %}
<form method="post" action="{% url auth.auth_systems.password.password_login_view %}">
<input type="hidden" name="election_uuid" value="{{election.uuid}}" />
<input type="hidden" name="csrf_token" value="{{csrf_token}}" />
<input type="hidden" name="return_url" value="{{return_url}}" />
<table>
{{form.as_table}}
</table>
<input type="submit" value="log in" />
<a style="font-size: 0.8em;" href="{% url auth.auth_systems.password.password_forgotten_view %}?return_url={{return_url|urlencode}}">forgot password?</a>
</form>
{% else %} {% else %}
<p> <p>
<a href="{{SECURE_URL_HOST}}{% url auth.views.start system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;"> <a href="{{SECURE_URL_HOST}}{% url auth.views.start system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;">
......
{% extends TEMPLATE_BASE %}
{% block content %}
<h1>Why we need your information</h1>
<p>
It looks like you declined to grant us permission to access your
information. We understand and value your privacy. We ask for this
information so we can contact you with your voting tracking number and
when an election in which you participated has been tallied. We
promise not to use this information for any other purpose. Without
this information, we unfortunately cannot help you vote.
</p>
<form method="POST" action="">
<input type="hidden" value="{{csrf_token}}" />
<input type="submit" value="OK, I understand, let's do this login thing." /><br /><br />
<a href="http://google.com">nope, get me out of here</a>.
</form>
{% endblock %}
...@@ -40,13 +40,12 @@ def index(request): ...@@ -40,13 +40,12 @@ def index(request):
if auth.DEFAULT_AUTH_SYSTEM: if auth.DEFAULT_AUTH_SYSTEM:
default_auth_system_obj = AUTH_SYSTEMS[auth.DEFAULT_AUTH_SYSTEM] default_auth_system_obj = AUTH_SYSTEMS[auth.DEFAULT_AUTH_SYSTEM]
form = password.LoginForm() #form = password.LoginForm()
return render_template(request,'index', {'return_url' : request.GET.get('return_url', '/'), return render_template(request,'index', {'return_url' : request.GET.get('return_url', '/'),
'enabled_auth_systems' : auth.ENABLED_AUTH_SYSTEMS, 'enabled_auth_systems' : auth.ENABLED_AUTH_SYSTEMS,
'default_auth_system': auth.DEFAULT_AUTH_SYSTEM, 'default_auth_system': auth.DEFAULT_AUTH_SYSTEM,
'default_auth_system_obj': default_auth_system_obj, 'default_auth_system_obj': default_auth_system_obj})
'form' : form})
def login_box_raw(request, return_url='/', auth_systems = None): def login_box_raw(request, return_url='/', auth_systems = None):
""" """
......
...@@ -29,7 +29,6 @@ class ElectionTimesForm(forms.Form): ...@@ -29,7 +29,6 @@ class ElectionTimesForm(forms.Form):
class EmailVotersForm(forms.Form): class EmailVotersForm(forms.Form):
subject = forms.CharField(max_length=80) subject = forms.CharField(max_length=80)
body = forms.CharField(max_length=2000, widget=forms.Textarea) body = forms.CharField(max_length=2000, widget=forms.Textarea)
suppress_election_links = forms.BooleanField(label = "Suppress links?", required=False)
send_to = forms.ChoiceField(label="Send To", choices= [('all', 'all voters'), ('voted', 'voters who have cast a ballot'), ('not-voted', 'voters who have not yet cast a ballot')]) send_to = forms.ChoiceField(label="Send To", choices= [('all', 'all voters'), ('voted', 'voters who have cast a ballot'), ('not-voted', 'voters who have not yet cast a ballot')])
class TallyNotificationEmailForm(forms.Form): class TallyNotificationEmailForm(forms.Form):
......
...@@ -5,28 +5,12 @@ ...@@ -5,28 +5,12 @@
<h2 class="title">{{election.name}} &mdash; Release Tally <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">cancel</a>]</span></h2> <h2 class="title">{{election.name}} &mdash; Release Tally <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">cancel</a>]</span></h2>
<p> <p>
The tally for this election will be released, and voters will be notified that of the availability of the tally.<br />The default message reads: You are about to release the tally for this election
</p> </p>
<pre style="margin:10px; border: 1px solid #888; padding:20px"> <form method="POST" action="">
Subject: {{default_subject}}
{{default_body|safe}}
</pre>
<p>
You may tweak the subject and add a custom message using the form below.
</p>
<form class="prettyform" action="" method="POST" id="email_form">
<input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
<table class="pretty"> <input type="submit" value="release the tally!" />
{{email_form.as_table}}
</table>
<div>
<label for="">&nbsp;</label><input type="submit" value="Release Tally and Notify Voters" id="send_button" />
</div>
</form> </form>
{% endblock %} {% endblock %}
...@@ -2,5 +2,7 @@ Dear {{voter.name}}, ...@@ -2,5 +2,7 @@ Dear {{voter.name}},
{{custom_message|safe}} {{custom_message|safe}}
Election Link: {{election_url}}
-- --
Helios Helios
Additional Information: {{custom_subject|safe}}
{{custom_subject|safe}} Tally Released - {{custom_subject|safe}}
...@@ -2,7 +2,7 @@ Dear {{voter.name}}, ...@@ -2,7 +2,7 @@ Dear {{voter.name}},
{{custom_message|safe}} {{custom_message|safe}}
Election URL: {{election_url}} Election URL: {{election_vote_url}}
Election Fingerprint: {{voter.election.hash}} Election Fingerprint: {{voter.election.hash}}
{% ifequal voter.voter_type "password" %} {% ifequal voter.voter_type "password" %}
Your voter ID: {{voter.voter_login_id}} Your voter ID: {{voter.voter_login_id}}
......
{{custom_subject|safe}} Vote: {{custom_subject|safe}}
{% extends TEMPLATE_BASE %} {% extends TEMPLATE_BASE %}
{% block title %}Email Voters for {{election.name}}{% endblock %} {% block title %}Contact Voters for {{election.name}}{% endblock %}
{% block content %} {% block content %}
<script> <script>
var BATCH_SIZE = 25;
var voter_id = null; var voter_id = null;
{% if voter %} {% if voter %}
voter_id = '{{voter.voter_id}}'; voter_id = '{{voter.voter_id}}';
{% endif %} {% endif %}
</script> </script>
<h2 class="title">{{election.name}} &mdash; Email Voters <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">back to election</a>]</span></h2> <h2 class="title">{{election.name}} &mdash; Contact Voters <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">back to election</a>]</span></h2>
{% if voter %} {% if voter %}
<p> <p>
...@@ -20,20 +18,30 @@ voter_id = '{{voter.voter_id}}'; ...@@ -20,20 +18,30 @@ voter_id = '{{voter.voter_id}}';
{% endif %} {% endif %}
<p> <p>
The email will <b><u>automatically</u></b> include a "Dear Voter" line, as well as a footer including<br /> <b>Templates</b>:
the election URL, the login information, and a simple email signature.<br />
No need to include these in the body of your email below. {% for template_option in templates %}
</p> {% if template_option.0 == template %}
<p> <b>{{template_option.1}}</b>
If the voter has already voted, the message will include a reminder of their smart ballot tracker.<br /> {% else %}
</p> <a href="?template={{template_option.0}}">{{template_option.1}}</a>
{% endif %}
&nbsp;&nbsp;&nbsp;
{% endfor %}
<pre style="margin:10px; border: 1px solid #888; padding:20px">
Subject: {{default_subject}}
{{default_body|safe}}
</pre>
<p> <p>
The subject of the email is set by default below, but can be changed to your liking. You may tweak the subject and add a custom message using the form below.
</p> </p>
<form class="prettyform" action="" method="POST" id="email_form"> <form class="prettyform" action="" method="POST" id="email_form">
<input type="hidden" name="csrf_token" value="{{csrf_token}}" /> <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
<input type="hidden" name="template" value="{{template}}" />
<table class="pretty"> <table class="pretty">
{{email_form.as_table}} {{email_form.as_table}}
</table> </table>
......
...@@ -547,7 +547,7 @@ class ElectionBlackboxTests(WebTest): ...@@ -547,7 +547,7 @@ class ElectionBlackboxTests(WebTest):
self.assertEquals(num_messages_after - num_messages_before, NUM_VOTERS) self.assertEquals(num_messages_after - num_messages_before, NUM_VOTERS)
email_message = mail.outbox[num_messages_before] email_message = mail.outbox[num_messages_before]
self.assertEquals(email_message.subject, "your password") assert "your password" in email_message.subject, "bad subject in email"
# get the username and password # get the username and password
username = re.search('voter ID: (.*)', email_message.body).group(1) username = re.search('voter ID: (.*)', email_message.body).group(1)
...@@ -655,11 +655,11 @@ class ElectionBlackboxTests(WebTest): ...@@ -655,11 +655,11 @@ class ElectionBlackboxTests(WebTest):
# combine decryptions # combine decryptions
response = self.client.post("/helios/elections/%s/combine_decryptions" % election_id, { response = self.client.post("/helios/elections/%s/combine_decryptions" % election_id, {
"csrf_token" : self.client.session['csrf_token'], "csrf_token" : self.client.session['csrf_token'],
"subject" : "tally subject",
"body" : "tally body",
"send_to" : "all"
}) })
self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
# after tallying, we now are supposed to see the email screen
# with the right template value of 'result'
self.assertRedirects(response, "/helios/elections/%s/voters/email?template=result" % election_id)
# check that tally matches # check that tally matches
response = self.client.get("/helios/elections/%s/result" % election_id) response = self.client.get("/helios/elections/%s/result" % election_id)
......
...@@ -1043,65 +1043,16 @@ def combine_decryptions(request, election): ...@@ -1043,65 +1043,16 @@ def combine_decryptions(request, election):
election_url = get_election_url(election) election_url = get_election_url(election)
default_subject = 'Tally released for %s' % election.name if request.method == "POST":
default_body = render_template_raw(None, 'email/result_body.txt', {
'election' : election,
'election_url' : election_url,
'custom_subject' : default_subject,
'custom_message': '&lt;YOUR MESSAGE HERE&gt;',
'voter': {'vote_hash' : '<SMART_TRACKER>',
'name': '<VOTER_NAME>'}
})
if request.method == "GET":
email_form = forms.TallyNotificationEmailForm(initial= {'subject': default_subject})
else:
check_csrf(request) check_csrf(request)
email_form = forms.TallyNotificationEmailForm(request.POST)
if email_form.is_valid():
election.combine_decryptions() election.combine_decryptions()
election.save() election.save()
# notify voters! return HttpResponseRedirect("%s?%s" % (reverse(voters_email, args=[election.uuid]), urllib.urlencode({'template': 'result'})))
extra_vars = {
'custom_subject' : email_form.cleaned_data['subject'],
'custom_message' : email_form.cleaned_data['body'],
'election_url' : election_url,
'election' : election
}
# if the user opted for notifying no one, then we skip this step
if email_form.cleaned_data['send_to'] != 'none':
# exclude those who have not voted
if email_form.cleaned_data['send_to'] == 'voted':
voter_constraints_exclude = {'vote_hash' : None}
else:
voter_constraints_exclude = {}
# full-length email
tasks.voters_email.delay(election_id = election.id,
subject_template = 'email/result_subject.txt',
body_template = 'email/result_body.txt',
extra_vars = extra_vars,
voter_constraints_exclude = voter_constraints_exclude)
# rapid short-message notification
# this inherently only applies to those who have voted (for the most part)
# and this is not configurable, this is ALWAYS sent
tasks.voters_notify.delay(election_id = election.id,
notification_template = 'notification/result.txt',
extra_vars = extra_vars)
return HttpResponseRedirect(reverse(one_election_view, args=[election.uuid]))
# if just viewing the form or the form is not valid # if just viewing the form or the form is not valid
return render_template(request, 'combine_decryptions', {'election': election, return render_template(request, 'combine_decryptions', {'election': election})
'email_form' : email_form,
'default_subject': default_subject,
'default_body': default_body})
@election_admin(frozen=True) @election_admin(frozen=True)
def one_election_set_result_and_proof(request, election): def one_election_set_result_and_proof(request, election):
...@@ -1270,6 +1221,15 @@ def voters_upload_cancel(request, election): ...@@ -1270,6 +1221,15 @@ def voters_upload_cancel(request, election):
def voters_email(request, election): def voters_email(request, election):
if not helios.VOTERS_EMAIL: if not helios.VOTERS_EMAIL:
return HttpResponseRedirect(reverse(one_election_view, args=[election.uuid])) return HttpResponseRedirect(reverse(one_election_view, args=[election.uuid]))
TEMPLATES = [
('vote', 'Time to Vote'),
('info', 'Additional Info'),
('result', 'Election Result')
]
template = request.REQUEST.get('template', 'vote')
if not template in [t[0] for t in TEMPLATES]:
raise Exception("bad template")
voter_id = request.REQUEST.get('voter_id', None) voter_id = request.REQUEST.get('voter_id', None)
if voter_id: if voter_id:
...@@ -1277,24 +1237,39 @@ def voters_email(request, election): ...@@ -1277,24 +1237,39 @@ def voters_email(request, election):
else: else:
voter = None voter = None
election_url = get_election_url(election)
election_vote_url = get_election_govote_url(election)
default_subject = render_template_raw(None, 'email/%s_subject.txt' % template, {
'custom_subject': "&lt;SUBJECT&gt;"
})
default_body = render_template_raw(None, 'email/%s_body.txt' % template, {
'election' : election,
'election_url' : election_url,
'election_vote_url' : election_vote_url,
'custom_subject' : default_subject,
'custom_message': '&lt;YOUR MESSAGE HERE&gt;',
'voter': {'vote_hash' : '<SMART_TRACKER>',
'name': '<VOTER_NAME>',
'voter_type' : election.voter_set.all()[0].voter_type,
'election' : election}
})
if request.method == "GET": if request.method == "GET":
email_form = forms.EmailVotersForm(initial={'subject': 'Vote in %s' % election.name}) email_form = forms.EmailVotersForm()
else: else:
email_form = forms.EmailVotersForm(request.POST) email_form = forms.EmailVotersForm(request.POST)
if email_form.is_valid(): if email_form.is_valid():
# the client knows to submit only once with a specific voter_id # the client knows to submit only once with a specific voter_id
subject_template = 'email/vote_subject.txt' subject_template = 'email/%s_subject.txt' % template
body_template = 'email/vote_body.txt' body_template = 'email/%s_body.txt' % template
if email_form.cleaned_data['suppress_election_links']:
body_template = 'email/vote_body_nolinks.txt'
extra_vars = { extra_vars = {
'custom_subject' : email_form.cleaned_data['subject'], 'custom_subject' : email_form.cleaned_data['subject'],
'custom_message' : email_form.cleaned_data['body'], 'custom_message' : email_form.cleaned_data['body'],
'election_url' : get_election_govote_url(election), 'election_url' : election_url,
'election' : election 'election' : election
} }
...@@ -1317,7 +1292,13 @@ def voters_email(request, election): ...@@ -1317,7 +1292,13 @@ def voters_email(request, election):
# this batch process is all async, so we can return a nice note # this batch process is all async, so we can return a nice note
return HttpResponseRedirect(reverse(one_election_view, args=[election.uuid])) return HttpResponseRedirect(reverse(one_election_view, args=[election.uuid]))
return render_template(request, "voters_email", {'email_form': email_form, 'election': election, 'voter': voter}) return render_template(request, "voters_email", {
'email_form': email_form, 'election': election,
'voter': voter,
'default_subject': default_subject,
'default_body' : default_body,
'template' : template,
'templates' : TEMPLATES})
# Individual Voters # Individual Voters
@election_view() @election_view()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment