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
pull/932/head
Ozzieisaacs 5 years ago
parent bfd0e87a17
commit 406d1c76c9

@ -81,10 +81,8 @@ except cPickle.UnpicklingError as error:
print("Can't read file cps/translations/iso639.pickle: %s" % error) print("Can't read file cps/translations/iso639.pickle: %s" % error)
sys.exit(1) sys.exit(1)
searched_ids = {} searched_ids = {}
from worker import WorkerThread from worker import WorkerThread
global_WorkerThread = WorkerThread() global_WorkerThread = WorkerThread()
@ -110,8 +108,6 @@ def create_app():
app.logger.setLevel(config.config_log_level) app.logger.setLevel(config.config_log_level)
app.logger.info('Starting Calibre Web...') app.logger.info('Starting Calibre Web...')
# logging.getLogger("uploader").addHandler(file_handler)
# logging.getLogger("uploader").setLevel(config.config_log_level)
Principal(app) Principal(app)
lm.init_app(app) lm.init_app(app)
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT') app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
@ -119,7 +115,6 @@ def create_app():
db.setup_db() db.setup_db()
babel.init_app(app) babel.init_app(app)
global_WorkerThread.start() global_WorkerThread.start()
return app return app
@babel.localeselector @babel.localeselector

@ -46,14 +46,6 @@ def title_sort(title):
return title.strip() return title.strip()
def lcase(s):
return unidecode.unidecode(s.lower())
def ucase(s):
return s.upper()
Base = declarative_base() Base = declarative_base()
books_authors_link = Table('books_authors_link', Base.metadata, 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 iso639 import languages as isoLanguages
from pagination import Pagination from pagination import Pagination
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
import json
try: try:
import gdriveutils as gd 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) e_mail, user_name, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
return return
def check_send_to_kindle(entry): def check_send_to_kindle(entry):
""" """
returns all available book formats for sending to Kindle returns all available book formats for sending to Kindle
@ -444,24 +446,33 @@ def delete_book(book, calibrepath, book_format):
return delete_book_file(book, calibrepath, book_format) return delete_book_file(book, calibrepath, book_format)
def get_book_cover(cover_path): def get_book_cover(book_id):
if config.config_use_google_drive: book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
try: if book.has_cover:
if not gd.is_gdrive_ready():
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg") if config.config_use_google_drive:
path=gd.get_cover_via_gdrive(cover_path) try:
if path: if not gd.is_gdrive_ready():
return redirect(path) 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: 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")
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: 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 # saves book cover from url
@ -698,24 +709,29 @@ def fill_indexpage(page, database, db_filter, order, *join):
return entries, randm, pagination 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 # 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):
def get_search_results(term): db.session.connection().connection.connection.create_function("lower", 1, lcase)
db.session.connection().connection.connection.create_function("lower", 1, db.lcase) q = list()
q = list() authorterms = re.split("[, ]+", term)
authorterms = re.split("[, ]+", term) for authorterm in authorterms:
for authorterm in authorterms: q.append(db.Books.authors.any(db.func.lower(db.Authors.name).ilike("%" + authorterm + "%")))
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(
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.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.series.any(db.func.lower(db.Series.name).ilike("%" + term + "%")), db.Books.authors.any(and_(*q)),
db.Books.authors.any(and_(*q)), db.Books.publishers.any(db.func.lower(db.Publishers.name).ilike("%" + term + "%")),
db.Books.publishers.any(db.func.lower(db.Publishers.name).ilike("%" + term + "%")), db.func.lower(db.Books.title).ilike("%" + term + "%")
db.func.lower(db.Books.title).ilike("%" + term + "%") )).all()
)).all()
def get_unique_other_books(library_books, author_books): 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 # 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) return do_download_file(book, book_format, data, headers)
else: else:
abort(404) 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>") @opds.route("/opds/cover/<book_id>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_get_cover(book_id): 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_id)
return helper.get_book_cover(book.path)
@opds.route("/opds/readbooks/") @opds.route("/opds/readbooks/")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano

