mirror of
https://github.com/janeczku/calibre-web
synced 2025-01-17 20:52:57 +00:00
Added logging of ip address (#3237)
Refactored Response(json.dumps -> make_response(jsonify..) Update mimetypes - Allow different mimetypes for download and file upload check (#3245, #3243) Bugfixes from tests Updated optional-requirements
This commit is contained in:
parent
14e57e3714
commit
42924d9508
@ -55,13 +55,13 @@ mimetypes.init()
|
||||
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
||||
mimetypes.add_type('application/epub+zip', '.epub')
|
||||
mimetypes.add_type('application/epub+zip', '.kepub')
|
||||
mimetypes.add_type('text/xml', '.fb2')
|
||||
mimetypes.add_type('application/fb2+zip', '.fb2')
|
||||
mimetypes.add_type('application/octet-stream', '.mobi')
|
||||
mimetypes.add_type('application/octet-stream', '.prc')
|
||||
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
|
||||
mimetypes.add_type('application/x-mobi8-ebook', '.azw3')
|
||||
mimetypes.add_type('application/x-rar', '.cbr')
|
||||
mimetypes.add_type('application/zip', '.cbz')
|
||||
mimetypes.add_type('application/x-cbr', '.cbr')
|
||||
mimetypes.add_type('application/x-cbz', '.cbz')
|
||||
mimetypes.add_type('application/x-tar', '.cbt')
|
||||
mimetypes.add_type('application/x-7z-compressed', '.cb7')
|
||||
mimetypes.add_type('image/vnd.djv', '.djv')
|
||||
|
24
cps/admin.py
24
cps/admin.py
@ -32,7 +32,8 @@ from datetime import time as datetime_time
|
||||
from functools import wraps
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
|
||||
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, \
|
||||
send_from_directory, g, jsonify
|
||||
from markupsafe import Markup
|
||||
from .cw_login import current_user
|
||||
from flask_babel import gettext as _
|
||||
@ -378,10 +379,7 @@ def list_users():
|
||||
user.default = get_user_locale_language(user.default_language)
|
||||
|
||||
table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": users}
|
||||
js_list = json.dumps(table_entries, cls=db.AlchemyEncoder)
|
||||
response = make_response(js_list)
|
||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
return make_response(json.dumps(table_entries, cls=db.AlchemyEncoder))
|
||||
|
||||
|
||||
@admi.route("/ajax/deleteuser", methods=['POST'])
|
||||
@ -400,7 +398,7 @@ def delete_user():
|
||||
success = list()
|
||||
if not users:
|
||||
log.error("User not found")
|
||||
return Response(json.dumps({'type': "danger", 'message': _("User not found")}), mimetype='application/json')
|
||||
return make_response(jsonify(type="danger", message=_("User not found")))
|
||||
for user in users:
|
||||
try:
|
||||
message = _delete_user(user)
|
||||
@ -416,7 +414,7 @@ def delete_user():
|
||||
log.info("Users {} deleted".format(user_ids))
|
||||
success = [{'type': "success", 'message': _("{} users deleted successfully").format(count)}]
|
||||
success.extend(errors)
|
||||
return Response(json.dumps(success), mimetype='application/json')
|
||||
return make_response(jsonify(success))
|
||||
|
||||
|
||||
@admi.route("/ajax/getlocale")
|
||||
@ -498,10 +496,10 @@ def edit_list_user(param):
|
||||
if not ub.session.query(ub.User). \
|
||||
filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||
ub.User.id != user.id).count():
|
||||
return Response(
|
||||
json.dumps([{'type': "danger",
|
||||
return make_response(
|
||||
jsonify([{'type': "danger",
|
||||
'message': _("No admin user remaining, can't remove admin role",
|
||||
nick=user.name)}]), mimetype='application/json')
|
||||
nick=user.name)}]))
|
||||
user.role &= ~value
|
||||
else:
|
||||
raise Exception(_("Value has to be true or false"))
|
||||
@ -947,7 +945,7 @@ def do_full_kobo_sync(userid):
|
||||
count = ub.session.query(ub.KoboSyncedBooks).filter(userid == ub.KoboSyncedBooks.user_id).delete()
|
||||
message = _("{} sync entries deleted").format(count)
|
||||
ub.session_commit(message)
|
||||
return Response(json.dumps([{"type": "success", "message": message}]), mimetype='application/json')
|
||||
return make_response(jsonify(type="success", message=message))
|
||||
|
||||
|
||||
def check_valid_read_column(column):
|
||||
@ -1264,7 +1262,7 @@ def _configuration_ldap_helper(to_save):
|
||||
@admin_required
|
||||
def simulatedbchange():
|
||||
db_change, db_valid = _db_simulate_change()
|
||||
return Response(json.dumps({"change": db_change, "valid": db_valid}), mimetype='application/json')
|
||||
return make_response(jsonify(change=db_change, valid=db_valid))
|
||||
|
||||
|
||||
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
||||
@ -1896,7 +1894,7 @@ def _configuration_result(error_flash=None, reboot=False):
|
||||
resp['result'] = [{'type': "success", 'message': _("Calibre-Web configuration updated")}]
|
||||
resp['reboot'] = reboot
|
||||
resp['config_upload'] = config.config_upload_formats
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
return make_response(jsonify(resp))
|
||||
|
||||
|
||||
def _db_configuration_result(error_flash=None, gdrive_error=None):
|
||||
|
106
cps/editbooks.py
106
cps/editbooks.py
@ -28,11 +28,11 @@ from shutil import copyfile
|
||||
from markupsafe import escape, Markup # dependency of flask
|
||||
from functools import wraps
|
||||
|
||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Response
|
||||
from flask import Blueprint, request, flash, redirect, url_for, abort, jsonify, make_response, Response
|
||||
from flask_babel import gettext as _
|
||||
from flask_babel import lazy_gettext as N_
|
||||
from flask_babel import get_locale
|
||||
from .cw_login import current_user, login_required
|
||||
from .cw_login import current_user
|
||||
from sqlalchemy.exc import OperationalError, IntegrityError, InterfaceError
|
||||
from sqlalchemy.orm.exc import StaleDataError
|
||||
from sqlalchemy.sql.expression import func
|
||||
@ -76,7 +76,7 @@ def edit_required(f):
|
||||
@editbook.route("/ajax/delete/<int:book_id>", methods=["POST"])
|
||||
@user_login_required
|
||||
def delete_book_from_details(book_id):
|
||||
return Response(delete_book_from_table(book_id, "", True), mimetype='application/json')
|
||||
return delete_book_from_table(book_id, "", True) # , mimetype='application/json')
|
||||
|
||||
|
||||
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
|
||||
@ -158,16 +158,16 @@ def upload():
|
||||
if len(request.files.getlist("btn-upload")) < 2:
|
||||
if current_user.role_edit() or current_user.role_admin():
|
||||
resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
return make_response(jsonify(resp))
|
||||
else:
|
||||
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
return make_response(jsonify(resp))
|
||||
except (OperationalError, IntegrityError, StaleDataError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
|
||||
category="error")
|
||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
return make_response(jsonify(location=url_for("web.index")))
|
||||
abort(404)
|
||||
|
||||
|
||||
@ -206,7 +206,7 @@ def table_get_custom_enum(c_id):
|
||||
ret.append({'value': "", 'text': ""})
|
||||
for idx, en in enumerate(cc.get_display_dict()['enum_values']):
|
||||
ret.append({'value': en, 'text': en})
|
||||
return json.dumps(ret)
|
||||
return make_response(jsonify(ret))
|
||||
|
||||
|
||||
@editbook.route("/ajax/editbooks/<param>", methods=['POST'])
|
||||
@ -221,68 +221,54 @@ def edit_list_book(param):
|
||||
try:
|
||||
if param == 'series_index':
|
||||
edit_book_series_index(vals['value'], book)
|
||||
ret = Response(json.dumps({'success': True, 'newValue': book.series_index}), mimetype='application/json')
|
||||
ret = make_response(jsonify(success=True, newValue=book.series_index))
|
||||
elif param == 'tags':
|
||||
edit_book_tags(vals['value'], book)
|
||||
ret = Response(json.dumps({'success': True, 'newValue': ', '.join([tag.name for tag in book.tags])}),
|
||||
mimetype='application/json')
|
||||
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 = Response(json.dumps({'success': True, 'newValue': ', '.join([serie.name for serie in book.series])}),
|
||||
mimetype='application/json')
|
||||
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 = Response(json.dumps({'success': True,
|
||||
'newValue': ', '.join([publisher.name for publisher in book.publishers])}),
|
||||
mimetype='application/json')
|
||||
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 = Response(json.dumps({'success': False,
|
||||
'msg': 'Invalid languages in request: {}'.format(','.join(invalid))}),
|
||||
mimetype='application/json')
|
||||
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 = Response(json.dumps({'success': True, 'newValue': ', '.join(lang_names)}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(success=True, newValue=', '.join(lang_names)))
|
||||
elif param == 'author_sort':
|
||||
book.author_sort = vals['value']
|
||||
ret = Response(json.dumps({'success': True, 'newValue': book.author_sort}),
|
||||
mimetype='application/json')
|
||||
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 = Response(json.dumps({'success': True, 'newValue': book.title}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(success=True, newValue=book.title))
|
||||
else:
|
||||
ret = Response(json.dumps({'success': False,
|
||||
'msg': rename_error}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(success=False, msg=rename_error))
|
||||
elif param == 'sort':
|
||||
book.sort = vals['value']
|
||||
ret = Response(json.dumps({'success': True, 'newValue': book.sort}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(success=True,newValue=book.sort))
|
||||
elif param == 'comments':
|
||||
edit_book_comments(vals['value'], book)
|
||||
ret = Response(json.dumps({'success': True, 'newValue': book.comments[0].text}),
|
||||
mimetype='application/json')
|
||||
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 = Response(json.dumps({
|
||||
'success': True,
|
||||
'newValue': ' & '.join([author.replace('|', ',') for author in input_authors])}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(
|
||||
success=True,
|
||||
newValue=' & '.join([author.replace('|', ',') for author in input_authors])))
|
||||
else:
|
||||
ret = Response(json.dumps({'success': False,
|
||||
'msg': rename_error}),
|
||||
mimetype='application/json')
|
||||
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']))
|
||||
@ -301,8 +287,7 @@ def edit_list_book(param):
|
||||
if vals['value'] in ["True", "False"]:
|
||||
ret = ""
|
||||
else:
|
||||
ret = Response(json.dumps({'success': True, 'newValue': vals['value']}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(success=True, newValue=vals['value']))
|
||||
else:
|
||||
return _("Parameter not found"), 400
|
||||
book.last_modified = datetime.now(timezone.utc)
|
||||
@ -315,9 +300,8 @@ def edit_list_book(param):
|
||||
except (OperationalError, IntegrityError, StaleDataError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
ret = Response(json.dumps({'success': False,
|
||||
'msg': 'Database error: {}'.format(e.orig if hasattr(e, "orig") else e)}),
|
||||
mimetype='application/json')
|
||||
ret = make_response(jsonify(success=False,
|
||||
msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e)))
|
||||
return ret
|
||||
|
||||
|
||||
@ -328,13 +312,13 @@ def get_sorted_entry(field, bookid):
|
||||
book = calibre_db.get_filtered_book(bookid)
|
||||
if book:
|
||||
if field == 'title':
|
||||
return json.dumps({'sort': book.sort})
|
||||
return make_response(jsonify(sort=book.sort))
|
||||
elif field == 'authors':
|
||||
return json.dumps({'author_sort': book.author_sort})
|
||||
return make_response(jsonify(author_sort=book.author_sort))
|
||||
if field == 'sort':
|
||||
return json.dumps({'sort': book.title})
|
||||
return make_response(jsonify(sort=book.title))
|
||||
if field == 'author_sort':
|
||||
return json.dumps({'authors': " & ".join([a.name for a in calibre_db.order_authors([book])])})
|
||||
return make_response(jsonify(authors=" & ".join([a.name for a in calibre_db.order_authors([book])])))
|
||||
return ""
|
||||
|
||||
|
||||
@ -350,7 +334,7 @@ def simulate_merge_list_book():
|
||||
from_book = []
|
||||
for book_id in vals:
|
||||
from_book.append(calibre_db.get_book(book_id).title)
|
||||
return json.dumps({'to': to_book, 'from': from_book})
|
||||
return make_response(jsonify({'to': to_book, 'from': from_book}))
|
||||
return ""
|
||||
|
||||
|
||||
@ -388,7 +372,7 @@ def merge_list_book():
|
||||
element.uncompressed_size,
|
||||
to_name))
|
||||
delete_book_from_table(from_book.id, "", True)
|
||||
return json.dumps({'success': True})
|
||||
return make_response(jsonify(success=True))
|
||||
return ""
|
||||
|
||||
|
||||
@ -428,11 +412,11 @@ def table_xchange_author_title():
|
||||
except (OperationalError, IntegrityError, StaleDataError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
return json.dumps({'success': False})
|
||||
return make_response(jsonify(success=False))
|
||||
|
||||
if config.config_use_google_drive:
|
||||
gdriveutils.updateGdriveCalibreFromLocal()
|
||||
return json.dumps({'success': True})
|
||||
return make_response(jsonify(success=True))
|
||||
return ""
|
||||
|
||||
|
||||
@ -560,7 +544,7 @@ def do_edit_book(book_id, upload_formats=None):
|
||||
|
||||
if upload_formats:
|
||||
resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
return make_response(jsonify(resp))
|
||||
|
||||
if "detail_view" in to_save:
|
||||
return redirect(url_for('web.show_book', book_id=book.id))
|
||||
@ -769,17 +753,17 @@ def file_handling_on_upload(requested_file):
|
||||
if config.config_check_extensions and allowed_extensions != ['']:
|
||||
if not validate_mime_type(requested_file, allowed_extensions):
|
||||
flash(_("File type isn't allowed to be uploaded to this server"), category="error")
|
||||
return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
return None, make_response(jsonify(location=url_for("web.index")))
|
||||
if '.' in requested_file.filename:
|
||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||
if file_ext not in allowed_extensions and '' not in allowed_extensions:
|
||||
flash(
|
||||
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
||||
ext=file_ext), category="error")
|
||||
return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
return None, make_response(jsonify(location=url_for("web.index")))
|
||||
else:
|
||||
flash(_('File to be uploaded must have an extension'), category="error")
|
||||
return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
return None, make_response(jsonify(location=url_for("web.index")))
|
||||
|
||||
# extract metadata from file
|
||||
try:
|
||||
@ -788,7 +772,7 @@ def file_handling_on_upload(requested_file):
|
||||
log.error("File %s could not saved to temp dir", requested_file.filename)
|
||||
flash(_("File %(filename)s could not saved to temp dir",
|
||||
filename=requested_file.filename), category="error")
|
||||
return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||
return None, make_response(jsonify(location=url_for("web.index")))
|
||||
return meta, None
|
||||
|
||||
|
||||
@ -860,7 +844,7 @@ def delete_whole_book(book_id, book):
|
||||
def render_delete_book_result(book_format, json_response, warning, book_id, location=""):
|
||||
if book_format:
|
||||
if json_response:
|
||||
return json.dumps([warning, {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
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')}])
|
||||
@ -869,7 +853,7 @@ def render_delete_book_result(book_format, json_response, warning, book_id, loca
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
else:
|
||||
if json_response:
|
||||
return json.dumps([warning, {"location": get_redirect_location(location, "web.index"),
|
||||
return jsonify([warning, {"location": get_redirect_location(location, "web.index"),
|
||||
"type": "success",
|
||||
"format": book_format,
|
||||
"message": _('Book Successfully Deleted')}])
|
||||
@ -887,7 +871,7 @@ def delete_book_from_table(book_id, book_format, json_response, location=""):
|
||||
result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper())
|
||||
if not result:
|
||||
if json_response:
|
||||
return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": error}])
|
||||
@ -914,7 +898,7 @@ def delete_book_from_table(book_id, book_format, json_response, location=""):
|
||||
log.error_or_exception(ex)
|
||||
calibre_db.session.rollback()
|
||||
if json_response:
|
||||
return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": ex}])
|
||||
@ -928,7 +912,7 @@ def delete_book_from_table(book_id, book_format, json_response, location=""):
|
||||
return render_delete_book_result(book_format, json_response, warning, book_id, location)
|
||||
message = _("You are missing permissions to delete books")
|
||||
if json_response:
|
||||
return json.dumps({"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
return jsonify({"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": message})
|
||||
|
@ -34,6 +34,15 @@ except ImportError as e:
|
||||
error = "Cannot import python-magic, checking uploaded file metadata will not work: {}".format(e)
|
||||
|
||||
|
||||
def get_mimetype(ext):
|
||||
# overwrite some mimetypes for proper file detection
|
||||
mimes = {".fb2": "text/xml",
|
||||
".cbz": "application/zip",
|
||||
".cbr": "application/x-rar"
|
||||
}
|
||||
return mimes.get(ext, mimetypes.types_map[ext])
|
||||
|
||||
|
||||
def get_temp_dir():
|
||||
tmp_dir = os.path.join(gettempdir(), 'calibre_web')
|
||||
if not os.path.isdir(tmp_dir):
|
||||
@ -54,7 +63,7 @@ def validate_mime_type(file_buffer, allowed_extensions):
|
||||
allowed_mimetypes = list()
|
||||
for x in allowed_extensions:
|
||||
try:
|
||||
allowed_mimetypes.append(mimetypes.types_map["." + x])
|
||||
allowed_mimetypes.append(get_mimetype("." + x))
|
||||
except KeyError:
|
||||
log.error("Unkown mimetype for Extension: {}".format(x))
|
||||
tmp_mime_type = mime.from_buffer(file_buffer.read())
|
||||
|
@ -30,7 +30,7 @@ import requests
|
||||
import unidecode
|
||||
from uuid import uuid4
|
||||
|
||||
from flask import send_from_directory, make_response, abort, url_for, Response
|
||||
from flask import send_from_directory, make_response, abort, url_for, Response, request
|
||||
from flask_babel import gettext as _
|
||||
from flask_babel import lazy_gettext as N_
|
||||
from flask_babel import get_locale
|
||||
@ -974,7 +974,8 @@ def do_download_file(book, book_format, client, data, headers):
|
||||
# ToDo Check headers parameter
|
||||
for element in headers:
|
||||
response.headers[element[0]] = element[1]
|
||||
log.info('Downloading file: {}'.format(os.path.join(filename, book_name + "." + book_format)))
|
||||
log.info('Downloading file: \'%s\' by %s - %s', format(os.path.join(filename, book_name + "." + book_format)),
|
||||
current_user.name, request.headers.get('X-Forwarded-For', request.remote_addr))
|
||||
return response
|
||||
|
||||
|
||||
|
@ -21,10 +21,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import json
|
||||
# import json
|
||||
from urllib.parse import unquote_plus
|
||||
|
||||
from flask import Blueprint, request, render_template, make_response, abort, Response, g
|
||||
from flask import Blueprint, request, render_template, make_response, abort, g, jsonify
|
||||
from flask_babel import get_locale
|
||||
from flask_babel import gettext as _
|
||||
|
||||
@ -451,7 +451,7 @@ def get_database_stats():
|
||||
stat['authors'] = calibre_db.session.query(db.Authors).count()
|
||||
stat['categories'] = calibre_db.session.query(db.Tags).count()
|
||||
stat['series'] = calibre_db.session.query(db.Series).count()
|
||||
return Response(json.dumps(stat), mimetype="application/json")
|
||||
return make_response(jsonify(stat))
|
||||
|
||||
|
||||
@opds.route("/opds/thumb_240_240/<book_id>")
|
||||
|
@ -23,7 +23,7 @@ import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from flask import Blueprint, Response, request, url_for
|
||||
from flask import Blueprint, request, url_for, make_response, jsonify
|
||||
from .cw_login import current_user
|
||||
from flask_babel import get_locale
|
||||
from sqlalchemy.exc import InvalidRequestError, OperationalError
|
||||
@ -89,7 +89,7 @@ def metadata_provider():
|
||||
provider.append(
|
||||
{"name": c.__name__, "active": ac, "initial": ac, "id": c.__id__}
|
||||
)
|
||||
return Response(json.dumps(provider), mimetype="application/json")
|
||||
return make_response(jsonify(provider))
|
||||
|
||||
|
||||
@meta.route("/metadata/provider", methods=["POST"])
|
||||
@ -114,9 +114,7 @@ def metadata_change_active_provider(prov_name):
|
||||
provider = next((c for c in cl if c.__id__ == prov_name), None)
|
||||
if provider is not None:
|
||||
data = provider.search(new_state.get("query", ""))
|
||||
return Response(
|
||||
json.dumps([asdict(x) for x in data]), mimetype="application/json"
|
||||
)
|
||||
return make_response(jsonify([asdict(x) for x in data]))
|
||||
return ""
|
||||
|
||||
|
||||
@ -138,4 +136,4 @@ def metadata_search():
|
||||
}
|
||||
for future in concurrent.futures.as_completed(meta):
|
||||
data.extend([asdict(x) for x in future.result() if x])
|
||||
return Response(json.dumps(data), mimetype="application/json")
|
||||
return make_response(jsonify(data))
|
||||
|
@ -210,13 +210,13 @@ class TaskConvert(CalibreTask):
|
||||
else:
|
||||
error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper())
|
||||
local_db.session.close()
|
||||
log.info("ebook converter failed with error while converting book")
|
||||
if not error_message:
|
||||
error_message = N_('Ebook converter failed with unknown error')
|
||||
else:
|
||||
log.error(error_message)
|
||||
self._handleError(error_message)
|
||||
return
|
||||
log.info("ebook converter failed with error while converting book")
|
||||
if not error_message:
|
||||
error_message = N_('Ebook converter failed with unknown error')
|
||||
else:
|
||||
log.error(error_message)
|
||||
self._handleError(error_message)
|
||||
return
|
||||
|
||||
def _convert_kepubify(self, file_path, format_old_ext, format_new_ext):
|
||||
if config.config_embed_metadata and config.config_binariesdir:
|
||||
|
15
cps/web.py
15
cps/web.py
@ -25,8 +25,7 @@ import chardet # dependency of requests
|
||||
import copy
|
||||
from importlib.metadata import metadata
|
||||
|
||||
from flask import Blueprint, jsonify
|
||||
from flask import request, redirect, send_from_directory, make_response, flash, abort, url_for, Response
|
||||
from flask import Blueprint, jsonify, request, redirect, send_from_directory, make_response, flash, abort, url_for
|
||||
from flask import session as flask_session
|
||||
from flask_babel import gettext as _
|
||||
from flask_babel import get_locale
|
||||
@ -1198,13 +1197,14 @@ def serve_book(book_id, book_format, anyname):
|
||||
if not data:
|
||||
return "File not in Database"
|
||||
range_header = request.headers.get('Range', None)
|
||||
|
||||
if not range_header:
|
||||
log.info('Serving book: \'%s\' to %s - %s', data.name, current_user.name,
|
||||
request.headers.get('X-Forwarded-For', request.remote_addr))
|
||||
if config.config_use_google_drive:
|
||||
try:
|
||||
headers = Headers()
|
||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||
if not range_header:
|
||||
log.info('Serving book: %s', data.name)
|
||||
headers['Accept-Ranges'] = 'bytes'
|
||||
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT'))
|
||||
@ -1213,7 +1213,6 @@ def serve_book(book_id, book_format, anyname):
|
||||
return "File Not Found"
|
||||
else:
|
||||
if book_format.upper() == 'TXT':
|
||||
log.info('Serving book: %s', data.name)
|
||||
try:
|
||||
rawdata = open(os.path.join(config.get_book_path(), book.path, data.name + "." + book_format),
|
||||
"rb").read()
|
||||
@ -1234,7 +1233,6 @@ def serve_book(book_id, book_format, anyname):
|
||||
response = make_response(
|
||||
send_from_directory(os.path.join(config.get_book_path(), book.path), data.name + "." + book_format))
|
||||
if not range_header:
|
||||
log.info('Serving book: %s', data.name)
|
||||
response.headers['Accept-Ranges'] = 'bytes'
|
||||
return response
|
||||
|
||||
@ -1253,8 +1251,7 @@ def download_link(book_id, book_format, anyname):
|
||||
@download_required
|
||||
def send_to_ereader(book_id, book_format, convert):
|
||||
if not config.get_mail_server_configured():
|
||||
response = [{'type': "danger", 'message': _("Please configure the SMTP mail settings first...")}]
|
||||
return Response(json.dumps(response), mimetype='application/json')
|
||||
return make_response(jsonify(type="danger", message=_("Please configure the SMTP mail settings first...")))
|
||||
elif current_user.kindle_mail:
|
||||
result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.get_book_path(),
|
||||
current_user.name)
|
||||
@ -1266,7 +1263,7 @@ def send_to_ereader(book_id, book_format, convert):
|
||||
response = [{'type': "danger", 'message': _("Oops! There was an error sending book: %(res)s", res=result)}]
|
||||
else:
|
||||
response = [{'type': "danger", 'message': _("Oops! Please update your profile with a valid eReader Email.")}]
|
||||
return Response(json.dumps(response), mimetype='application/json')
|
||||
return make_response(jsonify(response))
|
||||
|
||||
|
||||
# ################################### Login Logout ##################################################################
|
||||
|
@ -1,13 +1,13 @@
|
||||
# GDrive Integration
|
||||
google-api-python-client>=1.7.11,<2.200.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
|
||||
oauth2client>=4.0.0,<4.1.4
|
||||
uritemplate>=3.0.0,<4.2.0
|
||||
pyasn1-modules>=0.0.8,<0.5.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
|
||||
rsa>=3.4.2,<4.10.0
|
||||
|
||||
|
@ -37,20 +37,20 @@
|
||||
<div class="row">
|
||||
<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-11-29 20:17:45</p>
|
||||
<p class='text-justify attribute'><strong>Start Time: </strong>2024-12-06 17:23:58</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
||||
|
||||
<p class='text-justify attribute'><strong>Stop Time: </strong>2024-11-30 03:36:53</p>
|
||||
<p class='text-justify attribute'><strong>Stop Time: </strong>2024-12-07 00:45:19</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
||||
<p class='text-justify attribute'><strong>Duration: </strong>6h 12 min</p>
|
||||
<p class='text-justify attribute'><strong>Duration: </strong>6h 11 min</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1023,13 +1023,13 @@
|
||||
|
||||
|
||||
|
||||
<tr id="su" class="skipClass">
|
||||
<tr id="su" class="passClass">
|
||||
<td>TestEditAdditionalBooks</td>
|
||||
<td class="text-center">18</td>
|
||||
<td class="text-center">17</td>
|
||||
<td class="text-center">18</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">1</td>
|
||||
<td class="text-center">
|
||||
<a onclick="showClassDetail('c12', 18)">Detail</a>
|
||||
</td>
|
||||
@ -1172,11 +1172,11 @@
|
||||
|
||||
|
||||
|
||||
<tr id='st12.16' class='none bg-warning'>
|
||||
<tr id='pt12.16' class='hiddenRow bg-success'>
|
||||
<td>
|
||||
<div class='testcase'>TestEditAdditionalBooks - test_xss_author_edit</div>
|
||||
</td>
|
||||
<td colspan='6' align='center'>SKIP</td>
|
||||
<td colspan='6' align='center'>PASS</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@ -2074,11 +2074,11 @@ IndexError: list index out of range</pre>
|
||||
|
||||
|
||||
|
||||
<tr id="su" class="passClass">
|
||||
<tr id="su" class="failClass">
|
||||
<td>TestEditBooksOnGdrive</td>
|
||||
<td class="text-center">18</td>
|
||||
<td class="text-center">18</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">16</td>
|
||||
<td class="text-center">2</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">0</td>
|
||||
<td class="text-center">
|
||||
@ -2205,11 +2205,31 @@ IndexError: list index out of range</pre>
|
||||
|
||||
|
||||
|
||||
<tr id='pt19.14' class='hiddenRow bg-success'>
|
||||
<tr id="ft19.14" class="none bg-danger">
|
||||
<td>
|
||||
<div class='testcase'>TestEditBooksOnGdrive - test_edit_rating</div>
|
||||
</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_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">×</span></button>
|
||||
</div>
|
||||
<div class="text-left pull-left">
|
||||
<pre class="text-left">Traceback (most recent call last):
|
||||
File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 632, in test_edit_rating
|
||||
self.assertEqual(4, values['rating'])
|
||||
AssertionError: 4 != 0</pre>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--css div popup end-->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@ -2241,11 +2261,31 @@ IndexError: list index out of range</pre>
|
||||
|
||||
|
||||
|
||||
<tr id='pt19.18' class='hiddenRow bg-success'>
|
||||
<tr id="ft19.18" class="none bg-danger">
|
||||
<td>
|
||||
<div class='testcase'>TestEditBooksOnGdrive - test_watch_metadata</div>
|
||||
</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_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">×</span></button>
|
||||
</div>
|
||||
<div class="text-left pull-left">
|
||||
<pre class="text-left">Traceback (most recent call last):
|
||||
File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 976, in test_watch_metadata
|
||||
self.assertNotIn('series', book)
|
||||
AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': 'testbook', 'author': ['John Döe'], 'rating': 0, 'languages': ['English'], 'identifier': [], 'cover': '/cover/5/og?c=1733511155', 'tag': [], 'publisher': ['Randomhäus'], 'pubdate': 'Jan 19, 2017', 'comment': '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', 'add_shelf': [], 'del_shelf': [], 'edit_enable': True, 'kindle': None, 'kindlebtn': None, 'download': ['EPUB\n (6.7 kB)'], 'read': False, 'archived': False, 'series_all': 'Book 1 of test', 'series_index': '1', 'series': 'test', 'cust_columns': []}</pre>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--css div popup end-->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@ -5801,10 +5841,10 @@ IndexError: list index out of range</pre>
|
||||
<tr id='total_row' class="text-center bg-grey">
|
||||
<td>Total</td>
|
||||
<td>523</td>
|
||||
<td>514</td>
|
||||
<td>0</td>
|
||||
<td>513</td>
|
||||
<td>2</td>
|
||||
<td>1</td>
|
||||
<td>8</td>
|
||||
<td>7</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -6348,7 +6388,7 @@ IndexError: list index out of range</pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
drawCircle(514, 0, 1, 8);
|
||||
drawCircle(513, 2, 1, 7);
|
||||
showCase(5);
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user