From ae97e87506421716e5a2d42174198911b9af05d7 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 10 Apr 2021 11:32:11 +0200 Subject: [PATCH 1/3] Delete user working from user table (#1938) Comment in helper --- cps/admin.py | 48 ++++++++++++++++++++++++-------------- cps/helper.py | 2 +- cps/static/js/table.js | 52 ++++++++++++++++++++++++++---------------- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 4038977e..76092993 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -31,7 +31,7 @@ from datetime import datetime, timedelta from babel import Locale as LC from babel.dates import format_datetime -from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g +from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response from flask_login import login_required, current_user, logout_user, confirm_login from flask_babel import gettext as _ from sqlalchemy import and_ @@ -277,12 +277,19 @@ def list_users(): response.headers["Content-Type"] = "application/json; charset=utf-8" return response -@admi.route("/ajax/deleteuser") +@admi.route("/ajax/deleteuser", methods=['POST']) @login_required @admin_required def delete_user(): - # ToDo User delete check also not last one - return "" + user_id = request.values.get('userid', -1) + content = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).one_or_none() + try: + message = _delete_user(content) + return Response(json.dumps({'type': "success", 'message': message}), mimetype='application/json') + except Exception as ex: + return Response(json.dumps({'type': "danger", 'message':str(ex)}), mimetype='application/json') + log.error("User not found") + return Response(json.dumps({'type': "danger", 'message':_("User not found")}), mimetype='application/json') @admi.route("/ajax/getlocale") @login_required @@ -1194,22 +1201,29 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): ub.session.rollback() flash(_(u"Settings DB is not Writeable"), category="error") +def _delete_user(content): + if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, + ub.User.id != content.id).count(): + if content.name != "Guest": + ub.session.query(ub.User).filter(ub.User.id == content.id).delete() + ub.session_commit() + log.info(u"User {} deleted".format(content.name)) + return(_(u"User '%(nick)s' deleted", nick=content.name)) + else: + log.warning(_(u"Can't delete Guest User")) + raise Exception(_(u"Can't delete Guest User")) + else: + log.warning(u"No admin user remaining, can't delete user") + raise Exception(_(u"No admin user remaining, can't delete user")) + def _handle_edit_user(to_save, content, languages, translations, kobo_support): if to_save.get("delete"): - if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, - ub.User.id != content.id).count(): - if content.name != "Guest": - ub.session.query(ub.User).filter(ub.User.id == content.id).delete() - ub.session_commit() - flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") - return redirect(url_for('admin.admin')) - else: - flash(_(u"Can't delete Guest User"), category="error") - return redirect(url_for('admin.admin')) - else: - flash(_(u"No admin user remaining, can't delete user", nick=content.name), category="error") - return redirect(url_for('admin.admin')) + try: + flash(_delete_user(content), category="success") + except Exception as ex: + flash(str(ex), category="error") + return redirect(url_for('admin.admin')) else: if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, ub.User.id != content.id).count() and 'admin_role' not in to_save: diff --git a/cps/helper.py b/cps/helper.py index f1c32ea0..29163685 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -795,8 +795,8 @@ def tags_filters(): # checks if domain is in database (including wildcards) # example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name; # from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/ +# in all calls the email address is checked for validity def check_valid_domain(domain_text): - # domain_text = domain_text.split('@', 1)[-1].lower() sql = "SELECT * FROM registration WHERE (:domain LIKE domain and allow = 1);" result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all() if not len(result): diff --git a/cps/static/js/table.js b/cps/static/js/table.js index a0503976..96d28c85 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -525,7 +525,6 @@ $(function() { }); } - $("#user-table").on("click-cell.bs.table", function (field, value, row, $element) { if (value === "denied_column_value") { ConfirmDialog("btndeluser", "GeneralDeleteModal", $element.id, user_handle); @@ -563,7 +562,6 @@ $(function() { $(".button_head").removeClass("disabled"); $(".header_select").removeAttr("disabled"); } - }); }); @@ -603,7 +601,7 @@ function EbookActions (value, row) { /* Function for deleting books */ function UserActions (value, row) { return [ - "
", + "
", "", "
" ].join(""); @@ -715,26 +713,40 @@ function checkboxHeader(CheckboxState, field, field_index) { }); } -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); +function deleteUser(a,b){ + confirmDialog( + "btndeluser", + "GeneralDeleteModal", + 0, + function() { + $.ajax({ + method:"post", + url: window.location.pathname + "/../../ajax/deleteuser", + data: {"userid":b}, + success:function(data) { + $("#flash_success").remove(); + $("#flash_danger").remove(); + if (!jQuery.isEmptyObject(data)) { + $( ".navbar" ).after( '
' + + '
'+data.message+'
' + + '
'); + } + $.ajax({ + method: "get", + url: window.location.pathname + "/../../ajax/listusers", + async: true, + timeout: 900, + success: function (data) { + $("#user-table").bootstrapTable("load", data); + } + }); + } + }); } - }); + ); } -function checkboxSorter(a, b, c, d) -{ - return a - b +function user_handle (userId) { } function test(){ From b6177b27f4d49a7a7e2c7e860f06c2ee3ad0d368 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 11 Apr 2021 19:59:20 +0200 Subject: [PATCH 2/3] Sorting of users in table according to selection possible --- cps/admin.py | 36 ++++++++++++++++++++++++---------- cps/db.py | 15 ++++++++------ cps/static/js/table.js | 32 ++++++++++-------------------- cps/templates/user_table.html | 2 +- cps/web.py | 37 +++++++++++++++++++++++++++-------- 5 files changed, 75 insertions(+), 47 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 76092993..935aee45 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -38,6 +38,7 @@ 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_, text +# from sqlalchemy.func import field from . import constants, logger, helper, services from .cli import filepicker @@ -241,29 +242,44 @@ def edit_user_table(): @login_required @admin_required def list_users(): - off = request.args.get("offset") or 0 - limit = request.args.get("limit") or 10 + off = int(request.args.get("offset") or 0) + limit = int(request.args.get("limit") or 10) search = request.args.get("search") - sort = request.args.get("sort") + sort = request.args.get("sort", "state") order = request.args.get("order") - if sort and order: + state = None + if sort != "state" and order: order = text(sort + " " + order) else: order = ub.User.name.desc() + if sort == "state": + state = json.loads(request.args.get("state")) 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() + + total_count = filtered_count = all_user.count() + if search: - users = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"), + all_user = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"), func.lower(ub.User.kindle_mail).ilike("%" + search + "%"), - func.lower(ub.User.email).ilike("%" + search + "%")))\ - .order_by(order).offset(off).limit(limit).all() - filtered_count = len(users) + func.lower(ub.User.email).ilike("%" + search + "%"))) + if state: + outcome = list() + userlist = {user.id:user for user in all_user.all()} + for entry in state: + outcome.append(userlist[entry]) + del userlist[entry] + for entry in userlist: + outcome.append(userlist[entry]) + if request.args.get("order", "").lower() == "asc": + outcome.reverse() + users = outcome[off:off + limit] else: users = all_user.order_by(order).offset(off).limit(limit).all() - filtered_count = total_count + if search: + filtered_count = len(users) for user in users: if user.default_language == "all": diff --git a/cps/db.py b/cps/db.py index b875ded7..51aeeca1 100644 --- a/cps/db.py +++ b/cps/db.py @@ -689,23 +689,26 @@ class CalibreDB(): return self.session.query(Books) \ .filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first() - # read search results from calibre-database and return it (function is used for feed and simple search - def get_search_results(self, term, offset=None, order=None, limit=None): - order = order or [Books.sort] - pagination = None + def search_query(self, term): term.strip().lower() self.session.connection().connection.connection.create_function("lower", 1, lcase) q = list() authorterms = re.split("[, ]+", term) for authorterm in authorterms: q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%"))) - result = self.session.query(Books).filter(self.common_filters(True)).filter( + return self.session.query(Books).filter(self.common_filters(True)).filter( or_(Books.tags.any(func.lower(Tags.name).ilike("%" + term + "%")), Books.series.any(func.lower(Series.name).ilike("%" + term + "%")), Books.authors.any(and_(*q)), Books.publishers.any(func.lower(Publishers.name).ilike("%" + term + "%")), func.lower(Books.title).ilike("%" + term + "%") - )).order_by(*order).all() + )) + + # read search results from calibre-database and return it (function is used for feed and simple search + def get_search_results(self, term, offset=None, order=None, limit=None): + order = order or [Books.sort] + pagination = None + result = self.search_query(term).order_by(*order).all() result_count = len(result) if offset != None and limit != None: offset = int(offset) diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 96d28c85..59b70626 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -422,6 +422,7 @@ $(function() { $("#user-table").bootstrapTable({ sidePagination: "server", + queryParams: queryParams, pagination: true, paginationLoop: false, paginationDetailHAlign: " hidden", @@ -462,28 +463,10 @@ $(function() { $("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $(".user-remove[data-pk='"+guest.data("pk")+"']").prop("disabled", true); }, - - // 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) { + onSort: function(a, b) { + console.log("huh"); + }, + onColumnSwitch: function () { var visible = $("#user-table").bootstrapTable("getVisibleColumns"); var hidden = $("#user-table").bootstrapTable("getHiddenColumns"); var st = ""; @@ -746,6 +729,11 @@ function deleteUser(a,b){ ); } +function queryParams(params) +{ + params.state = JSON.stringify(selections); + return params; +} function user_handle (userId) { } diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index b8a9d2c2..5f33cb99 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -144,7 +144,7 @@ {{ restrict_modal() }} {% endblock %} {% block js %} - + diff --git a/cps/web.py b/cps/web.py index 658ff735..a8c7b71a 100644 --- a/cps/web.py +++ b/cps/web.py @@ -753,21 +753,42 @@ def books_table(): @web.route("/ajax/listbooks") @login_required def list_books(): - off = request.args.get("offset") or 0 - limit = request.args.get("limit") or config.config_books_per_page - sort = request.args.get("sort") + off = int(request.args.get("offset") or 0) + limit = int(request.args.get("limit") or config.config_books_per_page) + search = request.args.get("search") + sort = request.args.get("sort", "state") order = request.args.get("order") - if sort and order: + state = None + if sort != "state" and order: order = [text(sort + " " + order)] else: order = [db.Books.timestamp.desc()] - search = request.args.get("search") - total_count = calibre_db.session.query(db.Books).count() - if search: + if sort == "state": + state = json.loads(request.args.get("state")) + + total_count = filtered_count = calibre_db.session.query(db.Books).count() + + if state: + outcome = list() + if search: + books = calibre_db.search_query(search) + filtered_count = len(books) + else: + books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all() + booklist = {book.id: book for book in books} + for entry in state: + outcome.append(booklist[entry]) + del booklist[entry] + for entry in booklist: + outcome.append(booklist[entry]) + if request.args.get("order", "").lower() == "asc": + outcome.reverse() + entries = outcome[off:off + limit] + elif search: entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit) else: entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order) - filtered_count = total_count + for entry in entries: for index in range(0, len(entry.languages)): try: From 04971f86724da18e3e2a8ed126058bdc9f68048b Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 11 Apr 2021 20:01:40 +0200 Subject: [PATCH 3/3] Bugfix wrong js file in user list --- cps/templates/user_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index 5f33cb99..b8a9d2c2 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -144,7 +144,7 @@ {{ restrict_modal() }} {% endblock %} {% block js %} - +