@ -16,23 +16,88 @@
*/ */
var direction = 0; // Descending order 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() { $("#desc").click(function() {
if (direction === 0) { if (direction === 0) {
return; return;
} }
var index = 0;
var list = $('#list'); var list = $('#list');
var second = $('#second'); var second = $('#second');
// var cnt = ;
list.append(second.contents());
var listItems = list.children(".row"); var listItems = list.children(".row");
var reversed, elementLength, middle; var reversed, elementLength, middle;
Array.prototype.push.apply(listItems,second.children(".row"))
reversed = listItems.get().reverse(); reversed = listItems.get().reverse();
elementLength = reversed.length; 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)); list.append(reversed.slice(0, index));
second.append(reversed.slice(middle,elementLength)); second.append(reversed.slice(index,elementLength));
}
else {
list.append(reversed.slice(0, elementLength));
}
direction = 0; direction = 0;
}); });
@ -41,34 +106,91 @@ $("#asc").click(function() {
if (direction === 1) { if (direction === 1) {
return; return;
} }
var index = 0;
var list = $("#list"); var list = $("#list");
var second = $('#second'); var second = $('#second');
list.append(second.contents());
var listItems = list.children(".row"); var listItems = list.children(".row");
Array.prototype.push.apply(listItems,second.children(".row"));
reversed = listItems.get().reverse(); reversed = listItems.get().reverse();
elementLength = reversed.length; elementLength = reversed.length;
middle = parseInt(elementLength / 2) + (elementLength % 2);
list.append(reversed.slice(0, middle)); // Find count of middle element
second.append(reversed.slice(middle,elementLength)); 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; direction = 1;
}); });
$("#all").click(function() { $("#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(); $(this).show();
}); });
// Move second half of all elements
if (listlength > 20) {
$("#second").append(listItems.slice(middle,listlength));
}
}); });
$(".char").click(function() { $(".char").click(function() {
var character = this.innerText; 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() { $(".row").each(function() {
if (this.attributes["data-id"].value.charAt(0).toUpperCase() !== character) { if (this.attributes["data-id"].value.charAt(0).toUpperCase() !== character) {
$(this).hide(); $(this).hide();
} else { } else {
$(this).show(); $(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 id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}"> <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) }}" /> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
{% else %}
<img src="{{ url_for('static', filename='generic_cover.jpg') }}" />
{% endif %}
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">

@ -5,11 +5,7 @@
<div class="col-sm-3 col-lg-3 col-xs-12"> <div class="col-sm-3 col-lg-3 col-xs-12">
<div class="cover"> <div class="cover">
{% if book.has_cover %}
<img src="{{ url_for('web.get_cover', book_id=book.id) }}" alt="{{ book.title }}"/> <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> </div>
{% if g.user.role_delete_books() %} {% if g.user.role_delete_books() %}
<div class="text-center"> <div class="text-center">

@ -4,11 +4,7 @@
<div class="row"> <div class="row">
<div class="col-sm-3 col-lg-3 col-xs-5"> <div class="col-sm-3 col-lg-3 col-xs-5">
<div class="cover"> <div class="cover">
{% if entry.has_cover %}
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <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> </div>
<div class="col-sm-9 col-lg-9 book-meta"> <div class="col-sm-9 col-lg-9 book-meta">
@ -150,7 +146,7 @@
<span class="glyphicon glyphicon-tags"></span> <span class="glyphicon glyphicon-tags"></span>
{% for tag in entry.tags %} {% 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%} {%endfor%}
</p> </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="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <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 }}" /> <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> </a>
</div> </div>
<div class="meta"> <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="col-sm-3 col-lg-2 col-xs-6 book" id="books">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <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 }}"/> <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> </a>
</div> </div>
<div class="meta"> <div class="meta">

@ -3,6 +3,11 @@
<h1 class="{{page}}">{{_(title)}}</h1> <h1 class="{{page}}">{{_(title)}}</h1>
<div class="filterheader hidden-xs hidden-sm"> <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="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> <button id="asc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
{% if charlist|length %} {% if charlist|length %}
@ -21,9 +26,9 @@
</div> </div>
<div id="second" class="col-xs-12 col-sm-6"> <div id="second" class="col-xs-12 col-sm-6">
{% endif %} {% 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-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 %} {% if entry.name %}
<div class="rating"> <div class="rating">
{% for number in range(entry.name) %} {% for number in range(entry.name) %}

@ -35,9 +35,7 @@
<h2>{{ entry.title }}</h2> <h2>{{ entry.title }}</h2>
<div class="cover"> <div class="cover">
{% if entry.has_cover %} <img src="{{ url_for('web.get_cover', cover_path=entry.path.replace('\\','/')) }}" alt="{{ entry.title }}" />
<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 %}
</div> </div>
{% if entry.ratings.__len__() > 0 %} {% if entry.ratings.__len__() > 0 %}

@ -18,11 +18,7 @@
<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">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <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 }}" /> <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> </a>
</div> </div>
<div class="meta"> <div class="meta">

@ -100,7 +100,10 @@ Base = declarative_base()
def get_sidebar_config(kwargs=None): def get_sidebar_config(kwargs=None):
kwargs = kwargs or [] kwargs = kwargs or []
if 'content' in kwargs: 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: else:
content = False content = False
sidebar = list() sidebar = list()

@ -67,6 +67,8 @@ try:
from PIL import __version__ as PILversion from PIL import __version__ as PILversion
use_PIL = True use_PIL = True
except ImportError: 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 use_PIL = False

@ -21,31 +21,30 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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, lm, babel, ub, config, get_locale, language_table, app, db
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 helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \ from helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
order_authors order_authors, get_typeahead, render_task_status, json_serial, get_unique_other_books, get_cc_columns, \
import os get_book_cover, get_download_link, send_mail, generate_random_password, send_registration_mail, \
from sqlalchemy.exc import IntegrityError 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_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.security import generate_password_hash, check_password_hash
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from redirect import redirect_back
from pagination import Pagination
from babel import Locale as LC 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 from flask_babel import gettext as _
from sqlalchemy.sql.expression import text, func, true, false, not_ from sqlalchemy.sql.expression import text, func, true, false, not_
from sqlalchemy.exc import IntegrityError
import base64
import os.path
import json import json
import datetime import datetime
import isoLanguages import isoLanguages
import os.path
import gdriveutils 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() feature_support = dict()
@ -252,8 +251,8 @@ def before_request():
@login_required @login_required
def get_email_status_json(): def get_email_status_json():
tasks = global_WorkerThread.get_taskstatus() tasks = global_WorkerThread.get_taskstatus()
answer = helper.render_task_status(tasks) answer = render_task_status(tasks)
js = json.dumps(answer, default=helper.json_serial) js = json.dumps(answer, default=json_serial)
response = make_response(js) response = make_response(js)
response.headers["Content-Type"] = "application/json; charset=utf-8" response.headers["Content-Type"] = "application/json; charset=utf-8"
return response return response
@ -366,12 +365,6 @@ def get_comic_book(book_id, book_format, page):
# ################################### Typeahead ################################################################## # ################################### 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") @web.route("/get_authors_json")
@login_required_if_no_ano @login_required_if_no_ano
@ -422,7 +415,7 @@ def get_matching_tags():
tag_dict = {'tags': []} tag_dict = {'tags': []}
if request.method == "GET": if request.method == "GET":
q = db.session.query(db.Books) 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') author_input = request.args.get('author_name')
title_input = request.args.get('book_title') title_input = request.args.get('book_title')
include_tag_inputs = request.args.getlist('include_tag') 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) return render_hot_books(page)
elif data == "author": elif data == "author":
return render_author_books(page, book_id, order) 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: else:
entries, random, pagination = fill_indexpage(page, db.Books, True, order) entries, random, pagination = fill_indexpage(page, db.Books, True, 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,
@ -532,24 +535,6 @@ def render_hot_books(page):
abort(404) abort(404)
@web.route("/author")
@login_required_if_no_ano
def author_list():
if current_user.check_visibility(ub.SIDEBAR_AUTHOR):
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count'))\
.join(db.books_authors_link).join(db.Books).filter(common_filters())\
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
charlist = db.session.query(func.upper(func.substr(db.Authors.sort,1,1)).label('char')) \
.join(db.books_authors_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Authors.sort,1,1))).all()
for entry in entries:
entry.Authors.name = entry.Authors.name.replace('|', ',')
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=u"Author list", page="authorlist", data='author')
else:
abort(404)
# ToDo wrong order function # ToDo wrong order function
def render_author_books(page, book_id, order): def render_author_books(page, book_id, order):
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id), entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
@ -566,7 +551,7 @@ def render_author_books(page, book_id, order):
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 = helper.get_unique_other_books(entries.all(), author_info.books) other_books = 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')
@ -575,6 +560,81 @@ def render_author_books(page, book_id, order):
title=name, author=author_info, other_books=other_books, page="author") 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():
if current_user.check_visibility(ub.SIDEBAR_AUTHOR):
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count'))\
.join(db.books_authors_link).join(db.Books).filter(common_filters())\
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
charlist = db.session.query(func.upper(func.substr(db.Authors.sort,1,1)).label('char')) \
.join(db.books_authors_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Authors.sort,1,1))).all()
for entry in entries:
entry.Authors.name = entry.Authors.name.replace('|', ',')
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=u"Author list", page="authorlist", data='author')
else:
abort(404)
@web.route("/publisher") @web.route("/publisher")
@login_required_if_no_ano @login_required_if_no_ano
def publisher_list(): 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')) \ 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()) \ .join(db.books_publishers_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Publishers.name,1,1))).all() .group_by(func.upper(func.substr(db.Publishers.name,1,1))).all()
return render_title_template('list.html', entries=entries, folder='web.publisher', charlist=charlist, return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Publisher list"), page="publisherlist") title=_(u"Publisher list"), page="publisherlist", data="publisher")
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")
else: else:
abort(404) abort(404)
@ -616,22 +661,8 @@ def series_list():
charlist = db.session.query(func.upper(func.substr(db.Series.sort,1,1)).label('char')) \ 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()) \ .join(db.books_series_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Series.sort,1,1))).all() .group_by(func.upper(func.substr(db.Series.sort,1,1))).all()
return render_title_template('list.html', entries=entries, folder='web.series', charlist=charlist, return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Series list"), page="serieslist") title=_(u"Series list"), page="serieslist", data="series")
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")
else: else:
abort(404) abort(404)
@ -644,22 +675,8 @@ def ratings_list():
(db.Ratings.rating/2).label('name'))\ (db.Ratings.rating/2).label('name'))\
.join(db.books_ratings_link).join(db.Books).filter(common_filters())\ .join(db.books_ratings_link).join(db.Books).filter(common_filters())\
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all() .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(), return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
title=_(u"Ratings list"), page="ratingslist") title=_(u"Ratings list"), page="ratingslist", data="ratings")
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")
else: else:
abort(404) 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'))\ entries = db.session.query(db.Data, func.count('data.book').label('count'),db.Data.format.label('format'))\
.join(db.Books).filter(common_filters())\ .join(db.Books).filter(common_filters())\
.group_by(db.Data.format).order_by(db.Data.format).all() .group_by(db.Data.format).order_by(db.Data.format).all()
return render_title_template('list.html', entries=entries, folder='web.formats', charlist=list(), return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
title=_(u"File formats list"), page="formatslist") title=_(u"File formats list"), page="formatslist", data="formats")
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")
else: else:
abort(404) abort(404)
@ -714,7 +717,7 @@ def language_overview():
func.count('books_languages_link.book').label('bookcount')).group_by( func.count('books_languages_link.book').label('bookcount')).group_by(
text('books_languages_link.lang_code')).all() text('books_languages_link.lang_code')).all()
return render_title_template('languages.html', languages=languages, lang_counter=lang_counter, 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: else:
abort(404) abort(404)
@ -747,33 +750,20 @@ def category_list():
charlist = db.session.query(func.upper(func.substr(db.Tags.name,1,1)).label('char')) \ 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()) \ .join(db.books_tags_link).join(db.Books).filter(common_filters()) \
.group_by(func.upper(func.substr(db.Tags.name,1,1))).all() .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, return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Category list"), page="catlist") title=_(u"Category list"), page="catlist", data="category")
else: else:
abort(404) abort(404)
@web.route("/category/<int:book_id>", defaults={'page': 1}) # ################################### Task functions ################################################################
@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)
@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()
answer = helper.render_task_status(tasks) answer = render_task_status(tasks)
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")
@ -798,8 +788,8 @@ def search():
@login_required_if_no_ano @login_required_if_no_ano
def advanced_search(): def advanced_search():
# Build custom columns names # Build custom columns names
cc = helper.get_cc_columns() cc = get_cc_columns()
db.session.connection().connection.connection.create_function("lower", 1, db.lcase) db.session.connection().connection.connection.create_function("lower", 1, lcase)
q = db.session.query(db.Books) q = db.session.query(db.Books)
include_tag_inputs = request.args.getlist('include_tag') 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>") @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):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() return get_book_cover(book_id)
return helper.get_book_cover(book.path)
@web.route("/show/<book_id>/<book_format>") @web.route("/show/<book_id>/<book_format>")
@ -1007,7 +996,7 @@ def serve_book(book_id, book_format):
@login_required_if_no_ano @login_required_if_no_ano
@download_required @download_required
def download_link(book_id, book_format, anyname): 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>') @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": if settings.get("mail_server", "mail.example.com") == "mail.example.com":
flash(_(u"Please configure the SMTP mail settings first..."), category="error") flash(_(u"Please configure the SMTP mail settings first..."), category="error")
elif current_user.kindle_mail: 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) current_user.nickname)
if result is None: if result is None:
flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail), 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"]): if check_valid_domain(to_save["email"]):
content.nickname = to_save["nickname"] content.nickname = to_save["nickname"]
content.email = to_save["email"] content.email = to_save["email"]
password = helper.generate_random_password() password = generate_random_password()
content.password = generate_password_hash(password) content.password = generate_password_hash(password)
content.role = config.config_default_role content.role = config.config_default_role
content.sidebar_view = config.config_default_show content.sidebar_view = config.config_default_show
@ -1065,7 +1054,7 @@ def register():
ub.session.commit() ub.session.commit()
if feature_support['oauth']: if feature_support['oauth']:
register_user_with_oauth(content) 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: except Exception:
ub.session.rollback() ub.session.rollback()
flash(_(u"An unknown error occurred. Please try again later."), category="error") 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) app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
flash(_(u"Wrong Username or Password"), category="error") 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') next_url = url_for('web.index')
return render_title_template('login.html', title=_(u"login"), next_url=next_url, config=config, page="login") 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"]) @web.route("/me", methods=["GET", "POST"])
@login_required @login_required
def profile(): def profile():
# content = ub.session.query(ub.User).filter(ub.User.id == int(current_user.id)).first() global _
downloads = list() downloads = list()
languages = speaking_language() languages = speaking_language()
translations = babel.list_translations() + [LC('en')] translations = babel.list_translations() + [LC('en')]
@ -1282,9 +1269,9 @@ def profile():
registered_oauth=oauth_check, oauth_status=oauth_status) registered_oauth=oauth_check, oauth_status=oauth_status)
flash(_(u"Profile updated"), category="success") flash(_(u"Profile updated"), category="success")
return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages, return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages,
content=current_user, downloads=downloads, title=_(u"%(name)s's profile", content=current_user, downloads=downloads, title= _(u"%(name)s's profile",
name=current_user.nickname), page="me", registered_oauth=oauth_check, name=current_user.nickname),
oauth_status=oauth_status) page="me", registered_oauth=oauth_check, oauth_status=oauth_status)
# ###################################Show single book ################################################################## # ###################################Show single book ##################################################################
@ -1344,7 +1331,7 @@ def show_book(book_id):
except UnknownLocaleError: except UnknownLocaleError:
entries.languages[index].language_name = _( entries.languages[index].language_name = _(
isoLanguages.get(part3=entries.languages[index].lang_code).name) isoLanguages.get(part3=entries.languages[index].lang_code).name)
cc = helper.get_cc_columns() cc = get_cc_columns()
book_in_shelfs = [] book_in_shelfs = []
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all() shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
for entry in shelfs: for entry in shelfs:
@ -1371,8 +1358,8 @@ def show_book(book_id):
entries = order_authors(entries) entries = order_authors(entries)
kindle_list = helper.check_send_to_kindle(entries) kindle_list = check_send_to_kindle(entries)
reader_list = helper.check_read_formats(entries) reader_list = check_read_formats(entries)
audioentries = [] audioentries = []
for media_format in entries.data: for media_format in entries.data:

Loading…
Cancel
Save