codex 1.7.0__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 +17 -32
- codex/exceptions.py +3 -2
- codex/librarian/covers/coverd.py +4 -3
- codex/librarian/covers/create.py +7 -8
- codex/librarian/covers/path.py +2 -1
- codex/librarian/covers/purge.py +2 -2
- codex/librarian/janitor/integrity.py +10 -12
- codex/librarian/janitor/tasks.py +1 -1
- codex/librarian/librariand.py +0 -5
- codex/librarian/watchdog/emitter.py +19 -21
- codex/librarian/watchdog/event_batcherd.py +1 -0
- codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +0 -4
- codex/models/admin.py +5 -0
- codex/models/functions.py +5 -6
- codex/models/query.py +0 -6
- codex/run.py +9 -11
- codex/serializers/admin/tasks.py +2 -2
- codex/serializers/browser/mixins.py +14 -18
- codex/serializers/browser/settings.py +4 -4
- codex/serializers/choices.py +196 -0
- codex/serializers/fields.py +5 -9
- codex/startup.py +4 -6
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js +1 -0
- 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-CWiqcZ7a.js +1 -0
- 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-Dk3iB62X.bc66d5351baa.js +1 -0
- 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-Dk3iB62X.js +1 -0
- 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-D1_KU27A.b1d30587a973.js +1 -0
- 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-D1_KU27A.js +1 -0
- 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-38np3TSA.5a0cb7a9e56d.css +1 -0
- codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
- codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
- codex/static_root/assets/VDialog-38np3TSA.css +1 -0
- codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
- codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js +1 -0
- 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-GbxvFMMN.js +1 -0
- 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-Cywv_bEj.a762e63858e5.js +1 -0
- 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-Cywv_bEj.js +1 -0
- 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-C0etmxpZ.0eb3009a6300.js +1 -0
- 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-C0etmxpZ.js +1 -0
- 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-Ca2bIOso.6613989e4c45.css +1 -0
- codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css +1 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js +1 -0
- 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-txrnRiNJ.js +1 -0
- 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-CY6RmcvW.7e8824224f6c.js +1 -0
- 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-CY6RmcvW.js +1 -0
- 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-Dk_1upHt.2c836070ec75.css +1 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.br +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css +1 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css.br +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js +1 -0
- 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-DtEQMwY1.js +1 -0
- 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-2af995xo.6c5bf36631b2.js +1 -0
- 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-2af995xo.js +1 -0
- 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-Crc3gdNh.39995ccf4d52.css +1 -0
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css +1 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js +1 -0
- 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-G6CMj7yO.js +1 -0
- 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-CzoGd6Ul.721e5da11ad4.js +1 -0
- 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-CzoGd6Ul.js +1 -0
- 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-DJ7pIXDQ.css → admin-CWEjaFr5.7d4523387384.css} +1 -1
- codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.br +0 -0
- codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
- codex/static_root/assets/{admin-DJ7pIXDQ.e2a77af73ef2.css → admin-CWEjaFr5.css} +1 -1
- codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
- codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js +1 -0
- 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-CsPAIQeG.js +1 -0
- 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-BAT7GA64.73a064628c19.js +30 -0
- 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-BAT7GA64.js +30 -0
- 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/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +1 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +1 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.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-BmhedLCx.7c2103d319d4.css +1 -0
- codex/static_root/assets/confirm-dialog-BmhedLCx.css +1 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js +1 -0
- 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-CbW3hlOp.js +1 -0
- 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-uSqKZU37.b157b0743db5.js +1 -0
- 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-uSqKZU37.js +1 -0
- 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-CBl5KEsB.845a81573c2e.js +1 -0
- 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-CBl5KEsB.js +1 -0
- 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/flag-tab-DBqpLKi7.css +1 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +1 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js +1 -0
- 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-BOW9kigw.js +1 -0
- 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/group-tab-CBKuS3Iv.7e4746634782.css +1 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.br +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.gz +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css +1 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css.br +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css.gz +0 -0
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js +1 -0
- 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-C6yuLApo.js +1 -0
- 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-Wew8YOLv.0c1780a6b68e.css → library-tab-Bh7ErAk7.7c44200ee727.css} +1 -1
- codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +2 -0
- codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
- codex/static_root/assets/{library-tab-Wew8YOLv.css → library-tab-Bh7ErAk7.css} +1 -1
- codex/static_root/assets/library-tab-Bh7ErAk7.css.br +2 -0
- codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js +1 -0
- 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-D3WOtpE0.js +1 -0
- 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-JH3Kbffr.451171f7ac33.js +1 -0
- 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-JH3Kbffr.js +1 -0
- 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-ctqZIwOP.7e7b2c815ea4.js → pdf-doc-BcRNpoaW.882f6e967869.js} +3 -3
- 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-ctqZIwOP.js → pdf-doc-BcRNpoaW.js} +3 -3
- 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-nD70BeEz.06e85e73c391.css → reader-DveZZr9x.7b21b9f8f628.css} +1 -1
- 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-nD70BeEz.css → reader-DveZZr9x.css} +1 -1
- 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-3tTlR5QH.71f18bc92adc.js +1 -0
- 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-3tTlR5QH.js +1 -0
- 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-BdyFhA_r.151f4c3061ed.js +2 -0
- 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-BdyFhA_r.js +2 -0
- 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-BAYTqQNg.5f715428c394.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-BAYTqQNg.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-1XiWn9xP.06cb8e1b794d.css +1 -0
- codex/static_root/assets/{task-tab--cXi48kg.5dfd6890a1c2.css.br → task-tab-1XiWn9xP.06cb8e1b794d.css.br} +1 -1
- codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css.gz +0 -0
- codex/static_root/assets/task-tab-1XiWn9xP.css +1 -0
- codex/static_root/assets/{task-tab--cXi48kg.css.br → task-tab-1XiWn9xP.css.br} +1 -1
- codex/static_root/assets/task-tab-1XiWn9xP.css.gz +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js +1 -0
- 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-C8D0qRP_.js +1 -0
- 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/user-tab-BU31Z92y.35613c713faf.js +1 -0
- 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-BU31Z92y.js +1 -0
- 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 -0
- 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.2f6b0359e1ef.json +1 -0
- codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
- codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
- codex/static_root/js/choices.json +1 -0
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.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 +300 -377
- 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 +6 -5
- codex/threads.py +1 -1
- codex/urls/api/auth.py +1 -1
- codex/urls/api/browser.py +1 -1
- codex/views/admin/library.py +13 -18
- codex/views/admin/stats.py +16 -23
- codex/views/admin/tasks.py +2 -1
- codex/views/auth.py +77 -42
- codex/views/bookmark.py +153 -16
- codex/views/browser/annotations.py +339 -0
- codex/views/browser/base.py +139 -0
- codex/views/browser/breadcrumbs.py +11 -90
- codex/views/browser/browser.py +110 -44
- codex/views/browser/choices.py +18 -15
- codex/views/browser/cover.py +20 -10
- codex/views/browser/filters/annotations.py +125 -0
- codex/views/browser/filters/bookmark.py +19 -18
- codex/views/browser/filters/field.py +3 -8
- codex/views/browser/filters/group.py +6 -9
- codex/views/browser/filters/search/fts.py +19 -5
- codex/views/browser/filters/search/parse.py +24 -54
- codex/views/browser/metadata.py +332 -0
- codex/views/browser/mtime.py +10 -2
- codex/views/browser/order_by.py +22 -22
- codex/views/browser/page_in_bounds.py +2 -2
- codex/views/browser/paginate.py +15 -9
- codex/views/browser/title.py +6 -1
- codex/views/browser/validate.py +33 -68
- codex/views/download.py +1 -1
- codex/views/mixins.py +15 -27
- codex/views/opds/const.py +3 -2
- codex/views/opds/urls.py +2 -2
- codex/views/opds/util.py +3 -2
- codex/views/opds/v1/entry/links.py +1 -3
- codex/views/opds/v1/facets.py +12 -65
- codex/views/opds/v1/feed.py +64 -5
- codex/views/opds/v1/links.py +4 -5
- codex/views/opds/v2/feed.py +28 -1
- codex/views/opds/v2/links.py +1 -26
- codex/views/opds/v2/publications.py +6 -5
- codex/views/opds/v2/top_links.py +6 -3
- codex/views/public.py +2 -2
- codex/views/reader/arcs.py +3 -0
- codex/views/reader/books.py +41 -57
- codex/views/reader/{params.py → init.py} +31 -37
- codex/views/reader/page.py +4 -10
- codex/views/reader/reader.py +2 -1
- codex/views/session.py +54 -43
- codex/views/version.py +1 -1
- {codex-1.7.0.dist-info → codex-1.7.0a0.dist-info}/METADATA +5 -2
- {codex-1.7.0.dist-info → codex-1.7.0a0.dist-info}/RECORD +385 -419
- codex/choices.py +0 -419
- codex/choices_to_json.py +0 -122
- codex/librarian/bookmark/__init__.py +0 -1
- codex/librarian/bookmark/bookmarkd.py +0 -38
- codex/librarian/bookmark/tasks.py +0 -19
- codex/librarian/bookmark/update.py +0 -154
- codex/static_root/assets/VCheckbox-D_CWEmqR.84f2b49311e8.js +0 -1
- codex/static_root/assets/VCheckbox-D_CWEmqR.84f2b49311e8.js.br +0 -0
- codex/static_root/assets/VCheckbox-D_CWEmqR.84f2b49311e8.js.gz +0 -0
- codex/static_root/assets/VCheckbox-D_CWEmqR.js +0 -1
- codex/static_root/assets/VCheckbox-D_CWEmqR.js.br +0 -0
- codex/static_root/assets/VCheckbox-D_CWEmqR.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B1BhcFvi.0eaf3c516474.js +0 -1
- codex/static_root/assets/VCheckboxBtn-B1BhcFvi.0eaf3c516474.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B1BhcFvi.0eaf3c516474.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B1BhcFvi.js +0 -1
- codex/static_root/assets/VCheckboxBtn-B1BhcFvi.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B1BhcFvi.js.gz +0 -0
- codex/static_root/assets/VCombobox-CZAW6_3Q.0e2401e819a5.js +0 -1
- codex/static_root/assets/VCombobox-CZAW6_3Q.0e2401e819a5.js.br +0 -0
- codex/static_root/assets/VCombobox-CZAW6_3Q.0e2401e819a5.js.gz +0 -0
- codex/static_root/assets/VCombobox-CZAW6_3Q.js +0 -1
- codex/static_root/assets/VCombobox-CZAW6_3Q.js.br +0 -0
- codex/static_root/assets/VCombobox-CZAW6_3Q.js.gz +0 -0
- codex/static_root/assets/VDialog-CSHgHIlG.a0bcd336cb7b.js +0 -1
- codex/static_root/assets/VDialog-CSHgHIlG.a0bcd336cb7b.js.br +0 -0
- codex/static_root/assets/VDialog-CSHgHIlG.a0bcd336cb7b.js.gz +0 -0
- codex/static_root/assets/VDialog-CSHgHIlG.js +0 -1
- codex/static_root/assets/VDialog-CSHgHIlG.js.br +0 -0
- codex/static_root/assets/VDialog-CSHgHIlG.js.gz +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css +0 -1
- codex/static_root/assets/VDialog-CSvrKKb2.css.br +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css.gz +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css +0 -1
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.br +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CG6MpdMC.e96ae8afad97.js +0 -1
- codex/static_root/assets/VExpansionPanels-CG6MpdMC.e96ae8afad97.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CG6MpdMC.e96ae8afad97.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CG6MpdMC.js +0 -1
- codex/static_root/assets/VExpansionPanels-CG6MpdMC.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CG6MpdMC.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CAgQxkiU.f26ecb0c3e34.js +0 -1
- codex/static_root/assets/VRadioGroup-CAgQxkiU.f26ecb0c3e34.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CAgQxkiU.f26ecb0c3e34.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CAgQxkiU.js +0 -1
- codex/static_root/assets/VRadioGroup-CAgQxkiU.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CAgQxkiU.js.gz +0 -0
- codex/static_root/assets/VSelect-C1gez8rl.9b11d2f00faf.js +0 -1
- codex/static_root/assets/VSelect-C1gez8rl.9b11d2f00faf.js.br +0 -0
- codex/static_root/assets/VSelect-C1gez8rl.9b11d2f00faf.js.gz +0 -0
- codex/static_root/assets/VSelect-C1gez8rl.js +0 -1
- codex/static_root/assets/VSelect-C1gez8rl.js.br +0 -0
- codex/static_root/assets/VSelect-C1gez8rl.js.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +0 -1
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css +0 -1
- codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-DBqDlKjK.160beaa1b56c.js +0 -1
- codex/static_root/assets/VSelectionControl-DBqDlKjK.160beaa1b56c.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DBqDlKjK.160beaa1b56c.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DBqDlKjK.js +0 -1
- codex/static_root/assets/VSelectionControl-DBqDlKjK.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DBqDlKjK.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +0 -1
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css +0 -1
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-SqR_NAL1.40ba1d838a6e.js +0 -1
- codex/static_root/assets/VSlideGroup-SqR_NAL1.40ba1d838a6e.js.br +0 -0
- codex/static_root/assets/VSlideGroup-SqR_NAL1.40ba1d838a6e.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-SqR_NAL1.js +0 -1
- codex/static_root/assets/VSlideGroup-SqR_NAL1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-SqR_NAL1.js.gz +0 -0
- codex/static_root/assets/VTable-CYC-wMpk.8f15d66044da.js +0 -1
- codex/static_root/assets/VTable-CYC-wMpk.8f15d66044da.js.br +0 -0
- codex/static_root/assets/VTable-CYC-wMpk.8f15d66044da.js.gz +0 -0
- codex/static_root/assets/VTable-CYC-wMpk.js +0 -1
- codex/static_root/assets/VTable-CYC-wMpk.js.br +0 -0
- codex/static_root/assets/VTable-CYC-wMpk.js.gz +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css +0 -1
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.br +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.gz +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css +0 -1
- codex/static_root/assets/VTextField-BnCEPZIq.css.br +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css.gz +0 -0
- codex/static_root/assets/VTextField-DMRKRbbT.911ac84d8c9d.js +0 -1
- codex/static_root/assets/VTextField-DMRKRbbT.911ac84d8c9d.js.br +0 -0
- codex/static_root/assets/VTextField-DMRKRbbT.911ac84d8c9d.js.gz +0 -0
- codex/static_root/assets/VTextField-DMRKRbbT.js +0 -1
- codex/static_root/assets/VTextField-DMRKRbbT.js.br +0 -0
- codex/static_root/assets/VTextField-DMRKRbbT.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D0P2xNJl.6a10e3f3a153.js +0 -1
- codex/static_root/assets/VWindowItem-D0P2xNJl.6a10e3f3a153.js.br +0 -0
- codex/static_root/assets/VWindowItem-D0P2xNJl.6a10e3f3a153.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D0P2xNJl.js +0 -1
- codex/static_root/assets/VWindowItem-D0P2xNJl.js.br +0 -0
- codex/static_root/assets/VWindowItem-D0P2xNJl.js.gz +0 -0
- codex/static_root/assets/admin-CD90VMen.4c2b6f2a5111.js +0 -1
- codex/static_root/assets/admin-CD90VMen.4c2b6f2a5111.js.br +0 -0
- codex/static_root/assets/admin-CD90VMen.4c2b6f2a5111.js.gz +0 -0
- codex/static_root/assets/admin-CD90VMen.js +0 -1
- codex/static_root/assets/admin-CD90VMen.js.br +0 -0
- codex/static_root/assets/admin-CD90VMen.js.gz +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css +0 -1
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.br +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.gz +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css +0 -1
- codex/static_root/assets/admin-menu-B9bzD6hV.css.br +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css.gz +0 -0
- codex/static_root/assets/admin-menu-BpEPGKr_.c9f9178c5a82.js +0 -1
- codex/static_root/assets/admin-menu-BpEPGKr_.c9f9178c5a82.js.br +0 -0
- codex/static_root/assets/admin-menu-BpEPGKr_.c9f9178c5a82.js.gz +0 -0
- codex/static_root/assets/admin-menu-BpEPGKr_.js +0 -1
- codex/static_root/assets/admin-menu-BpEPGKr_.js.br +0 -0
- codex/static_root/assets/admin-menu-BpEPGKr_.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DDF4rY07.4f1158b650ab.js +0 -1
- codex/static_root/assets/admin-settings-button-progress-DDF4rY07.4f1158b650ab.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-DDF4rY07.4f1158b650ab.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DDF4rY07.js +0 -1
- codex/static_root/assets/admin-settings-button-progress-DDF4rY07.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-DDF4rY07.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.2548c49a853e.css +0 -1
- codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.css +0 -1
- codex/static_root/assets/browser-C0KS3g2E.0bf1c9c6319c.js +0 -1
- codex/static_root/assets/browser-C0KS3g2E.0bf1c9c6319c.js.br +0 -0
- codex/static_root/assets/browser-C0KS3g2E.0bf1c9c6319c.js.gz +0 -0
- codex/static_root/assets/browser-C0KS3g2E.js +0 -1
- codex/static_root/assets/browser-C0KS3g2E.js.br +0 -0
- codex/static_root/assets/browser-C0KS3g2E.js.gz +0 -0
- codex/static_root/assets/browser-JFL2hLjt.21cb2f7335f7.css +0 -1
- codex/static_root/assets/browser-JFL2hLjt.21cb2f7335f7.css.br +0 -0
- codex/static_root/assets/browser-JFL2hLjt.21cb2f7335f7.css.gz +0 -0
- codex/static_root/assets/browser-JFL2hLjt.css +0 -1
- codex/static_root/assets/browser-JFL2hLjt.css.br +0 -0
- codex/static_root/assets/browser-JFL2hLjt.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DXJalgW4.a9f34729599d.js +0 -1
- codex/static_root/assets/change-password-dialog-DXJalgW4.a9f34729599d.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DXJalgW4.a9f34729599d.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DXJalgW4.js +0 -1
- codex/static_root/assets/change-password-dialog-DXJalgW4.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DXJalgW4.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css +0 -1
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.br +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css +0 -1
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css.br +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css.gz +0 -0
- codex/static_root/assets/confirm-dialog-B7w6z2rf.472011c06253.css +0 -1
- codex/static_root/assets/confirm-dialog-B7w6z2rf.css +0 -1
- codex/static_root/assets/confirm-dialog-CqDEpU9k.e1811488121d.js +0 -1
- codex/static_root/assets/confirm-dialog-CqDEpU9k.e1811488121d.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CqDEpU9k.e1811488121d.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CqDEpU9k.js +0 -1
- codex/static_root/assets/confirm-dialog-CqDEpU9k.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CqDEpU9k.js.gz +0 -0
- codex/static_root/assets/datetime-column-UxNaJr8b.c06a11531af3.js +0 -1
- codex/static_root/assets/datetime-column-UxNaJr8b.c06a11531af3.js.br +0 -0
- codex/static_root/assets/datetime-column-UxNaJr8b.c06a11531af3.js.gz +0 -0
- codex/static_root/assets/datetime-column-UxNaJr8b.js +0 -1
- codex/static_root/assets/datetime-column-UxNaJr8b.js.br +0 -0
- codex/static_root/assets/datetime-column-UxNaJr8b.js.gz +0 -0
- codex/static_root/assets/filter-DskcqZHA.e707b5a05b4b.js +0 -1
- codex/static_root/assets/filter-DskcqZHA.e707b5a05b4b.js.br +0 -0
- codex/static_root/assets/filter-DskcqZHA.e707b5a05b4b.js.gz +0 -0
- codex/static_root/assets/filter-DskcqZHA.js +0 -1
- codex/static_root/assets/filter-DskcqZHA.js.br +0 -0
- codex/static_root/assets/filter-DskcqZHA.js.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +0 -1
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css +0 -1
- codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
- codex/static_root/assets/flag-tab-xBeWEQ0w.582e77d9e885.js +0 -1
- codex/static_root/assets/flag-tab-xBeWEQ0w.582e77d9e885.js.br +0 -0
- codex/static_root/assets/flag-tab-xBeWEQ0w.582e77d9e885.js.gz +0 -0
- codex/static_root/assets/flag-tab-xBeWEQ0w.js +0 -1
- codex/static_root/assets/flag-tab-xBeWEQ0w.js.br +0 -0
- codex/static_root/assets/flag-tab-xBeWEQ0w.js.gz +0 -0
- codex/static_root/assets/group-tab-Czt2x61x.56e2886b1bac.js +0 -1
- codex/static_root/assets/group-tab-Czt2x61x.56e2886b1bac.js.br +0 -0
- codex/static_root/assets/group-tab-Czt2x61x.56e2886b1bac.js.gz +0 -0
- codex/static_root/assets/group-tab-Czt2x61x.js +0 -1
- codex/static_root/assets/group-tab-Czt2x61x.js.br +0 -0
- codex/static_root/assets/group-tab-Czt2x61x.js.gz +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css +0 -1
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.br +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.gz +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css +0 -1
- codex/static_root/assets/group-tab-DkIN1Heu.css.br +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css.gz +0 -0
- codex/static_root/assets/http-error-NHcwE5e_.d49089e40589.js +0 -1
- codex/static_root/assets/http-error-NHcwE5e_.d49089e40589.js.br +0 -0
- codex/static_root/assets/http-error-NHcwE5e_.d49089e40589.js.gz +0 -0
- codex/static_root/assets/http-error-NHcwE5e_.js +0 -1
- codex/static_root/assets/http-error-NHcwE5e_.js.br +0 -0
- codex/static_root/assets/http-error-NHcwE5e_.js.gz +0 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css +0 -1
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.br +0 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.gz +0 -0
- codex/static_root/assets/index-CL_6g4tJ.css +0 -1
- codex/static_root/assets/index-CL_6g4tJ.css.br +0 -0
- codex/static_root/assets/index-CL_6g4tJ.css.gz +0 -0
- codex/static_root/assets/index-DYNNO5kn.24a2d270253a.js +0 -1
- codex/static_root/assets/index-DYNNO5kn.24a2d270253a.js.br +0 -0
- codex/static_root/assets/index-DYNNO5kn.24a2d270253a.js.gz +0 -0
- codex/static_root/assets/index-DYNNO5kn.js +0 -1
- codex/static_root/assets/index-DYNNO5kn.js.br +0 -0
- codex/static_root/assets/index-DYNNO5kn.js.gz +0 -0
- codex/static_root/assets/library-tab-CgIqcjvj.60327dfa8ecb.js +0 -1
- codex/static_root/assets/library-tab-CgIqcjvj.60327dfa8ecb.js.br +0 -0
- codex/static_root/assets/library-tab-CgIqcjvj.60327dfa8ecb.js.gz +0 -0
- codex/static_root/assets/library-tab-CgIqcjvj.js +0 -1
- codex/static_root/assets/library-tab-CgIqcjvj.js.br +0 -0
- codex/static_root/assets/library-tab-CgIqcjvj.js.gz +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.br +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.gz +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.css.br +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.css.gz +0 -0
- codex/static_root/assets/main-B3EwXQ3Q.14dfc21e272c.js +0 -35
- codex/static_root/assets/main-B3EwXQ3Q.14dfc21e272c.js.br +0 -0
- codex/static_root/assets/main-B3EwXQ3Q.14dfc21e272c.js.gz +0 -0
- codex/static_root/assets/main-B3EwXQ3Q.js +0 -35
- codex/static_root/assets/main-B3EwXQ3Q.js.br +0 -0
- codex/static_root/assets/main-B3EwXQ3Q.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-YkzUXx0B.f39d84e2380a.js +0 -1
- codex/static_root/assets/pager-full-pdf-YkzUXx0B.f39d84e2380a.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-YkzUXx0B.f39d84e2380a.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-YkzUXx0B.js +0 -1
- codex/static_root/assets/pager-full-pdf-YkzUXx0B.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-YkzUXx0B.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CrruiVov.3bf9b795350d.js +0 -1
- codex/static_root/assets/pagination-toolbar-CrruiVov.3bf9b795350d.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CrruiVov.3bf9b795350d.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CrruiVov.js +0 -1
- codex/static_root/assets/pagination-toolbar-CrruiVov.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CrruiVov.js.gz +0 -0
- codex/static_root/assets/pdf-doc-ctqZIwOP.7e7b2c815ea4.js.br +0 -0
- codex/static_root/assets/pdf-doc-ctqZIwOP.7e7b2c815ea4.js.gz +0 -0
- codex/static_root/assets/pdf-doc-ctqZIwOP.js.br +0 -0
- codex/static_root/assets/pdf-doc-ctqZIwOP.js.gz +0 -0
- codex/static_root/assets/reader-DPLhdrJD.268417c618c5.js +0 -2
- codex/static_root/assets/reader-DPLhdrJD.268417c618c5.js.br +0 -0
- codex/static_root/assets/reader-DPLhdrJD.268417c618c5.js.gz +0 -0
- codex/static_root/assets/reader-DPLhdrJD.js +0 -2
- codex/static_root/assets/reader-DPLhdrJD.js.br +0 -0
- codex/static_root/assets/reader-DPLhdrJD.js.gz +0 -0
- codex/static_root/assets/reader-nD70BeEz.06e85e73c391.css.br +0 -0
- codex/static_root/assets/reader-nD70BeEz.06e85e73c391.css.gz +0 -0
- codex/static_root/assets/reader-nD70BeEz.css.br +0 -0
- codex/static_root/assets/reader-nD70BeEz.css.gz +0 -0
- codex/static_root/assets/relation-chips-DOALMcWL.5d3b8100ba03.js +0 -1
- codex/static_root/assets/relation-chips-DOALMcWL.5d3b8100ba03.js.br +0 -0
- codex/static_root/assets/relation-chips-DOALMcWL.5d3b8100ba03.js.gz +0 -0
- codex/static_root/assets/relation-chips-DOALMcWL.js +0 -1
- codex/static_root/assets/relation-chips-DOALMcWL.js.br +0 -0
- codex/static_root/assets/relation-chips-DOALMcWL.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BVj1U_LC.c68369e0c2de.js +0 -2
- codex/static_root/assets/settings-drawer-BVj1U_LC.c68369e0c2de.js.br +0 -0
- codex/static_root/assets/settings-drawer-BVj1U_LC.c68369e0c2de.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BVj1U_LC.js +0 -2
- codex/static_root/assets/settings-drawer-BVj1U_LC.js.br +0 -0
- codex/static_root/assets/settings-drawer-BVj1U_LC.js.gz +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.br +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.gz +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.css.br +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.css.gz +0 -0
- codex/static_root/assets/stats-tab-DDLwwwnP.35305258097b.js +0 -1
- codex/static_root/assets/stats-tab-DDLwwwnP.35305258097b.js.br +0 -0
- codex/static_root/assets/stats-tab-DDLwwwnP.35305258097b.js.gz +0 -0
- codex/static_root/assets/stats-tab-DDLwwwnP.js +0 -1
- codex/static_root/assets/stats-tab-DDLwwwnP.js.br +0 -0
- codex/static_root/assets/stats-tab-DDLwwwnP.js.gz +0 -0
- codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css +0 -1
- codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css.gz +0 -0
- codex/static_root/assets/task-tab--cXi48kg.css +0 -1
- codex/static_root/assets/task-tab--cXi48kg.css.gz +0 -0
- codex/static_root/assets/task-tab-BpP3n2dO.c853eb565200.js +0 -1
- codex/static_root/assets/task-tab-BpP3n2dO.c853eb565200.js.br +0 -0
- codex/static_root/assets/task-tab-BpP3n2dO.c853eb565200.js.gz +0 -0
- codex/static_root/assets/task-tab-BpP3n2dO.js +0 -1
- codex/static_root/assets/task-tab-BpP3n2dO.js.br +0 -0
- codex/static_root/assets/task-tab-BpP3n2dO.js.gz +0 -0
- codex/static_root/assets/transition-3g2arGgl.c6e6f91b7978.js +0 -1
- codex/static_root/assets/transition-3g2arGgl.c6e6f91b7978.js.br +0 -0
- codex/static_root/assets/transition-3g2arGgl.c6e6f91b7978.js.gz +0 -0
- codex/static_root/assets/transition-3g2arGgl.js +0 -1
- codex/static_root/assets/transition-3g2arGgl.js.br +0 -0
- codex/static_root/assets/transition-3g2arGgl.js.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +0 -1
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +0 -4
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.css +0 -1
- codex/static_root/assets/transition-5LRFTPo5.css.br +0 -4
- codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
- codex/static_root/assets/unauthorized-CdumQ1Vf.79d9a61eb572.js +0 -1
- codex/static_root/assets/unauthorized-CdumQ1Vf.79d9a61eb572.js.br +0 -0
- codex/static_root/assets/unauthorized-CdumQ1Vf.79d9a61eb572.js.gz +0 -0
- codex/static_root/assets/unauthorized-CdumQ1Vf.js +0 -1
- codex/static_root/assets/unauthorized-CdumQ1Vf.js.br +0 -0
- codex/static_root/assets/unauthorized-CdumQ1Vf.js.gz +0 -0
- codex/static_root/assets/user-tab-D6oOKZTP.05944b00c700.js +0 -1
- codex/static_root/assets/user-tab-D6oOKZTP.05944b00c700.js.br +0 -0
- codex/static_root/assets/user-tab-D6oOKZTP.05944b00c700.js.gz +0 -0
- codex/static_root/assets/user-tab-D6oOKZTP.js +0 -1
- codex/static_root/assets/user-tab-D6oOKZTP.js.br +0 -0
- codex/static_root/assets/user-tab-D6oOKZTP.js.gz +0 -0
- codex/static_root/manifest.c5f1de88b1ac.json +0 -712
- codex/static_root/manifest.c5f1de88b1ac.json.br +0 -0
- codex/static_root/manifest.c5f1de88b1ac.json.gz +0 -0
- codex/views/browser/annotate/__init__.py +0 -1
- codex/views/browser/annotate/bookmark.py +0 -112
- codex/views/browser/annotate/card.py +0 -61
- codex/views/browser/annotate/order.py +0 -247
- codex/views/browser/filters/filter.py +0 -69
- codex/views/browser/group_mtime.py +0 -83
- codex/views/browser/metadata/__init__.py +0 -1
- codex/views/browser/metadata/annotate.py +0 -109
- codex/views/browser/metadata/copy_intersections.py +0 -90
- codex/views/browser/metadata/metadata.py +0 -83
- codex/views/browser/metadata/query_intersections.py +0 -112
- codex/views/browser/params.py +0 -64
- codex/views/timezone.py +0 -54
- {codex-1.7.0.dist-info → codex-1.7.0a0.dist-info}/LICENSE +0 -0
- {codex-1.7.0.dist-info → codex-1.7.0a0.dist-info}/WHEEL +0 -0
- {codex-1.7.0.dist-info → codex-1.7.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -2,29 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db.models import Q
|
|
4
4
|
|
|
5
|
-
from codex.views.browser.params import BrowserParamsView
|
|
6
5
|
from codex.views.const import (
|
|
7
6
|
FILTER_ONLY_GROUP_RELATION,
|
|
8
7
|
FOLDER_GROUP,
|
|
9
8
|
GROUP_RELATION,
|
|
10
9
|
)
|
|
10
|
+
from codex.views.session import SessionView
|
|
11
11
|
|
|
12
|
-
_GROUP_REL_TARGETS = frozenset({"cover", "choices"})
|
|
13
|
-
_PK_REL_TARGETS = frozenset({"metadata", "mtime"})
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
class GroupFilterView(BrowserParamsView):
|
|
13
|
+
class GroupFilterView(SessionView):
|
|
17
14
|
"""Group Filters."""
|
|
18
15
|
|
|
19
|
-
SESSION_KEY =
|
|
16
|
+
SESSION_KEY = SessionView.BROWSER_SESSION_KEY
|
|
20
17
|
|
|
21
18
|
def _get_rel_for_pks(self, group, page_mtime=False):
|
|
22
19
|
"""Get the relation from the model to the pks."""
|
|
23
20
|
# XXX these TARGET refs might be better as subclass get rel methods.
|
|
24
21
|
target: str = self.TARGET # type: ignore
|
|
25
|
-
if target in
|
|
22
|
+
if target in ("cover", "choices"):
|
|
26
23
|
rel = FILTER_ONLY_GROUP_RELATION[group]
|
|
27
|
-
elif target in
|
|
24
|
+
elif target in ("metadata", "mtime") or page_mtime:
|
|
28
25
|
# metadata, mtime, browser.page_mtime
|
|
29
26
|
rel = "pk"
|
|
30
27
|
else:
|
|
@@ -45,8 +42,8 @@ class GroupFilterView(BrowserParamsView):
|
|
|
45
42
|
rel = self._get_rel_for_pks(group, page_mtime)
|
|
46
43
|
group_filter_dict = {rel: pks}
|
|
47
44
|
elif group == FOLDER_GROUP:
|
|
48
|
-
# Top folder search
|
|
49
45
|
group_filter_dict = {"parent_folder": None}
|
|
50
46
|
else:
|
|
51
47
|
group_filter_dict = {}
|
|
48
|
+
|
|
52
49
|
return Q(**group_filter_dict)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""Search Filters Methods."""
|
|
2
2
|
|
|
3
|
+
from django.db.models.query import Q
|
|
4
|
+
|
|
3
5
|
from codex.logger.logging import get_logger
|
|
6
|
+
from codex.models import Comic, StoryArc
|
|
4
7
|
from codex.views.browser.filters.search.field.filter import BrowserFieldQueryFilter
|
|
5
8
|
|
|
6
9
|
LOG = get_logger(__name__)
|
|
@@ -9,16 +12,27 @@ LOG = get_logger(__name__)
|
|
|
9
12
|
class BrowserFTSFilter(BrowserFieldQueryFilter):
|
|
10
13
|
"""Search Filters Methods."""
|
|
11
14
|
|
|
15
|
+
@staticmethod
|
|
16
|
+
def _get_fts_filter(model, text):
|
|
17
|
+
"""Get the filter dict."""
|
|
18
|
+
prefix = (
|
|
19
|
+
""
|
|
20
|
+
if model == Comic
|
|
21
|
+
else "storyarcnumber__comic__"
|
|
22
|
+
if model == StoryArc
|
|
23
|
+
else "comic__"
|
|
24
|
+
)
|
|
25
|
+
# Custom Lookup defined in codex.models
|
|
26
|
+
rel = prefix + "comicfts__match"
|
|
27
|
+
return {rel: text}
|
|
28
|
+
|
|
12
29
|
def get_fts_filter(self, model, text):
|
|
13
30
|
"""Perform the search and return the scores as a dict."""
|
|
14
31
|
fts_filter = {}
|
|
15
32
|
try:
|
|
16
33
|
if text:
|
|
17
|
-
|
|
18
|
-
# Custom lookup defined in codex.models
|
|
19
|
-
rel += "comicfts__match"
|
|
20
|
-
fts_filter[rel] = text
|
|
34
|
+
fts_filter = self._get_fts_filter(model, text)
|
|
21
35
|
except Exception:
|
|
22
36
|
LOG.exception("Getting Full Text Search Filter.")
|
|
23
37
|
self.search_error = "Error creating full text search filter"
|
|
24
|
-
return fts_filter
|
|
38
|
+
return Q(**fts_filter)
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
"""Search Filters Methods."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from types import MappingProxyType
|
|
5
|
-
|
|
6
|
-
from django.db.models.query import Q
|
|
7
4
|
|
|
8
5
|
from codex.logger.logging import get_logger
|
|
9
|
-
from codex.models import AdminFlag
|
|
10
6
|
from codex.models.comic import ComicFTS
|
|
11
7
|
from codex.views.browser.filters.search.aliases import ALIAS_FIELD_MAP
|
|
12
8
|
from codex.views.browser.filters.search.fts import BrowserFTSFilter
|
|
@@ -60,43 +56,24 @@ _TOKEN_RE = re.compile(_TOKEN_REXP, flags=re.IGNORECASE)
|
|
|
60
56
|
class SearchFilterView(BrowserFTSFilter):
|
|
61
57
|
"""Search Query Parser."""
|
|
62
58
|
|
|
63
|
-
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
|
|
64
|
-
{
|
|
65
|
-
AdminFlag.FlagChoices.FOLDER_VIEW.value: "folder_view",
|
|
66
|
-
}
|
|
67
|
-
)
|
|
68
|
-
|
|
69
59
|
def __init__(self, *args, **kwargs):
|
|
70
60
|
"""Initialize search variables."""
|
|
71
61
|
super().__init__(*args, **kwargs)
|
|
72
|
-
self._admin_flags: MappingProxyType[str, bool] | None = None
|
|
73
62
|
self.fts_mode = False
|
|
74
63
|
self.search_mode = False
|
|
75
64
|
self.search_error = ""
|
|
76
65
|
|
|
77
|
-
@property
|
|
78
|
-
def admin_flags(self) -> MappingProxyType[str, bool]:
|
|
79
|
-
"""Set browser relevant admin flags."""
|
|
80
|
-
if self._admin_flags is None:
|
|
81
|
-
if self.ADMIN_FLAG_VALUE_KEY_MAP:
|
|
82
|
-
admin_pairs = AdminFlag.objects.filter(
|
|
83
|
-
key__in=self.ADMIN_FLAG_VALUE_KEY_MAP.keys()
|
|
84
|
-
).values_list("key", "on")
|
|
85
|
-
else:
|
|
86
|
-
admin_pairs = ()
|
|
87
|
-
admin_flags = {}
|
|
88
|
-
for key, on in admin_pairs:
|
|
89
|
-
export_key = self.ADMIN_FLAG_VALUE_KEY_MAP[key]
|
|
90
|
-
admin_flags[export_key] = on
|
|
91
|
-
self._admin_flags = MappingProxyType(admin_flags)
|
|
92
|
-
return self._admin_flags
|
|
93
|
-
|
|
94
66
|
def _is_path_column_allowed(self):
|
|
95
67
|
"""Is path column allowed."""
|
|
96
|
-
|
|
68
|
+
if not self.is_admin(): # type: ignore
|
|
69
|
+
if not "folder_view" not in self.admin_flags: # type: ignore
|
|
70
|
+
# Ensure admin flags for Cover View
|
|
71
|
+
self.set_admin_flags() # type: ignore
|
|
72
|
+
return bool(self.admin_flags.get("folder_view")) # type: ignore
|
|
73
|
+
return True
|
|
97
74
|
|
|
98
75
|
@staticmethod
|
|
99
|
-
def _is_column_operators_used(exp)
|
|
76
|
+
def _is_column_operators_used(exp):
|
|
100
77
|
"""Detect column expression operators, but not inside quotes."""
|
|
101
78
|
for match in _COLUMN_EXPRESSION_OPERATORS_RE.finditer(exp):
|
|
102
79
|
if match.group("star"):
|
|
@@ -172,7 +149,7 @@ class SearchFilterView(BrowserFTSFilter):
|
|
|
172
149
|
field_exclude_q_list = []
|
|
173
150
|
for preop, field_token_pairs in field_tokens_dict.items():
|
|
174
151
|
if preop == "not":
|
|
175
|
-
exclude_q_list,
|
|
152
|
+
exclude_q_list, filter_q_list = self.get_search_field_filters(
|
|
176
153
|
model, field_token_pairs
|
|
177
154
|
)
|
|
178
155
|
else:
|
|
@@ -180,44 +157,37 @@ class SearchFilterView(BrowserFTSFilter):
|
|
|
180
157
|
# XXX cannot do OR queries with MATCH, it decontextualizes MATCH somehow.
|
|
181
158
|
if preop == "or":
|
|
182
159
|
self.search_error = "OR preceding column tokens with operator expressions will act as AND"
|
|
183
|
-
|
|
160
|
+
filter_q_list, exclude_q_list = self.get_search_field_filters(
|
|
184
161
|
model, field_token_pairs
|
|
185
162
|
)
|
|
186
|
-
field_filter_q_list +=
|
|
163
|
+
field_filter_q_list += filter_q_list
|
|
187
164
|
field_exclude_q_list += exclude_q_list
|
|
165
|
+
fts_filter = self.get_fts_filter(model, fts_text)
|
|
166
|
+
return field_exclude_q_list, field_filter_q_list, fts_filter
|
|
188
167
|
|
|
189
|
-
|
|
190
|
-
if fts_filter_dict:
|
|
191
|
-
self.fts_mode = True
|
|
192
|
-
fts_q = Q(**fts_filter_dict)
|
|
193
|
-
else:
|
|
194
|
-
fts_q = Q()
|
|
195
|
-
|
|
196
|
-
return field_filter_q_list, field_exclude_q_list, fts_q
|
|
197
|
-
|
|
198
|
-
def _create_search_filter(self, filter_list):
|
|
168
|
+
def _apply_search_filter_list(self, qs, filter_list, exclude):
|
|
199
169
|
"""Apply search filter lists. Separate filter clauses are employed for m2m searches."""
|
|
200
|
-
combined_q = Q()
|
|
201
170
|
for q in filter_list:
|
|
202
171
|
if not q:
|
|
203
172
|
continue
|
|
204
|
-
|
|
173
|
+
qs = qs.exclude(q) if exclude else qs.filter(q)
|
|
205
174
|
self.search_mode = True
|
|
206
175
|
|
|
207
|
-
return
|
|
176
|
+
return qs
|
|
208
177
|
|
|
209
|
-
def
|
|
178
|
+
def apply_search_filter(self, qs, model):
|
|
210
179
|
"""Preparse search, search and return the filter and scores."""
|
|
211
|
-
include_q = Q()
|
|
212
|
-
exclude_q = Q()
|
|
213
|
-
fts_q = Q()
|
|
214
180
|
try:
|
|
215
|
-
|
|
181
|
+
field_exclude_q_list, field_filter_q_list, fts_filter = (
|
|
216
182
|
self._create_search_filters(model)
|
|
217
183
|
)
|
|
184
|
+
|
|
218
185
|
# Apply filters
|
|
219
|
-
|
|
220
|
-
|
|
186
|
+
qs = self._apply_search_filter_list(qs, field_exclude_q_list, True)
|
|
187
|
+
qs = self._apply_search_filter_list(qs, field_filter_q_list, False)
|
|
188
|
+
if fts_filter:
|
|
189
|
+
qs = qs.filter(fts_filter)
|
|
190
|
+
self.search_mode = self.fts_mode = True
|
|
221
191
|
|
|
222
192
|
except Exception as exc:
|
|
223
193
|
msg = "Creating search filters"
|
|
@@ -225,7 +195,7 @@ class SearchFilterView(BrowserFTSFilter):
|
|
|
225
195
|
msg = f"{msg} - {exc}"
|
|
226
196
|
self.search_error = msg
|
|
227
197
|
|
|
228
|
-
return
|
|
198
|
+
return qs
|
|
229
199
|
|
|
230
200
|
def _is_search_results_limited(self) -> bool:
|
|
231
201
|
"""Get search result limit from params."""
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""View for marking comics read and unread."""
|
|
2
|
+
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
|
|
5
|
+
from django.db.models import Count, IntegerField, Sum, Value
|
|
6
|
+
from drf_spectacular.utils import extend_schema
|
|
7
|
+
from rest_framework.exceptions import NotFound
|
|
8
|
+
from rest_framework.response import Response
|
|
9
|
+
|
|
10
|
+
from codex.librarian.importer.const import (
|
|
11
|
+
COMIC_M2M_FIELD_NAMES,
|
|
12
|
+
)
|
|
13
|
+
from codex.logger.logging import get_logger
|
|
14
|
+
from codex.models import AdminFlag, Comic
|
|
15
|
+
from codex.models.functions import JsonGroupArray
|
|
16
|
+
from codex.serializers.browser.metadata import PREFETCH_PREFIX, MetadataSerializer
|
|
17
|
+
from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
|
|
18
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
19
|
+
from codex.views.const import METADATA_GROUP_RELATION
|
|
20
|
+
|
|
21
|
+
LOG = get_logger(__name__)
|
|
22
|
+
_ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
|
|
23
|
+
_COMIC_VALUE_FIELDS_CONFLICTING = frozenset(
|
|
24
|
+
{
|
|
25
|
+
"created_at",
|
|
26
|
+
"name",
|
|
27
|
+
"path",
|
|
28
|
+
"updated_at",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
_DISABLED_VALUE_FIELD_NAMES = frozenset(
|
|
32
|
+
{"id", "pk", "sort_name", "stat"} | _COMIC_VALUE_FIELDS_CONFLICTING
|
|
33
|
+
)
|
|
34
|
+
_COMIC_VALUE_FIELD_NAMES = frozenset(
|
|
35
|
+
# contains path
|
|
36
|
+
field.name
|
|
37
|
+
for field in Comic._meta.get_fields()
|
|
38
|
+
if not field.is_relation and field.name not in _DISABLED_VALUE_FIELD_NAMES
|
|
39
|
+
)
|
|
40
|
+
_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
|
|
41
|
+
_COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_count"})
|
|
42
|
+
_PATH_GROUPS = frozenset({"c", "f"})
|
|
43
|
+
_CONTRIBUTOR_RELATIONS = ("role", "person")
|
|
44
|
+
_SUM_FIELDS = frozenset({"page_count", "size"})
|
|
45
|
+
_GROUP_RELS = MappingProxyType(
|
|
46
|
+
{
|
|
47
|
+
"i": ("publisher",),
|
|
48
|
+
"s": (
|
|
49
|
+
"publisher",
|
|
50
|
+
"imprint",
|
|
51
|
+
),
|
|
52
|
+
"v": (
|
|
53
|
+
"publisher",
|
|
54
|
+
"imprint",
|
|
55
|
+
"series",
|
|
56
|
+
),
|
|
57
|
+
"c": ("publisher", "imprint", "series", "volume"),
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MetadataView(BrowserAnnotationsView):
|
|
63
|
+
"""Comic metadata."""
|
|
64
|
+
|
|
65
|
+
serializer_class = MetadataSerializer
|
|
66
|
+
input_serializer_class = BrowserFilterChoicesInputSerilalizer
|
|
67
|
+
TARGET = "metadata"
|
|
68
|
+
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
|
|
69
|
+
{
|
|
70
|
+
AdminFlag.FlagChoices.FOLDER_VIEW.value: "folder_view",
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def init_request(self):
|
|
75
|
+
"""Initialize request."""
|
|
76
|
+
self.set_admin_flags()
|
|
77
|
+
self.parse_params()
|
|
78
|
+
self.set_model()
|
|
79
|
+
self.set_rel_prefix()
|
|
80
|
+
|
|
81
|
+
def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
|
|
82
|
+
"""Limited allowed nav groups for metadata."""
|
|
83
|
+
group = self.kwargs["group"]
|
|
84
|
+
self.valid_nav_groups = (group,)
|
|
85
|
+
|
|
86
|
+
def _get_comic_value_fields(self):
|
|
87
|
+
"""Include the path field for staff."""
|
|
88
|
+
fields = set(_COMIC_VALUE_FIELD_NAMES)
|
|
89
|
+
group = self.kwargs["group"]
|
|
90
|
+
if (
|
|
91
|
+
not (self.admin_flags["folder_view"] and self.is_admin())
|
|
92
|
+
or group not in _PATH_GROUPS
|
|
93
|
+
):
|
|
94
|
+
fields -= _ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS
|
|
95
|
+
return tuple(fields)
|
|
96
|
+
|
|
97
|
+
def _intersection_annotate(
|
|
98
|
+
self,
|
|
99
|
+
querysets,
|
|
100
|
+
fields,
|
|
101
|
+
related_suffix="",
|
|
102
|
+
annotation_prefix="",
|
|
103
|
+
):
|
|
104
|
+
"""Annotate the intersection of value and fk fields."""
|
|
105
|
+
(filtered_qs, qs) = querysets
|
|
106
|
+
|
|
107
|
+
for field in fields:
|
|
108
|
+
# Annotate variant counts
|
|
109
|
+
# If 1 variant, annotate value, otherwise None
|
|
110
|
+
ann_field = annotation_prefix + field.replace("__", "_")
|
|
111
|
+
|
|
112
|
+
full_field = self.rel_prefix + field
|
|
113
|
+
|
|
114
|
+
if field in _SUM_FIELDS:
|
|
115
|
+
val = Sum(full_field)
|
|
116
|
+
else:
|
|
117
|
+
# group_by makes the filter work, but prevents its
|
|
118
|
+
# use as a subquery in the big query.
|
|
119
|
+
sq = filtered_qs.alias(
|
|
120
|
+
filter_count=Count(full_field, distinct=True)
|
|
121
|
+
).filter(filter_count=1)
|
|
122
|
+
sq = self.add_group_by(sq)
|
|
123
|
+
sq = sq.values_list(full_field + related_suffix, flat=True)
|
|
124
|
+
try:
|
|
125
|
+
val = sq[0]
|
|
126
|
+
except IndexError:
|
|
127
|
+
val = None
|
|
128
|
+
if field.endswith("count"):
|
|
129
|
+
output_field = IntegerField()
|
|
130
|
+
else:
|
|
131
|
+
output_field = Comic._meta.get_field(field).__class__()
|
|
132
|
+
val = Value(val, output_field)
|
|
133
|
+
qs = qs.annotate(**{ann_field: val})
|
|
134
|
+
|
|
135
|
+
return filtered_qs, qs
|
|
136
|
+
|
|
137
|
+
def _annotate_values_and_fks(self, qs, filtered_qs):
|
|
138
|
+
"""Annotate comic values and comic foreign key values."""
|
|
139
|
+
# Simple Values
|
|
140
|
+
querysets = (filtered_qs, qs)
|
|
141
|
+
if not self.is_model_comic:
|
|
142
|
+
comic_value_fields = self._get_comic_value_fields()
|
|
143
|
+
querysets = self._intersection_annotate(querysets, comic_value_fields)
|
|
144
|
+
|
|
145
|
+
# Conflicting Simple Values
|
|
146
|
+
querysets = self._intersection_annotate(
|
|
147
|
+
querysets,
|
|
148
|
+
_COMIC_VALUE_FIELDS_CONFLICTING,
|
|
149
|
+
annotation_prefix=_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Foreign Keys with special count values
|
|
153
|
+
_, qs = self._intersection_annotate(
|
|
154
|
+
querysets,
|
|
155
|
+
_COMIC_RELATED_VALUE_FIELDS,
|
|
156
|
+
)
|
|
157
|
+
return qs
|
|
158
|
+
|
|
159
|
+
def _query_groups(self):
|
|
160
|
+
"""Query the through models to show group lists."""
|
|
161
|
+
groups = {}
|
|
162
|
+
if not self.model:
|
|
163
|
+
return groups
|
|
164
|
+
group = self.kwargs["group"]
|
|
165
|
+
rel = METADATA_GROUP_RELATION.get(group)
|
|
166
|
+
if not rel:
|
|
167
|
+
return groups
|
|
168
|
+
rel = rel + "__in"
|
|
169
|
+
pks = self.kwargs["pks"]
|
|
170
|
+
group_filter = {rel: pks}
|
|
171
|
+
|
|
172
|
+
for field_name in _GROUP_RELS.get(group, ()):
|
|
173
|
+
field = self.model._meta.get_field(field_name)
|
|
174
|
+
model = field.related_model
|
|
175
|
+
if not model:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
qs = model.objects.filter(**group_filter)
|
|
179
|
+
qs = qs.only("name").distinct()
|
|
180
|
+
qs = qs.group_by("name")
|
|
181
|
+
qs = qs.annotate(ids=JsonGroupArray("id", distinct=True))
|
|
182
|
+
qs = qs.values("ids", "name")
|
|
183
|
+
groups[field_name] = qs
|
|
184
|
+
return groups
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _get_optimized_m2m_query(key, qs):
|
|
188
|
+
if key == "contributors":
|
|
189
|
+
qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS)
|
|
190
|
+
qs = qs.only(*_CONTRIBUTOR_RELATIONS)
|
|
191
|
+
elif key == "story_arc_numbers":
|
|
192
|
+
qs = qs.select_related("story_arc")
|
|
193
|
+
qs = qs.only("story_arc", "number")
|
|
194
|
+
elif key == "identifiers":
|
|
195
|
+
qs = qs.select_related("identifier_type")
|
|
196
|
+
qs = qs.only("identifier_type", "nss", "url")
|
|
197
|
+
else:
|
|
198
|
+
qs = qs.only("name")
|
|
199
|
+
return qs
|
|
200
|
+
|
|
201
|
+
def _query_m2m_intersections(self, filtered_qs):
|
|
202
|
+
"""Query the through models to figure out m2m intersections."""
|
|
203
|
+
# Speed ok, but still does a query per m2m model
|
|
204
|
+
m2m_intersections = {}
|
|
205
|
+
pk_field = self.rel_prefix + "pk"
|
|
206
|
+
comic_pks = filtered_qs.values_list(pk_field, flat=True)
|
|
207
|
+
comic_pks_count = comic_pks.count()
|
|
208
|
+
for field_name in COMIC_M2M_FIELD_NAMES:
|
|
209
|
+
model = Comic._meta.get_field(field_name).related_model
|
|
210
|
+
if not model:
|
|
211
|
+
reason = f"No model found for comic field: {field_name}"
|
|
212
|
+
raise ValueError(reason)
|
|
213
|
+
|
|
214
|
+
intersection_qs = model.objects.filter(comic__pk__in=comic_pks)
|
|
215
|
+
intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
|
|
216
|
+
count=comic_pks_count
|
|
217
|
+
)
|
|
218
|
+
intersection_qs = self._get_optimized_m2m_query(field_name, intersection_qs)
|
|
219
|
+
|
|
220
|
+
m2m_intersections[field_name] = intersection_qs
|
|
221
|
+
return m2m_intersections
|
|
222
|
+
|
|
223
|
+
def _path_security(self, obj):
|
|
224
|
+
"""Secure filesystem information for acl situation."""
|
|
225
|
+
group = self.kwargs["group"]
|
|
226
|
+
is_path_group = group in _PATH_GROUPS
|
|
227
|
+
if is_path_group:
|
|
228
|
+
if self.is_admin():
|
|
229
|
+
return
|
|
230
|
+
if self.admin_flags["folder_view"]:
|
|
231
|
+
obj.path = obj.search_path()
|
|
232
|
+
else:
|
|
233
|
+
obj.path = ""
|
|
234
|
+
|
|
235
|
+
def _highlight_current_group(self, obj):
|
|
236
|
+
"""Values for highlighting the current group."""
|
|
237
|
+
if self.model and not self.is_model_comic:
|
|
238
|
+
# move the name of the group to the correct field
|
|
239
|
+
field = self.model.__name__.lower() + "_list"
|
|
240
|
+
group_list = self.model.objects.filter(pk__in=obj.ids).values("name")
|
|
241
|
+
setattr(obj, field, group_list)
|
|
242
|
+
obj.name = None
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def _copy_m2m_intersections(cls, obj, m2m_intersections):
|
|
246
|
+
"""Copy the m2m intersections into the object."""
|
|
247
|
+
# XXX It might even be faster to copy everything to a dict and not use the obj.
|
|
248
|
+
for key, qs in m2m_intersections.items():
|
|
249
|
+
serializer_key = (
|
|
250
|
+
f"{PREFETCH_PREFIX}{key}"
|
|
251
|
+
if key in ("identifiers", "contributors", "story_arc_numbers")
|
|
252
|
+
else key
|
|
253
|
+
)
|
|
254
|
+
if hasattr(obj, serializer_key):
|
|
255
|
+
# real db fields need to use their special set method.
|
|
256
|
+
field = getattr(obj, serializer_key)
|
|
257
|
+
field.set(
|
|
258
|
+
qs,
|
|
259
|
+
clear=True,
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
# fake db field is just a queryset attached.
|
|
263
|
+
setattr(obj, serializer_key, qs)
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
def _copy_groups(obj, groups):
|
|
267
|
+
for field, group_qs in groups.items():
|
|
268
|
+
setattr(obj, field + "_list", group_qs)
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def _copy_conflicting_simple_fields(obj):
|
|
272
|
+
for field in _COMIC_VALUE_FIELDS_CONFLICTING:
|
|
273
|
+
"""Copy conflicting fields over naturral fields."""
|
|
274
|
+
conflict_field = _COMIC_VALUE_FIELDS_CONFLICTING_PREFIX + field
|
|
275
|
+
val = getattr(obj, conflict_field)
|
|
276
|
+
setattr(obj, field, val)
|
|
277
|
+
|
|
278
|
+
def _copy_annotations_into_comic_fields(self, obj, groups, m2m_intersections):
|
|
279
|
+
"""Copy a bunch of values that i couldn't fit cleanly in the main queryset."""
|
|
280
|
+
self._path_security(obj)
|
|
281
|
+
self._highlight_current_group(obj)
|
|
282
|
+
self._copy_groups(obj, groups)
|
|
283
|
+
self._copy_m2m_intersections(obj, m2m_intersections)
|
|
284
|
+
if not self.is_model_comic:
|
|
285
|
+
self._copy_conflicting_simple_fields(obj)
|
|
286
|
+
|
|
287
|
+
return obj
|
|
288
|
+
|
|
289
|
+
def _raise_not_found(self, exc=None):
|
|
290
|
+
"""Raise an exception if the object is not found."""
|
|
291
|
+
pks = self.kwargs["pks"]
|
|
292
|
+
group = self.kwargs["group"]
|
|
293
|
+
detail = f"Filtered metadata for {group}/{pks} not found"
|
|
294
|
+
raise NotFound(detail=detail) from exc
|
|
295
|
+
|
|
296
|
+
def get_object(self):
|
|
297
|
+
"""Create a comic-like object from the current browser group."""
|
|
298
|
+
# Comic model goes through the same code path as groups because
|
|
299
|
+
# values dicts don't copy relations to the serializer. The values
|
|
300
|
+
# dict is necessary because of the folders view union in browser.py.
|
|
301
|
+
|
|
302
|
+
qs = self.get_filtered_queryset(self.model)
|
|
303
|
+
|
|
304
|
+
filtered_qs = qs
|
|
305
|
+
qs = self.annotate_order_aggregates(qs, self.model)
|
|
306
|
+
qs = self.annotate_card_aggregates(qs, self.model)
|
|
307
|
+
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
308
|
+
qs = self.add_group_by(qs)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
obj = qs[0]
|
|
312
|
+
if not obj:
|
|
313
|
+
reason = "Empty obj"
|
|
314
|
+
raise ValueError(reason) # noqa TRY301
|
|
315
|
+
except (IndexError, ValueError) as exc:
|
|
316
|
+
self._raise_not_found(exc)
|
|
317
|
+
|
|
318
|
+
groups = self._query_groups()
|
|
319
|
+
m2m_intersections = self._query_m2m_intersections(filtered_qs)
|
|
320
|
+
return self._copy_annotations_into_comic_fields(obj, groups, m2m_intersections) # type: ignore
|
|
321
|
+
|
|
322
|
+
@extend_schema(parameters=[input_serializer_class])
|
|
323
|
+
def get(self, *_args, **_kwargs):
|
|
324
|
+
"""Get metadata for a filtered browse group."""
|
|
325
|
+
# Init
|
|
326
|
+
try:
|
|
327
|
+
self.init_request()
|
|
328
|
+
obj = self.get_object()
|
|
329
|
+
serializer = self.get_serializer(obj)
|
|
330
|
+
return Response(serializer.data)
|
|
331
|
+
except Exception:
|
|
332
|
+
LOG.exception(f"Getting metadata {self.kwargs}")
|
codex/views/browser/mtime.py
CHANGED
|
@@ -7,13 +7,13 @@ from codex.logger.logging import get_logger
|
|
|
7
7
|
from codex.models.groups import Publisher
|
|
8
8
|
from codex.serializers.browser.mtime import GroupsMtimeSerializer, MtimeSerializer
|
|
9
9
|
from codex.util import max_none
|
|
10
|
-
from codex.views.browser.
|
|
10
|
+
from codex.views.browser.filters.annotations import BrowserAnnotationsFilterView
|
|
11
11
|
from codex.views.const import GROUP_MODEL_MAP
|
|
12
12
|
|
|
13
13
|
LOG = get_logger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class MtimeView(
|
|
16
|
+
class MtimeView(BrowserAnnotationsFilterView):
|
|
17
17
|
"""Get the mtimes for the submitted groups."""
|
|
18
18
|
|
|
19
19
|
input_serializer_class = GroupsMtimeSerializer
|
|
@@ -22,6 +22,11 @@ class MtimeView(BrowserGroupMtimeView):
|
|
|
22
22
|
REPARSE_JSON_FIELDS = frozenset({"groups", "filters"})
|
|
23
23
|
TARGET = "mtime"
|
|
24
24
|
|
|
25
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
26
|
+
"""Initialize."""
|
|
27
|
+
super().__init__(*args, **kwargs)
|
|
28
|
+
self.init_bookmark_data()
|
|
29
|
+
|
|
25
30
|
def _get_group_mtime(self, item):
|
|
26
31
|
"""Get one group's mtimes."""
|
|
27
32
|
group = item["group"]
|
|
@@ -45,6 +50,9 @@ class MtimeView(BrowserGroupMtimeView):
|
|
|
45
50
|
@extend_schema(parameters=[GroupsMtimeSerializer])
|
|
46
51
|
def get(self, *args, **kwargs):
|
|
47
52
|
"""Get the mtimes for the submitted groups."""
|
|
53
|
+
# Parse Request
|
|
54
|
+
self.parse_params()
|
|
55
|
+
|
|
48
56
|
max_mtime = self.get_max_groups_mtime()
|
|
49
57
|
|
|
50
58
|
# Serialize Response
|
codex/views/browser/order_by.py
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
"""Base view for ordering the query."""
|
|
2
2
|
|
|
3
|
+
from django.db.models.aggregates import Max, Min
|
|
4
|
+
|
|
3
5
|
from codex.models import Comic
|
|
4
|
-
from codex.views.browser.
|
|
6
|
+
from codex.views.browser.filters.annotations import (
|
|
7
|
+
BrowserAnnotationsFilterView,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
10
|
|
|
7
|
-
class BrowserOrderByView(
|
|
11
|
+
class BrowserOrderByView(BrowserAnnotationsFilterView):
|
|
8
12
|
"""Base class for views that need ordering."""
|
|
9
13
|
|
|
10
|
-
def
|
|
11
|
-
"""Initialize memoized vars."""
|
|
12
|
-
super().__init__(*args, **kwargs)
|
|
13
|
-
self._order_key: str = ""
|
|
14
|
-
self._comic_sort_names: tuple[str, ...] = ()
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
def order_key(self):
|
|
14
|
+
def set_order_key(self):
|
|
18
15
|
"""Get the default order key for the view."""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
order_key: str = self.params["order_by"]
|
|
17
|
+
if order_key == "search_score" and not self.fts_mode:
|
|
18
|
+
# if no search scores, use sort_name, asc
|
|
19
|
+
order_key = "sort_name"
|
|
20
|
+
order_reverse = False
|
|
21
|
+
else:
|
|
22
|
+
order_reverse = self.params.get("order_reverse")
|
|
23
|
+
self.order_key = order_key
|
|
24
|
+
self.order_agg_func = Max if order_reverse else Min
|
|
27
25
|
|
|
28
26
|
def _add_comic_order_by(self, order_key, comic_sort_names):
|
|
29
27
|
"""Order by for comics (and covers)."""
|
|
@@ -31,7 +29,7 @@ class BrowserOrderByView(BrowserGroupMtimeView):
|
|
|
31
29
|
order_key = self.order_key
|
|
32
30
|
if order_key == "sort_name":
|
|
33
31
|
if not comic_sort_names:
|
|
34
|
-
comic_sort_names = self.
|
|
32
|
+
comic_sort_names = self.comic_sort_names # type: ignore
|
|
35
33
|
order_fields_head = [
|
|
36
34
|
*comic_sort_names,
|
|
37
35
|
"issue_number",
|
|
@@ -47,13 +45,15 @@ class BrowserOrderByView(BrowserGroupMtimeView):
|
|
|
47
45
|
if order_key == "story_arc_number":
|
|
48
46
|
order_fields_head += ["date"]
|
|
49
47
|
elif order_key == "bookmark_updated_at":
|
|
50
|
-
order_fields_head += ["
|
|
48
|
+
order_fields_head += ["updated_at"]
|
|
51
49
|
return order_fields_head
|
|
52
50
|
|
|
53
|
-
def add_order_by(
|
|
51
|
+
def add_order_by(
|
|
52
|
+
self, qs, model, order_key="", do_reverse=True, comic_sort_names=None
|
|
53
|
+
):
|
|
54
54
|
"""Create the order_by list."""
|
|
55
55
|
order_fields_head = ()
|
|
56
|
-
if
|
|
56
|
+
if model == Comic:
|
|
57
57
|
order_fields_head = self._add_comic_order_by(order_key, comic_sort_names)
|
|
58
58
|
else:
|
|
59
59
|
order_fields_head = ["order_value"]
|