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