From 744f92b0650e3bcdd57109254aac2cb09e532521 Mon Sep 17 00:00:00 2001 From: Ben Adida <ben@adida.net> Date: Sun, 3 Jul 2011 15:51:02 -0700 Subject: [PATCH] fixed emailing to be consistent, with templates --- auth/templates/login_box.html | 10 -- auth/templates/perms_why.html | 21 ++++ auth/views.py | 5 +- helios/forms.py | 1 - helios/templates/combine_decryptions.html | 24 +--- .../{vote_body_nolinks.txt => info_body.txt} | 2 + helios/templates/email/info_subject.txt | 1 + helios/templates/email/result_subject.txt | 2 +- helios/templates/email/vote_body.txt | 2 +- helios/templates/email/vote_subject.txt | 2 +- helios/templates/voters_email.html | 40 ++++--- helios/tests.py | 10 +- helios/views.py | 107 +++++++----------- 13 files changed, 106 insertions(+), 121 deletions(-) create mode 100644 auth/templates/perms_why.html rename helios/templates/email/{vote_body_nolinks.txt => info_body.txt} (63%) create mode 100644 helios/templates/email/info_subject.txt diff --git a/auth/templates/login_box.html b/auth/templates/login_box.html index 3918f27..4ed3f81 100644 --- a/auth/templates/login_box.html +++ b/auth/templates/login_box.html @@ -3,16 +3,6 @@ {% else %} {% for auth_system in enabled_auth_systems %} {% 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 %} <p> <a href="{{SECURE_URL_HOST}}{% url auth.views.start system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;"> diff --git a/auth/templates/perms_why.html b/auth/templates/perms_why.html new file mode 100644 index 0000000..179d103 --- /dev/null +++ b/auth/templates/perms_why.html @@ -0,0 +1,21 @@ +{% 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 %} diff --git a/auth/views.py b/auth/views.py index 2469f9f..fda1dfc 100644 --- a/auth/views.py +++ b/auth/views.py @@ -40,13 +40,12 @@ def index(request): if 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', '/'), 'enabled_auth_systems' : auth.ENABLED_AUTH_SYSTEMS, 'default_auth_system': auth.DEFAULT_AUTH_SYSTEM, - 'default_auth_system_obj': default_auth_system_obj, - 'form' : form}) + 'default_auth_system_obj': default_auth_system_obj}) def login_box_raw(request, return_url='/', auth_systems = None): """ diff --git a/helios/forms.py b/helios/forms.py index 4fcf514..acc7a58 100644 --- a/helios/forms.py +++ b/helios/forms.py @@ -29,7 +29,6 @@ class ElectionTimesForm(forms.Form): class EmailVotersForm(forms.Form): subject = forms.CharField(max_length=80) 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): diff --git a/helios/templates/combine_decryptions.html b/helios/templates/combine_decryptions.html index 35bbe72..e83e1f8 100644 --- a/helios/templates/combine_decryptions.html +++ b/helios/templates/combine_decryptions.html @@ -5,28 +5,12 @@ <h2 class="title">{{election.name}} — 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 be released, and voters will be notified that of the availability of the tally.<br />The default message reads: -</p> + You are about to release the tally for this election + </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"> + <form method="POST" action=""> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> - <table class="pretty"> - {{email_form.as_table}} - </table> - <div> - <label for=""> </label><input type="submit" value="Release Tally and Notify Voters" id="send_button" /> - </div> + <input type="submit" value="release the tally!" /> </form> - {% endblock %} diff --git a/helios/templates/email/vote_body_nolinks.txt b/helios/templates/email/info_body.txt similarity index 63% rename from helios/templates/email/vote_body_nolinks.txt rename to helios/templates/email/info_body.txt index 43500ac..8a606ad 100644 --- a/helios/templates/email/vote_body_nolinks.txt +++ b/helios/templates/email/info_body.txt @@ -2,5 +2,7 @@ Dear {{voter.name}}, {{custom_message|safe}} +Election Link: {{election_url}} + -- Helios diff --git a/helios/templates/email/info_subject.txt b/helios/templates/email/info_subject.txt new file mode 100644 index 0000000..6b43baf --- /dev/null +++ b/helios/templates/email/info_subject.txt @@ -0,0 +1 @@ +Additional Information: {{custom_subject|safe}} diff --git a/helios/templates/email/result_subject.txt b/helios/templates/email/result_subject.txt index a7929f1..c7ac9f4 100644 --- a/helios/templates/email/result_subject.txt +++ b/helios/templates/email/result_subject.txt @@ -1 +1 @@ -{{custom_subject|safe}} +Tally Released - {{custom_subject|safe}} diff --git a/helios/templates/email/vote_body.txt b/helios/templates/email/vote_body.txt index b0c6875..c6f4422 100644 --- a/helios/templates/email/vote_body.txt +++ b/helios/templates/email/vote_body.txt @@ -2,7 +2,7 @@ Dear {{voter.name}}, {{custom_message|safe}} -Election URL: {{election_url}} +Election URL: {{election_vote_url}} Election Fingerprint: {{voter.election.hash}} {% ifequal voter.voter_type "password" %} Your voter ID: {{voter.voter_login_id}} diff --git a/helios/templates/email/vote_subject.txt b/helios/templates/email/vote_subject.txt index a7929f1..e048737 100644 --- a/helios/templates/email/vote_subject.txt +++ b/helios/templates/email/vote_subject.txt @@ -1 +1 @@ -{{custom_subject|safe}} +Vote: {{custom_subject|safe}} diff --git a/helios/templates/voters_email.html b/helios/templates/voters_email.html index cb7a26e..1c6155b 100644 --- a/helios/templates/voters_email.html +++ b/helios/templates/voters_email.html @@ -1,17 +1,15 @@ {% extends TEMPLATE_BASE %} -{% block title %}Email Voters for {{election.name}}{% endblock %} +{% block title %}Contact Voters for {{election.name}}{% endblock %} {% block content %} <script> -var BATCH_SIZE = 25; - var voter_id = null; {% if voter %} voter_id = '{{voter.voter_id}}'; {% endif %} </script> - <h2 class="title">{{election.name}} — 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}} — 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 %} <p> @@ -19,21 +17,31 @@ voter_id = '{{voter.voter_id}}'; </p> {% endif %} - <p> - The email will <b><u>automatically</u></b> include a "Dear Voter" line, as well as a footer including<br /> - the election URL, the login information, and a simple email signature.<br /> - No need to include these in the body of your email below. - </p> - <p> - If the voter has already voted, the message will include a reminder of their smart ballot tracker.<br /> - </p> +<p> +<b>Templates</b>: + +{% for template_option in templates %} +{% if template_option.0 == template %} +<b>{{template_option.1}}</b> +{% else %} +<a href="?template={{template_option.0}}">{{template_option.1}}</a> +{% endif %} + +{% endfor %} + +<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> - <p> - The subject of the email is set by default below, but can be changed to your liking. - </p> - <form class="prettyform" action="" method="POST" id="email_form"> <input type="hidden" name="csrf_token" value="{{csrf_token}}" /> + <input type="hidden" name="template" value="{{template}}" /> <table class="pretty"> {{email_form.as_table}} </table> diff --git a/helios/tests.py b/helios/tests.py index 7dcfe56..acce775 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -547,7 +547,7 @@ class ElectionBlackboxTests(WebTest): self.assertEquals(num_messages_after - num_messages_before, NUM_VOTERS) 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 username = re.search('voter ID: (.*)', email_message.body).group(1) @@ -655,11 +655,11 @@ class ElectionBlackboxTests(WebTest): # combine decryptions response = self.client.post("/helios/elections/%s/combine_decryptions" % election_id, { "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 response = self.client.get("/helios/elections/%s/result" % election_id) diff --git a/helios/views.py b/helios/views.py index 4e551e6..5f7e7a5 100644 --- a/helios/views.py +++ b/helios/views.py @@ -1043,65 +1043,16 @@ def combine_decryptions(request, election): 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': '<YOUR MESSAGE HERE>', - 'voter': {'vote_hash' : '<SMART_TRACKER>', - 'name': '<VOTER_NAME>'} - }) - - if request.method == "GET": - email_form = forms.TallyNotificationEmailForm(initial= {'subject': default_subject}) - else: + if request.method == "POST": 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 - } - - # 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) + election.combine_decryptions() + election.save() - return HttpResponseRedirect(reverse(one_election_view, args=[election.uuid])) + return HttpResponseRedirect("%s?%s" % (reverse(voters_email, args=[election.uuid]), urllib.urlencode({'template': 'result'}))) # 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}) - + return render_template(request, 'combine_decryptions', {'election': election}) @election_admin(frozen=True) def one_election_set_result_and_proof(request, election): @@ -1270,31 +1221,55 @@ def voters_upload_cancel(request, election): def voters_email(request, election): if not helios.VOTERS_EMAIL: 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) if voter_id: voter = Voter.get_by_election_and_voter_id(election, voter_id) else: 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': "<SUBJECT>" +}) + 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': '<YOUR MESSAGE HERE>', + 'voter': {'vote_hash' : '<SMART_TRACKER>', + 'name': '<VOTER_NAME>', + 'voter_type' : election.voter_set.all()[0].voter_type, + 'election' : election} + }) + if request.method == "GET": - email_form = forms.EmailVotersForm(initial={'subject': 'Vote in %s' % election.name}) + email_form = forms.EmailVotersForm() else: email_form = forms.EmailVotersForm(request.POST) if email_form.is_valid(): # the client knows to submit only once with a specific voter_id - subject_template = 'email/vote_subject.txt' - body_template = 'email/vote_body.txt' + subject_template = 'email/%s_subject.txt' % template + body_template = 'email/%s_body.txt' % template - if email_form.cleaned_data['suppress_election_links']: - body_template = 'email/vote_body_nolinks.txt' - extra_vars = { 'custom_subject' : email_form.cleaned_data['subject'], 'custom_message' : email_form.cleaned_data['body'], - 'election_url' : get_election_govote_url(election), + 'election_url' : election_url, 'election' : election } @@ -1317,7 +1292,13 @@ def voters_email(request, election): # this batch process is all async, so we can return a nice note 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 @election_view() -- GitLab