mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-13 19:10:30 +00:00
Save view settings
This commit is contained in:
parent
861f1b2ca3
commit
497fbdcdfc
@ -24,6 +24,13 @@ var $list = $("#list").isotope({
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#desc").click(function() {
|
$("#desc").click(function() {
|
||||||
|
$.ajax({
|
||||||
|
method:"post",
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
dataType: "json",
|
||||||
|
url: window.location.pathname + "/../../ajax/view",
|
||||||
|
data: "{\"" + page + "\": {\"dir\": \"desc\"}}",
|
||||||
|
});
|
||||||
$list.isotope({
|
$list.isotope({
|
||||||
sortBy: "name",
|
sortBy: "name",
|
||||||
sortAscending: true
|
sortAscending: true
|
||||||
@ -32,6 +39,13 @@ $("#desc").click(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#asc").click(function() {
|
$("#asc").click(function() {
|
||||||
|
$.ajax({
|
||||||
|
method:"post",
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
dataType: "json",
|
||||||
|
url: window.location.pathname + "/../../ajax/view",
|
||||||
|
data: "{\"" + page + "\": {\"dir\": \"asc\"}}",
|
||||||
|
});
|
||||||
$list.isotope({
|
$list.isotope({
|
||||||
sortBy: "name",
|
sortBy: "name",
|
||||||
sortAscending: false
|
sortAscending: false
|
||||||
|
@ -22,13 +22,13 @@ $("#sort_name").click(function() {
|
|||||||
var class_name = $("h1").attr('Class') + "_sort_name";
|
var class_name = $("h1").attr('Class') + "_sort_name";
|
||||||
var obj = {};
|
var obj = {};
|
||||||
obj[class_name] = sort;
|
obj[class_name] = sort;
|
||||||
$.ajax({
|
/*$.ajax({
|
||||||
method:"post",
|
method:"post",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
url: window.location.pathname + "/../../ajax/view",
|
url: window.location.pathname + "/../../ajax/view",
|
||||||
data: JSON.stringify({obj}),
|
data: JSON.stringify({obj}),
|
||||||
});
|
});*/
|
||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var index = 0;
|
var index = 0;
|
||||||
@ -75,6 +75,14 @@ $("#desc").click(function() {
|
|||||||
if (direction === 0) {
|
if (direction === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var page = $(this).data("id");
|
||||||
|
$.ajax({
|
||||||
|
method:"post",
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
dataType: "json",
|
||||||
|
url: window.location.pathname + "/../../ajax/view",
|
||||||
|
data: "{\"" + page + "\": {\"dir\": \"desc\"}}",
|
||||||
|
});
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var list = $("#list");
|
var list = $("#list");
|
||||||
var second = $("#second");
|
var second = $("#second");
|
||||||
@ -111,9 +119,18 @@ $("#desc").click(function() {
|
|||||||
|
|
||||||
|
|
||||||
$("#asc").click(function() {
|
$("#asc").click(function() {
|
||||||
|
|
||||||
if (direction === 1) {
|
if (direction === 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var page = $(this).data("id");
|
||||||
|
$.ajax({
|
||||||
|
method:"post",
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
dataType: "json",
|
||||||
|
url: window.location.pathname + "/../../ajax/view",
|
||||||
|
data: "{\"" + page + "\": {\"dir\": \"asc\"}}",
|
||||||
|
});
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var list = $("#list");
|
var list = $("#list");
|
||||||
var second = $("#second");
|
var second = $("#second");
|
||||||
@ -140,7 +157,6 @@ $("#asc").click(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// middle = parseInt(elementLength / 2) + (elementLength % 2);
|
// middle = parseInt(elementLength / 2) + (elementLength % 2);
|
||||||
|
|
||||||
list.append(reversed.slice(0, index));
|
list.append(reversed.slice(0, index));
|
||||||
second.append(reversed.slice(index, elementLength));
|
second.append(reversed.slice(index, elementLength));
|
||||||
} else {
|
} else {
|
||||||
|
@ -387,7 +387,7 @@ $(function() {
|
|||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
url: window.location.pathname + "/../../ajax/view",
|
url: window.location.pathname + "/../../ajax/view",
|
||||||
data: JSON.stringify({"series_view":view}),
|
data: "{\"series\": {\"series_view\": \""+ view +"\"}}",
|
||||||
success: function success() {
|
success: function success() {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
<button id="desc" data-id="series" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
||||||
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
<button id="asc" data-id="series" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
||||||
{% if charlist|length %}
|
{% if charlist|length %}
|
||||||
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
<button id="desc" data-id="{{ data }}" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
||||||
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
<button id="asc" data-id="{{ data }}" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
||||||
{% if charlist|length %}
|
{% if charlist|length %}
|
||||||
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
20
cps/ub.py
20
cps/ub.py
@ -44,6 +44,7 @@ from sqlalchemy import create_engine, exc, exists, event
|
|||||||
from sqlalchemy import Column, ForeignKey
|
from sqlalchemy import Column, ForeignKey
|
||||||
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON
|
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
from sqlalchemy.orm import backref, relationship, sessionmaker, Session
|
from sqlalchemy.orm import backref, relationship, sessionmaker, Session
|
||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
@ -192,6 +193,25 @@ 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):
|
||||||
|
if not self.view_settings.get(page):
|
||||||
|
return None
|
||||||
|
return self.view_settings[page].get(property)
|
||||||
|
|
||||||
|
def set_view_property(self, page, property, value):
|
||||||
|
if not self.view_settings.get(page):
|
||||||
|
self.view_settings[page] = dict()
|
||||||
|
self.view_settings[page][property] = value
|
||||||
|
try:
|
||||||
|
flag_modified(self, "view_settings")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
session.commit()
|
||||||
|
except (exc.OperationalError, exc.InvalidRequestError):
|
||||||
|
session.rollback()
|
||||||
|
# ToDo: Error message
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<User %r>' % self.nickname
|
return '<User %r>' % self.nickname
|
||||||
|
|
||||||
|
88
cps/web.py
88
cps/web.py
@ -467,27 +467,22 @@ def toggle_archived(book_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def update_view():
|
def update_view():
|
||||||
to_save = request.get_json()
|
to_save = request.get_json()
|
||||||
allowed_view = ['grid', 'list']
|
try:
|
||||||
if "series_view" in to_save and to_save["series_view"] in allowed_view:
|
for element in to_save:
|
||||||
|
if not current_user.view_settings.get(element):
|
||||||
|
current_user.view_settings[element]=dict()
|
||||||
|
for param in to_save[element]:
|
||||||
|
current_user.view_settings[element][param] = to_save[element][param]
|
||||||
try:
|
try:
|
||||||
#visibility = json.loads(current_user.view_settings)
|
flag_modified(current_user, "view_settings")
|
||||||
current_user.view_settings['series_view'] = to_save["series_view"]
|
except AttributeError:
|
||||||
# current_user.view_settings = json.dumps(visibility)
|
pass
|
||||||
try:
|
ub.session.commit()
|
||||||
flag_modified(current_user, "view_settings")
|
except InvalidRequestError:
|
||||||
except AttributeError:
|
log.error("Invalid request received: %r ", request, )
|
||||||
pass
|
return "Invalid request", 400
|
||||||
ub.session.commit()
|
except Exception as e:
|
||||||
except InvalidRequestError:
|
log.error("Could not save series_view_settings: %r %r", request, to_save)
|
||||||
log.error("Invalid request received: %r ", request, )
|
|
||||||
return "Invalid request", 400
|
|
||||||
except Exception:
|
|
||||||
log.error("Could not save series_view_settings: %r %r", request, to_save)
|
|
||||||
return "Invalid request", 400
|
|
||||||
elif "authorslist" in to_save:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
log.error("Invalid request received: %r %r", request, to_save)
|
|
||||||
return "Invalid request", 400
|
return "Invalid request", 400
|
||||||
return "1", 200
|
return "1", 200
|
||||||
|
|
||||||
@ -624,18 +619,9 @@ def render_title_template(*args, **kwargs):
|
|||||||
def render_books_list(data, sort, book_id, page):
|
def render_books_list(data, sort, book_id, page):
|
||||||
order = [db.Books.timestamp.desc()]
|
order = [db.Books.timestamp.desc()]
|
||||||
if sort == 'stored':
|
if sort == 'stored':
|
||||||
view = current_user.view_settings.get(data)
|
sort = current_user.get_view_property(data, 'stored')
|
||||||
sort = view
|
|
||||||
else:
|
else:
|
||||||
try:
|
current_user.set_view_property(data, 'stored', sort)
|
||||||
current_user.view_settings[data] = sort
|
|
||||||
try:
|
|
||||||
flag_modified(current_user, "view_settings")
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
ub.session.commit()
|
|
||||||
except InvalidRequestError:
|
|
||||||
log.error("Invalid request received: %r ", request, )
|
|
||||||
if sort == 'pubnew':
|
if sort == 'pubnew':
|
||||||
order = [db.Books.pubdate.desc()]
|
order = [db.Books.pubdate.desc()]
|
||||||
if sort == 'pubold':
|
if sort == 'pubold':
|
||||||
@ -1035,9 +1021,13 @@ def update_table_settings():
|
|||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def author_list():
|
def author_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_AUTHOR):
|
if current_user.check_visibility(constants.SIDEBAR_AUTHOR):
|
||||||
|
if current_user.get_view_property('author', 'dir') == 'asc':
|
||||||
|
order = db.Authors.sort.asc()
|
||||||
|
else:
|
||||||
|
order = db.Authors.sort.desc()
|
||||||
entries = calibre_db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \
|
||||||
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
|
.group_by(text('books_authors_link.author')).order_by(order).all()
|
||||||
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
|
||||||
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
|
||||||
@ -1052,10 +1042,14 @@ def author_list():
|
|||||||
@web.route("/publisher")
|
@web.route("/publisher")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def publisher_list():
|
def publisher_list():
|
||||||
|
if current_user.get_view_property('publisher', 'dir') == 'asc':
|
||||||
|
order = db.Publishers.name.asc()
|
||||||
|
else:
|
||||||
|
order = db.Publishers.name.desc()
|
||||||
if current_user.check_visibility(constants.SIDEBAR_PUBLISHER):
|
if current_user.check_visibility(constants.SIDEBAR_PUBLISHER):
|
||||||
entries = calibre_db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \
|
||||||
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.name).all()
|
.group_by(text('books_publishers_link.publisher')).order_by(order).all()
|
||||||
charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
|
||||||
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
|
||||||
@ -1069,10 +1063,14 @@ def publisher_list():
|
|||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def series_list():
|
def series_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_SERIES):
|
if current_user.check_visibility(constants.SIDEBAR_SERIES):
|
||||||
if current_user.view_settings.get('series_view') == 'list':
|
if current_user.get_view_property('series', 'dir') == 'asc':
|
||||||
|
order = db.Series.sort.asc()
|
||||||
|
else:
|
||||||
|
order = db.Series.sort.desc()
|
||||||
|
if current_user.get_view_property('series', 'series_view') == 'list':
|
||||||
entries = calibre_db.session.query(db.Series, func.count('books_series_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Series, func.count('books_series_link.book').label('count')) \
|
||||||
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
|
.group_by(text('books_series_link.series')).order_by(order).all()
|
||||||
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
||||||
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
||||||
@ -1081,7 +1079,7 @@ def series_list():
|
|||||||
else:
|
else:
|
||||||
entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count')) \
|
entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count')) \
|
||||||
.join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) \
|
.join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
|
.group_by(text('books_series_link.series')).order_by(order).all()
|
||||||
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
||||||
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
||||||
@ -1096,10 +1094,14 @@ def series_list():
|
|||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def ratings_list():
|
def ratings_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_RATING):
|
if current_user.check_visibility(constants.SIDEBAR_RATING):
|
||||||
|
if current_user.get_view_property('ratings', 'dir') == 'asc':
|
||||||
|
order = db.Ratings.rating.asc()
|
||||||
|
else:
|
||||||
|
order = db.Ratings.rating.desc()
|
||||||
entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
|
entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
|
||||||
(db.Ratings.rating / 2).label('name')) \
|
(db.Ratings.rating / 2).label('name')) \
|
||||||
.join(db.books_ratings_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_ratings_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
|
.group_by(text('books_ratings_link.rating')).order_by(order).all()
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
||||||
title=_(u"Ratings list"), page="ratingslist", data="ratings")
|
title=_(u"Ratings list"), page="ratingslist", data="ratings")
|
||||||
else:
|
else:
|
||||||
@ -1110,11 +1112,15 @@ def ratings_list():
|
|||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def formats_list():
|
def formats_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_FORMAT):
|
if current_user.check_visibility(constants.SIDEBAR_FORMAT):
|
||||||
|
if current_user.get_view_property('ratings', 'dir') == 'asc':
|
||||||
|
order = db.Data.format.asc()
|
||||||
|
else:
|
||||||
|
order = db.Data.format.desc()
|
||||||
entries = calibre_db.session.query(db.Data,
|
entries = calibre_db.session.query(db.Data,
|
||||||
func.count('data.book').label('count'),
|
func.count('data.book').label('count'),
|
||||||
db.Data.format.label('format')) \
|
db.Data.format.label('format')) \
|
||||||
.join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(db.Data.format).order_by(db.Data.format).all()
|
.group_by(db.Data.format).order_by(order).all()
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
||||||
title=_(u"File formats list"), page="formatslist", data="formats")
|
title=_(u"File formats list"), page="formatslist", data="formats")
|
||||||
else:
|
else:
|
||||||
@ -1154,8 +1160,12 @@ def language_overview():
|
|||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def category_list():
|
def category_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_CATEGORY):
|
if current_user.check_visibility(constants.SIDEBAR_CATEGORY):
|
||||||
|
if current_user.get_view_property('category', 'dir') == 'asc':
|
||||||
|
order = db.Tags.name.asc()
|
||||||
|
else:
|
||||||
|
order = db.Tags.name.desc()
|
||||||
entries = calibre_db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \
|
||||||
.join(db.books_tags_link).join(db.Books).order_by(db.Tags.name).filter(calibre_db.common_filters()) \
|
.join(db.books_tags_link).join(db.Books).order_by(order).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_tags_link.tag')).all()
|
.group_by(text('books_tags_link.tag')).all()
|
||||||
charlist = calibre_db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('char')) \
|
||||||
.join(db.books_tags_link).join(db.Books).filter(calibre_db.common_filters()) \
|
.join(db.books_tags_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
|
Loading…
Reference in New Issue
Block a user