From 08527ae3ce62e96acabb85d64691724ddb8de841 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 9 Dec 2024 17:08:58 +0100 Subject: [PATCH] Update mass edit Refactored delete User function Updated testresults --- cps/admin.py | 23 +- cps/editbooks.py | 421 ++++++++++++------------ cps/helper.py | 18 +- cps/kobo_sync_status.py | 2 +- cps/static/js/main.js | 8 +- cps/static/js/table.js | 210 ++++++++---- cps/templates/book_table.html | 43 +-- cps/templates/user_table.html | 4 +- pyproject.toml | 4 +- test/Calibre-Web TestSummary_Linux.html | 112 +++---- 10 files changed, 473 insertions(+), 372 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 71403e6c..3ac5eff9 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -40,7 +40,7 @@ from flask_babel import gettext as _ from flask_babel import get_locale, format_time, format_datetime, format_timedelta from sqlalchemy import and_ from sqlalchemy.orm.attributes import flag_modified -from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError +from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError, ArgumentError from sqlalchemy.sql.expression import func, or_, text from . import constants, logger, helper, services, cli_param @@ -386,13 +386,12 @@ def list_users(): @user_login_required @admin_required def delete_user(): - user_ids = request.form.to_dict(flat=False) - users = None + user_ids = request.get_json().get("userid") message = "" - if "userid[]" in user_ids: - users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids['userid[]'])).all() - elif "userid" in user_ids: - users = ub.session.query(ub.User).filter(ub.User.id == user_ids['userid'][0]).all() + try: + users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids)).all() + except (ArgumentError): + users = None count = 0 errors = list() success = list() @@ -408,10 +407,10 @@ def delete_user(): errors.append({'type': "danger", 'message': str(ex)}) if count == 1: - log.info("User {} deleted".format(user_ids)) + log.info("User {} deleted".format(user_ids[0])) success = [{'type': "success", 'message': message}] elif count > 1: - log.info("Users {} deleted".format(user_ids)) + log.info("Users {} deleted".format(", ".join([str(user_id) for user_id in user_ids]))) success = [{'type': "success", 'message': _("{} users deleted successfully").format(count)}] success.extend(errors) return make_response(jsonify(success)) @@ -618,6 +617,8 @@ def load_dialogtexts(element_id): texts["main"] = _('Do you really want to delete this domain?') elif element_id == "btndeluser": texts["main"] = _('Do you really want to delete this user?') + elif element_id == "btndelbook": + texts["main"] = _('Do you really want to delete this book?') elif element_id == "delete_shelf": texts["main"] = _('Are you sure you want to delete this shelf?') elif element_id == "select_locale": @@ -626,6 +627,10 @@ def load_dialogtexts(element_id): 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 == "archive_books": + texts["main"] = _('Are you sure you want to change the archive status for the selected book(s)?') + elif element_id == "read_books": + texts["main"] = _('Are you sure you want to change the read status for the selected book(s)?') elif element_id == "restrictions": texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?') elif element_id == "sidebar_view": diff --git a/cps/editbooks.py b/cps/editbooks.py index 6499cb3c..f82899f4 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -73,17 +73,18 @@ def edit_required(f): return inner -@editbook.route("/ajax/delete/", methods=["POST"]) +@editbook.route("/ajax/deletebook", methods=["POST"]) @user_login_required -def delete_book_from_details(book_id): - return delete_book_from_table(book_id, "", True) # , mimetype='application/json') +def delete_books_ajax(): + book_ids = request.get_json().get("bookid") + return check_delete_book(book_ids, "", True) @editbook.route("/delete/", defaults={'book_format': ""}, methods=["POST"]) @editbook.route("/delete//", methods=["POST"]) @user_login_required -def delete_book_ajax(book_id, book_format): - return delete_book_from_table(book_id, book_format, False, request.form.to_dict().get('location', "")) +def delete_book(book_id, book_format): + return check_delete_book(book_id, book_format, False, request.form.to_dict().get('location', "")) @editbook.route("/admin/book/", methods=['GET']) @@ -213,7 +214,7 @@ def table_get_custom_enum(c_id): @login_required_if_no_ano @edit_required def edit_list_book(param): - vals = request.form.to_dict() + vals = request.get_json() # form.to_dict(flat=False) return edit_book_param(param, vals) @editbook.route("/ajax/editselectedbooks", methods=['POST']) @@ -233,42 +234,39 @@ def edit_selected_books(): comments = d.get('comments') checkA = d.get('checkA') - if len(selections) != 0: - for book_id in selections: - vals = { - "pk": book_id, - "value": None, - "checkA": checkA, - } - if title: - vals['value'] = title - edit_book_param('title', vals) - if title_sort: - vals['value'] = title_sort - edit_book_param('sort', vals) - if author_sort: - vals['value'] = author_sort - edit_book_param('author_sort', vals) - if authors: - vals['value'] = authors - edit_book_param('authors', vals) - if categories: - vals['value'] = categories - edit_book_param('tags', vals) - if series: - vals['value'] = series - edit_book_param('series', vals) - if languages: - vals['value'] = languages - edit_book_param('languages', vals) - if publishers: - vals['value'] = publishers - edit_book_param('publishers', vals) - if comments: - vals['value'] = comments - edit_book_param('comments', vals) - return json.dumps({'success': True}) - return "" + vals = { + "pk": selections, + "value": None, + "checkA": checkA, + } + if title: + vals['value'] = title + edit_book_param('title', vals) + if title_sort: + vals['value'] = title_sort + edit_book_param('sort', vals) + if author_sort: + vals['value'] = author_sort + edit_book_param('author_sort', vals) + if authors: + vals['value'] = authors + edit_book_param('authors', vals) + if categories: + vals['value'] = categories + edit_book_param('tags', vals) + if series: + vals['value'] = series + edit_book_param('series', vals) + if languages: + vals['value'] = languages + edit_book_param('languages', vals) + if publishers: + vals['value'] = publishers + edit_book_param('publishers', vals) + if comments: + vals['value'] = comments + edit_book_param('comments', vals) + return json.dumps({'success': True}) # Separated from /editbooks so that /editselectedbooks can also use this # @@ -284,94 +282,96 @@ def edit_selected_books(): @login_required_if_no_ano @edit_required def edit_book_param(param, vals): - book = calibre_db.get_book(vals['pk']) - calibre_db.create_functions(config) - sort_param = "" - ret = "" - try: - if param == 'series_index': - edit_book_series_index(vals['value'], book) - ret = make_response(jsonify(success=True, newValue=book.series_index)) - elif param == 'tags': - edit_book_tags(vals['value'], book) - ret = make_response(jsonify(success=True, newValue=', '.join([tag.name for tag in book.tags]))) - elif param == 'series': - edit_book_series(vals['value'], book) - ret = make_response(jsonify(success=True, newValue=', '.join([serie.name for serie in book.series]))) - elif param == 'publishers': - edit_book_publisher(vals['value'], book) - ret = make_response(jsonify(success=True, - newValue=', '.join([publisher.name for publisher in book.publishers]))) - elif param == 'languages': - invalid = list() - edit_book_languages(vals['value'], book, invalid=invalid) - if invalid: - ret = make_response(jsonify(success=False, - msg='Invalid languages in request: {}'.format(','.join(invalid)))) - else: - lang_names = list() - for lang in book.languages: - lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code)) - ret = make_response(jsonify(success=True, newValue=', '.join(lang_names))) - elif param == 'author_sort': - book.author_sort = vals['value'] - ret = make_response(jsonify(success=True, newValue=book.author_sort)) - elif param == 'title': - sort_param = book.sort - if handle_title_on_edit(book, vals.get('value', "")): - rename_error = helper.update_dir_structure(book.id, config.get_book_path()) - if not rename_error: - ret = make_response(jsonify(success=True, newValue=book.title)) + elements = vals.get('pk',[]) + ret = {} + for elem in elements: + book = calibre_db.get_book(elem) + calibre_db.create_functions(config) + sort_param = "" + try: + if param == 'series_index': + edit_book_series_index(vals['value'], book) + ret = jsonify(success=True, newValue=book.series_index) + elif param == 'tags': + edit_book_tags(vals['value'], book) + ret = jsonify(success=True, newValue=', '.join([tag.name for tag in book.tags])) + elif param == 'series': + edit_book_series(vals['value'], book) + ret = jsonify(success=True, newValue=', '.join([serie.name for serie in book.series])) + elif param == 'publishers': + edit_book_publisher(vals['value'], book) + ret = jsonify(success=True, + newValue=', '.join([publisher.name for publisher in book.publishers])) + elif param == 'languages': + invalid = list() + edit_book_languages(vals['value'], book, invalid=invalid) + if invalid: + ret = jsonify(success=False, msg='Invalid languages in request: {}'.format(','.join(invalid))) else: - ret = make_response(jsonify(success=False, msg=rename_error)) - elif param == 'sort': - book.sort = vals['value'] - ret = make_response(jsonify(success=True,newValue=book.sort)) - elif param == 'comments': - edit_book_comments(vals['value'], book) - ret = make_response(jsonify(success=True, newValue=book.comments[0].text)) - elif param == 'authors': - input_authors, __ = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true") - rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0]) - if not rename_error: - ret = make_response(jsonify( - success=True, - newValue=' & '.join([author.replace('|', ',') for author in input_authors]))) + lang_names = list() + for lang in book.languages: + lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code)) + ret = jsonify(success=True, newValue=', '.join(lang_names)) + elif param == 'author_sort': + book.author_sort = vals['value'] + ret = jsonify(success=True, newValue=book.author_sort) + elif param == 'title': + sort_param = book.sort + if handle_title_on_edit(book, vals.get('value', "")): + rename_error = helper.update_dir_structure(book.id, config.get_book_path()) + if not rename_error: + ret = jsonify(success=True, newValue=book.title) + else: + ret = jsonify(success=False, msg=rename_error) + elif param == 'sort': + book.sort = vals['value'] + ret = jsonify(success=True,newValue=book.sort) + elif param == 'comments': + edit_book_comments(vals['value'], book) + ret = jsonify(success=True, newValue=book.comments[0].text) + elif param == 'authors': + input_authors, __ = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == True) + rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0]) + if not rename_error: + ret = jsonify( + success=True, + newValue=' & '.join([author.replace('|', ',') for author in input_authors])) + else: + ret = jsonify(success=False, msg=rename_error) + elif param == 'is_archived': + is_archived = change_archived_books(book.id, vals['value'] == "True", + message="Book {} archive bit set to: {}".format(book.id, + vals['value'])) + if is_archived: + kobo_sync_status.remove_synced_book(book.id) + continue + elif param == 'read_status': + error = helper.edit_book_read_status(book.id, vals['value'] == "True") + if error: + return error, 400 + continue + elif param.startswith("custom_column_"): + new_val = dict() + new_val[param] = vals['value'] + edit_single_cc_data(book.id, book, param[14:], new_val) + # ToDo: Very hacky find better solution + if vals['value'] in ["True", "False"]: + ret = {} + else: + ret = jsonify(success=True, newValue=vals['value']) else: - ret = make_response(jsonify(success=False, msg=rename_error)) - elif param == 'is_archived': - is_archived = change_archived_books(book.id, vals['value'] == "True", - message="Book {} archive bit set to: {}".format(book.id, vals['value'])) - if is_archived: - kobo_sync_status.remove_synced_book(book.id) - return "" - elif param == 'read_status': - ret = helper.edit_book_read_status(book.id, vals['value'] == "True") - if ret: - return ret, 400 - elif param.startswith("custom_column_"): - new_val = dict() - new_val[param] = vals['value'] - edit_single_cc_data(book.id, book, param[14:], new_val) - # ToDo: Very hacky find better solution - if vals['value'] in ["True", "False"]: - ret = "" - else: - ret = make_response(jsonify(success=True, newValue=vals['value'])) - else: - return _("Parameter not found"), 400 - book.last_modified = datetime.now(timezone.utc) + return _("Parameter not found"), 400 + book.last_modified = datetime.now(timezone.utc) - calibre_db.session.commit() - # revert change for sort if automatic fields link is deactivated - if param == 'title' and vals.get('checkT') == "false": - book.sort = sort_param calibre_db.session.commit() - except (OperationalError, IntegrityError, StaleDataError) as e: - calibre_db.session.rollback() - log.error_or_exception("Database error: {}".format(e)) - ret = make_response(jsonify(success=False, - msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e))) + # revert change for sort if automatic fields link is deactivated + if param == 'title' and vals.get('checkT') == False: + book.sort = sort_param + calibre_db.session.commit() + except (OperationalError, IntegrityError, StaleDataError, AttributeError) as e: + calibre_db.session.rollback() + log.error_or_exception("Database error: {}".format(e)) + ret = jsonify(success=False, msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e)) return ret @@ -433,16 +433,6 @@ def archive_selected_books(): return json.dumps({'success': True}) return "" -@editbook.route("/ajax/deleteselectedbooks", methods=['POST']) -@user_login_required -@edit_required -def delete_selected_books(): - vals = request.get_json().get('selections') - if vals: - for book_id in vals: - delete_book_from_table(book_id, "", True) - return json.dumps({'success': True}) - return "" @editbook.route("/ajax/readselectedbooks", methods=['POST']) @user_login_required @@ -498,7 +488,7 @@ def merge_list_book(): element.format, element.uncompressed_size, to_name)) - delete_book_from_table(from_book.id, "", True) + check_delete_book([from_book.id], "", True) return make_response(jsonify(success=True)) return "" @@ -968,86 +958,109 @@ def delete_whole_book(book_id, book): calibre_db.session.query(db.Books).filter(db.Books.id == book_id).delete() -def render_delete_book_result(book_format, json_response, warning, book_id, location=""): +def render_delete_book_result(book_format, book_id, location=""): if book_format: - if json_response: - return jsonify([warning, {"location": url_for("edit-book.show_edit_book", book_id=book_id), - "type": "success", - "format": book_format, - "message": _('Book Format Successfully Deleted')}]) - else: - flash(_('Book Format Successfully Deleted'), category="success") - return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) + flash(_('Book Format Successfully Deleted'), category="success") + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) else: - if json_response: - return jsonify([warning, {"location": get_redirect_location(location, "web.index"), - "type": "success", - "format": book_format, - "message": _('Book Successfully Deleted')}]) - else: - flash(_('Book Successfully Deleted'), category="success") - return redirect(get_redirect_location(location, "web.index")) + flash(_('Book Successfully Deleted'), category="success") + return redirect(get_redirect_location(location, "web.index")) -def delete_book_from_table(book_id, book_format, json_response, location=""): - warning = {} +def check_delete_book(book_id, book_format, json_response, location=""): if current_user.role_delete_books(): - book = calibre_db.get_book(book_id) - if book: - try: - result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper()) - if not result: - if json_response: - return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id), - "type": "danger", - "format": "", - "message": error}]) - else: - flash(error, category="error") - return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) - if error: - if json_response: - warning = {"location": url_for("edit-book.show_edit_book", book_id=book_id), - "type": "warning", - "format": "", - "message": error} - else: - flash(error, category="warning") - if not book_format: - delete_whole_book(book_id, book) - else: - calibre_db.session.query(db.Data).filter(db.Data.book == book.id).\ - filter(db.Data.format == book_format).delete() - if book_format.upper() in ['KEPUB', 'EPUB', 'EPUB3']: - kobo_sync_status.remove_synced_book(book.id, True) - calibre_db.session.commit() - except Exception as ex: - log.error_or_exception(ex) - calibre_db.session.rollback() - if json_response: - return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id), - "type": "danger", - "format": "", - "message": ex}]) - else: - flash(str(ex), category="error") - return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) - + if json_response: + # if json response is set, it's possible to delete more than one book, but never a format is deleted + res = list() + for b in book_id: + ret = delete_book_from_table(b) + if ret: + res.extend([ret]) + if len(res) == 0: + return [{"location": get_redirect_location(location, "web.index"), + "type": "success", + "format": "", + "message": _('Book Successfully Deleted')}] + return jsonify(res) else: - # book not found - log.error('Book with id "%s" could not be deleted: not found', book_id) - return render_delete_book_result(book_format, json_response, warning, book_id, location) + return delete_book_from_UI(book_id, book_format, location) message = _("You are missing permissions to delete books") if json_response: - return jsonify({"location": url_for("edit-book.show_edit_book", book_id=book_id), - "type": "danger", - "format": "", - "message": message}) + try: + return jsonify({"location": url_for("edit-book.show_edit_book", book_id=int(book_id)), + "type": "danger", + "format": "", + "message": message}) + except TypeError as e: + return jsonify({"location": url_for("web.index"), "type": "danger", "format": "", + "message": str(e)}) else: flash(message, category="error") return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) +def delete_book_from_UI(book_id, book_format, location=""): + book = calibre_db.get_book(book_id) + if book: + try: + result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper()) + if not result: + flash(error, category="error") + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) + if error: + flash(error, category="warning") + if not book_format: + delete_whole_book(book_id, book) + else: + calibre_db.session.query(db.Data).filter(db.Data.book == book.id). \ + filter(db.Data.format == book_format).delete() + if book_format.upper() in ['KEPUB', 'EPUB', 'EPUB3']: + kobo_sync_status.remove_synced_book(book.id, True) + calibre_db.session.commit() + except Exception as ex: + log.error_or_exception(ex) + calibre_db.session.rollback() + flash(str(ex), category="error") + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) + else: + # book not found + log.error('Book with id "%s" could not be deleted: not found', book_id) + return render_delete_book_result(book_format, book_id, location) + + +def delete_book_from_table(book_id): + book = calibre_db.get_book(book_id) + if book: + try: + result, error = helper.delete_book(book, config.get_book_path(), book_format="") + if not result: + return {"location": url_for("edit-book.show_edit_book", book_id=book_id), + "type": "danger", + "format": "", + "message": error} + delete_whole_book(book_id, book) + calibre_db.session.commit() + if error: + return {"location": url_for("edit-book.show_edit_book", book_id=book_id), + "type": "warning", + "format": "", + "message": error} + except Exception as ex: + log.error_or_exception(ex) + calibre_db.session.rollback() + return {"location": url_for("edit-book.show_edit_book", book_id=book_id), + "type": "danger", + "format": "", + "message": ex} + else: + # book not found + log.error('Book with id "%s" could not be deleted: not found', book_id) + return {"location": url_for("edit-book.show_edit_book", book_id=book_id), + "type": "danger", + "format": "", + "message": _('Book with id "{}" could not be deleted: not found'.format(book_id))} + + def render_edit_book(book_id): cc = calibre_db.session.query(db.CustomColumns).filter(db.CustomColumns.datatype.notin_(db.cc_exceptions)).all() book = calibre_db.get_filtered_book(book_id, allow_show_archived=True) diff --git a/cps/helper.py b/cps/helper.py index 44770143..e1949c83 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -307,18 +307,16 @@ def edit_book_read_status(book_id, read_status=None): if not config.config_read_column: book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).first() - if book: - if read_status is None: - if book.read_status == ub.ReadBook.STATUS_FINISHED: - book.read_status = ub.ReadBook.STATUS_UNREAD - else: - book.read_status = ub.ReadBook.STATUS_FINISHED - else: - book.read_status = ub.ReadBook.STATUS_FINISHED if read_status == True else ub.ReadBook.STATUS_UNREAD - else: + if not book: read_book = ub.ReadBook(user_id=current_user.id, book_id=book_id) - read_book.read_status = ub.ReadBook.STATUS_FINISHED book = read_book + if read_status is None: + if book.read_status == ub.ReadBook.STATUS_FINISHED: + book.read_status = ub.ReadBook.STATUS_UNREAD + else: + book.read_status = ub.ReadBook.STATUS_FINISHED + else: + book.read_status = ub.ReadBook.STATUS_FINISHED if read_status == True else ub.ReadBook.STATUS_UNREAD if not book.kobo_reading_state: kobo_reading_state = ub.KoboReadingState(user_id=current_user.id, book_id=book_id) kobo_reading_state.current_bookmark = ub.KoboBookmark() diff --git a/cps/kobo_sync_status.py b/cps/kobo_sync_status.py index 941cd73f..5df2f7a8 100644 --- a/cps/kobo_sync_status.py +++ b/cps/kobo_sync_status.py @@ -56,7 +56,7 @@ def remove_synced_book(book_id, all=False, session=None): def change_archived_books(book_id, state=None, message=None): archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id), ub.ArchivedBook.book_id == book_id)).first() - if not archived_book and (state == True or state == None): + if not archived_book: # and (state == True or state == None): archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id) archived_book.is_archived = state if state != None else not archived_book.is_archived diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 9994b450..ea72fdfd 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -229,10 +229,12 @@ $("#delete_confirm").click(function(event) { postButton(event, getPath() + "/delete/" + deleteId + "/" + bookFormat); } else { if (ajaxResponse) { - path = getPath() + "/ajax/delete/" + deleteId; $.ajax({ - method:"post", - url: path, + url: getPath() + "/ajax/deletebook", + method: "post", + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify({"bookid": [deleteId]}), timeout: 900, success:function(data) { data.forEach(function(item) { diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 67934489..421c2e29 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -119,18 +119,17 @@ $(function() { $("#edit_selected_books").attr("aria-disabled", true); } if (selections.length < 1) { - $("#delete_selection").addClass("disabled"); - $("#delete_selection").attr("aria-disabled", true); + // $("#book_delete_selection").addClass("disabled"); + // $("#book_delete_selection").attr("aria-disabled", true); $("#table_xchange").addClass("disabled"); $("#table_xchange").attr("aria-disabled", true); } else { - $("#delete_selection").removeClass("disabled"); - $("#delete_selection").attr("aria-disabled", false); + // $("#book_delete_selection").removeClass("disabled"); + // $("#book_delete_selection").attr("aria-disabled", false); $("#table_xchange").removeClass("disabled"); $("#table_xchange").attr("aria-disabled", false); - } - + handle_header_buttons(); }); // Small block to initialize the state of the author/title sort inputs in metadata form @@ -153,7 +152,7 @@ $(function() { }) ///// - $("#delete_selection").click(function() { + $("#book_delete_selection").click(function () { $("#books-table").bootstrapTable("uncheckAll"); }); @@ -162,7 +161,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/mergebooks", + url: getPath() + "/ajax/mergebooks", data: JSON.stringify({"Merge_books":selections}), success: function success() { $("#books-table").bootstrapTable("refresh"); @@ -181,7 +180,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/simulatemerge", + url: getPath() + "/ajax/simulatemerge", data: JSON.stringify({"Merge_books":selections}), success: function success(booTitles) { $('#merge_from').empty(); @@ -207,7 +206,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/editselectedbooks", + url: getPath() + "/ajax/editselectedbooks", data: JSON.stringify({ "selections": selections, "title": $("#title_input").val(), @@ -250,7 +249,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/displayselectedbooks", + url: getPath() + "/ajax/displayselectedbooks", data: JSON.stringify({"selections":selections}), success: function success(booTitles) { $('#display-archive-selected-books').empty(); @@ -262,12 +261,12 @@ $(function() { }); }); - $(document).on('click', '#archive_selected_confirm', function(event) { + /*$(document).on('click', '#archive_selected_confirm', function(event) { $.ajax({ method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/archiveselectedbooks", + url: getPath() + "/ajax/archiveselectedbooks", data: JSON.stringify({"selections":selections, "archive": true}), success: function success(booTitles) { $("#books-table").bootstrapTable("refresh"); @@ -286,7 +285,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/displayselectedbooks", + url: getPath() + "/ajax/displayselectedbooks", data: JSON.stringify({"selections":selections}), success: function success(booTitles) { $('#display-unarchive-selected-books').empty(); @@ -303,7 +302,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/archiveselectedbooks", + url: getPath() + "/ajax/archiveselectedbooks", data: JSON.stringify({"selections":selections, "archive": false}), success: function success(booTitles) { $("#books-table").bootstrapTable("refresh"); @@ -322,7 +321,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/displayselectedbooks", + url: getPath() + "/ajax/displayselectedbooks", data: JSON.stringify({"selections":selections}), success: function success(booTitles) { $('#display-delete-selected-books').empty(); @@ -339,7 +338,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/deleteselectedbooks", + url: getPath() + "/ajax/deleteselectedbooks", data: JSON.stringify({"selections":selections}), success: function success(booTitles) { $("#books-table").bootstrapTable("refresh"); @@ -358,7 +357,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/displayselectedbooks", + url: getPath() + "/ajax/displayselectedbooks", data: JSON.stringify({"selections":selections}), success: function success(booTitles) { $('#display-read-selected-books').empty(); @@ -375,7 +374,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/readselectedbooks", + url: getPath() + "/ajax/readselectedbooks", data: JSON.stringify({"selections":selections, "markAsRead": true}), success: function success(booTitles) { $("#books-table").bootstrapTable("refresh"); @@ -394,7 +393,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/displayselectedbooks", + url: getPath() + "/ajax/displayselectedbooks", data: JSON.stringify({"selections":selections}), success: function success(booTitles) { $('#display-unread-selected-books').empty(); @@ -411,21 +410,21 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/readselectedbooks", + url: getPath() + "/ajax/readselectedbooks", data: JSON.stringify({"selections":selections, "markAsRead": false}), success: function success(booTitles) { $("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("uncheckAll"); } }); - }); + });*/ $("#table_xchange").click(function() { $.ajax({ method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/xchange", + url: getPath() + "/ajax/xchange", data: JSON.stringify({"xchange":selections}), success: function success() { $("#books-table").bootstrapTable("refresh"); @@ -442,6 +441,10 @@ $(function() { editable: { mode: "inline", emptytext: "", + ajaxOptions: { + contentType: "application/json; charset=utf-8", + dataType: "json", + }, success: function (response, __) { if (!response.success) return response.msg; return {newValue: response.newValue}; @@ -449,7 +452,8 @@ $(function() { params: function (params) { params.checkA = $('#autoupdate_authorsort').prop('checked'); params.checkT = $('#autoupdate_titlesort').prop('checked'); - return params + params.pk = [params.pk]; + return JSON.stringify(params); } } }; @@ -483,7 +487,7 @@ $(function() { searchAlign: "left", showSearchButton : true, searchOnEnterKey: true, - checkboxHeader: false, + checkboxHeader: true, maintainMetaData: true, responseHandler: responseHandler, columns: column, @@ -497,7 +501,7 @@ $(function() { $.ajax({ method:"get", dataType: "json", - url: window.location.pathname + "/../ajax/sort_value/" + field + "/" + row.id, + url: getPath() + "/ajax/sort_value/" + field + "/" + row.id, success: function success(data) { var key = Object.keys(data)[0]; $("#books-table").bootstrapTable("updateCellByUniqueId", { @@ -509,6 +513,66 @@ $(function() { }); } }, + onPostBody () { + // 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(); + var index = elText.lastIndexOf('\n', elText.length - 2); + if ( index > -1) { + elText = elText.substr(index); + } + $(this).next().text(elText); + }); + }, + onPostHeader() { + $(".form-check").each(function () { + var item = $(this).parent(); + var parent = item.parent().parent(); + if (parent.prop('nodeName') === "TH") { + item.prependTo(parent); + } + }); + + if ($(".button_head").length) { + if (!$._data($(".button_head").get(0), "events")) { + $(".button_head").on("click", function () { + var result = $('#books-table').bootstrapTable('getSelections').map(a => a.id); + confirmDialog( + "btndelbook", + "GeneralDeleteModal", + 0, + function () { + $.ajax({ + method: "post", + url: getPath() + "/ajax/deletebook", + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify({"bookid": result}), + success: function (data) { + selections = selections.filter((el) => !result.includes(el)); + handleListServerResponse(data); + }, + error: function (data) { + handleListServerResponse([{type: "danger", message: data.responseText}]) + }, + }); + } + ); + }); + } + } + if ($(".check_head").length) { + if (!$._data($(".check_head").get(0), "events")) { + $(".check_head").on("change", function () { + var val = $(this).data("set"); + var name = $(this).data("name"); + var data = $(this).data("val"); + bookCheckboxHeader(val, name, data); + }); + } + } + }, // eslint-disable-next-line no-unused-vars onColumnSwitch: function (field, checked) { var visible = $("#books-table").bootstrapTable("getVisibleColumns"); @@ -525,10 +589,16 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../ajax/table_settings", + url: getPath() + "/ajax/table_settings", data: "{" + st + "}", }); + handle_header_buttons(); }, + onLoadSuccess: function() { + $("input:radio.check_head:checked").each(function () { + $(this).prop('checked', false); + }); + } }); $("#domain_allow_submit").click(function(event) { @@ -537,7 +607,7 @@ $(function() { $(this).closest("form").submit(); $.ajax ({ method:"get", - url: window.location.pathname + "/../../ajax/domainlist/1", + url: getPath() + "/ajax/domainlist/1", async: true, timeout: 900, success:function(data) { @@ -558,7 +628,7 @@ $(function() { $(this).closest("form").submit(); $.ajax ({ method:"get", - url: window.location.pathname + "/../../ajax/domainlist/0", + url: getPath() + "/ajax/domainlist/0", async: true, timeout: 900, success:function(data) { @@ -576,12 +646,12 @@ $(function() { function domainHandle(domainId) { $.ajax({ method:"post", - url: window.location.pathname + "/../../ajax/deletedomain", + url: getPath() + "/ajax/deletedomain", data: {"domainid":domainId} }); $.ajax({ method:"get", - url: window.location.pathname + "/../../ajax/domainlist/1", + url: getPath() + "/ajax/domainlist/1", async: true, timeout: 900, success:function(data) { @@ -590,7 +660,7 @@ $(function() { }); $.ajax({ method:"get", - url: window.location.pathname + "/../../ajax/domainlist/0", + url: getPath() + "/ajax/domainlist/0", async: true, timeout: 900, success:function(data) { @@ -826,7 +896,7 @@ $(function() { method:"post", contentType: "application/json; charset=utf-8", dataType: "json", - url: window.location.pathname + "/../../ajax/user_table_settings", + url: getPath() + "/ajax/user_table_settings", data: "{" + st + "}", }); handle_header_buttons(); @@ -852,8 +922,8 @@ $(function() { function handle_header_buttons () { if (selections.length < 1) { - $("#user_delete_selection").addClass("disabled"); - $("#user_delete_selection").attr("aria-disabled", true); + $(".mass_selection").addClass("disabled"); + $(".mass_selection").attr("aria-disabled", true); $(".check_head").attr("aria-disabled", true); $(".check_head").attr("disabled", true); $(".check_head").prop('checked', false); @@ -865,8 +935,8 @@ function handle_header_buttons () { $(".multi_selector").attr("disabled", true); $(".header_select").attr("disabled", true); } else { - $("#user_delete_selection").removeClass("disabled"); - $("#user_delete_selection").attr("aria-disabled", false); + $(".mass_selection").removeClass("disabled"); + $(".mass_selection").attr("aria-disabled", false); $(".check_head").attr("aria-disabled", false); $(".check_head").removeAttr("disabled"); $(".button_head").attr("aria-disabled", false); @@ -875,8 +945,10 @@ function handle_header_buttons () { $(".multi_head").removeClass("hidden"); $(".multi_selector").attr("aria-disabled", false); $(".multi_selector").removeAttr("disabled"); - $('.multi_selector').selectpicker('refresh'); $(".header_select").removeAttr("disabled"); + if (typeof $.fn.selectpicker === "function") { + $('.multi_selector').selectpicker('refresh'); + } } } @@ -1003,7 +1075,7 @@ function loadSuccess() { $("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); - $(".user-remove[data-pk='"+guest.data("pk")+"']").hide(); + $(".user-remove[data-pk='" + guest.data("pk") + "']").hide(); } function move_header_elements() { @@ -1045,7 +1117,7 @@ function move_header_elements() { function () { $.ajax({ method: "post", - url: window.location.pathname + "/../../ajax/editlistusers/" + field, + url: getPath() + "/ajax/editlistusers/" + field, data: {"pk": result, "value": values, "action": val}, success: function (data) { handleListServerResponse(data); @@ -1059,7 +1131,6 @@ function move_header_elements() { }); } } - $("#user_delete_selection").click(function () { $("#user-table").bootstrapTable("uncheckAll"); }); @@ -1090,8 +1161,10 @@ function move_header_elements() { function () { $.ajax({ method: "post", - url: window.location.pathname + "/../../ajax/deleteuser", - data: {"userid": result}, + url: getPath() + "/ajax/deleteuser", + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify({"userid": result}), success: function (data) { selections = selections.filter((el) => !result.includes(el)); handleListServerResponse(data); @@ -1117,7 +1190,7 @@ function handleListServerResponse (data) { ''); }); } - $("#user-table").bootstrapTable("refresh"); + $(".table.table-striped").bootstrapTable("refresh"); } function checkboxChange(checkbox, userId, field, field_index) { @@ -1132,20 +1205,21 @@ function checkboxChange(checkbox, userId, field, field_index) { }); } -function BookCheckboxChange(checkbox, userId, field) { +function BookCheckboxChange(checkbox, bookId, field) { var value = checkbox.checked ? "True" : "False"; var element = checkbox; $.ajax({ method: "post", url: getPath() + "/ajax/editbooks/" + field, - data: {"pk": userId, "value": value}, + data: JSON.stringify({"pk": [bookId], "value": value}), + contentType: "application/json; charset=utf-8", + dataType: "json", error: function(data) { element.checked = !element.checked; handleListServerResponse([{type:"danger", message:data.responseText}]) }, success: handleListServerResponse }); - console.log("test"); } function selectHeader(element, field) { @@ -1154,7 +1228,7 @@ function selectHeader(element, field) { var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); $.ajax({ method: "post", - url: window.location.pathname + "/../../ajax/editlistusers/" + field, + url: getPath() + "/ajax/editlistusers/" + field, data: {"pk": result, "value": element.value}, error: function (data) { handleListServerResponse([{type:"danger", message:data.responseText}]) @@ -1167,12 +1241,35 @@ function selectHeader(element, field) { } } +function bookCheckboxHeader(CheckboxState, text, field_index) { + confirmDialog(text, "GeneralChangeModal", 0, function() { + var result = $('#books-table').bootstrapTable('getSelections').map(a => a.id); + $.ajax({ + method: "post", + url: getPath() + "/ajax/editbooks/" + field_index, + data: JSON.stringify({"pk": result, "field_index": field_index, "value": CheckboxState}), + contentType: "application/json; charset=utf-8", + dataType: "json", + error: function (data) { + handleListServerResponse([{type:"danger", message:data.responseText}]) + }, + success: function (data) { + handleListServerResponse (data, true) + }, + }); + },function() { + $("input:radio.check_head:checked").each(function() { + $(this).prop('checked', false); + }); + }); +} + function checkboxHeader(CheckboxState, field, field_index) { 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, + url: getPath() + "/ajax/editlistusers/" + field, data: {"pk": result, "field_index": field_index, "value": CheckboxState}, error: function (data) { handleListServerResponse([{type:"danger", message:data.responseText}]) @@ -1188,7 +1285,7 @@ function checkboxHeader(CheckboxState, field, field_index) { }); } -function deleteUser(a,id){ +function deleteUser(a, id){ confirmDialog( "btndeluser", "GeneralDeleteModal", @@ -1196,8 +1293,10 @@ function deleteUser(a,id){ function() { $.ajax({ method:"post", - url: window.location.pathname + "/../../ajax/deleteuser", - data: {"userid":id}, + url: getPath() + "/ajax/deleteuser", + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify({"userid": [id]}), success: function (data) { userId = parseInt(id, 10); selections = selections.filter(item => item !== userId); @@ -1224,8 +1323,10 @@ function storeLocation() { function user_handle (userId) { $.ajax({ method:"post", - url: window.location.pathname + "/../../ajax/deleteuser", - data: {"userid":userId} + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify({"userid": [userId]}), + url: getPath() + "/ajax/deleteuser", }); $("#user-table").bootstrapTable("refresh"); } @@ -1233,6 +1334,5 @@ function user_handle (userId) { function shorten_html(value, response) { if(value) { $(this).html("[...]"); - // value.split('\n').slice(0, 2).join("") + } } diff --git a/cps/templates/book_table.html b/cps/templates/book_table.html index efc1c780..4b22e584 100644 --- a/cps/templates/book_table.html +++ b/cps/templates/book_table.html @@ -15,27 +15,28 @@ {%- endmacro %} {% macro book_checkbox_row(parameter, show_text, sort) -%} - {% if parameter == "is_archived" %} -
- {{_('Archive selected books')}} +
+
+ {{_('Archive selected books')}}
-
-
- {{_('Unarchive selected books')}} +
+ {{_('Unarchive selected books')}}
-
+
{% elif parameter == "read_status" %} -
- {{_('Mark selected books as read')}} +
+
+ {{_('Mark selected books as read')}}
-
-
- {{_('Mark selected books as unread')}}
-
+
+ {{_('Mark selected books as unread')}}
+
+
{% endif %} {{show_text}} @@ -55,7 +56,7 @@
{{_('Merge selected books')}}
-
+
{{_('Clear selections')}}
@@ -82,7 +83,7 @@ {% if current_user.role_edit() %} - + {% endif %} {{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }} @@ -123,9 +124,9 @@ {% endfor %} {% if current_user.role_delete_books() and current_user.role_edit()%} -
+
{{_('Delete selected books')}} -
+

{{_('Delete')}} @@ -137,6 +138,8 @@ {% endblock %} {% block modal %} {{ delete_book(current_user.role_delete_books()) }} +{{ delete_confirm_modal() }} +{{ change_confirm_modal() }} {% if current_user.role_edit() %} -