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,177 @@
|
|
|
1
|
+
"""Comic cover thumbnail view."""
|
|
2
|
+
|
|
3
|
+
from django.http import HttpResponse
|
|
4
|
+
from drf_spectacular.types import OpenApiTypes
|
|
5
|
+
from drf_spectacular.utils import extend_schema
|
|
6
|
+
from rest_framework.renderers import BaseRenderer
|
|
7
|
+
|
|
8
|
+
from codex.librarian.covers.create import CoverCreateMixin
|
|
9
|
+
from codex.librarian.covers.path import CoverPathMixin
|
|
10
|
+
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
11
|
+
from codex.logger.logging import get_logger
|
|
12
|
+
from codex.models import Comic, Folder, Volume
|
|
13
|
+
from codex.models.paths import CustomCover
|
|
14
|
+
from codex.serializers.choices import DEFAULTS
|
|
15
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
16
|
+
from codex.views.const import (
|
|
17
|
+
GROUP_RELATION,
|
|
18
|
+
MISSING_COVER_FN,
|
|
19
|
+
MISSING_COVER_NAME_MAP,
|
|
20
|
+
STATIC_IMG_PATH,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
LOG = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WEBPRenderer(BaseRenderer):
|
|
27
|
+
"""Render WEBP images."""
|
|
28
|
+
|
|
29
|
+
media_type = "image/webp"
|
|
30
|
+
format = "webp"
|
|
31
|
+
charset = None
|
|
32
|
+
render_style = "binary"
|
|
33
|
+
|
|
34
|
+
def render(self, data, *_args, **_kwargs):
|
|
35
|
+
"""Return raw data."""
|
|
36
|
+
return data
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CoverView(BrowserAnnotationsView):
|
|
40
|
+
"""ComicCover View."""
|
|
41
|
+
|
|
42
|
+
renderer_classes = (WEBPRenderer,)
|
|
43
|
+
content_type = "image/webp"
|
|
44
|
+
TARGET = "cover"
|
|
45
|
+
|
|
46
|
+
def get_model_group(self):
|
|
47
|
+
"""Return the url group."""
|
|
48
|
+
return self.kwargs["group"]
|
|
49
|
+
|
|
50
|
+
def init_request(self):
|
|
51
|
+
"""Initialize request."""
|
|
52
|
+
self.parse_params()
|
|
53
|
+
self.set_model()
|
|
54
|
+
|
|
55
|
+
def _get_comic_cover(self):
|
|
56
|
+
pks = self.kwargs["pks"]
|
|
57
|
+
return pks[0], False
|
|
58
|
+
|
|
59
|
+
def _use_dynamic_cover(self):
|
|
60
|
+
if not self.params.get("dynamic_covers"):
|
|
61
|
+
return False
|
|
62
|
+
filters = self.params.get("filters", {})
|
|
63
|
+
for key, value in filters.items():
|
|
64
|
+
if key == "bookmark" and value != DEFAULTS["bookmarkFilter"]:
|
|
65
|
+
return True
|
|
66
|
+
if value:
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
order_by = self.params.get("order_by")
|
|
70
|
+
default_order_by = "filename" if self.model == Folder else "sort_name"
|
|
71
|
+
if order_by != default_order_by:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
if self.params.get("order_reverse"):
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def _get_dynamic_cover(self):
|
|
80
|
+
"""Get dynamic cover."""
|
|
81
|
+
self.set_order_key()
|
|
82
|
+
pks = self.kwargs["pks"]
|
|
83
|
+
|
|
84
|
+
if self.model != Volume and self.params.get("custom_covers"):
|
|
85
|
+
group = self.kwargs["group"]
|
|
86
|
+
group_rel = "folder" if self.model == Folder else GROUP_RELATION[group] # type: ignore
|
|
87
|
+
comic_filter = {f"{group_rel}__in": pks}
|
|
88
|
+
custom_cover = CustomCover.objects.filter(**comic_filter).only("pk").first()
|
|
89
|
+
if custom_cover:
|
|
90
|
+
return custom_cover.pk, True
|
|
91
|
+
|
|
92
|
+
comic_qs = self.get_filtered_queryset(Comic)
|
|
93
|
+
comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
|
|
94
|
+
comic_qs = self.add_order_by(comic_qs, Comic)
|
|
95
|
+
comic_qs = comic_qs.only("pk")
|
|
96
|
+
group_by = self.get_group_by(Comic)
|
|
97
|
+
comic_qs.group_by(group_by)
|
|
98
|
+
comic = comic_qs.first()
|
|
99
|
+
cover_pk = comic.pk if comic else 0
|
|
100
|
+
return cover_pk, False
|
|
101
|
+
|
|
102
|
+
def _get_first_cover(self):
|
|
103
|
+
"""Get first cover."""
|
|
104
|
+
pks = self.kwargs["pks"]
|
|
105
|
+
order_by = (
|
|
106
|
+
"path"
|
|
107
|
+
if self.model == Folder
|
|
108
|
+
else "name"
|
|
109
|
+
if self.model == Volume
|
|
110
|
+
else "sort_name"
|
|
111
|
+
)
|
|
112
|
+
group_qs = self.model.objects.filter(pk__in=pks) # type: ignore
|
|
113
|
+
group_qs = group_qs.order_by(order_by)
|
|
114
|
+
select_related = ["first_comic"]
|
|
115
|
+
custom_covers = self.params.get("custom_covers")
|
|
116
|
+
if custom_covers and self.model != Volume:
|
|
117
|
+
select_related.append("custom_cover")
|
|
118
|
+
group_qs = group_qs.select_related(*select_related)
|
|
119
|
+
group_qs = group_qs.group_by("id") # type: ignore
|
|
120
|
+
group_obj = group_qs.first()
|
|
121
|
+
if custom_covers and self.model != Volume and group_obj.custom_cover:
|
|
122
|
+
return group_obj.custom_cover.pk, True
|
|
123
|
+
cover_pk = (
|
|
124
|
+
group_obj.first_comic.pk if group_obj and group_obj.first_comic else 0
|
|
125
|
+
)
|
|
126
|
+
return cover_pk, False
|
|
127
|
+
|
|
128
|
+
def _get_cover_pk(self) -> tuple[int, bool]:
|
|
129
|
+
"""Get Cover Pk queryset for comic queryset."""
|
|
130
|
+
if self.model == Comic:
|
|
131
|
+
cover_pk, custom = self._get_comic_cover()
|
|
132
|
+
elif self._use_dynamic_cover():
|
|
133
|
+
cover_pk, custom = self._get_dynamic_cover()
|
|
134
|
+
else:
|
|
135
|
+
cover_pk, custom = self._get_first_cover()
|
|
136
|
+
return cover_pk, custom
|
|
137
|
+
|
|
138
|
+
def _get_missing_cover_path(self):
|
|
139
|
+
"""Get the missing cover, which is a default svg if fetched for a group."""
|
|
140
|
+
group: str = self.kwargs["group"]
|
|
141
|
+
cover_name = MISSING_COVER_NAME_MAP.get(group)
|
|
142
|
+
if cover_name:
|
|
143
|
+
cover_fn = cover_name + ".svg"
|
|
144
|
+
content_type = "image/svg+xml"
|
|
145
|
+
else:
|
|
146
|
+
cover_fn = MISSING_COVER_FN
|
|
147
|
+
content_type = "image/webp"
|
|
148
|
+
cover_path = STATIC_IMG_PATH / cover_fn
|
|
149
|
+
return cover_path, content_type
|
|
150
|
+
|
|
151
|
+
def _get_cover_data(self, pk, custom):
|
|
152
|
+
thumb_image_data = None
|
|
153
|
+
content_type = "image/webp"
|
|
154
|
+
|
|
155
|
+
cover_path = CoverPathMixin.get_cover_path(pk, custom)
|
|
156
|
+
if not cover_path.exists():
|
|
157
|
+
thumb_image_data = CoverCreateMixin.create_cover_from_path(
|
|
158
|
+
pk, cover_path, LOG, LIBRARIAN_QUEUE, custom
|
|
159
|
+
)
|
|
160
|
+
if not thumb_image_data:
|
|
161
|
+
cover_path, content_type = self._get_missing_cover_path()
|
|
162
|
+
elif cover_path.stat().st_size == 0:
|
|
163
|
+
cover_path, content_type = self._get_missing_cover_path()
|
|
164
|
+
|
|
165
|
+
if not thumb_image_data:
|
|
166
|
+
# if not thumb_image_data:
|
|
167
|
+
with cover_path.open("rb") as f:
|
|
168
|
+
thumb_image_data = f.read()
|
|
169
|
+
return thumb_image_data, content_type
|
|
170
|
+
|
|
171
|
+
@extend_schema(responses={(200, content_type): OpenApiTypes.BINARY})
|
|
172
|
+
def get(self, *args, **kwargs): # type: ignore
|
|
173
|
+
"""Get comic cover."""
|
|
174
|
+
self.init_request()
|
|
175
|
+
pk, custom = self._get_cover_pk()
|
|
176
|
+
thumb_image_data, content_type = self._get_cover_data(pk, custom)
|
|
177
|
+
return HttpResponse(thumb_image_data, content_type=content_type)
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
"""Annotations used by a filter."""
|
|
2
2
|
|
|
3
3
|
from django.db.models.aggregates import Count
|
|
4
|
-
from django.db.models.expressions import Value
|
|
5
|
-
from django.db.models.fields import PositiveSmallIntegerField
|
|
6
4
|
|
|
7
5
|
from codex.models.comic import Comic
|
|
8
|
-
from codex.views.browser.
|
|
6
|
+
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
7
|
+
from codex.views.browser.validate import BrowserValidateView
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class BrowserAnnotationsFilterView(
|
|
10
|
+
class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
12
11
|
"""Annotations that also filter."""
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
def get_query_filters(self, model):
|
|
14
|
+
"""Return all the filters except the group filter."""
|
|
15
|
+
object_filter = self.get_group_acl_filter(model)
|
|
16
|
+
object_filter &= self.get_bookmark_filter(model)
|
|
17
|
+
object_filter &= self.get_comic_field_filter(model)
|
|
18
|
+
object_filter &= self.get_group_filter(model)
|
|
19
|
+
return object_filter
|
|
15
20
|
|
|
16
|
-
def
|
|
17
|
-
"""Annotate Child Count."""
|
|
21
|
+
def _filter_by_child_count(self, qs, model):
|
|
18
22
|
if model == Comic:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"""Filter queryset by annotations."""
|
|
33
|
-
qs = self._annotate_child_count(qs, model)
|
|
34
|
-
return self.apply_search_filter(qs, model, binary=binary)
|
|
23
|
+
return qs
|
|
24
|
+
|
|
25
|
+
rel = self.rel_prefix + "pk"
|
|
26
|
+
qs = qs.alias(pre_search_child_count=Count(rel, distinct=True))
|
|
27
|
+
return qs.filter(pre_search_child_count__gt=0)
|
|
28
|
+
|
|
29
|
+
def get_filtered_queryset(self, model):
|
|
30
|
+
"""Get a filtered queryset for the model."""
|
|
31
|
+
object_filter = self.get_query_filters(model)
|
|
32
|
+
|
|
33
|
+
qs = model.objects.filter(object_filter)
|
|
34
|
+
qs = self._filter_by_child_count(qs, model)
|
|
35
|
+
return self.apply_search_filter(qs, model)
|
|
@@ -2,20 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db.models import Q
|
|
4
4
|
|
|
5
|
-
from codex.serializers.choices import CHOICES
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
class BookmarkFilterMixin:
|
|
9
7
|
"""BookmarkFilter view methods."""
|
|
10
8
|
|
|
11
|
-
_BOOKMARK_FILTERS = frozenset(set(CHOICES["bookmarkFilter"].keys()) - {"ALL"})
|
|
12
|
-
|
|
13
9
|
def get_bm_rel(self, model):
|
|
14
10
|
"""Create bookmark relation."""
|
|
15
11
|
rel_prefix = self.get_rel_prefix(model) # type: ignore
|
|
16
12
|
return rel_prefix + "bookmark"
|
|
17
13
|
|
|
18
|
-
def
|
|
14
|
+
def get_my_bookmark_filter(self, bm_rel):
|
|
19
15
|
"""Get a filter for my session or user defined bookmarks."""
|
|
20
16
|
if self.request.user.is_authenticated: # type: ignore
|
|
21
17
|
my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
|
|
@@ -28,11 +24,15 @@ class BookmarkFilterMixin:
|
|
|
28
24
|
|
|
29
25
|
def get_bookmark_filter(self, model):
|
|
30
26
|
"""Build bookmark query."""
|
|
31
|
-
choice = self.params["filters"].get("bookmark", "
|
|
32
|
-
if choice
|
|
27
|
+
choice = self.params["filters"].get("bookmark", "") # type: ignore
|
|
28
|
+
if choice:
|
|
33
29
|
bm_rel = self.get_bm_rel(model)
|
|
34
|
-
my_bookmark_filter = self.
|
|
35
|
-
if choice
|
|
30
|
+
my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
|
|
31
|
+
if choice == "READ":
|
|
32
|
+
bookmark_filter = my_bookmark_filter & Q(
|
|
33
|
+
**{f"{bm_rel}__finished": True}
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
36
|
my_not_finished_filter = my_bookmark_filter & Q(
|
|
37
37
|
**{f"{bm_rel}__finished__in": (False, None)}
|
|
38
38
|
)
|
|
@@ -42,10 +42,6 @@ class BookmarkFilterMixin:
|
|
|
42
42
|
bookmark_filter = my_not_finished_filter & Q(
|
|
43
43
|
**{f"{bm_rel}__page__gt": 0}
|
|
44
44
|
)
|
|
45
|
-
else: # READ
|
|
46
|
-
bookmark_filter = my_bookmark_filter & Q(
|
|
47
|
-
**{f"{bm_rel}__finished": True}
|
|
48
|
-
)
|
|
49
45
|
else:
|
|
50
46
|
bookmark_filter = Q()
|
|
51
47
|
return bookmark_filter
|
|
@@ -5,18 +5,18 @@ from types import MappingProxyType
|
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
|
|
7
7
|
from codex.models.comic import Comic
|
|
8
|
-
from codex.views.
|
|
8
|
+
from codex.views.browser.filters.group import GroupFilterView
|
|
9
9
|
|
|
10
10
|
_FILTER_REL_MAP = MappingProxyType(
|
|
11
11
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
GroupFilterView.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
|
|
13
|
+
GroupFilterView.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
|
|
14
|
+
GroupFilterView.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
|
|
15
15
|
}
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class ComicFieldFilterView(GroupFilterView):
|
|
20
20
|
"""Comic field filters."""
|
|
21
21
|
|
|
22
22
|
def _filter_by_comic_field(self, field, rel_prefix):
|
|
@@ -2,25 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db.models import Q
|
|
4
4
|
|
|
5
|
-
from codex.
|
|
5
|
+
from codex.models import Comic
|
|
6
|
+
from codex.views.const import FOLDER_GROUP, GROUP_MODEL_MAP, GROUP_RELATION
|
|
7
|
+
from codex.views.session import SessionView
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
class
|
|
10
|
+
class GroupFilterView(SessionView):
|
|
9
11
|
"""Group Filters."""
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
SESSION_KEY = SessionView.BROWSER_SESSION_KEY
|
|
14
|
+
|
|
15
|
+
def get_group_filter(self, model):
|
|
12
16
|
"""Get filter for the displayed group."""
|
|
13
17
|
group = self.kwargs["group"] # type: ignore
|
|
14
18
|
pks = self.kwargs["pks"] # type: ignore
|
|
15
19
|
if pks: # type: ignore
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
if model == GROUP_MODEL_MAP.get(group):
|
|
21
|
+
# metadata only
|
|
22
|
+
rel = "pk"
|
|
23
|
+
elif model == Comic and group == FOLDER_GROUP:
|
|
24
|
+
# choices & covers
|
|
25
|
+
rel = "folders"
|
|
19
26
|
else:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
rel = GROUP_RELATION[group]
|
|
28
|
+
|
|
29
|
+
rel += "__in"
|
|
30
|
+
group_filter_dict = {rel: pks} # type: ignore
|
|
31
|
+
elif group == FOLDER_GROUP:
|
|
24
32
|
group_filter_dict = {"parent_folder": None}
|
|
25
33
|
else:
|
|
26
34
|
group_filter_dict = {}
|
|
@@ -9,12 +9,13 @@ from codex.logger.logging import get_logger
|
|
|
9
9
|
from codex.models import Comic
|
|
10
10
|
from codex.search.query import CodexSearchQuerySet
|
|
11
11
|
from codex.settings.settings import DEBUG
|
|
12
|
-
from codex.views.browser.
|
|
12
|
+
from codex.views.browser.filters.field import ComicFieldFilterView
|
|
13
|
+
from codex.views.const import MAX_OBJ_PER_PAGE
|
|
13
14
|
|
|
14
15
|
LOG = get_logger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
_SEARCH_SCORE_MAX = 100.0
|
|
17
|
+
_SEARCH_SCORE_MIN = 0.001
|
|
18
|
+
_SEARCH_SCORE_EMPTY = 0.0
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass
|
|
@@ -26,9 +27,11 @@ class SearchScorePks:
|
|
|
26
27
|
next_pks: tuple[int, ...] = ()
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
class
|
|
30
|
+
class SearchFilterView(ComicFieldFilterView):
|
|
30
31
|
"""Search Filters Methods."""
|
|
31
32
|
|
|
33
|
+
TARGET = ""
|
|
34
|
+
|
|
32
35
|
def _is_search_results_limited(self) -> bool:
|
|
33
36
|
"""Get search result limit from params."""
|
|
34
37
|
# user = self.request.user # type: ignore
|
|
@@ -114,7 +117,7 @@ class SearchFilterMixin:
|
|
|
114
117
|
|
|
115
118
|
return tuple(scores_pairs), tuple(prev_pks), tuple(next_pks), tuple(scored_pks)
|
|
116
119
|
|
|
117
|
-
def _get_search_scores(self,
|
|
120
|
+
def _get_search_scores(self, model, qs):
|
|
118
121
|
"""Perform the search and return the scores as a dict."""
|
|
119
122
|
text = self.params.get("q", "") # type: ignore
|
|
120
123
|
if not text:
|
|
@@ -127,13 +130,21 @@ class SearchFilterMixin:
|
|
|
127
130
|
try:
|
|
128
131
|
sqs = CodexSearchQuerySet()
|
|
129
132
|
sqs = sqs.auto_query(text) # .filter(score__gt=0.0)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
prefix = "" if model == Comic else self.rel_prefix # type: ignore
|
|
134
|
+
comic_ids = qs.values_list(prefix + "pk", flat=True)
|
|
135
|
+
if comic_ids:
|
|
136
|
+
# Prefilter comic ids. If nothing is allowed, don't search.
|
|
137
|
+
sqs = sqs.filter_comic_ids(comic_ids)
|
|
138
|
+
binary = (
|
|
139
|
+
self.TARGET in frozenset({"cover", "choices"})
|
|
140
|
+
or self.params.get("order_by", "") != "search_score"
|
|
136
141
|
)
|
|
142
|
+
if binary:
|
|
143
|
+
scored_pks = self._get_binary_search_scores(sqs)
|
|
144
|
+
else:
|
|
145
|
+
score_pairs, prev_pks, next_pks, scored_pks = (
|
|
146
|
+
self._get_browser_search_scores(sqs)
|
|
147
|
+
)
|
|
137
148
|
except MemoryError:
|
|
138
149
|
LOG.warning("Search engine needs more memory, results truncated.")
|
|
139
150
|
except Exception:
|
|
@@ -154,10 +165,12 @@ class SearchFilterMixin:
|
|
|
154
165
|
when = {prefix + "pk": pk, "then": score}
|
|
155
166
|
whens.append(When(**when))
|
|
156
167
|
if prev_pks:
|
|
157
|
-
whens.append(When(pk__in=prev_pks, then=
|
|
168
|
+
whens.append(When(pk__in=prev_pks, then=_SEARCH_SCORE_MAX))
|
|
158
169
|
if next_pks:
|
|
159
|
-
whens.append(When(pk__in=next_pks, then=
|
|
160
|
-
search_score = self.order_agg_func(
|
|
170
|
+
whens.append(When(pk__in=next_pks, then=_SEARCH_SCORE_MIN))
|
|
171
|
+
search_score = self.order_agg_func( # type: ignore
|
|
172
|
+
Case(*whens, default=_SEARCH_SCORE_EMPTY)
|
|
173
|
+
)
|
|
161
174
|
else:
|
|
162
175
|
search_score = Value(0.0)
|
|
163
176
|
return qs.annotate(search_score=search_score)
|
|
@@ -168,17 +181,16 @@ class SearchFilterMixin:
|
|
|
168
181
|
rel = prefix + "pk__in"
|
|
169
182
|
return {rel: scored_pks}
|
|
170
183
|
|
|
171
|
-
def apply_search_filter(self, qs, model
|
|
184
|
+
def apply_search_filter(self, qs, model):
|
|
172
185
|
"""Preparse search, search and return the filter and scores."""
|
|
173
186
|
try:
|
|
174
187
|
score_pairs, prev_pks, next_pks, scored_pks = self._get_search_scores(
|
|
175
|
-
|
|
188
|
+
model, qs
|
|
176
189
|
)
|
|
177
190
|
qs = self.annotate_search_score(qs, model, score_pairs, prev_pks, next_pks)
|
|
178
191
|
if score_pairs or scored_pks:
|
|
179
192
|
search_filter = self._get_search_query_filter(model, scored_pks)
|
|
180
193
|
qs = qs.filter(**search_filter)
|
|
181
|
-
self.cover_search_score_pairs = score_pairs
|
|
182
194
|
except Exception:
|
|
183
195
|
LOG.exception("Creating the search filter")
|
|
184
196
|
|
codex/views/browser/metadata.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""View for marking comics read and unread."""
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
|
-
from typing import ClassVar
|
|
5
4
|
|
|
6
5
|
from django.core.exceptions import FieldDoesNotExist
|
|
7
6
|
from django.db.models import Count, IntegerField, Sum, Value
|
|
@@ -9,12 +8,13 @@ from drf_spectacular.utils import extend_schema
|
|
|
9
8
|
from rest_framework.exceptions import NotFound
|
|
10
9
|
from rest_framework.response import Response
|
|
11
10
|
|
|
12
|
-
from codex.librarian.importer.const import
|
|
11
|
+
from codex.librarian.importer.const import (
|
|
12
|
+
COMIC_M2M_FIELD_NAMES,
|
|
13
|
+
)
|
|
13
14
|
from codex.logger.logging import get_logger
|
|
14
15
|
from codex.models import AdminFlag, Comic
|
|
15
16
|
from codex.serializers.browser.metadata import MetadataSerializer
|
|
16
|
-
from codex.views.
|
|
17
|
-
from codex.views.browser.browser_annotations import BrowserAnnotationsView
|
|
17
|
+
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
18
18
|
|
|
19
19
|
LOG = get_logger(__name__)
|
|
20
20
|
_ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
|
|
@@ -37,7 +37,7 @@ _COMIC_VALUE_FIELD_NAMES = frozenset(
|
|
|
37
37
|
)
|
|
38
38
|
_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
|
|
39
39
|
_COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_count"})
|
|
40
|
-
_PATH_GROUPS = ("c", "f")
|
|
40
|
+
_PATH_GROUPS = frozenset({"c", "f"})
|
|
41
41
|
_CONTRIBUTOR_RELATIONS = ("role", "person")
|
|
42
42
|
_SUM_FIELDS = frozenset({"page_count", "size"})
|
|
43
43
|
_GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
|
|
@@ -46,7 +46,6 @@ _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
|
|
|
46
46
|
class MetadataView(BrowserAnnotationsView):
|
|
47
47
|
"""Comic metadata."""
|
|
48
48
|
|
|
49
|
-
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
|
|
50
49
|
serializer_class = MetadataSerializer
|
|
51
50
|
TARGET = "metadata"
|
|
52
51
|
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
|
|
@@ -55,6 +54,11 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
55
54
|
}
|
|
56
55
|
)
|
|
57
56
|
|
|
57
|
+
def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
|
|
58
|
+
"""Limited allowed nav groups for metadata."""
|
|
59
|
+
group = self.kwargs["group"]
|
|
60
|
+
self.valid_nav_groups = (group,)
|
|
61
|
+
|
|
58
62
|
def _get_comic_value_fields(self):
|
|
59
63
|
"""Include the path field for staff."""
|
|
60
64
|
fields = set(_COMIC_VALUE_FIELD_NAMES)
|
|
@@ -156,6 +160,8 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
156
160
|
if field_name in _GROUP_RELS:
|
|
157
161
|
group_by = "name" if field_name == "volume" else "sort_name"
|
|
158
162
|
intersection_qs = intersection_qs.group_by(group_by)
|
|
163
|
+
if field_name == "identifiers":
|
|
164
|
+
intersection_qs = intersection_qs.select_related("identifier_type")
|
|
159
165
|
intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
|
|
160
166
|
count=comic_pks_count
|
|
161
167
|
)
|
|
@@ -188,18 +194,18 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
188
194
|
def _get_optimized_m2m_query(key, qs):
|
|
189
195
|
# XXX The prefetch gets removed by field.set() :(
|
|
190
196
|
if key == "contributors":
|
|
191
|
-
|
|
197
|
+
qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
|
|
192
198
|
*_CONTRIBUTOR_RELATIONS
|
|
193
199
|
)
|
|
194
200
|
elif key == "story_arc_numbers":
|
|
195
|
-
|
|
201
|
+
qs = qs.prefetch_related("story_arc")
|
|
202
|
+
qs = qs.only("story_arc", "number")
|
|
196
203
|
elif key == "identifiers":
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
204
|
+
qs = qs.prefetch_related("identifier_type")
|
|
205
|
+
qs = qs.only("identifier_type", "nss", "url")
|
|
200
206
|
else:
|
|
201
|
-
|
|
202
|
-
return
|
|
207
|
+
qs = qs.only("name")
|
|
208
|
+
return qs
|
|
203
209
|
|
|
204
210
|
@classmethod
|
|
205
211
|
def _copy_m2m_intersections(cls, obj, m2m_intersections):
|
|
@@ -247,27 +253,17 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
247
253
|
# values dicts don't copy relations to the serializer. The values
|
|
248
254
|
# dict is necessary because of the folders view union in browser.py.
|
|
249
255
|
|
|
250
|
-
|
|
251
|
-
# TODO this looks redundant after set_browse_model
|
|
252
|
-
group = self.kwargs["group"]
|
|
253
|
-
raise NotFound(detail=f"Cannot get metadata for {group=}")
|
|
256
|
+
qs = self.get_filtered_queryset(self.model)
|
|
254
257
|
|
|
255
|
-
object_filter = self.get_query_filters_without_group(self.model) # type: ignore
|
|
256
|
-
pks = self.kwargs["pks"]
|
|
257
|
-
qs = self.model.objects.filter(object_filter, pk__in=pks)
|
|
258
|
-
qs = self.filter_by_annotations(qs, self.model, binary=True)
|
|
259
258
|
filtered_qs = qs
|
|
260
259
|
qs = self.annotate_order_aggregates(qs, self.model)
|
|
261
260
|
qs = self.annotate_card_aggregates(qs, self.model)
|
|
262
|
-
qs = self.
|
|
261
|
+
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
263
262
|
group_by = self.get_group_by()
|
|
264
263
|
qs = qs.group_by(group_by)
|
|
265
|
-
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
266
|
-
|
|
267
|
-
qs_list = self.re_cover_multi_groups(qs)
|
|
268
264
|
|
|
269
265
|
try:
|
|
270
|
-
obj =
|
|
266
|
+
obj = qs[0]
|
|
271
267
|
if not obj:
|
|
272
268
|
reason = "Empty obj"
|
|
273
269
|
raise ValueError(reason) # noqa TRY301
|
|
@@ -277,11 +273,6 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
277
273
|
m2m_intersections = self._query_m2m_intersections(filtered_qs)
|
|
278
274
|
return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
|
|
279
275
|
|
|
280
|
-
def set_valid_browse_nav_groups(self, _valid_top_groups):
|
|
281
|
-
"""Limited allowed nav groups for metadata."""
|
|
282
|
-
group = self.kwargs["group"]
|
|
283
|
-
self.valid_nav_groups = (group,)
|
|
284
|
-
|
|
285
276
|
@extend_schema(request=BrowserAnnotationsView.input_serializer_class)
|
|
286
277
|
def get(self, *_args, **_kwargs):
|
|
287
278
|
"""Get metadata for a filtered browse group."""
|
|
@@ -6,22 +6,18 @@ from codex.models import Comic
|
|
|
6
6
|
from codex.views.browser.filters.annotations import (
|
|
7
7
|
BrowserAnnotationsFilterView,
|
|
8
8
|
)
|
|
9
|
+
from codex.views.const import FOLDER_GROUP
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class BrowserOrderByView(BrowserAnnotationsFilterView):
|
|
12
13
|
"""Base class for views that need ordering."""
|
|
13
14
|
|
|
14
|
-
def __init__(self, *args, **kwargs):
|
|
15
|
-
"""Set params for the type checker."""
|
|
16
|
-
super().__init__(*args, **kwargs)
|
|
17
|
-
self.order_key: str = ""
|
|
18
|
-
|
|
19
15
|
def set_order_key(self):
|
|
20
16
|
"""Get the default order key for the view."""
|
|
21
17
|
order_key: str = self.params.get("order_by", "") # type: ignore
|
|
22
18
|
if not order_key:
|
|
23
19
|
group = self.kwargs.get("group")
|
|
24
|
-
order_key = "filename" if group ==
|
|
20
|
+
order_key = "filename" if group == FOLDER_GROUP else "sort_name"
|
|
25
21
|
self.order_key: str = order_key
|
|
26
22
|
order_reverse = self.params.get("order_reverse")
|
|
27
23
|
self.order_agg_func = Max if order_reverse else Min
|