From f4ef6b01b1c0745caf9407f7f1bead179f9eccec Mon Sep 17 00:00:00 2001
From: Felix Sargent
Date: Thu, 25 Jul 2024 10:41:38 -0700
Subject: [PATCH] Improve and verify poll pages. (#213)
---
.github/workflows/django.yml | 1 +
.trunk/configs/.bandit | 2 +
approval_polls/settings.py | 52 ++--
approval_polls/staticfiles/create.js | 392 ++++++-------------------
approval_polls/staticfiles/detail.js | 300 ++++++++-----------
approval_polls/staticfiles/edit.js | 1 -
approval_polls/staticfiles/my_polls.js | 139 +++------
approval_polls/templates/base.html | 6 +-
approval_polls/templates/create.html | 283 ++++++++----------
approval_polls/templates/detail.html | 263 ++++++-----------
approval_polls/templates/edit.html | 301 -------------------
approval_polls/templates/index.html | 12 +-
approval_polls/templates/my_info.html | 16 +-
approval_polls/templates/my_polls.html | 6 -
approval_polls/tests.py | 183 +-----------
approval_polls/urls.py | 3 +-
approval_polls/views.py | 368 +++++++++++------------
pyproject.toml | 4 +
18 files changed, 711 insertions(+), 1621 deletions(-)
create mode 100644 .trunk/configs/.bandit
delete mode 100644 approval_polls/staticfiles/edit.js
delete mode 100644 approval_polls/templates/edit.html
diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml
index 020c180..7a61c73 100644
--- a/.github/workflows/django.yml
+++ b/.github/workflows/django.yml
@@ -27,4 +27,5 @@ jobs:
- run: poetry install
- name: Run Tests
run: |
+ poetry run python manage.py compress
poetry run pytest
diff --git a/.trunk/configs/.bandit b/.trunk/configs/.bandit
new file mode 100644
index 0000000..99a68b1
--- /dev/null
+++ b/.trunk/configs/.bandit
@@ -0,0 +1,2 @@
+[bandit]
+exclude = *_test.py, */test_*.py,*/tests.py
\ No newline at end of file
diff --git a/approval_polls/settings.py b/approval_polls/settings.py
index 84d6abe..ceef30a 100644
--- a/approval_polls/settings.py
+++ b/approval_polls/settings.py
@@ -25,18 +25,41 @@
SECRET_KEY = env("SECRET_KEY")
db_path = "/data/prod.sqlite3"
+
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
+APP_NAME = env("FLY_APP_NAME", str, "")
+ALLOWED_HOSTS = [f"{APP_NAME}.fly.dev", "vote.electionscience.org"] # ← Updated!
+
if DEBUG:
db_path = os.path.join(BASE_DIR, "db.sqlite3")
+ CSRF_TRUSTED_ORIGINS = ["http://localhost:8000", "http://127.0.0.1:8000"]
+ CSRF_ALLOWED_ORIGINS = ["http://localhost:8000", "http://127.0.0.1:8000"]
+ CORS_ORIGINS_WHITELIST = ["http://localhost:8000", "http://127.0.0.1:8000"]
+ ALLOWED_HOSTS.extend(["localhost", "0.0.0.0", "127.0.0.1"]) # trunk-ignore(bandit)
if not DEBUG:
COMPRESS_OFFLINE = True
LIBSASS_OUTPUT_STYLE = "compressed"
+ sentry_sdk.init(
+ dsn="https://78856604267db99554868743d5eb61e5@o4506681396625408.ingest.sentry.io/4506681396756480",
+ # Set traces_sample_rate to 1.0 to capture 100%
+ # of transactions for performance monitoring.
+ traces_sample_rate=1.0,
+ # Set profiles_sample_rate to 1.0 to profile 100%
+ # of sampled transactions.
+ # We recommend adjusting this value in production.
+ profiles_sample_rate=1.0,
+ )
+ CSRF_TRUSTED_ORIGINS = ["https://vote.electionscience.org"]
+ CSRF_ALLOWED_ORIGINS = ["https://vote.electionscience.org"]
+ CORS_ORIGINS_WHITELIST = ["https://vote.electionscience.org"]
+
-if "test" in sys.argv:
+if "test" in sys.argv or "pytest" in sys.argv:
COMPRESS_OFFLINE = False
COMPRESS_ENABLED = False
-
DATABASES = {
"default": {
# Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
@@ -45,16 +68,6 @@
}
}
-# Hosts/domain names that are valid for this site; required if DEBUG is False
-# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
-APP_NAME = env("FLY_APP_NAME", str, "")
-ALLOWED_HOSTS = [f"{APP_NAME}.fly.dev", "vote.electionscience.org"] # ← Updated!
-if DEBUG:
- ALLOWED_HOSTS.extend(["localhost", "0.0.0.0", "127.0.0.1"])
-
-CSRF_TRUSTED_ORIGINS = ["https://vote.electionscience.org"]
-CSRF_ALLOWED_ORIGINS = ["https://vote.electionscience.org"]
-CORS_ORIGINS_WHITELIST = ["https://vote.electionscience.org"]
# The following settings are required for the activation emails in the
# registration module to work.
@@ -136,7 +149,7 @@
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
- "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.filesystem.Loader"
"django.template.loaders.app_directories.Loader",
)
@@ -149,6 +162,7 @@
"django_ajax.middleware.AJAXMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"allauth.account.middleware.AccountMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
)
ROOT_URLCONF = "approval_polls.urls"
@@ -274,15 +288,3 @@
# ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5 # or any reasonable number
ACCOUNT_RATE_LIMITS = False
ACCOUNT_FORMS = {"signup": "approval_polls.forms.CustomSignupForm"}
-
-
-sentry_sdk.init(
- dsn="https://78856604267db99554868743d5eb61e5@o4506681396625408.ingest.sentry.io/4506681396756480",
- # Set traces_sample_rate to 1.0 to capture 100%
- # of transactions for performance monitoring.
- traces_sample_rate=1.0,
- # Set profiles_sample_rate to 1.0 to profile 100%
- # of sampled transactions.
- # We recommend adjusting this value in production.
- profiles_sample_rate=1.0,
-)
diff --git a/approval_polls/staticfiles/create.js b/approval_polls/staticfiles/create.js
index bb226e0..24d6d89 100644
--- a/approval_polls/staticfiles/create.js
+++ b/approval_polls/staticfiles/create.js
@@ -1,337 +1,115 @@
$(function () {
- var that = this;
- this.lastId = undefined;
- var changeDateLogic, roundMinutes, setDefaultOptions, changeDisabledOptions;
- var validateTokenField;
- /* Add an extra textfield for a poll choice on the poll creation page. */
- function addChoiceField(numChoiceFields) {
- var formGroup, input;
- numChoiceFields++;
- that.lastId = numChoiceFields;
- formGroup = $("");
-
- input = $(
- "",
- );
-
- formGroup.append(input);
-
- $(".form-group").last().after(formGroup);
- $("[data-toggle=tooltip]").tooltip();
- }
- $("button#add-choice").on("click", function () {
- addChoiceField(that.lastId || 4);
- });
-
- $("button#add-choice-edit").on("click", function () {
- if (that.lastId == undefined) {
- that.lastId = parseInt($("#LastId").val());
- }
- addChoiceField(that.lastId);
- });
-
- $("[data-toggle=tooltip]").tooltip();
+ const pollOptions = {
+ container: $("#poll-options"),
+ addButton: $("#add-choice"),
+ removeSelector: ".remove-choice",
+ };
- /* Allow user to attach an external link to an option. */
+ // Initialize lastId based on the number of existing options
+ let lastId = pollOptions.container.children().length;
- // Event delegation to capture dynamically added links
- $(".row")
- .on("click", "a.add-link", function (e) {
+ const initializePollOptions = () => {
+ pollOptions.container.on("click", pollOptions.removeSelector, function (e) {
e.preventDefault();
- var alertDiv, alertDivId, currentUrl;
- alertDivId = $(this).attr("id");
- alertDivId = alertDivId.split("-").pop();
-
- alertDiv =
- "" +
- "
" +
- "" +
- " " +
- "
";
-
- if ($("#alert-" + alertDivId).length === 0) {
- // Remove all previous alerts
- $(".alert").remove();
- // Append the alert box before selected option
- $("#div-" + alertDivId).before(alertDiv);
- // Populate textbox with the last 'valid and inserted' URL
- currentUrl = $("#linkurl-" + alertDivId).val();
- $("#url-" + alertDivId).val(currentUrl);
- }
+ $(this).closest(".poll-option").remove();
+ updateOptionNumbers();
+ });
- $('button[id^="confirm-link-"]').click(function () {
- var buttonId, linkUrl, validUrl, urlPattern;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- linkUrl = $("#url-" + buttonId).val();
- linkUrl = $.trim(linkUrl);
- // Check if URL begins with http or https or ftp
- // If not, prepend 'http://'
- urlPattern = new RegExp("^(http|https|ftp)://", "i");
- if (!urlPattern.test(linkUrl)) {
- linkUrl = "http://" + linkUrl;
- }
- // Source: https://github.com/jzaefferer/jquery-validation/blob/master/src/core.js
- validUrl =
- /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
- linkUrl,
- );
- if (!validUrl) {
- // If URL is not valid, change class of alert box
- $("#alert-" + buttonId).attr("class", "alert alert-info has-error");
- // If URL is not valid, show an error message
- $('label[for="url-' + buttonId + '"]').remove();
- $("#alert-" + buttonId).prepend(
- "",
- );
- } else {
- // Reset class of alert box
- $("#alert-" + buttonId).attr("class", "alert alert-info");
- // Remove any error message
- $("label[for='url-" + buttonId + "']").remove();
- // Update value of hidden input field
- $("#linkurl-" + alertDivId).val(linkUrl);
- // Remove alert box
- $("#alert-" + buttonId).remove();
- // Change color of link to show a valid insertion
- $("#link-" + buttonId).addClass("text-success");
- }
- });
+ pollOptions.addButton.on("click", addChoiceField);
+ };
- $('button[id^="cancel-link-"]').click(function () {
- var buttonId;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- // Remove alert box
- $("#alert-" + buttonId).remove();
- });
+ const addChoiceField = () => {
+ lastId++;
+ const newOption = `
+
+
+
+
+
+
+
+ `;
+ pollOptions.container.append(newOption);
+ };
- $('button[id^="remove-link-"]').click(function () {
- var buttonId;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- // Reset value of hidden input field to empty string
- $("#linkurl-" + buttonId).val("");
- // Reset value of textbox
- $("#url-" + alertDivId).val("");
- // Reset class of alert box
- $("#alert-" + buttonId).attr("class", "alert alert-info");
- // Remove any error message
- $("label[for='url-" + buttonId + "']").remove();
- // Reset color of link to show no current insertion
- $("#link-" + buttonId).removeClass("text-success");
- });
- // To prevent navigation
- return false;
- })
- .on("click", "a.remove-choice", function (e) {
- e.preventDefault();
- var container = $(e.currentTarget).closest(".form-group");
- container.remove();
- return false;
+ const updateOptionNumbers = () => {
+ pollOptions.container.children(".poll-option").each(function (index) {
+ const newIndex = index + 1;
+ $(this).find("label").text(`Option ${newIndex}`);
+ $(this)
+ .find("input")
+ .attr("id", `choice${newIndex}`)
+ .attr("name", `choice${newIndex}`);
});
+ lastId = pollOptions.container.children().length;
+ };
- // $('a.remove-choice').click(function(e) {
- // e.preventDefault();
- // var container = $(e.currentTarget).closest('.form-group');
- // container.remove();
- // return false;
- // });
+ const initializeTokenFields = () => {
+ const allTags = $("#allTags").length ? $("#allTags").val().split(",") : [];
- /* Allow user to select a poll closing date and time from a Jquery
- DateTime picker. */
+ emailValidation.tokenField
+ .on("tokenfield:createtoken", tokenize)
+ .on("tokenfield:createdtoken", validateEmailToken)
+ .on("tokenfield:removedtoken", validateTokenField)
+ .tokenfield();
- roundMinutes = function (today) {
- var hr, min, time;
- min = today.getMinutes();
- if (min >= 0 && min < 30) {
- today.setMinutes(30);
- } else if (min >= 30 && min < 60) {
- today.setHours(today.getHours() + 1);
- today.setMinutes(0);
- }
- hr = ("0" + today.getHours()).slice(-2);
- min = ("0" + today.getMinutes()).slice(-2);
- time = hr + ":" + min;
- return [time, today];
+ $("#tokenTagField")
+ .on("tokenfield:createtoken", tokenize)
+ .tokenfield()
+ .tokenfield("setTokens", allTags);
};
- setDefaultOptions = function () {
- var options, roundDateTime, roundDate, roundTime;
- options = {};
- roundDateTime = roundMinutes(new Date());
- roundTime = roundDateTime[0];
- roundDate = roundDateTime[1];
- options["defaultDate"] = roundDate;
- options["minDate"] = roundDate;
- options["defaultTime"] = roundTime;
- options["minTime"] = roundTime;
- return options;
+ const tokenize = (e) => {
+ const data = e.attrs.value.split("|");
+ e.attrs.value = data[1] || data[0];
+ e.attrs.label = data[1] ? `${data[0]} (${data[1]})` : data[0];
};
- changeDateLogic = function (ct, $i) {
- var selected, current, roundDate;
- roundDate = roundMinutes(new Date())[1];
- selected = new Date(ct.dateFormat("Y/m/d"));
- current = new Date(roundDate.dateFormat("Y/m/d"));
- if (selected.getTime() == current.getTime()) {
- if (ct.dateFormat("H:i") < roundDate.dateFormat("H:i")) {
- $("#datetimepicker").val("");
- }
- $i.datetimepicker(setDefaultOptions());
- } else if (selected.getTime() > current.getTime()) {
- $i.datetimepicker({
- minTime: false,
- });
- } else if (selected.getTime() < current.getTime()) {
- $("#datetimepicker").val("");
- $i.datetimepicker({
- defaultDate: false,
- minTime: "23:59",
- });
+ const validateEmailToken = (e) => {
+ const valid = emailValidation.regex.test(e.attrs.value);
+ if (!valid) {
+ $(e.relatedTarget).addClass("invalid");
}
- $("#datetimepicker").change();
+ validateTokenField();
};
- $("#datetimepicker").datetimepicker(setDefaultOptions());
-
- $("#datetimepicker").datetimepicker({
- step: 30,
- todayButton: false,
- onShow: changeDateLogic,
- onSelectDate: changeDateLogic,
- onChangeMonth: changeDateLogic,
- });
-
- $("#datetimepicker").keydown(function (e) {
- if (e.keyCode == 8 || e.keyCode == 46) {
- $(this).val("");
- $(this).change();
- e.preventDefault();
- }
- });
+ const validateTokenField = () => {
+ const existingTokens = emailValidation.tokenField.tokenfield("getTokens");
+ const tokensValid = existingTokens.every((token) =>
+ emailValidation.regex.test(token.value),
+ );
- changeDisabledOptions = function () {
- if ($("#datetimepicker").val() == "") {
- $("#checkbox1").attr("disabled", true);
- $("#checkbox2").attr("disabled", true);
- } else {
- $("#checkbox1").prop("disabled", false);
- $("#checkbox2").prop("disabled", false);
- }
+ emailValidation.errorElement.toggle(
+ !tokensValid && existingTokens.length > 0,
+ );
};
- $("#datetimepicker").change(function () {
- changeDisabledOptions();
- });
-
- $("#datetimepicker").change();
-
- /* Validate the token field for the email list. */
- validateTokenField = function () {
- var re = /\S+@\S+\.\S+/;
- var existingTokens;
- var tokensValid = true;
- existingTokens = $("#tokenEmailField").tokenfield("getTokens");
+ const initializeEmailPollDisplay = () => {
+ if ($("#poll-vtype").val() == 3) {
+ emailPollDisplay();
+ }
- if (existingTokens.length === 0) {
- $("#email-error").hide();
- } else {
- $.each(existingTokens, function (index, token) {
- if (!re.test(token.value)) {
- tokensValid = false;
- }
- });
- if (tokensValid) {
- $("#email-error").hide();
+ $("input[name=radio-poll-type]:radio").on("click", function () {
+ if ($(this).val() == 3) {
+ emailPollDisplay();
} else {
- $("#email-error").show();
+ $("#email-input, #existing-emails").hide();
+ $("#poll-visibility").prop("checked", true);
}
- }
+ });
};
- $("#tokenEmailField")
- .on("tokenfield:createtoken", function (e) {
- tokenize(e);
- })
- .on("tokenfield:createdtoken", function (e) {
- // Simple E-mail validation
- var re = /\S+@\S+\.\S+/;
- var valid = re.test(e.attrs.value);
- if (!valid) {
- $(e.relatedTarget).addClass("invalid");
- }
- validateTokenField();
- })
- .on("tokenfield:removedtoken", function (e) {
- validateTokenField();
- })
- .tokenfield();
-
- var allTags = [];
- if ($("#allTags").length) {
- allTags = $("#allTags").val().split(",");
- }
-
- $("#tokenTagField")
- .on("tokenfield:createtoken", function (e) {
- tokenize(e);
- })
- .tokenfield();
- $("#tokenTagField").tokenfield("setTokens", allTags);
-
- // For edit page, display email text field if poll.vtype is 3
- if ($("#poll-vtype").val() == 3) {
- emailPollDisplay();
- }
- // Toggle the visibility of the email input
- $("input[name=radio-poll-type]:radio").click(function () {
- if ($(this).attr("value") == 3) {
- emailPollDisplay();
- } else {
- $("#email-input").hide();
- $("#poll-visibility").prop("checked", true);
- $("#existing-emails").hide();
- }
- });
-
- function emailPollDisplay() {
- $("#email-input").show();
- if ($("#poll-id") == undefined) {
+ const emailPollDisplay = () => {
+ $("#email-input, #existing-emails").show();
+ if ($("#poll-id").length === 0) {
$("#poll-visibility").prop("checked", false);
}
- $("#existing-emails").show();
- }
+ };
- function tokenize(e) {
- var data = e.attrs.value.split("|");
- e.attrs.value = data[1] || data[0];
- e.attrs.label = data[1] ? data[0] + " (" + data[1] + ")" : data[0];
- }
+ // Initialize everything
+ initializePollOptions();
+ initializeTokenFields();
+ initializeEmailPollDisplay();
});
diff --git a/approval_polls/staticfiles/detail.js b/approval_polls/staticfiles/detail.js
index f95274d..8672acc 100644
--- a/approval_polls/staticfiles/detail.js
+++ b/approval_polls/staticfiles/detail.js
@@ -1,200 +1,136 @@
$(function () {
+ // Social sharing
$("#share").jsSocials({
showLabel: false,
showCount: false,
shares: ["email", "twitter", "facebook", "linkedin", "pinterest"],
});
- var invitation_key, invitation_email;
-
- this.numChoiceFields = $("input:checkbox").length;
-
- this.addChoiceField = function () {
- var checkBox, input;
-
- this.numChoiceFields++;
- checkBox = $("");
- input = $(
- "",
- );
-
- checkBox.append(input);
-
- $(".checkbox").last().after(checkBox);
- $("[data-toggle=tooltip]").tooltip();
+ // Poll options
+ const pollOptions = {
+ container: $("#poll-options"),
+ addButton: $("#add-option"),
+ removeSelector: ".remove-choice",
};
- $("[data-toggle=tooltip]").tooltip();
- $("button#add-option").click($.proxy(this.addChoiceField, this));
-
- /* Allow user to attach an external link to an option. */
-
- // Event delegation to capture dynamically added links
- $(".row-fluid").on("click", "a", function () {
- var alertDiv, alertDivId, currentUrl;
- alertDivId = $(this).attr("id");
- alertDivId = alertDivId.split("-").pop();
-
- alertDiv =
- "" +
- "
" +
- "" +
- " " +
- "
";
-
- if ($("#alert-" + alertDivId).length === 0) {
- // Remove all previous alerts
- $(".alert").remove();
- // Append the alert box before selected option
- $("#label-" + alertDivId).before(alertDiv);
- // Populate textbox with the last 'valid and inserted' URL
- currentUrl = $("#linkurl-" + alertDivId).val();
- $("#url-" + alertDivId).val(currentUrl);
- }
+ let lastId = pollOptions.container.children().length;
+
+ const addChoiceField = () => {
+ lastId++;
+ const newOption = `
+
+ `;
+ pollOptions.container.append(newOption);
+ };
- $('button[id^="confirm-link-"]').click(function () {
- var buttonId, linkUrl, validUrl, urlPattern;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- linkUrl = $("#url-" + buttonId).val();
- linkUrl = $.trim(linkUrl);
- // Check if URL begins with http or https or ftp
- // If not, prepend 'http://'
- urlPattern = new RegExp("^(http|https|ftp)://", "i");
- if (!urlPattern.test(linkUrl)) {
- linkUrl = "http://" + linkUrl;
- }
- // Source: https://github.com/jzaefferer/jquery-validation/blob/master/src/core.js
- validUrl =
- /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
- linkUrl,
- );
- if (!validUrl) {
- // If URL is not valid, change class of alert box
- $("#alert-" + buttonId).attr("class", "alert alert-info has-error");
- // If URL is not valid, show an error message
- $('label[for="url-' + buttonId + '"]').remove();
- $("#alert-" + buttonId).prepend(
- "",
- );
- } else {
- // Reset class of alert box
- $("#alert-" + buttonId).attr("class", "alert alert-info");
- // Remove any error message
- $("label[for='url-" + buttonId + "']").remove();
- // Update value of hidden input field
- $("#linkurl-" + alertDivId).val(linkUrl);
- // Remove alert box
- $("#alert-" + buttonId).remove();
- // Change color of link to show a valid insertion
- $("#link-" + buttonId).attr("class", "text-success");
- }
- });
-
- $('button[id^="cancel-link-"]').click(function () {
- var buttonId;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- // Remove alert box
- $("#alert-" + buttonId).remove();
- });
-
- $('button[id^="remove-link-"]').click(function () {
- var buttonId;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- // Reset value of hidden input field to empty string
- $("#linkurl-" + buttonId).val("");
- // Reset value of textbox
- $("#url-" + alertDivId).val("");
- // Reset class of alert box
- $("#alert-" + buttonId).attr("class", "alert alert-info");
- // Remove any error message
- $("label[for='url-" + buttonId + "']").remove();
- // Reset color of link to show no current insertion
- $("#link-" + buttonId).removeAttr("class");
- });
- // To prevent navigation
- return false;
+ pollOptions.addButton.on("click", addChoiceField);
+
+ // Link handling
+ pollOptions.container.on("click", ".add-link", function (e) {
+ e.preventDefault();
+ const choiceId = $(this)
+ .closest(".form-check")
+ .find('input[type="checkbox"]')
+ .attr("id");
+ const alertDiv = createAlertDiv(choiceId);
+ $(this).closest(".form-check").before(alertDiv);
});
- var convertSeconds, onZero, time_difference;
-
- convertSeconds = function (total_seconds) {
- var days, hours, minutes, seconds, currentTime;
-
- days = Math.floor(total_seconds / 86400);
- hours = Math.floor((total_seconds % 86400) / 3600);
- minutes = Math.floor(((total_seconds % 86400) % 3600) / 60);
- seconds = Math.floor(((total_seconds % 86400) % 3600) % 60);
- hours = ("0" + hours).slice(-2);
- minutes = ("0" + minutes).slice(-2);
- seconds = ("0" + seconds).slice(-2);
- currentTime = days + "d:" + hours + "h:" + minutes + "m:" + seconds + "s";
-
- return currentTime;
- };
+ function createAlertDiv(choiceId) {
+ return `
+
+
+
+
+
+
+ `;
+ }
+
+ pollOptions.container.on("click", "[id^='confirm-link-']", function () {
+ const choiceId = this.id.split("-").pop();
+ const linkUrl = $(`#url-${choiceId}`).val().trim();
+ if (validateUrl(linkUrl)) {
+ $(`#linkurl-${choiceId}`).val(linkUrl);
+ $(`#alert-${choiceId}`).remove();
+ $(`#${choiceId}`)
+ .closest(".form-check")
+ .find(".add-link")
+ .addClass("btn-success");
+ } else {
+ $(`#alert-${choiceId}`)
+ .addClass("alert-danger")
+ .removeClass("alert-info")
+ .prepend('Please enter a valid URL
');
+ }
+ });
- $.fn.countdown = function (callback, duration) {
- var currentTimeString, container, countdown, message;
+ pollOptions.container.on("click", "[id^='remove-link-']", function () {
+ const choiceId = this.id.split("-").pop();
+ $(`#linkurl-${choiceId}`).val("");
+ $(`#url-${choiceId}`).val("");
+ $(`#alert-${choiceId}`).removeClass("alert-danger").addClass("alert-info");
+ $(`#${choiceId}`)
+ .closest(".form-check")
+ .find(".add-link")
+ .removeClass("btn-success");
+ });
- message = "before poll closes";
- currentTimeString = convertSeconds(duration);
- container = $(this[0]).html(currentTimeString + " " + message);
+ pollOptions.container.on("click", "[id^='cancel-link-']", function () {
+ const choiceId = this.id.split("-").pop();
+ $(`#alert-${choiceId}`).remove();
+ });
- countdown = setInterval(function () {
- if (--duration) {
- currentTimeString = convertSeconds(duration);
- container.html(currentTimeString + " " + message);
- } else {
- clearInterval(countdown);
- callback.call(container);
+ function validateUrl(url) {
+ const pattern =
+ /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;
+ return pattern.test(url);
+ }
+
+ // Countdown timer
+ const timeDifference = $("#time_difference").val();
+ if (timeDifference) {
+ startCountdown(Math.ceil(timeDifference));
+ }
+
+ function startCountdown(duration) {
+ const timer = $("#timer");
+ const message = "before poll closes";
+
+ function updateTimer() {
+ const timeString = formatTime(duration);
+ timer.html(`${timeString} ${message}`);
+
+ if (--duration < 0) {
+ clearInterval(interval);
+ window.location.reload();
}
- }, 1000);
- };
-
- onZero = function () {
- window.location.reload();
- };
-
- time_difference =
- document.getElementById("time_difference") &&
- document.getElementById("time_difference").value;
+ }
- $("#timer").countdown(onZero, Math.ceil(time_difference));
+ updateTimer();
+ const interval = setInterval(updateTimer, 1000);
+ }
+
+ function formatTime(totalSeconds) {
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor(((totalSeconds % 86400) % 3600) / 60);
+ const seconds = Math.floor(((totalSeconds % 86400) % 3600) % 60);
+ return `${days}d:${padZero(hours)}h:${padZero(minutes)}m:${padZero(seconds)}s`;
+ }
+
+ function padZero(num) {
+ return num.toString().padStart(2, "0");
+ }
});
diff --git a/approval_polls/staticfiles/edit.js b/approval_polls/staticfiles/edit.js
deleted file mode 100644
index ad7629d..0000000
--- a/approval_polls/staticfiles/edit.js
+++ /dev/null
@@ -1 +0,0 @@
-$(function () {});
diff --git a/approval_polls/staticfiles/my_polls.js b/approval_polls/staticfiles/my_polls.js
index b4dc72b..4882369 100644
--- a/approval_polls/staticfiles/my_polls.js
+++ b/approval_polls/staticfiles/my_polls.js
@@ -1,120 +1,63 @@
$(function () {
- var csrfSafeMethod;
-
- csrfSafeMethod = function (method) {
- // these HTTP methods do not require CSRF protection
+ var csrfSafeMethod = function (method) {
return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);
};
- $('a[id^="delete-poll-"]').click(function (event) {
+ $('button[id^="delete-poll-"]').click(function (event) {
disableAction(event.target, "Delete");
});
- $('a[id^="suspend-poll-"]').click(function (event) {
- disableAction(event.target, "Suspend");
- });
- $('a[id^="unsuspend-poll-"]').click(function (event) {
- disableAction(event.target, "Unsuspend");
- });
function disableAction(target, action) {
- _action = action.toLowerCase();
- var alertDiv, alertDivId;
- alertDivId = $(target).attr("id");
- alertDivId = alertDivId.split("-").pop();
-
- alertDiv =
+ var pollId = $(target).attr("id").split("-").pop();
+ var alertDiv =
"" +
- "
This poll will be " +
- verbalizeAction(_action) +
- "." +
- (action == "Suspend"
- ? "Suspending the poll will not allow any voting on it."
- : "") +
- " " +
- " " +
+ "" +
"
";
- if ($("#alert" + alertDivId).length == 0) {
- $(".well").css("border-color", "#dcdcdc");
- $(".alert").remove();
- $("#well" + alertDivId).before(alertDiv);
- $("#well" + alertDivId).css("border-color", "red");
+ if ($("#alert" + pollId).length == 0) {
+ $("#poll-" + pollId).before(alertDiv);
}
- function confirmAction() {
- var csrfToken, buttonId;
- buttonId = $(target).attr("id");
- buttonId = buttonId.split("-").pop();
- csrfToken = $("#csrfmiddlewaretoken").val();
- $("#well" + buttonId).css("border-color", "#dcdcdc");
- $("#alert" + buttonId).remove();
- if (action == "Delete") {
- $.ajax({
- method: "DELETE",
- url: "/approval_polls/" + buttonId + "/",
- beforeSend: function (xhr, settings) {
- if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
- xhr.setRequestHeader("X-CSRFToken", csrfToken);
- }
- },
- success: function (data) {
- $("#well" + buttonId).remove();
- window.location.reload();
- },
- error: function (data) {},
- });
- } else {
- $.ajax({
- method: "PUT",
- url: "/approval_polls/" + buttonId + "/change_suspension/",
- beforeSend: function (xhr, settings) {
- if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
- xhr.setRequestHeader("X-CSRFToken", csrfToken);
- }
- },
- success: function (data) {
- window.location.reload();
- },
- });
- }
- }
- $('button[id^="confirm-delete-"]').click(confirmAction);
- $('button[id^="confirm-suspend-"]').click(confirmAction);
- $('button[id^="confirm-unsuspend-"]').click(confirmAction);
+ $("#confirm-delete-" + pollId).click(function () {
+ confirmAction(pollId);
+ });
- $('button[id^="cancel-delete-"]').click(cancelAction);
- $('button[id^="cancel-suspend-"]').click(cancelAction);
- $('button[id^="cancel-unsuspend-"]').click(cancelAction);
+ $("#cancel-delete-" + pollId).click(function () {
+ cancelAction(pollId);
+ });
}
- function cancelAction() {
- var buttonId;
- buttonId = $(this).attr("id");
- buttonId = buttonId.split("-").pop();
- $("#well" + buttonId).css("border-color", "#dcdcdc");
- $("#alert" + buttonId).remove();
+ function confirmAction(pollId) {
+ var csrfToken = $("#csrfmiddlewaretoken").val();
+ console.info(csrfToken);
+ $.ajax({
+ method: "POST",
+ url: "/" + pollId + "/delete/",
+ headers: {
+ "X-CSRFToken": csrfToken,
+ },
+ success: function (data) {
+ if (data.status === "success") {
+ $("#poll-" + pollId).remove();
+ $("#alert" + pollId).remove();
+ }
+ },
+ error: function (xhr, status, error) {
+ console.error("Error deleting poll:", error);
+ alert("An error occurred while deleting the poll.");
+ },
+ });
}
- function verbalizeAction(action) {
- switch (action) {
- case "delete":
- return "permanently deleted";
- case "unsuspend":
- return "unsuspended";
- case "suspend":
- return "suspended";
- }
+ function cancelAction(pollId) {
+ $("#alert" + pollId).remove();
}
});
diff --git a/approval_polls/templates/base.html b/approval_polls/templates/base.html
index af209f9..050d62b 100644
--- a/approval_polls/templates/base.html
+++ b/approval_polls/templates/base.html
@@ -105,13 +105,14 @@
Learn why it's better and
create your own election.
+
+ style="max-width: 200px" />
@@ -127,6 +128,7 @@
$('[data-toggle="popover"]').popover();
});
- {% block extra_js %}{% endblock %}
+ {% block extra_js %}
+ {% endblock extra_js %}