codex 1.6.2__py3-none-any.whl → 1.6.4__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 +42 -16
- codex/librarian/importer/importer.py +1 -1
- codex/librarian/janitor/cleanup.py +2 -4
- codex/librarian/janitor/update.py +10 -9
- codex/librarian/search/merge.py +37 -40
- codex/librarian/search/remove.py +30 -26
- codex/librarian/search/update.py +47 -38
- codex/librarian/stats.py +177 -0
- codex/librarian/watchdog/events.py +2 -2
- codex/serializers/admin/__init__.py +1 -0
- codex/serializers/admin/flags.py +15 -0
- codex/serializers/admin/groups.py +38 -0
- codex/serializers/admin/libraries.py +90 -0
- codex/serializers/admin/stats.py +126 -0
- codex/serializers/admin/tasks.py +16 -0
- codex/serializers/admin/users.py +40 -0
- codex/serializers/fields.py +30 -0
- codex/serializers/reader.py +8 -3
- codex/settings/settings.py +3 -3
- codex/static_root/assets/VCheckbox-DtXDauZz.54986ad416a1.js +1 -0
- codex/static_root/assets/VCheckbox-DtXDauZz.54986ad416a1.js.br +0 -0
- codex/static_root/assets/VCheckbox-DtXDauZz.54986ad416a1.js.gz +0 -0
- codex/static_root/assets/VCheckbox-DtXDauZz.js +1 -0
- codex/static_root/assets/VCheckbox-DtXDauZz.js.br +0 -0
- codex/static_root/assets/VCheckbox-DtXDauZz.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B-iT1MnG.8505f6eed0fe.js +1 -0
- codex/static_root/assets/VCheckboxBtn-B-iT1MnG.8505f6eed0fe.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B-iT1MnG.8505f6eed0fe.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B-iT1MnG.js +1 -0
- codex/static_root/assets/VCheckboxBtn-B-iT1MnG.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B-iT1MnG.js.gz +0 -0
- codex/static_root/assets/{VCombobox-CGyBgdqr.2f85598cc1a4.js → VCombobox-DZmum3X-.330883d59742.js} +1 -1
- codex/static_root/assets/VCombobox-DZmum3X-.330883d59742.js.br +0 -0
- codex/static_root/assets/VCombobox-DZmum3X-.330883d59742.js.gz +0 -0
- codex/static_root/assets/{VCombobox-CGyBgdqr.js → VCombobox-DZmum3X-.js} +1 -1
- codex/static_root/assets/VCombobox-DZmum3X-.js.br +0 -0
- codex/static_root/assets/VCombobox-DZmum3X-.js.gz +0 -0
- codex/static_root/assets/VDialog-DUdNpXLj.6088d518d3af.js +1 -0
- codex/static_root/assets/VDialog-DUdNpXLj.6088d518d3af.js.br +0 -0
- codex/static_root/assets/VDialog-DUdNpXLj.6088d518d3af.js.gz +0 -0
- codex/static_root/assets/VDialog-DUdNpXLj.js +1 -0
- codex/static_root/assets/VDialog-DUdNpXLj.js.br +0 -0
- codex/static_root/assets/VDialog-DUdNpXLj.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CurqcyFg.860017abf25d.js +1 -0
- codex/static_root/assets/VExpansionPanels-CurqcyFg.860017abf25d.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CurqcyFg.860017abf25d.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CurqcyFg.js +1 -0
- codex/static_root/assets/VExpansionPanels-CurqcyFg.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CurqcyFg.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-B7n5ReZ-.8acbc71ac411.js +1 -0
- codex/static_root/assets/VRadioGroup-B7n5ReZ-.8acbc71ac411.js.br +0 -0
- codex/static_root/assets/VRadioGroup-B7n5ReZ-.8acbc71ac411.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-B7n5ReZ-.js +1 -0
- codex/static_root/assets/VRadioGroup-B7n5ReZ-.js.br +0 -0
- codex/static_root/assets/VRadioGroup-B7n5ReZ-.js.gz +0 -0
- codex/static_root/assets/VSelect-FYWbH55D.83e91b93fdd1.js +1 -0
- codex/static_root/assets/VSelect-FYWbH55D.83e91b93fdd1.js.br +0 -0
- codex/static_root/assets/VSelect-FYWbH55D.83e91b93fdd1.js.gz +0 -0
- codex/static_root/assets/VSelect-FYWbH55D.js +1 -0
- codex/static_root/assets/VSelect-FYWbH55D.js.br +0 -0
- codex/static_root/assets/VSelect-FYWbH55D.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-DXxRmssH.js → VSelectionControl-CUlT3cc_.63f60240eb49.js} +1 -1
- codex/static_root/assets/VSelectionControl-CUlT3cc_.63f60240eb49.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CUlT3cc_.63f60240eb49.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-DXxRmssH.252890f79cc6.js → VSelectionControl-CUlT3cc_.js} +1 -1
- codex/static_root/assets/VSelectionControl-CUlT3cc_.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CUlT3cc_.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-DYJ3EYZI.a2542c5f90c4.js → VSlideGroup-CRJazQeR.cb89b2222518.js} +1 -1
- codex/static_root/assets/VSlideGroup-CRJazQeR.cb89b2222518.js.br +0 -0
- codex/static_root/assets/VSlideGroup-CRJazQeR.cb89b2222518.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-DYJ3EYZI.js → VSlideGroup-CRJazQeR.js} +1 -1
- codex/static_root/assets/VSlideGroup-CRJazQeR.js.br +0 -0
- codex/static_root/assets/VSlideGroup-CRJazQeR.js.gz +0 -0
- codex/static_root/assets/{VTable-BveGtxei.7f76f4ef812b.js → VTable-DtyBEZ5i.6848f15ea03f.js} +1 -1
- codex/static_root/assets/VTable-DtyBEZ5i.6848f15ea03f.js.br +0 -0
- codex/static_root/assets/VTable-DtyBEZ5i.6848f15ea03f.js.gz +0 -0
- codex/static_root/assets/{VTable-BveGtxei.js → VTable-DtyBEZ5i.js} +1 -1
- codex/static_root/assets/VTable-DtyBEZ5i.js.br +0 -0
- codex/static_root/assets/VTable-DtyBEZ5i.js.gz +0 -0
- codex/static_root/assets/VTextField-DEiVah7O.fbf3130f2dea.js +1 -0
- codex/static_root/assets/VTextField-DEiVah7O.fbf3130f2dea.js.br +0 -0
- codex/static_root/assets/VTextField-DEiVah7O.fbf3130f2dea.js.gz +0 -0
- codex/static_root/assets/VTextField-DEiVah7O.js +1 -0
- codex/static_root/assets/VTextField-DEiVah7O.js.br +0 -0
- codex/static_root/assets/VTextField-DEiVah7O.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-FBuIO_rT.8610c7e83905.js → VWindowItem-BVIaHO-S.419e4ecf523d.js} +1 -1
- codex/static_root/assets/VWindowItem-BVIaHO-S.419e4ecf523d.js.br +0 -0
- codex/static_root/assets/VWindowItem-BVIaHO-S.419e4ecf523d.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-FBuIO_rT.js → VWindowItem-BVIaHO-S.js} +1 -1
- codex/static_root/assets/VWindowItem-BVIaHO-S.js.br +0 -0
- codex/static_root/assets/VWindowItem-BVIaHO-S.js.gz +0 -0
- codex/static_root/assets/{admin-B8Bp2Uih.82c98714bcf1.css → admin-D4sw3N4T.b590f88a62cf.css} +1 -1
- codex/static_root/assets/admin-D4sw3N4T.b590f88a62cf.css.br +0 -0
- codex/static_root/assets/admin-D4sw3N4T.b590f88a62cf.css.gz +0 -0
- codex/static_root/assets/{admin-B8Bp2Uih.css → admin-D4sw3N4T.css} +1 -1
- codex/static_root/assets/admin-D4sw3N4T.css.br +0 -0
- codex/static_root/assets/admin-D4sw3N4T.css.gz +0 -0
- codex/static_root/assets/admin-S53JleJt.f629d90f73a1.js +1 -0
- codex/static_root/assets/admin-S53JleJt.f629d90f73a1.js.br +0 -0
- codex/static_root/assets/admin-S53JleJt.f629d90f73a1.js.gz +0 -0
- codex/static_root/assets/admin-S53JleJt.js +1 -0
- codex/static_root/assets/admin-S53JleJt.js.br +0 -0
- codex/static_root/assets/admin-S53JleJt.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-CnAnGx7w.1e4eb021cb21.js +56 -0
- codex/static_root/assets/admin-drawer-panel-CnAnGx7w.1e4eb021cb21.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-CnAnGx7w.1e4eb021cb21.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-CnAnGx7w.js +56 -0
- codex/static_root/assets/admin-drawer-panel-CnAnGx7w.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-CnAnGx7w.js.gz +0 -0
- codex/static_root/assets/browser-BHHgqDP7.7ada4400d63f.js +1 -0
- codex/static_root/assets/browser-BHHgqDP7.7ada4400d63f.js.br +0 -0
- codex/static_root/assets/browser-BHHgqDP7.7ada4400d63f.js.gz +0 -0
- codex/static_root/assets/browser-BHHgqDP7.js +1 -0
- codex/static_root/assets/browser-BHHgqDP7.js.br +0 -0
- codex/static_root/assets/browser-BHHgqDP7.js.gz +0 -0
- codex/static_root/assets/{browser-DP9eEJ0M.css → browser-Cy0OgQJC.css} +1 -1
- codex/static_root/assets/browser-Cy0OgQJC.css.br +0 -0
- codex/static_root/assets/browser-Cy0OgQJC.css.gz +0 -0
- codex/static_root/assets/{browser-DP9eEJ0M.276e8636f8fc.css → browser-Cy0OgQJC.fabbd1aaffcf.css} +1 -1
- codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.br +0 -0
- codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Du1bwEt1.a4c0205c9bc4.js +1 -0
- codex/static_root/assets/change-password-dialog-Du1bwEt1.a4c0205c9bc4.js.br +0 -0
- codex/static_root/assets/change-password-dialog-Du1bwEt1.a4c0205c9bc4.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-Du1bwEt1.js +1 -0
- codex/static_root/assets/change-password-dialog-Du1bwEt1.js.br +0 -0
- codex/static_root/assets/change-password-dialog-Du1bwEt1.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-eKN71B8a.af6187dc12bd.js +1 -0
- codex/static_root/assets/confirm-dialog-eKN71B8a.af6187dc12bd.js.br +0 -0
- codex/static_root/assets/confirm-dialog-eKN71B8a.af6187dc12bd.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-eKN71B8a.js +1 -0
- codex/static_root/assets/confirm-dialog-eKN71B8a.js.br +0 -0
- codex/static_root/assets/confirm-dialog-eKN71B8a.js.gz +0 -0
- codex/static_root/assets/{datetime-column-uaXWFgF_.29c6d50b483e.js → datetime-column-CWp600fz.e8a969126bf5.js} +1 -1
- codex/static_root/assets/datetime-column-CWp600fz.e8a969126bf5.js.br +0 -0
- codex/static_root/assets/datetime-column-CWp600fz.e8a969126bf5.js.gz +0 -0
- codex/static_root/assets/{datetime-column-uaXWFgF_.js → datetime-column-CWp600fz.js} +1 -1
- codex/static_root/assets/datetime-column-CWp600fz.js.br +0 -0
- codex/static_root/assets/datetime-column-CWp600fz.js.gz +0 -0
- codex/static_root/assets/{filter-DS84uTvk.4fbebd4832bc.js → filter-_XbKRiIR.2c7ac68a2d03.js} +1 -1
- codex/static_root/assets/filter-_XbKRiIR.2c7ac68a2d03.js.br +0 -0
- codex/static_root/assets/filter-_XbKRiIR.2c7ac68a2d03.js.gz +0 -0
- codex/static_root/assets/{filter-DS84uTvk.js → filter-_XbKRiIR.js} +1 -1
- codex/static_root/assets/filter-_XbKRiIR.js.br +0 -0
- codex/static_root/assets/filter-_XbKRiIR.js.gz +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css +1 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +1 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
- codex/static_root/assets/flag-tab-DR_nY0Jg.7323e386c6ed.js +1 -0
- codex/static_root/assets/flag-tab-DR_nY0Jg.7323e386c6ed.js.br +0 -0
- codex/static_root/assets/flag-tab-DR_nY0Jg.7323e386c6ed.js.gz +0 -0
- codex/static_root/assets/flag-tab-DR_nY0Jg.js +1 -0
- codex/static_root/assets/flag-tab-DR_nY0Jg.js.br +0 -0
- codex/static_root/assets/flag-tab-DR_nY0Jg.js.gz +0 -0
- codex/static_root/assets/group-tab-JKsMHOmK.d2c2f3fceec1.js +1 -0
- codex/static_root/assets/group-tab-JKsMHOmK.d2c2f3fceec1.js.br +0 -0
- codex/static_root/assets/group-tab-JKsMHOmK.d2c2f3fceec1.js.gz +0 -0
- codex/static_root/assets/group-tab-JKsMHOmK.js +1 -0
- codex/static_root/assets/group-tab-JKsMHOmK.js.br +0 -0
- codex/static_root/assets/group-tab-JKsMHOmK.js.gz +0 -0
- codex/static_root/assets/http-error-Bw1wUv3U.c8313a2fb129.js +1 -0
- codex/static_root/assets/http-error-Bw1wUv3U.c8313a2fb129.js.br +0 -0
- codex/static_root/assets/http-error-Bw1wUv3U.c8313a2fb129.js.gz +0 -0
- codex/static_root/assets/http-error-Bw1wUv3U.js +1 -0
- codex/static_root/assets/http-error-Bw1wUv3U.js.br +0 -0
- codex/static_root/assets/http-error-Bw1wUv3U.js.gz +0 -0
- codex/static_root/assets/{library-tab-BN0SfPcH.913e236f625e.css → library-tab-BygwYPnO.94e92aa60bf2.css} +1 -1
- codex/static_root/assets/library-tab-BygwYPnO.94e92aa60bf2.css.br +0 -0
- codex/static_root/assets/library-tab-BygwYPnO.94e92aa60bf2.css.gz +0 -0
- codex/static_root/assets/{library-tab-BN0SfPcH.css → library-tab-BygwYPnO.css} +1 -1
- codex/static_root/assets/library-tab-BygwYPnO.css.br +0 -0
- codex/static_root/assets/library-tab-BygwYPnO.css.gz +0 -0
- codex/static_root/assets/library-tab-D9GTugZ6.8e3e6ebd38b7.js +1 -0
- codex/static_root/assets/library-tab-D9GTugZ6.8e3e6ebd38b7.js.br +0 -0
- codex/static_root/assets/library-tab-D9GTugZ6.8e3e6ebd38b7.js.gz +0 -0
- codex/static_root/assets/library-tab-D9GTugZ6.js +1 -0
- codex/static_root/assets/library-tab-D9GTugZ6.js.br +0 -0
- codex/static_root/assets/library-tab-D9GTugZ6.js.gz +0 -0
- codex/static_root/assets/main-BJiV8IG3.9d708627091f.js +6 -0
- codex/static_root/assets/main-BJiV8IG3.9d708627091f.js.br +0 -0
- codex/static_root/assets/main-BJiV8IG3.9d708627091f.js.gz +0 -0
- codex/static_root/assets/main-BJiV8IG3.js +6 -0
- codex/static_root/assets/main-BJiV8IG3.js.br +0 -0
- codex/static_root/assets/main-BJiV8IG3.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-i2VoXaGH.c3dbb703af2e.js +1 -0
- codex/static_root/assets/pagination-toolbar-i2VoXaGH.c3dbb703af2e.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-i2VoXaGH.c3dbb703af2e.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-i2VoXaGH.js +1 -0
- codex/static_root/assets/pagination-toolbar-i2VoXaGH.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-i2VoXaGH.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-C-4EM9-d.ce9cf66bd4c3.js → pdf-doc-BLxZLD38.4cf9622c535f.js} +1 -1
- codex/static_root/assets/pdf-doc-BLxZLD38.4cf9622c535f.js.br +0 -0
- codex/static_root/assets/pdf-doc-BLxZLD38.4cf9622c535f.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-C-4EM9-d.js → pdf-doc-BLxZLD38.js} +1 -1
- codex/static_root/assets/pdf-doc-BLxZLD38.js.br +0 -0
- codex/static_root/assets/pdf-doc-BLxZLD38.js.gz +0 -0
- codex/static_root/assets/reader-D2O_yHTW.70d616ddc2cc.js +2 -0
- codex/static_root/assets/reader-D2O_yHTW.70d616ddc2cc.js.br +0 -0
- codex/static_root/assets/reader-D2O_yHTW.70d616ddc2cc.js.gz +0 -0
- codex/static_root/assets/reader-D2O_yHTW.js +2 -0
- codex/static_root/assets/reader-D2O_yHTW.js.br +0 -0
- codex/static_root/assets/reader-D2O_yHTW.js.gz +0 -0
- codex/static_root/assets/{reader-DOQuhDYO.css → reader-YbzUiD0T.css} +1 -1
- codex/static_root/assets/reader-YbzUiD0T.css.br +0 -0
- codex/static_root/assets/reader-YbzUiD0T.css.gz +0 -0
- codex/static_root/assets/{reader-DOQuhDYO.df559ad29ab3.css → reader-YbzUiD0T.efb15835b39d.css} +1 -1
- codex/static_root/assets/reader-YbzUiD0T.efb15835b39d.css.br +0 -0
- codex/static_root/assets/reader-YbzUiD0T.efb15835b39d.css.gz +0 -0
- codex/static_root/assets/{relation-chips-Bsc-gUxE.98e65384c3c9.css → relation-chips-BGitp3yx.b3e7d178a615.css} +1 -1
- codex/static_root/assets/relation-chips-BGitp3yx.b3e7d178a615.css.br +0 -0
- codex/static_root/assets/relation-chips-BGitp3yx.b3e7d178a615.css.gz +0 -0
- codex/static_root/assets/{relation-chips-Bsc-gUxE.css → relation-chips-BGitp3yx.css} +1 -1
- codex/static_root/assets/relation-chips-BGitp3yx.css.br +0 -0
- codex/static_root/assets/relation-chips-BGitp3yx.css.gz +0 -0
- codex/static_root/assets/relation-chips-CmeSIsRq.f169f7c3f48d.js +1 -0
- codex/static_root/assets/relation-chips-CmeSIsRq.f169f7c3f48d.js.br +0 -0
- codex/static_root/assets/relation-chips-CmeSIsRq.f169f7c3f48d.js.gz +0 -0
- codex/static_root/assets/relation-chips-CmeSIsRq.js +1 -0
- codex/static_root/assets/relation-chips-CmeSIsRq.js.br +0 -0
- codex/static_root/assets/relation-chips-CmeSIsRq.js.gz +0 -0
- codex/static_root/assets/settings-drawer-XH4sNDNT.98ee4b24901c.js +2 -0
- codex/static_root/assets/settings-drawer-XH4sNDNT.98ee4b24901c.js.br +0 -0
- codex/static_root/assets/settings-drawer-XH4sNDNT.98ee4b24901c.js.gz +0 -0
- codex/static_root/assets/settings-drawer-XH4sNDNT.js +2 -0
- codex/static_root/assets/settings-drawer-XH4sNDNT.js.br +0 -0
- codex/static_root/assets/settings-drawer-XH4sNDNT.js.gz +0 -0
- codex/static_root/assets/stats-tab-BGxkeKnY.44cf18ca895d.js +1 -0
- codex/static_root/assets/stats-tab-BGxkeKnY.44cf18ca895d.js.br +0 -0
- codex/static_root/assets/stats-tab-BGxkeKnY.44cf18ca895d.js.gz +0 -0
- codex/static_root/assets/stats-tab-BGxkeKnY.js +1 -0
- codex/static_root/assets/stats-tab-BGxkeKnY.js.br +0 -0
- codex/static_root/assets/stats-tab-BGxkeKnY.js.gz +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css +1 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.br +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.gz +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.css +1 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.css.br +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.css.gz +0 -0
- codex/static_root/assets/task-tab-B74Kah_b.58627993d3c6.js +1 -0
- codex/static_root/assets/task-tab-B74Kah_b.58627993d3c6.js.br +0 -0
- codex/static_root/assets/task-tab-B74Kah_b.58627993d3c6.js.gz +0 -0
- codex/static_root/assets/task-tab-B74Kah_b.js +1 -0
- codex/static_root/assets/task-tab-B74Kah_b.js.br +0 -0
- codex/static_root/assets/task-tab-B74Kah_b.js.gz +0 -0
- codex/static_root/assets/unauthorized-BF45der6.d34f8022124b.js +1 -0
- codex/static_root/assets/unauthorized-BF45der6.d34f8022124b.js.br +0 -0
- codex/static_root/assets/unauthorized-BF45der6.d34f8022124b.js.gz +0 -0
- codex/static_root/assets/unauthorized-BF45der6.js +1 -0
- codex/static_root/assets/unauthorized-BF45der6.js.br +0 -0
- codex/static_root/assets/unauthorized-BF45der6.js.gz +0 -0
- codex/static_root/assets/user-tab-BP2TTPg5.e63a5661a1a5.js +1 -0
- codex/static_root/assets/user-tab-BP2TTPg5.e63a5661a1a5.js.br +0 -0
- codex/static_root/assets/user-tab-BP2TTPg5.e63a5661a1a5.js.gz +0 -0
- codex/static_root/assets/user-tab-BP2TTPg5.js +1 -0
- codex/static_root/assets/user-tab-BP2TTPg5.js.br +0 -0
- codex/static_root/assets/user-tab-BP2TTPg5.js.gz +0 -0
- codex/static_root/{manifest.80e911824648.json → manifest.7033f9a22987.json} +230 -242
- codex/static_root/manifest.7033f9a22987.json.br +0 -0
- codex/static_root/manifest.7033f9a22987.json.gz +0 -0
- codex/static_root/manifest.json +230 -242
- 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/root.py +12 -2
- codex/views/admin/api_key.py +1 -1
- codex/views/admin/flag.py +1 -1
- codex/views/admin/group.py +1 -1
- codex/views/admin/library.py +1 -1
- codex/views/admin/stats.py +41 -155
- codex/views/admin/tasks.py +1 -1
- codex/views/admin/user.py +1 -1
- codex/views/browser/browser.py +4 -1
- codex/views/browser/filters/field.py +15 -6
- codex/views/browser/paginate.py +61 -11
- codex/views/browser/validate.py +7 -5
- codex/views/opds/binary.py +1 -3
- codex/views/opds/const.py +10 -0
- codex/views/opds/util.py +10 -0
- codex/views/opds/v1/entry/entry.py +14 -18
- codex/views/opds/v1/feed.py +10 -24
- codex/views/opds/v1/links.py +1 -0
- codex/views/reader/arcs.py +65 -53
- codex/views/reader/books.py +38 -38
- codex/views/reader/init.py +7 -5
- codex/views/reader/reader.py +0 -2
- {codex-1.6.2.dist-info → codex-1.6.4.dist-info}/METADATA +18 -7
- {codex-1.6.2.dist-info → codex-1.6.4.dist-info}/RECORD +293 -289
- codex/serializers/admin.py +0 -268
- codex/settings/logging.py +0 -20
- codex/static_root/assets/VCheckbox-DUxuAZcY.46ab94043464.js +0 -1
- codex/static_root/assets/VCheckbox-DUxuAZcY.46ab94043464.js.br +0 -0
- codex/static_root/assets/VCheckbox-DUxuAZcY.46ab94043464.js.gz +0 -0
- codex/static_root/assets/VCheckbox-DUxuAZcY.js +0 -1
- codex/static_root/assets/VCheckbox-DUxuAZcY.js.br +0 -0
- codex/static_root/assets/VCheckbox-DUxuAZcY.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BTFFFGx3.0cbde4eb9a65.js +0 -1
- codex/static_root/assets/VCheckboxBtn-BTFFFGx3.0cbde4eb9a65.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BTFFFGx3.0cbde4eb9a65.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BTFFFGx3.js +0 -1
- codex/static_root/assets/VCheckboxBtn-BTFFFGx3.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BTFFFGx3.js.gz +0 -0
- codex/static_root/assets/VCombobox-CGyBgdqr.2f85598cc1a4.js.br +0 -0
- codex/static_root/assets/VCombobox-CGyBgdqr.2f85598cc1a4.js.gz +0 -0
- codex/static_root/assets/VCombobox-CGyBgdqr.js.br +0 -0
- codex/static_root/assets/VCombobox-CGyBgdqr.js.gz +0 -0
- codex/static_root/assets/VDialog-DTWAoZBU.b8c4c276e9b2.js +0 -1
- codex/static_root/assets/VDialog-DTWAoZBU.b8c4c276e9b2.js.br +0 -0
- codex/static_root/assets/VDialog-DTWAoZBU.b8c4c276e9b2.js.gz +0 -0
- codex/static_root/assets/VDialog-DTWAoZBU.js +0 -1
- codex/static_root/assets/VDialog-DTWAoZBU.js.br +0 -0
- codex/static_root/assets/VDialog-DTWAoZBU.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-D4psrz4h.b55b901da65f.js +0 -1
- codex/static_root/assets/VExpansionPanels-D4psrz4h.b55b901da65f.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-D4psrz4h.b55b901da65f.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-D4psrz4h.js +0 -1
- codex/static_root/assets/VExpansionPanels-D4psrz4h.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-D4psrz4h.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BY572TP-.6d47a94795de.js +0 -1
- codex/static_root/assets/VRadioGroup-BY572TP-.6d47a94795de.js.br +0 -2
- codex/static_root/assets/VRadioGroup-BY572TP-.6d47a94795de.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BY572TP-.js +0 -1
- codex/static_root/assets/VRadioGroup-BY572TP-.js.br +0 -2
- codex/static_root/assets/VRadioGroup-BY572TP-.js.gz +0 -0
- codex/static_root/assets/VSelect-Bnhj5kBc.fdcd87c9e253.js +0 -1
- codex/static_root/assets/VSelect-Bnhj5kBc.fdcd87c9e253.js.br +0 -0
- codex/static_root/assets/VSelect-Bnhj5kBc.fdcd87c9e253.js.gz +0 -0
- codex/static_root/assets/VSelect-Bnhj5kBc.js +0 -1
- codex/static_root/assets/VSelect-Bnhj5kBc.js.br +0 -0
- codex/static_root/assets/VSelect-Bnhj5kBc.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DXxRmssH.252890f79cc6.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DXxRmssH.252890f79cc6.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DXxRmssH.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DXxRmssH.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-DYJ3EYZI.a2542c5f90c4.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DYJ3EYZI.a2542c5f90c4.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-DYJ3EYZI.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DYJ3EYZI.js.gz +0 -0
- codex/static_root/assets/VTable-BveGtxei.7f76f4ef812b.js.br +0 -0
- codex/static_root/assets/VTable-BveGtxei.7f76f4ef812b.js.gz +0 -0
- codex/static_root/assets/VTable-BveGtxei.js.br +0 -0
- codex/static_root/assets/VTable-BveGtxei.js.gz +0 -0
- codex/static_root/assets/VTextField-BayL7C1D.6e53c61b3760.js +0 -1
- codex/static_root/assets/VTextField-BayL7C1D.6e53c61b3760.js.br +0 -0
- codex/static_root/assets/VTextField-BayL7C1D.6e53c61b3760.js.gz +0 -0
- codex/static_root/assets/VTextField-BayL7C1D.js +0 -1
- codex/static_root/assets/VTextField-BayL7C1D.js.br +0 -0
- codex/static_root/assets/VTextField-BayL7C1D.js.gz +0 -0
- codex/static_root/assets/VWindowItem-FBuIO_rT.8610c7e83905.js.br +0 -0
- codex/static_root/assets/VWindowItem-FBuIO_rT.8610c7e83905.js.gz +0 -0
- codex/static_root/assets/VWindowItem-FBuIO_rT.js.br +0 -0
- codex/static_root/assets/VWindowItem-FBuIO_rT.js.gz +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.br +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.gz +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.css.br +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.css.gz +0 -0
- codex/static_root/assets/admin-BH6uiYBb.d51c720fa05c.js +0 -1
- codex/static_root/assets/admin-BH6uiYBb.d51c720fa05c.js.br +0 -0
- codex/static_root/assets/admin-BH6uiYBb.d51c720fa05c.js.gz +0 -0
- codex/static_root/assets/admin-BH6uiYBb.js +0 -1
- codex/static_root/assets/admin-BH6uiYBb.js.br +0 -0
- codex/static_root/assets/admin-BH6uiYBb.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-DsgLbfnD.7c409b430d79.js +0 -30
- codex/static_root/assets/admin-drawer-panel-DsgLbfnD.7c409b430d79.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-DsgLbfnD.7c409b430d79.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-DsgLbfnD.js +0 -30
- codex/static_root/assets/admin-drawer-panel-DsgLbfnD.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-DsgLbfnD.js.gz +0 -0
- codex/static_root/assets/browser-BVciW8XB.ab1d75272aa7.js +0 -1
- codex/static_root/assets/browser-BVciW8XB.ab1d75272aa7.js.br +0 -0
- codex/static_root/assets/browser-BVciW8XB.ab1d75272aa7.js.gz +0 -0
- codex/static_root/assets/browser-BVciW8XB.js +0 -1
- codex/static_root/assets/browser-BVciW8XB.js.br +0 -0
- codex/static_root/assets/browser-BVciW8XB.js.gz +0 -0
- codex/static_root/assets/browser-DP9eEJ0M.276e8636f8fc.css.br +0 -0
- codex/static_root/assets/browser-DP9eEJ0M.276e8636f8fc.css.gz +0 -0
- codex/static_root/assets/browser-DP9eEJ0M.css.br +0 -0
- codex/static_root/assets/browser-DP9eEJ0M.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-B-Sd5U5p.adf0a0f02866.js +0 -1
- codex/static_root/assets/change-password-dialog-B-Sd5U5p.adf0a0f02866.js.br +0 -0
- codex/static_root/assets/change-password-dialog-B-Sd5U5p.adf0a0f02866.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-B-Sd5U5p.js +0 -1
- codex/static_root/assets/change-password-dialog-B-Sd5U5p.js.br +0 -0
- codex/static_root/assets/change-password-dialog-B-Sd5U5p.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CYEBgtj5.a5f142656c12.js +0 -1
- codex/static_root/assets/confirm-dialog-CYEBgtj5.a5f142656c12.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CYEBgtj5.a5f142656c12.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-CYEBgtj5.js +0 -1
- codex/static_root/assets/confirm-dialog-CYEBgtj5.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CYEBgtj5.js.gz +0 -0
- codex/static_root/assets/datetime-column-uaXWFgF_.29c6d50b483e.js.br +0 -0
- codex/static_root/assets/datetime-column-uaXWFgF_.29c6d50b483e.js.gz +0 -0
- codex/static_root/assets/datetime-column-uaXWFgF_.js.br +0 -0
- codex/static_root/assets/datetime-column-uaXWFgF_.js.gz +0 -0
- codex/static_root/assets/filter-DS84uTvk.4fbebd4832bc.js.br +0 -0
- codex/static_root/assets/filter-DS84uTvk.4fbebd4832bc.js.gz +0 -0
- codex/static_root/assets/filter-DS84uTvk.js.br +0 -0
- codex/static_root/assets/filter-DS84uTvk.js.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css +0 -1
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.br +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css +0 -1
- codex/static_root/assets/flag-tab-BNwbLfrN.css.br +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css.gz +0 -0
- codex/static_root/assets/flag-tab-iJTJldQ1.213201f8e03b.js +0 -1
- codex/static_root/assets/flag-tab-iJTJldQ1.213201f8e03b.js.br +0 -0
- codex/static_root/assets/flag-tab-iJTJldQ1.213201f8e03b.js.gz +0 -0
- codex/static_root/assets/flag-tab-iJTJldQ1.js +0 -1
- codex/static_root/assets/flag-tab-iJTJldQ1.js.br +0 -0
- codex/static_root/assets/flag-tab-iJTJldQ1.js.gz +0 -0
- codex/static_root/assets/group-tab-DdmGE4gB.48ef83a4c7cd.js +0 -1
- codex/static_root/assets/group-tab-DdmGE4gB.48ef83a4c7cd.js.br +0 -0
- codex/static_root/assets/group-tab-DdmGE4gB.48ef83a4c7cd.js.gz +0 -0
- codex/static_root/assets/group-tab-DdmGE4gB.js +0 -1
- codex/static_root/assets/group-tab-DdmGE4gB.js.br +0 -0
- codex/static_root/assets/group-tab-DdmGE4gB.js.gz +0 -0
- codex/static_root/assets/http-error-HtOe8jPa.141ad665f81d.js +0 -1
- codex/static_root/assets/http-error-HtOe8jPa.141ad665f81d.js.br +0 -0
- codex/static_root/assets/http-error-HtOe8jPa.141ad665f81d.js.gz +0 -0
- codex/static_root/assets/http-error-HtOe8jPa.js +0 -1
- codex/static_root/assets/http-error-HtOe8jPa.js.br +0 -0
- codex/static_root/assets/http-error-HtOe8jPa.js.gz +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.br +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.gz +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css.br +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css.gz +0 -0
- codex/static_root/assets/library-tab-DLAJV6j2.dae6292bbf28.js +0 -1
- codex/static_root/assets/library-tab-DLAJV6j2.dae6292bbf28.js.br +0 -0
- codex/static_root/assets/library-tab-DLAJV6j2.dae6292bbf28.js.gz +0 -0
- codex/static_root/assets/library-tab-DLAJV6j2.js +0 -1
- codex/static_root/assets/library-tab-DLAJV6j2.js.br +0 -0
- codex/static_root/assets/library-tab-DLAJV6j2.js.gz +0 -0
- codex/static_root/assets/main-BjGE3kSi.22c1b19a9e19.js +0 -32
- codex/static_root/assets/main-BjGE3kSi.22c1b19a9e19.js.br +0 -0
- codex/static_root/assets/main-BjGE3kSi.22c1b19a9e19.js.gz +0 -0
- codex/static_root/assets/main-BjGE3kSi.js +0 -32
- codex/static_root/assets/main-BjGE3kSi.js.br +0 -0
- codex/static_root/assets/main-BjGE3kSi.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C8CSryCU.9b61f95fd710.js +0 -1
- codex/static_root/assets/pagination-toolbar-C8CSryCU.9b61f95fd710.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-C8CSryCU.9b61f95fd710.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C8CSryCU.js +0 -1
- codex/static_root/assets/pagination-toolbar-C8CSryCU.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-C8CSryCU.js.gz +0 -0
- codex/static_root/assets/pdf-doc-C-4EM9-d.ce9cf66bd4c3.js.br +0 -0
- codex/static_root/assets/pdf-doc-C-4EM9-d.ce9cf66bd4c3.js.gz +0 -0
- codex/static_root/assets/pdf-doc-C-4EM9-d.js.br +0 -0
- codex/static_root/assets/pdf-doc-C-4EM9-d.js.gz +0 -0
- codex/static_root/assets/reader-C5OTEQ7N.fae52c1fee51.js +0 -2
- codex/static_root/assets/reader-C5OTEQ7N.fae52c1fee51.js.br +0 -0
- codex/static_root/assets/reader-C5OTEQ7N.fae52c1fee51.js.gz +0 -0
- codex/static_root/assets/reader-C5OTEQ7N.js +0 -2
- codex/static_root/assets/reader-C5OTEQ7N.js.br +0 -0
- codex/static_root/assets/reader-C5OTEQ7N.js.gz +0 -0
- codex/static_root/assets/reader-DOQuhDYO.css.br +0 -0
- codex/static_root/assets/reader-DOQuhDYO.css.gz +0 -0
- codex/static_root/assets/reader-DOQuhDYO.df559ad29ab3.css.br +0 -0
- codex/static_root/assets/reader-DOQuhDYO.df559ad29ab3.css.gz +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.br +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.gz +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.css.br +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.css.gz +0 -0
- codex/static_root/assets/relation-chips-C4LQSHRc.3c85433df91c.js +0 -1
- codex/static_root/assets/relation-chips-C4LQSHRc.3c85433df91c.js.br +0 -0
- codex/static_root/assets/relation-chips-C4LQSHRc.3c85433df91c.js.gz +0 -0
- codex/static_root/assets/relation-chips-C4LQSHRc.js +0 -1
- codex/static_root/assets/relation-chips-C4LQSHRc.js.br +0 -0
- codex/static_root/assets/relation-chips-C4LQSHRc.js.gz +0 -0
- codex/static_root/assets/settings-drawer-aTAAoX2A.2f21008ade35.js +0 -2
- codex/static_root/assets/settings-drawer-aTAAoX2A.2f21008ade35.js.br +0 -0
- codex/static_root/assets/settings-drawer-aTAAoX2A.2f21008ade35.js.gz +0 -0
- codex/static_root/assets/settings-drawer-aTAAoX2A.js +0 -2
- codex/static_root/assets/settings-drawer-aTAAoX2A.js.br +0 -0
- codex/static_root/assets/settings-drawer-aTAAoX2A.js.gz +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +0 -1
- codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.css +0 -1
- codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
- codex/static_root/assets/stats-tab-DL4J3pvZ.b9056e70a70f.js +0 -1
- codex/static_root/assets/stats-tab-DL4J3pvZ.b9056e70a70f.js.br +0 -0
- codex/static_root/assets/stats-tab-DL4J3pvZ.b9056e70a70f.js.gz +0 -0
- codex/static_root/assets/stats-tab-DL4J3pvZ.js +0 -1
- codex/static_root/assets/stats-tab-DL4J3pvZ.js.br +0 -0
- codex/static_root/assets/stats-tab-DL4J3pvZ.js.gz +0 -0
- codex/static_root/assets/task-tab-0t7ZJlwL.1ff1cc14a6aa.js +0 -1
- codex/static_root/assets/task-tab-0t7ZJlwL.1ff1cc14a6aa.js.br +0 -0
- codex/static_root/assets/task-tab-0t7ZJlwL.1ff1cc14a6aa.js.gz +0 -0
- codex/static_root/assets/task-tab-0t7ZJlwL.js +0 -1
- codex/static_root/assets/task-tab-0t7ZJlwL.js.br +0 -0
- codex/static_root/assets/task-tab-0t7ZJlwL.js.gz +0 -0
- codex/static_root/assets/to-case-BCB751sX.4afb5ae1b77d.js +0 -1
- codex/static_root/assets/to-case-BCB751sX.js +0 -1
- codex/static_root/assets/unauthorized-Ovy_BO5R.598527e8e4fe.js +0 -1
- codex/static_root/assets/unauthorized-Ovy_BO5R.598527e8e4fe.js.br +0 -0
- codex/static_root/assets/unauthorized-Ovy_BO5R.598527e8e4fe.js.gz +0 -0
- codex/static_root/assets/unauthorized-Ovy_BO5R.js +0 -1
- codex/static_root/assets/unauthorized-Ovy_BO5R.js.br +0 -0
- codex/static_root/assets/unauthorized-Ovy_BO5R.js.gz +0 -0
- codex/static_root/assets/user-tab-Cdspo8ie.8e886a1a28e8.js +0 -1
- codex/static_root/assets/user-tab-Cdspo8ie.8e886a1a28e8.js.br +0 -0
- codex/static_root/assets/user-tab-Cdspo8ie.8e886a1a28e8.js.gz +0 -0
- codex/static_root/assets/user-tab-Cdspo8ie.js +0 -1
- codex/static_root/assets/user-tab-Cdspo8ie.js.br +0 -0
- codex/static_root/assets/user-tab-Cdspo8ie.js.gz +0 -0
- codex/static_root/manifest.80e911824648.json.br +0 -0
- codex/static_root/manifest.80e911824648.json.gz +0 -0
- {codex-1.6.2.dist-info → codex-1.6.4.dist-info}/LICENSE +0 -0
- {codex-1.6.2.dist-info → codex-1.6.4.dist-info}/WHEEL +0 -0
- {codex-1.6.2.dist-info → codex-1.6.4.dist-info}/entry_points.txt +0 -0
codex/exceptions.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Special Redirect Error."""
|
|
2
2
|
|
|
3
|
-
from collections.abc import Mapping
|
|
4
3
|
from copy import deepcopy
|
|
5
4
|
from pprint import pformat
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
+
from django.core.validators import EMPTY_VALUES
|
|
7
8
|
from django.shortcuts import redirect
|
|
8
9
|
from django.urls import reverse
|
|
9
10
|
from django.utils.http import urlencode
|
|
@@ -12,9 +13,18 @@ from rest_framework.status import HTTP_303_SEE_OTHER
|
|
|
12
13
|
|
|
13
14
|
from codex.logger.logging import get_logger
|
|
14
15
|
from codex.serializers.choices import DEFAULTS
|
|
16
|
+
from codex.serializers.fields import BreadcrumbsField
|
|
15
17
|
from codex.serializers.route import RouteSerializer
|
|
18
|
+
from codex.views.util import pop_name
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Mapping
|
|
22
|
+
|
|
16
23
|
|
|
17
24
|
LOG = get_logger(__name__)
|
|
25
|
+
_OPDS_REDIRECT_SETTINGS_KEYS = ("order_by", "top_group")
|
|
26
|
+
_REDIRECT_SETTINGS_KEYS = ("breadcrumbs", *_OPDS_REDIRECT_SETTINGS_KEYS)
|
|
27
|
+
_DEFAULT_ROUTE = DEFAULTS["breadcrumbs"][0]
|
|
18
28
|
|
|
19
29
|
|
|
20
30
|
class SeeOtherRedirectError(APIException):
|
|
@@ -24,33 +34,49 @@ class SeeOtherRedirectError(APIException):
|
|
|
24
34
|
default_code = "redirect"
|
|
25
35
|
default_detail = "redirect to a valid route"
|
|
26
36
|
|
|
27
|
-
def __init__(self, detail
|
|
37
|
+
def __init__(self, detail):
|
|
28
38
|
"""Create a response to pass to the exception handler."""
|
|
39
|
+
# Copy to edit and not write over refs
|
|
29
40
|
detail = dict(detail)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
params.
|
|
41
|
+
|
|
42
|
+
# Get route params
|
|
43
|
+
route = detail.get("route", {})
|
|
44
|
+
params = route.get("params", _DEFAULT_ROUTE)
|
|
45
|
+
params = pop_name(params)
|
|
46
|
+
route = deepcopy(route)
|
|
47
|
+
route["params"] = params
|
|
48
|
+
|
|
49
|
+
# For OPDS
|
|
34
50
|
self.route_kwargs = params
|
|
35
|
-
|
|
51
|
+
|
|
36
52
|
serializer = RouteSerializer(params)
|
|
37
|
-
route = deepcopy(route)
|
|
38
53
|
route["params"] = serializer.data
|
|
39
54
|
detail["route"] = route
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
|
|
56
|
+
filtered_settings = {}
|
|
57
|
+
settings = detail.get("settings", {})
|
|
58
|
+
for key in _REDIRECT_SETTINGS_KEYS:
|
|
59
|
+
value = settings.get(key)
|
|
60
|
+
if value in EMPTY_VALUES:
|
|
61
|
+
continue
|
|
62
|
+
if key == "breadcrumbs":
|
|
63
|
+
value = BreadcrumbsField().to_representation(value)
|
|
64
|
+
filtered_settings[key] = value
|
|
65
|
+
detail["settings"] = filtered_settings
|
|
66
|
+
|
|
67
|
+
self.detail = detail
|
|
68
|
+
|
|
69
|
+
LOG.debug(f"redirect {pformat(self.detail)}")
|
|
44
70
|
# super().__init__ converts every type into strings! do not use it.
|
|
45
71
|
|
|
46
72
|
def _add_query_params(self, url):
|
|
47
73
|
"""Change OPDS settings like the frontend does with error.detail."""
|
|
48
74
|
query_params = {}
|
|
49
75
|
settings: Mapping = self.detail.get("settings", {}) # type: ignore
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
for key in _OPDS_REDIRECT_SETTINGS_KEYS:
|
|
77
|
+
value = settings.get(key)
|
|
78
|
+
if value not in EMPTY_VALUES:
|
|
79
|
+
query_params[key] = value
|
|
54
80
|
if query_params:
|
|
55
81
|
url += "?" + urlencode(query_params)
|
|
56
82
|
return url
|
|
@@ -42,7 +42,7 @@ class ComicImporter(MovedImporter):
|
|
|
42
42
|
self.librarian_queue.put(LIBRARY_CHANGED_TASK)
|
|
43
43
|
|
|
44
44
|
# Wait to start the search index update in case more updates are incoming.
|
|
45
|
-
until = time() +
|
|
45
|
+
until = time() + 1
|
|
46
46
|
delayed_search_task = DelayedTasks(until, (SearchIndexUpdateTask(False),))
|
|
47
47
|
self.librarian_queue.put(delayed_search_task)
|
|
48
48
|
else:
|
|
@@ -111,7 +111,6 @@ class CleanupMixin(WorkerBaseMixin):
|
|
|
111
111
|
|
|
112
112
|
def cleanup_custom_covers(self):
|
|
113
113
|
"""Clean up unused custom covers."""
|
|
114
|
-
start = time()
|
|
115
114
|
covers = CustomCover.objects.only("path")
|
|
116
115
|
status = Status(JanitorStatusTypes.CLEANUP_COVERS, 0, covers.count())
|
|
117
116
|
delete_pks = []
|
|
@@ -128,12 +127,11 @@ class CleanupMixin(WorkerBaseMixin):
|
|
|
128
127
|
level = logging.INFO if status.complete else logging.DEBUG
|
|
129
128
|
self.log.log(level, f"Deleted {count} CustomCovers without source images.")
|
|
130
129
|
finally:
|
|
131
|
-
until =
|
|
130
|
+
until = time() + 1
|
|
132
131
|
self.status_controller.finish(status, until=until)
|
|
133
132
|
|
|
134
133
|
def cleanup_sessions(self):
|
|
135
134
|
"""Delete corrupt sessions."""
|
|
136
|
-
start = time()
|
|
137
135
|
status = Status(JanitorStatusTypes.CLEANUP_SESSIONS)
|
|
138
136
|
try:
|
|
139
137
|
self.status_controller.start(status)
|
|
@@ -152,5 +150,5 @@ class CleanupMixin(WorkerBaseMixin):
|
|
|
152
150
|
count, _ = bad_sessions.delete()
|
|
153
151
|
self.log.info(f"Deleted {count} corrupt sessions.")
|
|
154
152
|
finally:
|
|
155
|
-
until =
|
|
153
|
+
until = time() + 1
|
|
156
154
|
self.status_controller.finish(status, until=until)
|
|
@@ -33,15 +33,16 @@ class UpdateMixin(WorkerBaseMixin):
|
|
|
33
33
|
|
|
34
34
|
self.log.info("Codex seems outdated. Trying to update.")
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
args = (
|
|
37
|
+
sys.executable,
|
|
38
|
+
"-m",
|
|
39
|
+
"pip",
|
|
40
|
+
"install",
|
|
41
|
+
"--upgrade",
|
|
42
|
+
"codex",
|
|
43
|
+
)
|
|
44
|
+
subprocess.run( # noqa: S603
|
|
45
|
+
args,
|
|
45
46
|
check=True,
|
|
46
47
|
)
|
|
47
48
|
except Exception:
|
codex/librarian/search/merge.py
CHANGED
|
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
from humanize import naturaldelta, naturalsize
|
|
8
8
|
|
|
9
9
|
from codex.librarian.search.status import SearchIndexStatusTypes
|
|
10
|
-
from codex.librarian.search.tasks import SearchIndexRemoveStaleTask
|
|
11
10
|
from codex.librarian.search.version import VersionMixin
|
|
12
11
|
from codex.settings.settings import SEARCH_INDEX_PATH
|
|
13
12
|
from codex.status import Status
|
|
@@ -39,50 +38,48 @@ class MergeMixin(VersionMixin):
|
|
|
39
38
|
size += Path(segment).stat().st_size
|
|
40
39
|
return size
|
|
41
40
|
|
|
42
|
-
def
|
|
41
|
+
def _merge_search_index(self, optimize, status, name):
|
|
43
42
|
"""Optimize search index."""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
|
|
47
|
-
try:
|
|
48
|
-
statii = (
|
|
49
|
-
status,
|
|
50
|
-
Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
|
|
51
|
-
)
|
|
52
|
-
self.status_controller.start_many(statii)
|
|
53
|
-
start = time()
|
|
43
|
+
self.status_controller.start(status)
|
|
44
|
+
start = time()
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
segments, old_num_segments = self._get_segments_and_len()
|
|
47
|
+
if self._is_index_optimized(old_num_segments):
|
|
48
|
+
return
|
|
49
|
+
self.status_controller.start(status)
|
|
50
|
+
old_size = self._get_segments_size(segments)
|
|
51
|
+
# Optimize
|
|
52
|
+
self.log.info(
|
|
53
|
+
f"Search index found in {old_num_segments} segments," f" merging {name}..."
|
|
54
|
+
)
|
|
55
|
+
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
56
|
+
if optimize:
|
|
57
|
+
backend.optimize()
|
|
58
|
+
else:
|
|
59
|
+
backend.merge_small()
|
|
60
|
+
|
|
61
|
+
# Finish
|
|
62
|
+
segments, new_num_segments = self._get_segments_and_len()
|
|
63
|
+
new_size = self._get_segments_size(segments)
|
|
64
|
+
saved = naturalsize(old_size - new_size)
|
|
65
|
+
num_segments_diff = old_num_segments - new_num_segments
|
|
66
|
+
elapsed_time = time() - start
|
|
67
|
+
elapsed = naturaldelta(elapsed_time)
|
|
68
|
+
if num_segments_diff:
|
|
61
69
|
self.log.info(
|
|
62
|
-
f"
|
|
63
|
-
f"
|
|
70
|
+
f"Merged {num_segments_diff} search index segments in {elapsed}."
|
|
71
|
+
f"Saved {saved}."
|
|
64
72
|
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
backend.optimize()
|
|
68
|
-
else:
|
|
69
|
-
backend.merge_small()
|
|
73
|
+
else:
|
|
74
|
+
self.log.info("No small search index segments found.")
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if num_segments_diff:
|
|
79
|
-
self.log.info(
|
|
80
|
-
f"Merged {num_segments_diff} search index segments in {elapsed}."
|
|
81
|
-
f"Saved {saved}."
|
|
82
|
-
)
|
|
83
|
-
else:
|
|
84
|
-
self.log.info("No small search index segments found.")
|
|
85
|
-
self.librarian_queue.put(SearchIndexRemoveStaleTask())
|
|
76
|
+
def merge_search_index(self, optimize=False):
|
|
77
|
+
"""Optimize search index, trapping exceptions."""
|
|
78
|
+
verb = "All" if optimize else "Small"
|
|
79
|
+
name = f"Merge {verb} Segments"
|
|
80
|
+
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
|
|
81
|
+
try:
|
|
82
|
+
self._merge_search_index(optimize, status, name)
|
|
86
83
|
except Exception:
|
|
87
84
|
self.log.exception("Search index merge.")
|
|
88
85
|
finally:
|
codex/librarian/search/remove.py
CHANGED
|
@@ -38,39 +38,43 @@ class RemoveMixin(VersionMixin):
|
|
|
38
38
|
return delete_docnums
|
|
39
39
|
return delete_docnums
|
|
40
40
|
|
|
41
|
+
def _remove_stale_records(self, backend: CodexSearchBackend | None, status): # type: ignore
|
|
42
|
+
"""Remove records not in the database from the index."""
|
|
43
|
+
start_time = time()
|
|
44
|
+
if not backend:
|
|
45
|
+
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
46
|
+
if not backend.setup_complete:
|
|
47
|
+
backend.setup(False)
|
|
48
|
+
|
|
49
|
+
delete_docnums = self._get_delete_docnums(backend)
|
|
50
|
+
num_delete_docnums = len(delete_docnums)
|
|
51
|
+
count = 0
|
|
52
|
+
if num_delete_docnums:
|
|
53
|
+
status.total = num_delete_docnums
|
|
54
|
+
self.status_controller.start(status)
|
|
55
|
+
count = backend.remove_docnums(delete_docnums)
|
|
56
|
+
|
|
57
|
+
# Finish
|
|
58
|
+
if count:
|
|
59
|
+
elapsed_time = time() - start_time
|
|
60
|
+
elapsed = naturaldelta(elapsed_time)
|
|
61
|
+
cps = int(count / elapsed_time)
|
|
62
|
+
self.log.info(
|
|
63
|
+
f"Removed {count} stale records from the search index"
|
|
64
|
+
f" in {elapsed} at {cps} per second."
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
self.log.debug("No stale records to remove from the search index.")
|
|
68
|
+
|
|
41
69
|
def remove_stale_records(
|
|
42
70
|
self,
|
|
43
71
|
backend: CodexSearchBackend | None = None, # type: ignore
|
|
44
72
|
):
|
|
45
|
-
"""Remove records not in the database from the index."""
|
|
73
|
+
"""Remove records not in the database from the index, trapping exceptions."""
|
|
46
74
|
self.abort_event.clear()
|
|
47
75
|
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE)
|
|
48
76
|
try:
|
|
49
|
-
|
|
50
|
-
if not backend:
|
|
51
|
-
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
52
|
-
if not backend.setup_complete:
|
|
53
|
-
backend.setup(False)
|
|
54
|
-
|
|
55
|
-
delete_docnums = self._get_delete_docnums(backend)
|
|
56
|
-
num_delete_docnums = len(delete_docnums)
|
|
57
|
-
count = 0
|
|
58
|
-
if num_delete_docnums:
|
|
59
|
-
status.total = num_delete_docnums
|
|
60
|
-
self.status_controller.start(status)
|
|
61
|
-
count = backend.remove_docnums(delete_docnums)
|
|
62
|
-
|
|
63
|
-
# Finish
|
|
64
|
-
if count:
|
|
65
|
-
elapsed_time = time() - start_time
|
|
66
|
-
elapsed = naturaldelta(elapsed_time)
|
|
67
|
-
cps = int(count / elapsed_time)
|
|
68
|
-
self.log.info(
|
|
69
|
-
f"Removed {count} stale records from the search index"
|
|
70
|
-
f" in {elapsed} at {cps} per second."
|
|
71
|
-
)
|
|
72
|
-
else:
|
|
73
|
-
self.log.debug("No stale records to remove from the search index.")
|
|
77
|
+
self._remove_stale_records(backend, status)
|
|
74
78
|
except Exception:
|
|
75
79
|
self.log.exception("Removing stale records:")
|
|
76
80
|
finally:
|
codex/librarian/search/update.py
CHANGED
|
@@ -301,7 +301,7 @@ class UpdateMixin(RemoveMixin):
|
|
|
301
301
|
except Exception:
|
|
302
302
|
self.log.exception("Update search index with multiprocessing")
|
|
303
303
|
finally:
|
|
304
|
-
until =
|
|
304
|
+
until = time() + 1
|
|
305
305
|
self.status_controller.finish(status, until=until)
|
|
306
306
|
|
|
307
307
|
def clear_search_index(self):
|
|
@@ -313,50 +313,59 @@ class UpdateMixin(RemoveMixin):
|
|
|
313
313
|
self.status_controller.finish(clear_status)
|
|
314
314
|
self.log.info("Old search index cleared.")
|
|
315
315
|
|
|
316
|
-
def
|
|
316
|
+
def _update_search_index(self, start_time, rebuild):
|
|
317
317
|
"""Update or Rebuild the search index."""
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
)
|
|
328
|
-
return
|
|
318
|
+
remove_stale = False
|
|
319
|
+
any_update_in_progress = Library.objects.filter(
|
|
320
|
+
covers_only=False, update_in_progress=True
|
|
321
|
+
).exists()
|
|
322
|
+
if any_update_in_progress:
|
|
323
|
+
self.log.debug(
|
|
324
|
+
"Database update in progress, not updating search index yet."
|
|
325
|
+
)
|
|
326
|
+
return remove_stale
|
|
329
327
|
|
|
330
|
-
|
|
331
|
-
|
|
328
|
+
if not rebuild and not self.is_search_index_uuid_match():
|
|
329
|
+
rebuild = True
|
|
332
330
|
|
|
333
|
-
|
|
331
|
+
self._init_statuses(rebuild)
|
|
334
332
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
# Clear
|
|
334
|
+
if rebuild:
|
|
335
|
+
self.log.info("Rebuilding search index...")
|
|
336
|
+
self.clear_search_index()
|
|
339
337
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
338
|
+
# Update
|
|
339
|
+
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
340
|
+
backend.setup(False)
|
|
341
|
+
if self.abort_event.is_set():
|
|
342
|
+
self.log.debug("Abort update search index.")
|
|
343
|
+
return remove_stale
|
|
344
|
+
qs = self._get_queryset(backend, rebuild)
|
|
345
|
+
self._mp_update(backend, qs)
|
|
347
346
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
self.librarian_queue.put(task)
|
|
347
|
+
# Finish
|
|
348
|
+
if rebuild:
|
|
349
|
+
self.set_search_index_version()
|
|
350
|
+
else:
|
|
351
|
+
remove_stale = True
|
|
354
352
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
elapsed_time = time() - start_time
|
|
354
|
+
elapsed = naturaldelta(elapsed_time)
|
|
355
|
+
self.log.info(f"Search index updated in {elapsed}.")
|
|
356
|
+
return remove_stale
|
|
357
|
+
|
|
358
|
+
def update_search_index(self, rebuild=False):
|
|
359
|
+
"""Update or Rebuild the search index."""
|
|
360
|
+
start_time = time()
|
|
361
|
+
self.abort_event.clear()
|
|
362
|
+
remove_stale = False
|
|
363
|
+
try:
|
|
364
|
+
remove_stale = self._update_search_index(start_time, rebuild)
|
|
358
365
|
except Exception:
|
|
359
366
|
self.log.exception("Update search index")
|
|
360
367
|
finally:
|
|
361
|
-
|
|
362
|
-
|
|
368
|
+
self.status_controller.finish_many(self._STATUS_FINISH_TYPES)
|
|
369
|
+
if remove_stale:
|
|
370
|
+
task = SearchIndexRemoveStaleTask()
|
|
371
|
+
self.librarian_queue.put(task)
|
codex/librarian/stats.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Admin Flag View."""
|
|
2
|
+
|
|
3
|
+
from multiprocessing import cpu_count
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from platform import machine, python_version, release, system
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
|
|
8
|
+
from caseconverter import snakecase
|
|
9
|
+
from django.contrib.sessions.models import Session
|
|
10
|
+
from django.db.models import Count
|
|
11
|
+
|
|
12
|
+
from codex.logger.logging import get_logger
|
|
13
|
+
from codex.models import (
|
|
14
|
+
Comic,
|
|
15
|
+
Library,
|
|
16
|
+
)
|
|
17
|
+
from codex.version import VERSION
|
|
18
|
+
from codex.views.const import CONFIG_MODELS, METADATA_MODELS, STATS_GROUP_MODELS
|
|
19
|
+
from codex.views.session import SessionView
|
|
20
|
+
|
|
21
|
+
LOG = get_logger(__name__)
|
|
22
|
+
_KEY_MODELS_MAP = MappingProxyType(
|
|
23
|
+
{
|
|
24
|
+
"config": CONFIG_MODELS,
|
|
25
|
+
"groups": STATS_GROUP_MODELS,
|
|
26
|
+
"metadata": METADATA_MODELS,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
_DOCKERENV_PATH = Path("/.dockerenv")
|
|
30
|
+
_CGROUP_PATH = Path("/proc/self/cgroup")
|
|
31
|
+
_USER_STATS = MappingProxyType(
|
|
32
|
+
{
|
|
33
|
+
SessionView.BROWSER_SESSION_KEY: ("top_group", "order_by", "dynamic_covers"),
|
|
34
|
+
SessionView.READER_SESSION_KEY: (
|
|
35
|
+
"finish_on_last_page",
|
|
36
|
+
"fit_to",
|
|
37
|
+
"reading_direction",
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CodexStats:
|
|
44
|
+
"""Collect codex stats."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, params=None):
|
|
47
|
+
"""Specify which stats to collect. Default to all."""
|
|
48
|
+
if not params:
|
|
49
|
+
params = {}
|
|
50
|
+
self.params = params
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def _is_docker(cls):
|
|
54
|
+
"""Test if we're in a docker container."""
|
|
55
|
+
try:
|
|
56
|
+
return _DOCKERENV_PATH.is_file() or "docker" in _CGROUP_PATH.read_text()
|
|
57
|
+
except Exception:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
def _get_models(self, key):
|
|
61
|
+
"""Get models from request params."""
|
|
62
|
+
request_model_set = self.params.get(key, {})
|
|
63
|
+
all_models = _KEY_MODELS_MAP[key]
|
|
64
|
+
if request_model_set:
|
|
65
|
+
models = []
|
|
66
|
+
for model_name in request_model_set:
|
|
67
|
+
for model in all_models:
|
|
68
|
+
if model.__name__.lower() == model_name.lower():
|
|
69
|
+
models.append(model)
|
|
70
|
+
else:
|
|
71
|
+
models = all_models
|
|
72
|
+
return tuple(models)
|
|
73
|
+
|
|
74
|
+
def _get_model_counts(self, key):
|
|
75
|
+
"""Get database counts of each model group."""
|
|
76
|
+
models = self._get_models(key)
|
|
77
|
+
obj = {}
|
|
78
|
+
for model in models:
|
|
79
|
+
name = snakecase(model.__name__) + "_count"
|
|
80
|
+
qs = model.objects
|
|
81
|
+
if model == Library:
|
|
82
|
+
qs = qs.filter(covers_only=False)
|
|
83
|
+
obj[name] = qs.count()
|
|
84
|
+
return obj
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _aggregate_session_key(session, session_key, session_subkeys, user_stats):
|
|
88
|
+
session_dict = session.get(session_key, {})
|
|
89
|
+
for key in session_subkeys:
|
|
90
|
+
value = session_dict.get(key)
|
|
91
|
+
if value is None:
|
|
92
|
+
continue
|
|
93
|
+
if key not in user_stats:
|
|
94
|
+
user_stats[key] = {}
|
|
95
|
+
if value not in user_stats[key]:
|
|
96
|
+
user_stats[key][value] = 0
|
|
97
|
+
user_stats[key][value] += 1
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def _get_session_stats(cls):
|
|
101
|
+
"""Return the number of anonymous sessions."""
|
|
102
|
+
sessions = Session.objects.all()
|
|
103
|
+
anon_session_count = 0
|
|
104
|
+
user_stats = {}
|
|
105
|
+
for encoded_session in sessions:
|
|
106
|
+
session = encoded_session.get_decoded()
|
|
107
|
+
if not session.get("_auth_user_id"):
|
|
108
|
+
anon_session_count += 1
|
|
109
|
+
for session_key, subkeys in _USER_STATS.items():
|
|
110
|
+
cls._aggregate_session_key(session, session_key, subkeys, user_stats)
|
|
111
|
+
|
|
112
|
+
return user_stats, anon_session_count
|
|
113
|
+
|
|
114
|
+
def _get_platform(self, obj):
|
|
115
|
+
"""Add dict of platform information to object."""
|
|
116
|
+
platform = {
|
|
117
|
+
"docker": self._is_docker(),
|
|
118
|
+
"machine": machine(),
|
|
119
|
+
"cores": cpu_count(),
|
|
120
|
+
"system": {
|
|
121
|
+
"name": system(),
|
|
122
|
+
"release": release(),
|
|
123
|
+
},
|
|
124
|
+
"python_version": python_version(),
|
|
125
|
+
"codex_version": VERSION,
|
|
126
|
+
}
|
|
127
|
+
obj["platform"] = platform
|
|
128
|
+
|
|
129
|
+
def _get_config(self, obj):
|
|
130
|
+
"""Add dict of config informaation to object."""
|
|
131
|
+
config = self._get_model_counts("config")
|
|
132
|
+
sessions, config["user_anonymous_count"] = self._get_session_stats()
|
|
133
|
+
config["user_registered_count"] = config.pop("users_count", 0)
|
|
134
|
+
config["auth_group_count"] = config.pop("groups_count", 0)
|
|
135
|
+
obj["config"] = config
|
|
136
|
+
obj["sessions"] = sessions
|
|
137
|
+
|
|
138
|
+
def _get_groups(self, obj):
|
|
139
|
+
"""Add dict of groups information to object."""
|
|
140
|
+
groups = self._get_model_counts("groups")
|
|
141
|
+
obj["groups"] = groups
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _get_file_types(obj):
|
|
145
|
+
"""Query for file types."""
|
|
146
|
+
file_types = {}
|
|
147
|
+
qs = (
|
|
148
|
+
Comic.objects.values("file_type")
|
|
149
|
+
.annotate(count=Count("file_type"))
|
|
150
|
+
.order_by()
|
|
151
|
+
)
|
|
152
|
+
for query_group in qs:
|
|
153
|
+
value = query_group["file_type"]
|
|
154
|
+
name = value.lower() if value else "unknown"
|
|
155
|
+
file_types[name] = query_group["count"]
|
|
156
|
+
sorted_fts = dict(sorted(file_types.items()))
|
|
157
|
+
obj["file_types"] = sorted_fts
|
|
158
|
+
|
|
159
|
+
def _get_metadata(self, obj):
|
|
160
|
+
"""Add dict of metadata counts to object."""
|
|
161
|
+
metadata = self._get_model_counts("metadata")
|
|
162
|
+
obj["metadata"] = metadata
|
|
163
|
+
|
|
164
|
+
def get(self):
|
|
165
|
+
"""Construct the stats object."""
|
|
166
|
+
obj = {}
|
|
167
|
+
if not self.params or "platform" in self.params:
|
|
168
|
+
self._get_platform(obj)
|
|
169
|
+
if not self.params or "config" in self.params:
|
|
170
|
+
self._get_config(obj)
|
|
171
|
+
if not self.params or "groups" in self.params:
|
|
172
|
+
self._get_groups(obj)
|
|
173
|
+
if not self.params or "fileTypes" in self.params:
|
|
174
|
+
self._get_file_types(obj)
|
|
175
|
+
if not self.params or "metadata" in self.params:
|
|
176
|
+
self._get_metadata(obj)
|
|
177
|
+
return obj
|
|
@@ -191,8 +191,8 @@ class CodexLibraryEventHandler(CodexEventHandlerBase):
|
|
|
191
191
|
events = self._transform_file_event(event)
|
|
192
192
|
|
|
193
193
|
# Send it to the EventBatcher
|
|
194
|
-
for
|
|
195
|
-
task = WatchdogEventTask(self.library_pk,
|
|
194
|
+
for sub_event in events:
|
|
195
|
+
task = WatchdogEventTask(self.library_pk, sub_event)
|
|
196
196
|
self.librarian_queue.put(task)
|
|
197
197
|
|
|
198
198
|
# Calls stub event dispatchers
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Admin view serialzers."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Admin flag serializers."""
|
|
2
|
+
|
|
3
|
+
from codex.models import AdminFlag
|
|
4
|
+
from codex.serializers.models.base import BaseModelSerializer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AdminFlagSerializer(BaseModelSerializer):
|
|
8
|
+
"""Admin Flag Serializer."""
|
|
9
|
+
|
|
10
|
+
class Meta(BaseModelSerializer.Meta):
|
|
11
|
+
"""Specify Model."""
|
|
12
|
+
|
|
13
|
+
model = AdminFlag
|
|
14
|
+
fields = ("key", "on")
|
|
15
|
+
read_only_fields = ("key",)
|