Improved sorting for rated,random, hot books, read/unread book
This commit is contained in:
parent
a66873a9e2
commit
9c1b3f136f
|
@ -25,8 +25,8 @@ import os
|
||||||
from flask import Blueprint, flash, redirect, url_for
|
from flask import Blueprint, flash, redirect, url_for
|
||||||
from flask import abort, request, make_response
|
from flask import abort, request, make_response
|
||||||
from flask_login import login_required, current_user, logout_user
|
from flask_login import login_required, current_user, logout_user
|
||||||
from web import admin_required, render_title_template, before_request, speaking_language, unconfigured, \
|
from web import admin_required, render_title_template, before_request, unconfigured, \
|
||||||
login_required_if_no_ano, check_valid_domain
|
login_required_if_no_ano
|
||||||
from cps import db, ub, Server, get_locale, config, app, updater_thread, babel
|
from cps import db, ub, Server, get_locale, config, app, updater_thread, babel
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
@ -37,6 +37,7 @@ from babel import Locale as LC
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
|
from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
|
||||||
import helper
|
import helper
|
||||||
|
from helper import speaking_language, check_valid_domain
|
||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
try:
|
try:
|
||||||
from imp import reload
|
from imp import reload
|
||||||
|
|
|
@ -31,8 +31,9 @@ import json
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import helper
|
import helper
|
||||||
|
from helper import order_authors, common_filters
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from web import login_required_if_no_ano, common_filters, order_authors, render_title_template, edit_required, \
|
from web import login_required_if_no_ano, render_title_template, edit_required, \
|
||||||
upload_required, login_required, EXTENSIONS_UPLOAD
|
upload_required, login_required, EXTENSIONS_UPLOAD
|
||||||
import gdriveutils
|
import gdriveutils
|
||||||
from shutil import move, copyfile
|
from shutil import move, copyfile
|
||||||
|
|
112
cps/helper.py
112
cps/helper.py
|
@ -32,9 +32,15 @@ from flask import send_from_directory, make_response, redirect, abort
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
|
from babel.core import UnknownLocaleError
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from babel import Locale as LC
|
||||||
import shutil
|
import shutil
|
||||||
import requests
|
import requests
|
||||||
|
from sqlalchemy.sql.expression import true, and_, false, text, func
|
||||||
|
from iso639 import languages as isoLanguages
|
||||||
|
from pagination import Pagination
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import gdriveutils as gd
|
import gdriveutils as gd
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -558,3 +564,109 @@ def render_task_status(tasklist):
|
||||||
renderedtasklist.append(task)
|
renderedtasklist.append(task)
|
||||||
|
|
||||||
return renderedtasklist
|
return renderedtasklist
|
||||||
|
|
||||||
|
|
||||||
|
# Language and content filters for displaying in the UI
|
||||||
|
def common_filters():
|
||||||
|
if current_user.filter_language() != "all":
|
||||||
|
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||||
|
else:
|
||||||
|
lang_filter = true()
|
||||||
|
content_rating_filter = false() if current_user.mature_content else \
|
||||||
|
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
|
||||||
|
return and_(lang_filter, ~content_rating_filter)
|
||||||
|
|
||||||
|
|
||||||
|
# Creates for all stored languages a translated speaking name in the array for the UI
|
||||||
|
def speaking_language(languages=None):
|
||||||
|
if not languages:
|
||||||
|
languages = db.session.query(db.Languages).all()
|
||||||
|
for lang in languages:
|
||||||
|
try:
|
||||||
|
cur_l = LC.parse(lang.lang_code)
|
||||||
|
lang.name = cur_l.get_language_name(get_locale())
|
||||||
|
except UnknownLocaleError:
|
||||||
|
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
|
||||||
|
return languages
|
||||||
|
|
||||||
|
# checks if domain is in database (including wildcards)
|
||||||
|
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
|
||||||
|
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
|
||||||
|
def check_valid_domain(domain_text):
|
||||||
|
domain_text = domain_text.split('@', 1)[-1].lower()
|
||||||
|
sql = "SELECT * FROM registration WHERE :domain LIKE domain;"
|
||||||
|
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
|
||||||
|
return len(result)
|
||||||
|
|
||||||
|
|
||||||
|
# Orders all Authors in the list according to authors sort
|
||||||
|
def order_authors(entry):
|
||||||
|
sort_authors = entry.author_sort.split('&')
|
||||||
|
authors_ordered = list()
|
||||||
|
error = False
|
||||||
|
for auth in sort_authors:
|
||||||
|
# ToDo: How to handle not found authorname
|
||||||
|
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
|
||||||
|
if not result:
|
||||||
|
error = True
|
||||||
|
break
|
||||||
|
authors_ordered.append(result)
|
||||||
|
if not error:
|
||||||
|
entry.authors = authors_ordered
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
# Fill indexpage with all requested data from database
|
||||||
|
def fill_indexpage(page, database, db_filter, order, *join):
|
||||||
|
if current_user.show_detail_random():
|
||||||
|
randm = db.session.query(db.Books).filter(common_filters())\
|
||||||
|
.order_by(func.random()).limit(config.config_random_books)
|
||||||
|
else:
|
||||||
|
randm = false()
|
||||||
|
off = int(int(config.config_books_per_page) * (page - 1))
|
||||||
|
pagination = Pagination(page, config.config_books_per_page,
|
||||||
|
len(db.session.query(database).filter(db_filter).filter(common_filters()).all()))
|
||||||
|
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters()).\
|
||||||
|
order_by(*order).offset(off).limit(config.config_books_per_page).all()
|
||||||
|
for book in entries:
|
||||||
|
book = order_authors(book)
|
||||||
|
return entries, randm, pagination
|
||||||
|
|
||||||
|
|
||||||
|
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||||
|
def get_search_results(term):
|
||||||
|
q = list()
|
||||||
|
authorterms = re.split("[, ]+", term)
|
||||||
|
for authorterm in authorterms:
|
||||||
|
q.append(db.Books.authors.any(db.Authors.name.ilike("%" + authorterm + "%")))
|
||||||
|
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||||
|
db.Books.authors.any(db.Authors.name.ilike("%" + term + "%"))
|
||||||
|
|
||||||
|
return db.session.query(db.Books).filter(common_filters()).filter(
|
||||||
|
db.or_(db.Books.tags.any(db.Tags.name.ilike("%" + term + "%")),
|
||||||
|
db.Books.series.any(db.Series.name.ilike("%" + term + "%")),
|
||||||
|
db.Books.authors.any(and_(*q)),
|
||||||
|
db.Books.publishers.any(db.Publishers.name.ilike("%" + term + "%")),
|
||||||
|
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
|
||||||
|
# Note: Not all images will be shown, even though they're available on Goodreads.com.
|
||||||
|
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
|
||||||
|
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers),
|
||||||
|
library_books, [])
|
||||||
|
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers,
|
||||||
|
author_books)
|
||||||
|
|
||||||
|
# Fuzzy match book titles
|
||||||
|
if feature_support['levenshtein']:
|
||||||
|
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
|
||||||
|
other_books = filter(lambda author_book: not filter(
|
||||||
|
lambda library_book:
|
||||||
|
# Remove items in parentheses before comparing
|
||||||
|
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7,
|
||||||
|
library_titles
|
||||||
|
), other_books)
|
||||||
|
|
||||||
|
return other_books
|
||||||
|
|
|
@ -30,12 +30,12 @@ import datetime
|
||||||
import ub
|
import ub
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from web import login_required_if_no_ano, fill_indexpage, common_filters, get_search_results, render_read_books
|
from web import login_required_if_no_ano, common_filters, get_search_results, render_read_books, download_required
|
||||||
from sqlalchemy.sql.expression import func, text
|
from sqlalchemy.sql.expression import func, text
|
||||||
import helper
|
import helper
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
from web import download_required
|
from helper import fill_indexpage
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<div class="discover load-more">
|
<div class="discover load-more">
|
||||||
<h2>{{title}}</h2>
|
<h2>{{title}}</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
|
|
|
@ -58,14 +58,14 @@ web. {% for entry in random %}
|
||||||
<div class="discover load-more">
|
<div class="discover load-more">
|
||||||
<h2 class="{{title}}">{{_(title)}}</h2>
|
<h2 class="{{title}}">{{_(title)}}</h2>
|
||||||
<div class="filterheader hidden-xs hidden-sm">
|
<div class="filterheader hidden-xs hidden-sm">
|
||||||
<a id="new" class="btn btn-primary" href="{{url_for('web.newest_books')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
<a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
||||||
<a id="old" class="btn btn-primary" href="{{url_for('web.oldest_books')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
||||||
<a id="asc" class="btn btn-primary" href="{{url_for('web.titles_ascending')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
|
<a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
|
||||||
<a id="desc" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
|
<a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
|
||||||
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></a>
|
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
||||||
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></a>
|
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
||||||
<div class="btn-group character" role="group">
|
<div class="btn-group character" role="group">
|
||||||
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a>
|
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a>
|
||||||
<div id="all" class="btn btn-primary">{{_('All')}}</div>
|
<div id="all" class="btn btn-primary">{{_('All')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -121,22 +121,10 @@
|
||||||
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
|
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
|
||||||
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
|
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
|
||||||
{% if g.user.check_visibility(1024) %} <!-- ToDo: eliminate -->
|
{% if g.user.check_visibility(1024) %} <!-- ToDo: eliminate -->
|
||||||
<!--li id="nav_sort" class="dropdown">
|
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<span class="glyphicon glyphicon-sort-by-attributes"></span>{{_('Sorted Books')}}
|
|
||||||
<span class="caret"></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li id="nav_sort_old" {% if page == 'newest' %}class="active"{% endif %}><a href="{{url_for('web.newest_books')}}">{{_('Sort By')}} {{_('Newest')}}</a></li>
|
|
||||||
<li id="nav_sort_new" {% if page == 'oldest' %}class="active"{% endif %}><a href="{{url_for('web.oldest_books')}}">{{_('Sort By')}} {{_('Oldest')}}</a></li>
|
|
||||||
<li id="nav_sort_asc" {% if page == 'a-z' %}class="active"{% endif %}><a href="{{url_for('web.titles_ascending')}}">{{_('Sort By')}} {{_('Title')}} ({{_('Ascending')}})</a></li>
|
|
||||||
<li id="nav_sort_desc" {% if page == 'z-a' %}class="active"{% endif %}><a href="{{url_for('web.titles_descending')}}">{{_('Sort By')}} {{_('Title')}} ({{_('Descending')}})</a></li>
|
|
||||||
</ul>
|
|
||||||
</li-->
|
|
||||||
{%endif%}
|
{%endif%}
|
||||||
{% for element in sidebar %}
|
{% for element in sidebar %}
|
||||||
{% if g.user.check_visibility(element['visibility']) and element['public'] %}
|
{% if g.user.check_visibility(element['visibility']) and element['public'] %}
|
||||||
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'])}}"><span class="glyphicon {{element['glyph']}}"></span>{{_(element['text'])}}</a></li>
|
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'], data=element['page'], sort='new')}}"><span class="glyphicon {{element['glyph']}}"></span>{{_(element['text'])}}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
12
cps/ub.py
12
cps/ub.py
|
@ -107,21 +107,21 @@ def get_sidebar_config(kwargs=[]):
|
||||||
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
|
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
|
||||||
"visibility": SIDEBAR_RECENT, 'public': True, "page": "root",
|
"visibility": SIDEBAR_RECENT, 'public': True, "page": "root",
|
||||||
"show_text": _('Show recent books'), "config_show":True})
|
"show_text": _('Show recent books'), "config_show":True})
|
||||||
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.hot_books', "id": "hot",
|
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot",
|
||||||
"visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'),
|
"visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'),
|
||||||
"config_show":True})
|
"config_show":True})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.best_rated_books', "id": "rated",
|
{"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.books_list', "id": "rated",
|
||||||
"visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
|
"visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
|
||||||
"show_text": _('Show best rated books'), "config_show":True})
|
"show_text": _('Show best rated books'), "config_show":True})
|
||||||
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.read_books', "id": "read",
|
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
|
||||||
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
|
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
|
||||||
"show_text": _('Show read and unread'), "config_show": content})
|
"show_text": _('Show read and unread'), "config_show": content})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.unread_books', "id": "unread",
|
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
|
||||||
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
|
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
|
||||||
"show_text": _('Show unread'), "config_show":False})
|
"show_text": _('Show unread'), "config_show":False})
|
||||||
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.discover', "id": "rand",
|
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
|
||||||
"visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover",
|
"visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover",
|
||||||
"show_text": _('Show random books'), "config_show":True})
|
"show_text": _('Show random books'), "config_show":True})
|
||||||
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
|
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
|
||||||
|
|
718
cps/web.py
718
cps/web.py
|
@ -22,10 +22,11 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from cps import mimetypes, global_WorkerThread, searched_ids
|
from cps import mimetypes, global_WorkerThread, searched_ids
|
||||||
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, \
|
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
|
||||||
url_for
|
|
||||||
from werkzeug.exceptions import default_exceptions
|
from werkzeug.exceptions import default_exceptions
|
||||||
import helper
|
import helper
|
||||||
|
from helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
|
||||||
|
order_authors
|
||||||
import os
|
import os
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
|
@ -36,9 +37,7 @@ from babel import Locale as LC
|
||||||
from babel.dates import format_date
|
from babel.dates import format_date
|
||||||
from babel.core import UnknownLocaleError
|
from babel.core import UnknownLocaleError
|
||||||
import base64
|
import base64
|
||||||
# from sqlalchemy.sql import *
|
from sqlalchemy.sql.expression import text, func, true, and_, false, not_
|
||||||
from sqlalchemy import String as SQLString
|
|
||||||
from sqlalchemy.sql.expression import text, func, cast, true, and_, false
|
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
from iso639 import languages as isoLanguages
|
from iso639 import languages as isoLanguages
|
||||||
|
@ -135,6 +134,7 @@ for ex in default_exceptions:
|
||||||
|
|
||||||
web = Blueprint('web', __name__)
|
web = Blueprint('web', __name__)
|
||||||
|
|
||||||
|
# ################################### Login logic and rights management ###############################################
|
||||||
|
|
||||||
@lm.user_loader
|
@lm.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
|
@ -239,89 +239,7 @@ def edit_required(f):
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
# ################################### Helper functions ################################################################
|
||||||
# Language and content filters for displaying in the UI
|
|
||||||
def common_filters():
|
|
||||||
if current_user.filter_language() != "all":
|
|
||||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
|
||||||
else:
|
|
||||||
lang_filter = true()
|
|
||||||
content_rating_filter = false() if current_user.mature_content else \
|
|
||||||
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
|
|
||||||
return and_(lang_filter, ~content_rating_filter)
|
|
||||||
|
|
||||||
|
|
||||||
# Creates for all stored languages a translated speaking name in the array for the UI
|
|
||||||
def speaking_language(languages=None):
|
|
||||||
if not languages:
|
|
||||||
languages = db.session.query(db.Languages).all()
|
|
||||||
for lang in languages:
|
|
||||||
try:
|
|
||||||
cur_l = LC.parse(lang.lang_code)
|
|
||||||
lang.name = cur_l.get_language_name(get_locale())
|
|
||||||
except UnknownLocaleError:
|
|
||||||
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
|
|
||||||
return languages
|
|
||||||
|
|
||||||
# checks if domain is in database (including wildcards)
|
|
||||||
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
|
|
||||||
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
|
|
||||||
def check_valid_domain(domain_text):
|
|
||||||
domain_text = domain_text.split('@', 1)[-1].lower()
|
|
||||||
sql = "SELECT * FROM registration WHERE :domain LIKE domain;"
|
|
||||||
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
|
|
||||||
return len(result)
|
|
||||||
|
|
||||||
|
|
||||||
# Orders all Authors in the list according to authors sort
|
|
||||||
def order_authors(entry):
|
|
||||||
sort_authors = entry.author_sort.split('&')
|
|
||||||
authors_ordered = list()
|
|
||||||
error = False
|
|
||||||
for auth in sort_authors:
|
|
||||||
# ToDo: How to handle not found authorname
|
|
||||||
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
|
|
||||||
if not result:
|
|
||||||
error = True
|
|
||||||
break
|
|
||||||
authors_ordered.append(result)
|
|
||||||
if not error:
|
|
||||||
entry.authors = authors_ordered
|
|
||||||
return entry
|
|
||||||
|
|
||||||
|
|
||||||
# Fill indexpage with all requested data from database
|
|
||||||
def fill_indexpage(page, database, db_filter, order, *join):
|
|
||||||
if current_user.show_detail_random():
|
|
||||||
randm = db.session.query(db.Books).filter(common_filters())\
|
|
||||||
.order_by(func.random()).limit(config.config_random_books)
|
|
||||||
else:
|
|
||||||
randm = false()
|
|
||||||
off = int(int(config.config_books_per_page) * (page - 1))
|
|
||||||
pagination = Pagination(page, config.config_books_per_page,
|
|
||||||
len(db.session.query(database).filter(db_filter).filter(common_filters()).all()))
|
|
||||||
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters()).\
|
|
||||||
order_by(*order).offset(off).limit(config.config_books_per_page).all()
|
|
||||||
for book in entries:
|
|
||||||
book = order_authors(book)
|
|
||||||
return entries, randm, pagination
|
|
||||||
|
|
||||||
|
|
||||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
|
||||||
def get_search_results(term):
|
|
||||||
q = list()
|
|
||||||
authorterms = re.split("[, ]+", term)
|
|
||||||
for authorterm in authorterms:
|
|
||||||
q.append(db.Books.authors.any(db.Authors.name.ilike("%" + authorterm + "%")))
|
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
|
||||||
db.Books.authors.any(db.Authors.name.ilike("%" + term + "%"))
|
|
||||||
|
|
||||||
return db.session.query(db.Books).filter(common_filters()).filter(
|
|
||||||
db.or_(db.Books.tags.any(db.Tags.name.ilike("%" + term + "%")),
|
|
||||||
db.Books.series.any(db.Series.name.ilike("%" + term + "%")),
|
|
||||||
db.Books.authors.any(and_(*q)),
|
|
||||||
db.Books.publishers.any(db.Publishers.name.ilike("%" + term + "%")),
|
|
||||||
db.Books.title.ilike("%" + term + "%"))).all()
|
|
||||||
|
|
||||||
|
|
||||||
# Returns the template for rendering and includes the instance name
|
# Returns the template for rendering and includes the instance name
|
||||||
|
@ -343,6 +261,9 @@ def before_request():
|
||||||
return redirect(url_for('admin.basic_configuration'))
|
return redirect(url_for('admin.basic_configuration'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ################################### data provider functions #########################################################
|
||||||
|
|
||||||
@web.route("/ajax/emailstat")
|
@web.route("/ajax/emailstat")
|
||||||
@login_required
|
@login_required
|
||||||
def get_email_status_json():
|
def get_email_status_json():
|
||||||
|
@ -354,8 +275,59 @@ def get_email_status_json():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@web.route("/ajax/bookmark/<int:book_id>/<book_format>", methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def bookmark(book_id, book_format):
|
||||||
|
bookmark_key = request.form["bookmark"]
|
||||||
|
ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
|
||||||
|
ub.Bookmark.book_id == book_id,
|
||||||
|
ub.Bookmark.format == book_format)).delete()
|
||||||
|
if not bookmark_key:
|
||||||
|
ub.session.commit()
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
lbookmark = ub.Bookmark(user_id=current_user.id,
|
||||||
|
book_id=book_id,
|
||||||
|
format=book_format,
|
||||||
|
bookmark_key=bookmark_key)
|
||||||
|
ub.session.merge(lbookmark)
|
||||||
|
ub.session.commit()
|
||||||
|
return "", 201
|
||||||
|
|
||||||
|
|
||||||
|
@web.route("/ajax/toggleread/<int:book_id>", methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def toggle_read(book_id):
|
||||||
|
if not config.config_read_column:
|
||||||
|
book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
|
||||||
|
ub.ReadBook.book_id == book_id)).first()
|
||||||
|
if book:
|
||||||
|
book.is_read = not book.is_read
|
||||||
|
else:
|
||||||
|
readBook = ub.ReadBook()
|
||||||
|
readBook.user_id = int(current_user.id)
|
||||||
|
readBook.book_id = book_id
|
||||||
|
readBook.is_read = True
|
||||||
|
book = readBook
|
||||||
|
ub.session.merge(book)
|
||||||
|
ub.session.commit()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
||||||
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
|
||||||
|
if len(read_status):
|
||||||
|
read_status[0].value = not read_status[0].value
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
cc_class = db.cc_classes[config.config_read_column]
|
||||||
|
new_cc = cc_class(value=1, book=book_id)
|
||||||
|
db.session.add(new_cc)
|
||||||
|
db.session.commit()
|
||||||
|
except KeyError:
|
||||||
|
app.logger.error(
|
||||||
|
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
|
||||||
|
return ""
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
|
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
|
||||||
|
@ -408,6 +380,7 @@ def get_comic_book(book_id, book_format, page):
|
||||||
return "", 204
|
return "", 204
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# ################################### Typeahead ##################################################################
|
||||||
|
|
||||||
@web.route("/get_authors_json", methods=['GET', 'POST'])
|
@web.route("/get_authors_json", methods=['GET', 'POST'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
|
@ -491,6 +464,8 @@ def get_matching_tags():
|
||||||
return json_dumps
|
return json_dumps
|
||||||
|
|
||||||
|
|
||||||
|
# ################################### View Books list ##################################################################
|
||||||
|
|
||||||
@web.route("/", defaults={'page': 1})
|
@web.route("/", defaults={'page': 1})
|
||||||
@web.route('/page/<int:page>')
|
@web.route('/page/<int:page>')
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
|
@ -500,49 +475,48 @@ def index(page):
|
||||||
title=_(u"Recently Added Books"), page="root")
|
title=_(u"Recently Added Books"), page="root")
|
||||||
|
|
||||||
|
|
||||||
@web.route('/books/newest', defaults={'page': 1})
|
@web.route('/<data>/<sort>', defaults={'page': 1})
|
||||||
@web.route('/books/newest/page/<int:page>')
|
@web.route('/<data>/<sort>/<int:page>')
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def newest_books(page):
|
def books_list(data,sort, page):
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.pubdate.desc()])
|
order = [db.Books.timestamp.desc()]
|
||||||
|
if sort == 'pubnew':
|
||||||
|
order = [db.Books.pubdate.desc()]
|
||||||
|
if sort == 'pubold':
|
||||||
|
order = [db.Books.pubdate]
|
||||||
|
if sort == 'abc':
|
||||||
|
[db.Books.sort]
|
||||||
|
if sort == 'zyx':
|
||||||
|
[db.Books.sort.desc()]
|
||||||
|
if sort == 'new':
|
||||||
|
order = [db.Books.timestamp.desc()]
|
||||||
|
if sort == 'old':
|
||||||
|
order = [db.Books.timestamp]
|
||||||
|
|
||||||
|
if data == "rated":
|
||||||
|
if current_user.check_visibility(ub.SIDEBAR_BEST_RATED):
|
||||||
|
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
|
||||||
|
order)
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
title=_(u"Newest Books"), page="newest")
|
title=_(u"Best rated books"), page="rated")
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
@web.route('/books/oldest', defaults={'page': 1})
|
elif data == "discover":
|
||||||
@web.route('/books/oldest/page/<int:page>')
|
if current_user.check_visibility(ub.SIDEBAR_RANDOM):
|
||||||
@login_required_if_no_ano
|
entries, __, pagination = fill_indexpage(page, db.Books, True, [func.randomblob(2)])
|
||||||
def oldest_books(page):
|
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.pubdate])
|
return render_title_template('discover.html', entries=entries, pagination=pagination,
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
title=_(u"Random Books"), page="discover")
|
||||||
title=_(u"Oldest Books"), page="oldest")
|
else:
|
||||||
|
abort(404)
|
||||||
|
elif data == "unread":
|
||||||
@web.route('/books/a-z', defaults={'page': 1})
|
return render_read_books(page, False, order=order)
|
||||||
@web.route('/books/a-z/page/<int:page>')
|
elif data == "read":
|
||||||
@login_required_if_no_ano
|
return render_read_books(page, True, order=order)
|
||||||
def titles_ascending(page):
|
elif data == "hot":
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.sort])
|
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
|
||||||
title=_(u"Books (A-Z)"), page="a-z")
|
|
||||||
|
|
||||||
|
|
||||||
@web.route('/books/z-a', defaults={'page': 1})
|
|
||||||
@web.route('/books/z-a/page/<int:page>')
|
|
||||||
@login_required_if_no_ano
|
|
||||||
def titles_descending(page):
|
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.sort.desc()])
|
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
|
||||||
title=_(u"Books (Z-A)"), page="z-a")
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/hot", defaults={'page': 1})
|
|
||||||
@web.route('/hot/page/<int:page>')
|
|
||||||
@login_required_if_no_ano
|
|
||||||
def hot_books(page):
|
|
||||||
if current_user.check_visibility(ub.SIDEBAR_HOT):
|
if current_user.check_visibility(ub.SIDEBAR_HOT):
|
||||||
if current_user.show_detail_random():
|
if current_user.show_detail_random():
|
||||||
random = db.session.query(db.Books).filter(common_filters())\
|
random = db.session.query(db.Books).filter(common_filters()) \
|
||||||
.order_by(func.random()).limit(config.config_random_books)
|
.order_by(func.random()).limit(config.config_random_books)
|
||||||
else:
|
else:
|
||||||
random = false()
|
random = false()
|
||||||
|
@ -552,7 +526,8 @@ def hot_books(page):
|
||||||
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
||||||
entries = list()
|
entries = list()
|
||||||
for book in hot_books:
|
for book in hot_books:
|
||||||
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(db.Books.id == book.Downloads.book_id).first()
|
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(
|
||||||
|
db.Books.id == book.Downloads.book_id).first()
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
entries.append(downloadBook)
|
entries.append(downloadBook)
|
||||||
else:
|
else:
|
||||||
|
@ -565,6 +540,17 @@ def hot_books(page):
|
||||||
title=_(u"Hot Books (most downloaded)"), page="hot")
|
title=_(u"Hot Books (most downloaded)"), page="hot")
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
else:
|
||||||
|
entries, random, pagination = fill_indexpage(page, db.Books, True, order)
|
||||||
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
|
title=_(u"Books"), page="newest")
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@web.route("/hot", defaults={'page': 1})
|
||||||
|
@web.route('/hot/page/<int:page>')
|
||||||
|
@login_required_if_no_ano
|
||||||
|
def hot_books(page):
|
||||||
|
|
||||||
|
|
||||||
@web.route("/rated", defaults={'page': 1})
|
@web.route("/rated", defaults={'page': 1})
|
||||||
|
@ -590,7 +576,7 @@ def discover(page):
|
||||||
return render_title_template('discover.html', entries=entries, pagination=pagination,
|
return render_title_template('discover.html', entries=entries, pagination=pagination,
|
||||||
title=_(u"Random Books"), page="discover")
|
title=_(u"Random Books"), page="discover")
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)'''
|
||||||
|
|
||||||
|
|
||||||
@web.route("/author")
|
@web.route("/author")
|
||||||
|
@ -629,7 +615,7 @@ def author(book_id, page):
|
||||||
try:
|
try:
|
||||||
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
||||||
author_info = gc.find_author(author_name=name)
|
author_info = gc.find_author(author_name=name)
|
||||||
other_books = get_unique_other_books(entries.all(), author_info.books)
|
other_books = helper.get_unique_other_books(entries.all(), author_info.books)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Skip goodreads, if site is down/inaccessible
|
# Skip goodreads, if site is down/inaccessible
|
||||||
app.logger.error('Goodreads website is down/inaccessible')
|
app.logger.error('Goodreads website is down/inaccessible')
|
||||||
|
@ -670,28 +656,6 @@ def publisher(book_id, page):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
# Note: Not all images will be shown, even though they're available on Goodreads.com.
|
|
||||||
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
|
|
||||||
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers),
|
|
||||||
library_books, [])
|
|
||||||
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers,
|
|
||||||
author_books)
|
|
||||||
|
|
||||||
# Fuzzy match book titles
|
|
||||||
if feature_support['levenshtein']:
|
|
||||||
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
|
|
||||||
other_books = filter(lambda author_book: not filter(
|
|
||||||
lambda library_book:
|
|
||||||
# Remove items in parentheses before comparing
|
|
||||||
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7,
|
|
||||||
library_titles
|
|
||||||
), other_books)
|
|
||||||
|
|
||||||
return other_books
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/series")
|
@web.route("/series")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def series_list():
|
def series_list():
|
||||||
|
@ -854,136 +818,17 @@ def category(book_id, page):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
@web.route("/ajax/toggleread/<int:book_id>", methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def toggle_read(book_id):
|
|
||||||
if not config.config_read_column:
|
|
||||||
book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
|
|
||||||
ub.ReadBook.book_id == book_id)).first()
|
|
||||||
if book:
|
|
||||||
book.is_read = not book.is_read
|
|
||||||
else:
|
|
||||||
readBook = ub.ReadBook()
|
|
||||||
readBook.user_id = int(current_user.id)
|
|
||||||
readBook.book_id = book_id
|
|
||||||
readBook.is_read = True
|
|
||||||
book = readBook
|
|
||||||
ub.session.merge(book)
|
|
||||||
ub.session.commit()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
|
|
||||||
if len(read_status):
|
|
||||||
read_status[0].value = not read_status[0].value
|
|
||||||
db.session.commit()
|
|
||||||
else:
|
|
||||||
cc_class = db.cc_classes[config.config_read_column]
|
|
||||||
new_cc = cc_class(value=1, book=book_id)
|
|
||||||
db.session.add(new_cc)
|
|
||||||
db.session.commit()
|
|
||||||
except KeyError:
|
|
||||||
app.logger.error(
|
|
||||||
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/book/<int:book_id>")
|
|
||||||
@login_required_if_no_ano
|
|
||||||
def show_book(book_id):
|
|
||||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
if entries:
|
|
||||||
for index in range(0, len(entries.languages)):
|
|
||||||
try:
|
|
||||||
entries.languages[index].language_name = LC.parse(entries.languages[index].lang_code).get_language_name(
|
|
||||||
get_locale())
|
|
||||||
except UnknownLocaleError:
|
|
||||||
entries.languages[index].language_name = _(
|
|
||||||
isoLanguages.get(part3=entries.languages[index].lang_code).name)
|
|
||||||
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
|
||||||
|
|
||||||
if config.config_columns_to_ignore:
|
|
||||||
cc = []
|
|
||||||
for col in tmpcc:
|
|
||||||
r = re.compile(config.config_columns_to_ignore)
|
|
||||||
if r.match(col.label):
|
|
||||||
cc.append(col)
|
|
||||||
else:
|
|
||||||
cc = tmpcc
|
|
||||||
book_in_shelfs = []
|
|
||||||
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
|
||||||
for entry in shelfs:
|
|
||||||
book_in_shelfs.append(entry.shelf)
|
|
||||||
|
|
||||||
if not current_user.is_anonymous:
|
|
||||||
if not config.config_read_column:
|
|
||||||
matching_have_read_book = ub.session.query(ub.ReadBook).\
|
|
||||||
filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
|
|
||||||
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].is_read
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
matching_have_read_book = getattr(entries, 'custom_column_'+str(config.config_read_column))
|
|
||||||
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
|
|
||||||
except KeyError:
|
|
||||||
app.logger.error(
|
|
||||||
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
|
|
||||||
have_read = None
|
|
||||||
|
|
||||||
else:
|
|
||||||
have_read = None
|
|
||||||
|
|
||||||
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
|
|
||||||
|
|
||||||
entries = order_authors(entries)
|
|
||||||
|
|
||||||
kindle_list = helper.check_send_to_kindle(entries)
|
|
||||||
reader_list = helper.check_read_formats(entries)
|
|
||||||
|
|
||||||
audioentries = []
|
|
||||||
for media_format in entries.data:
|
|
||||||
if media_format.format.lower() in EXTENSIONS_AUDIO:
|
|
||||||
audioentries.append(media_format.format.lower())
|
|
||||||
|
|
||||||
return render_title_template('detail.html', entry=entries, audioentries=audioentries, cc=cc,
|
|
||||||
is_xhr=request.is_xhr, title=entries.title, books_shelfs=book_in_shelfs,
|
|
||||||
have_read=have_read, kindle_list=kindle_list, reader_list=reader_list, page="book")
|
|
||||||
else:
|
|
||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
|
||||||
return redirect(url_for("web.index"))
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/ajax/bookmark/<int:book_id>/<book_format>", methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def bookmark(book_id, book_format):
|
|
||||||
bookmark_key = request.form["bookmark"]
|
|
||||||
ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
|
|
||||||
ub.Bookmark.book_id == book_id,
|
|
||||||
ub.Bookmark.format == book_format)).delete()
|
|
||||||
if not bookmark_key:
|
|
||||||
ub.session.commit()
|
|
||||||
return "", 204
|
|
||||||
|
|
||||||
lbookmark = ub.Bookmark(user_id=current_user.id,
|
|
||||||
book_id=book_id,
|
|
||||||
format=book_format,
|
|
||||||
bookmark_key=bookmark_key)
|
|
||||||
ub.session.merge(lbookmark)
|
|
||||||
ub.session.commit()
|
|
||||||
return "", 201
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/tasks")
|
@web.route("/tasks")
|
||||||
@login_required
|
@login_required
|
||||||
def get_tasks_status():
|
def get_tasks_status():
|
||||||
# if current user admin, show all email, otherwise only own emails
|
# if current user admin, show all email, otherwise only own emails
|
||||||
tasks = global_WorkerThread.get_taskstatus()
|
tasks = global_WorkerThread.get_taskstatus()
|
||||||
# UIanswer = copy.deepcopy(answer)
|
|
||||||
answer = helper.render_task_status(tasks)
|
answer = helper.render_task_status(tasks)
|
||||||
# foreach row format row
|
|
||||||
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
|
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
|
||||||
|
|
||||||
|
|
||||||
|
# ################################### Search functions ################################################################
|
||||||
|
|
||||||
@web.route("/search", methods=["GET"])
|
@web.route("/search", methods=["GET"])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def search():
|
def search():
|
||||||
|
@ -1148,6 +993,60 @@ def advanced_search():
|
||||||
series=series, title=_(u"search"), cc=cc, page="advsearch")
|
series=series, title=_(u"search"), cc=cc, page="advsearch")
|
||||||
|
|
||||||
|
|
||||||
|
'''@web.route("/unreadbooks/", defaults={'page': 1})
|
||||||
|
@web.route("/unreadbooks/<int:page>'")
|
||||||
|
@login_required_if_no_ano
|
||||||
|
def unread_books(page):
|
||||||
|
return render_read_books(page, False)
|
||||||
|
|
||||||
|
|
||||||
|
@web.route("/readbooks/", defaults={'page': 1})
|
||||||
|
@web.route("/readbooks/<int:page>'")
|
||||||
|
@login_required_if_no_ano
|
||||||
|
def read_books(page):
|
||||||
|
return render_read_books(page, True)'''
|
||||||
|
|
||||||
|
|
||||||
|
def render_read_books(page, are_read, as_xml=False, order=[]):
|
||||||
|
if not config.config_read_column:
|
||||||
|
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
|
||||||
|
.filter(ub.ReadBook.is_read is True).all()
|
||||||
|
readBookIds = [x.book_id for x in readBooks]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
readBooks = db.session.query(db.cc_classes[config.config_read_column])\
|
||||||
|
.filter(db.cc_classes[config.config_read_column].value is True).all()
|
||||||
|
readBookIds = [x.book for x in readBooks]
|
||||||
|
except KeyError:
|
||||||
|
app.logger.error(u"Custom Column No.%d is not existing in calibre database" % config.config_read_column)
|
||||||
|
readBookIds = []
|
||||||
|
|
||||||
|
if are_read:
|
||||||
|
db_filter = db.Books.id.in_(readBookIds)
|
||||||
|
else:
|
||||||
|
db_filter = ~db.Books.id.in_(readBookIds)
|
||||||
|
|
||||||
|
entries, random, pagination = fill_indexpage(page, db.Books, db_filter, order)
|
||||||
|
|
||||||
|
if as_xml:
|
||||||
|
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
response = make_response(xml)
|
||||||
|
response.headers["Content-Type"] = "application/xml; charset=utf-8"
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
if are_read:
|
||||||
|
name = _(u'Read Books') + ' (' + str(len(readBookIds)) + ')'
|
||||||
|
pagename = "read"
|
||||||
|
else:
|
||||||
|
total_books = db.session.query(func.count(db.Books.id)).scalar()
|
||||||
|
name = _(u'Unread Books') + ' (' + str(total_books - len(readBookIds)) + ')'
|
||||||
|
pagename = "unread"
|
||||||
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
|
title=name, page=pagename)
|
||||||
|
|
||||||
|
|
||||||
|
# ################################### Download/Send ##################################################################
|
||||||
|
|
||||||
@web.route("/cover/<int:book_id>")
|
@web.route("/cover/<int:book_id>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_cover(book_id):
|
def get_cover(book_id):
|
||||||
|
@ -1175,113 +1074,6 @@ def serve_book(book_id, book_format):
|
||||||
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
||||||
|
|
||||||
|
|
||||||
@web.route("/unreadbooks/", defaults={'page': 1})
|
|
||||||
@web.route("/unreadbooks/<int:page>'")
|
|
||||||
@login_required_if_no_ano
|
|
||||||
def unread_books(page):
|
|
||||||
return render_read_books(page, False)
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/readbooks/", defaults={'page': 1})
|
|
||||||
@web.route("/readbooks/<int:page>'")
|
|
||||||
@login_required_if_no_ano
|
|
||||||
def read_books(page):
|
|
||||||
return render_read_books(page, True)
|
|
||||||
|
|
||||||
|
|
||||||
def render_read_books(page, are_read, as_xml=False):
|
|
||||||
if not config.config_read_column:
|
|
||||||
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
|
|
||||||
.filter(ub.ReadBook.is_read is True).all()
|
|
||||||
readBookIds = [x.book_id for x in readBooks]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
readBooks = db.session.query(db.cc_classes[config.config_read_column])\
|
|
||||||
.filter(db.cc_classes[config.config_read_column].value is True).all()
|
|
||||||
readBookIds = [x.book for x in readBooks]
|
|
||||||
except KeyError:
|
|
||||||
app.logger.error(u"Custom Column No.%d is not existing in calibre database" % config.config_read_column)
|
|
||||||
readBookIds = []
|
|
||||||
|
|
||||||
if are_read:
|
|
||||||
db_filter = db.Books.id.in_(readBookIds)
|
|
||||||
else:
|
|
||||||
db_filter = ~db.Books.id.in_(readBookIds)
|
|
||||||
|
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db_filter, [db.Books.timestamp.desc()])
|
|
||||||
|
|
||||||
if as_xml:
|
|
||||||
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
|
|
||||||
response = make_response(xml)
|
|
||||||
response.headers["Content-Type"] = "application/xml; charset=utf-8"
|
|
||||||
return response
|
|
||||||
else:
|
|
||||||
if are_read:
|
|
||||||
name = _(u'Read Books') + ' (' + str(len(readBookIds)) + ')'
|
|
||||||
else:
|
|
||||||
total_books = db.session.query(func.count(db.Books.id)).scalar()
|
|
||||||
name = _(u'Unread Books') + ' (' + str(total_books - len(readBookIds)) + ')'
|
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
|
||||||
title=_(name, name=name), page="read")
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/read/<int:book_id>/<book_format>")
|
|
||||||
@login_required_if_no_ano
|
|
||||||
def read_book(book_id, book_format):
|
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
|
||||||
if not book:
|
|
||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
|
||||||
return redirect(url_for("web.index"))
|
|
||||||
|
|
||||||
# check if book was downloaded before
|
|
||||||
bookmark = None
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
|
|
||||||
ub.Bookmark.book_id == book_id,
|
|
||||||
ub.Bookmark.format == book_format.upper())).first()
|
|
||||||
if book_format.lower() == "epub":
|
|
||||||
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
|
|
||||||
elif book_format.lower() == "pdf":
|
|
||||||
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
|
|
||||||
elif book_format.lower() == "txt":
|
|
||||||
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
|
|
||||||
elif book_format.lower() == "mp3":
|
|
||||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
|
||||||
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
|
||||||
elif book_format.lower() == "m4b":
|
|
||||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
|
||||||
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
|
||||||
elif book_format.lower() == "m4a":
|
|
||||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
|
||||||
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
|
||||||
else:
|
|
||||||
book_dir = os.path.join(config.get_main_dir, "cps", "static", str(book_id))
|
|
||||||
if not os.path.exists(book_dir):
|
|
||||||
os.mkdir(book_dir)
|
|
||||||
for fileext in ["cbr", "cbt", "cbz"]:
|
|
||||||
if book_format.lower() == fileext:
|
|
||||||
all_name = str(book_id) # + "/" + book.data[0].name + "." + fileext
|
|
||||||
# tmp_file = os.path.join(book_dir, book.data[0].name) + "." + fileext
|
|
||||||
# if not os.path.exists(all_name):
|
|
||||||
# cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + "." + fileext
|
|
||||||
# copyfile(cbr_file, tmp_file)
|
|
||||||
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"),
|
|
||||||
extension=fileext)
|
|
||||||
'''if feature_support['rar']:
|
|
||||||
extensionList = ["cbr","cbt","cbz"]
|
|
||||||
else:
|
|
||||||
extensionList = ["cbt","cbz"]
|
|
||||||
for fileext in extensionList:
|
|
||||||
if book_format.lower() == fileext:
|
|
||||||
return render_title_template('readcbr.html', comicfile=book_id,
|
|
||||||
extension=fileext, title=_(u"Read a Book"), book=book)
|
|
||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible."), category="error")
|
|
||||||
return redirect(url_for("web.index"))'''
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/download/<int:book_id>/<book_format>")
|
@web.route("/download/<int:book_id>/<book_format>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
@download_required
|
@download_required
|
||||||
|
@ -1317,6 +1109,29 @@ def get_download_link_ext(book_id, book_format, anyname):
|
||||||
return 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>')
|
||||||
|
@login_required
|
||||||
|
@download_required
|
||||||
|
def send_to_kindle(book_id, book_format, convert):
|
||||||
|
settings = ub.get_mail_settings()
|
||||||
|
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,
|
||||||
|
current_user.nickname)
|
||||||
|
if result is None:
|
||||||
|
flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail),
|
||||||
|
category="success")
|
||||||
|
ub.update_download(book_id, int(current_user.id))
|
||||||
|
else:
|
||||||
|
flash(_(u"There was an error sending this book: %(res)s", res=result), category="error")
|
||||||
|
else:
|
||||||
|
flash(_(u"Please configure your kindle e-mail address first..."), category="error")
|
||||||
|
return redirect(request.environ["HTTP_REFERER"])
|
||||||
|
|
||||||
|
|
||||||
|
# ################################### Login Logout ##################################################################
|
||||||
|
|
||||||
@web.route('/register', methods=['GET', 'POST'])
|
@web.route('/register', methods=['GET', 'POST'])
|
||||||
def register():
|
def register():
|
||||||
if not config.config_public_reg:
|
if not config.config_public_reg:
|
||||||
|
@ -1502,26 +1317,7 @@ def token_verified():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@web.route('/send/<int:book_id>/<book_format>/<int:convert>')
|
# ################################### Users own configuration #########################################################
|
||||||
@login_required
|
|
||||||
@download_required
|
|
||||||
def send_to_kindle(book_id, book_format, convert):
|
|
||||||
settings = ub.get_mail_settings()
|
|
||||||
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,
|
|
||||||
current_user.nickname)
|
|
||||||
if result is None:
|
|
||||||
flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail),
|
|
||||||
category="success")
|
|
||||||
ub.update_download(book_id, int(current_user.id))
|
|
||||||
else:
|
|
||||||
flash(_(u"There was an error sending this book: %(res)s", res=result), category="error")
|
|
||||||
else:
|
|
||||||
flash(_(u"Please configure your kindle e-mail address first..."), category="error")
|
|
||||||
return redirect(request.environ["HTTP_REFERER"])
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/me", methods=["GET", "POST"])
|
@web.route("/me", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -1589,3 +1385,125 @@ def profile():
|
||||||
name=current_user.nickname), page="me", registered_oauth=oauth_check,
|
name=current_user.nickname), page="me", registered_oauth=oauth_check,
|
||||||
oauth_status=oauth_status)
|
oauth_status=oauth_status)
|
||||||
|
|
||||||
|
|
||||||
|
# ###################################Show single book ##################################################################
|
||||||
|
|
||||||
|
@web.route("/read/<int:book_id>/<book_format>")
|
||||||
|
@login_required_if_no_ano
|
||||||
|
def read_book(book_id, book_format):
|
||||||
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||||
|
if not book:
|
||||||
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||||
|
return redirect(url_for("web.index"))
|
||||||
|
|
||||||
|
# check if book was downloaded before
|
||||||
|
bookmark = None
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
|
||||||
|
ub.Bookmark.book_id == book_id,
|
||||||
|
ub.Bookmark.format == book_format.upper())).first()
|
||||||
|
if book_format.lower() == "epub":
|
||||||
|
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
|
||||||
|
elif book_format.lower() == "pdf":
|
||||||
|
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
|
||||||
|
elif book_format.lower() == "txt":
|
||||||
|
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
|
||||||
|
elif book_format.lower() == "mp3":
|
||||||
|
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
||||||
|
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
||||||
|
elif book_format.lower() == "m4b":
|
||||||
|
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
||||||
|
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
||||||
|
elif book_format.lower() == "m4a":
|
||||||
|
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
||||||
|
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
||||||
|
else:
|
||||||
|
book_dir = os.path.join(config.get_main_dir, "cps", "static", str(book_id))
|
||||||
|
if not os.path.exists(book_dir):
|
||||||
|
os.mkdir(book_dir)
|
||||||
|
for fileext in ["cbr", "cbt", "cbz"]:
|
||||||
|
if book_format.lower() == fileext:
|
||||||
|
all_name = str(book_id) # + "/" + book.data[0].name + "." + fileext
|
||||||
|
# tmp_file = os.path.join(book_dir, book.data[0].name) + "." + fileext
|
||||||
|
# if not os.path.exists(all_name):
|
||||||
|
# cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + "." + fileext
|
||||||
|
# copyfile(cbr_file, tmp_file)
|
||||||
|
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"),
|
||||||
|
extension=fileext)
|
||||||
|
'''if feature_support['rar']:
|
||||||
|
extensionList = ["cbr","cbt","cbz"]
|
||||||
|
else:
|
||||||
|
extensionList = ["cbt","cbz"]
|
||||||
|
for fileext in extensionList:
|
||||||
|
if book_format.lower() == fileext:
|
||||||
|
return render_title_template('readcbr.html', comicfile=book_id,
|
||||||
|
extension=fileext, title=_(u"Read a Book"), book=book)
|
||||||
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible."), category="error")
|
||||||
|
return redirect(url_for("web.index"))'''
|
||||||
|
|
||||||
|
|
||||||
|
@web.route("/book/<int:book_id>")
|
||||||
|
@login_required_if_no_ano
|
||||||
|
def show_book(book_id):
|
||||||
|
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
|
if entries:
|
||||||
|
for index in range(0, len(entries.languages)):
|
||||||
|
try:
|
||||||
|
entries.languages[index].language_name = LC.parse(entries.languages[index].lang_code).get_language_name(
|
||||||
|
get_locale())
|
||||||
|
except UnknownLocaleError:
|
||||||
|
entries.languages[index].language_name = _(
|
||||||
|
isoLanguages.get(part3=entries.languages[index].lang_code).name)
|
||||||
|
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
|
|
||||||
|
if config.config_columns_to_ignore:
|
||||||
|
cc = []
|
||||||
|
for col in tmpcc:
|
||||||
|
r = re.compile(config.config_columns_to_ignore)
|
||||||
|
if r.match(col.label):
|
||||||
|
cc.append(col)
|
||||||
|
else:
|
||||||
|
cc = tmpcc
|
||||||
|
book_in_shelfs = []
|
||||||
|
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
||||||
|
for entry in shelfs:
|
||||||
|
book_in_shelfs.append(entry.shelf)
|
||||||
|
|
||||||
|
if not current_user.is_anonymous:
|
||||||
|
if not config.config_read_column:
|
||||||
|
matching_have_read_book = ub.session.query(ub.ReadBook).\
|
||||||
|
filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
|
||||||
|
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].is_read
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
matching_have_read_book = getattr(entries, 'custom_column_'+str(config.config_read_column))
|
||||||
|
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
|
||||||
|
except KeyError:
|
||||||
|
app.logger.error(
|
||||||
|
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
|
||||||
|
have_read = None
|
||||||
|
|
||||||
|
else:
|
||||||
|
have_read = None
|
||||||
|
|
||||||
|
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
|
||||||
|
|
||||||
|
entries = order_authors(entries)
|
||||||
|
|
||||||
|
kindle_list = helper.check_send_to_kindle(entries)
|
||||||
|
reader_list = helper.check_read_formats(entries)
|
||||||
|
|
||||||
|
audioentries = []
|
||||||
|
for media_format in entries.data:
|
||||||
|
if media_format.format.lower() in EXTENSIONS_AUDIO:
|
||||||
|
audioentries.append(media_format.format.lower())
|
||||||
|
|
||||||
|
return render_title_template('detail.html', entry=entries, audioentries=audioentries, cc=cc,
|
||||||
|
is_xhr=request.is_xhr, title=entries.title, books_shelfs=book_in_shelfs,
|
||||||
|
have_read=have_read, kindle_list=kindle_list, reader_list=reader_list, page="book")
|
||||||
|
else:
|
||||||
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||||
|
return redirect(url_for("web.index"))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user