mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-30 03:50:31 +00:00
Merged develop, fixed merged conflicts
This commit is contained in:
commit
35c60eaee5
42
cps/admin.py
42
cps/admin.py
@ -39,6 +39,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||
from sqlalchemy.sql.expression import func, or_
|
||||
|
||||
from . import constants, logger, helper, services, fs
|
||||
from .cli import filepicker
|
||||
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
|
||||
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash
|
||||
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||
@ -118,7 +119,7 @@ def before_request():
|
||||
g.shelves_access = ub.session.query(ub.Shelf).filter(
|
||||
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
|
||||
if not config.db_configured and request.endpoint not in (
|
||||
'admin.basic_configuration', 'login') and '/static/' not in request.path:
|
||||
'admin.basic_configuration', 'login', 'admin.config_pathchooser') and '/static/' not in request.path:
|
||||
return redirect(url_for('admin.basic_configuration'))
|
||||
|
||||
|
||||
@ -209,7 +210,7 @@ def admin():
|
||||
@admin_required
|
||||
def configuration():
|
||||
if request.method == "POST":
|
||||
return _configuration_update_helper()
|
||||
return _configuration_update_helper(True)
|
||||
return _configuration_result()
|
||||
|
||||
|
||||
@ -604,10 +605,11 @@ def list_restriction(res_type):
|
||||
return response
|
||||
|
||||
@admi.route("/basicconfig/pathchooser/")
|
||||
# @unconfigured
|
||||
@login_required
|
||||
@unconfigured
|
||||
def config_pathchooser():
|
||||
if filepicker:
|
||||
return pathchooser()
|
||||
abort(403)
|
||||
|
||||
@admi.route("/ajax/pathchooser/")
|
||||
@login_required
|
||||
@ -616,7 +618,7 @@ def ajax_pathchooser():
|
||||
return pathchooser()
|
||||
|
||||
def pathchooser():
|
||||
browse_for = "folder" # if request.endpoint == "admin.pathchooser" else "file"
|
||||
browse_for = "folder"
|
||||
folder_only = request.args.get('folder', False) == "true"
|
||||
file_filter = request.args.get('filter', "")
|
||||
path = os.path.normpath(request.args.get('path', ""))
|
||||
@ -702,8 +704,8 @@ def pathchooser():
|
||||
def basic_configuration():
|
||||
logout_user()
|
||||
if request.method == "POST":
|
||||
return _configuration_update_helper()
|
||||
return _configuration_result()
|
||||
return _configuration_update_helper(configured=filepicker)
|
||||
return _configuration_result(configured=filepicker)
|
||||
|
||||
|
||||
def _config_int(to_save, x, func=int):
|
||||
@ -858,7 +860,7 @@ def _configuration_ldap_helper(to_save, gdriveError):
|
||||
return reboot_required, None
|
||||
|
||||
|
||||
def _configuration_update_helper():
|
||||
def _configuration_update_helper(configured):
|
||||
reboot_required = False
|
||||
db_change = False
|
||||
to_save = request.form.to_dict()
|
||||
@ -878,11 +880,15 @@ def _configuration_update_helper():
|
||||
|
||||
reboot_required |= _config_string(to_save, "config_keyfile")
|
||||
if config.config_keyfile and not os.path.isfile(config.config_keyfile):
|
||||
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'),
|
||||
gdriveError,
|
||||
configured)
|
||||
|
||||
reboot_required |= _config_string(to_save, "config_certfile")
|
||||
if config.config_certfile and not os.path.isfile(config.config_certfile):
|
||||
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'),
|
||||
gdriveError,
|
||||
configured)
|
||||
|
||||
_config_checkbox_int(to_save, "config_uploading")
|
||||
# Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case
|
||||
@ -947,10 +953,10 @@ def _configuration_update_helper():
|
||||
if "config_rarfile_location" in to_save:
|
||||
unrar_status = helper.check_unrar(config.config_rarfile_location)
|
||||
if unrar_status:
|
||||
return _configuration_result(unrar_status, gdriveError)
|
||||
return _configuration_result(unrar_status, gdriveError, configured)
|
||||
except (OperationalError, InvalidRequestError):
|
||||
ub.session.rollback()
|
||||
_configuration_result(_(u"Settings DB is not Writeable"), gdriveError)
|
||||
_configuration_result(_(u"Settings DB is not Writeable"), gdriveError, configured)
|
||||
|
||||
try:
|
||||
metadata_db = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||
@ -958,11 +964,13 @@ def _configuration_update_helper():
|
||||
gdriveutils.downloadFile(None, "metadata.db", metadata_db)
|
||||
db_change = True
|
||||
except Exception as e:
|
||||
return _configuration_result('%s' % e, gdriveError)
|
||||
return _configuration_result('%s' % e, gdriveError, configured)
|
||||
|
||||
if db_change:
|
||||
if not calibre_db.setup_db(config, ub.app_DB_path):
|
||||
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'),
|
||||
gdriveError,
|
||||
configured)
|
||||
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
|
||||
flash(_(u"DB is not Writeable"), category="warning")
|
||||
|
||||
@ -971,10 +979,10 @@ def _configuration_update_helper():
|
||||
if reboot_required:
|
||||
web_server.stop(True)
|
||||
|
||||
return _configuration_result(None, gdriveError)
|
||||
return _configuration_result(None, gdriveError, configured)
|
||||
|
||||
|
||||
def _configuration_result(error_flash=None, gdriveError=None):
|
||||
def _configuration_result(error_flash=None, gdriveError=None, configured=True):
|
||||
gdrive_authenticate = not is_gdrive_ready()
|
||||
gdrivefolders = []
|
||||
if gdriveError is None:
|
||||
@ -995,7 +1003,7 @@ def _configuration_result(error_flash=None, gdriveError=None):
|
||||
|
||||
return render_title_template("config_edit.html", config=config, provider=oauthblueprints,
|
||||
show_back_button=show_back_button, show_login_button=show_login_button,
|
||||
show_authenticate_google_drive=gdrive_authenticate,
|
||||
show_authenticate_google_drive=gdrive_authenticate, filepicker=configured,
|
||||
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
|
||||
title=_(u"Basic Configuration"), page="config")
|
||||
|
||||
|
@ -45,6 +45,7 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num
|
||||
version=version_info())
|
||||
parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen')
|
||||
parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password')
|
||||
parser.add_argument('-f', action='store_true', help='Enables filepicker in unconfigured mode')
|
||||
args = parser.parse_args()
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
@ -110,3 +111,6 @@ if ipadress:
|
||||
|
||||
# handle and check user password argument
|
||||
user_password = args.s or None
|
||||
|
||||
# Handles enableing of filepicker
|
||||
filepicker = args.f or None
|
||||
|
@ -445,6 +445,8 @@ class CalibreDB():
|
||||
cls.config = config
|
||||
cls.dispose()
|
||||
|
||||
# toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync??
|
||||
|
||||
if not config.config_calibre_dir:
|
||||
config.invalidate()
|
||||
return False
|
||||
|
@ -20,11 +20,14 @@ from flask import render_template
|
||||
from flask_babel import gettext as _
|
||||
from flask import g
|
||||
from werkzeug.local import LocalProxy
|
||||
from flask_login import current_user
|
||||
|
||||
from . import config, constants
|
||||
from . import config, constants, ub, logger, db, calibre_db
|
||||
from .ub import User
|
||||
|
||||
|
||||
log = logger.create()
|
||||
|
||||
def get_sidebar_config(kwargs=None):
|
||||
kwargs = kwargs or []
|
||||
if 'content' in kwargs:
|
||||
@ -91,9 +94,23 @@ def get_sidebar_config(kwargs=None):
|
||||
|
||||
return sidebar
|
||||
|
||||
def get_readbooks_ids():
|
||||
if not config.config_read_column:
|
||||
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
|
||||
.filter(ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED).all()
|
||||
return frozenset([x.book_id for x in readBooks])
|
||||
else:
|
||||
try:
|
||||
readBooks = calibre_db.session.query(db.cc_classes[config.config_read_column])\
|
||||
.filter(db.cc_classes[config.config_read_column].value == True).all()
|
||||
return frozenset([x.book for x in readBooks])
|
||||
except KeyError:
|
||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
||||
return []
|
||||
|
||||
# Returns the template for rendering and includes the instance name
|
||||
def render_title_template(*args, **kwargs):
|
||||
sidebar = get_sidebar_config(kwargs)
|
||||
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar,
|
||||
accept=constants.EXTENSIONS_UPLOAD,
|
||||
accept=constants.EXTENSIONS_UPLOAD, read_book_ids=get_readbooks_ids(),
|
||||
*args, **kwargs)
|
||||
|
34
cps/shelf.py
34
cps/shelf.py
@ -381,27 +381,53 @@ def order_shelf(shelf_id):
|
||||
title=_(u"Change order of Shelf: '%(name)s'", name=shelf.name),
|
||||
shelf=shelf, page="shelforder")
|
||||
|
||||
def change_shelf_order(shelf_id, order):
|
||||
result = calibre_db.session.query(db.Books).join(ub.BookShelf,ub.BookShelf.book_id == db.Books.id)\
|
||||
.filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all()
|
||||
for index, entry in enumerate(result):
|
||||
book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
|
||||
.filter(ub.BookShelf.book_id == entry.id).first()
|
||||
book.order = index
|
||||
try:
|
||||
ub.session.commit()
|
||||
except OperationalError:
|
||||
ub.session.rollback()
|
||||
|
||||
def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
|
||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
||||
|
||||
# check user is allowed to access shelf
|
||||
if shelf and check_shelf_view_permissions(shelf):
|
||||
|
||||
if shelf_type == 1:
|
||||
# order = [ub.BookShelf.order.asc()]
|
||||
if sort_param == 'pubnew':
|
||||
change_shelf_order(shelf_id, [db.Books.pubdate.desc()])
|
||||
if sort_param == 'pubold':
|
||||
change_shelf_order(shelf_id, [db.Books.pubdate])
|
||||
if sort_param == 'abc':
|
||||
change_shelf_order(shelf_id, [db.Books.sort])
|
||||
if sort_param == 'zyx':
|
||||
change_shelf_order(shelf_id, [db.Books.sort.desc()])
|
||||
if sort_param == 'new':
|
||||
change_shelf_order(shelf_id, [db.Books.timestamp.desc()])
|
||||
if sort_param == 'old':
|
||||
change_shelf_order(shelf_id, [db.Books.timestamp])
|
||||
if sort_param == 'authaz':
|
||||
change_shelf_order(shelf_id, [db.Books.author_sort.asc()])
|
||||
if sort_param == 'authza':
|
||||
change_shelf_order(shelf_id, [db.Books.author_sort.desc()])
|
||||
page = "shelf.html"
|
||||
pagesize = 0
|
||||
order = [ub.BookShelf.order.asc()]
|
||||
else:
|
||||
pagesize = sys.maxsize
|
||||
page = 'shelfdown.html'
|
||||
order = [ub.BookShelf.order.asc()]
|
||||
|
||||
result, __, pagination = calibre_db.fill_indexpage(page_no, pagesize,
|
||||
db.Books,
|
||||
ub.BookShelf.shelf == shelf_id,
|
||||
order,
|
||||
[ub.BookShelf.order.asc()],
|
||||
ub.BookShelf,ub.BookShelf.book_id == db.Books.id)
|
||||
|
||||
# delete chelf entries where book is not existent anymore, can happen if book is deleted outside calibre-web
|
||||
wrong_entries = calibre_db.session.query(ub.BookShelf)\
|
||||
.join(db.Books, ub.BookShelf.book_id == db.Books.id, isouter=True)\
|
||||
|
@ -1,11 +1,11 @@
|
||||
body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cover .badge{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: #fff;
|
||||
background-color: #cc7b19;
|
||||
border-radius: 0;
|
||||
padding: 0 8px;
|
||||
@ -15,3 +15,8 @@ body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
|
||||
.cover{
|
||||
box-shadow: 0 0 4px rgba(0,0,0,.6);
|
||||
}
|
||||
|
||||
.cover .read{
|
||||
padding: 0 0px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
6
cps/static/css/libs/bootstrap-select.min.css
vendored
Normal file
6
cps/static/css/libs/bootstrap-select.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -116,6 +116,7 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.container-fluid .discover{ margin-bottom: 50px; }
|
||||
@ -132,12 +133,19 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container-fluid .book .cover img {
|
||||
.container-fluid .book .cover span.img {
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.container-fluid .book .cover span img {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
border: 1px solid #fff;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
-webkit-box-shadow: 0 5px 8px -6px #777;
|
||||
-moz-box-shadow: 0 5px 8px -6px #777;
|
||||
box-shadow: 0 5px 8px -6px #777;
|
||||
@ -206,11 +214,22 @@ span.glyphicon.glyphicon-tags {
|
||||
.navbar-default .navbar-toggle .icon-bar {background-color: #000; }
|
||||
.navbar-default .navbar-toggle {border-color: #000; }
|
||||
.cover { margin-bottom: 10px; }
|
||||
|
||||
.cover .badge{
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
background-color: #777;
|
||||
color: #000;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.cover .read{
|
||||
left: auto;
|
||||
right: 2px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
}
|
||||
.cover-height { max-height: 100px;}
|
||||
|
||||
|
@ -249,18 +249,26 @@ promisePublishers.done(function() {
|
||||
);
|
||||
});
|
||||
|
||||
$("#search").on("change input.typeahead:selected", function() {
|
||||
$("#search").on("change input.typeahead:selected", function(event) {
|
||||
if (event.target.type == "search" && event.target.tagName == "INPUT") {
|
||||
return;
|
||||
}
|
||||
var form = $("form").serialize();
|
||||
$.getJSON( getPath() + "/get_matching_tags", form, function( data ) {
|
||||
$(".tags_click").each(function() {
|
||||
if ($.inArray(parseInt($(this).children("input").first().val(), 10), data.tags) === -1 ) {
|
||||
if (!($(this).hasClass("active"))) {
|
||||
$(this).addClass("disabled");
|
||||
if ($.inArray(parseInt($(this).val(), 10), data.tags) === -1) {
|
||||
if(!$(this).prop("selected")) {
|
||||
$(this).prop("disabled", true);
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass("disabled");
|
||||
$(this).prop("disabled", false);
|
||||
}
|
||||
});
|
||||
$("#include_tag option:selected").each(function () {
|
||||
$("#exclude_tag").find("[value="+$(this).val()+"]").prop("disabled", true);
|
||||
});
|
||||
$('#include_tag').selectpicker("refresh");
|
||||
$('#exclude_tag').selectpicker("refresh");
|
||||
});
|
||||
});
|
||||
|
||||
|
9
cps/static/js/libs/bootstrap-select.min.js
vendored
Normal file
9
cps/static/js/libs/bootstrap-select.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
cps/static/js/libs/bootstrap-select/defaults-cs.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-cs.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,n){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return n(e)}):"object"==typeof module&&module.exports?module.exports=n(require("jquery")):n(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Vyberte ze seznamu",noneResultsText:"Pro hled\xe1n\xed {0} nebyly nalezeny \u017e\xe1dn\xe9 v\xfdsledky",countSelectedText:"Vybran\xe9 {0} z {1}",maxOptionsText:["Limit p\u0159ekro\u010den ({n} {var} max)","Limit skupiny p\u0159ekro\u010den ({n} {var} max)",["polo\u017eek","polo\u017eka"]],multipleSeparator:", ",selectAllText:"Vybrat v\u0161e",deselectAllText:"Zru\u0161it v\xfdb\u011br"}});
|
8
cps/static/js/libs/bootstrap-select/defaults-de.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-de.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Bitte w\xe4hlen...",noneResultsText:"Keine Ergebnisse f\xfcr {0}",countSelectedText:function(e,t){return 1==e?"{0} Element ausgew\xe4hlt":"{0} Elemente ausgew\xe4hlt"},maxOptionsText:function(e,t){return[1==e?"Limit erreicht ({n} Element max.)":"Limit erreicht ({n} Elemente max.)",1==t?"Gruppen-Limit erreicht ({n} Element max.)":"Gruppen-Limit erreicht ({n} Elemente max.)"]},selectAllText:"Alles ausw\xe4hlen",deselectAllText:"Nichts ausw\xe4hlen",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-es.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-es.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,o){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return o(e)}):"object"==typeof module&&module.exports?module.exports=o(require("jquery")):o(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"No hay selecci\xf3n",noneResultsText:"No hay resultados {0}",countSelectedText:"Seleccionados {0} de {1}",maxOptionsText:["L\xedmite alcanzado ({n} {var} max)","L\xedmite del grupo alcanzado({n} {var} max)",["elementos","element"]],multipleSeparator:", ",selectAllText:"Seleccionar Todos",deselectAllText:"Desmarcar Todos"}});
|
8
cps/static/js/libs/bootstrap-select/defaults-fi.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-fi.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Ei valintoja",noneResultsText:"Ei hakutuloksia {0}",countSelectedText:function(e,t){return 1==e?"{0} valittu":"{0} valitut"},maxOptionsText:function(e,t){return["Valintojen maksimim\xe4\xe4r\xe4 ({n} saavutettu)","Ryhm\xe4n maksimim\xe4\xe4r\xe4 ({n} saavutettu)"]},selectAllText:"Valitse kaikki",deselectAllText:"Poista kaikki",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-fr.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-fr.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Aucune s\xe9lection",noneResultsText:"Aucun r\xe9sultat pour {0}",countSelectedText:function(e,t){return 1<e?"{0} \xe9l\xe9ments s\xe9lectionn\xe9s":"{0} \xe9l\xe9ment s\xe9lectionn\xe9"},maxOptionsText:function(e,t){return[1<e?"Limite atteinte ({n} \xe9l\xe9ments max)":"Limite atteinte ({n} \xe9l\xe9ment max)",1<t?"Limite du groupe atteinte ({n} \xe9l\xe9ments max)":"Limite du groupe atteinte ({n} \xe9l\xe9ment max)"]},multipleSeparator:", ",selectAllText:"Tout s\xe9lectionner",deselectAllText:"Tout d\xe9s\xe9lectionner"}});
|
8
cps/static/js/libs/bootstrap-select/defaults-hu.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-hu.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"V\xe1lasszon!",noneResultsText:"Nincs tal\xe1lat {0}",countSelectedText:function(e,t){return"{0} elem kiv\xe1lasztva"},maxOptionsText:function(e,t){return["Legfeljebb {n} elem v\xe1laszthat\xf3","A csoportban legfeljebb {n} elem v\xe1laszthat\xf3"]},selectAllText:"Mind",deselectAllText:"Egyik sem",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-it.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-it.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Nessuna selezione",noneResultsText:"Nessun risultato per {0}",countSelectedText:function(e,t){return 1==e?"Selezionato {0} di {1}":"Selezionati {0} di {1}"},maxOptionsText:["Limite raggiunto ({n} {var} max)","Limite del gruppo raggiunto ({n} {var} max)",["elementi","elemento"]],multipleSeparator:", ",selectAllText:"Seleziona Tutto",deselectAllText:"Deseleziona Tutto"}});
|
8
cps/static/js/libs/bootstrap-select/defaults-ja.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-ja.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093",noneResultsText:"'{0}'\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093",countSelectedText:"{0}/{1} \u9078\u629e\u4e2d",maxOptionsText:["\u9078\u629e\u4e0a\u9650\u6570\u3092\u8d85\u3048\u3066\u3044\u307e\u3059(\u6700\u5927{n}{var})","\u30b0\u30eb\u30fc\u30d7\u306e\u9078\u629e\u4e0a\u9650\u6570\u3092\u8d85\u3048\u3066\u3044\u307e\u3059(\u6700\u5927{n}{var})",["\u30a2\u30a4\u30c6\u30e0","\u30a2\u30a4\u30c6\u30e0"]],selectAllText:"\u5168\u3066\u9078\u629e",deselectAllText:"\u9078\u629e\u3092\u30af\u30ea\u30a2",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-km.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-km.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u1798\u17b7\u1793\u1798\u17b6\u1793\u17a2\u17d2\u179c\u17b8\u1794\u17b6\u1793\u1787\u17d2\u179a\u17be\u179f\u179a\u17be\u179f",noneResultsText:"\u1798\u17b7\u1793\u1798\u17b6\u1793\u179b\u1791\u17d2\u1792\u1795\u179b {0}",countSelectedText:function(e,t){return"{0} \u1792\u17b6\u178f\u17bb\u178a\u17c2\u179b\u1794\u17b6\u1793\u1787\u17d2\u179a\u17be\u179f"},maxOptionsText:function(e,t){return[1==e?"\u1788\u17b6\u1793\u178a\u179b\u17cb\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb ( {n} \u1792\u17b6\u178f\u17bb\u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6)":"\u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6\u1788\u17b6\u1793\u178a\u179b\u17cb\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb ( {n} \u1792\u17b6\u178f\u17bb)",1==t?"\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb\u1780\u17d2\u179a\u17bb\u1798\u1788\u17b6\u1793\u178a\u179b\u17cb ( {n} \u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6\u1792\u17b6\u178f\u17bb)":"\u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6\u1780\u17d2\u179a\u17bb\u1798\u1788\u17b6\u1793\u178a\u179b\u17cb\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb ( {n} \u1792\u17b6\u178f\u17bb)"]},selectAllText:"\u1787\u17d2\u179a\u17be\u179f\u200b\u1799\u1780\u200b\u1791\u17b6\u17c6\u1784\u17a2\u179f\u17cb",deselectAllText:"\u1798\u17b7\u1793\u1787\u17d2\u179a\u17be\u179f\u200b\u1799\u1780\u200b\u1791\u17b6\u17c6\u1784\u17a2\u179f",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-nl.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-nl.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Niets geselecteerd",noneResultsText:"Geen resultaten gevonden voor {0}",countSelectedText:"{0} van {1} geselecteerd",maxOptionsText:["Limiet bereikt ({n} {var} max)","Groep limiet bereikt ({n} {var} max)",["items","item"]],selectAllText:"Alles selecteren",deselectAllText:"Alles deselecteren",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-pl.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-pl.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,n){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return n(e)}):"object"==typeof module&&module.exports?module.exports=n(require("jquery")):n(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Nic nie zaznaczono",noneResultsText:"Brak wynik\xf3w wyszukiwania {0}",countSelectedText:"Zaznaczono {0} z {1}",maxOptionsText:["Osi\u0105gni\u0119to limit ({n} {var} max)","Limit grupy osi\u0105gni\u0119ty ({n} {var} max)",["elementy","element"]],selectAllText:"Zaznacz wszystkie",deselectAllText:"Odznacz wszystkie",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-ru.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-ru.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043e",noneResultsText:"\u0421\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e {0}",countSelectedText:"\u0412\u044b\u0431\u0440\u0430\u043d\u043e {0} \u0438\u0437 {1}",maxOptionsText:["\u0414\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442 \u043f\u0440\u0435\u0434\u0435\u043b ({n} {var} \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c)","\u0414\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442 \u043f\u0440\u0435\u0434\u0435\u043b \u0432 \u0433\u0440\u0443\u043f\u043f\u0435 ({n} {var} \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c)",["\u0448\u0442.","\u0448\u0442."]],doneButtonText:"\u0417\u0430\u043a\u0440\u044b\u0442\u044c",selectAllText:"\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435",deselectAllText:"\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u0441\u0435",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-sv.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-sv.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Inget valt",noneResultsText:"Inget s\xf6kresultat matchar {0}",countSelectedText:function(e,t){return 1===e?"{0} alternativ valt":"{0} alternativ valda"},maxOptionsText:function(e,t){return["Gr\xe4ns uppn\xe5d (max {n} alternativ)","Gr\xe4ns uppn\xe5d (max {n} gruppalternativ)"]},selectAllText:"Markera alla",deselectAllText:"Avmarkera alla",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-tr.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-tr.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,i){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return i(e)}):"object"==typeof module&&module.exports?module.exports=i(require("jquery")):i(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Hi\xe7biri se\xe7ilmedi",noneResultsText:"Hi\xe7bir sonu\xe7 bulunamad\u0131 {0}",countSelectedText:function(e,i){return"{0} \xf6\u011fe se\xe7ildi"},maxOptionsText:function(e,i){return[1==e?"Limit a\u015f\u0131ld\u0131 (maksimum {n} say\u0131da \xf6\u011fe )":"Limit a\u015f\u0131ld\u0131 (maksimum {n} say\u0131da \xf6\u011fe)","Grup limiti a\u015f\u0131ld\u0131 (maksimum {n} say\u0131da \xf6\u011fe)"]},selectAllText:"T\xfcm\xfcn\xfc Se\xe7",deselectAllText:"Se\xe7iniz",multipleSeparator:", "}});
|
8
cps/static/js/libs/bootstrap-select/defaults-zh_Hans_CN.min.js
vendored
Normal file
8
cps/static/js/libs/bootstrap-select/defaults-zh_Hans_CN.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u6ca1\u6709\u9009\u4e2d\u4efb\u4f55\u9879",noneResultsText:"\u6ca1\u6709\u627e\u5230\u5339\u914d\u9879",countSelectedText:"\u9009\u4e2d{1}\u4e2d\u7684{0}\u9879",maxOptionsText:["\u8d85\u51fa\u9650\u5236 (\u6700\u591a\u9009\u62e9{n}\u9879)","\u7ec4\u9009\u62e9\u8d85\u51fa\u9650\u5236(\u6700\u591a\u9009\u62e9{n}\u7ec4)"],multipleSeparator:", ",selectAllText:"\u5168\u9009",deselectAllText:"\u53d6\u6d88\u5168\u9009"}});
|
@ -509,6 +509,19 @@ $(function() {
|
||||
);
|
||||
});
|
||||
|
||||
$("#toggle_order_shelf").click(function() {
|
||||
$("#new").toggleClass("disabled");
|
||||
$("#old").toggleClass("disabled");
|
||||
$("#asc").toggleClass("disabled");
|
||||
$("#desc").toggleClass("disabled");
|
||||
$("#auth_az").toggleClass("disabled");
|
||||
$("#auth_za").toggleClass("disabled");
|
||||
$("#pub_new").toggleClass("disabled");
|
||||
$("#pub_old").toggleClass("disabled");
|
||||
var alternative_text = $("#toggle_order_shelf").data('alt-text');
|
||||
$("#toggle_order_shelf")[0].attributes['data-alt-text'].value = $("#toggle_order_shelf").html();
|
||||
$("#toggle_order_shelf").html(alternative_text);
|
||||
});
|
||||
|
||||
$("#btndeluser").click(function() {
|
||||
ConfirmDialog(
|
||||
|
@ -36,7 +36,10 @@
|
||||
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<span class="img">
|
||||
{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -16,12 +16,19 @@
|
||||
<div id="collapseOne" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
|
||||
<div class="form-group required input-group">
|
||||
<div class="form-group required{% if filepicker %} input-group{% endif %}">
|
||||
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
|
||||
{% if filepicker %}
|
||||
<span class="input-group-btn">
|
||||
<button type="button" data-toggle="modal" id="converter_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not filepicker %}
|
||||
<div class="form-group">
|
||||
<label id="filepicker-hint">{{_('To activate serverside filepicker start Calibre-Web with -f optionn')}}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if feature_support['gdrive'] %}
|
||||
<div class="form-group required">
|
||||
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
|
||||
|
@ -9,7 +9,10 @@
|
||||
<div class="cover">
|
||||
{% if entry.has_cover is defined %}
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -29,8 +29,10 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
|
||||
<div class="cover">
|
||||
<a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry[0], entry[0].id|get_book_thumbnails(thumbnails)) }}
|
||||
<span class="badge">{{entry.count}}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -9,7 +9,10 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
@ -83,7 +86,10 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -44,7 +44,10 @@
|
||||
<div class="cover">
|
||||
{% if entry.has_cover is defined %}
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -31,86 +31,86 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="include_tag">{{_('Tags')}}</label>
|
||||
<div class="form-group" id="tag">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="form-group">
|
||||
<label for="read_status">{{_('Read Status')}}</label>
|
||||
<select name="read_status" id="read_status" class="form-control">
|
||||
<option value="" selected></option>
|
||||
<option value="True" >{{_('Yes')}}</option>
|
||||
<option value="False" >{{_('No')}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6" id="tag">
|
||||
<div><label for="include_tag">{{_('Tags')}}</label></div>
|
||||
<select class="selectpicker" name="include_tag" id="include_tag" data-live-search="true" data-style="btn-primary" multiple>
|
||||
{% for tag in tags %}
|
||||
<label id="tag_{{tag.id}}" class="btn btn-primary tags_click">
|
||||
<input type="checkbox" autocomplete="off" name="include_tag" id="include_tag" value="{{tag.id}}">{{tag.name}}</input>
|
||||
</label>
|
||||
<option class="tags_click" value="{{tag.id}}">{{tag.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label for="exclude_tag">{{_('Exclude Tags')}}</label>
|
||||
<div class="form-group">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="exclude_tag">{{_('Exclude Tags')}}</label></div>
|
||||
<select class="selectpicker" name="exclude_tag" id="exclude_tag" data-live-search="true" data-style="btn-danger" multiple>
|
||||
{% for tag in tags %}
|
||||
<label id="exclude_tag_{{tag.id}}" class="btn btn-danger tags_click">
|
||||
<input type="checkbox" autocomplete="off" name="exclude_tag" id="exclude_tag" value="{{tag.id}}">{{tag.name}}</input>
|
||||
</label>
|
||||
<option class="tags_click" value="{{tag.id}}">{{tag.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label for="include_serie">{{_('Series')}}</label>
|
||||
<div class="form-group">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="include_serie">{{_('Series')}}</label></div>
|
||||
<select class="selectpicker" name="include_serie" id="include_serie" data-live-search="true" data-style="btn-primary" multiple>
|
||||
{% for serie in series %}
|
||||
<label id="serie_{{serie.id}}" class="btn btn-primary serie_click">
|
||||
<input type="checkbox" autocomplete="off" name="include_serie" id="include_serie" value="{{serie.id}}">{{serie.name}}</input>
|
||||
</label>
|
||||
<option value="{{serie.id}}">{{serie.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label for="exclude_serie">{{_('Exclude Series')}}</label>
|
||||
<div class="form-group">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="exclude_serie">{{_('Exclude Series')}}</label></div>
|
||||
<select class="selectpicker" name="exclude_serie" id="exclude_serie" data-live-search="true" data-style="btn-danger" multiple>
|
||||
{% for serie in series %}
|
||||
<label id="exclude_serie_{{serie.id}}" class="btn btn-danger serie_click">
|
||||
<input type="checkbox" autocomplete="off" name="exclude_serie" id="exclude_serie" value="{{serie.id}}">{{serie.name}}</input>
|
||||
</label>
|
||||
<option value="{{serie.id}}">{{serie.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if languages %}
|
||||
<label for="include_language">{{_('Languages')}}</label>
|
||||
<div class="form-group">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="include_language">{{_('Languages')}}</label></div>
|
||||
<select class="selectpicker" name="include_language" id="include_language" data-live-search="true" data-style="btn-primary" multiple>
|
||||
{% for language in languages %}
|
||||
<label id="language_{{language.id}}" class="btn btn-primary serie_click">
|
||||
<input type="checkbox" autocomplete="off" name="include_language" id="include_language" value="{{language.id}}">{{language.name}}</input>
|
||||
</label>
|
||||
<option value="{{language.id}}">{{language.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label for="exclude_language">{{_('Exclude Languages')}}</label>
|
||||
<div class="form-group">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="exclude_language">{{_('Exclude Languages')}}</label></div>
|
||||
<select class="selectpicker" name="exclude_language" id="exclude_language" data-live-search="true" data-style="btn-danger" multiple>
|
||||
{% for language in languages %}
|
||||
<label id="exclude_language_{{language.id}}" class="btn btn-danger language_click">
|
||||
<input type="checkbox" autocomplete="off" name="exclude_language" id="exclude_language" value="{{language.id}}">{{language.name}}</input>
|
||||
</label>
|
||||
<option value="{{language.id}}">{{language.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif%}
|
||||
<label for="include_extension">{{_('Extensions')}}</label>
|
||||
<div class="form-group" id="extension">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="include_extension">{{_('Extensions')}}</label></div>
|
||||
<select id="include_extension" class="selectpicker" name="include_extension" id="include_extension" data-live-search="true" data-style="btn-primary" multiple>
|
||||
{% for extension in extensions %}
|
||||
<label id="extension_{{extension.format}}" class="btn btn-primary extension_click">
|
||||
<input type="checkbox" autocomplete="off" name="include_extension" id="include_extension" value="{{extension.format}}">{{extension.format}}</input>
|
||||
</label>
|
||||
<option value="{{extension.format}}">{{extension.format}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label for="exclude_extension">{{_('Exclude Extensions')}}</label>
|
||||
<div class="form-group">
|
||||
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
|
||||
<div class="form-group col-sm-6">
|
||||
<div><label for="exclude_extension">{{_('Exclude Extensions')}}</label></div>
|
||||
<select id="exclude_extension" class="selectpicker" name="exclude_extension" id="exclude_extension" data-live-search="true" data-style="btn-danger" multiple>
|
||||
{% for extension in extensions %}
|
||||
<label id="exclude_extension_{{extension.format}}" class="btn btn-danger extension_click">
|
||||
<input type="checkbox" autocomplete="off" name="exclude_extension" id="exclude_extension" value="{{extension.format}}">{{extension.format}}</input>
|
||||
</label>
|
||||
<option value="{{extension.format}}">{{extension.format}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -189,10 +189,13 @@
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-rating-input.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/typeahead.bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
|
||||
<script>
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
|
||||
{% if not g.user.locale == 'en' %}
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-select/defaults-' + g.user.locale + '.min.js') }}" charset="UTF-8"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block header %}
|
||||
<link href="{{ url_for('static', filename='css/libs/typeahead.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-datepicker3.min.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-select.min.css') }}" rel="stylesheet" >
|
||||
{% endblock %}
|
||||
|
@ -9,8 +9,10 @@
|
||||
{% if g.user.is_authenticated %}
|
||||
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
|
||||
<div class="btn btn-danger" id="delete_shelf" data-value="{{ shelf.id }}">{{ _('Delete this Shelf') }}</div>
|
||||
<a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf') }} </a>
|
||||
<a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf Properties') }} </a>
|
||||
{% if entries.__len__() %}
|
||||
<a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Arrange books manually') }} </a>
|
||||
<button id="toggle_order_shelf" type="button" data-alt-text="{{ _('Disable Change order') }}" class="btn btn-primary">{{ _('Enable Change order') }}</button>
|
||||
<div class="filterheader hidden-xs hidden-sm">
|
||||
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
||||
<a data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" id="old" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
||||
@ -20,8 +22,6 @@
|
||||
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='authza')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
|
||||
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" id="pub_new" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
||||
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" id="pub_old" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
||||
<a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary disabled">{{ _('Arrange books') }} </a>
|
||||
<button id="enable_order_shelf" type="button" class="btn btn-primary">{{ _('Change order') }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -31,7 +31,10 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button onclick="sendData('{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}')" class="btn btn-default" id="ChangeOrder">{{_('Change order')}}</button>
|
||||
<button onclick="sendData('{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}')" class="btn btn-default" id="ChangeOrder">{{_('Save')}}</button>
|
||||
<a href="{{ url_for('shelf.show_shelf', shelf_id=shelf.id) }}" id="shelf_back" class="btn btn-default">{{_('Back')}}</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -55,27 +55,14 @@
|
||||
</div>
|
||||
|
||||
<div class="btn-group" role="group" aria-label="Download, send to Kindle, reading">
|
||||
{% if g.user.role_download() %}
|
||||
{% if g.user.role_download() %}
|
||||
{% if entry.data|length %}
|
||||
<div class="btn-group" role="group">
|
||||
{% if entry.data|length < 2 %}
|
||||
|
||||
{% for format in entry.data %}
|
||||
<a href="{{ url_for('web.download_link', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format|lower) }}" id="btnGroupDrop1{{format.format|lower}}" class="btn btn-primary" role="button">
|
||||
<a href="{{ url_for('web.download_link', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format|lower) }}" id="btnGroupDrop{{entry.id}}{{format.format|lower}}" class="btn btn-primary" role="button">
|
||||
<span class="glyphicon glyphicon-download"></span>{{format.format}} ({{ format.uncompressed_size|filesizeformat }})
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-download"></span> {{_('Download')}}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
{% for format in entry.data %}
|
||||
<li><a href="{{ url_for('web.download_link', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format|lower) }}">{{format.format}} ({{ format.uncompressed_size|filesizeformat }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
@ -476,7 +476,7 @@ def migrate_Database(session):
|
||||
if not engine.dialect.has_table(engine.connect(), "thumbnail"):
|
||||
Thumbnail.__table__.create(bind=engine)
|
||||
if not engine.dialect.has_table(engine.connect(), "registration"):
|
||||
ReadBook.__table__.create(bind=engine)
|
||||
Registration.__table__.create(bind=engine)
|
||||
with engine.connect() as conn:
|
||||
conn.execute("insert into registration (domain, allow) values('%.%',1)")
|
||||
session.commit()
|
||||
@ -525,12 +525,16 @@ def migrate_Database(session):
|
||||
for book_shelf in session.query(BookShelf).all():
|
||||
book_shelf.date_added = datetime.datetime.now()
|
||||
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:
|
||||
session.query(exists().where(BookShelf.order)).scalar()
|
||||
except exc.OperationalError: # Database is not compatible, some columns are missing
|
||||
@ -615,7 +619,7 @@ def migrate_Database(session):
|
||||
session.commit()
|
||||
except exc.OperationalError:
|
||||
print('Settings database is not writeable. Exiting...')
|
||||
sys.exit(1)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def clean_database(session):
|
||||
|
31
cps/web.py
31
cps/web.py
@ -336,8 +336,6 @@ def get_matching_tags():
|
||||
title_input = request.args.get('book_title') or ''
|
||||
include_tag_inputs = request.args.getlist('include_tag') or ''
|
||||
exclude_tag_inputs = request.args.getlist('exclude_tag') or ''
|
||||
# include_extension_inputs = request.args.getlist('include_extension') or ''
|
||||
# exclude_extension_inputs = request.args.getlist('exclude_extension') or ''
|
||||
q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_input + "%")),
|
||||
func.lower(db.Books.title).ilike("%" + title_input + "%"))
|
||||
if len(include_tag_inputs) > 0:
|
||||
@ -637,7 +635,8 @@ def render_read_books(page, are_read, as_xml=False, order=None):
|
||||
db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
|
||||
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
|
||||
else:
|
||||
db_filter = coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED
|
||||
db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
|
||||
coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED)
|
||||
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
|
||||
db.Books,
|
||||
db_filter,
|
||||
@ -1059,6 +1058,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||
rating_low = term.get("ratinghigh")
|
||||
rating_high = term.get("ratinglow")
|
||||
description = term.get("comment")
|
||||
read_status = term.get("read_status")
|
||||
if author_name:
|
||||
author_name = author_name.strip().lower().replace(',', '|')
|
||||
if book_title:
|
||||
@ -1076,7 +1076,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||
if include_tag_inputs or exclude_tag_inputs or include_series_inputs or exclude_series_inputs 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 \
|
||||
include_extension_inputs or exclude_extension_inputs:
|
||||
include_extension_inputs or exclude_extension_inputs or read_status:
|
||||
searchterm.extend((author_name.replace('|', ','), book_title, publisher))
|
||||
if pub_start:
|
||||
try:
|
||||
@ -1094,8 +1094,12 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||
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)
|
||||
language_names = calibre_db.session.query(db.Languages).\
|
||||
filter(db.Languages.id.in_(include_languages_inputs)).all()
|
||||
if language_names:
|
||||
@ -1105,6 +1109,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||
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
|
||||
@ -1121,6 +1127,23 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||
q = q.filter(db.Books.pubdate >= pub_start)
|
||||
if pub_end:
|
||||
q = q.filter(db.Books.pubdate <= pub_end)
|
||||
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)
|
||||
if publisher:
|
||||
q = q.filter(db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + publisher + "%")))
|
||||
for tag in include_tag_inputs:
|
||||
|
Loading…
Reference in New Issue
Block a user