1
0
mirror of https://github.com/janeczku/calibre-web synced 2025-10-23 19:37:40 +00:00

Update mass edit

Refactored delete User function
Updated testresults
This commit is contained in:
Ozzie Isaacs
2024-12-09 17:08:58 +01:00
parent 89e9958222
commit 08527ae3ce
10 changed files with 473 additions and 372 deletions

View File

@@ -40,7 +40,7 @@ from flask_babel import gettext as _
from flask_babel import get_locale, format_time, format_datetime, format_timedelta from flask_babel import get_locale, format_time, format_datetime, format_timedelta
from sqlalchemy import and_ from sqlalchemy import and_
from sqlalchemy.orm.attributes import flag_modified 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 sqlalchemy.sql.expression import func, or_, text
from . import constants, logger, helper, services, cli_param from . import constants, logger, helper, services, cli_param
@@ -386,13 +386,12 @@ def list_users():
@user_login_required @user_login_required
@admin_required @admin_required
def delete_user(): def delete_user():
user_ids = request.form.to_dict(flat=False) user_ids = request.get_json().get("userid")
users = None
message = "" message = ""
if "userid[]" in user_ids: try:
users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids['userid[]'])).all() users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids)).all()
elif "userid" in user_ids: except (ArgumentError):
users = ub.session.query(ub.User).filter(ub.User.id == user_ids['userid'][0]).all() users = None
count = 0 count = 0
errors = list() errors = list()
success = list() success = list()
@@ -408,10 +407,10 @@ def delete_user():
errors.append({'type': "danger", 'message': str(ex)}) errors.append({'type': "danger", 'message': str(ex)})
if count == 1: if count == 1:
log.info("User {} deleted".format(user_ids)) log.info("User {} deleted".format(user_ids[0]))
success = [{'type': "success", 'message': message}] success = [{'type': "success", 'message': message}]
elif count > 1: 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 = [{'type': "success", 'message': _("{} users deleted successfully").format(count)}]
success.extend(errors) success.extend(errors)
return make_response(jsonify(success)) return make_response(jsonify(success))
@@ -618,6 +617,8 @@ def load_dialogtexts(element_id):
texts["main"] = _('Do you really want to delete this domain?') texts["main"] = _('Do you really want to delete this domain?')
elif element_id == "btndeluser": elif element_id == "btndeluser":
texts["main"] = _('Do you really want to delete this user?') 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": elif element_id == "delete_shelf":
texts["main"] = _('Are you sure you want to delete this shelf?') texts["main"] = _('Are you sure you want to delete this shelf?')
elif element_id == "select_locale": 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)?') texts["main"] = _('Are you sure you want to change visible book languages for selected user(s)?')
elif element_id == "role": elif element_id == "role":
texts["main"] = _('Are you sure you want to change the selected role for the selected user(s)?') 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": elif element_id == "restrictions":
texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?') texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?')
elif element_id == "sidebar_view": elif element_id == "sidebar_view":

View File

