Merge branch 'master' into Develop
# Conflicts: # cps/admin.py # cps/converter.py # cps/subproc_wrapper.py # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
commit
302679719d
|
@ -41,6 +41,6 @@ Open a new GitHub pull request with the patch. Ensure the PR description clearly
|
||||||
|
|
||||||
In case your code enhances features of Calibre-Web: Create your pull request for the development branch if your enhancement consists of more than some lines of code in a local section of Calibre-Webs code. This makes it easier to test it and check all implication before it's made public.
|
In case your code enhances features of Calibre-Web: Create your pull request for the development branch if your enhancement consists of more than some lines of code in a local section of Calibre-Webs code. This makes it easier to test it and check all implication before it's made public.
|
||||||
|
|
||||||
Please check if your code runs on Python 2.7 (still necessary in 2020) and mainly on python 3. If possible and the feature is related to operating system functions, try to check it on Windows and Linux.
|
Please check if your code runs with python 3, python 2 is no longer supported. If possible and the feature is related to operating system functions, try to check it on Windows and Linux.
|
||||||
Calibre-Web is automatically tested on Linux in combination with python 3.7. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests.
|
Calibre-Web is automatically tested on Linux in combination with python 3.8. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests.
|
||||||
A static code analysis is done by Codacy, but it's partly broken and doesn't run automatically. You could check your code with ESLint before contributing, a configuration file can be found in the projects root folder.
|
A static code analysis is done by Codacy, but it's partly broken and doesn't run automatically. You could check your code with ESLint before contributing, a configuration file can be found in the projects root folder.
|
||||||
|
|
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please report security issues to ozzie.fernandez.isaacs@googlemail.com
|
|
@ -102,8 +102,9 @@ def create_app():
|
||||||
|
|
||||||
log.info('Starting Calibre Web...')
|
log.info('Starting Calibre Web...')
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
log.info('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3')
|
log.info('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***')
|
||||||
print('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3')
|
print('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***')
|
||||||
|
sys.exit(5)
|
||||||
Principal(app)
|
Principal(app)
|
||||||
lm.init_app(app)
|
lm.init_app(app)
|
||||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||||
|
|
15
cps/admin.py
15
cps/admin.py
|
@ -99,10 +99,11 @@ def admin_required(f):
|
||||||
|
|
||||||
@admi.before_app_request
|
@admi.before_app_request
|
||||||
def before_request():
|
def before_request():
|
||||||
if not ub.check_user_session(current_user.id, flask_session.get('_id')):
|
# make remember me function work
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
confirm_login()
|
||||||
|
if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path:
|
||||||
logout_user()
|
logout_user()
|
||||||
# if current_user.is_authenticated:
|
|
||||||
# confirm_login()
|
|
||||||
g.constants = constants
|
g.constants = constants
|
||||||
g.user = current_user
|
g.user = current_user
|
||||||
g.allow_registration = config.config_public_reg
|
g.allow_registration = config.config_public_reg
|
||||||
|
@ -1375,11 +1376,11 @@ def _delete_user(content):
|
||||||
if content.name != "Guest":
|
if content.name != "Guest":
|
||||||
# Delete all books in shelfs belonging to user, all shelfs of user, downloadstat of user, read status
|
# Delete all books in shelfs belonging to user, all shelfs of user, downloadstat of user, read status
|
||||||
# and user itself
|
# and user itself
|
||||||
ub.session.query(ub.ReadBook).filter(ub.User.id == ub.ReadBook.user_id).delete()
|
ub.session.query(ub.ReadBook).filter(content.id == ub.ReadBook.user_id).delete()
|
||||||
ub.session.query(ub.Downloads).filter(ub.User.id == ub.Downloads.user_id).delete()
|
ub.session.query(ub.Downloads).filter(content.id == ub.Downloads.user_id).delete()
|
||||||
for us in ub.session.query(ub.Shelf).filter(ub.User.id == ub.Shelf.user_id):
|
for us in ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id):
|
||||||
ub.session.query(ub.BookShelf).filter(us.id == ub.BookShelf.shelf).delete()
|
ub.session.query(ub.BookShelf).filter(us.id == ub.BookShelf.shelf).delete()
|
||||||
ub.session.query(ub.Shelf).filter(ub.User.id == ub.Shelf.user_id).delete()
|
ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id).delete()
|
||||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||||
ub.session_commit()
|
ub.session_commit()
|
||||||
log.info(u"User {} deleted".format(content.name))
|
log.info(u"User {} deleted".format(content.name))
|
||||||
|
|
|
@ -20,6 +20,9 @@ from __future__ import division, print_function, unicode_literals
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from sqlalchemy import __version__ as sql_version
|
||||||
|
|
||||||
|
sqlalchemy_version2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0])
|
||||||
|
|
||||||
# if installed via pip this variable is set to true (empty file with name .HOMEDIR present)
|
# if installed via pip this variable is set to true (empty file with name .HOMEDIR present)
|
||||||
HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR'))
|
HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR'))
|
||||||
|
|
|
@ -39,7 +39,9 @@ def _get_command_version(path, pattern, argument=None):
|
||||||
if argument:
|
if argument:
|
||||||
command.append(argument)
|
command.append(argument)
|
||||||
try:
|
try:
|
||||||
return process_wait(command, pattern=pattern).string
|
match = process_wait(command, pattern=pattern)
|
||||||
|
if isinstance(match, re.Match):
|
||||||
|
return match.string
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.warning("%s: %s", path, ex)
|
log.warning("%s: %s", path, ex)
|
||||||
return _EXECUTION_ERROR
|
return _EXECUTION_ERROR
|
||||||
|
|
|
@ -690,6 +690,8 @@ class CalibreDB():
|
||||||
randm = false()
|
randm = false()
|
||||||
off = int(int(pagesize) * (page - 1))
|
off = int(int(pagesize) * (page - 1))
|
||||||
query = self.session.query(database)
|
query = self.session.query(database)
|
||||||
|
if len(join) == 6:
|
||||||
|
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5])
|
||||||
if len(join) == 3:
|
if len(join) == 3:
|
||||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||||
elif len(join) == 2:
|
elif len(join) == 2:
|
||||||
|
@ -755,6 +757,8 @@ class CalibreDB():
|
||||||
for authorterm in authorterms:
|
for authorterm in authorterms:
|
||||||
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
||||||
query = self.session.query(Books)
|
query = self.session.query(Books)
|
||||||
|
if len(join) == 6:
|
||||||
|
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5])
|
||||||
if len(join) == 3:
|
if len(join) == 3:
|
||||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||||
elif len(join) == 2:
|
elif len(join) == 2:
|
||||||
|
|
|
@ -22,10 +22,6 @@ import glob
|
||||||
import zipfile
|
import zipfile
|
||||||
import json
|
import json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
try:
|
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -38,9 +34,9 @@ log = logger.create()
|
||||||
|
|
||||||
def assemble_logfiles(file_name):
|
def assemble_logfiles(file_name):
|
||||||
log_list = sorted(glob.glob(file_name + '*'), reverse=True)
|
log_list = sorted(glob.glob(file_name + '*'), reverse=True)
|
||||||
wfd = StringIO()
|
wfd = BytesIO()
|
||||||
for f in log_list:
|
for f in log_list:
|
||||||
with open(f, 'r') as fd:
|
with open(f, 'rb') as fd:
|
||||||
shutil.copyfileobj(fd, wfd)
|
shutil.copyfileobj(fd, wfd)
|
||||||
wfd.seek(0)
|
wfd.seek(0)
|
||||||
if int(__version__.split('.')[0]) < 2:
|
if int(__version__.split('.')[0]) < 2:
|
||||||
|
|
|
@ -113,10 +113,8 @@ def yesno(value, yes, no):
|
||||||
|
|
||||||
@jinjia.app_template_filter('formatfloat')
|
@jinjia.app_template_filter('formatfloat')
|
||||||
def formatfloat(value, decimals=1):
|
def formatfloat(value, decimals=1):
|
||||||
formatedstring = '%d' % value
|
value = 0 if not value else value
|
||||||
if (value % 1) != 0:
|
return ('{0:.' + str(decimals) + 'f}').format(value).rstrip('0').rstrip('.')
|
||||||
formatedstring = ('%s.%d' % (formatedstring, (value % 1) * 10**decimals)).rstrip('0')
|
|
||||||
return formatedstring
|
|
||||||
|
|
||||||
|
|
||||||
@jinjia.app_template_filter('formatseriesindex')
|
@jinjia.app_template_filter('formatseriesindex')
|
||||||
|
|
17
cps/kobo.py
17
cps/kobo.py
|
@ -44,11 +44,11 @@ from werkzeug.datastructures import Headers
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.sql.expression import and_, or_
|
from sqlalchemy.sql.expression import and_, or_
|
||||||
from sqlalchemy.exc import StatementError
|
from sqlalchemy.exc import StatementError
|
||||||
from sqlalchemy import __version__ as sql_version
|
|
||||||
from sqlalchemy.sql import select
|
from sqlalchemy.sql import select
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
|
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
|
||||||
|
from .constants import sqlalchemy_version2
|
||||||
from .helper import get_download_link
|
from .helper import get_download_link
|
||||||
from .services import SyncToken as SyncToken
|
from .services import SyncToken as SyncToken
|
||||||
from .web import download_required
|
from .web import download_required
|
||||||
|
@ -66,7 +66,6 @@ kobo_auth.register_url_value_preprocessor(kobo)
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
sql2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0])
|
|
||||||
|
|
||||||
def get_store_url_for_current_request():
|
def get_store_url_for_current_request():
|
||||||
# Programmatically modify the current url to point to the official Kobo store
|
# Programmatically modify the current url to point to the official Kobo store
|
||||||
|
@ -139,6 +138,7 @@ def convert_to_kobo_timestamp_string(timestamp):
|
||||||
def HandleSyncRequest():
|
def HandleSyncRequest():
|
||||||
sync_token = SyncToken.SyncToken.from_headers(request.headers)
|
sync_token = SyncToken.SyncToken.from_headers(request.headers)
|
||||||
log.info("Kobo library sync request received.")
|
log.info("Kobo library sync request received.")
|
||||||
|
log.debug("SyncToken: {}".format(sync_token))
|
||||||
if not current_app.wsgi_app.is_proxied:
|
if not current_app.wsgi_app.is_proxied:
|
||||||
log.debug('Kobo: Received unproxied request, changed request port to external server port')
|
log.debug('Kobo: Received unproxied request, changed request port to external server port')
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ def HandleSyncRequest():
|
||||||
only_kobo_shelves = current_user.kobo_only_shelves_sync
|
only_kobo_shelves = current_user.kobo_only_shelves_sync
|
||||||
|
|
||||||
if only_kobo_shelves:
|
if only_kobo_shelves:
|
||||||
if sql2:
|
if sqlalchemy_version2:
|
||||||
changed_entries = select(db.Books,
|
changed_entries = select(db.Books,
|
||||||
ub.ArchivedBook.last_modified,
|
ub.ArchivedBook.last_modified,
|
||||||
ub.BookShelf.date_added,
|
ub.BookShelf.date_added,
|
||||||
|
@ -182,7 +182,7 @@ def HandleSyncRequest():
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if sql2:
|
if sqlalchemy_version2:
|
||||||
changed_entries = select(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived)
|
changed_entries = select(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived)
|
||||||
else:
|
else:
|
||||||
changed_entries = calibre_db.session.query(db.Books,
|
changed_entries = calibre_db.session.query(db.Books,
|
||||||
|
@ -201,7 +201,7 @@ def HandleSyncRequest():
|
||||||
changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id)
|
changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id)
|
||||||
|
|
||||||
reading_states_in_new_entitlements = []
|
reading_states_in_new_entitlements = []
|
||||||
if sql2:
|
if sqlalchemy_version2:
|
||||||
books = calibre_db.session.execute(changed_entries.limit(SYNC_ITEM_LIMIT))
|
books = calibre_db.session.execute(changed_entries.limit(SYNC_ITEM_LIMIT))
|
||||||
else:
|
else:
|
||||||
books = changed_entries.limit(SYNC_ITEM_LIMIT)
|
books = changed_entries.limit(SYNC_ITEM_LIMIT)
|
||||||
|
@ -245,7 +245,7 @@ def HandleSyncRequest():
|
||||||
|
|
||||||
new_books_last_created = max(ts_created, new_books_last_created)
|
new_books_last_created = max(ts_created, new_books_last_created)
|
||||||
|
|
||||||
if sql2:
|
if sqlalchemy_version2:
|
||||||
max_change = calibre_db.session.execute(changed_entries
|
max_change = calibre_db.session.execute(changed_entries
|
||||||
.filter(ub.ArchivedBook.is_archived)
|
.filter(ub.ArchivedBook.is_archived)
|
||||||
.order_by(func.datetime(ub.ArchivedBook.last_modified).desc()))\
|
.order_by(func.datetime(ub.ArchivedBook.last_modified).desc()))\
|
||||||
|
@ -259,7 +259,7 @@ def HandleSyncRequest():
|
||||||
new_archived_last_modified = max(new_archived_last_modified, max_change)
|
new_archived_last_modified = max(new_archived_last_modified, max_change)
|
||||||
|
|
||||||
# no. of books returned
|
# no. of books returned
|
||||||
if sql2:
|
if sqlalchemy_version2:
|
||||||
entries = calibre_db.session.execute(changed_entries).all()
|
entries = calibre_db.session.execute(changed_entries).all()
|
||||||
book_count = len(entries)
|
book_count = len(entries)
|
||||||
else:
|
else:
|
||||||
|
@ -330,6 +330,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False):
|
||||||
extra_headers["x-kobo-sync"] = "continue"
|
extra_headers["x-kobo-sync"] = "continue"
|
||||||
sync_token.to_headers(extra_headers)
|
sync_token.to_headers(extra_headers)
|
||||||
|
|
||||||
|
log.debug("Kobo Sync Content: {}".format(sync_results))
|
||||||
response = make_response(jsonify(sync_results), extra_headers)
|
response = make_response(jsonify(sync_results), extra_headers)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -695,7 +696,7 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False):
|
||||||
})
|
})
|
||||||
extra_filters.append(ub.Shelf.kobo_sync)
|
extra_filters.append(ub.Shelf.kobo_sync)
|
||||||
|
|
||||||
if sql2:
|
if sqlalchemy_version2:
|
||||||
shelflist = ub.session.execute(select(ub.Shelf).outerjoin(ub.BookShelf).filter(
|
shelflist = ub.session.execute(select(ub.Shelf).outerjoin(ub.BookShelf).filter(
|
||||||
or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
|
or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
|
||||||
func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified),
|
func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified),
|
||||||
|
|
|
@ -183,3 +183,12 @@ class SyncToken:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return b64encode_json(token)
|
return b64encode_json(token)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{},{},{},{},{},{},{}".format(self.raw_kobo_store_token,
|
||||||
|
self.books_last_created,
|
||||||
|
self.books_last_modified,
|
||||||
|
self.archive_last_modified,
|
||||||
|
self.reading_state_last_modified,
|
||||||
|
self.tags_last_modified,
|
||||||
|
self.books_last_id)
|
||||||
|
|
52
cps/shelf.py
52
cps/shelf.py
|
@ -72,10 +72,9 @@ def add_to_shelf(shelf_id, book_id):
|
||||||
|
|
||||||
if not check_shelf_edit_permissions(shelf):
|
if not check_shelf_edit_permissions(shelf):
|
||||||
if not xhr:
|
if not xhr:
|
||||||
flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name),
|
flash(_(u"Sorry you are not allowed to add a book to that shelf"), category="error")
|
||||||
category="error")
|
|
||||||
return redirect(url_for('web.index'))
|
return redirect(url_for('web.index'))
|
||||||
return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403
|
return "Sorry you are not allowed to add a book to the that shelf", 403
|
||||||
|
|
||||||
book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
|
book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
|
||||||
ub.BookShelf.book_id == book_id).first()
|
ub.BookShelf.book_id == book_id).first()
|
||||||
|
@ -228,18 +227,21 @@ def remove_from_shelf(shelf_id, book_id):
|
||||||
@login_required
|
@login_required
|
||||||
def create_shelf():
|
def create_shelf():
|
||||||
shelf = ub.Shelf()
|
shelf = ub.Shelf()
|
||||||
return create_edit_shelf(shelf, title=_(u"Create a Shelf"), page="shelfcreate")
|
return create_edit_shelf(shelf, page_title=_(u"Create a Shelf"), page="shelfcreate")
|
||||||
|
|
||||||
|
|
||||||
@shelf.route("/shelf/edit/<int:shelf_id>", methods=["GET", "POST"])
|
@shelf.route("/shelf/edit/<int:shelf_id>", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def edit_shelf(shelf_id):
|
def edit_shelf(shelf_id):
|
||||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
||||||
return create_edit_shelf(shelf, title=_(u"Edit a shelf"), page="shelfedit", shelf_id=shelf_id)
|
if not check_shelf_edit_permissions(shelf):
|
||||||
|
flash(_(u"Sorry you are not allowed to edit this shelf"), category="error")
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
return create_edit_shelf(shelf, page_title=_(u"Edit a shelf"), page="shelfedit", shelf_id=shelf_id)
|
||||||
|
|
||||||
|
|
||||||
# if shelf ID is set, we are editing a shelf
|
# if shelf ID is set, we are editing a shelf
|
||||||
def create_edit_shelf(shelf, title, page, shelf_id=False):
|
def create_edit_shelf(shelf, page_title, page, shelf_id=False):
|
||||||
sync_only_selected_shelves = current_user.kobo_only_shelves_sync
|
sync_only_selected_shelves = current_user.kobo_only_shelves_sync
|
||||||
# calibre_db.session.query(ub.Shelf).filter(ub.Shelf.user_id == current_user.id).filter(ub.Shelf.kobo_sync).count()
|
# calibre_db.session.query(ub.Shelf).filter(ub.Shelf.user_id == current_user.id).filter(ub.Shelf.kobo_sync).count()
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
@ -247,20 +249,20 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
|
||||||
shelf.is_public = 1 if to_save.get("is_public") else 0
|
shelf.is_public = 1 if to_save.get("is_public") else 0
|
||||||
if config.config_kobo_sync:
|
if config.config_kobo_sync:
|
||||||
shelf.kobo_sync = True if to_save.get("kobo_sync") else False
|
shelf.kobo_sync = True if to_save.get("kobo_sync") else False
|
||||||
|
shelf_title = to_save.get("title", "")
|
||||||
if check_shelf_is_unique(shelf, to_save, shelf_id):
|
if check_shelf_is_unique(shelf, shelf_title, shelf_id):
|
||||||
shelf.name = to_save["title"]
|
shelf.name = shelf_title
|
||||||
if not shelf_id:
|
if not shelf_id:
|
||||||
shelf.user_id = int(current_user.id)
|
shelf.user_id = int(current_user.id)
|
||||||
ub.session.add(shelf)
|
ub.session.add(shelf)
|
||||||
shelf_action = "created"
|
shelf_action = "created"
|
||||||
flash_text = _(u"Shelf %(title)s created", title=to_save["title"])
|
flash_text = _(u"Shelf %(title)s created", title=shelf_title)
|
||||||
else:
|
else:
|
||||||
shelf_action = "changed"
|
shelf_action = "changed"
|
||||||
flash_text = _(u"Shelf %(title)s changed", title=to_save["title"])
|
flash_text = _(u"Shelf %(title)s changed", title=shelf_title)
|
||||||
try:
|
try:
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
log.info(u"Shelf {} {}".format(to_save["title"], shelf_action))
|
log.info(u"Shelf {} {}".format(shelf_title, shelf_action))
|
||||||
flash(flash_text, category="success")
|
flash(flash_text, category="success")
|
||||||
return redirect(url_for('shelf.show_shelf', shelf_id=shelf.id))
|
return redirect(url_for('shelf.show_shelf', shelf_id=shelf.id))
|
||||||
except (OperationalError, InvalidRequestError) as ex:
|
except (OperationalError, InvalidRequestError) as ex:
|
||||||
|
@ -274,37 +276,37 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
|
||||||
flash(_(u"There was an error"), category="error")
|
flash(_(u"There was an error"), category="error")
|
||||||
return render_title_template('shelf_edit.html',
|
return render_title_template('shelf_edit.html',
|
||||||
shelf=shelf,
|
shelf=shelf,
|
||||||
title=title,
|
title=page_title,
|
||||||
page=page,
|
page=page,
|
||||||
kobo_sync_enabled=config.config_kobo_sync,
|
kobo_sync_enabled=config.config_kobo_sync,
|
||||||
sync_only_selected_shelves=sync_only_selected_shelves)
|
sync_only_selected_shelves=sync_only_selected_shelves)
|
||||||
|
|
||||||
|
|
||||||
def check_shelf_is_unique(shelf, to_save, shelf_id=False):
|
def check_shelf_is_unique(shelf, title, shelf_id=False):
|
||||||
if shelf_id:
|
if shelf_id:
|
||||||
ident = ub.Shelf.id != shelf_id
|
ident = ub.Shelf.id != shelf_id
|
||||||
else:
|
else:
|
||||||
ident = true()
|
ident = true()
|
||||||
if shelf.is_public == 1:
|
if shelf.is_public == 1:
|
||||||
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
||||||
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 1)) \
|
.filter((ub.Shelf.name == title) & (ub.Shelf.is_public == 1)) \
|
||||||
.filter(ident) \
|
.filter(ident) \
|
||||||
.first() is None
|
.first() is None
|
||||||
|
|
||||||
if not is_shelf_name_unique:
|
if not is_shelf_name_unique:
|
||||||
log.error("A public shelf with the name '{}' already exists.".format(to_save["title"]))
|
log.error("A public shelf with the name '{}' already exists.".format(title))
|
||||||
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=title),
|
||||||
category="error")
|
category="error")
|
||||||
else:
|
else:
|
||||||
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
||||||
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) &
|
.filter((ub.Shelf.name == title) & (ub.Shelf.is_public == 0) &
|
||||||
(ub.Shelf.user_id == int(current_user.id))) \
|
(ub.Shelf.user_id == int(current_user.id))) \
|
||||||
.filter(ident) \
|
.filter(ident) \
|
||||||
.first() is None
|
.first() is None
|
||||||
|
|
||||||
if not is_shelf_name_unique:
|
if not is_shelf_name_unique:
|
||||||
log.error("A private shelf with the name '{}' already exists.".format(to_save["title"]))
|
log.error("A private shelf with the name '{}' already exists.".format(title))
|
||||||
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=title),
|
||||||
category="error")
|
category="error")
|
||||||
return is_shelf_name_unique
|
return is_shelf_name_unique
|
||||||
|
|
||||||
|
@ -378,7 +380,9 @@ def order_shelf(shelf_id):
|
||||||
|
|
||||||
|
|
||||||
def change_shelf_order(shelf_id, order):
|
def change_shelf_order(shelf_id, order):
|
||||||
result = calibre_db.session.query(db.Books).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \
|
result = calibre_db.session.query(db.Books).outerjoin(db.books_series_link,
|
||||||
|
db.Books.id == db.books_series_link.c.book)\
|
||||||
|
.outerjoin(db.Series).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \
|
||||||
.filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all()
|
.filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all()
|
||||||
for index, entry in enumerate(result):
|
for index, entry in enumerate(result):
|
||||||
book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
|
book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
|
||||||
|
@ -408,9 +412,11 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
|
||||||
if sort_param == 'old':
|
if sort_param == 'old':
|
||||||
change_shelf_order(shelf_id, [db.Books.timestamp])
|
change_shelf_order(shelf_id, [db.Books.timestamp])
|
||||||
if sort_param == 'authaz':
|
if sort_param == 'authaz':
|
||||||
change_shelf_order(shelf_id, [db.Books.author_sort.asc()])
|
change_shelf_order(shelf_id, [db.Books.author_sort.asc(), db.Series.name, db.Books.series_index])
|
||||||
if sort_param == 'authza':
|
if sort_param == 'authza':
|
||||||
change_shelf_order(shelf_id, [db.Books.author_sort.desc()])
|
change_shelf_order(shelf_id, [db.Books.author_sort.desc(),
|
||||||
|
db.Series.name.desc(),
|
||||||
|
db.Books.series_index.desc()])
|
||||||
page = "shelf.html"
|
page = "shelf.html"
|
||||||
pagesize = 0
|
pagesize = 0
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -3291,7 +3291,6 @@ div.btn-group[role=group][aria-label="Download, send to Kindle, reading"] .dropd
|
||||||
transform-origin: center top;
|
transform-origin: center top;
|
||||||
border: 0;
|
border: 0;
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
max-height: 80%;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -413,7 +413,11 @@ if($("body.advsearch").length > 0) {
|
||||||
});
|
});
|
||||||
$('#add-to-shelf').height("40px");
|
$('#add-to-shelf').height("40px");
|
||||||
function search_dropdownToggle() {
|
function search_dropdownToggle() {
|
||||||
topPos = $("#add-to-shelf").offset().top-20;
|
if( $("#add-to-shelf").length) {
|
||||||
|
topPos = $("#add-to-shelf").offset().top - 20;
|
||||||
|
} else {
|
||||||
|
topPos = 0
|
||||||
|
}
|
||||||
if ($('div[aria-label="Add to shelves"]').length > 0) {
|
if ($('div[aria-label="Add to shelves"]').length > 0) {
|
||||||
|
|
||||||
position = $('div[aria-label="Add to shelves"]').offset().left
|
position = $('div[aria-label="Add to shelves"]').offset().left
|
||||||
|
|
|
@ -609,7 +609,10 @@ $(function() {
|
||||||
if (xhr.status < 400) {
|
if (xhr.status < 400) {
|
||||||
$("#spinning_success").hide();
|
$("#spinning_success").hide();
|
||||||
clearInterval(rebootInterval);
|
clearInterval(rebootInterval);
|
||||||
handle_response(data.result);
|
if (data.result) {
|
||||||
|
handle_response(data.result);
|
||||||
|
data.result = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -662,33 +662,34 @@ function move_header_elements() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$(".multi_selector").selectpicker();
|
$(".multi_selector").selectpicker();
|
||||||
|
if ($(".multi_head").length) {
|
||||||
if (! $._data($(".multi_head").get(0), "events") ) {
|
if (!$._data($(".multi_head").get(0), "events")) {
|
||||||
// Functions have to be here, otherwise the callbacks are not fired if visible columns are changed
|
// Functions have to be here, otherwise the callbacks are not fired if visible columns are changed
|
||||||
$(".multi_head").on("click", function () {
|
$(".multi_head").on("click", function () {
|
||||||
var val = $(this).data("set");
|
var val = $(this).data("set");
|
||||||
var field = $(this).data("name");
|
var field = $(this).data("name");
|
||||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||||
var values = $("#" + field).val();
|
var values = $("#" + field).val();
|
||||||
confirmDialog(
|
confirmDialog(
|
||||||
"restrictions",
|
"restrictions",
|
||||||
"GeneralChangeModal",
|
"GeneralChangeModal",
|
||||||
0,
|
0,
|
||||||
function () {
|
function () {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: "post",
|
method: "post",
|
||||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||||
data: {"pk": result, "value": values, "action": val},
|
data: {"pk": result, "value": values, "action": val},
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
handleListServerResponse(data);
|
handleListServerResponse(data);
|
||||||
},
|
},
|
||||||
error: function (data) {
|
error: function (data) {
|
||||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#user_delete_selection").click(function () {
|
$("#user_delete_selection").click(function () {
|
||||||
|
@ -700,38 +701,41 @@ function move_header_elements() {
|
||||||
$("#select_default_language").on("change", function () {
|
$("#select_default_language").on("change", function () {
|
||||||
selectHeader(this, "default_language");
|
selectHeader(this, "default_language");
|
||||||
});
|
});
|
||||||
|
if ($(".check_head").length) {
|
||||||
if (! $._data($(".check_head").get(0), "events") ) {
|
if (!$._data($(".check_head").get(0), "events")) {
|
||||||
$(".check_head").on("change", function () {
|
$(".check_head").on("change", function () {
|
||||||
var val = $(this).data("set");
|
var val = $(this).data("set");
|
||||||
var name = $(this).data("name");
|
var name = $(this).data("name");
|
||||||
var data = $(this).data("val");
|
var data = $(this).data("val");
|
||||||
checkboxHeader(val, name, data);
|
checkboxHeader(val, name, data);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (! $._data($(".button_head").get(0), "events") ) {
|
if ($(".button_head").length) {
|
||||||
$(".button_head").on("click", function () {
|
if (!$._data($(".button_head").get(0), "events")) {
|
||||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
$(".button_head").on("click", function () {
|
||||||
confirmDialog(
|
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||||
"btndeluser",
|
confirmDialog(
|
||||||
"GeneralDeleteModal",
|
"btndeluser",
|
||||||
0,
|
"GeneralDeleteModal",
|
||||||
function () {
|
0,
|
||||||
$.ajax({
|
function () {
|
||||||
method: "post",
|
$.ajax({
|
||||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
method: "post",
|
||||||
data: {"userid": result},
|
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||||
success: function (data) {
|
data: {"userid": result},
|
||||||
selections = selections.filter((el) => !result.includes(el));
|
success: function (data) {
|
||||||
handleListServerResponse(data);
|
selections = selections.filter((el) => !result.includes(el));
|
||||||
},
|
handleListServerResponse(data);
|
||||||
error: function (data) {
|
},
|
||||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
error: function (data) {
|
||||||
},
|
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,10 +52,11 @@ def process_wait(command, serr=subprocess.PIPE, pattern=""):
|
||||||
p.wait()
|
p.wait()
|
||||||
for line in p.stdout.readlines():
|
for line in p.stdout.readlines():
|
||||||
if isinstance(line, bytes):
|
if isinstance(line, bytes):
|
||||||
line = line.decode('utf-8')
|
line = line.decode('utf-8', errors="ignore")
|
||||||
match = re.search(pattern, line, re.IGNORECASE)
|
match = re.search(pattern, line, re.IGNORECASE)
|
||||||
if match and ret_val == "":
|
if match and ret_val == "":
|
||||||
ret_val = match
|
ret_val = match
|
||||||
|
break
|
||||||
p.stdout.close()
|
p.stdout.close()
|
||||||
p.stderr.close()
|
p.stderr.close()
|
||||||
return ret_val
|
return ret_val
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{% if author is not none %}
|
{% if author is not none %}
|
||||||
<section class="author-bio">
|
<section class="author-bio">
|
||||||
{%if author.image_url is not none %}
|
{%if author.image_url is not none %}
|
||||||
<img src="{{author.image_url}}" alt="{{author.name|safe}}" class="author-photo pull-left">
|
<img title="{{author.name|safe}}" src="{{author.image_url}}" alt="{{author.name|safe}}" class="author-photo pull-left">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{%if author.about is not none %}
|
{%if author.about is not none %}
|
||||||
|
@ -37,14 +37,14 @@
|
||||||
<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) }}">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
|
<img title="{{author.name|safe}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
|
||||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
|
@ -104,11 +104,11 @@
|
||||||
<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="https://www.goodreads.com/book/show/{{ entry.gid['#text'] }}" target="_blank" rel="noopener">
|
<a href="https://www.goodreads.com/book/show/{{ entry.gid['#text'] }}" target="_blank" rel="noopener">
|
||||||
<img src="{{ entry.image_url }}" />
|
<img title="{{entry.title}}" src="{{ entry.image_url }}" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
|
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% if book %}
|
{% if book %}
|
||||||
<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">
|
||||||
<img id="detailcover" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>
|
<img id="detailcover" title="{{book.title}}" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>
|
||||||
</div>
|
</div>
|
||||||
{% if g.user.role_delete_books() %}
|
{% if g.user.role_delete_books() %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
|
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
|
||||||
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
|
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
|
||||||
</div>
|
</div>
|
||||||
{% if not gdriveError %}
|
{% if not gdriveError and config.config_use_google_drive %}
|
||||||
{% if show_authenticate_google_drive and config.config_use_google_drive %}
|
{% if show_authenticate_google_drive and config.config_use_google_drive %}
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
|
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
|
||||||
|
|
|
@ -4,7 +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">
|
||||||
<img id="detailcover" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />
|
<img id="detailcover" title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-9 col-lg-9 book-meta">
|
<div class="col-sm-9 col-lg-9 book-meta">
|
||||||
|
@ -122,7 +122,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if entry.series|length > 0 %}
|
{% if entry.series|length > 0 %}
|
||||||
<p>{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
|
<p>{{_('Book')}} {{entry.series_index|formatfloat(2)}} {{_('of')}} <a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if entry.languages.__len__() > 0 %}
|
{% if entry.languages.__len__() > 0 %}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
{% if entry.has_cover is defined %}
|
{% if entry.has_cover is defined %}
|
||||||
<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">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<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">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
|
|
|
@ -29,14 +29,14 @@
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
|
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
|
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
|
||||||
<span class="badge">{{entry.count}}</span>
|
<span class="badge">{{entry.count}}</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
|
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
|
||||||
<p class="title">{{entry[0].series[0].name|shortentitle}}</p>
|
<p title="{{entry[0].series[0].name|shortentitle}}" class="title">{{entry[0].series[0].name|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,14 +9,14 @@
|
||||||
<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">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
<img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<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">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
|
@ -86,14 +86,14 @@
|
||||||
<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">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
|
<img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
|
||||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<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">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<title>ePub Reader</title>
|
<title>{{_('epub Reader')}} | {{title}}</title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Comic Reader</title>
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
|
<title>{{_('Comic Reader')}} | {{title}}</title>
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/libs/djvu_html5/Djvu_html5.css') }}">
|
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/libs/djvu_html5/Djvu_html5.css') }}">
|
||||||
|
|
||||||
<title>Djvu HTML5 browser demo</title>
|
<title>{{_('DJVU Reader')}} | {{title}}</title>
|
||||||
|
|
||||||
<script type="text/javascript" language="javascript"
|
<script type="text/javascript" language="javascript"
|
||||||
src="{{ url_for('static', filename='js/libs/djvu_html5/djvu_html5/djvu_html5.nocache.js') }}"></script>
|
src="{{ url_for('static', filename='js/libs/djvu_html5/djvu_html5/djvu_html5.nocache.js') }}"></script>
|
||||||
|
|
|
@ -26,7 +26,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<meta name="google" content="notranslate">
|
<meta name="google" content="notranslate">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>{{_('PDF reader')}}</title>
|
<title>{{_('PDF Reader')}} | {{title}}</title>
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/viewer.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/viewer.css') }}">
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<title>{{_('Basic txt Reader')}}</title>
|
<title>{{_('txt Reader')}} | {{title}}</title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
{% if entry.has_cover is defined %}
|
{% if entry.has_cover is defined %}
|
||||||
<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">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<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">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
|
|
|
@ -31,14 +31,14 @@
|
||||||
<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">
|
||||||
<span class="img">
|
<span class="img">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
||||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<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">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-2 col-sm-4 hidden-xs">
|
<div class="col-lg-2 col-sm-4 hidden-xs">
|
||||||
{% if entry['visible'] %}
|
{% if entry['visible'] %}
|
||||||
<img class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}">
|
<img title="{{entry.title}}" class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}">
|
||||||
{% else %}
|
{% else %}
|
||||||
<img class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}">
|
<img title="{{entry.title}}" class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-10 col-sm-8 col-xs-12">
|
<div class="col-lg-10 col-sm-8 col-xs-12">
|
||||||
|
|
|
@ -35,7 +35,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="meta">
|
<div class="meta">
|
||||||
<p class="title">{{entry.title|shortentitle}}</p>
|
<p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
|
||||||
<p class="author">
|
<p class="author">
|
||||||
{% for author in entry.authors %}
|
{% for author in entry.authors %}
|
||||||
<a href="{{url_for('web.books_list', data='author', sort_param='new', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>
|
<a href="{{url_for('web.books_list', data='author', sort_param='new', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>
|
||||||
|
|
|
@ -67,15 +67,14 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
{% for element in sidebar %}
|
{% for element in sidebar %}
|
||||||
{% if element['config_show'] %}
|
{% if element['config_show'] %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" name="show_{{element['visibility']}}" id="show_{{element['visibility']}}" {% if content.check_visibility(element['visibility']) %}checked{% endif %}>
|
<input type="checkbox" name="show_{{element['visibility']}}" id="show_{{element['visibility']}}" {% if content.check_visibility(element['visibility']) %}checked{% endif %}>
|
||||||
<label for="show_{{element['visibility']}}">{{element['show_text']}}</label>
|
<label for="show_{{element['visibility']}}">{{element['show_text']}}</label>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" name="Show_detail_random" id="Show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
|
<input type="checkbox" name="Show_detail_random" id="Show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
|
||||||
<label for="Show_detail_random">{{_('Show Random Books in Detail View')}}</label>
|
<label for="Show_detail_random">{{_('Show Random Books in Detail View')}}</label>
|
||||||
|
@ -131,32 +130,33 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
|
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
|
||||||
{% if not profile %}
|
{% if not profile %}
|
||||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
|
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
||||||
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
|
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="modal_kobo_token" tabindex="-1" role="dialog" aria-labelledby="kobo_tokenModalLabel">
|
<div class="modal fade" id="modal_kobo_token" tabindex="-1" role="dialog" aria-labelledby="kobo_tokenModalLabel">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
<h4 class="modal-title" id="kobo_tokenModalLabel">{{_('Generate Kobo Auth URL')}}</h4>
|
<h4 class="modal-title" id="kobo_tokenModalLabel">{{_('Generate Kobo Auth URL')}}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">...</div>
|
<div class="modal-body">...</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="kobo_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
<button type="button" id="kobo_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block modal %}
|
{% block modal %}
|
||||||
|
|
43
cps/ub.py
43
cps/ub.py
|
@ -27,6 +27,8 @@ from flask import session as flask_session
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
|
||||||
from flask_login import AnonymousUserMixin, current_user
|
from flask_login import AnonymousUserMixin, current_user
|
||||||
|
from flask_login import user_logged_in
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||||
|
@ -79,6 +81,36 @@ def delete_user_session(user_id, session_key):
|
||||||
def check_user_session(user_id, session_key):
|
def check_user_session(user_id, session_key):
|
||||||
return session_key in logged_in.get(str(user_id), [])
|
return session_key in logged_in.get(str(user_id), [])
|
||||||
|
|
||||||
|
def signal_store_user_session(object, user):
|
||||||
|
store_user_session()
|
||||||
|
|
||||||
|
def store_user_session():
|
||||||
|
if flask_session.get('_user_id', ""):
|
||||||
|
try:
|
||||||
|
if not check_user_session(flask_session.get('_user_id', ""), flask_session.get('_id', "")):
|
||||||
|
user_session = User_Sessions(flask_session.get('_user_id', ""), flask_session.get('_id', ""))
|
||||||
|
session.add(user_session)
|
||||||
|
session.commit()
|
||||||
|
except (exc.OperationalError, exc.InvalidRequestError):
|
||||||
|
session.rollback()
|
||||||
|
# log.debug(flask_session.get('_id', ""))
|
||||||
|
|
||||||
|
def delete_user_session(user_id, session_key):
|
||||||
|
try:
|
||||||
|
# log.debug(session_key)
|
||||||
|
session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
||||||
|
User_Sessions.session_key==session_key).delete()
|
||||||
|
session.commit()
|
||||||
|
except (exc.OperationalError, exc.InvalidRequestError):
|
||||||
|
session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
def check_user_session(user_id, session_key):
|
||||||
|
return bool(session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
||||||
|
User_Sessions.session_key==session_key).one_or_none())
|
||||||
|
|
||||||
|
user_logged_in.connect(signal_store_user_session)
|
||||||
|
|
||||||
def store_ids(result):
|
def store_ids(result):
|
||||||
ids = list()
|
ids = list()
|
||||||
for element in result:
|
for element in result:
|
||||||
|
@ -279,6 +311,17 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
||||||
flask_session['view'][page][prop] = value
|
flask_session['view'][page][prop] = value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class User_Sessions(Base):
|
||||||
|
__tablename__ = 'user_session'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
user_id = Column(Integer, ForeignKey('user.id'))
|
||||||
|
session_key = Column(String, default="")
|
||||||
|
|
||||||
|
def __init__(self, user_id, session_key):
|
||||||
|
self.user_id = user_id
|
||||||
|
self.session_key = session_key
|
||||||
|
|
||||||
|
|
||||||
# Baseclass representing Shelfs in calibre-web in app.db
|
# Baseclass representing Shelfs in calibre-web in app.db
|
||||||
class Shelf(Base):
|
class Shelf(Base):
|
||||||
|
|
|
@ -21,7 +21,8 @@ import binascii
|
||||||
|
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
from flask_login import login_required
|
from flask_login import login_required, login_user
|
||||||
|
|
||||||
|
|
||||||
from . import lm, ub, config, constants, services
|
from . import lm, ub, config, constants, services
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ def load_user_from_request(request):
|
||||||
if rp_header_username:
|
if rp_header_username:
|
||||||
user = _fetch_user_by_name(rp_header_username)
|
user = _fetch_user_by_name(rp_header_username)
|
||||||
if user:
|
if user:
|
||||||
|
login_user(user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
auth_header = request.headers.get("Authorization")
|
auth_header = request.headers.get("Authorization")
|
||||||
|
|
68
cps/web.py
68
cps/web.py
|
@ -360,9 +360,9 @@ def get_sort_function(sort, data):
|
||||||
if sort == 'old':
|
if sort == 'old':
|
||||||
order = [db.Books.timestamp]
|
order = [db.Books.timestamp]
|
||||||
if sort == 'authaz':
|
if sort == 'authaz':
|
||||||
order = [db.Books.author_sort.asc()]
|
order = [db.Books.author_sort.asc(), db.Series.name, db.Books.series_index]
|
||||||
if sort == 'authza':
|
if sort == 'authza':
|
||||||
order = [db.Books.author_sort.desc()]
|
order = [db.Books.author_sort.desc(), db.Series.name.desc(), db.Books.series_index.desc()]
|
||||||
if sort == 'seriesasc':
|
if sort == 'seriesasc':
|
||||||
order = [db.Books.series_index.asc()]
|
order = [db.Books.series_index.asc()]
|
||||||
if sort == 'seriesdesc':
|
if sort == 'seriesdesc':
|
||||||
|
@ -410,7 +410,10 @@ def render_books_list(data, sort, book_id, page):
|
||||||
return render_adv_search_results(term, offset, order, config.config_books_per_page)
|
return render_adv_search_results(term, offset, order, config.config_books_per_page)
|
||||||
else:
|
else:
|
||||||
website = data or "newest"
|
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,
|
||||||
|
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,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
title=_(u"Books"), page=website)
|
title=_(u"Books"), page=website)
|
||||||
|
|
||||||
|
@ -509,8 +512,10 @@ def render_author_books(page, author_id, order):
|
||||||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
||||||
category="error")
|
category="error")
|
||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
if constants.sqlalchemy_version2:
|
||||||
author = calibre_db.session.query(db.Authors).get(author_id)
|
author = calibre_db.session.get(db.Authors, author_id)
|
||||||
|
else:
|
||||||
|
author = calibre_db.session.query(db.Authors).get(author_id)
|
||||||
author_name = author.name.replace('|', ',')
|
author_name = author.name.replace('|', ',')
|
||||||
|
|
||||||
author_info = None
|
author_info = None
|
||||||
|
@ -713,7 +718,8 @@ def render_prepare_search_form(cc):
|
||||||
|
|
||||||
|
|
||||||
def render_search_results(term, offset=None, order=None, limit=None):
|
def render_search_results(term, offset=None, order=None, limit=None):
|
||||||
entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit)
|
join = db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
|
||||||
|
entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit, *join)
|
||||||
return render_title_template('search.html',
|
return render_title_template('search.html',
|
||||||
searchterm=term,
|
searchterm=term,
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
|
@ -775,8 +781,10 @@ def list_books():
|
||||||
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
|
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
|
||||||
join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
|
join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
|
||||||
elif sort == "authors":
|
elif sort == "authors":
|
||||||
order = [db.Authors.name.asc()] if order == "asc" else [db.Authors.name.desc()]
|
order = [db.Authors.name.asc(), db.Series.name, db.Books.series_index] if order == "asc" \
|
||||||
join = db.books_authors_link,db.Books.id == db.books_authors_link.c.book, db.Authors
|
else [db.Authors.name.desc(), db.Series.name.desc(), db.Books.series_index.desc()]
|
||||||
|
join = db.books_authors_link, db.Books.id == db.books_authors_link.c.book, db.Authors, \
|
||||||
|
db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
|
||||||
elif sort == "languages":
|
elif sort == "languages":
|
||||||
order = [db.Languages.lang_code.asc()] if order == "asc" else [db.Languages.lang_code.desc()]
|
order = [db.Languages.lang_code.asc()] if order == "asc" else [db.Languages.lang_code.desc()]
|
||||||
join = db.books_languages_link,db.Books.id == db.books_languages_link.c.book, db.Languages
|
join = db.books_languages_link,db.Books.id == db.books_languages_link.c.book, db.Languages
|
||||||
|
@ -793,7 +801,7 @@ def list_books():
|
||||||
filtered_count = len(books)
|
filtered_count = len(books)
|
||||||
else:
|
else:
|
||||||
books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
|
books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
|
||||||
entries = calibre_db.get_checkbox_sorted(books, state, off, limit,order)
|
entries = calibre_db.get_checkbox_sorted(books, state, off, limit, order)
|
||||||
elif search:
|
elif search:
|
||||||
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit, *join)
|
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit, *join)
|
||||||
else:
|
else:
|
||||||
|
@ -1242,7 +1250,9 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||||
|
|
||||||
cc = get_cc_columns(filter_config_custom_read=True)
|
cc = get_cc_columns(filter_config_custom_read=True)
|
||||||
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||||
q = calibre_db.session.query(db.Books).filter(calibre_db.common_filters(True))
|
q = calibre_db.session.query(db.Books).outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\
|
||||||
|
.outerjoin(db.Series)\
|
||||||
|
.filter(calibre_db.common_filters(True))
|
||||||
|
|
||||||
# parse multiselects to a complete dict
|
# parse multiselects to a complete dict
|
||||||
tags = dict()
|
tags = dict()
|
||||||
|
@ -1591,15 +1601,14 @@ def change_profile(kobo_support, local_oauth_check, oauth_status, translations,
|
||||||
if to_save.get("password"):
|
if to_save.get("password"):
|
||||||
current_user.password = generate_password_hash(to_save["password"])
|
current_user.password = generate_password_hash(to_save["password"])
|
||||||
try:
|
try:
|
||||||
if to_save.get("allowed_tags", current_user.allowed_tags) != current_user.allowed_tags:
|
|
||||||
current_user.allowed_tags = to_save["allowed_tags"].strip()
|
|
||||||
if to_save.get("kindle_mail", current_user.kindle_mail) != current_user.kindle_mail:
|
if to_save.get("kindle_mail", current_user.kindle_mail) != current_user.kindle_mail:
|
||||||
current_user.kindle_mail = valid_email(to_save["kindle_mail"])
|
current_user.kindle_mail = valid_email(to_save["kindle_mail"])
|
||||||
if to_save.get("email", current_user.email) != current_user.email:
|
if to_save.get("email", current_user.email) != current_user.email:
|
||||||
current_user.email = check_email(to_save["email"])
|
current_user.email = check_email(to_save["email"])
|
||||||
if to_save.get("name", current_user.name) != current_user.name:
|
if current_user.role_admin():
|
||||||
# Query User name, if not existing, change
|
if to_save.get("name", current_user.name) != current_user.name:
|
||||||
current_user.name = check_username(to_save["name"])
|
# Query User name, if not existing, change
|
||||||
|
current_user.name = check_username(to_save["name"])
|
||||||
current_user.random_books = 1 if to_save.get("show_random") == "on" else 0
|
current_user.random_books = 1 if to_save.get("show_random") == "on" else 0
|
||||||
if to_save.get("default_language"):
|
if to_save.get("default_language"):
|
||||||
current_user.default_language = to_save["default_language"]
|
current_user.default_language = to_save["default_language"]
|
||||||
|
@ -1609,10 +1618,16 @@ def change_profile(kobo_support, local_oauth_check, oauth_status, translations,
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
flash(str(ex), category="error")
|
flash(str(ex), category="error")
|
||||||
return render_title_template("user_edit.html", content=current_user,
|
return render_title_template("user_edit.html",
|
||||||
title=_(u"%(name)s's profile", name=current_user.name), page="me",
|
content=current_user,
|
||||||
|
translations=translations,
|
||||||
|
profile=1,
|
||||||
|
languages=languages,
|
||||||
|
title=_(u"%(name)s's profile", name=current_user.name),
|
||||||
|
page="me",
|
||||||
kobo_support=kobo_support,
|
kobo_support=kobo_support,
|
||||||
registered_oauth=local_oauth_check, oauth_status=oauth_status)
|
registered_oauth=local_oauth_check,
|
||||||
|
oauth_status=oauth_status)
|
||||||
|
|
||||||
val = 0
|
val = 0
|
||||||
for key, __ in to_save.items():
|
for key, __ in to_save.items():
|
||||||
|
@ -1684,28 +1699,33 @@ def read_book(book_id, book_format):
|
||||||
ub.Bookmark.format == book_format.upper())).first()
|
ub.Bookmark.format == book_format.upper())).first()
|
||||||
if book_format.lower() == "epub":
|
if book_format.lower() == "epub":
|
||||||
log.debug(u"Start epub reader for %d", book_id)
|
log.debug(u"Start epub reader for %d", book_id)
|
||||||
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
|
return render_title_template('read.html', bookid=book_id, title=book.title, bookmark=bookmark)
|
||||||
elif book_format.lower() == "pdf":
|
elif book_format.lower() == "pdf":
|
||||||
log.debug(u"Start pdf reader for %d", book_id)
|
log.debug(u"Start pdf reader for %d", book_id)
|
||||||
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
|
return render_title_template('readpdf.html', pdffile=book_id, title=book.title)
|
||||||
elif book_format.lower() == "txt":
|
elif book_format.lower() == "txt":
|
||||||
log.debug(u"Start txt reader for %d", book_id)
|
log.debug(u"Start txt reader for %d", book_id)
|
||||||
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
|
return render_title_template('readtxt.html', txtfile=book_id, title=book.title)
|
||||||
elif book_format.lower() == "djvu":
|
elif book_format.lower() == "djvu":
|
||||||
log.debug(u"Start djvu reader for %d", book_id)
|
log.debug(u"Start djvu reader for %d", book_id)
|
||||||
return render_title_template('readdjvu.html', djvufile=book_id, title=_(u"Read a Book"))
|
return render_title_template('readdjvu.html', djvufile=book_id, title=book.title)
|
||||||
else:
|
else:
|
||||||
for fileExt in constants.EXTENSIONS_AUDIO:
|
for fileExt in constants.EXTENSIONS_AUDIO:
|
||||||
if book_format.lower() == fileExt:
|
if book_format.lower() == fileExt:
|
||||||
entries = calibre_db.get_filtered_book(book_id)
|
entries = calibre_db.get_filtered_book(book_id)
|
||||||
log.debug(u"Start mp3 listening for %d", book_id)
|
log.debug(u"Start mp3 listening for %d", book_id)
|
||||||
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
||||||
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
entry=entries, bookmark=bookmark)
|
||||||
for fileExt in ["cbr", "cbt", "cbz"]:
|
for fileExt in ["cbr", "cbt", "cbz"]:
|
||||||
if book_format.lower() == fileExt:
|
if book_format.lower() == fileExt:
|
||||||
all_name = str(book_id)
|
all_name = str(book_id)
|
||||||
|
title = book.title
|
||||||
|
if len(book.series):
|
||||||
|
title = title + " - " + book.series[0].name
|
||||||
|
if book.series_index:
|
||||||
|
title = title + " #" + '{0:.2f}'.format(book.series_index).rstrip('0').rstrip('.')
|
||||||
log.debug(u"Start comic reader for %d", book_id)
|
log.debug(u"Start comic reader for %d", book_id)
|
||||||
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"),
|
return render_title_template('readcbr.html', comicfile=all_name, title=title,
|
||||||
extension=fileExt)
|
extension=fileExt)
|
||||||
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible")
|
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible")
|
||||||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"), category="error")
|
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"), category="error")
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user