Merge pull request #14 from vincentmdealmeida/BallotChoice

Added ballot choice UI that allows the user to choose between 2 gener…
This commit is contained in:
vincentmdealmeida 2018-07-17 09:59:36 +01:00 committed by GitHub
commit 0ff10474c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 190 additions and 95 deletions

View file

@ -334,7 +334,7 @@ def event_trustee_decrypt(request, event_id):
# TODO: Combine partial decryptions and gen results # TODO: Combine partial decryptions and gen results
combine_decryptions_and_tally.delay(event) combine_decryptions_and_tally.delay(event)
messages.add_message(request, messages.SUCCESS, 'Your secret key has been successfully submitted') messages.add_message(request, messages.SUCCESS, 'Your partial decryptions have been successfully submitted')
return HttpResponseRedirect(reverse("user_home")) return HttpResponseRedirect(reverse("user_home"))
# Without an access key, the client does not have permission to access this page # Without an access key, the client does not have permission to access this page

View file

@ -111,9 +111,17 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button> <button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title" style="text-align: center"><strong>Ballot</strong></h4> <h4 class="modal-title" style="text-align: center"><strong>Please Select a Ballot</strong></h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="choice-group">
<a id="choice-A" class="btn btn-sq btn-primary">
A
</a>
<a id="choice-B" class="btn btn-sq btn-warning choice">
B
</a>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>

View file

@ -1,68 +1,28 @@
from django.template import RequestContext from django.template import RequestContext
from django.shortcuts import render_to_response, render
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.views import generic from django.views import generic
from allauthdemo.polls.models import Event from allauthdemo.polls.models import Event
from django.shortcuts import get_object_or_404, render, render_to_response from django.shortcuts import render_to_response
@login_required @login_required
def member_index(request): def member_index(request):
return render_to_response("member/member-index.html", RequestContext(request)) return render_to_response("member/member-index.html", RequestContext(request))
#def member_events(request):
#self.publisher = get_object_or_404(Publisher, name=self.args[0])
#return Book.objects.filter(publisher=self.publisher)
#return render_to_response("member/member-events.html", RequestContext(request))
class MemberEvents(generic.ListView): class MemberEvents(generic.ListView):
model = Event model = Event
template_name = 'member/member-events.html' template_name = 'member/member-events.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(MemberEvents, self).get_context_data(**kwargs) context = super(MemberEvents, self).get_context_data(**kwargs)
#self.object.organisers.filter(email=self.request.user.email())
# no check needed for anon, as url should make sure we're logged in! # no check needed for anon, as url should make sure we're logged in!
return context return context
def get_queryset(self): def get_queryset(self):
#self.publisher = get_object_or_404(Publisher, name=self.args[0])
return self.request.user.organisers.all() return self.request.user.organisers.all()
@login_required @login_required
def member_action(request): def member_action(request):
return render_to_response("member/member-action.html", RequestContext(request)) return render_to_response("member/member-action.html", RequestContext(request))
'''
class EventListView(generic.ListView):
model = Event
def get_context_data(self, **kwargs):
context = super(EventListView, self).get_context_data(**kwargs)
#context['now'] = timezone.now()
return context
def get_context_data(self, **kwargs):
context = super(EventDetailView, self).get_context_data(**kwargs)
context['is_organiser'] = ((not self.request.user.is_anonymous()) and (self.object.users.filter(email=self.request.user.email).exists()))
#context['now'] = timezone.now()
return context
class PollDetailView(generic.DetailView):
model = Poll
def get_context_data(self, **kwargs):
context = super(PollDetailView, self).get_context_data(**kwargs)
#context['now'] = timezone.now()
context['form'] = VoteForm(instance=self.object)
context['poll_count'] = self.object.event.polls.all().count()
return context
'''

View file

