From 62447d6b89774564531032e899c26acde37222b1 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 31 Jan 2021 10:17:40 +0100 Subject: [PATCH 01/56] Basic User edit in tables --- cps/admin.py | 58 +++++++++++++++++++++ cps/static/js/table.js | 97 +++++++++++++++++++++++++++++++++++ cps/templates/admin.html | 4 ++ cps/templates/user_table.html | 46 +++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 cps/templates/user_table.html diff --git a/cps/admin.py b/cps/admin.py index 03b306a8..c7210f3f 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -212,6 +212,64 @@ def view_configuration(): restrictColumns=restrict_columns, title=_(u"UI Configuration"), page="uiconfig") +@admi.route("/admin/usertable") +@login_required +@admin_required +def edit_user_table(): + visibility = current_user.view_settings.get('useredit', {}) + allUser = ub.session.query(ub.User).all() + return render_title_template("user_table.html", users=allUser, visiblility=visibility, + title=_(u"Edit Users"), page="usertable") + +@admi.route("/axjax/listusers") +@login_required +@admin_required +def list_users(): + off = request.args.get("offset") or 0 + limit = request.args.get("limit") or 10 + + total_count = ub.session.query(ub.User).count() + search = request.args.get("search") + if search: + users = ub.session.query(ub.User).filter().offset(off).limit(limit).all() + filtered_count = users.length() + # entries, filtered_count, pagination = calibre_db.get_search_results(search, off, order, limit) + else: + users = ub.session.query(ub.User).offset(off).limit(limit).all() + filtered_count = total_count + + table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": users} + js_list = json.dumps(table_entries, cls=db.AlchemyEncoder) + + response = make_response(js_list) + response.headers["Content-Type"] = "application/json; charset=utf-8" + return response + + +@admi.route("/axjax/editlistusers/", methods=['POST']) +@login_required +@admin_required +def edit_list_user(param): + vals = request.form.to_dict() + user = ub.session.query(ub.User).filter(ub.User.id == vals['pk']).one_or_none() # ub.User.query calibre_db.get_book(vals['pk']) + if param =='nickname': + if not ub.session.query(ub.User).filter(ub.User.nickname == vals['value']).scalar(): + user.nickname = vals['value'] + else: + log.error(u"This username is already taken") + return _(u"This username is already taken"), 400 + elif param =='email': + existing_email = ub.session.query(ub.User).filter(ub.User.email == vals['value'].lower()).first() + if not existing_email: + user.email = vals['value'] + else: + log.error(u"Found an existing account for this e-mail address.") + return _(u"Found an existing account for this e-mail address."), 400 + elif param =='kindle_mail': + user.kindle_mail = vals['value'] + ub.session_commit() + return "" + @admi.route("/admin/viewconfig", methods=["POST"]) @login_required diff --git a/cps/static/js/table.js b/cps/static/js/table.js index efe0fad4..116a72d6 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -344,6 +344,103 @@ $(function() { $("#h3").removeClass("hidden"); }); + // User table handling + var user_column = []; + $("#user-table > thead > tr > th").each(function() { + var element = {}; + if ($(this).attr("data-edit")) { + element = { + editable: { + mode: "inline", + emptytext: "", + error: function(response) { + return response.responseText; + } + } + }; + } + var validateText = $(this).attr("data-edit-validate"); + if (validateText) { + element.editable.validate = function (value) { + if ($.trim(value) === "") return validateText; + }; + } + user_column.push(element); + }); + + $("#user-table").bootstrapTable({ + sidePagination: "server", + pagination: true, + paginationLoop: false, + paginationDetailHAlign: " hidden", + paginationHAlign: "left", + idField: "id", + uniqueId: "id", + search: true, + showColumns: true, + searchAlign: "left", + showSearchButton : false, + searchOnEnterKey: true, + checkboxHeader: false, + maintainMetaData: true, + responseHandler: responseHandler, + columns: user_column, + formatNoMatches: function () { + return ""; + }, + // eslint-disable-next-line no-unused-vars + /*onEditableSave: function (field, row, oldvalue, $el) { + if (field === "title" || field === "authors") { + $.ajax({ + method:"get", + dataType: "json", + url: window.location.pathname + "/../../ajax/sort_value/" + field + "/" + row.id, + success: function success(data) { + var key = Object.keys(data)[0]; + $("#books-table").bootstrapTable("updateCellByUniqueId", { + id: row.id, + field: key, + value: data[key] + }); + // console.log(data); + } + }); + } + },*/ + // eslint-disable-next-line no-unused-vars + onColumnSwitch: function (field, checked) { + var visible = $("#user-table").bootstrapTable("getVisibleColumns"); + var hidden = $("#user-table").bootstrapTable("getHiddenColumns"); + var st = ""; + visible.forEach(function(item) { + st += "\"" + item.field + "\":\"" + "true" + "\","; + }); + hidden.forEach(function(item) { + st += "\"" + item.field + "\":\"" + "false" + "\","; + }); + st = st.slice(0, -1); + /*$.ajax({ + method:"post", + contentType: "application/json; charset=utf-8", + dataType: "json", + url: window.location.pathname + "/../../ajax/table_settings", + data: "{" + st + "}", + });*/ + }, + }); + + $("#user-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", + function (e, rowsAfter, rowsBefore) { + var rows = rowsAfter; + + if (e.type === "uncheck-all") { + rows = rowsBefore; + } + + var ids = $.map(!$.isArray(rows) ? [rows] : rows, function (row) { + return row.id; + }); + }); }); /* Function for deleting domain restrictions */ diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 1ef64157..777a6f8a 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -7,6 +7,7 @@

{{_('Users')}}

+ {% if allUser.__len__() < 10 %} @@ -41,6 +42,9 @@ {% endif %} {% endfor %}
{{_('Username')}}
+ {% else %} + + {% endif %} {% if (config.config_login_type == 1) %}
{{_('Import LDAP Users')}}
diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html new file mode 100644 index 00000000..40c0f731 --- /dev/null +++ b/cps/templates/user_table.html @@ -0,0 +1,46 @@ +{% extends "layout.html" %} +{% macro user_table_row(parameter, edit_text, show_text, validate) -%} +{{ show_text }} +{%- endmacro %} + +{% block header %} + + +{% endblock %} +{% block body %} +

{{_(title)}}

