codex 1.6.0a5__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/covers/purge.py +1 -2
- codex/librarian/importer/aggregate_metadata.py +2 -10
- codex/librarian/importer/cache.py +47 -0
- codex/librarian/importer/const.py +8 -0
- codex/librarian/importer/deleted.py +45 -7
- codex/librarian/importer/importerd.py +11 -1
- codex/librarian/importer/update_comics.py +0 -2
- codex/librarian/janitor/janitor.py +1 -1
- codex/librarian/librariand.py +1 -1
- codex/migrations/0029_choices_adminflag_and_timestamp.py +44 -0
- codex/models/admin.py +0 -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 -3
- 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-FKMTcdkF.6c1eed84d25e.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-FKMTcdkF.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-Dftcik85.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-Dftcik85.cb2f1dda87e1.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-CYCwnt3s.28025dc36c6a.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-CYCwnt3s.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-BIBG6Bk8.3583021c124a.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-BIBG6Bk8.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-DARLmXRf.53fc6938fb8c.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-DARLmXRf.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-LpLgWpkf.16ce3324970f.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-LpLgWpkf.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-MK1lcjUU.98406d5babdc.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-MK1lcjUU.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-DYeEBYlT.a2a2ef088d1c.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-DYeEBYlT.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-CF3xt6JG.19fad7f62450.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-CF3xt6JG.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/{settings-drawer-BJOfMtqA.6471a50fc05b.css → settings-drawer-nZ9HvKSZ.01744e131806.css} +1 -1
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.br +0 -0
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.gz +0 -0
- codex/static_root/assets/{settings-drawer-BJOfMtqA.css → settings-drawer-nZ9HvKSZ.css} +1 -1
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.br +0 -0
- codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.gz +0 -0
- codex/static_root/assets/stats-tab-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 +265 -278
- 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} +119 -78
- codex/views/browser/base.py +24 -72
- codex/views/browser/breadcrumbs.py +200 -0
- codex/views/browser/browser.py +64 -344
- 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 +9 -6
- 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.0a5.dist-info → codex-1.6.0a7.dist-info}/METADATA +12 -8
- {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/RECORD +344 -330
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js +0 -1
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.br +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.js +0 -1
- codex/static_root/assets/VCheckbox-BF_99tgB.js.br +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js +0 -1
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js +0 -1
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.gz +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js +0 -1
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.br +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.gz +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js +0 -1
- codex/static_root/assets/VCombobox-CTRWnOuM.js.br +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js.gz +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js +0 -1
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.br +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.gz +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.js +0 -1
- codex/static_root/assets/VDialog-pzwzInEh.js.br +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js +0 -1
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js +0 -1
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js +0 -1
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.js +0 -1
- codex/static_root/assets/VRadioGroup-CW5GobGU.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.js.gz +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js +0 -1
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.br +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.gz +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js +0 -1
- codex/static_root/assets/VSelect-ChOlLRyH.js.br +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js +0 -1
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js +0 -1
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js +0 -1
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.br +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.js +0 -1
- codex/static_root/assets/VSlideGroup-zFz5_byd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.js.gz +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.br +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.gz +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.js.br +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.js.gz +0 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.br +0 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.gz +0 -0
- codex/static_root/assets/VTextField-Dftcik85.js.br +0 -0
- codex/static_root/assets/VTextField-Dftcik85.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.br +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.js.br +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.js.gz +0 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js +0 -1
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.br +0 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.gz +0 -0
- codex/static_root/assets/admin-DH_DrB0F.js +0 -1
- codex/static_root/assets/admin-DH_DrB0F.js.br +0 -0
- codex/static_root/assets/admin-DH_DrB0F.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js +0 -30
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js +0 -30
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.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/browser-CpppBvim.a1b5d4fe97b1.css +0 -1
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.br +0 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.gz +0 -0
- codex/static_root/assets/browser-CpppBvim.css +0 -1
- codex/static_root/assets/browser-CpppBvim.css.br +0 -0
- codex/static_root/assets/browser-CpppBvim.css.gz +0 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js +0 -1
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.br +0 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.gz +0 -0
- codex/static_root/assets/browser-lKU7rqLd.js +0 -1
- codex/static_root/assets/browser-lKU7rqLd.js.br +0 -0
- codex/static_root/assets/browser-lKU7rqLd.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.js.gz +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.br +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.gz +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.js.br +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.js.gz +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js +0 -1
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.br +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.gz +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.js +0 -1
- codex/static_root/assets/filter-jRB1Ob6r.js.br +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.js.gz +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js +0 -1
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.br +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.gz +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js +0 -1
- codex/static_root/assets/flag-tab-mYS2wg5e.js.br +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js.gz +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.br +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.gz +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.js.br +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.js.gz +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js +0 -1
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.br +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.gz +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.js +0 -1
- codex/static_root/assets/http-error-HFeYnF0n.js.br +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.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-DYeEBYlT.a2a2ef088d1c.js.br +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.gz +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js.br +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js.gz +0 -0
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js +0 -32
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.br +0 -0
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.gz +0 -0
- codex/static_root/assets/main-gSW7TFDY.js +0 -32
- codex/static_root/assets/main-gSW7TFDY.js.br +0 -0
- codex/static_root/assets/main-gSW7TFDY.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-Dlm70Bhs.8f23f8bd9a05.js +0 -1
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js +0 -1
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js +0 -35
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.br +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.js +0 -35
- codex/static_root/assets/pdf-doc-BTdS9hUz.js.br +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.js.gz +0 -0
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js +0 -2
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.br +0 -0
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.gz +0 -0
- codex/static_root/assets/reader-BtSTIETq.js +0 -2
- codex/static_root/assets/reader-BtSTIETq.js.br +0 -0
- codex/static_root/assets/reader-BtSTIETq.js.gz +0 -0
- codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.br +0 -0
- codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.gz +0 -0
- codex/static_root/assets/reader-CF3xt6JG.css.br +0 -0
- codex/static_root/assets/reader-CF3xt6JG.css.gz +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js +0 -1
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.br +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.gz +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js +0 -1
- codex/static_root/assets/relation-chips-C4Ob7ySi.js.br +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.br +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.gz +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.css.br +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.css.gz +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js +0 -2
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.br +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.gz +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.js +0 -2
- codex/static_root/assets/settings-drawer-xSctoxZn.js.br +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.js.gz +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js +0 -1
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.br +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.gz +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.js +0 -1
- codex/static_root/assets/stats-tab-C_uC_U7o.js.br +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.js.gz +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js +0 -1
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.br +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.gz +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js +0 -1
- codex/static_root/assets/task-tab-VIpy1Ula.js.br +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.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-kEuFL5br.475729603aeb.js +0 -1
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.br +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.gz +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.js +0 -1
- codex/static_root/assets/unauthorized-kEuFL5br.js.br +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.js.gz +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js +0 -1
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.br +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.gz +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.js +0 -1
- codex/static_root/assets/user-tab-CZNFTTEI.js.br +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.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.6ef112683dae.json +0 -642
- codex/static_root/manifest.6ef112683dae.json.br +0 -0
- codex/static_root/manifest.6ef112683dae.json.gz +0 -0
- codex/views/browser/browser_breadcrumbs.py +0 -175
- codex/views/browser/const.py +0 -3
- {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/LICENSE +0 -0
- {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/WHEEL +0 -0
- {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/entry_points.txt +0 -0
|
@@ -4,18 +4,20 @@ from types import MappingProxyType
|
|
|
4
4
|
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
|
|
7
|
-
from codex.
|
|
7
|
+
from codex.models.comic import Comic
|
|
8
|
+
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
9
|
+
from codex.views.browser.filters.group import GroupFilterView
|
|
8
10
|
|
|
9
11
|
_FILTER_REL_MAP = MappingProxyType(
|
|
10
12
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
GroupFilterView.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
|
|
14
|
+
GroupFilterView.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
|
|
15
|
+
GroupFilterView.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
|
|
14
16
|
}
|
|
15
17
|
)
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class
|
|
20
|
+
class ComicFieldFilterView(GroupFilterView, BookmarkFilterMixin):
|
|
19
21
|
"""Comic field filters."""
|
|
20
22
|
|
|
21
23
|
def _filter_by_comic_field(self, field, rel_prefix):
|
|
@@ -36,9 +38,10 @@ class ComicFieldFilter(BrowserSessionViewBase):
|
|
|
36
38
|
filter_query |= Q(**{f"{rel}__in": filter_list})
|
|
37
39
|
return filter_query
|
|
38
40
|
|
|
39
|
-
def get_comic_field_filter(self,
|
|
41
|
+
def get_comic_field_filter(self, model):
|
|
40
42
|
"""Filter the comics based on the form filters."""
|
|
41
43
|
comic_field_filter = Q()
|
|
44
|
+
rel_prefix = "" if model == Comic else self.rel_prefix # type: ignore
|
|
42
45
|
for attribute in self.FILTER_ATTRIBUTES:
|
|
43
46
|
comic_field_filter &= self._filter_by_comic_field(attribute, rel_prefix)
|
|
44
47
|
return comic_field_filter
|
|
@@ -2,25 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db.models import Q
|
|
4
4
|
|
|
5
|
-
from codex.views.
|
|
5
|
+
from codex.views.const import FOLDER_GROUP, GROUP_RELATION
|
|
6
|
+
from codex.views.session import SessionView
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class
|
|
9
|
+
class GroupFilterView(SessionView):
|
|
9
10
|
"""Group Filters."""
|
|
10
11
|
|
|
12
|
+
SESSION_KEY = SessionView.BROWSER_SESSION_KEY
|
|
13
|
+
|
|
11
14
|
def get_group_filter(self, choices):
|
|
12
15
|
"""Get filter for the displayed group."""
|
|
13
16
|
group = self.kwargs["group"] # type: ignore
|
|
14
17
|
pks = self.kwargs["pks"] # type: ignore
|
|
15
18
|
if pks: # type: ignore
|
|
16
19
|
group_relation = "comic__" if choices else ""
|
|
17
|
-
if choices and group ==
|
|
20
|
+
if choices and group == FOLDER_GROUP:
|
|
18
21
|
group_relation += "folders"
|
|
19
22
|
else:
|
|
20
|
-
group_relation +=
|
|
23
|
+
group_relation += GROUP_RELATION[group]
|
|
21
24
|
group_relation += "__in"
|
|
22
25
|
group_filter_dict = {group_relation: pks} # type: ignore
|
|
23
|
-
elif group ==
|
|
26
|
+
elif group == FOLDER_GROUP:
|
|
24
27
|
group_filter_dict = {"parent_folder": None}
|
|
25
28
|
else:
|
|
26
29
|
group_filter_dict = {}
|
|
@@ -9,12 +9,13 @@ from codex.logger.logging import get_logger
|
|
|
9
9
|
from codex.models import Comic
|
|
10
10
|
from codex.search.query import CodexSearchQuerySet
|
|
11
11
|
from codex.settings.settings import DEBUG
|
|
12
|
-
from codex.views.browser.
|
|
12
|
+
from codex.views.browser.filters.field import ComicFieldFilterView
|
|
13
|
+
from codex.views.const import MAX_OBJ_PER_PAGE
|
|
13
14
|
|
|
14
15
|
LOG = get_logger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
_SEARCH_SCORE_MAX = 100.0
|
|
17
|
+
_SEARCH_SCORE_MIN = 0.001
|
|
18
|
+
_SEARCH_SCORE_EMPTY = 0.0
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass
|
|
@@ -26,7 +27,7 @@ class SearchScorePks:
|
|
|
26
27
|
next_pks: tuple[int, ...] = ()
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
class
|
|
30
|
+
class SearchFilterView(ComicFieldFilterView):
|
|
30
31
|
"""Search Filters Methods."""
|
|
31
32
|
|
|
32
33
|
def _is_search_results_limited(self) -> bool:
|
|
@@ -154,10 +155,12 @@ class SearchFilterMixin:
|
|
|
154
155
|
when = {prefix + "pk": pk, "then": score}
|
|
155
156
|
whens.append(When(**when))
|
|
156
157
|
if prev_pks:
|
|
157
|
-
whens.append(When(pk__in=prev_pks, then=
|
|
158
|
+
whens.append(When(pk__in=prev_pks, then=_SEARCH_SCORE_MAX))
|
|
158
159
|
if next_pks:
|
|
159
|
-
whens.append(When(pk__in=next_pks, then=
|
|
160
|
-
search_score = self.order_agg_func(
|
|
160
|
+
whens.append(When(pk__in=next_pks, then=_SEARCH_SCORE_MIN))
|
|
161
|
+
search_score = self.order_agg_func( # type: ignore
|
|
162
|
+
Case(*whens, default=_SEARCH_SCORE_EMPTY)
|
|
163
|
+
)
|
|
161
164
|
else:
|
|
162
165
|
search_score = Value(0.0)
|
|
163
166
|
return qs.annotate(search_score=search_score)
|
codex/views/browser/metadata.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""View for marking comics read and unread."""
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
|
-
from typing import ClassVar
|
|
5
4
|
|
|
6
5
|
from django.core.exceptions import FieldDoesNotExist
|
|
7
6
|
from django.db.models import Count, IntegerField, Sum, Value
|
|
@@ -13,8 +12,7 @@ from codex.librarian.importer.const import COMIC_M2M_FIELD_NAMES
|
|
|
13
12
|
from codex.logger.logging import get_logger
|
|
14
13
|
from codex.models import AdminFlag, Comic
|
|
15
14
|
from codex.serializers.browser.metadata import MetadataSerializer
|
|
16
|
-
from codex.views.
|
|
17
|
-
from codex.views.browser.browser_annotations import BrowserAnnotationsView
|
|
15
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
18
16
|
|
|
19
17
|
LOG = get_logger(__name__)
|
|
20
18
|
_ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
|
|
@@ -37,7 +35,7 @@ _COMIC_VALUE_FIELD_NAMES = frozenset(
|
|
|
37
35
|
)
|
|
38
36
|
_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
|
|
39
37
|
_COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_count"})
|
|
40
|
-
_PATH_GROUPS = ("c", "f")
|
|
38
|
+
_PATH_GROUPS = frozenset({"c", "f"})
|
|
41
39
|
_CONTRIBUTOR_RELATIONS = ("role", "person")
|
|
42
40
|
_SUM_FIELDS = frozenset({"page_count", "size"})
|
|
43
41
|
_GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
|
|
@@ -46,7 +44,6 @@ _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
|
|
|
46
44
|
class MetadataView(BrowserAnnotationsView):
|
|
47
45
|
"""Comic metadata."""
|
|
48
46
|
|
|
49
|
-
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
|
|
50
47
|
serializer_class = MetadataSerializer
|
|
51
48
|
TARGET = "metadata"
|
|
52
49
|
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
|
|
@@ -259,7 +256,6 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
259
256
|
filtered_qs = qs
|
|
260
257
|
qs = self.annotate_order_aggregates(qs, self.model)
|
|
261
258
|
qs = self.annotate_card_aggregates(qs, self.model)
|
|
262
|
-
qs = self.annotate_for_metadata(qs, self.model)
|
|
263
259
|
group_by = self.get_group_by()
|
|
264
260
|
qs = qs.group_by(group_by)
|
|
265
261
|
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
@@ -277,7 +273,7 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
277
273
|
m2m_intersections = self._query_m2m_intersections(filtered_qs)
|
|
278
274
|
return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
|
|
279
275
|
|
|
280
|
-
def set_valid_browse_nav_groups(self,
|
|
276
|
+
def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
|
|
281
277
|
"""Limited allowed nav groups for metadata."""
|
|
282
278
|
group = self.kwargs["group"]
|
|
283
279
|
self.valid_nav_groups = (group,)
|
|
@@ -6,22 +6,18 @@ from codex.models import Comic
|
|
|
6
6
|
from codex.views.browser.filters.annotations import (
|
|
7
7
|
BrowserAnnotationsFilterView,
|
|
8
8
|
)
|
|
9
|
+
from codex.views.const import FOLDER_GROUP
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class BrowserOrderByView(BrowserAnnotationsFilterView):
|
|
12
13
|
"""Base class for views that need ordering."""
|
|
13
14
|
|
|
14
|
-
def __init__(self, *args, **kwargs):
|
|
15
|
-
"""Set params for the type checker."""
|
|
16
|
-
super().__init__(*args, **kwargs)
|
|
17
|
-
self.order_key: str = ""
|
|
18
|
-
|
|
19
15
|
def set_order_key(self):
|
|
20
16
|
"""Get the default order key for the view."""
|
|
21
17
|
order_key: str = self.params.get("order_by", "") # type: ignore
|
|
22
18
|
if not order_key:
|
|
23
19
|
group = self.kwargs.get("group")
|
|
24
|
-
order_key = "filename" if group ==
|
|
20
|
+
order_key = "filename" if group == FOLDER_GROUP else "sort_name"
|
|
25
21
|
self.order_key: str = order_key
|
|
26
22
|
order_reverse = self.params.get("order_reverse")
|
|
27
23
|
self.order_agg_func = Max if order_reverse else Min
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Browser pagination."""
|
|
2
|
+
|
|
3
|
+
from math import ceil
|
|
4
|
+
|
|
5
|
+
from django.core.paginator import EmptyPage, Paginator
|
|
6
|
+
|
|
7
|
+
from codex.logger.logging import get_logger
|
|
8
|
+
from codex.models import (
|
|
9
|
+
Comic,
|
|
10
|
+
Folder,
|
|
11
|
+
)
|
|
12
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
13
|
+
from codex.views.const import (
|
|
14
|
+
MAX_OBJ_PER_PAGE,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
LOG = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BrowserPaginateView(BrowserAnnotationsView):
|
|
21
|
+
"""Browser pagination."""
|
|
22
|
+
|
|
23
|
+
def _check_page_in_bounds(self, total_count):
|
|
24
|
+
"""Redirect page out of bounds."""
|
|
25
|
+
pks = self.kwargs.get("pks")
|
|
26
|
+
page = self.kwargs.get("page", 1)
|
|
27
|
+
num_pages = ceil(total_count / MAX_OBJ_PER_PAGE)
|
|
28
|
+
if (not pks and page == 1) or (page >= 1 and page <= num_pages):
|
|
29
|
+
# Don't redirect if on the root page for the group.
|
|
30
|
+
# Or page within valid range.
|
|
31
|
+
return num_pages
|
|
32
|
+
|
|
33
|
+
# Adjust route mask for redirect
|
|
34
|
+
group = self.kwargs.get("group")
|
|
35
|
+
if num_pages and page > 1:
|
|
36
|
+
# Try page 1 first.
|
|
37
|
+
new_page = num_pages if num_pages and page > num_pages else 1
|
|
38
|
+
pks = pks if pks else "0"
|
|
39
|
+
else:
|
|
40
|
+
# Redirect to root for the group.
|
|
41
|
+
pks = "0"
|
|
42
|
+
new_page = 1
|
|
43
|
+
|
|
44
|
+
reason = f"{page=} does not exist!"
|
|
45
|
+
route_changes = {"group": group, "pks": pks, "page": new_page}
|
|
46
|
+
return self.raise_redirect(reason, route_mask=route_changes)
|
|
47
|
+
|
|
48
|
+
def _paginate_section(self, model, qs, page):
|
|
49
|
+
"""Paginate a group or Comic section."""
|
|
50
|
+
orphans = 0 if self.model_group == "f" or self.params.get("q") else 5
|
|
51
|
+
paginator = Paginator(qs, MAX_OBJ_PER_PAGE, orphans=orphans)
|
|
52
|
+
try:
|
|
53
|
+
qs = paginator.page(page).object_list
|
|
54
|
+
except EmptyPage:
|
|
55
|
+
if model != Folder:
|
|
56
|
+
model_name = self.model.__name__ if self.model else "NO_MODEL"
|
|
57
|
+
LOG.warning(f"No {model_name}s on page {page}")
|
|
58
|
+
qs = model.objects.none()
|
|
59
|
+
|
|
60
|
+
return qs
|
|
61
|
+
|
|
62
|
+
def _paginate_groups(self, group_qs, total_group_count):
|
|
63
|
+
"""Paginate the group object list before books."""
|
|
64
|
+
if total_group_count:
|
|
65
|
+
page = self.kwargs.get("page", 1)
|
|
66
|
+
page_group_qs = self._paginate_section(self.model, group_qs, page)
|
|
67
|
+
else:
|
|
68
|
+
page_group_qs = self.model.objects.none() # type: ignore
|
|
69
|
+
return page_group_qs
|
|
70
|
+
|
|
71
|
+
def _paginate_books(self, book_qs, total_group_count, page_obj_count):
|
|
72
|
+
"""Paginate the book object list based on how many group/folders are showing."""
|
|
73
|
+
if page_obj_count >= MAX_OBJ_PER_PAGE:
|
|
74
|
+
# No books for this page
|
|
75
|
+
page_book_qs = Comic.objects.none()
|
|
76
|
+
else:
|
|
77
|
+
group_remainder = total_group_count % MAX_OBJ_PER_PAGE
|
|
78
|
+
if page_obj_count:
|
|
79
|
+
# There are books after the groups on the same page
|
|
80
|
+
# Manually add books without the paginator
|
|
81
|
+
book_limit = MAX_OBJ_PER_PAGE - group_remainder
|
|
82
|
+
page_book_qs = book_qs[:book_limit]
|
|
83
|
+
else:
|
|
84
|
+
# There are books after the groups on a new page
|
|
85
|
+
if group_remainder:
|
|
86
|
+
book_offset = MAX_OBJ_PER_PAGE - group_remainder
|
|
87
|
+
page_book_qs = book_qs[book_offset:]
|
|
88
|
+
else:
|
|
89
|
+
page_book_qs = book_qs
|
|
90
|
+
|
|
91
|
+
num_group_and_mixed_pages = ceil(total_group_count / MAX_OBJ_PER_PAGE)
|
|
92
|
+
book_only_page = self.kwargs.get("page", 1) - num_group_and_mixed_pages
|
|
93
|
+
page_book_qs = self._paginate_section(
|
|
94
|
+
Comic, page_book_qs, book_only_page
|
|
95
|
+
)
|
|
96
|
+
return page_book_qs
|
|
97
|
+
|
|
98
|
+
def paginate(self, group_qs, book_qs, group_count, book_count):
|
|
99
|
+
"""Paginate the queryset into a group and book object lists."""
|
|
100
|
+
num_pages = self._check_page_in_bounds(group_count + book_count)
|
|
101
|
+
page_group_qs = self._paginate_groups(group_qs, group_count)
|
|
102
|
+
page_group_count = page_group_qs.count()
|
|
103
|
+
page_book_qs = self._paginate_books(book_qs, group_count, page_group_count)
|
|
104
|
+
page_book_count = page_book_qs.count()
|
|
105
|
+
|
|
106
|
+
return page_group_qs, page_book_qs, num_pages, page_group_count, page_book_count
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Browser session view."""
|
|
2
2
|
|
|
3
3
|
from codex.serializers.browser.settings import BrowserSettingsSerializer
|
|
4
|
-
from codex.views.
|
|
4
|
+
from codex.views.settings import SettingsView
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class BrowserSettingsView(SettingsView):
|
|
8
8
|
"""Get Browser Settings."""
|
|
9
9
|
|
|
10
10
|
# Put Browser Settings is normally done through BrowserView.get()
|
|
11
|
-
|
|
12
11
|
serializer_class = BrowserSettingsSerializer # type: ignore
|
|
12
|
+
|
|
13
|
+
SESSION_KEY = SettingsView.BROWSER_SESSION_KEY
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Browser title."""
|
|
2
|
+
|
|
3
|
+
from codex.models import BrowserGroupModel, Comic, Volume
|
|
4
|
+
from codex.views.browser.breadcrumbs import BrowserBreadcrumbsView
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BrowserTitleView(BrowserBreadcrumbsView):
|
|
8
|
+
"""Browser title methods."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, *args, **kwargs):
|
|
11
|
+
"""Set params for the type checker."""
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
self.group_instance: BrowserGroupModel | None = None
|
|
14
|
+
|
|
15
|
+
def _get_root_group_name(self):
|
|
16
|
+
if not self.model:
|
|
17
|
+
reason = "No model set in browser"
|
|
18
|
+
raise ValueError(reason)
|
|
19
|
+
plural = self.model._meta.verbose_name_plural
|
|
20
|
+
if not plural:
|
|
21
|
+
reason = f"No plural name for {self.model}"
|
|
22
|
+
raise ValueError(reason)
|
|
23
|
+
return plural.capitalize(), 0
|
|
24
|
+
|
|
25
|
+
def _get_group_name(self):
|
|
26
|
+
group_count = 0
|
|
27
|
+
if gi := self.group_instance:
|
|
28
|
+
if isinstance(gi, Volume):
|
|
29
|
+
group_count = gi.series.volume_count
|
|
30
|
+
elif isinstance(gi, Comic):
|
|
31
|
+
group_count = gi.volume.issue_count
|
|
32
|
+
group_name = gi.name
|
|
33
|
+
else:
|
|
34
|
+
group_name = ""
|
|
35
|
+
return group_name, group_count
|
|
36
|
+
|
|
37
|
+
def get_browser_page_title(self):
|
|
38
|
+
"""Get the proper title for this browse level."""
|
|
39
|
+
pks = self.kwargs.get("pks")
|
|
40
|
+
if not pks:
|
|
41
|
+
group_name, group_count = self._get_root_group_name()
|
|
42
|
+
else:
|
|
43
|
+
group_name, group_count = self._get_group_name()
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"group_name": group_name,
|
|
47
|
+
"group_count": group_count,
|
|
48
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Browser Settings and URL Validation."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
|
|
6
|
+
from codex.exceptions import SeeOtherRedirectError
|
|
7
|
+
from codex.logger.logging import get_logger
|
|
8
|
+
from codex.serializers.choices import DEFAULTS
|
|
9
|
+
from codex.views.browser.base import BrowserBaseView
|
|
10
|
+
from codex.views.const import (
|
|
11
|
+
COMIC_GROUP,
|
|
12
|
+
FOLDER_GROUP,
|
|
13
|
+
ROOT_GROUP,
|
|
14
|
+
STORY_ARC_GROUP,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
LOG = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BrowserValidateView(BrowserBaseView):
|
|
21
|
+
"""Browser Settings and URL Validation."""
|
|
22
|
+
|
|
23
|
+
DEFAULT_ROUTE = MappingProxyType(
|
|
24
|
+
{
|
|
25
|
+
"name": "browser",
|
|
26
|
+
"params": deepcopy(DEFAULTS["breadcrumbs"][0]),
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def raise_redirect(self, reason, route_mask=None, settings_mask=None):
|
|
31
|
+
"""Redirect the client to a valid group url."""
|
|
32
|
+
route = deepcopy(dict(self.DEFAULT_ROUTE))
|
|
33
|
+
if route_mask:
|
|
34
|
+
route["params"].update(route_mask) # type: ignore
|
|
35
|
+
settings = deepcopy(dict(self.params))
|
|
36
|
+
if settings_mask:
|
|
37
|
+
settings.update(settings_mask)
|
|
38
|
+
detail = {"route": route, "settings": settings, "reason": reason}
|
|
39
|
+
raise SeeOtherRedirectError(detail=detail)
|
|
40
|
+
|
|
41
|
+
def _get_valid_browse_top_groups(self):
|
|
42
|
+
"""Get valid top groups for the current settings.
|
|
43
|
+
|
|
44
|
+
Valid top groups are determined by the Browser Settings.
|
|
45
|
+
"""
|
|
46
|
+
valid_top_groups = []
|
|
47
|
+
|
|
48
|
+
show = self.params["show"]
|
|
49
|
+
for nav_group, allowed in show.items():
|
|
50
|
+
if allowed:
|
|
51
|
+
valid_top_groups.append(nav_group)
|
|
52
|
+
# Issues is always a valid top group
|
|
53
|
+
valid_top_groups += [COMIC_GROUP]
|
|
54
|
+
|
|
55
|
+
return valid_top_groups
|
|
56
|
+
|
|
57
|
+
def _validate_top_group(self, valid_top_groups):
|
|
58
|
+
nav_group = self.kwargs.get("group")
|
|
59
|
+
top_group = self.params.get("top_group")
|
|
60
|
+
if top_group not in valid_top_groups:
|
|
61
|
+
reason = f"top_group {top_group} not in valid nav groups, changed to "
|
|
62
|
+
if nav_group in valid_top_groups:
|
|
63
|
+
valid_top_group = nav_group
|
|
64
|
+
reason += "nav group: "
|
|
65
|
+
else:
|
|
66
|
+
valid_top_group = valid_top_groups[0]
|
|
67
|
+
reason += "first valid top group "
|
|
68
|
+
reason += valid_top_group
|
|
69
|
+
pks = self.kwargs.get("pks", ())
|
|
70
|
+
page = self.kwargs["page"]
|
|
71
|
+
route = {"group": nav_group, "pks": pks, "page": page}
|
|
72
|
+
settings_mask = {"top_group": valid_top_group}
|
|
73
|
+
self.raise_redirect(reason, route, settings_mask)
|
|
74
|
+
|
|
75
|
+
def set_valid_browse_nav_groups(self, valid_top_groups):
|
|
76
|
+
"""Get valid nav groups for the current settings.
|
|
77
|
+
|
|
78
|
+
Valid nav groups are the top group and below that are also
|
|
79
|
+
enabled in browser settings.
|
|
80
|
+
|
|
81
|
+
May always navigate to root 'r' nav group.
|
|
82
|
+
"""
|
|
83
|
+
top_group = self.params["top_group"]
|
|
84
|
+
nav_group = self.kwargs["group"]
|
|
85
|
+
valid_nav_groups = [ROOT_GROUP]
|
|
86
|
+
|
|
87
|
+
for possible_index, possible_nav_group in enumerate(valid_top_groups):
|
|
88
|
+
if top_group == possible_nav_group:
|
|
89
|
+
# all the nav groups past this point,
|
|
90
|
+
# 'c' is obscured by the web reader url, but valid for opds
|
|
91
|
+
tail_top_groups = valid_top_groups[possible_index:]
|
|
92
|
+
valid_nav_groups += tail_top_groups
|
|
93
|
+
|
|
94
|
+
if nav_group not in valid_nav_groups:
|
|
95
|
+
reason = (
|
|
96
|
+
f"Nav group {nav_group} unavailable, "
|
|
97
|
+
f"redirect to {ROOT_GROUP}"
|
|
98
|
+
)
|
|
99
|
+
self.raise_redirect(reason)
|
|
100
|
+
break
|
|
101
|
+
self.valid_nav_groups = tuple(valid_nav_groups)
|
|
102
|
+
|
|
103
|
+
def _validate_folder_settings(self):
|
|
104
|
+
"""Check that all the view variables for folder mode are set right."""
|
|
105
|
+
# Check folder view admin flag
|
|
106
|
+
if not self.admin_flags["folder_view"]:
|
|
107
|
+
reason = "folder view disabled"
|
|
108
|
+
valid_top_groups = self._get_valid_browse_top_groups()
|
|
109
|
+
settings_mask = {"top_group": valid_top_groups[0]}
|
|
110
|
+
self.raise_redirect(reason, settings_mask=settings_mask)
|
|
111
|
+
|
|
112
|
+
valid_top_groups = (FOLDER_GROUP,)
|
|
113
|
+
self._validate_top_group(valid_top_groups)
|
|
114
|
+
self.valid_nav_groups = valid_top_groups
|
|
115
|
+
|
|
116
|
+
def _validate_browser_group_settings(self):
|
|
117
|
+
"""Check that all the view variables for browser mode are set right."""
|
|
118
|
+
# Validate Browser top_group
|
|
119
|
+
# Change top_group if its not in the valid top groups
|
|
120
|
+
valid_top_groups = self._get_valid_browse_top_groups()
|
|
121
|
+
self._validate_top_group(valid_top_groups)
|
|
122
|
+
|
|
123
|
+
# Validate pks
|
|
124
|
+
nav_group = self.kwargs["group"]
|
|
125
|
+
pks = self.kwargs["pks"]
|
|
126
|
+
if nav_group == ROOT_GROUP and (pks and 0 not in pks):
|
|
127
|
+
# r never has pks
|
|
128
|
+
reason = f"Redirect r with {pks=} to pks 0"
|
|
129
|
+
self.raise_redirect(reason)
|
|
130
|
+
|
|
131
|
+
# Validate Browser nav_group
|
|
132
|
+
# Redirect if nav group is wrong
|
|
133
|
+
self.set_valid_browse_nav_groups(valid_top_groups)
|
|
134
|
+
|
|
135
|
+
def _validate_story_arc_settings(self):
|
|
136
|
+
"""Validate story arc settings."""
|
|
137
|
+
valid_top_groups = (STORY_ARC_GROUP,)
|
|
138
|
+
self._validate_top_group(valid_top_groups)
|
|
139
|
+
self.valid_nav_groups = valid_top_groups
|
|
140
|
+
|
|
141
|
+
def validate_settings(self):
|
|
142
|
+
"""Validate group and top group settings."""
|
|
143
|
+
group = self.kwargs["group"]
|
|
144
|
+
if group == FOLDER_GROUP:
|
|
145
|
+
self._validate_folder_settings()
|
|
146
|
+
elif group == STORY_ARC_GROUP:
|
|
147
|
+
self._validate_story_arc_settings()
|
|
148
|
+
else:
|
|
149
|
+
self._validate_browser_group_settings()
|
|
150
|
+
|
|
151
|
+
# Validate order
|
|
152
|
+
if self.order_key == "filename" and not self.admin_flags["folder_view"]:
|
|
153
|
+
pks = self.kwargs["pks"]
|
|
154
|
+
page = self.kwargs["page"]
|
|
155
|
+
route_changes = {"group": group, "pks": pks, "page": page}
|
|
156
|
+
reason = "order by filename not allowed by admin flag."
|
|
157
|
+
settings_mask = {"order_by": "sort_name"}
|
|
158
|
+
self.raise_redirect(reason, route_changes, settings_mask)
|
codex/views/const.py
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
|
|
5
|
+
from django.contrib.auth.models import Group, User
|
|
6
|
+
from django.contrib.sessions.models import Session
|
|
7
|
+
|
|
8
|
+
from codex.models import (
|
|
9
|
+
AgeRating,
|
|
10
|
+
BrowserGroupModel,
|
|
11
|
+
Character,
|
|
12
|
+
Comic,
|
|
13
|
+
Contributor,
|
|
14
|
+
ContributorPerson,
|
|
15
|
+
ContributorRole,
|
|
16
|
+
Country,
|
|
17
|
+
Folder,
|
|
18
|
+
Genre,
|
|
19
|
+
Identifier,
|
|
20
|
+
IdentifierType,
|
|
21
|
+
Imprint,
|
|
22
|
+
Language,
|
|
23
|
+
Library,
|
|
24
|
+
Location,
|
|
25
|
+
OriginalFormat,
|
|
26
|
+
Publisher,
|
|
27
|
+
ScanInfo,
|
|
28
|
+
Series,
|
|
29
|
+
SeriesGroup,
|
|
30
|
+
StoryArc,
|
|
31
|
+
StoryArcNumber,
|
|
32
|
+
Tag,
|
|
33
|
+
Tagger,
|
|
34
|
+
Team,
|
|
35
|
+
Volume,
|
|
36
|
+
)
|
|
5
37
|
from codex.settings.settings import CODEX_PATH
|
|
6
38
|
|
|
7
39
|
FALSY = {None, "", "false", "0", False}
|
|
@@ -18,3 +50,68 @@ MISSING_COVER_NAME_MAP = MappingProxyType(
|
|
|
18
50
|
)
|
|
19
51
|
MISSING_COVER_FN = "missing-cover-165.webp"
|
|
20
52
|
MISSING_COVER_PATH = STATIC_IMG_PATH / MISSING_COVER_FN
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
ROOT_GROUP = "r"
|
|
56
|
+
FOLDER_GROUP = "f"
|
|
57
|
+
STORY_ARC_GROUP = "a"
|
|
58
|
+
COMIC_GROUP = "c"
|
|
59
|
+
GROUP_RELATION = MappingProxyType(
|
|
60
|
+
{
|
|
61
|
+
**GROUP_NAME_MAP,
|
|
62
|
+
COMIC_GROUP: "pk",
|
|
63
|
+
FOLDER_GROUP: "parent_folder",
|
|
64
|
+
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
|
|
68
|
+
MappingProxyType(
|
|
69
|
+
{
|
|
70
|
+
ROOT_GROUP: None,
|
|
71
|
+
"p": Publisher,
|
|
72
|
+
"i": Imprint,
|
|
73
|
+
"s": Series,
|
|
74
|
+
"v": Volume,
|
|
75
|
+
COMIC_GROUP: Comic,
|
|
76
|
+
FOLDER_GROUP: Folder,
|
|
77
|
+
STORY_ARC_GROUP: StoryArc,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
GROUP_MODELS = (
|
|
82
|
+
Publisher,
|
|
83
|
+
Imprint,
|
|
84
|
+
Series,
|
|
85
|
+
Volume,
|
|
86
|
+
Comic,
|
|
87
|
+
Folder,
|
|
88
|
+
)
|
|
89
|
+
METADATA_MODELS = (
|
|
90
|
+
AgeRating,
|
|
91
|
+
Character,
|
|
92
|
+
Country,
|
|
93
|
+
Genre,
|
|
94
|
+
Identifier,
|
|
95
|
+
IdentifierType,
|
|
96
|
+
Language,
|
|
97
|
+
Location,
|
|
98
|
+
OriginalFormat,
|
|
99
|
+
SeriesGroup,
|
|
100
|
+
ScanInfo,
|
|
101
|
+
StoryArc,
|
|
102
|
+
StoryArcNumber,
|
|
103
|
+
Team,
|
|
104
|
+
Tag,
|
|
105
|
+
Tagger,
|
|
106
|
+
Contributor,
|
|
107
|
+
ContributorPerson,
|
|
108
|
+
ContributorRole,
|
|
109
|
+
)
|
|
110
|
+
CONFIG_MODELS = (
|
|
111
|
+
Library,
|
|
112
|
+
User,
|
|
113
|
+
Group,
|
|
114
|
+
Session,
|
|
115
|
+
)
|
|
116
|
+
MAX_OBJ_PER_PAGE = 100
|
|
117
|
+
GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
|
codex/views/cover.py
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
"""Comic cover thumbnail view."""
|
|
2
2
|
|
|
3
|
-
from typing import ClassVar
|
|
4
|
-
|
|
5
3
|
from django.http import HttpResponse
|
|
6
4
|
from drf_spectacular.types import OpenApiTypes
|
|
7
5
|
from drf_spectacular.utils import extend_schema
|
|
8
6
|
from rest_framework.renderers import BaseRenderer
|
|
9
|
-
from rest_framework.views import APIView
|
|
10
7
|
|
|
11
8
|
from codex.librarian.covers.create import CoverCreateMixin
|
|
12
9
|
from codex.librarian.covers.path import CoverPathMixin
|
|
13
10
|
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
14
11
|
from codex.logger.logging import get_logger
|
|
15
|
-
from codex.views.auth import
|
|
12
|
+
from codex.views.auth import (
|
|
13
|
+
AuthAPIView,
|
|
14
|
+
)
|
|
16
15
|
from codex.views.const import MISSING_COVER_FN, MISSING_COVER_NAME_MAP, STATIC_IMG_PATH
|
|
17
16
|
|
|
18
17
|
LOG = get_logger(__name__)
|
|
@@ -31,10 +30,9 @@ class WEBPRenderer(BaseRenderer):
|
|
|
31
30
|
return data
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
class CoverView(
|
|
33
|
+
class CoverView(AuthAPIView):
|
|
35
34
|
"""ComicCover View."""
|
|
36
35
|
|
|
37
|
-
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
|
|
38
36
|
renderer_classes = (WEBPRenderer,)
|
|
39
37
|
content_type = "image/webp"
|
|
40
38
|
|