codex 1.6.0a6__py3-none-any.whl → 1.6.0a8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of codex might be problematic. Click here for more details.
- codex/exceptions.py +1 -0
- codex/integrity.py +4 -4
- codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +56 -86
- codex/librarian/importer/cache.py +180 -0
- codex/librarian/importer/const.py +15 -6
- codex/librarian/importer/create_comics.py +102 -17
- codex/librarian/importer/create_covers.py +98 -0
- codex/librarian/importer/create_fks.py +73 -170
- codex/librarian/importer/deleted.py +59 -46
- codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
- codex/librarian/importer/failed_imports.py +41 -62
- codex/librarian/importer/importer.py +96 -0
- codex/librarian/importer/importerd.py +71 -395
- codex/librarian/importer/init.py +265 -0
- codex/librarian/importer/link_comics.py +26 -81
- codex/librarian/importer/link_covers.py +73 -0
- codex/librarian/importer/moved.py +72 -96
- codex/librarian/importer/query_covers.py +52 -0
- codex/librarian/importer/query_fks.py +128 -172
- codex/librarian/importer/status.py +2 -39
- codex/librarian/importer/tasks.py +22 -13
- codex/librarian/janitor/cleanup.py +2 -2
- codex/librarian/janitor/janitor.py +11 -1
- codex/librarian/librariand.py +8 -2
- codex/migrations/0027_import_order_and_covers.py +345 -0
- codex/models/comic.py +1 -2
- codex/models/groups.py +3 -2
- codex/models/paths.py +0 -1
- codex/permissions.py +1 -1
- codex/search/query.py +25 -2
- codex/search/writing.py +1 -1
- codex/serializers/admin.py +1 -2
- codex/serializers/browser/filters.py +8 -101
- codex/serializers/browser/mixins.py +2 -3
- codex/serializers/browser/page.py +2 -2
- codex/serializers/browser/settings.py +4 -3
- codex/serializers/choices.py +10 -1
- codex/serializers/fields.py +123 -0
- codex/serializers/mtime.py +25 -0
- codex/serializers/opds/v2.py +7 -11
- codex/serializers/opds/v2_unused.py +5 -3
- codex/serializers/reader.py +5 -2
- codex/serializers/route.py +7 -3
- codex/settings/settings.py +2 -2
- codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +1 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.js +1 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js → VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.js → VCheckboxBtn-B8Z4NA-t.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +1 -0
- codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.js +1 -0
- codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js +1 -0
- codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.js +1 -0
- codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +1 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +1 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js +1 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.js +1 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +1 -0
- codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.js +1 -0
- codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js +1 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.js +1 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +1 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.js +1 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
- codex/static_root/assets/{VTable-C0gAriFq.fa6e4b60d4ef.js → VTable-D1esKtlw.52165930e3b9.js} +1 -1
- codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
- codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
- codex/static_root/assets/{VTable-C0gAriFq.js → VTable-D1esKtlw.js} +1 -1
- codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
- codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +1 -0
- codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.js +1 -0
- codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-CFhKCj49.b2458002a937.js → VWindowItem-B0KokcAk.ea94224aecb9.js} +1 -1
- codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
- codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-CFhKCj49.js → VWindowItem-B0KokcAk.js} +1 -1
- codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
- codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
- codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +1 -0
- codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
- codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
- codex/static_root/assets/admin-Y0bL21AB.js +1 -0
- codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
- codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +30 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +30 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
- codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
- codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
- codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
- codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +1 -0
- codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
- codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
- codex/static_root/assets/browser-CMAtBCLx.css +1 -0
- codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
- codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
- codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +1 -0
- codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
- codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
- codex/static_root/assets/browser-DoJk8uvb.js +1 -0
- codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
- codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-JYM9aAR9.js → change-password-dialog-CH4xbjJa.b749aa6ea07e.js} +1 -1
- codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-JYM9aAR9.f71d378f4fa6.js → change-password-dialog-CH4xbjJa.js} +1 -1
- codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.515858dd21e8.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
- codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.js → confirm-dialog-DkU0twE6.js} +1 -1
- codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
- codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +1 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.js +1 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
- codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
- codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
- codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +1 -0
- codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
- codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
- codex/static_root/assets/filter-DjxN78cK.js +1 -0
- codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
- codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Ci_XcDT0.a5ea640a08d6.js → flag-tab-D4ZNSbMR.e763a9937701.js} +1 -1
- codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
- codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Ci_XcDT0.js → flag-tab-D4ZNSbMR.js} +1 -1
- codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
- codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
- codex/static_root/assets/{group-tab-DFma2R-E.879c37b15bbd.js → group-tab-DucpNoBs.a6f37ae940c0.js} +1 -1
- codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
- codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
- codex/static_root/assets/{group-tab-DFma2R-E.js → group-tab-DucpNoBs.js} +1 -1
- codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
- codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js +1 -0
- codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.js +1 -0
- codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
- codex/static_root/assets/{library-tab-V8DUGoHN.js → library-tab-CzksdS3Q.566acdb45dec.js} +1 -1
- codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
- codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
- codex/static_root/assets/{library-tab-V8DUGoHN.809c548ff347.js → library-tab-CzksdS3Q.js} +1 -1
- codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
- codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
- codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +32 -0
- codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
- codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
- codex/static_root/assets/main-DX-k2724.js +32 -0
- codex/static_root/assets/main-DX-k2724.js.br +0 -0
- codex/static_root/assets/main-DX-k2724.js.gz +0 -0
- codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
- codex/static_root/assets/main-Ztp855PW.css.br +0 -0
- codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
- codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
- codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
- codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +1 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +1 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js +305 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.js +305 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
- codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
- codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
- codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
- codex/static_root/assets/reader-CCCfpM3x.css +1 -0
- codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
- codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +2 -0
- codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.js +2 -0
- codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +1 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.js +1 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +2 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.js +2 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +1 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.js +1 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +1 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.js +1 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
- codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +1 -0
- codex/static_root/assets/to-case-BR2frjFx.js +1 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +1 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.js +1 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js +1 -0
- codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.js +1 -0
- codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
- codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
- codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
- codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
- codex/static_root/js/choices-admin.json +1 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/choices.a0aaaa6c7ef7.json +1 -0
- codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
- codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
- codex/static_root/js/choices.json +1 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/manifest.16d52597ef50.json +629 -0
- codex/static_root/manifest.16d52597ef50.json.br +0 -0
- codex/static_root/manifest.16d52597ef50.json.gz +0 -0
- codex/static_root/manifest.json +275 -288
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/urls/api/auth.py +2 -1
- codex/urls/api/browser.py +13 -4
- codex/urls/api/reader.py +3 -9
- codex/urls/api/v3.py +2 -0
- codex/urls/opds/binary.py +1 -1
- codex/util.py +10 -0
- codex/views/admin/api_key.py +2 -6
- codex/views/admin/auth.py +28 -0
- codex/views/admin/flag.py +2 -7
- codex/views/admin/group.py +2 -6
- codex/views/admin/library.py +7 -20
- codex/views/admin/stats.py +7 -66
- codex/views/admin/tasks.py +24 -11
- codex/views/admin/user.py +3 -9
- codex/views/auth.py +30 -53
- codex/views/bookmark.py +7 -11
- codex/views/browser/{browser_annotations.py → annotations.py} +90 -181
- codex/views/browser/base.py +23 -88
- codex/views/browser/breadcrumbs.py +202 -0
- codex/views/browser/browser.py +64 -342
- codex/views/browser/choices.py +22 -54
- codex/views/browser/cover.py +177 -0
- codex/views/browser/filters/annotations.py +24 -23
- codex/views/browser/filters/bookmark.py +9 -13
- codex/views/browser/filters/field.py +5 -5
- codex/views/browser/filters/group.py +18 -10
- codex/views/browser/filters/search.py +30 -18
- codex/views/browser/metadata.py +22 -31
- codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
- codex/views/browser/paginate.py +106 -0
- codex/views/browser/{session.py → settings.py} +4 -3
- codex/views/browser/title.py +48 -0
- codex/views/browser/validate.py +158 -0
- codex/views/const.py +109 -0
- codex/views/download.py +5 -8
- codex/views/frontend.py +4 -2
- codex/views/mtime.py +72 -0
- codex/views/opds/auth.py +17 -0
- codex/views/opds/authentication_v1.py +46 -45
- codex/views/opds/binary.py +5 -10
- codex/views/opds/const.py +13 -0
- codex/views/opds/urls.py +6 -4
- codex/views/opds/util.py +4 -12
- codex/views/opds/v1/entry/links.py +18 -33
- codex/views/opds/v1/facets.py +7 -7
- codex/views/opds/v1/feed.py +52 -40
- codex/views/opds/v1/links.py +7 -5
- codex/views/opds/v1/opensearch_v1.py +2 -6
- codex/views/opds/v2/feed.py +22 -23
- codex/views/opds/v2/links.py +4 -2
- codex/views/opds/v2/publications.py +34 -54
- codex/views/opds/v2/top_links.py +2 -2
- codex/views/public.py +36 -0
- codex/views/reader/arcs.py +101 -0
- codex/views/reader/books.py +199 -0
- codex/views/reader/page.py +3 -7
- codex/views/reader/reader.py +56 -301
- codex/views/reader/{session.py → settings.py} +4 -2
- codex/views/session.py +77 -103
- codex/views/settings.py +34 -0
- codex/views/template.py +3 -3
- codex/views/util.py +63 -0
- codex/views/version.py +2 -2
- {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/METADATA +19 -14
- {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/RECORD +373 -362
- codex/librarian/importer/covers.py +0 -47
- codex/librarian/importer/update_comics.py +0 -85
- codex/migrations/0027_sort_name.py +0 -139
- codex/migrations/0028_custom_covers.py +0 -166
- codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js +0 -1
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.js +0 -1
- codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
- codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +0 -1
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js +0 -1
- codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
- codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js +0 -1
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.js +0 -1
- codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
- codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js +0 -1
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js +0 -1
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js +0 -1
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.js +0 -1
- codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +0 -1
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.js +0 -1
- codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
- codex/static_root/assets/VSelect-BnoCfCLY.js.gz +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
- codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js +0 -1
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js +0 -1
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js +0 -1
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js +0 -1
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
- codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
- codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
- codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
- codex/static_root/assets/VTable-C0gAriFq.js.gz +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js +0 -1
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js +0 -1
- codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
- codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
- codex/static_root/assets/VWindowItem-CFhKCj49.js.gz +0 -0
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js +0 -1
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
- codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
- codex/static_root/assets/admin-BpGFsvOH.js +0 -1
- codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
- codex/static_root/assets/admin-BpGFsvOH.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +0 -30
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +0 -30
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +0 -1
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
- codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
- codex/static_root/assets/browser-CRPvEdqv.js +0 -1
- codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
- codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +0 -1
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.css +0 -1
- codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
- codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
- codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js +0 -1
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.js +0 -1
- codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
- codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
- codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
- codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
- codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
- codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js +0 -1
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
- codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
- codex/static_root/assets/filter-frSNsexx.js +0 -1
- codex/static_root/assets/filter-frSNsexx.js.br +0 -0
- codex/static_root/assets/filter-frSNsexx.js.gz +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
- codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
- codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js +0 -1
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.js +0 -1
- codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
- codex/static_root/assets/http-error-B3fQGxpt.js.gz +0 -0
- codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
- codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
- codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
- codex/static_root/assets/index-DUjeNg4x.js +0 -1
- codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
- codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
- codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js +0 -32
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
- codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
- codex/static_root/assets/main-Cavvqkyp.js +0 -32
- codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
- codex/static_root/assets/main-Cavvqkyp.js.gz +0 -0
- codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
- codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css +0 -1
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css +0 -1
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js +0 -1
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +0 -1
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js +0 -35
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js +0 -35
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
- codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js +0 -2
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
- codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
- codex/static_root/assets/reader-C39uGGxn.js +0 -2
- codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
- codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css +0 -1
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.css +0 -1
- codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
- codex/static_root/assets/reader-N2Ei_y2K.css.gz +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js +0 -1
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js +0 -1
- codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
- codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js +0 -2
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.js +0 -2
- codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
- codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js +0 -1
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.js +0 -1
- codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
- codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +0 -1
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js +0 -1
- codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
- codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
- codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
- codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js +0 -1
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.js +0 -1
- codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
- codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js +0 -1
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.js +0 -1
- codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
- codex/static_root/assets/user-tab-BOq4BMMc.js.gz +0 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
- codex/static_root/js/choices.23fedac2b9ba.json +0 -1
- codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
- codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
- codex/static_root/manifest.ad9da9714e46.json +0 -642
- codex/static_root/manifest.ad9da9714e46.json.br +0 -0
- codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
- codex/views/browser/browser_breadcrumbs.py +0 -175
- codex/views/browser/const.py +0 -3
- codex/views/cover.py +0 -78
- {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
- {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
- {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
codex/views/auth.py
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
"""Views authorization."""
|
|
2
2
|
|
|
3
|
-
from types import MappingProxyType
|
|
4
|
-
|
|
5
|
-
from django.conf import settings
|
|
6
3
|
from django.contrib.auth.models import AnonymousUser
|
|
7
4
|
from django.db.models import Q
|
|
8
5
|
from drf_spectacular.utils import extend_schema
|
|
9
6
|
from rest_framework.generics import GenericAPIView
|
|
10
|
-
from rest_framework.mixins import RetrieveModelMixin
|
|
11
7
|
from rest_framework.permissions import IsAuthenticated
|
|
12
8
|
from rest_framework.response import Response
|
|
9
|
+
from rest_framework.views import APIView
|
|
13
10
|
|
|
14
11
|
from codex.logger.logging import get_logger
|
|
15
12
|
from codex.models import AdminFlag, Comic, Folder, StoryArc, UserActive
|
|
16
|
-
from codex.serializers.auth import
|
|
17
|
-
from codex.serializers.choices import CHOICES
|
|
13
|
+
from codex.serializers.auth import TimezoneSerializer
|
|
18
14
|
from codex.serializers.mixins import OKSerializer
|
|
19
|
-
from codex.views.const import GROUP_NAME_MAP
|
|
20
15
|
|
|
21
16
|
LOG = get_logger(__name__)
|
|
22
|
-
NULL_USER = {"pk": None, "username": None, "is_staff": False}
|
|
23
17
|
|
|
24
18
|
|
|
25
19
|
class IsAuthenticatedOrEnabledNonUsers(IsAuthenticated):
|
|
@@ -37,20 +31,33 @@ class IsAuthenticatedOrEnabledNonUsers(IsAuthenticated):
|
|
|
37
31
|
return super().has_permission(request, view)
|
|
38
32
|
|
|
39
33
|
|
|
40
|
-
class
|
|
34
|
+
class AuthMixin:
|
|
35
|
+
"""General Auth Policy."""
|
|
36
|
+
|
|
37
|
+
permission_classes = (IsAuthenticatedOrEnabledNonUsers,)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AuthAPIView(AuthMixin, APIView):
|
|
41
|
+
"""Auth Policy APIView."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AuthGenericAPIView(AuthMixin, GenericAPIView):
|
|
45
|
+
"""Auth Policy GenericAPIView."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TimezoneView(AuthGenericAPIView):
|
|
41
49
|
"""User info."""
|
|
42
50
|
|
|
43
51
|
input_serializer_class = TimezoneSerializer
|
|
44
52
|
serializer_class = OKSerializer
|
|
45
53
|
|
|
46
|
-
_AUTH_USER_MODEL_TYPE = type(settings.AUTH_USER_MODEL)
|
|
47
|
-
|
|
48
54
|
@extend_schema(request=input_serializer_class)
|
|
49
55
|
def post(self, request, *args, **kwargs):
|
|
50
56
|
"""Get the user info for the current user."""
|
|
51
|
-
|
|
57
|
+
data = self.request.data # type: ignore
|
|
58
|
+
serializer = self.input_serializer_class(data=data)
|
|
52
59
|
serializer.is_valid(raise_exception=True)
|
|
53
|
-
request.session["django_timezone"] = serializer.validated_data["timezone"]
|
|
60
|
+
request.session["django_timezone"] = serializer.validated_data["timezone"] # type: ignore
|
|
54
61
|
request.session.save()
|
|
55
62
|
user = self.request.user
|
|
56
63
|
if user.is_authenticated:
|
|
@@ -59,49 +66,11 @@ class TimezoneView(GenericAPIView):
|
|
|
59
66
|
return Response(serializer.data)
|
|
60
67
|
|
|
61
68
|
|
|
62
|
-
class AdminFlagsView(GenericAPIView, RetrieveModelMixin):
|
|
63
|
-
"""Get admin flags relevant to auth."""
|
|
64
|
-
|
|
65
|
-
_ADMIN_FLAG_KEYS = frozenset(
|
|
66
|
-
(
|
|
67
|
-
AdminFlag.FlagChoices.NON_USERS.value,
|
|
68
|
-
AdminFlag.FlagChoices.REGISTRATION.value,
|
|
69
|
-
)
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
serializer_class = AuthAdminFlagsSerializer
|
|
73
|
-
queryset = AdminFlag.objects.filter(key__in=_ADMIN_FLAG_KEYS).only("key", "on")
|
|
74
|
-
|
|
75
|
-
def get_object(self):
|
|
76
|
-
"""Get admin flags."""
|
|
77
|
-
flags = {}
|
|
78
|
-
for obj in self.get_queryset(): # type: ignore
|
|
79
|
-
name = CHOICES["admin"]["adminFlags"][obj.key].lower().replace(" ", "_")
|
|
80
|
-
flags[name] = obj.on
|
|
81
|
-
return flags
|
|
82
|
-
|
|
83
|
-
def get(self, request, *args, **kwargs):
|
|
84
|
-
"""Get admin flags relevant to auth."""
|
|
85
|
-
return self.retrieve(request, *args, **kwargs)
|
|
86
|
-
|
|
87
|
-
|
|
88
69
|
class GroupACLMixin:
|
|
89
70
|
"""Filter group ACLS for views."""
|
|
90
71
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
STORY_ARC_GROUP = "a"
|
|
94
|
-
COMIC_GROUP = "c"
|
|
95
|
-
GROUP_RELATION = MappingProxyType(
|
|
96
|
-
{
|
|
97
|
-
**GROUP_NAME_MAP,
|
|
98
|
-
COMIC_GROUP: "pk",
|
|
99
|
-
FOLDER_GROUP: "parent_folder",
|
|
100
|
-
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
101
|
-
}
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
def get_rel_prefix(self, model):
|
|
72
|
+
@staticmethod
|
|
73
|
+
def get_rel_prefix(model):
|
|
105
74
|
"""Return the relation prfiex for most fields."""
|
|
106
75
|
prefix = ""
|
|
107
76
|
if model != Comic:
|
|
@@ -133,3 +102,11 @@ class GroupACLMixin:
|
|
|
133
102
|
query |= auth_query
|
|
134
103
|
|
|
135
104
|
return query
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AuthFilterGenericAPIView(AuthGenericAPIView, GroupACLMixin):
|
|
108
|
+
"""Auth Enabled GenericAPIView."""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class AuthFilterAPIView(AuthAPIView, GroupACLMixin):
|
|
112
|
+
"""Auth Enabled APIView."""
|
codex/views/bookmark.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"""Bookmark views."""
|
|
2
2
|
|
|
3
|
-
from typing import ClassVar
|
|
4
|
-
|
|
5
3
|
from drf_spectacular.utils import extend_schema
|
|
6
|
-
from rest_framework.generics import GenericAPIView
|
|
7
4
|
from rest_framework.response import Response
|
|
8
5
|
|
|
9
6
|
from codex.logger.logging import get_logger
|
|
@@ -12,13 +9,16 @@ from codex.serializers.models.bookmark import (
|
|
|
12
9
|
BookmarkFinishedSerializer,
|
|
13
10
|
BookmarkSerializer,
|
|
14
11
|
)
|
|
15
|
-
from codex.views.auth import
|
|
12
|
+
from codex.views.auth import (
|
|
13
|
+
AuthFilterGenericAPIView,
|
|
14
|
+
)
|
|
15
|
+
from codex.views.const import GROUP_RELATION
|
|
16
16
|
|
|
17
17
|
LOG = get_logger(__name__)
|
|
18
18
|
VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class BookmarkBaseView(
|
|
21
|
+
class BookmarkBaseView(AuthFilterGenericAPIView):
|
|
22
22
|
"""Bookmark Updater."""
|
|
23
23
|
|
|
24
24
|
_BOOKMARK_UPDATE_FIELDS = (
|
|
@@ -160,12 +160,11 @@ class BookmarkBaseView(GenericAPIView, GroupACLMixin):
|
|
|
160
160
|
class BookmarkView(BookmarkBaseView):
|
|
161
161
|
"""User Bookmark View."""
|
|
162
162
|
|
|
163
|
-
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type:ignore
|
|
164
163
|
serializer_class = BookmarkSerializer
|
|
165
164
|
|
|
166
165
|
def _validate(self, serializer_class):
|
|
167
166
|
"""Validate and translate the submitted data."""
|
|
168
|
-
data = self.request.data
|
|
167
|
+
data = self.request.data # type: ignore
|
|
169
168
|
if serializer_class:
|
|
170
169
|
serializer = serializer_class(data=data, partial=True)
|
|
171
170
|
else:
|
|
@@ -184,10 +183,7 @@ class BookmarkView(BookmarkBaseView):
|
|
|
184
183
|
|
|
185
184
|
pks = self.kwargs.get("pks")
|
|
186
185
|
|
|
187
|
-
if group == "f"
|
|
188
|
-
relation = "folders__in"
|
|
189
|
-
else:
|
|
190
|
-
relation = self.GROUP_RELATION[group] + "__in"
|
|
186
|
+
relation = "folders__in" if group == "f" else GROUP_RELATION[group] + "__in"
|
|
191
187
|
comic_filter = {relation: pks}
|
|
192
188
|
|
|
193
189
|
self.update_bookmarks(updates, comic_filter=comic_filter)
|
|
@@ -17,82 +17,73 @@ from django.db.models import (
|
|
|
17
17
|
Value,
|
|
18
18
|
When,
|
|
19
19
|
)
|
|
20
|
+
from django.db.models.aggregates import Count
|
|
20
21
|
from django.db.models.fields import CharField, PositiveSmallIntegerField
|
|
21
22
|
from django.db.models.functions import Least, Reverse, Right, StrIndex
|
|
22
23
|
|
|
24
|
+
from codex.logger.logging import get_logger
|
|
23
25
|
from codex.models import (
|
|
24
26
|
BrowserGroupModel,
|
|
25
27
|
Comic,
|
|
26
28
|
Folder,
|
|
27
|
-
Imprint,
|
|
28
|
-
Publisher,
|
|
29
|
-
Series,
|
|
30
29
|
StoryArc,
|
|
31
30
|
Volume,
|
|
32
31
|
)
|
|
33
32
|
from codex.models.functions import JsonGroupArray
|
|
34
|
-
from codex.views.browser.
|
|
33
|
+
from codex.views.browser.order_by import (
|
|
35
34
|
BrowserOrderByView,
|
|
36
35
|
)
|
|
36
|
+
from codex.views.const import STORY_ARC_GROUP
|
|
37
37
|
from codex.views.mixins import SharedAnnotationsMixin
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
_NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
|
|
40
|
+
_NONE_DATETIMEFIELD = Value(None, DateTimeField())
|
|
41
|
+
_ORDER_AGGREGATE_FUNCS = MappingProxyType(
|
|
42
|
+
# These are annotated to order_value because they're simple relations
|
|
40
43
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
"age_rating": Avg,
|
|
45
|
+
"community_rating": Avg,
|
|
46
|
+
"created_at": Min,
|
|
47
|
+
"critical_rating": Avg,
|
|
48
|
+
"date": Min,
|
|
49
|
+
"page_count": Sum,
|
|
50
|
+
"size": Sum,
|
|
51
|
+
"updated_at": Min,
|
|
47
52
|
}
|
|
48
53
|
)
|
|
54
|
+
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
55
|
+
# These are annotated with their own functions
|
|
56
|
+
{
|
|
57
|
+
"filename",
|
|
58
|
+
"sort_name",
|
|
59
|
+
"search_score",
|
|
60
|
+
"bookmark_updated_at",
|
|
61
|
+
"story_arc_number",
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
_ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingProxyType(
|
|
65
|
+
{Comic: "id", Volume: "name"}
|
|
66
|
+
)
|
|
67
|
+
_ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
|
|
68
|
+
|
|
69
|
+
LOG = get_logger(__name__)
|
|
49
70
|
|
|
50
71
|
|
|
51
72
|
class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
52
73
|
"""Base class for views that need special metadata annotations."""
|
|
53
74
|
|
|
54
|
-
_NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
|
|
55
|
-
_NONE_DATETIMEFIELD = Value(None, DateTimeField())
|
|
56
|
-
_ORDER_AGGREGATE_FUNCS = MappingProxyType(
|
|
57
|
-
# These are annotated to order_value because they're simple relations
|
|
58
|
-
{
|
|
59
|
-
"age_rating": Avg,
|
|
60
|
-
"community_rating": Avg,
|
|
61
|
-
"created_at": Min,
|
|
62
|
-
"critical_rating": Avg,
|
|
63
|
-
"date": Min,
|
|
64
|
-
"page_count": Sum,
|
|
65
|
-
"size": Sum,
|
|
66
|
-
"updated_at": Min,
|
|
67
|
-
}
|
|
68
|
-
)
|
|
69
|
-
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
70
|
-
# These are annotated with their own functions
|
|
71
|
-
{
|
|
72
|
-
"filename",
|
|
73
|
-
"sort_name",
|
|
74
|
-
"search_score",
|
|
75
|
-
"bookmark_updated_at",
|
|
76
|
-
"story_arc_number",
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
_ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = (
|
|
80
|
-
MappingProxyType({Comic: "id", Volume: "name"})
|
|
81
|
-
)
|
|
82
|
-
|
|
83
75
|
def __init__(self, *args, **kwargs):
|
|
84
76
|
"""Set params for the type checker."""
|
|
85
77
|
super().__init__(*args, **kwargs)
|
|
86
78
|
self.is_opds_1_acquisition = False
|
|
87
79
|
self.comic_sort_names = ()
|
|
88
80
|
self.bm_annotataion_data: dict[BrowserGroupModel, tuple[str, dict]] = {}
|
|
89
|
-
self.cover_search_score_pairs: tuple[tuple[int, float], ...] = ()
|
|
90
81
|
|
|
91
82
|
def get_group_by(self, model=None):
|
|
92
83
|
"""Get the group by for the model."""
|
|
93
84
|
if model is None:
|
|
94
85
|
model = self.model
|
|
95
|
-
return
|
|
86
|
+
return _ALTERNATE_GROUP_BY.get(model, "sort_name") # type: ignore
|
|
96
87
|
|
|
97
88
|
def _alias_sort_names(self, qs, model):
|
|
98
89
|
"""Annotate sort_name."""
|
|
@@ -133,7 +124,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
133
124
|
# Get story_arc__pk
|
|
134
125
|
group = self.kwargs["group"]
|
|
135
126
|
pks = self.kwargs["pks"]
|
|
136
|
-
if group ==
|
|
127
|
+
if group == STORY_ARC_GROUP and pks:
|
|
137
128
|
story_arc_pks = pks
|
|
138
129
|
else:
|
|
139
130
|
story_arc_pks = self.params.get("filters", {}).get( # type: ignore
|
|
@@ -150,7 +141,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
150
141
|
)
|
|
151
142
|
story_arc_number = self.order_agg_func("selected_story_arc_number__number")
|
|
152
143
|
else:
|
|
153
|
-
story_arc_number =
|
|
144
|
+
story_arc_number = _NONE_INTEGERFIELD
|
|
154
145
|
|
|
155
146
|
return qs.alias(story_arc_number=story_arc_number)
|
|
156
147
|
|
|
@@ -168,19 +159,30 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
168
159
|
qs = qs.annotate(page_count=page_count_sum)
|
|
169
160
|
return qs
|
|
170
161
|
|
|
171
|
-
def
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
162
|
+
def get_bookmark_updated_at_aggregate(self, model, always_max=False):
|
|
163
|
+
"""Get The aggregate function for relevant bookmark.updated_at."""
|
|
164
|
+
bm_rel = self.get_bm_rel(model)
|
|
165
|
+
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
166
|
+
self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
|
|
167
|
+
|
|
168
|
+
updated_at_rel = f"{bm_rel}__updated_at"
|
|
169
|
+
agg_func = Max if always_max else self.order_agg_func
|
|
170
|
+
return agg_func(
|
|
171
|
+
updated_at_rel,
|
|
172
|
+
default=_NONE_DATETIMEFIELD,
|
|
173
|
+
filter=bm_filter,
|
|
174
|
+
)
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
def _annotate_bookmark_updated_at(self, qs, model):
|
|
177
|
+
if (
|
|
178
|
+
self.is_opds_1_acquisition
|
|
179
|
+
or self.order_key == "bookmark_updated_at"
|
|
180
|
+
or self.is_bookmark_filtered
|
|
181
|
+
):
|
|
182
|
+
bookmark_updated_at_aggregate = self.get_bookmark_updated_at_aggregate(
|
|
183
|
+
model
|
|
182
184
|
)
|
|
183
|
-
if self.TARGET == "opds1":
|
|
185
|
+
if self.TARGET == "opds1" or self.is_bookmark_filtered:
|
|
184
186
|
qs = qs.annotate(bookmark_updated_at=bookmark_updated_at_aggregate)
|
|
185
187
|
else:
|
|
186
188
|
qs = qs.alias(bookmark_updated_at=bookmark_updated_at_aggregate)
|
|
@@ -191,11 +193,11 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
191
193
|
# Determine order func
|
|
192
194
|
if model == Folder and self.order_key == "filename":
|
|
193
195
|
order_value = F("name")
|
|
194
|
-
elif model == Comic or self.order_key in
|
|
196
|
+
elif model == Comic or self.order_key in _ANNOTATED_ORDER_FIELDS:
|
|
195
197
|
# These are annotated in browser_annotaions
|
|
196
198
|
order_value = F(self.order_key)
|
|
197
199
|
else:
|
|
198
|
-
agg_func =
|
|
200
|
+
agg_func = _ORDER_AGGREGATE_FUNCS[self.order_key]
|
|
199
201
|
agg_func = self.order_agg_func if agg_func == Min else agg_func
|
|
200
202
|
field = self.rel_prefix + self.order_key
|
|
201
203
|
order_value = agg_func(field)
|
|
@@ -220,56 +222,29 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
220
222
|
qs = self._annotate_order_value(qs, model)
|
|
221
223
|
return qs
|
|
222
224
|
|
|
223
|
-
def _annotate_cover_pk(self, qs, model):
|
|
224
|
-
"""Annotate the query set for the coverpath for the sort."""
|
|
225
|
-
# Select comics for the children by an outer ref for annotation
|
|
226
|
-
# Order the descendant comics by the sort argumentst
|
|
227
|
-
cover_style = self.params.get("cover_style")
|
|
228
|
-
if model == Comic:
|
|
229
|
-
default_cover_pk = F("pk")
|
|
230
|
-
default_cover_mtime = F("updated_at")
|
|
231
|
-
elif cover_style == "i":
|
|
232
|
-
default_cover_pk = None
|
|
233
|
-
default_cover_mtime = None
|
|
234
|
-
else:
|
|
235
|
-
# At this point it's only been filtered.
|
|
236
|
-
default_cover_pk = F(self.rel_prefix + "pk")
|
|
237
|
-
default_cover_mtime = F(self.rel_prefix + "updated_at")
|
|
238
|
-
|
|
239
|
-
if model in (Volume, Comic):
|
|
240
|
-
cover_custom = Value(False)
|
|
241
|
-
cover_pk = default_cover_pk
|
|
242
|
-
cover_mtime = default_cover_mtime
|
|
243
|
-
else:
|
|
244
|
-
cover_custom = F("custom_cover")
|
|
245
|
-
cover_pk = Case(
|
|
246
|
-
When(custom_cover__isnull=False, then=F("custom_cover__pk")),
|
|
247
|
-
default=default_cover_pk,
|
|
248
|
-
)
|
|
249
|
-
cover_mtime = Case(
|
|
250
|
-
When(custom_cover__isnull=False, then=F("custom_cover__updated_at")),
|
|
251
|
-
default=default_cover_mtime,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
return qs.annotate(
|
|
255
|
-
cover_custom=cover_custom,
|
|
256
|
-
cover_pk=cover_pk,
|
|
257
|
-
cover_mtime=cover_mtime,
|
|
258
|
-
cover_pks=JsonGroupArray("cover_pk", distinct=True),
|
|
259
|
-
)
|
|
260
|
-
|
|
261
225
|
def _annotate_group(self, qs, model):
|
|
262
226
|
"""Annotate Group."""
|
|
263
227
|
value = "c" if model == Comic else self.model_group # type: ignore
|
|
264
228
|
return qs.annotate(group=Value(value, CharField(max_length=1)))
|
|
265
229
|
|
|
230
|
+
def _annotate_child_count(self, qs, model):
|
|
231
|
+
"""Annotate Child Count."""
|
|
232
|
+
if self.TARGET == "opds2":
|
|
233
|
+
return qs
|
|
234
|
+
|
|
235
|
+
if model == Comic:
|
|
236
|
+
child_count_sum = _ONE_INTEGERFIELD
|
|
237
|
+
else:
|
|
238
|
+
child_count_sum = Count(self.rel_prefix + "pk", distinct=True)
|
|
239
|
+
return qs.annotate(child_count=child_count_sum)
|
|
240
|
+
|
|
266
241
|
def _annotate_bookmarks(self, qs, model):
|
|
267
242
|
"""Hoist up bookmark annotations."""
|
|
268
243
|
if model in self.bm_annotation_data: # type: ignore
|
|
269
244
|
bm_rel, bm_filter = self.bm_annotation_data[model] # type: ignore
|
|
270
245
|
else:
|
|
271
246
|
bm_rel = self.get_bm_rel(model)
|
|
272
|
-
bm_filter = self.
|
|
247
|
+
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
273
248
|
|
|
274
249
|
page_rel = f"{bm_rel}__page"
|
|
275
250
|
finished_rel = f"{bm_rel}__finished"
|
|
@@ -318,20 +293,16 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
318
293
|
output_field=BooleanField(),
|
|
319
294
|
)
|
|
320
295
|
|
|
321
|
-
if (
|
|
322
|
-
self.
|
|
323
|
-
or self.is_model_comic
|
|
324
|
-
and self.TARGET == "browser"
|
|
296
|
+
if self.is_opds_1_acquisition or (
|
|
297
|
+
self.is_model_comic and self.TARGET == "browser"
|
|
325
298
|
):
|
|
326
299
|
qs = qs.annotate(page=bookmark_page)
|
|
327
|
-
|
|
300
|
+
elif self.TARGET in frozenset({"metadata", "browser"}):
|
|
328
301
|
qs = qs.alias(page=bookmark_page)
|
|
329
302
|
|
|
330
303
|
if self.TARGET in frozenset({"metadata", "browser"}):
|
|
304
|
+
# Only used for progress and progress is only for metadata and browser anyway
|
|
331
305
|
qs.annotate(finished=finished_aggregate)
|
|
332
|
-
else:
|
|
333
|
-
# Only used for progress
|
|
334
|
-
qs.alias(finished=finished_aggregate)
|
|
335
306
|
return qs
|
|
336
307
|
|
|
337
308
|
def _annotate_progress(self, qs):
|
|
@@ -346,88 +317,26 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
346
317
|
progress = Case(When(page_count__gt=0, then=then), default=0.0)
|
|
347
318
|
return qs.annotate(progress=progress)
|
|
348
319
|
|
|
320
|
+
def _annotate_mtime(self, qs, model):
|
|
321
|
+
"""Annotations mtime."""
|
|
322
|
+
bm_rel = self.get_bm_rel(model)
|
|
323
|
+
bm_ua_rel = bm_rel + "__updated_at"
|
|
324
|
+
when_filter = {bm_ua_rel + "__gt": F("updated_at")}
|
|
325
|
+
mtime = Case(
|
|
326
|
+
When(**when_filter),
|
|
327
|
+
then=F(bm_ua_rel),
|
|
328
|
+
default=F("updated_at"),
|
|
329
|
+
)
|
|
330
|
+
return qs.annotate(mtime=mtime)
|
|
331
|
+
|
|
349
332
|
def annotate_card_aggregates(self, qs, model):
|
|
350
333
|
"""Annotate aggregates that appear the browser card."""
|
|
351
|
-
qs = self._annotate_cover_pk(qs, model)
|
|
352
334
|
if model == Comic:
|
|
353
335
|
# comic adds order_value for cards late
|
|
354
336
|
qs = self._annotate_order_value(qs, model)
|
|
355
337
|
qs = self._annotate_group(qs, model)
|
|
356
338
|
qs = self.annotate_group_names(qs, model)
|
|
339
|
+
qs = self._annotate_child_count(qs, model)
|
|
357
340
|
qs = self._annotate_bookmarks(qs, model)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def annotate_for_metadata(self, qs, _model):
|
|
361
|
-
"""Annotations only for metadata."""
|
|
362
|
-
return qs.annotate(mtime=Max("updated_at"))
|
|
363
|
-
|
|
364
|
-
def _get_cover_pk_query(self, cover_pks, group_ids):
|
|
365
|
-
"""Get Cover Pk queryset for comic queryset."""
|
|
366
|
-
cover_style = self.params.get("cover_style")
|
|
367
|
-
if cover_style == "d":
|
|
368
|
-
# DYNAMIC COVERS
|
|
369
|
-
comic_qs = Comic.objects.filter(pk__in=cover_pks)
|
|
370
|
-
comic_qs = self.annotate_search_score(
|
|
371
|
-
comic_qs, Comic, self.cover_search_score_pairs
|
|
372
|
-
)
|
|
373
|
-
comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
|
|
374
|
-
comic_qs = self.add_order_by(comic_qs, Comic) # type: ignore
|
|
375
|
-
else: # "f"
|
|
376
|
-
# FIRST COVERS
|
|
377
|
-
# The only filter at all is the model relation.
|
|
378
|
-
group_rel = _REL_MAP[self.model] # type: ignore
|
|
379
|
-
comic_qs = Comic.objects.filter(**{f"{group_rel}__in": group_ids})
|
|
380
|
-
|
|
381
|
-
# Order generated irrespective of browser filters and order
|
|
382
|
-
if self.kwargs["group"] == "f":
|
|
383
|
-
order_key = "filename"
|
|
384
|
-
elif self.kwargs["group"] == "a":
|
|
385
|
-
order_key = "story_arc_number"
|
|
386
|
-
else:
|
|
387
|
-
order_key = "sort_name"
|
|
388
|
-
|
|
389
|
-
model_group = self.model_group # type: ignore
|
|
390
|
-
show = MappingProxyType(self.params["show"]) # type: ignore
|
|
391
|
-
model_group = self.kwargs["group"]
|
|
392
|
-
comic_qs, comic_sort_names = self.alias_sort_names(
|
|
393
|
-
comic_qs, Comic, cover_pks, model_group, show
|
|
394
|
-
)
|
|
395
|
-
comic_qs = self.add_order_by(
|
|
396
|
-
comic_qs,
|
|
397
|
-
Comic,
|
|
398
|
-
order_key=order_key,
|
|
399
|
-
do_reverse=False,
|
|
400
|
-
comic_sort_names=comic_sort_names,
|
|
401
|
-
)
|
|
402
|
-
comic_qs = comic_qs.group_by("id")
|
|
403
|
-
return comic_qs.only("pk", "updated_at")
|
|
404
|
-
|
|
405
|
-
def re_cover_multi_groups(self, group_qs):
|
|
406
|
-
"""Python hack to re-cover groups collapsed with group_by."""
|
|
407
|
-
# This would be better in the main query but OuterRef can't access annotations.
|
|
408
|
-
# So cover_pks aggregates all covers from multi groups like ids does.
|
|
409
|
-
if self.is_model_comic or self.params.get("cover_style") == "i":
|
|
410
|
-
return group_qs
|
|
411
|
-
|
|
412
|
-
recovered_group_list = []
|
|
413
|
-
for group in group_qs:
|
|
414
|
-
cover_pks = group.cover_pks
|
|
415
|
-
if len(cover_pks) == 1:
|
|
416
|
-
cover_pk = cover_pks[0]
|
|
417
|
-
cover_mtime = group.updated_at
|
|
418
|
-
else:
|
|
419
|
-
comic_qs = self._get_cover_pk_query(cover_pks, group.ids)
|
|
420
|
-
# print(comic_qs.explain())
|
|
421
|
-
# print(comic_qs.query)
|
|
422
|
-
cover = comic_qs[0]
|
|
423
|
-
cover_pk = cover.pk
|
|
424
|
-
if len(group.ids) == 1:
|
|
425
|
-
cover_mtime = group.updated_at
|
|
426
|
-
else:
|
|
427
|
-
cover_mtime = group.__class__.objects.filter(
|
|
428
|
-
pk__in=group.ids
|
|
429
|
-
).aggregate(updated_at=Max("updated_at"))["updated_at"]
|
|
430
|
-
group.cover_mtime = cover_mtime
|
|
431
|
-
group.cover_pk = cover_pk
|
|
432
|
-
recovered_group_list.append(group)
|
|
433
|
-
return recovered_group_list
|
|
341
|
+
qs = self._annotate_progress(qs)
|
|
342
|
+
return self._annotate_mtime(qs, model)
|