From 6dcafb2e9adc3e4a35fc1366affdc30e652717b1 Mon Sep 17 00:00:00 2001 From: vince0656 Date: Wed, 13 Jun 2018 13:01:55 +0100 Subject: [PATCH] Laid the ground work for client-side input validation by setting up a fn that's triggered before the form is submitted. The vote start and end date time is now being validated both server side and client side and now includes UTC offsets --- .../polls/utils/CreateNewEventModelAdaptor.py | 24 +++- allauthdemo/polls/views.py | 2 +- allauthdemo/templates/polls/create_event.html | 40 +++--- static/js/create-event-poll.js | 128 ++++++++++++++++-- 4 files changed, 157 insertions(+), 37 deletions(-) diff --git a/allauthdemo/polls/utils/CreateNewEventModelAdaptor.py b/allauthdemo/polls/utils/CreateNewEventModelAdaptor.py index 96c0d51..1444ce6 100644 --- a/allauthdemo/polls/utils/CreateNewEventModelAdaptor.py +++ b/allauthdemo/polls/utils/CreateNewEventModelAdaptor.py @@ -1,5 +1,7 @@ from datetime import datetime +from django.utils.dateparse import parse_datetime + from allauthdemo.polls.models import Event from allauthdemo.polls.models import Poll from allauthdemo.polls.models import PollOption @@ -54,11 +56,29 @@ class CreateNewEventModelAdaptor: self.identifier = self.form_data.pop('identifier-input')[0] # Extract start and end times as string and convert to datetime + # The UTC offset comes with a colon i.e. '+01:00' which needs to be removed starts_at = self.form_data.pop('vote-start-input')[0] - self.starts_at = datetime.strptime(starts_at, '%Y-%m-%d %H:%M') + starts_at_offset_index = starts_at.find('+') + + if starts_at_offset_index != -1: + starts_at_time = starts_at[0: starts_at_offset_index-1].replace(' ', 'T') + starts_at_offset = starts_at[starts_at_offset_index:].replace(':', '') + starts_at = starts_at_time + starts_at_offset + self.starts_at = parse_datetime(starts_at) + else: + self.starts_at = datetime.strptime(starts_at, '%Y-%m-%d %H:%M') + ends_at = self.form_data.pop('vote-end-input')[0] - self.ends_at = datetime.strptime(ends_at, '%Y-%m-%d %H:%M') + ends_at_offset_index = ends_at.find('+') + + if ends_at_offset_index != -1: + ends_at_time = ends_at[0:ends_at_offset_index-1].replace(' ', 'T') + ends_at_offset = ends_at[ends_at_offset_index:].replace(':', '') + ends_at = ends_at_time + ends_at_offset + self.ends_at = parse_datetime(ends_at) + else: + self.ends_at = datetime.strptime(ends_at, '%Y-%m-%d %H:%M') # Extract the list of organisers organisers_list = self.form_data.pop('organiser-email-input') diff --git a/allauthdemo/polls/views.py b/allauthdemo/polls/views.py index b7021f8..1409b51 100755 --- a/allauthdemo/polls/views.py +++ b/allauthdemo/polls/views.py @@ -302,7 +302,7 @@ def create_event(request): # TODO: Based on whether validation was successful within update model and whether # TODO: data was actually persisted, either perform a redirect (success) or flag an error - return HttpResponseRedirect("/event") + return HttpResponseRedirect("/event/") elif request.method == "GET": #form = EventForm() #organiser_formset = OrganiserFormSet(prefix="formset_organiser", initial=[{'email': request.user.email }]) diff --git a/allauthdemo/templates/polls/create_event.html b/allauthdemo/templates/polls/create_event.html index 71b8927..122196c 100755 --- a/allauthdemo/templates/polls/create_event.html +++ b/allauthdemo/templates/polls/create_event.html @@ -22,7 +22,7 @@
- + A short and clear name. @@ -34,7 +34,7 @@
- + Used in the election URL, it must only consist of letters, numbers, underscores or hyphens; no whitespace is permitted. @@ -47,7 +47,7 @@
- + / @@ -56,7 +56,7 @@
- Date and time when registered voters can commence voting. + Date and time when registered voters can commence voting. This includes the UTC offset starting with '+'.
@@ -65,7 +65,7 @@
- + / @@ -74,7 +74,7 @@
- Date and time when registered voters can no longer vote. + Date and time when registered voters can no longer vote. This includes the UTC offset starting with '+'.
@@ -82,7 +82,7 @@
- + Question / Statement that will be put forward to voters along with the below options. @@ -115,7 +115,7 @@
- +
@@ -136,7 +136,7 @@
- +
@@ -157,7 +157,7 @@
- +
@@ -190,12 +190,12 @@
- +
- +
@@ -231,7 +231,7 @@
- +
@@ -252,7 +252,7 @@
- +
@@ -272,7 +272,7 @@
- +
@@ -322,7 +322,7 @@
- +
@@ -343,7 +343,7 @@
- +
@@ -363,7 +363,7 @@
- +
@@ -392,7 +392,7 @@
- + Manually enter email addresses separated with commas. Alternatively, you can also upload a CSV file: @@ -422,7 +422,7 @@

