From 2e80233cbcec6ad4de704fd2cdfb6f236a2a627c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Sep 2020 00:45:14 +0200 Subject: [PATCH 01/13] Change debug option to "print stdout" / "edit wiki" in the Refresh activities script --- apps/scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/scripts b/apps/scripts index 525f091b..7246f4d1 160000 --- a/apps/scripts +++ b/apps/scripts @@ -1 +1 @@ -Subproject commit 525f091b0caddc69cb2da7eba545ab9609bb1bb0 +Subproject commit 7246f4d18aa4f5e161b6e185b02fa28187e04747 From 2fc13e5418e2ef9bf34e7ccc6fa7a909077d656a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Sep 2020 00:47:30 +0200 Subject: [PATCH 02/13] Edit the wiki after an activity update iff the wiki password is defined, and don't run the script asynchronous with a SQLite database --- apps/activity/models.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/activity/models.py b/apps/activity/models.py index 0aefaf59..9d3431be 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -1,6 +1,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +import os from datetime import timedelta from threading import Thread @@ -133,9 +134,13 @@ class Activity(models.Model): if not settings.DEBUG and self.pk and "scripts" in settings.INSTALLED_APPS: def refresh_activities(): from scripts.management.commands.refresh_activities import Command as RefreshActivitiesCommand - RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name) - RefreshActivitiesCommand.refresh_raw_wiki_page("Modification de l'activité " + self.name) - Thread(daemon=True, target=refresh_activities).start() + # Consider that we can update the wiki iff the WIKI_PASSWORD env var is not empty + RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name, + False, os.getenv("WIKI_PASSWORD")) + RefreshActivitiesCommand.refresh_raw_wiki_page("Modification de l'activité " + self.name, + False, os.getenv("WIKI_PASSWORD")) + Thread(daemon=True, target=refresh_activities).start()\ + if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities() return ret def __str__(self): From 94706328ff583db0da2fd0d62352cd2ae0966351 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Sep 2020 00:47:55 +0200 Subject: [PATCH 03/13] Tests can run between 12pm and 2am --- apps/member/tests/test_memberships.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/member/tests/test_memberships.py b/apps/member/tests/test_memberships.py index ef8b8209..90b1f382 100644 --- a/apps/member/tests/test_memberships.py +++ b/apps/member/tests/test_memberships.py @@ -197,7 +197,7 @@ class TestMemberships(TestCase): # Create a new membership response = self.client.post(reverse("member:club_add_member", args=(club.pk,)), data=dict( user=user.pk, - date_start="{:%Y-%m-%d}".format(timezone.now().date()), + date_start="{:%Y-%m-%d}".format(date.today()), soge=False, credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_amount=4200, @@ -236,7 +236,7 @@ class TestMemberships(TestCase): # Renew membership response = self.client.post(reverse("member:club_renew_membership", args=(membership.pk,)), data=dict( user=user.pk, - date_start="{:%Y-%m-%d}".format(timezone.now().date()), + date_start="{:%Y-%m-%d}".format(date.today()), soge=bde_parent, credit_type=NoteSpecial.objects.get(special_type="Chèque").id, credit_amount=14242, @@ -265,7 +265,7 @@ class TestMemberships(TestCase): response = self.client.post(reverse("member:club_add_member", args=(bde.pk,)), data=dict( user=user.pk, - date_start="{:%Y-%m-%d}".format(timezone.now().date()), + date_start="{:%Y-%m-%d}".format(date.today()), soge=True, credit_type=NoteSpecial.objects.get(special_type="Virement bancaire").id, credit_amount=(bde.membership_fee_paid + kfet.membership_fee_paid) / 100, From a97a36bc9e035b12aa06fbedfbaaf6b0ab3739f4 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Sat, 5 Sep 2020 08:17:27 +0200 Subject: [PATCH 04/13] Add apps/script submodule to CI --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 80d85791..c5ec9610 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,10 @@ stages: - test - quality-assurance +# Also fetch submodules +variables: + GIT_SUBMODULE_STRATEGY: recursive + # Debian Buster py37-django22: stage: test From bad5fe3c22ffb302431b09a3f7e02e7130a27347 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Sat, 5 Sep 2020 08:30:41 +0200 Subject: [PATCH 05/13] Format JS files --- apps/member/static/member/js/alias.js | 56 +- note_kfet/static/js/autocomplete_model.js | 90 ++- note_kfet/static/js/base.js | 519 +++++++------- note_kfet/static/js/consos.js | 408 ++++++----- note_kfet/static/js/dynamic-formset.js | 447 ++++++------ note_kfet/static/js/konami.js | 62 +- note_kfet/static/js/transfer.js | 822 +++++++++++----------- 7 files changed, 1175 insertions(+), 1229 deletions(-) diff --git a/apps/member/static/member/js/alias.js b/apps/member/static/member/js/alias.js index 2d652dde..444e1bcf 100644 --- a/apps/member/static/member/js/alias.js +++ b/apps/member/static/member/js/alias.js @@ -2,22 +2,22 @@ * On form submit, create a new alias */ function create_alias (e) { - // Do not submit HTML form - e.preventDefault(); + // Do not submit HTML form + e.preventDefault() - // Get data and send to API - const formData = new FormData(e.target); - $.post("/api/note/alias/", { - "csrfmiddlewaretoken": formData.get("csrfmiddlewaretoken"), - "name": formData.get("name"), - "note": formData.get("note") - }).done(function () { - // Reload table - $("#alias_table").load(location.pathname + " #alias_table"); - addMsg("Alias ajouté", "success"); - }).fail(function (xhr, _textStatus, _error) { - errMsg(xhr.responseJSON); - }); + // Get data and send to API + const formData = new FormData(e.target) + $.post('/api/note/alias/', { + csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'), + name: formData.get('name'), + note: formData.get('note') + }).done(function () { + // Reload table + $('#alias_table').load(location.pathname + ' #alias_table') + addMsg('Alias ajouté', 'success') + }).fail(function (xhr, _textStatus, _error) { + errMsg(xhr.responseJSON) + }) } /** @@ -25,19 +25,19 @@ function create_alias (e) { * @param Integer button_id Alias id to remove */ function delete_button (button_id) { - $.ajax({ - url: "/api/note/alias/" + button_id + "/", - method: "DELETE", - headers: { "X-CSRFTOKEN": CSRF_TOKEN } - }).done(function () { - addMsg('Alias supprimé', 'success'); - $("#alias_table").load(location.pathname + " #alias_table"); - }).fail(function (xhr, _textStatus, _error) { - errMsg(xhr.responseJSON); - }); + $.ajax({ + url: '/api/note/alias/' + button_id + '/', + method: 'DELETE', + headers: { 'X-CSRFTOKEN': CSRF_TOKEN } + }).done(function () { + addMsg('Alias supprimé', 'success') + $('#alias_table').load(location.pathname + ' #alias_table') + }).fail(function (xhr, _textStatus, _error) { + errMsg(xhr.responseJSON) + }) } $(document).ready(function () { - // Attach event - document.getElementById("form_alias").addEventListener("submit", create_alias); -}) \ No newline at end of file + // Attach event + document.getElementById('form_alias').addEventListener('submit', create_alias) +}) diff --git a/note_kfet/static/js/autocomplete_model.js b/note_kfet/static/js/autocomplete_model.js index c4f9405b..f7aafbc6 100644 --- a/note_kfet/static/js/autocomplete_model.js +++ b/note_kfet/static/js/autocomplete_model.js @@ -1,57 +1,53 @@ $(document).ready(function () { - $(".autocomplete").keyup(function(e) { - let target = $("#" + e.target.id); - let prefix = target.attr("id"); - let api_url = target.attr("api_url"); - let api_url_suffix = target.attr("api_url_suffix"); - if (!api_url_suffix) - api_url_suffix = ""; - let name_field = target.attr("name_field"); - if (!name_field) - name_field = "name"; - let input = target.val(); - target.addClass("is-invalid"); - target.removeClass("is-valid"); - $("#" + prefix + "_reset").removeClass("d-none"); + $('.autocomplete').keyup(function (e) { + const target = $('#' + e.target.id) + const prefix = target.attr('id') + const api_url = target.attr('api_url') + let api_url_suffix = target.attr('api_url_suffix') + if (!api_url_suffix) { api_url_suffix = '' } + let name_field = target.attr('name_field') + if (!name_field) { name_field = 'name' } + const input = target.val() + target.addClass('is-invalid') + target.removeClass('is-valid') + $('#' + prefix + '_reset').removeClass('d-none') - $.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) { - let html = ""; + $.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) { + let html = '' - objects.results.forEach(function (obj) { - html += li(prefix + "_" + obj.id, obj[name_field]); - }); + objects.results.forEach(function (obj) { + html += li(prefix + '_' + obj.id, obj[name_field]) + }) - let results_list = $("#" + prefix + "_list"); - results_list.html(html); + const results_list = $('#' + prefix + '_list') + results_list.html(html) - objects.results.forEach(function (obj) { - $("#" + prefix + "_" + obj.id).click(function() { - target.val(obj[name_field]); - $("#" + prefix + "_pk").val(obj.id); + objects.results.forEach(function (obj) { + $('#' + prefix + '_' + obj.id).click(function () { + target.val(obj[name_field]) + $('#' + prefix + '_pk').val(obj.id) - results_list.html(""); - target.removeClass("is-invalid"); - target.addClass("is-valid"); + results_list.html('') + target.removeClass('is-invalid') + target.addClass('is-valid') - if (typeof autocompleted != 'undefined') - autocompleted(obj, prefix) - }); + if (typeof autocompleted !== 'undefined') { autocompleted(obj, prefix) } + }) - if (input === obj[name_field]) - $("#" + prefix + "_pk").val(obj.id); - }); + if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) } + }) - if (results_list.children().length === 1 && e.originalEvent.keyCode >= 32) { - results_list.children().first().trigger("click"); - } - }); - }); + if (results_list.children().length === 1 && e.originalEvent.keyCode >= 32) { + results_list.children().first().trigger('click') + } + }) + }) - $(".autocomplete-reset").click(function() { - let name = $(this).attr("id").replace("_reset", ""); - $("#" + name + "_pk").val(""); - $("#" + name).val(""); - $("#" + name + "_list").html(""); - $(this).addClass("d-none"); - }); -}); \ No newline at end of file + $('.autocomplete-reset').click(function () { + const name = $(this).attr('id').replace('_reset', '') + $('#' + name + '_pk').val('') + $('#' + name).val('') + $('#' + name + '_list').html('') + $(this).addClass('d-none') + }) +}) diff --git a/note_kfet/static/js/base.js b/note_kfet/static/js/base.js index 8315f01a..87a99f64 100644 --- a/note_kfet/static/js/base.js +++ b/note_kfet/static/js/base.js @@ -1,18 +1,16 @@ // Copyright (C) 2018-2020 by BDE ENS Paris-Saclay // SPDX-License-Identifier: GPL-3.0-or-later - /** * Convert balance in cents to a human readable amount * @param value the balance, in cents * @returns {string} */ function pretty_money (value) { - if (value % 100 === 0) - return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + " €"; - else - return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + "." - + (Math.abs(value) % 100 < 10 ? "0" : "") + (Math.abs(value) % 100) + " €"; + if (value % 100 === 0) { return (value < 0 ? '- ' : '') + Math.floor(Math.abs(value) / 100) + ' €' } else { + return (value < 0 ? '- ' : '') + Math.floor(Math.abs(value) / 100) + '.' + + (Math.abs(value) % 100 < 10 ? '0' : '') + (Math.abs(value) % 100) + ' €' + } } /** @@ -22,19 +20,19 @@ function pretty_money (value) { * @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored. */ function addMsg (msg, alert_type, timeout = -1) { - let msgDiv = $("#messages"); - let html = msgDiv.html(); - let id = Math.floor(10000 * Math.random() + 1); - html += "
" + - "" - + msg + "
\n"; - msgDiv.html(html); + const msgDiv = $('#messages') + let html = msgDiv.html() + const id = Math.floor(10000 * Math.random() + 1) + html += '
' + + '' + + msg + '
\n' + msgDiv.html(html) - if (timeout > 0) { - setTimeout(function () { - $("#close-message-" + id).click(); - }, timeout); - } + if (timeout > 0) { + setTimeout(function () { + $('#close-message-' + id).click() + }, timeout) + } } /** @@ -43,34 +41,34 @@ function addMsg (msg, alert_type, timeout = -1) { * @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored. */ function errMsg (errs_obj, timeout = -1) { - for (const err_msg of Object.values(errs_obj)) { - addMsg(err_msg, 'danger', timeout); - } + for (const err_msg of Object.values(errs_obj)) { + addMsg(err_msg, 'danger', timeout) + } } var reloadWithTurbolinks = (function () { - var scrollPosition; + var scrollPosition - function reload () { - scrollPosition = [window.scrollX, window.scrollY]; - Turbolinks.visit(window.location.toString(), { action: 'replace' }) + function reload () { + scrollPosition = [window.scrollX, window.scrollY] + Turbolinks.visit(window.location.toString(), { action: 'replace' }) + } + + document.addEventListener('turbolinks:load', function () { + if (scrollPosition) { + window.scrollTo.apply(window, scrollPosition) + scrollPosition = null } + }) - document.addEventListener('turbolinks:load', function () { - if (scrollPosition) { - window.scrollTo.apply(window, scrollPosition); - scrollPosition = null - } - }); - - return reload; -})(); + return reload +})() /** * Reload the balance of the user on the right top corner */ function refreshBalance () { - $("#user_balance").load("/ #user_balance"); + $('#user_balance').load('/ #user_balance') } /** @@ -79,15 +77,15 @@ function refreshBalance () { * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called. */ function getMatchedNotes (pattern, fun) { - $.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club", fun); + $.getJSON('/api/note/alias/?format=json&alias=' + pattern + '&search=user|club', fun) } /** * Generate a
  • entry with a given id and text */ function li (id, text, extra_css) { - return "
  • " + text + "
  • \n"; + return '
  • ' + text + '
  • \n' } /** @@ -95,24 +93,13 @@ function li (id, text, extra_css) { * @param note The concerned note. */ function displayStyle (note) { - if (!note) - return ""; - let balance = note.balance; - var css = ""; - if (balance < -5000) - css += " text-danger bg-dark"; - else if (balance < -1000) - css += " text-danger"; - else if (balance < 0) - css += " text-warning"; - else if (!note.email_confirmed) - css += " text-white bg-primary"; - else if (!note.is_active || (note.membership && note.membership.date_end < new Date().toISOString())) - css += "text-white bg-info"; - return css; + if (!note) { return '' } + const balance = note.balance + var css = '' + if (balance < -5000) { css += ' text-danger bg-dark' } else if (balance < -1000) { css += ' text-danger' } else if (balance < 0) { css += ' text-warning' } else if (!note.email_confirmed) { css += ' text-white bg-primary' } else if (!note.is_active || (note.membership && note.membership.date_end < new Date().toISOString())) { css += 'text-white bg-info' } + return css } - /** * Render note name and picture * @param note The note to render @@ -121,23 +108,22 @@ function displayStyle (note) { * @param profile_pic_field */ function displayNote (note, alias, user_note_field = null, profile_pic_field = null) { - if (!note.display_image) { - note.display_image = '/static/member/img/default_picture.png'; - } - let img = note.display_image; - if (alias !== note.name && note.name) - alias += " (aka. " + note.name + ")"; - if (user_note_field !== null) { - $("#" + user_note_field).removeAttr('class'); - $("#" + user_note_field).addClass(displayStyle(note)); - $("#" + user_note_field).text(alias + (note.balance == null ? "" : (" :\n" + pretty_money(note.balance)))); - if (profile_pic_field != null) { - $("#" + profile_pic_field).attr('src', img); - $("#" + profile_pic_field + "_link").attr('href', note.resourcetype === "NoteUser" ? - "/accounts/user/" + note.user : note.resourcetype === "NoteClub" ? - "/accounts/club/" + note.club : "#"); - } + if (!note.display_image) { + note.display_image = '/static/member/img/default_picture.png' + } + const img = note.display_image + if (alias !== note.name && note.name) { alias += ' (aka. ' + note.name + ')' } + if (user_note_field !== null) { + $('#' + user_note_field).removeAttr('class') + $('#' + user_note_field).addClass(displayStyle(note)) + $('#' + user_note_field).text(alias + (note.balance == null ? '' : (' :\n' + pretty_money(note.balance)))) + if (profile_pic_field != null) { + $('#' + profile_pic_field).attr('src', img) + $('#' + profile_pic_field + '_link').attr('href', note.resourcetype === 'NoteUser' + ? '/accounts/user/' + note.user : note.resourcetype === 'NoteClub' + ? '/accounts/club/' + note.club : '#') } + } } /** @@ -152,35 +138,34 @@ function displayNote (note, alias, user_note_field = null, profile_pic_field = n * (useful in consumptions, put null if not used) * @returns an anonymous function to be compatible with jQuery events */ -function removeNote (d, note_prefix = "note", notes_display, note_list_id, user_note_field = null, profile_pic_field = null) { - return (function () { - let new_notes_display = []; - let html = ""; - notes_display.forEach(function (disp) { - if (disp.quantity > 1 || disp.id !== d.id) { - disp.quantity -= disp.id === d.id ? 1 : 0; - new_notes_display.push(disp); - html += li(note_prefix + "_" + disp.id, disp.name - + "" + disp.quantity + "", - displayStyle(disp.note)); - } - }); +function removeNote (d, note_prefix = 'note', notes_display, note_list_id, user_note_field = null, profile_pic_field = null) { + return function () { + const new_notes_display = [] + let html = '' + notes_display.forEach(function (disp) { + if (disp.quantity > 1 || disp.id !== d.id) { + disp.quantity -= disp.id === d.id ? 1 : 0 + new_notes_display.push(disp) + html += li(note_prefix + '_' + disp.id, disp.name + + '' + disp.quantity + '', + displayStyle(disp.note)) + } + }) - notes_display.length = 0; - new_notes_display.forEach(function (disp) { - notes_display.push(disp); - }); + notes_display.length = 0 + new_notes_display.forEach(function (disp) { + notes_display.push(disp) + }) - $("#" + note_list_id).html(html); - notes_display.forEach(function (disp) { - let obj = $("#" + note_prefix + "_" + disp.id); - obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field)); - obj.hover(function () { - if (disp.note) - displayNote(disp.note, disp.name, user_note_field, profile_pic_field); - }); - }); - }); + $('#' + note_list_id).html(html) + notes_display.forEach(function (disp) { + const obj = $('#' + note_prefix + '_' + disp.id) + obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field)) + obj.hover(function () { + if (disp.note) { displayNote(disp.note, disp.name, user_note_field, profile_pic_field) } + }) + }) + } } /** @@ -199,203 +184,193 @@ function removeNote (d, note_prefix = "note", notes_display, note_list_id, user_ * the associated note is not displayed. * Useful for a consumption if the item is selected before. */ -function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_prefix = "alias", - note_prefix = "note", user_note_field = null, profile_pic_field = null, alias_click = null) { - let field = $("#" + field_id); +function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_prefix = 'alias', + note_prefix = 'note', user_note_field = null, profile_pic_field = null, alias_click = null) { + const field = $('#' + field_id) - // Configure tooltip - field.tooltip({ - html: true, - placement: 'bottom', - title: 'Loading...', - trigger: 'manual', - container: field.parent(), - fallbackPlacement: 'clockwise' - }); + // Configure tooltip + field.tooltip({ + html: true, + placement: 'bottom', + title: 'Loading...', + trigger: 'manual', + container: field.parent(), + fallbackPlacement: 'clockwise' + }) - // When the user clicks elsewhere, we hide the tooltip - $(document).click(function(e) { - if (!e.target.id.startsWith(alias_prefix)) { - field.tooltip("hide"); - } - }); + // When the user clicks elsewhere, we hide the tooltip + $(document).click(function (e) { + if (!e.target.id.startsWith(alias_prefix)) { + field.tooltip('hide') + } + }) - let old_pattern = null; + let old_pattern = null - // Clear search on click - field.click(function () { - field.tooltip('hide'); - field.removeClass('is-invalid'); - field.val(""); - old_pattern = ""; - }); + // Clear search on click + field.click(function () { + field.tooltip('hide') + field.removeClass('is-invalid') + field.val('') + old_pattern = '' + }) - // When the user type "Enter", the first alias is clicked - field.keypress(function (event) { - if (event.originalEvent.charCode === 13 && notes.length > 0) { - let li_obj = field.parent().find("ul li").first(); - displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field); - li_obj.trigger("click"); - } - }); + // When the user type "Enter", the first alias is clicked + field.keypress(function (event) { + if (event.originalEvent.charCode === 13 && notes.length > 0) { + const li_obj = field.parent().find('ul li').first() + displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field) + li_obj.trigger('click') + } + }) - // When the user type something, the matched aliases are refreshed - field.keyup(function (e) { - field.removeClass('is-invalid'); + // When the user type something, the matched aliases are refreshed + field.keyup(function (e) { + field.removeClass('is-invalid') - if (e.originalEvent.charCode === 13) - return; + if (e.originalEvent.charCode === 13) { return } - let pattern = field.val(); + const pattern = field.val() - // If the pattern is not modified, we don't query the API - if (pattern === old_pattern) - return; - old_pattern = pattern; - notes.length = 0; + // If the pattern is not modified, we don't query the API + if (pattern === old_pattern) { return } + old_pattern = pattern + notes.length = 0 - // get matched Alias with note associated - if (pattern === "") { - field.tooltip('hide'); - notes.length = 0; - return; - } + // get matched Alias with note associated + if (pattern === '') { + field.tooltip('hide') + notes.length = 0 + return + } - $.getJSON("/api/note/consumer/?format=json&alias=" + pattern + "&search=user|club", - function (consumers) { - // The response arrived too late, we stop the request - if (pattern !== $("#" + field_id).val()) - return; + $.getJSON('/api/note/consumer/?format=json&alias=' + pattern + '&search=user|club', + function (consumers) { + // The response arrived too late, we stop the request + if (pattern !== $('#' + field_id).val()) { return } - // Build tooltip content - let aliases_matched_html = '
      '; - consumers.results.forEach(function (consumer) { - let note = consumer.note; - note.email_confirmed = consumer.email_confirmed; - if (consumer.hasOwnProperty("membership") && consumer.membership) - note.membership = consumer.membership; - else - note.membership = undefined; - let extra_css = displayStyle(note); - aliases_matched_html += li(alias_prefix + '_' + consumer.id, - consumer.name, - extra_css); - notes.push(note); - }); - aliases_matched_html += '
    '; + // Build tooltip content + let aliases_matched_html = '
      ' + consumers.results.forEach(function (consumer) { + const note = consumer.note + note.email_confirmed = consumer.email_confirmed + if (consumer.hasOwnProperty('membership') && consumer.membership) { note.membership = consumer.membership } else { note.membership = undefined } + const extra_css = displayStyle(note) + aliases_matched_html += li(alias_prefix + '_' + consumer.id, + consumer.name, + extra_css) + notes.push(note) + }) + aliases_matched_html += '
    ' - // Show tooltip - field.attr('data-original-title', aliases_matched_html).tooltip('show'); + // Show tooltip + field.attr('data-original-title', aliases_matched_html).tooltip('show') - consumers.results.forEach(function (consumer) { - let consumer_obj = $("#" + alias_prefix + "_" + consumer.id); - consumer_obj.hover(function () { - displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field) - }); - consumer_obj.click(function () { - var disp = null; - notes_display.forEach(function (d) { - // We compare the alias ids - if (d.id === consumer.id) { - d.quantity += 1; - disp = d; - } - }); - // In the other case, we add a new emitter - if (disp == null) { - disp = { - name: consumer.name, - id: consumer.id, - note: consumer.note, - quantity: 1 - }; - notes_display.push(disp); - } + consumers.results.forEach(function (consumer) { + const consumer_obj = $('#' + alias_prefix + '_' + consumer.id) + consumer_obj.hover(function () { + displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field) + }) + consumer_obj.click(function () { + var disp = null + notes_display.forEach(function (d) { + // We compare the alias ids + if (d.id === consumer.id) { + d.quantity += 1 + disp = d + } + }) + // In the other case, we add a new emitter + if (disp == null) { + disp = { + name: consumer.name, + id: consumer.id, + note: consumer.note, + quantity: 1 + } + notes_display.push(disp) + } - // If the function alias_click exists, it is called. If it doesn't return true, then the notes are - // note displayed. Useful for a consumption when a button is already clicked - if (alias_click && !alias_click()) - return; + // If the function alias_click exists, it is called. If it doesn't return true, then the notes are + // note displayed. Useful for a consumption when a button is already clicked + if (alias_click && !alias_click()) { return } - let note_list = $("#" + note_list_id); - let html = ""; - notes_display.forEach(function (disp) { - html += li(note_prefix + "_" + disp.id, - disp.name - + "" - + disp.quantity + "", - displayStyle(disp.note)); - }); + const note_list = $('#' + note_list_id) + let html = '' + notes_display.forEach(function (disp) { + html += li(note_prefix + '_' + disp.id, + disp.name + + '' + + disp.quantity + '', + displayStyle(disp.note)) + }) - // Emitters are displayed - note_list.html(html); + // Emitters are displayed + note_list.html(html) - // Update tooltip position - field.tooltip('update'); + // Update tooltip position + field.tooltip('update') - notes_display.forEach(function (disp) { - let line_obj = $("#" + note_prefix + "_" + disp.id); - // Hover an emitter display also the profile picture - line_obj.hover(function () { - displayNote(disp.note, disp.name, user_note_field, profile_pic_field); - }); + notes_display.forEach(function (disp) { + const line_obj = $('#' + note_prefix + '_' + disp.id) + // Hover an emitter display also the profile picture + line_obj.hover(function () { + displayNote(disp.note, disp.name, user_note_field, profile_pic_field) + }) - // When an emitter is clicked, it is removed - line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, - profile_pic_field)); - }); - }) - }); - - });// end getJSON alias - }); + // When an emitter is clicked, it is removed + line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, + profile_pic_field)) + }) + }) + }) + })// end getJSON alias + }) }// end function autocomplete - // When a validate button is clicked, we switch the validation status function de_validate (id, validated, resourcetype) { - let validate_obj = $("#validate_" + id); + const validate_obj = $('#validate_' + id) - if (validate_obj.data("pending")) - // The button is already clicked - return; + if (validate_obj.data('pending')) + // The button is already clicked + { return } - let invalidity_reason = $("#invalidity_reason_" + id).val(); - validate_obj.html(""); - validate_obj.data("pending", true); + const invalidity_reason = $('#invalidity_reason_' + id).val() + validate_obj.html('') + validate_obj.data('pending', true) - // Perform a PATCH request to the API in order to update the transaction - // If the user has insufficient rights, an error message will appear - $.ajax({ - "url": "/api/note/transaction/transaction/" + id + "/", - type: "PATCH", - dataType: "json", - headers: { - "X-CSRFTOKEN": CSRF_TOKEN - }, - data: { - "resourcetype": resourcetype, - "valid": !validated, - "invalidity_reason": invalidity_reason, - }, - success: function () { - refreshBalance(); - // error if this method doesn't exist. Please define it. - refreshHistory(); - }, - error: function (err) { - let errObj = JSON.parse(err.responseText); - let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"]; - if (!error) - error = err.responseText; - addMsg("Une erreur est survenue lors de la validation/dévalidation " + - "de cette transaction : " + error, "danger"); + // Perform a PATCH request to the API in order to update the transaction + // If the user has insufficient rights, an error message will appear + $.ajax({ + url: '/api/note/transaction/transaction/' + id + '/', + type: 'PATCH', + dataType: 'json', + headers: { + 'X-CSRFTOKEN': CSRF_TOKEN + }, + data: { + resourcetype: resourcetype, + valid: !validated, + invalidity_reason: invalidity_reason + }, + success: function () { + refreshBalance() + // error if this method doesn't exist. Please define it. + refreshHistory() + }, + error: function (err) { + const errObj = JSON.parse(err.responseText) + let error = errObj.detail ? errObj.detail : errObj.non_field_errors + if (!error) { error = err.responseText } + addMsg('Une erreur est survenue lors de la validation/dévalidation ' + + 'de cette transaction : ' + error, 'danger') - refreshBalance(); - // error if this method doesn't exist. Please define it. - refreshHistory(); - } - }); + refreshBalance() + // error if this method doesn't exist. Please define it. + refreshHistory() + } + }) } /** @@ -404,10 +379,10 @@ function de_validate (id, validated, resourcetype) { * @param wait Debounced milliseconds */ function debounce (callback, wait) { - let timeout; - return (...args) => { - const context = this; - clearTimeout(timeout); - timeout = setTimeout(() => callback.apply(context, args), wait); - }; + let timeout + return (...args) => { + const context = this + clearTimeout(timeout) + timeout = setTimeout(() => callback.apply(context, args), wait) + } } diff --git a/note_kfet/static/js/consos.js b/note_kfet/static/js/consos.js index 25e8113e..5a7e6144 100644 --- a/note_kfet/static/js/consos.js +++ b/note_kfet/static/js/consos.js @@ -2,95 +2,92 @@ // SPDX-License-Identifier: GPL-3.0-or-later // When a transaction is performed, lock the interface to prevent spam clicks. -var LOCK = false; +var LOCK = false /** * Refresh the history table on the consumptions page. */ -function refreshHistory() { - $("#history").load("/note/consos/ #history"); - $("#most_used").load("/note/consos/ #most_used"); +function refreshHistory () { + $('#history').load('/note/consos/ #history') + $('#most_used').load('/note/consos/ #most_used') } -$(document).ready(function() { - // If hash of a category in the URL, then select this category - // else select the first one - if (location.hash) { - $("a[href='" + location.hash + "']").tab("show"); - } else { - $("a[data-toggle='tab']").first().tab("show"); +$(document).ready(function () { + // If hash of a category in the URL, then select this category + // else select the first one + if (location.hash) { + $("a[href='" + location.hash + "']").tab('show') + } else { + $("a[data-toggle='tab']").first().tab('show') + } + + // When selecting a category, change URL + $(document.body).on('click', "a[data-toggle='tab']", function () { + location.hash = this.getAttribute('href') + }) + + // Switching in double consumptions mode should update the layout + $('#double_conso').change(function () { + $('#consos_list_div').removeClass('d-none') + $('#user_select_div').attr('class', 'col-xl-4') + $('#infos_div').attr('class', 'col-sm-5 col-xl-6') + + const note_list_obj = $('#note_list') + if (buttons.length > 0 && note_list_obj.text().length > 0) { + $('#consos_list').html(note_list_obj.html()) + note_list_obj.html('') + + buttons.forEach(function (button) { + $('#conso_button_' + button.id).click(function () { + if (LOCK) { return } + removeNote(button, 'conso_button', buttons, 'consos_list')() + }) + }) } + }) - // When selecting a category, change URL - $(document.body).on("click", "a[data-toggle='tab']", function() { - location.hash = this.getAttribute("href"); - }); + $('#single_conso').change(function () { + $('#consos_list_div').addClass('d-none') + $('#user_select_div').attr('class', 'col-xl-7') + $('#infos_div').attr('class', 'col-sm-5 col-md-4') - // Switching in double consumptions mode should update the layout - $("#double_conso").change(function() { - $("#consos_list_div").removeClass('d-none'); - $("#user_select_div").attr('class', 'col-xl-4'); - $("#infos_div").attr('class', 'col-sm-5 col-xl-6'); + const consos_list_obj = $('#consos_list') + if (buttons.length > 0) { + if (notes_display.length === 0 && consos_list_obj.text().length > 0) { + $('#note_list').html(consos_list_obj.html()) + consos_list_obj.html('') + buttons.forEach(function (button) { + $('#conso_button_' + button.id).click(function () { + if (LOCK) { return } + removeNote(button, 'conso_button', buttons, 'note_list')() + }) + }) + } else { + buttons.length = 0 + consos_list_obj.html('') + } + } + }) - let note_list_obj = $("#note_list"); - if (buttons.length > 0 && note_list_obj.text().length > 0) { - $("#consos_list").html(note_list_obj.html()); - note_list_obj.html(""); + // Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS + $("label[for='double_conso']").removeClass('active') - buttons.forEach(function(button) { - $("#conso_button_" + button.id).click(function() { - if (LOCK) - return; - removeNote(button, "conso_button", buttons,"consos_list")(); - }); - }); - } - }); + $('#consume_all').click(consumeAll) +}) - $("#single_conso").change(function() { - $("#consos_list_div").addClass('d-none'); - $("#user_select_div").attr('class', 'col-xl-7'); - $("#infos_div").attr('class', 'col-sm-5 col-md-4'); - - let consos_list_obj = $("#consos_list"); - if (buttons.length > 0) { - if (notes_display.length === 0 && consos_list_obj.text().length > 0) { - $("#note_list").html(consos_list_obj.html()); - consos_list_obj.html(""); - buttons.forEach(function(button) { - $("#conso_button_" + button.id).click(function() { - if (LOCK) - return; - removeNote(button, "conso_button", buttons,"note_list")(); - }); - }); - } - else { - buttons.length = 0; - consos_list_obj.html(""); - } - } - }); - - // Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS - $("label[for='double_conso']").removeClass('active'); - - $("#consume_all").click(consumeAll); -}); - -notes = []; -notes_display = []; -buttons = []; +notes = [] +notes_display = [] +buttons = [] // When the user searches an alias, we update the auto-completion -autoCompleteNote("note", "note_list", notes, notes_display, - "alias", "note", "user_note", "profile_pic", function() { - if (buttons.length > 0 && $("#single_conso").is(":checked")) { - consumeAll(); - return false; - } - return true; - }); +autoCompleteNote('note', 'note_list', notes, notes_display, + 'alias', 'note', 'user_note', 'profile_pic', function () { + if (buttons.length > 0 && $('#single_conso').is(':checked')) { + consumeAll() + return false + } + return true + }) /** * Add a transaction from a button. @@ -102,103 +99,98 @@ autoCompleteNote("note", "note_list", notes, notes_display, * @param template_id The identifier of the button * @param template_name The name of the button */ -function addConso(dest, amount, type, category_id, category_name, template_id, template_name) { - var button = null; - buttons.forEach(function(b) { - if (b.id === template_id) { - b.quantity += 1; - button = b; - } - }); - if (button == null) { - button = { - id: template_id, - name: template_name, - dest: dest, - quantity: 1, - amount: amount, - type: type, - category_id: category_id, - category_name: category_name - }; - buttons.push(button); +function addConso (dest, amount, type, category_id, category_name, template_id, template_name) { + var button = null + buttons.forEach(function (b) { + if (b.id === template_id) { + b.quantity += 1 + button = b } - - let dc_obj = $("#double_conso"); - if (dc_obj.is(":checked") || notes_display.length === 0) { - let list = dc_obj.is(":checked") ? "consos_list" : "note_list"; - let html = ""; - buttons.forEach(function(button) { - html += li("conso_button_" + button.id, button.name - + "" + button.quantity + ""); - }); - - $("#" + list).html(html); - - buttons.forEach(function(button) { - $("#conso_button_" + button.id).click(function() { - if (LOCK) - return; - removeNote(button, "conso_button", buttons, list)(); - }); - }); + }) + if (button == null) { + button = { + id: template_id, + name: template_name, + dest: dest, + quantity: 1, + amount: amount, + type: type, + category_id: category_id, + category_name: category_name } - else - consumeAll(); + buttons.push(button) + } + + const dc_obj = $('#double_conso') + if (dc_obj.is(':checked') || notes_display.length === 0) { + const list = dc_obj.is(':checked') ? 'consos_list' : 'note_list' + let html = '' + buttons.forEach(function (button) { + html += li('conso_button_' + button.id, button.name + + '' + button.quantity + '') + }) + + $('#' + list).html(html) + + buttons.forEach(function (button) { + $('#conso_button_' + button.id).click(function () { + if (LOCK) { return } + removeNote(button, 'conso_button', buttons, list)() + }) + }) + } else { consumeAll() } } /** * Reset the page as its initial state. */ -function reset() { - notes_display.length = 0; - notes.length = 0; - buttons.length = 0; - $("#note_list").html(""); - $("#consos_list").html(""); - $("#note").val(""); - $("#note").attr("data-original-title", "").tooltip("hide"); - $("#profile_pic").attr("src", "/static/member/img/default_picture.png"); - $("#profile_pic_link").attr("href", "#"); - refreshHistory(); - refreshBalance(); - LOCK = false; +function reset () { + notes_display.length = 0 + notes.length = 0 + buttons.length = 0 + $('#note_list').html('') + $('#consos_list').html('') + $('#note').val('') + $('#note').attr('data-original-title', '').tooltip('hide') + $('#profile_pic').attr('src', '/static/member/img/default_picture.png') + $('#profile_pic_link').attr('href', '#') + refreshHistory() + refreshBalance() + LOCK = false } - /** * Apply all transactions: all notes in `notes` buy each item in `buttons` */ -function consumeAll() { - if (LOCK) - return; +function consumeAll () { + if (LOCK) { return } - LOCK = true; + LOCK = true - let error = false; + let error = false - if (notes_display.length === 0) { - $("#note").addClass('is-invalid'); - $("#note_list").html(li("", "Ajoutez des émetteurs.", "text-danger")); - error = true; - } + if (notes_display.length === 0) { + $('#note').addClass('is-invalid') + $('#note_list').html(li('', 'Ajoutez des émetteurs.', 'text-danger')) + error = true + } - if (buttons.length === 0) { - $("#consos_list").html(li("", "Ajoutez des consommations.", "text-danger")); - error = true; - } + if (buttons.length === 0) { + $('#consos_list').html(li('', 'Ajoutez des consommations.', 'text-danger')) + error = true + } - if (error) { - LOCK = false; - return; - } + if (error) { + LOCK = false + return + } - notes_display.forEach(function(note_display) { - buttons.forEach(function(button) { - consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount, - button.name + " (" + button.category_name + ")", button.type, button.category_id, button.id); - }); - }); + notes_display.forEach(function (note_display) { + buttons.forEach(function (button) { + consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount, + button.name + ' (' + button.category_name + ')', button.type, button.category_id, button.id) + }) + }) } /** @@ -213,58 +205,60 @@ function consumeAll() { * @param category The category id of the button (type: int) * @param template The button id (type: int) */ -function consume(source, source_alias, dest, quantity, amount, reason, type, category, template) { - $.post("/api/note/transaction/transaction/", +function consume (source, source_alias, dest, quantity, amount, reason, type, category, template) { + $.post('/api/note/transaction/transaction/', + { + csrfmiddlewaretoken: CSRF_TOKEN, + quantity: quantity, + amount: amount, + reason: reason, + valid: true, + polymorphic_ctype: type, + resourcetype: 'RecurrentTransaction', + source: source.id, + source_alias: source_alias, + destination: dest, + template: template + }) + .done(function () { + if (!isNaN(source.balance)) { + const newBalance = source.balance - quantity * amount + if (newBalance <= -5000) { + addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' + + 'succès, mais la note émettrice ' + source_alias + ' est en négatif sévère.', + 'danger', 30000) + } else if (newBalance < 0) { + addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' + + 'succès, mais la note émettrice ' + source_alias + ' est en négatif.', + 'warning', 30000) + } + if (source.membership && source.membership.date_end < new Date().toISOString()) { + addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.", + 'danger', 30000) + } + } + reset() + }).fail(function (e) { + $.post('/api/note/transaction/transaction/', { - "csrfmiddlewaretoken": CSRF_TOKEN, - "quantity": quantity, - "amount": amount, - "reason": reason, - "valid": true, - "polymorphic_ctype": type, - "resourcetype": "RecurrentTransaction", - "source": source.id, - "source_alias": source_alias, - "destination": dest, - "template": template - }) - .done(function () { - if (!isNaN(source.balance)) { - let newBalance = source.balance - quantity * amount; - if (newBalance <= -5000) - addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " + - "succès, mais la note émettrice " + source_alias + " est en négatif sévère.", - "danger", 30000); - else if (newBalance < 0) - addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " + - "succès, mais la note émettrice " + source_alias + " est en négatif.", - "warning", 30000); - if (source.membership && source.membership.date_end < new Date().toISOString()) - addMsg("Attention : la note émettrice " + source.name + " n'est plus adhérente.", - "danger", 30000); - } - reset(); - }).fail(function (e) { - $.post("/api/note/transaction/transaction/", - { - "csrfmiddlewaretoken": CSRF_TOKEN, - "quantity": quantity, - "amount": amount, - "reason": reason, - "valid": false, - "invalidity_reason": "Solde insuffisant", - "polymorphic_ctype": type, - "resourcetype": "RecurrentTransaction", - "source": source, - "source_alias": source_alias, - "destination": dest, - "template": template - }).done(function() { - reset(); - addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", "danger", 10000); - }).fail(function () { - reset(); - errMsg(e.responseJSON); - }); - }); + csrfmiddlewaretoken: CSRF_TOKEN, + quantity: quantity, + amount: amount, + reason: reason, + valid: false, + invalidity_reason: 'Solde insuffisant', + polymorphic_ctype: type, + resourcetype: 'RecurrentTransaction', + source: source, + source_alias: source_alias, + destination: dest, + template: template + }).done(function () { + reset() + addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", 'danger', 10000) + }).fail(function () { + reset() + errMsg(e.responseJSON) + }) + }) } diff --git a/note_kfet/static/js/dynamic-formset.js b/note_kfet/static/js/dynamic-formset.js index c6ff3328..cb6151df 100644 --- a/note_kfet/static/js/dynamic-formset.js +++ b/note_kfet/static/js/dynamic-formset.js @@ -9,241 +9,240 @@ * Licensed under the New BSD License * See: http://www.opensource.org/licenses/bsd-license.php */ -;(function($) { - $.fn.formset = function(opts) - { - var options = $.extend({}, $.fn.formset.defaults, opts), - flatExtraClasses = options.extraClasses.join(' '), - totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'), - maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'), - minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'), - childElementSelector = 'input,select,textarea,label,div', - $$ = $(this), +;(function ($) { + $.fn.formset = function (opts) { + var options = $.extend({}, $.fn.formset.defaults, opts) + var flatExtraClasses = options.extraClasses.join(' ') + var totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS') + var maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS') + var minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS') + var childElementSelector = 'input,select,textarea,label,div' + var $$ = $(this) - applyExtraClasses = function(row, ndx) { - if (options.extraClasses) { - row.removeClass(flatExtraClasses); - row.addClass(options.extraClasses[ndx % options.extraClasses.length]); - } - }, + var applyExtraClasses = function (row, ndx) { + if (options.extraClasses) { + row.removeClass(flatExtraClasses) + row.addClass(options.extraClasses[ndx % options.extraClasses.length]) + } + } - updateElementIndex = function(elem, prefix, ndx) { - var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'), - replacement = prefix + '-' + ndx + '-'; - if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); - if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); - if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); - }, + var updateElementIndex = function (elem, prefix, ndx) { + var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-') + var replacement = prefix + '-' + ndx + '-' + if (elem.attr('for')) elem.attr('for', elem.attr('for').replace(idRegex, replacement)) + if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)) + if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)) + } - hasChildElements = function(row) { - return row.find(childElementSelector).length > 0; - }, + var hasChildElements = function (row) { + return row.find(childElementSelector).length > 0 + } - showAddButton = function() { - return maxForms.length == 0 || // For Django versions pre 1.2 - (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)); - }, + var showAddButton = function () { + return maxForms.length == 0 || // For Django versions pre 1.2 + (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)) + } - /** + /** * Indicates whether delete link(s) can be displayed - when total forms > min forms */ - showDeleteLinks = function() { - return minForms.length == 0 || // For Django versions pre 1.7 - (minForms.val() == '' || (totalForms.val() - minForms.val() > 0)); - }, + var showDeleteLinks = function () { + return minForms.length == 0 || // For Django versions pre 1.7 + (minForms.val() == '' || (totalForms.val() - minForms.val() > 0)) + } - insertDeleteLink = function(row) { - var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'), - addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.'); + var insertDeleteLink = function (row) { + var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.') + var addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.') - var delButtonHTML = '' + options.deleteText +''; - if (options.deleteContainerClass) { - // If we have a specific container for the remove button, - // place it as the last child of that container: - row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML); - } else if (row.is('TR')) { - // If the forms are laid out in table rows, insert - // the remove button into the last table cell: - row.children('td:last').append(delButtonHTML); - } else if (row.is('UL') || row.is('OL')) { - // If they're laid out as an ordered/unordered list, - // insert an
  • after the last list item: - row.append('
  • ' + delButtonHTML + '
  • '); - } else { - // Otherwise, just insert the remove button as the - // last child element of the form's container: - row.append(delButtonHTML); - } + var delButtonHTML = '' + options.deleteText + '' + if (options.deleteContainerClass) { + // If we have a specific container for the remove button, + // place it as the last child of that container: + row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML) + } else if (row.is('TR')) { + // If the forms are laid out in table rows, insert + // the remove button into the last table cell: + row.children('td:last').append(delButtonHTML) + } else if (row.is('UL') || row.is('OL')) { + // If they're laid out as an ordered/unordered list, + // insert an
  • after the last list item: + row.append('
  • ' + delButtonHTML + '
  • ') + } else { + // Otherwise, just insert the remove button as the + // last child element of the form's container: + row.append(delButtonHTML) + } - // Check if we're under the minimum number of forms - not to display delete link at rendering - if (!showDeleteLinks()){ - row.find('a.' + delCssSelector).hide(); - } + // Check if we're under the minimum number of forms - not to display delete link at rendering + if (!showDeleteLinks()) { + row.find('a.' + delCssSelector).hide() + } - row.find('a.' + delCssSelector).click(function() { - var row = $(this).parents('.' + options.formCssClass), - del = row.find('input:hidden[id $= "-DELETE"]'), - buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'), - forms; - if (del.length) { - // We're dealing with an inline formset. - // Rather than remove this form from the DOM, we'll mark it as deleted - // and hide it, then let Django handle the deleting: - del.val('on'); - row.hide(); - forms = $('.' + options.formCssClass).not(':hidden'); - } else { - row.remove(); - // Update the TOTAL_FORMS count: - forms = $('.' + options.formCssClass).not('.formset-custom-template'); - totalForms.val(forms.length); - } - for (var i=0, formCount=forms.length; i