@@ -73,17 +73,18 @@ def edit_required(f):
return inner return inner
@editbook.route("/ajax/delete/<int:book_id>", methods=["POST"]) @editbook.route("/ajax/deletebook", methods=["POST"])
@user_login_required @user_login_required
def delete_book_from_details(book_id): def delete_books_ajax():
return delete_book_from_table(book_id, "", True) # , mimetype='application/json') book_ids = request.get_json().get("bookid")
return check_delete_book(book_ids, "", True)
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}, methods=["POST"]) @editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
@editbook.route("/delete/<int:book_id>/<string:book_format>", methods=["POST"]) @editbook.route("/delete/<int:book_id>/<string:book_format>", methods=["POST"])
@user_login_required @user_login_required
def delete_book_ajax(book_id, book_format): def delete_book(book_id, book_format):
return delete_book_from_table(book_id, book_format, False, request.form.to_dict().get('location', "")) return check_delete_book(book_id, book_format, False, request.form.to_dict().get('location', ""))
@editbook.route("/admin/book/<int:book_id>", methods=['GET']) @editbook.route("/admin/book/<int:book_id>", methods=['GET'])
@@ -213,7 +214,7 @@ def table_get_custom_enum(c_id):
@login_required_if_no_ano @login_required_if_no_ano
@edit_required @edit_required
def edit_list_book(param): 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) return edit_book_param(param, vals)
@editbook.route("/ajax/editselectedbooks", methods=['POST']) @editbook.route("/ajax/editselectedbooks", methods=['POST'])
@@ -233,10 +234,8 @@ def edit_selected_books():
comments = d.get('comments') comments = d.get('comments')
checkA = d.get('checkA') checkA = d.get('checkA')
if len(selections) != 0:
for book_id in selections:
vals = { vals = {
"pk": book_id, "pk": selections,
"value": None, "value": None,
"checkA": checkA, "checkA": checkA,
} }
@@ -268,7 +267,6 @@ def edit_selected_books():
vals['value'] = comments vals['value'] = comments
edit_book_param('comments', vals) edit_book_param('comments', vals)
return json.dumps({'success': True}) return json.dumps({'success': True})
return ""
# Separated from /editbooks so that /editselectedbooks can also use this # Separated from /editbooks so that /editselectedbooks can also use this
# #
@@ -284,94 +282,96 @@ def edit_selected_books():
@login_required_if_no_ano @login_required_if_no_ano
@edit_required @edit_required
def edit_book_param(param, vals): def edit_book_param(param, vals):
book = calibre_db.get_book(vals['pk']) elements = vals.get('pk',[])
ret = {}
for elem in elements:
book = calibre_db.get_book(elem)
calibre_db.create_functions(config) calibre_db.create_functions(config)
sort_param = "" sort_param = ""
ret = ""
try: try:
if param == 'series_index': if param == 'series_index':
edit_book_series_index(vals['value'], book) edit_book_series_index(vals['value'], book)
ret = make_response(jsonify(success=True, newValue=book.series_index)) ret = jsonify(success=True, newValue=book.series_index)
elif param == 'tags': elif param == 'tags':
edit_book_tags(vals['value'], book) edit_book_tags(vals['value'], book)
ret = make_response(jsonify(success=True, newValue=', '.join([tag.name for tag in book.tags]))) ret = jsonify(success=True, newValue=', '.join([tag.name for tag in book.tags]))
elif param == 'series': elif param == 'series':
edit_book_series(vals['value'], book) edit_book_series(vals['value'], book)
ret = make_response(jsonify(success=True, newValue=', '.join([serie.name for serie in book.series]))) ret = jsonify(success=True, newValue=', '.join([serie.name for serie in book.series]))
elif param == 'publishers': elif param == 'publishers':
edit_book_publisher(vals['value'], book) edit_book_publisher(vals['value'], book)
ret = make_response(jsonify(success=True, ret = jsonify(success=True,
newValue=', '.join([publisher.name for publisher in book.publishers]))) newValue=', '.join([publisher.name for publisher in book.publishers]))
elif param == 'languages': elif param == 'languages':
invalid = list() invalid = list()
edit_book_languages(vals['value'], book, invalid=invalid) edit_book_languages(vals['value'], book, invalid=invalid)
if invalid: if invalid:
ret = make_response(jsonify(success=False, ret = jsonify(success=False, msg='Invalid languages in request: {}'.format(','.join(invalid)))
msg='Invalid languages in request: {}'.format(','.join(invalid))))
else: else:
lang_names = list() lang_names = list()
for lang in book.languages: for lang in book.languages:
lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code)) lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code))
ret = make_response(jsonify(success=True, newValue=', '.join(lang_names))) ret = jsonify(success=True, newValue=', '.join(lang_names))
elif param == 'author_sort': elif param == 'author_sort':
book.author_sort = vals['value'] book.author_sort = vals['value']
ret = make_response(jsonify(success=True, newValue=book.author_sort)) ret = jsonify(success=True, newValue=book.author_sort)
elif param == 'title': elif param == 'title':
sort_param = book.sort sort_param = book.sort
if handle_title_on_edit(book, vals.get('value', "")): if handle_title_on_edit(book, vals.get('value', "")):
rename_error = helper.update_dir_structure(book.id, config.get_book_path()) rename_error = helper.update_dir_structure(book.id, config.get_book_path())
if not rename_error: if not rename_error:
ret = make_response(jsonify(success=True, newValue=book.title)) ret = jsonify(success=True, newValue=book.title)
else: else:
ret = make_response(jsonify(success=False, msg=rename_error)) ret = jsonify(success=False, msg=rename_error)
elif param == 'sort': elif param == 'sort':
book.sort = vals['value'] book.sort = vals['value']
ret = make_response(jsonify(success=True,newValue=book.sort)) ret = jsonify(success=True,newValue=book.sort)
elif param == 'comments': elif param == 'comments':
edit_book_comments(vals['value'], book) edit_book_comments(vals['value'], book)
ret = make_response(jsonify(success=True, newValue=book.comments[0].text)) ret = jsonify(success=True, newValue=book.comments[0].text)
elif param == 'authors': elif param == 'authors':
input_authors, __ = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true") 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]) rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0])
if not rename_error: if not rename_error:
ret = make_response(jsonify( ret = jsonify(
success=True, success=True,
newValue=' & '.join([author.replace('|', ',') for author in input_authors]))) newValue=' & '.join([author.replace('|', ',') for author in input_authors]))
else: else:
ret = make_response(jsonify(success=False, msg=rename_error)) ret = jsonify(success=False, msg=rename_error)
elif param == 'is_archived': elif param == 'is_archived':
is_archived = change_archived_books(book.id, vals['value'] == "True", is_archived = change_archived_books(book.id, vals['value'] == "True",
message="Book {} archive bit set to: {}".format(book.id, vals['value'])) message="Book {} archive bit set to: {}".format(book.id,
vals['value']))
if is_archived: if is_archived:
kobo_sync_status.remove_synced_book(book.id) kobo_sync_status.remove_synced_book(book.id)
return "" continue
elif param == 'read_status': elif param == 'read_status':
ret = helper.edit_book_read_status(book.id, vals['value'] == "True") error = helper.edit_book_read_status(book.id, vals['value'] == "True")
if ret: if error:
return ret, 400 return error, 400
continue
elif param.startswith("custom_column_"): elif param.startswith("custom_column_"):
new_val = dict() new_val = dict()
new_val[param] = vals['value'] new_val[param] = vals['value']
edit_single_cc_data(book.id, book, param[14:], new_val) edit_single_cc_data(book.id, book, param[14:], new_val)
# ToDo: Very hacky find better solution # ToDo: Very hacky find better solution
if vals['value'] in ["True", "False"]: if vals['value'] in ["True", "False"]:
ret = "" ret = {}
else: else:
ret = make_response(jsonify(success=True, newValue=vals['value'])) ret = jsonify(success=True, newValue=vals['value'])
else: else:
return _("Parameter not found"), 400 return _("Parameter not found"), 400
book.last_modified = datetime.now(timezone.utc) book.last_modified = datetime.now(timezone.utc)
calibre_db.session.commit() calibre_db.session.commit()
# revert change for sort if automatic fields link is deactivated # revert change for sort if automatic fields link is deactivated
if param == 'title' and vals.get('checkT') == "false": if param == 'title' and vals.get('checkT') == False:
book.sort = sort_param book.sort = sort_param
calibre_db.session.commit() calibre_db.session.commit()
except (OperationalError, IntegrityError, StaleDataError) as e: except (OperationalError, IntegrityError, StaleDataError, AttributeError) as e:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error_or_exception("Database error: {}".format(e)) log.error_or_exception("Database error: {}".format(e))
ret = make_response(jsonify(success=False, ret = jsonify(success=False, msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e))
msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e)))
return ret return ret
@@ -433,16 +433,6 @@ def archive_selected_books():
return json.dumps({'success': True}) return json.dumps({'success': True})
return "" 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']) @editbook.route("/ajax/readselectedbooks", methods=['POST'])
@user_login_required @user_login_required
@@ -498,7 +488,7 @@ def merge_list_book():
element.format, element.format,
element.uncompressed_size, element.uncompressed_size,
to_name)) to_name))
delete_book_from_table(from_book.id, "", True) check_delete_book([from_book.id], "", True)
return make_response(jsonify(success=True)) return make_response(jsonify(success=True))
return "" return ""
@@ -968,50 +958,56 @@ def delete_whole_book(book_id, book):
calibre_db.session.query(db.Books).filter(db.Books.id == book_id).delete() 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 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") flash(_('Book Format Successfully Deleted'), category="success")
return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) 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: else:
flash(_('Book Successfully Deleted'), category="success") flash(_('Book Successfully Deleted'), category="success")
return redirect(get_redirect_location(location, "web.index")) return redirect(get_redirect_location(location, "web.index"))
def delete_book_from_table(book_id, book_format, json_response, location=""): def check_delete_book(book_id, book_format, json_response, location=""):
warning = {}
if current_user.role_delete_books(): if current_user.role_delete_books():
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:
return delete_book_from_UI(book_id, book_format, location)
message = _("You are missing permissions to delete books")
if json_response:
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) book = calibre_db.get_book(book_id)
if book: if book:
try: try:
result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper()) result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper())
if not result: 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") flash(error, category="error")
return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
if error: 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") flash(error, category="warning")
if not book_format: if not book_format:
delete_whole_book(book_id, book) delete_whole_book(book_id, book)
@@ -1024,28 +1020,45 @@ def delete_book_from_table(book_id, book_format, json_response, location=""):
except Exception as ex: except Exception as ex:
log.error_or_exception(ex) log.error_or_exception(ex)
calibre_db.session.rollback() 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") flash(str(ex), category="error")
return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
else: else:
# book not found # book not found
log.error('Book with id "%s" could not be deleted: not found', book_id) 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 render_delete_book_result(book_format, book_id, 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), 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", "type": "danger",
"format": "", "format": "",
"message": message}) "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: else:
flash(message, category="error") # book not found
return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) 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): def render_edit_book(book_id):

