codex 1.6.19__py3-none-any.whl → 1.7.0a0__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/db.py +119 -0
- codex/librarian/covers/purge.py +1 -1
- codex/librarian/importer/const.py +7 -1
- codex/librarian/importer/create_fks.py +5 -2
- codex/librarian/importer/importerd.py +17 -12
- codex/librarian/importer/moved.py +44 -13
- codex/librarian/importer/tasks.py +2 -0
- codex/librarian/janitor/cleanup.py +21 -8
- codex/librarian/janitor/integrity.py +369 -0
- codex/librarian/janitor/janitor.py +50 -15
- codex/librarian/janitor/latest_version.py +2 -1
- codex/librarian/janitor/status.py +5 -0
- codex/librarian/janitor/tasks.py +37 -0
- codex/librarian/janitor/update.py +1 -1
- codex/librarian/librariand.py +32 -24
- codex/librarian/search/optimize.py +43 -0
- codex/librarian/search/remove.py +18 -46
- codex/librarian/search/searchd.py +14 -17
- codex/librarian/search/status.py +2 -1
- codex/librarian/search/tasks.py +4 -9
- codex/librarian/search/update.py +223 -301
- codex/librarian/watchdog/observers.py +2 -1
- codex/migrations/0029_comicfts.py +58 -0
- codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +198 -0
- codex/models/admin.py +15 -6
- codex/models/base.py +9 -0
- codex/models/bookmark.py +3 -3
- codex/models/comic.py +68 -16
- codex/models/functions.py +59 -2
- codex/models/groups.py +6 -1
- codex/models/library.py +1 -3
- codex/models/paths.py +9 -3
- codex/serializers/browser/page.py +2 -0
- codex/settings/settings.py +16 -17
- codex/signals/django_signals.py +4 -10
- codex/startup.py +1 -35
- codex/static_root/assets/{VCheckbox-Cko1sQK-.170726b3b8a1.js → VCheckbox-CWiqcZ7a.3feebd47daab.js} +1 -1
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-Cko1sQK-.js → VCheckbox-CWiqcZ7a.js} +1 -1
- codex/static_root/assets/VCheckbox-CWiqcZ7a.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWiqcZ7a.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js → VCheckboxBtn-Dk3iB62X.bc66d5351baa.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.js → VCheckboxBtn-Dk3iB62X.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.gz +0 -0
- codex/static_root/assets/{VCombobox-Bjp6lffE.js → VCombobox-D1_KU27A.b1d30587a973.js} +1 -1
- codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.gz +0 -0
- codex/static_root/assets/{VCombobox-Bjp6lffE.caf59fba486e.js → VCombobox-D1_KU27A.js} +1 -1
- codex/static_root/assets/VCombobox-D1_KU27A.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_KU27A.js.gz +0 -0
- codex/static_root/assets/{VDialog-BkpVGB70.6eea3ca762a5.js → VDialog-GbxvFMMN.594fb3a39ced.js} +1 -1
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.br +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.gz +0 -0
- codex/static_root/assets/{VDialog-BkpVGB70.js → VDialog-GbxvFMMN.js} +1 -1
- codex/static_root/assets/VDialog-GbxvFMMN.js.br +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js → VExpansionPanels-Cywv_bEj.a762e63858e5.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.js → VExpansionPanels-Cywv_bEj.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-D3Py5BfQ.a4f25edb86d3.js → VRadioGroup-C0etmxpZ.0eb3009a6300.js} +1 -1
- codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-D3Py5BfQ.js → VRadioGroup-C0etmxpZ.js} +1 -1
- codex/static_root/assets/VRadioGroup-C0etmxpZ.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C0etmxpZ.js.gz +0 -0
- codex/static_root/assets/{VSelect-DtvZsYZz.e98745d858eb.js → VSelect-txrnRiNJ.a5625760018c.js} +1 -1
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.br +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.gz +0 -0
- codex/static_root/assets/{VSelect-DtvZsYZz.js → VSelect-txrnRiNJ.js} +1 -1
- codex/static_root/assets/VSelect-txrnRiNJ.js.br +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-D6kmykQW.1e1fda62ceba.js → VSelectionControl-CY6RmcvW.7e8824224f6c.js} +1 -1
- codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-D6kmykQW.js → VSelectionControl-CY6RmcvW.js} +1 -1
- codex/static_root/assets/VSelectionControl-CY6RmcvW.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CY6RmcvW.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-UGZWxpF5.360bdefcd215.js → VSlideGroup-DtEQMwY1.6c72325635ed.js} +1 -1
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-UGZWxpF5.js → VSlideGroup-DtEQMwY1.js} +1 -1
- codex/static_root/assets/VSlideGroup-DtEQMwY1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.js.gz +0 -0
- codex/static_root/assets/{VTable-DXH_yQ0o.1149f56ad566.js → VTable-2af995xo.6c5bf36631b2.js} +1 -1
- codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.br +0 -0
- codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.gz +0 -0
- codex/static_root/assets/{VTable-DXH_yQ0o.js → VTable-2af995xo.js} +1 -1
- codex/static_root/assets/VTable-2af995xo.js.br +0 -0
- codex/static_root/assets/VTable-2af995xo.js.gz +0 -0
- codex/static_root/assets/{VTextField-Blqy56_L.26d8761e0155.js → VTextField-G6CMj7yO.657a130dd302.js} +1 -1
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.br +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.gz +0 -0
- codex/static_root/assets/{VTextField-Blqy56_L.js → VTextField-G6CMj7yO.js} +1 -1
- codex/static_root/assets/VTextField-G6CMj7yO.js.br +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-C8_ovV7e.afbc08414765.js → VWindowItem-CzoGd6Ul.721e5da11ad4.js} +1 -1
- codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.br +0 -0
- codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-C8_ovV7e.js → VWindowItem-CzoGd6Ul.js} +1 -1
- codex/static_root/assets/VWindowItem-CzoGd6Ul.js.br +0 -0
- codex/static_root/assets/VWindowItem-CzoGd6Ul.js.gz +0 -0
- codex/static_root/assets/{admin-DTRn8bBs.d031ea42d8d0.js → admin-CsPAIQeG.5175467148a3.js} +1 -1
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.br +0 -0
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.gz +0 -0
- codex/static_root/assets/{admin-DTRn8bBs.js → admin-CsPAIQeG.js} +1 -1
- codex/static_root/assets/admin-CsPAIQeG.js.br +0 -0
- codex/static_root/assets/admin-CsPAIQeG.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BaXFOcru.b1a44b05f482.js → admin-drawer-panel-BAT7GA64.73a064628c19.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BaXFOcru.js → admin-drawer-panel-BAT7GA64.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js +1 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js.br +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.js +1 -0
- codex/static_root/assets/browser-C18l93yc.js.br +0 -0
- codex/static_root/assets/browser-C18l93yc.js.gz +0 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css +1 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.br +0 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.gz +0 -0
- codex/static_root/assets/browser-DaAqlBYO.css +1 -0
- codex/static_root/assets/browser-DaAqlBYO.css.br +0 -0
- codex/static_root/assets/browser-DaAqlBYO.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js +1 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js +1 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css +1 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css +1 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.gz +0 -0
- codex/static_root/assets/{confirm-dialog-D3_s2DQy.9dde66036c72.js → confirm-dialog-CbW3hlOp.9dd39b443c31.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-D3_s2DQy.js → confirm-dialog-CbW3hlOp.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BxC1Li9e.3dc314b3295d.js → datetime-column-uSqKZU37.b157b0743db5.js} +1 -1
- codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.br +0 -0
- codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BxC1Li9e.js → datetime-column-uSqKZU37.js} +1 -1
- codex/static_root/assets/datetime-column-uSqKZU37.js.br +0 -0
- codex/static_root/assets/datetime-column-uSqKZU37.js.gz +0 -0
- codex/static_root/assets/{filter-Bzk7x841.d0c3b602e62d.js → filter-CBl5KEsB.845a81573c2e.js} +1 -1
- codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.br +0 -0
- codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.gz +0 -0
- codex/static_root/assets/{filter-Bzk7x841.js → filter-CBl5KEsB.js} +1 -1
- codex/static_root/assets/filter-CBl5KEsB.js.br +0 -0
- codex/static_root/assets/filter-CBl5KEsB.js.gz +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js +1 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.br +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.gz +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js +1 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js.br +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js.gz +0 -0
- codex/static_root/assets/{group-tab-5esH4lwX.a81fdd258117.js → group-tab-BOW9kigw.7e5007596367.js} +1 -1
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.br +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.gz +0 -0
- codex/static_root/assets/{group-tab-5esH4lwX.js → group-tab-BOW9kigw.js} +1 -1
- codex/static_root/assets/group-tab-BOW9kigw.js.br +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.js.gz +0 -0
- codex/static_root/assets/{http-error-BTOBfcGY.52ced6f28420.js → http-error-C6yuLApo.45cf99eb7a70.js} +1 -1
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +4 -0
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
- codex/static_root/assets/{http-error-BTOBfcGY.js → http-error-C6yuLApo.js} +1 -1
- codex/static_root/assets/http-error-C6yuLApo.js.br +4 -0
- codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
- codex/static_root/assets/{library-tab-DiPEdOF7.ece2d642a9dc.js → library-tab-D3WOtpE0.9af34e6e7235.js} +1 -1
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.br +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.gz +0 -0
- codex/static_root/assets/{library-tab-DiPEdOF7.js → library-tab-D3WOtpE0.js} +1 -1
- codex/static_root/assets/library-tab-D3WOtpE0.js.br +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.js.gz +0 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js +6 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.br +0 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.gz +0 -0
- codex/static_root/assets/main-CmjU2oTp.js +6 -0
- codex/static_root/assets/main-CmjU2oTp.js.br +0 -0
- codex/static_root/assets/main-CmjU2oTp.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js +1 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js +1 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-C45-onwe.4722195bb60b.css → pagination-toolbar-DGrxp2OD.css} +1 -1
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-C45-onwe.css → pagination-toolbar-DGrxp2OD.f960a624f04f.css} +1 -1
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-CkIzuyx1.00c354a82e6e.js → pagination-toolbar-JH3Kbffr.451171f7ac33.js} +1 -1
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-CkIzuyx1.js → pagination-toolbar-JH3Kbffr.js} +1 -1
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BjlRPuf4.0f056fa321b6.js → pdf-doc-BcRNpoaW.882f6e967869.js} +1 -1
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BjlRPuf4.js → pdf-doc-BcRNpoaW.js} +1 -1
- codex/static_root/assets/pdf-doc-BcRNpoaW.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.js.gz +0 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js +2 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.br +0 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.gz +0 -0
- codex/static_root/assets/reader-DIPRg2VB.js +2 -0
- codex/static_root/assets/reader-DIPRg2VB.js.br +0 -0
- codex/static_root/assets/reader-DIPRg2VB.js.gz +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css +1 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -0
- codex/static_root/assets/reader-DveZZr9x.css +1 -0
- codex/static_root/assets/reader-DveZZr9x.css.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.css.gz +0 -0
- codex/static_root/assets/{relation-chips-CUSatZET.js → relation-chips-3tTlR5QH.71f18bc92adc.js} +1 -1
- codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.br +0 -0
- codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CUSatZET.a58031ff8141.js → relation-chips-3tTlR5QH.js} +1 -1
- codex/static_root/assets/relation-chips-3tTlR5QH.js.br +0 -0
- codex/static_root/assets/relation-chips-3tTlR5QH.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BSHTAsJ9.js → settings-drawer-BdyFhA_r.151f4c3061ed.js} +2 -2
- codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.br +0 -0
- codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BSHTAsJ9.1d07ce084fc9.js → settings-drawer-BdyFhA_r.js} +2 -2
- codex/static_root/assets/settings-drawer-BdyFhA_r.js.br +0 -0
- codex/static_root/assets/settings-drawer-BdyFhA_r.js.gz +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js +1 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.br +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.gz +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js +1 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js.br +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js.gz +0 -0
- codex/static_root/assets/{stats-tab-BXGisn5N.9b7a71e7fe28.css → stats-tab-DpfGsVX3.1cb66f2649a9.css} +1 -1
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
- codex/static_root/assets/{stats-tab-BXGisn5N.css → stats-tab-DpfGsVX3.css} +1 -1
- codex/static_root/assets/stats-tab-DpfGsVX3.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.css.gz +0 -0
- codex/static_root/assets/{task-tab-NORFAa9p.97e07e366ddd.js → task-tab-C8D0qRP_.d4ab174c69a3.js} +1 -1
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.br +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.gz +0 -0
- codex/static_root/assets/{task-tab-NORFAa9p.js → task-tab-C8D0qRP_.js} +1 -1
- codex/static_root/assets/task-tab-C8D0qRP_.js.br +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.js.gz +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js +1 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.gz +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js +1 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
- codex/static_root/assets/{unauthorized-muDsv-rC.css → unauthorized-BjhIc97q.728511c92cd0.css} +1 -1
- codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.br +0 -0
- codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.gz +0 -0
- codex/static_root/assets/{unauthorized-muDsv-rC.2166ec8b3a32.css → unauthorized-BjhIc97q.css} +1 -1
- codex/static_root/assets/unauthorized-BjhIc97q.css.br +0 -0
- codex/static_root/assets/unauthorized-BjhIc97q.css.gz +0 -0
- codex/static_root/assets/{user-tab-CHbyN1EW.e7206db08680.js → user-tab-BU31Z92y.35613c713faf.js} +1 -1
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.br +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.gz +0 -0
- codex/static_root/assets/{user-tab-CHbyN1EW.js → user-tab-BU31Z92y.js} +1 -1
- codex/static_root/assets/user-tab-BU31Z92y.js.br +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.js.gz +0 -0
- codex/static_root/js/choices-admin.8aa64c911203.json +1 -0
- codex/static_root/js/choices-admin.8aa64c911203.json.br +0 -0
- codex/static_root/js/choices-admin.8aa64c911203.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/manifest.45cb46e19686.json +635 -0
- codex/static_root/manifest.45cb46e19686.json.br +0 -0
- codex/static_root/manifest.45cb46e19686.json.gz +0 -0
- codex/static_root/manifest.json +248 -248
- 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 +10 -4
- codex/views/admin/tasks.py +46 -40
- codex/views/auth.py +15 -4
- codex/views/browser/annotations.py +94 -51
- codex/views/browser/base.py +1 -2
- codex/views/browser/browser.py +52 -21
- codex/views/browser/cover.py +8 -4
- codex/views/browser/filters/annotations.py +28 -6
- codex/views/browser/filters/search/__init__.py +1 -0
- codex/views/browser/filters/search/aliases.py +81 -0
- codex/views/browser/filters/search/field/__init__.py +1 -0
- codex/views/browser/filters/search/field/column.py +53 -0
- codex/views/browser/filters/search/field/expression.py +177 -0
- codex/views/browser/filters/search/field/filter.py +70 -0
- codex/views/browser/filters/search/field/optimize.py +71 -0
- codex/views/browser/filters/search/field/parse.py +187 -0
- codex/views/browser/filters/search/fts.py +38 -0
- codex/views/browser/filters/search/parse.py +220 -0
- codex/views/browser/order_by.py +8 -2
- codex/views/browser/page_in_bounds.py +88 -0
- codex/views/browser/paginate.py +28 -113
- codex/views/const.py +0 -1
- codex/views/opds/v1/feed.py +2 -1
- codex/views/opds/v2/feed.py +2 -1
- codex/views/opds/v2/links.py +1 -1
- codex/views/reader/books.py +1 -1
- codex/views/reader/page.py +3 -3
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/METADATA +18 -10
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/RECORD +321 -372
- codex/_vendor/__init__.py +0 -0
- codex/_vendor/django_haystack-3.2.1.dist-info/AUTHORS +0 -127
- codex/_vendor/django_haystack-3.2.1.dist-info/INSTALLER +0 -1
- codex/_vendor/django_haystack-3.2.1.dist-info/LICENSE +0 -31
- codex/_vendor/django_haystack-3.2.1.dist-info/METADATA +0 -98
- codex/_vendor/django_haystack-3.2.1.dist-info/RECORD +0 -97
- codex/_vendor/django_haystack-3.2.1.dist-info/REQUESTED +0 -0
- codex/_vendor/django_haystack-3.2.1.dist-info/WHEEL +0 -5
- codex/_vendor/django_haystack-3.2.1.dist-info/top_level.txt +0 -1
- codex/_vendor/haystack/__init__.py +0 -80
- codex/_vendor/haystack/admin.py +0 -163
- codex/_vendor/haystack/apps.py +0 -31
- codex/_vendor/haystack/backends/__init__.py +0 -1109
- codex/_vendor/haystack/backends/elasticsearch2_backend.py +0 -390
- codex/_vendor/haystack/backends/elasticsearch5_backend.py +0 -483
- codex/_vendor/haystack/backends/elasticsearch7_backend.py +0 -586
- codex/_vendor/haystack/backends/elasticsearch_backend.py +0 -1141
- codex/_vendor/haystack/backends/simple_backend.py +0 -132
- codex/_vendor/haystack/backends/solr_backend.py +0 -991
- codex/_vendor/haystack/backends/whoosh_backend.py +0 -1104
- codex/_vendor/haystack/constants.py +0 -57
- codex/_vendor/haystack/exceptions.py +0 -57
- codex/_vendor/haystack/fields.py +0 -562
- codex/_vendor/haystack/forms.py +0 -135
- codex/_vendor/haystack/generic_views.py +0 -141
- codex/_vendor/haystack/indexes.py +0 -548
- codex/_vendor/haystack/inputs.py +0 -169
- codex/_vendor/haystack/management/__init__.py +0 -0
- codex/_vendor/haystack/management/commands/__init__.py +0 -0
- codex/_vendor/haystack/management/commands/build_solr_schema.py +0 -187
- codex/_vendor/haystack/management/commands/clear_index.py +0 -67
- codex/_vendor/haystack/management/commands/haystack_info.py +0 -22
- codex/_vendor/haystack/management/commands/rebuild_index.py +0 -65
- codex/_vendor/haystack/management/commands/update_index.py +0 -438
- codex/_vendor/haystack/manager.py +0 -105
- codex/_vendor/haystack/models.py +0 -257
- codex/_vendor/haystack/panels.py +0 -89
- codex/_vendor/haystack/query.py +0 -771
- codex/_vendor/haystack/routers.py +0 -14
- codex/_vendor/haystack/signals.py +0 -88
- codex/_vendor/haystack/templates/panels/haystack.html +0 -33
- codex/_vendor/haystack/templates/search_configuration/schema.xml +0 -1056
- codex/_vendor/haystack/templates/search_configuration/solrconfig.xml +0 -1446
- codex/_vendor/haystack/templatetags/__init__.py +0 -0
- codex/_vendor/haystack/templatetags/highlight.py +0 -131
- codex/_vendor/haystack/templatetags/more_like_this.py +0 -119
- codex/_vendor/haystack/urls.py +0 -5
- codex/_vendor/haystack/utils/__init__.py +0 -84
- codex/_vendor/haystack/utils/app_loading.py +0 -34
- codex/_vendor/haystack/utils/geo.py +0 -71
- codex/_vendor/haystack/utils/highlighting.py +0 -165
- codex/_vendor/haystack/utils/loading.py +0 -374
- codex/_vendor/haystack/utils/log.py +0 -21
- codex/_vendor/haystack/version.py +0 -16
- codex/_vendor/haystack/views.py +0 -253
- codex/integrity.py +0 -492
- codex/librarian/search/merge.py +0 -86
- codex/librarian/search/version.py +0 -63
- codex/search/__init__.py +0 -1
- codex/search/backend.py +0 -524
- codex/search/backend_search.py +0 -229
- codex/search/engine.py +0 -33
- codex/search/indexes.py +0 -79
- codex/search/query.py +0 -67
- codex/search/writing.py +0 -180
- codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.br +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.gz +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.js.br +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.br +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.js.br +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.js.gz +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.br +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.gz +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.js.br +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.gz +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.br +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.gz +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.js.br +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.br +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.js.br +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.js.gz +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.br +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.gz +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.js.br +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.js.gz +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.br +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.gz +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.js.br +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.br +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.js.br +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.js.gz +0 -0
- codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.br +0 -0
- codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.gz +0 -0
- codex/static_root/assets/admin-DTRn8bBs.js.br +0 -0
- codex/static_root/assets/admin-DTRn8bBs.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.gz +0 -0
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js +0 -1
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.br +0 -0
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.gz +0 -0
- codex/static_root/assets/browser-BBpUyGmz.js +0 -1
- codex/static_root/assets/browser-BBpUyGmz.js.br +0 -0
- codex/static_root/assets/browser-BBpUyGmz.js.gz +0 -0
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css +0 -1
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.br +0 -0
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.gz +0 -0
- codex/static_root/assets/browser-Binc3n9H.css +0 -1
- codex/static_root/assets/browser-Binc3n9H.css.br +0 -0
- codex/static_root/assets/browser-Binc3n9H.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css +0 -1
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.css +0 -1
- codex/static_root/assets/change-password-dialog-Cebek1-c.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js +0 -1
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js +0 -1
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.br +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.js.br +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.js.gz +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.br +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.gz +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.js.br +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.js.gz +0 -0
- codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.br +0 -0
- codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.gz +0 -0
- codex/static_root/assets/filter-Bzk7x841.js.br +0 -0
- codex/static_root/assets/filter-Bzk7x841.js.gz +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js +0 -1
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.br +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.gz +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.js +0 -1
- codex/static_root/assets/flag-tab-C2bI5zq3.js.br +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.js.gz +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.br +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.gz +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.js.br +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.js.gz +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.br +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.gz +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.js.br +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.js.gz +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.br +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.gz +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.js.br +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.js.gz +0 -0
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js +0 -6
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.br +0 -0
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.gz +0 -0
- codex/static_root/assets/main-BPIPjzsc.js +0 -6
- codex/static_root/assets/main-BPIPjzsc.js.br +0 -0
- codex/static_root/assets/main-BPIPjzsc.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js +0 -1
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js +0 -1
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.br +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.js.br +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.js.gz +0 -0
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css +0 -1
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.br +0 -0
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.gz +0 -0
- codex/static_root/assets/reader-_stAWL-4.css +0 -1
- codex/static_root/assets/reader-_stAWL-4.css.br +0 -0
- codex/static_root/assets/reader-_stAWL-4.css.gz +0 -0
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js +0 -2
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.br +0 -0
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.gz +0 -0
- codex/static_root/assets/reader-ucxnLi2K.js +0 -2
- codex/static_root/assets/reader-ucxnLi2K.js.br +0 -0
- codex/static_root/assets/reader-ucxnLi2K.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.br +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.js.br +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.br +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.js.br +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.js.gz +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.br +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.gz +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.css.br +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.css.gz +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js +0 -1
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.br +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.gz +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.js +0 -1
- codex/static_root/assets/stats-tab-DKXVB2Cc.js.br +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.js.gz +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.br +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.gz +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.js.br +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.js.gz +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js +0 -1
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.br +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.gz +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.js +0 -1
- codex/static_root/assets/unauthorized-RHaQZRfr.js.br +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.js.gz +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.br +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.gz +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.css.br +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.css.gz +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.br +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.gz +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.js.br +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.js.gz +0 -0
- codex/static_root/js/choices-admin.1a20a5648f20.json +0 -1
- codex/static_root/js/choices-admin.1a20a5648f20.json.br +0 -0
- codex/static_root/js/choices-admin.1a20a5648f20.json.gz +0 -0
- codex/static_root/manifest.0fe31a6cb99f.json +0 -635
- codex/static_root/manifest.0fe31a6cb99f.json.br +0 -0
- codex/static_root/manifest.0fe31a6cb99f.json.gz +0 -0
- codex/templates/search/indexes/codex/comic_text.txt +0 -47
- codex/views/browser/filters/search.py +0 -196
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/LICENSE +0 -0
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/WHEEL +0 -0
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/entry_points.txt +0 -0
codex/integrity.py
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
"""Repair Database Integrity Errors."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
import sqlite3
|
|
5
|
-
from types import MappingProxyType
|
|
6
|
-
|
|
7
|
-
from django.apps import apps
|
|
8
|
-
from django.contrib.auth import get_user_model
|
|
9
|
-
from django.contrib.auth.models import Group
|
|
10
|
-
from django.contrib.sessions.models import Session
|
|
11
|
-
from django.db import DEFAULT_DB_ALIAS, connection, connections
|
|
12
|
-
from django.db.migrations.executor import MigrationExecutor
|
|
13
|
-
from django.db.migrations.recorder import MigrationRecorder
|
|
14
|
-
from django.db.models.functions import Now
|
|
15
|
-
from django.db.models.query import Q
|
|
16
|
-
from django.db.utils import OperationalError
|
|
17
|
-
|
|
18
|
-
from codex.logger.logging import get_logger
|
|
19
|
-
from codex.settings.settings import (
|
|
20
|
-
CODEX_PATH,
|
|
21
|
-
CONFIG_PATH,
|
|
22
|
-
CUSTOM_COVERS_DIR,
|
|
23
|
-
DB_PATH,
|
|
24
|
-
SKIP_INTEGRITY_CHECK,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
NO_MIGRATIONS_TABLE_ERROR = "no such table: django_migrations"
|
|
28
|
-
REPAIR_FLAG_PATH = CONFIG_PATH / "rebuild_db"
|
|
29
|
-
DUMP_LINE_MATCHER = re.compile("TRANSACTION|ROLLBACK|COMMIT")
|
|
30
|
-
REBUILT_DB_PATH = DB_PATH.parent / (DB_PATH.name + ".rebuilt")
|
|
31
|
-
BACKUP_DB_PATH = DB_PATH.parent / (DB_PATH.name + ".bak")
|
|
32
|
-
MIGRATION_0005 = "0005_auto_20200918_0146"
|
|
33
|
-
MIGRATION_0007 = "0007_auto_20211210_1710"
|
|
34
|
-
MIGRATION_0010 = "0010_haystack"
|
|
35
|
-
MIGRATION_0011 = "0010_library_groups_and_metadata_changes"
|
|
36
|
-
MIGRATION_0018 = "0018_rename_userbookmark_bookmark"
|
|
37
|
-
MIGRATION_0025 = "0025_add_story_arc_number"
|
|
38
|
-
MIGRATION_0026 = "0026_comicbox_1"
|
|
39
|
-
MIGRATION_0027 = "0027_import_order_and_covers"
|
|
40
|
-
M2M_NAMES = MappingProxyType(
|
|
41
|
-
{
|
|
42
|
-
"Character": "characters",
|
|
43
|
-
"Contributor": "contributors",
|
|
44
|
-
"Genre": "genres",
|
|
45
|
-
"Location": "locations",
|
|
46
|
-
"SeriesGroup": "series_groups",
|
|
47
|
-
"StoryArcNumber": "story_arc_numbers",
|
|
48
|
-
"Tag": "tags",
|
|
49
|
-
"Team": "teams",
|
|
50
|
-
"Folder": "folders",
|
|
51
|
-
}
|
|
52
|
-
)
|
|
53
|
-
SKIP_M2M_CHECKS = MappingProxyType(
|
|
54
|
-
{"Contributor": MIGRATION_0026, "StoryArcNumber": MIGRATION_0025}
|
|
55
|
-
)
|
|
56
|
-
NULL_SET = frozenset({None})
|
|
57
|
-
HAVE_LIBRARY_FKS = ("FailedImport", "Folder", "Comic", "CustomCover")
|
|
58
|
-
GROUP_HOSTS = MappingProxyType(
|
|
59
|
-
{
|
|
60
|
-
"Imprint": ("Publisher",),
|
|
61
|
-
"Series": ("Publisher", "Imprint"),
|
|
62
|
-
"Volume": ("Publisher", "Imprint", "Series"),
|
|
63
|
-
"Comic": ("Publisher", "Imprint", "Series", "Volume"),
|
|
64
|
-
}
|
|
65
|
-
)
|
|
66
|
-
COMIC_FK_FIELDS = MappingProxyType(
|
|
67
|
-
{
|
|
68
|
-
"AgeRating": "age_rating",
|
|
69
|
-
"Country": "country",
|
|
70
|
-
"Language": "language",
|
|
71
|
-
"OriginalFormat": "original_format",
|
|
72
|
-
"ScanInfo": "scan_info",
|
|
73
|
-
"Tagger": "tagger",
|
|
74
|
-
}
|
|
75
|
-
)
|
|
76
|
-
WATCHED_PATHS = ("Comic", "Folder")
|
|
77
|
-
CONTRIBUTOR_FIELDS = MappingProxyType(
|
|
78
|
-
{"ContributorRole": "role", "ContributorPerson": "person"}
|
|
79
|
-
)
|
|
80
|
-
OPERATIONAL_ERRORS_BEFORE_MIGRATIONS = (
|
|
81
|
-
"no such column: codex_comic.name",
|
|
82
|
-
"no such column: codex_comic.stat",
|
|
83
|
-
"no such column: codex_folder.stat",
|
|
84
|
-
"no such table: codex_comic_folders",
|
|
85
|
-
# "no such table: codex_comic_contributors",
|
|
86
|
-
"'QuerySet' object has no attribute 'stat'",
|
|
87
|
-
)
|
|
88
|
-
DELETE_BAD_COMIC_FOLDER_RELATIONS_SQL = (
|
|
89
|
-
'DELETE FROM "codex_comic_folder" '
|
|
90
|
-
'WHERE (NOT ("codex_comic_folder"."comic_id" '
|
|
91
|
-
'IN (SELECT "codex_comic"."id" FROM "codex_comic")) '
|
|
92
|
-
'OR NOT ("codex_comic_folder"."folder_id" '
|
|
93
|
-
'IN (SELECT "codex_folder"."id" FROM "codex_folder")))'
|
|
94
|
-
)
|
|
95
|
-
MIGRATION_DIR = CODEX_PATH / "migrations"
|
|
96
|
-
CUSTOM_COVER_MODEL_NAMES = MappingProxyType(
|
|
97
|
-
{"publisher": "p", "imprint": "i", "series": "s", "storyarc": "a", "folder": "f"}
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
LOG = get_logger(__name__)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def has_applied_migration(migration_name):
|
|
104
|
-
"""Check if a specific migration has been applied."""
|
|
105
|
-
return MigrationRecorder.Migration.objects.filter(
|
|
106
|
-
app="codex", name=migration_name
|
|
107
|
-
).exists()
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def has_unapplied_migrations():
|
|
111
|
-
"""Check if any migrations are outstanding."""
|
|
112
|
-
try:
|
|
113
|
-
connection = connections[DEFAULT_DB_ALIAS]
|
|
114
|
-
connection.prepare_database()
|
|
115
|
-
executor = MigrationExecutor(connection)
|
|
116
|
-
targets = [
|
|
117
|
-
key for key in executor.loader.graph.leaf_nodes() if key[0] == "codex"
|
|
118
|
-
]
|
|
119
|
-
plan = executor.migration_plan(targets)
|
|
120
|
-
except Exception as exc:
|
|
121
|
-
LOG.warning(f"has_unapplied_migrations(): {exc}")
|
|
122
|
-
return False
|
|
123
|
-
else:
|
|
124
|
-
return bool(plan)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _delete_old_comic_folder_fks():
|
|
128
|
-
if has_applied_migration(MIGRATION_0007):
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
try:
|
|
132
|
-
with connection.cursor() as cursor:
|
|
133
|
-
cursor.execute(DELETE_BAD_COMIC_FOLDER_RELATIONS_SQL)
|
|
134
|
-
LOG.info("Deleted old comic_folder relations with bad integrity.")
|
|
135
|
-
except Exception:
|
|
136
|
-
LOG.exception("Deleting old comic folders")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def _fix_comic_m2m_integrity_errors(apps, comic_model, m2m_model_name, field_name):
|
|
140
|
-
"""Fix Comic ManyToMany integrity errors."""
|
|
141
|
-
"""Delete relations to comics that don't exist and m2ms that don't exist."""
|
|
142
|
-
m2m_model = apps.get_model("codex", m2m_model_name)
|
|
143
|
-
field = getattr(comic_model, field_name)
|
|
144
|
-
through_model = field.through
|
|
145
|
-
link_col = field_name[:-1].replace("_", "") + "_id"
|
|
146
|
-
|
|
147
|
-
m2m_rels_with_bad_comic_ids = through_model.objects.exclude(
|
|
148
|
-
comic_id__in=comic_model.objects.all()
|
|
149
|
-
)
|
|
150
|
-
count_comic, _ = m2m_rels_with_bad_comic_ids.delete()
|
|
151
|
-
m2m_rels_with_bad_m2m_ids = through_model.objects.exclude(
|
|
152
|
-
**{f"{link_col}__in": m2m_model.objects.all()}
|
|
153
|
-
)
|
|
154
|
-
bad_comic_ids = comic_model.objects.filter(
|
|
155
|
-
pk__in=m2m_rels_with_bad_m2m_ids.values_list("comic_id", flat=True)
|
|
156
|
-
).values_list("pk", flat=True)
|
|
157
|
-
count_m2m, _ = m2m_rels_with_bad_m2m_ids.delete()
|
|
158
|
-
count = count_comic + count_m2m
|
|
159
|
-
if count:
|
|
160
|
-
LOG.info(f"Deleted {count} orphan relations to {field_name}")
|
|
161
|
-
return frozenset(bad_comic_ids)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def _fix_comic_fk_integrity_errors(apps, comic_model, fk_model_name, field_name):
|
|
165
|
-
fk_model = apps.get_model("codex", fk_model_name)
|
|
166
|
-
bad_pks = _null_missing_fk(comic_model, fk_model, field_name)
|
|
167
|
-
getattr(comic_model, field_name)
|
|
168
|
-
count = len(bad_pks)
|
|
169
|
-
if count:
|
|
170
|
-
LOG.info(f"Deleted {count} {field_name} links to missing rows.")
|
|
171
|
-
return frozenset(bad_pks)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _mark_comics_with_bad_rels_for_update(comic_model, bad_comic_ids):
|
|
175
|
-
"""Mark affected comics for update."""
|
|
176
|
-
try:
|
|
177
|
-
update_comics = []
|
|
178
|
-
bad_comics = comic_model.objects.filter(pk__in=bad_comic_ids).only("stat")
|
|
179
|
-
|
|
180
|
-
for comic in bad_comics:
|
|
181
|
-
stat_list = comic.stat
|
|
182
|
-
if not stat_list:
|
|
183
|
-
continue
|
|
184
|
-
stat_list[8] = 0.0
|
|
185
|
-
comic.stat = stat_list
|
|
186
|
-
update_comics.append(comic)
|
|
187
|
-
|
|
188
|
-
if not update_comics:
|
|
189
|
-
return
|
|
190
|
-
|
|
191
|
-
count = comic_model.objects.bulk_update(update_comics, fields=["stat"])
|
|
192
|
-
LOG.info(f"Marked {count} comics with bad relations for update by poller.")
|
|
193
|
-
except (OperationalError, AttributeError) as exc:
|
|
194
|
-
ok = False
|
|
195
|
-
for arg in exc.args:
|
|
196
|
-
if arg in OPERATIONAL_ERRORS_BEFORE_MIGRATIONS:
|
|
197
|
-
LOG.debug(
|
|
198
|
-
"Can't mark modified comics for update, "
|
|
199
|
-
"but if this happens right before migration 0007, "
|
|
200
|
-
"they'll be updated anyway."
|
|
201
|
-
)
|
|
202
|
-
ok = True
|
|
203
|
-
if not ok:
|
|
204
|
-
LOG.exception("Mark comics with bad relations for update.")
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def _find_fk_integrity_errors_with_models(
|
|
208
|
-
host_model, fk_model, fk_field_name, log=True
|
|
209
|
-
):
|
|
210
|
-
"""Find foreign key integrity errors with specified models."""
|
|
211
|
-
inner_qs = fk_model.objects.all()
|
|
212
|
-
exclude_filter = {f"{fk_field_name}__in": inner_qs}
|
|
213
|
-
invalid_host_objs = host_model.objects.exclude(**exclude_filter).only(fk_field_name)
|
|
214
|
-
if fk_field_name in ("parent_folder", "role"):
|
|
215
|
-
# Special fields can be null
|
|
216
|
-
# THIS IS VERY IMPORTANT TO AVOID DELETING ALL TOP LEVEL FOLDERS
|
|
217
|
-
not_null_filter = {f"{fk_field_name}__isnull": False}
|
|
218
|
-
invalid_host_objs = invalid_host_objs.filter(**not_null_filter)
|
|
219
|
-
|
|
220
|
-
count = invalid_host_objs.count()
|
|
221
|
-
if count and log:
|
|
222
|
-
LOG.info(f"Found {host_model.__name__}s with bad {fk_field_name}: {count}")
|
|
223
|
-
return invalid_host_objs
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def _find_fk_integrity_errors(apps, host_model_name, fk_model_name, fk_field_name):
|
|
227
|
-
"""Find foreign key integrity errors."""
|
|
228
|
-
host_model = apps.get_model("codex", host_model_name)
|
|
229
|
-
fk_model = apps.get_model("codex", fk_model_name)
|
|
230
|
-
return _find_fk_integrity_errors_with_models(host_model, fk_model, fk_field_name)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _delete_query(query, host_model_name, fk_model_name):
|
|
234
|
-
"""Execute the delete on the query."""
|
|
235
|
-
count, _ = query.delete()
|
|
236
|
-
if count:
|
|
237
|
-
LOG.info(f"Deleted {count} {host_model_name}s in nonextant {fk_model_name}s.")
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _delete_fk_integrity_errors(apps, host_model_name, fk_model_name, fk_field_name):
|
|
241
|
-
"""Delete objects with bad integrity."""
|
|
242
|
-
try:
|
|
243
|
-
if not has_applied_migration(MIGRATION_0026) and (
|
|
244
|
-
host_model_name == "Contributor"
|
|
245
|
-
or fk_model_name == "Contributor"
|
|
246
|
-
or fk_field_name == "contributors"
|
|
247
|
-
):
|
|
248
|
-
LOG.debug(
|
|
249
|
-
"Skipping Contributor integrity check until"
|
|
250
|
-
f"migration {MIGRATION_0026} applied."
|
|
251
|
-
)
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
bad_host_objs = _find_fk_integrity_errors(
|
|
255
|
-
apps, host_model_name, fk_model_name, fk_field_name
|
|
256
|
-
)
|
|
257
|
-
_delete_query(bad_host_objs, host_model_name, fk_model_name)
|
|
258
|
-
except OperationalError as exc:
|
|
259
|
-
ok = False
|
|
260
|
-
for arg in exc.args:
|
|
261
|
-
if arg in OPERATIONAL_ERRORS_BEFORE_MIGRATIONS:
|
|
262
|
-
ok = True
|
|
263
|
-
if not ok:
|
|
264
|
-
LOG.exception("Operational error before migrations")
|
|
265
|
-
except Exception:
|
|
266
|
-
LOG.exception("Delete foreign key integrity errors.")
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
def _null_missing_fk(host_model, fk_model, fk_field_name):
|
|
270
|
-
"""Set missing fks to null."""
|
|
271
|
-
query_missing_fks = _find_fk_integrity_errors_with_models(
|
|
272
|
-
host_model, fk_model, fk_field_name, log=False
|
|
273
|
-
)
|
|
274
|
-
not_null_filter = {f"{fk_field_name}__isnull": False}
|
|
275
|
-
query_missing_fks = query_missing_fks.filter(**not_null_filter)
|
|
276
|
-
count = query_missing_fks.count()
|
|
277
|
-
if count:
|
|
278
|
-
update_dict = {fk_field_name: None, "updated_at": Now()}
|
|
279
|
-
query_missing_fks.update(**update_dict)
|
|
280
|
-
LOG.info(f"Fixed {count} {host_model.__name__}s with missing {fk_field_name}")
|
|
281
|
-
return query_missing_fks.values_list("pk", flat=True)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def _delete_bookmark_integrity_errors(apps):
|
|
285
|
-
"""Fix Bookmarks with non codex model fields."""
|
|
286
|
-
if not has_applied_migration(MIGRATION_0018):
|
|
287
|
-
return
|
|
288
|
-
# Bookmarks that don't reference a valid comic are useless.
|
|
289
|
-
_delete_fk_integrity_errors(apps, "Bookmark", "Comic", "comic")
|
|
290
|
-
|
|
291
|
-
# Bookmarks that aren't linked to a valid session or a user are orphans
|
|
292
|
-
bm_model = apps.get_model("codex", "Bookmark")
|
|
293
|
-
_null_missing_fk(bm_model, Session, "session")
|
|
294
|
-
_null_missing_fk(bm_model, get_user_model(), "user")
|
|
295
|
-
orphan_bms = bm_model.objects.filter(session=None, user=None)
|
|
296
|
-
_delete_query(orphan_bms, "Bookmark", "session or user")
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def _repair_library_groups(apps):
|
|
300
|
-
"""Remove non-extant groups from libraries."""
|
|
301
|
-
if not has_applied_migration(MIGRATION_0011):
|
|
302
|
-
return
|
|
303
|
-
library_model = apps.get_model("codex", "library")
|
|
304
|
-
through_model = library_model.groups.through
|
|
305
|
-
valid_groups = Group.objects.all()
|
|
306
|
-
bad_relations = through_model.objects.exclude(group_id__in=valid_groups)
|
|
307
|
-
bad_relations.delete()
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def _delete_extra_custom_cover_libraries(apps):
|
|
311
|
-
if not has_applied_migration(MIGRATION_0027):
|
|
312
|
-
return
|
|
313
|
-
library_model = apps.get_model("codex", "library")
|
|
314
|
-
custom_cover_libraries = library_model.objects.filter(covers_only=True)
|
|
315
|
-
if custom_cover_libraries.count() <= 1:
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
# Attempt to remove the bad ones, probably futile
|
|
319
|
-
bad_lib_pks = []
|
|
320
|
-
for library in custom_cover_libraries:
|
|
321
|
-
if library.path != CUSTOM_COVERS_DIR:
|
|
322
|
-
bad_lib_pks.append(library.pk)
|
|
323
|
-
delete_libs = library_model.object.filter(pk__in=bad_lib_pks)
|
|
324
|
-
count = delete_libs.count()
|
|
325
|
-
if count:
|
|
326
|
-
delete_libs.delete()
|
|
327
|
-
LOG.warn(
|
|
328
|
-
f"Removed {count} duplicate custom cover libraries pointing to unused custom cover dirs."
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
custom_cover_libraries = library_model.object.filter(covers_only=True)
|
|
332
|
-
count = custom_cover_libraries.count()
|
|
333
|
-
if count <= 1:
|
|
334
|
-
return
|
|
335
|
-
custom_cover_libraries.delete()
|
|
336
|
-
LOG.warn(
|
|
337
|
-
f"Removed all ({count}) custom cover libraries, Unable to determine valid one. Will recreate upon startup."
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
def _repair_groups_with_custom_covers(apps):
|
|
342
|
-
if not has_applied_migration(MIGRATION_0027):
|
|
343
|
-
return
|
|
344
|
-
custom_cover_model = apps.get_model("codex", "customcover")
|
|
345
|
-
for model_name, group_letter in CUSTOM_COVER_MODEL_NAMES.items():
|
|
346
|
-
valid_covers = custom_cover_model.objects.filter(group=group_letter)
|
|
347
|
-
group_model = apps.get_model("codex", model_name)
|
|
348
|
-
objs = group_model.objects.exclude(
|
|
349
|
-
Q(custom_cover__isnull=True) | Q(custom_cover__in=valid_covers)
|
|
350
|
-
)
|
|
351
|
-
update_groups = []
|
|
352
|
-
for group in objs:
|
|
353
|
-
group.custom_cover = None
|
|
354
|
-
group.updated_at = Now()
|
|
355
|
-
update_groups.append(group)
|
|
356
|
-
group_model.objects.bulk_update(update_groups, ["custom_cover"])
|
|
357
|
-
count = len(update_groups)
|
|
358
|
-
if count:
|
|
359
|
-
LOG.warn(f"Removed illegal custom cover links from {count} {model_name}s")
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
def _delete_errors():
|
|
363
|
-
"""DELETE things we can't fix."""
|
|
364
|
-
_delete_extra_custom_cover_libraries(apps)
|
|
365
|
-
|
|
366
|
-
for host_model_name in HAVE_LIBRARY_FKS:
|
|
367
|
-
if host_model_name == "CustomCover" and not has_applied_migration(
|
|
368
|
-
MIGRATION_0027
|
|
369
|
-
):
|
|
370
|
-
continue
|
|
371
|
-
_delete_fk_integrity_errors(apps, host_model_name, "Library", "library")
|
|
372
|
-
|
|
373
|
-
for host_model_name in WATCHED_PATHS:
|
|
374
|
-
_delete_fk_integrity_errors(apps, host_model_name, "Folder", "parent_folder")
|
|
375
|
-
|
|
376
|
-
for host_model_name, groups in GROUP_HOSTS.items():
|
|
377
|
-
for group_model_name in groups:
|
|
378
|
-
group_field_name = group_model_name.lower()
|
|
379
|
-
_delete_fk_integrity_errors(
|
|
380
|
-
apps, host_model_name, group_model_name, group_field_name
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
if has_applied_migration(MIGRATION_0026):
|
|
384
|
-
for fk_model_name in COMIC_FK_FIELDS:
|
|
385
|
-
_delete_fk_integrity_errors(apps, fk_model_name, "Comic", "comic")
|
|
386
|
-
|
|
387
|
-
for fk_model_name, fk_field_name in CONTRIBUTOR_FIELDS.items():
|
|
388
|
-
_delete_fk_integrity_errors(apps, "Contributor", fk_model_name, fk_field_name)
|
|
389
|
-
|
|
390
|
-
_delete_bookmark_integrity_errors(apps)
|
|
391
|
-
|
|
392
|
-
_delete_old_comic_folder_fks()
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
def _check_field_for_migration(model_name):
|
|
396
|
-
"""Skip some models before migrations."""
|
|
397
|
-
for check_model_name, migration in SKIP_M2M_CHECKS.items():
|
|
398
|
-
if model_name == check_model_name and not has_applied_migration(migration):
|
|
399
|
-
LOG.debug(
|
|
400
|
-
f"Skipping {check_model_name} integrity check until"
|
|
401
|
-
f" migration {migration} applied."
|
|
402
|
-
)
|
|
403
|
-
return True
|
|
404
|
-
return False
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def _repair_integrity():
|
|
408
|
-
"""REPAIR the objects that are left."""
|
|
409
|
-
comic_model = apps.get_model("codex", "Comic")
|
|
410
|
-
bad_comic_ids = set()
|
|
411
|
-
for m2m_model_name, field_name in M2M_NAMES.items():
|
|
412
|
-
if _check_field_for_migration(m2m_model_name):
|
|
413
|
-
continue
|
|
414
|
-
try:
|
|
415
|
-
bad_comic_ids |= _fix_comic_m2m_integrity_errors(
|
|
416
|
-
apps, comic_model, m2m_model_name, field_name
|
|
417
|
-
)
|
|
418
|
-
except OperationalError as exc:
|
|
419
|
-
ok = False
|
|
420
|
-
for arg in exc.args:
|
|
421
|
-
if arg in OPERATIONAL_ERRORS_BEFORE_MIGRATIONS:
|
|
422
|
-
ok = True
|
|
423
|
-
if ok:
|
|
424
|
-
LOG.info(
|
|
425
|
-
"Couldn't look for comics with folder integrity problems before "
|
|
426
|
-
"migration 0007. We'll get them on the next restart."
|
|
427
|
-
)
|
|
428
|
-
else:
|
|
429
|
-
LOG.exception("Occurred after migration 0007")
|
|
430
|
-
|
|
431
|
-
if has_applied_migration(MIGRATION_0026):
|
|
432
|
-
for fk_model_name, field_name in COMIC_FK_FIELDS.items():
|
|
433
|
-
try:
|
|
434
|
-
bad_comic_ids |= _fix_comic_fk_integrity_errors(
|
|
435
|
-
apps, comic_model, fk_model_name, field_name
|
|
436
|
-
)
|
|
437
|
-
except Exception:
|
|
438
|
-
LOG.exception(f"Fix fk integrity error {fk_model_name}")
|
|
439
|
-
|
|
440
|
-
_mark_comics_with_bad_rels_for_update(comic_model, bad_comic_ids)
|
|
441
|
-
|
|
442
|
-
_repair_library_groups(apps)
|
|
443
|
-
_repair_groups_with_custom_covers(apps)
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
def repair_db():
|
|
447
|
-
"""Fix the db but trap errors if it goes wrong."""
|
|
448
|
-
try:
|
|
449
|
-
if SKIP_INTEGRITY_CHECK:
|
|
450
|
-
LOG.info("Skipping integrity check")
|
|
451
|
-
return
|
|
452
|
-
|
|
453
|
-
if not has_applied_migration(MIGRATION_0005):
|
|
454
|
-
LOG.info(
|
|
455
|
-
"Not running integrity checks until migration"
|
|
456
|
-
f"{MIGRATION_0005} has been applied."
|
|
457
|
-
)
|
|
458
|
-
return
|
|
459
|
-
|
|
460
|
-
LOG.debug("Reparing database integrity...")
|
|
461
|
-
_delete_errors()
|
|
462
|
-
_repair_integrity()
|
|
463
|
-
LOG.info("Database integrity confirmed.")
|
|
464
|
-
except OperationalError as exc:
|
|
465
|
-
if NO_MIGRATIONS_TABLE_ERROR in exc.args:
|
|
466
|
-
LOG.debug("Database not constructed yet, skipping integrity check.")
|
|
467
|
-
else:
|
|
468
|
-
raise
|
|
469
|
-
except Exception:
|
|
470
|
-
LOG.exception("Repair DB")
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
def rebuild_db():
|
|
474
|
-
"""Dump and rebuild the database."""
|
|
475
|
-
# Drastic
|
|
476
|
-
if not REPAIR_FLAG_PATH.exists():
|
|
477
|
-
return
|
|
478
|
-
|
|
479
|
-
LOG.warning("REBUILDING DATABASE!!")
|
|
480
|
-
with sqlite3.connect(REBUILT_DB_PATH) as new_db_conn:
|
|
481
|
-
new_db_cur = new_db_conn.cursor()
|
|
482
|
-
with sqlite3.connect(DB_PATH) as old_db_conn:
|
|
483
|
-
for line in old_db_conn.iterdump():
|
|
484
|
-
if DUMP_LINE_MATCHER.search(line):
|
|
485
|
-
continue
|
|
486
|
-
new_db_cur.execute(line)
|
|
487
|
-
|
|
488
|
-
DB_PATH.rename(BACKUP_DB_PATH)
|
|
489
|
-
LOG.info("Backed up old db to %s", BACKUP_DB_PATH)
|
|
490
|
-
REBUILT_DB_PATH.replace(DB_PATH)
|
|
491
|
-
REPAIR_FLAG_PATH.unlink(missing_ok=True)
|
|
492
|
-
LOG.info("Rebuilt database.")
|
codex/librarian/search/merge.py
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""Search Index Merging for Read Optimization."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from time import time
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
|
|
7
|
-
from humanize import naturaldelta, naturalsize
|
|
8
|
-
|
|
9
|
-
from codex.librarian.search.status import SearchIndexStatusTypes
|
|
10
|
-
from codex.librarian.search.version import VersionMixin
|
|
11
|
-
from codex.settings.settings import SEARCH_INDEX_PATH
|
|
12
|
-
from codex.status import Status
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from codex.search.backend import CodexSearchBackend
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class MergeMixin(VersionMixin):
|
|
19
|
-
"""Search Index Merge Methods."""
|
|
20
|
-
|
|
21
|
-
def _is_index_optimized(self, num_segments):
|
|
22
|
-
"""Is the index already in one segment."""
|
|
23
|
-
if num_segments <= 1:
|
|
24
|
-
self.log.info("Search index already optimized.")
|
|
25
|
-
return True
|
|
26
|
-
return False
|
|
27
|
-
|
|
28
|
-
@staticmethod
|
|
29
|
-
def _get_segments_and_len():
|
|
30
|
-
segments = SEARCH_INDEX_PATH.glob("*.seg")
|
|
31
|
-
num_segments = len(tuple(segments))
|
|
32
|
-
return segments, num_segments
|
|
33
|
-
|
|
34
|
-
@staticmethod
|
|
35
|
-
def _get_segments_size(segments):
|
|
36
|
-
size = 0
|
|
37
|
-
for segment in segments:
|
|
38
|
-
size += Path(segment).stat().st_size
|
|
39
|
-
return size
|
|
40
|
-
|
|
41
|
-
def _merge_search_index(self, optimize, status, name):
|
|
42
|
-
"""Optimize search index."""
|
|
43
|
-
self.status_controller.start(status)
|
|
44
|
-
start = time()
|
|
45
|
-
|
|
46
|
-
segments, old_num_segments = self._get_segments_and_len()
|
|
47
|
-
if self._is_index_optimized(old_num_segments):
|
|
48
|
-
return
|
|
49
|
-
self.status_controller.start(status)
|
|
50
|
-
old_size = self._get_segments_size(segments)
|
|
51
|
-
# Optimize
|
|
52
|
-
self.log.info(
|
|
53
|
-
f"Search index found in {old_num_segments} segments," f" merging {name}..."
|
|
54
|
-
)
|
|
55
|
-
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
56
|
-
if optimize:
|
|
57
|
-
backend.optimize()
|
|
58
|
-
else:
|
|
59
|
-
backend.merge_small()
|
|
60
|
-
|
|
61
|
-
# Finish
|
|
62
|
-
segments, new_num_segments = self._get_segments_and_len()
|
|
63
|
-
new_size = self._get_segments_size(segments)
|
|
64
|
-
saved = naturalsize(old_size - new_size)
|
|
65
|
-
num_segments_diff = old_num_segments - new_num_segments
|
|
66
|
-
elapsed_time = time() - start
|
|
67
|
-
elapsed = naturaldelta(elapsed_time)
|
|
68
|
-
if num_segments_diff:
|
|
69
|
-
self.log.info(
|
|
70
|
-
f"Merged {num_segments_diff} search index segments in {elapsed}."
|
|
71
|
-
f"Saved {saved}."
|
|
72
|
-
)
|
|
73
|
-
else:
|
|
74
|
-
self.log.info("No small search index segments found.")
|
|
75
|
-
|
|
76
|
-
def merge_search_index(self, optimize=False):
|
|
77
|
-
"""Optimize search index, trapping exceptions."""
|
|
78
|
-
verb = "All" if optimize else "Small"
|
|
79
|
-
name = f"Merge {verb} Segments"
|
|
80
|
-
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
|
|
81
|
-
try:
|
|
82
|
-
self._merge_search_index(optimize, status, name)
|
|
83
|
-
except Exception:
|
|
84
|
-
self.log.exception("Search index merge.")
|
|
85
|
-
finally:
|
|
86
|
-
self.status_controller.finish(status)
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""Search Engine to database matching."""
|
|
2
|
-
|
|
3
|
-
from uuid import uuid4
|
|
4
|
-
|
|
5
|
-
from codex.librarian.search.tasks import SearchIndexUpdateTask
|
|
6
|
-
from codex.models import Timestamp
|
|
7
|
-
from codex.search.engine import CodexSearchEngine
|
|
8
|
-
from codex.settings.settings import SEARCH_INDEX_PATH, SEARCH_INDEX_UUID_PATH
|
|
9
|
-
from codex.threads import QueuedThread
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class VersionMixin(QueuedThread):
|
|
13
|
-
"""Search Engine to database matching methods."""
|
|
14
|
-
|
|
15
|
-
def __init__(self, abort_event, *args, **kwargs):
|
|
16
|
-
"""Initialize search engine."""
|
|
17
|
-
self.abort_event = abort_event
|
|
18
|
-
super().__init__(*args, **kwargs)
|
|
19
|
-
queue_kwargs = {
|
|
20
|
-
"log_queue": self.log_queue,
|
|
21
|
-
"librarian_queue": self.librarian_queue,
|
|
22
|
-
}
|
|
23
|
-
self.engine = CodexSearchEngine(queue_kwargs=queue_kwargs)
|
|
24
|
-
|
|
25
|
-
def set_search_index_version(self):
|
|
26
|
-
"""Set the codex db to search index matching id."""
|
|
27
|
-
version = str(uuid4())
|
|
28
|
-
try:
|
|
29
|
-
lv = Timestamp.objects.get(
|
|
30
|
-
key=Timestamp.TimestampChoices.SEARCH_INDEX_UUID.value
|
|
31
|
-
)
|
|
32
|
-
lv.version = version
|
|
33
|
-
lv.save()
|
|
34
|
-
SEARCH_INDEX_PATH.mkdir(parents=True, exist_ok=True)
|
|
35
|
-
with SEARCH_INDEX_UUID_PATH.open("w") as uuid_file:
|
|
36
|
-
uuid_file.write(version)
|
|
37
|
-
except Exception:
|
|
38
|
-
self.log.exception("Setting search index to db synchronization token")
|
|
39
|
-
|
|
40
|
-
def is_search_index_uuid_match(self):
|
|
41
|
-
"""Is this search index for this database."""
|
|
42
|
-
result = False
|
|
43
|
-
try:
|
|
44
|
-
with SEARCH_INDEX_UUID_PATH.open("r") as uuid_file:
|
|
45
|
-
version = uuid_file.read()
|
|
46
|
-
result = Timestamp.objects.filter(
|
|
47
|
-
key=Timestamp.TimestampChoices.SEARCH_INDEX_UUID.value, version=version
|
|
48
|
-
).exists()
|
|
49
|
-
except (FileNotFoundError, Timestamp.DoesNotExist):
|
|
50
|
-
pass
|
|
51
|
-
except Exception:
|
|
52
|
-
self.log.exception("Does search index match database uuid")
|
|
53
|
-
if not result:
|
|
54
|
-
self.log.warning("Database does not match search index.")
|
|
55
|
-
return result
|
|
56
|
-
|
|
57
|
-
def rebuild_search_index_if_db_changed(self):
|
|
58
|
-
"""Rebuild the search index if the db changed."""
|
|
59
|
-
if not self.is_search_index_uuid_match():
|
|
60
|
-
task = SearchIndexUpdateTask(True)
|
|
61
|
-
self.librarian_queue.put(task)
|
|
62
|
-
else:
|
|
63
|
-
self.log.info("Database matches search index.")
|
codex/search/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Haystack Search Customizations."""
|