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
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
So we may safely create the comics next.
|
|
4
4
|
"""
|
|
5
|
+
from itertools import chain
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
|
-
from django.db.models.functions import Now
|
|
8
|
-
|
|
9
8
|
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
10
9
|
from codex.models import (
|
|
11
10
|
Creator,
|
|
@@ -15,11 +14,14 @@ from codex.models import (
|
|
|
15
14
|
Imprint,
|
|
16
15
|
Publisher,
|
|
17
16
|
Series,
|
|
17
|
+
StoryArc,
|
|
18
|
+
StoryArcNumber,
|
|
18
19
|
Volume,
|
|
19
20
|
)
|
|
21
|
+
from codex.status import Status
|
|
20
22
|
from codex.threads import QueuedThread
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
BULK_UPDATE_FOLDER_MODIFIED_FIELDS = ("stat", "updated_at")
|
|
23
25
|
_COUNT_FIELDS = {Series: "volume_count", Volume: "issue_count"}
|
|
24
26
|
_GROUP_UPDATE_FIELDS = {
|
|
25
27
|
Publisher: ("name",),
|
|
@@ -28,7 +30,11 @@ _GROUP_UPDATE_FIELDS = {
|
|
|
28
30
|
Volume: ("name", "publisher", "imprint", "series"),
|
|
29
31
|
}
|
|
30
32
|
_NAMED_MODEL_UPDATE_FIELDS = ("name",)
|
|
31
|
-
|
|
33
|
+
|
|
34
|
+
_CREATE_DICT_UPDATE_FIELDS = {
|
|
35
|
+
Creator: ("person", "role"),
|
|
36
|
+
StoryArcNumber: ("story_arc", "number"),
|
|
37
|
+
}
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
class CreateForeignKeysMixin(QueuedThread):
|
|
@@ -63,8 +69,8 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
63
69
|
@staticmethod
|
|
64
70
|
def _update_group_obj(group_class, group_param_tuple, count_dict, count_field):
|
|
65
71
|
"""Update group counts for a Series or Volume."""
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
count = count_dict.get(count_field)
|
|
73
|
+
if count is None:
|
|
68
74
|
return None
|
|
69
75
|
search_kwargs = {
|
|
70
76
|
"publisher__name": group_param_tuple[0],
|
|
@@ -76,15 +82,14 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
76
82
|
|
|
77
83
|
obj = group_class.objects.get(**search_kwargs)
|
|
78
84
|
obj_count = getattr(obj, count_field)
|
|
79
|
-
count
|
|
80
|
-
if obj_count is None or (count is not None and obj_count < count):
|
|
85
|
+
if obj_count is None or count > obj_count:
|
|
81
86
|
setattr(obj, count_field, count)
|
|
82
87
|
else:
|
|
83
88
|
obj = None
|
|
84
89
|
return obj
|
|
85
90
|
|
|
86
91
|
@status_notify()
|
|
87
|
-
def
|
|
92
|
+
def _bulk_group_creator(self, group_tree_counts, group_class, status=None):
|
|
88
93
|
"""Bulk creates groups."""
|
|
89
94
|
count = 0
|
|
90
95
|
if not group_tree_counts:
|
|
@@ -103,12 +108,13 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
103
108
|
count += len(create_groups)
|
|
104
109
|
self.log.info(f"Created {count} {group_class.__name__}s.")
|
|
105
110
|
if status:
|
|
111
|
+
status.complete = status.complete or 0
|
|
106
112
|
status.complete += count
|
|
107
113
|
self.status_controller.update(status)
|
|
108
114
|
return count
|
|
109
115
|
|
|
110
116
|
@status_notify()
|
|
111
|
-
def
|
|
117
|
+
def _bulk_group_updater(self, group_tree_counts, group_class, status=None):
|
|
112
118
|
"""Bulk update groups."""
|
|
113
119
|
count = 0
|
|
114
120
|
if not group_tree_counts:
|
|
@@ -125,33 +131,11 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
125
131
|
count += len(update_groups)
|
|
126
132
|
self.log.info(f"Updated {count} {group_class.__name__}s.")
|
|
127
133
|
if status:
|
|
134
|
+
status.complete = status.complete or 0
|
|
128
135
|
status.complete += count
|
|
129
136
|
self.status_controller.update(status)
|
|
130
137
|
return count
|
|
131
138
|
|
|
132
|
-
@status_notify(status_type=ImportStatusTypes.DIRS_MODIFIED, updates=False)
|
|
133
|
-
def bulk_folders_modified(self, paths, library, **kwargs):
|
|
134
|
-
"""Update folders stat and nothing else."""
|
|
135
|
-
count = 0
|
|
136
|
-
if not paths:
|
|
137
|
-
return count
|
|
138
|
-
folders = Folder.objects.filter(library=library, path__in=paths).only(
|
|
139
|
-
"stat", "updated_at"
|
|
140
|
-
)
|
|
141
|
-
update_folders = []
|
|
142
|
-
now = Now()
|
|
143
|
-
for folder in folders.iterator():
|
|
144
|
-
if Path(folder.path).exists():
|
|
145
|
-
folder.set_stat()
|
|
146
|
-
folder.updated_at = now
|
|
147
|
-
update_folders.append(folder)
|
|
148
|
-
Folder.objects.bulk_update(
|
|
149
|
-
update_folders, fields=_BULK_UPDATE_FOLDER_MODIFIED_FIELDS
|
|
150
|
-
)
|
|
151
|
-
count += len(update_folders)
|
|
152
|
-
self.log.info(f"Modified {count} folders")
|
|
153
|
-
return count
|
|
154
|
-
|
|
155
139
|
@status_notify()
|
|
156
140
|
def bulk_folders_create(self, folder_paths, library, status=None):
|
|
157
141
|
"""Create folders breadth first."""
|
|
@@ -176,9 +160,10 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
176
160
|
try:
|
|
177
161
|
parent = Folder.objects.get(path=parent_path)
|
|
178
162
|
except Folder.DoesNotExist:
|
|
179
|
-
if
|
|
163
|
+
if path.parent != Path(library.path):
|
|
180
164
|
self.log.exception(
|
|
181
|
-
f"Can't find parent folder {parent_path}
|
|
165
|
+
f"Can't find parent folder {parent_path}"
|
|
166
|
+
f" for {path} in library {library.path}"
|
|
182
167
|
)
|
|
183
168
|
folder = Folder(
|
|
184
169
|
library=library,
|
|
@@ -191,19 +176,20 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
191
176
|
Folder.objects.bulk_create(
|
|
192
177
|
create_folders,
|
|
193
178
|
update_conflicts=True,
|
|
194
|
-
update_fields=
|
|
179
|
+
update_fields=BULK_UPDATE_FOLDER_MODIFIED_FIELDS,
|
|
195
180
|
unique_fields=Folder._meta.unique_together[0], # type: ignore
|
|
196
181
|
)
|
|
197
182
|
count += len(create_folders)
|
|
198
183
|
if status:
|
|
199
|
-
status.complete =
|
|
184
|
+
status.complete = status.complete or 0
|
|
185
|
+
status.complete += len(create_folders)
|
|
200
186
|
self.status_controller.update(status)
|
|
201
187
|
|
|
202
188
|
self.log.info(f"Created {count} Folders.")
|
|
203
189
|
return count
|
|
204
190
|
|
|
205
191
|
@status_notify()
|
|
206
|
-
def
|
|
192
|
+
def _bulk_create_named_models(self, names, named_class, status=None):
|
|
207
193
|
"""Bulk create named models."""
|
|
208
194
|
count = len(names)
|
|
209
195
|
if not count:
|
|
@@ -221,33 +207,146 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
221
207
|
)
|
|
222
208
|
self.log.info(f"Created {count} {named_class.__name__}s.")
|
|
223
209
|
if status:
|
|
210
|
+
status.complete = status.complete or 0
|
|
224
211
|
status.complete += count
|
|
225
212
|
self.status_controller.update(status)
|
|
226
213
|
return count
|
|
227
214
|
|
|
228
|
-
@
|
|
229
|
-
def
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
215
|
+
@staticmethod
|
|
216
|
+
def _create_creator(role_name, person_name):
|
|
217
|
+
role = CreatorRole.objects.get(name=role_name) if role_name else None
|
|
218
|
+
person = CreatorPerson.objects.get(name=person_name)
|
|
219
|
+
return Creator(role=role, person=person)
|
|
233
220
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
creator = Creator(role=role, person=person)
|
|
221
|
+
@staticmethod
|
|
222
|
+
def _create_story_arc_number(name, number):
|
|
223
|
+
story_arc = StoryArc.objects.get(name=name)
|
|
224
|
+
return StoryArcNumber(story_arc=story_arc, number=number)
|
|
239
225
|
|
|
240
|
-
|
|
226
|
+
def _bulk_create_dict_models(
|
|
227
|
+
self, create_tuples, create_method, model, status=None
|
|
228
|
+
):
|
|
229
|
+
"""Bulk create a dict type m2m model."""
|
|
230
|
+
if not create_tuples:
|
|
231
|
+
return 0
|
|
241
232
|
|
|
242
|
-
|
|
243
|
-
|
|
233
|
+
create_objs = []
|
|
234
|
+
for key, value in create_tuples:
|
|
235
|
+
obj = create_method(key, value)
|
|
236
|
+
create_objs.append(obj)
|
|
237
|
+
|
|
238
|
+
model.objects.bulk_create(
|
|
239
|
+
create_objs,
|
|
244
240
|
update_conflicts=True,
|
|
245
|
-
update_fields=
|
|
246
|
-
unique_fields=
|
|
241
|
+
update_fields=_CREATE_DICT_UPDATE_FIELDS[model],
|
|
242
|
+
unique_fields=model._meta.unique_together[0], # type: ignore
|
|
247
243
|
)
|
|
248
|
-
count = len(
|
|
249
|
-
self.log.info(f"Created {count}
|
|
244
|
+
count = len(create_objs)
|
|
245
|
+
self.log.info(f"Created {count} {model.__class__.__name__}s.")
|
|
250
246
|
if status:
|
|
247
|
+
status.complete = status.complete or 0
|
|
251
248
|
status.complete += count
|
|
252
249
|
self.status_controller.update(status)
|
|
253
250
|
return count
|
|
251
|
+
|
|
252
|
+
@status_notify()
|
|
253
|
+
def _bulk_create_creators(self, create_creator_tuples, status=None):
|
|
254
|
+
"""Bulk create creators."""
|
|
255
|
+
return self._bulk_create_dict_models(
|
|
256
|
+
create_creator_tuples,
|
|
257
|
+
self._create_creator,
|
|
258
|
+
Creator,
|
|
259
|
+
status,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
@status_notify()
|
|
263
|
+
def _bulk_create_story_arc_numbers(
|
|
264
|
+
self, create_story_arc_number_tuples, status=None
|
|
265
|
+
):
|
|
266
|
+
"""Bulk create story_arc_numbers."""
|
|
267
|
+
return self._bulk_create_dict_models(
|
|
268
|
+
create_story_arc_number_tuples,
|
|
269
|
+
self._create_story_arc_number,
|
|
270
|
+
StoryArcNumber,
|
|
271
|
+
status,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _get_create_fks_totals(create_data):
|
|
276
|
+
(
|
|
277
|
+
create_groups,
|
|
278
|
+
update_groups,
|
|
279
|
+
create_folder_paths,
|
|
280
|
+
create_fks,
|
|
281
|
+
create_creators,
|
|
282
|
+
create_story_arc_numbers,
|
|
283
|
+
) = create_data
|
|
284
|
+
total_fks = 0
|
|
285
|
+
for data_group in chain(
|
|
286
|
+
create_groups.values(), update_groups.values(), create_fks.values()
|
|
287
|
+
):
|
|
288
|
+
total_fks += len(data_group)
|
|
289
|
+
total_fks += (
|
|
290
|
+
len(create_folder_paths)
|
|
291
|
+
+ len(create_creators)
|
|
292
|
+
+ len(create_story_arc_numbers)
|
|
293
|
+
)
|
|
294
|
+
return total_fks
|
|
295
|
+
|
|
296
|
+
def create_all_fks(self, library, create_data):
|
|
297
|
+
"""Bulk create all foreign keys."""
|
|
298
|
+
total_fks = self._get_create_fks_totals(create_data)
|
|
299
|
+
status = Status(ImportStatusTypes.CREATE_FKS, 0, total_fks)
|
|
300
|
+
try:
|
|
301
|
+
self.status_controller.start(status)
|
|
302
|
+
(
|
|
303
|
+
create_groups,
|
|
304
|
+
update_groups,
|
|
305
|
+
create_folder_paths,
|
|
306
|
+
create_fks,
|
|
307
|
+
create_creators,
|
|
308
|
+
create_story_arc_numbers,
|
|
309
|
+
) = create_data
|
|
310
|
+
|
|
311
|
+
for group_class, group_tree_counts in create_groups.items():
|
|
312
|
+
status.complete += self._bulk_group_creator( # type: ignore
|
|
313
|
+
group_tree_counts,
|
|
314
|
+
group_class,
|
|
315
|
+
status=status,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
for group_class, group_tree_counts in update_groups.items():
|
|
319
|
+
status.complete += self._bulk_group_updater( # type: ignore
|
|
320
|
+
group_tree_counts,
|
|
321
|
+
group_class,
|
|
322
|
+
status=status,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
status.complete += self.bulk_folders_create( # type: ignore
|
|
326
|
+
sorted(create_folder_paths),
|
|
327
|
+
library,
|
|
328
|
+
status=status,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
for named_class, names in create_fks.items():
|
|
332
|
+
status.complete += self._bulk_create_named_models(
|
|
333
|
+
names,
|
|
334
|
+
named_class,
|
|
335
|
+
status=status,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# This must happen after creator_fks created by create_named_models
|
|
339
|
+
status.complete += self._bulk_create_creators(
|
|
340
|
+
create_creators,
|
|
341
|
+
status=status,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# This must happen after story_arc_fks created by create_named_models
|
|
345
|
+
status.complete += self._bulk_create_story_arc_numbers(
|
|
346
|
+
create_story_arc_numbers,
|
|
347
|
+
status=status,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
finally:
|
|
351
|
+
self.status_controller.finish(status)
|
|
352
|
+
return status.complete
|
|
@@ -14,7 +14,7 @@ class DeletedMixin(QueuedThread):
|
|
|
14
14
|
self.librarian_queue.put(task)
|
|
15
15
|
|
|
16
16
|
@status_notify(status_type=ImportStatusTypes.DIRS_DELETED, updates=False)
|
|
17
|
-
def
|
|
17
|
+
def _bulk_folders_deleted(self, delete_folder_paths, library, **kwargs):
|
|
18
18
|
"""Bulk delete folders."""
|
|
19
19
|
if not delete_folder_paths:
|
|
20
20
|
return 0
|
|
@@ -36,7 +36,7 @@ class DeletedMixin(QueuedThread):
|
|
|
36
36
|
return count
|
|
37
37
|
|
|
38
38
|
@status_notify(status_type=ImportStatusTypes.FILES_DELETED, updates=False)
|
|
39
|
-
def
|
|
39
|
+
def _bulk_comics_deleted(self, delete_comic_paths, library, **kwargs):
|
|
40
40
|
"""Bulk delete comics found missing from the filesystem."""
|
|
41
41
|
if not delete_comic_paths:
|
|
42
42
|
return 0
|
|
@@ -50,3 +50,12 @@ class DeletedMixin(QueuedThread):
|
|
|
50
50
|
self.log.info(f"Deleted {count} comics from {library.path}")
|
|
51
51
|
|
|
52
52
|
return count
|
|
53
|
+
|
|
54
|
+
def delete(self, library, task):
|
|
55
|
+
"""Delete files and folders."""
|
|
56
|
+
count = self._bulk_folders_deleted(task.dirs_deleted, library)
|
|
57
|
+
task.dirs_deleted = None
|
|
58
|
+
|
|
59
|
+
count += self._bulk_comics_deleted(task.files_deleted, library)
|
|
60
|
+
task.files_deleted = None
|
|
61
|
+
return count
|
|
@@ -3,8 +3,9 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
from django.db.models.functions import Now
|
|
5
5
|
|
|
6
|
-
from codex.librarian.importer.status import status_notify
|
|
6
|
+
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
7
7
|
from codex.models import Comic, FailedImport
|
|
8
|
+
from codex.status import Status
|
|
8
9
|
from codex.threads import QueuedThread
|
|
9
10
|
|
|
10
11
|
_BULK_UPDATE_FAILED_IMPORT_FIELDS = ("name", "stat", "updated_at")
|
|
@@ -38,7 +39,7 @@ class FailedImportsMixin(QueuedThread):
|
|
|
38
39
|
return succeeded_failed_imports | missing_failed_imports
|
|
39
40
|
|
|
40
41
|
@status_notify()
|
|
41
|
-
def
|
|
42
|
+
def _query_failed_imports(
|
|
42
43
|
self,
|
|
43
44
|
failed_imports,
|
|
44
45
|
library,
|
|
@@ -49,6 +50,9 @@ class FailedImportsMixin(QueuedThread):
|
|
|
49
50
|
if not failed_imports:
|
|
50
51
|
return 0
|
|
51
52
|
|
|
53
|
+
# Remove the files deleted hack thing.
|
|
54
|
+
failed_imports.pop("files_deleted", None)
|
|
55
|
+
|
|
52
56
|
existing_failed_import_paths = set(
|
|
53
57
|
FailedImport.objects.filter(library=library).values_list("path", flat=True)
|
|
54
58
|
)
|
|
@@ -70,7 +74,7 @@ class FailedImportsMixin(QueuedThread):
|
|
|
70
74
|
return count
|
|
71
75
|
|
|
72
76
|
@status_notify()
|
|
73
|
-
def
|
|
77
|
+
def _bulk_update_failed_imports(
|
|
74
78
|
self, update_failed_imports, library, **kwargs
|
|
75
79
|
) -> int:
|
|
76
80
|
"""Bulk update failed imports."""
|
|
@@ -102,7 +106,7 @@ class FailedImportsMixin(QueuedThread):
|
|
|
102
106
|
return count
|
|
103
107
|
|
|
104
108
|
@status_notify()
|
|
105
|
-
def
|
|
109
|
+
def _bulk_create_failed_imports(self, create_failed_imports, library, **kwargs):
|
|
106
110
|
"""Bulk create failed imports."""
|
|
107
111
|
if not create_failed_imports:
|
|
108
112
|
return 0
|
|
@@ -127,7 +131,7 @@ class FailedImportsMixin(QueuedThread):
|
|
|
127
131
|
return count
|
|
128
132
|
|
|
129
133
|
@status_notify()
|
|
130
|
-
def
|
|
134
|
+
def _bulk_cleanup_failed_imports(
|
|
131
135
|
self, delete_failed_imports_paths, library, **kwargs
|
|
132
136
|
):
|
|
133
137
|
"""Remove FailedImport objects that have since succeeded."""
|
|
@@ -141,3 +145,38 @@ class FailedImportsMixin(QueuedThread):
|
|
|
141
145
|
count = len(delete_failed_imports_paths)
|
|
142
146
|
self.log.info(f"Cleaned up {count} failed imports from {library.path}")
|
|
143
147
|
return count
|
|
148
|
+
|
|
149
|
+
def fail_imports(self, library, failed_imports, is_files_deleted):
|
|
150
|
+
"""Handle failed imports."""
|
|
151
|
+
created_count = 0
|
|
152
|
+
try:
|
|
153
|
+
fis = {"update_fis": {}, "create_fis": {}, "delete_fi_paths": set()}
|
|
154
|
+
if is_files_deleted:
|
|
155
|
+
# if any files were deleted. Run the failed import check
|
|
156
|
+
failed_imports["files_deleted"] = True
|
|
157
|
+
status = Status(ImportStatusTypes.FAILED_IMPORTS, 0, len(failed_imports))
|
|
158
|
+
status.total = self._query_failed_imports(
|
|
159
|
+
failed_imports,
|
|
160
|
+
library,
|
|
161
|
+
fis,
|
|
162
|
+
status=status,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
self._bulk_update_failed_imports(
|
|
166
|
+
fis["update_fis"],
|
|
167
|
+
library,
|
|
168
|
+
status=status,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
created_count = self._bulk_create_failed_imports(
|
|
172
|
+
fis["create_fis"],
|
|
173
|
+
library,
|
|
174
|
+
status=status,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self._bulk_cleanup_failed_imports(
|
|
178
|
+
fis["delete_fi_paths"], library, status=status
|
|
179
|
+
)
|
|
180
|
+
except Exception:
|
|
181
|
+
self.log.exception("Processing failed imports")
|
|
182
|
+
return bool(created_count)
|
|
@@ -6,9 +6,13 @@ from time import sleep, time
|
|
|
6
6
|
from django.core.cache import cache
|
|
7
7
|
from humanize import naturaldelta
|
|
8
8
|
|
|
9
|
-
from codex.librarian.importer.
|
|
9
|
+
from codex.librarian.importer.aggregate_metadata import AggregateMetadataMixin
|
|
10
|
+
from codex.librarian.importer.deleted import DeletedMixin
|
|
11
|
+
from codex.librarian.importer.failed_imports import FailedImportsMixin
|
|
12
|
+
from codex.librarian.importer.moved import MovedMixin
|
|
10
13
|
from codex.librarian.importer.status import ImportStatusTypes
|
|
11
14
|
from codex.librarian.importer.tasks import AdoptOrphanFoldersTask, UpdaterDBDiffTask
|
|
15
|
+
from codex.librarian.importer.update_comics import UpdateComicsMixin
|
|
12
16
|
from codex.librarian.notifier.tasks import FAILED_IMPORTS_TASK, LIBRARY_CHANGED_TASK
|
|
13
17
|
from codex.librarian.search.status import SearchIndexStatusTypes
|
|
14
18
|
from codex.librarian.search.tasks import SearchIndexAbortTask, SearchIndexUpdateTask
|
|
@@ -19,7 +23,13 @@ from codex.status import Status
|
|
|
19
23
|
_WRITE_WAIT_EXPIRY = 60
|
|
20
24
|
|
|
21
25
|
|
|
22
|
-
class ComicImporterThread(
|
|
26
|
+
class ComicImporterThread(
|
|
27
|
+
AggregateMetadataMixin,
|
|
28
|
+
DeletedMixin,
|
|
29
|
+
UpdateComicsMixin,
|
|
30
|
+
FailedImportsMixin,
|
|
31
|
+
MovedMixin,
|
|
32
|
+
):
|
|
23
33
|
"""A worker to handle all bulk database updates."""
|
|
24
34
|
|
|
25
35
|
def _wait_for_filesystem_ops_to_finish(self, task: UpdaterDBDiffTask) -> bool:
|
|
@@ -177,6 +187,13 @@ class ComicImporterThread(ApplyDBOpsMixin):
|
|
|
177
187
|
self._log_task(library.path, task)
|
|
178
188
|
self._init_librarian_status(task, library.path)
|
|
179
189
|
|
|
190
|
+
def _create_comic_relations(self, library, fks):
|
|
191
|
+
"""Query all foreign keys to determine what needs creating, then create them."""
|
|
192
|
+
if not fks:
|
|
193
|
+
return 0
|
|
194
|
+
create_data = self.query_all_missing_fks(library.path, fks)
|
|
195
|
+
return self.create_all_fks(library, create_data)
|
|
196
|
+
|
|
180
197
|
def _finish_apply_status(self, library):
|
|
181
198
|
"""Finish all librarian statuses."""
|
|
182
199
|
library.update_in_progress = False
|
|
@@ -233,24 +250,32 @@ class ComicImporterThread(ApplyDBOpsMixin):
|
|
|
233
250
|
"fks": fks,
|
|
234
251
|
"fis": fis,
|
|
235
252
|
}
|
|
236
|
-
self.
|
|
237
|
-
|
|
253
|
+
self.get_aggregate_metadata(
|
|
254
|
+
modified_paths | created_paths, library.path, all_metadata
|
|
238
255
|
)
|
|
256
|
+
all_metadata = None
|
|
239
257
|
modified_paths -= fis.keys()
|
|
240
258
|
created_paths -= fis.keys()
|
|
241
259
|
|
|
242
|
-
changed += self.
|
|
260
|
+
changed += self._create_comic_relations(library, fks)
|
|
261
|
+
fks = None
|
|
243
262
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
library,
|
|
263
|
+
imported_count = self.bulk_update_comics(
|
|
264
|
+
modified_paths,
|
|
265
|
+
library,
|
|
266
|
+
created_paths,
|
|
267
|
+
mds,
|
|
247
268
|
)
|
|
269
|
+
modified_paths = None
|
|
270
|
+
imported_count += self.bulk_create_comics(created_paths, library, mds)
|
|
271
|
+
created_paths = mds = None
|
|
272
|
+
self.bulk_query_and_link_comic_m2m_fields(m2m_mds)
|
|
273
|
+
m2m_mds = None
|
|
248
274
|
changed += imported_count
|
|
249
|
-
all_metadata = (
|
|
250
|
-
simple_metadata
|
|
251
|
-
) = modified_paths = created_paths = mds = m2m_mds = fks = None
|
|
252
275
|
|
|
253
|
-
new_failed_imports = self.fail_imports(
|
|
276
|
+
new_failed_imports = self.fail_imports(
|
|
277
|
+
library, fis, bool(task.files_deleted)
|
|
278
|
+
)
|
|
254
279
|
|
|
255
280
|
changed += self.delete(library, task)
|
|
256
281
|
cache.clear()
|
|
@@ -11,6 +11,7 @@ from codex.models import (
|
|
|
11
11
|
Imprint,
|
|
12
12
|
Publisher,
|
|
13
13
|
Series,
|
|
14
|
+
StoryArcNumber,
|
|
14
15
|
Volume,
|
|
15
16
|
)
|
|
16
17
|
from codex.threads import QueuedThread
|
|
@@ -50,31 +51,31 @@ class LinkComicsMixin(QueuedThread):
|
|
|
50
51
|
md["parent_folder"] = Folder.objects.get(path=parent_path)
|
|
51
52
|
|
|
52
53
|
@staticmethod
|
|
53
|
-
def
|
|
54
|
+
def _get_link_folders_filter(folder_paths):
|
|
54
55
|
"""Get the ids of all folders to link."""
|
|
55
|
-
|
|
56
|
-
return set()
|
|
57
|
-
folder_pks = Folder.objects.filter(path__in=folder_paths).values_list(
|
|
58
|
-
"pk", flat=True
|
|
59
|
-
)
|
|
60
|
-
return frozenset(folder_pks)
|
|
56
|
+
return Q(path__in=folder_paths)
|
|
61
57
|
|
|
62
58
|
@staticmethod
|
|
63
|
-
def
|
|
59
|
+
def _get_link_creators_filter(creators_md):
|
|
64
60
|
"""Get the ids of all creators to link."""
|
|
65
|
-
if not creators_md:
|
|
66
|
-
return set()
|
|
67
61
|
creators_filter = Q()
|
|
68
62
|
for creator in creators_md:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
creators_filter |= Q(
|
|
64
|
+
role__name=creator.get("role"),
|
|
65
|
+
person__name=creator["person"],
|
|
66
|
+
)
|
|
67
|
+
return creators_filter
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _get_link_story_arc_numbers_filter(story_arc_numbers_md):
|
|
71
|
+
"""Get the ids of all story_arc_numbers to link."""
|
|
72
|
+
story_arc_numbers_filter = Q()
|
|
73
|
+
for name, number in story_arc_numbers_md.items():
|
|
74
|
+
story_arc_numbers_filter |= Q(
|
|
75
|
+
story_arc__name=name,
|
|
76
|
+
number=number,
|
|
77
|
+
)
|
|
78
|
+
return story_arc_numbers_filter
|
|
78
79
|
|
|
79
80
|
def _link_named_m2ms(self, all_m2m_links, comic_pk, md):
|
|
80
81
|
"""Set the ids of all named m2m fields into the comic dict."""
|
|
@@ -90,6 +91,20 @@ class LinkComicsMixin(QueuedThread):
|
|
|
90
91
|
all_m2m_links[field] = {}
|
|
91
92
|
all_m2m_links[field][comic_pk] = frozenset(pks)
|
|
92
93
|
|
|
94
|
+
def _link_prepare_special_m2ms(self, link_data, key, model, get_link_filter_method):
|
|
95
|
+
"""Prepare special m2m for linking."""
|
|
96
|
+
(all_m2m_links, md, comic_pk) = link_data
|
|
97
|
+
values = md.pop(key, [])
|
|
98
|
+
if not values:
|
|
99
|
+
return
|
|
100
|
+
if key not in all_m2m_links:
|
|
101
|
+
all_m2m_links[key] = {}
|
|
102
|
+
|
|
103
|
+
m2m_filter = get_link_filter_method(values)
|
|
104
|
+
pks = model.objects.filter(m2m_filter).values_list("pk", flat=True)
|
|
105
|
+
result = frozenset(pks)
|
|
106
|
+
all_m2m_links[key][comic_pk] = result
|
|
107
|
+
|
|
93
108
|
def _link_comic_m2m_fields(self, m2m_mds):
|
|
94
109
|
"""Get the complete m2m field data to create."""
|
|
95
110
|
all_m2m_links = {}
|
|
@@ -97,17 +112,19 @@ class LinkComicsMixin(QueuedThread):
|
|
|
97
112
|
comics = Comic.objects.filter(path__in=comic_paths).values_list("pk", "path")
|
|
98
113
|
for comic_pk, comic_path in comics:
|
|
99
114
|
md = m2m_mds[comic_path]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
link_data = (all_m2m_links, md, comic_pk)
|
|
116
|
+
self._link_prepare_special_m2ms(
|
|
117
|
+
link_data, "folders", Folder, self._get_link_folders_filter
|
|
118
|
+
)
|
|
119
|
+
self._link_prepare_special_m2ms(
|
|
120
|
+
link_data, "creators", Creator, self._get_link_creators_filter
|
|
121
|
+
)
|
|
122
|
+
self._link_prepare_special_m2ms(
|
|
123
|
+
link_data,
|
|
124
|
+
"story_arc_numbers",
|
|
125
|
+
StoryArcNumber,
|
|
126
|
+
self._get_link_story_arc_numbers_filter,
|
|
127
|
+
)
|
|
111
128
|
self._link_named_m2ms(all_m2m_links, comic_pk, md)
|
|
112
129
|
return all_m2m_links
|
|
113
130
|
|
|
@@ -125,6 +142,7 @@ class LinkComicsMixin(QueuedThread):
|
|
|
125
142
|
)
|
|
126
143
|
all_del_pks |= set(del_pks)
|
|
127
144
|
if status:
|
|
145
|
+
status.total = status.total or 0
|
|
128
146
|
status.total += len(del_pks)
|
|
129
147
|
self.status_controller.update(status)
|
|
130
148
|
|
|
@@ -135,6 +153,7 @@ class LinkComicsMixin(QueuedThread):
|
|
|
135
153
|
)
|
|
136
154
|
missing_pks = set(pks) - extant_pks
|
|
137
155
|
if status:
|
|
156
|
+
status.total = status.total or 0
|
|
138
157
|
status.total += len(missing_pks)
|
|
139
158
|
self.status_controller.update(status)
|
|
140
159
|
for pk in missing_pks:
|
|
@@ -165,6 +184,7 @@ class LinkComicsMixin(QueuedThread):
|
|
|
165
184
|
m2m_links, ThroughModel, through_field_id_name, status
|
|
166
185
|
)
|
|
167
186
|
if status:
|
|
187
|
+
status.total = status.total or 0
|
|
168
188
|
status.total += len(tms) + len(all_del_pks)
|
|
169
189
|
self.status_controller.update(status)
|
|
170
190
|
|
|
@@ -181,15 +201,18 @@ class LinkComicsMixin(QueuedThread):
|
|
|
181
201
|
" relations for altered comics."
|
|
182
202
|
)
|
|
183
203
|
if status:
|
|
204
|
+
status.complete = status.complete or 0
|
|
184
205
|
status.complete += created_count
|
|
185
206
|
self.status_controller.update(status)
|
|
186
207
|
|
|
187
208
|
if del_count := len(all_del_pks):
|
|
188
|
-
ThroughModel.objects.filter(
|
|
209
|
+
del_qs = ThroughModel.objects.filter(pk__in=all_del_pks)
|
|
210
|
+
del_qs.delete()
|
|
189
211
|
self.log.info(
|
|
190
212
|
f"Deleted {del_count} stale {field_name} relations for altered comics.",
|
|
191
213
|
)
|
|
192
214
|
if status:
|
|
215
|
+
status.complete = status.complete or 0
|
|
193
216
|
status.complete += del_count
|
|
194
217
|
self.status_controller.update(status)
|
|
195
218
|
|