@ -182,5 +182,22 @@ input[type="file"] {
/* Voting page */ /* Voting page */
.big-checkbox { .big-checkbox {
width: 30px; height: 30px; width: 30px !important; height: 30px;
}
.btn-sq {
width: 125px !important;
height: 125px !important;
font-size: 16px;
line-height: 115px;
text-align: center;
}
.choice {
margin-left: 2em;
}
.choice-group {
width: 54%;
margin: 0 auto;
} }

View file

@ -1,3 +1,18 @@
function showDialogWithText(titleTxt, bodyTxt) {
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
title.text(titleTxt);
var p = document.createElement("p");
p.innerHTML = bodyTxt;
body.empty();
body.append( p );
modalDialog.modal('show');
}
// This should stop people ticking more than the maximum permitted // This should stop people ticking more than the maximum permitted
function updateCheckboxInteractivity() { function updateCheckboxInteractivity() {
var inputs = $("label input[type=checkbox]"); var inputs = $("label input[type=checkbox]");
@ -52,21 +67,13 @@ function isVotingInputValid() {
// This will highlight when people haven't selected enough options // This will highlight when people haven't selected enough options
if(!valid) { if(!valid) {
var modalDialog = $('#modalDialog'); let errText = "You've only selected " + selectedCount
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
var errText = "You've only selected " + selectedCount
+ " option(s). The minimum number you need to select is " + MIN_SELECTIONS + " option(s). The minimum number you need to select is " + MIN_SELECTIONS
+ " and the maximum is " + MAX_SELECTIONS + ". Please go back and correct this."; + " and the maximum is " + MAX_SELECTIONS + ". Please go back and correct this.";
title.text('Voting Error'); let titleTxt = 'Voting Error';
var p = document.createElement("p"); showDialogWithText(titleTxt, errText);
p.innerHTML = errText;
body.empty();
body.append( p );
modalDialog.modal('show');
return; return;
} }
@ -88,7 +95,6 @@ function genBlankVote() {
return vote; return vote;
} }
var progress = 0;
var progressBar = document.getElementById("progress-bar"); var progressBar = document.getElementById("progress-bar");
// Based on the user's vote in the current poll, this generates a ballot which // Based on the user's vote in the current poll, this generates a ballot which
@ -125,15 +131,21 @@ function generateBallot() {
checkboxInputs.each(function() { checkboxInputs.each(function() {
var checkbox = $(this); var checkbox = $(this);
// Push the selected option values to an array // Push the selected option values (ones that have been checked) to an array
if(checkbox.prop('checked')) { if(checkbox.prop('checked')) {
unencryptedVotes.push(checkbox.val()); unencryptedVotes.push(checkbox.val());
} }
// For whatever hasn't been selected, push a blank vote to the array });
else {
// If there is a dif between the num selected and the max allowed, push blank votes to the array to pad this
// to prevent information leakage
if(unencryptedVotes.length < MAX_SELECTIONS) {
let blankVotesToPush = MAX_SELECTIONS - unencryptedVotes.length;
for(let i = 0; i < blankVotesToPush; i++) {
unencryptedVotes.push(genBlankVote()); unencryptedVotes.push(genBlankVote());
} }
}); }
// Encrypt all of the votes for this ballot // Encrypt all of the votes for this ballot
var encryptedVotes = []; var encryptedVotes = [];
@ -163,12 +175,9 @@ function generateBallot() {
}); });
}); });
var ballot = { return {
encryptedVotes: encryptedVotes encryptedVotes: encryptedVotes
}; };
return ballot;
} }
$('#gen-ballots-btn').click(function() { $('#gen-ballots-btn').click(function() {
@ -181,28 +190,19 @@ $('#gen-ballots-btn').click(function() {
$('#progress-bar-description').toggleClass('hidden'); $('#progress-bar-description').toggleClass('hidden');
$('#progress-bar-container').toggleClass('hidden'); $('#progress-bar-container').toggleClass('hidden');
setTimeout(generateBallotsAndShowUsr, 50); setTimeout(generateBallotsAndShowUsr, 25);
} }
}); });
function voteSuccessfullyReceived() { function voteSuccessfullyReceived() {
var modalDialog = $('#modalDialog'); let titleTxt = 'Vote Successfully Received';
var title = modalDialog.find('.modal-title'); let bodyText = "Thank you for voting!";
var body = modalDialog.find('.modal-body');
title.text('Vote Successfully Received');
var bodyText = "Thank you for voting!";
if(POLL_NUM !== POLL_COUNT) { if(POLL_NUM !== POLL_COUNT) {
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'."; bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
} }
var p = document.createElement("p"); showDialogWithText(titleTxt, bodyText);
p.innerHTML = bodyText;
body.empty();
body.append( p );
modalDialog.modal('show');
} }
var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val(); var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val();
@ -230,25 +230,135 @@ function sendBallotToServer(ballot) {
}); });
} }
var bytestostring = function(b) {
var s = "";
var len = b.length;
var ch;
for (var i = 0; i < len; i++) {
ch = b[i];
s += ((ch >>> 4) & 15).toString(16);
s += (ch & 15).toString(16);
}
return s;
};
var stringtobytes = function(s) {
var b = [];
for (var i = 0; i < s.length; i++) {
b.push(s.charCodeAt(i));
}
return b;
};
function SHA256Hash(bytes, toStr) {
var ctx = new CTX();
var R = [];
var H = new ctx.HASH256();
H.process_array(bytes);
R = H.hash();
if (R.length === 0) {
return null;
}
if(toStr) {
// If toStr is true we return the stringified version of the bytes of the hash
return bytestostring(R);
} else {
// If toStr is false we return the bytes of the hash
return R;
}
}
// FAO Ben: Called once the ballot has been sent to the back-end and dialog has closed
function onAfterBallotSend() {
// TODO: FAO Ben: Implement QR func here.
// TODO: Currently, there is a dialog already implemented in the event_vote.html page which is
// TODO: used for voting error information but could be used to display the QR code using JS in
// TODO: a similar way that showBallotChoiceDialog does.
}
function processBallotSelection(selection, selectionHash, successFn) {
// Dispatch the ballot to the server
sendBallotToServer(selection);
// Close the choice selection dialog
var modalDialog = $('#modalDialog');
modal.modal('hide');
// Call the successfn currently with the selection hash but this may not be needed
successFn(selectionHash);
}
function showBallotChoiceDialog(ballotA, ballotB) {
// Output hashes of the 2 ballots
const BALLOT_A_HASH = SHA256Hash(stringtobytes(JSON.stringify(ballotA)), true);
const BALLOT_B_HASH = SHA256Hash(stringtobytes(JSON.stringify(ballotB)), true);
// With the ballots and their hashes generated, we can display the ballot choice dialog
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
body.empty();
title.text('Please Select a Ballot');
// Generate the body of the dialog which consists of a button for A and for B as well as their hashes
var choiceGroupDiv = document.createElement('div');
choiceGroupDiv.setAttribute('class', 'choice-group');
var btnChoiceA = document.createElement('a');
btnChoiceA.setAttribute('id', 'choice-A');
btnChoiceA.setAttribute('class', 'btn btn-sq btn-primary');
btnChoiceA.innerHTML = 'A';
choiceGroupDiv.append(btnChoiceA);
var btnChoiceB = document.createElement('a');
btnChoiceB.setAttribute('id', 'choice-B');
btnChoiceB.setAttribute('class', 'btn btn-sq btn-warning choice');
btnChoiceB.innerHTML = 'B';
choiceGroupDiv.append(btnChoiceB);
// ----------------------------------------------
var hashGroupDiv = document.createElement('div');
var br = document.createElement('br');
hashGroupDiv.append( br );
var hashA = document.createElement("span");
hashA.innerHTML = "Hash A: " + BALLOT_A_HASH;
hashGroupDiv.append( hashA );
var br2 = document.createElement('br');
hashGroupDiv.append( br2 );
var hashB = document.createElement("span");
hashB.innerHTML = "Hash B: " + BALLOT_B_HASH;
hashGroupDiv.append( hashB );
// -----------------------------------------------
body.append(choiceGroupDiv);
body.append(hashGroupDiv);
modalDialog.modal('show');
// Register callback functions for the selection of either A or B
$('#choice-A').click(function(e) {
processBallotSelection(ballotA, BALLOT_A_HASH, onAfterBallotSend);
});
$('#choice-B').click(function(e) {
processBallotSelection(ballotB, BALLOT_B_HASH, onAfterBallotSend);
});
}
function generateBallotB(ballotA) { function generateBallotB(ballotA) {
var ballotB = generateBallot(); var ballotB = generateBallot();
progressBar.setAttribute("style", "width: 100%;"); progressBar.setAttribute("style", "width: 100%;");
var ballots = { showBallotChoiceDialog(ballotA, ballotB);
A : ballotA,
B : ballotB
};
// TODO: Implement ballot choice UI and QR func here. At the moment the code automatically
// TODO: submits the first ballot (as if the user selected it) to the server but this needs updating.
// TODO: Currently, there is a dialog already implemented in the event_vote.html page which is
// TODO: used for voting error information but could be used to display the ballot choices.
// This delay allows the execution thread to update the above CSS on the progress bar
var selectedBallot = ballots.A;
setTimeout(function () {
sendBallotToServer(selectedBallot);
}, 50);
} }
function generateBallotsAndShowUsr() { function generateBallotsAndShowUsr() {
@ -262,5 +372,5 @@ function generateBallotsAndShowUsr() {
// This delay allows the execution thread to update the above CSS on the progress bar // This delay allows the execution thread to update the above CSS on the progress bar
setTimeout(function () { setTimeout(function () {
generateBallotB(ballotA); generateBallotB(ballotA);
}, 125); }, 150);
} }