This commit is contained in:
cbartondock 2021-11-04 14:00:51 -04:00
commit d856c4d78e
18 changed files with 259 additions and 103 deletions

View File

@ -16,14 +16,14 @@ To receive fixes for security vulnerabilities it is required to always upgrade t
| V 0.6.7 |Hardcoded secret key for sessions |CVE-2020-12627 |
| V 0.6.13|Calibre-Web Metadata cross site scripting |CVE-2021-25964|
| V 0.6.13|Name of Shelves are only visible to users who can access the corresponding shelf Thanks to @ibarrionuevo||
| V 0.6.13|JavaScript could get executed in the description field. Thanks to @ranjit-git ||
| V 0.6.13|JavaScript could get executed in the description field. Thanks to @ranjit-git and Hagai Wechsler (WhiteSource)||
| V 0.6.13|JavaScript could get executed in a custom column of type "comment" field ||
| V 0.6.13|JavaScript could get executed after converting a book to another format with a title containing javascript code||
| V 0.6.13|JavaScript could get executed after converting a book to another format with a username containing javascript code||
| V 0.6.13|JavaScript could get executed in the description series, categories or publishers title||
| V 0.6.13|JavaScript could get executed in the shelf title||
| V 0.6.13|Login with the old session cookie after logout. Thanks to @ibarrionuevo||
| V 0.6.14|CSRF was possible. Thanks to @mik317 ||
| V 0.6.14|CSRF was possible. Thanks to @mik317 and Hagai Wechsler (WhiteSource) ||
| V 0.6.14|Cross-Site Scripting vulnerability on typeahead inputs. Thanks to @notdodo||

View File

@ -35,6 +35,7 @@ from flask_principal import Principal
from . import config_sql, logger, cache_buster, cli, ub, db
from .reverseproxy import ReverseProxied
from .server import WebServer
from .dep_check import dependency_check
try:
import lxml
@ -100,6 +101,7 @@ _BABEL_TRANSLATIONS = set()
log = logger.create()
from . import services
db.CalibreDB.update_config(config)
@ -126,7 +128,11 @@ def create_app():
print('*** "flask-WTF" is needed for calibre-web to run. Please install it using pip: "pip install flask-WTF" ***')
web_server.stop(True)
sys.exit(7)
for res in dependency_check() + dependency_check(True):
log.info('*** "{}" version does not fit the requirements. Should: {}, Found: {}, please consider updating. ***'
.format(res['name'],
res['target'],
res['found']))
app.wsgi_app = ReverseProxied(app.wsgi_app)
if os.environ.get('FLASK_DEBUG'):

View File

@ -780,7 +780,7 @@ class CalibreDB():
# read search results from calibre-database and return it (function is used for feed and simple search
def get_search_results(self, term, offset=None, order=None, limit=None, *join):
order = order or [Books.sort]
order = order[0] or [Books.sort]
pagination = None
result = self.search_query(term, *join).order_by(*order).all()
result_count = len(result)

83
cps/dep_check.py Normal file
View File

