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
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Browser pagination."""
|
|
2
|
+
|
|
3
|
+
from math import ceil
|
|
4
|
+
|
|
5
|
+
from django.core.paginator import EmptyPage, Paginator
|
|
6
|
+
|
|
7
|
+
from codex.logger.logging import get_logger
|
|
8
|
+
from codex.models import (
|
|
9
|
+
Comic,
|
|
10
|
+
Folder,
|
|
11
|
+
)
|
|
12
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
13
|
+
from codex.views.const import (
|
|
14
|
+
MAX_OBJ_PER_PAGE,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
LOG = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BrowserPaginateView(BrowserAnnotationsView):
|
|
21
|
+
"""Browser pagination."""
|
|
22
|
+
|
|
23
|
+
def _check_page_in_bounds(self, total_count):
|
|
24
|
+
"""Redirect page out of bounds."""
|
|
25
|
+
pks = self.kwargs.get("pks")
|
|
26
|
+
page = self.kwargs.get("page", 1)
|
|
27
|
+
num_pages = ceil(total_count / MAX_OBJ_PER_PAGE)
|
|
28
|
+
if (not pks and page == 1) or (page >= 1 and page <= num_pages):
|
|
29
|
+
# Don't redirect if on the root page for the group.
|
|
30
|
+
# Or page within valid range.
|
|
31
|
+
return num_pages
|
|
32
|
+
|
|
33
|
+
# Adjust route mask for redirect
|
|
34
|
+
group = self.kwargs.get("group")
|
|
35
|
+
if num_pages and page > 1:
|
|
36
|
+
# Try page 1 first.
|
|
37
|
+
new_page = num_pages if num_pages and page > num_pages else 1
|
|
38
|
+
pks = pks if pks else "0"
|
|
39
|
+
else:
|
|
40
|
+
# Redirect to root for the group.
|
|
41
|
+
pks = "0"
|
|
42
|
+
new_page = 1
|
|
43
|
+
|
|
44
|
+
reason = f"{page=} does not exist!"
|
|
45
|
+
route_changes = {"group": group, "pks": pks, "page": new_page}
|
|
46
|
+
return self.raise_redirect(reason, route_mask=route_changes)
|
|
47
|
+
|
|
48
|
+
def _paginate_section(self, model, qs, page):
|
|
49
|
+
"""Paginate a group or Comic section."""
|
|
50
|
+
orphans = 0 if self.model_group == "f" or self.params.get("q") else 5
|
|
51
|
+
paginator = Paginator(qs, MAX_OBJ_PER_PAGE, orphans=orphans)
|
|
52
|
+
try:
|
|
53
|
+
qs = paginator.page(page).object_list
|
|
54
|
+
except EmptyPage:
|
|
55
|
+
if model != Folder:
|
|
56
|
+
model_name = self.model.__name__ if self.model else "NO_MODEL"
|
|
57
|
+
LOG.warning(f"No {model_name}s on page {page}")
|
|
58
|
+
qs = model.objects.none()
|
|
59
|
+
|
|
60
|
+
return qs
|
|
61
|
+
|
|
62
|
+
def _paginate_groups(self, group_qs, total_group_count):
|
|
63
|
+
"""Paginate the group object list before books."""
|
|
64
|
+
if total_group_count:
|
|
65
|
+
page = self.kwargs.get("page", 1)
|
|
66
|
+
page_group_qs = self._paginate_section(self.model, group_qs, page)
|
|
67
|
+
else:
|
|
68
|
+
page_group_qs = self.model.objects.none() # type: ignore
|
|
69
|
+
return page_group_qs
|
|
70
|
+
|
|
71
|
+
def _paginate_books(self, book_qs, total_group_count, page_obj_count):
|
|
72
|
+
"""Paginate the book object list based on how many group/folders are showing."""
|
|
73
|
+
if page_obj_count >= MAX_OBJ_PER_PAGE:
|
|
74
|
+
# No books for this page
|
|
75
|
+
page_book_qs = Comic.objects.none()
|
|
76
|
+
else:
|
|
77
|
+
group_remainder = total_group_count % MAX_OBJ_PER_PAGE
|
|
78
|
+
if page_obj_count:
|
|
79
|
+
# There are books after the groups on the same page
|
|
80
|
+
# Manually add books without the paginator
|
|
81
|
+
book_limit = MAX_OBJ_PER_PAGE - group_remainder
|
|
82
|
+
page_book_qs = book_qs[:book_limit]
|
|
83
|
+
else:
|
|
84
|
+
# There are books after the groups on a new page
|
|
85
|
+
if group_remainder:
|
|
86
|
+
book_offset = MAX_OBJ_PER_PAGE - group_remainder
|
|
87
|
+
page_book_qs = book_qs[book_offset:]
|
|
88
|
+
else:
|
|
89
|
+
page_book_qs = book_qs
|
|
90
|
+
|
|
91
|
+
num_group_and_mixed_pages = ceil(total_group_count / MAX_OBJ_PER_PAGE)
|
|
92
|
+
book_only_page = self.kwargs.get("page", 1) - num_group_and_mixed_pages
|
|
93
|
+
page_book_qs = self._paginate_section(
|
|
94
|
+
Comic, page_book_qs, book_only_page
|
|
95
|
+
)
|
|
96
|
+
return page_book_qs
|
|
97
|
+
|
|
98
|
+
def paginate(self, group_qs, book_qs, group_count, book_count):
|
|
99
|
+
"""Paginate the queryset into a group and book object lists."""
|
|
100
|
+
num_pages = self._check_page_in_bounds(group_count + book_count)
|
|
101
|
+
page_group_qs = self._paginate_groups(group_qs, group_count)
|
|
102
|
+
page_group_count = page_group_qs.count()
|
|
103
|
+
page_book_qs = self._paginate_books(book_qs, group_count, page_group_count)
|
|
104
|
+
page_book_count = page_book_qs.count()
|
|
105
|
+
|
|
106
|
+
return page_group_qs, page_book_qs, num_pages, page_group_count, page_book_count
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Browser session view."""
|
|
2
2
|
|
|
3
3
|
from codex.serializers.browser.settings import BrowserSettingsSerializer
|
|
4
|
-
from codex.views.
|
|
4
|
+
from codex.views.settings import SettingsView
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class BrowserSettingsView(SettingsView):
|
|
8
8
|
"""Get Browser Settings."""
|
|
9
9
|
|
|
10
10
|
# Put Browser Settings is normally done through BrowserView.get()
|
|
11
|
-
|
|
12
11
|
serializer_class = BrowserSettingsSerializer # type: ignore
|
|
12
|
+
|
|
13
|
+
SESSION_KEY = SettingsView.BROWSER_SESSION_KEY
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Browser title."""
|
|
2
|
+
|
|
3
|
+
from codex.models import BrowserGroupModel, Comic, Volume
|
|
4
|
+
from codex.views.browser.breadcrumbs import BrowserBreadcrumbsView
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BrowserTitleView(BrowserBreadcrumbsView):
|
|
8
|
+
"""Browser title methods."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, *args, **kwargs):
|
|
11
|
+
"""Set params for the type checker."""
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
self.group_instance: BrowserGroupModel | None = None
|
|
14
|
+
|
|
15
|
+
def _get_root_group_name(self):
|
|
16
|
+
if not self.model:
|
|
17
|
+
reason = "No model set in browser"
|
|
18
|
+
raise ValueError(reason)
|
|
19
|
+
plural = self.model._meta.verbose_name_plural
|
|
20
|
+
if not plural:
|
|
21
|
+
reason = f"No plural name for {self.model}"
|
|
22
|
+
raise ValueError(reason)
|
|
23
|
+
return plural.capitalize(), 0
|
|
24
|
+
|
|
25
|
+
def _get_group_name(self):
|
|
26
|
+
group_count = 0
|
|
27
|
+
if gi := self.group_instance:
|
|
28
|
+
if isinstance(gi, Volume):
|
|
29
|
+
group_count = gi.series.volume_count
|
|
30
|
+
elif isinstance(gi, Comic):
|
|
31
|
+
group_count = gi.volume.issue_count
|
|
32
|
+
group_name = gi.name
|
|
33
|
+
else:
|
|
34
|
+
group_name = ""
|
|
35
|
+
return group_name, group_count
|
|
36
|
+
|
|
37
|
+
def get_browser_page_title(self):
|
|
38
|
+
"""Get the proper title for this browse level."""
|
|
39
|
+
pks = self.kwargs.get("pks")
|
|
40
|
+
if not pks:
|
|
41
|
+
group_name, group_count = self._get_root_group_name()
|
|
42
|
+
else:
|
|
43
|
+
group_name, group_count = self._get_group_name()
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"group_name": group_name,
|
|
47
|
+
"group_count": group_count,
|
|
48
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Browser Settings and URL Validation."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
|
|
6
|
+
from codex.exceptions import SeeOtherRedirectError
|
|
7
|
+
from codex.logger.logging import get_logger
|
|
8
|
+
from codex.serializers.choices import DEFAULTS
|
|
9
|
+
from codex.views.browser.base import BrowserBaseView
|
|
10
|
+
from codex.views.const import (
|
|
11
|
+
COMIC_GROUP,
|
|
12
|
+
FOLDER_GROUP,
|
|
13
|
+
ROOT_GROUP,
|
|
14
|
+
STORY_ARC_GROUP,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
LOG = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BrowserValidateView(BrowserBaseView):
|
|
21
|
+
"""Browser Settings and URL Validation."""
|
|
22
|
+
|
|
23
|
+
DEFAULT_ROUTE = MappingProxyType(
|
|
24
|
+
{
|
|
25
|
+
"name": "browser",
|
|
26
|
+
"params": deepcopy(DEFAULTS["breadcrumbs"][0]),
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def raise_redirect(self, reason, route_mask=None, settings_mask=None):
|
|
31
|
+
"""Redirect the client to a valid group url."""
|
|
32
|
+
route = deepcopy(dict(self.DEFAULT_ROUTE))
|
|
33
|
+
if route_mask:
|
|
34
|
+
route["params"].update(route_mask) # type: ignore
|
|
35
|
+
settings = deepcopy(dict(self.params))
|
|
36
|
+
if settings_mask:
|
|
37
|
+
settings.update(settings_mask)
|
|
38
|
+
detail = {"route": route, "settings": settings, "reason": reason}
|
|
39
|
+
raise SeeOtherRedirectError(detail=detail)
|
|
40
|
+
|
|
41
|
+
def _get_valid_browse_top_groups(self):
|
|
42
|
+
"""Get valid top groups for the current settings.
|
|
43
|
+
|
|
44
|
+
Valid top groups are determined by the Browser Settings.
|
|
45
|
+
"""
|
|
46
|
+
valid_top_groups = []
|
|
47
|
+
|
|
48
|
+
show = self.params["show"]
|
|
49
|
+
for nav_group, allowed in show.items():
|
|
50
|
+
if allowed:
|
|
51
|
+
valid_top_groups.append(nav_group)
|
|
52
|
+
# Issues is always a valid top group
|
|
53
|
+
valid_top_groups += [COMIC_GROUP]
|
|
54
|
+
|
|
55
|
+
return valid_top_groups
|
|
56
|
+
|
|
57
|
+
def _validate_top_group(self, valid_top_groups):
|
|
58
|
+
nav_group = self.kwargs.get("group")
|
|
59
|
+
top_group = self.params.get("top_group")
|
|
60
|
+
if top_group not in valid_top_groups:
|
|
61
|
+
reason = f"top_group {top_group} not in valid nav groups, changed to "
|
|
62
|
+
if nav_group in valid_top_groups:
|
|
63
|
+
valid_top_group = nav_group
|
|
64
|
+
reason += "nav group: "
|
|
65
|
+
else:
|
|
66
|
+
valid_top_group = valid_top_groups[0]
|
|
67
|
+
reason += "first valid top group "
|
|
68
|
+
reason += valid_top_group
|
|
69
|
+
pks = self.kwargs.get("pks", ())
|
|
70
|
+
page = self.kwargs["page"]
|
|
71
|
+
route = {"group": nav_group, "pks": pks, "page": page}
|
|
72
|
+
settings_mask = {"top_group": valid_top_group}
|
|
73
|
+
self.raise_redirect(reason, route, settings_mask)
|
|
74
|
+
|
|
75
|
+
def set_valid_browse_nav_groups(self, valid_top_groups):
|
|
76
|
+
"""Get valid nav groups for the current settings.
|
|
77
|
+
|
|
78
|
+
Valid nav groups are the top group and below that are also
|
|
79
|
+
enabled in browser settings.
|
|
80
|
+
|
|
81
|
+
May always navigate to root 'r' nav group.
|
|
82
|
+
"""
|
|
83
|
+
top_group = self.params["top_group"]
|
|
84
|
+
nav_group = self.kwargs["group"]
|
|
85
|
+
valid_nav_groups = [ROOT_GROUP]
|
|
86
|
+
|
|
87
|
+
for possible_index, possible_nav_group in enumerate(valid_top_groups):
|
|
88
|
+
if top_group == possible_nav_group:
|
|
89
|
+
# all the nav groups past this point,
|
|
90
|
+
# 'c' is obscured by the web reader url, but valid for opds
|
|
91
|
+
tail_top_groups = valid_top_groups[possible_index:]
|
|
92
|
+
valid_nav_groups += tail_top_groups
|
|
93
|
+
|
|
94
|
+
if nav_group not in valid_nav_groups:
|
|
95
|
+
reason = (
|
|
96
|
+
f"Nav group {nav_group} unavailable, "
|
|
97
|
+
f"redirect to {ROOT_GROUP}"
|
|
98
|
+
)
|
|
99
|
+
self.raise_redirect(reason)
|
|
100
|
+
break
|
|
101
|
+
self.valid_nav_groups = tuple(valid_nav_groups)
|
|
102
|
+
|
|
103
|
+
def _validate_folder_settings(self):
|
|
104
|
+
"""Check that all the view variables for folder mode are set right."""
|
|
105
|
+
# Check folder view admin flag
|
|
106
|
+
if not self.admin_flags["folder_view"]:
|
|
107
|
+
reason = "folder view disabled"
|
|
108
|
+
valid_top_groups = self._get_valid_browse_top_groups()
|
|
109
|
+
settings_mask = {"top_group": valid_top_groups[0]}
|
|
110
|
+
self.raise_redirect(reason, settings_mask=settings_mask)
|
|
111
|
+
|
|
112
|
+
valid_top_groups = (FOLDER_GROUP,)
|
|
113
|
+
self._validate_top_group(valid_top_groups)
|
|
114
|
+
self.valid_nav_groups = valid_top_groups
|
|
115
|
+
|
|
116
|
+
def _validate_browser_group_settings(self):
|
|
117
|
+
"""Check that all the view variables for browser mode are set right."""
|
|
118
|
+
# Validate Browser top_group
|
|
119
|
+
# Change top_group if its not in the valid top groups
|
|
120
|
+
valid_top_groups = self._get_valid_browse_top_groups()
|
|
121
|
+
self._validate_top_group(valid_top_groups)
|
|
122
|
+
|
|
123
|
+
# Validate pks
|
|
124
|
+
nav_group = self.kwargs["group"]
|
|
125
|
+
pks = self.kwargs["pks"]
|
|
126
|
+
if nav_group == ROOT_GROUP and (pks and 0 not in pks):
|
|
127
|
+
# r never has pks
|
|
128
|
+
reason = f"Redirect r with {pks=} to pks 0"
|
|
129
|
+
self.raise_redirect(reason)
|
|
130
|
+
|
|
131
|
+
# Validate Browser nav_group
|
|
132
|
+
# Redirect if nav group is wrong
|
|
133
|
+
self.set_valid_browse_nav_groups(valid_top_groups)
|
|
134
|
+
|
|
135
|
+
def _validate_story_arc_settings(self):
|
|
136
|
+
"""Validate story arc settings."""
|
|
137
|
+
valid_top_groups = (STORY_ARC_GROUP,)
|
|
138
|
+
self._validate_top_group(valid_top_groups)
|
|
139
|
+
self.valid_nav_groups = valid_top_groups
|
|
140
|
+
|
|
141
|
+
def validate_settings(self):
|
|
142
|
+
"""Validate group and top group settings."""
|
|
143
|
+
group = self.kwargs["group"]
|
|
144
|
+
if group == FOLDER_GROUP:
|
|
145
|
+
self._validate_folder_settings()
|
|
146
|
+
elif group == STORY_ARC_GROUP:
|
|
147
|
+
self._validate_story_arc_settings()
|
|
148
|
+
else:
|
|
149
|
+
self._validate_browser_group_settings()
|
|
150
|
+
|
|
151
|
+
# Validate order
|
|
152
|
+
if self.order_key == "filename" and not self.admin_flags["folder_view"]:
|
|
153
|
+
pks = self.kwargs["pks"]
|
|
154
|
+
page = self.kwargs["page"]
|
|
155
|
+
route_changes = {"group": group, "pks": pks, "page": page}
|
|
156
|
+
reason = "order by filename not allowed by admin flag."
|
|
157
|
+
settings_mask = {"order_by": "sort_name"}
|
|
158
|
+
self.raise_redirect(reason, route_changes, settings_mask)
|
codex/views/const.py
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
|
|
5
|
+
from django.contrib.auth.models import Group, User
|
|
6
|
+
from django.contrib.sessions.models import Session
|
|
7
|
+
|
|
8
|
+
from codex.models import (
|
|
9
|
+
AgeRating,
|
|
10
|
+
BrowserGroupModel,
|
|
11
|
+
Character,
|
|
12
|
+
Comic,
|
|
13
|
+
Contributor,
|
|
14
|
+
ContributorPerson,
|
|
15
|
+
ContributorRole,
|
|
16
|
+
Country,
|
|
17
|
+
Folder,
|
|
18
|
+
Genre,
|
|
19
|
+
Identifier,
|
|
20
|
+
IdentifierType,
|
|
21
|
+
Imprint,
|
|
22
|
+
Language,
|
|
23
|
+
Library,
|
|
24
|
+
Location,
|
|
25
|
+
OriginalFormat,
|
|
26
|
+
Publisher,
|
|
27
|
+
ScanInfo,
|
|
28
|
+
Series,
|
|
29
|
+
SeriesGroup,
|
|
30
|
+
StoryArc,
|
|
31
|
+
StoryArcNumber,
|
|
32
|
+
Tag,
|
|
33
|
+
Tagger,
|
|
34
|
+
Team,
|
|
35
|
+
Volume,
|
|
36
|
+
)
|
|
5
37
|
from codex.settings.settings import CODEX_PATH
|
|
6
38
|
|
|
7
39
|
FALSY = {None, "", "false", "0", False}
|
|
@@ -18,3 +50,80 @@ MISSING_COVER_NAME_MAP = MappingProxyType(
|
|
|
18
50
|
)
|
|
19
51
|
MISSING_COVER_FN = "missing-cover-165.webp"
|
|
20
52
|
MISSING_COVER_PATH = STATIC_IMG_PATH / MISSING_COVER_FN
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
ROOT_GROUP = "r"
|
|
56
|
+
FOLDER_GROUP = "f"
|
|
57
|
+
STORY_ARC_GROUP = "a"
|
|
58
|
+
COMIC_GROUP = "c"
|
|
59
|
+
GROUP_RELATION = MappingProxyType(
|
|
60
|
+
{
|
|
61
|
+
**GROUP_NAME_MAP,
|
|
62
|
+
COMIC_GROUP: "pk",
|
|
63
|
+
FOLDER_GROUP: "parent_folder",
|
|
64
|
+
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
GROUP_ORDER = "rpisv"
|
|
68
|
+
MODEL_REL_MAP = MappingProxyType(
|
|
69
|
+
{
|
|
70
|
+
Publisher: "publisher",
|
|
71
|
+
Imprint: "imprint",
|
|
72
|
+
Series: "series",
|
|
73
|
+
Volume: "volume",
|
|
74
|
+
Folder: "parent_folder",
|
|
75
|
+
StoryArc: "story_arc_numbers__story_arc",
|
|
76
|
+
Comic: "pk",
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
|
|
80
|
+
MappingProxyType(
|
|
81
|
+
{
|
|
82
|
+
ROOT_GROUP: None,
|
|
83
|
+
"p": Publisher,
|
|
84
|
+
"i": Imprint,
|
|
85
|
+
"s": Series,
|
|
86
|
+
"v": Volume,
|
|
87
|
+
COMIC_GROUP: Comic,
|
|
88
|
+
FOLDER_GROUP: Folder,
|
|
89
|
+
STORY_ARC_GROUP: StoryArc,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
GROUP_MODELS = (
|
|
94
|
+
Publisher,
|
|
95
|
+
Imprint,
|
|
96
|
+
Series,
|
|
97
|
+
Volume,
|
|
98
|
+
Comic,
|
|
99
|
+
Folder,
|
|
100
|
+
)
|
|
101
|
+
METADATA_MODELS = (
|
|
102
|
+
AgeRating,
|
|
103
|
+
Character,
|
|
104
|
+
Country,
|
|
105
|
+
Genre,
|
|
106
|
+
Identifier,
|
|
107
|
+
IdentifierType,
|
|
108
|
+
Language,
|
|
109
|
+
Location,
|
|
110
|
+
OriginalFormat,
|
|
111
|
+
SeriesGroup,
|
|
112
|
+
ScanInfo,
|
|
113
|
+
StoryArc,
|
|
114
|
+
StoryArcNumber,
|
|
115
|
+
Team,
|
|
116
|
+
Tag,
|
|
117
|
+
Tagger,
|
|
118
|
+
Contributor,
|
|
119
|
+
ContributorPerson,
|
|
120
|
+
ContributorRole,
|
|
121
|
+
)
|
|
122
|
+
CONFIG_MODELS = (
|
|
123
|
+
Library,
|
|
124
|
+
User,
|
|
125
|
+
Group,
|
|
126
|
+
Session,
|
|
127
|
+
)
|
|
128
|
+
MAX_OBJ_PER_PAGE = 100
|
|
129
|
+
GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
|
codex/views/download.py
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
"""Download a comic archive."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import ClassVar
|
|
5
4
|
|
|
6
5
|
from django.http import FileResponse, Http404
|
|
7
6
|
from drf_spectacular.types import OpenApiTypes
|
|
8
7
|
from drf_spectacular.utils import extend_schema
|
|
9
|
-
from rest_framework.views import APIView
|
|
10
8
|
|
|
11
9
|
from codex.models import Comic
|
|
12
|
-
from codex.views.auth import
|
|
10
|
+
from codex.views.auth import AuthFilterAPIView
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
class DownloadView(
|
|
13
|
+
class DownloadView(AuthFilterAPIView):
|
|
16
14
|
"""Return the comic archive file as an attachment."""
|
|
17
15
|
|
|
18
|
-
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
|
|
19
16
|
content_type = "application/vnd.comicbook+zip"
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
_AS_ATTACHMENT = True
|
|
22
19
|
|
|
23
20
|
@extend_schema(responses={(200, content_type): OpenApiTypes.BINARY})
|
|
24
21
|
def get(self, *_args, **kwargs):
|
|
@@ -48,7 +45,7 @@ class DownloadView(APIView, GroupACLMixin):
|
|
|
48
45
|
filename = comic.get_filename()
|
|
49
46
|
return FileResponse(
|
|
50
47
|
comic_file,
|
|
51
|
-
as_attachment=self.
|
|
48
|
+
as_attachment=self._AS_ATTACHMENT,
|
|
52
49
|
content_type=content_type,
|
|
53
50
|
filename=filename,
|
|
54
51
|
)
|
|
@@ -57,4 +54,4 @@ class DownloadView(APIView, GroupACLMixin):
|
|
|
57
54
|
class FileView(DownloadView):
|
|
58
55
|
"""View a comic in the browser."""
|
|
59
56
|
|
|
60
|
-
|
|
57
|
+
_AS_ATTACHMENT = False
|
codex/views/frontend.py
CHANGED
|
@@ -6,12 +6,14 @@ from rest_framework.renderers import TemplateHTMLRenderer
|
|
|
6
6
|
from rest_framework.response import Response
|
|
7
7
|
|
|
8
8
|
from codex.serializers.route import RouteSerializer
|
|
9
|
-
from codex.views.session import
|
|
9
|
+
from codex.views.session import SessionView
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class IndexView(
|
|
12
|
+
class IndexView(SessionView):
|
|
13
13
|
"""The main app."""
|
|
14
14
|
|
|
15
|
+
SESSION_KEY = SessionView.BROWSER_SESSION_KEY
|
|
16
|
+
|
|
15
17
|
renderer_classes: ClassVar[list] = [TemplateHTMLRenderer] # type: ignore
|
|
16
18
|
template_name = "index.html"
|
|
17
19
|
|
codex/views/mtime.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Get the mtimes for the submitted groups."""
|
|
2
|
+
|
|
3
|
+
from django.db.models import Max
|
|
4
|
+
from rest_framework.response import Response
|
|
5
|
+
|
|
6
|
+
from codex.models.groups import Publisher
|
|
7
|
+
from codex.serializers.mtime import GroupsMtimeSerializer, MtimeSerializer
|
|
8
|
+
from codex.util import max_none
|
|
9
|
+
from codex.views.auth import AuthGenericAPIView
|
|
10
|
+
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
11
|
+
from codex.views.const import GROUP_MODEL_MAP
|
|
12
|
+
from codex.views.util import reparse_json_query_params
|
|
13
|
+
|
|
14
|
+
_REPARSE_JSON_FIELDS = frozenset({"groups"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MtimeView(AuthGenericAPIView, BookmarkFilterMixin):
|
|
18
|
+
"""Get the mtimes for the submitted groups."""
|
|
19
|
+
|
|
20
|
+
serializer_class = GroupsMtimeSerializer
|
|
21
|
+
response_serializer_class = MtimeSerializer
|
|
22
|
+
|
|
23
|
+
def _parse_params(self):
|
|
24
|
+
params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
|
|
25
|
+
self.groups = params.get("groups", "")
|
|
26
|
+
self.use_bookmark_filter = self.request.GET.get("use_bookmark_filter", False)
|
|
27
|
+
|
|
28
|
+
def _get_group_mtime(self, group, pks):
|
|
29
|
+
"""Get one group's mtimes."""
|
|
30
|
+
model = GROUP_MODEL_MAP[group]
|
|
31
|
+
if not model:
|
|
32
|
+
model = Publisher
|
|
33
|
+
|
|
34
|
+
qs = model.objects.all()
|
|
35
|
+
|
|
36
|
+
if pks and 0 not in pks:
|
|
37
|
+
qs = qs.filter(pk__in=pks)
|
|
38
|
+
updated_at_max = qs.aggregate(max=Max("updated_at"))["max"]
|
|
39
|
+
|
|
40
|
+
if self.use_bookmark_filter:
|
|
41
|
+
bm_rel = self.get_bm_rel(model)
|
|
42
|
+
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
43
|
+
qs = qs.filter(bm_filter)
|
|
44
|
+
bookmark_updated_at_max = qs.aggregate(max=(Max(f"{bm_rel}__updated_at")))[
|
|
45
|
+
"max"
|
|
46
|
+
]
|
|
47
|
+
updated_at_max = max_none(updated_at_max, bookmark_updated_at_max)
|
|
48
|
+
|
|
49
|
+
return updated_at_max
|
|
50
|
+
|
|
51
|
+
def get_max_groups_mtime(self):
|
|
52
|
+
"""Get max mtime for all groups."""
|
|
53
|
+
max_mtime = None
|
|
54
|
+
|
|
55
|
+
for item in self.groups:
|
|
56
|
+
group = item["group"]
|
|
57
|
+
pks = tuple(int(pk) for pk in item["pks"].split(","))
|
|
58
|
+
mtime = self._get_group_mtime(group, pks)
|
|
59
|
+
max_mtime = max_none(max_mtime, mtime)
|
|
60
|
+
return max_mtime
|
|
61
|
+
|
|
62
|
+
def get(self, *args, **kwargs):
|
|
63
|
+
"""Get the mtimes for the submitted groups."""
|
|
64
|
+
# Parse Request
|
|
65
|
+
self._parse_params()
|
|
66
|
+
|
|
67
|
+
max_mtime = self.get_max_groups_mtime()
|
|
68
|
+
|
|
69
|
+
# Serialize Response
|
|
70
|
+
result = {"max_mtime": max_mtime}
|
|
71
|
+
serializer = self.response_serializer_class(result)
|
|
72
|
+
return Response(serializer.data)
|
codex/views/opds/auth.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""OPDS Authentican mixin."""
|
|
2
|
+
|
|
3
|
+
from rest_framework.authentication import BasicAuthentication, SessionAuthentication
|
|
4
|
+
|
|
5
|
+
from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
|
|
6
|
+
from codex.views.template import CodexXMLTemplateView
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OPDSAuthMixin:
|
|
10
|
+
"""Add Basic Auth."""
|
|
11
|
+
|
|
12
|
+
authentication_classes = (SessionAuthentication, BasicAuthentication)
|
|
13
|
+
permission_classes = (IsAuthenticatedOrEnabledNonUsers,)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OPDSTemplateView(OPDSAuthMixin, CodexXMLTemplateView):
|
|
17
|
+
"""XML Template view with OPDSAuth."""
|