mirror of
https://github.com/JonathanHerrewijnen/calibre-web.git
synced 2024-11-11 05:33:57 +00:00
Sorting and filtering of lists working (except file formats)
Refactored and bugfixing show_cover Refactored import of helper in web.py Fix for displaying /me (gettext) throwing error 500 Fix get search results throwing error 500 Fix routing books_list for python2.7 Fix for "me" and "settings" pages Update sidebarview and list view
This commit is contained in:
parent
bfd0e87a17
commit
406d1c76c9
@ -81,10 +81,8 @@ except cPickle.UnpicklingError as error:
|
||||
print("Can't read file cps/translations/iso639.pickle: %s" % error)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
searched_ids = {}
|
||||
|
||||
|
||||
from worker import WorkerThread
|
||||
global_WorkerThread = WorkerThread()
|
||||
|
||||
@ -110,8 +108,6 @@ def create_app():
|
||||
app.logger.setLevel(config.config_log_level)
|
||||
|
||||
app.logger.info('Starting Calibre Web...')
|
||||
# logging.getLogger("uploader").addHandler(file_handler)
|
||||
# logging.getLogger("uploader").setLevel(config.config_log_level)
|
||||
Principal(app)
|
||||
lm.init_app(app)
|
||||
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
|
||||
@ -119,7 +115,6 @@ def create_app():
|
||||
db.setup_db()
|
||||
babel.init_app(app)
|
||||
global_WorkerThread.start()
|
||||
|
||||
return app
|
||||
|
||||
@babel.localeselector
|
||||
|
@ -46,14 +46,6 @@ def title_sort(title):
|
||||
return title.strip()
|
||||
|
||||
|
||||
def lcase(s):
|
||||
return unidecode.unidecode(s.lower())
|
||||
|
||||
|
||||
def ucase(s):
|
||||
return s.upper()
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
books_authors_link = Table('books_authors_link', Base.metadata,
|
||||
|
@ -42,6 +42,7 @@ from sqlalchemy.sql.expression import true, and_, false, text, func
|
||||
from iso639 import languages as isoLanguages
|
||||
from pagination import Pagination
|
||||
from werkzeug.datastructures import Headers
|
||||
import json
|
||||
|
||||
try:
|
||||
import gdriveutils as gd
|
||||
@ -150,6 +151,7 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
|
||||
e_mail, user_name, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
|
||||
return
|
||||
|
||||
|
||||
def check_send_to_kindle(entry):
|
||||
"""
|
||||
returns all available book formats for sending to Kindle
|
||||
@ -444,24 +446,33 @@ def delete_book(book, calibrepath, book_format):
|
||||
return delete_book_file(book, calibrepath, book_format)
|
||||
|
||||
|
||||
def get_book_cover(cover_path):
|
||||
if config.config_use_google_drive:
|
||||
try:
|
||||
if not gd.is_gdrive_ready():
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||
path=gd.get_cover_via_gdrive(cover_path)
|
||||
if path:
|
||||
return redirect(path)
|
||||
def get_book_cover(book_id):
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||
if book.has_cover:
|
||||
|
||||
if config.config_use_google_drive:
|
||||
try:
|
||||
if not gd.is_gdrive_ready():
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||
path=gd.get_cover_via_gdrive(book.path)
|
||||
if path:
|
||||
return redirect(path)
|
||||
else:
|
||||
app.logger.error(book.path + '/cover.jpg not found on Google Drive')
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||
except Exception as e:
|
||||
app.logger.error("Error Message: " + e.message)
|
||||
app.logger.exception(e)
|
||||
# traceback.print_exc()
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
||||
else:
|
||||
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
|
||||
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
|
||||
return send_from_directory(cover_file_path, "cover.jpg")
|
||||
else:
|
||||
app.logger.error(cover_path + '/cover.jpg not found on Google Drive')
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||
except Exception as e:
|
||||
app.logger.error("Error Message: " + e.message)
|
||||
app.logger.exception(e)
|
||||
# traceback.print_exc()
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
||||
else:
|
||||
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
|
||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
||||
|
||||
|
||||
# saves book cover from url
|
||||
@ -698,24 +709,29 @@ def fill_indexpage(page, database, db_filter, order, *join):
|
||||
return entries, randm, pagination
|
||||
|
||||
|
||||
def get_typeahead(database, query, replace=('','')):
|
||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
entries = db.session.query(database).filter(db.func.lower(database.name).ilike("%" + query + "%")).all()
|
||||
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
||||
return json_dumps
|
||||
|
||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||
def get_search_results(term):
|
||||
def get_search_results(term):
|
||||
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||
q = list()
|
||||
authorterms = re.split("[, ]+", term)
|
||||
for authorterm in authorterms:
|
||||
q.append(db.Books.authors.any(db.func.lower(db.Authors.name).ilike("%" + authorterm + "%")))
|
||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
q = list()
|
||||
authorterms = re.split("[, ]+", term)
|
||||
for authorterm in authorterms:
|
||||
q.append(db.Books.authors.any(db.func.lower(db.Authors.name).ilike("%" + authorterm + "%")))
|
||||
|
||||
db.Books.authors.any(db.func.lower(db.Authors.name).ilike("%" + term + "%"))
|
||||
db.Books.authors.any(db.func.lower(db.Authors.name).ilike("%" + term + "%"))
|
||||
|
||||
return db.session.query(db.Books).filter(common_filters()).filter(
|
||||
db.or_(db.Books.tags.any(db.func.lower(db.Tags.name).ilike("%" + term + "%")),
|
||||
db.Books.series.any(db.func.lower(db.Series.name).ilike("%" + term + "%")),
|
||||
db.Books.authors.any(and_(*q)),
|
||||
db.Books.publishers.any(db.func.lower(db.Publishers.name).ilike("%" + term + "%")),
|
||||
db.func.lower(db.Books.title).ilike("%" + term + "%")
|
||||
)).all()
|
||||
return db.session.query(db.Books).filter(common_filters()).filter(
|
||||
db.or_(db.Books.tags.any(db.func.lower(db.Tags.name).ilike("%" + term + "%")),
|
||||
db.Books.series.any(db.func.lower(db.Series.name).ilike("%" + term + "%")),
|
||||
db.Books.authors.any(and_(*q)),
|
||||
db.Books.publishers.any(db.func.lower(db.Publishers.name).ilike("%" + term + "%")),
|
||||
db.func.lower(db.Books.title).ilike("%" + term + "%")
|
||||
)).all()
|
||||
|
||||
def get_unique_other_books(library_books, author_books):
|
||||
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
|
||||
@ -774,3 +790,10 @@ def get_download_link(book_id, book_format):
|
||||
return do_download_file(book, book_format, data, headers)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
|
||||
############### Database Helper functions
|
||||
|
||||
def lcase(s):
|
||||
return unidecode.unidecode(s.lower())
|
||||
|
@ -308,8 +308,7 @@ def render_xml_template(*args, **kwargs):
|
||||
@opds.route("/opds/cover/<book_id>")
|
||||
@requires_basic_auth_if_no_ano
|
||||
def feed_get_cover(book_id):
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||
return helper.get_book_cover(book.path)
|
||||
return helper.get_book_cover(book_id)
|
||||
|
||||
@opds.route("/opds/readbooks/")
|
||||
@requires_basic_auth_if_no_ano
|
||||
|
@ -16,23 +16,88 @@
|
||||
*/
|
||||
|
||||
var direction = 0; // Descending order
|
||||
var sort = 0; // Show sorted entries
|
||||
|
||||
$("#sort_name").click(function() {
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
var store;
|
||||
// Append 2nd half of list to first half for easier processing
|
||||
var cnt = $("#second").contents();
|
||||
$("#list").append(cnt);
|
||||
// Count no of elements
|
||||
var listItems = $('#list').children(".row");
|
||||
var listlength = listItems.length;
|
||||
// check for each element if its Starting character matches
|
||||
$(".row").each(function() {
|
||||
if ( sort === 1) {
|
||||
store = this.attributes["data-name"];
|
||||
} else {
|
||||
store = this.attributes["data-id"];
|
||||
}
|
||||
$(this).find('a').html(store.value);
|
||||
if($(this).css("display") != "none") {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
/*listItems.sort(function(a,b){
|
||||
return $(a).children()[1].innerText.localeCompare($(b).children()[1].innerText)
|
||||
});*/
|
||||
// Find count of middle element
|
||||
if (count > 20) {
|
||||
var middle = parseInt(count / 2) + (count % 2);
|
||||
// search for the middle of all visibe elements
|
||||
$(".row").each(function() {
|
||||
index++;
|
||||
if($(this).css("display") != "none") {
|
||||
middle--;
|
||||
if (middle <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Move second half of visible elements
|
||||
$("#second").append(listItems.slice(index, listlength));
|
||||
}
|
||||
sort = (sort + 1) % 2;
|
||||
});
|
||||
|
||||
$("#desc").click(function() {
|
||||
if (direction === 0) {
|
||||
return;
|
||||
}
|
||||
var index = 0;
|
||||
var list = $('#list');
|
||||
var second = $('#second');
|
||||
// var cnt = ;
|
||||
list.append(second.contents());
|
||||
var listItems = list.children(".row");
|
||||
var reversed, elementLength, middle;
|
||||
Array.prototype.push.apply(listItems,second.children(".row"))
|
||||
reversed = listItems.get().reverse();
|
||||
elementLength = reversed.length;
|
||||
// Find count of middle element
|
||||
var count = $(".row:visible").length;
|
||||
if (count > 20) {
|
||||
var middle = parseInt(count / 2) + (count % 2);
|
||||
|
||||
middle = parseInt(elementLength / 2) + (elementLength % 2);
|
||||
//var middle = parseInt(count / 2) + (count % 2);
|
||||
// search for the middle of all visible elements
|
||||
$(reversed).each(function() {
|
||||
index++;
|
||||
if($(this).css("display") != "none") {
|
||||
middle--;
|
||||
if (middle <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
list.append(reversed.slice(0, middle));
|
||||
second.append(reversed.slice(middle,elementLength));
|
||||
list.append(reversed.slice(0, index));
|
||||
second.append(reversed.slice(index,elementLength));
|
||||
}
|
||||
else {
|
||||
list.append(reversed.slice(0, elementLength));
|
||||
}
|
||||
direction = 0;
|
||||
});
|
||||
|
||||
@ -41,34 +106,91 @@ $("#asc").click(function() {
|
||||
if (direction === 1) {
|
||||
return;
|
||||
}
|
||||
var index = 0;
|
||||
var list = $("#list");
|
||||
var second = $('#second');
|
||||
list.append(second.contents());
|
||||
var listItems = list.children(".row");
|
||||
Array.prototype.push.apply(listItems,second.children(".row"));
|
||||
reversed = listItems.get().reverse();
|
||||
elementLength = reversed.length;
|
||||
middle = parseInt(elementLength / 2) + (elementLength % 2);
|
||||
|
||||
list.append(reversed.slice(0, middle));
|
||||
second.append(reversed.slice(middle,elementLength));
|
||||
// Find count of middle element
|
||||
var count = $(".row:visible").length;
|
||||
if (count > 20) {
|
||||
var middle = parseInt(count / 2) + (count % 2);
|
||||
|
||||
//var middle = parseInt(count / 2) + (count % 2);
|
||||
// search for the middle of all visible elements
|
||||
$(reversed).each(function() {
|
||||
index++;
|
||||
if($(this).css("display") != "none") {
|
||||
middle--;
|
||||
if (middle <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// middle = parseInt(elementLength / 2) + (elementLength % 2);
|
||||
|
||||
list.append(reversed.slice(0, index));
|
||||
second.append(reversed.slice(index,elementLength));
|
||||
} else {
|
||||
list.append(reversed.slice(0, elementLength));
|
||||
}
|
||||
direction = 1;
|
||||
});
|
||||
|
||||
$("#all").click(function() {
|
||||
$(".row").each(function() {
|
||||
var cnt = $("#second").contents();
|
||||
$("#list").append(cnt);
|
||||
// Find count of middle element
|
||||
var listItems = $('#list').children(".row");
|
||||
var listlength = listItems.length;
|
||||
var middle = parseInt(listlength / 2) + (listlength % 2);
|
||||
// go through all elements and make them visible
|
||||
listItems.each(function() {
|
||||
$(this).show();
|
||||
});
|
||||
// Move second half of all elements
|
||||
if (listlength > 20) {
|
||||
$("#second").append(listItems.slice(middle,listlength));
|
||||
}
|
||||
});
|
||||
|
||||
$(".char").click(function() {
|
||||
var character = this.innerText;
|
||||
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
// Append 2nd half of list to first half for easier processing
|
||||
var cnt = $("#second").contents();
|
||||
$("#list").append(cnt);
|
||||
// Count no of elements
|
||||
var listItems = $('#list').children(".row");
|
||||
var listlength = listItems.length;
|
||||
// check for each element if its Starting character matches
|
||||
$(".row").each(function() {
|
||||
if (this.attributes["data-id"].value.charAt(0).toUpperCase() !== character) {
|
||||
$(this).hide();
|
||||
} else {
|
||||
$(this).show();
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
if (count > 20) {
|
||||
// Find count of middle element
|
||||
var middle = parseInt(count / 2) + (count % 2);
|
||||
// search for the middle of all visibe elements
|
||||
$(".row").each(function() {
|
||||
index++;
|
||||
if($(this).css("display") != "none") {
|
||||
middle--;
|
||||
if (middle <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Move second half of visible elements
|
||||
$("#second").append(listItems.slice(index,listlength));
|
||||
}
|
||||
});
|
||||
|
@ -40,11 +40,7 @@
|
||||
<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) }}">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" />
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -5,11 +5,7 @@
|
||||
|
||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||
<div class="cover">
|
||||
{% if book.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', book_id=book.id) }}" alt="{{ book.title }}"/>
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" alt="{{ book.title }}"/>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if g.user.role_delete_books() %}
|
||||
<div class="text-center">
|
||||
|
@ -4,11 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||
<div class="cover">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" alt="{{ entry.title }}" />
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-lg-9 book-meta">
|
||||
@ -150,7 +146,7 @@
|
||||
<span class="glyphicon glyphicon-tags"></span>
|
||||
|
||||
{% for tag in entry.tags %}
|
||||
<a href="{{ url_for('web.category', book_id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
|
||||
<a href="{{ url_for('web.books_list', data='category', sort='new', book_id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
|
||||
{%endfor%}
|
||||
</p>
|
||||
|
||||
|
@ -8,11 +8,7 @@ web. {% for entry in random %}
|
||||
<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">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" alt="{{ entry.title }}" />
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
@ -75,11 +71,7 @@ web. {% for entry in random %}
|
||||
<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">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" alt="{{ entry.title }}" />
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -3,6 +3,11 @@
|
||||
<h1 class="{{page}}">{{_(title)}}</h1>
|
||||
|
||||
<div class="filterheader hidden-xs hidden-sm">
|
||||
{% if entries.__len__ %}
|
||||
{% if not entries[0].sort %}
|
||||
<button id="sort_name" class="btn btn-success"><b>B,A <-> A B</b></button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<button id="desc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
||||
<button id="asc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
||||
{% if charlist|length %}
|
||||
@ -21,9 +26,9 @@
|
||||
</div>
|
||||
<div id="second" class="col-xs-12 col-sm-6">
|
||||
{% endif %}
|
||||
<div class="row" data-id="{% if entry[0].sort %}{{entry[0].sort}}{% else %}{% if entry.name %}{{entry.name}}{% else %}{{entry[0].name}}{% endif %}{% endif %}">
|
||||
<div class="row" {% if entry[0].sort %}data-name="{{entry[0].name}}"{% endif %} data-id="{% if entry[0].sort %}{{entry[0].sort}}{% else %}{% if entry.name %}{{entry.name}}{% else %}{{entry[0].name}}{% endif %}{% endif %}">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{entry.count}}</span></div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{% if entry.format %}{{url_for(folder, data=data, sort='new', book_id=entry.format )}}{% else %}{{url_for(folder, data=data, sort='new', book_id=entry[0].id )}}{% endif %}">
|
||||
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{% if entry.format %}{{url_for('web.books_list', data=data, sort='new', book_id=entry.format )}}{% else %}{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].id )}}{% endif %}">
|
||||
{% if entry.name %}
|
||||
<div class="rating">
|
||||
{% for number in range(entry.name) %}
|
||||
|
@ -35,9 +35,7 @@
|
||||
<h2>{{ entry.title }}</h2>
|
||||
|
||||
<div class="cover">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', cover_path=entry.path.replace('\\','/')) }}" alt="{{ entry.title }}" /> {% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" alt="{{ entry.title }}" /> {% endif %}
|
||||
<img src="{{ url_for('web.get_cover', cover_path=entry.path.replace('\\','/')) }}" alt="{{ entry.title }}" />
|
||||
</div>
|
||||
|
||||
{% if entry.ratings.__len__() > 0 %}
|
||||
|
@ -18,11 +18,7 @@
|
||||
<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">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" alt="{{ entry.title }}" />
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -100,7 +100,10 @@ Base = declarative_base()
|
||||
def get_sidebar_config(kwargs=None):
|
||||
kwargs = kwargs or []
|
||||
if 'content' in kwargs:
|
||||
content = not kwargs['content'].role_anonymous()
|
||||
if not isinstance(kwargs['content'], Settings):
|
||||
content = not kwargs['content'].role_anonymous()
|
||||
else:
|
||||
content = False
|
||||
else:
|
||||
content = False
|
||||
sidebar = list()
|
||||
|
@ -67,6 +67,8 @@ try:
|
||||
from PIL import __version__ as PILversion
|
||||
use_PIL = True
|
||||
except ImportError:
|
||||
app.logger.warning('cannot import Pillow, using png and webp images as cover will not work: %s', e)
|
||||
use_generic_pdf_cover = True
|
||||
use_PIL = False
|
||||
|
||||
|
||||
|
281
cps/web.py
281
cps/web.py
@ -21,31 +21,30 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from cps import mimetypes, global_WorkerThread, searched_ids
|
||||
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
|
||||
from werkzeug.exceptions import default_exceptions
|
||||
import helper
|
||||
from cps import mimetypes, global_WorkerThread, searched_ids, lm, babel, ub, config, get_locale, language_table, app, db
|
||||
from helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
|
||||
order_authors
|
||||
import os
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
order_authors, get_typeahead, render_task_status, json_serial, get_unique_other_books, get_cc_columns, \
|
||||
get_book_cover, get_download_link, send_mail, generate_random_password, send_registration_mail, \
|
||||
check_send_to_kindle, check_read_formats, lcase
|
||||
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from flask_babel import gettext as _
|
||||
from werkzeug.exceptions import default_exceptions
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from werkzeug.datastructures import Headers
|
||||
from redirect import redirect_back
|
||||
from pagination import Pagination
|
||||
from babel import Locale as LC
|
||||
from babel.dates import format_date
|
||||
from babel.core import UnknownLocaleError
|
||||
import base64
|
||||
from flask_babel import gettext as _
|
||||
from sqlalchemy.sql.expression import text, func, true, false, not_
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
import base64
|
||||
import os.path
|
||||
import json
|
||||
import datetime
|
||||
import isoLanguages
|
||||
import os.path
|
||||
import gdriveutils
|
||||
from redirect import redirect_back
|
||||
from cps import lm, babel, ub, config, get_locale, language_table, app, db
|
||||
from pagination import Pagination
|
||||
|
||||
|
||||
feature_support = dict()
|
||||
@ -252,8 +251,8 @@ def before_request():
|
||||
@login_required
|
||||
def get_email_status_json():
|
||||
tasks = global_WorkerThread.get_taskstatus()
|
||||
answer = helper.render_task_status(tasks)
|
||||
js = json.dumps(answer, default=helper.json_serial)
|
||||
answer = render_task_status(tasks)
|
||||
js = json.dumps(answer, default=json_serial)
|
||||
response = make_response(js)
|
||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
@ -366,12 +365,6 @@ def get_comic_book(book_id, book_format, page):
|
||||
|
||||
# ################################### Typeahead ##################################################################
|
||||
|
||||
def get_typeahead(database, query, replace=('','')):
|
||||
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||
entries = db.session.query(database).filter(db.func.lower(database.name).ilike("%" + query + "%")).all()
|
||||
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
||||
return json_dumps
|
||||
|
||||
|
||||
@web.route("/get_authors_json")
|
||||
@login_required_if_no_ano
|
||||
@ -422,7 +415,7 @@ def get_matching_tags():
|
||||
tag_dict = {'tags': []}
|
||||
if request.method == "GET":
|
||||
q = db.session.query(db.Books)
|
||||
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
author_input = request.args.get('author_name')
|
||||
title_input = request.args.get('book_title')
|
||||
include_tag_inputs = request.args.getlist('include_tag')
|
||||
@ -497,6 +490,16 @@ def books_list(data, sort, book_id, page):
|
||||
return render_hot_books(page)
|
||||
elif data == "author":
|
||||
return render_author_books(page, book_id, order)
|
||||
elif data == "publisher":
|
||||
return render_publisher_books(page, book_id, order)
|
||||
elif data == "series":
|
||||
return render_series_books(page, book_id, order)
|
||||
elif data == "ratings":
|
||||
return render_ratings_books(page, book_id, order)
|
||||
elif data == "formats":
|
||||
return render_formats_books(page, book_id, order)
|
||||
elif data == "category":
|
||||
return render_category_books(page, book_id, order)
|
||||
else:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, True, order)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
@ -532,6 +535,88 @@ def render_hot_books(page):
|
||||
abort(404)
|
||||
|
||||
|
||||
# ToDo wrong order function
|
||||
def render_author_books(page, book_id, order):
|
||||
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
|
||||
[db.Series.name, db.Books.series_index, order[0]], db.books_series_link, db.Series)
|
||||
if entries is None:
|
||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||
return redirect(url_for("web.index"))
|
||||
|
||||
name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name.replace('|', ',')
|
||||
|
||||
author_info = None
|
||||
other_books = []
|
||||
if feature_support['goodreads'] and config.config_use_goodreads:
|
||||
try:
|
||||
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
||||
author_info = gc.find_author(author_name=name)
|
||||
other_books = get_unique_other_books(entries.all(), author_info.books)
|
||||
except Exception:
|
||||
# Skip goodreads, if site is down/inaccessible
|
||||
app.logger.error('Goodreads website is down/inaccessible')
|
||||
|
||||
return render_title_template('author.html', entries=entries, pagination=pagination,
|
||||
title=name, author=author_info, other_books=other_books, page="author")
|
||||
|
||||
|
||||
def render_publisher_books(page, book_id, order):
|
||||
publisher = db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first()
|
||||
if publisher:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books,
|
||||
db.Books.publishers.any(db.Publishers.id == book_id),
|
||||
[db.Books.series_index, order[0]])
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
def render_series_books(page, book_id, order):
|
||||
name = db.session.query(db.Series).filter(db.Series.id == book_id).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
|
||||
[db.Books.series_index, order[0]])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
|
||||
title=_(u"Series: %(serie)s", serie=name.name), page="series")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
def render_ratings_books(page, book_id, order):
|
||||
if book_id <=5:
|
||||
name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id),
|
||||
[db.Books.timestamp.desc(), order[0]])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
|
||||
title=_(u"Rating: %(rating)s stars", rating=int(name.rating/2)), page="ratings")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
def render_formats_books(page, book_id, order):
|
||||
name = db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.data.any(db.Data.format == book_id.upper()),
|
||||
[db.Books.timestamp.desc(), order[0]])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
|
||||
title=_(u"File format: %(format)s", format=name.format), page="formats")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
def render_category_books(page, book_id, order):
|
||||
name = db.session.query(db.Tags).filter(db.Tags.id == book_id).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
|
||||
[db.Series.name, db.Books.series_index, order[0]],
|
||||
db.books_series_link, db.Series)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Category: %(name)s", name=name.name), page="category")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@web.route("/author")
|
||||
@login_required_if_no_ano
|
||||
def author_list():
|
||||
@ -550,31 +635,6 @@ def author_list():
|
||||
abort(404)
|
||||
|
||||
|
||||
# ToDo wrong order function
|
||||
def render_author_books(page, book_id, order):
|
||||
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
|
||||
[db.Series.name, db.Books.series_index, order[0]], db.books_series_link, db.Series)
|
||||
if entries is None:
|
||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||
return redirect(url_for("web.index"))
|
||||
|
||||
name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name.replace('|', ',')
|
||||
|
||||
author_info = None
|
||||
other_books = []
|
||||
if feature_support['goodreads'] and config.config_use_goodreads:
|
||||
try:
|
||||
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
||||
author_info = gc.find_author(author_name=name)
|
||||
other_books = helper.get_unique_other_books(entries.all(), author_info.books)
|
||||
except Exception:
|
||||
# Skip goodreads, if site is down/inaccessible
|
||||
app.logger.error('Goodreads website is down/inaccessible')
|
||||
|
||||
return render_title_template('author.html', entries=entries, pagination=pagination,
|
||||
title=name, author=author_info, other_books=other_books, page="author")
|
||||
|
||||
|
||||
@web.route("/publisher")
|
||||
@login_required_if_no_ano
|
||||
def publisher_list():
|
||||
@ -585,23 +645,8 @@ def publisher_list():
|
||||
charlist = db.session.query(func.upper(func.substr(db.Publishers.name,1,1)).label('char')) \
|
||||
.join(db.books_publishers_link).join(db.Books).filter(common_filters()) \
|
||||
.group_by(func.upper(func.substr(db.Publishers.name,1,1))).all()
|
||||
return render_title_template('list.html', entries=entries, folder='web.publisher', charlist=charlist,
|
||||
title=_(u"Publisher list"), page="publisherlist")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@web.route("/publisher/<int:book_id>", defaults={'page': 1})
|
||||
@web.route('/publisher/<int:book_id>/<int:page>')
|
||||
@login_required_if_no_ano
|
||||
def publisher(book_id, page):
|
||||
publisher = db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first()
|
||||
if publisher:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books,
|
||||
db.Books.publishers.any(db.Publishers.id == book_id),
|
||||
[db.Books.series_index])
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher")
|
||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||
title=_(u"Publisher list"), page="publisherlist", data="publisher")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -616,22 +661,8 @@ def series_list():
|
||||
charlist = db.session.query(func.upper(func.substr(db.Series.sort,1,1)).label('char')) \
|
||||
.join(db.books_series_link).join(db.Books).filter(common_filters()) \
|
||||
.group_by(func.upper(func.substr(db.Series.sort,1,1))).all()
|
||||
return render_title_template('list.html', entries=entries, folder='web.series', charlist=charlist,
|
||||
title=_(u"Series list"), page="serieslist")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@web.route("/series/<int:book_id>/", defaults={'page': 1})
|
||||
@web.route("/series/<int:book_id>/<int:page>")
|
||||
@login_required_if_no_ano
|
||||
def series(book_id, page):
|
||||
name = db.session.query(db.Series).filter(db.Series.id == book_id).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
|
||||
[db.Books.series_index])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
|
||||
title=_(u"Series: %(serie)s", serie=name.name), page="series")
|
||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||
title=_(u"Series list"), page="serieslist", data="series")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -644,22 +675,8 @@ def ratings_list():
|
||||
(db.Ratings.rating/2).label('name'))\
|
||||
.join(db.books_ratings_link).join(db.Books).filter(common_filters())\
|
||||
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
|
||||
return render_title_template('list.html', entries=entries, folder='web.ratings', charlist=list(),
|
||||
title=_(u"Ratings list"), page="ratingslist")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@web.route("/ratings/<int:book_id>/", defaults={'page': 1})
|
||||
@web.route("/ratings/<int:book_id>/<int:page>")
|
||||
@login_required_if_no_ano
|
||||
def ratings(book_id, page):
|
||||
if book_id <=5:
|
||||
name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id),
|
||||
[db.Books.timestamp.desc()])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
|
||||
title=_(u"Rating: %(rating)s stars", rating=int(name.rating/2)), page="ratings")
|
||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
||||
title=_(u"Ratings list"), page="ratingslist", data="ratings")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -671,22 +688,8 @@ def formats_list():
|
||||
entries = db.session.query(db.Data, func.count('data.book').label('count'),db.Data.format.label('format'))\
|
||||
.join(db.Books).filter(common_filters())\
|
||||
.group_by(db.Data.format).order_by(db.Data.format).all()
|
||||
return render_title_template('list.html', entries=entries, folder='web.formats', charlist=list(),
|
||||
title=_(u"File formats list"), page="formatslist")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@web.route("/formats/<book_id>/", defaults={'page': 1})
|
||||
@web.route("/formats/<book_id>/<int:page>")
|
||||
@login_required_if_no_ano
|
||||
def formats(book_id, page):
|
||||
name = db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.data.any(db.Data.format == book_id.upper()),
|
||||
[db.Books.timestamp.desc()])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
|
||||
title=_(u"File format: %(format)s", format=name.format), page="formats")
|
||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
||||
title=_(u"File formats list"), page="formatslist", data="formats")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -714,7 +717,7 @@ def language_overview():
|
||||
func.count('books_languages_link.book').label('bookcount')).group_by(
|
||||
text('books_languages_link.lang_code')).all()
|
||||
return render_title_template('languages.html', languages=languages, lang_counter=lang_counter,
|
||||
charlist=charlist, title=_(u"Available languages"), page="langlist")
|
||||
charlist=charlist, title=_(u"Available languages"), page="langlist", data="language")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -747,33 +750,20 @@ def category_list():
|
||||
charlist = db.session.query(func.upper(func.substr(db.Tags.name,1,1)).label('char')) \
|
||||
.join(db.books_tags_link).join(db.Books).filter(common_filters()) \
|
||||
.group_by(func.upper(func.substr(db.Tags.name,1,1))).all()
|
||||
return render_title_template('list.html', entries=entries, folder='web.category', charlist=charlist,
|
||||
title=_(u"Category list"), page="catlist")
|
||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||
title=_(u"Category list"), page="catlist", data="category")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@web.route("/category/<int:book_id>", defaults={'page': 1})
|
||||
@web.route('/category/<int:book_id>/<int:page>')
|
||||
@login_required_if_no_ano
|
||||
def category(book_id, page):
|
||||
name = db.session.query(db.Tags).filter(db.Tags.id == book_id).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
|
||||
(db.Series.name, db.Books.series_index),db.books_series_link,
|
||||
db.Series)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Category: %(name)s", name=name.name), page="category")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
# ################################### Task functions ################################################################
|
||||
|
||||
@web.route("/tasks")
|
||||
@login_required
|
||||
def get_tasks_status():
|
||||
# if current user admin, show all email, otherwise only own emails
|
||||
tasks = global_WorkerThread.get_taskstatus()
|
||||
answer = helper.render_task_status(tasks)
|
||||
answer = render_task_status(tasks)
|
||||
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
|
||||
|
||||
|
||||
@ -798,8 +788,8 @@ def search():
|
||||
@login_required_if_no_ano
|
||||
def advanced_search():
|
||||
# Build custom columns names
|
||||
cc = helper.get_cc_columns()
|
||||
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||
cc = get_cc_columns()
|
||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
q = db.session.query(db.Books)
|
||||
|
||||
include_tag_inputs = request.args.getlist('include_tag')
|
||||
@ -978,8 +968,7 @@ def render_read_books(page, are_read, as_xml=False, order=None):
|
||||
@web.route("/cover/<int:book_id>")
|
||||
@login_required_if_no_ano
|
||||
def get_cover(book_id):
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||
return helper.get_book_cover(book.path)
|
||||
return get_book_cover(book_id)
|
||||
|
||||
|
||||
@web.route("/show/<book_id>/<book_format>")
|
||||
@ -1007,7 +996,7 @@ def serve_book(book_id, book_format):
|
||||
@login_required_if_no_ano
|
||||
@download_required
|
||||
def download_link(book_id, book_format, anyname):
|
||||
return helper.get_download_link(book_id, book_format)
|
||||
return get_download_link(book_id, book_format)
|
||||
|
||||
|
||||
@web.route('/send/<int:book_id>/<book_format>/<int:convert>')
|
||||
@ -1018,7 +1007,7 @@ def send_to_kindle(book_id, book_format, convert):
|
||||
if settings.get("mail_server", "mail.example.com") == "mail.example.com":
|
||||
flash(_(u"Please configure the SMTP mail settings first..."), category="error")
|
||||
elif current_user.kindle_mail:
|
||||
result = helper.send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir,
|
||||
result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir,
|
||||
current_user.nickname)
|
||||
if result is None:
|
||||
flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail),
|
||||
@ -1055,7 +1044,7 @@ def register():
|
||||
if check_valid_domain(to_save["email"]):
|
||||
content.nickname = to_save["nickname"]
|
||||
content.email = to_save["email"]
|
||||
password = helper.generate_random_password()
|
||||
password = generate_random_password()
|
||||
content.password = generate_password_hash(password)
|
||||
content.role = config.config_default_role
|
||||
content.sidebar_view = config.config_default_show
|
||||
@ -1065,7 +1054,7 @@ def register():
|
||||
ub.session.commit()
|
||||
if feature_support['oauth']:
|
||||
register_user_with_oauth(content)
|
||||
helper.send_registration_mail(to_save["email"], to_save["nickname"], password)
|
||||
send_registration_mail(to_save["email"], to_save["nickname"], password)
|
||||
except Exception:
|
||||
ub.session.rollback()
|
||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||
@ -1120,8 +1109,6 @@ def login():
|
||||
app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
|
||||
flash(_(u"Wrong Username or Password"), category="error")
|
||||
|
||||
# next_url = request.args.get('next')
|
||||
# if next_url is None or not is_safe_url(next_url):
|
||||
next_url = url_for('web.index')
|
||||
|
||||
return render_title_template('login.html', title=_(u"login"), next_url=next_url, config=config, page="login")
|
||||
@ -1223,7 +1210,7 @@ def token_verified():
|
||||
@web.route("/me", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def profile():
|
||||
# content = ub.session.query(ub.User).filter(ub.User.id == int(current_user.id)).first()
|
||||
global _
|
||||
downloads = list()
|
||||
languages = speaking_language()
|
||||
translations = babel.list_translations() + [LC('en')]
|
||||
@ -1282,9 +1269,9 @@ def profile():
|
||||
registered_oauth=oauth_check, oauth_status=oauth_status)
|
||||
flash(_(u"Profile updated"), category="success")
|
||||
return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages,
|
||||
content=current_user, downloads=downloads, title=_(u"%(name)s's profile",
|
||||
name=current_user.nickname), page="me", registered_oauth=oauth_check,
|
||||
oauth_status=oauth_status)
|
||||
content=current_user, downloads=downloads, title= _(u"%(name)s's profile",
|
||||
name=current_user.nickname),
|
||||
page="me", registered_oauth=oauth_check, oauth_status=oauth_status)
|
||||
|
||||
|
||||
# ###################################Show single book ##################################################################
|
||||
@ -1344,7 +1331,7 @@ def show_book(book_id):
|
||||
except UnknownLocaleError:
|
||||
entries.languages[index].language_name = _(
|
||||
isoLanguages.get(part3=entries.languages[index].lang_code).name)
|
||||
cc = helper.get_cc_columns()
|
||||
cc = get_cc_columns()
|
||||
book_in_shelfs = []
|
||||
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
||||
for entry in shelfs:
|
||||
@ -1371,8 +1358,8 @@ def show_book(book_id):
|
||||
|
||||
entries = order_authors(entries)
|
||||
|
||||
kindle_list = helper.check_send_to_kindle(entries)
|
||||
reader_list = helper.check_read_formats(entries)
|
||||
kindle_list = check_send_to_kindle(entries)
|
||||
reader_list = check_read_formats(entries)
|
||||
|
||||
audioentries = []
|
||||
for media_format in entries.data:
|
||||
|
Loading…
Reference in New Issue
Block a user