View File

@@ -307,7 +307,9 @@ def edit_book_read_status(book_id, read_status=None):
if not config.config_read_column: if not config.config_read_column:
book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id), book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.book_id == book_id)).first() ub.ReadBook.book_id == book_id)).first()
if book: if not book:
read_book = ub.ReadBook(user_id=current_user.id, book_id=book_id)
book = read_book
if read_status is None: if read_status is None:
if book.read_status == ub.ReadBook.STATUS_FINISHED: if book.read_status == ub.ReadBook.STATUS_FINISHED:
book.read_status = ub.ReadBook.STATUS_UNREAD book.read_status = ub.ReadBook.STATUS_UNREAD
@@ -315,10 +317,6 @@ def edit_book_read_status(book_id, read_status=None):
book.read_status = ub.ReadBook.STATUS_FINISHED book.read_status = ub.ReadBook.STATUS_FINISHED
else: else:
book.read_status = ub.ReadBook.STATUS_FINISHED if read_status == True else ub.ReadBook.STATUS_UNREAD book.read_status = ub.ReadBook.STATUS_FINISHED if read_status == True else ub.ReadBook.STATUS_UNREAD
else:
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 not book.kobo_reading_state: if not book.kobo_reading_state:
kobo_reading_state = ub.KoboReadingState(user_id=current_user.id, book_id=book_id) kobo_reading_state = ub.KoboReadingState(user_id=current_user.id, book_id=book_id)
kobo_reading_state.current_bookmark = ub.KoboBookmark() kobo_reading_state.current_bookmark = ub.KoboBookmark()

View File

@@ -56,7 +56,7 @@ def remove_synced_book(book_id, all=False, session=None):
def change_archived_books(book_id, state=None, message=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), archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id),
ub.ArchivedBook.book_id == book_id)).first() 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 = 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 archived_book.is_archived = state if state != None else not archived_book.is_archived

View File