@ -0,0 +1,83 @@
import os
import re
from .constants import BASE_DIR
try:
from importlib_metadata import version
importlib = True
ImportNotFound = BaseException
except ImportError:
importlib = False
if not importlib:
try:
import pkg_resources
from pkg_resources import DistributionNotFound as ImportNotFound
pkgresources = True
except ImportError as e:
pkgresources = False
def dependency_check(optional=False):
dep = list()
if importlib or pkgresources:
if optional:
req_path = os.path.join(BASE_DIR, "optional-requirements.txt")
else:
req_path = os.path.join(BASE_DIR, "requirements.txt")
if os.path.exists(req_path):
try:
with open(req_path, 'r') as f:
for line in f:
if not line.startswith('#') and not line == '\n' and not line.startswith('git'):
res = re.match(r'(.*?)([<=>\s]+)([\d\.]+),?\s?([<=>\s]+)?([\d\.]+)?', line.strip())
try:
if importlib:
dep_version = version(res.group(1))
else:
dep_version = pkg_resources.get_distribution(res.group(1)).version
except ImportNotFound:
if optional:
continue
else:
return [{'name':res.group(1),
'target': "available",
'found': "Not available"
}]
if res.group(2).strip() == "==":
if dep_version.split('.') != res.group(3).split('.'):
dep.append({'name': res.group(1),
'found': dep_version,
"target": res.group(2) + res.group(3)})
continue
elif res.group(2).strip() == ">=":
if dep_version.split('.') < res.group(3).split('.'):
dep.append({'name': res.group(1),
'found': dep_version,
"target": res.group(2) + res.group(3)})
continue
elif res.group(2).strip() == ">":
if dep_version.split('.') <= res.group(3).split('.'):
dep.append({'name': res.group(1),
'found': dep_version,
"target": res.group(2) + res.group(3)})
continue
if res.group(4) and res.group(5):
if res.group(4).strip() == "<":
if dep_version.split('.') >= res.group(5).split('.'):
dep.append(
{'name': res.group(1),
'found': dep_version,
"target": res.group(4) + res.group(5)})
continue
elif res.group(2).strip() == "<=":
if dep_version.split('.') > res.group(5).split('.'):
dep.append(
{'name': res.group(1),
'found': dep_version,
"target": res.group(4) + res.group(5)})
continue
except Exception as e:
print(e)
return dep

View File

