From a3f17deb17bcc7201eae42e517d28c70e3af8257 Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Sat, 6 Feb 2021 20:29:43 +0100 Subject: [PATCH 001/240] Added options in order to synchronize only selected shelf on Kobo device --- cps/kobo.py | 68 ++++++++++++++++++++++++----------- cps/shelf.py | 6 ++++ cps/templates/shelf_edit.html | 5 +++ cps/ub.py | 10 ++++++ 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/cps/kobo.py b/cps/kobo.py index d019e918..f114706f 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -82,6 +82,7 @@ CONNECTION_SPECIFIC_HEADERS = [ "transfer-encoding", ] + def get_kobo_activated(): return config.config_kobo_sync @@ -152,30 +153,35 @@ def HandleSyncRequest(): # in case of external changes (e.g: adding a book through Calibre). calibre_db.reconnect_db(config, ub.app_DB_path) - if sync_token.books_last_id > -1: - changed_entries = ( - calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) - .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) - .filter(db.Books.last_modified >= sync_token.books_last_modified) - .filter(db.Books.id>sync_token.books_last_id) - .filter(db.Data.format.in_(KOBO_FORMATS)) - .order_by(db.Books.last_modified) - .order_by(db.Books.id) - .limit(SYNC_ITEM_LIMIT) - ) - else: - changed_entries = ( - calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) + changed_entries = ( + calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) .filter(db.Books.last_modified > sync_token.books_last_modified) .filter(db.Data.format.in_(KOBO_FORMATS)) .order_by(db.Books.last_modified) .order_by(db.Books.id) - .limit(SYNC_ITEM_LIMIT) + ) + + if sync_token.books_last_id > -1: + changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id) + + only_kobo_shelfs = ( + calibre_db.session.query(ub.Shelf) + .filter(ub.Shelf.user_id == current_user.id) + .filter(ub.Shelf.kobo_sync) + .count() + ) > 0 + + if only_kobo_shelfs: + changed_entries = ( + changed_entries.join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id) + .join(ub.Shelf) + .filter(ub.Shelf.kobo_sync) + .distinct() ) reading_states_in_new_entitlements = [] - for book in changed_entries: + for book in changed_entries.limit(SYNC_ITEM_LIMIT): formats = [data.format for data in book.Books.data] if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats: helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.nickname) @@ -238,7 +244,7 @@ def HandleSyncRequest(): }) new_reading_state_last_modified = max(new_reading_state_last_modified, kobo_reading_state.last_modified) - sync_shelves(sync_token, sync_results) + sync_shelves(sync_token, sync_results, only_kobo_shelfs=only_kobo_shelfs) sync_token.books_last_created = new_books_last_created sync_token.books_last_modified = new_books_last_modified @@ -392,7 +398,7 @@ def get_metadata(book): book_uuid = book.uuid metadata = { - "Categories": ["00000000-0000-0000-0000-000000000001",], + "Categories": ["00000000-0000-0000-0000-000000000001", ], # "Contributors": get_author(book), "CoverImageId": book_uuid, "CrossRevisionId": book_uuid, @@ -599,7 +605,7 @@ def HandleTagRemoveItem(tag_id): # Add new, changed, or deleted shelves to the sync_results. # Note: Public shelves that aren't owned by the user aren't supported. -def sync_shelves(sync_token, sync_results): +def sync_shelves(sync_token, sync_results, only_kobo_shelfs=False): new_tags_last_modified = sync_token.tags_last_modified for shelf in ub.session.query(ub.ShelfArchive).filter(func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified, @@ -615,8 +621,28 @@ def sync_shelves(sync_token, sync_results): } }) - for shelf in ub.session.query(ub.Shelf).filter(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, - ub.Shelf.user_id == current_user.id): + extra_filters = [] + if only_kobo_shelfs: + for shelf in ub.session.query(ub.Shelf).filter( + func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, + ub.Shelf.user_id == current_user.id, + not ub.Shelf.kobo_sync + ): + sync_results.append({ + "DeletedTag": { + "Tag": { + "Id": shelf.uuid, + "LastModified": convert_to_kobo_timestamp_string(shelf.last_modified) + } + } + }) + extra_filters.append(ub.Shelf.kobo_sync) + + for shelf in ub.session.query(ub.Shelf).filter( + func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, + ub.Shelf.user_id == current_user.id, + *extra_filters + ): if not shelf_lib.check_shelf_view_permissions(shelf): continue diff --git a/cps/shelf.py b/cps/shelf.py index 5c6037ac..bde61e00 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -239,6 +239,12 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): shelf.is_public = 1 else: shelf.is_public = 0 + + if "kobo_sync" in to_save: + shelf.kobo_sync = True + else: + shelf.kobo_sync = False + if check_shelf_is_unique(shelf, to_save, shelf_id): shelf.name = to_save["title"] # shelf.last_modified = datetime.utcnow() diff --git a/cps/templates/shelf_edit.html b/cps/templates/shelf_edit.html index 934efe2b..98acbdf8 100644 --- a/cps/templates/shelf_edit.html +++ b/cps/templates/shelf_edit.html @@ -13,6 +13,11 @@ {{_('Share with Everyone')}} +
+ +
{% endif %} {% if shelf.id != None %} diff --git a/cps/ub.py b/cps/ub.py index 1969ef53..e540f107 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -268,6 +268,7 @@ class Shelf(Base): name = Column(String) is_public = Column(Integer, default=0) user_id = Column(Integer, ForeignKey('user.id')) + kobo_sync = Column(Boolean, default=False) books = relationship("BookShelf", backref="ub_shelf", cascade="all, delete-orphan", lazy="dynamic") created = Column(DateTime, default=datetime.datetime.utcnow) last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow) @@ -504,6 +505,15 @@ def migrate_Database(session): for book_shelf in session.query(BookShelf).all(): book_shelf.date_added = datetime.datetime.now() session.commit() + + try: + session.query(exists().where(Shelf.kobo_sync)).scalar() + except exc.OperationalError: + with engine.connect() as conn: + + conn.execute("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false") + session.commit() + try: # Handle table exists, but no content cnt = session.query(Registration).count() From 8fe762709bb16a54df47e769a54c859d2017296a Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Sat, 6 Feb 2021 21:15:36 +0100 Subject: [PATCH 002/240] Fix mistake --- cps/kobo.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cps/kobo.py b/cps/kobo.py index f114706f..567e202e 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -165,14 +165,14 @@ def HandleSyncRequest(): if sync_token.books_last_id > -1: changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id) - only_kobo_shelfs = ( + only_kobo_shelves = ( calibre_db.session.query(ub.Shelf) .filter(ub.Shelf.user_id == current_user.id) .filter(ub.Shelf.kobo_sync) .count() ) > 0 - if only_kobo_shelfs: + if only_kobo_shelves: changed_entries = ( changed_entries.join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id) .join(ub.Shelf) @@ -244,7 +244,7 @@ def HandleSyncRequest(): }) new_reading_state_last_modified = max(new_reading_state_last_modified, kobo_reading_state.last_modified) - sync_shelves(sync_token, sync_results, only_kobo_shelfs=only_kobo_shelfs) + sync_shelves(sync_token, sync_results, only_kobo_shelves=only_kobo_shelves) sync_token.books_last_created = new_books_last_created sync_token.books_last_modified = new_books_last_modified @@ -605,7 +605,7 @@ def HandleTagRemoveItem(tag_id): # Add new, changed, or deleted shelves to the sync_results. # Note: Public shelves that aren't owned by the user aren't supported. -def sync_shelves(sync_token, sync_results, only_kobo_shelfs=False): +def sync_shelves(sync_token, sync_results, only_kobo_shelves=False): new_tags_last_modified = sync_token.tags_last_modified for shelf in ub.session.query(ub.ShelfArchive).filter(func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified, @@ -622,7 +622,7 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelfs=False): }) extra_filters = [] - if only_kobo_shelfs: + if only_kobo_shelves: for shelf in ub.session.query(ub.Shelf).filter( func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, ub.Shelf.user_id == current_user.id, From 69b7d947741a6d602809d9d690a6d987a788a49f Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Sun, 7 Feb 2021 00:19:24 +0100 Subject: [PATCH 003/240] Fixes and remove shelf kobo sync flag when kobo sync disabled --- cps/shelf.py | 12 +++++++----- cps/templates/shelf_edit.html | 9 ++++++--- cps/ub.py | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cps/shelf.py b/cps/shelf.py index bde61e00..c8cf1dc2 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -30,7 +30,7 @@ from flask_login import login_required, current_user from sqlalchemy.sql.expression import func, true from sqlalchemy.exc import OperationalError, InvalidRequestError -from . import logger, ub, calibre_db, db +from . import logger, ub, calibre_db, db, config from .render_template import render_title_template from .usermanagement import login_required_if_no_ano @@ -240,10 +240,8 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): else: shelf.is_public = 0 - if "kobo_sync" in to_save: + if config.config_kobo_sync and "kobo_sync" in to_save: shelf.kobo_sync = True - else: - shelf.kobo_sync = False if check_shelf_is_unique(shelf, to_save, shelf_id): shelf.name = to_save["title"] @@ -269,7 +267,11 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): ub.session.rollback() log.debug_or_exception(e) flash(_(u"There was an error"), category="error") - return render_title_template('shelf_edit.html', shelf=shelf, title=title, page=page) + return render_title_template('shelf_edit.html', + shelf=shelf, + title=title, + page=page, + kobo_sync_enabled=config.config_kobo_sync) def check_shelf_is_unique(shelf, to_save, shelf_id=False): diff --git a/cps/templates/shelf_edit.html b/cps/templates/shelf_edit.html index 98acbdf8..246e45ec 100644 --- a/cps/templates/shelf_edit.html +++ b/cps/templates/shelf_edit.html @@ -13,10 +13,13 @@ {{_('Share with Everyone')}} + {% endif %} + {% if kobo_sync_enabled %}
- +
{% endif %} diff --git a/cps/ub.py b/cps/ub.py index e540f107..b773bbe9 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -498,6 +498,7 @@ def migrate_Database(session): conn.execute("ALTER TABLE shelf ADD column 'created' DATETIME") conn.execute("ALTER TABLE shelf ADD column 'last_modified' DATETIME") conn.execute("ALTER TABLE book_shelf_link ADD column 'date_added' DATETIME") + conn.execute("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false") for shelf in session.query(Shelf).all(): shelf.uuid = str(uuid.uuid4()) shelf.created = datetime.datetime.now() From 2b7c1345ee9915fd97f498bf23a0d789d3e36535 Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Sun, 7 Feb 2021 00:21:51 +0100 Subject: [PATCH 004/240] Fix disable shelf kobo sync --- cps/shelf.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/cps/shelf.py b/cps/shelf.py index c8cf1dc2..46468cc3 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -21,20 +21,20 @@ # along with this program. If not, see . from __future__ import division, print_function, unicode_literals -from datetime import datetime + import sys +from datetime import datetime -from flask import Blueprint, request, flash, redirect, url_for +from flask import Blueprint, flash, redirect, request, url_for from flask_babel import gettext as _ -from flask_login import login_required, current_user +from flask_login import current_user, login_required +from sqlalchemy.exc import InvalidRequestError, OperationalError from sqlalchemy.sql.expression import func, true -from sqlalchemy.exc import OperationalError, InvalidRequestError -from . import logger, ub, calibre_db, db, config +from . import calibre_db, config, db, logger, ub from .render_template import render_title_template from .usermanagement import login_required_if_no_ano - shelf = Blueprint('shelf', __name__) log = logger.create() @@ -240,8 +240,11 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): else: shelf.is_public = 0 - if config.config_kobo_sync and "kobo_sync" in to_save: - shelf.kobo_sync = True + if config.config_kobo_sync: + if "kobo_sync" in to_save: + shelf.kobo_sync = True + else: + shelf.kobo_sync = False if check_shelf_is_unique(shelf, to_save, shelf_id): shelf.name = to_save["title"] @@ -358,8 +361,8 @@ def order_shelf(shelf_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() result = list() if shelf and check_shelf_view_permissions(shelf): - result = calibre_db.session.query(db.Books)\ - .join(ub.BookShelf,ub.BookShelf.book_id == db.Books.id , isouter=True) \ + result = calibre_db.session.query(db.Books) \ + .join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id, isouter=True) \ .add_columns(calibre_db.common_filters().label("visible")) \ .filter(ub.BookShelf.shelf == shelf_id).order_by(ub.BookShelf.order.asc()).all() return render_title_template('shelf_order.html', entries=result, @@ -368,7 +371,7 @@ def order_shelf(shelf_id): 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).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \ .filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all() for index, entry in enumerate(result): book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \ @@ -408,13 +411,13 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param): page = 'shelfdown.html' result, __, pagination = calibre_db.fill_indexpage(page_no, pagesize, - db.Books, - ub.BookShelf.shelf == shelf_id, - [ub.BookShelf.order.asc()], - ub.BookShelf,ub.BookShelf.book_id == db.Books.id) + db.Books, + ub.BookShelf.shelf == shelf_id, + [ub.BookShelf.order.asc()], + ub.BookShelf, ub.BookShelf.book_id == db.Books.id) # delete chelf entries where book is not existent anymore, can happen if book is deleted outside calibre-web - wrong_entries = calibre_db.session.query(ub.BookShelf)\ - .join(db.Books, ub.BookShelf.book_id == db.Books.id, isouter=True)\ + wrong_entries = calibre_db.session.query(ub.BookShelf) \ + .join(db.Books, ub.BookShelf.book_id == db.Books.id, isouter=True) \ .filter(db.Books.id == None).all() for entry in wrong_entries: log.info('Not existing book {} in {} deleted'.format(entry.book_id, shelf)) From 6014b04b2a6ab5cd4790a827005d9727431a80c7 Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Mon, 8 Feb 2021 20:10:12 +0100 Subject: [PATCH 005/240] Use BookShelf added_date as date reference --- cps/kobo.py | 79 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/cps/kobo.py b/cps/kobo.py index 567e202e..141d745d 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -153,32 +153,42 @@ def HandleSyncRequest(): # in case of external changes (e.g: adding a book through Calibre). calibre_db.reconnect_db(config, ub.app_DB_path) - changed_entries = ( - calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) - .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) - .filter(db.Books.last_modified > sync_token.books_last_modified) - .filter(db.Data.format.in_(KOBO_FORMATS)) - .order_by(db.Books.last_modified) - .order_by(db.Books.id) - ) - - if sync_token.books_last_id > -1: - changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id) - only_kobo_shelves = ( - calibre_db.session.query(ub.Shelf) - .filter(ub.Shelf.user_id == current_user.id) - .filter(ub.Shelf.kobo_sync) - .count() - ) > 0 + calibre_db.session.query(ub.Shelf) + .filter(ub.Shelf.user_id == current_user.id) + .filter(ub.Shelf.kobo_sync) + .count() + ) > 0 if only_kobo_shelves: changed_entries = ( - changed_entries.join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id) + calibre_db.session.query(db.Books, + ub.ArchivedBook.last_modified, + ub.BookShelf.date_added, + ub.ArchivedBook.is_archived) + .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) + .filter(or_(db.Books.last_modified > sync_token.books_last_modified, + ub.BookShelf.date_added > sync_token.books_last_modified)) + .filter(db.Data.format.in_(KOBO_FORMATS)) + .order_by(db.Books.id) + .order_by('last_modified') + .join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id) .join(ub.Shelf) .filter(ub.Shelf.kobo_sync) .distinct() ) + else: + changed_entries = ( + calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) + .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) + .filter(db.Books.last_modified > sync_token.books_last_modified) + .filter(db.Data.format.in_(KOBO_FORMATS)) + .order_by(db.Books.last_modified) + .order_by(db.Books.id) + ) + + if sync_token.books_last_id > -1: + changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id) reading_states_in_new_entitlements = [] for book in changed_entries.limit(SYNC_ITEM_LIMIT): @@ -197,7 +207,14 @@ def HandleSyncRequest(): new_reading_state_last_modified = max(new_reading_state_last_modified, kobo_reading_state.last_modified) reading_states_in_new_entitlements.append(book.Books.id) - if book.Books.timestamp > sync_token.books_last_created: + ts_created = book.Books.timestamp + + try: + ts_created = max(ts_created, book.date_added) + except AttributeError: + pass + + if ts_created > sync_token.books_last_created: sync_results.append({"NewEntitlement": entitlement}) else: sync_results.append({"ChangedEntitlement": entitlement}) @@ -205,7 +222,14 @@ def HandleSyncRequest(): new_books_last_modified = max( book.Books.last_modified, new_books_last_modified ) - new_books_last_created = max(book.Books.timestamp, new_books_last_created) + try: + new_books_last_modified = max( + new_books_last_modified, book.date_added + ) + except AttributeError: + pass + + new_books_last_created = max(ts_created, new_books_last_created) max_change = (changed_entries .from_self() @@ -608,10 +632,10 @@ def HandleTagRemoveItem(tag_id): def sync_shelves(sync_token, sync_results, only_kobo_shelves=False): new_tags_last_modified = sync_token.tags_last_modified - for shelf in ub.session.query(ub.ShelfArchive).filter(func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified, - ub.ShelfArchive.user_id == current_user.id): - new_tags_last_modified = max(shelf.last_modified, new_tags_last_modified) - + for shelf in ub.session.query(ub.ShelfArchive).filter( + func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified, + ub.ShelfArchive.user_id == current_user.id + ): sync_results.append({ "DeletedTag": { "Tag": { @@ -638,11 +662,12 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False): }) extra_filters.append(ub.Shelf.kobo_sync) - for shelf in ub.session.query(ub.Shelf).filter( - func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, + for shelf in ub.session.query(ub.Shelf).join(ub.BookShelf).filter( + or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, + ub.BookShelf.date_added > sync_token.tags_last_modified), ub.Shelf.user_id == current_user.id, *extra_filters - ): + ).distinct().order_by(func.datetime(ub.Shelf.last_modified).asc()): if not shelf_lib.check_shelf_view_permissions(shelf): continue From 24bbf226a14304db30c7ed95ec3567fdde3e3a59 Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Sun, 14 Mar 2021 11:29:23 +0100 Subject: [PATCH 006/240] Sync reading state only for books on kobo shelves --- cps/kobo.py | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/cps/kobo.py b/cps/kobo.py index 141d745d..19122314 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -171,7 +171,7 @@ def HandleSyncRequest(): ub.BookShelf.date_added > sync_token.books_last_modified)) .filter(db.Data.format.in_(KOBO_FORMATS)) .order_by(db.Books.id) - .order_by('last_modified') + .order_by(ub.ArchivedBook.last_modified) .join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id) .join(ub.Shelf) .filter(ub.Shelf.kobo_sync) @@ -253,11 +253,36 @@ def HandleSyncRequest(): books_last_id = -1 # generate reading state data + changed_reading_states = ub.session.query(ub.KoboReadingState) + + if only_kobo_shelves: + changed_reading_states = ( + changed_reading_states.join(ub.BookShelf, ub.KoboReadingState.book_id == ub.BookShelf.book_id) + .join(ub.Shelf) + .filter( + ub.Shelf.kobo_sync, + or_( + func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified, + ub.BookShelf.date_added > sync_token.books_last_modified + ) + ) + ).distinct() + + else: + changed_reading_states = ( + changed_reading_states.filter( + func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified + ) + ) changed_reading_states = ( - ub.session.query(ub.KoboReadingState) - .filter(and_(func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified, - ub.KoboReadingState.user_id == current_user.id, - ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements)))) + changed_reading_states.filter( + and_( + ub.KoboReadingState.user_id == current_user.id, + ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements) + ) + ) + ) + for kobo_reading_state in changed_reading_states.all(): book = calibre_db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none() if book: From 59ebc1af8a505d31a1a3d37e8c5081668faa65b3 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 08:19:54 +0100 Subject: [PATCH 007/240] Code refactoring --- cps/admin.py | 89 ++++++++++++++++-------------- cps/db.py | 138 ++++++++++++++++++++++++---------------------- cps/editbooks.py | 83 ++++++++++++++++------------ cps/epub.py | 58 ++++++++++--------- cps/oauth_bb.py | 73 ++++++++++++------------ cps/tasks/mail.py | 23 ++++---- cps/updater.py | 93 +++++++++++++++++-------------- 7 files changed, 301 insertions(+), 256 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 9c3524da..e360d1d4 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -1495,6 +1495,51 @@ def get_updater_status(): return '' +def ldap_import_create_user(user, user_data): + user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object) + + username = user_data[user_login_field][0].decode('utf-8') + # check for duplicate username + if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first(): + # if ub.session.query(ub.User).filter(ub.User.nickname == username).first(): + log.warning("LDAP User %s Already in Database", user_data) + return 0, None + + kindlemail = '' + if 'mail' in user_data: + useremail = user_data['mail'][0].decode('utf-8') + if len(user_data['mail']) > 1: + kindlemail = user_data['mail'][1].decode('utf-8') + + else: + log.debug('No Mail Field Found in LDAP Response') + useremail = username + '@email.com' + # check for duplicate email + if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first(): + log.warning("LDAP Email %s Already in Database", user_data) + return 0, None + content = ub.User() + content.nickname = username + content.password = '' # dummy password which will be replaced by ldap one + content.email = useremail + content.kindle_mail = kindlemail + content.role = config.config_default_role + content.sidebar_view = config.config_default_show + content.allowed_tags = config.config_allowed_tags + content.denied_tags = config.config_denied_tags + content.allowed_column_value = config.config_allowed_column_value + content.denied_column_value = config.config_denied_column_value + ub.session.add(content) + try: + ub.session.commit() + return 1, None # increase no of users + except Exception as e: + log.warning("Failed to create LDAP user: %s - %s", user, e) + ub.session.rollback() + message = _(u'Failed to Create at Least One LDAP User') + return 0, message + + @admi.route('/import_ldap_users') @login_required @admin_required @@ -1534,47 +1579,11 @@ def import_ldap_users(): log.debug_or_exception(e) continue if user_data: - user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object) - - username = user_data[user_login_field][0].decode('utf-8') - # check for duplicate username - if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first(): - # if ub.session.query(ub.User).filter(ub.User.nickname == username).first(): - log.warning("LDAP User %s Already in Database", user_data) - continue - - kindlemail = '' - if 'mail' in user_data: - useremail = user_data['mail'][0].decode('utf-8') - if len(user_data['mail']) > 1: - kindlemail = user_data['mail'][1].decode('utf-8') - + user_count, message = ldap_import_create_user(user, user_data, showtext) + if message: + showtext['text'] = message else: - log.debug('No Mail Field Found in LDAP Response') - useremail = username + '@email.com' - # check for duplicate email - if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first(): - log.warning("LDAP Email %s Already in Database", user_data) - continue - content = ub.User() - content.nickname = username - content.password = '' # dummy password which will be replaced by ldap one - content.email = useremail - content.kindle_mail = kindlemail - content.role = config.config_default_role - content.sidebar_view = config.config_default_show - content.allowed_tags = config.config_allowed_tags - content.denied_tags = config.config_denied_tags - content.allowed_column_value = config.config_allowed_column_value - content.denied_column_value = config.config_denied_column_value - ub.session.add(content) - try: - ub.session.commit() - imported += 1 - except Exception as e: - log.warning("Failed to create LDAP user: %s - %s", user, e) - ub.session.rollback() - showtext['text'] = _(u'Failed to Create at Least One LDAP User') + imported += user_count else: log.warning("LDAP User: %s Not Found", user) showtext['text'] = _(u'At Least One LDAP User Not Found in Database') diff --git a/cps/db.py b/cps/db.py index ac59ac2b..fcd4a11f 100644 --- a/cps/db.py +++ b/cps/db.py @@ -442,12 +442,80 @@ class CalibreDB(): self.instances.add(self) - def initSession(self, expire_on_commit=True): self.session = self.session_factory() self.session.expire_on_commit = expire_on_commit self.update_title_sort(self.config) + @classmethod + def setup_db_cc_classes(self, cc): + cc_ids = [] + books_custom_column_links = {} + for row in cc: + if row.datatype not in cc_exceptions: + if row.datatype == 'series': + dicttable = {'__tablename__': 'books_custom_column_' + str(row.id) + '_link', + 'id': Column(Integer, primary_key=True), + 'book': Column(Integer, ForeignKey('books.id'), + primary_key=True), + 'map_value': Column('value', Integer, + ForeignKey('custom_column_' + + str(row.id) + '.id'), + primary_key=True), + 'extra': Column(Float), + 'asoc': relationship('custom_column_' + str(row.id), uselist=False), + 'value': association_proxy('asoc', 'value') + } + books_custom_column_links[row.id] = type(str('books_custom_column_' + str(row.id) + '_link'), + (Base,), dicttable) + else: + books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', + Base.metadata, + Column('book', Integer, ForeignKey('books.id'), + primary_key=True), + Column('value', Integer, + ForeignKey('custom_column_' + + str(row.id) + '.id'), + primary_key=True) + ) + cc_ids.append([row.id, row.datatype]) + + ccdict = {'__tablename__': 'custom_column_' + str(row.id), + 'id': Column(Integer, primary_key=True)} + if row.datatype == 'float': + ccdict['value'] = Column(Float) + elif row.datatype == 'int': + ccdict['value'] = Column(Integer) + elif row.datatype == 'bool': + ccdict['value'] = Column(Boolean) + else: + ccdict['value'] = Column(String) + if row.datatype in ['float', 'int', 'bool']: + ccdict['book'] = Column(Integer, ForeignKey('books.id')) + cc_classes[row.id] = type(str('custom_column_' + str(row.id)), (Base,), ccdict) + + for cc_id in cc_ids: + if (cc_id[1] == 'bool') or (cc_id[1] == 'int') or (cc_id[1] == 'float'): + setattr(Books, + 'custom_column_' + str(cc_id[0]), + relationship(cc_classes[cc_id[0]], + primaryjoin=( + Books.id == cc_classes[cc_id[0]].book), + backref='books')) + elif (cc_id[1] == 'series'): + setattr(Books, + 'custom_column_' + str(cc_id[0]), + relationship(books_custom_column_links[cc_id[0]], + backref='books')) + else: + setattr(Books, + 'custom_column_' + str(cc_id[0]), + relationship(cc_classes[cc_id[0]], + secondary=books_custom_column_links[cc_id[0]], + backref='books')) + + return cc_classes + @classmethod def setup_db(cls, config, app_db_path): cls.config = config @@ -483,72 +551,8 @@ class CalibreDB(): config.db_configured = True if not cc_classes: - cc = conn.execute(text("SELECT id, datatype FROM custom_columns")) - - cc_ids = [] - books_custom_column_links = {} - for row in cc: - if row.datatype not in cc_exceptions: - if row.datatype == 'series': - dicttable = {'__tablename__': 'books_custom_column_' + str(row.id) + '_link', - 'id': Column(Integer, primary_key=True), - 'book': Column(Integer, ForeignKey('books.id'), - primary_key=True), - 'map_value': Column('value', Integer, - ForeignKey('custom_column_' + - str(row.id) + '.id'), - primary_key=True), - 'extra': Column(Float), - 'asoc': relationship('custom_column_' + str(row.id), uselist=False), - 'value': association_proxy('asoc', 'value') - } - books_custom_column_links[row.id] = type(str('books_custom_column_' + str(row.id) + '_link'), - (Base,), dicttable) - else: - books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', - Base.metadata, - Column('book', Integer, ForeignKey('books.id'), - primary_key=True), - Column('value', Integer, - ForeignKey('custom_column_' + - str(row.id) + '.id'), - primary_key=True) - ) - cc_ids.append([row.id, row.datatype]) - - ccdict = {'__tablename__': 'custom_column_' + str(row.id), - 'id': Column(Integer, primary_key=True)} - if row.datatype == 'float': - ccdict['value'] = Column(Float) - elif row.datatype == 'int': - ccdict['value'] = Column(Integer) - elif row.datatype == 'bool': - ccdict['value'] = Column(Boolean) - else: - ccdict['value'] = Column(String) - if row.datatype in ['float', 'int', 'bool']: - ccdict['book'] = Column(Integer, ForeignKey('books.id')) - cc_classes[row.id] = type(str('custom_column_' + str(row.id)), (Base,), ccdict) - - for cc_id in cc_ids: - if (cc_id[1] == 'bool') or (cc_id[1] == 'int') or (cc_id[1] == 'float'): - setattr(Books, - 'custom_column_' + str(cc_id[0]), - relationship(cc_classes[cc_id[0]], - primaryjoin=( - Books.id == cc_classes[cc_id[0]].book), - backref='books')) - elif (cc_id[1] == 'series'): - setattr(Books, - 'custom_column_' + str(cc_id[0]), - relationship(books_custom_column_links[cc_id[0]], - backref='books')) - else: - setattr(Books, - 'custom_column_' + str(cc_id[0]), - relationship(cc_classes[cc_id[0]], - secondary=books_custom_column_links[cc_id[0]], - backref='books')) + cc = conn.execute("SELECT id, datatype FROM custom_columns") + cls.setup_db_cc_classes(cc) cls.session_factory = scoped_session(sessionmaker(autocommit=False, autoflush=True, diff --git a/cps/editbooks.py b/cps/editbooks.py index 28cad5c5..0b32c7be 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -883,6 +883,48 @@ def create_book_on_upload(modif_date, meta): calibre_db.session.flush() return db_book, input_authors, title_dir +def file_handling_on_upload(requested_file): + # check if file extension is correct + if '.' in requested_file.filename: + file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() + if file_ext not in constants.EXTENSIONS_UPLOAD and '' not in constants.EXTENSIONS_UPLOAD: + flash( + _("File extension '%(ext)s' is not allowed to be uploaded to this server", + ext=file_ext), category="error") + return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + else: + flash(_('File to be uploaded must have an extension'), category="error") + return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + + # extract metadata from file + try: + meta = uploader.upload(requested_file, config.config_rarfile_location) + except (IOError, OSError): + log.error("File %s could not saved to temp dir", requested_file.filename) + flash(_(u"File %(filename)s could not saved to temp dir", + filename=requested_file.filename), category="error") + return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + return meta, None + + +def move_coverfile(meta, db_book): + # move cover to final directory, including book id + if meta.cover: + coverfile = meta.cover + else: + coverfile = os.path.join(constants.STATIC_DIR, 'generic_cover.jpg') + new_coverpath = os.path.join(config.config_calibre_dir, db_book.path, "cover.jpg") + try: + copyfile(coverfile, new_coverpath) + if meta.cover: + os.unlink(meta.cover) + except OSError as e: + log.error("Failed to move cover file %s: %s", new_coverpath, e) + flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=new_coverpath, + error=e), + category="error") + + @editbook.route("/upload", methods=["GET", "POST"]) @login_required_if_no_ano @upload_required @@ -897,30 +939,15 @@ def upload(): calibre_db.update_title_sort(config) calibre_db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4())) - # check if file extension is correct - if '.' in requested_file.filename: - file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() - if file_ext not in constants.EXTENSIONS_UPLOAD and '' not in constants.EXTENSIONS_UPLOAD: - flash( - _("File extension '%(ext)s' is not allowed to be uploaded to this server", - ext=file_ext), category="error") - return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + response, error = file_handling_on_upload(requested_file) + if error: + return response else: - flash(_('File to be uploaded must have an extension'), category="error") - return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') - - # extract metadata from file - try: - meta = uploader.upload(requested_file, config.config_rarfile_location) - except (IOError, OSError): - log.error("File %s could not saved to temp dir", requested_file.filename) - flash(_(u"File %(filename)s could not saved to temp dir", - filename= requested_file.filename), category="error") - return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + meta = response db_book, input_authors, title_dir = create_book_on_upload(modif_date, meta) - # Comments needs book id therfore only possible after flush + # Comments needs book id therefore only possible after flush modif_date |= edit_book_comments(Markup(meta.description).unescape(), db_book) book_id = db_book.id @@ -932,21 +959,7 @@ def upload(): meta.file_path, title_dir + meta.extension) - # move cover to final directory, including book id - if meta.cover: - coverfile = meta.cover - else: - coverfile = os.path.join(constants.STATIC_DIR, 'generic_cover.jpg') - new_coverpath = os.path.join(config.config_calibre_dir, db_book.path, "cover.jpg") - try: - copyfile(coverfile, new_coverpath) - if meta.cover: - os.unlink(meta.cover) - except OSError as e: - log.error("Failed to move cover file %s: %s", new_coverpath, e) - flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=new_coverpath, - error=e), - category="error") + move_coverfile(meta, db_book) # save data to database, reread data calibre_db.session.commit() diff --git a/cps/epub.py b/cps/epub.py index 5833c2aa..428ce839 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -87,18 +87,29 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): lang = epub_metadata['language'].split('-', 1)[0].lower() epub_metadata['language'] = isoLanguages.get_lang3(lang) - series = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='calibre:series']/@content", namespaces=ns) - if len(series) > 0: - epub_metadata['series'] = series[0] - else: - epub_metadata['series'] = '' + epub_metadata = parse_epbub_series(tree, epub_metadata) - series_id = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='calibre:series_index']/@content", namespaces=ns) - if len(series_id) > 0: - epub_metadata['series_id'] = series_id[0] - else: - epub_metadata['series_id'] = '1' + coverfile = parse_ebpub_cover(ns, tree, epubZip, coverpath, tmp_file_path) + if not epub_metadata['title']: + title = original_file_name + else: + title = epub_metadata['title'] + + return BookMeta( + file_path=tmp_file_path, + extension=original_file_extension, + title=title.encode('utf-8').decode('utf-8'), + author=epub_metadata['creator'].encode('utf-8').decode('utf-8'), + cover=coverfile, + description=epub_metadata['description'], + tags=epub_metadata['subject'].encode('utf-8').decode('utf-8'), + series=epub_metadata['series'].encode('utf-8').decode('utf-8'), + series_id=epub_metadata['series_id'].encode('utf-8').decode('utf-8'), + languages=epub_metadata['language'], + publisher="") + +def parse_ebpub_cover(ns, tree, epubZip, coverpath, tmp_file_path): coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover-image']/@href", namespaces=ns) coverfile = None if len(coversection) > 0: @@ -126,21 +137,18 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): coverfile = extractCover(epubZip, filename, "", tmp_file_path) else: coverfile = extractCover(epubZip, coversection[0], coverpath, tmp_file_path) + return coverfile - if not epub_metadata['title']: - title = original_file_name +def parse_epbub_series(tree, epub_metadata): + series = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='calibre:series']/@content", namespaces=ns) + if len(series) > 0: + epub_metadata['series'] = series[0] else: - title = epub_metadata['title'] + epub_metadata['series'] = '' - return BookMeta( - file_path=tmp_file_path, - extension=original_file_extension, - title=title.encode('utf-8').decode('utf-8'), - author=epub_metadata['creator'].encode('utf-8').decode('utf-8'), - cover=coverfile, - description=epub_metadata['description'], - tags=epub_metadata['subject'].encode('utf-8').decode('utf-8'), - series=epub_metadata['series'].encode('utf-8').decode('utf-8'), - series_id=epub_metadata['series_id'].encode('utf-8').decode('utf-8'), - languages=epub_metadata['language'], - publisher="") + series_id = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='calibre:series_index']/@content", namespaces=ns) + if len(series_id) > 0: + epub_metadata['series_id'] = series_id[0] + else: + epub_metadata['series_id'] = '1' + return epub_metadata diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index 1fd7c9b1..597c864c 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -299,39 +299,6 @@ if ub.oauth_support: ) # ToDo: Translate flash(msg, category="error") - - @oauth.route('/link/github') - @oauth_required - def github_login(): - if not github.authorized: - return redirect(url_for('github.login')) - account_info = github.get('/user') - if account_info.ok: - account_info_json = account_info.json() - return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github') - flash(_(u"GitHub Oauth error, please retry later."), category="error") - return redirect(url_for('web.login')) - - - @oauth.route('/unlink/github', methods=["GET"]) - @login_required - def github_login_unlink(): - return unlink_oauth(oauthblueprints[0]['id']) - - - @oauth.route('/link/google') - @oauth_required - def google_login(): - if not google.authorized: - return redirect(url_for("google.login")) - resp = google.get("/oauth2/v2/userinfo") - if resp.ok: - account_info_json = resp.json() - return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google') - flash(_(u"Google Oauth error, please retry later."), category="error") - return redirect(url_for('web.login')) - - @oauth_error.connect_via(oauthblueprints[1]['blueprint']) def google_error(blueprint, error, error_description=None, error_uri=None): msg = ( @@ -346,7 +313,39 @@ if ub.oauth_support: flash(msg, category="error") - @oauth.route('/unlink/google', methods=["GET"]) - @login_required - def google_login_unlink(): - return unlink_oauth(oauthblueprints[1]['id']) +@oauth.route('/link/github') +@oauth_required +def github_login(): + if not github.authorized: + return redirect(url_for('github.login')) + account_info = github.get('/user') + if account_info.ok: + account_info_json = account_info.json() + return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github') + flash(_(u"GitHub Oauth error, please retry later."), category="error") + return redirect(url_for('web.login')) + + +@oauth.route('/unlink/github', methods=["GET"]) +@login_required +def github_login_unlink(): + return unlink_oauth(oauthblueprints[0]['id']) + + +@oauth.route('/link/google') +@oauth_required +def google_login(): + if not google.authorized: + return redirect(url_for("google.login")) + resp = google.get("/oauth2/v2/userinfo") + if resp.ok: + account_info_json = resp.json() + return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google') + flash(_(u"Google Oauth error, please retry later."), category="error") + return redirect(url_for('web.login')) + + +@oauth.route('/unlink/google', methods=["GET"]) +@login_required +def google_login_unlink(): + return unlink_oauth(oauthblueprints[1]['id']) diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 4f7ed777..c1def491 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -110,8 +110,7 @@ class TaskEmail(CalibreTask): self.results = dict() - def run(self, worker_thread): - # create MIME message + def prepare_message(self): msg = MIMEMultipart() msg['Subject'] = self.subject msg['Message-Id'] = make_msgid('calibre-web') @@ -128,19 +127,22 @@ class TaskEmail(CalibreTask): msg['From'] = self.settings["mail_from"] msg['To'] = self.recipent + # convert MIME message to string + fp = StringIO() + gen = Generator(fp, mangle_from_=False) + gen.flatten(msg) + return fp.getvalue() + + def run(self, worker_thread): + # create MIME message + msg = self.prepare_message() use_ssl = int(self.settings.get('mail_use_ssl', 0)) try: - # convert MIME message to string - fp = StringIO() - gen = Generator(fp, mangle_from_=False) - gen.flatten(msg) - msg = fp.getvalue() - # send email timeout = 600 # set timeout to 5mins - # redirect output to logfile on python2 pn python3 debugoutput is caught with overwritten + # redirect output to logfile on python2 on python3 debugoutput is caught with overwritten # _print_debug function if sys.version_info < (3, 0): org_smtpstderr = smtplib.stderr @@ -169,7 +171,6 @@ class TaskEmail(CalibreTask): except (MemoryError) as e: log.debug_or_exception(e) self._handleError(u'MemoryError sending email: ' + str(e)) - # return None except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e: if hasattr(e, "smtp_error"): text = e.smtp_error.decode('utf-8').replace("\n", '. ') @@ -181,10 +182,8 @@ class TaskEmail(CalibreTask): log.debug_or_exception(e) text = '' self._handleError(u'Smtplib Error sending email: ' + text) - # return None except (socket.error) as e: self._handleError(u'Socket Error sending email: ' + e.strerror) - # return None @property diff --git a/cps/updater.py b/cps/updater.py index 278f437b..956ae33b 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -403,6 +403,52 @@ class Updater(threading.Thread): return json.dumps(status) return '' + def _stable_updater_set_status(self, i, newer, status, parents, commit): + if i == -1 and newer == False: + status.update({ + 'update': True, + 'success': True, + 'message': _( + u'Click on the button below to update to the latest stable version.'), + 'history': parents + }) + self.updateFile = commit[0]['zipball_url'] + elif i == -1 and newer == True: + status.update({ + 'update': True, + 'success': True, + 'message': _(u'A new update is available. Click on the button below to ' + u'update to version: %(version)s', version=commit[0]['tag_name']), + 'history': parents + }) + self.updateFile = commit[0]['zipball_url'] + return status + + def _stable_updater_parse_major_version(self, commit, i, parents, current_version, status): + if int(commit[i + 1]['tag_name'].split('.')[1]) == current_version[1]: + parents.append([commit[i]['tag_name'], + commit[i]['body'].replace('\r\n', '

').replace('\n', '

')]) + status.update({ + 'update': True, + 'success': True, + 'message': _(u'A new update is available. Click on the button below to ' + u'update to version: %(version)s', version=commit[i]['tag_name']), + 'history': parents + }) + self.updateFile = commit[i]['zipball_url'] + else: + parents.append([commit[i + 1]['tag_name'], + commit[i + 1]['body'].replace('\r\n', '

').replace('\n', '

')]) + status.update({ + 'update': True, + 'success': True, + 'message': _(u'A new update is available. Click on the button below to ' + u'update to version: %(version)s', version=commit[i + 1]['tag_name']), + 'history': parents + }) + self.updateFile = commit[i + 1]['zipball_url'] + return status, parents + def _stable_available_updates(self, request_method): if request_method == "GET": parents = [] @@ -464,48 +510,15 @@ class Updater(threading.Thread): # before major update if i == (len(commit) - 1): i -= 1 - if int(commit[i+1]['tag_name'].split('.')[1]) == current_version[1]: - parents.append([commit[i]['tag_name'], - commit[i]['body'].replace('\r\n', '

').replace('\n', '

')]) - status.update({ - 'update': True, - 'success': True, - 'message': _(u'A new update is available. Click on the button below to ' - u'update to version: %(version)s', version=commit[i]['tag_name']), - 'history': parents - }) - self.updateFile = commit[i]['zipball_url'] - else: - parents.append([commit[i+1]['tag_name'], - commit[i+1]['body'].replace('\r\n', '

').replace('\n', '

')]) - status.update({ - 'update': True, - 'success': True, - 'message': _(u'A new update is available. Click on the button below to ' - u'update to version: %(version)s', version=commit[i+1]['tag_name']), - 'history': parents - }) - self.updateFile = commit[i+1]['zipball_url'] + status, parents = self._stable_updater_parse_major_version(self, + commit, + i, + parents, + current_version, + status) break - if i == -1 and newer == False: - status.update({ - 'update': True, - 'success': True, - 'message': _( - u'Click on the button below to update to the latest stable version.'), - 'history': parents - }) - self.updateFile = commit[0]['zipball_url'] - elif i == -1 and newer == True: - status.update({ - 'update': True, - 'success': True, - 'message': _(u'A new update is available. Click on the button below to ' - u'update to version: %(version)s', version=commit[0]['tag_name']), - 'history': parents - }) - self.updateFile = commit[0]['zipball_url'] + status = self._stable_updater_set_status(self, i, newer, status, parents, commit) return json.dumps(status) def _get_request_path(self): From 130a4ed2d309519c9807223ccf9edc59abde65a6 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 11:54:39 +0100 Subject: [PATCH 008/240] Fix opds and error logging in mail --- cps/opds.py | 2 +- cps/tasks/mail.py | 3 ++- cps/templates/feed.xml | 8 ++++---- cps/templates/index.xml | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index c66ee836..e8d3fad9 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -94,7 +94,7 @@ def feed_cc_search(query): @opds.route("/opds/search", methods=["GET"]) @requires_basic_auth_if_no_ano def feed_normal_search(): - return feed_search(request.args.get("query").strip()) + return feed_search(request.args.get("query", "").strip()) @opds.route("/opds/new") diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index c1def491..6aa18fde 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -172,6 +172,7 @@ class TaskEmail(CalibreTask): log.debug_or_exception(e) self._handleError(u'MemoryError sending email: ' + str(e)) except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e: + log.debug_or_exception(e) if hasattr(e, "smtp_error"): text = e.smtp_error.decode('utf-8').replace("\n", '. ') elif hasattr(e, "message"): @@ -179,10 +180,10 @@ class TaskEmail(CalibreTask): elif hasattr(e, "args"): text = '\n'.join(e.args) else: - log.debug_or_exception(e) text = '' self._handleError(u'Smtplib Error sending email: ' + text) except (socket.error) as e: + log.debug_or_exception(e) self._handleError(u'Socket Error sending email: ' + e.strerror) diff --git a/cps/templates/feed.xml b/cps/templates/feed.xml index 4b65b1ca..4ad1db8c 100644 --- a/cps/templates/feed.xml +++ b/cps/templates/feed.xml @@ -11,18 +11,18 @@ -{% if pagination.has_prev %} +{% if pagination and pagination.has_prev %} {% endif %} -{% if pagination.has_next %} +{% if pagination and pagination.has_next %} {% endif %} -{% if pagination.has_prev %} +{% if pagination and pagination.has_prev %} @@ -30,7 +30,7 @@ - + {{instance}} {{instance}} diff --git a/cps/templates/index.xml b/cps/templates/index.xml index 6a69d732..c6a6e8f0 100644 --- a/cps/templates/index.xml +++ b/cps/templates/index.xml @@ -8,6 +8,7 @@ + {{instance}} {{instance}} From 33bdc07f5599c4c0e1531a9b49b41a1a079094ec Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 11:57:42 +0100 Subject: [PATCH 009/240] Fix for #1845 (ods not working in Moonreader an Librera) Fix opds search with wrong parameter no longer causes error 500 --- cps/opds.py | 2 +- cps/templates/feed.xml | 8 ++++---- cps/templates/index.xml | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index c66ee836..e8d3fad9 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -94,7 +94,7 @@ def feed_cc_search(query): @opds.route("/opds/search", methods=["GET"]) @requires_basic_auth_if_no_ano def feed_normal_search(): - return feed_search(request.args.get("query").strip()) + return feed_search(request.args.get("query", "").strip()) @opds.route("/opds/new") diff --git a/cps/templates/feed.xml b/cps/templates/feed.xml index 4b65b1ca..4ad1db8c 100644 --- a/cps/templates/feed.xml +++ b/cps/templates/feed.xml @@ -11,18 +11,18 @@ -{% if pagination.has_prev %} +{% if pagination and pagination.has_prev %} {% endif %} -{% if pagination.has_next %} +{% if pagination and pagination.has_next %} {% endif %} -{% if pagination.has_prev %} +{% if pagination and pagination.has_prev %} @@ -30,7 +30,7 @@ - + {{instance}} {{instance}} diff --git a/cps/templates/index.xml b/cps/templates/index.xml index 6a69d732..c6a6e8f0 100644 --- a/cps/templates/index.xml +++ b/cps/templates/index.xml @@ -8,6 +8,7 @@ + {{instance}} {{instance}} From f4ddac16f9ea6acd719306fa81e725677bdbdbac Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 11:58:51 +0100 Subject: [PATCH 010/240] Improved error logging for #1905 --- cps/tasks/mail.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 4f7ed777..4d0b2f77 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -169,8 +169,8 @@ class TaskEmail(CalibreTask): except (MemoryError) as e: log.debug_or_exception(e) self._handleError(u'MemoryError sending email: ' + str(e)) - # return None except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e: + log.debug_or_exception(e) if hasattr(e, "smtp_error"): text = e.smtp_error.decode('utf-8').replace("\n", '. ') elif hasattr(e, "message"): @@ -178,13 +178,11 @@ class TaskEmail(CalibreTask): elif hasattr(e, "args"): text = '\n'.join(e.args) else: - log.debug_or_exception(e) text = '' self._handleError(u'Smtplib Error sending email: ' + text) - # return None except (socket.error) as e: + log.debug_or_exception(e) self._handleError(u'Socket Error sending email: ' + e.strerror) - # return None @property From e9530eda9dbfeeef79e1d4f0356f1eaa61429016 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 13:46:13 +0100 Subject: [PATCH 011/240] Bugfix from refactoring --- cps/admin.py | 2 +- cps/updater.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index e360d1d4..6cd04246 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -1579,7 +1579,7 @@ def import_ldap_users(): log.debug_or_exception(e) continue if user_data: - user_count, message = ldap_import_create_user(user, user_data, showtext) + user_count, message = ldap_import_create_user(user, user_data) if message: showtext['text'] = message else: diff --git a/cps/updater.py b/cps/updater.py index 956ae33b..9fb85bf5 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -510,15 +510,14 @@ class Updater(threading.Thread): # before major update if i == (len(commit) - 1): i -= 1 - status, parents = self._stable_updater_parse_major_version(self, - commit, + status, parents = self._stable_updater_parse_major_version(commit, i, parents, current_version, status) break - status = self._stable_updater_set_status(self, i, newer, status, parents, commit) + status = self._stable_updater_set_status(i, newer, status, parents, commit) return json.dumps(status) def _get_request_path(self): From 436f60caa96c9176b9bff41356c26e816cce1a7d Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 14:17:07 +0100 Subject: [PATCH 012/240] refactored some functions --- cps/admin.py | 2 +- cps/editbooks.py | 236 ++++++++++++++++++++++++++--------------------- 2 files changed, 133 insertions(+), 105 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 6cd04246..6242c439 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -345,7 +345,7 @@ def edit_list_user(param): else: log.error(u"Found an existing account for this e-mail address.") return _(u"Found an existing account for this e-mail address."), 400 - elif param =='kindle_mail': + elif param == 'kindle_mail': user.kindle_mail = vals['value'] elif param == 'role': if vals['value'] == 'true': diff --git a/cps/editbooks.py b/cps/editbooks.py index 0b32c7be..f22cf976 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -68,17 +68,7 @@ def edit_required(f): return inner - -# Modifies different Database objects, first check if elements have to be added to database, than check -# if elements have to be deleted, because they are no longer used -def modify_database_object(input_elements, db_book_object, db_object, db_session, db_type): - # passing input_elements not as a list may lead to undesired results - if not isinstance(input_elements, list): - raise TypeError(str(input_elements) + " should be passed as a list") - changed = False - input_elements = [x for x in input_elements if x != ''] - # we have all input element (authors, series, tags) names now - # 1. search for elements to remove +def search_objects_remove(db_book_object, db_type, input_elements): del_elements = [] for c_elements in db_book_object: found = False @@ -96,7 +86,10 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session # if the element was not found in the new list, add it to remove list if not found: del_elements.append(c_elements) - # 2. search for elements that need to be added + return del_elements + + +def search_objects_add(db_book_object, db_type, input_elements): add_elements = [] for inp_element in input_elements: found = False @@ -112,65 +105,93 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session break if not found: add_elements.append(inp_element) - # if there are elements to remove, we remove them now + return add_elements + + +def remove_objects(db_book_object, db_session, del_elements): if len(del_elements) > 0: for del_element in del_elements: db_book_object.remove(del_element) changed = True if len(del_element.books) == 0: db_session.delete(del_element) + +def add_objects(db_book_object, db_object, db_session, db_type, add_elements): + changed = False + if db_type == 'languages': + db_filter = db_object.lang_code + elif db_type == 'custom': + db_filter = db_object.value + else: + db_filter = db_object.name + for add_element in add_elements: + # check if a element with that name exists + db_element = db_session.query(db_object).filter(db_filter == add_element).first() + # if no element is found add it + # if new_element is None: + if db_type == 'author': + new_element = db_object(add_element, helper.get_sorted_author(add_element.replace('|', ',')), "") + elif db_type == 'series': + new_element = db_object(add_element, add_element) + elif db_type == 'custom': + new_element = db_object(value=add_element) + elif db_type == 'publisher': + new_element = db_object(add_element, None) + else: # db_type should be tag or language + new_element = db_object(add_element) + if db_element is None: + changed = True + db_session.add(new_element) + db_book_object.append(new_element) + else: + db_element = create_objects_for_addition(db_element, add_element, db_type) + changed = True + # add element to book + db_book_object.append(db_element) + return changed + + +def create_objects_for_addition(db_element, add_element, db_type): + if db_type == 'custom': + if db_element.value != add_element: + db_element.value = add_element + elif db_type == 'languages': + if db_element.lang_code != add_element: + db_element.lang_code = add_element + elif db_type == 'series': + if db_element.name != add_element: + db_element.name = add_element + db_element.sort = add_element + elif db_type == 'author': + if db_element.name != add_element: + db_element.name = add_element + db_element.sort = add_element.replace('|', ',') + elif db_type == 'publisher': + if db_element.name != add_element: + db_element.name = add_element + db_element.sort = None + elif db_element.name != add_element: + db_element.name = add_element + + +# Modifies different Database objects, first check if elements if elements have to be deleted, +# because they are no longer used, than check if elements have to be added to database +def modify_database_object(input_elements, db_book_object, db_object, db_session, db_type): + # passing input_elements not as a list may lead to undesired results + if not isinstance(input_elements, list): + raise TypeError(str(input_elements) + " should be passed as a list") + input_elements = [x for x in input_elements if x != ''] + # we have all input element (authors, series, tags) names now + # 1. search for elements to remove + del_elements = search_objects_remove(db_book_object, db_type, input_elements) + # 2. search for elements that need to be added + add_elements = search_objects_add(db_book_object, db_type, input_elements) + # if there are elements to remove, we remove them now + remove_objects(db_book_object, db_session, del_elements) # if there are elements to add, we add them now! if len(add_elements) > 0: - if db_type == 'languages': - db_filter = db_object.lang_code - elif db_type == 'custom': - db_filter = db_object.value - else: - db_filter = db_object.name - for add_element in add_elements: - # check if a element with that name exists - db_element = db_session.query(db_object).filter(db_filter == add_element).first() - # if no element is found add it - # if new_element is None: - if db_type == 'author': - new_element = db_object(add_element, helper.get_sorted_author(add_element.replace('|', ',')), "") - elif db_type == 'series': - new_element = db_object(add_element, add_element) - elif db_type == 'custom': - new_element = db_object(value=add_element) - elif db_type == 'publisher': - new_element = db_object(add_element, None) - else: # db_type should be tag or language - new_element = db_object(add_element) - if db_element is None: - changed = True - db_session.add(new_element) - db_book_object.append(new_element) - else: - if db_type == 'custom': - if db_element.value != add_element: - new_element.value = add_element - elif db_type == 'languages': - if db_element.lang_code != add_element: - db_element.lang_code = add_element - elif db_type == 'series': - if db_element.name != add_element: - db_element.name = add_element - db_element.sort = add_element - elif db_type == 'author': - if db_element.name != add_element: - db_element.name = add_element - db_element.sort = add_element.replace('|', ',') - elif db_type == 'publisher': - if db_element.name != add_element: - db_element.name = add_element - db_element.sort = None - elif db_element.name != add_element: - db_element.name = add_element - # add element to book - changed = True - db_book_object.append(db_element) - return changed + return add_objects(db_book_object, db_object, db_session, db_type, add_elements) + return False def modify_identifiers(input_identifiers, db_identifiers, db_session): @@ -620,6 +641,43 @@ def upload_cover(request, book): return False return None +def handle_title_on_edit(book, to_save): + # handle book title + if book.title != to_save["book_title"].rstrip().strip(): + if to_save["book_title"] == '': + to_save["book_title"] = _(u'Unknown') + book.title = to_save["book_title"].rstrip().strip() + return True + return False + +def handle_author_on_edit(book, to_save): + # handle author(s) + input_authors = to_save["author_name"].split('&') + input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors)) + # Remove duplicates in authors list + input_authors = helper.uniq(input_authors) + # we have all author names now + if input_authors == ['']: + input_authors = [_(u'Unknown')] # prevent empty Author + + change = modify_database_object(input_authors, book.authors, db.Authors, calibre_db.session, 'author') + + # Search for each author if author is in database, if not, authorname and sorted authorname is generated new + # everything then is assembled for sorted author field in database + sort_authors_list = list() + for inp in input_authors: + stored_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == inp).first() + if not stored_author: + stored_author = helper.get_sorted_author(inp) + else: + stored_author = stored_author.sort + sort_authors_list.append(helper.get_sorted_author(stored_author)) + sort_authors = ' & '.join(sort_authors_list) + if book.author_sort != sort_authors: + book.author_sort = sort_authors + change = True + return input_authors, change + @editbook.route("/admin/book/", methods=['GET', 'POST']) @login_required_if_no_ano @@ -638,7 +696,6 @@ def edit_book(book_id): if request.method != 'POST': return render_edit_book(book_id) - book = calibre_db.get_filtered_book(book_id, allow_show_archived=True) # Book not found @@ -656,7 +713,7 @@ def edit_book(book_id): # Update book edited_books_id = None - #handle book title + # handle book title if book.title != to_save["book_title"].rstrip().strip(): if to_save["book_title"] == '': to_save["book_title"] = _(u'Unknown') @@ -664,31 +721,9 @@ def edit_book(book_id): edited_books_id = book.id modif_date = True - # handle author(s) - input_authors = to_save["author_name"].split('&') - input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors)) - # Remove duplicates in authors list - input_authors = helper.uniq(input_authors) - # we have all author names now - if input_authors == ['']: - input_authors = [_(u'Unknown')] # prevent empty Author - - modif_date |= modify_database_object(input_authors, book.authors, db.Authors, calibre_db.session, 'author') - - # Search for each author if author is in database, if not, authorname and sorted authorname is generated new - # everything then is assembled for sorted author field in database - sort_authors_list = list() - for inp in input_authors: - stored_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == inp).first() - if not stored_author: - stored_author = helper.get_sorted_author(inp) - else: - stored_author = stored_author.sort - sort_authors_list.append(helper.get_sorted_author(stored_author)) - sort_authors = ' & '.join(sort_authors_list) - if book.author_sort != sort_authors: + input_authors, change = handle_author_on_edit(book, to_save) + if change: edited_books_id = book.id - book.author_sort = sort_authors modif_date = True if config.config_use_google_drive: @@ -715,10 +750,8 @@ def edit_book(book_id): # Add default series_index to book modif_date |= edit_book_series_index(to_save["series_index"], book) - # Handle book comments/description modif_date |= edit_book_comments(to_save["description"], book) - # Handle identifiers input_identifiers = identifier_list(to_save, book) modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) @@ -727,9 +760,16 @@ def edit_book(book_id): modif_date |= modification # Handle book tags modif_date |= edit_book_tags(to_save['tags'], book) - # Handle book series modif_date |= edit_book_series(to_save["series"], book) + # handle book publisher + modif_date |= edit_book_publisher(to_save['publisher'], book) + # handle book languages + modif_date |= edit_book_languages(to_save['languages'], book) + # handle book ratings + modif_date |= edit_book_ratings(to_save, book) + # handle cc data + modif_date |= edit_cc_data(book_id, book, to_save) if to_save["pubdate"]: try: @@ -739,18 +779,6 @@ def edit_book(book_id): else: book.pubdate = db.Books.DEFAULT_PUBDATE - # handle book publisher - modif_date |= edit_book_publisher(to_save['publisher'], book) - - # handle book languages - modif_date |= edit_book_languages(to_save['languages'], book) - - # handle book ratings - modif_date |= edit_book_ratings(to_save, book) - - # handle cc data - modif_date |= edit_cc_data(book_id, book, to_save) - if modif_date: book.last_modified = datetime.utcnow() calibre_db.session.merge(book) From 9864d932e08d8b5de938affb754761301e57ffe3 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 18:55:02 +0100 Subject: [PATCH 013/240] Changed user.nickname to user.name Added function to view downloads of all users for admins --- cps/__init__.py | 2 +- cps/admin.py | 76 +++++++++++++++++------------------ cps/config_sql.py | 12 +++--- cps/db.py | 9 +++-- cps/editbooks.py | 6 +-- cps/helper.py | 6 +-- cps/jinjia.py | 2 +- cps/kobo.py | 2 +- cps/kobo_auth.py | 2 +- cps/oauth_bb.py | 6 +-- cps/opds.py | 2 +- cps/remotelogin.py | 4 +- cps/render_template.py | 2 +- cps/templates/admin.html | 2 +- cps/templates/layout.html | 2 +- cps/templates/register.html | 4 +- cps/templates/user_edit.html | 6 +-- cps/templates/user_table.html | 2 +- cps/ub.py | 49 +++++++++++----------- cps/usermanagement.py | 2 +- cps/web.py | 72 ++++++++++++++++++++------------- 21 files changed, 147 insertions(+), 123 deletions(-) diff --git a/cps/__init__.py b/cps/__init__.py index 627cca0b..357e21e9 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -127,7 +127,7 @@ def get_locale(): user = getattr(g, 'user', None) # user = None if user is not None and hasattr(user, "locale"): - if user.nickname != 'Guest': # if the account is the guest account bypass the config lang settings + if user.name != 'Guest': # if the account is the guest account bypass the config lang settings return user.locale preferred = list() diff --git a/cps/admin.py b/cps/admin.py index 6242c439..004ff4ad 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -248,7 +248,7 @@ def list_users(): all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) total_count = all_user.count() if search: - users = all_user.filter(or_(func.lower(ub.User.nickname).ilike("%" + search + "%"), + users = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"), func.lower(ub.User.kindle_mail).ilike("%" + search + "%"), func.lower(ub.User.email).ilike("%" + search + "%")))\ .offset(off).limit(limit).all() @@ -332,9 +332,9 @@ def edit_list_user(param): else: return "" for user in users: - if param =='nickname': - if not ub.session.query(ub.User).filter(ub.User.nickname == vals['value']).scalar(): - user.nickname = vals['value'] + if param =='name': + if not ub.session.query(ub.User).filter(ub.User.name == vals['value']).scalar(): + user.name = vals['value'] else: log.error(u"This username is already taken") return _(u"This username is already taken"), 400 @@ -532,7 +532,7 @@ def edit_restriction(res_type, user_id): elementlist = usr.list_allowed_tags() elementlist[int(element['id'][1:])] = element['Element'] usr.allowed_tags = ','.join(elementlist) - ub.session_commit("Changed allowed tags of user {} to {}".format(usr.nickname, usr.allowed_tags)) + ub.session_commit("Changed allowed tags of user {} to {}".format(usr.name, usr.allowed_tags)) if res_type == 3: # CColumn per user if isinstance(user_id, int): usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() @@ -541,7 +541,7 @@ def edit_restriction(res_type, user_id): elementlist = usr.list_allowed_column_values() elementlist[int(element['id'][1:])] = element['Element'] usr.allowed_column_value = ','.join(elementlist) - ub.session_commit("Changed allowed columns of user {} to {}".format(usr.nickname, usr.allowed_column_value)) + ub.session_commit("Changed allowed columns of user {} to {}".format(usr.name, usr.allowed_column_value)) if element['id'].startswith('d'): if res_type == 0: # Tags as template elementlist = config.list_denied_tags() @@ -561,7 +561,7 @@ def edit_restriction(res_type, user_id): elementlist = usr.list_denied_tags() elementlist[int(element['id'][1:])] = element['Element'] usr.denied_tags = ','.join(elementlist) - ub.session_commit("Changed denied tags of user {} to {}".format(usr.nickname, usr.denied_tags)) + ub.session_commit("Changed denied tags of user {} to {}".format(usr.name, usr.denied_tags)) if res_type == 3: # CColumn per user if isinstance(user_id, int): usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() @@ -570,7 +570,7 @@ def edit_restriction(res_type, user_id): elementlist = usr.list_denied_column_values() elementlist[int(element['id'][1:])] = element['Element'] usr.denied_column_value = ','.join(elementlist) - ub.session_commit("Changed denied columns of user {} to {}".format(usr.nickname, usr.denied_column_value)) + ub.session_commit("Changed denied columns of user {} to {}".format(usr.name, usr.denied_column_value)) return "" @@ -617,10 +617,10 @@ def add_restriction(res_type, user_id): usr = current_user if 'submit_allow' in element: usr.allowed_tags = restriction_addition(element, usr.list_allowed_tags) - ub.session_commit("Changed allowed tags of user {} to {}".format(usr.nickname, usr.list_allowed_tags)) + ub.session_commit("Changed allowed tags of user {} to {}".format(usr.name, usr.list_allowed_tags)) elif 'submit_deny' in element: usr.denied_tags = restriction_addition(element, usr.list_denied_tags) - ub.session_commit("Changed denied tags of user {} to {}".format(usr.nickname, usr.list_denied_tags)) + ub.session_commit("Changed denied tags of user {} to {}".format(usr.name, usr.list_denied_tags)) if res_type == 3: # CustomC per user if isinstance(user_id, int): usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() @@ -628,11 +628,11 @@ def add_restriction(res_type, user_id): usr = current_user if 'submit_allow' in element: usr.allowed_column_value = restriction_addition(element, usr.list_allowed_column_values) - ub.session_commit("Changed allowed columns of user {} to {}".format(usr.nickname, + ub.session_commit("Changed allowed columns of user {} to {}".format(usr.name, usr.list_allowed_column_values)) elif 'submit_deny' in element: usr.denied_column_value = restriction_addition(element, usr.list_denied_column_values) - ub.session_commit("Changed denied columns of user {} to {}".format(usr.nickname, + ub.session_commit("Changed denied columns of user {} to {}".format(usr.name, usr.list_denied_column_values)) return "" @@ -664,10 +664,10 @@ def delete_restriction(res_type, user_id): usr = current_user if element['id'].startswith('a'): usr.allowed_tags = restriction_deletion(element, usr.list_allowed_tags) - ub.session_commit("Deleted allowed tags of user {}: {}".format(usr.nickname, usr.list_allowed_tags)) + ub.session_commit("Deleted allowed tags of user {}: {}".format(usr.name, usr.list_allowed_tags)) elif element['id'].startswith('d'): usr.denied_tags = restriction_deletion(element, usr.list_denied_tags) - ub.session_commit("Deleted denied tags of user {}: {}".format(usr.nickname, usr.list_allowed_tags)) + ub.session_commit("Deleted denied tags of user {}: {}".format(usr.name, usr.list_allowed_tags)) elif res_type == 3: # Columns per user if isinstance(user_id, int): usr = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() @@ -675,12 +675,12 @@ def delete_restriction(res_type, user_id): usr = current_user if element['id'].startswith('a'): usr.allowed_column_value = restriction_deletion(element, usr.list_allowed_column_values) - ub.session_commit("Deleted allowed columns of user {}: {}".format(usr.nickname, + ub.session_commit("Deleted allowed columns of user {}: {}".format(usr.name, usr.list_allowed_column_values)) elif element['id'].startswith('d'): usr.denied_column_value = restriction_deletion(element, usr.list_denied_column_values) - ub.session_commit("Deleted denied columns of user {}: {}".format(usr.nickname, + ub.session_commit("Deleted denied columns of user {}: {}".format(usr.name, usr.list_denied_column_values)) return "" @@ -1156,18 +1156,18 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): content.role = constants.selected_roles(to_save) - if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: + if not to_save["name"] or not to_save["email"] or not to_save["password"]: flash(_(u"Please fill out all fields!"), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, registered_oauth=oauth_check, kobo_support=kobo_support, title=_(u"Add new user")) content.password = generate_password_hash(to_save["password"]) - existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"].lower()) \ + existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == to_save["name"].lower()) \ .first() existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \ .first() if not existing_user and not existing_email: - content.nickname = to_save["nickname"] + content.name = to_save["name"] if config.config_public_reg and not check_valid_domain(to_save["email"]): flash(_(u"E-mail is not from valid domain"), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, @@ -1176,7 +1176,7 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): else: content.email = to_save["email"] else: - flash(_(u"Found an existing account for this e-mail address or nickname."), category="error") + flash(_(u"Found an existing account for this e-mail address or name."), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, languages=languages, title=_(u"Add new user"), page="newuser", kobo_support=kobo_support, registered_oauth=oauth_check) @@ -1187,11 +1187,11 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): content.denied_column_value = config.config_denied_column_value ub.session.add(content) ub.session.commit() - flash(_(u"User '%(user)s' created", user=content.nickname), category="success") + flash(_(u"User '%(user)s' created", user=content.name), category="success") return redirect(url_for('admin.admin')) except IntegrityError: ub.session.rollback() - flash(_(u"Found an existing account for this e-mail address or nickname."), category="error") + flash(_(u"Found an existing account for this e-mail address or name."), category="error") except OperationalError: ub.session.rollback() flash(_(u"Settings DB is not Writeable"), category="error") @@ -1203,15 +1203,15 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): ub.User.id != content.id).count(): ub.session.query(ub.User).filter(ub.User.id == content.id).delete() ub.session_commit() - flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success") + flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") return redirect(url_for('admin.admin')) else: - flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), category="error") + flash(_(u"No admin user remaining, can't delete user", nick=content.name), category="error") return redirect(url_for('admin.admin')) else: if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, ub.User.id != content.id).count() and 'admin_role' not in to_save: - flash(_(u"No admin user remaining, can't remove admin role", nick=content.nickname), category="error") + flash(_(u"No admin user remaining, can't remove admin role", nick=content.name), category="error") return redirect(url_for('admin.admin')) if "password" in to_save and to_save["password"]: @@ -1256,11 +1256,11 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): new_user=0, content=content, registered_oauth=oauth_check, - title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser") - if "nickname" in to_save and to_save["nickname"] != content.nickname: - # Query User nickname, if not existing, change - if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar(): - content.nickname = to_save["nickname"] + title=_(u"Edit User %(nick)s", nick=content.name), page="edituser") + if "name" in to_save and to_save["name"] != content.name: + # Query User name, if not existing, change + if not ub.session.query(ub.User).filter(ub.User.name == to_save["name"]).scalar(): + content.name = to_save["name"] else: flash(_(u"This username is already taken"), category="error") return render_title_template("user_edit.html", @@ -1270,14 +1270,14 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): new_user=0, content=content, registered_oauth=oauth_check, kobo_support=kobo_support, - title=_(u"Edit User %(nick)s", nick=content.nickname), + title=_(u"Edit User %(nick)s", nick=content.name), page="edituser") if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail: content.kindle_mail = to_save["kindle_mail"] try: ub.session_commit() - flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success") + flash(_(u"User '%(nick)s' updated", nick=content.name), category="success") except IntegrityError: ub.session.rollback() flash(_(u"An unknown error occured."), category="error") @@ -1337,7 +1337,7 @@ def update_mailsettings(): if to_save.get("test"): if current_user.email: - result = send_test_mail(current_user.email, current_user.nickname) + result = send_test_mail(current_user.email, current_user.name) if result is None: flash(_(u"Test e-mail successfully send to %(kindlemail)s", kindlemail=current_user.email), category="success") @@ -1356,7 +1356,7 @@ def update_mailsettings(): @admin_required def edit_user(user_id): content = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() # type: ub.User - if not content or (not config.config_anonbrowse and content.nickname == "Guest"): + if not content or (not config.config_anonbrowse and content.name == "Guest"): flash(_(u"User not found"), category="error") return redirect(url_for('admin.admin')) languages = calibre_db.speaking_language() @@ -1373,7 +1373,7 @@ def edit_user(user_id): registered_oauth=oauth_check, mail_configured=config.get_mail_server_configured(), kobo_support=kobo_support, - title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser") + title=_(u"Edit User %(nick)s", nick=content.name), page="edituser") @admi.route("/admin/resetpassword/") @@ -1500,8 +1500,8 @@ def ldap_import_create_user(user, user_data): username = user_data[user_login_field][0].decode('utf-8') # check for duplicate username - if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first(): - # if ub.session.query(ub.User).filter(ub.User.nickname == username).first(): + if ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).first(): + # if ub.session.query(ub.User).filter(ub.User.name == username).first(): log.warning("LDAP User %s Already in Database", user_data) return 0, None @@ -1519,7 +1519,7 @@ def ldap_import_create_user(user, user_data): log.warning("LDAP Email %s Already in Database", user_data) return 0, None content = ub.User() - content.nickname = username + content.name = username content.password = '' # dummy password which will be replaced by ldap one content.email = useremail content.kindle_mail = kindlemail diff --git a/cps/config_sql.py b/cps/config_sql.py index 17f1c613..f35d1dd1 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -430,12 +430,12 @@ def load_configuration(session): session.commit() conf = _ConfigSQL(session) # Migrate from global restrictions to user based restrictions - if bool(conf.config_default_show & constants.MATURE_CONTENT) and conf.config_denied_tags == "": - conf.config_denied_tags = conf.config_mature_content_tags - conf.save() - session.query(ub.User).filter(ub.User.mature_content != True). \ - update({"denied_tags": conf.config_mature_content_tags}, synchronize_session=False) - session.commit() + #if bool(conf.config_default_show & constants.MATURE_CONTENT) and conf.config_denied_tags == "": + # conf.config_denied_tags = conf.config_mature_content_tags + # conf.save() + # session.query(ub.User).filter(ub.User.mature_content != True). \ + # update({"denied_tags": conf.config_mature_content_tags}, synchronize_session=False) + # session.commit() return conf def get_flask_session_key(session): diff --git a/cps/db.py b/cps/db.py index fcd4a11f..a3b21da8 100644 --- a/cps/db.py +++ b/cps/db.py @@ -31,6 +31,7 @@ from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float from sqlalchemy.orm import relationship, sessionmaker, scoped_session from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.ext.declarative import DeclarativeMeta +from sqlalchemy.exc import OperationalError try: # Compability with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base @@ -331,7 +332,6 @@ class Books(Base): has_cover = Column(Integer, default=0) uuid = Column(String) isbn = Column(String(collation='NOCASE'), default="") - # Iccn = Column(String(collation='NOCASE'), default="") flags = Column(Integer, nullable=False, default=1) authors = relationship('Authors', secondary=books_authors_link, backref='books') @@ -551,8 +551,11 @@ class CalibreDB(): config.db_configured = True if not cc_classes: - cc = conn.execute("SELECT id, datatype FROM custom_columns") - cls.setup_db_cc_classes(cc) + try: + cc = conn.execute("SELECT id, datatype FROM custom_columns") + cls.setup_db_cc_classes(cc) + except OperationalError as e: + log.debug_or_exception(e) cls.session_factory = scoped_session(sessionmaker(autocommit=False, autoflush=True, diff --git a/cps/editbooks.py b/cps/editbooks.py index f22cf976..854b56ba 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -618,7 +618,7 @@ def upload_single_file(request, book, book_id): # Queue uploader info uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) - WorkerThread.add(current_user.nickname, TaskUpload( + WorkerThread.add(current_user.name, TaskUpload( "" + uploadText + "")) return uploader.process( @@ -997,7 +997,7 @@ def upload(): if error: flash(error, category="error") uploadText=_(u"File %(file)s uploaded", file=title) - WorkerThread.add(current_user.nickname, TaskUpload( + WorkerThread.add(current_user.name, TaskUpload( "" + uploadText + "")) if len(request.files.getlist("btn-upload")) < 2: @@ -1027,7 +1027,7 @@ def convert_bookformat(book_id): log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to) rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(), - book_format_to.upper(), current_user.nickname) + book_format_to.upper(), current_user.name) if rtn is None: flash(_(u"Book successfully queued for converting to %(book_format)s", diff --git a/cps/helper.py b/cps/helper.py index e18ae33b..d7be6dfe 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -480,8 +480,8 @@ def reset_password(user_id): password = generate_random_password() existing_user.password = generate_password_hash(password) ub.session.commit() - send_registration_mail(existing_user.email, existing_user.nickname, password, True) - return 1, existing_user.nickname + send_registration_mail(existing_user.email, existing_user.name, password, True) + return 1, existing_user.name except Exception: ub.session.rollback() return 0, None @@ -731,7 +731,7 @@ def format_runtime(runtime): def render_task_status(tasklist): renderedtasklist = list() for __, user, __, task in tasklist: - if user == current_user.nickname or current_user.role_admin(): + if user == current_user.name or current_user.role_admin(): ret = {} if task.start_time: ret['starttime'] = format_datetime(task.start_time, format='short', locale=get_locale()) diff --git a/cps/jinjia.py b/cps/jinjia.py index 688d1fba..f37dfb49 100644 --- a/cps/jinjia.py +++ b/cps/jinjia.py @@ -82,7 +82,7 @@ def formatdate_filter(val): except AttributeError as e: log.error('Babel error: %s, Current user locale: %s, Current User: %s', e, current_user.locale, - current_user.nickname + current_user.name ) return val diff --git a/cps/kobo.py b/cps/kobo.py index a9dd8865..b6920332 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -177,7 +177,7 @@ def HandleSyncRequest(): for book in changed_entries: formats = [data.format for data in book.Books.data] if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats: - helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.nickname) + helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name) kobo_reading_state = get_or_create_reading_state(book.Books.id) entitlement = { diff --git a/cps/kobo_auth.py b/cps/kobo_auth.py index 23f60fe2..a51095c8 100644 --- a/cps/kobo_auth.py +++ b/cps/kobo_auth.py @@ -155,7 +155,7 @@ def generate_auth_token(user_id): for book in books: formats = [data.format for data in book.data] if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats: - helper.convert_book_format(book.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.nickname) + helper.convert_book_format(book.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name) return render_title_template( "generate_kobo_auth_url.html", diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index 597c864c..c579c85e 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -87,7 +87,7 @@ def register_user_with_oauth(user=None): except NoResultFound: # no found, return error return - ub.session_commit("User {} with OAuth for provider {} registered".format(user.nickname, oauth_key)) + ub.session_commit("User {} with OAuth for provider {} registered".format(user.name, oauth_key)) def logout_oauth_user(): @@ -133,8 +133,8 @@ def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider # already bind with user, just login if oauth_entry.user: login_user(oauth_entry.user) - log.debug(u"You are now logged in as: '%s'", oauth_entry.user.nickname) - flash(_(u"you are now logged in as: '%(nickname)s'", nickname= oauth_entry.user.nickname), + log.debug(u"You are now logged in as: '%s'", oauth_entry.user.name) + flash(_(u"you are now logged in as: '%(nickname)s'", nickname= oauth_entry.user.name), category="success") return redirect(url_for('web.index')) else: diff --git a/cps/opds.py b/cps/opds.py index e8d3fad9..5ccd683e 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -428,7 +428,7 @@ def check_auth(username, password): username = username.encode('windows-1252') except UnicodeEncodeError: username = username.encode('utf-8') - user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == + user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.decode('utf-8').lower()).first() if bool(user and check_password_hash(str(user.password), password)): return True diff --git a/cps/remotelogin.py b/cps/remotelogin.py index d9e7388f..47d10c20 100644 --- a/cps/remotelogin.py +++ b/cps/remotelogin.py @@ -126,11 +126,11 @@ def token_verified(): login_user(user) ub.session.delete(auth_token) - ub.session_commit("User {} logged in via remotelogin, token deleted".format(user.nickname)) + ub.session_commit("User {} logged in via remotelogin, token deleted".format(user.name)) data['status'] = 'success' log.debug(u"Remote Login for userid %s succeded", user.id) - flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") + flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.name), category="success") response = make_response(json.dumps(data, ensure_ascii=False)) response.headers["Content-Type"] = "application/json; charset=utf-8" diff --git a/cps/render_template.py b/cps/render_template.py index fdd8abb7..1476a3ad 100644 --- a/cps/render_template.py +++ b/cps/render_template.py @@ -42,7 +42,7 @@ def get_sidebar_config(kwargs=None): sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot", "visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show Hot Books'), "config_show": True}) - sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list', + sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list', "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), "page": "download", "show_text": _('Show Downloaded Books'), "config_show": content}) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 576652d4..7348fe97 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -26,7 +26,7 @@ {% for user in allUser %} {% if not user.role_anonymous() or config.config_anonbrowse %} - {{user.nickname}} + {{user.name}} {{user.email}} {{user.kindle_mail}} {{user.downloads.count()}} diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 318140fa..d4e0800e 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -76,7 +76,7 @@ {% if g.user.role_admin() %}

  • {% endif %} -
  • +
  • {% if not g.user.is_anonymous %}
  • {% endif %} diff --git a/cps/templates/register.html b/cps/templates/register.html index 043378c3..db8644fb 100644 --- a/cps/templates/register.html +++ b/cps/templates/register.html @@ -5,8 +5,8 @@
    {% if not config.config_register_email %}
    - - + +
    {% endif %}
    diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 90a32acc..5cdf01bb 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -4,10 +4,10 @@

    {{title}}

    - {% if new_user or ( g.user and content.nickname != "Guest" and g.user.role_admin() ) %} + {% if new_user or ( g.user and content.name != "Guest" and g.user.role_admin() ) %}
    - - + +
    {% endif %}
    diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index 0db620cd..9817faa6 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -99,7 +99,7 @@ {{_('Edit')}} - {{ user_table_row('nickname', _('Enter Username'), _('Username'), true) }} + {{ user_table_row('name', _('Enter Username'), _('Username'), true) }} {{ user_table_row('email', _('Enter E-mail Address'), _('E-mail Address'), true) }} {{ user_table_row('kindle_mail', _('Enter Kindle E-mail Address'), _('Kindle E-mail'), true) }} {{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }} diff --git a/cps/ub.py b/cps/ub.py index ecf98679..2031a597 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -162,7 +162,7 @@ class UserBase: # ToDo: Error message def __repr__(self): - return '' % self.nickname + return '' % self.name # Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from @@ -172,7 +172,7 @@ class User(UserBase, Base): __table_args__ = {'sqlite_autoincrement': True} id = Column(Integer, primary_key=True) - nickname = Column(String(64), unique=True) + name = Column(String(64), unique=True) email = Column(String(120), unique=True, default="") role = Column(SmallInteger, default=constants.ROLE_USER) password = Column(String) @@ -182,7 +182,6 @@ class User(UserBase, Base): locale = Column(String(2), default="en") sidebar_view = Column(Integer, default=1) default_language = Column(String(3), default="all") - mature_content = Column(Boolean, default=True) denied_tags = Column(String, default="") allowed_tags = Column(String, default="") denied_column_value = Column(String, default="") @@ -218,13 +217,12 @@ class Anonymous(AnonymousUserMixin, UserBase): def loadSettings(self): data = session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS)\ .first() # type: User - self.nickname = data.nickname + self.name = data.name self.role = data.role self.id=data.id self.sidebar_view = data.sidebar_view self.default_language = data.default_language self.locale = data.locale - # self.mature_content = data.mature_content self.kindle_mail = data.kindle_mail self.denied_tags = data.denied_tags self.allowed_tags = data.allowed_tags @@ -488,7 +486,7 @@ def migrate_registration_table(engine, session): def migrate_guest_password(engine, session): try: with engine.connect() as conn: - conn.execute(text("UPDATE user SET password='' where nickname = 'Guest' and password !=''")) + conn.execute(text("UPDATE user SET password='' where name = 'Guest' and password !=''")) session.commit() except exc.OperationalError: print('Settings database is not writeable. Exiting...') @@ -594,37 +592,42 @@ def migrate_Database(session): with engine.connect() as conn: conn.execute("ALTER TABLE user ADD column `view_settings` VARCHAR(10) DEFAULT '{}'") session.commit() - - if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() \ - is None: - create_anonymous_user(session) try: - # check if one table with autoincrement is existing (should be user table) - with engine.connect() as conn: - conn.execute(text("SELECT COUNT(*) FROM sqlite_sequence WHERE name='user'")) + # check if name is in User table instead of nickname + session.query(exists().where(User.name)).scalar() except exc.OperationalError: # Create new table user_id and copy contents of table user into it with engine.connect() as conn: conn.execute(text("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," - "nickname VARCHAR(64)," + "name VARCHAR(64)," "email VARCHAR(120)," "role SMALLINT," "password VARCHAR," "kindle_mail VARCHAR(120)," "locale VARCHAR(2)," "sidebar_view INTEGER," - "default_language VARCHAR(3)," - "view_settings VARCHAR," - "UNIQUE (nickname)," + "default_language VARCHAR(3)," + "denied_tags VARCHAR," + "allowed_tags VARCHAR," + "denied_column_value VARCHAR," + "allowed_column_value VARCHAR," + "view_settings JSON," + "UNIQUE (name)," "UNIQUE (email))")) - conn.execute(text("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale," - "sidebar_view, default_language, view_settings) " + conn.execute(text("INSERT INTO user_id(id, name, email, role, password, kindle_mail,locale," + "sidebar_view, default_language, denied_tags, allowed_tags, denied_column_value, " + "allowed_column_value, view_settings)" "SELECT id, nickname, email, role, password, kindle_mail, locale," - "sidebar_view, default_language FROM user")) + "sidebar_view, default_language, denied_tags, allowed_tags, denied_column_value, " + "allowed_column_value, view_settings FROM user")) # delete old user table and rename new user_id table to user: conn.execute(text("DROP TABLE user")) conn.execute(text("ALTER TABLE user_id RENAME TO user")) session.commit() + if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() \ + is None: + create_anonymous_user(session) + migrate_guest_password(engine, session) @@ -660,7 +663,7 @@ def delete_download(book_id): # Generate user Guest (translated text), as anonymous user, no rights def create_anonymous_user(session): user = User() - user.nickname = "Guest" + user.name = "Guest" user.email = 'no@email' user.role = constants.ROLE_ANONYMOUS user.password = '' @@ -675,7 +678,7 @@ def create_anonymous_user(session): # Generate User admin with admin123 password, and access to everything def create_admin_user(session): user = User() - user.nickname = "admin" + user.name = "admin" user.role = constants.ADMIN_USER_ROLES user.sidebar_view = constants.ADMIN_USER_SIDEBAR @@ -711,7 +714,7 @@ def init_db(app_db_path): if cli.user_credentials: username, password = cli.user_credentials.split(':') - user = session.query(User).filter(func.lower(User.nickname) == username.lower()).first() + user = session.query(User).filter(func.lower(User.name) == username.lower()).first() if user: user.password = generate_password_hash(password) if session_commit() == "": diff --git a/cps/usermanagement.py b/cps/usermanagement.py index cdba4d98..ef7174c4 100644 --- a/cps/usermanagement.py +++ b/cps/usermanagement.py @@ -41,7 +41,7 @@ def login_required_if_no_ano(func): def _fetch_user_by_name(username): - return ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first() + return ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).first() @lm.user_loader diff --git a/cps/web.py b/cps/web.py index 8e4ce297..8a8bed93 100644 --- a/cps/web.py +++ b/cps/web.py @@ -371,7 +371,6 @@ def get_sort_function(sort, data): def render_books_list(data, sort, book_id, page): order = get_sort_function(sort, data) - if data == "rated": return render_rated_books(page, book_id, order=order) elif data == "discover": @@ -383,7 +382,7 @@ def render_books_list(data, sort, book_id, page): elif data == "hot": return render_hot_books(page) elif data == "download": - return render_downloaded_books(page, order) + return render_downloaded_books(page, order, book_id) elif data == "author": return render_author_books(page, book_id, order) elif data == "publisher": @@ -463,7 +462,8 @@ def render_hot_books(page): abort(404) -def render_downloaded_books(page, order): +def render_downloaded_books(page, order, user_id): + user_id = int(user_id) if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD): if current_user.show_detail_random(): random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \ @@ -474,19 +474,19 @@ def render_downloaded_books(page, order): entries, __, pagination = calibre_db.fill_indexpage(page, 0, db.Books, - ub.Downloads.user_id == int(current_user.id), + ub.Downloads.user_id == user_id, order, 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()) \ .filter(db.Books.id == book.id).first(): ub.delete_download(book.id) - + user = ub.session.query(ub.User).filter(ub.User.id == user_id).first() return render_title_template('index.html', random=random, entries=entries, pagination=pagination, - title=_(u"Downloaded books by %(user)s",user=current_user.nickname), + title=_(u"Downloaded books by %(user)s",user=user.name), page="download") else: abort(404) @@ -814,6 +814,24 @@ def author_list(): else: abort(404) +@web.route("/downloadlist") +@login_required_if_no_ano +def download_list(): + if current_user.get_view_property('download', 'dir') == 'desc': + order = ub.User.name.desc() # ToDo + else: + order = ub.User.name.asc() # ToDo + if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD) and current_user.role_admin(): + entries = ub.session.query(ub.User, func.count(ub.Downloads.book_id).label('count'))\ + .join(ub.Downloads).group_by(ub.Downloads.user_id).order_by(order).all() + charlist = ub.session.query(func.upper(func.substr(ub.User.name, 1, 1)).label('char')) \ + .filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) \ + .group_by(func.upper(func.substr(ub.User.name, 1, 1))).all() + return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, + title=_(u"Downloads"), page="downloadlist", data="download") + else: + abort(404) + @web.route("/publisher") @login_required_if_no_ano @@ -1320,7 +1338,7 @@ def send_to_kindle(book_id, book_format, convert): flash(_(u"Please configure the SMTP mail settings first..."), category="error") elif current_user.kindle_mail: result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir, - current_user.nickname) + current_user.name) if result is None: flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail), category="success") @@ -1353,7 +1371,7 @@ def register(): if config.config_register_email: nickname = to_save["email"] else: - nickname = to_save.get('nickname', None) + nickname = to_save.get('name', None) if not nickname or not to_save.get("email", None): flash(_(u"Please fill out all fields!"), category="error") return render_title_template('register.html', title=_(u"register"), page="register") @@ -1365,13 +1383,13 @@ def register(): log.warning('Registering failed for user "%s" e-mail address: %s', nickname, to_save["email"]) return render_title_template('register.html', title=_(u"register"), page="register") - existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == nickname + existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == nickname .lower()).first() existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()).first() if not existing_user and not existing_email: content = ub.User() if check_valid_domain(to_save["email"]): - content.nickname = nickname + content.name = nickname content.email = to_save["email"] password = generate_random_password() content.password = generate_password_hash(password) @@ -1414,22 +1432,22 @@ def login(): flash(_(u"Cannot activate LDAP authentication"), category="error") if request.method == "POST": form = request.form.to_dict() - user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower()) \ + user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == form['username'].strip().lower()) \ .first() if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user and form['password'] != "": login_result, error = services.ldap.bind_user(form['username'], form['password']) if login_result: login_user(user, remember=bool(form.get('remember_me'))) - log.debug(u"You are now logged in as: '%s'", user.nickname) - flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), + log.debug(u"You are now logged in as: '%s'", user.name) + flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.name), category="success") return redirect_back(url_for("web.index")) elif login_result is None and user and check_password_hash(str(user.password), form['password']) \ - and user.nickname != "Guest": + and user.name != "Guest": login_user(user, remember=bool(form.get('remember_me'))) - log.info("Local Fallback Login as: '%s'", user.nickname) + log.info("Local Fallback Login as: '%s'", user.name) flash(_(u"Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known", - nickname=user.nickname), + nickname=user.name), category="warning") return redirect_back(url_for("web.index")) elif login_result is None: @@ -1442,7 +1460,7 @@ def login(): else: ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) if 'forgot' in form and form['forgot'] == 'forgot': - if user != None and user.nickname != "Guest": + if user != None and user.name != "Guest": ret, __ = reset_password(user.id) if ret == 1: flash(_(u"New Password was send to your email address"), category="info") @@ -1454,10 +1472,10 @@ def login(): flash(_(u"Please enter valid username to reset password"), category="error") log.warning('Username missing for password reset IP-address: %s', ipAdress) else: - if user and check_password_hash(str(user.password), form['password']) and user.nickname != "Guest": + if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest": login_user(user, remember=bool(form.get('remember_me'))) - log.debug(u"You are now logged in as: '%s'", user.nickname) - flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") + log.debug(u"You are now logged in as: '%s'", user.name) + flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.name), category="success") config.config_is_initial = False return redirect_back(url_for("web.index")) else: @@ -1495,16 +1513,16 @@ def change_profile_email(to_save, kobo_support, local_oauth_check, oauth_status) if config.config_public_reg and not check_valid_domain(to_save["email"]): flash(_(u"E-mail is not from valid domain"), category="error") return render_title_template("user_edit.html", content=current_user, - title=_(u"%(name)s's profile", name=current_user.nickname), page="me", + title=_(u"%(name)s's profile", name=current_user.name), page="me", kobo_support=kobo_support, registered_oauth=local_oauth_check, oauth_status=oauth_status) current_user.email = to_save["email"] def change_profile_nickname(to_save, kobo_support, local_oauth_check, translations, languages): - if "nickname" in to_save and to_save["nickname"] != current_user.nickname: - # Query User nickname, if not existing, change - if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar(): - current_user.nickname = to_save["nickname"] + if "name" in to_save and to_save["name"] != current_user.name: + # Query User name, if not existing, change + if not ub.session.query(ub.User).filter(ub.User.name == to_save["name"]).scalar(): + current_user.name = to_save["name"] else: flash(_(u"This username is already taken"), category="error") return render_title_template("user_edit.html", @@ -1514,7 +1532,7 @@ def change_profile_nickname(to_save, kobo_support, local_oauth_check, translatio new_user=0, content=current_user, registered_oauth=local_oauth_check, title=_(u"Edit User %(nick)s", - nick=current_user.nickname), + nick=current_user.name), page="edituser") @@ -1580,7 +1598,7 @@ def profile(): languages=languages, content=current_user, kobo_support=kobo_support, - title=_(u"%(name)s's profile", name=current_user.nickname), + title=_(u"%(name)s's profile", name=current_user.name), page="me", registered_oauth=local_oauth_check, oauth_status=oauth_status) From 4664b478513dcefb421ff4d7cf11632c9e30619e Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 19:31:32 +0100 Subject: [PATCH 014/240] Fixed alphabetical order in list and grid view Completed download section --- cps/render_template.py | 14 +++++++++---- cps/static/js/filter_grid.js | 8 ++++++++ cps/static/js/filter_list.js | 2 +- cps/templates/grid.html | 4 ++-- cps/templates/list.html | 4 ++-- cps/web.py | 39 +++++++++++++++++++++++++++--------- 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/cps/render_template.py b/cps/render_template.py index 1476a3ad..51e4db95 100644 --- a/cps/render_template.py +++ b/cps/render_template.py @@ -42,10 +42,16 @@ def get_sidebar_config(kwargs=None): sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot", "visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show Hot Books'), "config_show": True}) - sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list', - "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), - "page": "download", "show_text": _('Show Downloaded Books'), - "config_show": content}) + if current_user.role_admin(): + sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list', + "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), + "page": "download", "show_text": _('Show Downloaded Books'), + "config_show": content}) + else: + sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list', + "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), + "page": "download", "show_text": _('Show Downloaded Books'), + "config_show": content}) sidebar.append( {"glyph": "glyphicon-star", "text": _('Top Rated Books'), "link": 'web.books_list', "id": "rated", "visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated", diff --git a/cps/static/js/filter_grid.js b/cps/static/js/filter_grid.js index d57d155f..623ffdc1 100644 --- a/cps/static/js/filter_grid.js +++ b/cps/static/js/filter_grid.js @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +var direction = $("#asc").data('order'); // 0=Descending order; 1= ascending order + var $list = $("#list").isotope({ itemSelector: ".book", layoutMode: "fitRows", @@ -24,6 +26,9 @@ var $list = $("#list").isotope({ }); $("#desc").click(function() { + if (direction === 0) { + return; + } var page = $(this).data("id"); $.ajax({ method:"post", @@ -39,6 +44,9 @@ $("#desc").click(function() { }); $("#asc").click(function() { + if (direction === 1) { + return; + } var page = $(this).data("id"); $.ajax({ method:"post", diff --git a/cps/static/js/filter_list.js b/cps/static/js/filter_list.js index ef7639fa..b8f79f4e 100644 --- a/cps/static/js/filter_list.js +++ b/cps/static/js/filter_list.js @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -var direction = 0; // Descending order +var direction = $("#asc").data('order'); // 0=Descending order; 1= ascending order var sort = 0; // Show sorted entries $("#sort_name").click(function() { diff --git a/cps/templates/grid.html b/cps/templates/grid.html index 9724e31d..6b03d89c 100644 --- a/cps/templates/grid.html +++ b/cps/templates/grid.html @@ -8,8 +8,8 @@ {% endif %} {% endif %} - - + + {% if charlist|length %} {% endif %} diff --git a/cps/templates/list.html b/cps/templates/list.html index 53a712d1..1e171ca5 100644 --- a/cps/templates/list.html +++ b/cps/templates/list.html @@ -8,8 +8,8 @@ {% endif %} {% endif %} - - + + {% if charlist|length %} {% endif %} diff --git a/cps/web.py b/cps/web.py index 8a8bed93..b21224d1 100644 --- a/cps/web.py +++ b/cps/web.py @@ -463,7 +463,10 @@ def render_hot_books(page): def render_downloaded_books(page, order, user_id): - user_id = int(user_id) + if current_user.role_admin(): + user_id = int(user_id) + else: + user_id = current_user.id if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD): if current_user.show_detail_random(): random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \ @@ -486,6 +489,7 @@ def render_downloaded_books(page, order, user_id): random=random, entries=entries, pagination=pagination, + id=user_id, title=_(u"Downloaded books by %(user)s",user=user.name), page="download") else: @@ -799,8 +803,10 @@ def author_list(): if current_user.check_visibility(constants.SIDEBAR_AUTHOR): if current_user.get_view_property('author', 'dir') == 'desc': order = db.Authors.sort.desc() + order_no = 0 else: order = db.Authors.sort.asc() + order_no = 1 entries = calibre_db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \ .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(text('books_authors_link.author')).order_by(order).all() @@ -810,7 +816,7 @@ def author_list(): for entry in entries: entry.Authors.name = entry.Authors.name.replace('|', ',') return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, - title=u"Authors", page="authorlist", data='author') + title=u"Authors", page="authorlist", data='author', order=order_no) else: abort(404) @@ -818,9 +824,11 @@ def author_list(): @login_required_if_no_ano def download_list(): if current_user.get_view_property('download', 'dir') == 'desc': - order = ub.User.name.desc() # ToDo + order = ub.User.name.desc() + order_no = 0 else: - order = ub.User.name.asc() # ToDo + order = ub.User.name.asc() + order_no = 1 if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD) and current_user.role_admin(): entries = ub.session.query(ub.User, func.count(ub.Downloads.book_id).label('count'))\ .join(ub.Downloads).group_by(ub.Downloads.user_id).order_by(order).all() @@ -828,7 +836,7 @@ def download_list(): .filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) \ .group_by(func.upper(func.substr(ub.User.name, 1, 1))).all() return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, - title=_(u"Downloads"), page="downloadlist", data="download") + title=_(u"Downloads"), page="downloadlist", data="download", order=order_no) else: abort(404) @@ -838,8 +846,10 @@ def download_list(): def publisher_list(): if current_user.get_view_property('publisher', 'dir') == 'desc': order = db.Publishers.name.desc() + order_no = 0 else: order = db.Publishers.name.asc() + order_no = 1 if current_user.check_visibility(constants.SIDEBAR_PUBLISHER): entries = calibre_db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \ .join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \ @@ -848,7 +858,7 @@ def publisher_list(): .join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all() return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, - title=_(u"Publishers"), page="publisherlist", data="publisher") + title=_(u"Publishers"), page="publisherlist", data="publisher", order=order_no) else: abort(404) @@ -859,8 +869,10 @@ def series_list(): if current_user.check_visibility(constants.SIDEBAR_SERIES): if current_user.get_view_property('series', 'dir') == 'desc': order = db.Series.sort.desc() + order_no = 0 else: order = db.Series.sort.asc() + order_no = 1 if current_user.get_view_property('series', 'series_view') == 'list': entries = calibre_db.session.query(db.Series, func.count('books_series_link.book').label('count')) \ .join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \ @@ -879,7 +891,8 @@ def series_list(): .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=charlist, - title=_(u"Series"), page="serieslist", data="series", bodyClass="grid-view") + title=_(u"Series"), page="serieslist", data="series", bodyClass="grid-view", + order=order_no) else: abort(404) @@ -890,14 +903,16 @@ def ratings_list(): if current_user.check_visibility(constants.SIDEBAR_RATING): if current_user.get_view_property('ratings', 'dir') == 'desc': order = db.Ratings.rating.desc() + order_no = 0 else: order = db.Ratings.rating.asc() + order_no = 1 entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'), (db.Ratings.rating / 2).label('name')) \ .join(db.books_ratings_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(text('books_ratings_link.rating')).order_by(order).all() return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(), - title=_(u"Ratings list"), page="ratingslist", data="ratings") + title=_(u"Ratings list"), page="ratingslist", data="ratings", order=order_no) else: abort(404) @@ -908,15 +923,17 @@ def formats_list(): if current_user.check_visibility(constants.SIDEBAR_FORMAT): if current_user.get_view_property('ratings', 'dir') == 'desc': order = db.Data.format.desc() + order_no = 0 else: order = db.Data.format.asc() + order_no = 1 entries = calibre_db.session.query(db.Data, func.count('data.book').label('count'), db.Data.format.label('format')) \ .join(db.Books).filter(calibre_db.common_filters()) \ .group_by(db.Data.format).order_by(order).all() return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(), - title=_(u"File formats list"), page="formatslist", data="formats") + title=_(u"File formats list"), page="formatslist", data="formats", order=order_no) else: abort(404) @@ -956,8 +973,10 @@ def category_list(): if current_user.check_visibility(constants.SIDEBAR_CATEGORY): if current_user.get_view_property('category', 'dir') == 'desc': order = db.Tags.name.desc() + order_no = 0 else: order = db.Tags.name.asc() + order_no = 1 entries = calibre_db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \ .join(db.books_tags_link).join(db.Books).order_by(order).filter(calibre_db.common_filters()) \ .group_by(text('books_tags_link.tag')).all() @@ -965,7 +984,7 @@ def category_list(): .join(db.books_tags_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(func.upper(func.substr(db.Tags.name, 1, 1))).all() return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, - title=_(u"Categories"), page="catlist", data="category") + title=_(u"Categories"), page="catlist", data="category", order=order_no) else: abort(404) From 837fc4988d10cc4136b24e05c9823b85bbd19576 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 21 Mar 2021 20:14:17 +0100 Subject: [PATCH 015/240] Letterize authors --- cps/opds.py | 33 +++++++++++++++++++++++++++++---- cps/templates/index.xml | 7 +++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index 5ccd683e..23334462 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -27,7 +27,7 @@ from functools import wraps from flask import Blueprint, request, render_template, Response, g, make_response, abort from flask_login import current_user -from sqlalchemy.sql.expression import func, text, or_, and_ +from sqlalchemy.sql.expression import func, text, or_, and_, true from werkzeug.security import check_password_hash from . import constants, logger, config, db, calibre_db, ub, services, get_locale, isoLanguages @@ -97,6 +97,15 @@ def feed_normal_search(): return feed_search(request.args.get("query", "").strip()) +@opds.route("/opds/books") +@requires_basic_auth_if_no_ano +def feed_books(): + off = request.args.get("offset") or 0 + entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), 0, + db.Books, True, [db.Books.sort]) + return render_xml_template('feed.xml', entries=entries, pagination=pagination) + + @opds.route("/opds/new") @requires_basic_auth_if_no_ano def feed_new(): @@ -151,13 +160,29 @@ def feed_hot(): @requires_basic_auth_if_no_ano def feed_authorindex(): off = request.args.get("offset") or 0 + entries = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('id'), + func.upper(func.substr(db.Authors.sort, 1, 1)).label('name')) \ + .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \ + .group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all() + + # ToDo: Add All to list -> All: id = 0 + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(entries)) + return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_letter_author', pagination=pagination) + + +@opds.route("/opds/author/letter/") +@requires_basic_auth_if_no_ano +def feed_letter_author(book_id): + off = request.args.get("offset") or 0 + letter = true() if book_id == "0" else func.upper(db.Authors.sort).startswith(book_id) entries = calibre_db.session.query(db.Authors).join(db.books_authors_link).join(db.Books)\ - .filter(calibre_db.common_filters())\ + .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_authors_link.author'))\ .order_by(db.Authors.sort).limit(config.config_books_per_page)\ - .offset(off) + .offset(off).all() pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(calibre_db.session.query(db.Authors).all())) + len(entries)) return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination) diff --git a/cps/templates/index.xml b/cps/templates/index.xml index c6a6e8f0..1553f399 100644 --- a/cps/templates/index.xml +++ b/cps/templates/index.xml @@ -14,6 +14,13 @@ {{instance}} https://github.com/janeczku/calibre-web + + {{_('Alphabetical Books')}} + + {{url_for('opds.feed_books')}} + {{ current_time }} + {{_('Books sorted alphabetically')}} + {{_('Hot Books')}} From fc855868092ea8fcdba9cad2b07fbcb9623f1cdb Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 22 Mar 2021 16:04:53 +0100 Subject: [PATCH 016/240] Bugfixes for sqlalchemy 1.4.0 --- cps/db.py | 12 +++++++----- cps/web.py | 6 +++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cps/db.py b/cps/db.py index a3b21da8..7a4660f4 100644 --- a/cps/db.py +++ b/cps/db.py @@ -627,15 +627,17 @@ class CalibreDB(): randm = self.session.query(Books) \ .filter(self.common_filters(allow_show_archived)) \ .order_by(func.random()) \ - .limit(self.config.config_random_books) + .limit(self.config.config_random_books).all() else: randm = false() off = int(int(pagesize) * (page - 1)) - query = self.session.query(database) \ - .filter(db_filter) \ + query = self.session.query(database) + if len(join) == 3: + query = query.outerjoin(join[0], join[1]).outerjoin(join[2]) + elif len(join) == 2: + query = query.outerjoin(join[0], join[1]) + query = query.filter(db_filter)\ .filter(self.common_filters(allow_show_archived)) - if len(join): - query = query.join(*join, isouter=True) entries = list() pagination = list() try: diff --git a/cps/web.py b/cps/web.py index b21224d1..0eff88f1 100644 --- a/cps/web.py +++ b/cps/web.py @@ -502,6 +502,7 @@ def render_author_books(page, author_id, order): db.Books.authors.any(db.Authors.id == author_id), [order[0], db.Series.name, db.Books.series_index], db.books_series_link, + db.Books.id == db.books_series_link.c.book, db.Series) if entries is None or not len(entries): flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"), @@ -530,6 +531,7 @@ def render_publisher_books(page, book_id, order): db.Books.publishers.any(db.Publishers.id == book_id), [db.Series.name, order[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") @@ -583,7 +585,9 @@ def render_category_books(page, book_id, order): db.Books, db.Books.tags.any(db.Tags.id == book_id), [order[0], db.Series.name, db.Books.series_index], - db.books_series_link, db.Series) + 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") else: From 670eab62bf2e8f88c1d9171645f148c29dbbfd2f Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 22 Mar 2021 17:46:15 +0100 Subject: [PATCH 017/240] Update opds feed with letters for all titles, authors, categories and series --- cps/opds.py | 98 ++++++++++++++++++++++++++++++++++++----- cps/templates/feed.xml | 7 +++ cps/templates/index.xml | 4 +- 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index 23334462..a68cb58c 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -99,10 +99,35 @@ def feed_normal_search(): @opds.route("/opds/books") @requires_basic_auth_if_no_ano -def feed_books(): +def feed_booksindex(): off = request.args.get("offset") or 0 + entries = calibre_db.session.query(func.upper(func.substr(db.Books.sort, 1, 1)).label('id'))\ + .filter(calibre_db.common_filters()).group_by(func.upper(func.substr(db.Books.sort, 1, 1))).all() + + elements = [] + if off == 0: + elements.append({'id': "00", 'name':_("All")}) + for entry in entries: + elements.append({'id': entry.id, 'name': entry.id}) + + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(elements)) + return render_xml_template('feed.xml', + letterelements=elements, + folder='opds.feed_letter_books', + pagination=pagination) + + +@opds.route("/opds/books/letter/") +@requires_basic_auth_if_no_ano +def feed_letter_books(book_id): + off = request.args.get("offset") or 0 + letter = true() if book_id == "00" else func.upper(db.Books.sort).startswith(book_id) entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), 0, - db.Books, True, [db.Books.sort]) + db.Books, + letter, + [db.Books.sort]) + return render_xml_template('feed.xml', entries=entries, pagination=pagination) @@ -160,22 +185,29 @@ def feed_hot(): @requires_basic_auth_if_no_ano def feed_authorindex(): off = request.args.get("offset") or 0 - entries = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('id'), - func.upper(func.substr(db.Authors.sort, 1, 1)).label('name')) \ - .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \ + entries = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('id'))\ + .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters())\ .group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all() - # ToDo: Add All to list -> All: id = 0 + elements = [] + if off == 0: + elements.append({'id': "00", 'name':_("All")}) + for entry in entries: + elements.append({'id': entry.id, 'name': entry.id}) + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(entries)) - return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_letter_author', pagination=pagination) + len(elements)) + return render_xml_template('feed.xml', + letterelements=elements, + folder='opds.feed_letter_author', + pagination=pagination) @opds.route("/opds/author/letter/") @requires_basic_auth_if_no_ano def feed_letter_author(book_id): off = request.args.get("offset") or 0 - letter = true() if book_id == "0" else func.upper(db.Authors.sort).startswith(book_id) + letter = true() if book_id == "00" else func.upper(db.Authors.sort).startswith(book_id) entries = calibre_db.session.query(db.Authors).join(db.books_authors_link).join(db.Books)\ .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_authors_link.author'))\ @@ -227,10 +259,31 @@ def feed_publisher(book_id): @requires_basic_auth_if_no_ano def feed_categoryindex(): off = request.args.get("offset") or 0 + entries = calibre_db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('id'))\ + .join(db.books_tags_link).join(db.Books).filter(calibre_db.common_filters())\ + .group_by(func.upper(func.substr(db.Tags.name, 1, 1))).all() + elements = [] + if off == 0: + elements.append({'id': "00", 'name':_("All")}) + for entry in entries: + elements.append({'id': entry.id, 'name': entry.id}) + + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(elements)) + return render_xml_template('feed.xml', + letterelements=elements, + folder='opds.feed_letter_category', + pagination=pagination) + +@opds.route("/opds/category/letter/") +@requires_basic_auth_if_no_ano +def feed_letter_category(book_id): + off = request.args.get("offset") or 0 + letter = true() if book_id == "00" else func.upper(db.Tags.name).startswith(book_id) entries = calibre_db.session.query(db.Tags)\ .join(db.books_tags_link)\ .join(db.Books)\ - .filter(calibre_db.common_filters())\ + .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_tags_link.tag'))\ .order_by(db.Tags.name)\ .offset(off)\ @@ -255,10 +308,31 @@ def feed_category(book_id): @requires_basic_auth_if_no_ano def feed_seriesindex(): off = request.args.get("offset") or 0 + entries = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('id'))\ + .join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters())\ + .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() + elements = [] + if off == 0: + elements.append({'id': "00", 'name':_("All")}) + for entry in entries: + elements.append({'id': entry.id, 'name': entry.id}) + + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(elements)) + return render_xml_template('feed.xml', + letterelements=elements, + folder='opds.feed_letter_series', + pagination=pagination) + +@opds.route("/opds/series/letter/") +@requires_basic_auth_if_no_ano +def feed_letter_series(book_id): + off = request.args.get("offset") or 0 + letter = true() if book_id == "00" else func.upper(db.Series.sort).startswith(book_id) entries = calibre_db.session.query(db.Series)\ .join(db.books_series_link)\ .join(db.Books)\ - .filter(calibre_db.common_filters())\ + .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_series_link.series'))\ .order_by(db.Series.sort)\ .offset(off).all() @@ -294,7 +368,7 @@ def feed_ratingindex(): len(entries)) element = list() for entry in entries: - element.append(FeedObject(entry[0].id, "{} Stars".format(entry.name))) + element.append(FeedObject(entry[0].id, _("{} Stars").format(entry.name))) return render_xml_template('feed.xml', listelements=element, folder='opds.feed_ratings', pagination=pagination) diff --git a/cps/templates/feed.xml b/cps/templates/feed.xml index 4ad1db8c..9073142e 100644 --- a/cps/templates/feed.xml +++ b/cps/templates/feed.xml @@ -84,4 +84,11 @@ {% endfor %} + {% for entry in letterelements %} + + {{entry['name']}} + {{ url_for(folder, book_id=entry['id']) }} + + + {% endfor %} diff --git a/cps/templates/index.xml b/cps/templates/index.xml index 1553f399..4ffd4290 100644 --- a/cps/templates/index.xml +++ b/cps/templates/index.xml @@ -16,8 +16,8 @@ {{_('Alphabetical Books')}} - - {{url_for('opds.feed_books')}} + + {{url_for('opds.feed_booksindex')}} {{ current_time }} {{_('Books sorted alphabetically')}} From 7a58e48cae281d982ec362d1d12ef7cd259b18c4 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 22 Mar 2021 19:01:18 +0100 Subject: [PATCH 018/240] Bugfixes opds feed --- cps/opds.py | 60 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index a68cb58c..85a978a7 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -100,18 +100,22 @@ def feed_normal_search(): @opds.route("/opds/books") @requires_basic_auth_if_no_ano def feed_booksindex(): - off = request.args.get("offset") or 0 + shift = 0 + off = int(request.args.get("offset") or 0) entries = calibre_db.session.query(func.upper(func.substr(db.Books.sort, 1, 1)).label('id'))\ .filter(calibre_db.common_filters()).group_by(func.upper(func.substr(db.Books.sort, 1, 1))).all() elements = [] if off == 0: elements.append({'id': "00", 'name':_("All")}) - for entry in entries: + shift = 1 + for entry in entries[ + off + shift - 1: + int(off + int(config.config_books_per_page) - shift)]: elements.append({'id': entry.id, 'name': entry.id}) pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(elements)) + len(entries) + 1) return render_xml_template('feed.xml', letterelements=elements, folder='opds.feed_letter_books', @@ -184,7 +188,8 @@ def feed_hot(): @opds.route("/opds/author") @requires_basic_auth_if_no_ano def feed_authorindex(): - off = request.args.get("offset") or 0 + shift = 0 + off = int(request.args.get("offset") or 0) entries = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('id'))\ .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters())\ .group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all() @@ -192,11 +197,14 @@ def feed_authorindex(): elements = [] if off == 0: elements.append({'id': "00", 'name':_("All")}) - for entry in entries: + shift = 1 + for entry in entries[ + off + shift - 1: + int(off + int(config.config_books_per_page) - shift)]: elements.append({'id': entry.id, 'name': entry.id}) pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(elements)) + len(entries) + 1) return render_xml_template('feed.xml', letterelements=elements, folder='opds.feed_letter_author', @@ -211,10 +219,10 @@ def feed_letter_author(book_id): entries = calibre_db.session.query(db.Authors).join(db.books_authors_link).join(db.Books)\ .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_authors_link.author'))\ - .order_by(db.Authors.sort).limit(config.config_books_per_page)\ - .offset(off).all() + .order_by(db.Authors.sort) pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(entries)) + entries.count()) + entries = entries.limit(config.config_books_per_page).offset(off).all() return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination) @@ -258,18 +266,22 @@ def feed_publisher(book_id): @opds.route("/opds/category") @requires_basic_auth_if_no_ano def feed_categoryindex(): - off = request.args.get("offset") or 0 + shift = 0 + off = int(request.args.get("offset") or 0) entries = calibre_db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('id'))\ .join(db.books_tags_link).join(db.Books).filter(calibre_db.common_filters())\ .group_by(func.upper(func.substr(db.Tags.name, 1, 1))).all() elements = [] if off == 0: elements.append({'id': "00", 'name':_("All")}) - for entry in entries: + shift = 1 + for entry in entries[ + off + shift - 1: + int(off + int(config.config_books_per_page) - shift)]: elements.append({'id': entry.id, 'name': entry.id}) pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(elements)) + len(entries) + 1) return render_xml_template('feed.xml', letterelements=elements, folder='opds.feed_letter_category', @@ -285,11 +297,10 @@ def feed_letter_category(book_id): .join(db.Books)\ .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_tags_link.tag'))\ - .order_by(db.Tags.name)\ - .offset(off)\ - .limit(config.config_books_per_page) + .order_by(db.Tags.name) pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(calibre_db.session.query(db.Tags).all())) + entries.count()) + entries = entries.offset(off).limit(config.config_books_per_page).all() return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_category', pagination=pagination) @@ -307,18 +318,21 @@ def feed_category(book_id): @opds.route("/opds/series") @requires_basic_auth_if_no_ano def feed_seriesindex(): - off = request.args.get("offset") or 0 + shift = 0 + off = int(request.args.get("offset") or 0) entries = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('id'))\ .join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters())\ .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() elements = [] if off == 0: elements.append({'id': "00", 'name':_("All")}) - for entry in entries: + shift = 1 + for entry in entries[ + off + shift - 1: + int(off + int(config.config_books_per_page) - shift)]: elements.append({'id': entry.id, 'name': entry.id}) - pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(elements)) + len(entries) + 1) return render_xml_template('feed.xml', letterelements=elements, folder='opds.feed_letter_series', @@ -334,10 +348,10 @@ def feed_letter_series(book_id): .join(db.Books)\ .filter(calibre_db.common_filters()).filter(letter)\ .group_by(text('books_series_link.series'))\ - .order_by(db.Series.sort)\ - .offset(off).all() + .order_by(db.Series.sort) pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, - len(calibre_db.session.query(db.Series).all())) + entries.count()) + entries = entries.offset(off).limit(config.config_books_per_page).all() return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_series', pagination=pagination) From 657cba042aae9f3d767f820e67550ce09b8a6149 Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Mon, 22 Mar 2021 17:25:38 -0700 Subject: [PATCH 019/240] Use btn classes on anchors not div so the entire button is clickable --- cps/templates/admin.html | 12 ++++++------ cps/templates/logviewer.html | 4 ++-- cps/templates/user_edit.html | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index fb0c758f..e762c0c2 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -41,7 +41,7 @@ {% endif %} {% endfor %} - + {{_('Add New User')}} {% if (config.config_login_type == 1) %}
    {{_('Import LDAP Users')}}
    {% endif %} @@ -75,7 +75,7 @@
    {% endif %} - + {{_('Edit E-mail Server Settings')}}
    @@ -134,15 +134,15 @@ {% endif %} - - + {{_('Edit Basic Configuration')}} + {{_('Edit UI Configuration')}}
    {{_('Reconnect Calibre Database')}}
    diff --git a/cps/templates/logviewer.html b/cps/templates/logviewer.html index 9827d15e..db27bdf4 100644 --- a/cps/templates/logviewer.html +++ b/cps/templates/logviewer.html @@ -15,10 +15,10 @@
    {% if log_enable %} - + {{_('Download Calibre-Web Log')}} {% endif %} {% if accesslog_enable %} - + {{_('Download Access Log')}} {% endif %}
    diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 90a32acc..84791fcb 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -16,7 +16,7 @@
    {% if ( g.user and g.user.role_passwd() or g.user.role_admin() ) and not content.role_anonymous() %} {% if g.user and g.user.role_admin() and not new_user and not profile and ( mail_configured and content.email if content.email != None ) %} - + {{_('Reset user Password')}} {% endif %}
    @@ -52,9 +52,9 @@
    {% if id not in oauth_status %} - + {{_('Link')}} {% else %} - + {{_('Unlink')}} {% endif %} {% endfor %}
    From c279055af40a13af57bf21b6c1469be688b18233 Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Mon, 22 Mar 2021 18:08:31 -0700 Subject: [PATCH 020/240] Use the kobo auth'd version for download link when proxied -- Fixes #1908 --- cps/kobo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cps/kobo.py b/cps/kobo.py index a9dd8865..2b956bfc 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -305,7 +305,8 @@ def get_download_url_for_book(book, book_format): book_format=book_format.lower() ) return url_for( - "web.download_link", + "kobo.download_book", + auth_token=kobo_auth.get_auth_token(), book_id=book.id, book_format=book_format.lower(), _external=True, From 3d6c836e7dca12c34ec04df6015a315711658469 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 23 Mar 2021 17:39:51 +0100 Subject: [PATCH 021/240] Update tests --- test/Calibre-Web TestSummary_Linux.html | 2354 +++++++++++++++++++---- 1 file changed, 1947 insertions(+), 407 deletions(-) diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index c18e6bad..7a88f553 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
    -

    Start Time: 2021-02-09 20:40:28

    +

    Start Time: 2021-03-22 19:04:38

    -

    Stop Time: 2021-02-09 23:08:52

    +

    Stop Time: 2021-03-22 21:16:53

    -

    Duration: 2h 0 min

    +

    Duration: 1h 43 min

    @@ -653,12 +653,12 @@ - + TestEditAdditionalBooks 13 - 12 - 0 - 0 + 5 + 2 + 5 1 Detail @@ -667,11 +667,31 @@ - +
    TestEditAdditionalBooks - test_change_upload_formats
    - PASS + +
    + FAIL +
    + + + + @@ -685,11 +705,31 @@ - +
    TestEditAdditionalBooks - test_delete_role
    - PASS + +
    + FAIL +
    + + + + @@ -730,38 +770,138 @@ - +
    TestEditAdditionalBooks - test_title_sort
    - PASS + +
    + ERROR +
    + + + + - +
    TestEditAdditionalBooks - test_upload_edit_role
    - PASS + +
    + ERROR +
    + + + + - +
    TestEditAdditionalBooks - test_upload_metadata_cbr
    - PASS + +
    + ERROR +
    + + + + - +
    TestEditAdditionalBooks - test_upload_metadata_cbt
    - PASS + +
    + ERROR +
    + + + + @@ -792,58 +932,395 @@ - +
    TestEditAdditionalBooks - test_writeonly_path
    - PASS + +
    + ERROR +
    + + + + - - TestEditBooks - 33 - 31 + + _ErrorHolder + 6 0 0 - 2 + 6 + 0 - Detail + Detail - + + +
    tearDownClass (test_edit_additional_books)
    + + +
    + ERROR +
    + + + + + + + + + + +
    tearDownClass (test_edit_books)
    + + +
    + ERROR +
    + + + + + + + + + + +
    tearDownClass (test_email_ssl)
    + + +
    + ERROR +
    + + + + + + + + + + +
    tearDownClass (test_ldap)
    + + +
    + ERROR +
    + + + + + + + + + + +
    tearDownClass (test_pdf_metadata)
    + + +
    + ERROR +
    + + + + + + + + + + +
    tearDownClass (test_reader)
    + + +
    + ERROR +
    + + + + + + + + + + + TestEditBooks + 33 + 12 + 4 + 15 + 2 + + Detail + + + + + +
    TestEditBooks - test_download_book
    - PASS + +
    + FAIL +
    + + + + - +
    TestEditBooks - test_edit_author
    - PASS + +
    + ERROR +
    + + + + - +
    TestEditBooks - test_edit_category
    - PASS + +
    + FAIL +
    + + + + - +
    TestEditBooks - test_edit_comments
    @@ -852,7 +1329,7 @@ - +
    TestEditBooks - test_edit_custom_bool
    @@ -861,7 +1338,7 @@ - +
    TestEditBooks - test_edit_custom_categories
    @@ -870,7 +1347,7 @@ - +
    TestEditBooks - test_edit_custom_float
    @@ -879,7 +1356,7 @@ - +
    TestEditBooks - test_edit_custom_int
    @@ -888,7 +1365,7 @@ - +
    TestEditBooks - test_edit_custom_rating
    @@ -897,7 +1374,7 @@ - +
    TestEditBooks - test_edit_custom_single_select
    @@ -906,7 +1383,7 @@ - +
    TestEditBooks - test_edit_custom_text
    @@ -915,37 +1392,81 @@ - +
    TestEditBooks - test_edit_language
    - PASS + +
    + ERROR +
    + + + + - +
    TestEditBooks - test_edit_publisher
    - PASS + +
    + FAIL +
    + + + + - +
    TestEditBooks - test_edit_publishing_date
    - SKIP + SKIP
    -