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
codex/views/browser/browser.py
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from math import ceil, floor, log10
|
|
4
4
|
from types import MappingProxyType
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
|
-
from django.db.models import Max
|
|
7
|
+
from django.db.models import Max, Subquery
|
|
8
|
+
from django.db.models.expressions import OuterRef
|
|
7
9
|
from django.db.utils import OperationalError
|
|
8
10
|
from drf_spectacular.utils import extend_schema
|
|
9
11
|
from rest_framework.response import Response
|
|
@@ -11,9 +13,12 @@ from rest_framework.response import Response
|
|
|
11
13
|
from codex.logger.logging import get_logger
|
|
12
14
|
from codex.models import (
|
|
13
15
|
AdminFlag,
|
|
16
|
+
BrowserGroupModel,
|
|
14
17
|
Comic,
|
|
15
18
|
Folder,
|
|
19
|
+
Imprint,
|
|
16
20
|
Library,
|
|
21
|
+
Volume,
|
|
17
22
|
)
|
|
18
23
|
from codex.serializers.browser.page import BrowserPageSerializer
|
|
19
24
|
from codex.views.browser.title import BrowserTitleView
|
|
@@ -21,10 +26,24 @@ from codex.views.const import (
|
|
|
21
26
|
COMIC_GROUP,
|
|
22
27
|
FOLDER_GROUP,
|
|
23
28
|
MAX_OBJ_PER_PAGE,
|
|
29
|
+
NONE_DATETIMEFIELD,
|
|
24
30
|
STORY_ARC_GROUP,
|
|
25
31
|
)
|
|
26
32
|
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from django.db.models.query import QuerySet
|
|
35
|
+
|
|
36
|
+
|
|
27
37
|
LOG = get_logger(__name__)
|
|
38
|
+
_GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
|
|
39
|
+
type[BrowserGroupModel], tuple[str | None, ...]
|
|
40
|
+
] = MappingProxyType(
|
|
41
|
+
{
|
|
42
|
+
Comic: ("series", "volume"),
|
|
43
|
+
Volume: ("series",),
|
|
44
|
+
Imprint: ("publisher",),
|
|
45
|
+
}
|
|
46
|
+
)
|
|
28
47
|
|
|
29
48
|
|
|
30
49
|
class BrowserView(BrowserTitleView):
|
|
@@ -40,31 +59,61 @@ class BrowserView(BrowserTitleView):
|
|
|
40
59
|
)
|
|
41
60
|
TARGET = "browser"
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
def __init__(self, *args, **kwargs):
|
|
63
|
+
"""Set params for the type checker."""
|
|
64
|
+
super().__init__(*args, **kwargs)
|
|
65
|
+
self.is_opds_2_acquisition = False
|
|
66
|
+
self.group_query: QuerySet = Comic.objects.none()
|
|
67
|
+
self.valid_nav_groups: tuple[str, ...] = ()
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
def _set_group_query_and_instance(self):
|
|
70
|
+
"""Create group_class instance."""
|
|
71
|
+
pks = self.kwargs.get("pks")
|
|
72
|
+
if pks and self.group_class:
|
|
73
|
+
try:
|
|
74
|
+
select_related: tuple[str | None, ...] = (
|
|
75
|
+
_GROUP_INSTANCE_SELECT_RELATED.get(self.group_class, (None,))
|
|
76
|
+
)
|
|
77
|
+
self.group_query = self.group_class.objects.select_related(
|
|
78
|
+
*select_related
|
|
79
|
+
).filter(pk__in=pks)
|
|
80
|
+
except self.group_class.DoesNotExist:
|
|
81
|
+
group = self.kwargs.get("group")
|
|
82
|
+
page = self.kwargs.get("page")
|
|
83
|
+
if group == "r" and not pks and page == 1:
|
|
84
|
+
self.group_query = self.group_class.objects.none()
|
|
85
|
+
else:
|
|
86
|
+
reason = f"{group}__in={pks} does not exist!"
|
|
87
|
+
settings_mask = {"breadcrumbs": []}
|
|
88
|
+
self.raise_redirect(
|
|
89
|
+
reason, route_mask={"group": group}, settings_mask=settings_mask
|
|
90
|
+
)
|
|
91
|
+
elif self.group_class:
|
|
92
|
+
self.group_query = self.group_class.objects.none()
|
|
93
|
+
else:
|
|
94
|
+
self.group_query = Comic.objects.none()
|
|
95
|
+
self.group_instance = self.group_query.first()
|
|
96
|
+
|
|
97
|
+
def init_request(self):
|
|
98
|
+
"""Initialize request."""
|
|
99
|
+
super().init_request()
|
|
100
|
+
self._set_group_query_and_instance()
|
|
101
|
+
|
|
102
|
+
def get_model_group(self):
|
|
49
103
|
"""Get the group of the models to browse."""
|
|
50
104
|
# the model group shown must be:
|
|
51
105
|
# A valid nav group or 'c'
|
|
52
106
|
# the child of the current nav group or 'c'
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else:
|
|
64
|
-
self._model_group = self.valid_nav_groups[
|
|
65
|
-
self.valid_nav_groups.index(group) + 1
|
|
66
|
-
]
|
|
67
|
-
return self._model_group
|
|
107
|
+
group = self.kwargs["group"]
|
|
108
|
+
if group == FOLDER_GROUP:
|
|
109
|
+
return group
|
|
110
|
+
if group == STORY_ARC_GROUP:
|
|
111
|
+
pks = self.kwargs.get("pks")
|
|
112
|
+
return COMIC_GROUP if pks else group
|
|
113
|
+
if group == self.valid_nav_groups[-1]:
|
|
114
|
+
# special case for lowest valid group
|
|
115
|
+
return COMIC_GROUP
|
|
116
|
+
return self.valid_nav_groups[self.valid_nav_groups.index(group) + 1]
|
|
68
117
|
|
|
69
118
|
################
|
|
70
119
|
# MAIN QUERIES #
|
|
@@ -85,8 +134,8 @@ class BrowserView(BrowserTitleView):
|
|
|
85
134
|
"""Create queryset common to group & books."""
|
|
86
135
|
qs = self.get_filtered_queryset(model)
|
|
87
136
|
limit = self._get_limit()
|
|
137
|
+
count_qs = self.add_group_by(qs, model)
|
|
88
138
|
try:
|
|
89
|
-
count_qs = self.add_group_by(qs)
|
|
90
139
|
if limit:
|
|
91
140
|
count_qs = count_qs[:limit]
|
|
92
141
|
# Get count after filters and before any annotations or orders
|
|
@@ -99,19 +148,20 @@ class BrowserView(BrowserTitleView):
|
|
|
99
148
|
qs = model.objects.none()
|
|
100
149
|
|
|
101
150
|
if count:
|
|
102
|
-
qs = self.annotate_order_aggregates(qs)
|
|
103
|
-
qs = self.add_order_by(qs)
|
|
151
|
+
qs = self.annotate_order_aggregates(qs, model)
|
|
152
|
+
qs = self.add_order_by(qs, model)
|
|
104
153
|
if limit:
|
|
105
154
|
qs = qs[:limit]
|
|
106
|
-
else:
|
|
107
|
-
qs = qs.order_by("pk")
|
|
108
155
|
|
|
109
156
|
return qs, count
|
|
110
157
|
|
|
111
158
|
def _get_group_queryset(self):
|
|
112
159
|
"""Create group queryset."""
|
|
113
|
-
if self.model
|
|
114
|
-
|
|
160
|
+
if not self.model:
|
|
161
|
+
reason = "Model not set for browser queryset."
|
|
162
|
+
raise ValueError(reason)
|
|
163
|
+
if self.is_model_comic:
|
|
164
|
+
qs = self.model.objects.none()
|
|
115
165
|
count = 0
|
|
116
166
|
else:
|
|
117
167
|
qs, count = self._get_common_queryset(self.model)
|
|
@@ -123,10 +173,28 @@ class BrowserView(BrowserTitleView):
|
|
|
123
173
|
if self.model in (Comic, Folder):
|
|
124
174
|
qs, count = self._get_common_queryset(Comic)
|
|
125
175
|
else:
|
|
126
|
-
qs = Comic.objects.none()
|
|
176
|
+
qs = Comic.objects.none()
|
|
127
177
|
count = 0
|
|
128
178
|
return qs, count
|
|
129
179
|
|
|
180
|
+
def _requery_max_bookmark_updated_at(self, group_qs):
|
|
181
|
+
"""Get max updated at without bookmark filter and aware of multi-groups."""
|
|
182
|
+
if not self.is_bookmark_filtered:
|
|
183
|
+
return group_qs
|
|
184
|
+
if not self.model:
|
|
185
|
+
reason = "No model to compute max bookmark updated_at"
|
|
186
|
+
raise ValueError(reason)
|
|
187
|
+
|
|
188
|
+
qs = self.model.objects.filter(pk=OuterRef("pk"))
|
|
189
|
+
|
|
190
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(self.model)
|
|
191
|
+
bm_updated_at_rel = f"{bm_rel}__updated_at"
|
|
192
|
+
max_bmua = Max(bm_updated_at_rel, default=NONE_DATETIMEFIELD, filter=bm_filter)
|
|
193
|
+
|
|
194
|
+
mbua = qs.aggregate(max=max_bmua)["max"]
|
|
195
|
+
|
|
196
|
+
return group_qs.annotate(max_bookmark_updated_at=Subquery(mbua))
|
|
197
|
+
|
|
130
198
|
@staticmethod
|
|
131
199
|
def _get_zero_pad(book_qs):
|
|
132
200
|
"""Get the zero padding for the display."""
|
|
@@ -139,16 +207,12 @@ class BrowserView(BrowserTitleView):
|
|
|
139
207
|
return zero_pad
|
|
140
208
|
|
|
141
209
|
def _get_page_mtime(self):
|
|
142
|
-
|
|
210
|
+
if not self.is_bookmark_filtered and self.group_instance:
|
|
211
|
+
# Nice optimization if we can get get it.
|
|
212
|
+
return self.group_instance.updated_at
|
|
143
213
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if group_count:
|
|
147
|
-
LOG.debug(group_qs.explain())
|
|
148
|
-
LOG.debug(group_qs.query)
|
|
149
|
-
if book_count:
|
|
150
|
-
LOG.debug(book_qs.explain())
|
|
151
|
-
LOG.debug(book_qs.query)
|
|
214
|
+
group_model = self.group_class if self.group_class else self.model
|
|
215
|
+
return self.get_group_mtime(group_model, page_mtime=True)
|
|
152
216
|
|
|
153
217
|
def _get_group_and_books(self):
|
|
154
218
|
"""Create the main queries with filters, annotation and pagination."""
|
|
@@ -164,22 +228,22 @@ class BrowserView(BrowserTitleView):
|
|
|
164
228
|
|
|
165
229
|
# Annotate
|
|
166
230
|
if page_group_count:
|
|
167
|
-
group_qs = self.annotate_card_aggregates(group_qs)
|
|
168
|
-
group_qs = self.
|
|
231
|
+
group_qs = self.annotate_card_aggregates(group_qs, self.model)
|
|
232
|
+
group_qs = self._requery_max_bookmark_updated_at(group_qs)
|
|
169
233
|
if page_book_count:
|
|
170
234
|
zero_pad = self._get_zero_pad(book_qs)
|
|
171
|
-
book_qs = self.annotate_card_aggregates(book_qs)
|
|
172
|
-
book_qs = self.force_inner_joins(book_qs)
|
|
235
|
+
book_qs = self.annotate_card_aggregates(book_qs, Comic)
|
|
173
236
|
else:
|
|
174
237
|
zero_pad = 1
|
|
175
238
|
|
|
176
|
-
#
|
|
239
|
+
# print(book_qs.explain())
|
|
240
|
+
# print(book_qs.query)
|
|
177
241
|
|
|
178
242
|
total_page_count = page_group_count + page_book_count
|
|
179
243
|
mtime = self._get_page_mtime()
|
|
180
244
|
return group_qs, book_qs, num_pages, total_page_count, zero_pad, mtime
|
|
181
245
|
|
|
182
|
-
def get_object(self):
|
|
246
|
+
def get_object(self):
|
|
183
247
|
"""Validate settings and get the querysets."""
|
|
184
248
|
group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
|
|
185
249
|
self._get_group_and_books()
|
|
@@ -191,6 +255,7 @@ class BrowserView(BrowserTitleView):
|
|
|
191
255
|
# needs to happen after pagination
|
|
192
256
|
# runs obj list query twice :/
|
|
193
257
|
libraries_exist = Library.objects.filter(covers_only=False).exists()
|
|
258
|
+
|
|
194
259
|
# construct final data structure
|
|
195
260
|
return MappingProxyType(
|
|
196
261
|
{
|
|
@@ -213,6 +278,7 @@ class BrowserView(BrowserTitleView):
|
|
|
213
278
|
@extend_schema(parameters=[BrowserTitleView.input_serializer_class])
|
|
214
279
|
def get(self, *_args, **_kwargs):
|
|
215
280
|
"""Get browser settings."""
|
|
281
|
+
self.init_request()
|
|
216
282
|
data = self.get_object()
|
|
217
283
|
serializer = self.get_serializer(data)
|
|
218
284
|
return Response(serializer.data)
|
codex/views/browser/choices.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"""View for marking comics read and unread."""
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
|
-
from typing import Any
|
|
5
4
|
|
|
6
5
|
from caseconverter import snakecase
|
|
7
6
|
from django.db.models import QuerySet
|
|
8
7
|
from drf_spectacular.utils import extend_schema
|
|
9
8
|
from rest_framework.response import Response
|
|
10
9
|
|
|
11
|
-
from codex.choices import DUMMY_NULL_NAME, VUETIFY_NULL_CODE
|
|
12
10
|
from codex.logger.logging import get_logger
|
|
13
11
|
from codex.models import (
|
|
14
12
|
Comic,
|
|
@@ -23,26 +21,24 @@ from codex.serializers.browser.filters import (
|
|
|
23
21
|
BrowserFilterChoicesSerializer,
|
|
24
22
|
)
|
|
25
23
|
from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
|
|
26
|
-
from codex.
|
|
27
|
-
from codex.views.
|
|
28
|
-
|
|
29
|
-
IDENTIFIER_TYPE_UI_FIELD,
|
|
30
|
-
STORY_ARC_UI_FIELD,
|
|
24
|
+
from codex.serializers.choices import DUMMY_NULL_NAME, VUETIFY_NULL_CODE
|
|
25
|
+
from codex.views.browser.filters.annotations import (
|
|
26
|
+
BrowserAnnotationsFilterView,
|
|
31
27
|
)
|
|
32
28
|
|
|
33
29
|
LOG = get_logger(__name__)
|
|
34
30
|
|
|
35
31
|
_FIELD_TO_REL_MODEL_MAP = MappingProxyType(
|
|
36
32
|
{
|
|
37
|
-
CONTRIBUTOR_PERSON_UI_FIELD: (
|
|
33
|
+
BrowserAnnotationsFilterView.CONTRIBUTOR_PERSON_UI_FIELD: (
|
|
38
34
|
"contributors__person",
|
|
39
35
|
ContributorPerson,
|
|
40
36
|
),
|
|
41
|
-
STORY_ARC_UI_FIELD: (
|
|
37
|
+
BrowserAnnotationsFilterView.STORY_ARC_UI_FIELD: (
|
|
42
38
|
"story_arc_numbers__story_arc",
|
|
43
39
|
StoryArc,
|
|
44
40
|
),
|
|
45
|
-
IDENTIFIER_TYPE_UI_FIELD: (
|
|
41
|
+
BrowserAnnotationsFilterView.IDENTIFIER_TYPE_UI_FIELD: (
|
|
46
42
|
"identifiers__identifier_type",
|
|
47
43
|
IdentifierType,
|
|
48
44
|
),
|
|
@@ -58,12 +54,18 @@ _BACK_REL_MAP = MappingProxyType(
|
|
|
58
54
|
_NULL_NAMED_ROW = MappingProxyType({"pk": VUETIFY_NULL_CODE, "name": DUMMY_NULL_NAME})
|
|
59
55
|
|
|
60
56
|
|
|
61
|
-
class BrowserChoicesViewBase(
|
|
57
|
+
class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
62
58
|
"""Get choices for filter dialog."""
|
|
63
59
|
|
|
64
60
|
input_serializer_class = BrowserFilterChoicesInputSerilalizer
|
|
65
61
|
TARGET = "choices"
|
|
66
62
|
|
|
63
|
+
def init_request(self):
|
|
64
|
+
"""Initialieze request."""
|
|
65
|
+
self.parse_params()
|
|
66
|
+
self.set_model()
|
|
67
|
+
self.set_rel_prefix()
|
|
68
|
+
|
|
67
69
|
@staticmethod
|
|
68
70
|
def get_field_choices_query(comic_qs, field_name):
|
|
69
71
|
"""Get distinct values for the field."""
|
|
@@ -98,13 +100,14 @@ class BrowserChoicesViewBase(BrowserFilterView):
|
|
|
98
100
|
|
|
99
101
|
return rel, model
|
|
100
102
|
|
|
101
|
-
def get_object(self)
|
|
103
|
+
def get_object(self):
|
|
102
104
|
"""Get the comic subquery use for the choices."""
|
|
103
105
|
return self.get_filtered_queryset(Comic)
|
|
104
106
|
|
|
105
107
|
@extend_schema(parameters=[input_serializer_class])
|
|
106
108
|
def get(self, *_args, **_kwargs):
|
|
107
109
|
"""Return choices."""
|
|
110
|
+
self.init_request()
|
|
108
111
|
obj = self.get_object()
|
|
109
112
|
serializer = self.get_serializer(obj)
|
|
110
113
|
return Response(serializer.data)
|
|
@@ -136,13 +139,13 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
|
|
|
136
139
|
|
|
137
140
|
return count
|
|
138
141
|
|
|
139
|
-
def get_object(self)
|
|
142
|
+
def get_object(self):
|
|
140
143
|
"""Get choice counts."""
|
|
141
144
|
qs = super().get_object()
|
|
142
145
|
filters = self.params.get("filters", {})
|
|
143
146
|
data = {}
|
|
144
147
|
for field_name in self.serializer_class().get_fields(): # type: ignore
|
|
145
|
-
if field_name == "story_arcs" and
|
|
148
|
+
if field_name == "story_arcs" and self.model == StoryArc:
|
|
146
149
|
# don't allow filtering on story arc in story arc view.
|
|
147
150
|
continue
|
|
148
151
|
rel, m2m_model = self.get_rel_and_model(field_name)
|
|
@@ -185,7 +188,7 @@ class BrowserChoicesView(BrowserChoicesViewBase):
|
|
|
185
188
|
field_name = self.kwargs.get("field_name", "")
|
|
186
189
|
return snakecase(field_name)
|
|
187
190
|
|
|
188
|
-
def get_object(self)
|
|
191
|
+
def get_object(self):
|
|
189
192
|
"""Return choices with more than one choice."""
|
|
190
193
|
qs = super().get_object()
|
|
191
194
|
field_name = self._get_field_name()
|
codex/views/browser/cover.py
CHANGED
|
@@ -7,7 +7,7 @@ from drf_spectacular.types import OpenApiTypes
|
|
|
7
7
|
from drf_spectacular.utils import extend_schema
|
|
8
8
|
from rest_framework.renderers import BaseRenderer
|
|
9
9
|
|
|
10
|
-
from codex.librarian.covers.create import
|
|
10
|
+
from codex.librarian.covers.create import CoverCreateMixin
|
|
11
11
|
from codex.librarian.covers.path import CoverPathMixin
|
|
12
12
|
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
13
13
|
from codex.logger.logging import get_logger
|
|
@@ -15,7 +15,7 @@ from codex.models import Comic, Volume
|
|
|
15
15
|
from codex.models.groups import Folder
|
|
16
16
|
from codex.models.paths import CustomCover
|
|
17
17
|
from codex.serializers.browser.settings import BrowserCoverInputSerializer
|
|
18
|
-
from codex.views.browser.
|
|
18
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
19
19
|
from codex.views.const import (
|
|
20
20
|
CUSTOM_COVER_GROUP_RELATION,
|
|
21
21
|
GROUP_RELATION,
|
|
@@ -41,7 +41,7 @@ class WEBPRenderer(BaseRenderer):
|
|
|
41
41
|
return data
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
class CoverView(
|
|
44
|
+
class CoverView(BrowserAnnotationsView):
|
|
45
45
|
"""ComicCover View."""
|
|
46
46
|
|
|
47
47
|
input_serializer_class = BrowserCoverInputSerializer
|
|
@@ -49,7 +49,7 @@ class CoverView(BrowserAnnotateOrderView):
|
|
|
49
49
|
content_type = "image/webp"
|
|
50
50
|
TARGET = "cover"
|
|
51
51
|
REPARSE_JSON_FIELDS = frozenset(
|
|
52
|
-
|
|
52
|
+
BrowserAnnotationsView.REPARSE_JSON_FIELDS | {"parent"}
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
def get_group_filter(self, group=None, pks=None, page_mtime=False):
|
|
@@ -71,13 +71,22 @@ class CoverView(BrowserAnnotateOrderView):
|
|
|
71
71
|
group_filter[f"{parent_rel}__pk__in"] = parent_pks
|
|
72
72
|
return Q(**group_filter)
|
|
73
73
|
|
|
74
|
+
def get_model_group(self):
|
|
75
|
+
"""Return the url group."""
|
|
76
|
+
return self.kwargs["group"]
|
|
77
|
+
|
|
78
|
+
def init_request(self):
|
|
79
|
+
"""Initialize request."""
|
|
80
|
+
self.parse_params()
|
|
81
|
+
self.set_model()
|
|
82
|
+
|
|
74
83
|
def _get_comic_cover(self):
|
|
75
84
|
pks = self.kwargs["pks"]
|
|
76
85
|
return pks[0], False
|
|
77
86
|
|
|
78
87
|
def _get_custom_cover(self):
|
|
79
88
|
"""Get Custom Cover."""
|
|
80
|
-
if self.model
|
|
89
|
+
if self.model == Volume or not self.params.get("custom_covers"):
|
|
81
90
|
return None
|
|
82
91
|
group = self.kwargs["group"]
|
|
83
92
|
group_rel = CUSTOM_COVER_GROUP_RELATION[group]
|
|
@@ -90,8 +99,8 @@ class CoverView(BrowserAnnotateOrderView):
|
|
|
90
99
|
def _get_dynamic_cover(self):
|
|
91
100
|
"""Get dynamic cover."""
|
|
92
101
|
comic_qs = self.get_filtered_queryset(Comic)
|
|
93
|
-
comic_qs = self.annotate_order_aggregates(comic_qs)
|
|
94
|
-
comic_qs = self.add_order_by(comic_qs)
|
|
102
|
+
comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
|
|
103
|
+
comic_qs = self.add_order_by(comic_qs, Comic)
|
|
95
104
|
comic_qs = comic_qs.only("pk")
|
|
96
105
|
comic = comic_qs.first()
|
|
97
106
|
cover_pk = comic.pk if comic else 0
|
|
@@ -99,7 +108,7 @@ class CoverView(BrowserAnnotateOrderView):
|
|
|
99
108
|
|
|
100
109
|
def _get_cover_pk(self) -> tuple[int, bool]:
|
|
101
110
|
"""Get Cover Pk queryset for comic queryset."""
|
|
102
|
-
if self.model
|
|
111
|
+
if self.model == Comic:
|
|
103
112
|
cover_pk, custom = self._get_comic_cover()
|
|
104
113
|
elif custom_cover := self._get_custom_cover():
|
|
105
114
|
cover_pk = custom_cover.pk
|
|
@@ -127,7 +136,7 @@ class CoverView(BrowserAnnotateOrderView):
|
|
|
127
136
|
|
|
128
137
|
cover_path = CoverPathMixin.get_cover_path(pk, custom)
|
|
129
138
|
if not cover_path.exists():
|
|
130
|
-
thumb_buffer =
|
|
139
|
+
thumb_buffer = CoverCreateMixin.create_cover_from_path(
|
|
131
140
|
pk, cover_path, LOG, LIBRARIAN_QUEUE, custom
|
|
132
141
|
)
|
|
133
142
|
if not thumb_buffer:
|
|
@@ -139,12 +148,13 @@ class CoverView(BrowserAnnotateOrderView):
|
|
|
139
148
|
return cover_file, content_type
|
|
140
149
|
|
|
141
150
|
@extend_schema(
|
|
142
|
-
parameters=[
|
|
151
|
+
parameters=[BrowserAnnotationsView.input_serializer_class],
|
|
143
152
|
responses={(200, content_type): OpenApiTypes.BINARY},
|
|
144
153
|
)
|
|
145
154
|
def get(self, *args, **kwargs): # type: ignore
|
|
146
155
|
"""Get comic cover."""
|
|
147
156
|
try:
|
|
157
|
+
self.init_request()
|
|
148
158
|
try:
|
|
149
159
|
pk, custom = self._get_cover_pk()
|
|
150
160
|
except OperationalError as exc:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Annotations used by a filter."""
|
|
2
|
+
|
|
3
|
+
from logging import DEBUG, WARNING
|
|
4
|
+
|
|
5
|
+
from django.db.models.aggregates import Count, Max
|
|
6
|
+
from django.db.models.functions import Coalesce, Greatest
|
|
7
|
+
from django.db.utils import OperationalError
|
|
8
|
+
|
|
9
|
+
from codex.logger.logging import get_logger
|
|
10
|
+
from codex.models.comic import Comic
|
|
11
|
+
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
12
|
+
from codex.views.browser.validate import BrowserValidateView
|
|
13
|
+
from codex.views.const import (
|
|
14
|
+
EPOCH_START_DATETIMEFIELD,
|
|
15
|
+
ONE_INTEGERFIELD,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
_CHILD_COUNT = "child_count"
|
|
19
|
+
_FTS5_PREFIX = "fts5: "
|
|
20
|
+
LOG = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
24
|
+
"""Annotations that also filter."""
|
|
25
|
+
|
|
26
|
+
TARGET = ""
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
29
|
+
"""Initialize."""
|
|
30
|
+
super().__init__(*args, **kwargs)
|
|
31
|
+
self.init_bookmark_data()
|
|
32
|
+
|
|
33
|
+
def _get_query_filters( # noqa: PLR0913
|
|
34
|
+
self, model, group, pks, bookmark_filter, page_mtime=False, group_filter=True
|
|
35
|
+
):
|
|
36
|
+
"""Return all the filters except the group filter."""
|
|
37
|
+
# XXX all these ifs and flags are awful
|
|
38
|
+
object_filter = self.get_group_acl_filter(model)
|
|
39
|
+
if group_filter:
|
|
40
|
+
object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
|
|
41
|
+
object_filter &= self.get_comic_field_filter(model)
|
|
42
|
+
if bookmark_filter:
|
|
43
|
+
# not needed when used by outerrefl OuterRef is applied next
|
|
44
|
+
object_filter &= self.get_bookmark_filter(model)
|
|
45
|
+
return object_filter
|
|
46
|
+
|
|
47
|
+
def _filter_by_child_count(self, qs, model):
|
|
48
|
+
"""Filter group by child count."""
|
|
49
|
+
rel = self.rel_prefix + "pk"
|
|
50
|
+
count_func = ONE_INTEGERFIELD if model == Comic else Count(rel, distinct=True)
|
|
51
|
+
ann = {_CHILD_COUNT: count_func}
|
|
52
|
+
if self.TARGET == "opds2":
|
|
53
|
+
if model != Comic:
|
|
54
|
+
qs = qs.alias(**ann)
|
|
55
|
+
else:
|
|
56
|
+
qs = qs.annotate(**ann)
|
|
57
|
+
if model != Comic:
|
|
58
|
+
qs = qs.filter(**{f"{_CHILD_COUNT}__gt": 0})
|
|
59
|
+
return qs
|
|
60
|
+
|
|
61
|
+
def get_filtered_queryset( # noqa: PLR0913
|
|
62
|
+
self,
|
|
63
|
+
model,
|
|
64
|
+
group=None,
|
|
65
|
+
pks=None,
|
|
66
|
+
bookmark_filter=True,
|
|
67
|
+
page_mtime=False,
|
|
68
|
+
group_filter=True,
|
|
69
|
+
):
|
|
70
|
+
"""Get a filtered queryset for the model."""
|
|
71
|
+
object_filter = self._get_query_filters(
|
|
72
|
+
model,
|
|
73
|
+
group,
|
|
74
|
+
pks,
|
|
75
|
+
bookmark_filter,
|
|
76
|
+
page_mtime=page_mtime,
|
|
77
|
+
group_filter=group_filter,
|
|
78
|
+
)
|
|
79
|
+
qs = model.objects.filter(object_filter)
|
|
80
|
+
qs = self.apply_search_filter(qs, model)
|
|
81
|
+
return self._filter_by_child_count(qs, model)
|
|
82
|
+
|
|
83
|
+
def _handle_operational_error(self, err):
|
|
84
|
+
msg = err.args[0] if err.args else ""
|
|
85
|
+
if msg.startswith(_FTS5_PREFIX):
|
|
86
|
+
level = DEBUG
|
|
87
|
+
self.search_error = msg.removeprefix(_FTS5_PREFIX)
|
|
88
|
+
else:
|
|
89
|
+
level = WARNING
|
|
90
|
+
msg = str(err)
|
|
91
|
+
LOG.log(level, f"Query Error: {msg}")
|
|
92
|
+
|
|
93
|
+
def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
|
|
94
|
+
"""Get a filtered mtime for browser pages and mtime checker."""
|
|
95
|
+
qs = self.get_filtered_queryset(
|
|
96
|
+
model,
|
|
97
|
+
group=group,
|
|
98
|
+
pks=pks,
|
|
99
|
+
bookmark_filter=self.is_bookmark_filtered,
|
|
100
|
+
page_mtime=page_mtime,
|
|
101
|
+
)
|
|
102
|
+
qs = qs.annotate(max_updated_at=Max("updated_at"))
|
|
103
|
+
|
|
104
|
+
if self.is_bookmark_filtered:
|
|
105
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
|
|
106
|
+
qs = qs.filter(bm_filter)
|
|
107
|
+
ua_rel = bm_rel + "__updated_at"
|
|
108
|
+
mbua = Max(ua_rel)
|
|
109
|
+
else:
|
|
110
|
+
mbua = EPOCH_START_DATETIMEFIELD
|
|
111
|
+
qs = qs.annotate(max_bookmark_updated_at=mbua)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
mtime = qs.aggregate(
|
|
115
|
+
max=Greatest(
|
|
116
|
+
Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFIELD),
|
|
117
|
+
"max_updated_at",
|
|
118
|
+
)
|
|
119
|
+
)["max"]
|
|
120
|
+
except OperationalError as exc:
|
|
121
|
+
self._handle_operational_error(exc)
|
|
122
|
+
mtime = None
|
|
123
|
+
if mtime == NotImplemented:
|
|
124
|
+
mtime = None
|
|
125
|
+
return mtime
|
|
@@ -4,45 +4,46 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
|
|
7
|
-
from codex.views.browser.validate import BrowserValidateView
|
|
8
|
-
|
|
9
7
|
if TYPE_CHECKING:
|
|
10
8
|
from codex.models import BrowserGroupModel
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
class
|
|
11
|
+
class BookmarkFilterMixin:
|
|
14
12
|
"""BookmarkFilter view methods."""
|
|
15
13
|
|
|
16
|
-
def
|
|
14
|
+
def init_bookmark_data(self):
|
|
17
15
|
"""Initialize the bm_annotation_data."""
|
|
18
|
-
|
|
19
|
-
self._bm_rels: dict[BrowserGroupModel, str] = {}
|
|
20
|
-
self._bm_filters: dict[BrowserGroupModel, Q] = {}
|
|
16
|
+
self._bm_annotation_data: dict[BrowserGroupModel, tuple[str, Q]] = {}
|
|
21
17
|
|
|
22
18
|
def _get_bm_rel(self, model):
|
|
23
19
|
"""Create bookmark relation."""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
self._bm_rels[model] = rel_prefix + "bookmark"
|
|
27
|
-
return self._bm_rels[model]
|
|
20
|
+
rel_prefix = self.get_rel_prefix(model) # type: ignore
|
|
21
|
+
return rel_prefix + "bookmark"
|
|
28
22
|
|
|
29
23
|
def _get_my_bookmark_filter(self, bm_rel):
|
|
30
24
|
"""Get a filter for my session or user defined bookmarks."""
|
|
31
|
-
if self.request.user
|
|
32
|
-
|
|
33
|
-
value = self.request.user
|
|
25
|
+
if self.request.user.is_authenticated: # type: ignore
|
|
26
|
+
my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
|
|
34
27
|
else:
|
|
35
28
|
key = f"{bm_rel}__session__session_key"
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
my_bookmarks_kwargs = {
|
|
30
|
+
key: self.request.session.session_key # type: ignore
|
|
31
|
+
}
|
|
38
32
|
return Q(**my_bookmarks_kwargs)
|
|
39
33
|
|
|
34
|
+
def get_bookmark_rel_and_filter(self, model):
|
|
35
|
+
"""Get the bookmark rel and filter once."""
|
|
36
|
+
if model not in self._bm_annotation_data:
|
|
37
|
+
bm_rel = self._get_bm_rel(model)
|
|
38
|
+
bm_filter = self._get_my_bookmark_filter(bm_rel)
|
|
39
|
+
self._bm_annotation_data[model] = (bm_rel, bm_filter)
|
|
40
|
+
return self._bm_annotation_data[model]
|
|
41
|
+
|
|
40
42
|
def get_bookmark_filter(self, model):
|
|
41
43
|
"""Build bookmark query."""
|
|
42
44
|
choice: str = self.params.get("filters", {}).get("bookmark", "") # type: ignore
|
|
43
45
|
if choice:
|
|
44
|
-
bm_rel = self.
|
|
45
|
-
my_bookmark_filter = self._get_my_bookmark_filter(bm_rel)
|
|
46
|
+
bm_rel, my_bookmark_filter = self.get_bookmark_rel_and_filter(model)
|
|
46
47
|
if choice == "READ":
|
|
47
48
|
bookmark_filter = my_bookmark_filter & Q(
|
|
48
49
|
**{f"{bm_rel}__finished": True}
|
|
@@ -5,17 +5,12 @@ from types import MappingProxyType
|
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
|
|
7
7
|
from codex.views.browser.filters.group import GroupFilterView
|
|
8
|
-
from codex.views.session import (
|
|
9
|
-
CONTRIBUTOR_PERSON_UI_FIELD,
|
|
10
|
-
IDENTIFIER_TYPE_UI_FIELD,
|
|
11
|
-
STORY_ARC_UI_FIELD,
|
|
12
|
-
)
|
|
13
8
|
|
|
14
9
|
_FILTER_REL_MAP = MappingProxyType(
|
|
15
10
|
{
|
|
16
|
-
CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
|
|
17
|
-
STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
|
|
18
|
-
IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
|
|
11
|
+
GroupFilterView.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
|
|
12
|
+
GroupFilterView.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
|
|
13
|
+
GroupFilterView.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
|
|
19
14
|
}
|
|
20
15
|
)
|
|
21
16
|
|