- +
diff --git a/static/js/create-event-poll.js b/static/js/create-event-poll.js index 4f685d5..d4bd438 100755 --- a/static/js/create-event-poll.js +++ b/static/js/create-event-poll.js @@ -1,3 +1,111 @@ +// Form submission and validation +var submitBtn = $("#submit-event-create"); +var dateRegex = /^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))\s[0-9]{2}:[0-9]{2}\s\+[0-9]{2}:[0-9]{2}$/; +var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +var reCaptchaValid = false; + +$("#election-form").submit(function(e) { + // Intercept submission of form and temporarily suspend it + e.preventDefault(); + var form = this; + + // Get a reference to the submit button + submitBtn.prop('disabled', true); + submitBtn.val('Please wait...'); + + // Disable the cancel button during validation + var cancelBtn = $("#cancel-event-create"); + cancelBtn.prop('disabled', true); + + // Perform input validation + var formDataValid = isFormValid(); + + if( formDataValid === true ) { + form.submit(); + } else { + submitBtn.val('Errors Found'); + cancelBtn.removeAttr('disabled'); + } +}); + +function isFormValid() { + var nameValid = isNameValid(); + var slugValid = isSlugValid(); + var voteStartValid = isVoteStartValid(); + var voteEndValid = isVoteEndValid(); + var pollOptsValid = arePollsAndOptsValid(); + var minSelectionValid = isMinSelectionValid(); + var maxSelectionValid = isMaxSelectionValid(); + var organisersEmailsValid = areOrganisersEmailsValid(); + var trusteesEmailsValid = areTrusteesEmailsValid(); + var votersListValid = isVotersListValid(); + var reCaptchaValid = isReCaptchaStillValid(); + + return nameValid && slugValid && voteStartValid && voteEndValid + && pollOptsValid && minSelectionValid && maxSelectionValid + && organisersEmailsValid && trusteesEmailsValid && votersListValid + && reCaptchaValid; +} + +function isNameValid() { + // Based on a list of names supplied + return true; +} + +function isSlugValid() { + return true; +} + +function isVoteStartValid() { + var start_date_time = $('#vote-start-input').val(); + return isDateValid(start_date_time); +} + +function isVoteEndValid() { + var end_date_time = $('#vote-end-input').val(); + return isDateValid(end_date_time); +} + +function isDateValid(date_time) { + return dateRegex.test(date_time); +} + +function arePollsAndOptsValid() { + // Future validation could be added here + return true; +} + +function isMinSelectionValid() { + return true; +} + +function isMaxSelectionValid() { + return true; +} + +function areOrganisersEmailsValid() { + return true; +} + +function areTrusteesEmailsValid() { + return true; +} + +function isVotersListValid() { + return true; +} + +function isReCaptchaStillValid() { + return true; +} + +$('.input-control').on('input', function(e) { + if(reCaptchaValid === true) { + submitBtn.val('Create Event'); + submitBtn.removeAttr('disabled'); + } +}); + // File handling function processFileChange(event) { @@ -55,11 +163,14 @@ document.getElementById('files').addEventListener('change', processFileChange, f // reCAPTCHA function reCVerificationCallback() { - $('#submit-event-create').removeAttr('disabled'); + // TODO: call isFormValid before doing this and highlighting errors if any found + reCaptchaValid = true; + submitBtn.removeAttr('disabled'); } function reCExpiredCallback() { - $('#submit-event-create').prop('disabled', true); + reCaptchaValid = false; + submitBtn.prop('disabled', true); } // Slug field. @@ -128,6 +239,7 @@ $('#vote-start-input, #vote-end-input').parent('.date').datetimepicker({ }, minDate: moment().startOf('day'), useCurrent: false, + locale: moment.utc() }); // Form management and Sortable rows @@ -138,18 +250,6 @@ function update(event, ui) { updateFormset(formset); } -/*$('#options-input-table').rowSorter({ - "handler" : null, // drag handler selector (default: null) - "tbody" : true, // True if you want to sort only tbody > tr. (default: true) - "tableClass" : "sorting-table", // This is added to the table during sorting - "dragClass": "sorting-row", // dragging row's class name (default: "sorting-row"). - "stickTopRows": 0, // count of top sticky rows (default: 0) - "stickBottomRows": 0, // count of bottom sticky rows (default: 0) - "onDragStart": dragStart, // (default: null) - "onDragEnd": dragEnd, // (default: null) - "onDrop": drop // (default: null) -});*/ - $("#options-input-table, #organisers-input-table, #trustees-input-table").sortable({ items: "tr", update: update