codex 1.6.0a4__py3-none-any.whl → 1.6.0a5__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/exceptions.py +17 -6
- codex/integrity.py +67 -3
- codex/librarian/covers/coverd.py +2 -2
- codex/librarian/covers/create.py +29 -13
- codex/librarian/covers/path.py +8 -6
- codex/librarian/covers/purge.py +27 -16
- codex/librarian/covers/tasks.py +3 -2
- codex/librarian/importer/aggregate_metadata.py +2 -2
- codex/librarian/importer/const.py +23 -3
- codex/librarian/importer/create_fks.py +88 -28
- codex/librarian/importer/deleted.py +26 -2
- codex/librarian/importer/importerd.py +124 -27
- codex/librarian/importer/link_comics.py +55 -0
- codex/librarian/importer/moved.py +78 -3
- codex/librarian/importer/query_fks.py +65 -1
- codex/librarian/importer/status.py +6 -0
- codex/librarian/importer/tasks.py +11 -3
- codex/librarian/importer/update_comics.py +1 -3
- codex/librarian/janitor/cleanup.py +24 -0
- codex/librarian/janitor/failed_imports.py +1 -1
- codex/librarian/janitor/janitor.py +5 -0
- codex/librarian/janitor/janitord.py +1 -1
- codex/librarian/janitor/status.py +1 -0
- codex/librarian/janitor/tasks.py +5 -0
- codex/librarian/librariand.py +3 -3
- codex/librarian/search/update.py +1 -1
- codex/librarian/watchdog/db_snapshot.py +9 -6
- codex/librarian/watchdog/emitter.py +3 -0
- codex/librarian/watchdog/event_batcherd.py +19 -3
- codex/librarian/watchdog/events.py +176 -36
- codex/librarian/watchdog/observers.py +13 -3
- codex/migrations/0027_sort_name.py +2 -12
- codex/migrations/0028_custom_covers.py +166 -0
- codex/models/admin.py +0 -1
- codex/models/base.py +3 -0
- codex/models/comic.py +15 -9
- codex/models/groups.py +30 -14
- codex/models/library.py +11 -3
- codex/models/paths.py +62 -14
- codex/models/{const.py → util.py} +15 -2
- codex/serializers/admin.py +11 -2
- codex/serializers/browser/mixins.py +1 -0
- codex/serializers/browser/settings.py +3 -0
- codex/settings/settings.py +11 -5
- codex/startup.py +77 -3
- codex/static_root/assets/{VCheckbox-CN6Sn_k7.9bf27129091c.js → VCheckbox-BF_99tgB.7fe59e56d9d4.js} +1 -1
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.br +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-CN6Sn_k7.js → VCheckbox-BF_99tgB.js} +1 -1
- codex/static_root/assets/VCheckbox-BF_99tgB.js.br +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CN1RPZPQ.b763c6a7abaf.js → VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CN1RPZPQ.js → VCheckboxBtn-Coc7g8m7.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.gz +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js +1 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.br +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.gz +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js +1 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js.br +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js.gz +0 -0
- codex/static_root/assets/{VDialog-DC3KGsMT.71e2a460e44b.js → VDialog-pzwzInEh.51acf566a069.js} +1 -1
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.br +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.gz +0 -0
- codex/static_root/assets/{VDialog-DC3KGsMT.js → VDialog-pzwzInEh.js} +1 -1
- codex/static_root/assets/VDialog-pzwzInEh.js.br +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DJQYXCn2.f0d471a1b80a.js → VExpansionPanels-CKSq1GpG.0ecf702c5b67.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DJQYXCn2.js → VExpansionPanels-CKSq1GpG.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-CgodTThy.b20290d57146.js → VRadioGroup-CW5GobGU.b72925bca3de.js} +1 -1
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-CgodTThy.js → VRadioGroup-CW5GobGU.js} +1 -1
- codex/static_root/assets/VRadioGroup-CW5GobGU.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.js.gz +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js +1 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.br +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.gz +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js +1 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js.br +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js.gz +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +1 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css +1 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
- codex/static_root/assets/{VSelectionControl-zPp4DXWq.635b7f5aa882.js → VSelectionControl-DKOhc2zB.061dca094303.js} +1 -1
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-zPp4DXWq.js → VSelectionControl-DKOhc2zB.js} +1 -1
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-pK9Xompk.67014b86fc74.js → VSlideGroup-zFz5_byd.e0e246b7acbb.js} +1 -1
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.br +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-pK9Xompk.js → VSlideGroup-zFz5_byd.js} +1 -1
- codex/static_root/assets/VSlideGroup-zFz5_byd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.js.gz +0 -0
- codex/static_root/assets/{VTable-CMi6sDtP.8445f91d9023.js → VTable-FKMTcdkF.6c1eed84d25e.js} +1 -1
- codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.br +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.gz +0 -0
- codex/static_root/assets/{VTable-CMi6sDtP.js → VTable-FKMTcdkF.js} +1 -1
- codex/static_root/assets/VTable-FKMTcdkF.js.br +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.js.gz +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css +1 -0
- codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css.br +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css.gz +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.css +1 -0
- codex/static_root/assets/VTextField-BssVoDhB.css.br +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.css.gz +0 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js +1 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.br +0 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.gz +0 -0
- codex/static_root/assets/VTextField-Dftcik85.js +1 -0
- codex/static_root/assets/VTextField-Dftcik85.js.br +0 -0
- codex/static_root/assets/VTextField-Dftcik85.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-DNsRQx9l.c42f636c386e.js → VWindowItem-CYCwnt3s.28025dc36c6a.js} +1 -1
- codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.br +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-DNsRQx9l.js → VWindowItem-CYCwnt3s.js} +1 -1
- codex/static_root/assets/VWindowItem-CYCwnt3s.js.br +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.js.gz +0 -0
- codex/static_root/assets/{admin-BbrOU9Y9.29936748af9e.css → admin-B8Bp2Uih.82c98714bcf1.css} +1 -1
- codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.br +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.gz +0 -0
- codex/static_root/assets/{admin-BbrOU9Y9.css → admin-B8Bp2Uih.css} +1 -1
- codex/static_root/assets/admin-B8Bp2Uih.css.br +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.css.gz +0 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js +1 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.br +0 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.gz +0 -0
- codex/static_root/assets/admin-DH_DrB0F.js +1 -0
- codex/static_root/assets/admin-DH_DrB0F.js.br +0 -0
- codex/static_root/assets/admin-DH_DrB0F.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js +30 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js +30 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css +1 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css +1 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.gz +0 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css +1 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.br +0 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.gz +0 -0
- codex/static_root/assets/browser-CpppBvim.css +1 -0
- codex/static_root/assets/browser-CpppBvim.css.br +0 -0
- codex/static_root/assets/browser-CpppBvim.css.gz +0 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js +1 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.br +0 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.gz +0 -0
- codex/static_root/assets/browser-lKU7rqLd.js +1 -0
- codex/static_root/assets/browser-lKU7rqLd.js.br +0 -0
- codex/static_root/assets/browser-lKU7rqLd.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-BHTkpC6Z.2d5d5dd57ad3.js → change-password-dialog-BIBG6Bk8.3583021c124a.js} +1 -1
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-BHTkpC6Z.js → change-password-dialog-BIBG6Bk8.js} +1 -1
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DmXGDg4z.284812bd376d.js → confirm-dialog-DARLmXRf.53fc6938fb8c.js} +1 -1
- codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DmXGDg4z.js → confirm-dialog-DARLmXRf.js} +1 -1
- codex/static_root/assets/confirm-dialog-DARLmXRf.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.js.gz +0 -0
- codex/static_root/assets/{datetime-column-DKaH5S3z.5c6042016200.js → datetime-column-LpLgWpkf.16ce3324970f.js} +1 -1
- codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.br +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.gz +0 -0
- codex/static_root/assets/{datetime-column-DKaH5S3z.js → datetime-column-LpLgWpkf.js} +1 -1
- codex/static_root/assets/datetime-column-LpLgWpkf.js.br +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.js.gz +0 -0
- codex/static_root/assets/{filter-DUVATQW3.e1e7aa6d0b18.js → filter-jRB1Ob6r.6a559a53e805.js} +1 -1
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.br +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.gz +0 -0
- codex/static_root/assets/{filter-DUVATQW3.js → filter-jRB1Ob6r.js} +1 -1
- codex/static_root/assets/filter-jRB1Ob6r.js.br +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.js.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css +1 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.br +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css +1 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css.br +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css.gz +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js +1 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.br +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.gz +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js +1 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js.br +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js.gz +0 -0
- codex/static_root/assets/{group-tab-D3CyU-Il.fbf9ce5ef593.js → group-tab-MK1lcjUU.98406d5babdc.js} +1 -1
- codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.br +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.gz +0 -0
- codex/static_root/assets/{group-tab-D3CyU-Il.js → group-tab-MK1lcjUU.js} +1 -1
- codex/static_root/assets/group-tab-MK1lcjUU.js.br +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.js.gz +0 -0
- codex/static_root/assets/{http-error-0ef6-Ajc.e2879bcd242f.js → http-error-HFeYnF0n.a3c578a0265e.js} +1 -1
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.br +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.gz +0 -0
- codex/static_root/assets/{http-error-0ef6-Ajc.js → http-error-HFeYnF0n.js} +1 -1
- codex/static_root/assets/http-error-HFeYnF0n.js.br +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.js.gz +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css +1 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.br +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.gz +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css +1 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css.br +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css.gz +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js +1 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.br +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.gz +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js +1 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js.br +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js.gz +0 -0
- codex/static_root/assets/{main-CuzJev9T.js → main-gSW7TFDY.329223b148d4.js} +6 -6
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.br +0 -0
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.gz +0 -0
- codex/static_root/assets/{main-CuzJev9T.f58771cc37e2.js → main-gSW7TFDY.js} +6 -6
- codex/static_root/assets/main-gSW7TFDY.js.br +0 -0
- codex/static_root/assets/main-gSW7TFDY.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-B3wS4_7p.7695fafdefce.css → pagination-toolbar-ClZui9Zf.697d7ae11c21.css} +1 -1
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-B3wS4_7p.css → pagination-toolbar-ClZui9Zf.css} +1 -1
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js +1 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js +1 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BlU5mm5n.0431bcdf48d1.js → pdf-doc-BTdS9hUz.7c966524280a.js} +1 -1
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.br +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BlU5mm5n.js → pdf-doc-BTdS9hUz.js} +1 -1
- codex/static_root/assets/pdf-doc-BTdS9hUz.js.br +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.js.gz +0 -0
- codex/static_root/assets/{reader-s7jttB_F.f7b43b04961a.js → reader-BtSTIETq.f940f66c0e58.js} +2 -2
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.br +0 -0
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.gz +0 -0
- codex/static_root/assets/{reader-s7jttB_F.js → reader-BtSTIETq.js} +2 -2
- codex/static_root/assets/reader-BtSTIETq.js.br +0 -0
- codex/static_root/assets/reader-BtSTIETq.js.gz +0 -0
- codex/static_root/assets/{reader-BiKxuxj7.css → reader-CF3xt6JG.19fad7f62450.css} +1 -1
- codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.br +0 -0
- codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.gz +0 -0
- codex/static_root/assets/{reader-BiKxuxj7.dabe592f46f1.css → reader-CF3xt6JG.css} +1 -1
- codex/static_root/assets/reader-CF3xt6JG.css.br +0 -0
- codex/static_root/assets/reader-CF3xt6JG.css.gz +0 -0
- codex/static_root/assets/{relation-chips-D6T6FX7G.css → relation-chips-Bsc-gUxE.98e65384c3c9.css} +1 -1
- codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.br +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.gz +0 -0
- codex/static_root/assets/{relation-chips-D6T6FX7G.db095b2497e4.css → relation-chips-Bsc-gUxE.css} +1 -1
- codex/static_root/assets/relation-chips-Bsc-gUxE.css.br +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.css.gz +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js +1 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.br +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.gz +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js +1 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js.br +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-DbJuixzh.352aaaab5925.js → settings-drawer-xSctoxZn.4ce8afcb879a.js} +2 -2
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.br +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-DbJuixzh.js → settings-drawer-xSctoxZn.js} +2 -2
- codex/static_root/assets/settings-drawer-xSctoxZn.js.br +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.js.gz +0 -0
- codex/static_root/assets/{stats-tab-BhRLh7XB.7be95b5021c3.js → stats-tab-C_uC_U7o.e6b014b6e8c6.js} +1 -1
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.br +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.gz +0 -0
- codex/static_root/assets/{stats-tab-BhRLh7XB.js → stats-tab-C_uC_U7o.js} +1 -1
- codex/static_root/assets/stats-tab-C_uC_U7o.js.br +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.js.gz +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js +1 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.br +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.gz +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js +1 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js.br +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js.gz +0 -0
- codex/static_root/assets/{unauthorized-BgXQnNpn.f63b36d526b5.js → unauthorized-kEuFL5br.475729603aeb.js} +1 -1
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.br +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.gz +0 -0
- codex/static_root/assets/{unauthorized-BgXQnNpn.js → unauthorized-kEuFL5br.js} +1 -1
- codex/static_root/assets/unauthorized-kEuFL5br.js.br +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.js.gz +0 -0
- codex/static_root/assets/{user-tab-cZoJDlb-.2a497e8b2b24.js → user-tab-CZNFTTEI.f3940a75daae.js} +1 -1
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.br +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.gz +0 -0
- codex/static_root/assets/{user-tab-cZoJDlb-.js → user-tab-CZNFTTEI.js} +1 -1
- codex/static_root/assets/user-tab-CZNFTTEI.js.br +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.js.gz +0 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json +1 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.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.b60817ba0a93.json → choices.23fedac2b9ba.json} +1 -1
- codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
- codex/static_root/js/choices.23fedac2b9ba.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.6ef112683dae.json +642 -0
- codex/static_root/manifest.6ef112683dae.json.br +0 -0
- codex/static_root/manifest.6ef112683dae.json.gz +0 -0
- codex/static_root/manifest.json +254 -254
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/status_controller.py +11 -2
- codex/views/admin/library.py +47 -27
- codex/views/admin/stats.py +4 -1
- codex/views/admin/tasks.py +6 -1
- codex/views/browser/base.py +2 -2
- codex/views/browser/browser.py +1 -2
- codex/views/browser/browser_annotations.py +87 -17
- codex/views/browser/browser_breadcrumbs.py +2 -2
- codex/views/browser/browser_order_by.py +31 -20
- codex/views/browser/filters/search.py +1 -1
- codex/views/browser/metadata.py +0 -1
- codex/views/cover.py +3 -2
- codex/views/session.py +1 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/METADATA +3 -2
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/RECORD +340 -339
- codex/static_root/assets/VCheckbox-CN6Sn_k7.9bf27129091c.js.br +0 -0
- codex/static_root/assets/VCheckbox-CN6Sn_k7.9bf27129091c.js.gz +0 -0
- codex/static_root/assets/VCheckbox-CN6Sn_k7.js.br +0 -0
- codex/static_root/assets/VCheckbox-CN6Sn_k7.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.b763c6a7abaf.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.b763c6a7abaf.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.js.gz +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.6e49097cea2c.js +0 -1
- codex/static_root/assets/VCombobox-CtLUGYtD.6e49097cea2c.js.br +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.6e49097cea2c.js.gz +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.js +0 -1
- codex/static_root/assets/VCombobox-CtLUGYtD.js.br +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.js.gz +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.71e2a460e44b.js.br +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.71e2a460e44b.js.gz +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.js.br +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.f0d471a1b80a.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.f0d471a1b80a.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.b20290d57146.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.b20290d57146.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.js.gz +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +0 -1
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css +0 -1
- codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.de1030c82785.js +0 -1
- codex/static_root/assets/VSelect-B6s-x36h.de1030c82785.js.br +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.de1030c82785.js.gz +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.js +0 -1
- codex/static_root/assets/VSelect-B6s-x36h.js.br +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.635b7f5aa882.js.br +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.635b7f5aa882.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.js.br +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.67014b86fc74.js.br +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.67014b86fc74.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.js.br +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.js.gz +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.8445f91d9023.js.br +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.8445f91d9023.js.gz +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.js.br +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.js.gz +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css +0 -1
- codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css.br +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css.gz +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.css +0 -1
- codex/static_root/assets/VTextField-DuIIp-VY.css.br +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.css.gz +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.e93eefec0fdf.js +0 -1
- codex/static_root/assets/VTextField-dMTizQEN.e93eefec0fdf.js.br +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.e93eefec0fdf.js.gz +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.js +0 -1
- codex/static_root/assets/VTextField-dMTizQEN.js.br +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.js.gz +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.c42f636c386e.js.br +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.c42f636c386e.js.gz +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.js.br +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.js.gz +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.29936748af9e.css.br +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.29936748af9e.css.gz +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.css.br +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.css.gz +0 -0
- codex/static_root/assets/admin-CApphpty.5a41cbf0eefc.js +0 -1
- codex/static_root/assets/admin-CApphpty.5a41cbf0eefc.js.br +0 -0
- codex/static_root/assets/admin-CApphpty.5a41cbf0eefc.js.gz +0 -0
- codex/static_root/assets/admin-CApphpty.js +0 -1
- codex/static_root/assets/admin-CApphpty.js.br +0 -0
- codex/static_root/assets/admin-CApphpty.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css +0 -1
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css +0 -1
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.a79eda3055a7.js +0 -30
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.a79eda3055a7.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.a79eda3055a7.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.js +0 -30
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.js.gz +0 -0
- codex/static_root/assets/browser-C_PHQbyN.bb065e2ce21a.js +0 -1
- codex/static_root/assets/browser-C_PHQbyN.bb065e2ce21a.js.br +0 -0
- codex/static_root/assets/browser-C_PHQbyN.bb065e2ce21a.js.gz +0 -0
- codex/static_root/assets/browser-C_PHQbyN.js +0 -1
- codex/static_root/assets/browser-C_PHQbyN.js.br +0 -0
- codex/static_root/assets/browser-C_PHQbyN.js.gz +0 -0
- codex/static_root/assets/browser-DVI4LUNv.css +0 -1
- codex/static_root/assets/browser-DVI4LUNv.css.br +0 -0
- codex/static_root/assets/browser-DVI4LUNv.css.gz +0 -0
- codex/static_root/assets/browser-DVI4LUNv.fef2b997145a.css +0 -1
- codex/static_root/assets/browser-DVI4LUNv.fef2b997145a.css.br +0 -0
- codex/static_root/assets/browser-DVI4LUNv.fef2b997145a.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.2d5d5dd57ad3.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.2d5d5dd57ad3.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.284812bd376d.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.284812bd376d.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.js.gz +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.5c6042016200.js.br +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.5c6042016200.js.gz +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.js.br +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.js.gz +0 -0
- codex/static_root/assets/filter-DUVATQW3.e1e7aa6d0b18.js.br +0 -0
- codex/static_root/assets/filter-DUVATQW3.e1e7aa6d0b18.js.gz +0 -0
- codex/static_root/assets/filter-DUVATQW3.js.br +0 -0
- codex/static_root/assets/filter-DUVATQW3.js.gz +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css +0 -1
- codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css.br +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css.gz +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.css +0 -1
- codex/static_root/assets/flag-tab-BacZLlVi.css.br +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.css.gz +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.5d5c0604541b.js +0 -1
- codex/static_root/assets/flag-tab-CSKMJEn-.5d5c0604541b.js.br +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.5d5c0604541b.js.gz +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.js +0 -1
- codex/static_root/assets/flag-tab-CSKMJEn-.js.br +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.js.gz +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.fbf9ce5ef593.js.br +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.fbf9ce5ef593.js.gz +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.js.br +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.js.gz +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.e2879bcd242f.js.br +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.e2879bcd242f.js.gz +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.js.br +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.js.gz +0 -0
- codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css +0 -1
- codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css.br +0 -2
- codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css.gz +0 -0
- codex/static_root/assets/library-tab-CXbkxNZt.css +0 -1
- codex/static_root/assets/library-tab-CXbkxNZt.css.br +0 -2
- codex/static_root/assets/library-tab-CXbkxNZt.css.gz +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.70b55460b8e1.js +0 -1
- codex/static_root/assets/library-tab-IdDfLn4k.70b55460b8e1.js.br +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.70b55460b8e1.js.gz +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.js +0 -1
- codex/static_root/assets/library-tab-IdDfLn4k.js.br +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.js.gz +0 -0
- codex/static_root/assets/main-CuzJev9T.f58771cc37e2.js.br +0 -0
- codex/static_root/assets/main-CuzJev9T.f58771cc37e2.js.gz +0 -0
- codex/static_root/assets/main-CuzJev9T.js.br +0 -0
- codex/static_root/assets/main-CuzJev9T.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.66d3b756d974.js +0 -1
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.66d3b756d974.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.66d3b756d974.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.js +0 -1
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.0431bcdf48d1.js.br +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.0431bcdf48d1.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.js.br +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.js.gz +0 -0
- codex/static_root/assets/reader-BiKxuxj7.css.br +0 -0
- codex/static_root/assets/reader-BiKxuxj7.css.gz +0 -0
- codex/static_root/assets/reader-BiKxuxj7.dabe592f46f1.css.br +0 -0
- codex/static_root/assets/reader-BiKxuxj7.dabe592f46f1.css.gz +0 -0
- codex/static_root/assets/reader-s7jttB_F.f7b43b04961a.js.br +0 -0
- codex/static_root/assets/reader-s7jttB_F.f7b43b04961a.js.gz +0 -0
- codex/static_root/assets/reader-s7jttB_F.js.br +0 -0
- codex/static_root/assets/reader-s7jttB_F.js.gz +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.27eca8616ba4.js +0 -1
- codex/static_root/assets/relation-chips-BiVsYCd9.27eca8616ba4.js.br +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.27eca8616ba4.js.gz +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.js +0 -1
- codex/static_root/assets/relation-chips-BiVsYCd9.js.br +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.js.gz +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.css.br +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.css.gz +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.db095b2497e4.css.br +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.db095b2497e4.css.gz +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.352aaaab5925.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.352aaaab5925.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.js.gz +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.7be95b5021c3.js.br +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.7be95b5021c3.js.gz +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.js.br +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.js.gz +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.c574111658b9.js +0 -1
- codex/static_root/assets/task-tab-DLrd54z2.c574111658b9.js.br +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.c574111658b9.js.gz +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.js +0 -1
- codex/static_root/assets/task-tab-DLrd54z2.js.br +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.js.gz +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.f63b36d526b5.js.br +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.f63b36d526b5.js.gz +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.js.br +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.js.gz +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.2a497e8b2b24.js.br +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.2a497e8b2b24.js.gz +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.js.br +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.js.gz +0 -0
- codex/static_root/js/choices-admin.248384ece3f6.json +0 -1
- codex/static_root/js/choices-admin.248384ece3f6.json.br +0 -0
- codex/static_root/js/choices-admin.248384ece3f6.json.gz +0 -0
- codex/static_root/js/choices.b60817ba0a93.json.br +0 -0
- codex/static_root/js/choices.b60817ba0a93.json.gz +0 -0
- codex/static_root/manifest.3c2f342d5f5a.json +0 -642
- codex/static_root/manifest.3c2f342d5f5a.json.br +0 -0
- codex/static_root/manifest.3c2f342d5f5a.json.gz +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/LICENSE +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/WHEEL +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/entry_points.txt +0 -0
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
So we may safely create the comics next.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from itertools import chain
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
|
|
8
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
9
|
+
from django.db.models.functions.datetime import Now
|
|
10
|
+
|
|
9
11
|
from codex.librarian.importer.const import (
|
|
10
12
|
BULK_UPDATE_FOLDER_FIELDS,
|
|
13
|
+
CLASS_CUSTOM_COVER_GROUP_MAP,
|
|
11
14
|
COUNT_FIELDS,
|
|
12
15
|
CREATE_DICT_UPDATE_FIELDS,
|
|
16
|
+
CUSTOM_COVER_UPDATE_FIELDS,
|
|
13
17
|
GROUP_BASE_FIELDS,
|
|
14
18
|
GROUP_UPDATE_FIELDS,
|
|
15
19
|
IMPRINT,
|
|
@@ -24,6 +28,7 @@ from codex.models import (
|
|
|
24
28
|
Contributor,
|
|
25
29
|
ContributorPerson,
|
|
26
30
|
ContributorRole,
|
|
31
|
+
CustomCover,
|
|
27
32
|
Folder,
|
|
28
33
|
Imprint,
|
|
29
34
|
Publisher,
|
|
@@ -41,7 +46,20 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
41
46
|
"""Methods for creating foreign keys."""
|
|
42
47
|
|
|
43
48
|
@staticmethod
|
|
44
|
-
def
|
|
49
|
+
def _add_custom_cover_to_group(group_class, obj):
|
|
50
|
+
"""If a custom cover exists for this group, add it."""
|
|
51
|
+
group = CLASS_CUSTOM_COVER_GROUP_MAP.get(group_class)
|
|
52
|
+
if not group:
|
|
53
|
+
# Normal, volume doesn't link to covers
|
|
54
|
+
return
|
|
55
|
+
try:
|
|
56
|
+
cover = CustomCover.objects.filter(group=group, sort_name=obj.sort_name)[0]
|
|
57
|
+
obj.custom_cover = cover
|
|
58
|
+
except (IndexError, ObjectDoesNotExist):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def _create_group_obj(cls, group_class, group_param_tuple, group_count):
|
|
45
63
|
"""Create a set of browser group objects."""
|
|
46
64
|
defaults = {"name": group_param_tuple[-1]}
|
|
47
65
|
if group_class in (Imprint, Series, Volume):
|
|
@@ -64,6 +82,7 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
64
82
|
|
|
65
83
|
obj = group_class(**defaults)
|
|
66
84
|
obj.presave()
|
|
85
|
+
cls._add_custom_cover_to_group(group_class, obj)
|
|
67
86
|
return obj
|
|
68
87
|
|
|
69
88
|
@staticmethod
|
|
@@ -99,6 +118,8 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
99
118
|
obj = self._create_group_obj(group_class, group_param_tuple, group_count)
|
|
100
119
|
create_groups.append(obj)
|
|
101
120
|
update_fields = GROUP_UPDATE_FIELDS[group_class]
|
|
121
|
+
if group_class in CLASS_CUSTOM_COVER_GROUP_MAP:
|
|
122
|
+
update_fields += ("custom_cover",)
|
|
102
123
|
group_class.objects.bulk_create(
|
|
103
124
|
create_groups,
|
|
104
125
|
update_conflicts=True,
|
|
@@ -157,6 +178,7 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
157
178
|
parent_folder=parent,
|
|
158
179
|
)
|
|
159
180
|
folder.presave()
|
|
181
|
+
self._add_custom_cover_to_group(Folder, folder)
|
|
160
182
|
create_folders.append(folder)
|
|
161
183
|
|
|
162
184
|
def _bulk_folders_create_depth_level(self, library, paths, status):
|
|
@@ -212,6 +234,7 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
212
234
|
named_obj = named_class(name=name)
|
|
213
235
|
if is_story_arc:
|
|
214
236
|
named_obj.presave()
|
|
237
|
+
self._add_custom_cover_to_group(StoryArc, named_obj)
|
|
215
238
|
create_named_objs.append(named_obj)
|
|
216
239
|
|
|
217
240
|
update_fields = NAMED_MODEL_UPDATE_FIELDS
|
|
@@ -282,8 +305,8 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
282
305
|
self.status_controller.update(status)
|
|
283
306
|
return count
|
|
284
307
|
|
|
285
|
-
|
|
286
|
-
|
|
308
|
+
def create_all_fks(self, library, create_data) -> int:
|
|
309
|
+
"""Bulk create all foreign keys."""
|
|
287
310
|
(
|
|
288
311
|
create_groups,
|
|
289
312
|
update_groups,
|
|
@@ -292,35 +315,12 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
292
315
|
create_contributors,
|
|
293
316
|
create_story_arc_numbers,
|
|
294
317
|
create_identifiers,
|
|
318
|
+
total_fks,
|
|
295
319
|
) = create_data
|
|
296
|
-
total_fks = 0
|
|
297
|
-
for data_group in chain(
|
|
298
|
-
create_groups.values(), update_groups.values(), create_fks.values()
|
|
299
|
-
):
|
|
300
|
-
total_fks += len(data_group)
|
|
301
|
-
total_fks += (
|
|
302
|
-
len(create_folder_paths)
|
|
303
|
-
+ len(create_contributors)
|
|
304
|
-
+ len(create_story_arc_numbers)
|
|
305
|
-
+ len(create_identifiers)
|
|
306
|
-
)
|
|
307
|
-
return total_fks
|
|
308
320
|
|
|
309
|
-
def create_all_fks(self, library, create_data) -> int:
|
|
310
|
-
"""Bulk create all foreign keys."""
|
|
311
|
-
total_fks = self._get_create_fks_totals(create_data)
|
|
312
321
|
status = Status(ImportStatusTypes.CREATE_FKS, 0, total_fks)
|
|
313
322
|
try:
|
|
314
323
|
self.status_controller.start(status)
|
|
315
|
-
(
|
|
316
|
-
create_groups,
|
|
317
|
-
update_groups,
|
|
318
|
-
create_folder_paths,
|
|
319
|
-
create_fks,
|
|
320
|
-
create_contributors,
|
|
321
|
-
create_story_arc_numbers,
|
|
322
|
-
create_identifiers,
|
|
323
|
-
) = create_data
|
|
324
324
|
|
|
325
325
|
for group_class, group_tree_counts in create_groups.items():
|
|
326
326
|
count = self._bulk_group_create(
|
|
@@ -375,3 +375,63 @@ class CreateForeignKeysMixin(QueuedThread):
|
|
|
375
375
|
finally:
|
|
376
376
|
self.status_controller.finish(status)
|
|
377
377
|
return status.complete if status.complete else 0
|
|
378
|
+
|
|
379
|
+
@status_notify(ImportStatusTypes.COVERS_MODIFIED, updates=False)
|
|
380
|
+
def update_custom_covers(
|
|
381
|
+
self, update_covers_qs, link_cover_pks, status=None
|
|
382
|
+
) -> int:
|
|
383
|
+
"""Update Custom Covers."""
|
|
384
|
+
count = 0
|
|
385
|
+
update_covers_count = update_covers_qs.count()
|
|
386
|
+
if not update_covers_count:
|
|
387
|
+
return count
|
|
388
|
+
if status:
|
|
389
|
+
status.total = update_covers_count
|
|
390
|
+
now = Now()
|
|
391
|
+
|
|
392
|
+
update_covers = []
|
|
393
|
+
for cover in update_covers_qs.only(*CUSTOM_COVER_UPDATE_FIELDS):
|
|
394
|
+
cover.updated_at = now
|
|
395
|
+
cover.presave()
|
|
396
|
+
update_covers.append(cover)
|
|
397
|
+
|
|
398
|
+
if update_covers:
|
|
399
|
+
CustomCover.objects.bulk_update(update_covers, CUSTOM_COVER_UPDATE_FIELDS)
|
|
400
|
+
update_cover_pks = update_covers_qs.values_list("pk", flat=True)
|
|
401
|
+
link_cover_pks.update(update_cover_pks)
|
|
402
|
+
self._remove_covers(update_cover_pks, custom=True) # type: ignore
|
|
403
|
+
count = len(update_covers)
|
|
404
|
+
if status:
|
|
405
|
+
status.add_complete(count)
|
|
406
|
+
return count
|
|
407
|
+
|
|
408
|
+
@status_notify(ImportStatusTypes.COVERS_CREATED, updates=False)
|
|
409
|
+
def create_custom_covers(
|
|
410
|
+
self, create_cover_paths, library, link_cover_pks, status=None
|
|
411
|
+
) -> int:
|
|
412
|
+
"""Create Custom Covers."""
|
|
413
|
+
count = 0
|
|
414
|
+
if not create_cover_paths:
|
|
415
|
+
return count
|
|
416
|
+
if status:
|
|
417
|
+
status.total = len(create_cover_paths)
|
|
418
|
+
|
|
419
|
+
create_covers = []
|
|
420
|
+
for path in create_cover_paths:
|
|
421
|
+
cover = CustomCover(library=library, path=path)
|
|
422
|
+
cover.presave()
|
|
423
|
+
create_covers.append(cover)
|
|
424
|
+
|
|
425
|
+
if create_covers:
|
|
426
|
+
objs = CustomCover.objects.bulk_create(
|
|
427
|
+
create_covers,
|
|
428
|
+
update_conflicts=True,
|
|
429
|
+
update_fields=("path", "stat"),
|
|
430
|
+
unique_fields=CustomCover._meta.unique_together[0],
|
|
431
|
+
)
|
|
432
|
+
created_pks = frozenset(obj.pk for obj in objs)
|
|
433
|
+
link_cover_pks.update(created_pks)
|
|
434
|
+
count = len(created_pks)
|
|
435
|
+
if status:
|
|
436
|
+
status.add_complete(count)
|
|
437
|
+
return count
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
from codex.librarian.covers.tasks import CoverRemoveTask
|
|
4
4
|
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
5
5
|
from codex.models import Comic, Folder
|
|
6
|
+
from codex.models.paths import CustomCover
|
|
6
7
|
from codex.threads import QueuedThread
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class DeletedMixin(QueuedThread):
|
|
10
11
|
"""Clean up database methods."""
|
|
11
12
|
|
|
12
|
-
def _remove_covers(self,
|
|
13
|
-
task = CoverRemoveTask(
|
|
13
|
+
def _remove_covers(self, delete_pks, custom=False):
|
|
14
|
+
task = CoverRemoveTask(delete_pks, custom)
|
|
14
15
|
self.librarian_queue.put(task)
|
|
15
16
|
|
|
16
17
|
@status_notify(status_type=ImportStatusTypes.DIRS_DELETED, updates=False)
|
|
@@ -53,6 +54,25 @@ class DeletedMixin(QueuedThread):
|
|
|
53
54
|
|
|
54
55
|
return count
|
|
55
56
|
|
|
57
|
+
@status_notify(status_type=ImportStatusTypes.COVERS_DELETED, updates=False)
|
|
58
|
+
def _bulk_covers_deleted(self, delete_cover_paths, library, **kwargs):
|
|
59
|
+
"""Bulk delete comics found missing from the filesystem."""
|
|
60
|
+
if not delete_cover_paths:
|
|
61
|
+
return 0
|
|
62
|
+
covers = CustomCover.objects.filter(
|
|
63
|
+
library=library, path__in=delete_cover_paths
|
|
64
|
+
)
|
|
65
|
+
delete_cover_pks = frozenset(covers.values_list("pk", flat=True))
|
|
66
|
+
covers.delete()
|
|
67
|
+
|
|
68
|
+
self._remove_covers(delete_cover_pks, custom=True)
|
|
69
|
+
|
|
70
|
+
count = len(delete_cover_paths)
|
|
71
|
+
if count:
|
|
72
|
+
self.log.info(f"Deleted {count} custom covers from {library.path}")
|
|
73
|
+
|
|
74
|
+
return count
|
|
75
|
+
|
|
56
76
|
def delete(self, library, task):
|
|
57
77
|
"""Delete files and folders."""
|
|
58
78
|
count = self._bulk_folders_deleted(task.dirs_deleted, library)
|
|
@@ -60,4 +80,8 @@ class DeletedMixin(QueuedThread):
|
|
|
60
80
|
|
|
61
81
|
count += self._bulk_comics_deleted(task.files_deleted, library)
|
|
62
82
|
task.files_deleted = None
|
|
83
|
+
|
|
84
|
+
count += self._bulk_covers_deleted(task.covers_deleted, library)
|
|
85
|
+
task.covers_deleted = None
|
|
86
|
+
|
|
63
87
|
return count
|
|
@@ -117,6 +117,29 @@ class ComicImporterThread(
|
|
|
117
117
|
log += ", ".join(dirs_log)
|
|
118
118
|
self.log.debug(" " + log)
|
|
119
119
|
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _init_librarian_status_moved(task, status_list):
|
|
122
|
+
"""Initialize moved statuses."""
|
|
123
|
+
search_index_updates = 0
|
|
124
|
+
if task.dirs_moved:
|
|
125
|
+
status_list += [
|
|
126
|
+
Status(ImportStatusTypes.DIRS_MOVED, None, len(task.dirs_moved))
|
|
127
|
+
]
|
|
128
|
+
if task.files_moved:
|
|
129
|
+
status_list += [
|
|
130
|
+
Status(ImportStatusTypes.FILES_MOVED, None, len(task.files_moved))
|
|
131
|
+
]
|
|
132
|
+
search_index_updates += len(task.files_moved)
|
|
133
|
+
if task.covers_moved:
|
|
134
|
+
status_list += [
|
|
135
|
+
Status(ImportStatusTypes.COVERS_MOVED, None, len(task.covers_moved))
|
|
136
|
+
]
|
|
137
|
+
if task.dirs_modified:
|
|
138
|
+
status_list += [
|
|
139
|
+
Status(ImportStatusTypes.DIRS_MODIFIED, None, len(task.dirs_modified))
|
|
140
|
+
]
|
|
141
|
+
return search_index_updates
|
|
142
|
+
|
|
120
143
|
@staticmethod
|
|
121
144
|
def _init_if_modified_or_created(task, path, status_list):
|
|
122
145
|
"""Initialize librarian statuses for modified or created ops."""
|
|
@@ -124,6 +147,7 @@ class ComicImporterThread(
|
|
|
124
147
|
status_list += [
|
|
125
148
|
Status(ImportStatusTypes.AGGREGATE_TAGS, 0, total_paths, subtitle=path),
|
|
126
149
|
Status(ImportStatusTypes.QUERY_MISSING_FKS),
|
|
150
|
+
Status(ImportStatusTypes.QUERY_MISSING_COVERS),
|
|
127
151
|
Status(ImportStatusTypes.CREATE_FKS),
|
|
128
152
|
]
|
|
129
153
|
if task.files_modified:
|
|
@@ -134,40 +158,56 @@ class ComicImporterThread(
|
|
|
134
158
|
len(task.files_modified),
|
|
135
159
|
)
|
|
136
160
|
]
|
|
137
|
-
if task.
|
|
161
|
+
if task.covers_modified:
|
|
138
162
|
status_list += [
|
|
139
|
-
Status(
|
|
163
|
+
Status(
|
|
164
|
+
ImportStatusTypes.COVERS_MODIFIED,
|
|
165
|
+
None,
|
|
166
|
+
len(task.covers_modified),
|
|
167
|
+
)
|
|
140
168
|
]
|
|
141
|
-
if task.files_modified or task.files_created:
|
|
142
|
-
status_list += [Status(ImportStatusTypes.LINK_M2M_FIELDS)]
|
|
143
|
-
return total_paths
|
|
144
169
|
|
|
145
|
-
|
|
146
|
-
"""Update the librarian status tasks."""
|
|
147
|
-
status_list = []
|
|
148
|
-
search_index_updates = 0
|
|
149
|
-
if task.dirs_moved:
|
|
170
|
+
if task.files_created or task.covers_created:
|
|
150
171
|
status_list += [
|
|
151
|
-
Status(ImportStatusTypes.
|
|
172
|
+
Status(ImportStatusTypes.FILES_CREATED, None, len(task.files_created))
|
|
152
173
|
]
|
|
153
|
-
if task.
|
|
174
|
+
if task.covers_created:
|
|
154
175
|
status_list += [
|
|
155
|
-
Status(ImportStatusTypes.
|
|
176
|
+
Status(ImportStatusTypes.COVERS_CREATED, None, len(task.covers_created))
|
|
156
177
|
]
|
|
157
|
-
|
|
158
|
-
if task.files_modified:
|
|
178
|
+
|
|
179
|
+
if task.files_modified or task.files_created:
|
|
180
|
+
status_list += [Status(ImportStatusTypes.LINK_M2M_FIELDS)]
|
|
181
|
+
|
|
182
|
+
num_covers_linked = (
|
|
183
|
+
len(task.covers_moved)
|
|
184
|
+
+ len(task.covers_modified)
|
|
185
|
+
+ len(task.covers_created)
|
|
186
|
+
)
|
|
187
|
+
if num_covers_linked:
|
|
159
188
|
status_list += [
|
|
160
|
-
Status(ImportStatusTypes.
|
|
189
|
+
Status(ImportStatusTypes.COVERS_LINK, None, num_covers_linked)
|
|
161
190
|
]
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
return total_paths
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _init_librarian_status_deleted(task, status_list):
|
|
195
|
+
"""Init deleted statuses."""
|
|
196
|
+
search_index_updates = 0
|
|
166
197
|
if task.files_deleted:
|
|
167
198
|
status_list += [
|
|
168
199
|
Status(ImportStatusTypes.FILES_DELETED, None, len(task.files_deleted))
|
|
169
200
|
]
|
|
170
201
|
search_index_updates += len(task.files_deleted)
|
|
202
|
+
if task.covers_deleted:
|
|
203
|
+
status_list += [
|
|
204
|
+
Status(ImportStatusTypes.COVERS_DELETED, None, len(task.covers_deleted))
|
|
205
|
+
]
|
|
206
|
+
return search_index_updates
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _init_librarian_status_search_index(search_index_updates, status_list):
|
|
210
|
+
"""Init search index statuses."""
|
|
171
211
|
status_list += [
|
|
172
212
|
Status(
|
|
173
213
|
SearchIndexStatusTypes.SEARCH_INDEX_UPDATE,
|
|
@@ -176,6 +216,23 @@ class ComicImporterThread(
|
|
|
176
216
|
),
|
|
177
217
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
|
|
178
218
|
]
|
|
219
|
+
|
|
220
|
+
def _init_librarian_status(self, task, path):
|
|
221
|
+
"""Update the librarian status tasks."""
|
|
222
|
+
status_list = []
|
|
223
|
+
search_index_updates = 0
|
|
224
|
+
search_index_updates += self._init_librarian_status_moved(task, status_list)
|
|
225
|
+
if (
|
|
226
|
+
task.files_modified
|
|
227
|
+
or task.files_created
|
|
228
|
+
or task.covers_modified
|
|
229
|
+
or task.covers_created
|
|
230
|
+
):
|
|
231
|
+
search_index_updates += self._init_if_modified_or_created(
|
|
232
|
+
task, path, status_list
|
|
233
|
+
)
|
|
234
|
+
search_index_updates += self._init_librarian_status_deleted(task, status_list)
|
|
235
|
+
self._init_librarian_status_search_index(search_index_updates, status_list)
|
|
179
236
|
self.status_controller.start_many(status_list)
|
|
180
237
|
|
|
181
238
|
def _init_apply(self, library, task):
|
|
@@ -193,12 +250,38 @@ class ComicImporterThread(
|
|
|
193
250
|
self._log_task(library.path, task)
|
|
194
251
|
self._init_librarian_status(task, library.path)
|
|
195
252
|
|
|
196
|
-
def
|
|
253
|
+
def _create_comic_and_cover_relations(
|
|
254
|
+
self, library, fks, cover_paths: frozenset[int], link_cover_pks: set[int]
|
|
255
|
+
) -> int:
|
|
197
256
|
"""Query all foreign keys to determine what needs creating, then create them."""
|
|
198
|
-
|
|
199
|
-
|
|
257
|
+
count = 0
|
|
258
|
+
if not fks and not cover_paths:
|
|
259
|
+
return count
|
|
200
260
|
create_data = self.query_all_missing_fks(library.path, fks)
|
|
201
|
-
|
|
261
|
+
query_cover_data = []
|
|
262
|
+
count += self.query_missing_custom_covers(
|
|
263
|
+
cover_paths,
|
|
264
|
+
library,
|
|
265
|
+
query_cover_data,
|
|
266
|
+
)
|
|
267
|
+
count += self.create_all_fks(library, create_data)
|
|
268
|
+
if query_cover_data:
|
|
269
|
+
update_covers_qs, create_cover_paths = query_cover_data
|
|
270
|
+
count += self.update_custom_covers(update_covers_qs, link_cover_pks)
|
|
271
|
+
link_covers_status = Status(
|
|
272
|
+
ImportStatusTypes.COVERS_LINK, 0, len(link_cover_pks)
|
|
273
|
+
)
|
|
274
|
+
self.status_controller.update(link_covers_status, notify=False)
|
|
275
|
+
count += self.create_custom_covers(
|
|
276
|
+
create_cover_paths,
|
|
277
|
+
library,
|
|
278
|
+
link_cover_pks,
|
|
279
|
+
)
|
|
280
|
+
link_covers_status = Status(
|
|
281
|
+
ImportStatusTypes.COVERS_LINK, 0, len(link_cover_pks)
|
|
282
|
+
)
|
|
283
|
+
self.status_controller.update(link_covers_status, notify=False)
|
|
284
|
+
return count
|
|
202
285
|
|
|
203
286
|
def _finish_apply_status(self, library):
|
|
204
287
|
"""Finish all librarian statuses."""
|
|
@@ -241,11 +324,13 @@ class ComicImporterThread(
|
|
|
241
324
|
self._init_apply(library, task)
|
|
242
325
|
|
|
243
326
|
changed: int = 0
|
|
244
|
-
|
|
327
|
+
link_cover_pks: set[int] = set()
|
|
328
|
+
changed += self.move_and_modify_dirs(library, task, link_cover_pks)
|
|
245
329
|
|
|
246
330
|
modified_paths = task.files_modified
|
|
247
331
|
created_paths = task.files_created
|
|
248
332
|
task.files_modified = task.files_created = None
|
|
333
|
+
|
|
249
334
|
mds = {}
|
|
250
335
|
m2m_mds = {}
|
|
251
336
|
fks = {}
|
|
@@ -266,8 +351,13 @@ class ComicImporterThread(
|
|
|
266
351
|
modified_paths -= fis.keys()
|
|
267
352
|
created_paths -= fis.keys()
|
|
268
353
|
|
|
269
|
-
|
|
270
|
-
|
|
354
|
+
cover_paths = frozenset(task.covers_modified | task.covers_created)
|
|
355
|
+
task.covers_modified = task.covers_created = None
|
|
356
|
+
|
|
357
|
+
changed += self._create_comic_and_cover_relations(
|
|
358
|
+
library, fks, cover_paths, link_cover_pks
|
|
359
|
+
)
|
|
360
|
+
fks = cover_paths = None
|
|
271
361
|
|
|
272
362
|
imported_count = self.bulk_update_comics(
|
|
273
363
|
modified_paths,
|
|
@@ -282,6 +372,9 @@ class ComicImporterThread(
|
|
|
282
372
|
m2m_mds = None
|
|
283
373
|
changed += imported_count
|
|
284
374
|
|
|
375
|
+
self.link_custom_covers(link_cover_pks)
|
|
376
|
+
link_cover_pks = set()
|
|
377
|
+
|
|
285
378
|
new_failed_imports = self.fail_imports(
|
|
286
379
|
library, fis, bool(task.files_deleted)
|
|
287
380
|
)
|
|
@@ -310,11 +403,15 @@ class ComicImporterThread(
|
|
|
310
403
|
library_id=library_id,
|
|
311
404
|
dirs_moved={},
|
|
312
405
|
files_moved={},
|
|
406
|
+
covers_moved={},
|
|
313
407
|
dirs_modified=frozenset(),
|
|
314
408
|
files_modified=frozenset(paths),
|
|
409
|
+
covers_modified=frozenset(),
|
|
315
410
|
files_created=frozenset(),
|
|
411
|
+
covers_created=frozenset(),
|
|
316
412
|
dirs_deleted=frozenset(),
|
|
317
413
|
files_deleted=frozenset(),
|
|
414
|
+
covers_deleted=frozenset(),
|
|
318
415
|
force_import_metadata=True,
|
|
319
416
|
)
|
|
320
417
|
self._apply(task)
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
|
|
7
7
|
from codex.librarian.importer.const import (
|
|
8
|
+
CLASS_CUSTOM_COVER_GROUP_MAP,
|
|
8
9
|
COMIC_FK_FIELD_NAMES,
|
|
9
10
|
DICT_MODEL_FIELD_NAME_CLASS_MAP,
|
|
10
11
|
DICT_MODEL_REL_LINK_MAP,
|
|
@@ -20,6 +21,7 @@ from codex.librarian.importer.const import (
|
|
|
20
21
|
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
21
22
|
from codex.models import (
|
|
22
23
|
Comic,
|
|
24
|
+
CustomCover,
|
|
23
25
|
Folder,
|
|
24
26
|
Imprint,
|
|
25
27
|
Publisher,
|
|
@@ -257,3 +259,56 @@ class LinkComicsMixin(QueuedThread):
|
|
|
257
259
|
if del_total:
|
|
258
260
|
self.log.info(f"Deleted {del_total} stale relations for altered comics.")
|
|
259
261
|
return created_total + del_total
|
|
262
|
+
|
|
263
|
+
def _link_custom_cover_prepare(self, cover, model_map):
|
|
264
|
+
"""Prepare one cover in the model map for bulk update."""
|
|
265
|
+
if cover.library and cover.library.covers_only:
|
|
266
|
+
model = CLASS_CUSTOM_COVER_GROUP_MAP.inverse.get(cover.group)
|
|
267
|
+
if not model:
|
|
268
|
+
self.log.warning(f"Custom Cover model not found for {cover.path}")
|
|
269
|
+
return
|
|
270
|
+
group_filter = {"sort_name": cover.sort_name}
|
|
271
|
+
else:
|
|
272
|
+
model = Folder
|
|
273
|
+
path = str(Path(cover.path).parent)
|
|
274
|
+
group_filter = {"path": path}
|
|
275
|
+
qs = model.objects.filter(**group_filter).exclude(custom_cover=cover)
|
|
276
|
+
if not qs.exists():
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
if model not in model_map:
|
|
280
|
+
model_map[model] = []
|
|
281
|
+
|
|
282
|
+
for obj in qs.iterator():
|
|
283
|
+
obj.custom_cover = cover
|
|
284
|
+
model_map[model].append(obj)
|
|
285
|
+
|
|
286
|
+
def _link_custom_cover_group(self, model, objs, status):
|
|
287
|
+
"""Bulk link a group to it's custom covers."""
|
|
288
|
+
count = 0
|
|
289
|
+
if not objs:
|
|
290
|
+
return count
|
|
291
|
+
model.objects.bulk_update(objs, ["custom_cover"])
|
|
292
|
+
count += len(objs)
|
|
293
|
+
self.log.info(f"Linked {count} custom covers to {model.__name__}s")
|
|
294
|
+
if status:
|
|
295
|
+
status.complete = status.complete or 0
|
|
296
|
+
status.complete += count
|
|
297
|
+
return count
|
|
298
|
+
|
|
299
|
+
@status_notify(status_type=ImportStatusTypes.COVERS_LINK)
|
|
300
|
+
def link_custom_covers(self, link_cover_pks, status=None):
|
|
301
|
+
"""Link Custom Covers to Groups."""
|
|
302
|
+
# Aggregate objs to update for each group model.
|
|
303
|
+
model_map = {}
|
|
304
|
+
covers = CustomCover.objects.filter(pk__in=link_cover_pks).only(
|
|
305
|
+
"library", "path"
|
|
306
|
+
)
|
|
307
|
+
for cover in covers:
|
|
308
|
+
self._link_custom_cover_prepare(cover, model_map)
|
|
309
|
+
|
|
310
|
+
# Bulk update each model type
|
|
311
|
+
total_count = 0
|
|
312
|
+
for model, objs in model_map.items():
|
|
313
|
+
total_count += self._link_custom_cover_group(model, objs, status)
|
|
314
|
+
return total_count
|
|
@@ -6,6 +6,8 @@ from django.db.models.functions import Now
|
|
|
6
6
|
|
|
7
7
|
from codex.librarian.importer.const import (
|
|
8
8
|
BULK_UPDATE_FOLDER_MODIFIED_FIELDS,
|
|
9
|
+
CLASS_CUSTOM_COVER_GROUP_MAP,
|
|
10
|
+
CUSTOM_COVER_UPDATE_FIELDS,
|
|
9
11
|
FOLDERS_FIELD,
|
|
10
12
|
MOVED_BULK_COMIC_UPDATE_FIELDS,
|
|
11
13
|
MOVED_BULK_FOLDER_UPDATE_FIELDS,
|
|
@@ -17,7 +19,7 @@ from codex.librarian.importer.create_fks import (
|
|
|
17
19
|
)
|
|
18
20
|
from codex.librarian.importer.query_fks import QueryForeignKeysMixin
|
|
19
21
|
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
20
|
-
from codex.models import Comic, Folder, Library
|
|
22
|
+
from codex.models import Comic, CustomCover, Folder, Library
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class MovedMixin(CreateComicsMixin, CreateForeignKeysMixin, QueryForeignKeysMixin):
|
|
@@ -73,6 +75,76 @@ class MovedMixin(CreateComicsMixin, CreateForeignKeysMixin, QueryForeignKeysMixi
|
|
|
73
75
|
|
|
74
76
|
return count
|
|
75
77
|
|
|
78
|
+
def _bulk_covers_moved_prepare(self, library, moved_paths, status):
|
|
79
|
+
"""Create an update map for bulk update."""
|
|
80
|
+
covers = CustomCover.objects.filter(
|
|
81
|
+
library=library, path__in=moved_paths.keys()
|
|
82
|
+
).only("pk", "path")
|
|
83
|
+
|
|
84
|
+
if status:
|
|
85
|
+
status.total = covers.count()
|
|
86
|
+
|
|
87
|
+
now = Now()
|
|
88
|
+
moved_covers = []
|
|
89
|
+
unlink_pks = set()
|
|
90
|
+
for cover in covers.iterator():
|
|
91
|
+
try:
|
|
92
|
+
new_path = moved_paths[cover.path]
|
|
93
|
+
cover.path = new_path
|
|
94
|
+
new_path = Path(new_path)
|
|
95
|
+
cover.updated_at = now
|
|
96
|
+
cover.presave()
|
|
97
|
+
moved_covers.append(cover)
|
|
98
|
+
unlink_pks.add(cover.pk)
|
|
99
|
+
except Exception:
|
|
100
|
+
self.log.exception(f"moving {cover.path}")
|
|
101
|
+
return moved_covers, unlink_pks
|
|
102
|
+
|
|
103
|
+
def _bulk_covers_moved_unlink(self, unlink_pks):
|
|
104
|
+
"""Unlink moved covers because they could have moved between group dirs."""
|
|
105
|
+
if not unlink_pks:
|
|
106
|
+
return
|
|
107
|
+
self.log.debug(f"Unlinking {len(unlink_pks)} moved custom covers.")
|
|
108
|
+
for model in CLASS_CUSTOM_COVER_GROUP_MAP:
|
|
109
|
+
groups = model.objects.filter(custom_cover__in=unlink_pks)
|
|
110
|
+
unlink_groups = []
|
|
111
|
+
for group in groups:
|
|
112
|
+
group.custom_cover = None
|
|
113
|
+
unlink_groups.append(group)
|
|
114
|
+
if unlink_groups:
|
|
115
|
+
model.objects.bulk_update(unlink_groups, ["custom_cover"])
|
|
116
|
+
self.log.debug(
|
|
117
|
+
f"Unlinked {len(unlink_groups)} {model.__name__} moved custom covers."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
self._remove_covers(unlink_pks, custom=True) # type: ignore
|
|
121
|
+
|
|
122
|
+
@status_notify(status_type=ImportStatusTypes.FILES_MOVED, updates=False)
|
|
123
|
+
def _bulk_covers_moved(self, moved_paths, library, link_cover_pks, status=None):
|
|
124
|
+
"""Move covers."""
|
|
125
|
+
count = 0
|
|
126
|
+
if not moved_paths:
|
|
127
|
+
return count
|
|
128
|
+
if status:
|
|
129
|
+
status.total = len(moved_paths)
|
|
130
|
+
|
|
131
|
+
moved_covers, unlink_pks = self._bulk_covers_moved_prepare(
|
|
132
|
+
library, moved_paths, status
|
|
133
|
+
)
|
|
134
|
+
link_cover_pks.update(unlink_pks)
|
|
135
|
+
if moved_covers:
|
|
136
|
+
CustomCover.objects.bulk_update(moved_covers, CUSTOM_COVER_UPDATE_FIELDS)
|
|
137
|
+
count = len(moved_covers)
|
|
138
|
+
|
|
139
|
+
self._bulk_covers_moved_unlink(unlink_pks)
|
|
140
|
+
|
|
141
|
+
if count:
|
|
142
|
+
self.log.info(f"Moved {count} custom covers.")
|
|
143
|
+
if status:
|
|
144
|
+
status.add_complete(count)
|
|
145
|
+
|
|
146
|
+
return count
|
|
147
|
+
|
|
76
148
|
def _get_parent_folders(self, library, dest_folder_paths, status):
|
|
77
149
|
"""Get destination parent folders."""
|
|
78
150
|
# Determine parent folder paths.
|
|
@@ -163,7 +235,7 @@ class MovedMixin(CreateComicsMixin, CreateForeignKeysMixin, QueryForeignKeysMixi
|
|
|
163
235
|
|
|
164
236
|
def adopt_orphan_folders(self):
|
|
165
237
|
"""Find orphan folders and move them into their correct place."""
|
|
166
|
-
libraries = Library.objects.only("pk", "path")
|
|
238
|
+
libraries = Library.objects.filter(covers_only=False).only("pk", "path")
|
|
167
239
|
for library in libraries.iterator():
|
|
168
240
|
orphan_folder_paths = (
|
|
169
241
|
Folder.objects.filter(library=library, parent_folder=None)
|
|
@@ -177,7 +249,7 @@ class MovedMixin(CreateComicsMixin, CreateForeignKeysMixin, QueryForeignKeysMixi
|
|
|
177
249
|
|
|
178
250
|
self._bulk_folders_moved(folders_moved, library)
|
|
179
251
|
|
|
180
|
-
def move_and_modify_dirs(self, library, task):
|
|
252
|
+
def move_and_modify_dirs(self, library, task, link_cover_pks):
|
|
181
253
|
"""Move files and dirs and modify dirs."""
|
|
182
254
|
changed = 0
|
|
183
255
|
changed += self._bulk_folders_moved(task.dirs_moved, library)
|
|
@@ -186,6 +258,9 @@ class MovedMixin(CreateComicsMixin, CreateForeignKeysMixin, QueryForeignKeysMixi
|
|
|
186
258
|
changed += self._bulk_comics_moved(task.files_moved, library)
|
|
187
259
|
task.files_moved = None
|
|
188
260
|
|
|
261
|
+
changed += self._bulk_covers_moved(task.covers_moved, library, link_cover_pks)
|
|
262
|
+
task.covers_moved = None
|
|
263
|
+
|
|
189
264
|
changed += self.bulk_folders_modified(task.dirs_modified, library)
|
|
190
265
|
task.dirs_modified = None
|
|
191
266
|
|