From ef0ce78087f998aa3c969ac4cac0a2c6cfedd64a Mon Sep 17 00:00:00 2001
From: Ben Adida <ben@adida.net>
Date: Wed, 17 Nov 2010 05:47:36 -0800
Subject: [PATCH] reworked the last 'combine decryptions' step and added
 ability to customize email message to voters

---
 helios/forms.py                           |  5 ++
 helios/templates/combine_decryptions.html | 36 ++++++++++
 helios/templates/election_view.html       |  2 +-
 helios/templates/email/result_body.txt    | 15 +++--
 helios/templates/email/result_subject.txt |  2 +-
 helios/views.py                           | 80 +++++++++++++++++------
 6 files changed, 115 insertions(+), 25 deletions(-)
 create mode 100644 helios/templates/combine_decryptions.html

diff --git a/helios/forms.py b/helios/forms.py
index 21cff19..c15e5e0 100644
--- a/helios/forms.py
+++ b/helios/forms.py
@@ -27,3 +27,8 @@ class EmailVotersForm(forms.Form):
   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')])
+
+class TallyNotificationEmailForm(forms.Form):
+  subject = forms.CharField(max_length=80)
+  body = forms.CharField(max_length=2000, widget=forms.Textarea)
+  send_to = forms.ChoiceField(label="Send To", choices= [('all', 'all voters'), ('voted', 'only voters who cast a ballot')])
diff --git a/helios/templates/combine_decryptions.html b/helios/templates/combine_decryptions.html
new file mode 100644
index 0000000..efb9f85
--- /dev/null
+++ b/helios/templates/combine_decryptions.html
@@ -0,0 +1,36 @@
+{% extends TEMPLATE_BASE %}
+
+{% block title %}Combine Decryptions and Release Tally &mdash; {{election.name}}{% endblock %}
+{% block content %}
+  <h2 class="title">{{election.name}} &mdash; Combine Decryptions and Release Tally <span style="font-size:0.7em;">[<a href="{% url helios.views.one_election_view election.uuid %}">cancel</a>]</span></h2>
+
+  <p>
+    The tally for this election will now be computed and displayed on the main election page.
+  </p>
+
+  <p>
+In addition, voters will be notified that of the availability of the tally. The default message reads:
+</p>
+
+<pre style="margin:10px; border: 1px solid #888; padding:20px">
+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}}" />
+    <table class="pretty">
+     {{email_form.as_table}}
+     </table>
+  <div>
+  <label for="">&nbsp;</label><input type="submit" value="Release Tally and Notify Voters" id="send_button" />
+  </div>
+  </form>
+  
+
+{% endblock %}
diff --git a/helios/templates/election_view.html b/helios/templates/election_view.html
index aa0563e..6434fe6 100644
--- a/helios/templates/election_view.html
+++ b/helios/templates/election_view.html
@@ -109,7 +109,7 @@ trustees will be asked to provide their share of the decryption.
 {% else %}
 
 {% if election.ready_for_decryption_combination %}
-<a onclick="return confirm('Ready for the tally? Voters will be notified immediately.');" href="{% url helios.views.combine_decryptions election.uuid %}">combine trustee decryptions and release results</a><br />
+<a href="{% url helios.views.combine_decryptions election.uuid %}">combine trustee decryptions and release results</a><br />
 The decryption shares from the trustees are combined and the tally is decrypted.<br />
 Once you do this, the tally will be immediately available for all to see, and
 all voters will be notified that the tally is ready.
diff --git a/helios/templates/email/result_body.txt b/helios/templates/email/result_body.txt
index c578bfe..2c1d7f0 100644
--- a/helios/templates/email/result_body.txt
+++ b/helios/templates/email/result_body.txt
@@ -1,12 +1,19 @@
 Dear {{voter.name}},
 
-The tally has been computed for 
+The tally for {{election.name}} has been computed and released:
 
-  {{voter.election.name}}
+  {{election_url}}
 
-Check out the results at:
+{{custom_message|safe}}
 
-  {{election_url}}
+{% if voter.vote_hash %}You smart ballot tracker in this election was:
+
+  {{voter.vote_hash}}
 
+If you believe this tracker to be in error, please contact us.
+{% else %}
+It appears you did not cast a vote in this election.
+Please contact us if you believe you did.
+{% endif %}
 --
 Helios
diff --git a/helios/templates/email/result_subject.txt b/helios/templates/email/result_subject.txt
index 2e1baa9..a7929f1 100644
--- a/helios/templates/email/result_subject.txt
+++ b/helios/templates/email/result_subject.txt
@@ -1 +1 @@
-Tally computed for {{voter.election.name}}
+{{custom_subject|safe}}
diff --git a/helios/views.py b/helios/views.py
index 56440fd..6e2e5d3 100644
--- a/helios/views.py
+++ b/helios/views.py
@@ -851,26 +851,68 @@ def trustee_upload_decryption(request, election, trustee_uuid):
   
 @election_admin(frozen=True)
 def combine_decryptions(request, election):
-  election.combine_decryptions()
-  election.save()
+  """
+  combine trustee decryptions
+  """
+
+  election_url = get_election_url(election)
+
+  default_subject = 'Tally released for %s' % election.name
+  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)
+
+    email_form = forms.TallyNotificationEmailForm(request.POST)
+
+    if email_form.is_valid():
+      election.combine_decryptions()
+      election.save()
+
+      # notify voters!
+      extra_vars = {
+        'custom_subject' : email_form.cleaned_data['subject'],
+        'custom_message' : email_form.cleaned_data['body'],
+        'election_url' : election_url,
+        'election' : election
+        }
+
+      # 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)
+      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
+  return render_template(request, 'combine_decryptions', {'election': election,
+                                                          'email_form' : email_form,
+                                                          'default_subject': default_subject,
+                                                          'default_body': default_body})
 
-  # notify voters!
-  extra_vars = {
-    'election_url' : get_election_url(election)
-    }
-  
-  # 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)
-
-  # rapid short-message notification
-  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]))
 
 @election_admin(frozen=True)
 def one_election_set_result_and_proof(request, election):
-- 
GitLab