merge changes
This commit is contained in:
commit
1e40ffd1cc
206
cps/admin.py
206
cps/admin.py
|
@ -61,6 +61,7 @@ feature_support = {
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# pylint: disable=unused-import
|
||||||
import rarfile
|
import rarfile
|
||||||
feature_support['rar'] = True
|
feature_support['rar'] = True
|
||||||
except (ImportError, SyntaxError):
|
except (ImportError, SyntaxError):
|
||||||
|
@ -184,10 +185,10 @@ def admin():
|
||||||
else:
|
else:
|
||||||
commit = version['version']
|
commit = version['version']
|
||||||
|
|
||||||
allUser = ub.session.query(ub.User).all()
|
all_user = ub.session.query(ub.User).all()
|
||||||
email_settings = config.get_mail_settings()
|
email_settings = config.get_mail_settings()
|
||||||
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
||||||
return render_title_template("admin.html", allUser=allUser, email=email_settings, config=config, commit=commit,
|
return render_title_template("admin.html", allUser=all_user, email=email_settings, config=config, commit=commit,
|
||||||
feature_support=feature_support, kobo_support=kobo_support,
|
feature_support=feature_support, kobo_support=kobo_support,
|
||||||
title=_(u"Admin page"), page="admin")
|
title=_(u"Admin page"), page="admin")
|
||||||
|
|
||||||
|
@ -729,35 +730,13 @@ def _configuration_logfile_helper(to_save, gdrive_error):
|
||||||
return reboot_required, None
|
return reboot_required, None
|
||||||
|
|
||||||
|
|
||||||
def _configuration_ldap_helper(to_save, gdrive_error):
|
def _configuration_ldap_check(reboot_required, to_save, gdrive_error):
|
||||||
reboot_required = False
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_provider_url")
|
|
||||||
reboot_required |= _config_int(to_save, "config_ldap_port")
|
|
||||||
reboot_required |= _config_int(to_save, "config_ldap_authentication")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_dn")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_serv_username")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_user_object")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_group_object_filter")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_group_members_field")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_member_user_object")
|
|
||||||
reboot_required |= _config_checkbox(to_save, "config_ldap_openldap")
|
|
||||||
reboot_required |= _config_int(to_save, "config_ldap_encryption")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_cacert_path")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_cert_path")
|
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_key_path")
|
|
||||||
_config_string(to_save, "config_ldap_group_name")
|
|
||||||
if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "":
|
|
||||||
reboot_required |= 1
|
|
||||||
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8')
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
if not config.config_ldap_provider_url \
|
if not config.config_ldap_provider_url \
|
||||||
or not config.config_ldap_port \
|
or not config.config_ldap_port \
|
||||||
or not config.config_ldap_dn \
|
or not config.config_ldap_dn \
|
||||||
or not config.config_ldap_user_object:
|
or not config.config_ldap_user_object:
|
||||||
return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, '
|
return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, '
|
||||||
'Port, DN and User Object Identifier'), gdrive_error)
|
'Port, DN and User Object Identifier'), gdrive_error)
|
||||||
|
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
||||||
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
|
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
|
||||||
|
@ -767,6 +746,14 @@ def _configuration_ldap_helper(to_save, gdrive_error):
|
||||||
if not config.config_ldap_serv_username:
|
if not config.config_ldap_serv_username:
|
||||||
return reboot_required, _configuration_result('Please Enter a LDAP Service Account', gdrive_error)
|
return reboot_required, _configuration_result('Please Enter a LDAP Service Account', gdrive_error)
|
||||||
|
|
||||||
|
if config.config_ldap_group_object_filter:
|
||||||
|
if config.config_ldap_group_object_filter.count("%s") != 1:
|
||||||
|
return reboot_required, \
|
||||||
|
_configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'),
|
||||||
|
gdrive_error)
|
||||||
|
if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"):
|
||||||
|
return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'),
|
||||||
|
gdrive_error)
|
||||||
if config.config_ldap_group_object_filter:
|
if config.config_ldap_group_object_filter:
|
||||||
if config.config_ldap_group_object_filter.count("%s") != 1:
|
if config.config_ldap_group_object_filter.count("%s") != 1:
|
||||||
return reboot_required, \
|
return reboot_required, \
|
||||||
|
@ -806,6 +793,31 @@ def _configuration_ldap_helper(to_save, gdrive_error):
|
||||||
return reboot_required, None
|
return reboot_required, None
|
||||||
|
|
||||||
|
|
||||||
|
def _configuration_ldap_helper(to_save, gdrive_error):
|
||||||
|
reboot_required = False
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_provider_url")
|
||||||
|
reboot_required |= _config_int(to_save, "config_ldap_port")
|
||||||
|
reboot_required |= _config_int(to_save, "config_ldap_authentication")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_dn")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_serv_username")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_user_object")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_group_object_filter")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_group_members_field")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_member_user_object")
|
||||||
|
reboot_required |= _config_checkbox(to_save, "config_ldap_openldap")
|
||||||
|
reboot_required |= _config_int(to_save, "config_ldap_encryption")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_cacert_path")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_cert_path")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_key_path")
|
||||||
|
_config_string(to_save, "config_ldap_group_name")
|
||||||
|
if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "":
|
||||||
|
reboot_required |= 1
|
||||||
|
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8')
|
||||||
|
config.save()
|
||||||
|
|
||||||
|
return _configuration_ldap_check(reboot_required, to_save, gdrive_error)
|
||||||
|
|
||||||
|
|
||||||
def _configuration_update_helper(configured):
|
def _configuration_update_helper(configured):
|
||||||
reboot_required = False
|
reboot_required = False
|
||||||
db_change = False
|
db_change = False
|
||||||
|
@ -1011,18 +1023,33 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
|
||||||
ub.session.rollback()
|
ub.session.rollback()
|
||||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
flash(_(u"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():
|
||||||
|
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||||
|
ub.session_commit()
|
||||||
|
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
||||||
|
return redirect(url_for('admin.admin'))
|
||||||
|
else:
|
||||||
|
flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), category="error")
|
||||||
|
return redirect(url_for('admin.admin'))
|
||||||
|
|
||||||
|
|
||||||
|
def save_edited_user(content):
|
||||||
|
try:
|
||||||
|
ub.session_commit()
|
||||||
|
flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success")
|
||||||
|
except IntegrityError:
|
||||||
|
ub.session.rollback()
|
||||||
|
flash(_(u"An unknown error occured."), category="error")
|
||||||
|
except OperationalError:
|
||||||
|
ub.session.rollback()
|
||||||
|
flash(_(u"Settings DB is not Writeable"), category="error")
|
||||||
|
|
||||||
|
|
||||||
def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||||
if "delete" in to_save:
|
if "delete" in to_save:
|
||||||
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
return delete_user(content)
|
||||||
ub.User.id != content.id).count():
|
|
||||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
|
||||||
ub.session_commit()
|
|
||||||
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
|
||||||
return redirect(url_for('admin.admin'))
|
|
||||||
else:
|
|
||||||
flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), 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:
|
||||||
|
@ -1090,15 +1117,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||||
|
|
||||||
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
|
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
|
||||||
content.kindle_mail = to_save["kindle_mail"]
|
content.kindle_mail = to_save["kindle_mail"]
|
||||||
try:
|
return save_edited_user(content)
|
||||||
ub.session_commit()
|
|
||||||
flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success")
|
|
||||||
except IntegrityError:
|
|
||||||
ub.session.rollback()
|
|
||||||
flash(_(u"An unknown error occured."), category="error")
|
|
||||||
except OperationalError:
|
|
||||||
ub.session.rollback()
|
|
||||||
flash(_(u"Settings DB is not Writeable"), category="error")
|
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
||||||
|
@ -1310,6 +1329,55 @@ def get_updater_status():
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def create_ldap_user(user, user_data, config):
|
||||||
|
imported = 0
|
||||||
|
showtext = None
|
||||||
|
|
||||||
|
user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object)
|
||||||
|
|
||||||
|
username = user_data[user_login_field][0].decode('utf-8')
|
||||||
|
# check for duplicate username
|
||||||
|
if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first():
|
||||||
|
# if ub.session.query(ub.User).filter(ub.User.nickname == username).first():
|
||||||
|
log.warning("LDAP User %s Already in Database", user_data)
|
||||||
|
return imported, showtext
|
||||||
|
|
||||||
|
kindlemail = ''
|
||||||
|
if 'mail' in user_data:
|
||||||
|
useremail = user_data['mail'][0].decode('utf-8')
|
||||||
|
if len(user_data['mail']) > 1:
|
||||||
|
kindlemail = user_data['mail'][1].decode('utf-8')
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.debug('No Mail Field Found in LDAP Response')
|
||||||
|
useremail = username + '@email.com'
|
||||||
|
# check for duplicate email
|
||||||
|
if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first():
|
||||||
|
log.warning("LDAP Email %s Already in Database", user_data)
|
||||||
|
return imported, showtext
|
||||||
|
|
||||||
|
content = ub.User()
|
||||||
|
content.nickname = username
|
||||||
|
content.password = '' # dummy password which will be replaced by ldap one
|
||||||
|
content.email = useremail
|
||||||
|
content.kindle_mail = kindlemail
|
||||||
|
content.role = config.config_default_role
|
||||||
|
content.sidebar_view = config.config_default_show
|
||||||
|
content.allowed_tags = config.config_allowed_tags
|
||||||
|
content.denied_tags = config.config_denied_tags
|
||||||
|
content.allowed_column_value = config.config_allowed_column_value
|
||||||
|
content.denied_column_value = config.config_denied_column_value
|
||||||
|
ub.session.add(content)
|
||||||
|
try:
|
||||||
|
ub.session.commit()
|
||||||
|
imported = 1
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Failed to create LDAP user: %s - %s", user, e)
|
||||||
|
ub.session.rollback()
|
||||||
|
showtext = _(u'Failed to Create at Least One LDAP User')
|
||||||
|
return imported, showtext
|
||||||
|
|
||||||
|
|
||||||
@admi.route('/import_ldap_users')
|
@admi.route('/import_ldap_users')
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
|
@ -1349,47 +1417,11 @@ def import_ldap_users():
|
||||||
log.debug_or_exception(e)
|
log.debug_or_exception(e)
|
||||||
continue
|
continue
|
||||||
if user_data:
|
if user_data:
|
||||||
user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object)
|
success, txt = create_ldap_user(user, user_data, config)
|
||||||
|
# In case of error store text for showing it
|
||||||
username = user_data[user_login_field][0].decode('utf-8')
|
if txt:
|
||||||
# check for duplicate username
|
showtext['text'] = txt
|
||||||
if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first():
|
imported += success
|
||||||
# if ub.session.query(ub.User).filter(ub.User.nickname == username).first():
|
|
||||||
log.warning("LDAP User %s Already in Database", user_data)
|
|
||||||
continue
|
|
||||||
|
|
||||||
kindlemail = ''
|
|
||||||
if 'mail' in user_data:
|
|
||||||
useremail = user_data['mail'][0].decode('utf-8')
|
|
||||||
if len(user_data['mail']) > 1:
|
|
||||||
kindlemail = user_data['mail'][1].decode('utf-8')
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.debug('No Mail Field Found in LDAP Response')
|
|
||||||
useremail = username + '@email.com'
|
|
||||||
# check for duplicate email
|
|
||||||
if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first():
|
|
||||||
log.warning("LDAP Email %s Already in Database", user_data)
|
|
||||||
continue
|
|
||||||
content = ub.User()
|
|
||||||
content.nickname = username
|
|
||||||
content.password = '' # dummy password which will be replaced by ldap one
|
|
||||||
content.email = useremail
|
|
||||||
content.kindle_mail = kindlemail
|
|
||||||
content.role = config.config_default_role
|
|
||||||
content.sidebar_view = config.config_default_show
|
|
||||||
content.allowed_tags = config.config_allowed_tags
|
|
||||||
content.denied_tags = config.config_denied_tags
|
|
||||||
content.allowed_column_value = config.config_allowed_column_value
|
|
||||||
content.denied_column_value = config.config_denied_column_value
|
|
||||||
ub.session.add(content)
|
|
||||||
try:
|
|
||||||
ub.session.commit()
|
|
||||||
imported += 1
|
|
||||||
except Exception as e:
|
|
||||||
log.warning("Failed to create LDAP user: %s - %s", user, e)
|
|
||||||
ub.session.rollback()
|
|
||||||
showtext['text'] = _(u'Failed to Create at Least One LDAP User')
|
|
||||||
else:
|
else:
|
||||||
log.warning("LDAP User: %s Not Found", user)
|
log.warning("LDAP User: %s Not Found", user)
|
||||||
showtext['text'] = _(u'At Least One LDAP User Not Found in Database')
|
showtext['text'] = _(u'At Least One LDAP User Not Found in Database')
|
||||||
|
|
70
cps/comic.py
70
cps/comic.py
|
@ -74,6 +74,41 @@ def _cover_processing(tmp_file_name, img, extension):
|
||||||
return tmp_cover_name
|
return tmp_cover_name
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_Cover_from_archive(original_file_extension, tmp_file_name, rarExecutable):
|
||||||
|
cover_data = None
|
||||||
|
if original_file_extension.upper() == '.CBZ':
|
||||||
|
cf = zipfile.ZipFile(tmp_file_name)
|
||||||
|
for name in cf.namelist():
|
||||||
|
ext = os.path.splitext(name)
|
||||||
|
if len(ext) > 1:
|
||||||
|
extension = ext[1].lower()
|
||||||
|
if extension in COVER_EXTENSIONS:
|
||||||
|
cover_data = cf.read(name)
|
||||||
|
break
|
||||||
|
elif original_file_extension.upper() == '.CBT':
|
||||||
|
cf = tarfile.TarFile(tmp_file_name)
|
||||||
|
for name in cf.getnames():
|
||||||
|
ext = os.path.splitext(name)
|
||||||
|
if len(ext) > 1:
|
||||||
|
extension = ext[1].lower()
|
||||||
|
if extension in COVER_EXTENSIONS:
|
||||||
|
cover_data = cf.extractfile(name).read()
|
||||||
|
break
|
||||||
|
elif original_file_extension.upper() == '.CBR' and use_rarfile:
|
||||||
|
try:
|
||||||
|
rarfile.UNRAR_TOOL = rarExecutable
|
||||||
|
cf = rarfile.RarFile(tmp_file_name)
|
||||||
|
for name in cf.getnames():
|
||||||
|
ext = os.path.splitext(name)
|
||||||
|
if len(ext) > 1:
|
||||||
|
extension = ext[1].lower()
|
||||||
|
if extension in COVER_EXTENSIONS:
|
||||||
|
cover_data = cf.read(name)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log.debug('Rarfile failed with error: %s', e)
|
||||||
|
return cover_data
|
||||||
|
|
||||||
|
|
||||||
def _extractCover(tmp_file_name, original_file_extension, rarExecutable):
|
def _extractCover(tmp_file_name, original_file_extension, rarExecutable):
|
||||||
cover_data = extension = None
|
cover_data = extension = None
|
||||||
|
@ -87,37 +122,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExecutable):
|
||||||
cover_data = archive.getPage(index)
|
cover_data = archive.getPage(index)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if original_file_extension.upper() == '.CBZ':
|
cover_data = _extract_Cover_from_archive(original_file_extension, tmp_file_name, rarExecutable)
|
||||||
cf = zipfile.ZipFile(tmp_file_name)
|
|
||||||
for name in cf.namelist():
|
|
||||||
ext = os.path.splitext(name)
|
|
||||||
if len(ext) > 1:
|
|
||||||
extension = ext[1].lower()
|
|
||||||
if extension in COVER_EXTENSIONS:
|
|
||||||
cover_data = cf.read(name)
|
|
||||||
break
|
|
||||||
elif original_file_extension.upper() == '.CBT':
|
|
||||||
cf = tarfile.TarFile(tmp_file_name)
|
|
||||||
for name in cf.getnames():
|
|
||||||
ext = os.path.splitext(name)
|
|
||||||
if len(ext) > 1:
|
|
||||||
extension = ext[1].lower()
|
|
||||||
if extension in COVER_EXTENSIONS:
|
|
||||||
cover_data = cf.extractfile(name).read()
|
|
||||||
break
|
|
||||||
elif original_file_extension.upper() == '.CBR' and use_rarfile:
|
|
||||||
try:
|
|
||||||
rarfile.UNRAR_TOOL = rarExecutable
|
|
||||||
cf = rarfile.RarFile(tmp_file_name)
|
|
||||||
for name in cf.getnames():
|
|
||||||
ext = os.path.splitext(name)
|
|
||||||
if len(ext) > 1:
|
|
||||||
extension = ext[1].lower()
|
|
||||||
if extension in COVER_EXTENSIONS:
|
|
||||||
cover_data = cf.read(name)
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
log.debug('Rarfile failed with error: %s', e)
|
|
||||||
return _cover_processing(tmp_file_name, cover_data, extension)
|
return _cover_processing(tmp_file_name, cover_data, extension)
|
||||||
|
|
||||||
|
|
||||||
|
@ -142,7 +147,8 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r
|
||||||
file_path=tmp_file_path,
|
file_path=tmp_file_path,
|
||||||
extension=original_file_extension,
|
extension=original_file_extension,
|
||||||
title=loadedMetadata.title or original_file_name,
|
title=loadedMetadata.title or original_file_name,
|
||||||
author=" & ".join([credit["person"] for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u'Unknown',
|
author=" & ".join([credit["person"]
|
||||||
|
for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u'Unknown',
|
||||||
cover=_extractCover(tmp_file_path, original_file_extension, rarExecutable),
|
cover=_extractCover(tmp_file_path, original_file_extension, rarExecutable),
|
||||||
description=loadedMetadata.comments or "",
|
description=loadedMetadata.comments or "",
|
||||||
tags="",
|
tags="",
|
||||||
|
|
|
@ -146,15 +146,16 @@ class _ConfigSQL(object):
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
change = False
|
change = False
|
||||||
if self.config_converterpath == None:
|
if self.config_converterpath == None: # pylint: disable=access-member-before-definition
|
||||||
change = True
|
change = True
|
||||||
self.config_converterpath = autodetect_calibre_binary()
|
self.config_converterpath = autodetect_calibre_binary()
|
||||||
|
|
||||||
if self.config_kepubifypath == None:
|
if self.config_kepubifypath == None: # pylint: disable=access-member-before-definition
|
||||||
|
|
||||||
change = True
|
change = True
|
||||||
self.config_kepubifypath = autodetect_kepubify_binary()
|
self.config_kepubifypath = autodetect_kepubify_binary()
|
||||||
|
|
||||||
if self.config_rarfile_location == None:
|
if self.config_rarfile_location == None: # pylint: disable=access-member-before-definition
|
||||||
change = True
|
change = True
|
||||||
self.config_rarfile_location = autodetect_unrar_binary()
|
self.config_rarfile_location = autodetect_unrar_binary()
|
||||||
if change:
|
if change:
|
||||||
|
@ -181,7 +182,8 @@ class _ConfigSQL(object):
|
||||||
return None
|
return None
|
||||||
return self.config_keyfile
|
return self.config_keyfile
|
||||||
|
|
||||||
def get_config_ipaddress(self):
|
@staticmethod
|
||||||
|
def get_config_ipaddress():
|
||||||
return cli.ipadress or ""
|
return cli.ipadress or ""
|
||||||
|
|
||||||
def _has_role(self, role_flag):
|
def _has_role(self, role_flag):
|
||||||
|
@ -299,6 +301,7 @@ class _ConfigSQL(object):
|
||||||
have_metadata_db = os.path.isfile(db_file)
|
have_metadata_db = os.path.isfile(db_file)
|
||||||
self.db_configured = have_metadata_db
|
self.db_configured = have_metadata_db
|
||||||
constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')]
|
constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')]
|
||||||
|
# pylint: disable=access-member-before-definition
|
||||||
logfile = logger.setup(self.config_logfile, self.config_log_level)
|
logfile = logger.setup(self.config_logfile, self.config_log_level)
|
||||||
if logfile != self.config_logfile:
|
if logfile != self.config_logfile:
|
||||||
log.warning("Log path %s not valid, falling back to default", self.config_logfile)
|
log.warning("Log path %s not valid, falling back to default", self.config_logfile)
|
||||||
|
|
|
@ -104,7 +104,7 @@ LDAP_AUTH_SIMPLE = 0
|
||||||
|
|
||||||
DEFAULT_MAIL_SERVER = "mail.example.org"
|
DEFAULT_MAIL_SERVER = "mail.example.org"
|
||||||
|
|
||||||
DEFAULT_PASSWORD = "admin123"
|
DEFAULT_PASSWORD = "admin123" # nosec # noqa
|
||||||
DEFAULT_PORT = 8083
|
DEFAULT_PORT = 8083
|
||||||
env_CALIBRE_PORT = os.environ.get("CALIBRE_PORT", DEFAULT_PORT)
|
env_CALIBRE_PORT = os.environ.get("CALIBRE_PORT", DEFAULT_PORT)
|
||||||
try:
|
try:
|
||||||
|
|
22
cps/db.py
22
cps/db.py
|
@ -156,10 +156,8 @@ class Identifiers(Base):
|
||||||
return u"https://portal.issn.org/resource/ISSN/{0}".format(self.val)
|
return u"https://portal.issn.org/resource/ISSN/{0}".format(self.val)
|
||||||
elif format_type == "isfdb":
|
elif format_type == "isfdb":
|
||||||
return u"http://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val)
|
return u"http://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val)
|
||||||
elif format_type == "url":
|
|
||||||
return u"{0}".format(self.val)
|
|
||||||
else:
|
else:
|
||||||
return u""
|
return u"{0}".format(self.val)
|
||||||
|
|
||||||
|
|
||||||
class Comments(Base):
|
class Comments(Base):
|
||||||
|
@ -386,14 +384,14 @@ class Custom_Columns(Base):
|
||||||
|
|
||||||
class AlchemyEncoder(json.JSONEncoder):
|
class AlchemyEncoder(json.JSONEncoder):
|
||||||
|
|
||||||
def default(self, obj):
|
def default(self, o):
|
||||||
if isinstance(obj.__class__, DeclarativeMeta):
|
if isinstance(o.__class__, DeclarativeMeta):
|
||||||
# an SQLAlchemy class
|
# an SQLAlchemy class
|
||||||
fields = {}
|
fields = {}
|
||||||
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
|
for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata']:
|
||||||
if field == 'books':
|
if field == 'books':
|
||||||
continue
|
continue
|
||||||
data = obj.__getattribute__(field)
|
data = o.__getattribute__(field)
|
||||||
try:
|
try:
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = data.replace("'", "\'")
|
data = data.replace("'", "\'")
|
||||||
|
@ -413,12 +411,12 @@ class AlchemyEncoder(json.JSONEncoder):
|
||||||
else:
|
else:
|
||||||
json.dumps(data)
|
json.dumps(data)
|
||||||
fields[field] = data
|
fields[field] = data
|
||||||
except:
|
except Exception:
|
||||||
fields[field] = ""
|
fields[field] = ""
|
||||||
# a json-encodable dict
|
# a json-encodable dict
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
return json.JSONEncoder.default(self, obj)
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
class CalibreDB():
|
class CalibreDB():
|
||||||
|
@ -565,8 +563,8 @@ class CalibreDB():
|
||||||
def get_book_by_uuid(self, book_uuid):
|
def get_book_by_uuid(self, book_uuid):
|
||||||
return self.session.query(Books).filter(Books.uuid == book_uuid).first()
|
return self.session.query(Books).filter(Books.uuid == book_uuid).first()
|
||||||
|
|
||||||
def get_book_format(self, book_id, format):
|
def get_book_format(self, book_id, file_format):
|
||||||
return self.session.query(Data).filter(Data.book == book_id).filter(Data.format == format).first()
|
return self.session.query(Data).filter(Data.book == book_id).filter(Data.format == file_format).first()
|
||||||
|
|
||||||
# Language and content filters for displaying in the UI
|
# Language and content filters for displaying in the UI
|
||||||
def common_filters(self, allow_show_archived=False):
|
def common_filters(self, allow_show_archived=False):
|
||||||
|
@ -744,7 +742,7 @@ class CalibreDB():
|
||||||
if old_session:
|
if old_session:
|
||||||
try:
|
try:
|
||||||
old_session.close()
|
old_session.close()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if old_session.bind:
|
if old_session.bind:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -47,7 +47,7 @@ except ImportError as err:
|
||||||
|
|
||||||
current_milli_time = lambda: int(round(time() * 1000))
|
current_milli_time = lambda: int(round(time() * 1000))
|
||||||
|
|
||||||
gdrive_watch_callback_token = 'target=calibreweb-watch_files'
|
gdrive_watch_callback_token = 'target=calibreweb-watch_files' #nosec
|
||||||
|
|
||||||
|
|
||||||
@gdrive.route("/authenticate")
|
@gdrive.route("/authenticate")
|
||||||
|
|
104
cps/helper.py
104
cps/helper.py
|
@ -134,63 +134,71 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
|
||||||
taskMessage=_(u"Registration e-mail for user: %(name)s", name=user_name),
|
taskMessage=_(u"Registration e-mail for user: %(name)s", name=user_name),
|
||||||
text=txt
|
text=txt
|
||||||
))
|
))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def check_send_to_kindle_without_converter(entry):
|
||||||
|
bookformats = list()
|
||||||
|
# no converter - only for mobi and pdf formats
|
||||||
|
for ele in iter(entry.data):
|
||||||
|
if ele.uncompressed_size < config.mail_size:
|
||||||
|
if 'MOBI' in ele.format:
|
||||||
|
bookformats.append({'format': 'Mobi',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
||||||
|
if 'PDF' in ele.format:
|
||||||
|
bookformats.append({'format': 'Pdf',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
||||||
|
if 'AZW' in ele.format:
|
||||||
|
bookformats.append({'format': 'Azw',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Azw')})
|
||||||
|
return bookformats
|
||||||
|
|
||||||
|
def check_send_to_kindle_with_converter(entry):
|
||||||
|
bookformats = list()
|
||||||
|
formats = list()
|
||||||
|
for ele in iter(entry.data):
|
||||||
|
if ele.uncompressed_size < config.mail_size:
|
||||||
|
formats.append(ele.format)
|
||||||
|
if 'MOBI' in formats:
|
||||||
|
bookformats.append({'format': 'Mobi',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
||||||
|
if 'AZW' in formats:
|
||||||
|
bookformats.append({'format': 'Azw',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Azw')})
|
||||||
|
if 'PDF' in formats:
|
||||||
|
bookformats.append({'format': 'Pdf',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
||||||
|
if 'EPUB' in formats and 'MOBI' not in formats:
|
||||||
|
bookformats.append({'format': 'Mobi',
|
||||||
|
'convert': 1,
|
||||||
|
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
||||||
|
orig='Epub',
|
||||||
|
format='Mobi')})
|
||||||
|
if 'AZW3' in formats and not 'MOBI' in formats:
|
||||||
|
bookformats.append({'format': 'Mobi',
|
||||||
|
'convert': 2,
|
||||||
|
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
||||||
|
orig='Azw3',
|
||||||
|
format='Mobi')})
|
||||||
|
return bookformats
|
||||||
|
|
||||||
|
|
||||||
def check_send_to_kindle(entry):
|
def check_send_to_kindle(entry):
|
||||||
"""
|
"""
|
||||||
returns all available book formats for sending to Kindle
|
returns all available book formats for sending to Kindle
|
||||||
"""
|
"""
|
||||||
if len(entry.data):
|
if len(entry.data):
|
||||||
bookformats = list()
|
|
||||||
if not config.config_converterpath:
|
if not config.config_converterpath:
|
||||||
# no converter - only for mobi and pdf formats
|
book_formats = check_send_to_kindle_with_converter(entry)
|
||||||
for ele in iter(entry.data):
|
|
||||||
if ele.uncompressed_size < config.mail_size:
|
|
||||||
if 'MOBI' in ele.format:
|
|
||||||
bookformats.append({'format': 'Mobi',
|
|
||||||
'convert': 0,
|
|
||||||
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
|
||||||
if 'PDF' in ele.format:
|
|
||||||
bookformats.append({'format': 'Pdf',
|
|
||||||
'convert': 0,
|
|
||||||
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
|
||||||
if 'AZW' in ele.format:
|
|
||||||
bookformats.append({'format': 'Azw',
|
|
||||||
'convert': 0,
|
|
||||||
'text': _('Send %(format)s to Kindle', format='Azw')})
|
|
||||||
else:
|
else:
|
||||||
formats = list()
|
book_formats = check_send_to_kindle_with_converter(entry)
|
||||||
for ele in iter(entry.data):
|
return book_formats
|
||||||
if ele.uncompressed_size < config.mail_size:
|
|
||||||
formats.append(ele.format)
|
|
||||||
if 'MOBI' in formats:
|
|
||||||
bookformats.append({'format': 'Mobi',
|
|
||||||
'convert': 0,
|
|
||||||
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
|
||||||
if 'AZW' in formats:
|
|
||||||
bookformats.append({'format': 'Azw',
|
|
||||||
'convert': 0,
|
|
||||||
'text': _('Send %(format)s to Kindle', format='Azw')})
|
|
||||||
if 'PDF' in formats:
|
|
||||||
bookformats.append({'format': 'Pdf',
|
|
||||||
'convert': 0,
|
|
||||||
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
|
||||||
if config.config_converterpath:
|
|
||||||
if 'EPUB' in formats and 'MOBI' not in formats:
|
|
||||||
bookformats.append({'format': 'Mobi',
|
|
||||||
'convert':1,
|
|
||||||
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
|
||||||
orig='Epub',
|
|
||||||
format='Mobi')})
|
|
||||||
if 'AZW3' in formats and not 'MOBI' in formats:
|
|
||||||
bookformats.append({'format': 'Mobi',
|
|
||||||
'convert': 2,
|
|
||||||
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
|
||||||
orig='Azw3',
|
|
||||||
format='Mobi')})
|
|
||||||
return bookformats
|
|
||||||
else:
|
else:
|
||||||
log.error(u'Cannot find book entry %d', entry.id)
|
log.error(u'Cannot find book entry %d', entry.id)
|
||||||
return None
|
return None
|
||||||
|
@ -742,7 +750,7 @@ def format_runtime(runtime):
|
||||||
# helper function to apply localize status information in tasklist entries
|
# helper function to apply localize status information in tasklist entries
|
||||||
def render_task_status(tasklist):
|
def render_task_status(tasklist):
|
||||||
renderedtasklist = list()
|
renderedtasklist = list()
|
||||||
for num, user, added, task in tasklist:
|
for __, user, added, task in tasklist:
|
||||||
if user == current_user.nickname or current_user.role_admin():
|
if user == current_user.nickname or current_user.role_admin():
|
||||||
ret = {}
|
ret = {}
|
||||||
if task.start_time:
|
if task.start_time:
|
||||||
|
|
|
@ -71,7 +71,7 @@ def get_valid_language_codes(locale, language_names, remainder=None):
|
||||||
languages = list()
|
languages = list()
|
||||||
if "" in language_names:
|
if "" in language_names:
|
||||||
language_names.remove("")
|
language_names.remove("")
|
||||||
for k, v in get_language_names(locale).items():
|
for k, __ in get_language_names(locale).items():
|
||||||
if k in language_names:
|
if k in language_names:
|
||||||
languages.append(k)
|
languages.append(k)
|
||||||
language_names.remove(k)
|
language_names.remove(k)
|
||||||
|
|
16
cps/kobo.py
16
cps/kobo.py
|
@ -42,8 +42,7 @@ from flask import (
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.sql.expression import and_, or_
|
from sqlalchemy.sql.expression import and_
|
||||||
from sqlalchemy.orm import load_only
|
|
||||||
from sqlalchemy.exc import StatementError
|
from sqlalchemy.exc import StatementError
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -893,17 +892,6 @@ def HandleProductsRequest(dummy=None):
|
||||||
return redirect_or_proxy_request()
|
return redirect_or_proxy_request()
|
||||||
|
|
||||||
|
|
||||||
'''@kobo.errorhandler(404)
|
|
||||||
def handle_404(err):
|
|
||||||
# This handler acts as a catch-all for endpoints that we don't have an interest in
|
|
||||||
# implementing (e.g: v1/analytics/gettests, v1/user/recommendations, etc)
|
|
||||||
if err:
|
|
||||||
print('404')
|
|
||||||
return jsonify(error=str(err)), 404
|
|
||||||
log.debug("Unknown Request received: %s, method: %s, data: %s", request.base_url, request.method, request.data)
|
|
||||||
return redirect_or_proxy_request()'''
|
|
||||||
|
|
||||||
|
|
||||||
def make_calibre_web_auth_response():
|
def make_calibre_web_auth_response():
|
||||||
# As described in kobo_auth.py, CalibreWeb doesn't make use practical use of this auth/device API call for
|
# As described in kobo_auth.py, CalibreWeb doesn't make use practical use of this auth/device API call for
|
||||||
# authentation (nor for authorization). We return a dummy response just to keep the device happy.
|
# authentation (nor for authorization). We return a dummy response just to keep the device happy.
|
||||||
|
@ -947,7 +935,7 @@ def HandleInitRequest():
|
||||||
store_response_json = store_response.json()
|
store_response_json = store_response.json()
|
||||||
if "Resources" in store_response_json:
|
if "Resources" in store_response_json:
|
||||||
kobo_resources = store_response_json["Resources"]
|
kobo_resources = store_response_json["Resources"]
|
||||||
except:
|
except Exception:
|
||||||
log.error("Failed to receive or parse response from Kobo's init endpoint. Falling back to un-proxied mode.")
|
log.error("Failed to receive or parse response from Kobo's init endpoint. Falling back to un-proxied mode.")
|
||||||
if not kobo_resources:
|
if not kobo_resources:
|
||||||
kobo_resources = NATIVE_KOBO_RESOURCES()
|
kobo_resources = NATIVE_KOBO_RESOURCES()
|
||||||
|
|
238
cps/oauth.py
238
cps/oauth.py
|
@ -19,7 +19,6 @@
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
from flask import session
|
from flask import session
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
|
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
@ -34,134 +33,131 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
|
||||||
class OAuthBackend(SQLAlchemyBackend):
|
|
||||||
"""
|
|
||||||
Stores and retrieves OAuth tokens using a relational database through
|
|
||||||
the `SQLAlchemy`_ ORM.
|
|
||||||
|
|
||||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
class OAuthBackend(SQLAlchemyBackend):
|
||||||
"""
|
"""
|
||||||
def __init__(self, model, session, provider_id,
|
Stores and retrieves OAuth tokens using a relational database through
|
||||||
user=None, user_id=None, user_required=None, anon_user=None,
|
the `SQLAlchemy`_ ORM.
|
||||||
cache=None):
|
|
||||||
self.provider_id = provider_id
|
|
||||||
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
|
|
||||||
|
|
||||||
def get(self, blueprint, user=None, user_id=None):
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '':
|
"""
|
||||||
return session[self.provider_id + '_oauth_token']
|
def __init__(self, model, session, provider_id,
|
||||||
# check cache
|
user=None, user_id=None, user_required=None, anon_user=None,
|
||||||
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
|
cache=None):
|
||||||
token = self.cache.get(cache_key)
|
self.provider_id = provider_id
|
||||||
if token:
|
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
|
||||||
return token
|
|
||||||
|
|
||||||
# if not cached, make database queries
|
|
||||||
query = (
|
|
||||||
self.session.query(self.model)
|
|
||||||
.filter_by(provider=self.provider_id)
|
|
||||||
)
|
|
||||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
|
||||||
u = first(_get_real_user(ref, self.anon_user)
|
|
||||||
for ref in (user, self.user, blueprint.config.get("user")))
|
|
||||||
|
|
||||||
use_provider_user_id = False
|
|
||||||
if self.provider_id + '_oauth_user_id' in session and session[self.provider_id + '_oauth_user_id'] != '':
|
|
||||||
query = query.filter_by(provider_user_id=session[self.provider_id + '_oauth_user_id'])
|
|
||||||
use_provider_user_id = True
|
|
||||||
|
|
||||||
if self.user_required and not u and not uid and not use_provider_user_id:
|
|
||||||
# raise ValueError("Cannot get OAuth token without an associated user")
|
|
||||||
return None
|
|
||||||
# check for user ID
|
|
||||||
if hasattr(self.model, "user_id") and uid:
|
|
||||||
query = query.filter_by(user_id=uid)
|
|
||||||
# check for user (relationship property)
|
|
||||||
elif hasattr(self.model, "user") and u:
|
|
||||||
query = query.filter_by(user=u)
|
|
||||||
# if we have the property, but not value, filter by None
|
|
||||||
elif hasattr(self.model, "user_id"):
|
|
||||||
query = query.filter_by(user_id=None)
|
|
||||||
# run query
|
|
||||||
try:
|
|
||||||
token = query.one().token
|
|
||||||
except NoResultFound:
|
|
||||||
token = None
|
|
||||||
|
|
||||||
# cache the result
|
|
||||||
self.cache.set(cache_key, token)
|
|
||||||
|
|
||||||
|
def get(self, blueprint, user=None, user_id=None):
|
||||||
|
if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '':
|
||||||
|
return session[self.provider_id + '_oauth_token']
|
||||||
|
# check cache
|
||||||
|
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
|
||||||
|
token = self.cache.get(cache_key)
|
||||||
|
if token:
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def set(self, blueprint, token, user=None, user_id=None):
|
# if not cached, make database queries
|
||||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
query = (
|
||||||
u = first(_get_real_user(ref, self.anon_user)
|
self.session.query(self.model)
|
||||||
for ref in (user, self.user, blueprint.config.get("user")))
|
.filter_by(provider=self.provider_id)
|
||||||
|
)
|
||||||
|
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||||
|
u = first(_get_real_user(ref, self.anon_user)
|
||||||
|
for ref in (user, self.user, blueprint.config.get("user")))
|
||||||
|
|
||||||
if self.user_required and not u and not uid:
|
use_provider_user_id = False
|
||||||
raise ValueError("Cannot set OAuth token without an associated user")
|
if self.provider_id + '_oauth_user_id' in session and session[self.provider_id + '_oauth_user_id'] != '':
|
||||||
|
query = query.filter_by(provider_user_id=session[self.provider_id + '_oauth_user_id'])
|
||||||
|
use_provider_user_id = True
|
||||||
|
|
||||||
# if there was an existing model, delete it
|
if self.user_required and not u and not uid and not use_provider_user_id:
|
||||||
existing_query = (
|
# raise ValueError("Cannot get OAuth token without an associated user")
|
||||||
self.session.query(self.model)
|
return None
|
||||||
.filter_by(provider=self.provider_id)
|
# check for user ID
|
||||||
)
|
if hasattr(self.model, "user_id") and uid:
|
||||||
# check for user ID
|
query = query.filter_by(user_id=uid)
|
||||||
has_user_id = hasattr(self.model, "user_id")
|
# check for user (relationship property)
|
||||||
if has_user_id and uid:
|
elif hasattr(self.model, "user") and u:
|
||||||
existing_query = existing_query.filter_by(user_id=uid)
|
query = query.filter_by(user=u)
|
||||||
# check for user (relationship property)
|
# if we have the property, but not value, filter by None
|
||||||
has_user = hasattr(self.model, "user")
|
elif hasattr(self.model, "user_id"):
|
||||||
if has_user and u:
|
query = query.filter_by(user_id=None)
|
||||||
existing_query = existing_query.filter_by(user=u)
|
# run query
|
||||||
# queue up delete query -- won't be run until commit()
|
try:
|
||||||
existing_query.delete()
|
token = query.one().token
|
||||||
# create a new model for this token
|
except NoResultFound:
|
||||||
kwargs = {
|
token = None
|
||||||
"provider": self.provider_id,
|
|
||||||
"token": token,
|
|
||||||
}
|
|
||||||
if has_user_id and uid:
|
|
||||||
kwargs["user_id"] = uid
|
|
||||||
if has_user and u:
|
|
||||||
kwargs["user"] = u
|
|
||||||
self.session.add(self.model(**kwargs))
|
|
||||||
# commit to delete and add simultaneously
|
|
||||||
self.session.commit()
|
|
||||||
# invalidate cache
|
|
||||||
self.cache.delete(self.make_cache_key(
|
|
||||||
blueprint=blueprint, user=user, user_id=user_id
|
|
||||||
))
|
|
||||||
|
|
||||||
def delete(self, blueprint, user=None, user_id=None):
|
# cache the result
|
||||||
query = (
|
self.cache.set(cache_key, token)
|
||||||
self.session.query(self.model)
|
|
||||||
.filter_by(provider=self.provider_id)
|
|
||||||
)
|
|
||||||
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
|
||||||
u = first(_get_real_user(ref, self.anon_user)
|
|
||||||
for ref in (user, self.user, blueprint.config.get("user")))
|
|
||||||
|
|
||||||
if self.user_required and not u and not uid:
|
return token
|
||||||
raise ValueError("Cannot delete OAuth token without an associated user")
|
|
||||||
|
|
||||||
# check for user ID
|
def set(self, blueprint, token, user=None, user_id=None):
|
||||||
if hasattr(self.model, "user_id") and uid:
|
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||||
query = query.filter_by(user_id=uid)
|
u = first(_get_real_user(ref, self.anon_user)
|
||||||
# check for user (relationship property)
|
for ref in (user, self.user, blueprint.config.get("user")))
|
||||||
elif hasattr(self.model, "user") and u:
|
|
||||||
query = query.filter_by(user=u)
|
|
||||||
# if we have the property, but not value, filter by None
|
|
||||||
elif hasattr(self.model, "user_id"):
|
|
||||||
query = query.filter_by(user_id=None)
|
|
||||||
# run query
|
|
||||||
query.delete()
|
|
||||||
self.session.commit()
|
|
||||||
# invalidate cache
|
|
||||||
self.cache.delete(self.make_cache_key(
|
|
||||||
blueprint=blueprint, user=user, user_id=user_id,
|
|
||||||
))
|
|
||||||
|
|
||||||
except Exception:
|
if self.user_required and not u and not uid:
|
||||||
pass
|
raise ValueError("Cannot set OAuth token without an associated user")
|
||||||
|
|
||||||
|
# if there was an existing model, delete it
|
||||||
|
existing_query = (
|
||||||
|
self.session.query(self.model)
|
||||||
|
.filter_by(provider=self.provider_id)
|
||||||
|
)
|
||||||
|
# check for user ID
|
||||||
|
has_user_id = hasattr(self.model, "user_id")
|
||||||
|
if has_user_id and uid:
|
||||||
|
existing_query = existing_query.filter_by(user_id=uid)
|
||||||
|
# check for user (relationship property)
|
||||||
|
has_user = hasattr(self.model, "user")
|
||||||
|
if has_user and u:
|
||||||
|
existing_query = existing_query.filter_by(user=u)
|
||||||
|
# queue up delete query -- won't be run until commit()
|
||||||
|
existing_query.delete()
|
||||||
|
# create a new model for this token
|
||||||
|
kwargs = {
|
||||||
|
"provider": self.provider_id,
|
||||||
|
"token": token,
|
||||||
|
}
|
||||||
|
if has_user_id and uid:
|
||||||
|
kwargs["user_id"] = uid
|
||||||
|
if has_user and u:
|
||||||
|
kwargs["user"] = u
|
||||||
|
self.session.add(self.model(**kwargs))
|
||||||
|
# commit to delete and add simultaneously
|
||||||
|
self.session.commit()
|
||||||
|
# invalidate cache
|
||||||
|
self.cache.delete(self.make_cache_key(
|
||||||
|
blueprint=blueprint, user=user, user_id=user_id
|
||||||
|
))
|
||||||
|
|
||||||
|
def delete(self, blueprint, user=None, user_id=None):
|
||||||
|
query = (
|
||||||
|
self.session.query(self.model)
|
||||||
|
.filter_by(provider=self.provider_id)
|
||||||
|
)
|
||||||
|
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
|
||||||
|
u = first(_get_real_user(ref, self.anon_user)
|
||||||
|
for ref in (user, self.user, blueprint.config.get("user")))
|
||||||
|
|
||||||
|
if self.user_required and not u and not uid:
|
||||||
|
raise ValueError("Cannot delete OAuth token without an associated user")
|
||||||
|
|
||||||
|
# check for user ID
|
||||||
|
if hasattr(self.model, "user_id") and uid:
|
||||||
|
query = query.filter_by(user_id=uid)
|
||||||
|
# check for user (relationship property)
|
||||||
|
elif hasattr(self.model, "user") and u:
|
||||||
|
query = query.filter_by(user=u)
|
||||||
|
# if we have the property, but not value, filter by None
|
||||||
|
elif hasattr(self.model, "user_id"):
|
||||||
|
query = query.filter_by(user_id=None)
|
||||||
|
# run query
|
||||||
|
query.delete()
|
||||||
|
self.session.commit()
|
||||||
|
# invalidate cache
|
||||||
|
self.cache.delete(self.make_cache_key(
|
||||||
|
blueprint=blueprint, user=user, user_id=user_id,
|
||||||
|
))
|
||||||
|
|
|
@ -35,7 +35,10 @@ from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from . import constants, logger, config, app, ub
|
from . import constants, logger, config, app, ub
|
||||||
|
|
||||||
from .oauth import OAuthBackend, backend_resultcode
|
try:
|
||||||
|
from .oauth import OAuthBackend, backend_resultcode
|
||||||
|
except NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
oauth_check = {}
|
oauth_check = {}
|
||||||
|
|
|
@ -137,8 +137,8 @@ class WebServer(object):
|
||||||
|
|
||||||
return sock, _readable_listen_address(*address)
|
return sock, _readable_listen_address(*address)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def _get_args_for_reloading(self):
|
def _get_args_for_reloading():
|
||||||
"""Determine how the script was executed, and return the args needed
|
"""Determine how the script was executed, and return the args needed
|
||||||
to execute it again in a new process.
|
to execute it again in a new process.
|
||||||
Code from https://github.com/pyload/pyload. Author GammaC0de, voulter
|
Code from https://github.com/pyload/pyload. Author GammaC0de, voulter
|
||||||
|
|
|
@ -64,7 +64,7 @@ class SyncToken:
|
||||||
books_last_modified: Datetime representing the last modified book that the device knows about.
|
books_last_modified: Datetime representing the last modified book that the device knows about.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SYNC_TOKEN_HEADER = "x-kobo-synctoken"
|
SYNC_TOKEN_HEADER = "x-kobo-synctoken" # nosec
|
||||||
VERSION = "1-1-0"
|
VERSION = "1-1-0"
|
||||||
LAST_MODIFIED_ADDED_VERSION = "1-1-0"
|
LAST_MODIFIED_ADDED_VERSION = "1-1-0"
|
||||||
MIN_VERSION = "1-0-0"
|
MIN_VERSION = "1-0-0"
|
||||||
|
@ -91,7 +91,7 @@ class SyncToken:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
raw_kobo_store_token="",
|
raw_kobo_store_token="", # nosec
|
||||||
books_last_created=datetime.min,
|
books_last_created=datetime.min,
|
||||||
books_last_modified=datetime.min,
|
books_last_modified=datetime.min,
|
||||||
archive_last_modified=datetime.min,
|
archive_last_modified=datetime.min,
|
||||||
|
@ -110,7 +110,7 @@ class SyncToken:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_headers(headers):
|
def from_headers(headers):
|
||||||
sync_token_header = headers.get(SyncToken.SYNC_TOKEN_HEADER, "")
|
sync_token_header = headers.get(SyncToken.SYNC_TOKEN_HEADER, "")
|
||||||
if sync_token_header == "":
|
if sync_token_header == "": # nosec
|
||||||
return SyncToken()
|
return SyncToken()
|
||||||
|
|
||||||
# On the first sync from a Kobo device, we may receive the SyncToken
|
# On the first sync from a Kobo device, we may receive the SyncToken
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
|
body.serieslist.grid-view div.container-fluid > div > div.col-sm-10::before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
|
||||||
.cover .badge{
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #cc7b19;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0 8px;
|
|
||||||
box-shadow: 0 0 4px rgba(0,0,0,.6);
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
.cover{
|
|
||||||
box-shadow: 0 0 4px rgba(0,0,0,.6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cover .read{
|
.cover .badge {
|
||||||
padding: 0 0px;
|
position: absolute;
|
||||||
line-height: 15px;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #cc7b19;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0 8px;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover .read {
|
||||||
|
padding: 0 0;
|
||||||
|
line-height: 15px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ body {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ body {
|
||||||
|
|
||||||
#sidebar a.active,
|
#sidebar a.active,
|
||||||
#sidebar a.active img + span {
|
#sidebar a.active img + span {
|
||||||
background-color: #45B29D;
|
background-color: #45b29d;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar li img {
|
#sidebar li img {
|
||||||
|
@ -99,7 +98,7 @@ body {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress .bar-read {
|
#progress .bar-read {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #45b29d;
|
background-color: #45b29d;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,19 +66,12 @@ body {
|
||||||
right: 40px;
|
right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
xmp,
|
|
||||||
pre,
|
|
||||||
plaintext {
|
|
||||||
display: block;
|
|
||||||
font-family: -moz-fixed;
|
|
||||||
white-space: pre;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin: 1em 0;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
font-family: -moz-fixed;
|
font-family: -moz-fixed, sans-serif;
|
||||||
column-count: 2;
|
column-count: 2;
|
||||||
-webkit-columns: 2;
|
-webkit-columns: 2;
|
||||||
-moz-columns: 2;
|
-moz-columns: 2;
|
||||||
|
|
|
@ -35,7 +35,6 @@ body {
|
||||||
height: 8%;
|
height: 8%;
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
/* margin: 0 50px 0 50px; */
|
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #4f4f4f;
|
color: #4f4f4f;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
|
@ -73,6 +72,16 @@ body {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#panels a {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 18px;
|
||||||
|
height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
color: #ccc;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
#titlebar a:active {
|
#titlebar a:active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
@ -114,7 +123,7 @@ body {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin-top: -192px;
|
margin-top: -192px;
|
||||||
font-size: 64px;
|
font-size: 64px;
|
||||||
color: #E2E2E2;
|
color: #e2e2e2;
|
||||||
font-family: arial, sans-serif;
|
font-family: arial, sans-serif;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -148,12 +157,6 @@ body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar.open {
|
|
||||||
/* left: 0; */
|
|
||||||
/* -webkit-transform: translate(0, 0);
|
|
||||||
-moz-transform: translate(0, 0); */
|
|
||||||
}
|
|
||||||
|
|
||||||
#main.closed {
|
#main.closed {
|
||||||
/* left: 300px; */
|
/* left: 300px; */
|
||||||
-webkit-transform: translate(300px, 0);
|
-webkit-transform: translate(300px, 0);
|
||||||
|
@ -196,19 +199,24 @@ body {
|
||||||
|
|
||||||
#title-controls { float: right; }
|
#title-controls { float: right; }
|
||||||
|
|
||||||
#panels a {
|
|
||||||
visibility: hidden;
|
|
||||||
width: 18px;
|
|
||||||
height: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
color: #ccc;
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#panels a::before { visibility: visible; }
|
#panels a::before { visibility: visible; }
|
||||||
#panels a:hover { color: #aaa; }
|
#panels a:hover { color: #aaa; }
|
||||||
|
|
||||||
|
.list_item.currentChapter > a,
|
||||||
|
.list_item a:hover {
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list_item a {
|
||||||
|
color: #aaa;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults a {
|
||||||
|
color: #aaa;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
#panels a:active {
|
#panels a:active {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
margin: 1px 0 -1px 6px;
|
margin: 1px 0 -1px 6px;
|
||||||
|
@ -238,7 +246,7 @@ input:-moz-placeholder { color: #454545; }
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
top: 10%;
|
top: 10%;
|
||||||
opacity: .15;
|
opacity: 0.15;
|
||||||
box-shadow: -2px 0 15px rgba(0, 0, 0, 1);
|
box-shadow: -2px 0 15px rgba(0, 0, 0, 1);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -281,6 +289,17 @@ input:-moz-placeholder { color: #454545; }
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list_item ul {
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list_item.currentChapter > ul,
|
||||||
|
.list_item.openChapter > ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
#tocView > ul,
|
#tocView > ul,
|
||||||
#bookmarksView > ul {
|
#bookmarksView > ul {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
|
@ -291,22 +310,41 @@ input:-moz-placeholder { color: #454545; }
|
||||||
|
|
||||||
#tocView li,
|
#tocView li,
|
||||||
#bookmarksView li {
|
#bookmarksView li {
|
||||||
margin-bottom:10px;
|
margin-bottom: 10px;
|
||||||
width: 225px;
|
width: 225px;
|
||||||
font-family: Georgia, "Times New Roman", Times, serif;
|
font-family: Georgia, "Times New Roman", Times, serif;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tocView li:active,
|
.md-content > div ul li {
|
||||||
#tocView li.currentChapter
|
padding: 5px 0;
|
||||||
{
|
}
|
||||||
|
|
||||||
|
#settingsPanel li {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults li {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 225px;
|
||||||
|
font-family: Georgia, "Times New Roman", Times, serif;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list_item a {
|
#notes li {
|
||||||
color: #aaa;
|
color: #eee;
|
||||||
text-decoration: none;
|
font-size: 12px;
|
||||||
|
width: 240px;
|
||||||
|
border-top: 1px #fff solid;
|
||||||
|
padding-top: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tocView li:active,
|
||||||
|
#tocView li.currentChapter {
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list_item a.chapter {
|
.list_item a.chapter {
|
||||||
|
@ -317,27 +355,11 @@ input:-moz-placeholder { color: #454545; }
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list_item.currentChapter > a,
|
|
||||||
.list_item a:hover {
|
|
||||||
color: #f1f1f1
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #tocView li.openChapter > a, */
|
/* #tocView li.openChapter > a, */
|
||||||
.list_item a:hover {
|
.list_item a:hover {
|
||||||
color: #e2e2e2;
|
color: #e2e2e2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list_item ul {
|
|
||||||
padding-left:10px;
|
|
||||||
margin-top: 8px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list_item.currentChapter > ul,
|
|
||||||
.list_item.openChapter > ul {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tocView.hidden {
|
#tocView.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -352,14 +374,14 @@ input:-moz-placeholder { color: #454545; }
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc_toggle:before {
|
.toc_toggle::before {
|
||||||
content: '▸';
|
content: '▸';
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-right: -4px;
|
margin-right: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currentChapter > .toc_toggle:before,
|
.currentChapter > .toc_toggle::before,
|
||||||
.openChapter > .toc_toggle:before {
|
.openChapter > .toc_toggle::before {
|
||||||
content: '▾';
|
content: '▾';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,18 +399,6 @@ input:-moz-placeholder { color: #454545; }
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#searchResults li {
|
|
||||||
margin-bottom:10px;
|
|
||||||
width: 225px;
|
|
||||||
font-family: Georgia, "Times New Roman", Times, serif;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchResults a {
|
|
||||||
color: #aaa;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchResults p {
|
#searchResults p {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -400,10 +410,21 @@ input:-moz-placeholder { color: #454545; }
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-content > div p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
#searchResults li > p {
|
#searchResults li > p {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#notes li a {
|
||||||
|
color: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
#searchResults li a:hover {
|
#searchResults li a:hover {
|
||||||
color: #e2e2e2;
|
color: #e2e2e2;
|
||||||
}
|
}
|
||||||
|
@ -414,22 +435,7 @@ input:-moz-placeholder { color: #454545; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#notes {
|
#notes {
|
||||||
padding: 0 0 0 34px;
|
padding: 0 0 0 34px;
|
||||||
}
|
|
||||||
|
|
||||||
#notes li {
|
|
||||||
color: #eee;
|
|
||||||
font-size: 12px;
|
|
||||||
width: 240px;
|
|
||||||
border-top: 1px #fff solid;
|
|
||||||
padding-top: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notes li a {
|
|
||||||
color: #fff;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#notes li a:hover {
|
#notes li a:hover {
|
||||||
|
@ -449,8 +455,9 @@ input:-moz-placeholder { color: #454545; }
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#note-text[disabled], #note-text[disabled="disabled"]{
|
#note-text[disabled],
|
||||||
opacity: 0.5;
|
#note-text[disabled="disabled"] {
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#note-anchor {
|
#note-anchor {
|
||||||
|
@ -462,6 +469,22 @@ input:-moz-placeholder { color: #454545; }
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-content h3 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 300;
|
||||||
|
opacity: 0.8;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-content > div ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
#settingsPanel h3 {
|
#settingsPanel h3 {
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
font-family: Georgia, "Times New Roman", Times, serif;
|
font-family: Georgia, "Times New Roman", Times, serif;
|
||||||
|
@ -473,31 +496,24 @@ input:-moz-placeholder { color: #454545; }
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settingsPanel li {
|
#settingsPanel .xsmall { font-size: x-small; }
|
||||||
font-size: 1em;
|
#settingsPanel .small { font-size: small; }
|
||||||
color: #f1f1f1;
|
#settingsPanel .medium { font-size: medium; }
|
||||||
}
|
#settingsPanel .large { font-size: large; }
|
||||||
|
#settingsPanel .xlarge { font-size: x-large; }
|
||||||
|
|
||||||
#settingsPanel .xsmall { font-size: x-small; }
|
.highlight { background-color: yellow; }
|
||||||
#settingsPanel .small { font-size: small; }
|
|
||||||
#settingsPanel .medium { font-size: medium; }
|
|
||||||
#settingsPanel .large { font-size: large; }
|
|
||||||
#settingsPanel .xlarge { font-size: x-large; }
|
|
||||||
|
|
||||||
.highlight { background-color: yellow }
|
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
// width: 50%;
|
|
||||||
width: 630px;
|
width: 630px;
|
||||||
height: auto;
|
height: auto;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
margin-left: -320px;
|
margin-left: -320px;
|
||||||
margin-top: -160px;
|
margin-top: -160px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
|
@ -516,12 +532,12 @@ input:-moz-placeholder { color: #454545; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-show {
|
.md-show {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-show ~ .overlay {
|
.md-show ~ .overlay {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content styles */
|
/* Content styles */
|
||||||
|
@ -534,17 +550,6 @@ input:-moz-placeholder { color: #454545; }
|
||||||
height: 320px;
|
height: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-content h3 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 6px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 300;
|
|
||||||
opacity: 0.8;
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-content > div {
|
.md-content > div {
|
||||||
padding: 15px 40px 30px;
|
padding: 15px 40px 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -552,20 +557,6 @@ input:-moz-placeholder { color: #454545; }
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-content > div p {
|
|
||||||
margin: 0;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-content > div ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 0 30px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-content > div ul li {
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-content button {
|
.md-content button {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
@ -593,7 +584,6 @@ input:-moz-placeholder { color: #454545; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-content > .closer {
|
.md-content > .closer {
|
||||||
//font-size: 18px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -602,7 +592,7 @@ input:-moz-placeholder { color: #454545; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1040px) and (orientation: portrait) {
|
@media only screen and (max-width: 1040px) and (orientation: portrait) {
|
||||||
#viewer{
|
#viewer {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
}
|
}
|
||||||
|
@ -614,7 +604,7 @@ input:-moz-placeholder { color: #454545; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 900px) {
|
@media only screen and (max-width: 900px) {
|
||||||
#viewer{
|
#viewer {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
margin-left: 20%;
|
margin-left: 20%;
|
||||||
}
|
}
|
||||||
|
@ -629,7 +619,7 @@ input:-moz-placeholder { color: #454545; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 550px) {
|
@media only screen and (max-width: 550px) {
|
||||||
#viewer{
|
#viewer {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
}
|
}
|
||||||
|
@ -653,9 +643,9 @@ input:-moz-placeholder { color: #454545; }
|
||||||
-webkit-transform: translate(0, 0);
|
-webkit-transform: translate(0, 0);
|
||||||
-moz-transform: translate(0, 0);
|
-moz-transform: translate(0, 0);
|
||||||
-ms-transform: translate(0, 0);
|
-ms-transform: translate(0, 0);
|
||||||
-webkit-transition: -webkit-transform .3s;
|
-webkit-transition: -webkit-transform 0.3s;
|
||||||
-moz-transition: -moz-transform .3s;
|
-moz-transition: -moz-transform 0.3s;
|
||||||
transition: -moz-transform .3s;
|
transition: -moz-transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main.closed {
|
#main.closed {
|
||||||
|
@ -664,11 +654,6 @@ input:-moz-placeholder { color: #454545; }
|
||||||
-ms-transform: translate(260px, 0);
|
-ms-transform: translate(260px, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#titlebar {
|
|
||||||
/* font-size: 16px; */
|
|
||||||
/* margin: 0 50px 0 50px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
#metainfo {
|
#metainfo {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
@ -681,130 +666,129 @@ input:-moz-placeholder { color: #454545; }
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tocView > ul{
|
#tocView > ul {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* For iPad portrait layouts only */
|
/* For iPad portrait layouts only */
|
||||||
@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: portrait) {
|
@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: portrait) {
|
||||||
#viewer iframe {
|
#viewer iframe {
|
||||||
width: 460px;
|
width: 460px;
|
||||||
height: 740px;
|
height: 740px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*For iPad landscape layouts only *//*
|
|
||||||
@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) {
|
|
||||||
#viewer iframe {
|
|
||||||
width: 460px;
|
|
||||||
height: 415px;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
@media only screen
|
@media only screen
|
||||||
and (min-device-width : 768px)
|
and (min-device-width: 768px)
|
||||||
and (max-device-width : 1024px)
|
and (max-device-width: 1024px)
|
||||||
and (orientation : landscape)
|
and (orientation: landscape)
|
||||||
/*and (-webkit-min-device-pixel-ratio: 2)*/ {
|
/* and (-webkit-min-device-pixel-ratio: 2)*/ {
|
||||||
#viewer{
|
#viewer {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#divider,
|
#divider,
|
||||||
#divider.show {
|
#divider.show {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*For iPad landscape layouts only */
|
/* For iPad landscape layouts only */
|
||||||
@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) {
|
@media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) {
|
||||||
#viewer iframe {
|
#viewer iframe {
|
||||||
width: 960px;
|
width: 960px;
|
||||||
height: 515px;
|
height: 515px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For iPhone 6\6s portrait layouts only */
|
/* For iPhone 6\6s portrait layouts only */
|
||||||
@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation: portrait) {
|
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (orientation: portrait) {
|
||||||
#viewer {
|
#viewer {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
}
|
}
|
||||||
#viewer iframe {
|
|
||||||
width: 300px;
|
#viewer iframe {
|
||||||
height: 480px;
|
width: 300px;
|
||||||
}
|
height: 480px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For iPhone 6\6s landscape layouts only */
|
/* For iPhone 6\6s landscape layouts only */
|
||||||
@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation: landscape) {
|
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (orientation: landscape) {
|
||||||
#viewer {
|
#viewer {
|
||||||
width: 450px;
|
width: 450px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
#viewer iframe {
|
|
||||||
width: 450px;
|
#viewer iframe {
|
||||||
height: 300px;
|
width: 450px;
|
||||||
}
|
height: 300px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For iPhone portrait layouts only */
|
/* For iPhone portrait layouts only */
|
||||||
@media only screen and (max-device-width: 374px) and (orientation: portrait) {
|
@media only screen and (max-device-width: 374px) and (orientation: portrait) {
|
||||||
#viewer {
|
#viewer {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
height: 432px;
|
height: 432px;
|
||||||
}
|
}
|
||||||
#viewer iframe {
|
|
||||||
width: 256px;
|
#viewer iframe {
|
||||||
height: 432px;
|
width: 256px;
|
||||||
}
|
height: 432px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For iPhone landscape layouts only */
|
/* For iPhone landscape layouts only */
|
||||||
@media only screen and (max-device-width: 374px) and (orientation: landscape) {
|
@media only screen and (max-device-width: 374px) and (orientation: landscape) {
|
||||||
#viewer iframe {
|
#viewer iframe {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
height: 124px;
|
height: 124px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
[class^="icon-"]::before,
|
||||||
font-family: "fontello", serif;
|
[class*=" icon-"]::before {
|
||||||
font-style: normal;
|
font-family: "fontello", serif;
|
||||||
font-weight: normal;
|
font-style: normal;
|
||||||
speak: none;
|
font-weight: normal;
|
||||||
display: inline-block;
|
speak: none;
|
||||||
text-decoration: inherit;
|
display: inline-block;
|
||||||
width: 1em;
|
text-decoration: inherit;
|
||||||
margin-right: 0.2em;
|
width: 1em;
|
||||||
text-align: center;
|
margin-right: 0.2em;
|
||||||
/* For safety - reset parent styles, that can break glyph codes*/
|
text-align: center;
|
||||||
font-variant: normal;
|
|
||||||
text-transform: none;
|
/* For safety - reset parent styles, that can break glyph codes */
|
||||||
/* you can be more comfortable with increased icons size */
|
font-variant: normal;
|
||||||
font-size: 112%;
|
text-transform: none;
|
||||||
|
|
||||||
|
/* you can be more comfortable with increased icons size */
|
||||||
|
font-size: 112%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-search:before { content: '\e807'; } /* '' */
|
.icon-search::before { content: '\e807'; } /* '' */
|
||||||
.icon-resize-full-1:before { content: '\e804'; } /* '' */
|
.icon-resize-full-1::before { content: '\e804'; } /* '' */
|
||||||
.icon-cancel-circled2:before { content: '\e80f'; } /* '' */
|
.icon-cancel-circled2::before { content: '\e80f'; } /* '' */
|
||||||
.icon-link:before { content: '\e80d'; } /* '' */
|
.icon-link::before { content: '\e80d'; } /* '' */
|
||||||
.icon-bookmark:before { content: '\e805'; } /* '' */
|
.icon-bookmark::before { content: '\e805'; } /* '' */
|
||||||
.icon-bookmark-empty:before { content: '\e806'; } /* '' */
|
.icon-bookmark-empty::before { content: '\e806'; } /* '' */
|
||||||
.icon-download-cloud:before { content: '\e811'; } /* '' */
|
.icon-download-cloud::before { content: '\e811'; } /* '' */
|
||||||
.icon-edit:before { content: '\e814'; } /* '' */
|
.icon-edit::before { content: '\e814'; } /* '' */
|
||||||
.icon-menu:before { content: '\e802'; } /* '' */
|
.icon-menu::before { content: '\e802'; } /* '' */
|
||||||
.icon-cog:before { content: '\e813'; } /* '' */
|
.icon-cog::before { content: '\e813'; } /* '' */
|
||||||
.icon-resize-full:before { content: '\e812'; } /* '' */
|
.icon-resize-full::before { content: '\e812'; } /* '' */
|
||||||
.icon-cancel-circled:before { content: '\e80e'; } /* '' */
|
.icon-cancel-circled::before { content: '\e80e'; } /* '' */
|
||||||
.icon-up-dir:before { content: '\e80c'; } /* '' */
|
.icon-up-dir::before { content: '\e80c'; } /* '' */
|
||||||
.icon-right-dir:before { content: '\e80b'; } /* '' */
|
.icon-right-dir::before { content: '\e80b'; } /* '' */
|
||||||
.icon-angle-right:before { content: '\e809'; } /* '' */
|
.icon-angle-right::before { content: '\e809'; } /* '' */
|
||||||
.icon-angle-down:before { content: '\e80a'; } /* '' */
|
.icon-angle-down::before { content: '\e80a'; } /* '' */
|
||||||
.icon-right:before { content: '\e815'; } /* '' */
|
.icon-right::before { content: '\e815'; } /* '' */
|
||||||
.icon-list-1:before { content: '\e803'; } /* '' */
|
.icon-list-1::before { content: '\e803'; } /* '' */
|
||||||
.icon-list-numbered:before { content: '\e801'; } /* '' */
|
.icon-list-numbered::before { content: '\e801'; } /* '' */
|
||||||
.icon-columns:before { content: '\e810'; } /* '' */
|
.icon-columns::before { content: '\e810'; } /* '' */
|
||||||
.icon-list:before { content: '\e800'; } /* '' */
|
.icon-list::before { content: '\e800'; } /* '' */
|
||||||
.icon-resize-small:before { content: '\e808'; } /* '' */
|
.icon-resize-small::before { content: '\e808'; } /* '' */
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
.tooltip.bottom .tooltip-inner {
|
.tooltip.bottom .tooltip-inner {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-family: Open Sans Semibold,Helvetica Neue,Helvetica,Arial,sans-serif;
|
font-family: Open Sans Semibold, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
|
@ -28,6 +28,11 @@ html.http-error {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f2f2f2;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.http-error body {
|
.http-error body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -41,32 +46,39 @@ html.http-error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
background: #f2f2f2;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body h2 {
|
body h2 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color:#444;
|
color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d; }
|
a,
|
||||||
|
.danger,
|
||||||
.book-remove:hover { color: #23527c; }
|
.book-remove,
|
||||||
|
.editable-empty,
|
||||||
|
.editable-empty:hover { color: #45b29d; }
|
||||||
|
.user-remove:hover { color: #23527c; }
|
||||||
.btn-default a { color: #444; }
|
.btn-default a { color: #444; }
|
||||||
|
.panel-title > a { text-decoration: none; }
|
||||||
|
|
||||||
|
.navigation li a {
|
||||||
|
color: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-default a:hover {
|
.btn-default a:hover {
|
||||||
color: #45b29d;
|
color: #45b29d;
|
||||||
text-decoration: None;
|
text-decoration: None;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-default:hover {
|
.btn-default:hover {
|
||||||
color: #45b29d;
|
color: #45b29d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editable-click, a.editable-click, a.editable-click:hover { border-bottom: None; }
|
.editable-click,
|
||||||
|
a.editable-click,
|
||||||
|
a.editable-click:hover { border-bottom: None; }
|
||||||
|
|
||||||
.navigation .nav-head {
|
.navigation .nav-head {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -79,11 +91,17 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation li a {
|
.book-meta .tags a { display: inline; }
|
||||||
color: #444;
|
table .bg-primary a { color: #fff; }
|
||||||
|
table .bg-dark-danger a { color: #fff; }
|
||||||
|
.book-meta .identifiers a { display: inline; }
|
||||||
|
|
||||||
|
.navigation .create-shelf a {
|
||||||
|
color: #fff;
|
||||||
|
background: #45b29d;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation li a:hover {
|
.navigation li a:hover {
|
||||||
|
@ -99,14 +117,6 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation .create-shelf a {
|
|
||||||
color: #fff;
|
|
||||||
background: #45b29d;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row.display-flex {
|
.row.display-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -119,25 +129,41 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-fluid .discover{ margin-bottom: 50px; }
|
.container-fluid .discover { margin-bottom: 50px; }
|
||||||
.container-fluid .new-books { border-top: 1px solid #ccc; }
|
.container-fluid .new-books { border-top: 1px solid #ccc; }
|
||||||
.container-fluid .new-books h2 { margin: 50px 0 0 0; }
|
.container-fluid .new-books h2 { margin: 50px 0 0 0; }
|
||||||
|
|
||||||
.container-fluid .book {
|
.container-fluid .book {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
.cover { margin-bottom: 10px; }
|
||||||
|
|
||||||
.container-fluid .book .cover {
|
.container-fluid .book .cover {
|
||||||
height: 225px;
|
height: 225px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.author-link img {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.container-fluid .book .cover span.img {
|
.container-fluid .book .cover span.img {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
.author-bio img { margin: 0 1em 1em 0; }
|
||||||
|
|
||||||
|
.container-fluid .single .cover img {
|
||||||
|
border: 1px solid #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-box-shadow: 0 5px 8px -6px #777;
|
||||||
|
-moz-box-shadow: 0 5px 8px -6px #777;
|
||||||
|
box-shadow: 0 5px 8px -6px #777;
|
||||||
|
}
|
||||||
|
|
||||||
.container-fluid .book .cover span img {
|
.container-fluid .book .cover span img {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -152,6 +178,7 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-fluid .book .meta { margin-top: 10px; }
|
.container-fluid .book .meta { margin-top: 10px; }
|
||||||
|
.media-body p { text-align: justify; }
|
||||||
.container-fluid .book .meta p { margin: 0; }
|
.container-fluid .book .meta p { margin: 0; }
|
||||||
|
|
||||||
.container-fluid .book .meta .title {
|
.container-fluid .book .meta .title {
|
||||||
|
@ -174,9 +201,10 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||||
.container-fluid .book .meta .rating { margin-top: 5px; }
|
.container-fluid .book .meta .rating { margin-top: 5px; }
|
||||||
.rating .glyphicon-star-empty { color: #444; }
|
.rating .glyphicon-star-empty { color: #444; }
|
||||||
.rating .glyphicon-star.good { color: #444; }
|
.rating .glyphicon-star.good { color: #444; }
|
||||||
.rating-clear .glyphicon-remove { color: #333 }
|
.rating-clear .glyphicon-remove { color: #333; }
|
||||||
|
|
||||||
.container-fluid .author .author-hidden, .container-fluid .author .author-hidden-divider { display: none; }
|
.container-fluid .author .author-hidden,
|
||||||
|
.container-fluid .author .author-hidden-divider { display: none; }
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-family: 'Grand Hotel', cursive;
|
font-family: 'Grand Hotel', cursive;
|
||||||
|
@ -190,7 +218,7 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-stuff>li { margin-bottom: 10px; }
|
.more-stuff > li { margin-bottom: 10px; }
|
||||||
.navbar-collapse.in .navbar-nav { margin: 0; }
|
.navbar-collapse.in .navbar-nav { margin: 0; }
|
||||||
|
|
||||||
span.glyphicon.glyphicon-tags {
|
span.glyphicon.glyphicon-tags {
|
||||||
|
@ -200,30 +228,20 @@ span.glyphicon.glyphicon-tags {
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-meta { padding-bottom: 20px; }
|
.book-meta { padding-bottom: 20px; }
|
||||||
.book-meta .tags a { display: inline; }
|
|
||||||
.book-meta .identifiers a { display: inline; }
|
|
||||||
|
|
||||||
.container-fluid .single .cover img {
|
.navbar-default .navbar-toggle .icon-bar { background-color: #000; }
|
||||||
border: 1px solid #fff;
|
.navbar-default .navbar-toggle { border-color: #000; }
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-shadow: 0 5px 8px -6px #777;
|
.cover .badge {
|
||||||
-moz-box-shadow: 0 5px 8px -6px #777;
|
position: absolute;
|
||||||
box-shadow: 0 5px 8px -6px #777;
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
color: #000;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-default .navbar-toggle .icon-bar {background-color: #000; }
|
.cover .read {
|
||||||
.navbar-default .navbar-toggle {border-color: #000; }
|
|
||||||
.cover { margin-bottom: 10px; }
|
|
||||||
|
|
||||||
.cover .badge{
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
color: #000;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.cover .read{
|
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
width: 17px;
|
width: 17px;
|
||||||
|
@ -231,14 +249,17 @@ span.glyphicon.glyphicon-tags {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
.cover-height { max-height: 100px;}
|
.cover-height { max-height: 100px; }
|
||||||
|
|
||||||
.col-sm-2 a .cover-small {
|
.col-sm-2 a .cover-small {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-file {position: relative; overflow: hidden;}
|
.btn-file {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-file input[type=file] {
|
.btn-file input[type=file] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -256,24 +277,60 @@ span.glyphicon.glyphicon-tags {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-toolbar .btn,.discover .btn { margin-bottom: 5px; }
|
.btn-toolbar .btn,
|
||||||
.button-link {color: #fff; }
|
.discover .btn { margin-bottom: 5px; }
|
||||||
.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary{ background-color: #1C5484; }
|
.button-link { color: #fff; }
|
||||||
.btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #89B9E2; }
|
|
||||||
.btn-toolbar>.btn+.btn, .btn-toolbar>.btn-group+.btn, .btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn-group { margin-left: 0; }
|
|
||||||
.panel-body {background-color: #f5f5f5; }
|
|
||||||
.spinner {margin: 0 41%; }
|
|
||||||
.spinner2 {margin: 0 41%; }
|
|
||||||
.intend-form { margin-left:20px; }
|
|
||||||
table .bg-dark-danger {background-color: #d9534f; color: #fff; }
|
|
||||||
table .bg-dark-danger a {color: #fff; }
|
|
||||||
table .bg-dark-danger:hover {background-color: #c9302c; }
|
|
||||||
table .bg-primary:hover {background-color: #1C5484; }
|
|
||||||
table .bg-primary a {color: #fff; }
|
|
||||||
.block-label {display: block;}
|
|
||||||
.fake-input {position: absolute; pointer-events: none; top: 0; }
|
|
||||||
|
|
||||||
input.pill { position: absolute; opacity: 0; }
|
.btn-primary:hover,
|
||||||
|
.btn-primary:focus,
|
||||||
|
.btn-primary:active,
|
||||||
|
.btn-primary.active,
|
||||||
|
.open .dropdown-toggle.btn-primary { background-color: #1c5484; }
|
||||||
|
|
||||||
|
.btn-primary.disabled,
|
||||||
|
.btn-primary[disabled],
|
||||||
|
fieldset[disabled] .btn-primary,
|
||||||
|
.btn-primary.disabled:hover,
|
||||||
|
.btn-primary[disabled]:hover,
|
||||||
|
fieldset[disabled] .btn-primary:hover,
|
||||||
|
.btn-primary.disabled:focus,
|
||||||
|
.btn-primary[disabled]:focus,
|
||||||
|
fieldset[disabled] .btn-primary:focus,
|
||||||
|
.btn-primary.disabled:active,
|
||||||
|
.btn-primary[disabled]:active,
|
||||||
|
fieldset[disabled] .btn-primary:active,
|
||||||
|
.btn-primary.disabled.active,
|
||||||
|
.btn-primary[disabled].active,
|
||||||
|
fieldset[disabled] .btn-primary.active { background-color: #89b9e2; }
|
||||||
|
|
||||||
|
.btn-toolbar > .btn + .btn,
|
||||||
|
.btn-toolbar > .btn-group + .btn,
|
||||||
|
.btn-toolbar > .btn + .btn-group,
|
||||||
|
.btn-toolbar > .btn-group + .btn-group { margin-left: 0; }
|
||||||
|
|
||||||
|
.panel-body { background-color: #f5f5f5; }
|
||||||
|
.spinner { margin: 0 41%; }
|
||||||
|
.spinner2 { margin: 0 41%; }
|
||||||
|
.intend-form { margin-left: 20px; }
|
||||||
|
|
||||||
|
table .bg-dark-danger {
|
||||||
|
background-color: #d9534f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
table .bg-dark-danger:hover { background-color: #c9302c; }
|
||||||
|
table .bg-primary:hover { background-color: #1c5484; }
|
||||||
|
.block-label { display: block; }
|
||||||
|
|
||||||
|
.fake-input {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.pill {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
input.pill + label {
|
input.pill + label {
|
||||||
border: 2px solid #45b29d;
|
border: 2px solid #45b29d;
|
||||||
|
@ -295,12 +352,18 @@ input.pill:checked + label {
|
||||||
|
|
||||||
input.pill:not(:checked) + label .glyphicon { display: none; }
|
input.pill:not(:checked) + label .glyphicon { display: none; }
|
||||||
|
|
||||||
.author-bio img { margin: 0 1em 1em 0; }
|
.author-link {
|
||||||
.author-link { display: inline-block; margin-top: 10px; width: 100px; }
|
display: inline-block;
|
||||||
.author-link img { display: block; height: 100%; }
|
margin-top: 10px;
|
||||||
#remove-from-shelves .btn, #shelf-action-errors { margin-left: 5px; }
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.tags_click, .serie_click, .language_click { margin-right: 5px; }
|
#remove-from-shelves .btn,
|
||||||
|
#shelf-action-errors { margin-left: 5px; }
|
||||||
|
|
||||||
|
.tags_click,
|
||||||
|
.serie_click,
|
||||||
|
.language_click { margin-right: 5px; }
|
||||||
|
|
||||||
#meta-info {
|
#meta-info {
|
||||||
height: 600px;
|
height: 600px;
|
||||||
|
@ -308,7 +371,6 @@ input.pill:not(:checked) + label .glyphicon { display: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-list { padding-right: 15px; }
|
.media-list { padding-right: 15px; }
|
||||||
.media-body p { text-align: justify; }
|
|
||||||
|
|
||||||
#meta-info img {
|
#meta-info img {
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
|
@ -321,13 +383,13 @@ input.pill:not(:checked) + label .glyphicon { display: none; }
|
||||||
#btn-upload-format { display: none; }
|
#btn-upload-format { display: none; }
|
||||||
.upload-cover-input-text { display: initial; }
|
.upload-cover-input-text { display: initial; }
|
||||||
#btn-upload-cover { display: none; }
|
#btn-upload-cover { display: none; }
|
||||||
.panel-title > a { text-decoration: none; }
|
|
||||||
.editable-buttons {
|
.editable-buttons {
|
||||||
display:inline-block;
|
display: inline-block;
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editable-input { display:inline-block; }
|
.editable-input { display: inline-block; }
|
||||||
|
|
||||||
.editable-cancel {
|
.editable-cancel {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
|
@ -354,4 +416,3 @@ div.log {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -710,7 +710,7 @@ $(".navbar-collapse.collapse.in").before('<div class="sidebar-backdrop"></div>')
|
||||||
// Get rid of leading white space
|
// Get rid of leading white space
|
||||||
recentlyAdded = $("#nav_new a:contains('Recently')").text().trim();
|
recentlyAdded = $("#nav_new a:contains('Recently')").text().trim();
|
||||||
$("#nav_new a:contains('Recently')").contents().filter(function () {
|
$("#nav_new a:contains('Recently')").contents().filter(function () {
|
||||||
return this.nodeType == 3
|
return this.nodeType === 3
|
||||||
}).each(function () {
|
}).each(function () {
|
||||||
this.textContent = this.textContent.replace(" Recently Added", recentlyAdded);
|
this.textContent = this.textContent.replace(" Recently Added", recentlyAdded);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Created by SpeedProg on 05.04.2015.
|
* Created by SpeedProg on 05.04.2015.
|
||||||
*/
|
*/
|
||||||
/* global Bloodhound, language, Modernizr, tinymce */
|
/* global Bloodhound, language, Modernizr, tinymce, getPath */
|
||||||
|
|
||||||
if ($("#description").length) {
|
if ($("#description").length) {
|
||||||
tinymce.init({
|
tinymce.init({
|
||||||
|
@ -250,14 +250,14 @@ promisePublishers.done(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("change input.typeahead:selected", function(event) {
|
$("#search").on("change input.typeahead:selected", function(event) {
|
||||||
if (event.target.type == "search" && event.target.tagName == "INPUT") {
|
if (event.target.type === "search" && event.target.tagName === "INPUT") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var form = $("form").serialize();
|
var form = $("form").serialize();
|
||||||
$.getJSON( getPath() + "/get_matching_tags", form, function( data ) {
|
$.getJSON( getPath() + "/get_matching_tags", form, function( data ) {
|
||||||
$(".tags_click").each(function() {
|
$(".tags_click").each(function() {
|
||||||
if ($.inArray(parseInt($(this).val(), 10), data.tags) === -1) {
|
if ($.inArray(parseInt($(this).val(), 10), data.tags) === -1) {
|
||||||
if(!$(this).prop("selected")) {
|
if (!$(this).prop("selected")) {
|
||||||
$(this).prop("disabled", true);
|
$(this).prop("disabled", true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,10 +265,10 @@ $("#search").on("change input.typeahead:selected", function(event) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#include_tag option:selected").each(function () {
|
$("#include_tag option:selected").each(function () {
|
||||||
$("#exclude_tag").find("[value="+$(this).val()+"]").prop("disabled", true);
|
$("#exclude_tag").find("[value=" + $(this).val() + "]").prop("disabled", true);
|
||||||
});
|
});
|
||||||
$('#include_tag').selectpicker("refresh");
|
$("#include_tag").selectpicker("refresh");
|
||||||
$('#exclude_tag').selectpicker("refresh");
|
$("#exclude_tag").selectpicker("refresh");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ $("#desc").click(function() {
|
||||||
sortBy: "name",
|
sortBy: "name",
|
||||||
sortAscending: true
|
sortAscending: true
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#asc").click(function() {
|
$("#asc").click(function() {
|
||||||
|
@ -52,19 +51,20 @@ $("#asc").click(function() {
|
||||||
sortBy: "name",
|
sortBy: "name",
|
||||||
sortAscending: false
|
sortAscending: false
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#all").click(function() {
|
$("#all").click(function() {
|
||||||
// go through all elements and make them visible
|
// go through all elements and make them visible
|
||||||
$list.isotope({ filter: function() {
|
$list.isotope({ filter: function() {
|
||||||
return true;
|
return true;
|
||||||
} })
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".char").click(function() {
|
$(".char").click(function() {
|
||||||
var character = this.innerText;
|
var character = this.innerText;
|
||||||
$list.isotope({ filter: function() {
|
$list.isotope({ filter: function() {
|
||||||
return this.attributes["data-id"].value.charAt(0).toUpperCase() == character;
|
return this.attributes["data-id"].value.charAt(0).toUpperCase() === character;
|
||||||
} })
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,7 +88,7 @@ $("#desc").click(function() {
|
||||||
// Find count of middle element
|
// Find count of middle element
|
||||||
var count = $(".row:visible").length;
|
var count = $(".row:visible").length;
|
||||||
if (count > 20) {
|
if (count > 20) {
|
||||||
middle = parseInt(count / 2) + (count % 2);
|
middle = parseInt(count / 2, 10) + (count % 2);
|
||||||
|
|
||||||
//var middle = parseInt(count / 2) + (count % 2);
|
//var middle = parseInt(count / 2) + (count % 2);
|
||||||
// search for the middle of all visible elements
|
// search for the middle of all visible elements
|
||||||
|
@ -135,7 +135,7 @@ $("#asc").click(function() {
|
||||||
// Find count of middle element
|
// Find count of middle element
|
||||||
var count = $(".row:visible").length;
|
var count = $(".row:visible").length;
|
||||||
if (count > 20) {
|
if (count > 20) {
|
||||||
var middle = parseInt(count / 2) + (count % 2);
|
var middle = parseInt(count / 2, 10) + (count % 2);
|
||||||
|
|
||||||
//var middle = parseInt(count / 2) + (count % 2);
|
//var middle = parseInt(count / 2) + (count % 2);
|
||||||
// search for the middle of all visible elements
|
// search for the middle of all visible elements
|
||||||
|
|
|
@ -138,8 +138,8 @@ $(function () {
|
||||||
seriesTitle = result.series.title;
|
seriesTitle = result.series.title;
|
||||||
}
|
}
|
||||||
var dateFomers = result.pubdate.split("-");
|
var dateFomers = result.pubdate.split("-");
|
||||||
var publishedYear = parseInt(dateFomers[0]);
|
var publishedYear = parseInt(dateFomers[0], 10);
|
||||||
var publishedMonth = parseInt(dateFomers[1]);
|
var publishedMonth = parseInt(dateFomers[1], 10);
|
||||||
var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
|
var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
|
||||||
|
|
||||||
publishedDate = formatDate(publishedDate);
|
publishedDate = formatDate(publishedDate);
|
||||||
|
@ -194,8 +194,8 @@ $(function () {
|
||||||
} else {
|
} else {
|
||||||
dateFomers = result.date_added.split("-");
|
dateFomers = result.date_added.split("-");
|
||||||
}
|
}
|
||||||
var publishedYear = parseInt(dateFomers[0]);
|
var publishedYear = parseInt(dateFomers[0], 10);
|
||||||
var publishedMonth = parseInt(dateFomers[1]);
|
var publishedMonth = parseInt(dateFomers[1], 10);
|
||||||
var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
|
var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
|
||||||
|
|
||||||
publishedDate = formatDate(publishedDate);
|
publishedDate = formatDate(publishedDate);
|
||||||
|
|
|
@ -38,10 +38,10 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () {
|
||||||
$(document).on("change", "select[data-control]", function() {
|
$(document).on("change", "select[data-control]", function() {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.data("control");
|
var name = $this.data("control");
|
||||||
var showOrHide = parseInt($this.val());
|
var showOrHide = parseInt($this.val(), 10);
|
||||||
// var showOrHideLast = $("#" + name + " option:last").val()
|
// var showOrHideLast = $("#" + name + " option:last").val()
|
||||||
for (var i = 0; i < $(this)[0].length; i++) {
|
for (var i = 0; i < $(this)[0].length; i++) {
|
||||||
var element = parseInt($(this)[0][i].value);
|
var element = parseInt($(this)[0][i].value, 10);
|
||||||
if (element === showOrHide) {
|
if (element === showOrHide) {
|
||||||
$("[data-related^=" + name + "][data-related*=-" + element + "]").show();
|
$("[data-related^=" + name + "][data-related*=-" + element + "]").show();
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,7 +55,7 @@ $(document).on("change", "select[data-control]", function() {
|
||||||
$(document).on("change", "select[data-controlall]", function() {
|
$(document).on("change", "select[data-controlall]", function() {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = $this.data("controlall");
|
var name = $this.data("controlall");
|
||||||
var showOrHide = parseInt($this.val());
|
var showOrHide = parseInt($this.val(), 10);
|
||||||
if (showOrHide) {
|
if (showOrHide) {
|
||||||
$("[data-related=" + name + "]").show();
|
$("[data-related=" + name + "]").show();
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,7 +114,7 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function ConfirmDialog(id, dataValue, yesFn, noFn) {
|
function confirmDialog(id, dataValue, yesFn, noFn) {
|
||||||
var $confirm = $("#GeneralDeleteModal");
|
var $confirm = $("#GeneralDeleteModal");
|
||||||
// var dataValue= e.data('value'); // target.data('value');
|
// var dataValue= e.data('value'); // target.data('value');
|
||||||
$confirm.modal('show');
|
$confirm.modal('show');
|
||||||
|
@ -481,7 +481,7 @@ $(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#config_delete_kobo_token").click(function() {
|
$("#config_delete_kobo_token").click(function() {
|
||||||
ConfirmDialog(
|
confirmDialog(
|
||||||
$(this).attr('id'),
|
$(this).attr('id'),
|
||||||
$(this).data('value'),
|
$(this).data('value'),
|
||||||
function (value) {
|
function (value) {
|
||||||
|
@ -509,7 +509,7 @@ $(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#btndeluser").click(function() {
|
$("#btndeluser").click(function() {
|
||||||
ConfirmDialog(
|
confirmDialog(
|
||||||
$(this).attr('id'),
|
$(this).attr('id'),
|
||||||
$(this).data('value'),
|
$(this).data('value'),
|
||||||
function(value){
|
function(value){
|
||||||
|
@ -527,7 +527,7 @@ $(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#delete_shelf").click(function() {
|
$("#delete_shelf").click(function() {
|
||||||
ConfirmDialog(
|
confirmDialog(
|
||||||
$(this).attr('id'),
|
$(this).attr('id'),
|
||||||
$(this).data('value'),
|
$(this).data('value'),
|
||||||
function(value){
|
function(value){
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* exported TableActions, RestrictionActions, EbookActions, responseHandler */
|
/* exported TableActions, RestrictionActions, EbookActions, responseHandler */
|
||||||
|
/* global getPath, confirmDialog */
|
||||||
|
|
||||||
var selections = [];
|
var selections = [];
|
||||||
|
|
||||||
|
@ -209,7 +210,7 @@ $(function() {
|
||||||
striped: false
|
striped: false
|
||||||
});
|
});
|
||||||
|
|
||||||
function domain_handle(domainId) {
|
function domainHandle(domainId) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method:"post",
|
method:"post",
|
||||||
url: window.location.pathname + "/../../ajax/deletedomain",
|
url: window.location.pathname + "/../../ajax/deletedomain",
|
||||||
|
@ -236,12 +237,12 @@ $(function() {
|
||||||
}
|
}
|
||||||
$("#domain-allow-table").on("click-cell.bs.table", function (field, value, row, $element) {
|
$("#domain-allow-table").on("click-cell.bs.table", function (field, value, row, $element) {
|
||||||
if (value === 2) {
|
if (value === 2) {
|
||||||
ConfirmDialog("btndeletedomain", $element.id, domain_handle);
|
confirmDialog("btndeletedomain", $element.id, domainHandle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#domain-deny-table").on("click-cell.bs.table", function (field, value, row, $element) {
|
$("#domain-deny-table").on("click-cell.bs.table", function (field, value, row, $element) {
|
||||||
if (value === 2) {
|
if (value === 2) {
|
||||||
ConfirmDialog("btndeletedomain", $element.id, domain_handle);
|
confirmDialog("btndeletedomain", $element.id, domainHandle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -255,12 +256,12 @@ $(function() {
|
||||||
$("#h3").addClass("hidden");
|
$("#h3").addClass("hidden");
|
||||||
$("#h4").addClass("hidden");
|
$("#h4").addClass("hidden");
|
||||||
});
|
});
|
||||||
function startTable(type, user_id) {
|
function startTable(type, userId) {
|
||||||
$("#restrict-elements-table").bootstrapTable({
|
$("#restrict-elements-table").bootstrapTable({
|
||||||
formatNoMatches: function () {
|
formatNoMatches: function () {
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
url: getPath() + "/ajax/listrestriction/" + type + "/" + user_id,
|
url: getPath() + "/ajax/listrestriction/" + type + "/" + userId,
|
||||||
rowStyle: function(row) {
|
rowStyle: function(row) {
|
||||||
// console.log('Reihe :' + row + " Index :" + index);
|
// console.log('Reihe :' + row + " Index :" + index);
|
||||||
if (row.id.charAt(0) === "a") {
|
if (row.id.charAt(0) === "a") {
|
||||||
|
@ -274,13 +275,13 @@ $(function() {
|
||||||
$.ajax ({
|
$.ajax ({
|
||||||
type: "Post",
|
type: "Post",
|
||||||
data: "id=" + row.id + "&type=" + row.type + "&Element=" + encodeURIComponent(row.Element),
|
data: "id=" + row.id + "&type=" + row.type + "&Element=" + encodeURIComponent(row.Element),
|
||||||
url: getPath() + "/ajax/deleterestriction/" + type + "/" + user_id,
|
url: getPath() + "/ajax/deleterestriction/" + type + "/" + userId,
|
||||||
async: true,
|
async: true,
|
||||||
timeout: 900,
|
timeout: 900,
|
||||||
success:function() {
|
success:function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method:"get",
|
method:"get",
|
||||||
url: getPath() + "/ajax/listrestriction/" + type + "/" + user_id,
|
url: getPath() + "/ajax/listrestriction/" + type + "/" + userId,
|
||||||
async: true,
|
async: true,
|
||||||
timeout: 900,
|
timeout: 900,
|
||||||
success:function(data) {
|
success:function(data) {
|
||||||
|
@ -296,7 +297,7 @@ $(function() {
|
||||||
$("#restrict-elements-table").removeClass("table-hover");
|
$("#restrict-elements-table").removeClass("table-hover");
|
||||||
$("#restrict-elements-table").on("editable-save.bs.table", function (e, field, row) {
|
$("#restrict-elements-table").on("editable-save.bs.table", function (e, field, row) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: getPath() + "/ajax/editrestriction/" + type + "/" + user_id,
|
url: getPath() + "/ajax/editrestriction/" + type + "/" + userId,
|
||||||
type: "Post",
|
type: "Post",
|
||||||
data: row
|
data: row
|
||||||
});
|
});
|
||||||
|
@ -304,13 +305,13 @@ $(function() {
|
||||||
$("[id^=submit_]").click(function() {
|
$("[id^=submit_]").click(function() {
|
||||||
$(this)[0].blur();
|
$(this)[0].blur();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: getPath() + "/ajax/addrestriction/" + type + "/" + user_id,
|
url: getPath() + "/ajax/addrestriction/" + type + "/" + userId,
|
||||||
type: "Post",
|
type: "Post",
|
||||||
data: $(this).closest("form").serialize() + "&" + $(this)[0].name + "=",
|
data: $(this).closest("form").serialize() + "&" + $(this)[0].name + "=",
|
||||||
success: function () {
|
success: function () {
|
||||||
$.ajax ({
|
$.ajax ({
|
||||||
method:"get",
|
method:"get",
|
||||||
url: getPath() + "/ajax/listrestriction/" + type + "/" + user_id,
|
url: getPath() + "/ajax/listrestriction/" + type + "/" + userId,
|
||||||
async: true,
|
async: true,
|
||||||
timeout: 900,
|
timeout: 900,
|
||||||
success:function(data) {
|
success:function(data) {
|
||||||
|
@ -332,12 +333,12 @@ $(function() {
|
||||||
$("#h1").removeClass("hidden");
|
$("#h1").removeClass("hidden");
|
||||||
});
|
});
|
||||||
$("#get_user_column_values").on("click", function() {
|
$("#get_user_column_values").on("click", function() {
|
||||||
startTable(3, $(this).data('id'));
|
startTable(3, $(this).data("id"));
|
||||||
$("#h4").removeClass("hidden");
|
$("#h4").removeClass("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#get_user_tags").on("click", function() {
|
$("#get_user_tags").on("click", function() {
|
||||||
startTable(2, $(this).data('id'));
|
startTable(2, $(this).data("id"));
|
||||||
$(this)[0].blur();
|
$(this)[0].blur();
|
||||||
$("#h3").removeClass("hidden");
|
$("#h3").removeClass("hidden");
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,6 @@ class TaskUpload(CalibreTask):
|
||||||
|
|
||||||
def run(self, worker_thread):
|
def run(self, worker_thread):
|
||||||
"""Upload task doesn't have anything to do, it's simply a way to add information to the task list"""
|
"""Upload task doesn't have anything to do, it's simply a way to add information to the task list"""
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
|
117
cps/ub.py
117
cps/ub.py
|
@ -138,15 +138,15 @@ class UserBase:
|
||||||
mct = self.allowed_column_value or ""
|
mct = self.allowed_column_value or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [t.strip() for t in mct.split(",")]
|
||||||
|
|
||||||
def get_view_property(self, page, property):
|
def get_view_property(self, page, prop):
|
||||||
if not self.view_settings.get(page):
|
if not self.view_settings.get(page):
|
||||||
return None
|
return None
|
||||||
return self.view_settings[page].get(property)
|
return self.view_settings[page].get(prop)
|
||||||
|
|
||||||
def set_view_property(self, page, property, value):
|
def set_view_property(self, page, prop, value):
|
||||||
if not self.view_settings.get(page):
|
if not self.view_settings.get(page):
|
||||||
self.view_settings[page] = dict()
|
self.view_settings[page] = dict()
|
||||||
self.view_settings[page][property] = value
|
self.view_settings[page][prop] = value
|
||||||
try:
|
try:
|
||||||
flag_modified(self, "view_settings")
|
flag_modified(self, "view_settings")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -437,11 +437,8 @@ class RemoteAuthToken(Base):
|
||||||
return '<Token %r>' % self.id
|
return '<Token %r>' % self.id
|
||||||
|
|
||||||
|
|
||||||
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
# Add missing tables during migration of database
|
||||||
# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding
|
def add_missing_tables(engine, session):
|
||||||
# rows with SQL commands
|
|
||||||
def migrate_Database(session):
|
|
||||||
engine = session.bind
|
|
||||||
if not engine.dialect.has_table(engine.connect(), "book_read_link"):
|
if not engine.dialect.has_table(engine.connect(), "book_read_link"):
|
||||||
ReadBook.__table__.create(bind=engine)
|
ReadBook.__table__.create(bind=engine)
|
||||||
if not engine.dialect.has_table(engine.connect(), "bookmark"):
|
if not engine.dialect.has_table(engine.connect(), "bookmark"):
|
||||||
|
@ -459,6 +456,10 @@ def migrate_Database(session):
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
conn.execute("insert into registration (domain, allow) values('%.%',1)")
|
conn.execute("insert into registration (domain, allow) values('%.%',1)")
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
# migrate all settings missing in registration table
|
||||||
|
def migrate_registration_table(engine, session):
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(Registration.allow)).scalar()
|
session.query(exists().where(Registration.allow)).scalar()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
@ -468,27 +469,29 @@ def migrate_Database(session):
|
||||||
conn.execute("update registration set 'allow' = 1")
|
conn.execute("update registration set 'allow' = 1")
|
||||||
session.commit()
|
session.commit()
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(RemoteAuthToken.token_type)).scalar()
|
# Handle table exists, but no content
|
||||||
session.commit()
|
cnt = session.query(Registration).count()
|
||||||
except exc.OperationalError: # Database is not compatible, some columns are missing
|
if not cnt:
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
conn.execute("ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0")
|
conn.execute("insert into registration (domain, allow) values('%.%',1)")
|
||||||
conn.execute("update remote_auth_token set 'token_type' = 0")
|
session.commit()
|
||||||
session.commit()
|
except exc.OperationalError: # Database is not writeable
|
||||||
|
print('Settings database is not writeable. Exiting...')
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove login capability of user Guest
|
||||||
|
def migrate_guest_password(engine, session):
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(ReadBook.read_status)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
conn.execute("ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0")
|
conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''")
|
||||||
conn.execute("UPDATE book_read_link SET 'read_status' = 1 WHERE is_read")
|
|
||||||
conn.execute("ALTER TABLE book_read_link ADD column 'last_modified' DATETIME")
|
|
||||||
conn.execute("ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME")
|
|
||||||
conn.execute("ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0")
|
|
||||||
session.commit()
|
session.commit()
|
||||||
test = session.query(ReadBook).filter(ReadBook.last_modified == None).all()
|
except exc.OperationalError:
|
||||||
for book in test:
|
print('Settings database is not writeable. Exiting...')
|
||||||
book.last_modified = datetime.datetime.utcnow()
|
sys.exit(2)
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
def migrate_shelfs(engine, session):
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(Shelf.uuid)).scalar()
|
session.query(exists().where(Shelf.uuid)).scalar()
|
||||||
except exc.OperationalError:
|
except exc.OperationalError:
|
||||||
|
@ -504,22 +507,51 @@ def migrate_Database(session):
|
||||||
for book_shelf in session.query(BookShelf).all():
|
for book_shelf in session.query(BookShelf).all():
|
||||||
book_shelf.date_added = datetime.datetime.now()
|
book_shelf.date_added = datetime.datetime.now()
|
||||||
session.commit()
|
session.commit()
|
||||||
try:
|
|
||||||
# Handle table exists, but no content
|
|
||||||
cnt = session.query(Registration).count()
|
|
||||||
if not cnt:
|
|
||||||
with engine.connect() as conn:
|
|
||||||
conn.execute("insert into registration (domain, allow) values('%.%',1)")
|
|
||||||
session.commit()
|
|
||||||
except exc.OperationalError: # Database is not writeable
|
|
||||||
print('Settings database is not writeable. Exiting...')
|
|
||||||
sys.exit(2)
|
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(BookShelf.order)).scalar()
|
session.query(exists().where(BookShelf.order)).scalar()
|
||||||
except exc.OperationalError: # Database is not compatible, some columns are missing
|
except exc.OperationalError: # Database is not compatible, some columns are missing
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1")
|
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1")
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_readBook(engine, session):
|
||||||
|
try:
|
||||||
|
session.query(exists().where(ReadBook.read_status)).scalar()
|
||||||
|
except exc.OperationalError:
|
||||||
|
with engine.connect() as conn:
|
||||||
|
conn.execute("ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0")
|
||||||
|
conn.execute("UPDATE book_read_link SET 'read_status' = 1 WHERE is_read")
|
||||||
|
conn.execute("ALTER TABLE book_read_link ADD column 'last_modified' DATETIME")
|
||||||
|
conn.execute("ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME")
|
||||||
|
conn.execute("ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0")
|
||||||
|
session.commit()
|
||||||
|
test = session.query(ReadBook).filter(ReadBook.last_modified == None).all()
|
||||||
|
for book in test:
|
||||||
|
book.last_modified = datetime.datetime.utcnow()
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_remoteAuthToken(engine, session):
|
||||||
|
try:
|
||||||
|
session.query(exists().where(RemoteAuthToken.token_type)).scalar()
|
||||||
|
session.commit()
|
||||||
|
except exc.OperationalError: # Database is not compatible, some columns are missing
|
||||||
|
with engine.connect() as conn:
|
||||||
|
conn.execute("ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0")
|
||||||
|
conn.execute("update remote_auth_token set 'token_type' = 0")
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
||||||
|
# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding
|
||||||
|
# rows with SQL commands
|
||||||
|
def migrate_Database(session):
|
||||||
|
engine = session.bind
|
||||||
|
add_missing_tables(engine, session)
|
||||||
|
migrate_registration_table(engine, session)
|
||||||
|
migrate_readBook(engine, session)
|
||||||
|
migrate_remoteAuthToken(engine, session)
|
||||||
|
migrate_shelfs(engine, session)
|
||||||
try:
|
try:
|
||||||
create = False
|
create = False
|
||||||
session.query(exists().where(User.sidebar_view)).scalar()
|
session.query(exists().where(User.sidebar_view)).scalar()
|
||||||
|
@ -578,7 +610,6 @@ def migrate_Database(session):
|
||||||
"locale VARCHAR(2),"
|
"locale VARCHAR(2),"
|
||||||
"sidebar_view INTEGER,"
|
"sidebar_view INTEGER,"
|
||||||
"default_language VARCHAR(3),"
|
"default_language VARCHAR(3),"
|
||||||
# "series_view VARCHAR(10),"
|
|
||||||
"view_settings VARCHAR,"
|
"view_settings VARCHAR,"
|
||||||
"UNIQUE (nickname),"
|
"UNIQUE (nickname),"
|
||||||
"UNIQUE (email))")
|
"UNIQUE (email))")
|
||||||
|
@ -590,15 +621,7 @@ def migrate_Database(session):
|
||||||
conn.execute("DROP TABLE user")
|
conn.execute("DROP TABLE user")
|
||||||
conn.execute("ALTER TABLE user_id RENAME TO user")
|
conn.execute("ALTER TABLE user_id RENAME TO user")
|
||||||
session.commit()
|
session.commit()
|
||||||
|
migrate_guest_password(engine, session)
|
||||||
# Remove login capability of user Guest
|
|
||||||
try:
|
|
||||||
with engine.connect() as conn:
|
|
||||||
conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''")
|
|
||||||
session.commit()
|
|
||||||
except exc.OperationalError:
|
|
||||||
print('Settings database is not writeable. Exiting...')
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
|
|
||||||
def clean_database(session):
|
def clean_database(session):
|
||||||
|
|
|
@ -267,7 +267,8 @@ class Updater(threading.Thread):
|
||||||
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)
|
||||||
|
|
||||||
def is_venv(self):
|
@staticmethod
|
||||||
|
def is_venv():
|
||||||
if (hasattr(sys, 'real_prefix')) or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
|
if (hasattr(sys, 'real_prefix')) or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
|
||||||
return os.sep + os.path.relpath(sys.prefix, constants.BASE_DIR)
|
return os.sep + os.path.relpath(sys.prefix, constants.BASE_DIR)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -72,7 +72,7 @@ def load_user_from_request(request):
|
||||||
def load_user_from_auth_header(header_val):
|
def load_user_from_auth_header(header_val):
|
||||||
if header_val.startswith('Basic '):
|
if header_val.startswith('Basic '):
|
||||||
header_val = header_val.replace('Basic ', '', 1)
|
header_val = header_val.replace('Basic ', '', 1)
|
||||||
basic_username = basic_password = ''
|
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]
|
basic_username = header_val.split(':')[0]
|
||||||
|
|
485
cps/web.py
485
cps/web.py
|
@ -216,7 +216,7 @@ def update_view():
|
||||||
for param in to_save[element]:
|
for param in to_save[element]:
|
||||||
current_user.set_view_property(element, param, to_save[element][param])
|
current_user.set_view_property(element, param, to_save[element][param])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("Could not save view_settings: %r %r: e", request, to_save, e)
|
log.error("Could not save view_settings: %r %r: %e", request, to_save, e)
|
||||||
return "Invalid request", 400
|
return "Invalid request", 400
|
||||||
return "1", 200
|
return "1", 200
|
||||||
|
|
||||||
|
@ -340,7 +340,7 @@ def get_matching_tags():
|
||||||
return json_dumps
|
return json_dumps
|
||||||
|
|
||||||
|
|
||||||
def render_books_list(data, sort, book_id, page):
|
def get_sort_function(sort, data):
|
||||||
order = [db.Books.timestamp.desc()]
|
order = [db.Books.timestamp.desc()]
|
||||||
if sort == 'stored':
|
if sort == 'stored':
|
||||||
sort = current_user.get_view_property(data, 'stored')
|
sort = current_user.get_view_property(data, 'stored')
|
||||||
|
@ -366,25 +366,16 @@ def render_books_list(data, sort, book_id, page):
|
||||||
order = [db.Books.series_index.asc()]
|
order = [db.Books.series_index.asc()]
|
||||||
if sort == 'seriesdesc':
|
if sort == 'seriesdesc':
|
||||||
order = [db.Books.series_index.desc()]
|
order = [db.Books.series_index.desc()]
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
def render_books_list(data, sort, book_id, page):
|
||||||
|
order = get_sort_function(sort, data)
|
||||||
|
|
||||||
if data == "rated":
|
if data == "rated":
|
||||||
if current_user.check_visibility(constants.SIDEBAR_BEST_RATED):
|
return render_rated_books(page, book_id, order=order)
|
||||||
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
|
|
||||||
db.Books,
|
|
||||||
db.Books.ratings.any(db.Ratings.rating > 9),
|
|
||||||
order)
|
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
|
||||||
id=book_id, title=_(u"Top Rated Books"), page="rated")
|
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
elif data == "discover":
|
elif data == "discover":
|
||||||
if current_user.check_visibility(constants.SIDEBAR_RANDOM):
|
return render_discover_books(page, book_id)
|
||||||
entries, __, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, [func.randomblob(2)])
|
|
||||||
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
|
|
||||||
return render_title_template('discover.html', entries=entries, pagination=pagination, id=book_id,
|
|
||||||
title=_(u"Discover (Random Books)"), page="discover")
|
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
elif data == "unread":
|
elif data == "unread":
|
||||||
return render_read_books(page, False, order=order)
|
return render_read_books(page, False, order=order)
|
||||||
elif data == "read":
|
elif data == "read":
|
||||||
|
@ -424,6 +415,27 @@ def render_books_list(data, sort, book_id, page):
|
||||||
title=_(u"Books"), page=website)
|
title=_(u"Books"), page=website)
|
||||||
|
|
||||||
|
|
||||||
|
def render_rated_books(page, book_id, order):
|
||||||
|
if current_user.check_visibility(constants.SIDEBAR_BEST_RATED):
|
||||||
|
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
|
||||||
|
db.Books,
|
||||||
|
db.Books.ratings.any(db.Ratings.rating > 9),
|
||||||
|
order)
|
||||||
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
|
id=book_id, title=_(u"Top Rated Books"), page="rated")
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
def render_discover_books(page, book_id):
|
||||||
|
if current_user.check_visibility(constants.SIDEBAR_RANDOM):
|
||||||
|
entries, __, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, [func.randomblob(2)])
|
||||||
|
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
|
||||||
|
return render_title_template('discover.html', entries=entries, pagination=pagination, id=book_id,
|
||||||
|
title=_(u"Discover (Random Books)"), page="discover")
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
def render_hot_books(page):
|
def render_hot_books(page):
|
||||||
if current_user.check_visibility(constants.SIDEBAR_HOT):
|
if current_user.check_visibility(constants.SIDEBAR_HOT):
|
||||||
if current_user.show_detail_random():
|
if current_user.show_detail_random():
|
||||||
|
@ -453,18 +465,11 @@ def render_hot_books(page):
|
||||||
|
|
||||||
def render_downloaded_books(page, order):
|
def render_downloaded_books(page, order):
|
||||||
if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD):
|
if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD):
|
||||||
# order = order or []
|
|
||||||
if current_user.show_detail_random():
|
if current_user.show_detail_random():
|
||||||
random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \
|
random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.order_by(func.random()).limit(config.config_random_books)
|
.order_by(func.random()).limit(config.config_random_books)
|
||||||
else:
|
else:
|
||||||
random = false()
|
random = false()
|
||||||
# off = int(int(config.config_books_per_page) * (page - 1))
|
|
||||||
'''entries, random, pagination = calibre_db.fill_indexpage(page, 0,
|
|
||||||
db.Books,
|
|
||||||
db_filter,
|
|
||||||
order,
|
|
||||||
ub.ReadBook, db.Books.id==ub.ReadBook.book_id)'''
|
|
||||||
|
|
||||||
entries, __, pagination = calibre_db.fill_indexpage(page,
|
entries, __, pagination = calibre_db.fill_indexpage(page,
|
||||||
0,
|
0,
|
||||||
|
@ -753,7 +758,7 @@ def list_books():
|
||||||
search = request.args.get("search")
|
search = request.args.get("search")
|
||||||
total_count = calibre_db.session.query(db.Books).count()
|
total_count = calibre_db.session.query(db.Books).count()
|
||||||
if search:
|
if search:
|
||||||
entries, filtered_count, pagination = calibre_db.get_search_results(search, off, order, limit)
|
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit)
|
||||||
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)
|
||||||
filtered_count = total_count
|
filtered_count = total_count
|
||||||
|
@ -997,6 +1002,161 @@ def advanced_search():
|
||||||
return redirect(url_for('web.books_list', data="advsearch", sort_param='stored', query=""))
|
return redirect(url_for('web.books_list', data="advsearch", sort_param='stored', query=""))
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_custom_columns(cc, term, q):
|
||||||
|
for c in cc:
|
||||||
|
custom_query = term.get('custom_column_' + str(c.id))
|
||||||
|
if custom_query != '' and custom_query is not None:
|
||||||
|
if c.datatype == 'bool':
|
||||||
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
|
db.cc_classes[c.id].value == (custom_query == "True")))
|
||||||
|
elif c.datatype == 'int' or c.datatype == 'float':
|
||||||
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
|
db.cc_classes[c.id].value == custom_query))
|
||||||
|
elif c.datatype == 'rating':
|
||||||
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
|
db.cc_classes[c.id].value == int(float(custom_query) * 2)))
|
||||||
|
else:
|
||||||
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
|
func.lower(db.cc_classes[c.id].value).ilike("%" + custom_query + "%")))
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_language(q, include_languages_inputs, exclude_languages_inputs):
|
||||||
|
if current_user.filter_language() != "all":
|
||||||
|
q = q.filter(db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()))
|
||||||
|
else:
|
||||||
|
for language in include_languages_inputs:
|
||||||
|
q = q.filter(db.Books.languages.any(db.Languages.id == language))
|
||||||
|
for language in exclude_languages_inputs:
|
||||||
|
q = q.filter(not_(db.Books.series.any(db.Languages.id == language)))
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_ratings(q, rating_high, rating_low):
|
||||||
|
if rating_high:
|
||||||
|
rating_high = int(rating_high) * 2
|
||||||
|
q = q.filter(db.Books.ratings.any(db.Ratings.rating <= rating_high))
|
||||||
|
if rating_low:
|
||||||
|
rating_low = int(rating_low) * 2
|
||||||
|
q = q.filter(db.Books.ratings.any(db.Ratings.rating >= rating_low))
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_read_status(q, read_status):
|
||||||
|
if read_status:
|
||||||
|
if config.config_read_column:
|
||||||
|
if read_status == "True":
|
||||||
|
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||||
|
.filter(db.cc_classes[config.config_read_column].value == True)
|
||||||
|
else:
|
||||||
|
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||||
|
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
|
||||||
|
else:
|
||||||
|
if read_status == "True":
|
||||||
|
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
|
||||||
|
.filter(ub.ReadBook.user_id == int(current_user.id),
|
||||||
|
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
|
||||||
|
else:
|
||||||
|
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
|
||||||
|
.filter(ub.ReadBook.user_id == int(current_user.id),
|
||||||
|
coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED)
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_extension(q, include_extension_inputs, exclude_extension_inputs):
|
||||||
|
for extension in include_extension_inputs:
|
||||||
|
q = q.filter(db.Books.data.any(db.Data.format == extension))
|
||||||
|
for extension in exclude_extension_inputs:
|
||||||
|
q = q.filter(not_(db.Books.data.any(db.Data.format == extension)))
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_tag(q, include_tag_inputs, exclude_tag_inputs):
|
||||||
|
for tag in include_tag_inputs:
|
||||||
|
q = q.filter(db.Books.tags.any(db.Tags.id == tag))
|
||||||
|
for tag in exclude_tag_inputs:
|
||||||
|
q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag)))
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def adv_search_serie(q, include_series_inputs, exclude_series_inputs):
|
||||||
|
for serie in include_series_inputs:
|
||||||
|
q = q.filter(db.Books.series.any(db.Series.id == serie))
|
||||||
|
for serie in exclude_series_inputs:
|
||||||
|
q = q.filter(not_(db.Books.series.any(db.Series.id == serie)))
|
||||||
|
return q
|
||||||
|
|
||||||
|
def adv_search_shelf(q, include_shelf_inputs, exclude_shelf_inputs):
|
||||||
|
q = q.outerjoin(ub.BookShelf,db.Books.id==ub.BookShelf.book_id)\
|
||||||
|
.filter(or_(ub.BookShelf.shelf==None,ub.BookShelf.shelf.notin_(exclude_shelf_inputs)))
|
||||||
|
if len(include_shelf_inputs) >0:
|
||||||
|
q = q.filter(ub.BookShelf.shelf.in_(include_shelf_inputs))
|
||||||
|
return q
|
||||||
|
|
||||||
|
def extend_search_term(searchterm,
|
||||||
|
author_name,
|
||||||
|
book_title,
|
||||||
|
publisher,
|
||||||
|
pub_start,
|
||||||
|
pub_end,
|
||||||
|
include_tag_inputs,
|
||||||
|
exclude_tag_inputs,
|
||||||
|
include_series_inputs,
|
||||||
|
exclude_series_inputs,
|
||||||
|
include_shelf_inputs,
|
||||||
|
exclude_shelf_inputs,
|
||||||
|
include_languages_inputs,
|
||||||
|
rating_high,
|
||||||
|
rating_low,
|
||||||
|
read_status,
|
||||||
|
include_extension_inputs,
|
||||||
|
exclude_extension_inputs
|
||||||
|
):
|
||||||
|
searchterm.extend((author_name.replace('|', ','), book_title, publisher))
|
||||||
|
if pub_start:
|
||||||
|
try:
|
||||||
|
searchterm.extend([_(u"Published after ") +
|
||||||
|
format_date(datetime.strptime(pub_start, "%Y-%m-%d"),
|
||||||
|
format='medium', locale=get_locale())])
|
||||||
|
except ValueError:
|
||||||
|
pub_start = u""
|
||||||
|
if pub_end:
|
||||||
|
try:
|
||||||
|
searchterm.extend([_(u"Published before ") +
|
||||||
|
format_date(datetime.strptime(pub_end, "%Y-%m-%d"),
|
||||||
|
format='medium', locale=get_locale())])
|
||||||
|
except ValueError:
|
||||||
|
pub_start = u""
|
||||||
|
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
|
||||||
|
searchterm.extend(tag.name for tag in tag_names)
|
||||||
|
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(exclude_tag_inputs)).all()
|
||||||
|
searchterm.extend(tag.name for tag in tag_names)
|
||||||
|
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
|
||||||
|
searchterm.extend(serie.name for serie in serie_names)
|
||||||
|
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(exclude_series_inputs)).all()
|
||||||
|
searchterm.extend(serie.name for serie in serie_names)
|
||||||
|
shelf_names = ub.session.query(ub.Shelf).filter(ub.Shelf.id.in_(include_shelf_inputs)).all()
|
||||||
|
searchterm.extend(shelf.name for shelf in shelf_names)
|
||||||
|
shelf_names = ub.session.query(ub.Shelf).filter(ub.Shelf.id.in_(exclude_shelf_inputs)).all()
|
||||||
|
searchterm.extend(shelf.name for shelf in shelf_names)
|
||||||
|
language_names = calibre_db.session.query(db.Languages). \
|
||||||
|
filter(db.Languages.id.in_(include_languages_inputs)).all()
|
||||||
|
if language_names:
|
||||||
|
language_names = calibre_db.speaking_language(language_names)
|
||||||
|
searchterm.extend(language.name for language in language_names)
|
||||||
|
if rating_high:
|
||||||
|
searchterm.extend([_(u"Rating <= %(rating)s", rating=rating_high)])
|
||||||
|
if rating_low:
|
||||||
|
searchterm.extend([_(u"Rating >= %(rating)s", rating=rating_low)])
|
||||||
|
if read_status:
|
||||||
|
searchterm.extend([_(u"Read Status = %(status)s", status=read_status)])
|
||||||
|
searchterm.extend(ext for ext in include_extension_inputs)
|
||||||
|
searchterm.extend(ext for ext in exclude_extension_inputs)
|
||||||
|
# handle custom columns
|
||||||
|
searchterm = " + ".join(filter(None, searchterm))
|
||||||
|
return searchterm, pub_start, pub_end
|
||||||
|
|
||||||
|
|
||||||
def render_adv_search_results(term, offset=None, order=None, limit=None):
|
def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||||
order = order or [db.Books.sort]
|
order = order or [db.Books.sort]
|
||||||
pagination = None
|
pagination = None
|
||||||
|
@ -1044,53 +1204,24 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||||
include_languages_inputs or exclude_languages_inputs or author_name or book_title or \
|
include_languages_inputs or exclude_languages_inputs or author_name or book_title or \
|
||||||
publisher or pub_start or pub_end or rating_low or rating_high or description or cc_present or \
|
publisher or pub_start or pub_end or rating_low or rating_high or description or cc_present or \
|
||||||
include_extension_inputs or exclude_extension_inputs or read_status:
|
include_extension_inputs or exclude_extension_inputs or read_status:
|
||||||
searchterm.extend((author_name.replace('|', ','), book_title, publisher))
|
searchterm, pub_start, pub_end = extend_search_term(searchterm,
|
||||||
if pub_start:
|
author_name,
|
||||||
try:
|
book_title,
|
||||||
searchterm.extend([_(u"Published after ") +
|
publisher,
|
||||||
format_date(datetime.strptime(pub_start, "%Y-%m-%d"),
|
pub_start,
|
||||||
format='medium', locale=get_locale())])
|
pub_end,
|
||||||
except ValueError:
|
include_tag_inputs,
|
||||||
pub_start = u""
|
exclude_tag_inputs,
|
||||||
if pub_end:
|
include_series_inputs,
|
||||||
try:
|
exclude_series_inputs,
|
||||||
searchterm.extend([_(u"Published before ") +
|
include_shelf_inputs,
|
||||||
format_date(datetime.strptime(pub_end, "%Y-%m-%d"),
|
exclude_shelf_inputs,
|
||||||
format='medium', locale=get_locale())])
|
include_languages_inputs,
|
||||||
except ValueError:
|
rating_high,
|
||||||
pub_start = u""
|
rating_low,
|
||||||
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
|
read_status,
|
||||||
searchterm.extend(tag.name for tag in tag_names)
|
include_extension_inputs,
|
||||||
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(exclude_tag_inputs)).all()
|
exclude_extension_inputs)
|
||||||
searchterm.extend(tag.name for tag in tag_names)
|
|
||||||
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
|
|
||||||
searchterm.extend(serie.name for serie in serie_names)
|
|
||||||
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(exclude_series_inputs)).all()
|
|
||||||
searchterm.extend(serie.name for serie in serie_names)
|
|
||||||
shelf_names = ub.session.query(ub.Shelf).filter(ub.Shelf.id.in_(include_shelf_inputs)).all()
|
|
||||||
searchterm.extend(shelf.name for shelf in shelf_names)
|
|
||||||
shelf_names = ub.session.query(ub.Shelf).filter(ub.Shelf.id.in_(exclude_shelf_inputs)).all()
|
|
||||||
searchterm.extend(shelf.name for shelf in shelf_names)
|
|
||||||
|
|
||||||
|
|
||||||
language_names = calibre_db.session.query(db.Languages).\
|
|
||||||
filter(db.Languages.id.in_(include_languages_inputs)).all()
|
|
||||||
if language_names:
|
|
||||||
language_names = calibre_db.speaking_language(language_names)
|
|
||||||
searchterm.extend(language.name for language in language_names)
|
|
||||||
if rating_high:
|
|
||||||
searchterm.extend([_(u"Rating <= %(rating)s", rating=rating_high)])
|
|
||||||
if rating_low:
|
|
||||||
searchterm.extend([_(u"Rating >= %(rating)s", rating=rating_low)])
|
|
||||||
if read_status:
|
|
||||||
searchterm.extend([_(u"Read Status = %(status)s", status=read_status)])
|
|
||||||
searchterm.extend(ext for ext in include_extension_inputs)
|
|
||||||
searchterm.extend(ext for ext in exclude_extension_inputs)
|
|
||||||
# handle custom columns
|
|
||||||
#for c in cc:
|
|
||||||
# if term.get('custom_column_' + str(c.id)):
|
|
||||||
# searchterm.extend([(u"%s: %s" % (c.name, term.get('custom_column_' + str(c.id))))])
|
|
||||||
searchterm = " + ".join(filter(None, searchterm))
|
|
||||||
q = q.filter()
|
q = q.filter()
|
||||||
if author_name:
|
if author_name:
|
||||||
q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_name + "%")))
|
q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_name + "%")))
|
||||||
|
@ -1100,77 +1231,25 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||||
q = q.filter(db.Books.pubdate >= pub_start)
|
q = q.filter(db.Books.pubdate >= pub_start)
|
||||||
if pub_end:
|
if pub_end:
|
||||||
q = q.filter(db.Books.pubdate <= pub_end)
|
q = q.filter(db.Books.pubdate <= pub_end)
|
||||||
if read_status:
|
q = adv_search_read_status(q, read_status)
|
||||||
if config.config_read_column:
|
|
||||||
if read_status=="True":
|
|
||||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
|
||||||
.filter(db.cc_classes[config.config_read_column].value == True)
|
|
||||||
else:
|
|
||||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
|
||||||
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
|
|
||||||
else:
|
|
||||||
if read_status == "True":
|
|
||||||
q = q.join(ub.ReadBook, db.Books.id==ub.ReadBook.book_id, isouter=True)\
|
|
||||||
.filter(ub.ReadBook.user_id == int(current_user.id),
|
|
||||||
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
|
|
||||||
else:
|
|
||||||
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
|
|
||||||
.filter(ub.ReadBook.user_id == int(current_user.id),
|
|
||||||
coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED)
|
|
||||||
if publisher:
|
if publisher:
|
||||||
q = q.filter(db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + publisher + "%")))
|
q = q.filter(db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + publisher + "%")))
|
||||||
for tag in include_tag_inputs:
|
q = adv_search_tag(q, include_tag_inputs, exclude_tag_inputs)
|
||||||
q = q.filter(db.Books.tags.any(db.Tags.id == tag))
|
q = adv_search_serie(q, include_series_inputs, exclude_series_inputs)
|
||||||
for tag in exclude_tag_inputs:
|
q = adv_search_shelf(q, include_shelf_inputs, exclude_shelf_inputs)
|
||||||
q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag)))
|
q = adv_search_extension(q, include_extension_inputs, exclude_extension_inputs)
|
||||||
for serie in include_series_inputs:
|
q = adv_search_language(q, include_languages_inputs, exclude_languages_inputs)
|
||||||
q = q.filter(db.Books.series.any(db.Series.id == serie))
|
q = adv_search_ratings(q, rating_high, rating_low)
|
||||||
for serie in exclude_series_inputs:
|
|
||||||
q = q.filter(not_(db.Books.series.any(db.Series.id == serie)))
|
|
||||||
q = q.outerjoin(ub.BookShelf,db.Books.id==ub.BookShelf.book_id)\
|
|
||||||
.filter(or_(ub.BookShelf.shelf==None,ub.BookShelf.shelf.notin_(exclude_shelf_inputs)))
|
|
||||||
if len(include_shelf_inputs) >0:
|
|
||||||
q = q.filter(ub.BookShelf.shelf.in_(include_shelf_inputs))
|
|
||||||
for extension in include_extension_inputs:
|
|
||||||
q = q.filter(db.Books.data.any(db.Data.format == extension))
|
|
||||||
for extension in exclude_extension_inputs:
|
|
||||||
q = q.filter(not_(db.Books.data.any(db.Data.format == extension)))
|
|
||||||
if current_user.filter_language() != "all":
|
|
||||||
q = q.filter(db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()))
|
|
||||||
else:
|
|
||||||
for language in include_languages_inputs:
|
|
||||||
q = q.filter(db.Books.languages.any(db.Languages.id == language))
|
|
||||||
for language in exclude_languages_inputs:
|
|
||||||
q = q.filter(not_(db.Books.series.any(db.Languages.id == language)))
|
|
||||||
if rating_high:
|
|
||||||
rating_high = int(rating_high) * 2
|
|
||||||
q = q.filter(db.Books.ratings.any(db.Ratings.rating <= rating_high))
|
|
||||||
if rating_low:
|
|
||||||
rating_low = int(rating_low) * 2
|
|
||||||
q = q.filter(db.Books.ratings.any(db.Ratings.rating >= rating_low))
|
|
||||||
if description:
|
if description:
|
||||||
q = q.filter(db.Books.comments.any(func.lower(db.Comments.text).ilike("%" + description + "%")))
|
q = q.filter(db.Books.comments.any(func.lower(db.Comments.text).ilike("%" + description + "%")))
|
||||||
|
|
||||||
# search custom culumns
|
# search custom culumns
|
||||||
for c in cc:
|
q = adv_search_custom_columns(cc, term, q)
|
||||||
custom_query = term.get('custom_column_' + str(c.id))
|
|
||||||
if custom_query != '' and custom_query is not None:
|
|
||||||
if c.datatype == 'bool':
|
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
|
||||||
db.cc_classes[c.id].value == (custom_query == "True")))
|
|
||||||
elif c.datatype == 'int' or c.datatype == 'float':
|
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
|
||||||
db.cc_classes[c.id].value == custom_query))
|
|
||||||
elif c.datatype == 'rating':
|
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
|
||||||
db.cc_classes[c.id].value == int(float(custom_query) * 2)))
|
|
||||||
else:
|
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
|
||||||
func.lower(db.cc_classes[c.id].value).ilike("%" + custom_query + "%")))
|
|
||||||
q = q.order_by(*order).all()
|
q = q.order_by(*order).all()
|
||||||
flask_session['query'] = json.dumps(term)
|
flask_session['query'] = json.dumps(term)
|
||||||
ub.store_ids(q)
|
ub.store_ids(q)
|
||||||
# entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit)
|
|
||||||
result_count = len(q)
|
result_count = len(q)
|
||||||
if offset != None and limit != None:
|
if offset != None and limit != None:
|
||||||
offset = int(offset)
|
offset = int(offset)
|
||||||
|
@ -1429,12 +1508,78 @@ def logout():
|
||||||
|
|
||||||
|
|
||||||
# ################################### Users own configuration #########################################################
|
# ################################### Users own configuration #########################################################
|
||||||
|
def change_profile_email(to_save, kobo_support, local_oauth_check, oauth_status):
|
||||||
|
if "email" in to_save and to_save["email"] != current_user.email:
|
||||||
|
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
||||||
|
flash(_(u"E-mail is not from valid domain"), category="error")
|
||||||
|
return render_title_template("user_edit.html", content=current_user,
|
||||||
|
title=_(u"%(name)s's profile", name=current_user.nickname), page="me",
|
||||||
|
kobo_support=kobo_support,
|
||||||
|
registered_oauth=local_oauth_check, oauth_status=oauth_status)
|
||||||
|
current_user.email = to_save["email"]
|
||||||
|
|
||||||
|
def change_profile_nickname(to_save, kobo_support, local_oauth_check, translations, languages):
|
||||||
|
if "nickname" in to_save and to_save["nickname"] != current_user.nickname:
|
||||||
|
# Query User nickname, if not existing, change
|
||||||
|
if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar():
|
||||||
|
current_user.nickname = to_save["nickname"]
|
||||||
|
else:
|
||||||
|
flash(_(u"This username is already taken"), category="error")
|
||||||
|
return render_title_template("user_edit.html",
|
||||||
|
translations=translations,
|
||||||
|
languages=languages,
|
||||||
|
kobo_support=kobo_support,
|
||||||
|
new_user=0, content=current_user,
|
||||||
|
registered_oauth=local_oauth_check,
|
||||||
|
title=_(u"Edit User %(nick)s",
|
||||||
|
nick=current_user.nickname),
|
||||||
|
page="edituser")
|
||||||
|
|
||||||
|
|
||||||
|
def change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages):
|
||||||
|
to_save = request.form.to_dict()
|
||||||
|
current_user.random_books = 0
|
||||||
|
if current_user.role_passwd() or current_user.role_admin():
|
||||||
|
if "password" in to_save and to_save["password"]:
|
||||||
|
current_user.password = generate_password_hash(to_save["password"])
|
||||||
|
if "kindle_mail" in to_save and to_save["kindle_mail"] != current_user.kindle_mail:
|
||||||
|
current_user.kindle_mail = to_save["kindle_mail"]
|
||||||
|
if "allowed_tags" in to_save and to_save["allowed_tags"] != current_user.allowed_tags:
|
||||||
|
current_user.allowed_tags = to_save["allowed_tags"].strip()
|
||||||
|
change_profile_email(to_save, kobo_support, local_oauth_check, oauth_status)
|
||||||
|
change_profile_nickname(to_save, kobo_support, local_oauth_check, translations, languages)
|
||||||
|
if "show_random" in to_save and to_save["show_random"] == "on":
|
||||||
|
current_user.random_books = 1
|
||||||
|
if "default_language" in to_save:
|
||||||
|
current_user.default_language = to_save["default_language"]
|
||||||
|
if "locale" in to_save:
|
||||||
|
current_user.locale = to_save["locale"]
|
||||||
|
|
||||||
|
val = 0
|
||||||
|
for key, __ in to_save.items():
|
||||||
|
if key.startswith('show'):
|
||||||
|
val += int(key[5:])
|
||||||
|
current_user.sidebar_view = val
|
||||||
|
if "Show_detail_random" in to_save:
|
||||||
|
current_user.sidebar_view += constants.DETAIL_RANDOM
|
||||||
|
|
||||||
|
try:
|
||||||
|
ub.session.commit()
|
||||||
|
flash(_(u"Profile updated"), category="success")
|
||||||
|
log.debug(u"Profile updated")
|
||||||
|
except IntegrityError:
|
||||||
|
ub.session.rollback()
|
||||||
|
flash(_(u"Found an existing account for this e-mail address."), category="error")
|
||||||
|
log.debug(u"Found an existing account for this e-mail address.")
|
||||||
|
except OperationalError as e:
|
||||||
|
ub.session.rollback()
|
||||||
|
log.error("Database error: %s", e)
|
||||||
|
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||||
|
|
||||||
|
|
||||||
@web.route("/me", methods=["GET", "POST"])
|
@web.route("/me", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def profile():
|
def profile():
|
||||||
# downloads = list()
|
|
||||||
languages = calibre_db.speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
translations = babel.list_translations() + [LC('en')]
|
translations = babel.list_translations() + [LC('en')]
|
||||||
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
||||||
|
@ -1445,74 +1590,8 @@ def profile():
|
||||||
oauth_status = None
|
oauth_status = None
|
||||||
local_oauth_check = {}
|
local_oauth_check = {}
|
||||||
|
|
||||||
'''entries, __, pagination = calibre_db.fill_indexpage(page,
|
|
||||||
0,
|
|
||||||
db.Books,
|
|
||||||
ub.Downloads.user_id == int(current_user.id), # True,
|
|
||||||
[],
|
|
||||||
ub.Downloads, db.Books.id == ub.Downloads.book_id)'''
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
to_save = request.form.to_dict()
|
change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages)
|
||||||
current_user.random_books = 0
|
|
||||||
if current_user.role_passwd() or current_user.role_admin():
|
|
||||||
if "password" in to_save and to_save["password"]:
|
|
||||||
current_user.password = generate_password_hash(to_save["password"])
|
|
||||||
if "kindle_mail" in to_save and to_save["kindle_mail"] != current_user.kindle_mail:
|
|
||||||
current_user.kindle_mail = to_save["kindle_mail"]
|
|
||||||
if "allowed_tags" in to_save and to_save["allowed_tags"] != current_user.allowed_tags:
|
|
||||||
current_user.allowed_tags = to_save["allowed_tags"].strip()
|
|
||||||
if "email" in to_save and to_save["email"] != current_user.email:
|
|
||||||
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
|
||||||
flash(_(u"E-mail is not from valid domain"), category="error")
|
|
||||||
return render_title_template("user_edit.html", content=current_user,
|
|
||||||
title=_(u"%(name)s's profile", name=current_user.nickname), page="me",
|
|
||||||
kobo_support=kobo_support,
|
|
||||||
registered_oauth=local_oauth_check, oauth_status=oauth_status)
|
|
||||||
current_user.email = to_save["email"]
|
|
||||||
if "nickname" in to_save and to_save["nickname"] != current_user.nickname:
|
|
||||||
# Query User nickname, if not existing, change
|
|
||||||
if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar():
|
|
||||||
current_user.nickname = to_save["nickname"]
|
|
||||||
else:
|
|
||||||
flash(_(u"This username is already taken"), category="error")
|
|
||||||
return render_title_template("user_edit.html",
|
|
||||||
translations=translations,
|
|
||||||
languages=languages,
|
|
||||||
kobo_support=kobo_support,
|
|
||||||
new_user=0, content=current_user,
|
|
||||||
registered_oauth=local_oauth_check,
|
|
||||||
title=_(u"Edit User %(nick)s",
|
|
||||||
nick=current_user.nickname),
|
|
||||||
page="edituser")
|
|
||||||
if "show_random" in to_save and to_save["show_random"] == "on":
|
|
||||||
current_user.random_books = 1
|
|
||||||
if "default_language" in to_save:
|
|
||||||
current_user.default_language = to_save["default_language"]
|
|
||||||
if "locale" in to_save:
|
|
||||||
current_user.locale = to_save["locale"]
|
|
||||||
|
|
||||||
val = 0
|
|
||||||
for key, __ in to_save.items():
|
|
||||||
if key.startswith('show'):
|
|
||||||
val += int(key[5:])
|
|
||||||
current_user.sidebar_view = val
|
|
||||||
if "Show_detail_random" in to_save:
|
|
||||||
current_user.sidebar_view += constants.DETAIL_RANDOM
|
|
||||||
|
|
||||||
try:
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u"Profile updated"), category="success")
|
|
||||||
log.debug(u"Profile updated")
|
|
||||||
except IntegrityError:
|
|
||||||
ub.session.rollback()
|
|
||||||
flash(_(u"Found an existing account for this e-mail address."), category="error")
|
|
||||||
log.debug(u"Found an existing account for this e-mail address.")
|
|
||||||
except OperationalError as e:
|
|
||||||
ub.session.rollback()
|
|
||||||
log.error("Database error: %s", e)
|
|
||||||
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
|
||||||
|
|
||||||
return render_title_template("user_edit.html",
|
return render_title_template("user_edit.html",
|
||||||
translations=translations,
|
translations=translations,
|
||||||
profile=1,
|
profile=1,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user