codex 1.6.0a6__py3-none-any.whl → 1.6.0a7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of codex might be problematic. Click here for more details.
- codex/exceptions.py +1 -0
- codex/librarian/importer/aggregate_metadata.py +2 -10
- codex/librarian/importer/{covers.py → cache.py} +2 -2
- codex/librarian/importer/importerd.py +5 -3
- codex/librarian/janitor/janitor.py +1 -1
- codex/librarian/librariand.py +1 -1
- codex/permissions.py +1 -1
- codex/serializers/admin.py +1 -2
- codex/serializers/browser/filters.py +8 -101
- codex/serializers/browser/mixins.py +20 -3
- codex/serializers/browser/page.py +2 -2
- codex/serializers/browser/settings.py +2 -0
- codex/serializers/fields.py +123 -0
- codex/serializers/mtime.py +25 -0
- codex/serializers/opds/v2.py +7 -11
- codex/serializers/opds/v2_unused.py +5 -3
- codex/serializers/reader.py +5 -2
- codex/serializers/route.py +7 -3
- codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js +1 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.js +1 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js +1 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js +1 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js +1 -0
- codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.js +1 -0
- codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js +1 -0
- codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.js +1 -0
- codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js +1 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.js +1 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js +1 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.js +1 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +1 -0
- codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.js +1 -0
- codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js +1 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.js +1 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js +1 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.js +1 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
- codex/static_root/assets/{VTable-C0gAriFq.fa6e4b60d4ef.js → VTable-C7pKI7gU.087912f706f5.js} +1 -1
- codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
- codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
- codex/static_root/assets/{VTable-C0gAriFq.js → VTable-C7pKI7gU.js} +1 -1
- codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
- codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
- codex/static_root/assets/{VTextField-Da_Hs7ls.c295f24c251e.js → VTextField-Bs8oq9mk.7999fef12a03.js} +1 -1
- codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
- codex/static_root/assets/{VTextField-Da_Hs7ls.js → VTextField-Bs8oq9mk.js} +1 -1
- codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-CFhKCj49.b2458002a937.js → VWindowItem-iyZ1XOkP.409f7d292253.js} +1 -1
- codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
- codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-CFhKCj49.js → VWindowItem-iyZ1XOkP.js} +1 -1
- codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
- codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
- codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js +1 -0
- codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
- codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
- codex/static_root/assets/admin-B0pCBjma.js +1 -0
- codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
- codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js +30 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js +30 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
- codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
- codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
- codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +1 -0
- codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
- codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
- codex/static_root/assets/browser-DbyAjCNh.js +1 -0
- codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
- codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
- codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +1 -0
- codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
- codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
- codex/static_root/assets/browser-VnzNj1t3.css +1 -0
- codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
- codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
- codex/static_root/assets/{change-password-dialog-JYM9aAR9.f71d378f4fa6.js → change-password-dialog-DZ9dP8_F.930f4cae3cb0.js} +1 -1
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-JYM9aAR9.js → change-password-dialog-DZ9dP8_F.js} +1 -1
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.515858dd21e8.js → confirm-dialog-RP7JNStF.a752a7c1e697.js} +1 -1
- codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
- codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.js → confirm-dialog-RP7JNStF.js} +1 -1
- codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
- codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
- codex/static_root/assets/{datetime-column-BT1MSP82.9617f6b94ebf.js → datetime-column-Cu3WYyPj.54b4c3817b74.js} +1 -1
- codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
- codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BT1MSP82.js → datetime-column-Cu3WYyPj.js} +1 -1
- codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
- codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
- codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js +1 -0
- codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
- codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
- codex/static_root/assets/filter-DAdGUt-1.js +1 -0
- codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
- codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +1 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.js +1 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
- codex/static_root/assets/{group-tab-DFma2R-E.879c37b15bbd.js → group-tab-DEwh4jfB.a95097b5e320.js} +1 -1
- codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
- codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
- codex/static_root/assets/{group-tab-DFma2R-E.js → group-tab-DEwh4jfB.js} +1 -1
- codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
- codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +1 -0
- codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.js +1 -0
- codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
- codex/static_root/assets/{library-tab-V8DUGoHN.809c548ff347.js → library-tab-DrXvD9B2.f765f9d3bae4.js} +1 -1
- codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
- codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
- codex/static_root/assets/{library-tab-V8DUGoHN.js → library-tab-DrXvD9B2.js} +1 -1
- codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
- codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +32 -0
- codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.js +32 -0
- codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
- codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
- codex/static_root/assets/main-Ztp855PW.css.br +0 -0
- codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
- codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
- codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
- codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +1 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +1 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-ClZui9Zf.697d7ae11c21.css → pagination-toolbar-r6nbtWxg.4088ab042c0c.css} +1 -1
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-ClZui9Zf.css → pagination-toolbar-r6nbtWxg.css} +1 -1
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js +305 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.js +305 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
- codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +2 -0
- codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
- codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
- codex/static_root/assets/reader-BXKUaUeC.js +2 -0
- codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
- codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
- codex/static_root/assets/{reader-N2Ei_y2K.8c0cdc0d513f.css → reader-OXJ1KVaW.a463b19d0508.css} +1 -1
- codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
- codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
- codex/static_root/assets/{reader-N2Ei_y2K.css → reader-OXJ1KVaW.css} +1 -1
- codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
- codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js +1 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.js +1 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js +2 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.js +2 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js +1 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.js +1 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +1 -0
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.js +1 -0
- codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
- codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +1 -0
- codex/static_root/assets/to-case-CR9beRR0.js +1 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js +1 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.js +1 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js +1 -0
- codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.js +1 -0
- codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
- codex/static_root/js/choices.079a01e0be1a.json +1 -0
- codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
- codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
- codex/static_root/js/choices.json +1 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/manifest.dad79c8475ae.json +629 -0
- codex/static_root/manifest.dad79c8475ae.json.br +0 -0
- codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
- codex/static_root/manifest.json +263 -276
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/urls/api/auth.py +2 -1
- codex/urls/api/browser.py +2 -2
- codex/urls/api/reader.py +2 -2
- codex/urls/api/v3.py +2 -0
- codex/util.py +10 -0
- codex/views/admin/api_key.py +2 -6
- codex/views/admin/auth.py +30 -0
- codex/views/admin/flag.py +2 -7
- codex/views/admin/group.py +2 -6
- codex/views/admin/library.py +7 -20
- codex/views/admin/stats.py +7 -66
- codex/views/admin/tasks.py +12 -11
- codex/views/admin/user.py +3 -9
- codex/views/auth.py +31 -52
- codex/views/bookmark.py +7 -11
- codex/views/browser/{browser_annotations.py → annotations.py} +91 -61
- codex/views/browser/base.py +23 -71
- codex/views/browser/breadcrumbs.py +200 -0
- codex/views/browser/browser.py +64 -337
- codex/views/browser/choices.py +25 -33
- codex/views/browser/filters/annotations.py +2 -2
- codex/views/browser/filters/bookmark.py +9 -13
- codex/views/browser/filters/field.py +6 -5
- codex/views/browser/filters/group.py +8 -5
- codex/views/browser/filters/search.py +11 -8
- codex/views/browser/metadata.py +3 -7
- codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
- codex/views/browser/paginate.py +106 -0
- codex/views/browser/{session.py → settings.py} +4 -3
- codex/views/browser/title.py +48 -0
- codex/views/browser/validate.py +158 -0
- codex/views/const.py +97 -0
- codex/views/cover.py +4 -6
- codex/views/download.py +5 -8
- codex/views/frontend.py +4 -2
- codex/views/mtime.py +70 -0
- codex/views/opds/auth.py +17 -0
- codex/views/opds/authentication_v1.py +46 -45
- codex/views/opds/binary.py +4 -9
- codex/views/opds/const.py +13 -0
- codex/views/opds/urls.py +5 -4
- codex/views/opds/util.py +2 -8
- codex/views/opds/v1/facets.py +6 -7
- codex/views/opds/v1/feed.py +10 -17
- codex/views/opds/v1/links.py +6 -5
- codex/views/opds/v1/opensearch_v1.py +2 -6
- codex/views/opds/v2/feed.py +12 -15
- codex/views/opds/v2/links.py +3 -2
- codex/views/opds/v2/publications.py +21 -20
- codex/views/opds/v2/top_links.py +2 -2
- codex/views/public.py +38 -0
- codex/views/reader/arcs.py +101 -0
- codex/views/reader/books.py +199 -0
- codex/views/reader/page.py +3 -7
- codex/views/reader/reader.py +53 -298
- codex/views/reader/{session.py → settings.py} +4 -2
- codex/views/session.py +74 -102
- codex/views/settings.py +34 -0
- codex/views/template.py +3 -3
- codex/views/util.py +31 -0
- codex/views/utils.py +26 -0
- codex/views/version.py +2 -2
- {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/METADATA +12 -8
- {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/RECORD +332 -320
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js +0 -1
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.js +0 -1
- codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js +0 -1
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js +0 -1
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +0 -1
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js +0 -1
- codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js +0 -1
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.js +0 -1
- codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js +0 -1
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js +0 -1
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js +0 -1
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.js +0 -1
- codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +0 -1
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.js +0 -1
- codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js +0 -1
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js +0 -1
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js +0 -1
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js +0 -1
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
- codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
- codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
- codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
- codex/static_root/assets/VTable-C0gAriFq.js.gz +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.js.gz +0 -0
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js +0 -1
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
- codex/static_root/assets/admin-BpGFsvOH.js +0 -1
- codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
- codex/static_root/assets/admin-BpGFsvOH.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +0 -30
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +0 -30
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +0 -1
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
- codex/static_root/assets/browser-CRPvEdqv.js +0 -1
- codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
- codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +0 -1
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.css +0 -1
- codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js +0 -1
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
- codex/static_root/assets/filter-frSNsexx.js +0 -1
- codex/static_root/assets/filter-frSNsexx.js.br +0 -0
- codex/static_root/assets/filter-frSNsexx.js.gz +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js +0 -1
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js +0 -1
- codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js +0 -1
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.js +0 -1
- codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.js.gz +0 -0
- codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
- codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
- codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
- codex/static_root/assets/index-DUjeNg4x.js +0 -1
- codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
- codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js +0 -32
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
- codex/static_root/assets/main-Cavvqkyp.js +0 -32
- codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
- codex/static_root/assets/main-Cavvqkyp.js.gz +0 -0
- codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
- codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js +0 -1
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +0 -1
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js +0 -35
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js +0 -35
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js +0 -2
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
- codex/static_root/assets/reader-C39uGGxn.js +0 -2
- codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
- codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.css.gz +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js +0 -1
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js +0 -1
- codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js +0 -2
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.js +0 -2
- codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js +0 -1
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.js +0 -1
- codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +0 -1
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js +0 -1
- codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
- codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
- codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js +0 -1
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.js +0 -1
- codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js +0 -1
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.js +0 -1
- codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.js.gz +0 -0
- codex/static_root/js/choices.23fedac2b9ba.json +0 -1
- codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
- codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
- codex/static_root/manifest.ad9da9714e46.json +0 -642
- codex/static_root/manifest.ad9da9714e46.json.br +0 -0
- codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
- codex/views/browser/browser_breadcrumbs.py +0 -175
- codex/views/browser/const.py +0 -3
- {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/LICENSE +0 -0
- {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/WHEEL +0 -0
- {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/entry_points.txt +0 -0
codex/views/auth.py
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
"""Views authorization."""
|
|
2
2
|
|
|
3
|
-
from types import MappingProxyType
|
|
4
|
-
|
|
5
|
-
from django.conf import settings
|
|
6
3
|
from django.contrib.auth.models import AnonymousUser
|
|
7
4
|
from django.db.models import Q
|
|
8
5
|
from drf_spectacular.utils import extend_schema
|
|
9
6
|
from rest_framework.generics import GenericAPIView
|
|
10
|
-
from rest_framework.
|
|
7
|
+
from rest_framework.parsers import JSONParser
|
|
11
8
|
from rest_framework.permissions import IsAuthenticated
|
|
12
9
|
from rest_framework.response import Response
|
|
10
|
+
from rest_framework.views import APIView
|
|
13
11
|
|
|
14
12
|
from codex.logger.logging import get_logger
|
|
15
13
|
from codex.models import AdminFlag, Comic, Folder, StoryArc, UserActive
|
|
16
|
-
from codex.serializers.auth import
|
|
17
|
-
from codex.serializers.choices import CHOICES
|
|
14
|
+
from codex.serializers.auth import TimezoneSerializer
|
|
18
15
|
from codex.serializers.mixins import OKSerializer
|
|
19
|
-
from codex.views.const import GROUP_NAME_MAP
|
|
20
16
|
|
|
21
17
|
LOG = get_logger(__name__)
|
|
22
|
-
NULL_USER = {"pk": None, "username": None, "is_staff": False}
|
|
23
18
|
|
|
24
19
|
|
|
25
20
|
class IsAuthenticatedOrEnabledNonUsers(IsAuthenticated):
|
|
@@ -37,20 +32,35 @@ class IsAuthenticatedOrEnabledNonUsers(IsAuthenticated):
|
|
|
37
32
|
return super().has_permission(request, view)
|
|
38
33
|
|
|
39
34
|
|
|
40
|
-
class
|
|
35
|
+
class AuthMixin:
|
|
36
|
+
"""General Auth Policy."""
|
|
37
|
+
|
|
38
|
+
parser_classes = (JSONParser,)
|
|
39
|
+
permission_classes = (IsAuthenticatedOrEnabledNonUsers,)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AuthAPIView(AuthMixin, APIView):
|
|
43
|
+
"""Auth Policy APIView."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AuthGenericAPIView(AuthMixin, GenericAPIView):
|
|
47
|
+
"""Auth Policy GenericAPIView."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TimezoneView(AuthGenericAPIView):
|
|
41
51
|
"""User info."""
|
|
42
52
|
|
|
53
|
+
parser_classes = (JSONParser,)
|
|
43
54
|
input_serializer_class = TimezoneSerializer
|
|
44
55
|
serializer_class = OKSerializer
|
|
45
56
|
|
|
46
|
-
_AUTH_USER_MODEL_TYPE = type(settings.AUTH_USER_MODEL)
|
|
47
|
-
|
|
48
57
|
@extend_schema(request=input_serializer_class)
|
|
49
58
|
def post(self, request, *args, **kwargs):
|
|
50
59
|
"""Get the user info for the current user."""
|
|
51
|
-
|
|
60
|
+
data = self.request.data # type: ignore
|
|
61
|
+
serializer = self.input_serializer_class(data=data)
|
|
52
62
|
serializer.is_valid(raise_exception=True)
|
|
53
|
-
request.session["django_timezone"] = serializer.validated_data["timezone"]
|
|
63
|
+
request.session["django_timezone"] = serializer.validated_data["timezone"] # type: ignore
|
|
54
64
|
request.session.save()
|
|
55
65
|
user = self.request.user
|
|
56
66
|
if user.is_authenticated:
|
|
@@ -59,48 +69,9 @@ class TimezoneView(GenericAPIView):
|
|
|
59
69
|
return Response(serializer.data)
|
|
60
70
|
|
|
61
71
|
|
|
62
|
-
class AdminFlagsView(GenericAPIView, RetrieveModelMixin):
|
|
63
|
-
"""Get admin flags relevant to auth."""
|
|
64
|
-
|
|
65
|
-
_ADMIN_FLAG_KEYS = frozenset(
|
|
66
|
-
(
|
|
67
|
-
AdminFlag.FlagChoices.NON_USERS.value,
|
|
68
|
-
AdminFlag.FlagChoices.REGISTRATION.value,
|
|
69
|
-
)
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
serializer_class = AuthAdminFlagsSerializer
|
|
73
|
-
queryset = AdminFlag.objects.filter(key__in=_ADMIN_FLAG_KEYS).only("key", "on")
|
|
74
|
-
|
|
75
|
-
def get_object(self):
|
|
76
|
-
"""Get admin flags."""
|
|
77
|
-
flags = {}
|
|
78
|
-
for obj in self.get_queryset(): # type: ignore
|
|
79
|
-
name = CHOICES["admin"]["adminFlags"][obj.key].lower().replace(" ", "_")
|
|
80
|
-
flags[name] = obj.on
|
|
81
|
-
return flags
|
|
82
|
-
|
|
83
|
-
def get(self, request, *args, **kwargs):
|
|
84
|
-
"""Get admin flags relevant to auth."""
|
|
85
|
-
return self.retrieve(request, *args, **kwargs)
|
|
86
|
-
|
|
87
|
-
|
|
88
72
|
class GroupACLMixin:
|
|
89
73
|
"""Filter group ACLS for views."""
|
|
90
74
|
|
|
91
|
-
ROOT_GROUP = "r"
|
|
92
|
-
FOLDER_GROUP = "f"
|
|
93
|
-
STORY_ARC_GROUP = "a"
|
|
94
|
-
COMIC_GROUP = "c"
|
|
95
|
-
GROUP_RELATION = MappingProxyType(
|
|
96
|
-
{
|
|
97
|
-
**GROUP_NAME_MAP,
|
|
98
|
-
COMIC_GROUP: "pk",
|
|
99
|
-
FOLDER_GROUP: "parent_folder",
|
|
100
|
-
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
101
|
-
}
|
|
102
|
-
)
|
|
103
|
-
|
|
104
75
|
def get_rel_prefix(self, model):
|
|
105
76
|
"""Return the relation prfiex for most fields."""
|
|
106
77
|
prefix = ""
|
|
@@ -133,3 +104,11 @@ class GroupACLMixin:
|
|
|
133
104
|
query |= auth_query
|
|
134
105
|
|
|
135
106
|
return query
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class AuthFilterGenericAPIView(AuthGenericAPIView, GroupACLMixin):
|
|
110
|
+
"""Auth Enabled GenericAPIView."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class AuthFilterAPIView(AuthAPIView, GroupACLMixin):
|
|
114
|
+
"""Auth Enabled APIView."""
|
codex/views/bookmark.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"""Bookmark views."""
|
|
2
2
|
|
|
3
|
-
from typing import ClassVar
|
|
4
|
-
|
|
5
3
|
from drf_spectacular.utils import extend_schema
|
|
6
|
-
from rest_framework.generics import GenericAPIView
|
|
7
4
|
from rest_framework.response import Response
|
|
8
5
|
|
|
9
6
|
from codex.logger.logging import get_logger
|
|
@@ -12,13 +9,16 @@ from codex.serializers.models.bookmark import (
|
|
|
12
9
|
BookmarkFinishedSerializer,
|
|
13
10
|
BookmarkSerializer,
|
|
14
11
|
)
|
|
15
|
-
from codex.views.auth import
|
|
12
|
+
from codex.views.auth import (
|
|
13
|
+
AuthFilterGenericAPIView,
|
|
14
|
+
)
|
|
15
|
+
from codex.views.const import GROUP_RELATION
|
|
16
16
|
|
|
17
17
|
LOG = get_logger(__name__)
|
|
18
18
|
VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class BookmarkBaseView(
|
|
21
|
+
class BookmarkBaseView(AuthFilterGenericAPIView):
|
|
22
22
|
"""Bookmark Updater."""
|
|
23
23
|
|
|
24
24
|
_BOOKMARK_UPDATE_FIELDS = (
|
|
@@ -160,12 +160,11 @@ class BookmarkBaseView(GenericAPIView, GroupACLMixin):
|
|
|
160
160
|
class BookmarkView(BookmarkBaseView):
|
|
161
161
|
"""User Bookmark View."""
|
|
162
162
|
|
|
163
|
-
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type:ignore
|
|
164
163
|
serializer_class = BookmarkSerializer
|
|
165
164
|
|
|
166
165
|
def _validate(self, serializer_class):
|
|
167
166
|
"""Validate and translate the submitted data."""
|
|
168
|
-
data = self.request.data
|
|
167
|
+
data = self.request.data # type: ignore
|
|
169
168
|
if serializer_class:
|
|
170
169
|
serializer = serializer_class(data=data, partial=True)
|
|
171
170
|
else:
|
|
@@ -184,10 +183,7 @@ class BookmarkView(BookmarkBaseView):
|
|
|
184
183
|
|
|
185
184
|
pks = self.kwargs.get("pks")
|
|
186
185
|
|
|
187
|
-
if group == "f"
|
|
188
|
-
relation = "folders__in"
|
|
189
|
-
else:
|
|
190
|
-
relation = self.GROUP_RELATION[group] + "__in"
|
|
186
|
+
relation = "folders__in" if group == "f" else GROUP_RELATION[group] + "__in"
|
|
191
187
|
comic_filter = {relation: pks}
|
|
192
188
|
|
|
193
189
|
self.update_bookmarks(updates, comic_filter=comic_filter)
|
|
@@ -31,9 +31,10 @@ from codex.models import (
|
|
|
31
31
|
Volume,
|
|
32
32
|
)
|
|
33
33
|
from codex.models.functions import JsonGroupArray
|
|
34
|
-
from codex.views.browser.
|
|
34
|
+
from codex.views.browser.order_by import (
|
|
35
35
|
BrowserOrderByView,
|
|
36
36
|
)
|
|
37
|
+
from codex.views.const import STORY_ARC_GROUP
|
|
37
38
|
from codex.views.mixins import SharedAnnotationsMixin
|
|
38
39
|
|
|
39
40
|
_REL_MAP = MappingProxyType(
|
|
@@ -46,40 +47,39 @@ _REL_MAP = MappingProxyType(
|
|
|
46
47
|
StoryArc: "story_arc_numbers__story_arc",
|
|
47
48
|
}
|
|
48
49
|
)
|
|
50
|
+
_NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
|
|
51
|
+
_NONE_DATETIMEFIELD = Value(None, DateTimeField())
|
|
52
|
+
_ORDER_AGGREGATE_FUNCS = MappingProxyType(
|
|
53
|
+
# These are annotated to order_value because they're simple relations
|
|
54
|
+
{
|
|
55
|
+
"age_rating": Avg,
|
|
56
|
+
"community_rating": Avg,
|
|
57
|
+
"created_at": Min,
|
|
58
|
+
"critical_rating": Avg,
|
|
59
|
+
"date": Min,
|
|
60
|
+
"page_count": Sum,
|
|
61
|
+
"size": Sum,
|
|
62
|
+
"updated_at": Min,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
66
|
+
# These are annotated with their own functions
|
|
67
|
+
{
|
|
68
|
+
"filename",
|
|
69
|
+
"sort_name",
|
|
70
|
+
"search_score",
|
|
71
|
+
"bookmark_updated_at",
|
|
72
|
+
"story_arc_number",
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
_ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingProxyType(
|
|
76
|
+
{Comic: "id", Volume: "name"}
|
|
77
|
+
)
|
|
49
78
|
|
|
50
79
|
|
|
51
80
|
class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
52
81
|
"""Base class for views that need special metadata annotations."""
|
|
53
82
|
|
|
54
|
-
_NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
|
|
55
|
-
_NONE_DATETIMEFIELD = Value(None, DateTimeField())
|
|
56
|
-
_ORDER_AGGREGATE_FUNCS = MappingProxyType(
|
|
57
|
-
# These are annotated to order_value because they're simple relations
|
|
58
|
-
{
|
|
59
|
-
"age_rating": Avg,
|
|
60
|
-
"community_rating": Avg,
|
|
61
|
-
"created_at": Min,
|
|
62
|
-
"critical_rating": Avg,
|
|
63
|
-
"date": Min,
|
|
64
|
-
"page_count": Sum,
|
|
65
|
-
"size": Sum,
|
|
66
|
-
"updated_at": Min,
|
|
67
|
-
}
|
|
68
|
-
)
|
|
69
|
-
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
70
|
-
# These are annotated with their own functions
|
|
71
|
-
{
|
|
72
|
-
"filename",
|
|
73
|
-
"sort_name",
|
|
74
|
-
"search_score",
|
|
75
|
-
"bookmark_updated_at",
|
|
76
|
-
"story_arc_number",
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
_ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = (
|
|
80
|
-
MappingProxyType({Comic: "id", Volume: "name"})
|
|
81
|
-
)
|
|
82
|
-
|
|
83
83
|
def __init__(self, *args, **kwargs):
|
|
84
84
|
"""Set params for the type checker."""
|
|
85
85
|
super().__init__(*args, **kwargs)
|
|
@@ -92,7 +92,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
92
92
|
"""Get the group by for the model."""
|
|
93
93
|
if model is None:
|
|
94
94
|
model = self.model
|
|
95
|
-
return
|
|
95
|
+
return _ALTERNATE_GROUP_BY.get(model, "sort_name") # type: ignore
|
|
96
96
|
|
|
97
97
|
def _alias_sort_names(self, qs, model):
|
|
98
98
|
"""Annotate sort_name."""
|
|
@@ -133,7 +133,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
133
133
|
# Get story_arc__pk
|
|
134
134
|
group = self.kwargs["group"]
|
|
135
135
|
pks = self.kwargs["pks"]
|
|
136
|
-
if group ==
|
|
136
|
+
if group == STORY_ARC_GROUP and pks:
|
|
137
137
|
story_arc_pks = pks
|
|
138
138
|
else:
|
|
139
139
|
story_arc_pks = self.params.get("filters", {}).get( # type: ignore
|
|
@@ -150,7 +150,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
150
150
|
)
|
|
151
151
|
story_arc_number = self.order_agg_func("selected_story_arc_number__number")
|
|
152
152
|
else:
|
|
153
|
-
story_arc_number =
|
|
153
|
+
story_arc_number = _NONE_INTEGERFIELD
|
|
154
154
|
|
|
155
155
|
return qs.alias(story_arc_number=story_arc_number)
|
|
156
156
|
|
|
@@ -168,19 +168,30 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
168
168
|
qs = qs.annotate(page_count=page_count_sum)
|
|
169
169
|
return qs
|
|
170
170
|
|
|
171
|
-
def
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
def get_bookmark_updated_at_aggregate(self, model, always_max=False):
|
|
172
|
+
"""Get The aggregate function for relevant bookmark.updated_at."""
|
|
173
|
+
bm_rel = self.get_bm_rel(model)
|
|
174
|
+
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
175
|
+
self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
|
|
176
|
+
|
|
177
|
+
updated_at_rel = f"{bm_rel}__updated_at"
|
|
178
|
+
agg_func = Max if always_max else self.order_agg_func
|
|
179
|
+
return agg_func(
|
|
180
|
+
updated_at_rel,
|
|
181
|
+
default=_NONE_DATETIMEFIELD,
|
|
182
|
+
filter=bm_filter,
|
|
183
|
+
)
|
|
176
184
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
def _annotate_bookmark_updated_at(self, qs, model):
|
|
186
|
+
if (
|
|
187
|
+
self.is_opds_1_acquisition
|
|
188
|
+
or self.order_key == "bookmark_updated_at"
|
|
189
|
+
or self.is_bookmark_filtered
|
|
190
|
+
):
|
|
191
|
+
bookmark_updated_at_aggregate = self.get_bookmark_updated_at_aggregate(
|
|
192
|
+
model
|
|
182
193
|
)
|
|
183
|
-
if self.TARGET == "opds1":
|
|
194
|
+
if self.TARGET == "opds1" or self.is_bookmark_filtered:
|
|
184
195
|
qs = qs.annotate(bookmark_updated_at=bookmark_updated_at_aggregate)
|
|
185
196
|
else:
|
|
186
197
|
qs = qs.alias(bookmark_updated_at=bookmark_updated_at_aggregate)
|
|
@@ -191,11 +202,11 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
191
202
|
# Determine order func
|
|
192
203
|
if model == Folder and self.order_key == "filename":
|
|
193
204
|
order_value = F("name")
|
|
194
|
-
elif model == Comic or self.order_key in
|
|
205
|
+
elif model == Comic or self.order_key in _ANNOTATED_ORDER_FIELDS:
|
|
195
206
|
# These are annotated in browser_annotaions
|
|
196
207
|
order_value = F(self.order_key)
|
|
197
208
|
else:
|
|
198
|
-
agg_func =
|
|
209
|
+
agg_func = _ORDER_AGGREGATE_FUNCS[self.order_key]
|
|
199
210
|
agg_func = self.order_agg_func if agg_func == Min else agg_func
|
|
200
211
|
field = self.rel_prefix + self.order_key
|
|
201
212
|
order_value = agg_func(field)
|
|
@@ -231,10 +242,20 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
231
242
|
elif cover_style == "i":
|
|
232
243
|
default_cover_pk = None
|
|
233
244
|
default_cover_mtime = None
|
|
234
|
-
|
|
245
|
+
elif cover_style == "f":
|
|
235
246
|
# At this point it's only been filtered.
|
|
236
247
|
default_cover_pk = F(self.rel_prefix + "pk")
|
|
237
248
|
default_cover_mtime = F(self.rel_prefix + "updated_at")
|
|
249
|
+
else: # cover_style == "d"
|
|
250
|
+
default_cover_pk = F(self.rel_prefix + "pk")
|
|
251
|
+
if self.is_bookmark_filtered:
|
|
252
|
+
default_cover_mtime = Case(
|
|
253
|
+
When(bookmark_updated_at__gt=F(self.rel_prefix + "updated_at")),
|
|
254
|
+
then=F("bookmark_updated_at"),
|
|
255
|
+
default=F(self.rel_prefix + "updated_at"),
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
default_cover_mtime = F(self.rel_prefix + "updated_at")
|
|
238
259
|
|
|
239
260
|
if model in (Volume, Comic):
|
|
240
261
|
cover_custom = Value(False)
|
|
@@ -269,7 +290,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
269
290
|
bm_rel, bm_filter = self.bm_annotation_data[model] # type: ignore
|
|
270
291
|
else:
|
|
271
292
|
bm_rel = self.get_bm_rel(model)
|
|
272
|
-
bm_filter = self.
|
|
293
|
+
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
273
294
|
|
|
274
295
|
page_rel = f"{bm_rel}__page"
|
|
275
296
|
finished_rel = f"{bm_rel}__finished"
|
|
@@ -346,6 +367,16 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
346
367
|
progress = Case(When(page_count__gt=0, then=then), default=0.0)
|
|
347
368
|
return qs.annotate(progress=progress)
|
|
348
369
|
|
|
370
|
+
def _annotate_mtime(self, qs):
|
|
371
|
+
"""Annotations mtime."""
|
|
372
|
+
when_filter = {self.rel_prefix + "bookmark__updated_at__gt": F("updated_at")}
|
|
373
|
+
mtime = Case(
|
|
374
|
+
When(**when_filter),
|
|
375
|
+
then=F(self.rel_prefix + "bookmark__updated_at"),
|
|
376
|
+
default=F("updated_at"),
|
|
377
|
+
)
|
|
378
|
+
return qs.annotate(mtime=mtime)
|
|
379
|
+
|
|
349
380
|
def annotate_card_aggregates(self, qs, model):
|
|
350
381
|
"""Annotate aggregates that appear the browser card."""
|
|
351
382
|
qs = self._annotate_cover_pk(qs, model)
|
|
@@ -355,11 +386,8 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
355
386
|
qs = self._annotate_group(qs, model)
|
|
356
387
|
qs = self.annotate_group_names(qs, model)
|
|
357
388
|
qs = self._annotate_bookmarks(qs, model)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def annotate_for_metadata(self, qs, _model):
|
|
361
|
-
"""Annotations only for metadata."""
|
|
362
|
-
return qs.annotate(mtime=Max("updated_at"))
|
|
389
|
+
qs = self._annotate_progress(qs)
|
|
390
|
+
return self._annotate_mtime(qs)
|
|
363
391
|
|
|
364
392
|
def _get_cover_pk_query(self, cover_pks, group_ids):
|
|
365
393
|
"""Get Cover Pk queryset for comic queryset."""
|
|
@@ -406,7 +434,8 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
406
434
|
"""Python hack to re-cover groups collapsed with group_by."""
|
|
407
435
|
# This would be better in the main query but OuterRef can't access annotations.
|
|
408
436
|
# So cover_pks aggregates all covers from multi groups like ids does.
|
|
409
|
-
|
|
437
|
+
cover_style = self.params.get("cover_style")
|
|
438
|
+
if self.is_model_comic or cover_style == "i":
|
|
410
439
|
return group_qs
|
|
411
440
|
|
|
412
441
|
recovered_group_list = []
|
|
@@ -414,19 +443,20 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
414
443
|
cover_pks = group.cover_pks
|
|
415
444
|
if len(cover_pks) == 1:
|
|
416
445
|
cover_pk = cover_pks[0]
|
|
417
|
-
cover_mtime = group.updated_at
|
|
418
446
|
else:
|
|
419
447
|
comic_qs = self._get_cover_pk_query(cover_pks, group.ids)
|
|
420
448
|
# print(comic_qs.explain())
|
|
421
449
|
# print(comic_qs.query)
|
|
422
450
|
cover = comic_qs[0]
|
|
423
451
|
cover_pk = cover.pk
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
452
|
+
|
|
453
|
+
if len(group.ids) == 1:
|
|
454
|
+
cover_mtime = group.updated_at
|
|
455
|
+
else:
|
|
456
|
+
cover_mtime = group.__class__.objects.filter(
|
|
457
|
+
pk__in=group.ids
|
|
458
|
+
).aggregate(updated_at=Max("updated_at"))["updated_at"]
|
|
459
|
+
|
|
430
460
|
group.cover_mtime = cover_mtime
|
|
431
461
|
group.cover_pk = cover_pk
|
|
432
462
|
recovered_group_list.append(group)
|
codex/views/browser/base.py
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
"""Views for browsing comic library."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
from copy import deepcopy
|
|
5
3
|
from types import MappingProxyType
|
|
6
|
-
from typing import Any
|
|
7
|
-
from urllib.parse import unquote_plus
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
8
5
|
|
|
9
6
|
from django.contrib.auth.models import User
|
|
10
7
|
from django.db.models import Q
|
|
11
8
|
from django.db.models.aggregates import Max, Min
|
|
12
|
-
from djangorestframework_camel_case.settings import api_settings
|
|
13
|
-
from djangorestframework_camel_case.util import underscoreize
|
|
14
9
|
from rest_framework.exceptions import NotFound
|
|
15
10
|
|
|
16
11
|
from codex.logger.logging import get_logger
|
|
@@ -18,43 +13,25 @@ from codex.models import (
|
|
|
18
13
|
AdminFlag,
|
|
19
14
|
Comic,
|
|
20
15
|
Folder,
|
|
21
|
-
Imprint,
|
|
22
|
-
Publisher,
|
|
23
|
-
Series,
|
|
24
|
-
StoryArc,
|
|
25
|
-
Volume,
|
|
26
16
|
)
|
|
27
|
-
from codex.models.groups import BrowserGroupModel
|
|
28
17
|
from codex.serializers.browser.settings import BrowserSettingsSerializer
|
|
29
|
-
from codex.views.browser.filters.
|
|
30
|
-
from codex.views.
|
|
31
|
-
from codex.views.
|
|
32
|
-
from codex.views.browser.filters.search import SearchFilterMixin
|
|
18
|
+
from codex.views.browser.filters.search import SearchFilterView
|
|
19
|
+
from codex.views.const import GROUP_MODEL_MAP, ROOT_GROUP
|
|
20
|
+
from codex.views.utils import reparse_json_query_params
|
|
33
21
|
|
|
34
22
|
LOG = get_logger(__name__)
|
|
35
23
|
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from codex.models.groups import BrowserGroupModel
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
_REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BrowserBaseView(SearchFilterView):
|
|
40
31
|
"""Browse comics with a variety of filters and sorts."""
|
|
41
32
|
|
|
42
33
|
input_serializer_class = BrowserSettingsSerializer
|
|
43
34
|
|
|
44
|
-
GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
|
|
45
|
-
MappingProxyType(
|
|
46
|
-
{
|
|
47
|
-
GroupFilterMixin.ROOT_GROUP: None,
|
|
48
|
-
"p": Publisher,
|
|
49
|
-
"i": Imprint,
|
|
50
|
-
"s": Series,
|
|
51
|
-
"v": Volume,
|
|
52
|
-
GroupFilterMixin.COMIC_GROUP: Comic,
|
|
53
|
-
GroupFilterMixin.FOLDER_GROUP: Folder,
|
|
54
|
-
GroupFilterMixin.STORY_ARC_GROUP: StoryArc,
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
35
|
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType({})
|
|
59
36
|
|
|
60
37
|
_GET_JSON_KEYS = frozenset({"filters", "show"})
|
|
@@ -73,6 +50,7 @@ class BrowserBaseView(
|
|
|
73
50
|
self.is_model_comic: bool = False
|
|
74
51
|
self.admin_flags: MappingProxyType[str, bool] = MappingProxyType({})
|
|
75
52
|
self.order_agg_func: type[Min | Max] = Min
|
|
53
|
+
self.order_key: str = ""
|
|
76
54
|
|
|
77
55
|
def is_admin(self):
|
|
78
56
|
"""Is the current user an admin."""
|
|
@@ -95,7 +73,7 @@ class BrowserBaseView(
|
|
|
95
73
|
def get_model_group(self):
|
|
96
74
|
"""Determine model group for set_browse_model()."""
|
|
97
75
|
group = self.kwargs["group"]
|
|
98
|
-
if group ==
|
|
76
|
+
if group == ROOT_GROUP:
|
|
99
77
|
group = self.params["top_group"]
|
|
100
78
|
return group
|
|
101
79
|
|
|
@@ -113,47 +91,21 @@ class BrowserBaseView(
|
|
|
113
91
|
object_filter &= self.get_group_filter(choices)
|
|
114
92
|
return object_filter
|
|
115
93
|
|
|
116
|
-
def _parse_query_params(self
|
|
94
|
+
def _parse_query_params(self):
|
|
117
95
|
"""Parse GET query parameters: filter object & snake case."""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if not parsed_val:
|
|
124
|
-
continue
|
|
125
|
-
parsed_key = key
|
|
126
|
-
elif key in ("q", "query"):
|
|
127
|
-
# parse and strip query param
|
|
128
|
-
# "query" is used by opds v2
|
|
129
|
-
if "q" in result:
|
|
130
|
-
continue
|
|
131
|
-
parsed_val = val.strip()
|
|
132
|
-
if not parsed_val:
|
|
133
|
-
continue
|
|
134
|
-
parsed_key = "q"
|
|
135
|
-
else:
|
|
136
|
-
parsed_key = key
|
|
137
|
-
parsed_val = val
|
|
138
|
-
|
|
139
|
-
result[parsed_key] = parsed_val
|
|
140
|
-
return underscoreize(result, **api_settings.JSON_UNDERSCOREIZE)
|
|
96
|
+
query_params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
|
|
97
|
+
if "q" not in query_params and (query := query_params.get("query")):
|
|
98
|
+
# parse query param for opds v2
|
|
99
|
+
query_params["q"] = query
|
|
100
|
+
return query_params
|
|
141
101
|
|
|
142
102
|
def parse_params(self):
|
|
143
103
|
"""Validate sbmitted settings and apply them over the session settings."""
|
|
144
|
-
|
|
145
|
-
data = self._parse_query_params(self.request.GET)
|
|
146
|
-
elif hasattr(self.request, "data"):
|
|
147
|
-
data = self._parse_query_params(self.request.data)
|
|
148
|
-
else:
|
|
149
|
-
data = {}
|
|
150
|
-
|
|
104
|
+
data = self._parse_query_params()
|
|
151
105
|
serializer = self.input_serializer_class(data=data)
|
|
152
106
|
serializer.is_valid(raise_exception=True)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
params.update(serializer.validated_data)
|
|
156
|
-
self.params = MappingProxyType(params)
|
|
107
|
+
self.load_params_from_session(serializer.validated_data)
|
|
108
|
+
self.is_bookmark_filtered = bool(self.params.get("filters", {}).get("bookmark"))
|
|
157
109
|
|
|
158
110
|
def set_order_key(self):
|
|
159
111
|
"""Unused until browser."""
|
|
@@ -164,10 +116,10 @@ class BrowserBaseView(
|
|
|
164
116
|
def set_model(self):
|
|
165
117
|
"""Set the model for the browse list."""
|
|
166
118
|
group = self.kwargs["group"]
|
|
167
|
-
self.group_class =
|
|
119
|
+
self.group_class = GROUP_MODEL_MAP[group]
|
|
168
120
|
|
|
169
121
|
self.model_group = self.get_model_group()
|
|
170
|
-
self.model =
|
|
122
|
+
self.model = GROUP_MODEL_MAP.get(self.model_group)
|
|
171
123
|
if self.model is None:
|
|
172
124
|
detail = f"Cannot browse {group=}"
|
|
173
125
|
LOG.debug(detail)
|