+ + + + + + {{ user_table_row('nickname', _('Enter Username'),_('Username'), true) }} + {{ user_table_row('email', _('Enter E-mail Address'),_('E-mail Address'), true) }} + {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'),_('Kindle E-mail'), true) }} + + + + + + + + + +
+{% endblock %} +{% block js %} + + + + + +{% endblock %} From eeb7974e05fd333ebf8dc1a597d57033be856ea9 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 31 Jan 2021 14:54:45 +0100 Subject: [PATCH 02/56] User table: Added button for single user edit Added delete-trash-icon (not working) Roles are displayed correct per user Guest user is not visible if anonymous browsing is disabled --- cps/admin.py | 33 +++++++++++++++++++++++---------- cps/static/css/style.css | 4 +++- cps/static/js/table.js | 34 ++++++++++++++++++++++++++++++++-- cps/templates/user_table.html | 30 ++++++++++++++++++++---------- 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index c7210f3f..014f32ef 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -217,25 +217,34 @@ def view_configuration(): @admin_required def edit_user_table(): visibility = current_user.view_settings.get('useredit', {}) - allUser = ub.session.query(ub.User).all() - return render_title_template("user_table.html", users=allUser, visiblility=visibility, - title=_(u"Edit Users"), page="usertable") + allUser = ub.session.query(ub.User) + if not config.config_anonbrowse: + allUser = allUser.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) + + return render_title_template("user_table.html", + users=allUser.all(), + visiblility=visibility, + all_roles = constants.ALL_ROLES, + title=_(u"Edit Users"), + page="usertable") @admi.route("/axjax/listusers") @login_required @admin_required def list_users(): off = request.args.get("offset") or 0 - limit = request.args.get("limit") or 10 - - total_count = ub.session.query(ub.User).count() + limit = request.args.get("limit") or 40 search = request.args.get("search") + + all_user = ub.session.query(ub.User) + if not config.config_anonbrowse: + all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) + total_count = all_user.count() if search: - users = ub.session.query(ub.User).filter().offset(off).limit(limit).all() + users = all_user.filter().offset(off).limit(limit).all() filtered_count = users.length() - # entries, filtered_count, pagination = calibre_db.get_search_results(search, off, order, limit) else: - users = ub.session.query(ub.User).offset(off).limit(limit).all() + users = all_user.offset(off).limit(limit).all() filtered_count = total_count table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": users} @@ -251,7 +260,11 @@ def list_users(): @admin_required def edit_list_user(param): vals = request.form.to_dict() - user = ub.session.query(ub.User).filter(ub.User.id == vals['pk']).one_or_none() # ub.User.query calibre_db.get_book(vals['pk']) + all_user = ub.session.query(ub.User) + if not config.config_anonbrowse: + all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) + + user = all_user.filter(ub.User.id == vals['pk']).one_or_none() if param =='nickname': if not ub.session.query(ub.User).filter(ub.User.nickname == vals['value']).scalar(): user.nickname = vals['value'] diff --git a/cps/static/css/style.css b/cps/static/css/style.css index 8c99aaa0..664cdb9a 100644 --- a/cps/static/css/style.css +++ b/cps/static/css/style.css @@ -51,10 +51,12 @@ body h2 { color:#444; } -a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d; } +a, .danger, .book-remove, .user-remove, .editable-empty, .editable-empty:hover { color: #45b29d; } .book-remove:hover { color: #23527c; } +.user-remove:hover { color: #23527c; } + .btn-default a { color: #444; } .btn-default a:hover { diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 116a72d6..51134fa1 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -429,7 +429,8 @@ $(function() { }, }); - $("#user-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", + + /*$("#user-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) { var rows = rowsAfter; @@ -440,7 +441,7 @@ $(function() { var ids = $.map(!$.isArray(rows) ? [rows] : rows, function (row) { return row.id; }); - }); + });*/ }); /* Function for deleting domain restrictions */ @@ -472,6 +473,15 @@ function EbookActions (value, row) { ].join(""); } +/* Function for deleting books */ +function UserActions (value, row) { + return [ + "
", + "", + "
" + ].join(""); +} + /* Function for keeping checked rows */ function responseHandler(res) { $.each(res.rows, function (i, row) { @@ -479,3 +489,23 @@ function responseHandler(res) { }); return res; } + +function singleUserFormatter(value, row) { + return '' +} + +function checkboxFormatter(value, row, index){ + if(value & this.column) + return ''; + else + return ''; +} + +function checkboxChange(checkbox, index){ + $('#user-table').bootstrapTable('updateCell', { + index: index, + field: 'role', + value: checkbox.checked, + reinit: false + }); +} diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index 40c0f731..043b3339 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -20,22 +20,32 @@ data-url="{{url_for('admin.list_users')}}"> + - {{ user_table_row('nickname', _('Enter Username'),_('Username'), true) }} - {{ user_table_row('email', _('Enter E-mail Address'),_('E-mail Address'), true) }} - {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'),_('Kindle E-mail'), true) }} - - - - - - - + {{ user_table_row('nickname', _('Enter Username'), _('Username'), true) }} + {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} + {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} + {{_('Admin')}} + {{_('Upload')}} + {{_('Download')}} + {{_('Edit')}} + {{_('Change Password')}} + {{_('Edit Public Shelfs')}} + {{_('Delete')}} + {{_('View')}} + {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Users's Locale"), true) }} + {{ user_table_row('allowed_tags', _("Enter Users's Locale"), _("Users's Locale"), true) }} + {{ user_table_row('allowed_column_value', _("Enter Users's Locale"), _("Users's Locale"), true) }} + {{ user_table_row('denied_column_value', _("Enter Users's Locale"), _("Users's Locale"), true) }} + {{_('Delete User')}} {% endblock %} +{% block modal %} +{{ delete_confirm_modal() }} +{% endblock %} {% block js %} From 33a0a4c173a5ae229427a1e296d38da63d54293f Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 31 Jan 2021 18:55:32 +0100 Subject: [PATCH 03/56] Changed function for getting path in js file --- cps/static/js/edit_books.js | 4 ++-- cps/static/js/main.js | 28 ++++++++++++---------------- cps/templates/admin.html | 3 +-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/cps/static/js/edit_books.js b/cps/static/js/edit_books.js index 5f9154fc..b7890764 100644 --- a/cps/static/js/edit_books.js +++ b/cps/static/js/edit_books.js @@ -78,10 +78,10 @@ function prefixedSource(prefix, query, cb, bhAdapter) { }); } -function getPath() { +/*function getPath() { var jsFileLocation = $("script[src*=edit_books]").attr("src"); // the js file path return jsFileLocation.substr(0, jsFileLocation.search("/static/js/edit_books.js")); // the js folder path -} +}*/ var authors = new Bloodhound({ name: "authors", diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 33881aaa..fe617763 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -15,6 +15,11 @@ * along with this program. If not, see . */ + +function getPath() { + var jsFileLocation = $("script[src*=jquery]").attr("src"); // the js file path + return jsFileLocation.substr(0, jsFileLocation.search("/static/js/libs/jquery.min.js")); // the js folder path +} // Generic control/related handler to show/hide fields based on a checkbox' value // e.g. // @@ -110,15 +115,13 @@ $(document).ready(function() { }); function ConfirmDialog(id, dataValue, yesFn, noFn) { - var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src; - var path = src.substring(0, src.lastIndexOf("/")); var $confirm = $("#GeneralDeleteModal"); // var dataValue= e.data('value'); // target.data('value'); $confirm.modal('show'); $.ajax({ method:"get", dataType: "json", - url: path + "/../../ajax/loaddialogtexts/" + id, + url: getPath() + "/ajax/loaddialogtexts/" + id, success: function success(data) { $("#header").html(data.header); $("#text").html(data.main); @@ -140,15 +143,13 @@ function ConfirmDialog(id, dataValue, yesFn, noFn) { $("#delete_confirm").click(function() { //get data-id attribute of the clicked element - var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src; - var path = src.substring(0, src.lastIndexOf("/")); var deleteId = $(this).data("delete-id"); var bookFormat = $(this).data("delete-format"); if (bookFormat) { - window.location.href = path + "/../../delete/" + deleteId + "/" + bookFormat; + window.location.href = getPath() + "/delete/" + deleteId + "/" + bookFormat; } else { if ($(this).data("delete-format")) { - path = path + "/../../ajax/delete/" + deleteId; + path = getPath() + "/ajax/delete/" + deleteId; $.ajax({ method:"get", url: path, @@ -168,7 +169,7 @@ $("#delete_confirm").click(function() { } }); } else { - window.location.href = path + "/../../delete/" + deleteId; + window.location.href = getPath() + "/delete/" + deleteId; } } @@ -224,7 +225,7 @@ $(function() { function updateTimer() { $.ajax({ dataType: "json", - url: window.location.pathname + "/../../get_updater_status", + url: window.location.pathname + "/get_updater_status", success: function success(data) { // console.log(data.status); $("#DialogContent").html(updateText[data.status]); @@ -484,12 +485,9 @@ $(function() { $(this).attr('id'), $(this).data('value'), function (value) { - var pathname = document.getElementsByTagName("script"); - var src = pathname[pathname.length - 1].src; - var path = src.substring(0, src.lastIndexOf("/")); $.ajax({ method: "get", - url: path + "/../../kobo_auth/deleteauthtoken/" + value, + url: getPath() + "/kobo_auth/deleteauthtoken/" + value, }); $("#config_delete_kobo_token").hide(); } @@ -582,12 +580,10 @@ $(function() { $("#DialogFinished").addClass("hidden"); $("#DialogContent").html(""); $("#spinner2").show(); - var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src; - var path = src.substring(0, src.lastIndexOf("/")); $.ajax({ method:"get", dataType: "json", - url: path + "/../../import_ldap_users", + url: getPath() + "/import_ldap_users", success: function success(data) { $("#spinner2").hide(); $("#DialogContent").html(data.text); diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 777a6f8a..89e423a2 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -42,9 +42,8 @@ {% endif %} {% endfor %} - {% else %} - {% endif %} + {% if (config.config_login_type == 1) %}
{{_('Import LDAP Users')}}
From 60497c60c1c16b9482cc5f0b576ee672729bb687 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 7 Feb 2021 09:20:37 +0100 Subject: [PATCH 04/56] Update creating Home_Config variable for packaging support, added .HOMEDIR to ignored filename during update --- cps/constants.py | 6 ++++-- cps/updater.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cps/constants.py b/cps/constants.py index ac48a5b8..613d17ac 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -21,8 +21,10 @@ import sys import os from collections import namedtuple -# if installed via pip this variable is set to true -HOME_CONFIG = False +# if installed via pip this variable is set to true (empty file with name .HOMEDIR present) +HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR')) + +#In executables updater is not available, so variable is set to False there UPDATER_AVAILABLE = True # Base dir is parent of current file, necessary if called from different folder diff --git a/cps/updater.py b/cps/updater.py index b03a0844..4d5117a9 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -227,7 +227,7 @@ class Updater(threading.Thread): os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep + 'client_secrets.json', os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv', os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2', - os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so' + os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR' ) additional_path = self.is_venv() if additional_path: From e0ce135838e85bc6f50626d25cc8d8aabad414a3 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 7 Feb 2021 09:39:36 +0100 Subject: [PATCH 05/56] Move "create_engine" call --- cps/ub.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cps/ub.py b/cps/ub.py index 1969ef53..b295c6e3 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -673,12 +673,11 @@ def init_db(app_db_path): Session.configure(bind=engine) session = Session() + Base.metadata.create_all(engine) if os.path.exists(app_db_path): - Base.metadata.create_all(engine) migrate_Database(session) clean_database(session) else: - Base.metadata.create_all(engine) create_admin_user(session) create_anonymous_user(session) From fbb905957bf25291b003b9e64419711e72231872 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 8 Feb 2021 18:55:48 +0100 Subject: [PATCH 06/56] Prevent redirect 308 on / routes --- cps/web.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/web.py b/cps/web.py index 5b805ac8..09744baf 100644 --- a/cps/web.py +++ b/cps/web.py @@ -719,8 +719,8 @@ def index(page): return render_books_list("newest", sort_param, 1, page) -@web.route('//', defaults={'page': 1, 'book_id': "1"}) -@web.route('///', defaults={'page': 1, 'book_id': "1"}) +@web.route('//', defaults={'page': 1, 'book_id': 1}) +@web.route('///', defaults={'page': 1, 'book_id': 1}) @web.route('///', defaults={'page': 1}) @web.route('////') @login_required_if_no_ano From 8aebf4819355372e4392b3aa137037339ce43394 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 8 Feb 2021 19:28:56 +0100 Subject: [PATCH 07/56] Update handling for package data --- cps/updater.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cps/updater.py b/cps/updater.py index 4d5117a9..5ef060ef 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -232,6 +232,11 @@ class Updater(threading.Thread): additional_path = self.is_venv() if additional_path: exclude = exclude + (additional_path,) + + # check if we are in a package, rename cps.py to __init__.py + if constants.HOME_CONFIG: + shutil.move(os.path.join(source, 'cps.py'), os.path.join(source, '__init__.py')) + for root, dirs, files in os.walk(destination, topdown=True): for name in files: old_list.append(os.path.join(root, name).replace(destination, '')) From 5dac13b1da4243d1e292e21a4727dbb87c87d4d9 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 8 Feb 2021 20:17:02 +0100 Subject: [PATCH 08/56] Revert change in ub.py --- cps/ub.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cps/ub.py b/cps/ub.py index b295c6e3..1969ef53 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -673,11 +673,12 @@ def init_db(app_db_path): Session.configure(bind=engine) session = Session() - Base.metadata.create_all(engine) if os.path.exists(app_db_path): + Base.metadata.create_all(engine) migrate_Database(session) clean_database(session) else: + Base.metadata.create_all(engine) create_admin_user(session) create_anonymous_user(session) From cefdd2f66c635c19701aed27007a5650be2ed3a3 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 8 Feb 2021 20:39:03 +0100 Subject: [PATCH 09/56] suppress asyncio message always on restart or shutdown --- cps/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cps/server.py b/cps/server.py index 3148aed6..3787d2e9 100644 --- a/cps/server.py +++ b/cps/server.py @@ -251,10 +251,11 @@ class WebServer(object): finally: self.wsgiserver = None + # prevent irritiating log of pending tasks message from asyncio + logger.get('asyncio').setLevel(logger.logging.CRITICAL) + if not self.restart: log.info("Performing shutdown of Calibre-Web") - # prevent irritiating log of pending tasks message from asyncio - logger.get('asyncio').setLevel(logger.logging.CRITICAL) return True log.info("Performing restart of Calibre-Web") From c810c5275a7b45d2dfc1cdd2f8542bc7e0f98535 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 9 Feb 2021 18:05:32 +0100 Subject: [PATCH 10/56] Merge branch 'master' into development --- test/Calibre-Web TestSummary_Linux.html | 92 +++++++++++++------------ 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 365e47c8..702a2668 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,14 +37,14 @@
-

Start Time: 2021-02-01 19:02:39

+

Start Time: 2021-02-08 20:40:55

-

Stop Time: 2021-02-01 21:32:03

+

Stop Time: 2021-02-08 23:09:33

@@ -1190,11 +1190,11 @@ - + TestEditBooksOnGdrive 20 - 19 - 1 + 20 + 0 0 0 @@ -1375,31 +1375,11 @@ - +
TestEditBooksOnGdrive - test_watch_metadata
- -
- FAIL -
- - - - + PASS @@ -1615,12 +1595,12 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te - + TestKoboSync 9 - 9 - 0 + 8 0 + 1 0 Detail @@ -1629,11 +1609,31 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te - +
TestKoboSync - test_book_download
- PASS + +
+ ERROR +
+ + + + @@ -3400,8 +3400,8 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te Total 299 291 - 1 0 + 1 7   @@ -3520,7 +3520,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te SQLAlchemy - 1.3.22 + 1.3.23 Basic @@ -3556,7 +3556,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te httplib2 - 0.18.1 + 0.19.0 TestEbookConvertCalibreGDrive @@ -3574,7 +3574,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te PyDrive2 - 1.7.0 + 1.7.2 TestEbookConvertCalibreGDrive @@ -3592,7 +3592,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te httplib2 - 0.18.1 + 0.19.0 TestEbookConvertGDriveKepubify @@ -3610,7 +3610,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te PyDrive2 - 1.7.0 + 1.7.2 TestEbookConvertGDriveKepubify @@ -3652,7 +3652,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te httplib2 - 0.18.1 + 0.19.0 TestEditBooksOnGdrive @@ -3670,7 +3670,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te PyDrive2 - 1.7.0 + 1.7.2 TestEditBooksOnGdrive @@ -3688,7 +3688,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te httplib2 - 0.18.1 + 0.19.0 TestSetupGdrive @@ -3700,7 +3700,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te PyDrive2 - 1.7.0 + 1.7.2 TestSetupGdrive @@ -3716,6 +3716,12 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te TestGoodreads + + python-Levenshtein + 0.12.2 + TestGoodreads + + jsonschema 3.2.0 @@ -3760,7 +3766,7 @@ AssertionError: 'series' unexpectedly found in {'reader': ['epub'], 'title': 'te
From 0992bafe308249aad9ef56f5005f11c59ccf9512 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 13 Feb 2021 13:17:02 +0100 Subject: [PATCH 11/56] Bulk User management --- cps/admin.py | 39 ++++++- cps/constants.py | 20 ++++ cps/static/js/table.js | 57 ++++++++-- cps/templates/user_table.html | 41 ++++--- test/Calibre-Web TestSummary_Linux.html | 138 ++++++++++++++++-------- 5 files changed, 226 insertions(+), 69 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index aa608ef5..30b268cb 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -35,6 +35,7 @@ from flask import Blueprint, flash, redirect, url_for, abort, request, make_resp from flask_login import login_required, current_user, logout_user, confirm_login from flask_babel import gettext as _ from sqlalchemy import and_ +from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError from sqlalchemy.sql.expression import func, or_ @@ -225,11 +226,12 @@ def edit_user_table(): return render_title_template("user_table.html", users=allUser.all(), visiblility=visibility, - all_roles = constants.ALL_ROLES, + all_roles=constants.ALL_ROLES, + sidebar_settings=constants.sidebar_settings, title=_(u"Edit Users"), page="usertable") -@admi.route("/axjax/listusers") +@admi.route("/ajax/listusers") @login_required @admin_required def list_users(): @@ -242,8 +244,11 @@ def list_users(): all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) total_count = all_user.count() if search: - users = all_user.filter().offset(off).limit(limit).all() - filtered_count = users.length() + users = all_user.filter(or_(func.lower(ub.User.nickname).ilike("%" + search + "%"), + func.lower(ub.User.kindle_mail).ilike("%" + search + "%"), + func.lower(ub.User.email).ilike("%" + search + "%")))\ + .offset(off).limit(limit).all() + filtered_count = len(users) else: users = all_user.offset(off).limit(limit).all() filtered_count = total_count @@ -255,6 +260,14 @@ def list_users(): response.headers["Content-Type"] = "application/json; charset=utf-8" return response +@admi.route("/ajax/deleteuser") +@login_required +@admin_required +def delete_user(): + # ToDo User delete check also not last one + pass + return + @admi.route("/axjax/editlistusers/", methods=['POST']) @login_required @@ -285,6 +298,24 @@ def edit_list_user(param): return "" +@admi.route("/ajax/user_table_settings", methods=['POST']) +@login_required +@admin_required +def update_table_settings(): + # ToDo: Save table settings + current_user.view_settings['useredit'] = json.loads(request.data) + try: + try: + flag_modified(current_user, "view_settings") + except AttributeError: + pass + ub.session.commit() + except (InvalidRequestError, OperationalError): + log.error("Invalid request received: %r ", request, ) + return "Invalid request", 400 + return "" + + @admi.route("/admin/viewconfig", methods=["POST"]) @login_required @admin_required diff --git a/cps/constants.py b/cps/constants.py index 613d17ac..1712da3f 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -88,6 +88,26 @@ SIDEBAR_ARCHIVED = 1 << 15 SIDEBAR_DOWNLOAD = 1 << 16 SIDEBAR_LIST = 1 << 17 +sidebar_settings = { + "detail_random": DETAIL_RANDOM, + "sidebar_language": SIDEBAR_LANGUAGE, + "sidebar_series": SIDEBAR_SERIES, + "sidebar_category": SIDEBAR_CATEGORY, + "sidebar_random": SIDEBAR_RANDOM, + "sidebar_author": SIDEBAR_AUTHOR, + "sidebar_best_rated": SIDEBAR_BEST_RATED, + "sidebar_read_and_unread": SIDEBAR_READ_AND_UNREAD, + "sidebar_recent": SIDEBAR_RECENT, + "sidebar_sorted": SIDEBAR_SORTED, + "sidebar_publisher": SIDEBAR_PUBLISHER, + "sidebar_rating": SIDEBAR_RATING, + "sidebar_format": SIDEBAR_FORMAT, + "sidebar_archived": SIDEBAR_ARCHIVED, + "sidebar_download": SIDEBAR_DOWNLOAD, + "sidebar_list": SIDEBAR_LIST, + } + + ADMIN_USER_ROLES = sum(r for r in ALL_ROLES.values()) & ~ROLE_ANONYMOUS ADMIN_USER_SIDEBAR = (SIDEBAR_LIST << 1) - 1 diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 0695ee92..b857fc95 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -116,7 +116,7 @@ $(function() { search: true, showColumns: true, searchAlign: "left", - showSearchButton : false, + showSearchButton : true, searchOnEnterKey: true, checkboxHeader: false, maintainMetaData: true, @@ -377,15 +377,24 @@ $(function() { search: true, showColumns: true, searchAlign: "left", - showSearchButton : false, + showSearchButton : true, searchOnEnterKey: true, - checkboxHeader: false, + checkboxHeader: true, maintainMetaData: true, responseHandler: responseHandler, columns: user_column, formatNoMatches: function () { return ""; }, + onPostBody () { + // var elements = ; + // Remove all checkboxes from Headers for showing the texts in the column selector + $('.columns [data-field]').each(function(){ + var elText = $(this).next().text(); + $(this).next().empty(); + $(this).next().text(elText); + }); + }, // eslint-disable-next-line no-unused-vars /*onEditableSave: function (field, row, oldvalue, $el) { if (field === "title" || field === "authors") { @@ -411,22 +420,47 @@ $(function() { var hidden = $("#user-table").bootstrapTable("getHiddenColumns"); var st = ""; visible.forEach(function(item) { - st += "\"" + item.field + "\":\"" + "true" + "\","; + st += "\"" + item.name + "\":\"" + "true" + "\","; }); hidden.forEach(function(item) { - st += "\"" + item.field + "\":\"" + "false" + "\","; + st += "\"" + item.name + "\":\"" + "false" + "\","; }); st = st.slice(0, -1); - /*$.ajax({ + $.ajax({ method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../../ajax/table_settings", + url: window.location.pathname + "/../../ajax/user_table_settings", data: "{" + st + "}", - });*/ + }); }, }); + function user_handle (userId) { + $.ajax({ + method:"post", + url: window.location.pathname + "/../../ajax/deleteuser", + data: {"userid":userId} + }); + $.ajax({ + method:"get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success:function(data) { + $("#user-table").bootstrapTable("load", data); + } + }); + + + } + + + $("#user-table").on("click-cell.bs.table", function (field, value, row, $element) { + if (value === "denied_column_value") { + ConfirmDialog("btndeluser", $element.id, user_handle); + } + }); /*$("#user-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) { @@ -474,7 +508,7 @@ function EbookActions (value, row) { /* Function for deleting books */ function UserActions (value, row) { return [ - "
", + "
", "", "
" ].join(""); @@ -507,3 +541,8 @@ function checkboxChange(checkbox, index){ reinit: false }); } + + +function checkboxHeader(element) { + console.log("hallo"); +} diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index 043b3339..3c6d4ed5 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -1,7 +1,8 @@ {% extends "layout.html" %} {% macro user_table_row(parameter, edit_text, show_text, validate) -%} {{_(title)}} + +
- - - + + + {{ user_table_row('nickname', _('Enter Username'), _('Username'), true) }} {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} - - - - - - - - + + + + + + + + {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Users's Locale"), true) }} {{ user_table_row('allowed_tags', _("Enter Users's Locale"), _("Users's Locale"), true) }} {{ user_table_row('allowed_column_value', _("Enter Users's Locale"), _("Users's Locale"), true) }} {{ user_table_row('denied_column_value', _("Enter Users's Locale"), _("Users's Locale"), true) }} + + + + + + + + + + + + + + diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 702a2668..c18e6bad 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,14 +37,14 @@
-

Start Time: 2021-02-08 20:40:55

+

Start Time: 2021-02-09 20:40:28

-

Stop Time: 2021-02-08 23:09:33

+

Stop Time: 2021-02-09 23:08:52

@@ -1595,12 +1595,12 @@ - + - + + - + - + @@ -2376,11 +2356,11 @@ IndexError: list index out of range - + - - + + + - + @@ -2594,12 +2594,12 @@ IndexError: list index out of range - + - - - + + + + - + @@ -2688,11 +2708,41 @@ IndexError: list index out of range - + - + @@ -3399,8 +3449,8 @@ IndexError: list index out of range - - + + @@ -3430,7 +3480,7 @@ IndexError: list index out of range - + @@ -3766,7 +3816,7 @@ IndexError: list index out of range From a3a11bdf3f2bf38a7d15e86a7cf8796037993758 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 14 Feb 2021 13:09:31 +0100 Subject: [PATCH 12/56] Changed checkbox states are saved on server --- cps/admin.py | 24 ++++++++-- cps/static/js/table.js | 89 ++++++++++++++++++++++++++++------- cps/templates/user_table.html | 83 ++++++++++++++++++++------------ 3 files changed, 146 insertions(+), 50 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 30b268cb..03f83d5a 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -269,16 +269,21 @@ def delete_user(): return -@admi.route("/axjax/editlistusers/", methods=['POST']) +# @admi.route("/ajax/editlistusers/", defaults={"value": 0}, methods=['POST']) +@admi.route("/ajax/editlistusers/", methods=['POST']) @login_required @admin_required def edit_list_user(param): - vals = request.form.to_dict() + vals = request.form.to_dict(flat=False) all_user = ub.session.query(ub.User) if not config.config_anonbrowse: all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) - - user = all_user.filter(ub.User.id == vals['pk']).one_or_none() + # only one user is posted + if "pk" in vals: + user = all_user.filter(ub.User.id == vals['pk']).one_or_none() + else: + # ToDo + user = all_user.filter(ub.User.id == vals['pk[]']).all() if param =='nickname': if not ub.session.query(ub.User).filter(ub.User.nickname == vals['value']).scalar(): user.nickname = vals['value'] @@ -294,6 +299,17 @@ def edit_list_user(param): return _(u"Found an existing account for this e-mail address."), 400 elif param =='kindle_mail': user.kindle_mail = vals['value'] + elif param == 'role': + if vals['value'] == 'true': + user.role |= int(vals['field_index']) + else: + user.role &= ~int(vals['field_index']) + elif param == 'sidebar_view': + if vals['value'] == 'true': + user.sidebar_view |= int(vals['field_index']) + else: + user.sidebar_view &= ~int(vals['field_index']) + ub.session_commit() return "" diff --git a/cps/static/js/table.js b/cps/static/js/table.js index b857fc95..46502d69 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -387,7 +387,6 @@ $(function() { return ""; }, onPostBody () { - // var elements = ; // Remove all checkboxes from Headers for showing the texts in the column selector $('.columns [data-field]').each(function(){ var elText = $(this).next().text(); @@ -436,6 +435,10 @@ $(function() { }, }); + $("#user_delete_selection").click(function() { + $("#user-table").bootstrapTable("uncheckAll"); + }); + function user_handle (userId) { $.ajax({ method:"post", @@ -451,8 +454,6 @@ $(function() { $("#user-table").bootstrapTable("load", data); } }); - - } @@ -462,7 +463,7 @@ $(function() { } }); - /*$("#user-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", + $("#user-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) { var rows = rowsAfter; @@ -473,7 +474,23 @@ $(function() { var ids = $.map(!$.isArray(rows) ? [rows] : rows, function (row) { return row.id; }); - });*/ + var func = $.inArray(e.type, ["check", "check-all"]) > -1 ? "union" : "difference"; + selections = window._[func](selections, ids); + if (selections.length < 1) { + $("#user_delete_selection").addClass("disabled"); + $("#user_delete_selection").attr("aria-disabled", true); + $(".check_head").attr("aria-disabled", true); + $(".check_head").attr("disabled", true); + $(".check_head").prop('checked', false); + } else { + $("#user_delete_selection").removeClass("disabled"); + $("#user_delete_selection").attr("aria-disabled", false); + $(".check_head").attr("aria-disabled", false); + $(".check_head").removeAttr("disabled"); + + } + + }); }); /* Function for deleting domain restrictions */ @@ -528,21 +545,61 @@ function singleUserFormatter(value, row) { function checkboxFormatter(value, row, index){ if(value & this.column) - return ''; + return ''; else - return ''; + return ''; } -function checkboxChange(checkbox, index){ - $('#user-table').bootstrapTable('updateCell', { - index: index, - field: 'role', - value: checkbox.checked, - reinit: false +function checkboxChange(checkbox, userId, field, field_index) { + $.ajax({ + method:"post", + url: window.location.pathname + "/../../ajax/editlistusers/" + field, + data: {"pk":userId, "field_index":field_index, "value": checkbox.checked} + }); + $.ajax({ + method:"get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success:function(data) { + $("#user-table").bootstrapTable("load", data); + } }); } - -function checkboxHeader(element) { - console.log("hallo"); +function checkboxHeader(checkbox, field, field_index) { + var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); + $.ajax({ + method:"post", + url: window.location.pathname + "/../../ajax/editlistusers/" + field, + data: {"pk":result, "field_index":field_index, "value": checkbox.checked} + }); + $.ajax({ + method:"get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success:function(data) { + $("#user-table").bootstrapTable("load", data); + } + }); +} + +function user_handle (userId) { + $.ajax({ + method:"post", + url: window.location.pathname + "/../../ajax/deleteuser", + data: {"userid":userId} + }); + $.ajax({ + method:"get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success:function(data) { + $("#user-table").bootstrapTable("load", data); + } + }); + + } diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index 3c6d4ed5..e2ac62e5 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -7,8 +7,28 @@ data-editable-url="{{ url_for('admin.edit_list_user', param=parameter)}}" data-editable-title="{{ edit_text }}" data-edit="true" - {% if validate %}data-edit-validate="{{ _('This Field is Required') }}" {% endif %} ->{{ show_text }} + {% if validate %}data-edit-validate="{{ _('This Field is Required') }}" {% endif %}> + {{ show_text }} + +{%- endmacro %} + +{% macro user_checkbox_row(parameter, array_field, show_text, element, value) -%} + {%- endmacro %} {% block header %} @@ -17,8 +37,11 @@ {% endblock %} {% block body %}

{{_(title)}}

-
{{_('Edit')}}{{_('Admin')}}{{_('Upload')}}{{_('Download')}}{{_('Edit')}}{{_('Change Password')}}{{_('Edit Public Shelfs')}}{{_('Delete')}}{{_('View')}}
{{_('Admin')}}

{{_('Upload')}}

{{_('Download')}}

{{_('Edit')}}

{{_('Change Password')}}

{{_('Edit Public Shelfs')}}

{{_('Delete')}}

{{_('View')}}

{{_('Show Random Books in Detail View')}}

{{_('Show language selection')}}

{{_('Show series selection')}}

{{ _('Show category selection')}}

{{ _('Show random books')}}

{{_('Show author selection')}}

{{_('Show Top Rated Books')}}

{{_('Show random books')}}

{{_('Show publisher selection')}}

{{_('Show ratings selection')}}

{{_('Show file formats selection')}}

{{_('Show archived books')}}

{{_('Show Downloaded Books')}}

{{_('Show Books List')}}
{{_('Delete User')}}
TestKoboSync 9890 01 0 Detail @@ -1609,31 +1609,11 @@ -
TestKoboSync - test_book_download
-
- ERROR -
- - - -
PASS
TestRegister 88071 0 0 @@ -2453,11 +2433,31 @@ IndexError: list index out of range -
TestRegister - test_user_change_password
PASS +
+ FAIL +
+ + + +
TestUpdater 8700511 1 Detail @@ -2653,11 +2653,31 @@ IndexError: list index out of range -
TestUpdater - test_perform_update_stable_errors
PASS +
+ FAIL +
+ + + +
TestUpdater - test_reconnect_database
PASS +
+ ERROR +
+ + + +
Total 29929102892 1 7  
PlatformLinux 5.8.0-41-generic #46~20.04.1-Ubuntu SMP Mon Jan 18 17:52:23 UTC 2021 x86_64 x86_64Linux 5.8.0-43-generic #49~20.04.1-Ubuntu SMP Fri Feb 5 09:57:56 UTC 2021 x86_64 x86_64 Basic
+
+ +
+
+ +
+ {{show_text}} +
-
+
+
+
{{_('Remove Selections')}}
+
+
@@ -29,32 +52,32 @@ {{ user_table_row('nickname', _('Enter Username'), _('Username'), true) }} {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} - - - - - - - - - {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Users's Locale"), true) }} - {{ user_table_row('allowed_tags', _("Enter Users's Locale"), _("Users's Locale"), true) }} - {{ user_table_row('allowed_column_value', _("Enter Users's Locale"), _("Users's Locale"), true) }} - {{ user_table_row('denied_column_value', _("Enter Users's Locale"), _("Users's Locale"), true) }} - - - - - - - - - - - - - - + {{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}} + {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Denied Tags"), true) }} + {{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), true) }} + {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), true) }} + {{ user_table_row('denied_column_value', _("Enter Users's Locale"), _("Denied Columns Values"), true) }} + {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show category selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show random books'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_author", _('Show author selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_best_rated", _('Show Top Rated Books'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show random books'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_publisher", _('Show publisher selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_rating", _('Show ratings selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_format", _('Show file formats selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_archived", _('Show archived books'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_download", _('Show Downloaded Books'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_list", _('Show Books List'), visiblility, sidebar_settings)}} From e64a504bb11eb47b8853b0794904850180902739 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 20 Feb 2021 14:18:27 +0100 Subject: [PATCH 13/56] Working Locale and default language selection in user table edit --- cps/admin.py | 119 +++++++++++++++++++++++++--------- cps/isoLanguages.py | 1 + cps/static/js/table.js | 32 +++++---- cps/templates/user_table.html | 29 +++++++-- 4 files changed, 132 insertions(+), 49 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 03f83d5a..8bedef8a 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -31,6 +31,7 @@ from datetime import datetime, timedelta from babel import Locale as LC from babel.dates import format_datetime +from babel.core import UnknownLocaleError from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g from flask_login import login_required, current_user, logout_user, confirm_login from flask_babel import gettext as _ @@ -39,7 +40,7 @@ from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError from sqlalchemy.sql.expression import func, or_ -from . import constants, logger, helper, services +from . import constants, logger, helper, services, isoLanguages from .cli import filepicker from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash @@ -253,7 +254,21 @@ def list_users(): users = all_user.offset(off).limit(limit).all() filtered_count = total_count + for user in users: + # set readable locale + #try: + # user.local = LC.parse(user.locale).get_language_name(get_locale()) + #except UnknownLocaleError: + # # This should not happen + # user.local = _(isoLanguages.get(part1=user.locale).name) + # Set default language + if user.default_language == "all": + user.default = _("all") + else: + user.default = LC.parse(user.default_language).get_language_name(get_locale()) + table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": users} + js_list = json.dumps(table_entries, cls=db.AlchemyEncoder) response = make_response(js_list) @@ -266,10 +281,32 @@ def list_users(): def delete_user(): # ToDo User delete check also not last one pass - return + return "" + +@admi.route("/ajax/getlocale") +@login_required +@admin_required +def table_get_locale(): + locale = babel.list_translations() + [LC('en')] + ret = list() + current_locale = get_locale() + for loc in locale: + ret.append({'value':str(loc),'text':loc.get_language_name(current_locale)}) + return json.dumps(ret) + + +@admi.route("/ajax/getdefaultlanguage") +@login_required +@admin_required +def table_get_default_lang(): + languages = calibre_db.speaking_language() + ret = list() + ret.append({'value':'all','text':_('Show All')}) + for lang in languages: + ret.append({'value':lang.lang_code,'text': lang.name}) + return json.dumps(ret) -# @admi.route("/ajax/editlistusers/", defaults={"value": 0}, methods=['POST']) @admi.route("/ajax/editlistusers/", methods=['POST']) @login_required @admin_required @@ -280,36 +317,56 @@ def edit_list_user(param): all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) # only one user is posted if "pk" in vals: - user = all_user.filter(ub.User.id == vals['pk']).one_or_none() + users = [all_user.filter(ub.User.id == vals['pk'][0]).one_or_none()] else: - # ToDo - user = all_user.filter(ub.User.id == vals['pk[]']).all() - if param =='nickname': - if not ub.session.query(ub.User).filter(ub.User.nickname == vals['value']).scalar(): - user.nickname = vals['value'] + if "pk[]" in vals: + users = all_user.filter(ub.User.id.in_(vals['pk[]'])).all() else: - log.error(u"This username is already taken") - return _(u"This username is already taken"), 400 - elif param =='email': - existing_email = ub.session.query(ub.User).filter(ub.User.email == vals['value'].lower()).first() - if not existing_email: - user.email = vals['value'] - else: - log.error(u"Found an existing account for this e-mail address.") - return _(u"Found an existing account for this e-mail address."), 400 - elif param =='kindle_mail': - user.kindle_mail = vals['value'] - elif param == 'role': - if vals['value'] == 'true': - user.role |= int(vals['field_index']) - else: - user.role &= ~int(vals['field_index']) - elif param == 'sidebar_view': - if vals['value'] == 'true': - user.sidebar_view |= int(vals['field_index']) - else: - user.sidebar_view &= ~int(vals['field_index']) - + return "" + if 'field_index' in vals: + vals['field_index'] = vals['field_index'][0] + if 'value' in vals: + vals['value'] = vals['value'][0] + else: + return "" + for user in users: + if param =='nickname': + if not ub.session.query(ub.User).filter(ub.User.nickname == vals['value']).scalar(): + user.nickname = vals['value'] + else: + log.error(u"This username is already taken") + return _(u"This username is already taken"), 400 + elif param =='email': + existing_email = ub.session.query(ub.User).filter(ub.User.email == vals['value'].lower()).first() + if not existing_email: + user.email = vals['value'] + else: + log.error(u"Found an existing account for this e-mail address.") + return _(u"Found an existing account for this e-mail address."), 400 + elif param =='kindle_mail': + user.kindle_mail = vals['value'] + elif param == 'role': + if vals['value'] == 'true': + user.role |= int(vals['field_index']) + else: + user.role &= ~int(vals['field_index']) + elif param == 'sidebar_view': + if vals['value'] == 'true': + user.sidebar_view |= int(vals['field_index']) + else: + user.sidebar_view &= ~int(vals['field_index']) + elif param == 'denied_tags': + user.denied_tags = vals['value'] + elif param == 'allowed_tags': + user.allowed_tags = vals['value'] + elif param == 'allowed_column_value': + user.allowed_column_value = vals['value'] + elif param == 'denied_column_value': + user.denied_column_value = vals['value'] + elif param == 'locale': + user.locale = vals['value'] + elif param == 'default_language': + user.default_language = vals['value'] ub.session_commit() return "" diff --git a/cps/isoLanguages.py b/cps/isoLanguages.py index 4c0aefc3..4fda9088 100644 --- a/cps/isoLanguages.py +++ b/cps/isoLanguages.py @@ -67,6 +67,7 @@ def get_language_codes(locale, language_names, remainder=None): remainder.extend(language_names) return languages + def get_valid_language_codes(locale, language_names, remainder=None): languages = list() if "" in language_names: diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 46502d69..eaac7c49 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -391,6 +391,10 @@ $(function() { $('.columns [data-field]').each(function(){ var elText = $(this).next().text(); $(this).next().empty(); + var index = elText.lastIndexOf('\n', elText.length - 2); + if ( index > -1) { + elText = elText.substr(index); + } $(this).next().text(elText); }); }, @@ -567,20 +571,22 @@ function checkboxChange(checkbox, userId, field, field_index) { }); } -function checkboxHeader(checkbox, field, field_index) { +function checkboxHeader(CheckboxState, field, field_index) { var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); $.ajax({ method:"post", url: window.location.pathname + "/../../ajax/editlistusers/" + field, - data: {"pk":result, "field_index":field_index, "value": checkbox.checked} - }); - $.ajax({ - method:"get", - url: window.location.pathname + "/../../ajax/listusers", - async: true, - timeout: 900, - success:function(data) { - $("#user-table").bootstrapTable("load", data); + data: {"pk":result, "field_index":field_index, "value": CheckboxState}, + success:function() { + $.ajax({ + method:"get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success:function(data) { + $("#user-table").bootstrapTable("load", data); + } + }); } }); } @@ -600,6 +606,8 @@ function user_handle (userId) { $("#user-table").bootstrapTable("load", data); } }); - - +} + +function test(){ + console.log("hello"); } diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index e2ac62e5..f8acb8fc 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -19,18 +19,33 @@ data-formatter="checkboxFormatter">
{{show_text}} {%- endmacro %} +{% macro user_select_row(parameter, url, show_text, validate) -%} +
+ +{%- endmacro %} + + {% block header %} @@ -51,7 +66,13 @@ {{ user_table_row('nickname', _('Enter Username'), _('Username'), true) }} {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} + {{ user_select_row('locale', url_for('admin.table_get_locale'), _('Locale'), true) }} + {{ user_select_row('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }} {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} + {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Denied Tags"), true) }} + {{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), true) }} + {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), true) }} + {{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), true) }} {{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}} {{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}} {{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}} @@ -60,10 +81,6 @@ {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} {{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}} - {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Denied Tags"), true) }} - {{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), true) }} - {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), true) }} - {{ user_table_row('denied_column_value', _("Enter Users's Locale"), _("Denied Columns Values"), true) }} {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}} From 81c30d5fd578f9cf0652810f4d627fee3c86fc37 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 20 Feb 2021 19:55:51 +0100 Subject: [PATCH 14/56] Add missing files from bootstrap editable Added user-table single-select header for locale and default-language --- cps/admin.py | 4 +++ cps/static/img/clear.png | Bin 0 -> 509 bytes cps/static/img/loading.gif | Bin 0 -> 1849 bytes cps/templates/user_table.html | 63 ++++++++++++++++++++++++++-------- 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 cps/static/img/clear.png create mode 100644 cps/static/img/loading.gif diff --git a/cps/admin.py b/cps/admin.py index 8bedef8a..386e2125 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -220,12 +220,16 @@ def view_configuration(): @admin_required def edit_user_table(): visibility = current_user.view_settings.get('useredit', {}) + languages = calibre_db.speaking_language() + translations = babel.list_translations() + [LC('en')] allUser = ub.session.query(ub.User) if not config.config_anonbrowse: allUser = allUser.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) return render_title_template("user_table.html", users=allUser.all(), + translations=translations, + languages=languages, visiblility=visibility, all_roles=constants.ALL_ROLES, sidebar_settings=constants.sidebar_settings, diff --git a/cps/static/img/clear.png b/cps/static/img/clear.png new file mode 100644 index 0000000000000000000000000000000000000000..580b52a5be8a644f826def0c7ed6a13f90c0915c GIT binary patch literal 509 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4u6ByT*@`3|I*lDyqr82-2SpV<%OaTa()76WMy zFm^kcZ3hx8D{xE)(qO#|GLJ6IVqjoo^>lFzk+^JnVSiR|B17B9``_m-NuMxfR?C!* zh07MrSo)3sr09zP_wDzlhX=fPF>UDu=?vANf(w(JrZ%)>D41|8J9+Zugm01epVrPx zINBDzitE|2b$6T`9`!DJFmdU=eKrTz=_*cb3=s8r9VQ%8yxBf7d%v=5Nxh^7ZP6=ia-yr`GWA z@1JRG_RM@X%BIyHqImbIN_g6wl?zIFvhME$`)4a}gbAnqdolZft=%U7gPsvQH z#I2z#?2;8wgCxj?;QX|b^2DN42FH~Aq*MjZ+{Ezopr E0Crl)MgRZ+ literal 0 HcmV?d00001 diff --git a/cps/static/img/loading.gif b/cps/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b33f7e54f4e55b6b8774d86d96895db9af044b4 GIT binary patch literal 1849 zcma*odr(tX9tZI2z31lM+(&YVk%mZ}5P~KlG2s=WSbGzm0!x7^P##Mnh7t-jP!X0Q zk_SQ}Po-L1tlDK;6l?(>v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw literal 0 HcmV?d00001 diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index f8acb8fc..be14ff1a 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -1,13 +1,19 @@ {% extends "layout.html" %} -{% macro user_table_row(parameter, edit_text, show_text, validate) -%} - {%- endmacro %} @@ -31,18 +37,47 @@ {%- endmacro %} -{% macro user_select_row(parameter, url, show_text, validate) -%} - +{%- endmacro %} + +{% macro user_select_translations(parameter, url, show_text, validate) -%} + - {%- endmacro %} @@ -66,13 +101,13 @@ {{ user_table_row('nickname', _('Enter Username'), _('Username'), true) }} {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} - {{ user_select_row('locale', url_for('admin.table_get_locale'), _('Locale'), true) }} - {{ user_select_row('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }} {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} - {{ user_table_row('denied_tags', _("Enter Users's Locale"), _("Denied Tags"), true) }} - {{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), true) }} - {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), true) }} - {{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), true) }} + {{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }} + {{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }} + {{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), true, true) }} + {{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), true, true) }} + {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), true, true) }} + {{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), true, true) }} {{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}} {{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}} {{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}} @@ -95,7 +130,7 @@ {{ user_checkbox_row("sidebar_view", "sidebar_archived", _('Show archived books'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_download", _('Show Downloaded Books'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_list", _('Show Books List'), visiblility, sidebar_settings)}} - +

{{_('Admin')}}

{{_('Upload')}}

{{_('Download')}}

{{_('Edit')}}

{{_('Change Password')}}

{{_('Edit Public Shelfs')}}

{{_('Delete')}}

{{_('View')}}

{{_('Show Random Books in Detail View')}}

{{_('Show language selection')}}

{{_('Show series selection')}}

{{ _('Show category selection')}}

{{ _('Show random books')}}

{{_('Show author selection')}}

{{_('Show Top Rated Books')}}

{{_('Show random books')}}

{{_('Show publisher selection')}}

{{_('Show ratings selection')}}

{{_('Show file formats selection')}}

{{_('Show archived books')}}

{{_('Show Downloaded Books')}}

{{_('Show Books List')}}
{{_('Delete User')}}
+ {{ show_text }} + + {% if not button %} + data-sortable="true" + {% endif %} + {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> + {% if button %} +
{{edit_text}}

+ {% endif %} {{ show_text }}
+ {% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}> +
+ +

+ + {{ show_text }} +
+
+ +

{{ show_text }}
{{_('Delete User')}}
{{_('Delete User')}}

{{_('Delete User')}}
From b9c0c8d2dc1e2748665319b973d226e76830ca1a Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 20 Feb 2021 20:03:39 +0100 Subject: [PATCH 15/56] Update bootstrap table to 1.18.2 --- cps/static/{ => css}/img/clear.png | Bin cps/static/{ => css}/img/loading.gif | Bin cps/static/css/libs/bootstrap-table.min.css | 4 ++-- .../bootstrap-table/bootstrap-table-editable.min.js | 4 ++-- .../js/libs/bootstrap-table/bootstrap-table.min.js | 4 ++-- .../locale/bootstrap-table-af-ZA.min.js | 4 ++-- .../locale/bootstrap-table-ar-SA.min.js | 4 ++-- .../locale/bootstrap-table-bg-BG.min.js | 10 ++++++++++ .../locale/bootstrap-table-ca-ES.min.js | 4 ++-- .../locale/bootstrap-table-cs-CZ.min.js | 4 ++-- .../locale/bootstrap-table-da-DK.min.js | 4 ++-- .../locale/bootstrap-table-de-DE.min.js | 4 ++-- .../locale/bootstrap-table-el-GR.min.js | 4 ++-- .../locale/bootstrap-table-en-US.min.js | 4 ++-- .../locale/bootstrap-table-es-AR.min.js | 4 ++-- .../locale/bootstrap-table-es-CL.min.js | 4 ++-- .../locale/bootstrap-table-es-CR.min.js | 4 ++-- .../locale/bootstrap-table-es-ES.min.js | 4 ++-- .../locale/bootstrap-table-es-MX.min.js | 4 ++-- .../locale/bootstrap-table-es-NI.min.js | 4 ++-- .../locale/bootstrap-table-es-SP.min.js | 4 ++-- .../locale/bootstrap-table-et-EE.min.js | 4 ++-- .../locale/bootstrap-table-eu-EU.min.js | 4 ++-- .../locale/bootstrap-table-fa-IR.min.js | 4 ++-- .../locale/bootstrap-table-fi-FI.min.js | 4 ++-- .../locale/bootstrap-table-fr-BE.min.js | 4 ++-- .../locale/bootstrap-table-fr-CH.min.js | 4 ++-- .../locale/bootstrap-table-fr-FR.min.js | 4 ++-- .../locale/bootstrap-table-fr-LU.min.js | 4 ++-- .../locale/bootstrap-table-he-IL.min.js | 4 ++-- .../locale/bootstrap-table-hr-HR.min.js | 4 ++-- .../locale/bootstrap-table-hu-HU.min.js | 4 ++-- .../locale/bootstrap-table-id-ID.min.js | 4 ++-- .../locale/bootstrap-table-it-IT.min.js | 4 ++-- .../locale/bootstrap-table-ja-JP.min.js | 4 ++-- .../locale/bootstrap-table-ka-GE.min.js | 4 ++-- .../locale/bootstrap-table-ko-KR.min.js | 4 ++-- .../locale/bootstrap-table-ms-MY.min.js | 4 ++-- .../locale/bootstrap-table-nb-NO.min.js | 4 ++-- .../locale/bootstrap-table-nl-BE.min.js | 4 ++-- .../locale/bootstrap-table-nl-NL.min.js | 4 ++-- .../locale/bootstrap-table-pl-PL.min.js | 4 ++-- .../locale/bootstrap-table-pt-BR.min.js | 4 ++-- .../locale/bootstrap-table-pt-PT.min.js | 4 ++-- .../locale/bootstrap-table-ro-RO.min.js | 4 ++-- .../locale/bootstrap-table-ru-RU.min.js | 4 ++-- .../locale/bootstrap-table-sk-SK.min.js | 4 ++-- .../locale/bootstrap-table-sr-Cyrl-RS.min.js | 4 ++-- .../locale/bootstrap-table-sr-Latn-RS.min.js | 4 ++-- .../locale/bootstrap-table-sv-SE.min.js | 4 ++-- .../locale/bootstrap-table-th-TH.min.js | 4 ++-- .../locale/bootstrap-table-tr-TR.min.js | 4 ++-- .../locale/bootstrap-table-uk-UA.min.js | 4 ++-- .../locale/bootstrap-table-ur-PK.min.js | 4 ++-- .../locale/bootstrap-table-uz-Latn-UZ.min.js | 4 ++-- .../locale/bootstrap-table-vi-VN.min.js | 4 ++-- .../locale/bootstrap-table-zh-CN.min.js | 4 ++-- .../locale/bootstrap-table-zh-TW.min.js | 4 ++-- 58 files changed, 120 insertions(+), 110 deletions(-) rename cps/static/{ => css}/img/clear.png (100%) rename cps/static/{ => css}/img/loading.gif (100%) create mode 100644 cps/static/js/libs/bootstrap-table/locale/bootstrap-table-bg-BG.min.js diff --git a/cps/static/img/clear.png b/cps/static/css/img/clear.png similarity index 100% rename from cps/static/img/clear.png rename to cps/static/css/img/clear.png diff --git a/cps/static/img/loading.gif b/cps/static/css/img/loading.gif similarity index 100% rename from cps/static/img/loading.gif rename to cps/static/css/img/loading.gif diff --git a/cps/static/css/libs/bootstrap-table.min.css b/cps/static/css/libs/bootstrap-table.min.css index 72a8f74f..8afaf62b 100644 --- a/cps/static/css/libs/bootstrap-table.min.css +++ b/cps/static/css/libs/bootstrap-table.min.css @@ -1,10 +1,10 @@ /** * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) * - * @version v1.16.0 + * @version v1.18.2 * @homepage https://bootstrap-table.com * @author wenzhixin (http://wenzhixin.net.cn/) * @license MIT */ -.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table thead th .both{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC")}.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.3rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:none;justify-content:center;position:absolute;bottom:0;width:100%;z-index:1000}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{font-size:2rem;margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:LOADING;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination a{padding:6px 12px;line-height:1.428571429}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:'\2B05'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:'\27A1'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:calc(100vh);overflow-y:scroll}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes LOADING{0%{opacity:0}50%{opacity:1}to{opacity:0}} \ No newline at end of file +.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table thead th .both{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC")}.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value{width:100%}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.3rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:flex;justify-content:center;position:absolute;bottom:0;width:100%;z-index:1000;transition:visibility 0s,opacity .15s ease-in-out;opacity:0;visibility:hidden}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open{visibility:visible;opacity:1}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:LOADING;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:'\2B05'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:'\27A1'}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:calc(100vh);overflow-y:scroll}.bootstrap-table.bootstrap4 .pagination-lg .page-link,.bootstrap-table.bootstrap5 .pagination-lg .page-link{padding:.5rem 1rem}.bootstrap-table.bootstrap5 .float-left{float:left}.bootstrap-table.bootstrap5 .float-right{float:right}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes LOADING{0%{opacity:0}50%{opacity:1}to{opacity:0}} \ No newline at end of file diff --git a/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js b/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js index cd411037..b14b30da 100644 --- a/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js +++ b/cps/static/js/libs/bootstrap-table/bootstrap-table-editable.min.js @@ -1,10 +1,10 @@ /** * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) * - * @version v1.16.0 + * @version v1.18.2 * @homepage https://bootstrap-table.com * @author wenzhixin (http://wenzhixin.net.cn/) * @license MIT */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):e((t=t||self).jQuery)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(t,e){return t(e={exports:{}},e.exports),e.exports}var r=function(t){return t&&t.Math==Math&&t},o=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||Function("return this")(),i=function(t){try{return!!t()}catch(t){return!0}},a=!i((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})),c={}.propertyIsEnumerable,u=Object.getOwnPropertyDescriptor,f={f:u&&!c.call({1:2},1)?function(t){var e=u(this,t);return!!e&&e.enumerable}:c},l=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},s={}.toString,d=function(t){return s.call(t).slice(8,-1)},p="".split,h=i((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==d(t)?p.call(t,""):Object(t)}:Object,v=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},y=function(t){return h(v(t))},g=function(t){return"object"==typeof t?null!==t:"function"==typeof t},b=function(t,e){if(!g(t))return t;var n,r;if(e&&"function"==typeof(n=t.toString)&&!g(r=n.call(t)))return r;if("function"==typeof(n=t.valueOf)&&!g(r=n.call(t)))return r;if(!e&&"function"==typeof(n=t.toString)&&!g(r=n.call(t)))return r;throw TypeError("Can't convert object to primitive value")},m={}.hasOwnProperty,x=function(t,e){return m.call(t,e)},O=o.document,w=g(O)&&g(O.createElement),E=function(t){return w?O.createElement(t):{}},j=!a&&!i((function(){return 7!=Object.defineProperty(E("div"),"a",{get:function(){return 7}}).a})),S=Object.getOwnPropertyDescriptor,T={f:a?S:function(t,e){if(t=y(t),e=b(e,!0),j)try{return S(t,e)}catch(t){}if(x(t,e))return l(!f.f.call(t,e),t[e])}},P=function(t){if(!g(t))throw TypeError(String(t)+" is not an object");return t},A=Object.defineProperty,_={f:a?A:function(t,e,n){if(P(t),e=b(e,!0),P(n),j)try{return A(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},I=a?function(t,e,n){return _.f(t,e,l(1,n))}:function(t,e,n){return t[e]=n,t},R=function(t,e){try{I(o,t,e)}catch(n){o[t]=e}return e},C=o["__core-js_shared__"]||R("__core-js_shared__",{}),k=Function.toString;"function"!=typeof C.inspectSource&&(C.inspectSource=function(t){return k.call(t)});var M,$,F,D=C.inspectSource,N=o.WeakMap,q="function"==typeof N&&/native code/.test(D(N)),B=n((function(t){(t.exports=function(t,e){return C[t]||(C[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.6.0",mode:"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})})),L=0,K=Math.random(),V=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++L+K).toString(36)},U=B("keys"),W=function(t){return U[t]||(U[t]=V(t))},z={},Y=o.WeakMap;if(q){var G=new Y,H=G.get,Q=G.has,X=G.set;M=function(t,e){return X.call(G,t,e),e},$=function(t){return H.call(G,t)||{}},F=function(t){return Q.call(G,t)}}else{var Z=W("state");z[Z]=!0,M=function(t,e){return I(t,Z,e),e},$=function(t){return x(t,Z)?t[Z]:{}},F=function(t){return x(t,Z)}}var J,tt,et={set:M,get:$,has:F,enforce:function(t){return F(t)?$(t):M(t,{})},getterFor:function(t){return function(e){var n;if(!g(e)||(n=$(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}}},nt=n((function(t){var e=et.get,n=et.enforce,r=String(String).split("String");(t.exports=function(t,e,i,a){var c=!!a&&!!a.unsafe,u=!!a&&!!a.enumerable,f=!!a&&!!a.noTargetGet;"function"==typeof i&&("string"!=typeof e||x(i,"name")||I(i,"name",e),n(i).source=r.join("string"==typeof e?e:"")),t!==o?(c?!f&&t[e]&&(u=!0):delete t[e],u?t[e]=i:I(t,e,i)):u?t[e]=i:R(e,i)})(Function.prototype,"toString",(function(){return"function"==typeof this&&e(this).source||D(this)}))})),rt=o,ot=function(t){return"function"==typeof t?t:void 0},it=function(t,e){return arguments.length<2?ot(rt[t])||ot(o[t]):rt[t]&&rt[t][e]||o[t]&&o[t][e]},at=Math.ceil,ct=Math.floor,ut=function(t){return isNaN(t=+t)?0:(t>0?ct:at)(t)},ft=Math.min,lt=function(t){return t>0?ft(ut(t),9007199254740991):0},st=Math.max,dt=Math.min,pt=function(t){return function(e,n,r){var o,i=y(e),a=lt(i.length),c=function(t,e){var n=ut(t);return n<0?st(n+e,0):dt(n,e)}(r,a);if(t&&n!=n){for(;a>c;)if((o=i[c++])!=o)return!0}else for(;a>c;c++)if((t||c in i)&&i[c]===n)return t||c||0;return!t&&-1}},ht={includes:pt(!0),indexOf:pt(!1)},vt=ht.indexOf,yt=function(t,e){var n,r=y(t),o=0,i=[];for(n in r)!x(z,n)&&x(r,n)&&i.push(n);for(;e.length>o;)x(r,n=e[o++])&&(~vt(i,n)||i.push(n));return i},gt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],bt=gt.concat("length","prototype"),mt={f:Object.getOwnPropertyNames||function(t){return yt(t,bt)}},xt={f:Object.getOwnPropertySymbols},Ot=it("Reflect","ownKeys")||function(t){var e=mt.f(P(t)),n=xt.f;return n?e.concat(n(t)):e},wt=function(t,e){for(var n=Ot(e),r=_.f,o=T.f,i=0;i=74)&&(J=Vt.match(/Chrome\/(\d+)/))&&(tt=J[1]);var Yt,Gt=tt&&+tt,Ht=Bt("species"),Qt=Bt("isConcatSpreadable"),Xt=Gt>=51||!i((function(){var t=[];return t[Qt]=!1,t.concat()[0]!==t})),Zt=(Yt="concat",Gt>=51||!i((function(){var t=[];return(t.constructor={})[Ht]=function(){return{foo:1}},1!==t[Yt](Boolean).foo}))),Jt=function(t){if(!g(t))return!1;var e=t[Qt];return void 0!==e?!!e:Ct(t)};Rt({target:"Array",proto:!0,forced:!Xt||!Zt},{concat:function(t){var e,n,r,o,i,a=kt(this),c=Kt(a,0),u=0;for(e=-1,r=arguments.length;e9007199254740991)throw TypeError("Maximum allowed index exceeded");for(n=0;n=9007199254740991)throw TypeError("Maximum allowed index exceeded");Mt(c,u++,i)}return c.length=u,c}});var te,ee=function(t,e,n){if(function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function")}(t),void 0===e)return t;switch(n){case 0:return function(){return t.call(e)};case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}},ne=[].push,re=function(t){var e=1==t,n=2==t,r=3==t,o=4==t,i=6==t,a=5==t||i;return function(c,u,f,l){for(var s,d,p=kt(c),v=h(p),y=ee(u,f,3),g=lt(v.length),b=0,m=l||Kt,x=e?m(c,g):n?m(c,0):void 0;g>b;b++)if((a||b in v)&&(d=y(s=v[b],b,p),t))if(e)x[b]=d;else if(d)switch(t){case 3:return!0;case 5:return s;case 6:return b;case 2:ne.call(x,s)}else if(o)return!1;return i?-1:r||o?o:x}},oe={forEach:re(0),map:re(1),filter:re(2),some:re(3),every:re(4),find:re(5),findIndex:re(6)},ie=Object.keys||function(t){return yt(t,gt)},ae=a?Object.defineProperties:function(t,e){P(t);for(var n,r=ie(e),o=r.length,i=0;o>i;)_.f(t,n=r[i++],e[n]);return t},ce=it("document","documentElement"),ue=W("IE_PROTO"),fe=function(){},le=function(t){return" From b070ba142f324ac42df9931bc918a08e29551fd3 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 16 Mar 2021 15:40:58 +0100 Subject: [PATCH 24/56] Selects are working in user management with generic confirm dialog --- cps/admin.py | 8 +++ cps/static/js/main.js | 18 ++--- cps/static/js/table.js | 109 ++++++++++++++++--------------- cps/templates/layout.html | 2 +- cps/templates/modal_dialogs.html | 26 ++++++-- cps/templates/user_table.html | 3 +- 6 files changed, 100 insertions(+), 66 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 386e2125..78cfebf1 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -440,6 +440,14 @@ def load_dialogtexts(element_id): texts["main"] = _('Do you really want to delete this user?') elif element_id == "delete_shelf": texts["main"] = _('Are you sure you want to delete this shelf?') + elif element_id == "select_locale": + texts["main"] = _('Are you sure you want to change locales of selected user(s)?') + elif element_id == "select_default_language": + texts["main"] = _('Are you sure you want to change visible book languages for selected user(s)?') + elif element_id == "role": + texts["main"] = _('Are you sure you want to change the selected role for the selected user(s)?') + elif element_id == "sidebar_view": + texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?') return json.dumps(texts) diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 6f7e7b56..834b9b30 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -114,26 +114,23 @@ $(document).ready(function() { } }); -function confirmDialog(id, dataValue, yesFn, noFn) { - var $confirm = $("#GeneralDeleteModal"); - // var dataValue= e.data('value'); // target.data('value'); +function confirmDialog(id, dialogid, dataValue, yesFn, noFn) { + var $confirm = $("#" + dialogid); $confirm.modal('show'); $.ajax({ method:"get", dataType: "json", url: getPath() + "/ajax/loaddialogtexts/" + id, success: function success(data) { - $("#header").html(data.header); - $("#text").html(data.main); + $("#header-"+ dialogid).html(data.header); + $("#text-"+ dialogid).html(data.main); } }); - - - $("#btnConfirmYes").off('click').click(function () { + $("#btnConfirmYes-"+ dialogid).off('click').click(function () { yesFn(dataValue); $confirm.modal("hide"); }); - $("#btnConfirmNo").off('click').click(function () { + $("#btnConfirmNo-"+ dialogid).off('click').click(function () { if (typeof noFn !== 'undefined') { noFn(dataValue); } @@ -483,6 +480,7 @@ $(function() { $("#config_delete_kobo_token").click(function() { confirmDialog( $(this).attr('id'), + "GeneralDeleteModal", $(this).data('value'), function (value) { $.ajax({ @@ -511,6 +509,7 @@ $(function() { $("#btndeluser").click(function() { confirmDialog( $(this).attr('id'), + "GeneralDeleteModal", $(this).data('value'), function(value){ var subform = $('#user_submit').closest("form"); @@ -529,6 +528,7 @@ $(function() { $("#delete_shelf").click(function() { confirmDialog( $(this).attr('id'), + "GeneralDeleteModal", $(this).data('value'), function(value){ window.location.href = window.location.pathname + "/../../shelf/delete/" + value diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 31eff2f0..1512c5f9 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -237,12 +237,12 @@ $(function() { } $("#domain-allow-table").on("click-cell.bs.table", function (field, value, row, $element) { if (value === 2) { - confirmDialog("btndeletedomain", $element.id, domainHandle); + confirmDialog("btndeletedomain", "GeneralDeleteModal", $element.id, domainHandle); } }); $("#domain-deny-table").on("click-cell.bs.table", function (field, value, row, $element) { if (value === 2) { - confirmDialog("btndeletedomain", $element.id, domainHandle); + confirmDialog("btndeletedomain", "GeneralDeleteModal", $element.id, domainHandle); } }); @@ -508,7 +508,7 @@ $(function() { $("#user-table").on("click-cell.bs.table", function (field, value, row, $element) { if (value === "denied_column_value") { - ConfirmDialog("btndeluser", $element.id, user_handle); + ConfirmDialog("btndeluser", "GeneralDeleteModal", $element.id, user_handle); } }); @@ -621,62 +621,69 @@ function checkboxChange(checkbox, userId, field, field_index) { } }); } +function deactivateHeaderButtons(e) { + $("#user_delete_selection").addClass("disabled"); + $("#user_delete_selection").attr("aria-disabled", true); + $(".check_head").attr("aria-disabled", true); + $(".check_head").attr("disabled", true); + $(".check_head").prop('checked', false); + $(".button_head").attr("aria-disabled", true); + $(".button_head").addClass("disabled"); + $(".header_select").attr("disabled", true); +} function selectHeader(element, field) { - var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); - $.ajax({ - method:"post", - url: window.location.pathname + "/../../ajax/editlistusers/" + field, - data: {"pk":result, "value": element.value}, - success:function() { + if (element.value !== "None") { + confirmDialog(element.id, "GeneralChangeModal", 0, function () { + var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); $.ajax({ - method:"get", - url: window.location.pathname + "/../../ajax/listusers", - async: true, - timeout: 900, - success:function(data) { - $("#user-table").bootstrapTable("load", data); - $("#user_delete_selection").addClass("disabled"); - $("#user_delete_selection").attr("aria-disabled", true); - $(".check_head").attr("aria-disabled", true); - $(".check_head").attr("disabled", true); - $(".check_head").prop('checked', false); - $(".button_head").attr("aria-disabled", true); - $(".button_head").addClass("disabled"); - $(".header_select").attr("disabled", true); + method: "post", + url: window.location.pathname + "/../../ajax/editlistusers/" + field, + data: {"pk": result, "value": element.value}, + success: function () { + $.ajax({ + method: "get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success: function (data) { + $("#user-table").bootstrapTable("load", data); + deactivateHeaderButtons(); + } + }); } }); - } - }); - - console.log("test"); + }); + } } function checkboxHeader(CheckboxState, field, field_index) { - var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); - $.ajax({ - method:"post", - url: window.location.pathname + "/../../ajax/editlistusers/" + field, - data: {"pk":result, "field_index":field_index, "value": CheckboxState}, - success:function() { - $.ajax({ - method:"get", - url: window.location.pathname + "/../../ajax/listusers", - async: true, - timeout: 900, - success:function(data) { - $("#user-table").bootstrapTable("load", data); - $("#user_delete_selection").addClass("disabled"); - $("#user_delete_selection").attr("aria-disabled", true); - $(".check_head").attr("aria-disabled", true); - $(".check_head").attr("disabled", true); - $(".check_head").prop('checked', false); - $(".button_head").attr("aria-disabled", true); - $(".button_head").addClass("disabled"); - $(".header_select").attr("disabled", true); - } - }); - } + confirmDialog(field, "GeneralChangeModal", 0, function() { + var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); + $.ajax({ + method: "post", + url: window.location.pathname + "/../../ajax/editlistusers/" + field, + data: {"pk": result, "field_index": field_index, "value": CheckboxState}, + success: function () { + $.ajax({ + method: "get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success: function (data) { + $("#user-table").bootstrapTable("load", data); + $("#user_delete_selection").addClass("disabled"); + $("#user_delete_selection").attr("aria-disabled", true); + $(".check_head").attr("aria-disabled", true); + $(".check_head").attr("disabled", true); + $(".check_head").prop('checked', false); + $(".button_head").attr("aria-disabled", true); + $(".button_head").addClass("disabled"); + $(".header_select").attr("disabled", true); + } + }); + } + }); }); } diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 643459cd..318140fa 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -1,4 +1,4 @@ -{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal %} +{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %} diff --git a/cps/templates/modal_dialogs.html b/cps/templates/modal_dialogs.html index 94e76811..e134236e 100644 --- a/cps/templates/modal_dialogs.html +++ b/cps/templates/modal_dialogs.html @@ -108,13 +108,31 @@ +
+{% endmacro %} + +{% macro change_confirm_modal() %} +