codex 1.7.2__py3-none-any.whl → 1.7.3a0__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/choices.py +13 -5
- codex/librarian/bookmark/bookmarkd.py +4 -5
- codex/librarian/bookmark/tasks.py +1 -1
- codex/librarian/bookmark/update.py +15 -29
- codex/librarian/cron/crond.py +1 -1
- codex/librarian/importer/query_fks.py +2 -2
- codex/librarian/janitor/janitor.py +11 -1
- codex/librarian/janitor/log_compress.py +77 -0
- codex/librarian/janitor/status.py +1 -0
- codex/librarian/janitor/tasks.py +5 -0
- codex/librarian/librariand.py +18 -13
- codex/librarian/search/update.py +8 -4
- codex/migrations/0031_adminflag_banner.py +50 -0
- codex/models/admin.py +4 -3
- codex/serializers/admin/flags.py +1 -1
- codex/serializers/auth.py +1 -0
- codex/serializers/opds/v2/__init__.py +1 -0
- codex/serializers/opds/v2/facet.py +13 -0
- codex/serializers/opds/v2/feed.py +35 -0
- codex/serializers/opds/v2/links.py +73 -0
- codex/serializers/opds/v2/metadata.py +22 -0
- codex/serializers/opds/v2/progression.py +63 -0
- codex/serializers/opds/v2/publication.py +78 -0
- codex/serializers/opds/{v2_unused.py → v2/unused.py} +1 -1
- codex/settings/settings.py +1 -0
- codex/static_root/assets/VCheckbox-pHjSEgdV.176f3c1f4191.js +1 -0
- codex/static_root/assets/VCheckbox-pHjSEgdV.176f3c1f4191.js.br +0 -0
- codex/static_root/assets/VCheckbox-pHjSEgdV.176f3c1f4191.js.gz +0 -0
- codex/static_root/assets/VCheckbox-pHjSEgdV.js +1 -0
- codex/static_root/assets/VCheckbox-pHjSEgdV.js.br +0 -0
- codex/static_root/assets/VCheckbox-pHjSEgdV.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-DBKqrZGq.467e75859b62.js +1 -0
- codex/static_root/assets/VCheckboxBtn-DBKqrZGq.467e75859b62.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DBKqrZGq.467e75859b62.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-DBKqrZGq.js +1 -0
- codex/static_root/assets/VCheckboxBtn-DBKqrZGq.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DBKqrZGq.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bg7uOTbF.2fc6073b789f.js +1 -0
- codex/static_root/assets/VCombobox-Bg7uOTbF.2fc6073b789f.js.br +0 -0
- codex/static_root/assets/VCombobox-Bg7uOTbF.2fc6073b789f.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bg7uOTbF.js +1 -0
- codex/static_root/assets/VCombobox-Bg7uOTbF.js.br +0 -0
- codex/static_root/assets/VCombobox-Bg7uOTbF.js.gz +0 -0
- codex/static_root/assets/VDialog-Dyd05WHA.b07aca713774.css +1 -0
- codex/static_root/assets/VDialog-Dyd05WHA.b07aca713774.css.br +0 -0
- codex/static_root/assets/VDialog-Dyd05WHA.b07aca713774.css.gz +0 -0
- codex/static_root/assets/VDialog-Dyd05WHA.css +1 -0
- codex/static_root/assets/VDialog-Dyd05WHA.css.br +0 -0
- codex/static_root/assets/VDialog-Dyd05WHA.css.gz +0 -0
- codex/static_root/assets/VDialog-IrbBYFLM.91a058d572c4.js +1 -0
- codex/static_root/assets/VDialog-IrbBYFLM.91a058d572c4.js.br +0 -0
- codex/static_root/assets/VDialog-IrbBYFLM.91a058d572c4.js.gz +0 -0
- codex/static_root/assets/VDialog-IrbBYFLM.js +1 -0
- codex/static_root/assets/VDialog-IrbBYFLM.js.br +0 -0
- codex/static_root/assets/VDialog-IrbBYFLM.js.gz +0 -0
- codex/static_root/assets/VDivider-CuRohbVQ.3c12b040aed7.js +1 -0
- codex/static_root/assets/VDivider-CuRohbVQ.3c12b040aed7.js.br +0 -0
- codex/static_root/assets/VDivider-CuRohbVQ.3c12b040aed7.js.gz +0 -0
- codex/static_root/assets/VDivider-CuRohbVQ.js +1 -0
- codex/static_root/assets/VDivider-CuRohbVQ.js.br +0 -0
- codex/static_root/assets/VDivider-CuRohbVQ.js.gz +0 -0
- codex/static_root/assets/VDivider-DE8MJPGz.3d2e17799e5c.css +1 -0
- codex/static_root/assets/VDivider-DE8MJPGz.3d2e17799e5c.css.br +0 -0
- codex/static_root/assets/VDivider-DE8MJPGz.3d2e17799e5c.css.gz +0 -0
- codex/static_root/assets/VDivider-DE8MJPGz.css +1 -0
- codex/static_root/assets/VDivider-DE8MJPGz.css.br +0 -0
- codex/static_root/assets/VDivider-DE8MJPGz.css.gz +0 -0
- codex/static_root/assets/VExpansionPanels-b0xWLH75.bff6ea8b4055.js +1 -0
- codex/static_root/assets/VExpansionPanels-b0xWLH75.bff6ea8b4055.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-b0xWLH75.bff6ea8b4055.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-b0xWLH75.js +1 -0
- codex/static_root/assets/VExpansionPanels-b0xWLH75.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-b0xWLH75.js.gz +0 -0
- codex/static_root/assets/VForm-B4hkqnS8.afbd5cf0908a.js +1 -0
- codex/static_root/assets/VForm-B4hkqnS8.afbd5cf0908a.js.br +0 -0
- codex/static_root/assets/VForm-B4hkqnS8.afbd5cf0908a.js.gz +0 -0
- codex/static_root/assets/VForm-B4hkqnS8.js +1 -0
- codex/static_root/assets/VForm-B4hkqnS8.js.br +0 -0
- codex/static_root/assets/VForm-B4hkqnS8.js.gz +0 -0
- codex/static_root/assets/VForm-v1Gmu6E1.138c0b89b6fc.css +1 -0
- codex/static_root/assets/VForm-v1Gmu6E1.138c0b89b6fc.css.br +0 -0
- codex/static_root/assets/VForm-v1Gmu6E1.138c0b89b6fc.css.gz +0 -0
- codex/static_root/assets/VForm-v1Gmu6E1.css +1 -0
- codex/static_root/assets/VForm-v1Gmu6E1.css.br +0 -0
- codex/static_root/assets/VForm-v1Gmu6E1.css.gz +0 -0
- codex/static_root/assets/VRadioGroup-Cbayik6d.034f0f275265.js +1 -0
- codex/static_root/assets/VRadioGroup-Cbayik6d.034f0f275265.js.br +0 -0
- codex/static_root/assets/VRadioGroup-Cbayik6d.034f0f275265.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-Cbayik6d.js +1 -0
- codex/static_root/assets/VRadioGroup-Cbayik6d.js.br +0 -0
- codex/static_root/assets/VRadioGroup-Cbayik6d.js.gz +0 -0
- codex/static_root/assets/VSelect-8MFoaOUz.6309f7bf17bb.css +1 -0
- codex/static_root/assets/VSelect-8MFoaOUz.6309f7bf17bb.css.br +0 -0
- codex/static_root/assets/VSelect-8MFoaOUz.6309f7bf17bb.css.gz +0 -0
- codex/static_root/assets/VSelect-8MFoaOUz.css +1 -0
- codex/static_root/assets/VSelect-8MFoaOUz.css.br +0 -0
- codex/static_root/assets/VSelect-8MFoaOUz.css.gz +0 -0
- codex/static_root/assets/VSelect-BsuB-2P1.abe187c8b53f.js +1 -0
- codex/static_root/assets/VSelect-BsuB-2P1.abe187c8b53f.js.br +0 -0
- codex/static_root/assets/VSelect-BsuB-2P1.abe187c8b53f.js.gz +0 -0
- codex/static_root/assets/VSelect-BsuB-2P1.js +1 -0
- codex/static_root/assets/VSelect-BsuB-2P1.js.br +0 -0
- codex/static_root/assets/VSelect-BsuB-2P1.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DLxcLoTI.cc18de4e83f6.css +1 -0
- codex/static_root/assets/VSelectionControl-DLxcLoTI.cc18de4e83f6.css.br +0 -0
- codex/static_root/assets/VSelectionControl-DLxcLoTI.cc18de4e83f6.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-DLxcLoTI.css +1 -0
- codex/static_root/assets/VSelectionControl-DLxcLoTI.css.br +0 -0
- codex/static_root/assets/VSelectionControl-DLxcLoTI.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-L41n5bnX.f4674bb62425.js +1 -0
- codex/static_root/assets/VSelectionControl-L41n5bnX.f4674bb62425.js.br +0 -0
- codex/static_root/assets/VSelectionControl-L41n5bnX.f4674bb62425.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-L41n5bnX.js +1 -0
- codex/static_root/assets/VSelectionControl-L41n5bnX.js.br +0 -0
- codex/static_root/assets/VSelectionControl-L41n5bnX.js.gz +0 -0
- codex/static_root/assets/VTable-CYv4gUUt.76e4658abeb3.js +1 -0
- codex/static_root/assets/VTable-CYv4gUUt.76e4658abeb3.js.br +0 -0
- codex/static_root/assets/VTable-CYv4gUUt.76e4658abeb3.js.gz +0 -0
- codex/static_root/assets/VTable-CYv4gUUt.js +1 -0
- codex/static_root/assets/VTable-CYv4gUUt.js.br +0 -0
- codex/static_root/assets/VTable-CYv4gUUt.js.gz +0 -0
- codex/static_root/assets/VWindowItem-f8UkxXa_.1d3e9e78fc9b.js +1 -0
- codex/static_root/assets/VWindowItem-f8UkxXa_.1d3e9e78fc9b.js.br +0 -0
- codex/static_root/assets/VWindowItem-f8UkxXa_.1d3e9e78fc9b.js.gz +0 -0
- codex/static_root/assets/VWindowItem-f8UkxXa_.js +1 -0
- codex/static_root/assets/VWindowItem-f8UkxXa_.js.br +0 -0
- codex/static_root/assets/VWindowItem-f8UkxXa_.js.gz +0 -0
- codex/static_root/assets/admin-BmuWJcAB.b0b253aefd88.js +1 -0
- codex/static_root/assets/admin-BmuWJcAB.b0b253aefd88.js.br +0 -0
- codex/static_root/assets/admin-BmuWJcAB.b0b253aefd88.js.gz +0 -0
- codex/static_root/assets/admin-BmuWJcAB.js +1 -0
- codex/static_root/assets/admin-BmuWJcAB.js.br +0 -0
- codex/static_root/assets/admin-BmuWJcAB.js.gz +0 -0
- codex/static_root/assets/admin-Izb24SXM.504ea9d9515c.js +1 -0
- codex/static_root/assets/admin-Izb24SXM.504ea9d9515c.js.br +0 -0
- codex/static_root/assets/admin-Izb24SXM.504ea9d9515c.js.gz +0 -0
- codex/static_root/assets/admin-Izb24SXM.js +1 -0
- codex/static_root/assets/admin-Izb24SXM.js.br +0 -0
- codex/static_root/assets/admin-Izb24SXM.js.gz +0 -0
- codex/static_root/assets/admin-dm3bSP7O.ae1234cc0beb.css +1 -0
- codex/static_root/assets/admin-dm3bSP7O.ae1234cc0beb.css.br +0 -0
- codex/static_root/assets/admin-dm3bSP7O.ae1234cc0beb.css.gz +0 -0
- codex/static_root/assets/admin-dm3bSP7O.css +1 -0
- codex/static_root/assets/admin-dm3bSP7O.css.br +0 -0
- codex/static_root/assets/admin-dm3bSP7O.css.gz +0 -0
- codex/static_root/assets/admin-menu-C712OV-9.ecd1f2af4cf4.js +1 -0
- codex/static_root/assets/admin-menu-C712OV-9.ecd1f2af4cf4.js.br +0 -0
- codex/static_root/assets/admin-menu-C712OV-9.ecd1f2af4cf4.js.gz +0 -0
- codex/static_root/assets/admin-menu-C712OV-9.js +1 -0
- codex/static_root/assets/admin-menu-C712OV-9.js.br +0 -0
- codex/static_root/assets/admin-menu-C712OV-9.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.2536580dece1.js +1 -0
- codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.2536580dece1.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.2536580dece1.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.js +1 -0
- codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.js.gz +0 -0
- codex/static_root/assets/browser-CReWGhn_.521193ab0710.css +1 -0
- codex/static_root/assets/browser-CReWGhn_.521193ab0710.css.br +0 -0
- codex/static_root/assets/browser-CReWGhn_.521193ab0710.css.gz +0 -0
- codex/static_root/assets/browser-CReWGhn_.css +1 -0
- codex/static_root/assets/browser-CReWGhn_.css.br +0 -0
- codex/static_root/assets/browser-CReWGhn_.css.gz +0 -0
- codex/static_root/assets/browser-DiAN9Ipo.a5b62a3cbe1d.js +1 -0
- codex/static_root/assets/browser-DiAN9Ipo.a5b62a3cbe1d.js.br +0 -0
- codex/static_root/assets/browser-DiAN9Ipo.a5b62a3cbe1d.js.gz +0 -0
- codex/static_root/assets/browser-DiAN9Ipo.js +1 -0
- codex/static_root/assets/browser-DiAN9Ipo.js.br +0 -0
- codex/static_root/assets/browser-DiAN9Ipo.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BZPmAOML.73fdc23e13a4.js +1 -0
- codex/static_root/assets/change-password-dialog-BZPmAOML.73fdc23e13a4.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BZPmAOML.73fdc23e13a4.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BZPmAOML.js +1 -0
- codex/static_root/assets/change-password-dialog-BZPmAOML.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BZPmAOML.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CBLQOik_.f53acd0c622a.js +1 -0
- codex/static_root/assets/confirm-dialog-CBLQOik_.f53acd0c622a.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CBLQOik_.f53acd0c622a.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CBLQOik_.js +1 -0
- codex/static_root/assets/confirm-dialog-CBLQOik_.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CBLQOik_.js.gz +0 -0
- codex/static_root/assets/datetime-column-CU19rCbi.9edc0d2da736.js +1 -0
- codex/static_root/assets/datetime-column-CU19rCbi.9edc0d2da736.js.br +1 -0
- codex/static_root/assets/datetime-column-CU19rCbi.9edc0d2da736.js.gz +0 -0
- codex/static_root/assets/datetime-column-CU19rCbi.js +1 -0
- codex/static_root/assets/datetime-column-CU19rCbi.js.br +1 -0
- codex/static_root/assets/datetime-column-CU19rCbi.js.gz +0 -0
- codex/static_root/assets/{filter-ud5mPZpn.0f0a537959aa.js → filter-CZ7kkBLq.171b4fcba146.js} +1 -1
- codex/static_root/assets/filter-CZ7kkBLq.171b4fcba146.js.br +0 -0
- codex/static_root/assets/filter-CZ7kkBLq.171b4fcba146.js.gz +0 -0
- codex/static_root/assets/{filter-ud5mPZpn.js → filter-CZ7kkBLq.js} +1 -1
- codex/static_root/assets/filter-CZ7kkBLq.js.br +0 -0
- codex/static_root/assets/filter-CZ7kkBLq.js.gz +0 -0
- codex/static_root/assets/flag-tab-DHGfv0Q3.2b7ef394b850.css +1 -0
- codex/static_root/assets/flag-tab-DHGfv0Q3.2b7ef394b850.css.br +0 -0
- codex/static_root/assets/flag-tab-DHGfv0Q3.2b7ef394b850.css.gz +0 -0
- codex/static_root/assets/flag-tab-DHGfv0Q3.css +1 -0
- codex/static_root/assets/flag-tab-DHGfv0Q3.css.br +0 -0
- codex/static_root/assets/flag-tab-DHGfv0Q3.css.gz +0 -0
- codex/static_root/assets/flag-tab-kcBN36xe.631c48ab75fd.js +1 -0
- codex/static_root/assets/flag-tab-kcBN36xe.631c48ab75fd.js.br +0 -0
- codex/static_root/assets/flag-tab-kcBN36xe.631c48ab75fd.js.gz +0 -0
- codex/static_root/assets/flag-tab-kcBN36xe.js +1 -0
- codex/static_root/assets/flag-tab-kcBN36xe.js.br +0 -0
- codex/static_root/assets/flag-tab-kcBN36xe.js.gz +0 -0
- codex/static_root/assets/forwardRefs-DNAemYUk.06a315272ed9.js +1 -0
- codex/static_root/assets/forwardRefs-DNAemYUk.06a315272ed9.js.br +0 -0
- codex/static_root/assets/forwardRefs-DNAemYUk.06a315272ed9.js.gz +0 -0
- codex/static_root/assets/forwardRefs-DNAemYUk.js +1 -0
- codex/static_root/assets/forwardRefs-DNAemYUk.js.br +0 -0
- codex/static_root/assets/forwardRefs-DNAemYUk.js.gz +0 -0
- codex/static_root/assets/forwardRefs-zKOZsbbr.9b56b4459240.css +1 -0
- codex/static_root/assets/forwardRefs-zKOZsbbr.9b56b4459240.css.br +0 -0
- codex/static_root/assets/forwardRefs-zKOZsbbr.9b56b4459240.css.gz +0 -0
- codex/static_root/assets/forwardRefs-zKOZsbbr.css +1 -0
- codex/static_root/assets/forwardRefs-zKOZsbbr.css.br +0 -0
- codex/static_root/assets/forwardRefs-zKOZsbbr.css.gz +0 -0
- codex/static_root/assets/group-tab-D9gwAdJc.60fb0ffa734c.js +1 -0
- codex/static_root/assets/group-tab-D9gwAdJc.60fb0ffa734c.js.br +0 -0
- codex/static_root/assets/group-tab-D9gwAdJc.60fb0ffa734c.js.gz +0 -0
- codex/static_root/assets/group-tab-D9gwAdJc.js +1 -0
- codex/static_root/assets/group-tab-D9gwAdJc.js.br +0 -0
- codex/static_root/assets/group-tab-D9gwAdJc.js.gz +0 -0
- codex/static_root/assets/http-error-DEJmFobt.8c13689ae574.css +1 -0
- codex/static_root/assets/http-error-DEJmFobt.8c13689ae574.css.br +0 -0
- codex/static_root/assets/http-error-DEJmFobt.8c13689ae574.css.gz +0 -0
- codex/static_root/assets/http-error-DEJmFobt.css +1 -0
- codex/static_root/assets/http-error-DEJmFobt.css.br +0 -0
- codex/static_root/assets/http-error-DEJmFobt.css.gz +0 -0
- codex/static_root/assets/http-error-DmFqD2Xz.593d626b5f1b.js +1 -0
- codex/static_root/assets/http-error-DmFqD2Xz.593d626b5f1b.js.br +0 -0
- codex/static_root/assets/http-error-DmFqD2Xz.593d626b5f1b.js.gz +0 -0
- codex/static_root/assets/http-error-DmFqD2Xz.js +1 -0
- codex/static_root/assets/http-error-DmFqD2Xz.js.br +0 -0
- codex/static_root/assets/http-error-DmFqD2Xz.js.gz +0 -0
- codex/static_root/assets/index-hvGLIBbY.30ce8cca0c6d.js +1 -0
- codex/static_root/assets/index-hvGLIBbY.30ce8cca0c6d.js.br +0 -0
- codex/static_root/assets/index-hvGLIBbY.30ce8cca0c6d.js.gz +0 -0
- codex/static_root/assets/index-hvGLIBbY.js +1 -0
- codex/static_root/assets/index-hvGLIBbY.js.br +0 -0
- codex/static_root/assets/index-hvGLIBbY.js.gz +0 -0
- codex/static_root/assets/library-tab-CF-RAEgP.a0ad816fb994.js +1 -0
- codex/static_root/assets/library-tab-CF-RAEgP.a0ad816fb994.js.br +0 -0
- codex/static_root/assets/library-tab-CF-RAEgP.a0ad816fb994.js.gz +0 -0
- codex/static_root/assets/library-tab-CF-RAEgP.js +1 -0
- codex/static_root/assets/library-tab-CF-RAEgP.js.br +0 -0
- codex/static_root/assets/library-tab-CF-RAEgP.js.gz +0 -0
- codex/static_root/assets/{library-tab-Cat0MAK5.5911f022188e.css → library-tab-Ce6qaeCV.75baedb7e976.css} +1 -1
- codex/static_root/assets/library-tab-Ce6qaeCV.75baedb7e976.css.br +0 -0
- codex/static_root/assets/library-tab-Ce6qaeCV.75baedb7e976.css.gz +0 -0
- codex/static_root/assets/{library-tab-Cat0MAK5.css → library-tab-Ce6qaeCV.css} +1 -1
- codex/static_root/assets/library-tab-Ce6qaeCV.css.br +0 -0
- codex/static_root/assets/library-tab-Ce6qaeCV.css.gz +0 -0
- codex/static_root/assets/main-Dv8e0bMI.bd74f1ba1627.js +35 -0
- codex/static_root/assets/main-Dv8e0bMI.bd74f1ba1627.js.br +0 -0
- codex/static_root/assets/main-Dv8e0bMI.bd74f1ba1627.js.gz +0 -0
- codex/static_root/assets/main-Dv8e0bMI.js +35 -0
- codex/static_root/assets/main-Dv8e0bMI.js.br +0 -0
- codex/static_root/assets/main-Dv8e0bMI.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.04a4f5c451b4.js +1 -0
- codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.04a4f5c451b4.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.04a4f5c451b4.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.js +1 -0
- codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DSe0TkMm.css +1 -0
- codex/static_root/assets/pagination-toolbar-DSe0TkMm.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DSe0TkMm.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DSe0TkMm.dcb0eba65827.css +1 -0
- codex/static_root/assets/pagination-toolbar-DSe0TkMm.dcb0eba65827.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DSe0TkMm.dcb0eba65827.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-D_whea1o.9bbd927058f1.js +1 -0
- codex/static_root/assets/pagination-toolbar-D_whea1o.9bbd927058f1.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-D_whea1o.9bbd927058f1.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-D_whea1o.js +1 -0
- codex/static_root/assets/pagination-toolbar-D_whea1o.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-D_whea1o.js.gz +0 -0
- codex/static_root/assets/pdf-doc-dnr5fHbU.fa1d7ed9255b.js +348 -0
- codex/static_root/assets/pdf-doc-dnr5fHbU.fa1d7ed9255b.js.br +0 -0
- codex/static_root/assets/pdf-doc-dnr5fHbU.fa1d7ed9255b.js.gz +0 -0
- codex/static_root/assets/pdf-doc-dnr5fHbU.js +348 -0
- codex/static_root/assets/pdf-doc-dnr5fHbU.js.br +0 -0
- codex/static_root/assets/pdf-doc-dnr5fHbU.js.gz +0 -0
- codex/static_root/assets/{reader-Dd2KmdCN.115d4bfe9280.css → reader-B3hgta6g.2ed32f08e906.css} +1 -1
- codex/static_root/assets/reader-B3hgta6g.2ed32f08e906.css.br +0 -0
- codex/static_root/assets/reader-B3hgta6g.2ed32f08e906.css.gz +0 -0
- codex/static_root/assets/{reader-Dd2KmdCN.css → reader-B3hgta6g.css} +1 -1
- codex/static_root/assets/reader-B3hgta6g.css.br +0 -0
- codex/static_root/assets/reader-B3hgta6g.css.gz +0 -0
- codex/static_root/assets/reader-Cn-ra_Dv.f38c0b419dec.js +2 -0
- codex/static_root/assets/reader-Cn-ra_Dv.f38c0b419dec.js.br +0 -0
- codex/static_root/assets/reader-Cn-ra_Dv.f38c0b419dec.js.gz +0 -0
- codex/static_root/assets/reader-Cn-ra_Dv.js +2 -0
- codex/static_root/assets/reader-Cn-ra_Dv.js.br +0 -0
- codex/static_root/assets/reader-Cn-ra_Dv.js.gz +0 -0
- codex/static_root/assets/relation-chips-fOPxWAMU.78e5fe70c014.js +1 -0
- codex/static_root/assets/relation-chips-fOPxWAMU.78e5fe70c014.js.br +0 -0
- codex/static_root/assets/relation-chips-fOPxWAMU.78e5fe70c014.js.gz +0 -0
- codex/static_root/assets/relation-chips-fOPxWAMU.js +1 -0
- codex/static_root/assets/relation-chips-fOPxWAMU.js.br +0 -0
- codex/static_root/assets/relation-chips-fOPxWAMU.js.gz +0 -0
- codex/static_root/assets/settings-drawer-1UEq8JY1.d95cee9aefb5.js +2 -0
- codex/static_root/assets/settings-drawer-1UEq8JY1.d95cee9aefb5.js.br +0 -0
- codex/static_root/assets/settings-drawer-1UEq8JY1.d95cee9aefb5.js.gz +0 -0
- codex/static_root/assets/settings-drawer-1UEq8JY1.js +2 -0
- codex/static_root/assets/settings-drawer-1UEq8JY1.js.br +0 -0
- codex/static_root/assets/settings-drawer-1UEq8JY1.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CqJ4gXLq.75a6ea969e71.css +1 -0
- codex/static_root/assets/settings-drawer-CqJ4gXLq.75a6ea969e71.css.br +0 -0
- codex/static_root/assets/settings-drawer-CqJ4gXLq.75a6ea969e71.css.gz +0 -0
- codex/static_root/assets/settings-drawer-CqJ4gXLq.css +1 -0
- codex/static_root/assets/settings-drawer-CqJ4gXLq.css.br +0 -0
- codex/static_root/assets/settings-drawer-CqJ4gXLq.css.gz +0 -0
- codex/static_root/assets/stats-tab-0ptRCeKw.5514d3508e37.js +1 -0
- codex/static_root/assets/stats-tab-0ptRCeKw.5514d3508e37.js.br +0 -0
- codex/static_root/assets/stats-tab-0ptRCeKw.5514d3508e37.js.gz +0 -0
- codex/static_root/assets/stats-tab-0ptRCeKw.js +1 -0
- codex/static_root/assets/stats-tab-0ptRCeKw.js.br +0 -0
- codex/static_root/assets/stats-tab-0ptRCeKw.js.gz +0 -0
- codex/static_root/assets/task-tab-CG9_bi6Z.ad9f647f8e5f.js +1 -0
- codex/static_root/assets/task-tab-CG9_bi6Z.ad9f647f8e5f.js.br +0 -0
- codex/static_root/assets/task-tab-CG9_bi6Z.ad9f647f8e5f.js.gz +0 -0
- codex/static_root/assets/task-tab-CG9_bi6Z.js +1 -0
- codex/static_root/assets/task-tab-CG9_bi6Z.js.br +0 -0
- codex/static_root/assets/task-tab-CG9_bi6Z.js.gz +0 -0
- codex/static_root/assets/unauthorized-VnIXUO1w.c5536d9dae75.css +1 -0
- codex/static_root/assets/unauthorized-VnIXUO1w.c5536d9dae75.css.br +0 -0
- codex/static_root/assets/unauthorized-VnIXUO1w.c5536d9dae75.css.gz +0 -0
- codex/static_root/assets/unauthorized-VnIXUO1w.css +1 -0
- codex/static_root/assets/unauthorized-VnIXUO1w.css.br +0 -0
- codex/static_root/assets/unauthorized-VnIXUO1w.css.gz +0 -0
- codex/static_root/assets/unauthorized-vG1Lh0m_.0c4bae81ee09.js +1 -0
- codex/static_root/assets/unauthorized-vG1Lh0m_.0c4bae81ee09.js.br +0 -0
- codex/static_root/assets/unauthorized-vG1Lh0m_.0c4bae81ee09.js.gz +0 -0
- codex/static_root/assets/unauthorized-vG1Lh0m_.js +1 -0
- codex/static_root/assets/unauthorized-vG1Lh0m_.js.br +0 -0
- codex/static_root/assets/unauthorized-vG1Lh0m_.js.gz +0 -0
- codex/static_root/assets/user-tab-BOxWlpY7.1a94280a799b.js +1 -0
- codex/static_root/assets/user-tab-BOxWlpY7.1a94280a799b.js.br +0 -0
- codex/static_root/assets/user-tab-BOxWlpY7.1a94280a799b.js.gz +0 -0
- codex/static_root/assets/user-tab-BOxWlpY7.js +1 -0
- codex/static_root/assets/user-tab-BOxWlpY7.js.br +0 -0
- codex/static_root/assets/user-tab-BOxWlpY7.js.gz +0 -0
- codex/static_root/manifest.9b3479a6a4a6.json +735 -0
- codex/static_root/manifest.9b3479a6a4a6.json.br +0 -0
- codex/static_root/manifest.9b3479a6a4a6.json.gz +0 -0
- codex/static_root/manifest.json +365 -342
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/templates/index.html +1 -1
- codex/threads.py +8 -3
- codex/urls/api/admin.py +1 -0
- codex/urls/api/browser.py +1 -1
- codex/urls/opds/v2.py +6 -0
- codex/views/admin/flag.py +5 -1
- codex/views/admin/tasks.py +2 -0
- codex/views/bookmark.py +53 -41
- codex/views/browser/annotate/bookmark.py +6 -4
- codex/views/browser/annotate/card.py +1 -16
- codex/views/browser/annotate/order.py +45 -18
- codex/views/browser/bookmark.py +53 -0
- codex/views/browser/download.py +2 -0
- codex/views/browser/filters/bookmark.py +4 -32
- codex/views/browser/group_mtime.py +2 -2
- codex/views/browser/metadata/annotate.py +1 -1
- codex/views/browser/metadata/metadata.py +1 -1
- codex/views/browser/order_by.py +6 -1
- codex/views/frontend.py +19 -4
- codex/views/mixins.py +16 -28
- codex/views/opds/const.py +2 -0
- codex/views/opds/util.py +2 -2
- codex/views/opds/v1/entry/links.py +1 -2
- codex/views/opds/v2/feed.py +3 -2
- codex/views/opds/v2/href.py +60 -0
- codex/views/opds/v2/links.py +13 -47
- codex/views/opds/v2/progression.py +137 -0
- codex/views/opds/v2/publications.py +21 -1
- codex/views/opds/v2/top_links.py +2 -1
- codex/views/public.py +11 -4
- codex/views/reader/books.py +19 -8
- codex/views/reader/page.py +5 -5
- {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/METADATA +21 -26
- {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/RECORD +387 -370
- codex/serializers/opds/v2.py +0 -178
- codex/static_root/assets/VCheckbox-DLMzIII1.1ff8fc2d9a21.js +0 -1
- codex/static_root/assets/VCheckbox-DLMzIII1.1ff8fc2d9a21.js.br +0 -0
- codex/static_root/assets/VCheckbox-DLMzIII1.1ff8fc2d9a21.js.gz +0 -0
- codex/static_root/assets/VCheckbox-DLMzIII1.js +0 -1
- codex/static_root/assets/VCheckbox-DLMzIII1.js.br +0 -0
- codex/static_root/assets/VCheckbox-DLMzIII1.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BzoQl8XC.094679a3624b.js +0 -1
- codex/static_root/assets/VCheckboxBtn-BzoQl8XC.094679a3624b.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BzoQl8XC.094679a3624b.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BzoQl8XC.js +0 -1
- codex/static_root/assets/VCheckboxBtn-BzoQl8XC.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BzoQl8XC.js.gz +0 -0
- codex/static_root/assets/VCombobox-CBY8K5CP.fcf2221a9edd.js +0 -1
- codex/static_root/assets/VCombobox-CBY8K5CP.fcf2221a9edd.js.br +0 -0
- codex/static_root/assets/VCombobox-CBY8K5CP.fcf2221a9edd.js.gz +0 -0
- codex/static_root/assets/VCombobox-CBY8K5CP.js +0 -1
- codex/static_root/assets/VCombobox-CBY8K5CP.js.br +0 -0
- codex/static_root/assets/VCombobox-CBY8K5CP.js.gz +0 -0
- codex/static_root/assets/VDialog-DXQz0Ylp.7b23d86dbd0d.css +0 -1
- codex/static_root/assets/VDialog-DXQz0Ylp.7b23d86dbd0d.css.br +0 -0
- codex/static_root/assets/VDialog-DXQz0Ylp.7b23d86dbd0d.css.gz +0 -0
- codex/static_root/assets/VDialog-DXQz0Ylp.css +0 -1
- codex/static_root/assets/VDialog-DXQz0Ylp.css.br +0 -0
- codex/static_root/assets/VDialog-DXQz0Ylp.css.gz +0 -0
- codex/static_root/assets/VDialog-Db4tJYqR.e21a79a5030e.js +0 -1
- codex/static_root/assets/VDialog-Db4tJYqR.e21a79a5030e.js.br +0 -0
- codex/static_root/assets/VDialog-Db4tJYqR.e21a79a5030e.js.gz +0 -0
- codex/static_root/assets/VDialog-Db4tJYqR.js +0 -1
- codex/static_root/assets/VDialog-Db4tJYqR.js.br +0 -0
- codex/static_root/assets/VDialog-Db4tJYqR.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DN4yGbOt.163e59e38865.js +0 -1
- codex/static_root/assets/VExpansionPanels-DN4yGbOt.163e59e38865.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DN4yGbOt.163e59e38865.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DN4yGbOt.js +0 -1
- codex/static_root/assets/VExpansionPanels-DN4yGbOt.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DN4yGbOt.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D2-PWEfs.ce7644863c25.js +0 -1
- codex/static_root/assets/VRadioGroup-D2-PWEfs.ce7644863c25.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D2-PWEfs.ce7644863c25.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D2-PWEfs.js +0 -1
- codex/static_root/assets/VRadioGroup-D2-PWEfs.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D2-PWEfs.js.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +0 -1
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css +0 -1
- codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
- codex/static_root/assets/VSelect-DqXJ23Pc.a1360cbed050.js +0 -1
- codex/static_root/assets/VSelect-DqXJ23Pc.a1360cbed050.js.br +0 -0
- codex/static_root/assets/VSelect-DqXJ23Pc.a1360cbed050.js.gz +0 -0
- codex/static_root/assets/VSelect-DqXJ23Pc.js +0 -1
- codex/static_root/assets/VSelect-DqXJ23Pc.js.br +0 -0
- codex/static_root/assets/VSelect-DqXJ23Pc.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css +0 -1
- codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css.br +0 -0
- codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-CiViEYoC.css +0 -1
- codex/static_root/assets/VSelectionControl-CiViEYoC.css.br +0 -0
- codex/static_root/assets/VSelectionControl-CiViEYoC.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-CxhDNufT.dd7ca99567fc.js +0 -1
- codex/static_root/assets/VSelectionControl-CxhDNufT.dd7ca99567fc.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CxhDNufT.dd7ca99567fc.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-CxhDNufT.js +0 -1
- codex/static_root/assets/VSelectionControl-CxhDNufT.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CxhDNufT.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +0 -1
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css +0 -1
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-DRbthp6q.7365c5542d47.js +0 -1
- codex/static_root/assets/VSlideGroup-DRbthp6q.7365c5542d47.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DRbthp6q.7365c5542d47.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-DRbthp6q.js +0 -1
- codex/static_root/assets/VSlideGroup-DRbthp6q.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DRbthp6q.js.gz +0 -0
- codex/static_root/assets/VTable-Cbq-yJos.c884706cac21.js +0 -1
- codex/static_root/assets/VTable-Cbq-yJos.c884706cac21.js.br +0 -0
- codex/static_root/assets/VTable-Cbq-yJos.c884706cac21.js.gz +0 -0
- codex/static_root/assets/VTable-Cbq-yJos.js +0 -1
- codex/static_root/assets/VTable-Cbq-yJos.js.br +0 -0
- codex/static_root/assets/VTable-Cbq-yJos.js.gz +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css +0 -1
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
- codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
- codex/static_root/assets/VTextField-X5yAw35c.155e276dd747.js +0 -1
- codex/static_root/assets/VTextField-X5yAw35c.155e276dd747.js.br +0 -0
- codex/static_root/assets/VTextField-X5yAw35c.155e276dd747.js.gz +0 -0
- codex/static_root/assets/VTextField-X5yAw35c.js +0 -1
- codex/static_root/assets/VTextField-X5yAw35c.js.br +0 -0
- codex/static_root/assets/VTextField-X5yAw35c.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CRqhVh-9.cd73d01e0591.js +0 -1
- codex/static_root/assets/VWindowItem-CRqhVh-9.cd73d01e0591.js.br +0 -0
- codex/static_root/assets/VWindowItem-CRqhVh-9.cd73d01e0591.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CRqhVh-9.js +0 -1
- codex/static_root/assets/VWindowItem-CRqhVh-9.js.br +0 -0
- codex/static_root/assets/VWindowItem-CRqhVh-9.js.gz +0 -0
- codex/static_root/assets/admin-BFvQwinN.6cfff21f780a.js +0 -1
- codex/static_root/assets/admin-BFvQwinN.6cfff21f780a.js.br +0 -0
- codex/static_root/assets/admin-BFvQwinN.6cfff21f780a.js.gz +0 -0
- codex/static_root/assets/admin-BFvQwinN.js +0 -1
- codex/static_root/assets/admin-BFvQwinN.js.br +0 -0
- codex/static_root/assets/admin-BFvQwinN.js.gz +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.css +0 -1
- codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css +0 -1
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
- codex/static_root/assets/admin-menu-Jfmp_Yd8.39fa7a18b3b4.js +0 -1
- codex/static_root/assets/admin-menu-Jfmp_Yd8.39fa7a18b3b4.js.br +0 -0
- codex/static_root/assets/admin-menu-Jfmp_Yd8.39fa7a18b3b4.js.gz +0 -0
- codex/static_root/assets/admin-menu-Jfmp_Yd8.js +0 -1
- codex/static_root/assets/admin-menu-Jfmp_Yd8.js.br +0 -0
- codex/static_root/assets/admin-menu-Jfmp_Yd8.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.cbf73c71330d.js +0 -1
- codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.cbf73c71330d.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.cbf73c71330d.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.js +0 -1
- codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.js.gz +0 -0
- codex/static_root/assets/browser-CctlSJji.css +0 -1
- codex/static_root/assets/browser-CctlSJji.css.br +0 -0
- codex/static_root/assets/browser-CctlSJji.css.gz +0 -0
- codex/static_root/assets/browser-CctlSJji.d42d796476d2.css +0 -1
- codex/static_root/assets/browser-CctlSJji.d42d796476d2.css.br +0 -0
- codex/static_root/assets/browser-CctlSJji.d42d796476d2.css.gz +0 -0
- codex/static_root/assets/browser-DJnEeVHz.a78c47516b08.js +0 -1
- codex/static_root/assets/browser-DJnEeVHz.a78c47516b08.js.br +0 -0
- codex/static_root/assets/browser-DJnEeVHz.a78c47516b08.js.gz +0 -0
- codex/static_root/assets/browser-DJnEeVHz.js +0 -1
- codex/static_root/assets/browser-DJnEeVHz.js.br +0 -0
- codex/static_root/assets/browser-DJnEeVHz.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DJtOQw7K.be7fa5ec653d.js +0 -1
- codex/static_root/assets/change-password-dialog-DJtOQw7K.be7fa5ec653d.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DJtOQw7K.be7fa5ec653d.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DJtOQw7K.js +0 -1
- codex/static_root/assets/change-password-dialog-DJtOQw7K.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DJtOQw7K.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CME4gKSZ.1606233e784c.js +0 -1
- codex/static_root/assets/confirm-dialog-CME4gKSZ.1606233e784c.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CME4gKSZ.1606233e784c.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CME4gKSZ.js +0 -1
- codex/static_root/assets/confirm-dialog-CME4gKSZ.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CME4gKSZ.js.gz +0 -0
- codex/static_root/assets/datetime-column-D_F30OLW.09cdce0ef96e.js +0 -1
- codex/static_root/assets/datetime-column-D_F30OLW.09cdce0ef96e.js.br +0 -2
- codex/static_root/assets/datetime-column-D_F30OLW.09cdce0ef96e.js.gz +0 -0
- codex/static_root/assets/datetime-column-D_F30OLW.js +0 -1
- codex/static_root/assets/datetime-column-D_F30OLW.js.br +0 -2
- codex/static_root/assets/datetime-column-D_F30OLW.js.gz +0 -0
- codex/static_root/assets/filter-ud5mPZpn.0f0a537959aa.js.br +0 -0
- codex/static_root/assets/filter-ud5mPZpn.0f0a537959aa.js.gz +0 -0
- codex/static_root/assets/filter-ud5mPZpn.js.br +0 -0
- codex/static_root/assets/filter-ud5mPZpn.js.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +0 -1
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css +0 -1
- codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
- codex/static_root/assets/flag-tab-k2g6u_vi.3e1da9318af9.js +0 -1
- codex/static_root/assets/flag-tab-k2g6u_vi.3e1da9318af9.js.br +0 -0
- codex/static_root/assets/flag-tab-k2g6u_vi.3e1da9318af9.js.gz +0 -0
- codex/static_root/assets/flag-tab-k2g6u_vi.js +0 -1
- codex/static_root/assets/flag-tab-k2g6u_vi.js.br +0 -0
- codex/static_root/assets/flag-tab-k2g6u_vi.js.gz +0 -0
- codex/static_root/assets/group-tab-C6Ykf03Z.ff0cc204013a.js +0 -1
- codex/static_root/assets/group-tab-C6Ykf03Z.ff0cc204013a.js.br +0 -0
- codex/static_root/assets/group-tab-C6Ykf03Z.ff0cc204013a.js.gz +0 -0
- codex/static_root/assets/group-tab-C6Ykf03Z.js +0 -1
- codex/static_root/assets/group-tab-C6Ykf03Z.js.br +0 -0
- codex/static_root/assets/group-tab-C6Ykf03Z.js.gz +0 -0
- codex/static_root/assets/http-error-Ctcws1xe.cfd378a792d2.css +0 -1
- codex/static_root/assets/http-error-Ctcws1xe.cfd378a792d2.css.br +0 -0
- codex/static_root/assets/http-error-Ctcws1xe.cfd378a792d2.css.gz +0 -0
- codex/static_root/assets/http-error-Ctcws1xe.css +0 -1
- codex/static_root/assets/http-error-Ctcws1xe.css.br +0 -0
- codex/static_root/assets/http-error-Ctcws1xe.css.gz +0 -0
- codex/static_root/assets/http-error-DQBHK8v5.aea50b1327fd.js +0 -1
- codex/static_root/assets/http-error-DQBHK8v5.aea50b1327fd.js.br +0 -0
- codex/static_root/assets/http-error-DQBHK8v5.aea50b1327fd.js.gz +0 -0
- codex/static_root/assets/http-error-DQBHK8v5.js +0 -1
- codex/static_root/assets/http-error-DQBHK8v5.js.br +0 -0
- codex/static_root/assets/http-error-DQBHK8v5.js.gz +0 -0
- codex/static_root/assets/index-ClGiHwWc.0717bacf5135.js +0 -1
- codex/static_root/assets/index-ClGiHwWc.0717bacf5135.js.br +0 -0
- codex/static_root/assets/index-ClGiHwWc.0717bacf5135.js.gz +0 -0
- codex/static_root/assets/index-ClGiHwWc.js +0 -1
- codex/static_root/assets/index-ClGiHwWc.js.br +0 -0
- codex/static_root/assets/index-ClGiHwWc.js.gz +0 -0
- codex/static_root/assets/library-tab-BpinbqAu.53fa2625e727.js +0 -1
- codex/static_root/assets/library-tab-BpinbqAu.53fa2625e727.js.br +0 -0
- codex/static_root/assets/library-tab-BpinbqAu.53fa2625e727.js.gz +0 -0
- codex/static_root/assets/library-tab-BpinbqAu.js +0 -1
- codex/static_root/assets/library-tab-BpinbqAu.js.br +0 -0
- codex/static_root/assets/library-tab-BpinbqAu.js.gz +0 -0
- codex/static_root/assets/library-tab-Cat0MAK5.5911f022188e.css.br +0 -0
- codex/static_root/assets/library-tab-Cat0MAK5.5911f022188e.css.gz +0 -0
- codex/static_root/assets/library-tab-Cat0MAK5.css.br +0 -0
- codex/static_root/assets/library-tab-Cat0MAK5.css.gz +0 -0
- codex/static_root/assets/main-BVKrDunc.1c6f8cdc3afb.js +0 -35
- codex/static_root/assets/main-BVKrDunc.1c6f8cdc3afb.js.br +0 -0
- codex/static_root/assets/main-BVKrDunc.1c6f8cdc3afb.js.gz +0 -0
- codex/static_root/assets/main-BVKrDunc.js +0 -35
- codex/static_root/assets/main-BVKrDunc.js.br +0 -0
- codex/static_root/assets/main-BVKrDunc.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-C1ZSyXfG.f2e95b19621c.js +0 -1
- codex/static_root/assets/pager-full-pdf-C1ZSyXfG.f2e95b19621c.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-C1ZSyXfG.f2e95b19621c.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-C1ZSyXfG.js +0 -1
- codex/static_root/assets/pager-full-pdf-C1ZSyXfG.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-C1ZSyXfG.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Cx_9nh6D.546121fa7a0d.css +0 -1
- codex/static_root/assets/pagination-toolbar-Cx_9nh6D.546121fa7a0d.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-Cx_9nh6D.546121fa7a0d.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Cx_9nh6D.css +0 -1
- codex/static_root/assets/pagination-toolbar-Cx_9nh6D.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-Cx_9nh6D.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Diad0EdD.140911bf86af.js +0 -1
- codex/static_root/assets/pagination-toolbar-Diad0EdD.140911bf86af.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Diad0EdD.140911bf86af.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Diad0EdD.js +0 -1
- codex/static_root/assets/pagination-toolbar-Diad0EdD.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Diad0EdD.js.gz +0 -0
- codex/static_root/assets/pdf-doc-b-GyNR6z.c9d18f0d36ed.js +0 -305
- codex/static_root/assets/pdf-doc-b-GyNR6z.c9d18f0d36ed.js.br +0 -0
- codex/static_root/assets/pdf-doc-b-GyNR6z.c9d18f0d36ed.js.gz +0 -0
- codex/static_root/assets/pdf-doc-b-GyNR6z.js +0 -305
- codex/static_root/assets/pdf-doc-b-GyNR6z.js.br +0 -0
- codex/static_root/assets/pdf-doc-b-GyNR6z.js.gz +0 -0
- codex/static_root/assets/reader-2up6WEuE.c56ad81d14c6.js +0 -2
- codex/static_root/assets/reader-2up6WEuE.c56ad81d14c6.js.br +0 -0
- codex/static_root/assets/reader-2up6WEuE.c56ad81d14c6.js.gz +0 -0
- codex/static_root/assets/reader-2up6WEuE.js +0 -2
- codex/static_root/assets/reader-2up6WEuE.js.br +0 -0
- codex/static_root/assets/reader-2up6WEuE.js.gz +0 -0
- codex/static_root/assets/reader-Dd2KmdCN.115d4bfe9280.css.br +0 -0
- codex/static_root/assets/reader-Dd2KmdCN.115d4bfe9280.css.gz +0 -0
- codex/static_root/assets/reader-Dd2KmdCN.css.br +0 -0
- codex/static_root/assets/reader-Dd2KmdCN.css.gz +0 -0
- codex/static_root/assets/relation-chips-piYv1IcD.f8a2ae79de89.js +0 -1
- codex/static_root/assets/relation-chips-piYv1IcD.f8a2ae79de89.js.br +0 -0
- codex/static_root/assets/relation-chips-piYv1IcD.f8a2ae79de89.js.gz +0 -0
- codex/static_root/assets/relation-chips-piYv1IcD.js +0 -1
- codex/static_root/assets/relation-chips-piYv1IcD.js.br +0 -0
- codex/static_root/assets/relation-chips-piYv1IcD.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css +0 -1
- codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css.br +0 -0
- codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css.gz +0 -0
- codex/static_root/assets/settings-drawer-DKncO_vX.css +0 -1
- codex/static_root/assets/settings-drawer-DKncO_vX.css.br +0 -0
- codex/static_root/assets/settings-drawer-DKncO_vX.css.gz +0 -0
- codex/static_root/assets/settings-drawer-yVoY-WCS.1240cc47a246.js +0 -2
- codex/static_root/assets/settings-drawer-yVoY-WCS.1240cc47a246.js.br +0 -0
- codex/static_root/assets/settings-drawer-yVoY-WCS.1240cc47a246.js.gz +0 -0
- codex/static_root/assets/settings-drawer-yVoY-WCS.js +0 -2
- codex/static_root/assets/settings-drawer-yVoY-WCS.js.br +0 -0
- codex/static_root/assets/settings-drawer-yVoY-WCS.js.gz +0 -0
- codex/static_root/assets/stats-tab-bqSPLvTp.05106cb03ef0.js +0 -1
- codex/static_root/assets/stats-tab-bqSPLvTp.05106cb03ef0.js.br +0 -0
- codex/static_root/assets/stats-tab-bqSPLvTp.05106cb03ef0.js.gz +0 -0
- codex/static_root/assets/stats-tab-bqSPLvTp.js +0 -1
- codex/static_root/assets/stats-tab-bqSPLvTp.js.br +0 -0
- codex/static_root/assets/stats-tab-bqSPLvTp.js.gz +0 -0
- codex/static_root/assets/task-tab-DWPKaGPT.da4df28dde5c.js +0 -1
- codex/static_root/assets/task-tab-DWPKaGPT.da4df28dde5c.js.br +0 -0
- codex/static_root/assets/task-tab-DWPKaGPT.da4df28dde5c.js.gz +0 -0
- codex/static_root/assets/task-tab-DWPKaGPT.js +0 -1
- codex/static_root/assets/task-tab-DWPKaGPT.js.br +0 -0
- codex/static_root/assets/task-tab-DWPKaGPT.js.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +0 -1
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +0 -4
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.css +0 -1
- codex/static_root/assets/transition-5LRFTPo5.css.br +0 -4
- codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
- codex/static_root/assets/transition-DWVQ2EZ3.0bdfaa37b937.js +0 -1
- codex/static_root/assets/transition-DWVQ2EZ3.0bdfaa37b937.js.br +0 -0
- codex/static_root/assets/transition-DWVQ2EZ3.0bdfaa37b937.js.gz +0 -0
- codex/static_root/assets/transition-DWVQ2EZ3.js +0 -1
- codex/static_root/assets/transition-DWVQ2EZ3.js.br +0 -0
- codex/static_root/assets/transition-DWVQ2EZ3.js.gz +0 -0
- codex/static_root/assets/unauthorized-CEGD20tR.064b0993a944.js +0 -1
- codex/static_root/assets/unauthorized-CEGD20tR.064b0993a944.js.br +0 -0
- codex/static_root/assets/unauthorized-CEGD20tR.064b0993a944.js.gz +0 -0
- codex/static_root/assets/unauthorized-CEGD20tR.js +0 -1
- codex/static_root/assets/unauthorized-CEGD20tR.js.br +0 -0
- codex/static_root/assets/unauthorized-CEGD20tR.js.gz +0 -0
- codex/static_root/assets/unauthorized-DyojW2ic.css +0 -1
- codex/static_root/assets/unauthorized-DyojW2ic.css.br +0 -0
- codex/static_root/assets/unauthorized-DyojW2ic.css.gz +0 -0
- codex/static_root/assets/unauthorized-DyojW2ic.feefa7628a23.css +0 -1
- codex/static_root/assets/unauthorized-DyojW2ic.feefa7628a23.css.br +0 -0
- codex/static_root/assets/unauthorized-DyojW2ic.feefa7628a23.css.gz +0 -0
- codex/static_root/assets/user-tab-EsokzMCB.b0acc46b56a3.js +0 -1
- codex/static_root/assets/user-tab-EsokzMCB.b0acc46b56a3.js.br +0 -0
- codex/static_root/assets/user-tab-EsokzMCB.b0acc46b56a3.js.gz +0 -0
- codex/static_root/assets/user-tab-EsokzMCB.js +0 -1
- codex/static_root/assets/user-tab-EsokzMCB.js.br +0 -0
- codex/static_root/assets/user-tab-EsokzMCB.js.gz +0 -0
- codex/static_root/manifest.4a38de3db1c5.json +0 -712
- codex/static_root/manifest.4a38de3db1c5.json.br +0 -0
- codex/static_root/manifest.4a38de3db1c5.json.gz +0 -0
- {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/LICENSE +0 -0
- {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/WHEEL +0 -0
- {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/entry_points.txt +0 -0
codex/choices.py
CHANGED
|
@@ -12,17 +12,18 @@ BROWSER_BOOKMARK_FILTER_CHOICES = MappingProxyType(
|
|
|
12
12
|
)
|
|
13
13
|
BROWSER_ORDER_BY_CHOICES = MappingProxyType(
|
|
14
14
|
{
|
|
15
|
+
"created_at": "Added Time",
|
|
15
16
|
"age_rating": "Age Rating",
|
|
16
|
-
"
|
|
17
|
+
"child_count": "Child Count",
|
|
17
18
|
"community_rating": "Community Rating",
|
|
18
|
-
"created_at": "Added Time",
|
|
19
19
|
"critical_rating": "Critical Rating",
|
|
20
|
-
"date": "Publish Date",
|
|
21
20
|
"filename": "Filename",
|
|
22
|
-
"page_count": "Page Count",
|
|
23
|
-
"search_score": "Search Score",
|
|
24
21
|
"size": "File Size",
|
|
22
|
+
"bookmark_updated_at": "Last Read",
|
|
25
23
|
"sort_name": "Name",
|
|
24
|
+
"page_count": "Page Count",
|
|
25
|
+
"date": "Publish Date",
|
|
26
|
+
"search_score": "Search Score",
|
|
26
27
|
"story_arc_number": "Story Arc Number",
|
|
27
28
|
"updated_at": "Updated Time",
|
|
28
29
|
}
|
|
@@ -116,6 +117,7 @@ ADMIN_FLAG_CHOICES = MappingProxyType(
|
|
|
116
117
|
"NU": "Non Users",
|
|
117
118
|
"RG": "Registration",
|
|
118
119
|
"ST": "Send Stats",
|
|
120
|
+
"BT": "Banner Text",
|
|
119
121
|
}
|
|
120
122
|
)
|
|
121
123
|
|
|
@@ -159,6 +161,7 @@ ADMIN_STATUS_TITLES = MappingProxyType(
|
|
|
159
161
|
"JLV": "Check Codex Latest Version",
|
|
160
162
|
"JSD": "Cleanup Expired Sessions",
|
|
161
163
|
"JTD": "Cleanup Stale Tags",
|
|
164
|
+
"JCL": "Compress Logs",
|
|
162
165
|
"SIC": "Search Index Create",
|
|
163
166
|
"SID": "Search Index Remove Stale Records",
|
|
164
167
|
"SIO": "Search Index Optimize",
|
|
@@ -374,6 +377,11 @@ ADMIN_TASK_GROUPS = MappingProxyType(
|
|
|
374
377
|
"title": "Clear Librarian Statuses",
|
|
375
378
|
"desc": "Mark all Librarian tasks finished.",
|
|
376
379
|
},
|
|
380
|
+
{
|
|
381
|
+
"value": "compress_logs",
|
|
382
|
+
"title": "Compress Old Logs",
|
|
383
|
+
"desc": "Compress old Codex logs. Runs nightly.",
|
|
384
|
+
},
|
|
377
385
|
{
|
|
378
386
|
"value": "janitor_nightly",
|
|
379
387
|
"title": "Run Nightly Maintenance",
|
|
@@ -14,7 +14,7 @@ class BookmarkThread(AggregateMessageQueuedThread, BookmarkUpdate):
|
|
|
14
14
|
def aggregate_items(self, item):
|
|
15
15
|
"""Aggregate bookmark updates."""
|
|
16
16
|
if isinstance(item, BookmarkUpdateTask):
|
|
17
|
-
key = (tuple(item.auth_filter.items()),
|
|
17
|
+
key = (tuple(item.auth_filter.items()), item.comic_pks)
|
|
18
18
|
if key not in self.cache:
|
|
19
19
|
self.cache[key] = {}
|
|
20
20
|
self.cache[key].update(item.updates)
|
|
@@ -26,11 +26,10 @@ class BookmarkThread(AggregateMessageQueuedThread, BookmarkUpdate):
|
|
|
26
26
|
cleanup = set()
|
|
27
27
|
for filters, updates in self.cache.items():
|
|
28
28
|
try:
|
|
29
|
-
auth_filter,
|
|
29
|
+
auth_filter, comic_pks = filters
|
|
30
30
|
auth_filter = dict(auth_filter)
|
|
31
|
-
|
|
32
|
-
self.
|
|
33
|
-
self.log.debug(f"updated {auth_filter | comic_filter}")
|
|
31
|
+
self.update_bookmarks(auth_filter, comic_pks, updates, self.log)
|
|
32
|
+
self.log.debug(f"updated {auth_filter} pk__in={comic_pks}")
|
|
34
33
|
cleanup.add(filters)
|
|
35
34
|
except Exception:
|
|
36
35
|
self.log.exception("Updating bookmarks")
|
|
@@ -10,9 +10,8 @@ from codex.librarian.notifier.tasks import NotifierTask
|
|
|
10
10
|
from codex.models import Bookmark, Comic
|
|
11
11
|
from codex.models.admin import UserActive
|
|
12
12
|
from codex.views.auth import GroupACLMixin
|
|
13
|
-
from codex.views.mixins import BookmarkSearchMixin
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
_VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
|
|
16
15
|
_BOOKMARK_UPDATE_FIELDS = frozenset(
|
|
17
16
|
{
|
|
18
17
|
"page",
|
|
@@ -22,10 +21,9 @@ _BOOKMARK_UPDATE_FIELDS = frozenset(
|
|
|
22
21
|
"reading_direction",
|
|
23
22
|
}
|
|
24
23
|
)
|
|
25
|
-
_COMIC_ONLY_FIELDS = ("pk", "page_count")
|
|
26
24
|
|
|
27
25
|
|
|
28
|
-
class BookmarkUpdate(GroupACLMixin
|
|
26
|
+
class BookmarkUpdate(GroupACLMixin):
|
|
29
27
|
"""Update Bookmarks."""
|
|
30
28
|
|
|
31
29
|
# Used by Bookmarkd and view.bookmark.
|
|
@@ -47,10 +45,8 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
|
|
|
47
45
|
def _update_bookmarks_validate_two_pages(bm, updates):
|
|
48
46
|
"""Force vertical view to not use two pages."""
|
|
49
47
|
rd = updates.get("reading_direction")
|
|
50
|
-
if (
|
|
51
|
-
bm.
|
|
52
|
-
and rd in VERTICAL_READING_DIRECTIONS
|
|
53
|
-
or bm.reading_direction in VERTICAL_READING_DIRECTIONS
|
|
48
|
+
if bm.two_pages and bool(
|
|
49
|
+
{rd, bm.reading_direction}.intersection(_VERTICAL_READING_DIRECTIONS)
|
|
54
50
|
):
|
|
55
51
|
updates["two_pages"] = None
|
|
56
52
|
|
|
@@ -62,13 +58,13 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
|
|
|
62
58
|
LIBRARIAN_QUEUE.put(task)
|
|
63
59
|
|
|
64
60
|
@classmethod
|
|
65
|
-
def _update_bookmarks(cls,
|
|
61
|
+
def _update_bookmarks(cls, auth_filter, comic_pks, updates, user) -> int:
|
|
66
62
|
"""Update existing bookmarks."""
|
|
67
63
|
count = 0
|
|
68
64
|
if not updates:
|
|
69
65
|
return count
|
|
70
66
|
group_acl_filter = cls.get_group_acl_filter(Bookmark, user)
|
|
71
|
-
query_filter = group_acl_filter & Q(**
|
|
67
|
+
query_filter = group_acl_filter & Q(**auth_filter) & Q(comic__in=comic_pks)
|
|
72
68
|
existing_bookmarks = Bookmark.objects.filter(query_filter)
|
|
73
69
|
if updates.get("page") is not None:
|
|
74
70
|
existing_bookmarks = existing_bookmarks.annotate(
|
|
@@ -98,12 +94,12 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
|
|
|
98
94
|
"""Force vertical view to not use two pages."""
|
|
99
95
|
if (
|
|
100
96
|
updates.get("two_pages")
|
|
101
|
-
and updates.get("reading_direction") in
|
|
97
|
+
and updates.get("reading_direction") in _VERTICAL_READING_DIRECTIONS
|
|
102
98
|
):
|
|
103
99
|
updates.pop("two_pages", None)
|
|
104
100
|
|
|
105
101
|
@classmethod
|
|
106
|
-
def _create_bookmarks(cls,
|
|
102
|
+
def _create_bookmarks(cls, auth_filter, comic_pks, updates, user) -> int:
|
|
107
103
|
"""Create new bookmarks for comics that don't exist yet."""
|
|
108
104
|
count = 0
|
|
109
105
|
cls._create_bookmarks_validate_two_pages(updates)
|
|
@@ -112,20 +108,12 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
|
|
|
112
108
|
create_bookmarks = []
|
|
113
109
|
group_acl_filter = cls.get_group_acl_filter(Comic, user)
|
|
114
110
|
exclude = {}
|
|
115
|
-
for key, value in
|
|
111
|
+
for key, value in auth_filter.items():
|
|
116
112
|
exclude["bookmark__" + key] = value
|
|
117
|
-
query_filter = group_acl_filter & Q(
|
|
118
|
-
create_bookmark_comics = Comic.objects.filter(query_filter).only(
|
|
119
|
-
*_COMIC_ONLY_FIELDS
|
|
120
|
-
)
|
|
113
|
+
query_filter = group_acl_filter & Q(pk__in=comic_pks) & ~Q(**exclude)
|
|
114
|
+
create_bookmark_comics = Comic.objects.filter(query_filter).only("pk")
|
|
121
115
|
for comic in create_bookmark_comics:
|
|
122
|
-
|
|
123
|
-
defaults.update(updates)
|
|
124
|
-
for key, value in search_kwargs.items():
|
|
125
|
-
if not key.startswith("comic"):
|
|
126
|
-
defaults[key] = value
|
|
127
|
-
|
|
128
|
-
bm = Bookmark(**defaults)
|
|
116
|
+
bm = Bookmark(comic=comic, **auth_filter, **updates)
|
|
129
117
|
create_bookmarks.append(bm)
|
|
130
118
|
count = len(create_bookmarks)
|
|
131
119
|
if count:
|
|
@@ -147,18 +135,16 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
|
|
|
147
135
|
log.warning(reason)
|
|
148
136
|
|
|
149
137
|
@classmethod
|
|
150
|
-
def update_bookmarks(cls, auth_filter,
|
|
138
|
+
def update_bookmarks(cls, auth_filter, comic_pks, updates, log):
|
|
151
139
|
"""Update a user bookmark."""
|
|
152
|
-
search_kwargs = cls.get_bookmark_search_kwargs(auth_filter, comic_filter)
|
|
153
|
-
|
|
154
140
|
user = None
|
|
155
141
|
for key, value in auth_filter.items():
|
|
156
142
|
if key == "user":
|
|
157
143
|
user = User.objects.get(pk=value)
|
|
158
144
|
break
|
|
159
145
|
|
|
160
|
-
count = cls._update_bookmarks(
|
|
161
|
-
count += cls._create_bookmarks(
|
|
146
|
+
count = cls._update_bookmarks(auth_filter, comic_pks, updates, user)
|
|
147
|
+
count += cls._create_bookmarks(auth_filter, comic_pks, updates, user)
|
|
162
148
|
if user:
|
|
163
149
|
cls._update_user_active(user, log)
|
|
164
150
|
if count:
|
codex/librarian/cron/crond.py
CHANGED
|
@@ -28,7 +28,7 @@ class CronThread(NamedThread):
|
|
|
28
28
|
self._stop_event = Event()
|
|
29
29
|
self._cond = Condition()
|
|
30
30
|
self._task_times = ()
|
|
31
|
-
super().__init__(*args,
|
|
31
|
+
super().__init__(*args, daemon=True, **kwargs)
|
|
32
32
|
|
|
33
33
|
def _create_task_times(self):
|
|
34
34
|
task_times = {}
|
|
@@ -96,7 +96,7 @@ class QueryForeignKeysImporter(QueryCustomCoversImporter):
|
|
|
96
96
|
if extra_fields:
|
|
97
97
|
fields += extra_fields
|
|
98
98
|
flat = len(fields) == 1 and fk_cls != Publisher
|
|
99
|
-
qs = fk_cls.objects.filter(fk_filter).values_list(*fields, flat=flat)
|
|
99
|
+
qs = fk_cls.objects.filter(fk_filter).distinct().values_list(*fields, flat=flat)
|
|
100
100
|
return frozenset(qs)
|
|
101
101
|
|
|
102
102
|
def _query_create_metadata( # noqa: PLR0913
|
|
@@ -449,7 +449,7 @@ class QueryForeignKeysImporter(QueryCustomCoversImporter):
|
|
|
449
449
|
|
|
450
450
|
# Build combined query object from the value_filter
|
|
451
451
|
model = DICT_MODEL_FIELD_MODEL_MAP[field_name]
|
|
452
|
-
if model
|
|
452
|
+
if model in (Identifier, Contributor):
|
|
453
453
|
for filter_and_prefix, value_filter in query_filter_map.items():
|
|
454
454
|
self._query_create_metadata(
|
|
455
455
|
model,
|
|
@@ -10,6 +10,7 @@ from codex.librarian.janitor.cleanup import TOTAL_NUM_FK_CLASSES, CleanupMixin
|
|
|
10
10
|
from codex.librarian.janitor.failed_imports import UpdateFailedImportsMixin
|
|
11
11
|
from codex.librarian.janitor.integrity import IntegrityMixin
|
|
12
12
|
from codex.librarian.janitor.latest_version import LatestVersionMixin
|
|
13
|
+
from codex.librarian.janitor.log_compress import LogCompressMixin
|
|
13
14
|
from codex.librarian.janitor.status import JanitorStatusTypes
|
|
14
15
|
from codex.librarian.janitor.tasks import (
|
|
15
16
|
ForceUpdateAllFailedImportsTask,
|
|
@@ -20,6 +21,7 @@ from codex.librarian.janitor.tasks import (
|
|
|
20
21
|
JanitorCleanupBookmarksTask,
|
|
21
22
|
JanitorCleanupSessionsTask,
|
|
22
23
|
JanitorClearStatusTask,
|
|
24
|
+
JanitorCompressOldLogs,
|
|
23
25
|
JanitorForeignKeyCheck,
|
|
24
26
|
JanitorFTSIntegrityCheck,
|
|
25
27
|
JanitorFTSRebuildTask,
|
|
@@ -61,6 +63,7 @@ _JANITOR_STATII = (
|
|
|
61
63
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_UPDATE),
|
|
62
64
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
|
|
63
65
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_OPTIMIZE),
|
|
66
|
+
Status(JanitorStatusTypes.COMPRESS_LOGS),
|
|
64
67
|
)
|
|
65
68
|
|
|
66
69
|
|
|
@@ -71,6 +74,7 @@ class Janitor(
|
|
|
71
74
|
UpdateFailedImportsMixin,
|
|
72
75
|
VacuumMixin,
|
|
73
76
|
IntegrityMixin,
|
|
77
|
+
LogCompressMixin,
|
|
74
78
|
):
|
|
75
79
|
"""Janitor inline task runner."""
|
|
76
80
|
|
|
@@ -149,9 +153,15 @@ class Janitor(
|
|
|
149
153
|
for next_task in next_tasks:
|
|
150
154
|
self.librarian_queue.put(next_task)
|
|
151
155
|
case JanitorSearchOptimizeFinishedTask():
|
|
152
|
-
next_tasks = (
|
|
156
|
+
next_tasks = (
|
|
157
|
+
JanitorVacuumTask(),
|
|
158
|
+
JanitorBackupTask(),
|
|
159
|
+
JanitorCompressOldLogs(),
|
|
160
|
+
)
|
|
153
161
|
for next_task in next_tasks:
|
|
154
162
|
self.librarian_queue.put(next_task)
|
|
163
|
+
case JanitorCompressOldLogs():
|
|
164
|
+
self.log_compress()
|
|
155
165
|
case _:
|
|
156
166
|
self.log.warning(f"Janitor received unknown task {task}")
|
|
157
167
|
except Exception:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Rotate logs."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from lzma import LZMAFile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from humanize import naturalsize
|
|
8
|
+
|
|
9
|
+
from codex.librarian.janitor.status import JanitorStatusTypes
|
|
10
|
+
from codex.settings.settings import LOG_DIR
|
|
11
|
+
from codex.status import Status
|
|
12
|
+
from codex.worker_base import WorkerBaseMixin
|
|
13
|
+
|
|
14
|
+
_LOG_GLOB = "codex.log.*"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class LogStats:
|
|
19
|
+
"""Collect stats for compressed logs."""
|
|
20
|
+
|
|
21
|
+
count = 0
|
|
22
|
+
old_size = 0
|
|
23
|
+
new_size = 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LogCompressMixin(WorkerBaseMixin):
|
|
27
|
+
"""Rotate logs with janitor."""
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _get_old_log_paths():
|
|
31
|
+
"""Get old log paths to compress and sort them by reverse age."""
|
|
32
|
+
old_log_paths = {}
|
|
33
|
+
for path_str in LOG_DIR.glob(_LOG_GLOB):
|
|
34
|
+
old_path = Path(path_str)
|
|
35
|
+
try:
|
|
36
|
+
log_number = int(old_path.suffix[1:])
|
|
37
|
+
if log_number == 1:
|
|
38
|
+
continue
|
|
39
|
+
except ValueError:
|
|
40
|
+
continue
|
|
41
|
+
old_log_paths[log_number] = old_path
|
|
42
|
+
return dict(sorted(old_log_paths.items(), reverse=True)).values()
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _rotate_log(old_path, stats):
|
|
46
|
+
"""Rotate one log file."""
|
|
47
|
+
new_path = Path(str(old_path) + ".xz")
|
|
48
|
+
if new_path.exists():
|
|
49
|
+
return
|
|
50
|
+
with (
|
|
51
|
+
LZMAFile(new_path, mode="w", preset=9) as log_archive,
|
|
52
|
+
old_path.open("r") as log_file,
|
|
53
|
+
):
|
|
54
|
+
data = log_file.read()
|
|
55
|
+
log_archive.write(data.encode())
|
|
56
|
+
stats.count += 1
|
|
57
|
+
stats.old_size += old_path.stat().st_size
|
|
58
|
+
stats.new_size += new_path.stat().st_size
|
|
59
|
+
if new_path.exists():
|
|
60
|
+
old_path.unlink(missing_ok=True)
|
|
61
|
+
|
|
62
|
+
def log_compress(self):
|
|
63
|
+
"""Compress old log files and report on savings."""
|
|
64
|
+
status = Status(JanitorStatusTypes.COMPRESS_LOGS)
|
|
65
|
+
try:
|
|
66
|
+
self.status_controller.start(status)
|
|
67
|
+
old_log_paths = self._get_old_log_paths()
|
|
68
|
+
stats = LogStats()
|
|
69
|
+
for old_path in old_log_paths:
|
|
70
|
+
self._rotate_log(old_path, stats)
|
|
71
|
+
if stats.count:
|
|
72
|
+
saved = naturalsize(stats.old_size - stats.new_size)
|
|
73
|
+
self.log.info(f"Compressed logs. Saved {saved}.")
|
|
74
|
+
else:
|
|
75
|
+
self.log.debug("No logs compressed.")
|
|
76
|
+
finally:
|
|
77
|
+
self.status_controller.finish(status)
|
codex/librarian/janitor/tasks.py
CHANGED
|
@@ -107,3 +107,8 @@ class JanitorAdoptOrphanFoldersFinishedTask(JanitorTask):
|
|
|
107
107
|
@dataclass
|
|
108
108
|
class JanitorSearchOptimizeFinishedTask(JanitorTask):
|
|
109
109
|
"""Signals to the Janitor that the Search Optimize task is finished."""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class JanitorCompressOldLogs(JanitorTask):
|
|
114
|
+
"""Compress old logs."""
|
codex/librarian/librariand.py
CHANGED
|
@@ -42,6 +42,7 @@ from codex.librarian.watchdog.tasks import (
|
|
|
42
42
|
WatchdogSyncTask,
|
|
43
43
|
)
|
|
44
44
|
from codex.logger_base import LoggerBaseMixin
|
|
45
|
+
from codex.settings.settings import SET_PROC_TITLE
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
@@ -121,7 +122,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
121
122
|
case DelayedTasks():
|
|
122
123
|
self._threads.delayed_tasks_thread.queue.put(task)
|
|
123
124
|
case LibrarianShutdownTask():
|
|
124
|
-
self.log.info(f"Shutting down {self.
|
|
125
|
+
self.log.info(f"Shutting down {self.name}...")
|
|
125
126
|
self.run_loop = False
|
|
126
127
|
case _:
|
|
127
128
|
self.log.warning(f"Unhandled Librarian task: {task}")
|
|
@@ -150,34 +151,38 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
150
151
|
|
|
151
152
|
def _start_threads(self):
|
|
152
153
|
"""Start all librarian's threads."""
|
|
153
|
-
self.log.debug(f"{self.
|
|
154
|
+
self.log.debug(f"{self.name} starting all threads.")
|
|
154
155
|
for thread in self._threads:
|
|
155
156
|
thread.start()
|
|
156
|
-
self.log.info(f"{self.
|
|
157
|
+
self.log.info(f"{self.name} started all threads.")
|
|
157
158
|
|
|
158
159
|
def _startup(self):
|
|
159
160
|
"""Initialize threads."""
|
|
161
|
+
if SET_PROC_TITLE:
|
|
162
|
+
from setproctitle import setproctitle
|
|
163
|
+
|
|
164
|
+
setproctitle("Codex" + self.name)
|
|
160
165
|
self.init_logger(self.log_queue)
|
|
161
|
-
self.log.debug(f"Started {self.
|
|
166
|
+
self.log.debug(f"Started {self.name}.")
|
|
162
167
|
self.janitor = Janitor(self.log_queue, self.queue)
|
|
163
168
|
self._create_threads() # can't do this in init.
|
|
164
169
|
self._start_threads()
|
|
165
170
|
self.run_loop = True
|
|
166
|
-
self.log.info(f"{self.
|
|
171
|
+
self.log.info(f"{self.name} ready for tasks.")
|
|
167
172
|
|
|
168
173
|
def _stop_threads(self):
|
|
169
174
|
"""Stop all librarian's threads."""
|
|
170
|
-
self.log.debug(f"{self.
|
|
175
|
+
self.log.debug(f"{self.name} stopping all threads...")
|
|
171
176
|
for thread in self._reversed_threads:
|
|
172
177
|
thread.stop()
|
|
173
|
-
self.log.debug(f"{self.
|
|
178
|
+
self.log.debug(f"{self.name} stopped all threads.")
|
|
174
179
|
|
|
175
180
|
def _join_threads(self):
|
|
176
181
|
"""Join all librarian threads."""
|
|
177
|
-
self.log.debug(f"{self.
|
|
182
|
+
self.log.debug(f"{self.name} joining all threads...")
|
|
178
183
|
for thread in self._reversed_threads:
|
|
179
184
|
thread.join()
|
|
180
|
-
self.log.info(f"{self.
|
|
185
|
+
self.log.info(f"{self.name} joined all threads.")
|
|
181
186
|
|
|
182
187
|
def _shutdown(self):
|
|
183
188
|
"""Shutdown threads and queues."""
|
|
@@ -188,7 +193,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
188
193
|
self.queue.get_nowait()
|
|
189
194
|
self.queue.close()
|
|
190
195
|
self.queue.join_thread()
|
|
191
|
-
self.log.info(f"{self.
|
|
196
|
+
self.log.info(f"{self.name} finished.")
|
|
192
197
|
self.log_queue.close()
|
|
193
198
|
self.log_queue.join_thread()
|
|
194
199
|
|
|
@@ -205,11 +210,11 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
205
210
|
task = self.queue.get()
|
|
206
211
|
self._process_task(task)
|
|
207
212
|
except Exception:
|
|
208
|
-
self.log.exception(f"In {self.
|
|
213
|
+
self.log.exception(f"In {self.name} loop")
|
|
209
214
|
except Exception:
|
|
210
|
-
self.log.exception(f"{self.
|
|
215
|
+
self.log.exception(f"{self.name} crashed.")
|
|
211
216
|
except KeyboardInterrupt:
|
|
212
|
-
self.log.debug(f"{self.
|
|
217
|
+
self.log.debug(f"{self.name} Keyboard interrupt")
|
|
213
218
|
finally:
|
|
214
219
|
self._shutdown()
|
|
215
220
|
|
codex/librarian/search/update.py
CHANGED
|
@@ -114,7 +114,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
114
114
|
return ""
|
|
115
115
|
return ",".join((iso_code, field_instance.to_representation(iso_code)))
|
|
116
116
|
|
|
117
|
-
def
|
|
117
|
+
def _get_comics_fts_list(
|
|
118
118
|
self, comics, country_field, language_field, obj_list, create
|
|
119
119
|
):
|
|
120
120
|
"""Prepare a batch of search entries."""
|
|
@@ -157,6 +157,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
157
157
|
if create:
|
|
158
158
|
comicfts.created_at = now
|
|
159
159
|
obj_list.append(comicfts)
|
|
160
|
+
self.log.debug(f"{len(obj_list)}/{comics.count()} entries prepped.")
|
|
160
161
|
|
|
161
162
|
def _get_comicfts_list(self, comics, create=False):
|
|
162
163
|
"""Create a ComicFTS object for bulk_create or bulk_update."""
|
|
@@ -164,7 +165,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
164
165
|
language_field = LanguageField()
|
|
165
166
|
obj_list = []
|
|
166
167
|
comics = self._annotate_fts_query(comics)
|
|
167
|
-
self.
|
|
168
|
+
self._get_comics_fts_list(
|
|
168
169
|
comics, country_field, language_field, obj_list, create
|
|
169
170
|
)
|
|
170
171
|
return obj_list
|
|
@@ -205,11 +206,14 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
205
206
|
batch_start = time()
|
|
206
207
|
batch_to = batch_from + SEARCH_INDEX_BATCH_SIZE
|
|
207
208
|
comics_batch = comics_qs[batch_from:batch_to]
|
|
208
|
-
|
|
209
|
+
self.log.debug(
|
|
210
|
+
f"Checking {comics_batch.count()}/{count} comics for search index operations..."
|
|
211
|
+
)
|
|
209
212
|
if self.abort_event.is_set():
|
|
210
213
|
break
|
|
211
214
|
operate_comicfts = self._get_comicfts_list(comics_batch, create)
|
|
212
215
|
operate_comicfts_count = len(operate_comicfts)
|
|
216
|
+
self.log.debug(f"{verb}ing {operate_comicfts_count} search entries...")
|
|
213
217
|
if self.abort_event.is_set():
|
|
214
218
|
break
|
|
215
219
|
if create:
|
|
@@ -223,7 +227,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
223
227
|
batch_time = time() - batch_start
|
|
224
228
|
eps = round(count / batch_time)
|
|
225
229
|
self.log.debug(
|
|
226
|
-
f"{verb} {
|
|
230
|
+
f"{verb} {operate_comicfts_count}/{count} search entries in {batch_time}, {eps} per second."
|
|
227
231
|
)
|
|
228
232
|
batch_from = batch_to
|
|
229
233
|
finally:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Generated by Django 5.1.2 on 2024-10-22 03:09."""
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
"""Migrate db."""
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("codex", "0030_nocase_collation_day_month_indexes_status_types"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name="adminflag",
|
|
16
|
+
name="value",
|
|
17
|
+
field=models.CharField(blank=True, default="", max_length=32),
|
|
18
|
+
),
|
|
19
|
+
migrations.AlterField(
|
|
20
|
+
model_name="adminflag",
|
|
21
|
+
name="key",
|
|
22
|
+
field=models.CharField(
|
|
23
|
+
choices=[
|
|
24
|
+
("FV", "Folder View"),
|
|
25
|
+
("RG", "Registration"),
|
|
26
|
+
("NU", "Non Users"),
|
|
27
|
+
("AU", "Auto Update"),
|
|
28
|
+
("IM", "Import Metadata"),
|
|
29
|
+
("ST", "Send Telemetry"),
|
|
30
|
+
("BT", "Banner Text"),
|
|
31
|
+
],
|
|
32
|
+
db_index=True,
|
|
33
|
+
max_length=2,
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
migrations.AlterField(
|
|
37
|
+
model_name="timestamp",
|
|
38
|
+
name="key",
|
|
39
|
+
field=models.CharField(
|
|
40
|
+
choices=[
|
|
41
|
+
("AP", "API Key"),
|
|
42
|
+
("VR", "Codex Version"),
|
|
43
|
+
("JA", "Janitor"),
|
|
44
|
+
("TS", "Telemeter Sent"),
|
|
45
|
+
],
|
|
46
|
+
db_index=True,
|
|
47
|
+
max_length=2,
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
]
|
codex/models/admin.py
CHANGED
|
@@ -39,6 +39,7 @@ class AdminFlag(BaseModel):
|
|
|
39
39
|
AUTO_UPDATE = "AU"
|
|
40
40
|
IMPORT_METADATA = "IM"
|
|
41
41
|
SEND_TELEMETRY = "ST"
|
|
42
|
+
BANNER_TEXT = "BT"
|
|
42
43
|
|
|
43
44
|
FALSE_DEFAULTS = frozenset({FlagChoices.AUTO_UPDATE})
|
|
44
45
|
|
|
@@ -48,6 +49,7 @@ class AdminFlag(BaseModel):
|
|
|
48
49
|
choices=FlagChoices.choices,
|
|
49
50
|
)
|
|
50
51
|
on = BooleanField(default=True)
|
|
52
|
+
value = CharField(max_length=32, default="", blank=True)
|
|
51
53
|
|
|
52
54
|
class Meta(BaseModel.Meta):
|
|
53
55
|
"""Constraints."""
|
|
@@ -88,10 +90,9 @@ class Timestamp(BaseModel):
|
|
|
88
90
|
class TimestampChoices(TextChoices):
|
|
89
91
|
"""Choices for Timestamps."""
|
|
90
92
|
|
|
91
|
-
JANITOR = "JA", _("Janitor")
|
|
92
|
-
CODEX_VERSION = "VR", _("Codex Version")
|
|
93
|
-
SEARCH_INDEX_UUID = "SI", _("Search Index UUID")
|
|
94
93
|
API_KEY = "AP", _("API Key")
|
|
94
|
+
CODEX_VERSION = "VR", _("Codex Version")
|
|
95
|
+
JANITOR = "JA", _("Janitor")
|
|
95
96
|
TELEMETER_SENT = "TS", _("Telemeter Sent")
|
|
96
97
|
|
|
97
98
|
key = CharField(
|
codex/serializers/admin/flags.py
CHANGED
codex/serializers/auth.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""OPDS v2.0 Serializers."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""OPDS v2.0 Facet Serializer."""
|
|
2
|
+
|
|
3
|
+
from rest_framework.serializers import Serializer
|
|
4
|
+
|
|
5
|
+
from codex.serializers.opds.v2.links import OPDS2LinkListField
|
|
6
|
+
from codex.serializers.opds.v2.metadata import OPDS2MetadataSerializer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OPDS2FacetSerializer(Serializer):
|
|
10
|
+
"""Facets."""
|
|
11
|
+
|
|
12
|
+
metadata = OPDS2MetadataSerializer(read_only=True)
|
|
13
|
+
links = OPDS2LinkListField(read_only=True)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""OPDS v2.0 Feed Serializer."""
|
|
2
|
+
|
|
3
|
+
from rest_framework.fields import (
|
|
4
|
+
ListField,
|
|
5
|
+
)
|
|
6
|
+
from rest_framework.serializers import Serializer
|
|
7
|
+
|
|
8
|
+
from codex.serializers.opds.v2.facet import OPDS2FacetSerializer
|
|
9
|
+
from codex.serializers.opds.v2.links import OPDS2LinkListField
|
|
10
|
+
from codex.serializers.opds.v2.publication import (
|
|
11
|
+
OPDS2MetadataSerializer,
|
|
12
|
+
OPDS2PublicationSerializer,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OPDS2GroupSerializer(Serializer):
|
|
17
|
+
"""Group."""
|
|
18
|
+
|
|
19
|
+
metadata = OPDS2MetadataSerializer(read_only=True)
|
|
20
|
+
links = OPDS2LinkListField(read_only=True, required=False)
|
|
21
|
+
publications = ListField(
|
|
22
|
+
child=OPDS2PublicationSerializer(), read_only=True, required=False
|
|
23
|
+
)
|
|
24
|
+
navigation = OPDS2LinkListField(read_only=True, required=False)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OPDS2FeedSerializer(OPDS2GroupSerializer):
|
|
28
|
+
"""Feed.
|
|
29
|
+
|
|
30
|
+
https://drafts.opds.io/schema/feed.schema.json
|
|
31
|
+
https://readium.org/webpub-manifest/schema/subcollection.schema.json
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
facets = ListField(child=OPDS2FacetSerializer(), read_only=True, required=False)
|
|
35
|
+
groups = ListField(child=OPDS2GroupSerializer(), read_only=True, required=False)
|