Update mass edit

Refactored delete User function
Updated testresults
This commit is contained in:
Ozzie Isaacs
2024-12-13 18:03:14 +01:00
parent 89e9958222
commit 08527ae3ce
10 changed files with 473 additions and 372 deletions
+14 -9
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 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":
+217 -204
View File
@@ -73,17 +73,18 @@ def edit_required(f):
return inner
@editbook.route("/ajax/delete/<int:book_id>", 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/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
@editbook.route("/delete/<int:book_id>/<string:book_format>", 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/<int:book_id>", 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)
+8 -10
View File
@@ -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()
+1 -1
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):
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
+5 -3
View File
@@ -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) {
+155 -55
View File
@@ -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: "<span class='glyphicon glyphicon-plus'></span>",
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) {
'</div>');
});
}
$("#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("") +
}
}
+23 -20
View File
@@ -15,27 +15,28 @@
{%- endmacro %}
{% 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 %}
data-visible="{{visiblility.get(parameter)}}"
data-formatter="bookCheckboxFormatter">
{% if parameter == "is_archived" %}
<div class="btn btn-default disabled" id="archive_selected_books" aria-disabled="true">
{{_('Archive selected books')}}
<div class="form-check">
<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>
<br>
<div class="btn btn-default disabled" id="unarchive_selected_books" aria-disabled="true">
{{_('Unarchive selected books')}}
<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>
<br>
</div>
{% elif parameter == "read_status" %}
<div class="btn btn-default disabled" id="read_selected_books" aria-disabled="true">
{{_('Mark selected books as read')}}
<div class="form-check">
<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>
<br>
<div class="btn btn-default disabled" id="unread_selected_books" aria-disabled="true">
{{_('Mark selected books as unread')}}</div>
<br>
<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>
{% endif %}
{{show_text}}
</th>
@@ -55,7 +56,7 @@
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">
{{_('Merge selected books')}}
</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')}}
</div>
<div class="btn btn-default disabled" id="edit_selected_books" aria-disabled="true">
@@ -82,7 +83,7 @@
<thead>
<tr>
{% 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 %}
<th data-field="id" id="id" data-visible="false" data-switchable="false"></th>
{{ 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()%}
<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')}}
</div>
</div></div>
<br>
{{_('Delete')}}
</th>
@@ -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() %}
<div class="modal fade" id="mergeModal" role="dialog" aria-labelledby="metaMergeLabel">
<div class="modal-dialog">
@@ -162,7 +165,7 @@
</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-content">
<div class="modal-header bg-danger text-center">
@@ -180,7 +183,7 @@
</div>
</div>
</div>
</div>
</div-->
<div class="modal fade" id="archive_selected_modal" role="dialog" aria-labelledby="metaArchiveSelectedLabel">
<div class="modal-dialog">
@@ -265,7 +268,7 @@
<div class="modal fade" id="edit_selected_modal" role="dialog" aria-labelledby="metaEditSelectedLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-center">
<div class="modal-header bg-info text-center">
<span>{{_('Edit Metadata')}}</span>
</div>
<div class="modal-body">
+2 -2
View File
@@ -53,7 +53,7 @@
data-visible="{{element.get(array_field)}}"
data-column="{{value.get(array_field)}}"
data-formatter="checkboxFormatter">
<div class="form-check">
<div class="form-check">
<div>
<input type="radio" class="check_head" data-set="false" data-val="{{value.get(array_field)}}" name="options_{{array_field}}" id="false_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
</div>
@@ -121,7 +121,7 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-12">
<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>
<table id="user-table" class="table table-no-bordered table-striped"