@@ -229,10 +229,12 @@ $("#delete_confirm").click(function(event) {
postButton(event, getPath() + "/delete/" + deleteId + "/" + bookFormat); postButton(event, getPath() + "/delete/" + deleteId + "/" + bookFormat);
} else { } else {
if (ajaxResponse) { if (ajaxResponse) {
path = getPath() + "/ajax/delete/" + deleteId;
$.ajax({ $.ajax({
url: getPath() + "/ajax/deletebook",
method: "post", method: "post",
url: path, contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({"bookid": [deleteId]}),
timeout: 900, timeout: 900,
success:function(data) { success:function(data) {
data.forEach(function(item) { data.forEach(function(item) {

View File

@@ -119,18 +119,17 @@ $(function() {
$("#edit_selected_books").attr("aria-disabled", true); $("#edit_selected_books").attr("aria-disabled", true);
} }
if (selections.length < 1) { if (selections.length < 1) {
$("#delete_selection").addClass("disabled"); // $("#book_delete_selection").addClass("disabled");
$("#delete_selection").attr("aria-disabled", true); // $("#book_delete_selection").attr("aria-disabled", true);
$("#table_xchange").addClass("disabled"); $("#table_xchange").addClass("disabled");
$("#table_xchange").attr("aria-disabled", true); $("#table_xchange").attr("aria-disabled", true);
} else { } else {
$("#delete_selection").removeClass("disabled"); // $("#book_delete_selection").removeClass("disabled");
$("#delete_selection").attr("aria-disabled", false); // $("#book_delete_selection").attr("aria-disabled", false);
$("#table_xchange").removeClass("disabled"); $("#table_xchange").removeClass("disabled");
$("#table_xchange").attr("aria-disabled", false); $("#table_xchange").attr("aria-disabled", false);
} }
handle_header_buttons();
}); });
// Small block to initialize the state of the author/title sort inputs in metadata form // 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"); $("#books-table").bootstrapTable("uncheckAll");
}); });
@@ -162,7 +161,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/mergebooks", url: getPath() + "/ajax/mergebooks",
data: JSON.stringify({"Merge_books":selections}), data: JSON.stringify({"Merge_books":selections}),
success: function success() { success: function success() {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
@@ -181,7 +180,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/simulatemerge", url: getPath() + "/ajax/simulatemerge",
data: JSON.stringify({"Merge_books":selections}), data: JSON.stringify({"Merge_books":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$('#merge_from').empty(); $('#merge_from').empty();
@@ -207,7 +206,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/editselectedbooks", url: getPath() + "/ajax/editselectedbooks",
data: JSON.stringify({ data: JSON.stringify({
"selections": selections, "selections": selections,
"title": $("#title_input").val(), "title": $("#title_input").val(),
@@ -250,7 +249,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks", url: getPath() + "/ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}), data: JSON.stringify({"selections":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$('#display-archive-selected-books').empty(); $('#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({ $.ajax({
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/archiveselectedbooks", url: getPath() + "/ajax/archiveselectedbooks",
data: JSON.stringify({"selections":selections, "archive": true}), data: JSON.stringify({"selections":selections, "archive": true}),
success: function success(booTitles) { success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
@@ -286,7 +285,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks", url: getPath() + "/ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}), data: JSON.stringify({"selections":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$('#display-unarchive-selected-books').empty(); $('#display-unarchive-selected-books').empty();
@@ -303,7 +302,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/archiveselectedbooks", url: getPath() + "/ajax/archiveselectedbooks",
data: JSON.stringify({"selections":selections, "archive": false}), data: JSON.stringify({"selections":selections, "archive": false}),
success: function success(booTitles) { success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
@@ -322,7 +321,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks", url: getPath() + "/ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}), data: JSON.stringify({"selections":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$('#display-delete-selected-books').empty(); $('#display-delete-selected-books').empty();
@@ -339,7 +338,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/deleteselectedbooks", url: getPath() + "/ajax/deleteselectedbooks",
data: JSON.stringify({"selections":selections}), data: JSON.stringify({"selections":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
@@ -358,7 +357,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks", url: getPath() + "/ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}), data: JSON.stringify({"selections":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$('#display-read-selected-books').empty(); $('#display-read-selected-books').empty();
@@ -375,7 +374,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/readselectedbooks", url: getPath() + "/ajax/readselectedbooks",
data: JSON.stringify({"selections":selections, "markAsRead": true}), data: JSON.stringify({"selections":selections, "markAsRead": true}),
success: function success(booTitles) { success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
@@ -394,7 +393,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks", url: getPath() + "/ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}), data: JSON.stringify({"selections":selections}),
success: function success(booTitles) { success: function success(booTitles) {
$('#display-unread-selected-books').empty(); $('#display-unread-selected-books').empty();
@@ -411,21 +410,21 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/readselectedbooks", url: getPath() + "/ajax/readselectedbooks",
data: JSON.stringify({"selections":selections, "markAsRead": false}), data: JSON.stringify({"selections":selections, "markAsRead": false}),
success: function success(booTitles) { success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
$("#books-table").bootstrapTable("uncheckAll"); $("#books-table").bootstrapTable("uncheckAll");
} }
}); });
}); });*/
$("#table_xchange").click(function() { $("#table_xchange").click(function() {
$.ajax({ $.ajax({
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/xchange", url: getPath() + "/ajax/xchange",
data: JSON.stringify({"xchange":selections}), data: JSON.stringify({"xchange":selections}),
success: function success() { success: function success() {
$("#books-table").bootstrapTable("refresh"); $("#books-table").bootstrapTable("refresh");
@@ -442,6 +441,10 @@ $(function() {
editable: { editable: {
mode: "inline", mode: "inline",
emptytext: "<span class='glyphicon glyphicon-plus'></span>", emptytext: "<span class='glyphicon glyphicon-plus'></span>",
ajaxOptions: {
contentType: "application/json; charset=utf-8",
dataType: "json",
},
success: function (response, __) { success: function (response, __) {
if (!response.success) return response.msg; if (!response.success) return response.msg;
return {newValue: response.newValue}; return {newValue: response.newValue};
@@ -449,7 +452,8 @@ $(function() {
params: function (params) { params: function (params) {
params.checkA = $('#autoupdate_authorsort').prop('checked'); params.checkA = $('#autoupdate_authorsort').prop('checked');
params.checkT = $('#autoupdate_titlesort').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", searchAlign: "left",
showSearchButton : true, showSearchButton : true,
searchOnEnterKey: true, searchOnEnterKey: true,
checkboxHeader: false, checkboxHeader: true,
maintainMetaData: true, maintainMetaData: true,
responseHandler: responseHandler, responseHandler: responseHandler,
columns: column, columns: column,
@@ -497,7 +501,7 @@ $(function() {
$.ajax({ $.ajax({
method:"get", method:"get",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/sort_value/" + field + "/" + row.id, url: getPath() + "/ajax/sort_value/" + field + "/" + row.id,
success: function success(data) { success: function success(data) {
var key = Object.keys(data)[0]; var key = Object.keys(data)[0];
$("#books-table").bootstrapTable("updateCellByUniqueId", { $("#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 // eslint-disable-next-line no-unused-vars
onColumnSwitch: function (field, checked) { onColumnSwitch: function (field, checked) {
var visible = $("#books-table").bootstrapTable("getVisibleColumns"); var visible = $("#books-table").bootstrapTable("getVisibleColumns");
@@ -525,10 +589,16 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/table_settings", url: getPath() + "/ajax/table_settings",
data: "{" + st + "}", data: "{" + st + "}",
}); });
handle_header_buttons();
}, },
onLoadSuccess: function() {
$("input:radio.check_head:checked").each(function () {
$(this).prop('checked', false);
});
}
}); });
$("#domain_allow_submit").click(function(event) { $("#domain_allow_submit").click(function(event) {
@@ -537,7 +607,7 @@ $(function() {
$(this).closest("form").submit(); $(this).closest("form").submit();
$.ajax ({ $.ajax ({
method:"get", method:"get",
url: window.location.pathname + "/../../ajax/domainlist/1", url: getPath() + "/ajax/domainlist/1",
async: true, async: true,
timeout: 900, timeout: 900,
success:function(data) { success:function(data) {
@@ -558,7 +628,7 @@ $(function() {
$(this).closest("form").submit(); $(this).closest("form").submit();
$.ajax ({ $.ajax ({
method:"get", method:"get",
url: window.location.pathname + "/../../ajax/domainlist/0", url: getPath() + "/ajax/domainlist/0",
async: true, async: true,
timeout: 900, timeout: 900,
success:function(data) { success:function(data) {
@@ -576,12 +646,12 @@ $(function() {
function domainHandle(domainId) { function domainHandle(domainId) {
$.ajax({ $.ajax({
method:"post", method:"post",
url: window.location.pathname + "/../../ajax/deletedomain", url: getPath() + "/ajax/deletedomain",
data: {"domainid":domainId} data: {"domainid":domainId}
}); });
$.ajax({ $.ajax({
method:"get", method:"get",
url: window.location.pathname + "/../../ajax/domainlist/1", url: getPath() + "/ajax/domainlist/1",
async: true, async: true,
timeout: 900, timeout: 900,
success:function(data) { success:function(data) {
@@ -590,7 +660,7 @@ $(function() {
}); });
$.ajax({ $.ajax({
method:"get", method:"get",
url: window.location.pathname + "/../../ajax/domainlist/0", url: getPath() + "/ajax/domainlist/0",
async: true, async: true,
timeout: 900, timeout: 900,
success:function(data) { success:function(data) {
@@ -826,7 +896,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../../ajax/user_table_settings", url: getPath() + "/ajax/user_table_settings",
data: "{" + st + "}", data: "{" + st + "}",
}); });
handle_header_buttons(); handle_header_buttons();
@@ -852,8 +922,8 @@ $(function() {
function handle_header_buttons () { function handle_header_buttons () {
if (selections.length < 1) { if (selections.length < 1) {
$("#user_delete_selection").addClass("disabled"); $(".mass_selection").addClass("disabled");
$("#user_delete_selection").attr("aria-disabled", true); $(".mass_selection").attr("aria-disabled", true);
$(".check_head").attr("aria-disabled", true); $(".check_head").attr("aria-disabled", true);
$(".check_head").attr("disabled", true); $(".check_head").attr("disabled", true);
$(".check_head").prop('checked', false); $(".check_head").prop('checked', false);
@@ -865,8 +935,8 @@ function handle_header_buttons () {
$(".multi_selector").attr("disabled", true); $(".multi_selector").attr("disabled", true);
$(".header_select").attr("disabled", true); $(".header_select").attr("disabled", true);
} else { } else {
$("#user_delete_selection").removeClass("disabled"); $(".mass_selection").removeClass("disabled");
$("#user_delete_selection").attr("aria-disabled", false); $(".mass_selection").attr("aria-disabled", false);
$(".check_head").attr("aria-disabled", false); $(".check_head").attr("aria-disabled", false);
$(".check_head").removeAttr("disabled"); $(".check_head").removeAttr("disabled");
$(".button_head").attr("aria-disabled", false); $(".button_head").attr("aria-disabled", false);
@@ -875,8 +945,10 @@ function handle_header_buttons () {
$(".multi_head").removeClass("hidden"); $(".multi_head").removeClass("hidden");
$(".multi_selector").attr("aria-disabled", false); $(".multi_selector").attr("aria-disabled", false);
$(".multi_selector").removeAttr("disabled"); $(".multi_selector").removeAttr("disabled");
$('.multi_selector').selectpicker('refresh');
$(".header_select").removeAttr("disabled"); $(".header_select").removeAttr("disabled");
if (typeof $.fn.selectpicker === "function") {
$('.multi_selector').selectpicker('refresh');
}
} }
} }
@@ -1045,7 +1117,7 @@ function move_header_elements() {
function () { function () {
$.ajax({ $.ajax({
method: "post", method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field, url: getPath() + "/ajax/editlistusers/" + field,
data: {"pk": result, "value": values, "action": val}, data: {"pk": result, "value": values, "action": val},
success: function (data) { success: function (data) {
handleListServerResponse(data); handleListServerResponse(data);
@@ -1059,7 +1131,6 @@ function move_header_elements() {
}); });
} }
} }
$("#user_delete_selection").click(function () { $("#user_delete_selection").click(function () {
$("#user-table").bootstrapTable("uncheckAll"); $("#user-table").bootstrapTable("uncheckAll");
}); });
@@ -1090,8 +1161,10 @@ function move_header_elements() {
function () { function () {
$.ajax({ $.ajax({
method: "post", method: "post",
url: window.location.pathname + "/../../ajax/deleteuser", url: getPath() + "/ajax/deleteuser",
data: {"userid": result}, contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({"userid": result}),
success: function (data) { success: function (data) {
selections = selections.filter((el) => !result.includes(el)); selections = selections.filter((el) => !result.includes(el));
handleListServerResponse(data); handleListServerResponse(data);
@@ -1117,7 +1190,7 @@ function handleListServerResponse (data) {
'</div>'); '</div>');
}); });
} }
$("#user-table").bootstrapTable("refresh"); $(".table.table-striped").bootstrapTable("refresh");
} }
function checkboxChange(checkbox, userId, field, field_index) { 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 value = checkbox.checked ? "True" : "False";
var element = checkbox; var element = checkbox;
$.ajax({ $.ajax({
method: "post", method: "post",
url: getPath() + "/ajax/editbooks/" + field, 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) { error: function(data) {
element.checked = !element.checked; element.checked = !element.checked;
handleListServerResponse([{type:"danger", message:data.responseText}]) handleListServerResponse([{type:"danger", message:data.responseText}])
}, },
success: handleListServerResponse success: handleListServerResponse
}); });
console.log("test");
} }
function selectHeader(element, field) { function selectHeader(element, field) {
@@ -1154,7 +1228,7 @@ function selectHeader(element, field) {
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
$.ajax({ $.ajax({
method: "post", method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field, url: getPath() + "/ajax/editlistusers/" + field,
data: {"pk": result, "value": element.value}, data: {"pk": result, "value": element.value},
error: function (data) { error: function (data) {
handleListServerResponse([{type:"danger", message:data.responseText}]) 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) { function checkboxHeader(CheckboxState, field, field_index) {
confirmDialog(field, "GeneralChangeModal", 0, function() { confirmDialog(field, "GeneralChangeModal", 0, function() {
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
$.ajax({ $.ajax({
method: "post", method: "post",
url: window.location.pathname + "/../../ajax/editlistusers/" + field, url: getPath() + "/ajax/editlistusers/" + field,
data: {"pk": result, "field_index": field_index, "value": CheckboxState}, data: {"pk": result, "field_index": field_index, "value": CheckboxState},
error: function (data) { error: function (data) {
handleListServerResponse([{type:"danger", message:data.responseText}]) handleListServerResponse([{type:"danger", message:data.responseText}])
@@ -1196,8 +1293,10 @@ function deleteUser(a,id){
function() { function() {
$.ajax({ $.ajax({
method:"post", method:"post",
url: window.location.pathname + "/../../ajax/deleteuser", url: getPath() + "/ajax/deleteuser",
data: {"userid":id}, contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({"userid": [id]}),
success: function (data) { success: function (data) {
userId = parseInt(id, 10); userId = parseInt(id, 10);
selections = selections.filter(item => item !== userId); selections = selections.filter(item => item !== userId);
@@ -1224,8 +1323,10 @@ function storeLocation() {
function user_handle (userId) { function user_handle (userId) {
$.ajax({ $.ajax({
method:"post", method:"post",
url: window.location.pathname + "/../../ajax/deleteuser", contentType: "application/json; charset=utf-8",
data: {"userid":userId} dataType: "json",
data: JSON.stringify({"userid": [userId]}),
url: getPath() + "/ajax/deleteuser",
}); });
$("#user-table").bootstrapTable("refresh"); $("#user-table").bootstrapTable("refresh");
} }
@@ -1233,6 +1334,5 @@ function user_handle (userId) {
function shorten_html(value, response) { function shorten_html(value, response) {
if(value) { if(value) {
$(this).html("[...]"); $(this).html("[...]");
// value.split('\n').slice(0, 2).join("") +
} }
} }

View File

@@ -15,27 +15,28 @@
{%- endmacro %} {%- endmacro %}
{% macro book_checkbox_row(parameter, show_text, sort) -%} {% macro book_checkbox_row(parameter, show_text, sort) -%}
<th data-name="{{parameter}}" data-field="{{parameter}}" data-switchable="false" <th data-name="{{parameter}}" data-field="{{parameter}}"
{% if sort %}data-sortable="true" {% endif %} {% if sort %}data-sortable="true" {% endif %}
data-visible="{{visiblility.get(parameter)}}" data-visible="{{visiblility.get(parameter)}}"
data-formatter="bookCheckboxFormatter"> data-formatter="bookCheckboxFormatter">
{% if parameter == "is_archived" %} {% if parameter == "is_archived" %}
<div class="btn btn-default disabled" id="archive_selected_books" aria-disabled="true"> <div class="form-check">
{{_('Archive selected books')}} <div>
<input type="radio" class="check_head" data-set="True" data-val="{{ parameter }}" name="options_archive_selected_books" id="false_archive_selected_books" data-name="archive_books" disabled>{{_('Archive selected books')}}
</div>
<div>
<input type="radio" class="check_head" data-set="False" data-val="{{ parameter }}" name="options_unarchive_selected_books" data-name="archive_books" disabled>{{_('Unarchive selected books')}}
</div> </div>
<br>
<div class="btn btn-default disabled" id="unarchive_selected_books" aria-disabled="true">
{{_('Unarchive selected books')}}
</div> </div>
<br>
{% elif parameter == "read_status" %} {% elif parameter == "read_status" %}
<div class="btn btn-default disabled" id="read_selected_books" aria-disabled="true"> <div class="form-check">
{{_('Mark selected books as read')}} <div>
<input type="radio" class="check_head" data-set="True" data-val="{{ parameter }}" name="options_read_selected_books" id="false_read_selected_books" data-name="read_books" disabled>{{_('Mark selected books as read')}}
</div>
<div>
<input type="radio" class="check_head" data-set="False" data-val="{{ parameter }}" name="options_unread_selected_books" data-name="read_books" disabled>{{_('Mark selected books as unread')}}</div>
</div>
</div> </div>
<br>
<div class="btn btn-default disabled" id="unread_selected_books" aria-disabled="true">
{{_('Mark selected books as unread')}}</div>
<br>
{% endif %} {% endif %}
{{show_text}} {{show_text}}
</th> </th>
@@ -55,7 +56,7 @@
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true"> <div class="btn btn-default disabled" id="merge_books" aria-disabled="true">
{{_('Merge selected books')}} {{_('Merge selected books')}}
</div> </div>
<div class="btn btn-default disabled" id="delete_selection" aria-disabled="true"> <div class="btn btn-default disabled mass_selection" id="book_delete_selection" aria-disabled="true">
{{_('Clear selections')}} {{_('Clear selections')}}
</div> </div>
<div class="btn btn-default disabled" id="edit_selected_books" aria-disabled="true"> <div class="btn btn-default disabled" id="edit_selected_books" aria-disabled="true">
@@ -82,7 +83,7 @@
<thead> <thead>
<tr> <tr>
{% if current_user.role_edit() %} {% if current_user.role_edit() %}
<th data-field="state" data-checkbox="true" data-sortable="true"></th> <th data-field="state" data-checkbox="true" data-visible="true" data-sortable="true"></th>
{% endif %} {% endif %}
<th data-field="id" id="id" data-visible="false" data-switchable="false"></th> <th data-field="id" id="id" data-visible="false" data-switchable="false"></th>
{{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }} {{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }}
@@ -123,9 +124,9 @@
{% endfor %} {% endfor %}
{% if current_user.role_delete_books() and current_user.role_edit()%} {% if current_user.role_delete_books() and current_user.role_edit()%}
<th data-align="right" data-formatter="EbookActions" data-switchable="false"> <th data-align="right" data-formatter="EbookActions" data-switchable="false">
<div class="btn btn-default disabled" id="delete_selected_books" aria-disabled="true"> <div><div class="btn btn-default button_head disabled" aria-disabled="true">
{{_('Delete selected books')}} {{_('Delete selected books')}}
</div> </div></div>
<br> <br>
{{_('Delete')}} {{_('Delete')}}
</th> </th>
@@ -137,6 +138,8 @@
{% endblock %} {% endblock %}
{% block modal %} {% block modal %}
{{ delete_book(current_user.role_delete_books()) }} {{ delete_book(current_user.role_delete_books()) }}
{{ delete_confirm_modal() }}
{{ change_confirm_modal() }}
{% if current_user.role_edit() %} {% if current_user.role_edit() %}
<div class="modal fade" id="mergeModal" role="dialog" aria-labelledby="metaMergeLabel"> <div class="modal fade" id="mergeModal" role="dialog" aria-labelledby="metaMergeLabel">
<div class="modal-dialog"> <div class="modal-dialog">
@@ -162,7 +165,7 @@
</div> </div>
</div> </div>
<div class="modal fade" id="delete_selected_modal" role="dialog" aria-labelledby="metaDeleteSelectedLabel"> <!--div class="modal fade" id="delete_selected_modal" role="dialog" aria-labelledby="metaDeleteSelectedLabel">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header bg-danger text-center"> <div class="modal-header bg-danger text-center">
@@ -180,7 +183,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div-->
<div class="modal fade" id="archive_selected_modal" role="dialog" aria-labelledby="metaArchiveSelectedLabel"> <div class="modal fade" id="archive_selected_modal" role="dialog" aria-labelledby="metaArchiveSelectedLabel">
<div class="modal-dialog"> <div class="modal-dialog">
@@ -265,7 +268,7 @@
<div class="modal fade" id="edit_selected_modal" role="dialog" aria-labelledby="metaEditSelectedLabel"> <div class="modal fade" id="edit_selected_modal" role="dialog" aria-labelledby="metaEditSelectedLabel">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header text-center"> <div class="modal-header bg-info text-center">
<span>{{_('Edit Metadata')}}</span> <span>{{_('Edit Metadata')}}</span>
</div> </div>
<div class="modal-body"> <div class="modal-body">

View File

@@ -121,7 +121,7 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-12"> <div class="col-xs-12 col-sm-12">
<div class="row"> <div class="row">
<div class="btn btn-default disabled" id="user_delete_selection" aria-disabled="true">{{_('Remove Selections')}}</div> <div class="btn btn-default disabled mass_selection" id="user_delete_selection" aria-disabled="true">{{_('Clear selections')}}</div>
</div> </div>
</div> </div>
<table id="user-table" class="table table-no-bordered table-striped" <table id="user-table" class="table table-no-bordered table-striped"

View File

@@ -71,13 +71,13 @@ content-type = "text/markdown"
gdrive = [ gdrive = [
"google-api-python-client>=1.7.11,<2.200.0", "google-api-python-client>=1.7.11,<2.200.0",
"gevent>20.6.0,<24.3.0", "gevent>20.6.0,<24.3.0",
"greenlet>=0.4.17,<3.1.0", "greenlet>=0.4.17,<3.2.0",
"httplib2>=0.9.2,<0.23.0", "httplib2>=0.9.2,<0.23.0",
"oauth2client>=4.0.0,<4.1.4", "oauth2client>=4.0.0,<4.1.4",
"uritemplate>=3.0.0,<4.2.0", "uritemplate>=3.0.0,<4.2.0",
"pyasn1-modules>=0.0.8,<0.5.0", "pyasn1-modules>=0.0.8,<0.5.0",
"pyasn1>=0.1.9,<0.7.0", "pyasn1>=0.1.9,<0.7.0",
"PyDrive2>=1.3.1,<1.20.0", "PyDrive2>=1.3.1,<1.22.0",
"PyYAML>=3.12,<6.1", "PyYAML>=3.12,<6.1",
"rsa>=3.4.2,<4.10.0", "rsa>=3.4.2,<4.10.0",
] ]

View File

@@ -37,20 +37,20 @@
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
<p class='text-justify attribute'><strong>Start Time: </strong>2024-12-06 17:23:58</p> <p class='text-justify attribute'><strong>Start Time: </strong>2024-12-12 21:32:32</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3"> <div class="col-xs-6 col-md-6 col-sm-offset-3">
<p class='text-justify attribute'><strong>Stop Time: </strong>2024-12-07 00:45:19</p> <p class='text-justify attribute'><strong>Stop Time: </strong>2024-12-13 04:26:53</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3"> <div class="col-xs-6 col-md-6 col-sm-offset-3">
<p class='text-justify attribute'><strong>Duration: </strong>6h 11 min</p> <p class='text-justify attribute'><strong>Duration: </strong>5h 49 min</p>
</div> </div>
</div> </div>
</div> </div>
@@ -2074,11 +2074,11 @@ IndexError: list index out of range</pre>
<tr id="su" class="failClass"> <tr id="su" class="passClass">
<td>TestEditBooksOnGdrive</td> <td>TestEditBooksOnGdrive</td>
<td class="text-center">18</td> <td class="text-center">18</td>
<td class="text-center">16</td> <td class="text-center">18</td>
<td class="text-center">2</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center"> <td class="text-center">
@@ -2205,31 +2205,11 @@ IndexError: list index out of range</pre>
<tr id="ft19.14" class="none bg-danger"> <tr id='pt19.14' class='hiddenRow bg-success'>
<td> <td>
<div class='testcase'>TestEditBooksOnGdrive - test_edit_rating</div> <div class='testcase'>TestEditBooksOnGdrive - test_edit_rating</div>
</td> </td>
<td colspan='6'> <td colspan='6' align='center'>PASS</td>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft19.14')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft19.14" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft19.14').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 632, in test_edit_rating
self.assertEqual(4, values[&#39;rating&#39;])
AssertionError: 4 != 0</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
@@ -2261,31 +2241,11 @@ AssertionError: 4 != 0</pre>
<tr id="ft19.18" class="none bg-danger"> <tr id='pt19.18' class='hiddenRow bg-success'>
<td> <td>
<div class='testcase'>TestEditBooksOnGdrive - test_watch_metadata</div> <div class='testcase'>TestEditBooksOnGdrive - test_watch_metadata</div>
</td> </td>
<td colspan='6'> <td colspan='6' align='center'>PASS</td>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft19.18')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft19.18" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft19.18').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 976, in test_watch_metadata
self.assertNotIn(&#39;series&#39;, book)
AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;reader&#39;: [], &#39;title&#39;: &#39;testbook&#39;, &#39;author&#39;: [&#39;John Döe&#39;], &#39;rating&#39;: 0, &#39;languages&#39;: [&#39;English&#39;], &#39;identifier&#39;: [], &#39;cover&#39;: &#39;/cover/5/og?c=1733511155&#39;, &#39;tag&#39;: [], &#39;publisher&#39;: [&#39;Randomhäus&#39;], &#39;pubdate&#39;: &#39;Jan 19, 2017&#39;, &#39;comment&#39;: &#39;Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Aenean commodo ligula eget dolor.Aenean massa.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.Nulla consequat massa quis enim.Donec pede justo, fringilla vel, aliquet nec, vulputate&#39;, &#39;add_shelf&#39;: [], &#39;del_shelf&#39;: [], &#39;edit_enable&#39;: True, &#39;kindle&#39;: None, &#39;kindlebtn&#39;: None, &#39;download&#39;: [&#39;EPUB\n (6.7 kB)&#39;], &#39;read&#39;: False, &#39;archived&#39;: False, &#39;series_all&#39;: &#39;Book 1 of test&#39;, &#39;series_index&#39;: &#39;1&#39;, &#39;series&#39;: &#39;test&#39;, &#39;cust_columns&#39;: []}</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
@@ -3579,11 +3539,11 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr id="su" class="passClass"> <tr id="su" class="failClass">
<td>TestMergeBooksList</td> <td>TestMergeBooksList</td>
<td class="text-center">2</td> <td class="text-center">2</td>
<td class="text-center">2</td> <td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">1</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center">0</td> <td class="text-center">0</td>
<td class="text-center"> <td class="text-center">
@@ -3602,11 +3562,31 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr id='pt37.2' class='hiddenRow bg-success'> <tr id="ft37.2" class="none bg-danger">
<td> <td>
<div class='testcase'>TestMergeBooksList - test_delete_book</div> <div class='testcase'>TestMergeBooksList - test_delete_book</div>
</td> </td>
<td colspan='6' align='center'>PASS</td> <td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft37.2')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft37.2" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft37.2').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_merge_books_list.py&#34;, line 67, in test_delete_book
self.assertTrue(self.check_element_on_page((By.ID, &#34;flash_warning&#34;)))
AssertionError: False is not true</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
</tr> </tr>
@@ -5841,8 +5821,8 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr id='total_row' class="text-center bg-grey"> <tr id='total_row' class="text-center bg-grey">
<td>Total</td> <td>Total</td>
<td>523</td> <td>523</td>
<td>513</td> <td>514</td>
<td>2</td> <td>1</td>
<td>1</td> <td>1</td>
<td>7</td> <td>7</td>
<td>&nbsp;</td> <td>&nbsp;</td>
@@ -6046,7 +6026,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestBackupMetadataGdrive</td> <td>TestBackupMetadataGdrive</td>
</tr> </tr>
@@ -6076,7 +6056,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestCliGdrivedb</td> <td>TestCliGdrivedb</td>
</tr> </tr>
@@ -6106,7 +6086,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestEbookConvertCalibreGDrive</td> <td>TestEbookConvertCalibreGDrive</td>
</tr> </tr>
@@ -6136,7 +6116,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestEbookConvertGDriveKepubify</td> <td>TestEbookConvertGDriveKepubify</td>
</tr> </tr>
@@ -6184,7 +6164,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestEditAuthorsGdrive</td> <td>TestEditAuthorsGdrive</td>
</tr> </tr>
@@ -6220,7 +6200,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestEditBooksOnGdrive</td> <td>TestEditBooksOnGdrive</td>
</tr> </tr>
@@ -6262,7 +6242,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestEmbedMetadataGdrive</td> <td>TestEmbedMetadataGdrive</td>
</tr> </tr>
@@ -6292,7 +6272,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr> <tr>
<th>google-api-python-client</th> <th>google-api-python-client</th>
<td>2.154.0</td> <td>2.155.0</td>
<td>TestSetupGdrive</td> <td>TestSetupGdrive</td>
</tr> </tr>
@@ -6388,7 +6368,7 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
</div> </div>
<script> <script>
drawCircle(513, 2, 1, 7); drawCircle(514, 1, 1, 7);
showCase(5); showCase(5);
</script> </script>