@ -30,6 +30,9 @@ $("#desc").click(function() {
if (direction === 0) {
return;
}
$("#asc").removeClass("active");
$("#desc").addClass("active");
var page = $(this).data("id");
$.ajax({
method:"post",
@ -50,6 +53,9 @@ $("#asc").click(function() {
if (direction === 1) {
return;
}
$("#desc").removeClass("active");
$("#asc").addClass("active");
var page = $(this).data("id");
$.ajax({
method:"post",
@ -66,6 +72,8 @@ $("#asc").click(function() {
});
$("#all").click(function() {
$(".char").removeClass("active");
$("#all").addClass("active");
// go through all elements and make them visible
$list.isotope({ filter: function() {
return true;
@ -74,6 +82,9 @@ $("#all").click(function() {
});
$(".char").click(function() {
$(".char").removeClass("active");
$(this).addClass("active");
$("#all").removeClass("active");
var character = this.innerText;
$list.isotope({ filter: function() {
return this.attributes["data-id"].value.charAt(0).toUpperCase() === character;

View File

@ -19,6 +19,7 @@ var direction = $("#asc").data('order'); // 0=Descending order; 1= ascending or
var sort = 0; // Show sorted entries
$("#sort_name").click(function() {
$("#sort_name").toggleClass("active");
var className = $("h1").attr("Class") + "_sort_name";
var obj = {};
obj[className] = sort;
@ -68,6 +69,9 @@ $("#desc").click(function() {
if (direction === 0) {
return;
}
$("#asc").removeClass("active");
$("#desc").addClass("active");
var page = $(this).data("id");
$.ajax({
method:"post",
@ -112,10 +116,12 @@ $("#desc").click(function() {
$("#asc").click(function() {
if (direction === 1) {
return;
}
$("#desc").removeClass("active");
$("#asc").addClass("active");
var page = $(this).data("id");
$.ajax({
method:"post",
@ -159,6 +165,8 @@ $("#asc").click(function() {
});
$("#all").click(function() {
$("#all").addClass("active");
$(".char").removeClass("active");
var cnt = $("#second").contents();
$("#list").append(cnt);
// Find count of middle element
@ -176,6 +184,9 @@ $("#all").click(function() {
});
$(".char").click(function() {
$(".char").removeClass("active");
$(this).addClass("active");
$("#all").removeClass("active");
var character = this.innerText;
var count = 0;
var index = 0;

View File

@ -28,14 +28,17 @@ $(function () {
function populateForm (book) {
tinymce.get("description").setContent(book.description);
var uniqueTags = [];
var uniqueTags = $.map($("#tags").val().split(","), $.trim);
if ( uniqueTags.length == 1 && uniqueTags[0] == "") {
uniqueTags = [];
}
$.each(book.tags, function(i, el) {
if ($.inArray(el, uniqueTags) === -1) uniqueTags.push(el);
});
var ampSeparatedAuthors = (book.authors || []).join(" & ");
$("#bookAuthor").val(ampSeparatedAuthors);
$("#book_title").val(book.title);
$("#tags").val(uniqueTags.join(","));
$("#tags").val(uniqueTags.join(", "));
$("#rating").data("rating").setValue(Math.round(book.rating));
if(book.cover !== null){
$(".cover img").attr("src", book.cover);

View File

@ -151,6 +151,7 @@ class TaskConvert(CalibreTask):
local_db.session.rollback()
log.error("Database error: %s", e)
local_db.session.close()
self._handleError(error_message)
return
self.results['path'] = cur_book.path
self.title = cur_book.title

View File

@ -16,7 +16,9 @@
<th>{{_('Downloads')}}</th>
<th class="hidden-xs ">{{_('Admin')}}</th>
<th class="hidden-xs hidden-sm">{{_('Password')}}</th>
{% if config.config_upload %}
<th class="hidden-xs hidden-sm">{{_('Upload')}}</th>
{% endif %}
<th class="hidden-xs hidden-sm">{{_('Download')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('View Books')}}</th>
<th class="hidden-xs hidden-sm hidden-md">{{_('Edit')}}</th>
@ -32,7 +34,9 @@
<td>{{user.downloads.count()}}</td>
<td class="hidden-xs">{{ display_bool_setting(user.role_admin()) }}</td>
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_passwd()) }}</td>
{% if config.config_upload %}
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_upload()) }}</td>
{% endif %}
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_download()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_viewer()) }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit()) }}</td>

View File

@ -23,12 +23,12 @@
<h3>{{_("In Library")}}</h3>
{% endif %}
<div class="filterheader hidden-xs">
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary{% if order == "new" %} active{% endif%}" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary{% if order == "old" %} active{% endif%}" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary{% if order == "abc" %} active{% endif%}" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary{% if order == "zyx" %} active{% endif%}" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary{% if order == "pubnew" %} active{% endif%}" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary{% if order == "pubold" %} active{% endif%}" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
</div>
<div class="row display-flex">
{% if entries[0] %}

View File

@ -95,17 +95,21 @@
<input type="checkbox" name="viewer_role" id="viewer_role" {% if conf.role_viewer() %}checked{% endif %}>
<label for="viewer_role">{{_('Allow eBook Viewer')}}</label>
</div>
{% if config.config_upload %}
<div class="form-group">
<input type="checkbox" name="upload_role" id="upload_role" {% if conf.role_upload() %}checked{% endif %}>
<label for="upload_role">{{_('Allow Uploads')}}</label>
</div>
{% endif %}
<div class="form-group">
<input type="checkbox" name="edit_role" id="edit_role" {% if conf.role_edit() %}checked{% endif %}>
<input type="checkbox" name="edit_role" data-control="edit_settings" id="edit_role" {% if conf.role_edit() %}checked{% endif %}>
<label for="edit_role">{{_('Allow Edit')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="delete_role" id="delete_role" {% if conf.role_delete_books() %}checked{% endif %}>
<label for="delete_role">{{_('Allow Delete Books')}}</label>
<div data-related="edit_settings">
<div class="form-group">
<input type="checkbox" name="delete_role" id="delete_role" {% if conf.role_delete_books() %}checked{% endif %}>
<label for="delete_role">{{_('Allow Delete Books')}}</label>
</div>
</div>
<div class="form-group">
<input type="checkbox" name="passwd_role" id="passwd_role" {% if conf.role_passwd() %}checked{% endif %}>

View File

@ -4,20 +4,20 @@
<div class="filterheader hidden-xs">
{% if entries.__len__() and data == 'author' %}
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
<div id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></div>
{% endif %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button id="asc" data-id="series" data-order="{{ order }}" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
<button id="desc" data-id="series" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
<div id="asc" data-id="series" data-order="{{ order }}" class="btn btn-primary{% if order == 1 %} active{% endif%}"><span class="glyphicon glyphicon-sort-by-alphabet"></span></div>
<div id="desc" data-id="series" class="btn btn-primary{% if order == 0 %} active{% endif%}"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></div>
{% if charlist|length %}
<button id="all" class="btn btn-primary {% if charlist|length > 9 %}hidden-sm{% endif %}">{{_('All')}}</button>
<div id="all" class="active btn btn-primary {% if charlist|length > 9 %}hidden-sm{% endif %}">{{_('All')}}</div>
{% endif %}
<div class="btn-group character {% if charlist|length > 9 %}hidden-sm{% endif %}" role="group">
{% for char in charlist%}
<button class="btn btn-primary char">{{char.char}}</button>
<div class="btn btn-primary char">{{char.char}}</div>
{% endfor %}
</div>
<button class="update-view btn btn-primary" data-target="series_view" id="list-button" data-view="list">List</button>
<div class="update-view btn btn-primary" data-target="series_view" id="list-button" data-view="list">List</div>
</div>
{% if entries[0] %}
@ -27,7 +27,7 @@
<div class="cover">
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
<span class="img" title="{{entry[0].series[0].name}}">
<img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
<img src="{{ url_for('web.get_cover', book_id=entry[3]) }}" alt="{{ entry[0].series[0].name }}"/>
<span class="badge">{{entry.count}}</span>
</span>
</a>

View File

@ -65,18 +65,23 @@
<div class="discover load-more">
<h2 class="{{title}}">{{title}}</h2>
<div class="filterheader hidden-xs">
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" id="auth_az" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authaz')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authza')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% if page == 'hot' %}
<a data-toggle="tooltip" title="{{_('Sort ascending according to download count')}}" id="hot_asc" class="btn btn-primary{% if order == "hotasc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='hotasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort descending according to download count')}}" id="hot_desc" class="btn btn-primary{% if order == "hotdesc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='hotdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% else %}
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary{% if order == "new" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" id="old" class="btn btn-primary{% if order == "old" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" id="asc" class="btn btn-primary{% if order == "abc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" id="desc" class="btn btn-primary{% if order == "zyx" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" id="auth_az" class="btn btn-primary{% if order == "authaz" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authaz')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary{% if order == "authza" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authza')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" id="pub_new" class="btn btn-primary{% if order == "pubnew" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" id="pub_old" class="btn btn-primary{% if order == "pubold" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% if page == 'series' %}
<a data-toggle="tooltip" title="{{_('Sort ascending according to series index')}}" id="series_asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort descending according to series index')}}" id="series_desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort ascending according to series index')}}" id="series_asc" class="btn btn-primary{% if order == "seriesasc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort descending according to series index')}}" id="series_desc" class="btn btn-primary{% if order == "seriesdesc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% endif %}
{% endif %}
</div>
<div class="row display-flex">

View File

@ -5,16 +5,16 @@
<div class="filterheader hidden-xs">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if entries.__len__() and data == 'author' %}
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
<div id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></div>
{% endif %}
<button id="asc" data-order="{{ order }}" data-id="{{ data }}" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
<button id="desc" data-id="{{ data }}" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
<div id="asc" data-order="{{ order }}" data-id="{{ data }}" class="btn btn-primary {% if order == 1 %} active{% endif%}"><span class="glyphicon glyphicon-sort-by-alphabet"></span></div>
<div id="desc" data-id="{{ data }}" class="btn btn-primary{% if order == 0 %} active{% endif%}"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></div>
{% if charlist|length %}
<button id="all" class="btn btn-primary {% if charlist|length > 9 %}hidden-sm{% endif %}">{{_('All')}}</button>
<div id="all" class="active btn btn-primary {% if charlist|length > 9 %}hidden-sm{% endif %}">{{_('All')}}</div>
{% endif %}
<div class="btn-group character {% if charlist|length > 9 %}hidden-sm{% endif %}" role="group">
{% for char in charlist%}
<button class="btn btn-primary char">{{char.char}}</button>
<div class="btn btn-primary char">{{char.char}}</div>
{% endfor %}
</div>

View File

@ -26,14 +26,14 @@
{% endif %}
{% endif %}
<div class="filterheader hidden-xs"><!-- ToDo: Implement filter for search results -->
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='new', query=query)}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='old', query=query)}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='abc', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='zyx', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="auth_az" data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='authaz', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="auth_za" data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='authza', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='pubnew', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='pubold', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary{% if order == "new" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='new', query=query)}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary{% if order == "old" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='old', query=query)}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary{% if order == "abc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='abc', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary{% if order == "zyx" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='zyx', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="auth_az" data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" class="btn btn-primary{% if order == "authaz" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='authaz', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="auth_za" data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" class="btn btn-primary{% if order == "authza" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='authza', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary{% if order == "pubnew" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='pubnew', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary{% if order == "pubold" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='pubold', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
</div>
{% endif %}

View File

@ -101,29 +101,33 @@
<input type="checkbox" name="viewer_role" id="viewer_role" {% if content.role_viewer() %}checked{% endif %}>
<label for="viewer_role">{{_('Allow eBook Viewer')}}</label>
</div>
{% if config.config_upload %}
<div class="form-group">
<input type="checkbox" name="upload_role" id="upload_role" {% if content.role_upload() %}checked{% endif %}>
<label for="upload_role">{{_('Allow Uploads')}}</label>
</div>
{% endif %}
<div class="form-group">
<input type="checkbox" name="edit_role" id="edit_role" {% if content.role_edit() %}checked{% endif %}>
<input type="checkbox" name="edit_role" data-control="edit_settings" id="edit_role" {% if content.role_edit() %}checked{% endif %}>
<label for="edit_role">{{_('Allow Edit')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="delete_role" id="delete_role" {% if content.role_delete_books() %}checked{% endif %}>
<label for="delete_role">{{_('Allow Delete Books')}}</label>
<div data-related="edit_settings">
<div class="form-group">
<input type="checkbox" name="delete_role" id="delete_role" {% if content.role_delete_books() %}checked{% endif %}>
<label for="delete_role">{{_('Allow Delete Books')}}</label>
</div>
</div>
{% if not content.role_anonymous() %}
<div class="form-group">
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
<label for="passwd_role">{{_('Allow Changing Password')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if content.role_edit_shelfs() %}checked{% endif %}>
<label for="edit_shelf_role">{{_('Allow Editing Public Shelves')}}</label>
</div>
{% endif %}
{% if not content.role_anonymous() %}
<div class="form-group">
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
<label for="passwd_role">{{_('Allow Changing Password')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if content.role_edit_shelfs() %}checked{% endif %}>
<label for="edit_shelf_role">{{_('Allow Editing Public Shelves')}}</label>
</div>
{% endif %}
{% endif %}
{% if kobo_support and not content.role_anonymous() %}
<div class="form-group">
<input type="checkbox" name="kobo_only_shelves_sync" id="kobo_only_shelves_sync" {% if content.kobo_only_shelves_sync %}checked{% endif %}>

View File

@ -339,7 +339,7 @@ def get_matching_tags():
def get_sort_function(sort, data):
order = [db.Books.timestamp.desc()]
order = [db.Books.sort]
if sort == 'stored':
sort = current_user.get_view_property(data, 'stored')
else:
@ -364,7 +364,13 @@ def get_sort_function(sort, data):
order = [db.Books.series_index.asc()]
if sort == 'seriesdesc':
order = [db.Books.series_index.desc()]
return order
if sort == 'hotdesc':
order = [func.count(ub.Downloads.book_id).desc()]
if sort == 'hotasc':
order = [func.count(ub.Downloads.book_id).asc()]
if sort is None:
sort = "abc"
return order, sort
def render_books_list(data, sort, book_id, page):
@ -378,7 +384,7 @@ def render_books_list(data, sort, book_id, page):
elif data == "read":
return render_read_books(page, True, order=order)
elif data == "hot":
return render_hot_books(page)
return render_hot_books(page, order)
elif data == "download":
return render_downloaded_books(page, order, book_id)
elif data == "author":
@ -407,12 +413,12 @@ def render_books_list(data, sort, book_id, page):
return render_adv_search_results(term, offset, order, config.config_books_per_page)
else:
website = data or "newest"
entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order,
entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order[0],
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Books"), page=website)
title=_(u"Books"), page=website, order=order[1])
def render_rated_books(page, book_id, order):
@ -420,13 +426,13 @@ def render_rated_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.ratings.any(db.Ratings.rating > 9),
order,
order[0],
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
id=book_id, title=_(u"Top Rated Books"), page="rated")
id=book_id, title=_(u"Top Rated Books"), page="rated", order=order[1])
else:
abort(404)
@ -440,16 +446,21 @@ def render_discover_books(page, book_id):
else:
abort(404)
def render_hot_books(page):
def render_hot_books(page, order):
if current_user.check_visibility(constants.SIDEBAR_HOT):
if order[1] not in ['hotasc', 'hotdesc']:
# Unary expression comparsion only working (for this expression) in sqlalchemy 1.4+
#if not (order[0][0].compare(func.count(ub.Downloads.book_id).desc()) or
# order[0][0].compare(func.count(ub.Downloads.book_id).asc())):
order = [func.count(ub.Downloads.book_id).desc()], 'hotdesc'
if current_user.show_detail_random():
random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \
.order_by(func.random()).limit(config.config_random_books)
else:
random = false()
off = int(int(config.config_books_per_page) * (page - 1))
all_books = ub.session.query(ub.Downloads, func.count(ub.Downloads.book_id)).order_by(
func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id)
all_books = ub.session.query(ub.Downloads, func.count(ub.Downloads.book_id))\
.order_by(*order[0]).group_by(ub.Downloads.book_id)
hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list()
for book in hot_books:
@ -462,7 +473,7 @@ def render_hot_books(page):
numBooks = entries.__len__()
pagination = Pagination(page, config.config_books_per_page, numBooks)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Hot Books (Most Downloaded)"), page="hot")
title=_(u"Hot Books (Most Downloaded)"), page="hot", order=order[1])
else:
abort(404)
@ -483,7 +494,10 @@ def render_downloaded_books(page, order, user_id):
0,
db.Books,
ub.Downloads.user_id == user_id,
order,
order[0],
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series,
ub.Downloads, db.Books.id == ub.Downloads.book_id)
for book in entries:
if not calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \
@ -496,7 +510,8 @@ def render_downloaded_books(page, order, user_id):
pagination=pagination,
id=user_id,
title=_(u"Downloaded books by %(user)s",user=user.name),
page="download")
page="download",
order=order[1])
else:
abort(404)
@ -505,7 +520,7 @@ def render_author_books(page, author_id, order):
entries, __, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.authors.any(db.Authors.id == author_id),
[order[0], db.Series.name, db.Books.series_index],
[order[0][0], db.Series.name, db.Books.series_index],
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
@ -527,7 +542,7 @@ def render_author_books(page, author_id, order):
return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
title=_(u"Author: %(name)s", name=author_name), author=author_info,
other_books=other_books, page="author")
other_books=other_books, page="author", order=order[1])
def render_publisher_books(page, book_id, order):
@ -536,12 +551,14 @@ def render_publisher_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.publishers.any(db.Publishers.id == book_id),
[db.Series.name, order[0], db.Books.series_index],
[db.Series.name, order[0][0], db.Books.series_index],
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher")
title=_(u"Publisher: %(name)s", name=publisher.name),
page="publisher",
order=order[1])
else:
abort(404)
@ -552,9 +569,9 @@ def render_series_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.series.any(db.Series.id == book_id),
[order[0]])
[order[0][0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"Series: %(serie)s", serie=name.name), page="series")
title=_(u"Series: %(serie)s", serie=name.name), page="series", order=order[1])
else:
abort(404)
@ -564,10 +581,12 @@ def render_ratings_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.ratings.any(db.Ratings.id == book_id),
[order[0]])
[order[0][0]])
if name and name.rating <= 10:
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings")
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)),
page="ratings",
order=order[1])
else:
abort(404)
@ -578,9 +597,11 @@ def render_formats_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.data.any(db.Data.format == book_id.upper()),
[order[0]])
[order[0][0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"File format: %(format)s", format=name.format), page="formats")
title=_(u"File format: %(format)s", format=name.format),
page="formats",
order=order[1])
else:
abort(404)
@ -591,12 +612,12 @@ def render_category_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.tags.any(db.Tags.id == book_id),
[order[0], db.Series.name, db.Books.series_index],
[order[0][0], db.Series.name, db.Books.series_index],
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
title=_(u"Category: %(name)s", name=name.name), page="category")
title=_(u"Category: %(name)s", name=name.name), page="category", order=order[1])
else:
abort(404)
@ -610,13 +631,13 @@ def render_language_books(page, name, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.languages.any(db.Languages.lang_code == name),
[order[0]])
[order[0][0]])
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
title=_(u"Language: %(name)s", name=lang_name), page="language")
title=_(u"Language: %(name)s", name=lang_name), page="language", order=order[1])
def render_read_books(page, are_read, as_xml=False, order=None):
order = order or []
sort = order[0] or []
if not config.config_read_column:
if are_read:
db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
@ -626,7 +647,7 @@ def render_read_books(page, are_read, as_xml=False, order=None):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db_filter,
order,
sort,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series,
@ -640,7 +661,7 @@ def render_read_books(page, are_read, as_xml=False, order=None):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db_filter,
order,
sort,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series,
@ -663,11 +684,11 @@ def render_read_books(page, are_read, as_xml=False, order=None):
name = _(u'Unread Books') + ' (' + str(pagination.total_count) + ')'
pagename = "unread"
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=name, page=pagename)
title=name, page=pagename, order=order[1])
def render_archived_books(page, order):
order = order or []
def render_archived_books(page, sort):
order = sort[0] or []
archived_books = (
ub.session.query(ub.ArchivedBook)
.filter(ub.ArchivedBook.user_id == int(current_user.id))
@ -687,7 +708,7 @@ def render_archived_books(page, order):
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
pagename = "archived"
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=name, page=pagename)
title=name, page=pagename, order=sort[1])
def render_prepare_search_form(cc):
@ -732,7 +753,8 @@ def render_search_results(term, offset=None, order=None, limit=None):
entries=entries,
result_count=result_count,
title=_(u"Search"),
page="search")
page="search",
order=order[1])
# ################################### View Books list ##################################################################
@ -931,8 +953,9 @@ def series_list():
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Series"), page="serieslist", data="series", order=order_no)
else:
entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count')) \
.join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) \
entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count'),
func.max(db.Books.series_index), db.Books.id) \
.join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters())\
.group_by(text('books_series_link.series')).order_by(order).all()
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
@ -1249,7 +1272,7 @@ def extend_search_term(searchterm,
def render_adv_search_results(term, offset=None, order=None, limit=None):
order = order or [db.Books.sort]
sort = order[0] or [db.Books.sort]
pagination = None
cc = get_cc_columns(filter_config_custom_read=True)
@ -1347,7 +1370,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
log.debug_or_exception(ex)
flash(_("Error on search for custom columns, please restart Calibre-Web"), category="error")
q = q.order_by(*order).all()
q = q.order_by(*sort).all()
flask_session['query'] = json.dumps(term)
ub.store_ids(q)
result_count = len(q)
@ -1363,7 +1386,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
pagination=pagination,
entries=q[offset:limit_all],
result_count=result_count,
title=_(u"Advanced Search"), page="advsearch")
title=_(u"Advanced Search"), page="advsearch",
order=order[1])

View File

@ -34,8 +34,8 @@ rarfile>=2.7
scholarly>=1.2.0, <1.3
# other
natsort>=2.2.0,<7.2.0
comicapi>= 2.2.0,<2.3.0
natsort>=2.2.0,<8.1.0
comicapi>=2.2.0,<2.3.0
#Kobo integration
jsonschema>=3.2.0,<3.3.0