Merge pull request #2 from vincentmdealmeida/ValidationV1
Laid the ground work for client-side input validation by setting up a…
This commit is contained in:
commit
4ca3398683
4 changed files with 157 additions and 37 deletions
|
@ -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,10 +56,28 @@ 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]
|
||||
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]
|
||||
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
|
||||
|
|
|
@ -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 }])
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div class="form-group"> <!-- Excluded class(missing %s): { if election_form.name.errors }has-error{ endif } -->
|
||||
<label for="name-input" class="col-sm-3 col-md-2 control-label">Name:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control" id="name-input" placeholder="Example: My poll" name="name-input" maxlength="255">
|
||||
<input type="text" class="form-control input-control" id="name-input" placeholder="Example: My poll" name="name-input" maxlength="255">
|
||||
<span id="name-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
A short and clear name.
|
||||
|
@ -34,7 +34,7 @@
|
|||
<div class="form-group"> <!-- Excluded class(missing %s): { if election_form.slug.errors }has-error{ endif } -->
|
||||
<label for="identifier-input" class="col-sm-3 col-md-2 control-label">Identifier:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control" id="identifier-input" placeholder="Example: My-poll" name="identifier-input" maxlength="255">
|
||||
<input type="text" class="form-control input-control" id="identifier-input" placeholder="Example: My-poll" name="identifier-input" maxlength="255">
|
||||
<span id="identifier-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Used in the election URL, it must only consist of letters, numbers, underscores or hyphens; no whitespace is permitted.
|
||||
|
@ -47,7 +47,7 @@
|
|||
<label for="vote-start-input" class="col-sm-3 col-md-2 control-label">Voting starts at:</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<div class="input-group date">
|
||||
<input type="text" class="form-control" data-date-format="YYYY-MM-DD H:mm" id="vote-start-input" name="vote-start-input">
|
||||
<input type="text" class="form-control input-control" data-date-format="YYYY-MM-DD H:mm Z" id="vote-start-input" name="vote-start-input">
|
||||
<span class="input-group-addon btn">
|
||||
<i class="fa fa-calendar" aria-hidden="true"></i>
|
||||
/
|
||||
|
@ -56,7 +56,7 @@
|
|||
</div>
|
||||
<span id="vote-start-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
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 '+'.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,7 +65,7 @@
|
|||
<label for="vote-end-input" class="col-sm-3 col-md-2 control-label">Voting ends at:</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<div class="input-group date">
|
||||
<input type="text" class="form-control" data-date-format="YYYY-MM-DD H:mm" id="vote-end-input" name="vote-end-input">
|
||||
<input type="text" class="form-control input-control" data-date-format="YYYY-MM-DD H:mm Z" id="vote-end-input" name="vote-end-input">
|
||||
<span class="input-group-addon btn">
|
||||
<i class="fa fa-calendar" aria-hidden="true"></i>
|
||||
/
|
||||
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
<span id="vote-end-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
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 '+'.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -82,7 +82,7 @@
|
|||
<div class="form-group">
|
||||
<label for="question-input" class="col-sm-3 col-md-2 control-label">Question / Statement:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control" id="question-input" placeholder="Example: Elections for the European Parliament" name="question-input" maxlength="200">
|
||||
<input type="text" class="form-control input-control" id="question-input" placeholder="Example: Elections for the European Parliament" name="question-input" maxlength="200">
|
||||
<span id="question-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Question / Statement that will be put forward to voters along with the below options.
|
||||
|
@ -115,7 +115,7 @@
|
|||
<td>
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: Candidate 1" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate 1" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -136,7 +136,7 @@
|
|||
<td>
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: Candidate 2" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate 2" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -157,7 +157,7 @@
|
|||
<td>
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: Candidate X" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate X" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -190,12 +190,12 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label class="sr-only" for="minimum-input">Minimum</label>
|
||||
<input type="number" class="form-control" id="minimum-input" placeholder="Minimum" value="" name="minimum-input" min="0"> <!-- TODO: Max should be set to the number of options -->
|
||||
<input type="number" class="form-control input-control" id="minimum-input" placeholder="Minimum" value="" name="minimum-input" min="0"> <!-- TODO: Max should be set to the number of options -->
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<label class="sr-only" for="maximum-input">Maximum</label>
|
||||
<input type="number" class="form-control" id="maximum-input" placeholder="Maximum" value="" name="maximum-input" min="1"> <!-- TODO: Max should be set to the number of options -->
|
||||
<input type="number" class="form-control input-control" id="maximum-input" placeholder="Maximum" value="" name="maximum-input" min="1"> <!-- TODO: Max should be set to the number of options -->
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</div>
|
||||
|
@ -231,7 +231,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" value="{{ user_email }}" maxlength="255">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" value="{{ user_email }}" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -252,7 +252,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -272,7 +272,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -322,7 +322,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" value="{{ user_email }}" maxlength="255">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" value="{{ user_email }}" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -343,7 +343,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -363,7 +363,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
|
@ -392,7 +392,7 @@
|
|||
<div class="form-group">
|
||||
<label for="voters-list-input" class="col-sm-3 col-md-2 control-label">Voters List:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<textarea class="form-control" id="voters-list-input" placeholder="alice@example.com, bob@example.com..." name="voters-list-input" rows="4"></textarea>
|
||||
<textarea class="form-control input-control" id="voters-list-input" placeholder="alice@example.com, bob@example.com..." name="voters-list-input" rows="4"></textarea>
|
||||
<span id="voters-list-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Manually enter email addresses separated with commas. Alternatively, you can also upload a CSV file:
|
||||
|
@ -422,7 +422,7 @@
|
|||
</div>
|
||||
<hr>
|
||||
<input class="btn btn-success" type="submit" value="Create Event" id="submit-event-create" disabled/>
|
||||
<input class="btn btn-danger" type="button" value="Cancel" onclick="location.href='{% url 'polls:index' %}'" />
|
||||
<input class="btn btn-danger" type="button" value="Cancel" id="cancel-event-create" onclick="location.href='{% url 'polls:index' %}'" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue