From 3a08b91ffa686c6c8e23ccd1e14d1d2142e8b7d6 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 16 Aug 2023 18:44:03 +0200 Subject: [PATCH 1/7] Added cb7 to supported comic files for upload and metadata extraction --- cps/__init__.py | 3 ++- cps/comic.py | 22 ++++++++++++++++++++-- cps/uploader.py | 2 +- optional-requirements.txt | 1 + 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cps/__init__.py b/cps/__init__.py index 8b0b86ad..f4f8dbf2 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -64,7 +64,8 @@ mimetypes.add_type('application/x-mobi8-ebook', '.azw3') mimetypes.add_type('application/x-cbr', '.cbr') mimetypes.add_type('application/x-cbz', '.cbz') mimetypes.add_type('application/x-cbt', '.cbt') -mimetypes.add_type('image/vnd.djvu', '.djvu') +mimetypes.add_type('application/x-cb7', '.cb7') +mimetypes.add_type('image/vnd.djv', '.djv') mimetypes.add_type('application/mpeg', '.mpeg') mimetypes.add_type('application/mpeg', '.mp3') mimetypes.add_type('application/mp4', '.m4a') diff --git a/cps/comic.py b/cps/comic.py index 13774756..4242bb2f 100644 --- a/cps/comic.py +++ b/cps/comic.py @@ -52,6 +52,12 @@ except (ImportError, LookupError) as e: except (ImportError, SyntaxError) as e: log.debug('Cannot import rarfile, extracting cover files from rar files will not work: %s', e) use_rarfile = False + try: + import py7zr + use_7zip = True + except (ImportError, SyntaxError) as e: + log.debug('Cannot import py7zr, extracting cover files from CB7 files will not work: %s', e) + use_7zip = False use_comic_meta = False @@ -84,10 +90,22 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec if len(ext) > 1: extension = ext[1].lower() if extension in cover.COVER_EXTENSIONS: - cover_data = cf.read(name) + cover_data = cf.read([name]) break except Exception as ex: - log.debug('Rarfile failed with error: {}'.format(ex)) + log.error('Rarfile failed with error: {}'.format(ex)) + elif original_file_extension.upper() == '.CB7' and use_7zip: + cf = py7zr.SevenZipFile(tmp_file_name) + for name in cf.getnames(): + ext = os.path.splitext(name) + if len(ext) > 1: + extension = ext[1].lower() + if extension in cover.COVER_EXTENSIONS: + try: + cover_data = cf.read(name)[name].read() + except (py7zr.Bad7zFile, OSError) as ex: + log.error('7Zip file failed with error: {}'.format(ex)) + break return cover_data, extension diff --git a/cps/uploader.py b/cps/uploader.py index bf30094d..23dfc4a6 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -79,7 +79,7 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec meta = epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension) elif ".FB2" == extension_upper and use_fb2_meta is True: meta = fb2.get_fb2_info(tmp_file_path, original_file_extension) - elif extension_upper in ['.CBZ', '.CBT', '.CBR']: + elif extension_upper in ['.CBZ', '.CBT', '.CBR', ".CB7"]: meta = comic.get_comic_info(tmp_file_path, original_file_name, original_file_extension, diff --git a/optional-requirements.txt b/optional-requirements.txt index ff02d04f..d34d09aa 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -35,6 +35,7 @@ html2text>=2020.1.16,<2022.1.1 python-dateutil>=2.1,<2.9.0 beautifulsoup4>=4.0.1,<4.12.0 faust-cchardet>=2.1.18 +py7zr>=0.15.0,<0.21.0 # Comics natsort>=2.2.0,<8.4.0 From f7ff3e7cbae4daa7e2b514a30e200902b3225566 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 23 Aug 2023 20:50:39 +0200 Subject: [PATCH 2/7] Added py7zr to setup.cfg --- setup.cfg | 1 + test/Calibre-Web TestSummary_Linux.html | 501 +++++++++++++----------- 2 files changed, 280 insertions(+), 222 deletions(-) diff --git a/setup.cfg b/setup.cfg index b445eb5e..1f617648 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,6 +92,7 @@ metadata = python-dateutil>=2.1,<2.9.0 beautifulsoup4>=4.0.1,<4.12.0 faust-cchardet>=2.1.18 + py7zr>=0.15.0,<0.21.0 comics = natsort>=2.2.0,<8.4.0 comicapi>=2.2.0,<3.3.0 diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 309c9a25..83cb4395 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2023-07-26 21:47:14

+

Start Time: 2023-08-17 20:59:16

-

Stop Time: 2023-07-27 04:10:01

+

Stop Time: 2023-08-18 03:31:30

-

Duration: 5h 21 min

+

Duration: 5h 32 min

@@ -234,12 +234,12 @@ - + TestBackupMetadata 22 - 20 - 1 - 1 + 22 + 0 + 0 0 Detail @@ -293,32 +293,11 @@ - +
TestBackupMetadata - test_backup_change_book_publisher
- -
- FAIL -
- - - - + PASS @@ -395,33 +374,11 @@ AssertionError: '' != 'Lo,执|1u' - +
TestBackupMetadata - test_backup_change_custom_categories
- -
- ERROR -
- - - - + PASS @@ -1015,11 +972,11 @@ TypeError: 'NoneType' object is not iterable - + TestEbookConvertGDriveKepubify 3 - 2 - 1 + 3 + 0 0 0 @@ -1038,33 +995,11 @@ TypeError: 'NoneType' object is not iterable - +
TestEbookConvertGDriveKepubify - test_convert_only
- -
- FAIL -
- - - - + PASS @@ -1079,15 +1014,15 @@ AssertionError: 'Started' != 'Finished' - + TestEditAdditionalBooks + 20 17 - 16 - 0 0 1 + 2 - Detail + Detail @@ -1201,7 +1136,36 @@ AssertionError: 'Started' != 'Finished' - + + +
TestEditAdditionalBooks - test_upload_metadata_cb7
+ + +
+ ERROR +
+ + + + + + + + +
TestEditAdditionalBooks - test_upload_metadata_cbr
@@ -1210,7 +1174,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditAdditionalBooks - test_upload_metadata_cbt
@@ -1219,7 +1183,42 @@ AssertionError: 'Started' != 'Finished' - + + +
TestEditAdditionalBooks - test_writeonly_calibre_database
+ + +
+ SKIP +
+ + + + + + + + + + +
TestEditAdditionalBooks - test_writeonly_path
+ + PASS + + + + +
TestEditAdditionalBooks - test_xss_author_edit
@@ -1228,7 +1227,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditAdditionalBooks - test_xss_comment_edit
@@ -1237,7 +1236,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditAdditionalBooks - test_xss_custom_comment_edit
@@ -1247,15 +1246,15 @@ AssertionError: 'Started' != 'Finished' - + TestEditBooks - 37 - 35 - 0 + 38 + 34 0 2 + 2 - Detail + Detail @@ -1538,7 +1537,36 @@ AssertionError: 'Started' != 'Finished' - + + +
TestEditBooks - test_upload_book_cb7
+ + +
+ ERROR +
+ + + + + + + + +
TestEditBooks - test_upload_book_cbr
@@ -1547,7 +1575,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_cbt
@@ -1556,7 +1584,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_cbz
@@ -1565,7 +1593,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_epub
@@ -1574,7 +1602,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_fb2
@@ -1583,7 +1611,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_lit
@@ -1592,7 +1620,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_mobi
@@ -1601,7 +1629,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_pdf
@@ -1610,7 +1638,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_cbz_coverformats
@@ -1619,11 +1647,31 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_cover_hdd
- PASS + +
+ ERROR +
+ + + + @@ -1944,11 +1992,11 @@ AssertionError: 'Started' != 'Finished' - + TestLoadMetadata 1 - 1 0 + 1 0 0 @@ -1958,22 +2006,42 @@ AssertionError: 'Started' != 'Finished' - +
TestLoadMetadata - test_load_metadata
- PASS + +
+ FAIL +
+ + + + - + TestEditBooksOnGdrive 18 - 17 + 16 + 1 1 - 0 0 Detail @@ -1991,11 +2059,34 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooksOnGdrive - test_edit_author
- PASS + +
+ FAIL +
+ + + + @@ -2117,11 +2208,31 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooksOnGdrive - test_edit_title
- PASS + +
+ ERROR +
+ + + + @@ -2135,31 +2246,11 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooksOnGdrive - test_watch_metadata
- -
- FAIL -
- - - - + PASS @@ -3606,11 +3697,11 @@ AssertionError: False is not true - + TestReader 6 - 5 - 1 + 6 + 0 0 0 @@ -3656,37 +3747,11 @@ AssertionError: False is not true - +
TestReader - test_sound_listener
- -
- FAIL -
- - - - + PASS @@ -4054,11 +4119,11 @@ AssertionError: '0:03' != '0:02' - + TestThumbnails 8 - 6 - 1 + 7 + 0 0 1 @@ -4095,31 +4160,11 @@ AssertionError: '0:03' != '0:02' - +
TestThumbnails - test_cover_change_on_upload_new_cover
- -
- FAIL -
- - - - + PASS @@ -5229,11 +5274,11 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Total - 457 - 443 - 5 - 1 - 8 + 461 + 446 + 2 + 4 + 9   @@ -5261,13 +5306,13 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Platform - Linux 6.2.0-25-generic #25~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Wed Jun 28 09:55:23 UTC 2 x86_64 x86_64 + Linux 6.2.0-26-generic #26~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 16:27:29 UTC 2 x86_64 x86_64 Basic Python - 3.10.6 + 3.10.12 Basic @@ -5279,7 +5324,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 APScheduler - 3.10.1 + 3.10.3 Basic @@ -5405,13 +5450,13 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Werkzeug - 2.3.6 + 2.3.7 Basic google-api-python-client - 2.95.0 + 2.97.0 TestBackupMetadataGdrive @@ -5429,7 +5474,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestBackupMetadataGdrive @@ -5441,7 +5486,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestCliGdrivedb @@ -5459,7 +5504,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestCliGdrivedb @@ -5471,7 +5516,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestEbookConvertCalibreGDrive @@ -5489,7 +5534,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEbookConvertCalibreGDrive @@ -5501,7 +5546,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestEbookConvertGDriveKepubify @@ -5519,7 +5564,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEbookConvertGDriveKepubify @@ -5535,15 +5580,27 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 TestEditAdditionalBooks + + py7zr + 0.20.6 + TestEditAdditionalBooks + + rarfile 4.0 TestEditAdditionalBooks + + py7zr + 0.20.6 + TestEditBooks + + google-api-python-client - 2.95.0 + 2.97.0 TestEditAuthorsGdrive @@ -5561,7 +5618,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEditAuthorsGdrive @@ -5579,7 +5636,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestEditBooksOnGdrive @@ -5597,7 +5654,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEditBooksOnGdrive @@ -5621,7 +5678,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestSetupGdrive @@ -5639,7 +5696,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestSetupGdrive @@ -5663,13 +5720,13 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 jsonschema - 4.18.4 + 4.19.0 TestKoboSync jsonschema - 4.18.4 + 4.19.0 TestKoboSyncBig @@ -5681,7 +5738,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 jsonschema - 4.18.4 + 4.19.0 TestLdapLogin @@ -5711,7 +5768,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 From a1899bf582c5de73fd0b5d27c0b51f8c6718a4fa Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 23 Aug 2023 21:12:59 +0200 Subject: [PATCH 3/7] Fix for #2603 (Kobo UserKey in request missing due to no kobo account) --- cps/kobo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/kobo.py b/cps/kobo.py index 1655fb5e..47cc4bda 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -1047,7 +1047,7 @@ def make_calibre_web_auth_response(): "RefreshToken": RefreshToken, "TokenType": "Bearer", "TrackingId": str(uuid.uuid4()), - "UserKey": content['UserKey'], + "UserKey": content.get('UserKey',""), } ) ) From b3a85ffcbbc333a00f22ac69f3a21d220769d268 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Thu, 24 Aug 2023 10:51:16 +0200 Subject: [PATCH 4/7] Added CB7 to supported upload formats --- cps/constants.py | 2 +- test/Calibre-Web TestSummary_Linux.html | 89 ++++++++----------------- 2 files changed, 27 insertions(+), 64 deletions(-) diff --git a/cps/constants.py b/cps/constants.py index 069630b6..c7d3a6ce 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -147,7 +147,7 @@ EXTENSIONS_CONVERT_FROM = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'txt', 'htmlz', 'rtf', 'odt', 'cbz', 'cbr'] EXTENSIONS_CONVERT_TO = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'] -EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'djv', +EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'cb7', 'djvu', 'djv', 'prc', 'doc', 'docx', 'fb2', 'html', 'rtf', 'lit', 'odt', 'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac', 'm4a', 'm4b'} diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 83cb4395..66d4df88 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2023-08-17 20:59:16

+

Start Time: 2023-08-23 21:16:31

-

Stop Time: 2023-08-18 03:31:30

+

Stop Time: 2023-08-24 03:51:45

-

Duration: 5h 32 min

+

Duration: 5h 34 min

@@ -2023,9 +2023,15 @@ NameError: name 'details' is not defined
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_edit_books_metadata.py", line 167, in test_load_metadata
-    self.assertGreaterEqual(diff(BytesIO(cover), BytesIO(original_cover), delete_diff_file=True), 0.05)
-AssertionError: 0.0 not greater than or equal to 0.05
+ File "/home/ozzie/Development/calibre-web-test/test/test_edit_books_metadata.py", line 209, in test_load_metadata + self.assertEqual(old_results, results) +AssertionError: Lists differ: [] != [{'cover_element': <selenium.webdriver.rem[10121 chars]4/'}] + +Second list contains 20 additional elements. +First extra element 0: +{'cover_element': <selenium.webdriver.remote.webelement.WebElement (session="34034d2d-f804-47c1-b9ad-fcf09f75f812", element="6dfe81e2-4752-4f1f-bd33-9388d0d529c1")>, 'cover': 'https://books.google.com/books/content?id=Ub8TAQAAIAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api&fife=w800-h900', 'source': 'https://books.google.com/', 'author': 'Martin Vogt', 'publisher': '', 'title': 'Der Buchtitel in der römischen Poesie', 'title_link': 'https://books.google.com/books?id=Ub8TAQAAIAAJ'} + +Diff is 10795 characters long. Set self.maxDiff to None to see it.
@@ -2036,12 +2042,12 @@ AssertionError: 0.0 not greater than or equal to 0.05 - + TestEditBooksOnGdrive 18 - 16 - 1 - 1 + 18 + 0 + 0 0 Detail @@ -2059,34 +2065,11 @@ AssertionError: 0.0 not greater than or equal to 0.05 - +
TestEditBooksOnGdrive - test_edit_author
- -
- FAIL -
- - - - + PASS @@ -2208,31 +2191,11 @@ AssertionError: 'O0ü name' != ' O0ü name ' - +
TestEditBooksOnGdrive - test_edit_title
- -
- ERROR -
- - - - + PASS @@ -5275,9 +5238,9 @@ KeyError: 'title' Total 461 - 446 - 2 - 4 + 448 + 1 + 3 9   @@ -5324,7 +5287,7 @@ KeyError: 'title' APScheduler - 3.10.3 + 3.10.4 Basic @@ -5342,7 +5305,7 @@ KeyError: 'title' Flask - 2.3.2 + 2.3.3 Basic @@ -5768,7 +5731,7 @@ KeyError: 'title' From 0499e578cdd45db656da34cd2d7152c8d88ceb23 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Thu, 24 Aug 2023 13:49:22 +0200 Subject: [PATCH 5/7] Added /opds/stats route --- cps/opds.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cps/opds.py b/cps/opds.py index 074a9b73..4067712f 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -21,9 +21,10 @@ # along with this program. If not, see . import datetime +import json from urllib.parse import unquote_plus -from flask import Blueprint, request, render_template, make_response, abort +from flask import Blueprint, request, render_template, make_response, abort, Response from flask_login import current_user from flask_babel import get_locale from flask_babel import gettext as _ @@ -412,6 +413,17 @@ def get_metadata_calibre_companion(uuid, library): return "" +@opds.route("/opds/stats") +@requires_basic_auth_if_no_ano +def get_database_stats(): + stat = dict() + stat['books'] = calibre_db.session.query(db.Books).count() + stat['authors'] = calibre_db.session.query(db.Authors).count() + stat['categories'] = calibre_db.session.query(db.Tags).count() + stat['series'] = calibre_db.session.query(db.Series).count() + return Response(json.dumps(stat), mimetype="application/json") + + @opds.route("/opds/thumb_240_240/") @opds.route("/opds/cover_240_240/") @opds.route("/opds/cover_90_90/") From 8535bb5821ca868a3187dce59121c019902b8066 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 27 Aug 2023 11:20:53 +0200 Subject: [PATCH 6/7] Fix "got an unexpected keyword argument 'rarExecutable'" during format upload --- cps/editbooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index 5a15740c..f52f08aa 100755 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -1215,7 +1215,7 @@ def upload_single_file(file_request, book, book_id): return uploader.process( saved_filename, *os.path.splitext(requested_file.filename), - rarExecutable=config.config_rarfile_location) + rar_executable=config.config_rarfile_location) return None From 6a14e2cf687c908cb1a7a288f052e569ba58bd8b Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 27 Aug 2023 12:00:15 +0200 Subject: [PATCH 7/7] Next try showing last book of series in grid view --- cps/templates/grid.html | 2 +- cps/web.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cps/templates/grid.html b/cps/templates/grid.html index 1905d52d..3fa6958f 100644 --- a/cps/templates/grid.html +++ b/cps/templates/grid.html @@ -28,7 +28,7 @@
- {{ image.series(entry[0].series[0], alt=entry[0].series[0].name|shortentitle) }} + {{ image.book_cover(entry[0])}} {{entry.count}} diff --git a/cps/web.py b/cps/web.py index 51ff32b3..9f94d1b4 100755 --- a/cps/web.py +++ b/cps/web.py @@ -1002,13 +1002,21 @@ def series_list(): if no_series_count: entries.append([db.Category(_("None"), "-1"), no_series_count]) entries = sorted(entries, key=lambda x: x[0].name.lower(), reverse=not order_no) - return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, - title=_("Series"), page="serieslist", data="series", order=order_no) + return render_title_template('list.html', + entries=entries, + folder='web.books_list', + charlist=char_list, + title=_("Series"), + page="serieslist", + data="series", order=order_no) else: - entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count'), - func.max(db.Books.series_index), db.Books.id) \ - .join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) \ - .group_by(text('books_series_link.series')).order_by(order).all() + entries = (calibre_db.session.query(db.Books, func.count('books_series_link').label('count'), + func.max(db.Books.series_index), db.Books.id) + .join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) + .group_by(text('books_series_link.series')) + .having(func.max(db.Books.series_index)) + .order_by(order) + .all()) return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=char_list, title=_("Series"), page="serieslist", data="series", bodyClass="grid-view", order=order_no)