mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-28 04:19:59 +00:00
Merge branch 'master' into Develop
This commit is contained in:
commit
b34672ed19
324
cps/admin.py
324
cps/admin.py
@ -31,13 +31,13 @@ from datetime import datetime, timedelta
|
||||
|
||||
from babel import Locale as LC
|
||||
from babel.dates import format_datetime
|
||||
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g
|
||||
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
|
||||
from flask_login import login_required, current_user, logout_user, confirm_login
|
||||
from flask_babel import gettext as _
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||
from sqlalchemy.sql.expression import func, or_
|
||||
from sqlalchemy.sql.expression import func, or_, text
|
||||
|
||||
from . import constants, logger, helper, services
|
||||
from .cli import filepicker
|
||||
@ -46,7 +46,7 @@ from .helper import check_valid_domain, send_test_mail, reset_password, generate
|
||||
valid_email, check_username
|
||||
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||
from .render_template import render_title_template, get_sidebar_config
|
||||
from . import debug_info
|
||||
from . import debug_info, _BABEL_TRANSLATIONS
|
||||
|
||||
try:
|
||||
from functools import wraps
|
||||
@ -224,11 +224,23 @@ def edit_user_table():
|
||||
languages = calibre_db.speaking_language()
|
||||
translations = babel.list_translations() + [LC('en')]
|
||||
allUser = ub.session.query(ub.User)
|
||||
tags = calibre_db.session.query(db.Tags)\
|
||||
.join(db.books_tags_link)\
|
||||
.join(db.Books)\
|
||||
.filter(calibre_db.common_filters()) \
|
||||
.group_by(text('books_tags_link.tag'))\
|
||||
.order_by(db.Tags.name).all()
|
||||
if config.config_restricted_column:
|
||||
custom_values = calibre_db.session.query(db.cc_classes[config.config_restricted_column]).all()
|
||||
else:
|
||||
custom_values = []
|
||||
if not config.config_anonbrowse:
|
||||
allUser = allUser.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS)
|
||||
|
||||
return render_title_template("user_table.html",
|
||||
users=allUser.all(),
|
||||
tags=tags,
|
||||
custom_values=custom_values,
|
||||
translations=translations,
|
||||
languages=languages,
|
||||
visiblility=visibility,
|
||||
@ -237,26 +249,41 @@ def edit_user_table():
|
||||
title=_(u"Edit Users"),
|
||||
page="usertable")
|
||||
|
||||
|
||||
@admi.route("/ajax/listusers")
|
||||
@login_required
|
||||
@admin_required
|
||||
def list_users():
|
||||
off = request.args.get("offset") or 0
|
||||
limit = request.args.get("limit") or 10
|
||||
off = int(request.args.get("offset") or 0)
|
||||
limit = int(request.args.get("limit") or 10)
|
||||
search = request.args.get("search")
|
||||
sort = request.args.get("sort", "id")
|
||||
order = request.args.get("order", "").lower()
|
||||
state = None
|
||||
if sort == "state":
|
||||
state = json.loads(request.args.get("state", "[]"))
|
||||
|
||||
if sort != "state" and order:
|
||||
order = text(sort + " " + order)
|
||||
elif not state:
|
||||
order = ub.User.id.asc()
|
||||
|
||||
all_user = ub.session.query(ub.User)
|
||||
if not config.config_anonbrowse:
|
||||
all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS)
|
||||
total_count = all_user.count()
|
||||
|
||||
total_count = filtered_count = all_user.count()
|
||||
|
||||
if search:
|
||||
users = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"),
|
||||
all_user = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"),
|
||||
func.lower(ub.User.kindle_mail).ilike("%" + search + "%"),
|
||||
func.lower(ub.User.email).ilike("%" + search + "%")))\
|
||||
.offset(off).limit(limit).all()
|
||||
filtered_count = len(users)
|
||||
func.lower(ub.User.email).ilike("%" + search + "%")))
|
||||
if state:
|
||||
users = calibre_db.get_checkbox_sorted(all_user.all(), state, off, limit, request.args.get("order", "").lower())
|
||||
else:
|
||||
users = all_user.offset(off).limit(limit).all()
|
||||
filtered_count = total_count
|
||||
users = all_user.order_by(order).offset(off).limit(limit).all()
|
||||
if search:
|
||||
filtered_count = len(users)
|
||||
|
||||
for user in users:
|
||||
if user.default_language == "all":
|
||||
@ -270,12 +297,38 @@ def list_users():
|
||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
|
||||
@admi.route("/ajax/deleteuser")
|
||||
@admi.route("/ajax/deleteuser", methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_user():
|
||||
# ToDo User delete check also not last one
|
||||
return ""
|
||||
user_ids = request.form.to_dict(flat=False)
|
||||
users = None
|
||||
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()
|
||||
count = 0
|
||||
errors = list()
|
||||
success = list()
|
||||
if not users:
|
||||
log.error("User not found")
|
||||
return Response(json.dumps({'type': "danger", 'message': _("User not found")}), mimetype='application/json')
|
||||
for user in users:
|
||||
try:
|
||||
message = _delete_user(user)
|
||||
count += 1
|
||||
except Exception as ex:
|
||||
log.error(ex)
|
||||
errors.append({'type': "danger", 'message': str(ex)})
|
||||
|
||||
if count == 1:
|
||||
log.info("User {} deleted".format(user_ids))
|
||||
success = [{'type': "success", 'message': message}]
|
||||
elif count > 1:
|
||||
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')
|
||||
|
||||
@admi.route("/ajax/getlocale")
|
||||
@login_required
|
||||
@ -295,7 +348,7 @@ def table_get_locale():
|
||||
def table_get_default_lang():
|
||||
languages = calibre_db.speaking_language()
|
||||
ret = list()
|
||||
ret.append({'value':'all','text':_('Show All')})
|
||||
ret.append({'value': 'all', 'text': _('Show All')})
|
||||
for lang in languages:
|
||||
ret.append({'value': lang.lang_code, 'text': lang.name})
|
||||
return json.dumps(ret)
|
||||
@ -316,52 +369,89 @@ def edit_list_user(param):
|
||||
if "pk[]" in vals:
|
||||
users = all_user.filter(ub.User.id.in_(vals['pk[]'])).all()
|
||||
else:
|
||||
return ""
|
||||
return _("Malformed request"), 400
|
||||
if 'field_index' in vals:
|
||||
vals['field_index'] = vals['field_index'][0]
|
||||
if 'value' in vals:
|
||||
vals['value'] = vals['value'][0]
|
||||
else:
|
||||
return ""
|
||||
elif not ('value[]' in vals):
|
||||
return _("Malformed request"), 400
|
||||
for user in users:
|
||||
try:
|
||||
vals['value'] = vals['value'].strip()
|
||||
if param == 'name':
|
||||
if user.name == "Guest":
|
||||
raise Exception(_("Guest Name can't be changed"))
|
||||
user.name = check_username(vals['value'])
|
||||
elif param =='email':
|
||||
user.email = check_email(vals['value'])
|
||||
elif param == 'kindle_mail':
|
||||
user.kindle_mail = valid_email(vals['value']) if vals['value'] else ""
|
||||
elif param == 'role':
|
||||
if vals['value'] == 'true':
|
||||
user.role |= int(vals['field_index'])
|
||||
if param in ['denied_tags', 'allowed_tags', 'allowed_column_value', 'denied_column_value']:
|
||||
if 'value[]' in vals:
|
||||
setattr(user, param, prepare_tags(user, vals['action'][0], param, vals['value[]']))
|
||||
else:
|
||||
if int(vals['field_index']) == constants.ROLE_ADMIN:
|
||||
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 _(u"No admin user remaining, can't remove admin role", nick=user.name), 400
|
||||
user.role &= ~int(vals['field_index'])
|
||||
elif param == 'sidebar_view':
|
||||
if vals['value'] == 'true':
|
||||
user.sidebar_view |= int(vals['field_index'])
|
||||
setattr(user, param, vals['value'].strip())
|
||||
else:
|
||||
vals['value'] = vals['value'].strip()
|
||||
if param == 'name':
|
||||
if user.name == "Guest":
|
||||
raise Exception(_("Guest Name can't be changed"))
|
||||
user.name = check_username(vals['value'])
|
||||
elif param =='email':
|
||||
user.email = check_email(vals['value'])
|
||||
elif param == 'kindle_mail':
|
||||
user.kindle_mail = valid_email(vals['value']) if vals['value'] else ""
|
||||
elif param.endswith('role'):
|
||||
value = int(vals['field_index'])
|
||||
if user.name == "Guest" and value in \
|
||||
[constants.ROLE_ADMIN, constants.ROLE_PASSWD, constants.ROLE_EDIT_SHELFS]:
|
||||
raise Exception(_("Guest can't have this role"))
|
||||
# check for valid value, last on checks for power of 2 value
|
||||
if value > 0 and value <= constants.ROLE_VIEWER and (value & value-1 == 0 or value == 1):
|
||||
if vals['value'] == 'true':
|
||||
user.role |= value
|
||||
elif vals['value'] == 'false':
|
||||
if value == constants.ROLE_ADMIN:
|
||||
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",
|
||||
'message':_(u"No admin user remaining, can't remove admin role",
|
||||
nick=user.name)}]), mimetype='application/json')
|
||||
user.role &= ~value
|
||||
else:
|
||||
raise Exception(_("Value has to be true or false"))
|
||||
else:
|
||||
raise Exception(_("Invalid role"))
|
||||
elif param.startswith('sidebar'):
|
||||
value = int(vals['field_index'])
|
||||
if user.name == "Guest" and value == constants.SIDEBAR_READ_AND_UNREAD:
|
||||
raise Exception(_("Guest can't have this view"))
|
||||
# check for valid value, last on checks for power of 2 value
|
||||
if value > 0 and value <= constants.SIDEBAR_LIST and (value & value-1 == 0 or value == 1):
|
||||
if vals['value'] == 'true':
|
||||
user.sidebar_view |= value
|
||||
elif vals['value'] == 'false':
|
||||
user.sidebar_view &= ~value
|
||||
else:
|
||||
raise Exception(_("Value has to be true or false"))
|
||||
else:
|
||||
raise Exception(_("Invalid view"))
|
||||
elif param == 'locale':
|
||||
if user.name == "Guest":
|
||||
raise Exception(_("Guest's Locale is determined automatically and can't be set"))
|
||||
if vals['value'] in _BABEL_TRANSLATIONS:
|
||||
user.locale = vals['value']
|
||||
else:
|
||||
raise Exception(_("No Valid Locale Given"))
|
||||
elif param == 'default_language':
|
||||
languages = calibre_db.session.query(db.Languages) \
|
||||
.join(db.books_languages_link) \
|
||||
.join(db.Books) \
|
||||
.filter(calibre_db.common_filters()) \
|
||||
.group_by(text('books_languages_link.lang_code')).all()
|
||||
lang_codes = [lang.lang_code for lang in languages] + ["all"]
|
||||
if vals['value'] in lang_codes:
|
||||
user.default_language = vals['value']
|
||||
else:
|
||||
raise Exception(_("No Valid Book Language Given"))
|
||||
else:
|
||||
user.sidebar_view &= ~int(vals['field_index'])
|
||||
elif param == 'denied_tags':
|
||||
user.denied_tags = vals['value']
|
||||
elif param == 'allowed_tags':
|
||||
user.allowed_tags = vals['value']
|
||||
elif param == 'allowed_column_value':
|
||||
user.allowed_column_value = vals['value']
|
||||
elif param == 'denied_column_value':
|
||||
user.denied_column_value = vals['value']
|
||||
elif param == 'locale':
|
||||
user.locale = vals['value']
|
||||
elif param == 'default_language':
|
||||
user.default_language = vals['value']
|
||||
return _("Parameter not found"), 400
|
||||
except Exception as ex:
|
||||
log.debug_or_exception(ex)
|
||||
return str(ex), 400
|
||||
ub.session_commit()
|
||||
return ""
|
||||
@ -383,6 +473,21 @@ def update_table_settings():
|
||||
return "Invalid request", 400
|
||||
return ""
|
||||
|
||||
def check_valid_read_column(column):
|
||||
if column != "0":
|
||||
if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
|
||||
.filter(and_(db.Custom_Columns.datatype == 'bool', db.Custom_Columns.mark_for_delete == 0)).all():
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_valid_restricted_column(column):
|
||||
if column != "0":
|
||||
if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
|
||||
.filter(and_(db.Custom_Columns.datatype == 'text', db.Custom_Columns.mark_for_delete == 0)).all():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@admi.route("/admin/viewconfig", methods=["POST"])
|
||||
@login_required
|
||||
@ -398,12 +503,23 @@ def update_view_configuration():
|
||||
if _config_string("config_title_regex"):
|
||||
calibre_db.update_title_sort(config)
|
||||
|
||||
if not check_valid_read_column(to_save.get("config_read_column", "0")):
|
||||
flash(_(u"Invalid Read Column"), category="error")
|
||||
log.debug("Invalid Read column")
|
||||
return view_configuration()
|
||||
_config_int("config_read_column")
|
||||
|
||||
if not check_valid_restricted_column(to_save.get("config_restricted_column", "0")):
|
||||
flash(_(u"Invalid Restricted Column"), category="error")
|
||||
log.debug("Invalid Restricted Column")
|
||||
return view_configuration()
|
||||
_config_int("config_restricted_column")
|
||||
|
||||
_config_int("config_theme")
|
||||
_config_int("config_random_books")
|
||||
_config_int("config_books_per_page")
|
||||
_config_int("config_authors_max")
|
||||
_config_int("config_restricted_column")
|
||||
|
||||
|
||||
config.config_default_role = constants.selected_roles(to_save)
|
||||
config.config_default_role &= ~constants.ROLE_ANONYMOUS
|
||||
@ -414,6 +530,7 @@ def update_view_configuration():
|
||||
|
||||
config.save()
|
||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||
log.debug("Calibre-Web configuration updated")
|
||||
before_request()
|
||||
|
||||
return view_configuration()
|
||||
@ -437,6 +554,8 @@ 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 == "restrictions":
|
||||
texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?')
|
||||
elif element_id == "sidebar_view":
|
||||
texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?')
|
||||
return json.dumps(texts)
|
||||
@ -583,6 +702,26 @@ def restriction_deletion(element, list_func):
|
||||
return ','.join(elementlist)
|
||||
|
||||
|
||||
def prepare_tags(user, action, tags_name, id_list):
|
||||
if "tags" in tags_name:
|
||||
tags = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(id_list)).all()
|
||||
if not tags:
|
||||
raise Exception(_("Tag not found"))
|
||||
new_tags_list = [x.name for x in tags]
|
||||
else:
|
||||
tags = calibre_db.session.query(db.cc_classes[config.config_restricted_column])\
|
||||
.filter(db.cc_classes[config.config_restricted_column].id.in_(id_list)).all()
|
||||
new_tags_list = [x.value for x in tags]
|
||||
saved_tags_list = user.__dict__[tags_name].split(",") if len(user.__dict__[tags_name]) else []
|
||||
if action == "remove":
|
||||
saved_tags_list = [x for x in saved_tags_list if x not in new_tags_list]
|
||||
elif action == "add":
|
||||
saved_tags_list.extend(x for x in new_tags_list if x not in saved_tags_list)
|
||||
else:
|
||||
raise Exception(_("Invalid Action"))
|
||||
return ",".join(saved_tags_list)
|
||||
|
||||
|
||||
@admi.route("/ajax/addrestriction/<int:res_type>", defaults={"user_id": 0}, methods=['POST'])
|
||||
@admi.route("/ajax/addrestriction/<int:res_type>/<int:user_id>", methods=['POST'])
|
||||
@login_required
|
||||
@ -610,10 +749,10 @@ def add_restriction(res_type, user_id):
|
||||
usr = current_user
|
||||
if 'submit_allow' in element:
|
||||
usr.allowed_tags = restriction_addition(element, usr.list_allowed_tags)
|
||||
ub.session_commit("Changed allowed tags of user {} to {}".format(usr.name, usr.list_allowed_tags))
|
||||
ub.session_commit("Changed allowed tags of user {} to {}".format(usr.name, usr.list_allowed_tags()))
|
||||
elif 'submit_deny' in element:
|
||||
usr.denied_tags = restriction_addition(element, usr.list_denied_tags)
|
||||
ub.session_commit("Changed denied tags of user {} to {}".format(usr.name, usr.list_denied_tags))
|
||||
ub.session_commit("Changed denied tags of user {} to {}".format(usr.name, usr.list_denied_tags()))
|
||||
if res_type == 3: # CustomC per user
|
||||
if isinstance(user_id, int):
|
||||
usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first()
|
||||
@ -622,11 +761,11 @@ def add_restriction(res_type, user_id):
|
||||
if 'submit_allow' in element:
|
||||
usr.allowed_column_value = restriction_addition(element, usr.list_allowed_column_values)
|
||||
ub.session_commit("Changed allowed columns of user {} to {}".format(usr.name,
|
||||
usr.list_allowed_column_values))
|
||||
usr.list_allowed_column_values()))
|
||||
elif 'submit_deny' in element:
|
||||
usr.denied_column_value = restriction_addition(element, usr.list_denied_column_values)
|
||||
ub.session_commit("Changed denied columns of user {} to {}".format(usr.name,
|
||||
usr.list_denied_column_values))
|
||||
usr.list_denied_column_values()))
|
||||
return ""
|
||||
|
||||
|
||||
@ -824,6 +963,7 @@ def pathchooser():
|
||||
def basic_configuration():
|
||||
logout_user()
|
||||
if request.method == "POST":
|
||||
log.debug("Basic Configuration send")
|
||||
return _configuration_update_helper(configured=filepicker)
|
||||
return _configuration_result(configured=filepicker)
|
||||
|
||||
@ -862,7 +1002,10 @@ def _configuration_gdrive_helper(to_save):
|
||||
)
|
||||
|
||||
# always show google drive settings, but in case of error deny support
|
||||
config.config_use_google_drive = (not gdrive_error) and ("config_use_google_drive" in to_save)
|
||||
new_gdrive_value = (not gdrive_error) and ("config_use_google_drive" in to_save)
|
||||
if config.config_use_google_drive and not new_gdrive_value:
|
||||
config.config_google_drive_watch_changes_response = {}
|
||||
config.config_use_google_drive = new_gdrive_value
|
||||
if _config_string(to_save, "config_google_drive_folder"):
|
||||
gdriveutils.deleteDatabaseOnChange()
|
||||
return gdrive_error
|
||||
@ -1079,7 +1222,8 @@ def _configuration_update_helper(configured):
|
||||
return _configuration_result(unrar_status, gdrive_error, configured)
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
_configuration_result(_(u"Settings DB is not Writeable"), gdrive_error, configured)
|
||||
log.error("Settings DB is not Writeable")
|
||||
_configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured)
|
||||
|
||||
try:
|
||||
metadata_db = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||
@ -1111,15 +1255,16 @@ def _configuration_result(error_flash=None, gdrive_error=None, configured=True):
|
||||
if gdrive_error is None:
|
||||
gdrive_error = gdriveutils.get_error_text()
|
||||
if gdrive_error:
|
||||
log.error(gdrive_error)
|
||||
gdrive_error = _(gdrive_error)
|
||||
else:
|
||||
# if config.config_use_google_drive and\
|
||||
if not gdrive_authenticate and gdrive_support:
|
||||
gdrivefolders = gdriveutils.listRootFolders()
|
||||
|
||||
show_back_button = current_user.is_authenticated
|
||||
show_login_button = config.db_configured and not current_user.is_authenticated
|
||||
if error_flash:
|
||||
log.error(error_flash)
|
||||
config.load()
|
||||
flash(error_flash, category="error")
|
||||
show_login_button = False
|
||||
@ -1172,30 +1317,46 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
|
||||
ub.session.add(content)
|
||||
ub.session.commit()
|
||||
flash(_(u"User '%(user)s' created", user=content.name), category="success")
|
||||
log.debug("User {} created".format(content.name))
|
||||
return redirect(url_for('admin.admin'))
|
||||
except IntegrityError:
|
||||
ub.session.rollback()
|
||||
flash(_(u"Found an existing account for this e-mail address or name."), category="error")
|
||||
log.error("Found an existing account for {} or {}".format(content.name, content.email))
|
||||
flash(_("Found an existing account for this e-mail address or name."), category="error")
|
||||
except OperationalError:
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
|
||||
def _delete_user(content):
|
||||
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||
ub.User.id != content.id).count():
|
||||
if content.name != "Guest":
|
||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||
ub.session_commit()
|
||||
log.info(u"User {} deleted".format(content.name))
|
||||
return(_(u"User '%(nick)s' deleted", nick=content.name))
|
||||
else:
|
||||
log.warning(_(u"Can't delete Guest User"))
|
||||
raise Exception(_(u"Can't delete Guest User"))
|
||||
else:
|
||||
log.warning(u"No admin user remaining, can't delete user")
|
||||
raise Exception(_(u"No admin user remaining, can't delete user"))
|
||||
|
||||
|
||||
def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||
if to_save.get("delete"):
|
||||
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||
ub.User.id != content.id).count():
|
||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||
ub.session_commit()
|
||||
flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success")
|
||||
return redirect(url_for('admin.admin'))
|
||||
else:
|
||||
flash(_(u"No admin user remaining, can't delete user", nick=content.name), category="error")
|
||||
return redirect(url_for('admin.admin'))
|
||||
try:
|
||||
flash(_delete_user(content), category="success")
|
||||
except Exception as ex:
|
||||
log.error(ex)
|
||||
flash(str(ex), category="error")
|
||||
return redirect(url_for('admin.admin'))
|
||||
else:
|
||||
if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||
ub.User.id != content.id).count() and 'admin_role' not in to_save:
|
||||
flash(_(u"No admin user remaining, can't remove admin role", nick=content.name), category="error")
|
||||
log.warning("No admin user remaining, can't remove admin role from {}".format(content.name))
|
||||
flash(_("No admin user remaining, can't remove admin role"), category="error")
|
||||
return redirect(url_for('admin.admin'))
|
||||
if to_save.get("password"):
|
||||
content.password = generate_password_hash(to_save["password"])
|
||||
@ -1235,6 +1396,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||
if to_save.get("kindle_mail") != content.kindle_mail:
|
||||
content.kindle_mail = valid_email(to_save["kindle_mail"]) if to_save["kindle_mail"] else ""
|
||||
except Exception as ex:
|
||||
log.error(ex)
|
||||
flash(str(ex), category="error")
|
||||
return render_title_template("user_edit.html",
|
||||
translations=translations,
|
||||
@ -1249,12 +1411,15 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||
try:
|
||||
ub.session_commit()
|
||||
flash(_(u"User '%(nick)s' updated", nick=content.name), category="success")
|
||||
except IntegrityError:
|
||||
except IntegrityError as ex:
|
||||
ub.session.rollback()
|
||||
flash(_(u"An unknown error occured."), category="error")
|
||||
log.error("An unknown error occurred while changing user: {}".format(str(ex)))
|
||||
flash(_(u"An unknown error occurred."), category="error")
|
||||
except OperationalError:
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
return ""
|
||||
|
||||
|
||||
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
||||
@ -1318,7 +1483,8 @@ def update_mailsettings():
|
||||
config.save()
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
return edit_mailsettings()
|
||||
|
||||
if to_save.get("test"):
|
||||
@ -1350,7 +1516,9 @@ def edit_user(user_id):
|
||||
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
||||
if request.method == "POST":
|
||||
to_save = request.form.to_dict()
|
||||
_handle_edit_user(to_save, content, languages, translations, kobo_support)
|
||||
resp = _handle_edit_user(to_save, content, languages, translations, kobo_support)
|
||||
if resp:
|
||||
return resp
|
||||
return render_title_template("user_edit.html",
|
||||
translations=translations,
|
||||
languages=languages,
|
||||
|
24
cps/cli.py
24
cps/cli.py
@ -71,7 +71,7 @@ if args.c:
|
||||
if os.path.isfile(args.c):
|
||||
certfilepath = args.c
|
||||
else:
|
||||
print("Certfilepath is invalid. Exiting...")
|
||||
print("Certfile path is invalid. Exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
if args.c == "":
|
||||
@ -81,7 +81,7 @@ if args.k:
|
||||
if os.path.isfile(args.k):
|
||||
keyfilepath = args.k
|
||||
else:
|
||||
print("Keyfilepath is invalid. Exiting...")
|
||||
print("Keyfile path is invalid. Exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
if (args.k and not args.c) or (not args.k and args.c):
|
||||
@ -91,29 +91,29 @@ if (args.k and not args.c) or (not args.k and args.c):
|
||||
if args.k == "":
|
||||
keyfilepath = ""
|
||||
|
||||
# handle and check ipadress argument
|
||||
ipadress = args.i or None
|
||||
if ipadress:
|
||||
# handle and check ip address argument
|
||||
ip_address = args.i or None
|
||||
if ip_address:
|
||||
try:
|
||||
# try to parse the given ip address with socket
|
||||
if hasattr(socket, 'inet_pton'):
|
||||
if ':' in ipadress:
|
||||
socket.inet_pton(socket.AF_INET6, ipadress)
|
||||
if ':' in ip_address:
|
||||
socket.inet_pton(socket.AF_INET6, ip_address)
|
||||
else:
|
||||
socket.inet_pton(socket.AF_INET, ipadress)
|
||||
socket.inet_pton(socket.AF_INET, ip_address)
|
||||
else:
|
||||
# on windows python < 3.4, inet_pton is not available
|
||||
# inet_atom only handles IPv4 addresses
|
||||
socket.inet_aton(ipadress)
|
||||
socket.inet_aton(ip_address)
|
||||
except socket.error as err:
|
||||
print(ipadress, ':', err)
|
||||
print(ip_address, ':', err)
|
||||
sys.exit(1)
|
||||
|
||||
# handle and check user password argument
|
||||
user_credentials = args.s or None
|
||||
if user_credentials and ":" not in user_credentials:
|
||||
print("No valid username:password format")
|
||||
print("No valid 'username:password' format")
|
||||
sys.exit(3)
|
||||
|
||||
# Handles enableing of filepicker
|
||||
# Handles enabling of filepicker
|
||||
filepicker = args.f or None
|
||||
|
@ -20,16 +20,18 @@
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
|
||||
from sqlalchemy import Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlalchemy.sql.expression import text
|
||||
try:
|
||||
# Compability with sqlalchemy 2.0
|
||||
# Compatibility with sqlalchemy 2.0
|
||||
from sqlalchemy.orm import declarative_base
|
||||
except ImportError:
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from . import constants, cli, logger, ub
|
||||
from . import constants, cli, logger
|
||||
|
||||
|
||||
log = logger.create()
|
||||
@ -190,7 +192,7 @@ class _ConfigSQL(object):
|
||||
|
||||
@staticmethod
|
||||
def get_config_ipaddress():
|
||||
return cli.ipadress or ""
|
||||
return cli.ip_address or ""
|
||||
|
||||
def _has_role(self, role_flag):
|
||||
return constants.has_flag(self.config_default_role, role_flag)
|
||||
@ -260,7 +262,6 @@ class _ConfigSQL(object):
|
||||
"""
|
||||
new_value = dictionary.get(field, default)
|
||||
if new_value is None:
|
||||
# log.debug("_ConfigSQL set_from_dictionary field '%s' not found", field)
|
||||
return False
|
||||
|
||||
if field not in self.__dict__:
|
||||
@ -277,7 +278,6 @@ class _ConfigSQL(object):
|
||||
if current_value == new_value:
|
||||
return False
|
||||
|
||||
# log.debug("_ConfigSQL set_from_dictionary '%s' = %r (was %r)", field, new_value, current_value)
|
||||
setattr(self, field, new_value)
|
||||
return True
|
||||
|
||||
@ -358,7 +358,7 @@ def _migrate_table(session, orm_class):
|
||||
if column_name[0] != '_':
|
||||
try:
|
||||
session.query(column).first()
|
||||
except exc.OperationalError as err:
|
||||
except OperationalError as err:
|
||||
log.debug("%s: %s", column_name, err.args[0])
|
||||
if column.default is not None:
|
||||
if sys.version_info < (3, 0):
|
||||
@ -368,20 +368,23 @@ def _migrate_table(session, orm_class):
|
||||
column_default = ""
|
||||
else:
|
||||
if isinstance(column.default.arg, bool):
|
||||
column_default = ("DEFAULT %r" % int(column.default.arg))
|
||||
column_default = "DEFAULT {}".format(int(column.default.arg))
|
||||
else:
|
||||
column_default = ("DEFAULT '%r'" % column.default.arg)
|
||||
column_default = "DEFAULT `{}`".format(column.default.arg)
|
||||
if isinstance(column.type, JSON):
|
||||
column_type = "JSON"
|
||||
else:
|
||||
column_type = column.type
|
||||
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
|
||||
alter_table = text("ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
|
||||
column_name,
|
||||
column_type,
|
||||
column_default)
|
||||
column_default))
|
||||
log.debug(alter_table)
|
||||
session.execute(alter_table)
|
||||
changed = True
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
log.error("Database corrupt column: {}".format(column_name))
|
||||
log.debug(e)
|
||||
|
||||
if changed:
|
||||
try:
|
||||
|
73
cps/db.py
73
cps/db.py
@ -33,7 +33,7 @@ from sqlalchemy.orm.collections import InstrumentedList
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
from sqlalchemy.exc import OperationalError
|
||||
try:
|
||||
# Compability with sqlalchemy 2.0
|
||||
# Compatibility with sqlalchemy 2.0
|
||||
from sqlalchemy.orm import declarative_base
|
||||
except ImportError:
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
@ -44,6 +44,7 @@ from flask_login import current_user
|
||||
from babel import Locale as LC
|
||||
from babel.core import UnknownLocaleError
|
||||
from flask_babel import gettext as _
|
||||
from flask import flash
|
||||
|
||||
from . import logger, ub, isoLanguages
|
||||
from .pagination import Pagination
|
||||
@ -121,6 +122,8 @@ class Identifiers(Base):
|
||||
return u"Douban"
|
||||
elif format_type == "goodreads":
|
||||
return u"Goodreads"
|
||||
elif format_type == "babelio":
|
||||
return u"Babelio"
|
||||
elif format_type == "google":
|
||||
return u"Google Books"
|
||||
elif format_type == "kobo":
|
||||
@ -148,6 +151,8 @@ class Identifiers(Base):
|
||||
return u"https://dx.doi.org/{0}".format(self.val)
|
||||
elif format_type == "goodreads":
|
||||
return u"https://www.goodreads.com/book/show/{0}".format(self.val)
|
||||
elif format_type == "babelio":
|
||||
return u"https://www.babelio.com/livres/titre/{0}".format(self.val)
|
||||
elif format_type == "douban":
|
||||
return u"https://book.douban.com/subject/{0}".format(self.val)
|
||||
elif format_type == "google":
|
||||
@ -393,7 +398,7 @@ class AlchemyEncoder(json.JSONEncoder):
|
||||
if isinstance(o.__class__, DeclarativeMeta):
|
||||
# an SQLAlchemy class
|
||||
fields = {}
|
||||
for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata']:
|
||||
for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata' and x!="password"]:
|
||||
if field == 'books':
|
||||
continue
|
||||
data = o.__getattribute__(field)
|
||||
@ -602,20 +607,46 @@ class CalibreDB():
|
||||
neg_content_tags_filter = false() if negtags_list == [''] else Books.tags.any(Tags.name.in_(negtags_list))
|
||||
pos_content_tags_filter = true() if postags_list == [''] else Books.tags.any(Tags.name.in_(postags_list))
|
||||
if self.config.config_restricted_column:
|
||||
pos_cc_list = current_user.allowed_column_value.split(',')
|
||||
pos_content_cc_filter = true() if pos_cc_list == [''] else \
|
||||
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||
any(cc_classes[self.config.config_restricted_column].value.in_(pos_cc_list))
|
||||
neg_cc_list = current_user.denied_column_value.split(',')
|
||||
neg_content_cc_filter = false() if neg_cc_list == [''] else \
|
||||
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||
any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list))
|
||||
try:
|
||||
pos_cc_list = current_user.allowed_column_value.split(',')
|
||||
pos_content_cc_filter = true() if pos_cc_list == [''] else \
|
||||
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||
any(cc_classes[self.config.config_restricted_column].value.in_(pos_cc_list))
|
||||
neg_cc_list = current_user.denied_column_value.split(',')
|
||||
neg_content_cc_filter = false() if neg_cc_list == [''] else \
|
||||
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||
any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list))
|
||||
except (KeyError, AttributeError):
|
||||
pos_content_cc_filter = false()
|
||||
neg_content_cc_filter = true()
|
||||
log.error(u"Custom Column No.%d is not existing in calibre database",
|
||||
self.config.config_restricted_column)
|
||||
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
||||
column=self.config.config_restricted_column),
|
||||
category="error")
|
||||
|
||||
else:
|
||||
pos_content_cc_filter = true()
|
||||
neg_content_cc_filter = false()
|
||||
return and_(lang_filter, pos_content_tags_filter, ~neg_content_tags_filter,
|
||||
pos_content_cc_filter, ~neg_content_cc_filter, archived_filter)
|
||||
|
||||
@staticmethod
|
||||
def get_checkbox_sorted(inputlist, state, offset, limit, order):
|
||||
outcome = list()
|
||||
elementlist = {ele.id: ele for ele in inputlist}
|
||||
for entry in state:
|
||||
try:
|
||||
outcome.append(elementlist[entry])
|
||||
except KeyError:
|
||||
pass
|
||||
del elementlist[entry]
|
||||
for entry in elementlist:
|
||||
outcome.append(elementlist[entry])
|
||||
if order == "asc":
|
||||
outcome.reverse()
|
||||
return outcome[offset:offset + limit]
|
||||
|
||||
# Fill indexpage with all requested data from database
|
||||
def fill_indexpage(self, page, pagesize, database, db_filter, order, *join):
|
||||
return self.fill_indexpage_with_archived_books(page, pagesize, database, db_filter, order, False, *join)
|
||||
@ -689,23 +720,33 @@ class CalibreDB():
|
||||
return self.session.query(Books) \
|
||||
.filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first()
|
||||
|
||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||
def get_search_results(self, term, offset=None, order=None, limit=None):
|
||||
order = order or [Books.sort]
|
||||
pagination = None
|
||||
def search_query(self, term, *join):
|
||||
term.strip().lower()
|
||||
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
q = list()
|
||||
authorterms = re.split("[, ]+", term)
|
||||
for authorterm in authorterms:
|
||||
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
||||
result = self.session.query(Books).filter(self.common_filters(True)).filter(
|
||||
query = self.session.query(Books)
|
||||
if len(join) == 3:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||
elif len(join) == 2:
|
||||
query = query.outerjoin(join[0], join[1])
|
||||
elif len(join) == 1:
|
||||
query = query.outerjoin(join[0])
|
||||
return query.filter(self.common_filters(True)).filter(
|
||||
or_(Books.tags.any(func.lower(Tags.name).ilike("%" + term + "%")),
|
||||
Books.series.any(func.lower(Series.name).ilike("%" + term + "%")),
|
||||
Books.authors.any(and_(*q)),
|
||||
Books.publishers.any(func.lower(Publishers.name).ilike("%" + term + "%")),
|
||||
func.lower(Books.title).ilike("%" + term + "%")
|
||||
)).order_by(*order).all()
|
||||
))
|
||||
|
||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||
def get_search_results(self, term, offset=None, order=None, limit=None, *join):
|
||||
order = order or [Books.sort]
|
||||
pagination = None
|
||||
result = self.search_query(term, *join).order_by(*order).all()
|
||||
result_count = len(result)
|
||||
if offset != None and limit != None:
|
||||
offset = int(offset)
|
||||
|
@ -229,14 +229,14 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session):
|
||||
@editbook.route("/ajax/delete/<int:book_id>")
|
||||
@login_required
|
||||
def delete_book_from_details(book_id):
|
||||
return Response(delete_book(book_id,"", True), mimetype='application/json')
|
||||
return Response(delete_book(book_id, "", True), mimetype='application/json')
|
||||
|
||||
|
||||
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""})
|
||||
@editbook.route("/delete/<int:book_id>/<string:book_format>")
|
||||
@login_required
|
||||
def delete_book_ajax(book_id, book_format):
|
||||
return delete_book(book_id,book_format, False)
|
||||
return delete_book(book_id, book_format, False)
|
||||
|
||||
|
||||
def delete_whole_book(book_id, book):
|
||||
@ -315,19 +315,19 @@ def delete_book(book_id, book_format, jsonResponse):
|
||||
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
|
||||
if not result:
|
||||
if jsonResponse:
|
||||
return json.dumps({"location": url_for("editbook.edit_book"),
|
||||
"type": "alert",
|
||||
return json.dumps([{"location": url_for("editbook.edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"error": error}),
|
||||
"message": error}])
|
||||
else:
|
||||
flash(error, category="error")
|
||||
return redirect(url_for('editbook.edit_book', book_id=book_id))
|
||||
if error:
|
||||
if jsonResponse:
|
||||
warning = {"location": url_for("editbook.edit_book"),
|
||||
warning = {"location": url_for("editbook.edit_book", book_id=book_id),
|
||||
"type": "warning",
|
||||
"format": "",
|
||||
"error": error}
|
||||
"message": error}
|
||||
else:
|
||||
flash(error, category="warning")
|
||||
if not book_format:
|
||||
@ -339,6 +339,15 @@ def delete_book(book_id, book_format, jsonResponse):
|
||||
except Exception as ex:
|
||||
log.debug_or_exception(ex)
|
||||
calibre_db.session.rollback()
|
||||
if jsonResponse:
|
||||
return json.dumps([{"location": url_for("editbook.edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": ex}])
|
||||
else:
|
||||
flash(str(ex), category="error")
|
||||
return redirect(url_for('editbook.edit_book', book_id=book_id))
|
||||
|
||||
else:
|
||||
# book not found
|
||||
log.error('Book with id "%s" could not be deleted: not found', book_id)
|
||||
@ -1176,6 +1185,6 @@ def merge_list_book():
|
||||
element.format,
|
||||
element.uncompressed_size,
|
||||
to_name))
|
||||
delete_book(from_book.id,"", True) # json_resp =
|
||||
delete_book(from_book.id,"", True)
|
||||
return json.dumps({'success': True})
|
||||
return ""
|
||||
|
@ -29,7 +29,7 @@ from sqlalchemy import Column, UniqueConstraint
|
||||
from sqlalchemy import String, Integer
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
try:
|
||||
# Compability with sqlalchemy 2.0
|
||||
# Compatibility with sqlalchemy 2.0
|
||||
from sqlalchemy.orm import declarative_base
|
||||
except ImportError:
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
@ -221,7 +221,7 @@ def listRootFolders():
|
||||
drive = getDrive(Gdrive.Instance().drive)
|
||||
folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false"
|
||||
fileList = drive.ListFile({'q': folder}).GetList()
|
||||
except (ServerNotFoundError, ssl.SSLError) as e:
|
||||
except (ServerNotFoundError, ssl.SSLError, RefreshError) as e:
|
||||
log.info("GDrive Error %s" % e)
|
||||
fileList = []
|
||||
return fileList
|
||||
@ -257,7 +257,12 @@ def getEbooksFolderId(drive=None):
|
||||
log.error('Error gDrive, root ID not found')
|
||||
gDriveId.path = '/'
|
||||
session.merge(gDriveId)
|
||||
session.commit()
|
||||
try:
|
||||
session.commit()
|
||||
except OperationalError as ex:
|
||||
log.error("gdrive.db DB is not Writeable")
|
||||
log.debug('Database error: %s', ex)
|
||||
session.rollback()
|
||||
return gDriveId.gdrive_id
|
||||
|
||||
|
||||
@ -272,37 +277,42 @@ def getFile(pathId, fileName, drive):
|
||||
|
||||
def getFolderId(path, drive):
|
||||
# drive = getDrive(drive)
|
||||
currentFolderId = getEbooksFolderId(drive)
|
||||
sqlCheckPath = path if path[-1] == '/' else path + '/'
|
||||
storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
|
||||
try:
|
||||
currentFolderId = getEbooksFolderId(drive)
|
||||
sqlCheckPath = path if path[-1] == '/' else path + '/'
|
||||
storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
|
||||
|
||||
if not storedPathName:
|
||||
dbChange = False
|
||||
s = path.split('/')
|
||||
for i, x in enumerate(s):
|
||||
if len(x) > 0:
|
||||
currentPath = "/".join(s[:i+1])
|
||||
if currentPath[-1] != '/':
|
||||
currentPath = currentPath + '/'
|
||||
storedPathName = session.query(GdriveId).filter(GdriveId.path == currentPath).first()
|
||||
if storedPathName:
|
||||
currentFolderId = storedPathName.gdrive_id
|
||||
else:
|
||||
currentFolder = getFolderInFolder(currentFolderId, x, drive)
|
||||
if currentFolder:
|
||||
gDriveId = GdriveId()
|
||||
gDriveId.gdrive_id = currentFolder['id']
|
||||
gDriveId.path = currentPath
|
||||
session.merge(gDriveId)
|
||||
dbChange = True
|
||||
currentFolderId = currentFolder['id']
|
||||
if not storedPathName:
|
||||
dbChange = False
|
||||
s = path.split('/')
|
||||
for i, x in enumerate(s):
|
||||
if len(x) > 0:
|
||||
currentPath = "/".join(s[:i+1])
|
||||
if currentPath[-1] != '/':
|
||||
currentPath = currentPath + '/'
|
||||
storedPathName = session.query(GdriveId).filter(GdriveId.path == currentPath).first()
|
||||
if storedPathName:
|
||||
currentFolderId = storedPathName.gdrive_id
|
||||
else:
|
||||
currentFolderId = None
|
||||
break
|
||||
if dbChange:
|
||||
session.commit()
|
||||
else:
|
||||
currentFolderId = storedPathName.gdrive_id
|
||||
currentFolder = getFolderInFolder(currentFolderId, x, drive)
|
||||
if currentFolder:
|
||||
gDriveId = GdriveId()
|
||||
gDriveId.gdrive_id = currentFolder['id']
|
||||
gDriveId.path = currentPath
|
||||
session.merge(gDriveId)
|
||||
dbChange = True
|
||||
currentFolderId = currentFolder['id']
|
||||
else:
|
||||
currentFolderId = None
|
||||
break
|
||||
if dbChange:
|
||||
session.commit()
|
||||
else:
|
||||
currentFolderId = storedPathName.gdrive_id
|
||||
except OperationalError as ex:
|
||||
log.error("gdrive.db DB is not Writeable")
|
||||
log.debug('Database error: %s', ex)
|
||||
session.rollback()
|
||||
return currentFolderId
|
||||
|
||||
|
||||
@ -346,7 +356,7 @@ def moveGdriveFolderRemote(origin_file, target_folder):
|
||||
addParents=gFileTargetDir['id'],
|
||||
removeParents=previous_parents,
|
||||
fields='id, parents').execute()
|
||||
# if previous_parents has no childs anymore, delete original fileparent
|
||||
# if previous_parents has no children anymore, delete original fileparent
|
||||
if len(children['items']) == 1:
|
||||
deleteDatabaseEntry(previous_parents)
|
||||
drive.auth.service.files().delete(fileId=previous_parents).execute()
|
||||
@ -507,9 +517,10 @@ def deleteDatabaseOnChange():
|
||||
try:
|
||||
session.query(GdriveId).delete()
|
||||
session.commit()
|
||||
except (OperationalError, InvalidRequestError):
|
||||
except (OperationalError, InvalidRequestError) as ex:
|
||||
session.rollback()
|
||||
log.info(u"GDrive DB is not Writeable")
|
||||
log.debug('Database error: %s', ex)
|
||||
log.error(u"GDrive DB is not Writeable")
|
||||
|
||||
|
||||
def updateGdriveCalibreFromLocal():
|
||||
@ -524,13 +535,23 @@ def updateDatabaseOnEdit(ID,newPath):
|
||||
storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first()
|
||||
if storedPathName:
|
||||
storedPathName.path = sqlCheckPath
|
||||
session.commit()
|
||||
try:
|
||||
session.commit()
|
||||
except OperationalError as ex:
|
||||
log.error("gdrive.db DB is not Writeable")
|
||||
log.debug('Database error: %s', ex)
|
||||
session.rollback()
|
||||
|
||||
|
||||
# Deletes the hashes in database of deleted book
|
||||
def deleteDatabaseEntry(ID):
|
||||
session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete()
|
||||
session.commit()
|
||||
try:
|
||||
session.commit()
|
||||
except OperationalError as ex:
|
||||
log.error("gdrive.db DB is not Writeable")
|
||||
log.debug('Database error: %s', ex)
|
||||
session.rollback()
|
||||
|
||||
|
||||
# Gets cover file from gdrive
|
||||
@ -547,7 +568,12 @@ def get_cover_via_gdrive(cover_path):
|
||||
permissionAdded = PermissionAdded()
|
||||
permissionAdded.gdrive_id = df['id']
|
||||
session.add(permissionAdded)
|
||||
session.commit()
|
||||
try:
|
||||
session.commit()
|
||||
except OperationalError as ex:
|
||||
log.error("gdrive.db DB is not Writeable")
|
||||
log.debug('Database error: %s', ex)
|
||||
session.rollback()
|
||||
return df.metadata.get('webContentLink')
|
||||
else:
|
||||
return None
|
||||
|
@ -693,6 +693,7 @@ 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, data.name + "." + book_format)))
|
||||
return response
|
||||
|
||||
##################################
|
||||
@ -732,7 +733,6 @@ def json_serial(obj):
|
||||
'seconds': obj.seconds,
|
||||
'microseconds': obj.microseconds,
|
||||
}
|
||||
# return obj.isoformat()
|
||||
raise TypeError("Type %s not serializable" % type(obj))
|
||||
|
||||
|
||||
@ -795,8 +795,8 @@ def tags_filters():
|
||||
# checks if domain is in database (including wildcards)
|
||||
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
|
||||
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
|
||||
# in all calls the email address is checked for validity
|
||||
def check_valid_domain(domain_text):
|
||||
# domain_text = domain_text.split('@', 1)[-1].lower()
|
||||
sql = "SELECT * FROM registration WHERE (:domain LIKE domain and allow = 1);"
|
||||
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
|
||||
if not len(result):
|
||||
@ -830,6 +830,7 @@ def get_download_link(book_id, book_format, client):
|
||||
if book:
|
||||
data1 = calibre_db.get_book_format(book.id, book_format.upper())
|
||||
else:
|
||||
log.error("Book id {} not found for downloading".format(book_id))
|
||||
abort(404)
|
||||
if data1:
|
||||
# collect downloaded books only for registered user and not for anonymous user
|
||||
|
@ -62,11 +62,11 @@ class _Logger(logging.Logger):
|
||||
|
||||
|
||||
def debug_no_auth(self, message, *args, **kwargs):
|
||||
message = message.strip("\r\n")
|
||||
if message.startswith("send: AUTH"):
|
||||
self.debug(message[:16], stacklevel=2, *args, **kwargs)
|
||||
self.debug(message[:16], *args, **kwargs)
|
||||
else:
|
||||
self.debug(message, stacklevel=2, *args, **kwargs)
|
||||
|
||||
self.debug(message, *args, **kwargs)
|
||||
|
||||
|
||||
def get(name=None):
|
||||
@ -153,11 +153,11 @@ def setup(log_file, log_level=None):
|
||||
file_handler.baseFilename = log_file
|
||||
else:
|
||||
try:
|
||||
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2, encoding='utf-8')
|
||||
file_handler = RotatingFileHandler(log_file, maxBytes=100000, backupCount=2, encoding='utf-8')
|
||||
except IOError:
|
||||
if log_file == DEFAULT_LOG_FILE:
|
||||
raise
|
||||
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2, encoding='utf-8')
|
||||
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=100000, backupCount=2, encoding='utf-8')
|
||||
log_file = ""
|
||||
file_handler.setFormatter(FORMATTER)
|
||||
|
||||
|
@ -30,6 +30,7 @@ from flask_babel import gettext as _
|
||||
from flask_dance.consumer import oauth_authorized, oauth_error
|
||||
from flask_dance.contrib.github import make_github_blueprint, github
|
||||
from flask_dance.contrib.google import make_google_blueprint, google
|
||||
from oauthlib.oauth2 import TokenExpiredError, InvalidGrantError
|
||||
from flask_login import login_user, current_user, login_required
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
@ -146,6 +147,7 @@ def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider
|
||||
ub.session.add(oauth_entry)
|
||||
ub.session.commit()
|
||||
flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success")
|
||||
log.info("Link to {} Succeeded".format(provider_name))
|
||||
return redirect(url_for('web.profile'))
|
||||
except Exception as ex:
|
||||
log.debug_or_exception(ex)
|
||||
@ -194,6 +196,7 @@ def unlink_oauth(provider):
|
||||
ub.session.commit()
|
||||
logout_oauth_user()
|
||||
flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success")
|
||||
log.info("Unlink to {} Succeeded".format(oauth_check[provider]))
|
||||
except Exception as ex:
|
||||
log.debug_or_exception(ex)
|
||||
ub.session.rollback()
|
||||
@ -257,11 +260,13 @@ if ub.oauth_support:
|
||||
def github_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash(_(u"Failed to log in with GitHub."), category="error")
|
||||
log.error("Failed to log in with GitHub")
|
||||
return False
|
||||
|
||||
resp = blueprint.session.get("/user")
|
||||
if not resp.ok:
|
||||
flash(_(u"Failed to fetch user info from GitHub."), category="error")
|
||||
log.error("Failed to fetch user info from GitHub")
|
||||
return False
|
||||
|
||||
github_info = resp.json()
|
||||
@ -273,11 +278,13 @@ if ub.oauth_support:
|
||||
def google_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash(_(u"Failed to log in with Google."), category="error")
|
||||
log.error("Failed to log in with Google")
|
||||
return False
|
||||
|
||||
resp = blueprint.session.get("/oauth2/v2/userinfo")
|
||||
if not resp.ok:
|
||||
flash(_(u"Failed to fetch user info from Google."), category="error")
|
||||
log.error("Failed to fetch user info from Google")
|
||||
return False
|
||||
|
||||
google_info = resp.json()
|
||||
@ -318,11 +325,16 @@ if ub.oauth_support:
|
||||
def github_login():
|
||||
if not github.authorized:
|
||||
return redirect(url_for('github.login'))
|
||||
account_info = github.get('/user')
|
||||
if account_info.ok:
|
||||
account_info_json = account_info.json()
|
||||
return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github')
|
||||
flash(_(u"GitHub Oauth error, please retry later."), category="error")
|
||||
try:
|
||||
account_info = github.get('/user')
|
||||
if account_info.ok:
|
||||
account_info_json = account_info.json()
|
||||
return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github')
|
||||
flash(_(u"GitHub Oauth error, please retry later."), category="error")
|
||||
log.error("GitHub Oauth error, please retry later")
|
||||
except (InvalidGrantError, TokenExpiredError) as e:
|
||||
flash(_(u"GitHub Oauth error: {}").format(e), category="error")
|
||||
log.error(e)
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
|
||||
@ -337,11 +349,16 @@ def github_login_unlink():
|
||||
def google_login():
|
||||
if not google.authorized:
|
||||
return redirect(url_for("google.login"))
|
||||
resp = google.get("/oauth2/v2/userinfo")
|
||||
if resp.ok:
|
||||
account_info_json = resp.json()
|
||||
return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google')
|
||||
flash(_(u"Google Oauth error, please retry later."), category="error")
|
||||
try:
|
||||
resp = google.get("/oauth2/v2/userinfo")
|
||||
if resp.ok:
|
||||
account_info_json = resp.json()
|
||||
return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google')
|
||||
flash(_(u"Google Oauth error, please retry later."), category="error")
|
||||
log.error("Google Oauth error, please retry later")
|
||||
except (InvalidGrantError, TokenExpiredError) as e:
|
||||
flash(_(u"Google Oauth error: {}").format(e), category="error")
|
||||
log.error(e)
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
|
||||
|
@ -546,8 +546,8 @@ def check_auth(username, password):
|
||||
if bool(user and check_password_hash(str(user.password), password)):
|
||||
return True
|
||||
else:
|
||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ipAdress)
|
||||
ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ip_Address)
|
||||
return False
|
||||
|
||||
|
||||
|
@ -49,5 +49,5 @@ except ImportError as err:
|
||||
try:
|
||||
from . import gmail
|
||||
except ImportError as err:
|
||||
log.debug("Cannot import Gmail, sending books via G-Mail Accounts will not work: %s", err)
|
||||
log.debug("Cannot import gmail, sending books via Gmail Oauth2 Verification will not work: %s", err)
|
||||
gmail = None
|
||||
|
@ -53,6 +53,7 @@ def setup_gmail(token):
|
||||
'expiry': creds.expiry.isoformat(),
|
||||
'email': user_info
|
||||
}
|
||||
return {}
|
||||
|
||||
def get_user_info(credentials):
|
||||
user_info_service = build(serviceName='oauth2', version='v2',credentials=credentials)
|
||||
@ -60,6 +61,7 @@ def get_user_info(credentials):
|
||||
return user_info.get('email', "")
|
||||
|
||||
def send_messsage(token, msg):
|
||||
log.debug("Start sending email via Gmail")
|
||||
creds = Credentials(
|
||||
token=token['token'],
|
||||
refresh_token=token['refresh_token'],
|
||||
@ -78,3 +80,4 @@ def send_messsage(token, msg):
|
||||
body = {'raw': raw}
|
||||
|
||||
(service.users().messages().send(userId='me', body=body).execute())
|
||||
log.debug("Email send successfully via Gmail")
|
||||
|
@ -45,7 +45,7 @@ class ImprovedQueue(queue.Queue):
|
||||
with self.mutex:
|
||||
return list(self.queue)
|
||||
|
||||
#Class for all worker tasks in the background
|
||||
# Class for all worker tasks in the background
|
||||
class WorkerThread(threading.Thread):
|
||||
_instance = None
|
||||
|
||||
@ -69,6 +69,7 @@ class WorkerThread(threading.Thread):
|
||||
def add(cls, user, task):
|
||||
ins = cls.getInstance()
|
||||
ins.num += 1
|
||||
log.debug("Add Task for user: {}: {}".format(user, task))
|
||||
ins.queue.put(QueuedTask(
|
||||
num=ins.num,
|
||||
user=user,
|
||||
|
29
cps/shelf.py
29
cps/shelf.py
@ -99,12 +99,14 @@ def add_to_shelf(shelf_id, book_id):
|
||||
ub.session.commit()
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
if "HTTP_REFERER" in request.environ:
|
||||
return redirect(request.environ["HTTP_REFERER"])
|
||||
else:
|
||||
return redirect(url_for('web.index'))
|
||||
if not xhr:
|
||||
log.debug("Book has been added to shelf: {}".format(shelf.name))
|
||||
flash(_(u"Book has been added to shelf: %(sname)s", sname=shelf.name), category="success")
|
||||
if "HTTP_REFERER" in request.environ:
|
||||
return redirect(request.environ["HTTP_REFERER"])
|
||||
@ -123,6 +125,7 @@ def search_to_shelf(shelf_id):
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
if not check_shelf_edit_permissions(shelf):
|
||||
log.warning("You are not allowed to add a book to the the shelf: {}".format(shelf.name))
|
||||
flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
@ -140,7 +143,7 @@ def search_to_shelf(shelf_id):
|
||||
books_for_shelf = ub.searched_ids[current_user.id]
|
||||
|
||||
if not books_for_shelf:
|
||||
log.error("Books are already part of %s", shelf.name)
|
||||
log.error("Books are already part of {}".format(shelf.name))
|
||||
flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
@ -156,8 +159,10 @@ def search_to_shelf(shelf_id):
|
||||
flash(_(u"Books have been added to shelf: %(sname)s", sname=shelf.name), category="success")
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
else:
|
||||
log.error("Could not add books to shelf: {}".format(shelf.name))
|
||||
flash(_(u"Could not add books to shelf: %(sname)s", sname=shelf.name), category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
@ -168,7 +173,7 @@ def remove_from_shelf(shelf_id, book_id):
|
||||
xhr = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
|
||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
||||
if shelf is None:
|
||||
log.error("Invalid shelf specified: %s", shelf_id)
|
||||
log.error("Invalid shelf specified: {}".format(shelf_id))
|
||||
if not xhr:
|
||||
return redirect(url_for('web.index'))
|
||||
return "Invalid shelf specified", 400
|
||||
@ -197,7 +202,8 @@ def remove_from_shelf(shelf_id, book_id):
|
||||
ub.session.commit()
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
if "HTTP_REFERER" in request.environ:
|
||||
return redirect(request.environ["HTTP_REFERER"])
|
||||
else:
|
||||
@ -211,6 +217,7 @@ def remove_from_shelf(shelf_id, book_id):
|
||||
return "", 204
|
||||
else:
|
||||
if not xhr:
|
||||
log.warning("You are not allowed to remove a book from shelf: {}".format(shelf.name))
|
||||
flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name),
|
||||
category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
@ -258,7 +265,8 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
|
||||
except (OperationalError, InvalidRequestError) as ex:
|
||||
ub.session.rollback()
|
||||
log.debug_or_exception(ex)
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
except Exception as ex:
|
||||
ub.session.rollback()
|
||||
log.debug_or_exception(ex)
|
||||
@ -278,6 +286,7 @@ def check_shelf_is_unique(shelf, to_save, shelf_id=False):
|
||||
.first() is None
|
||||
|
||||
if not is_shelf_name_unique:
|
||||
log.error("A public shelf with the name '{}' already exists.".format(to_save["title"]))
|
||||
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||
category="error")
|
||||
else:
|
||||
@ -288,6 +297,7 @@ def check_shelf_is_unique(shelf, to_save, shelf_id=False):
|
||||
.first() is None
|
||||
|
||||
if not is_shelf_name_unique:
|
||||
log.error("A private shelf with the name '{}' already exists.".format(to_save["title"]))
|
||||
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||
category="error")
|
||||
return is_shelf_name_unique
|
||||
@ -311,7 +321,8 @@ def delete_shelf(shelf_id):
|
||||
delete_shelf_helper(cur_shelf)
|
||||
except InvalidRequestError:
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
|
||||
|
||||
@ -345,7 +356,8 @@ def order_shelf(shelf_id):
|
||||
ub.session.commit()
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
|
||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
||||
result = list()
|
||||
@ -415,7 +427,8 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
|
||||
ub.session.commit()
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
|
||||
return render_title_template(page,
|
||||
entries=result,
|
||||
|
@ -84,15 +84,24 @@ body {
|
||||
#progress .bar-load,
|
||||
#progress .bar-read {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
transition: width 150ms ease-in-out;
|
||||
}
|
||||
|
||||
#progress .from-left {
|
||||
left: 0;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#progress .from-right {
|
||||
right: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
#progress .bar-load {
|
||||
color: #000;
|
||||
background-color: #ccc;
|
||||
|
@ -15,6 +15,10 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.myselect {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
#main {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
@ -22,7 +22,21 @@ $(function() {
|
||||
});
|
||||
|
||||
$("#have_read_cb").on("change", function() {
|
||||
$(this).closest("form").submit();
|
||||
$.post({
|
||||
url: this.closest("form").action,
|
||||
error: function(response) {
|
||||
var data = [{type:"danger", message:response.responseText}]
|
||||
$("#flash_success").remove();
|
||||
$("#flash_danger").remove();
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
data.forEach(function (item) {
|
||||
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
|
||||
'</div>');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(function() {
|
||||
|
@ -171,7 +171,10 @@ kthoom.ImageFile = function(file) {
|
||||
|
||||
function initProgressClick() {
|
||||
$("#progress").click(function(e) {
|
||||
var page = Math.max(1, Math.ceil((e.offsetX / $(this).width()) * totalImages)) - 1;
|
||||
var offset = $(this).offset();
|
||||
var x = e.pageX - offset.left;
|
||||
var rate = settings.direction === 0 ? x / $(this).width() : 1 - x / $(this).width();
|
||||
var page = Math.max(1, Math.ceil(rate * totalImages)) - 1;
|
||||
currentImage = page;
|
||||
updatePage();
|
||||
});
|
||||
@ -285,6 +288,22 @@ function updatePage() {
|
||||
}
|
||||
|
||||
function updateProgress(loadPercentage) {
|
||||
if (settings.direction === 0) {
|
||||
$("#progress .bar-read")
|
||||
.removeClass("from-right")
|
||||
.addClass("from-left");
|
||||
$("#progress .bar-load")
|
||||
.removeClass("from-right")
|
||||
.addClass("from-left");
|
||||
} else {
|
||||
$("#progress .bar-read")
|
||||
.removeClass("from-left")
|
||||
.addClass("from-right");
|
||||
$("#progress .bar-load")
|
||||
.removeClass("from-left")
|
||||
.addClass("from-right");
|
||||
}
|
||||
|
||||
// Set the load/unzip progress if it's passed in
|
||||
if (loadPercentage) {
|
||||
$("#progress .bar-load").css({ width: loadPercentage + "%" });
|
||||
@ -526,18 +545,17 @@ function keyHandler(evt) {
|
||||
break;
|
||||
case kthoom.Key.SPACE:
|
||||
var container = $("#mainContent");
|
||||
var atTop = container.scrollTop() === 0;
|
||||
var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
|
||||
// var atTop = container.scrollTop() === 0;
|
||||
// var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
|
||||
|
||||
if (evt.shiftKey && atTop) {
|
||||
if (evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
// If it's Shift + Space and the container is at the top of the page
|
||||
showLeftPage();
|
||||
} else if (!evt.shiftKey && atBottom) {
|
||||
showPrevPage();
|
||||
} else {
|
||||
evt.preventDefault();
|
||||
// If you're at the bottom of the page and you only pressed space
|
||||
showRightPage();
|
||||
container.scrollTop(0);
|
||||
showNextPage();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -114,18 +114,22 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
$(".session").click(function() {
|
||||
window.sessionStorage.setItem("back", window.location.pathname);
|
||||
});
|
||||
|
||||
$("#back").click(function() {
|
||||
var loc = sessionStorage.getItem("back");
|
||||
if (!loc) {
|
||||
loc = $(this).data("back");
|
||||
}
|
||||
sessionStorage.removeItem("back");
|
||||
window.location.href = loc;
|
||||
|
||||
});
|
||||
|
||||
function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
|
||||
var $confirm = $("#" + dialogid);
|
||||
$confirm.modal('show');
|
||||
$.ajax({
|
||||
method:"get",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/loaddialogtexts/" + id,
|
||||
success: function success(data) {
|
||||
$("#header-"+ dialogid).html(data.header);
|
||||
$("#text-"+ dialogid).html(data.main);
|
||||
}
|
||||
});
|
||||
$("#btnConfirmYes-"+ dialogid).off('click').click(function () {
|
||||
yesFn(dataValue);
|
||||
$confirm.modal("hide");
|
||||
@ -136,16 +140,27 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) {
|
||||
}
|
||||
$confirm.modal("hide");
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/loaddialogtexts/" + id,
|
||||
success: function success(data) {
|
||||
$("#header-"+ dialogid).html(data.header);
|
||||
$("#text-"+ dialogid).html(data.main);
|
||||
}
|
||||
});
|
||||
$confirm.modal('show');
|
||||
}
|
||||
|
||||
$("#delete_confirm").click(function() {
|
||||
//get data-id attribute of the clicked element
|
||||
var deleteId = $(this).data("delete-id");
|
||||
var bookFormat = $(this).data("delete-format");
|
||||
var ajaxResponse = $(this).data("ajax");
|
||||
if (bookFormat) {
|
||||
window.location.href = getPath() + "/delete/" + deleteId + "/" + bookFormat;
|
||||
} else {
|
||||
if ($(this).data("delete-format")) {
|
||||
if (ajaxResponse) {
|
||||
path = getPath() + "/ajax/delete/" + deleteId;
|
||||
$.ajax({
|
||||
method:"get",
|
||||
@ -163,6 +178,19 @@ $("#delete_confirm").click(function() {
|
||||
|
||||
}
|
||||
});
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
/*$.ajax({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/listbooks",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
|
||||
|
||||
$("#book-table").bootstrapTable("load", data);
|
||||
loadSuccess();
|
||||
}
|
||||
});*/
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -187,6 +215,7 @@ $("#deleteModal").on("show.bs.modal", function(e) {
|
||||
}
|
||||
$(e.currentTarget).find("#delete_confirm").data("delete-id", bookId);
|
||||
$(e.currentTarget).find("#delete_confirm").data("delete-format", bookfomat);
|
||||
$(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax"));
|
||||
});
|
||||
|
||||
|
||||
|
@ -19,9 +19,9 @@
|
||||
/* global getPath, confirmDialog */
|
||||
|
||||
var selections = [];
|
||||
var reload = false;
|
||||
|
||||
$(function() {
|
||||
|
||||
$("#books-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table",
|
||||
function (e, rowsAfter, rowsBefore) {
|
||||
var rows = rowsAfter;
|
||||
@ -117,6 +117,7 @@ $(function() {
|
||||
|
||||
$("#books-table").bootstrapTable({
|
||||
sidePagination: "server",
|
||||
queryParams: queryParams,
|
||||
pagination: true,
|
||||
paginationLoop: false,
|
||||
paginationDetailHAlign: " hidden",
|
||||
@ -176,7 +177,6 @@ $(function() {
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
$("#domain_allow_submit").click(function(event) {
|
||||
event.preventDefault();
|
||||
$("#domain_add_allow").ajaxForm();
|
||||
@ -318,7 +318,6 @@ $(function() {
|
||||
},
|
||||
url: getPath() + "/ajax/listrestriction/" + type + "/" + userId,
|
||||
rowStyle: function(row) {
|
||||
// console.log('Reihe :' + row + " Index :" + index);
|
||||
if (row.id.charAt(0) === "a") {
|
||||
return {classes: "bg-primary"};
|
||||
} else {
|
||||
@ -387,7 +386,6 @@ $(function() {
|
||||
var target = $(e.relatedTarget).attr('id');
|
||||
var dataId;
|
||||
$(e.relatedTarget).one('focus', function(e){$(this).blur();});
|
||||
//$(e.relatedTarget).blur();
|
||||
if ($(e.relatedTarget).hasClass("button_head")) {
|
||||
dataId = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
} else {
|
||||
@ -422,6 +420,7 @@ $(function() {
|
||||
|
||||
$("#user-table").bootstrapTable({
|
||||
sidePagination: "server",
|
||||
queryParams: queryParams,
|
||||
pagination: true,
|
||||
paginationLoop: false,
|
||||
paginationDetailHAlign: " hidden",
|
||||
@ -452,38 +451,13 @@ $(function() {
|
||||
$(this).next().text(elText);
|
||||
});
|
||||
},
|
||||
onLoadSuccess: function () {
|
||||
var guest = $(".editable[data-name='name'][data-value='Guest']");
|
||||
guest.editable("disable");
|
||||
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable");
|
||||
$("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("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);
|
||||
// ToDo: Disable delete
|
||||
|
||||
onPostHeader () {
|
||||
move_header_elements();
|
||||
},
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/*onEditableSave: function (field, row, oldvalue, $el) {
|
||||
if (field === "title" || field === "authors") {
|
||||
$.ajax({
|
||||
method:"get",
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../ajax/sort_value/" + field + "/" + row.id,
|
||||
success: function success(data) {
|
||||
var key = Object.keys(data)[0];
|
||||
$("#books-table").bootstrapTable("updateCellByUniqueId", {
|
||||
id: row.id,
|
||||
field: key,
|
||||
value: data[key]
|
||||
});
|
||||
// console.log(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
},*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onColumnSwitch: function (field, checked) {
|
||||
onLoadSuccess: function () {
|
||||
loadSuccess();
|
||||
},
|
||||
onColumnSwitch: function () {
|
||||
var visible = $("#user-table").bootstrapTable("getVisibleColumns");
|
||||
var hidden = $("#user-table").bootstrapTable("getHiddenColumns");
|
||||
var st = "";
|
||||
@ -501,31 +475,10 @@ $(function() {
|
||||
url: window.location.pathname + "/../../ajax/user_table_settings",
|
||||
data: "{" + st + "}",
|
||||
});
|
||||
handle_header_buttons();
|
||||
},
|
||||
});
|
||||
|
||||
$("#user_delete_selection").click(function() {
|
||||
$("#user-table").bootstrapTable("uncheckAll");
|
||||
});
|
||||
|
||||
function user_handle (userId) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid":userId}
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$("#user-table").on("click-cell.bs.table", function (field, value, row, $element) {
|
||||
if (value === "denied_column_value") {
|
||||
ConfirmDialog("btndeluser", "GeneralDeleteModal", $element.id, user_handle);
|
||||
@ -545,29 +498,39 @@ $(function() {
|
||||
});
|
||||
var func = $.inArray(e.type, ["check", "check-all"]) > -1 ? "union" : "difference";
|
||||
selections = window._[func](selections, ids);
|
||||
if (selections.length < 1) {
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
$(".check_head").attr("disabled", true);
|
||||
$(".check_head").prop('checked', false);
|
||||
$(".button_head").attr("aria-disabled", true);
|
||||
$(".button_head").addClass("disabled");
|
||||
$(".header_select").attr("disabled", true);
|
||||
} else {
|
||||
$("#user_delete_selection").removeClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", false);
|
||||
$(".check_head").attr("aria-disabled", false);
|
||||
$(".check_head").removeAttr("disabled");
|
||||
$(".button_head").attr("aria-disabled", false);
|
||||
$(".button_head").removeClass("disabled");
|
||||
$(".header_select").removeAttr("disabled");
|
||||
}
|
||||
|
||||
handle_header_buttons();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function handle_header_buttons () {
|
||||
if (selections.length < 1) {
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
$(".check_head").attr("disabled", true);
|
||||
$(".check_head").prop('checked', false);
|
||||
$(".button_head").attr("aria-disabled", true);
|
||||
$(".button_head").addClass("disabled");
|
||||
$(".multi_head").attr("aria-disabled", true);
|
||||
$(".multi_head").addClass("hidden");
|
||||
$(".multi_selector").attr("aria-disabled", true);
|
||||
$(".multi_selector").attr("disabled", true);
|
||||
$(".header_select").attr("disabled", true);
|
||||
} else {
|
||||
$("#user_delete_selection").removeClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", false);
|
||||
$(".check_head").attr("aria-disabled", false);
|
||||
$(".check_head").removeAttr("disabled");
|
||||
$(".button_head").attr("aria-disabled", false);
|
||||
$(".button_head").removeClass("disabled");
|
||||
$(".multi_head").attr("aria-disabled", false);
|
||||
$(".multi_head").removeClass("hidden");
|
||||
$(".multi_selector").attr("aria-disabled", false);
|
||||
$(".multi_selector").removeAttr("disabled");
|
||||
$('.multi_selector').selectpicker('refresh');
|
||||
$(".header_select").removeAttr("disabled");
|
||||
}
|
||||
}
|
||||
/* Function for deleting domain restrictions */
|
||||
function TableActions (value, row) {
|
||||
return [
|
||||
@ -578,10 +541,6 @@ function TableActions (value, row) {
|
||||
].join("");
|
||||
}
|
||||
|
||||
function editEntry(param)
|
||||
{
|
||||
console.log(param);
|
||||
}
|
||||
/* Function for deleting domain restrictions */
|
||||
function RestrictionActions (value, row) {
|
||||
return [
|
||||
@ -600,10 +559,10 @@ function EbookActions (value, row) {
|
||||
].join("");
|
||||
}
|
||||
|
||||
/* Function for deleting books */
|
||||
/* Function for deleting Users */
|
||||
function UserActions (value, row) {
|
||||
return [
|
||||
"<div class=\"user-remove\" data-target=\"#GeneralDeleteModal\" title=\"Remove\">",
|
||||
"<div class=\"user-remove\" data-value=\"delete\" onclick=\"deleteUser(this, '" + row.id + "')\" data-pk=\"" + row.id + "\" title=\"Remove\">",
|
||||
"<i class=\"glyphicon glyphicon-trash\"></i>",
|
||||
"</div>"
|
||||
].join("");
|
||||
@ -618,46 +577,160 @@ function responseHandler(res) {
|
||||
}
|
||||
|
||||
function singleUserFormatter(value, row) {
|
||||
return '<a class="btn btn-default" href="/admin/user/' + row.id + '">' + this.buttontext + '</a>'
|
||||
return '<a class="btn btn-default" onclick="storeLocation()" href="' + window.location.pathname + '/../../admin/user/' + row.id + '">' + this.buttontext + '</a>'
|
||||
}
|
||||
|
||||
function checkboxFormatter(value, row, index){
|
||||
if(value & this.column)
|
||||
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.name + '" checked onchange="checkboxChange(this, ' + row.id + ', \'' + this.field + '\', ' + this.column + ')">';
|
||||
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" checked onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
|
||||
else
|
||||
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.name + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.field + '\', ' + this.column + ')">';
|
||||
return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
|
||||
}
|
||||
|
||||
/* Do some hiding disabling after user list is loaded */
|
||||
function loadSuccess() {
|
||||
var guest = $(".editable[data-name='name'][data-value='Guest']");
|
||||
guest.editable("disable");
|
||||
$("input:radio.check_head:checked").each(function() {
|
||||
$(this).prop('checked', false);
|
||||
});
|
||||
$(".header_select").each(function() {
|
||||
$(this).prop("selectedIndex", 0);
|
||||
});
|
||||
$(".header_select").each(function() {
|
||||
$(this).prop("selectedIndex", 0);
|
||||
});
|
||||
$('.multi_selector').selectpicker('deselectAll');
|
||||
$('.multi_selector').selectpicker('refresh');
|
||||
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").editable("disable");
|
||||
$(".editable[data-name='locale'][data-pk='"+guest.data("pk")+"']").hide();
|
||||
$("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("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();
|
||||
}
|
||||
|
||||
function move_header_elements() {
|
||||
$(".header_select").each(function () {
|
||||
var item = $(this).parent();
|
||||
var parent = item.parent().parent();
|
||||
if (parent.prop('nodeName') === "TH") {
|
||||
item.prependTo(parent);
|
||||
}
|
||||
});
|
||||
$(".form-check").each(function () {
|
||||
var item = $(this).parent();
|
||||
var parent = item.parent().parent();
|
||||
if (parent.prop('nodeName') === "TH") {
|
||||
item.prependTo(parent);
|
||||
}
|
||||
});
|
||||
$(".multi_select").each(function () {
|
||||
var item = $(this);
|
||||
var parent = item.parent().parent();
|
||||
if (parent.prop('nodeName') === "TH") {
|
||||
item.prependTo(parent);
|
||||
item.addClass("myselect");
|
||||
}
|
||||
});
|
||||
$(".multi_selector").selectpicker();
|
||||
|
||||
if (! $._data($(".multi_head").get(0), "events") ) {
|
||||
// Functions have to be here, otherwise the callbacks are not fired if visible columns are changed
|
||||
$(".multi_head").on("click", function () {
|
||||
var val = $(this).data("set");
|
||||
var field = $(this).data("name");
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
var values = $("#" + field).val();
|
||||
confirmDialog(
|
||||
"restrictions",
|
||||
"GeneralChangeModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": values, "action": val},
|
||||
success: function (data) {
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$("#user_delete_selection").click(function () {
|
||||
$("#user-table").bootstrapTable("uncheckAll");
|
||||
});
|
||||
$("#select_locale").on("change", function () {
|
||||
selectHeader(this, "locale");
|
||||
});
|
||||
$("#select_default_language").on("change", function () {
|
||||
selectHeader(this, "default_language");
|
||||
});
|
||||
|
||||
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");
|
||||
checkboxHeader(val, name, data);
|
||||
});
|
||||
}
|
||||
if (! $._data($(".button_head").get(0), "events") ) {
|
||||
$(".button_head").on("click", function () {
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
confirmDialog(
|
||||
"btndeluser",
|
||||
"GeneralDeleteModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid": result},
|
||||
success: function (data) {
|
||||
selections = selections.filter((el) => !result.includes(el));
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleListServerResponse (data) {
|
||||
$("#flash_success").remove();
|
||||
$("#flash_danger").remove();
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
data.forEach(function(item) {
|
||||
$(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' +
|
||||
'<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' +
|
||||
'</div>');
|
||||
});
|
||||
}
|
||||
$("#user-table").bootstrapTable("refresh");
|
||||
}
|
||||
|
||||
function checkboxChange(checkbox, userId, field, field_index) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk":userId, "field_index":field_index, "value": checkbox.checked}
|
||||
/*<div className="editable-buttons">
|
||||
<button type="button" className="btn btn-default btn-sm editable-cancel"><i
|
||||
className="glyphicon glyphicon-remove"></i></button>
|
||||
</div>*/
|
||||
/*<div className="editable-error-block help-block" style="">Text to show</div>*/
|
||||
data: {"pk": userId, "field_index": field_index, "value": checkbox.checked},
|
||||
error: function(data) {
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
},
|
||||
success: handleListServerResponse
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
function deactivateHeaderButtons(e) {
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
$(".check_head").attr("disabled", true);
|
||||
$(".check_head").prop('checked', false);
|
||||
$(".button_head").attr("aria-disabled", true);
|
||||
$(".button_head").addClass("disabled");
|
||||
$(".header_select").attr("disabled", true);
|
||||
}
|
||||
|
||||
function selectHeader(element, field) {
|
||||
@ -668,19 +741,13 @@ function selectHeader(element, field) {
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": element.value},
|
||||
success: function () {
|
||||
$.ajax({
|
||||
method: "get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success: function (data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
deactivateHeaderButtons();
|
||||
}
|
||||
});
|
||||
}
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
},
|
||||
success: handleListServerResponse,
|
||||
});
|
||||
},function() {
|
||||
$(element).prop("selectedIndex", 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -692,46 +759,58 @@ function checkboxHeader(CheckboxState, field, field_index) {
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "field_index": field_index, "value": CheckboxState},
|
||||
success: function () {
|
||||
$.ajax({
|
||||
method: "get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success: function (data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
$(".check_head").attr("disabled", true);
|
||||
$(".check_head").prop('checked', false);
|
||||
$(".button_head").attr("aria-disabled", true);
|
||||
$(".button_head").addClass("disabled");
|
||||
$(".header_select").attr("disabled", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
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 deleteUser(a,id){
|
||||
confirmDialog(
|
||||
"btndeluser",
|
||||
"GeneralDeleteModal",
|
||||
0,
|
||||
function() {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid":id},
|
||||
success: function (data) {
|
||||
userId = parseInt(id, 10);
|
||||
selections = selections.filter(item => item !== userId);
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function queryParams(params)
|
||||
{
|
||||
params.state = JSON.stringify(selections);
|
||||
return params;
|
||||
}
|
||||
|
||||
function storeLocation() {
|
||||
window.sessionStorage.setItem("back", window.location.pathname);
|
||||
}
|
||||
|
||||
function user_handle (userId) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid":userId}
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/listusers",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
$("#user-table").bootstrapTable("load", data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function test(){
|
||||
console.log("hello");
|
||||
$("#user-table").bootstrapTable("refresh");
|
||||
}
|
||||
|
@ -134,9 +134,9 @@ class TaskEmail(CalibreTask):
|
||||
return message
|
||||
|
||||
def run(self, worker_thread):
|
||||
# create MIME message
|
||||
msg = self.prepare_message()
|
||||
try:
|
||||
# create MIME message
|
||||
msg = self.prepare_message()
|
||||
if self.settings['mail_server_type'] == 0:
|
||||
self.send_standard_email(msg)
|
||||
else:
|
||||
@ -173,6 +173,7 @@ class TaskEmail(CalibreTask):
|
||||
org_smtpstderr = smtplib.stderr
|
||||
smtplib.stderr = logger.StderrLogger('worker.smtp')
|
||||
|
||||
log.debug("Start sending email")
|
||||
if use_ssl == 2:
|
||||
self.asyncSMTP = EmailSSL(self.settings["mail_server"], self.settings["mail_port"],
|
||||
timeout=timeout)
|
||||
@ -195,11 +196,11 @@ class TaskEmail(CalibreTask):
|
||||
self.asyncSMTP.sendmail(self.settings["mail_from"], self.recipent, fp.getvalue())
|
||||
self.asyncSMTP.quit()
|
||||
self._handleSuccess()
|
||||
log.debug("Email send successfully")
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
smtplib.stderr = org_smtpstderr
|
||||
|
||||
|
||||
def send_gmail_email(self, message):
|
||||
return gmail.send_messsage(self.settings.get('mail_gmail_token', None), message)
|
||||
|
||||
@ -258,3 +259,6 @@ class TaskEmail(CalibreTask):
|
||||
@property
|
||||
def name(self):
|
||||
return "Email"
|
||||
|
||||
def __str__(self):
|
||||
return "{}, {}".format(self.name, self.subject)
|
||||
|
@ -26,7 +26,7 @@
|
||||
{% for user in allUser %}
|
||||
{% if not user.role_anonymous() or config.config_anonbrowse %}
|
||||
<tr>
|
||||
<td><a href="{{url_for('admin.edit_user', user_id=user.id)}}">{{user.name}}</a></td>
|
||||
<td><a class="session" href="{{url_for('admin.edit_user', user_id=user.id)}}">{{user.name}}</a></td>
|
||||
<td>{{user.email}}</td>
|
||||
<td>{{user.kindle_mail}}</td>
|
||||
<td>{{user.downloads.count()}}</td>
|
||||
|
@ -1,6 +1,7 @@
|
||||
{% extends "layout.html" %}
|
||||
{% macro text_table_row(parameter, edit_text, show_text, validate) -%}
|
||||
<th data-field="{{ parameter }}" id="{{ parameter }}" data-sortable="true"
|
||||
{% macro text_table_row(parameter, edit_text, show_text, validate, sort) -%}
|
||||
<th data-field="{{ parameter }}" id="{{ parameter }}"
|
||||
{% if sort %}data-sortable="true" {% endif %}
|
||||
data-visible = "{{visiblility.get(parameter)}}"
|
||||
{% if g.user.role_edit() %}
|
||||
data-editable-type="text"
|
||||
@ -43,16 +44,16 @@
|
||||
<th data-field="state" data-checkbox="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) }}
|
||||
{{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false) }}
|
||||
{{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false) }}
|
||||
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true) }}
|
||||
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false) }}
|
||||
{{ text_table_row('series', _('Enter Series'),_('Series'), false) }}
|
||||
{{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }}
|
||||
{{ text_table_row('sort', _('Enter Title Sort'),_('Title Sort'), false, true) }}
|
||||
{{ text_table_row('author_sort', _('Enter Author Sort'),_('Author Sort'), false, true) }}
|
||||
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true, true) }}
|
||||
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, true) }}
|
||||
{{ text_table_row('series', _('Enter Series'),_('Series'), false, true) }}
|
||||
<th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('editbook.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter title')}}"{% endif %}>{{_('Series Index')}}</th>
|
||||
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false) }}
|
||||
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, true) }}
|
||||
<!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th-->
|
||||
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false) }}
|
||||
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, true) }}
|
||||
{% if g.user.role_delete_books() and g.user.role_edit()%}
|
||||
<th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th>
|
||||
{% endif %}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<label for="mail_server_type">{{_('Choose Server Type')}}</label>
|
||||
<select name="mail_server_type" id="config_email_type" class="form-control" data-control="email-settings">
|
||||
<option value="0" {% if content.mail_server_type == 0 %}selected{% endif %}>{{_('Use Standard E-Mail Account')}}</option>
|
||||
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('G-Mail Account with OAuth2 Verfification')}}</option>
|
||||
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('Gmail Account with OAuth2 Verfification')}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-related="email-settings-1">
|
||||
@ -20,7 +20,7 @@
|
||||
{% if content.mail_gmail_token == {} %}
|
||||
<button type="submit" id="gmail_server" name="gmail" value="submit" class="btn btn-default">{{_('Setup Gmail Account as E-Mail Server')}}</button>
|
||||
{% else %}
|
||||
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke G-Mail Access')}}</button>
|
||||
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke Gmail Access')}}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -66,7 +66,7 @@
|
||||
{% if feature_support['gmail'] %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Back')}}</a>
|
||||
<a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Back')}}</a>
|
||||
</form>
|
||||
{% if g.allow_registration %}
|
||||
<div class="col-md-10 col-lg-6">
|
||||
|
@ -92,7 +92,7 @@
|
||||
{% for message in get_flashed_messages(with_categories=True) %}
|
||||
{%if message[0] == "error" %}
|
||||
<div class="row-fluid text-center" style="margin-top: -20px;">
|
||||
<div id="flash_alert" class="alert alert-danger">{{ message[1] }}</div>
|
||||
<div id="flash_danger" class="alert alert-danger">{{ message[1] }}</div>
|
||||
</div>
|
||||
{%endif%}
|
||||
{%if message[0] == "info" %}
|
||||
|
@ -60,12 +60,12 @@
|
||||
<a id="fullscreen" class="icon-resize-full">Fullscreen</a>
|
||||
</div>
|
||||
<div id="progress" role="progressbar" class="loading">
|
||||
<div class="bar-load">
|
||||
<div class="bar-load from-left">
|
||||
<div class="text load">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar-read">
|
||||
<div class="bar-read from-left">
|
||||
<div class="text page"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -129,7 +129,7 @@
|
||||
<div class="col-sm-12">
|
||||
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
|
||||
{% if not profile %}
|
||||
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
|
||||
{% endif %}
|
||||
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
||||
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
{% macro user_table_row(parameter, edit_text, show_text, validate, button=False, id=0) -%}
|
||||
{% macro user_table_row(parameter, edit_text, show_text, validate, elements=False) -%}
|
||||
<th data-field="{{ parameter }}" id="{{ parameter }}"
|
||||
data-name="{{ parameter }}"
|
||||
data-visible="{{visiblility.get(parameter)}}"
|
||||
@ -11,8 +11,22 @@
|
||||
data-sortable="true"
|
||||
{% endif %}
|
||||
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
|
||||
{% if button %}
|
||||
<!--div><button data-id="{{id}}" data-toggle="modal" data-target="#restrictModal" class="btn btn-default button_head disabled" aria-disabled="true">{{edit_text}}</button></div--><br>
|
||||
{% if elements %}
|
||||
<div class="multi_select">
|
||||
<select class="multi_selector" id="{{ parameter }}" data-live-search="true" data-style="btn-default" data-dropup-auto="false" aria-disabled="true" multiple disabled>
|
||||
{% for tag in elements %}
|
||||
<option class="tags_click" value="{{tag.id}}">{% if tag.name %}{{tag.name}}{% else %}{{tag.value}}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="btn-group btn-group-justified" role="group">
|
||||
<div class="btn-group" role="group">
|
||||
<div class="multi_head btn btn-default hidden" data-set="remove" data-name="{{parameter}}" aria-disabled="true">{{_('Remove')}}</div>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<div class="multi_head btn btn-default hidden" data-set="add" data-name="{{parameter}}" aria-disabled="true">{{_('Add')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ show_text }}
|
||||
</th>
|
||||
@ -23,15 +37,17 @@
|
||||
data-visible="{{element.get(array_field)}}"
|
||||
data-column="{{value.get(array_field)}}"
|
||||
data-formatter="checkboxFormatter">
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="radio" class="check_head" name="options_{{array_field}}" onchange="checkboxHeader('false', '{{parameter}}', {{value.get(array_field)}})" disabled>{{_('Deny')}}
|
||||
</label>
|
||||
<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>
|
||||
<div>
|
||||
|
||||
<input type="radio" class="check_head" data-set="true" data-val={{value.get(array_field)}} name="options_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Allow')}}
|
||||
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label>
|
||||
<input type="radio" class="check_head" name="options_{{array_field}}" onchange="checkboxHeader('true', '{{parameter}}', {{value.get(array_field)}})" disabled>{{_('Allow')}}
|
||||
</label>
|
||||
</div>
|
||||
{{show_text}}
|
||||
</th>
|
||||
@ -48,14 +64,14 @@
|
||||
data-editable-source={{url}}
|
||||
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
|
||||
<div>
|
||||
<select id="select_{{ parameter }}" class="header_select" onchange="selectHeader(this, '{{parameter}}')" disabled="">
|
||||
<select id="select_{{ parameter }}" class="header_select" disabled="">
|
||||
<option value="none">{{ _('Select...') }}</option>
|
||||
<option value="all">{{ _('Show All') }}</option>
|
||||
{% for language in languages %}
|
||||
<option value="{{language.lang_code}}">{{language.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div><br>
|
||||
|
||||
</div>
|
||||
{{ show_text }}
|
||||
</th>
|
||||
{%- endmacro %}
|
||||
@ -71,13 +87,13 @@
|
||||
data-editable-source={{url}}
|
||||
{% if validate %}data-edit-validate="{{ _('This Field is Required') }}"{% endif %}>
|
||||
<div>
|
||||
<select id="select_{{ parameter }}" class="header_select" onchange="selectHeader(this, '{{parameter}}')" disabled="">
|
||||
<select id="select_{{ parameter }}" class="header_select" disabled="">
|
||||
<option value="None">{{_('Select...')}}</option>
|
||||
{% for translation in translations %}
|
||||
<option value="{{translation}}">{{translation.display_name|capitalize}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div><br>
|
||||
</div>
|
||||
{{ show_text }}
|
||||
</th>
|
||||
{%- endmacro %}
|
||||
@ -86,6 +102,7 @@
|
||||
{% block header %}
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-select.min.css') }}" rel="stylesheet" >
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<h2 class="{{page}}">{{_(title)}}</h2>
|
||||
@ -106,20 +123,21 @@
|
||||
{{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), false) }}
|
||||
{{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }}
|
||||
{{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }}
|
||||
{{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, true, 0) }}
|
||||
{{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), false, true, 1) }}
|
||||
{{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, true, 2) }}
|
||||
{{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), false, true, 3) }}
|
||||
{{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, tags) }}
|
||||
{{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), false, tags) }}
|
||||
{{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, custom_values) }}
|
||||
{{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), false, custom_values) }}
|
||||
{{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "upload_role",_('Upload'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "download_role", _('Download'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}}
|
||||
{{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}}
|
||||
{{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}}
|
||||
{{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show read/unread selection'), visiblility, sidebar_settings)}}
|
||||
{{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}}
|
||||
{{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show category selection'), visiblility, sidebar_settings)}}
|
||||
{{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show random books'), visiblility, sidebar_settings)}}
|
||||
@ -136,6 +154,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="errorlink">
|
||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Back')}}</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block modal %}
|
||||
{{ delete_confirm_modal() }}
|
||||
@ -146,7 +167,7 @@
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/table.js') }}"></script>
|
||||
<script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -44,7 +44,7 @@ from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float,
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy.sql.expression import func
|
||||
try:
|
||||
# Compability with sqlalchemy 2.0
|
||||
# Compatibility with sqlalchemy 2.0
|
||||
from sqlalchemy.orm import declarative_base
|
||||
except ImportError:
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
@ -713,9 +713,12 @@ def init_db(app_db_path):
|
||||
create_anonymous_user(session)
|
||||
|
||||
if cli.user_credentials:
|
||||
username, password = cli.user_credentials.split(':')
|
||||
username, password = cli.user_credentials.split(':', 1)
|
||||
user = session.query(User).filter(func.lower(User.name) == username.lower()).first()
|
||||
if user:
|
||||
if not password:
|
||||
print("Empty password is not allowed")
|
||||
sys.exit(4)
|
||||
user.password = generate_password_hash(password)
|
||||
if session_commit() == "":
|
||||
print("Password for user '{}' changed".format(username))
|
||||
|
@ -103,20 +103,21 @@ class Updater(threading.Thread):
|
||||
time.sleep(2)
|
||||
return True
|
||||
except requests.exceptions.HTTPError as ex:
|
||||
log.info(u'HTTP Error %s', ex)
|
||||
log.error(u'HTTP Error %s', ex)
|
||||
self.status = 8
|
||||
except requests.exceptions.ConnectionError:
|
||||
log.info(u'Connection error')
|
||||
log.error(u'Connection error')
|
||||
self.status = 9
|
||||
except requests.exceptions.Timeout:
|
||||
log.info(u'Timeout while establishing connection')
|
||||
log.error(u'Timeout while establishing connection')
|
||||
self.status = 10
|
||||
except (requests.exceptions.RequestException, zipfile.BadZipFile):
|
||||
self.status = 11
|
||||
log.info(u'General error')
|
||||
except (IOError, OSError):
|
||||
log.error(u'General error')
|
||||
except (IOError, OSError) as ex:
|
||||
self.status = 12
|
||||
log.info(u'Update File Could Not be Saved in Temp Dir')
|
||||
log.error(u'Possible Reason for error: update file could not be saved in temp dir')
|
||||
log.debug_or_exception(ex)
|
||||
self.pause()
|
||||
return False
|
||||
|
||||
@ -182,39 +183,50 @@ class Updater(threading.Thread):
|
||||
|
||||
@classmethod
|
||||
def moveallfiles(cls, root_src_dir, root_dst_dir):
|
||||
change_permissions = True
|
||||
new_permissions = os.stat(root_dst_dir)
|
||||
if sys.platform == "win32" or sys.platform == "darwin":
|
||||
change_permissions = False
|
||||
else:
|
||||
log.debug('Update on OS-System : %s', sys.platform)
|
||||
log.debug('Performing Update on OS-System: %s', sys.platform)
|
||||
change_permissions = (sys.platform == "win32" or sys.platform == "darwin")
|
||||
for src_dir, __, files in os.walk(root_src_dir):
|
||||
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
|
||||
if not os.path.exists(dst_dir):
|
||||
os.makedirs(dst_dir)
|
||||
log.debug('Create-Dir: %s', dst_dir)
|
||||
try:
|
||||
os.makedirs(dst_dir)
|
||||
log.debug('Create directory: {}', dst_dir)
|
||||
except OSError as e:
|
||||
log.error('Failed creating folder: {} with error {}'.format(dst_dir, e))
|
||||
if change_permissions:
|
||||
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
|
||||
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
|
||||
try:
|
||||
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
|
||||
except OSError as e:
|
||||
old_permissions = os.stat(dst_dir)
|
||||
log.error('Failed changing permissions of %s. Before: %s:%s After %s:%s error: %s',
|
||||
dst_dir, old_permissions.st_uid, old_permissions.st_gid,
|
||||
new_permissions.st_uid, new_permissions.st_gid, e)
|
||||
for file_ in files:
|
||||
src_file = os.path.join(src_dir, file_)
|
||||
dst_file = os.path.join(dst_dir, file_)
|
||||
if os.path.exists(dst_file):
|
||||
if change_permissions:
|
||||
permission = os.stat(dst_file)
|
||||
log.debug('Remove file before copy: %s', dst_file)
|
||||
os.remove(dst_file)
|
||||
try:
|
||||
os.remove(dst_file)
|
||||
log.debug('Remove file before copy: %s', dst_file)
|
||||
except OSError as e:
|
||||
log.error('Failed removing file: {} with error {}'.format(dst_file, e))
|
||||
else:
|
||||
if change_permissions:
|
||||
permission = new_permissions
|
||||
shutil.move(src_file, dst_dir)
|
||||
log.debug('Move File %s to %s', src_file, dst_dir)
|
||||
try:
|
||||
shutil.move(src_file, dst_dir)
|
||||
log.debug('Move File %s to %s', src_file, dst_dir)
|
||||
except OSError as ex:
|
||||
log.error('Failed moving file from {} to {} with error {}'.format(src_file, dst_dir, ex))
|
||||
if change_permissions:
|
||||
try:
|
||||
os.chown(dst_file, permission.st_uid, permission.st_gid)
|
||||
except OSError as e:
|
||||
old_permissions = os.stat(dst_file)
|
||||
log.debug('Fail change permissions of %s. Before: %s:%s After %s:%s error: %s',
|
||||
log.error('Failed changing permissions of %s. Before: %s:%s After %s:%s error: %s',
|
||||
dst_file, old_permissions.st_uid, old_permissions.st_gid,
|
||||
permission.st_uid, permission.st_gid, e)
|
||||
return
|
||||
@ -266,9 +278,8 @@ class Updater(threading.Thread):
|
||||
shutil.rmtree(item_path, ignore_errors=True)
|
||||
else:
|
||||
try:
|
||||
log.debug("Delete file %s", item_path)
|
||||
# log_from_thread("Delete file " + item_path)
|
||||
os.remove(item_path)
|
||||
log.debug("Delete file %s", item_path)
|
||||
except OSError:
|
||||
log.debug("Could not remove: %s", item_path)
|
||||
shutil.rmtree(source, ignore_errors=True)
|
||||
@ -283,11 +294,13 @@ class Updater(threading.Thread):
|
||||
@classmethod
|
||||
def _nightly_version_info(cls):
|
||||
if is_sha1(constants.NIGHTLY_VERSION[0]) and len(constants.NIGHTLY_VERSION[1]) > 0:
|
||||
log.debug("Nightly version: {}, {}".format(constants.NIGHTLY_VERSION[0], constants.NIGHTLY_VERSION[1]))
|
||||
return {'version': constants.NIGHTLY_VERSION[0], 'datetime': constants.NIGHTLY_VERSION[1]}
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _stable_version_info(cls):
|
||||
log.debug("Stable version: {}".format(constants.STABLE_VERSION))
|
||||
return constants.STABLE_VERSION # Current version
|
||||
|
||||
@staticmethod
|
||||
@ -381,6 +394,7 @@ class Updater(threading.Thread):
|
||||
|
||||
# if 'committer' in update_data and 'message' in update_data:
|
||||
try:
|
||||
log.debug("A new update is available.")
|
||||
status['success'] = True
|
||||
status['message'] = _(
|
||||
u'A new update is available. Click on the button below to update to the latest version.')
|
||||
@ -401,6 +415,7 @@ class Updater(threading.Thread):
|
||||
except (IndexError, KeyError):
|
||||
status['success'] = False
|
||||
status['message'] = _(u'Could not fetch update information')
|
||||
log.error("Could not fetch update information")
|
||||
return json.dumps(status)
|
||||
return ''
|
||||
|
||||
@ -468,6 +483,7 @@ class Updater(threading.Thread):
|
||||
# we are already on newest version, no update available
|
||||
if 'tag_name' not in commit[0]:
|
||||
status['message'] = _(u'Unexpected data while reading update information')
|
||||
log.error("Unexpected data while reading update information")
|
||||
return json.dumps(status)
|
||||
if commit[0]['tag_name'] == version:
|
||||
status.update({
|
||||
|
@ -75,8 +75,9 @@ def load_user_from_auth_header(header_val):
|
||||
basic_username = basic_password = '' # nosec
|
||||
try:
|
||||
header_val = base64.b64decode(header_val).decode('utf-8')
|
||||
basic_username = header_val.split(':')[0]
|
||||
basic_password = header_val.split(':')[1]
|
||||
# Users with colon are invalid: rfc7617 page 4
|
||||
basic_username = header_val.split(':', 1)[0]
|
||||
basic_password = header_val.split(':', 1)[1]
|
||||
except (TypeError, UnicodeDecodeError, binascii.Error):
|
||||
pass
|
||||
user = _fetch_user_by_name(basic_username)
|
||||
|
98
cps/web.py
98
cps/web.py
@ -26,6 +26,7 @@ from datetime import datetime
|
||||
import json
|
||||
import mimetypes
|
||||
import chardet # dependency of requests
|
||||
import copy
|
||||
|
||||
from babel.dates import format_date
|
||||
from babel import Locale as LC
|
||||
@ -183,11 +184,12 @@ def toggle_read(book_id):
|
||||
calibre_db.session.add(new_cc)
|
||||
calibre_db.session.commit()
|
||||
except (KeyError, AttributeError):
|
||||
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
|
||||
log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
||||
return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column), 400
|
||||
except (OperationalError, InvalidRequestError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error(u"Read status could not set: %e", e)
|
||||
|
||||
return "Read status could not set: {}".format(e), 400
|
||||
return ""
|
||||
|
||||
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST'])
|
||||
@ -753,20 +755,50 @@ def books_table():
|
||||
@web.route("/ajax/listbooks")
|
||||
@login_required
|
||||
def list_books():
|
||||
off = request.args.get("offset") or 0
|
||||
limit = request.args.get("limit") or config.config_books_per_page
|
||||
# sort = request.args.get("sort")
|
||||
if request.args.get("order") == 'desc':
|
||||
order = [db.Books.timestamp.desc()]
|
||||
else:
|
||||
order = [db.Books.timestamp.asc()]
|
||||
off = int(request.args.get("offset") or 0)
|
||||
limit = int(request.args.get("limit") or config.config_books_per_page)
|
||||
search = request.args.get("search")
|
||||
total_count = calibre_db.session.query(db.Books).count()
|
||||
if search:
|
||||
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit)
|
||||
sort = request.args.get("sort", "id")
|
||||
order = request.args.get("order", "").lower()
|
||||
state = None
|
||||
join = tuple()
|
||||
|
||||
if sort == "state":
|
||||
state = json.loads(request.args.get("state", "[]"))
|
||||
elif sort == "tags":
|
||||
order = [db.Tags.name.asc()] if order == "asc" else [db.Tags.name.desc()]
|
||||
join = db.books_tags_link,db.Books.id == db.books_tags_link.c.book, db.Tags
|
||||
elif sort == "series":
|
||||
order = [db.Series.name.asc()] if order == "asc" else [db.Series.name.desc()]
|
||||
join = db.books_series_link,db.Books.id == db.books_series_link.c.book, db.Series
|
||||
elif sort == "publishers":
|
||||
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
|
||||
join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
|
||||
elif sort == "authors":
|
||||
order = [db.Authors.name.asc()] if order == "asc" else [db.Authors.name.desc()]
|
||||
join = db.books_authors_link,db.Books.id == db.books_authors_link.c.book, db.Authors
|
||||
elif sort == "languages":
|
||||
order = [db.Languages.lang_code.asc()] if order == "asc" else [db.Languages.lang_code.desc()]
|
||||
join = db.books_languages_link,db.Books.id == db.books_languages_link.c.book, db.Languages
|
||||
elif order and sort in ["sort", "title", "authors_sort", "series_index"]:
|
||||
order = [text(sort + " " + order)]
|
||||
elif not state:
|
||||
order = [db.Books.timestamp.desc()]
|
||||
|
||||
total_count = filtered_count = calibre_db.session.query(db.Books).count()
|
||||
|
||||
if state:
|
||||
if search:
|
||||
books = calibre_db.search_query(search).all()
|
||||
filtered_count = len(books)
|
||||
else:
|
||||
books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
|
||||
entries = calibre_db.get_checkbox_sorted(books, state, off, limit,order)
|
||||
elif search:
|
||||
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit, *join)
|
||||
else:
|
||||
entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order)
|
||||
filtered_count = total_count
|
||||
entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order, *join)
|
||||
|
||||
for entry in entries:
|
||||
for index in range(0, len(entry.languages)):
|
||||
try:
|
||||
@ -816,9 +848,12 @@ def author_list():
|
||||
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
|
||||
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
|
||||
for entry in entries:
|
||||
# If not creating a copy, readonly databases can not display authornames with "|" in it as changing the name
|
||||
# starts a change session
|
||||
autor_copy = copy.deepcopy(entries)
|
||||
for entry in autor_copy:
|
||||
entry.Authors.name = entry.Authors.name.replace('|', ',')
|
||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||
return render_title_template('list.html', entries=autor_copy, folder='web.books_list', charlist=charlist,
|
||||
title=u"Authors", page="authorlist", data='author', order=order_no)
|
||||
else:
|
||||
abort(404)
|
||||
@ -1083,12 +1118,19 @@ def adv_search_ratings(q, rating_high, rating_low):
|
||||
def adv_search_read_status(q, read_status):
|
||||
if read_status:
|
||||
if config.config_read_column:
|
||||
if read_status == "True":
|
||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||
.filter(db.cc_classes[config.config_read_column].value == True)
|
||||
else:
|
||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
|
||||
try:
|
||||
if read_status == "True":
|
||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||
.filter(db.cc_classes[config.config_read_column].value == True)
|
||||
else:
|
||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
|
||||
except (KeyError, AttributeError):
|
||||
log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
||||
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
||||
column=config.config_read_column),
|
||||
category="error")
|
||||
return q
|
||||
else:
|
||||
if read_status == "True":
|
||||
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
|
||||
@ -1453,23 +1495,23 @@ def login():
|
||||
log.info(error)
|
||||
flash(_(u"Could not login: %(message)s", message=error), category="error")
|
||||
else:
|
||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ipAdress)
|
||||
ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ip_Address)
|
||||
flash(_(u"Wrong Username or Password"), category="error")
|
||||
else:
|
||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
if 'forgot' in form and form['forgot'] == 'forgot':
|
||||
if user != None and user.name != "Guest":
|
||||
ret, __ = reset_password(user.id)
|
||||
if ret == 1:
|
||||
flash(_(u"New Password was send to your email address"), category="info")
|
||||
log.info('Password reset for user "%s" IP-address: %s', form['username'], ipAdress)
|
||||
log.info('Password reset for user "%s" IP-address: %s', form['username'], ip_Address)
|
||||
else:
|
||||
log.error(u"An unknown error occurred. Please try again later")
|
||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||
else:
|
||||
flash(_(u"Please enter valid username to reset password"), category="error")
|
||||
log.warning('Username missing for password reset IP-address: %s', ipAdress)
|
||||
log.warning('Username missing for password reset IP-address: %s', ip_Address)
|
||||
else:
|
||||
if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest":
|
||||
login_user(user, remember=bool(form.get('remember_me')))
|
||||
@ -1478,7 +1520,7 @@ def login():
|
||||
config.config_is_initial = False
|
||||
return redirect_back(url_for("web.index"))
|
||||
else:
|
||||
log.warning('Login failed for user "%s" IP-address: %s', form['username'], ipAdress)
|
||||
log.warning('Login failed for user "%s" IP-address: %s', form['username'], ip_Address)
|
||||
flash(_(u"Wrong Username or Password"), category="error")
|
||||
|
||||
next_url = request.args.get('next', default=url_for("web.index"), type=str)
|
||||
|
1079
messages.pot
1079
messages.pot
File diff suppressed because it is too large
Load Diff
@ -26,8 +26,8 @@ python-ldap>=3.0.0,<3.4.0
|
||||
Flask-SimpleLDAP>=1.4.0,<1.5.0
|
||||
|
||||
#oauth
|
||||
Flask-Dance>=1.4.0,<3.1.0
|
||||
SQLAlchemy-Utils>=0.33.5,<0.37.0
|
||||
Flask-Dance>=1.4.0,<4.1.0
|
||||
SQLAlchemy-Utils>=0.33.5,<0.38.0
|
||||
|
||||
# extracting metadata
|
||||
lxml>=3.8.0,<4.7.0
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
|
||||
output_list = Array();
|
||||
|
||||
/* Level - 0: Summary; 1: Failed; 2: All; 3: Skipped 4: Error*/
|
||||
@ -24,9 +25,9 @@ function showCase(level) {
|
||||
row.classList.add('hiddenRow');
|
||||
}
|
||||
}
|
||||
// Show skipped if all or skipped or summary problems selected
|
||||
// Show skipped if all or skipped selected
|
||||
if (id.substr(0,2) == 'st') {
|
||||
if (level ==2 || level ==3 || level == 5) {
|
||||
if (level ==2 || level ==3) {
|
||||
row.classList.remove('hiddenRow');
|
||||
}
|
||||
else {
|
||||
|
Loading…
Reference in New Issue
Block a user