codex 1.4.0a0__py3-none-any.whl → 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of codex might be problematic. Click here for more details.
- codex/config_default.yaml +12 -4
- codex/db_functions.py +4 -2
- codex/integrity.py +17 -6
- codex/librarian/covers/coverd.py +3 -11
- codex/librarian/covers/create.py +15 -21
- codex/librarian/covers/tasks.py +2 -16
- codex/librarian/importer/aggregate_metadata.py +75 -41
- codex/librarian/importer/clean_metadata.py +30 -7
- codex/librarian/importer/create_fks.py +154 -55
- codex/librarian/importer/deleted.py +11 -2
- codex/librarian/importer/failed_imports.py +44 -5
- codex/librarian/importer/importerd.py +37 -12
- codex/librarian/importer/link_comics.py +54 -31
- codex/librarian/importer/moved.py +55 -11
- codex/librarian/importer/query_fks.py +210 -48
- codex/librarian/importer/tasks.py +7 -7
- codex/librarian/janitor/cleanup.py +17 -5
- codex/librarian/librariand.py +10 -0
- codex/librarian/watchdog/events.py +11 -14
- codex/librarian/watchdog/observers.py +5 -1
- codex/logger/loggerd.py +7 -3
- codex/logger/logging.py +1 -1
- codex/migrations/0024_comic_gtin_comic_story_arc_number.py +24 -0
- codex/migrations/0025_add_story_arc_number.py +83 -0
- codex/models.py +21 -11
- codex/search/backend.py +1 -1
- codex/search/indexes.py +1 -1
- codex/serializers/browser.py +1 -0
- codex/serializers/metadata.py +5 -1
- codex/serializers/models.py +16 -1
- codex/serializers/opds/v1.py +1 -0
- codex/serializers/opds/v2.py +5 -2
- codex/serializers/reader.py +55 -16
- codex/settings/settings.py +1 -1
- codex/static_root/assets/admin-12749881.ef0f50bac290.js +41 -0
- codex/static_root/assets/admin-12749881.ef0f50bac290.js.br +0 -0
- codex/static_root/assets/admin-12749881.ef0f50bac290.js.gz +0 -0
- codex/static_root/assets/admin-12749881.js +41 -0
- codex/static_root/assets/admin-12749881.js.br +0 -0
- codex/static_root/assets/admin-12749881.js.gz +0 -0
- codex/static_root/assets/admin-beda768d.a614eee46307.css +1 -0
- codex/static_root/assets/admin-beda768d.a614eee46307.css.br +0 -0
- codex/static_root/assets/admin-beda768d.a614eee46307.css.gz +0 -0
- codex/static_root/assets/admin-beda768d.css +1 -0
- codex/static_root/assets/admin-beda768d.css.br +0 -0
- codex/static_root/assets/admin-beda768d.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css +1 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.css +1 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js +1 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js +1 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js.gz +0 -0
- codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css +1 -0
- codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.br +0 -0
- codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.gz +0 -0
- codex/static_root/assets/browser-7f7d7134.css +1 -0
- codex/static_root/assets/browser-7f7d7134.css.br +0 -0
- codex/static_root/assets/browser-7f7d7134.css.gz +0 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js +1 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js.br +0 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js.gz +0 -0
- codex/static_root/assets/browser-af622672.js +1 -0
- codex/static_root/assets/browser-af622672.js.br +0 -0
- codex/static_root/assets/browser-af622672.js.gz +0 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js +1 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.br +0 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.gz +0 -0
- codex/static_root/assets/http-error-5e17b794.js +1 -0
- codex/static_root/assets/http-error-5e17b794.js.br +0 -0
- codex/static_root/assets/http-error-5e17b794.js.gz +0 -0
- codex/static_root/assets/{main-a6ac9581.2fd9e52cbcc3.css → main-0898f4bb.181e0145c642.css} +1 -1
- codex/static_root/assets/main-0898f4bb.181e0145c642.css.br +0 -0
- codex/static_root/assets/{main-a6ac9581.2fd9e52cbcc3.css.gz → main-0898f4bb.181e0145c642.css.gz} +0 -0
- codex/static_root/assets/{main-a6ac9581.css → main-0898f4bb.css} +1 -1
- codex/static_root/assets/main-0898f4bb.css.br +0 -0
- codex/static_root/assets/{main-a6ac9581.css.gz → main-0898f4bb.css.gz} +0 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js +1 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.br +0 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.gz +0 -0
- codex/static_root/assets/main-9e76a4c3.js +1 -0
- codex/static_root/assets/main-9e76a4c3.js.br +0 -0
- codex/static_root/assets/main-9e76a4c3.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js +1 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.br +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.js +1 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.js.br +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.js.gz +0 -0
- codex/static_root/assets/{metadata-dialog-785c4cfc.694a251cda37.css → metadata-dialog-cb306ffd.cc304996d7bb.css} +1 -1
- codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.br +0 -0
- codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.gz +0 -0
- codex/static_root/assets/{metadata-dialog-785c4cfc.css → metadata-dialog-cb306ffd.css} +1 -1
- codex/static_root/assets/metadata-dialog-cb306ffd.css.br +0 -0
- codex/static_root/assets/metadata-dialog-cb306ffd.css.gz +0 -0
- codex/static_root/assets/{page-pdf-abfd509d.3870dab8eaf4.js → page-pdf-157ba97e.613d7c2beb77.js} +61 -51
- codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.br +0 -0
- codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.gz +0 -0
- codex/static_root/assets/{page-pdf-abfd509d.js → page-pdf-157ba97e.js} +61 -51
- codex/static_root/assets/page-pdf-157ba97e.js.br +0 -0
- codex/static_root/assets/page-pdf-157ba97e.js.gz +0 -0
- codex/static_root/assets/reader-36266549.0b2cf1291f27.js +1 -0
- codex/static_root/assets/reader-36266549.0b2cf1291f27.js.br +0 -0
- codex/static_root/assets/reader-36266549.0b2cf1291f27.js.gz +0 -0
- codex/static_root/assets/reader-36266549.js +1 -0
- codex/static_root/assets/reader-36266549.js.br +0 -0
- codex/static_root/assets/reader-36266549.js.gz +0 -0
- codex/static_root/assets/reader-7f004141.506eecc6954b.css +1 -0
- codex/static_root/assets/reader-7f004141.506eecc6954b.css.br +0 -0
- codex/static_root/assets/reader-7f004141.506eecc6954b.css.gz +0 -0
- codex/static_root/assets/reader-7f004141.css +1 -0
- codex/static_root/assets/reader-7f004141.css.br +0 -0
- codex/static_root/assets/reader-7f004141.css.gz +0 -0
- codex/static_root/js/choices-admin.24cecf0a0568.json +1 -0
- codex/static_root/js/choices-admin.24cecf0a0568.json.br +0 -0
- codex/static_root/js/choices-admin.24cecf0a0568.json.gz +0 -0
- codex/static_root/js/choices-admin.json +1 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/choices.8c58714cf5b2.json +1 -0
- codex/static_root/js/choices.8c58714cf5b2.json.br +5 -0
- codex/static_root/js/choices.8c58714cf5b2.json.gz +0 -0
- codex/static_root/js/choices.json +1 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/{manifest.64a989215af8.json → manifest.d2f93a519ada.json} +34 -34
- codex/static_root/manifest.d2f93a519ada.json.br +0 -0
- codex/static_root/manifest.d2f93a519ada.json.gz +0 -0
- codex/static_root/manifest.json +34 -34
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/templates/headers-script-globals.html +1 -1
- codex/templates/{opds → opds_v1}/index.xml +3 -1
- codex/templates/{opds/opensearch.xml → opds_v1/opensearch_v1.xml} +1 -1
- codex/templates/search/indexes/codex/comic_text.txt +2 -2
- codex/urls/converters.py +1 -1
- codex/urls/opds/authentication.py +1 -1
- codex/urls/opds/root.py +8 -12
- codex/urls/opds/v1.py +12 -5
- codex/urls/opds/v2.py +2 -2
- codex/views/admin/tasks.py +6 -1
- codex/views/bookmark.py +2 -2
- codex/views/browser/base.py +23 -7
- codex/views/browser/browser.py +66 -56
- codex/views/browser/browser_annotations.py +159 -50
- codex/views/browser/browser_order_by.py +51 -105
- codex/views/browser/choices.py +75 -38
- codex/views/browser/filters/bookmark.py +6 -9
- codex/views/browser/filters/field.py +9 -6
- codex/views/browser/filters/group.py +12 -27
- codex/views/browser/filters/search.py +5 -10
- codex/views/browser/metadata.py +44 -19
- codex/views/download.py +1 -1
- codex/views/frontend.py +2 -3
- codex/views/mixins.py +15 -2
- codex/views/opds/const.py +8 -1
- codex/views/opds/util.py +37 -1
- codex/views/opds/v1/__init__.py +1 -1
- codex/views/opds/v1/data.py +21 -0
- codex/views/opds/v1/entry/__init__.py +1 -0
- codex/views/opds/v1/entry/data.py +23 -0
- codex/views/opds/v1/entry/entry.py +151 -0
- codex/views/opds/v1/entry/links.py +135 -0
- codex/views/opds/v1/facets.py +190 -0
- codex/views/opds/v1/feed.py +199 -0
- codex/views/opds/v1/links.py +198 -0
- codex/views/opds/{opensearch.py → v1/opensearch_v1.py} +3 -3
- codex/views/opds/v2/__init__.py +1 -1
- codex/views/opds/v2/const.py +10 -2
- codex/views/opds/v2/feed.py +82 -21
- codex/views/opds/v2/links.py +1 -1
- codex/views/opds/v2/publications.py +1 -1
- codex/views/opds/v2/top_links.py +1 -1
- codex/views/reader/page.py +6 -7
- codex/views/reader/reader.py +191 -61
- codex/views/session.py +2 -1
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/METADATA +10 -41
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/RECORD +187 -185
- codex/librarian/importer/db_ops.py +0 -248
- codex/pdf.py +0 -115
- codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js +0 -48
- codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js.br +0 -0
- codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js.gz +0 -0
- codex/static_root/assets/admin-73d93dc7.js +0 -48
- codex/static_root/assets/admin-73d93dc7.js.br +0 -0
- codex/static_root/assets/admin-73d93dc7.js.gz +0 -0
- codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css +0 -1
- codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css.br +0 -0
- codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css.gz +0 -0
- codex/static_root/assets/admin-79555229.css +0 -1
- codex/static_root/assets/admin-79555229.css.br +0 -0
- codex/static_root/assets/admin-79555229.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js +0 -1
- codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.js +0 -1
- codex/static_root/assets/admin-drawer-panel-64bcc083.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css +0 -1
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.br +0 -2
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.css +0 -1
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.br +0 -2
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.gz +0 -0
- codex/static_root/assets/browser-7325db61.css +0 -1
- codex/static_root/assets/browser-7325db61.css.br +0 -0
- codex/static_root/assets/browser-7325db61.css.gz +0 -0
- codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css +0 -1
- codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.br +0 -0
- codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.gz +0 -0
- codex/static_root/assets/browser-d2caeed7.2262000a6d55.js +0 -1
- codex/static_root/assets/browser-d2caeed7.2262000a6d55.js.br +0 -0
- codex/static_root/assets/browser-d2caeed7.2262000a6d55.js.gz +0 -0
- codex/static_root/assets/browser-d2caeed7.js +0 -1
- codex/static_root/assets/browser-d2caeed7.js.br +0 -0
- codex/static_root/assets/browser-d2caeed7.js.gz +0 -0
- codex/static_root/assets/http-error-0221c37d.480d5066da92.js +0 -1
- codex/static_root/assets/http-error-0221c37d.480d5066da92.js.br +0 -0
- codex/static_root/assets/http-error-0221c37d.480d5066da92.js.gz +0 -0
- codex/static_root/assets/http-error-0221c37d.js +0 -1
- codex/static_root/assets/http-error-0221c37d.js.br +0 -0
- codex/static_root/assets/http-error-0221c37d.js.gz +0 -0
- codex/static_root/assets/main-a6ac9581.2fd9e52cbcc3.css.br +0 -0
- codex/static_root/assets/main-a6ac9581.css.br +0 -0
- codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js +0 -1
- codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js.br +0 -0
- codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js.gz +0 -0
- codex/static_root/assets/main-e33dcfb0.js +0 -1
- codex/static_root/assets/main-e33dcfb0.js.br +0 -0
- codex/static_root/assets/main-e33dcfb0.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.br +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.gz +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.css.br +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.css.gz +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js +0 -1
- codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js.br +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.js +0 -1
- codex/static_root/assets/metadata-dialog-8b0e8aaa.js.br +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.js.gz +0 -0
- codex/static_root/assets/page-pdf-abfd509d.3870dab8eaf4.js.br +0 -0
- codex/static_root/assets/page-pdf-abfd509d.3870dab8eaf4.js.gz +0 -0
- codex/static_root/assets/page-pdf-abfd509d.js.br +0 -0
- codex/static_root/assets/page-pdf-abfd509d.js.gz +0 -0
- codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css +0 -1
- codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css.br +0 -0
- codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css.gz +0 -0
- codex/static_root/assets/reader-a8b8f766.css +0 -1
- codex/static_root/assets/reader-a8b8f766.css.br +0 -0
- codex/static_root/assets/reader-a8b8f766.css.gz +0 -0
- codex/static_root/assets/reader-fe9345d2.759c31f82998.js +0 -1
- codex/static_root/assets/reader-fe9345d2.759c31f82998.js.br +0 -0
- codex/static_root/assets/reader-fe9345d2.759c31f82998.js.gz +0 -0
- codex/static_root/assets/reader-fe9345d2.js +0 -1
- codex/static_root/assets/reader-fe9345d2.js.br +0 -0
- codex/static_root/assets/reader-fe9345d2.js.gz +0 -0
- codex/static_root/js/choices-admin.3d958ea7f83b.json +0 -1
- codex/static_root/js/choices-admin.3d958ea7f83b.json.br +0 -0
- codex/static_root/js/choices-admin.3d958ea7f83b.json.gz +0 -0
- codex/static_root/js/choices.6bfc2a3d293f.json +0 -1
- codex/static_root/js/choices.6bfc2a3d293f.json.br +0 -0
- codex/static_root/js/choices.6bfc2a3d293f.json.gz +0 -0
- codex/static_root/manifest.64a989215af8.json.br +0 -0
- codex/static_root/manifest.64a989215af8.json.gz +0 -0
- codex/urls/opds/opensearch.py +0 -18
- codex/views/opds/v1/browser.py +0 -346
- codex/views/opds/v1/entry.py +0 -278
- codex/views/opds/v1/start.py +0 -28
- codex/views/opds/v1/util.py +0 -162
- codex/views/opds/v2/start.py +0 -28
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/LICENSE +0 -0
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/WHEEL +0 -0
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/entry_points.txt +0 -0
codex/config_default.yaml
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
comicbox:
|
|
2
|
-
comet: True
|
|
3
|
-
comicbookinfo: True
|
|
4
|
-
comicinfoxml: True
|
|
5
2
|
delete_orig: False
|
|
6
3
|
delete_tags: False
|
|
7
4
|
dest_path: .
|
|
8
5
|
dry_run: False
|
|
9
|
-
filename: True
|
|
10
6
|
raw: False
|
|
7
|
+
read_comet: True
|
|
8
|
+
read_comicbookinfo: True
|
|
9
|
+
read_comicinfoxml: True
|
|
10
|
+
read_filename: True
|
|
11
|
+
read_pdf: True
|
|
11
12
|
recurse: False
|
|
12
13
|
metadata: {}
|
|
14
|
+
close_fd: True
|
|
15
|
+
check_unrar: False
|
|
16
|
+
write_comet: False
|
|
17
|
+
write_comicbookinfo: False
|
|
18
|
+
write_comicinfoxml: False
|
|
19
|
+
write_filename: False
|
|
20
|
+
write_pdf: False
|
codex/db_functions.py
CHANGED
|
@@ -3,9 +3,11 @@ from django.db.models import Aggregate
|
|
|
3
3
|
from django.db.models.fields import CharField
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
# TODO unused
|
|
7
6
|
class GroupConcat(Aggregate):
|
|
8
|
-
"""Sqlite3 group_concat() function.
|
|
7
|
+
"""Sqlite3 group_concat() function.
|
|
8
|
+
|
|
9
|
+
Unused.
|
|
10
|
+
"""
|
|
9
11
|
|
|
10
12
|
function = "GROUP_CONCAT"
|
|
11
13
|
template = "%(function)s(%(distinct)s %(expressions)s)"
|
codex/integrity.py
CHANGED
|
@@ -31,17 +31,19 @@ MIGRATION_0010 = "0010_haystack"
|
|
|
31
31
|
MIGRATION_0011 = "0010_library_groups_and_metadata_changes"
|
|
32
32
|
MIGRATION_0018 = "0018_rename_userbookmark_bookmark"
|
|
33
33
|
MIGRATION_0023 = "0023_rename_credit_creator_and_more"
|
|
34
|
+
MIGRATION_0025 = "0025_add_story_arc_number"
|
|
34
35
|
M2M_NAMES = {
|
|
35
36
|
"Character": "characters",
|
|
36
37
|
"Creator": "creators",
|
|
37
38
|
"Genre": "genres",
|
|
38
39
|
"Location": "locations",
|
|
39
40
|
"SeriesGroup": "series_groups",
|
|
40
|
-
"
|
|
41
|
+
"StoryArcNumber": "story_arc_numbers",
|
|
41
42
|
"Tag": "tags",
|
|
42
43
|
"Team": "teams",
|
|
43
44
|
"Folder": "folders",
|
|
44
45
|
}
|
|
46
|
+
SKIP_M2M_CHECKS = {"Creator": MIGRATION_0023, "StoryArcNumber": MIGRATION_0025}
|
|
45
47
|
NULL_SET = frozenset([None])
|
|
46
48
|
HAVE_LIBRARY_FKS = ("FailedImport", "Folder", "Comic")
|
|
47
49
|
GROUP_HOSTS = {
|
|
@@ -68,6 +70,7 @@ DELETE_BAD_COMIC_FOLDER_RELATIONS_SQL = (
|
|
|
68
70
|
'IN (SELECT "codex_folder"."id" FROM "codex_folder")))'
|
|
69
71
|
)
|
|
70
72
|
MIGRATION_DIR = CODEX_PATH / "migrations"
|
|
73
|
+
|
|
71
74
|
LOG = get_logger(__name__)
|
|
72
75
|
|
|
73
76
|
|
|
@@ -289,16 +292,24 @@ def _delete_errors():
|
|
|
289
292
|
_delete_old_comic_folder_fks()
|
|
290
293
|
|
|
291
294
|
|
|
295
|
+
def _check_field_for_migration(model_name):
|
|
296
|
+
"""Skip some models before migrations."""
|
|
297
|
+
for check_model_name, migration in SKIP_M2M_CHECKS.items():
|
|
298
|
+
if model_name == check_model_name and not has_applied_migration(migration):
|
|
299
|
+
LOG.debug(
|
|
300
|
+
f"Skipping {check_model_name} integrity check until"
|
|
301
|
+
f"migration {migration} applied."
|
|
302
|
+
)
|
|
303
|
+
return True
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
|
|
292
307
|
def _repair_integrity():
|
|
293
308
|
"""REPAIR the objects that are left."""
|
|
294
309
|
comic_model = apps.get_model("codex", "Comic")
|
|
295
310
|
bad_comic_ids = comic_model.objects.none()
|
|
296
311
|
for m2m_model_name, field_name in M2M_NAMES.items():
|
|
297
|
-
if m2m_model_name
|
|
298
|
-
LOG.debug(
|
|
299
|
-
"Skipping Creator integrity check until"
|
|
300
|
-
f"migration {MIGRATION_0023} applied."
|
|
301
|
-
)
|
|
312
|
+
if _check_field_for_migration(m2m_model_name):
|
|
302
313
|
continue
|
|
303
314
|
try:
|
|
304
315
|
bad_comic_ids |= _fix_comic_m2m_integrity_errors(
|
codex/librarian/covers/coverd.py
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
from codex.librarian.covers.create import CoverCreateMixin
|
|
3
3
|
from codex.librarian.covers.purge import CoverPurgeMixin
|
|
4
4
|
from codex.librarian.covers.tasks import (
|
|
5
|
-
|
|
6
|
-
CoverCreateTask,
|
|
5
|
+
CoverCreateAllTask,
|
|
7
6
|
CoverRemoveAllTask,
|
|
8
7
|
CoverRemoveOrphansTask,
|
|
9
8
|
CoverRemoveTask,
|
|
@@ -24,14 +23,7 @@ class CoverCreatorThread(CoverCreateMixin, CoverPurgeMixin):
|
|
|
24
23
|
self.purge_comic_covers(task.comic_pks)
|
|
25
24
|
elif isinstance(task, CoverRemoveOrphansTask):
|
|
26
25
|
self.cleanup_orphan_covers()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# UNUSED BELOW HERE #
|
|
30
|
-
#####################
|
|
31
|
-
|
|
32
|
-
elif isinstance(task, CoverCreateTask):
|
|
33
|
-
self.create_cover(task.pk)
|
|
34
|
-
elif isinstance(task, CoverBulkCreateTask):
|
|
35
|
-
self.bulk_create_comic_covers(task.comic_pks)
|
|
26
|
+
elif isinstance(task, CoverCreateAllTask):
|
|
27
|
+
self.create_all_covers()
|
|
36
28
|
else:
|
|
37
29
|
self.log.error(f"Bad task sent to {self.__class__.__name__}: {task}")
|
codex/librarian/covers/create.py
CHANGED
|
@@ -10,7 +10,6 @@ from codex.librarian.covers.path import CoverPathMixin
|
|
|
10
10
|
from codex.librarian.covers.status import CoverStatusTypes
|
|
11
11
|
from codex.librarian.covers.tasks import CoverSaveToCache
|
|
12
12
|
from codex.models import Comic
|
|
13
|
-
from codex.pdf import PDF
|
|
14
13
|
from codex.status import Status
|
|
15
14
|
from codex.version import COMICBOX_CONFIG
|
|
16
15
|
|
|
@@ -39,13 +38,12 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
39
38
|
return cover_thumb_buffer.getvalue()
|
|
40
39
|
|
|
41
40
|
@classmethod
|
|
42
|
-
def _get_comic_cover_image(cls,
|
|
41
|
+
def _get_comic_cover_image(cls, comic_path):
|
|
43
42
|
"""Create comic cover if none exists.
|
|
44
43
|
|
|
45
44
|
Return image thumb data or path to missing file thumb.
|
|
46
45
|
"""
|
|
47
|
-
|
|
48
|
-
with car_class(comic.path, config=COMICBOX_CONFIG) as car:
|
|
46
|
+
with ComicArchive(comic_path, config=COMICBOX_CONFIG) as car:
|
|
49
47
|
image_data = car.get_cover_image()
|
|
50
48
|
if not image_data:
|
|
51
49
|
reason = "Read empty cover"
|
|
@@ -58,14 +56,14 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
58
56
|
|
|
59
57
|
Called from views/cover.
|
|
60
58
|
"""
|
|
61
|
-
|
|
59
|
+
comic_path = None
|
|
62
60
|
try:
|
|
63
|
-
|
|
64
|
-
cover_image = cls._get_comic_cover_image(
|
|
61
|
+
comic_path = Comic.objects.only("path").get(pk=pk).path
|
|
62
|
+
cover_image = cls._get_comic_cover_image(comic_path)
|
|
65
63
|
data = cls._create_cover_thumbnail(cover_image)
|
|
66
64
|
except Exception as exc:
|
|
67
65
|
data = b""
|
|
68
|
-
comic_str =
|
|
66
|
+
comic_str = comic_path if comic_path else f"{pk=}"
|
|
69
67
|
log.warning(f"Could not create cover thumbnail for {comic_str}: {exc}")
|
|
70
68
|
|
|
71
69
|
task = CoverSaveToCache(cover_path, data)
|
|
@@ -81,19 +79,8 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
81
79
|
else:
|
|
82
80
|
cover_path.symlink_to(self.MISSING_COVER_PATH)
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
# UNUSED BELOW HERE #
|
|
86
|
-
#####################
|
|
87
|
-
|
|
88
|
-
def create_cover(self, pk):
|
|
89
|
-
"""Create a cover from a comic id."""
|
|
90
|
-
# XXX Unused.
|
|
91
|
-
cover_path = self.get_cover_path(pk)
|
|
92
|
-
self.create_cover_from_path(pk, cover_path, self.log, self.librarian_queue)
|
|
93
|
-
|
|
94
|
-
def bulk_create_comic_covers(self, comic_pks):
|
|
82
|
+
def _bulk_create_comic_covers(self, comic_pks):
|
|
95
83
|
"""Create bulk comic covers."""
|
|
96
|
-
# XXX Unused
|
|
97
84
|
num_comics = len(comic_pks)
|
|
98
85
|
if not num_comics:
|
|
99
86
|
return None
|
|
@@ -111,7 +98,9 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
111
98
|
status.decrement_total()
|
|
112
99
|
else:
|
|
113
100
|
# bulk creator creates covers inline
|
|
114
|
-
self.
|
|
101
|
+
self.create_cover_from_path(
|
|
102
|
+
pk, cover_path, self.log, self.librarian_queue
|
|
103
|
+
)
|
|
115
104
|
status.increment_complete()
|
|
116
105
|
self.status_controller.update(status)
|
|
117
106
|
|
|
@@ -120,3 +109,8 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
120
109
|
finally:
|
|
121
110
|
self.status_controller.finish(status)
|
|
122
111
|
return status.complete
|
|
112
|
+
|
|
113
|
+
def create_all_covers(self):
|
|
114
|
+
"""Create all covers for all libraries."""
|
|
115
|
+
pks = Comic.objects.values_list("pk", flat=True)
|
|
116
|
+
self._bulk_create_comic_covers(pks)
|
codex/librarian/covers/tasks.py
CHANGED
|
@@ -39,20 +39,6 @@ class CoverSaveToCache(CoverTask):
|
|
|
39
39
|
data: bytes
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
#####################
|
|
43
|
-
# UNUSED BELOW HERE #
|
|
44
|
-
#####################
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@dataclass
|
|
48
|
-
class CoverCreateTask(CoverTask):
|
|
49
|
-
"""Create one comic cover."""
|
|
50
|
-
|
|
51
|
-
pk: int
|
|
52
|
-
|
|
53
|
-
|
|
54
42
|
@dataclass
|
|
55
|
-
class
|
|
56
|
-
"""A
|
|
57
|
-
|
|
58
|
-
comic_pks: frozenset
|
|
43
|
+
class CoverCreateAllTask(CoverTask):
|
|
44
|
+
"""A create all comic covers."""
|
|
@@ -4,13 +4,13 @@ from zipfile import BadZipFile
|
|
|
4
4
|
|
|
5
5
|
from comicbox.comic_archive import ComicArchive
|
|
6
6
|
from comicbox.exceptions import UnsupportedArchiveTypeError
|
|
7
|
+
from confuse import AttrDict
|
|
7
8
|
from rarfile import BadRarFile
|
|
8
9
|
|
|
9
10
|
from codex.comic_field_names import COMIC_M2M_FIELD_NAMES
|
|
10
11
|
from codex.librarian.importer.clean_metadata import CleanMetadataMixin
|
|
11
12
|
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
12
13
|
from codex.models import Comic, Imprint, Publisher, Series, Volume
|
|
13
|
-
from codex.pdf import PDF
|
|
14
14
|
from codex.status import Status
|
|
15
15
|
from codex.version import COMICBOX_CONFIG
|
|
16
16
|
|
|
@@ -20,6 +20,10 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
20
20
|
|
|
21
21
|
_BROWSER_GROUPS = (Publisher, Imprint, Series, Volume)
|
|
22
22
|
_BROWSER_GROUP_TREE_COUNT_FIELDS = frozenset(["volume_count", "issue_count"])
|
|
23
|
+
_GROUP_TREES_INIT = {
|
|
24
|
+
"group_trees": {Publisher: {}, Imprint: {}, Series: {}, Volume: {}},
|
|
25
|
+
}
|
|
26
|
+
_AGGREGATE_COMICBOX_CONFIG = AttrDict({**COMICBOX_CONFIG, "close_fd": False})
|
|
23
27
|
|
|
24
28
|
@staticmethod
|
|
25
29
|
def _get_file_type(path):
|
|
@@ -39,8 +43,7 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
39
43
|
group_tree_md = {}
|
|
40
44
|
failed_import = {}
|
|
41
45
|
try:
|
|
42
|
-
|
|
43
|
-
with car_class(path, config=COMICBOX_CONFIG, closefd=False) as car:
|
|
46
|
+
with ComicArchive(path, config=self._AGGREGATE_COMICBOX_CONFIG) as car:
|
|
44
47
|
md = car.get_metadata()
|
|
45
48
|
md["file_type"] = car.get_file_type()
|
|
46
49
|
|
|
@@ -79,27 +82,53 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
79
82
|
return md, m2m_md, group_tree_md, failed_import
|
|
80
83
|
|
|
81
84
|
@staticmethod
|
|
82
|
-
def
|
|
85
|
+
def _aggregate_m2m_metadata_creators(creator_dict_list, all_fks):
|
|
86
|
+
"""Aggregate creators metadata."""
|
|
87
|
+
if not creator_dict_list:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if "creators" not in all_fks:
|
|
91
|
+
all_fks["creators"] = set()
|
|
92
|
+
|
|
93
|
+
for creator_dict in creator_dict_list:
|
|
94
|
+
# add the fk relations to fks to query.
|
|
95
|
+
for creator_field, name in creator_dict.items():
|
|
96
|
+
# These fields are ambiguous because they're fks to creator
|
|
97
|
+
# but aren't ever in Comic so query_fks.py can
|
|
98
|
+
# disambiguate with special code
|
|
99
|
+
if creator_field not in all_fks:
|
|
100
|
+
all_fks[creator_field] = set()
|
|
101
|
+
all_fks[creator_field].add(name)
|
|
102
|
+
|
|
103
|
+
# Add creators to the all_fks list as well.
|
|
104
|
+
creator_tuple = tuple(sorted(creator_dict.items()))
|
|
105
|
+
all_fks["creators"].add(creator_tuple)
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _aggregate_m2m_metadata_story_arc_numbers(story_arc_numbers_dict, all_fks):
|
|
109
|
+
"""Aggregate story arc numbers."""
|
|
110
|
+
if not story_arc_numbers_dict:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if "story_arc" not in all_fks:
|
|
114
|
+
all_fks["story_arc"] = set()
|
|
115
|
+
all_fks["story_arc"] |= frozenset(story_arc_numbers_dict.keys())
|
|
116
|
+
|
|
117
|
+
if "story_arc_numbers" not in all_fks:
|
|
118
|
+
all_fks["story_arc_numbers"] = set()
|
|
119
|
+
all_fks["story_arc_numbers"] |= frozenset(story_arc_numbers_dict.items())
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def _aggregate_m2m_metadata(cls, all_m2m_mds, m2m_md, all_fks, path):
|
|
83
123
|
"""Aggregate many to many metadata by ."""
|
|
84
124
|
# m2m fields and fks
|
|
85
125
|
all_m2m_mds[path] = m2m_md
|
|
86
126
|
# aggregate fks
|
|
87
127
|
for field, names in m2m_md.items():
|
|
88
128
|
if field == "creators":
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# for creators add the creator fks to fks
|
|
93
|
-
for creator_dict in names:
|
|
94
|
-
for creator_field, name in creator_dict.items():
|
|
95
|
-
# These fields are ambiguous because they're fks to creator
|
|
96
|
-
# but aren't ever in Comic so query_fks.py can
|
|
97
|
-
# disambiguate with special code
|
|
98
|
-
if creator_field not in all_fks:
|
|
99
|
-
all_fks[creator_field] = set()
|
|
100
|
-
all_fks[creator_field].add(name)
|
|
101
|
-
creator_tuple = tuple(sorted(creator_dict.items()))
|
|
102
|
-
all_fks["creators"].add(creator_tuple)
|
|
129
|
+
cls._aggregate_m2m_metadata_creators(names, all_fks)
|
|
130
|
+
elif field == "story_arc_numbers":
|
|
131
|
+
cls._aggregate_m2m_metadata_story_arc_numbers(names, all_fks)
|
|
103
132
|
elif field != "folders":
|
|
104
133
|
if field not in all_fks:
|
|
105
134
|
all_fks[field] = set()
|
|
@@ -138,6 +167,28 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
138
167
|
cls._set_max_group_count(common_args, Series, 3, "volume_count")
|
|
139
168
|
cls._set_max_group_count(common_args, Volume, 4, "issue_count")
|
|
140
169
|
|
|
170
|
+
def _aggregate_path(self, data, path):
|
|
171
|
+
"""Aggregate metadata for one path."""
|
|
172
|
+
path_str = str(path)
|
|
173
|
+
md, m2m_md, group_tree_md, failed_import = self._get_path_metadata(path_str)
|
|
174
|
+
|
|
175
|
+
all_failed_imports, all_mds, all_m2m_mds, all_fks, status = data
|
|
176
|
+
if failed_import:
|
|
177
|
+
all_failed_imports.update(failed_import)
|
|
178
|
+
else:
|
|
179
|
+
if md:
|
|
180
|
+
all_mds[path_str] = md
|
|
181
|
+
|
|
182
|
+
if m2m_md:
|
|
183
|
+
self._aggregate_m2m_metadata(all_m2m_mds, m2m_md, all_fks, path_str)
|
|
184
|
+
|
|
185
|
+
if group_tree_md:
|
|
186
|
+
self._aggregate_group_tree_metadata(all_fks, group_tree_md)
|
|
187
|
+
|
|
188
|
+
if status:
|
|
189
|
+
status.complete += 1
|
|
190
|
+
self.status_controller.update(status)
|
|
191
|
+
|
|
141
192
|
@status_notify(status_type=ImportStatusTypes.AGGREGATE_TAGS)
|
|
142
193
|
def get_aggregate_metadata(
|
|
143
194
|
self,
|
|
@@ -155,30 +206,13 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
155
206
|
all_m2m_mds = metadata["m2m_mds"]
|
|
156
207
|
all_fks = metadata["fks"]
|
|
157
208
|
all_failed_imports = metadata["fis"]
|
|
158
|
-
all_fks.update(
|
|
159
|
-
{
|
|
160
|
-
"group_trees": {Publisher: {}, Imprint: {}, Series: {}, Volume: {}},
|
|
161
|
-
}
|
|
162
|
-
)
|
|
163
|
-
for path in all_paths:
|
|
164
|
-
path_str = str(path)
|
|
165
|
-
md, m2m_md, group_tree_md, failed_import = self._get_path_metadata(path_str)
|
|
209
|
+
all_fks.update(self._GROUP_TREES_INIT)
|
|
166
210
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if m2m_md:
|
|
174
|
-
self._aggregate_m2m_metadata(all_m2m_mds, m2m_md, all_fks, path_str)
|
|
175
|
-
|
|
176
|
-
if group_tree_md:
|
|
177
|
-
self._aggregate_group_tree_metadata(all_fks, group_tree_md)
|
|
178
|
-
|
|
179
|
-
if status:
|
|
180
|
-
status.complete += 1
|
|
181
|
-
self.status_controller.update(status)
|
|
211
|
+
if status and status.complete is None:
|
|
212
|
+
status.complete = 0
|
|
213
|
+
data = (all_failed_imports, all_mds, all_m2m_mds, all_fks, status)
|
|
214
|
+
for path in all_paths:
|
|
215
|
+
self._aggregate_path(data, path)
|
|
182
216
|
|
|
183
217
|
all_fks["comic_paths"] = frozenset(all_mds.keys())
|
|
184
218
|
fi_status = Status(ImportStatusTypes.FAILED_IMPORTS, 0, len(all_failed_imports))
|
|
@@ -21,10 +21,10 @@ _MD_INVALID_KEYS = frozenset(
|
|
|
21
21
|
"updated_at",
|
|
22
22
|
)
|
|
23
23
|
)
|
|
24
|
-
|
|
24
|
+
_MD_TRANSFORMED_KEYS = frozenset(("credits", "story_arcs"))
|
|
25
25
|
_MD_VALID_KEYS = (
|
|
26
26
|
frozenset([field.name for field in Comic._meta.get_fields()]) - _MD_INVALID_KEYS
|
|
27
|
-
|
|
|
27
|
+
| _MD_TRANSFORMED_KEYS
|
|
28
28
|
)
|
|
29
29
|
_MD_DECIMAL_KEYS = frozenset(("community_rating", "critical_rating"))
|
|
30
30
|
_MD_PSI_KEYS = frozenset(
|
|
@@ -33,6 +33,7 @@ _MD_PSI_KEYS = frozenset(
|
|
|
33
33
|
"month",
|
|
34
34
|
"day",
|
|
35
35
|
"page_count",
|
|
36
|
+
"story_arc_number",
|
|
36
37
|
) # computed by presave: ("decade", "max_page", "size")
|
|
37
38
|
)
|
|
38
39
|
_MD_CHAR_KEYS = frozenset(
|
|
@@ -42,6 +43,7 @@ _MD_CHAR_KEYS = frozenset(
|
|
|
42
43
|
"cover_pk",
|
|
43
44
|
"file_type",
|
|
44
45
|
"format",
|
|
46
|
+
"gtin",
|
|
45
47
|
"issue_suffix",
|
|
46
48
|
"language",
|
|
47
49
|
"name",
|
|
@@ -59,7 +61,6 @@ _M2M_NAMED_KEYS = frozenset(
|
|
|
59
61
|
"genres",
|
|
60
62
|
"locations",
|
|
61
63
|
"series_groups",
|
|
62
|
-
"story_arcs",
|
|
63
64
|
"tags",
|
|
64
65
|
"teams",
|
|
65
66
|
)
|
|
@@ -134,6 +135,16 @@ class CleanMetadataMixin(QueuedThread):
|
|
|
134
135
|
summary += description
|
|
135
136
|
md["summary"] = summary
|
|
136
137
|
|
|
138
|
+
@staticmethod
|
|
139
|
+
def _append_review(md) -> None:
|
|
140
|
+
"""Append the CIX review to the CIX summary."""
|
|
141
|
+
if review := md.get("review", ""):
|
|
142
|
+
summary: str = md.get("summary", "")
|
|
143
|
+
if summary:
|
|
144
|
+
summary += "\n"
|
|
145
|
+
summary += review
|
|
146
|
+
md["summary"] = summary
|
|
147
|
+
|
|
137
148
|
@staticmethod
|
|
138
149
|
def _title_to_name(md):
|
|
139
150
|
"""Convert title to name for comics."""
|
|
@@ -174,13 +185,23 @@ class CleanMetadataMixin(QueuedThread):
|
|
|
174
185
|
good_creators = []
|
|
175
186
|
field: CharField = NamedModel._meta.get_field("name") # type:ignore
|
|
176
187
|
for creator in creators:
|
|
177
|
-
person
|
|
178
|
-
if person:
|
|
188
|
+
if person := cls._clean_charfield(creator.get("person"), field):
|
|
179
189
|
role = cls._clean_charfield(creator.get("role"), field)
|
|
180
190
|
good_creator = {"role": role, "person": person}
|
|
181
191
|
good_creators.append(good_creator)
|
|
182
192
|
md["creators"] = good_creators
|
|
183
193
|
|
|
194
|
+
@classmethod
|
|
195
|
+
def _clean_comic_story_arcs(cls, md):
|
|
196
|
+
"""Replace story_arcs with good story_arc_numbers."""
|
|
197
|
+
if story_arc_numbers := md.pop("story_arcs", None):
|
|
198
|
+
good_story_arc_numbers = {}
|
|
199
|
+
field: CharField = NamedModel._meta.get_field("name") # type:ignore
|
|
200
|
+
for story_arc_name, number in story_arc_numbers.items():
|
|
201
|
+
if good_story_arc_name := cls._clean_charfield(story_arc_name, field):
|
|
202
|
+
good_story_arc_numbers[good_story_arc_name] = number
|
|
203
|
+
md["story_arc_numbers"] = good_story_arc_numbers
|
|
204
|
+
|
|
184
205
|
@staticmethod
|
|
185
206
|
def _clean_comic_web(md):
|
|
186
207
|
"""URL field is a special charfield."""
|
|
@@ -194,14 +215,14 @@ class CleanMetadataMixin(QueuedThread):
|
|
|
194
215
|
@classmethod
|
|
195
216
|
def _clean_comic_m2m_named(cls, md: dict[str, Any], md_keys: frozenset[str]):
|
|
196
217
|
"""Clean the named models in the m2m fields."""
|
|
218
|
+
named_field: CharField = NamedModel._meta.get_field("name") # type:ignore
|
|
197
219
|
for key in _M2M_NAMED_KEYS.intersection(md_keys):
|
|
198
220
|
names = md.get(key)
|
|
199
221
|
if not names:
|
|
200
222
|
continue
|
|
201
223
|
cleaned_names = []
|
|
202
|
-
field: CharField = NamedModel._meta.get_field("name") # type:ignore
|
|
203
224
|
for name in names:
|
|
204
|
-
cleaned_name = cls._clean_charfield(name,
|
|
225
|
+
cleaned_name = cls._clean_charfield(name, named_field)
|
|
205
226
|
if cleaned_name:
|
|
206
227
|
cleaned_names.append(cleaned_name)
|
|
207
228
|
md[key] = cleaned_names
|
|
@@ -228,6 +249,8 @@ class CleanMetadataMixin(QueuedThread):
|
|
|
228
249
|
self._clean_comic_charfields(md, md_keys)
|
|
229
250
|
self._clean_comic_web(md)
|
|
230
251
|
self._append_description(md)
|
|
252
|
+
self._append_review(md)
|
|
231
253
|
self._clean_comic_creators(md)
|
|
254
|
+
self._clean_comic_story_arcs(md)
|
|
232
255
|
self._clean_comic_m2m_named(md, md_keys)
|
|
233
256
|
return md
|