codex 1.6.0a4__py3-none-any.whl → 1.6.0a6__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 +28 -18
- codex/librarian/covers/tasks.py +3 -2
- codex/librarian/importer/aggregate_metadata.py +2 -2
- codex/librarian/importer/const.py +31 -3
- codex/librarian/importer/covers.py +47 -0
- codex/librarian/importer/create_fks.py +88 -28
- codex/librarian/importer/deleted.py +71 -9
- codex/librarian/importer/importerd.py +133 -28
- 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 -5
- 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/migrations/0029_choices_adminflag_and_timestamp.py +44 -0
- codex/models/admin.py +0 -2
- 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/page.py +0 -1
- 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-BS5fdM4M.a5d446e4aeb3.js} +1 -1
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-CN6Sn_k7.js → VCheckbox-BS5fdM4M.js} +1 -1
- codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CN1RPZPQ.b763c6a7abaf.js → VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CN1RPZPQ.js → VCheckboxBtn-m4IbdjEe.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +1 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js +1 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
- codex/static_root/assets/{VDialog-DC3KGsMT.71e2a460e44b.js → VDialog-DiixpkQL.f84fd1fd1f84.js} +1 -1
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
- codex/static_root/assets/{VDialog-DC3KGsMT.js → VDialog-DiixpkQL.js} +1 -1
- codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DJQYXCn2.f0d471a1b80a.js → VExpansionPanels-Db5mPFue.56d0ad094a62.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DJQYXCn2.js → VExpansionPanels-Db5mPFue.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-CgodTThy.b20290d57146.js → VRadioGroup-DULWiMSI.4ae535e01d2b.js} +1 -1
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-CgodTThy.js → VRadioGroup-DULWiMSI.js} +1 -1
- codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +1 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.js +1 -0
- codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.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-BSL0qeL9.5b5835d286df.js} +1 -1
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-zPp4DXWq.js → VSelectionControl-BSL0qeL9.js} +1 -1
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-pK9Xompk.67014b86fc74.js → VSlideGroup-BQtz8h9p.ff97dbf209e1.js} +1 -1
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-pK9Xompk.js → VSlideGroup-BQtz8h9p.js} +1 -1
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
- codex/static_root/assets/{VTable-CMi6sDtP.8445f91d9023.js → VTable-C0gAriFq.fa6e4b60d4ef.js} +1 -1
- codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
- codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
- codex/static_root/assets/{VTable-CMi6sDtP.js → VTable-C0gAriFq.js} +1 -1
- codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
- codex/static_root/assets/VTable-C0gAriFq.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-Da_Hs7ls.c295f24c251e.js +1 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js +1 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-DNsRQx9l.c42f636c386e.js → VWindowItem-CFhKCj49.b2458002a937.js} +1 -1
- codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-DNsRQx9l.js → VWindowItem-CFhKCj49.js} +1 -1
- codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.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-BpGFsvOH.d7193c78d0dd.js +1 -0
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
- codex/static_root/assets/admin-BpGFsvOH.js +1 -0
- codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
- codex/static_root/assets/admin-BpGFsvOH.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/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +30 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +30 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +1 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
- codex/static_root/assets/browser-CRPvEdqv.js +1 -0
- codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
- codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +1 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.css +1 -0
- codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
- codex/static_root/assets/{change-password-dialog-BHTkpC6Z.2d5d5dd57ad3.js → change-password-dialog-JYM9aAR9.f71d378f4fa6.js} +1 -1
- codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-BHTkpC6Z.js → change-password-dialog-JYM9aAR9.js} +1 -1
- codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DmXGDg4z.284812bd376d.js → confirm-dialog-ZAZ6P1Rc.515858dd21e8.js} +1 -1
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DmXGDg4z.js → confirm-dialog-ZAZ6P1Rc.js} +1 -1
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
- codex/static_root/assets/{datetime-column-DKaH5S3z.5c6042016200.js → datetime-column-BT1MSP82.9617f6b94ebf.js} +1 -1
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
- codex/static_root/assets/{datetime-column-DKaH5S3z.js → datetime-column-BT1MSP82.js} +1 -1
- codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
- codex/static_root/assets/{filter-DUVATQW3.e1e7aa6d0b18.js → filter-frSNsexx.eab1570bd32d.js} +1 -1
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
- codex/static_root/assets/{filter-DUVATQW3.js → filter-frSNsexx.js} +1 -1
- codex/static_root/assets/filter-frSNsexx.js.br +0 -0
- codex/static_root/assets/filter-frSNsexx.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-Ci_XcDT0.a5ea640a08d6.js +1 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js +1 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
- codex/static_root/assets/{group-tab-D3CyU-Il.fbf9ce5ef593.js → group-tab-DFma2R-E.879c37b15bbd.js} +1 -1
- codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
- codex/static_root/assets/{group-tab-D3CyU-Il.js → group-tab-DFma2R-E.js} +1 -1
- codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
- codex/static_root/assets/{http-error-0ef6-Ajc.e2879bcd242f.js → http-error-B3fQGxpt.9efc29496e30.js} +1 -1
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
- codex/static_root/assets/{http-error-0ef6-Ajc.js → http-error-B3fQGxpt.js} +1 -1
- codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.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-V8DUGoHN.809c548ff347.js +1 -0
- codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js +1 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
- codex/static_root/assets/{main-CuzJev9T.js → main-Cavvqkyp.a9c4df906406.js} +6 -6
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
- codex/static_root/assets/{main-CuzJev9T.f58771cc37e2.js → main-Cavvqkyp.js} +6 -6
- codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
- codex/static_root/assets/main-Cavvqkyp.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-Dg9tH1Ju.b2eb1e0c6985.js +1 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +1 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BlU5mm5n.0431bcdf48d1.js → pdf-doc-4PKLN-Z3.62d8de4d1fd8.js} +1 -1
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BlU5mm5n.js → pdf-doc-4PKLN-Z3.js} +1 -1
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
- codex/static_root/assets/{reader-s7jttB_F.f7b43b04961a.js → reader-C39uGGxn.67b0f324cd5e.js} +2 -2
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
- codex/static_root/assets/{reader-s7jttB_F.js → reader-C39uGGxn.js} +2 -2
- codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
- codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
- codex/static_root/assets/{reader-BiKxuxj7.css → reader-N2Ei_y2K.8c0cdc0d513f.css} +1 -1
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
- codex/static_root/assets/{reader-BiKxuxj7.dabe592f46f1.css → reader-N2Ei_y2K.css} +1 -1
- codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.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-CAC5ytqH.f205d920ffea.js +1 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js +1 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-DbJuixzh.352aaaab5925.js → settings-drawer-CT73ieiV.fac6f69e86ff.js} +2 -2
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-DbJuixzh.js → settings-drawer-CT73ieiV.js} +2 -2
- codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BJOfMtqA.6471a50fc05b.css → settings-drawer-nZ9HvKSZ.01744e131806.css} +1 -1
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.br +0 -0
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.gz +0 -0
- codex/static_root/assets/{settings-drawer-BJOfMtqA.css → settings-drawer-nZ9HvKSZ.css} +1 -1
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.br +0 -0
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.gz +0 -0
- codex/static_root/assets/{stats-tab-BhRLh7XB.7be95b5021c3.js → stats-tab-C6qAnV6-.f08c46b5f141.js} +1 -1
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
- codex/static_root/assets/{stats-tab-BhRLh7XB.js → stats-tab-C6qAnV6-.js} +1 -1
- codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +1 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js +1 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
- codex/static_root/assets/{unauthorized-BgXQnNpn.f63b36d526b5.js → unauthorized-28bmiwAU.c74825b5c16f.js} +1 -1
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
- codex/static_root/assets/{unauthorized-BgXQnNpn.js → unauthorized-28bmiwAU.js} +1 -1
- codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
- codex/static_root/assets/{user-tab-cZoJDlb-.2a497e8b2b24.js → user-tab-BOq4BMMc.b64ac15dd772.js} +1 -1
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
- codex/static_root/assets/{user-tab-cZoJDlb-.js → user-tab-BOq4BMMc.js} +1 -1
- codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.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.ad9da9714e46.json +642 -0
- codex/static_root/manifest.ad9da9714e46.json.br +0 -0
- codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
- codex/static_root/manifest.json +256 -256
- 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 +3 -3
- codex/views/browser/browser.py +1 -9
- codex/views/browser/browser_annotations.py +101 -20
- codex/views/browser/browser_breadcrumbs.py +2 -2
- codex/views/browser/browser_order_by.py +31 -20
- codex/views/browser/filters/field.py +3 -1
- 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.0a6.dist-info}/METADATA +3 -2
- {codex-1.6.0a4.dist-info → codex-1.6.0a6.dist-info}/RECORD +350 -347
- 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-BJOfMtqA.6471a50fc05b.css.br +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.gz +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.css.br +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.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.0a6.dist-info}/LICENSE +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a6.dist-info}/WHEEL +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a6.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
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"""Clean up the database after moves or imports."""
|
|
2
2
|
|
|
3
3
|
from codex.librarian.covers.tasks import CoverRemoveTask
|
|
4
|
+
from codex.librarian.importer.const import COMIC_GROUP_FIELD_NAMES
|
|
4
5
|
from codex.librarian.importer.status import ImportStatusTypes, status_notify
|
|
5
|
-
from codex.models import Comic, Folder
|
|
6
|
+
from codex.models import Comic, Folder, StoryArc
|
|
7
|
+
from codex.models.paths import CustomCover
|
|
8
|
+
from codex.settings.settings import MAX_CHUNK_SIZE
|
|
6
9
|
from codex.threads import QueuedThread
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class DeletedMixin(QueuedThread):
|
|
10
13
|
"""Clean up database methods."""
|
|
11
14
|
|
|
12
|
-
def _remove_covers(self,
|
|
13
|
-
task = CoverRemoveTask(
|
|
15
|
+
def _remove_covers(self, delete_pks, custom=False):
|
|
16
|
+
task = CoverRemoveTask(delete_pks, custom)
|
|
14
17
|
self.librarian_queue.put(task)
|
|
15
18
|
|
|
16
19
|
@status_notify(status_type=ImportStatusTypes.DIRS_DELETED, updates=False)
|
|
@@ -36,14 +39,47 @@ class DeletedMixin(QueuedThread):
|
|
|
36
39
|
)
|
|
37
40
|
return count
|
|
38
41
|
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _populate_deleted_comic_groups(delete_qs, deleted_comic_groups):
|
|
44
|
+
"""Populate changed groups for cover timestamp updater."""
|
|
45
|
+
comics_deleted_qs = delete_qs.only(*COMIC_GROUP_FIELD_NAMES).prefetch_related(
|
|
46
|
+
"story_arc_numbers__story_arc"
|
|
47
|
+
)
|
|
48
|
+
for comic in comics_deleted_qs.iterator(chunk_size=MAX_CHUNK_SIZE):
|
|
49
|
+
for field_name in COMIC_GROUP_FIELD_NAMES:
|
|
50
|
+
if field_name == "story_arc_numbers":
|
|
51
|
+
for san in comic.story_arc_numbers.select_related("story_arc").only(
|
|
52
|
+
"story_arc"
|
|
53
|
+
):
|
|
54
|
+
deleted_comic_groups[StoryArc].add(san.story_arc.pk)
|
|
55
|
+
elif field_name == "folders":
|
|
56
|
+
for folder in comic.folders.only("pk"):
|
|
57
|
+
deleted_comic_groups[Folder].add(folder.pk)
|
|
58
|
+
else:
|
|
59
|
+
related_model = comic._meta.get_field(field_name).related_model
|
|
60
|
+
related_id = getattr(comic, field_name).pk
|
|
61
|
+
deleted_comic_groups[related_model].add(related_id)
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _init_deleted_comic_groups(deleted_comic_groups):
|
|
65
|
+
"""Init deleted_comic_groups, used later even if no deletes."""
|
|
66
|
+
for field_name in COMIC_GROUP_FIELD_NAMES:
|
|
67
|
+
related_model = Comic._meta.get_field(field_name).related_model
|
|
68
|
+
deleted_comic_groups[related_model] = set()
|
|
69
|
+
|
|
39
70
|
@status_notify(status_type=ImportStatusTypes.FILES_DELETED, updates=False)
|
|
40
|
-
def _bulk_comics_deleted(
|
|
71
|
+
def _bulk_comics_deleted(
|
|
72
|
+
self, delete_comic_paths, library, deleted_comic_groups, **kwargs
|
|
73
|
+
):
|
|
41
74
|
"""Bulk delete comics found missing from the filesystem."""
|
|
42
75
|
if not delete_comic_paths:
|
|
43
76
|
return 0
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
delete_qs = Comic.objects.filter(library=library, path__in=delete_comic_paths)
|
|
78
|
+
|
|
79
|
+
self._populate_deleted_comic_groups(delete_qs, deleted_comic_groups)
|
|
80
|
+
|
|
81
|
+
delete_comic_pks = frozenset(delete_qs.values_list("pk", flat=True))
|
|
82
|
+
delete_qs.delete()
|
|
47
83
|
|
|
48
84
|
self._remove_covers(delete_comic_pks)
|
|
49
85
|
|
|
@@ -53,11 +89,37 @@ class DeletedMixin(QueuedThread):
|
|
|
53
89
|
|
|
54
90
|
return count
|
|
55
91
|
|
|
56
|
-
|
|
92
|
+
@status_notify(status_type=ImportStatusTypes.COVERS_DELETED, updates=False)
|
|
93
|
+
def _bulk_covers_deleted(self, delete_cover_paths, library, **kwargs):
|
|
94
|
+
"""Bulk delete comics found missing from the filesystem."""
|
|
95
|
+
if not delete_cover_paths:
|
|
96
|
+
return 0
|
|
97
|
+
covers = CustomCover.objects.filter(
|
|
98
|
+
library=library, path__in=delete_cover_paths
|
|
99
|
+
)
|
|
100
|
+
delete_cover_pks = frozenset(covers.values_list("pk", flat=True))
|
|
101
|
+
covers.delete()
|
|
102
|
+
|
|
103
|
+
self._remove_covers(delete_cover_pks, custom=True)
|
|
104
|
+
|
|
105
|
+
count = len(delete_cover_paths)
|
|
106
|
+
if count:
|
|
107
|
+
self.log.info(f"Deleted {count} custom covers from {library.path}")
|
|
108
|
+
|
|
109
|
+
return count
|
|
110
|
+
|
|
111
|
+
def delete(self, library, task, deleted_comic_groups):
|
|
57
112
|
"""Delete files and folders."""
|
|
58
113
|
count = self._bulk_folders_deleted(task.dirs_deleted, library)
|
|
59
114
|
task.dirs_deleted = None
|
|
60
115
|
|
|
61
|
-
|
|
116
|
+
self._init_deleted_comic_groups(deleted_comic_groups)
|
|
117
|
+
count += self._bulk_comics_deleted(
|
|
118
|
+
task.files_deleted, library, deleted_comic_groups
|
|
119
|
+
)
|
|
62
120
|
task.files_deleted = None
|
|
121
|
+
|
|
122
|
+
count += self._bulk_covers_deleted(task.covers_deleted, library)
|
|
123
|
+
task.covers_deleted = None
|
|
124
|
+
|
|
63
125
|
return count
|
|
@@ -5,10 +5,12 @@ from pathlib import Path
|
|
|
5
5
|
from time import sleep, time
|
|
6
6
|
|
|
7
7
|
from django.core.cache import cache
|
|
8
|
+
from django.utils.timezone import now
|
|
8
9
|
from humanize import naturaldelta
|
|
9
10
|
|
|
10
11
|
from codex.librarian.importer.aggregate_metadata import AggregateMetadataMixin
|
|
11
12
|
from codex.librarian.importer.const import FIS, FKS, M2M_MDS, MDS
|
|
13
|
+
from codex.librarian.importer.covers import CoversMixin
|
|
12
14
|
from codex.librarian.importer.deleted import DeletedMixin
|
|
13
15
|
from codex.librarian.importer.failed_imports import FailedImportsMixin
|
|
14
16
|
from codex.librarian.importer.moved import MovedMixin
|
|
@@ -35,6 +37,7 @@ class ComicImporterThread(
|
|
|
35
37
|
UpdateComicsMixin,
|
|
36
38
|
FailedImportsMixin,
|
|
37
39
|
MovedMixin,
|
|
40
|
+
CoversMixin,
|
|
38
41
|
):
|
|
39
42
|
"""A worker to handle all bulk database updates."""
|
|
40
43
|
|
|
@@ -117,6 +120,29 @@ class ComicImporterThread(
|
|
|
117
120
|
log += ", ".join(dirs_log)
|
|
118
121
|
self.log.debug(" " + log)
|
|
119
122
|
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _init_librarian_status_moved(task, status_list):
|
|
125
|
+
"""Initialize moved statuses."""
|
|
126
|
+
search_index_updates = 0
|
|
127
|
+
if task.dirs_moved:
|
|
128
|
+
status_list += [
|
|
129
|
+
Status(ImportStatusTypes.DIRS_MOVED, None, len(task.dirs_moved))
|
|
130
|
+
]
|
|
131
|
+
if task.files_moved:
|
|
132
|
+
status_list += [
|
|
133
|
+
Status(ImportStatusTypes.FILES_MOVED, None, len(task.files_moved))
|
|
134
|
+
]
|
|
135
|
+
search_index_updates += len(task.files_moved)
|
|
136
|
+
if task.covers_moved:
|
|
137
|
+
status_list += [
|
|
138
|
+
Status(ImportStatusTypes.COVERS_MOVED, None, len(task.covers_moved))
|
|
139
|
+
]
|
|
140
|
+
if task.dirs_modified:
|
|
141
|
+
status_list += [
|
|
142
|
+
Status(ImportStatusTypes.DIRS_MODIFIED, None, len(task.dirs_modified))
|
|
143
|
+
]
|
|
144
|
+
return search_index_updates
|
|
145
|
+
|
|
120
146
|
@staticmethod
|
|
121
147
|
def _init_if_modified_or_created(task, path, status_list):
|
|
122
148
|
"""Initialize librarian statuses for modified or created ops."""
|
|
@@ -124,6 +150,7 @@ class ComicImporterThread(
|
|
|
124
150
|
status_list += [
|
|
125
151
|
Status(ImportStatusTypes.AGGREGATE_TAGS, 0, total_paths, subtitle=path),
|
|
126
152
|
Status(ImportStatusTypes.QUERY_MISSING_FKS),
|
|
153
|
+
Status(ImportStatusTypes.QUERY_MISSING_COVERS),
|
|
127
154
|
Status(ImportStatusTypes.CREATE_FKS),
|
|
128
155
|
]
|
|
129
156
|
if task.files_modified:
|
|
@@ -134,40 +161,56 @@ class ComicImporterThread(
|
|
|
134
161
|
len(task.files_modified),
|
|
135
162
|
)
|
|
136
163
|
]
|
|
137
|
-
if task.
|
|
164
|
+
if task.covers_modified:
|
|
138
165
|
status_list += [
|
|
139
|
-
Status(
|
|
166
|
+
Status(
|
|
167
|
+
ImportStatusTypes.COVERS_MODIFIED,
|
|
168
|
+
None,
|
|
169
|
+
len(task.covers_modified),
|
|
170
|
+
)
|
|
140
171
|
]
|
|
141
|
-
if task.files_modified or task.files_created:
|
|
142
|
-
status_list += [Status(ImportStatusTypes.LINK_M2M_FIELDS)]
|
|
143
|
-
return total_paths
|
|
144
172
|
|
|
145
|
-
|
|
146
|
-
"""Update the librarian status tasks."""
|
|
147
|
-
status_list = []
|
|
148
|
-
search_index_updates = 0
|
|
149
|
-
if task.dirs_moved:
|
|
173
|
+
if task.files_created or task.covers_created:
|
|
150
174
|
status_list += [
|
|
151
|
-
Status(ImportStatusTypes.
|
|
175
|
+
Status(ImportStatusTypes.FILES_CREATED, None, len(task.files_created))
|
|
152
176
|
]
|
|
153
|
-
if task.
|
|
177
|
+
if task.covers_created:
|
|
154
178
|
status_list += [
|
|
155
|
-
Status(ImportStatusTypes.
|
|
179
|
+
Status(ImportStatusTypes.COVERS_CREATED, None, len(task.covers_created))
|
|
156
180
|
]
|
|
157
|
-
|
|
158
|
-
if task.files_modified:
|
|
181
|
+
|
|
182
|
+
if task.files_modified or task.files_created:
|
|
183
|
+
status_list += [Status(ImportStatusTypes.LINK_M2M_FIELDS)]
|
|
184
|
+
|
|
185
|
+
num_covers_linked = (
|
|
186
|
+
len(task.covers_moved)
|
|
187
|
+
+ len(task.covers_modified)
|
|
188
|
+
+ len(task.covers_created)
|
|
189
|
+
)
|
|
190
|
+
if num_covers_linked:
|
|
159
191
|
status_list += [
|
|
160
|
-
Status(ImportStatusTypes.
|
|
192
|
+
Status(ImportStatusTypes.COVERS_LINK, None, num_covers_linked)
|
|
161
193
|
]
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
194
|
+
return total_paths
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _init_librarian_status_deleted(task, status_list):
|
|
198
|
+
"""Init deleted statuses."""
|
|
199
|
+
search_index_updates = 0
|
|
166
200
|
if task.files_deleted:
|
|
167
201
|
status_list += [
|
|
168
202
|
Status(ImportStatusTypes.FILES_DELETED, None, len(task.files_deleted))
|
|
169
203
|
]
|
|
170
204
|
search_index_updates += len(task.files_deleted)
|
|
205
|
+
if task.covers_deleted:
|
|
206
|
+
status_list += [
|
|
207
|
+
Status(ImportStatusTypes.COVERS_DELETED, None, len(task.covers_deleted))
|
|
208
|
+
]
|
|
209
|
+
return search_index_updates
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _init_librarian_status_search_index(search_index_updates, status_list):
|
|
213
|
+
"""Init search index statuses."""
|
|
171
214
|
status_list += [
|
|
172
215
|
Status(
|
|
173
216
|
SearchIndexStatusTypes.SEARCH_INDEX_UPDATE,
|
|
@@ -176,6 +219,23 @@ class ComicImporterThread(
|
|
|
176
219
|
),
|
|
177
220
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
|
|
178
221
|
]
|
|
222
|
+
|
|
223
|
+
def _init_librarian_status(self, task, path):
|
|
224
|
+
"""Update the librarian status tasks."""
|
|
225
|
+
status_list = []
|
|
226
|
+
search_index_updates = 0
|
|
227
|
+
search_index_updates += self._init_librarian_status_moved(task, status_list)
|
|
228
|
+
if (
|
|
229
|
+
task.files_modified
|
|
230
|
+
or task.files_created
|
|
231
|
+
or task.covers_modified
|
|
232
|
+
or task.covers_created
|
|
233
|
+
):
|
|
234
|
+
search_index_updates += self._init_if_modified_or_created(
|
|
235
|
+
task, path, status_list
|
|
236
|
+
)
|
|
237
|
+
search_index_updates += self._init_librarian_status_deleted(task, status_list)
|
|
238
|
+
self._init_librarian_status_search_index(search_index_updates, status_list)
|
|
179
239
|
self.status_controller.start_many(status_list)
|
|
180
240
|
|
|
181
241
|
def _init_apply(self, library, task):
|
|
@@ -193,12 +253,38 @@ class ComicImporterThread(
|
|
|
193
253
|
self._log_task(library.path, task)
|
|
194
254
|
self._init_librarian_status(task, library.path)
|
|
195
255
|
|
|
196
|
-
def
|
|
256
|
+
def _create_comic_and_cover_relations(
|
|
257
|
+
self, library, fks, cover_paths: frozenset[int], link_cover_pks: set[int]
|
|
258
|
+
) -> int:
|
|
197
259
|
"""Query all foreign keys to determine what needs creating, then create them."""
|
|
198
|
-
|
|
199
|
-
|
|
260
|
+
count = 0
|
|
261
|
+
if not fks and not cover_paths:
|
|
262
|
+
return count
|
|
200
263
|
create_data = self.query_all_missing_fks(library.path, fks)
|
|
201
|
-
|
|
264
|
+
query_cover_data = []
|
|
265
|
+
count += self.query_missing_custom_covers(
|
|
266
|
+
cover_paths,
|
|
267
|
+
library,
|
|
268
|
+
query_cover_data,
|
|
269
|
+
)
|
|
270
|
+
count += self.create_all_fks(library, create_data)
|
|
271
|
+
if query_cover_data:
|
|
272
|
+
update_covers_qs, create_cover_paths = query_cover_data
|
|
273
|
+
count += self.update_custom_covers(update_covers_qs, link_cover_pks)
|
|
274
|
+
link_covers_status = Status(
|
|
275
|
+
ImportStatusTypes.COVERS_LINK, 0, len(link_cover_pks)
|
|
276
|
+
)
|
|
277
|
+
self.status_controller.update(link_covers_status, notify=False)
|
|
278
|
+
count += self.create_custom_covers(
|
|
279
|
+
create_cover_paths,
|
|
280
|
+
library,
|
|
281
|
+
link_cover_pks,
|
|
282
|
+
)
|
|
283
|
+
link_covers_status = Status(
|
|
284
|
+
ImportStatusTypes.COVERS_LINK, 0, len(link_cover_pks)
|
|
285
|
+
)
|
|
286
|
+
self.status_controller.update(link_covers_status, notify=False)
|
|
287
|
+
return count
|
|
202
288
|
|
|
203
289
|
def _finish_apply_status(self, library):
|
|
204
290
|
"""Finish all librarian statuses."""
|
|
@@ -239,13 +325,16 @@ class ComicImporterThread(
|
|
|
239
325
|
library = Library.objects.get(pk=task.library_id)
|
|
240
326
|
try:
|
|
241
327
|
self._init_apply(library, task)
|
|
328
|
+
import_start_time = now()
|
|
242
329
|
|
|
243
330
|
changed: int = 0
|
|
244
|
-
|
|
331
|
+
link_cover_pks: set[int] = set()
|
|
332
|
+
changed += self.move_and_modify_dirs(library, task, link_cover_pks)
|
|
245
333
|
|
|
246
334
|
modified_paths = task.files_modified
|
|
247
335
|
created_paths = task.files_created
|
|
248
336
|
task.files_modified = task.files_created = None
|
|
337
|
+
|
|
249
338
|
mds = {}
|
|
250
339
|
m2m_mds = {}
|
|
251
340
|
fks = {}
|
|
@@ -266,8 +355,13 @@ class ComicImporterThread(
|
|
|
266
355
|
modified_paths -= fis.keys()
|
|
267
356
|
created_paths -= fis.keys()
|
|
268
357
|
|
|
269
|
-
|
|
270
|
-
|
|
358
|
+
cover_paths = frozenset(task.covers_modified | task.covers_created)
|
|
359
|
+
task.covers_modified = task.covers_created = None
|
|
360
|
+
|
|
361
|
+
changed += self._create_comic_and_cover_relations(
|
|
362
|
+
library, fks, cover_paths, link_cover_pks
|
|
363
|
+
)
|
|
364
|
+
fks = cover_paths = None
|
|
271
365
|
|
|
272
366
|
imported_count = self.bulk_update_comics(
|
|
273
367
|
modified_paths,
|
|
@@ -282,11 +376,18 @@ class ComicImporterThread(
|
|
|
282
376
|
m2m_mds = None
|
|
283
377
|
changed += imported_count
|
|
284
378
|
|
|
379
|
+
self.link_custom_covers(link_cover_pks)
|
|
380
|
+
link_cover_pks = set()
|
|
381
|
+
|
|
285
382
|
new_failed_imports = self.fail_imports(
|
|
286
383
|
library, fis, bool(task.files_deleted)
|
|
287
384
|
)
|
|
288
385
|
|
|
289
|
-
|
|
386
|
+
deleted_comic_groups = {}
|
|
387
|
+
changed += self.delete(library, task, deleted_comic_groups)
|
|
388
|
+
|
|
389
|
+
self.update_groups_for_covers(import_start_time, deleted_comic_groups)
|
|
390
|
+
|
|
290
391
|
if changed:
|
|
291
392
|
cache.clear()
|
|
292
393
|
finally:
|
|
@@ -310,11 +411,15 @@ class ComicImporterThread(
|
|
|
310
411
|
library_id=library_id,
|
|
311
412
|
dirs_moved={},
|
|
312
413
|
files_moved={},
|
|
414
|
+
covers_moved={},
|
|
313
415
|
dirs_modified=frozenset(),
|
|
314
416
|
files_modified=frozenset(paths),
|
|
417
|
+
covers_modified=frozenset(),
|
|
315
418
|
files_created=frozenset(),
|
|
419
|
+
covers_created=frozenset(),
|
|
316
420
|
dirs_deleted=frozenset(),
|
|
317
421
|
files_deleted=frozenset(),
|
|
422
|
+
covers_deleted=frozenset(),
|
|
318
423
|
force_import_metadata=True,
|
|
319
424
|
)
|
|
320
425
|
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
|