codex 1.6.0a7__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/integrity.py +4 -4
- codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +54 -76
- codex/librarian/importer/cache.py +162 -29
- 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 -397
- 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 +10 -0
- codex/librarian/librariand.py +7 -1
- 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/search/query.py +25 -2
- codex/search/writing.py +1 -1
- codex/serializers/browser/mixins.py +0 -18
- codex/serializers/browser/settings.py +2 -3
- codex/serializers/choices.py +10 -1
- codex/settings/settings.py +2 -2
- codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-DI-DgMb4.8eeb1bd3e18b.js} +1 -1
- 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-BOUtyxuo.js → VCheckbox-DI-DgMb4.js} +1 -1
- 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-B1-m5pEh.e2b8cbdb92c9.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-B1-m5pEh.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-DjkDXe33.22617ea193b1.js → VCombobox-D1_0lCri.6ef85a29c2dc.js} +1 -1
- 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-DjkDXe33.js → VCombobox-D1_0lCri.js} +1 -1
- 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-X0zn9AGX.0d89c749b9a6.js → VDialog-opmV0YZz.d77216e97599.js} +1 -1
- 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-X0zn9AGX.js → VDialog-opmV0YZz.js} +1 -1
- 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-CRevojaF.43d65c777c58.js → VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js} +1 -1
- 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-CRevojaF.js → VExpansionPanels-DX2dhUf5.js} +1 -1
- 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-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-BGa6_q5a.d1a3ad7266bd.js} +1 -1
- 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-8nrrxjDv.js → VRadioGroup-BGa6_q5a.js} +1 -1
- 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-CGu0oz0K.3030b7a270d6.js → VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js} +1 -1
- 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-CGu0oz0K.js → VSelectionControl-C8DP9rgp.js} +1 -1
- 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-D_oNvCOd.js → VSlideGroup-BRYhkLH8.3534ec94b2f1.js} +1 -1
- 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-D_oNvCOd.71a7101cdddd.js → VSlideGroup-BRYhkLH8.js} +1 -1
- 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-C7pKI7gU.087912f706f5.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-C7pKI7gU.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-iyZ1XOkP.409f7d292253.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-iyZ1XOkP.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-B0pCBjma.2407da79bd20.js → admin-Y0bL21AB.42d4f0d68163.js} +1 -1
- 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-B0pCBjma.js → admin-Y0bL21AB.js} +1 -1
- 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-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-DNngWUEM.4e992e3fe86e.js} +11 -11
- 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-BxoNUHQr.js → admin-drawer-panel-DNngWUEM.js} +11 -11
- 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/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-DZ9dP8_F.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-DZ9dP8_F.930f4cae3cb0.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-RP7JNStF.a752a7c1e697.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
- codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js.br → 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-RP7JNStF.js → confirm-dialog-DkU0twE6.js} +1 -1
- codex/static_root/assets/{confirm-dialog-RP7JNStF.js.br → confirm-dialog-DkU0twE6.js.br} +0 -0
- codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +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-DAdGUt-1.3261cbcd50b5.js → filter-DjxN78cK.35673f6e49b1.js} +1 -1
- 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-DAdGUt-1.js → filter-DjxN78cK.js} +1 -1
- 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-D4ZNSbMR.e763a9937701.js +1 -0
- 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-D4ZNSbMR.js +1 -0
- 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-DEwh4jfB.a95097b5e320.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-DEwh4jfB.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-DrXvD9B2.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-DrXvD9B2.f765f9d3bae4.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/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-obrBwxa6.ec9bd13a5786.js → pdf-doc-GkMk4o5L.ef24c859a7a4.js} +1 -1
- 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-obrBwxa6.js → pdf-doc-GkMk4o5L.js} +1 -1
- 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-CZJcLjc8.1d7426dab654.js → relation-chips-CZ8VvR4y.d4232f3c2518.js} +1 -1
- 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-CZJcLjc8.js → relation-chips-CZ8VvR4y.js} +1 -1
- 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-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DyS0A2pN.17a98928589f.js} +2 -2
- 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-7iTnAdjf.js → settings-drawer-DyS0A2pN.js} +2 -2
- 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-CQsK1n8O.544bf50ffb22.js → stats-tab-R3Ekobfm.70fa09a67518.js} +1 -1
- 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-CQsK1n8O.js → stats-tab-R3Ekobfm.js} +1 -1
- 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-Bs7NSqea.c55650fac055.js → unauthorized-BbXjAe5l.d48912d34a68.js} +1 -1
- 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-Bs7NSqea.js → unauthorized-BbXjAe5l.js} +1 -1
- 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-ChuH7Atm.7e80cf1fb78e.js → user-tab-DxITIl2d.d79d7f062930.js} +1 -1
- 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-ChuH7Atm.js → user-tab-DxITIl2d.js} +1 -1
- 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.079a01e0be1a.json → choices.a0aaaa6c7ef7.json} +1 -1
- 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.dad79c8475ae.json → manifest.16d52597ef50.json} +239 -239
- codex/static_root/manifest.16d52597ef50.json.br +0 -0
- codex/static_root/manifest.16d52597ef50.json.gz +0 -0
- codex/static_root/manifest.json +239 -239
- 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/browser.py +11 -2
- codex/urls/api/reader.py +1 -7
- codex/urls/opds/binary.py +1 -1
- codex/views/admin/auth.py +0 -2
- codex/views/admin/tasks.py +13 -1
- codex/views/auth.py +2 -4
- codex/views/browser/annotations.py +27 -148
- codex/views/browser/base.py +1 -18
- codex/views/browser/breadcrumbs.py +16 -14
- codex/views/browser/browser.py +5 -10
- codex/views/browser/choices.py +15 -39
- codex/views/browser/cover.py +177 -0
- codex/views/browser/filters/annotations.py +23 -22
- codex/views/browser/filters/field.py +1 -2
- codex/views/browser/filters/group.py +13 -8
- codex/views/browser/filters/search.py +19 -10
- codex/views/browser/metadata.py +20 -25
- codex/views/const.py +12 -0
- codex/views/mtime.py +14 -12
- codex/views/opds/binary.py +1 -1
- codex/views/opds/urls.py +2 -1
- codex/views/opds/util.py +2 -4
- codex/views/opds/v1/entry/links.py +18 -33
- codex/views/opds/v1/facets.py +2 -1
- codex/views/opds/v1/feed.py +42 -23
- codex/views/opds/v1/links.py +2 -1
- codex/views/opds/v2/feed.py +11 -9
- codex/views/opds/v2/links.py +2 -1
- codex/views/opds/v2/publications.py +13 -34
- codex/views/public.py +0 -2
- codex/views/reader/books.py +1 -1
- codex/views/reader/reader.py +2 -2
- codex/views/session.py +5 -3
- codex/views/util.py +32 -0
- {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/METADATA +8 -7
- {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/RECORD +317 -318
- 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-BOUtyxuo.c690f0cdbe48.js.br +0 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
- codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
- codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
- codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
- codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +0 -1
- codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.js +0 -1
- codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
- codex/static_root/assets/VSelect-DgfIKRnC.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-CGu0oz0K.3030b7a270d6.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
- codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
- codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
- codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
- codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js +0 -1
- codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.js +0 -1
- codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
- codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
- codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
- codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
- codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
- codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
- codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
- codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
- codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
- codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +0 -1
- codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
- codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
- codex/static_root/assets/browser-DbyAjCNh.js +0 -1
- codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
- codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
- codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +0 -1
- codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
- codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
- codex/static_root/assets/browser-VnzNj1t3.css +0 -1
- codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
- codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
- codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js +0 -1
- codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
- codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
- codex/static_root/assets/datetime-column-Cu3WYyPj.js +0 -1
- codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
- codex/static_root/assets/datetime-column-Cu3WYyPj.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-DAdGUt-1.3261cbcd50b5.js.br +0 -0
- codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
- codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
- codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +0 -1
- codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.js +0 -1
- codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
- codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
- codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
- codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
- codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
- codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +0 -1
- codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.js +0 -1
- codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
- codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
- codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
- codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
- codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
- codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +0 -32
- codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.js +0 -32
- codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
- codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +0 -1
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +0 -1
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css +0 -1
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.css +0 -1
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
- codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
- codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +0 -2
- codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
- codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
- codex/static_root/assets/reader-BXKUaUeC.js +0 -2
- codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
- codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
- codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css +0 -1
- codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
- codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
- codex/static_root/assets/reader-OXJ1KVaW.css +0 -1
- codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
- codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
- codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
- codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
- codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +0 -1
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.js +0 -1
- codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
- codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +0 -1
- codex/static_root/assets/to-case-CR9beRR0.js +0 -1
- codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
- codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
- codex/static_root/assets/user-tab-ChuH7Atm.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.079a01e0be1a.json.br +0 -0
- codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
- codex/static_root/manifest.dad79c8475ae.json.br +0 -0
- codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
- codex/views/cover.py +0 -76
- codex/views/utils.py +0 -26
- {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
- {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
- {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
codex/views/browser/browser.py
CHANGED
|
@@ -121,9 +121,7 @@ class BrowserView(BrowserTitleView):
|
|
|
121
121
|
################
|
|
122
122
|
def _get_common_queryset(self, model):
|
|
123
123
|
"""Create queryset common to group & books."""
|
|
124
|
-
|
|
125
|
-
qs = model.objects.filter(object_filter)
|
|
126
|
-
qs = self.filter_by_annotations(qs, model)
|
|
124
|
+
qs = self.get_filtered_queryset(model)
|
|
127
125
|
count_group_by = self.get_group_by(model)
|
|
128
126
|
count = qs.group_by(count_group_by).count()
|
|
129
127
|
if count:
|
|
@@ -184,14 +182,12 @@ class BrowserView(BrowserTitleView):
|
|
|
184
182
|
def _get_group_and_books(self):
|
|
185
183
|
"""Create the main queries with filters, annotation and pagination."""
|
|
186
184
|
group_qs, group_count = self._get_group_queryset()
|
|
185
|
+
mtime = self._get_page_mtime(group_qs)
|
|
187
186
|
book_qs, book_count = self._get_book_queryset()
|
|
188
187
|
# Paginate
|
|
189
188
|
group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
|
|
190
189
|
group_qs, book_qs, group_count, book_count
|
|
191
190
|
)
|
|
192
|
-
mtime = self._get_page_mtime(
|
|
193
|
-
group_qs
|
|
194
|
-
) # TODO should this be after paginate or not?
|
|
195
191
|
if page_group_count:
|
|
196
192
|
group_qs = self.annotate_card_aggregates(group_qs, self.model)
|
|
197
193
|
if page_book_count:
|
|
@@ -203,13 +199,12 @@ class BrowserView(BrowserTitleView):
|
|
|
203
199
|
# print(group_qs.explain())
|
|
204
200
|
# print(group_qs.query)
|
|
205
201
|
|
|
206
|
-
recovered_group_list = self.re_cover_multi_groups(group_qs)
|
|
207
202
|
total_count = page_group_count + page_book_count
|
|
208
|
-
return
|
|
203
|
+
return group_qs, book_qs, num_pages, total_count, zero_pad, mtime
|
|
209
204
|
|
|
210
205
|
def get_object(self):
|
|
211
206
|
"""Validate settings and get the querysets."""
|
|
212
|
-
|
|
207
|
+
group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
|
|
213
208
|
self._get_group_and_books()
|
|
214
209
|
)
|
|
215
210
|
|
|
@@ -226,7 +221,7 @@ class BrowserView(BrowserTitleView):
|
|
|
226
221
|
"breadcrumbs": parent_breadcrumbs,
|
|
227
222
|
"title": title,
|
|
228
223
|
"model_group": self.model_group,
|
|
229
|
-
"groups":
|
|
224
|
+
"groups": group_qs,
|
|
230
225
|
"books": book_qs,
|
|
231
226
|
"zero_pad": zero_pad,
|
|
232
227
|
"num_pages": num_pages,
|
codex/views/browser/choices.py
CHANGED
|
@@ -12,12 +12,7 @@ from codex.logger.logging import get_logger
|
|
|
12
12
|
from codex.models import (
|
|
13
13
|
Comic,
|
|
14
14
|
ContributorPerson,
|
|
15
|
-
Folder,
|
|
16
|
-
Imprint,
|
|
17
|
-
Publisher,
|
|
18
|
-
Series,
|
|
19
15
|
StoryArc,
|
|
20
|
-
Volume,
|
|
21
16
|
)
|
|
22
17
|
from codex.models.named import IdentifierType
|
|
23
18
|
from codex.serializers.browser.choices import CHOICES_SERIALIZER_CLASS_MAP
|
|
@@ -48,7 +43,6 @@ _FIELD_TO_REL_MODEL_MAP = MappingProxyType(
|
|
|
48
43
|
),
|
|
49
44
|
}
|
|
50
45
|
)
|
|
51
|
-
_NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
|
|
52
46
|
_BACK_REL_MAP = MappingProxyType(
|
|
53
47
|
{
|
|
54
48
|
ContributorPerson: "contributor__",
|
|
@@ -56,24 +50,15 @@ _BACK_REL_MAP = MappingProxyType(
|
|
|
56
50
|
IdentifierType: "identifier__",
|
|
57
51
|
}
|
|
58
52
|
)
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
Publisher: "publisher",
|
|
62
|
-
Imprint: "imprint",
|
|
63
|
-
Series: "series",
|
|
64
|
-
Volume: "volume",
|
|
65
|
-
Comic: "pk",
|
|
66
|
-
Folder: "parent_folder",
|
|
67
|
-
StoryArc: "story_arc_numbers__story_arc",
|
|
68
|
-
IdentifierType: "identifiers__identifier_type",
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
_SERIALIZER_MANY = False
|
|
53
|
+
_NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
|
|
72
54
|
|
|
73
55
|
|
|
74
56
|
class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
75
57
|
"""Get choices for filter dialog."""
|
|
76
58
|
|
|
59
|
+
SERIALIZER_MANY = False
|
|
60
|
+
TARGET = "choices"
|
|
61
|
+
|
|
77
62
|
@staticmethod
|
|
78
63
|
def get_field_choices_query(comic_qs, field_name):
|
|
79
64
|
"""Get distinct values for the field."""
|
|
@@ -85,21 +70,13 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
|
85
70
|
|
|
86
71
|
def get_m2m_field_query(self, model, comic_qs: QuerySet):
|
|
87
72
|
"""Get distinct m2m value objects for the relation."""
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
m2m_filter = {}
|
|
91
|
-
else:
|
|
92
|
-
model_rel: str = _CLASS_REL_MAP[self.model] # type: ignore
|
|
93
|
-
back_rel = _BACK_REL_MAP.get(model, "")
|
|
94
|
-
back_rel += "comic__"
|
|
95
|
-
back_rel += model_rel
|
|
96
|
-
back_rel += "__in"
|
|
97
|
-
m2m_filter = {back_rel: comic_qs}
|
|
73
|
+
back_rel = _BACK_REL_MAP.get(model, "")
|
|
74
|
+
m2m_filter = {f"{back_rel}comic__in": comic_qs}
|
|
98
75
|
return model.objects.filter(**m2m_filter).values("pk", "name").distinct()
|
|
99
76
|
|
|
100
77
|
@staticmethod
|
|
101
78
|
def does_m2m_null_exist(comic_qs, rel):
|
|
102
|
-
"""Get if
|
|
79
|
+
"""Get if comics exist in the filter without values exists for an m2m field."""
|
|
103
80
|
return comic_qs.filter(**{f"{rel}__isnull": True}).exists()
|
|
104
81
|
|
|
105
82
|
def get_rel_and_model(self, field_name):
|
|
@@ -114,22 +91,18 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
|
114
91
|
rel = field_name
|
|
115
92
|
model = remote_field.model if remote_field else None
|
|
116
93
|
|
|
117
|
-
rel = self.rel_prefix + rel
|
|
118
|
-
|
|
119
94
|
return rel, model
|
|
120
95
|
|
|
121
96
|
def get_object(self):
|
|
122
97
|
"""Get the comic subquery use for the choices."""
|
|
123
|
-
|
|
124
|
-
qs = self.model.objects.filter(object_filter) # type: ignore
|
|
125
|
-
return self.filter_by_annotations(qs, self.model, binary=True)
|
|
98
|
+
return self.get_filtered_queryset(Comic)
|
|
126
99
|
|
|
127
100
|
@extend_schema(request=BrowserAnnotationsFilterView.input_serializer_class)
|
|
128
101
|
def get(self, *_args, **_kwargs):
|
|
129
102
|
"""Return choices."""
|
|
130
103
|
self.init_request()
|
|
131
104
|
obj = self.get_object()
|
|
132
|
-
serializer = self.get_serializer(obj, many=
|
|
105
|
+
serializer = self.get_serializer(obj, many=self.SERIALIZER_MANY)
|
|
133
106
|
return Response(serializer.data)
|
|
134
107
|
|
|
135
108
|
|
|
@@ -162,6 +135,7 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
|
|
|
162
135
|
def get_object(self):
|
|
163
136
|
"""Get choice counts."""
|
|
164
137
|
qs = super().get_object()
|
|
138
|
+
filters = self.params.get("filters", {})
|
|
165
139
|
data = {}
|
|
166
140
|
for field_name in self.serializer_class().get_fields(): # type: ignore
|
|
167
141
|
if field_name == "story_arcs" and self.model == StoryArc:
|
|
@@ -174,9 +148,9 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
|
|
|
174
148
|
else:
|
|
175
149
|
count = self._get_field_choices_count(qs, rel)
|
|
176
150
|
|
|
177
|
-
filters = self.params.get("filters", {})
|
|
178
151
|
try:
|
|
179
|
-
|
|
152
|
+
is_filter_set = bool(filters.get(field_name))
|
|
153
|
+
flag = is_filter_set or count > 1 # type: ignore
|
|
180
154
|
except TypeError:
|
|
181
155
|
flag = False
|
|
182
156
|
data[field_name] = flag
|
|
@@ -187,7 +161,9 @@ class BrowserChoicesView(BrowserChoicesViewBase):
|
|
|
187
161
|
"""Get choices for filter dialog."""
|
|
188
162
|
|
|
189
163
|
# serializer_class = Dynamic class determined in get()
|
|
190
|
-
|
|
164
|
+
SERIALIZER_MANY = True
|
|
165
|
+
# HACK get_serializer(data, many=True) requires this to be set for the debug API view
|
|
166
|
+
queryset = Comic.objects.none()
|
|
191
167
|
|
|
192
168
|
def _get_field_choices(self, comic_qs, field_name):
|
|
193
169
|
"""Create a pk:name object for fields without tables."""
|
|
@@ -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
|
|
6
|
+
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
8
7
|
from codex.views.browser.validate import BrowserValidateView
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class BrowserAnnotationsFilterView(BrowserValidateView):
|
|
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)
|
|
@@ -5,7 +5,6 @@ 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.browser.filters.bookmark import BookmarkFilterMixin
|
|
9
8
|
from codex.views.browser.filters.group import GroupFilterView
|
|
10
9
|
|
|
11
10
|
_FILTER_REL_MAP = MappingProxyType(
|
|
@@ -17,7 +16,7 @@ _FILTER_REL_MAP = MappingProxyType(
|
|
|
17
16
|
)
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
class ComicFieldFilterView(GroupFilterView
|
|
19
|
+
class ComicFieldFilterView(GroupFilterView):
|
|
21
20
|
"""Comic field filters."""
|
|
22
21
|
|
|
23
22
|
def _filter_by_comic_field(self, field, rel_prefix):
|
|
@@ -2,7 +2,8 @@
|
|
|
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
|
|
6
7
|
from codex.views.session import SessionView
|
|
7
8
|
|
|
8
9
|
|
|
@@ -11,18 +12,22 @@ class GroupFilterView(SessionView):
|
|
|
11
12
|
|
|
12
13
|
SESSION_KEY = SessionView.BROWSER_SESSION_KEY
|
|
13
14
|
|
|
14
|
-
def get_group_filter(self,
|
|
15
|
+
def get_group_filter(self, model):
|
|
15
16
|
"""Get filter for the displayed group."""
|
|
16
17
|
group = self.kwargs["group"] # type: ignore
|
|
17
18
|
pks = self.kwargs["pks"] # type: ignore
|
|
18
19
|
if pks: # type: ignore
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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"
|
|
22
26
|
else:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
rel = GROUP_RELATION[group]
|
|
28
|
+
|
|
29
|
+
rel += "__in"
|
|
30
|
+
group_filter_dict = {rel: pks} # type: ignore
|
|
26
31
|
elif group == FOLDER_GROUP:
|
|
27
32
|
group_filter_dict = {"parent_folder": None}
|
|
28
33
|
else:
|
|
@@ -30,6 +30,8 @@ class SearchScorePks:
|
|
|
30
30
|
class SearchFilterView(ComicFieldFilterView):
|
|
31
31
|
"""Search Filters Methods."""
|
|
32
32
|
|
|
33
|
+
TARGET = ""
|
|
34
|
+
|
|
33
35
|
def _is_search_results_limited(self) -> bool:
|
|
34
36
|
"""Get search result limit from params."""
|
|
35
37
|
# user = self.request.user # type: ignore
|
|
@@ -115,7 +117,7 @@ class SearchFilterView(ComicFieldFilterView):
|
|
|
115
117
|
|
|
116
118
|
return tuple(scores_pairs), tuple(prev_pks), tuple(next_pks), tuple(scored_pks)
|
|
117
119
|
|
|
118
|
-
def _get_search_scores(self,
|
|
120
|
+
def _get_search_scores(self, model, qs):
|
|
119
121
|
"""Perform the search and return the scores as a dict."""
|
|
120
122
|
text = self.params.get("q", "") # type: ignore
|
|
121
123
|
if not text:
|
|
@@ -128,13 +130,21 @@ class SearchFilterView(ComicFieldFilterView):
|
|
|
128
130
|
try:
|
|
129
131
|
sqs = CodexSearchQuerySet()
|
|
130
132
|
sqs = sqs.auto_query(text) # .filter(score__gt=0.0)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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"
|
|
137
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
|
+
)
|
|
138
148
|
except MemoryError:
|
|
139
149
|
LOG.warning("Search engine needs more memory, results truncated.")
|
|
140
150
|
except Exception:
|
|
@@ -171,17 +181,16 @@ class SearchFilterView(ComicFieldFilterView):
|
|
|
171
181
|
rel = prefix + "pk__in"
|
|
172
182
|
return {rel: scored_pks}
|
|
173
183
|
|
|
174
|
-
def apply_search_filter(self, qs, model
|
|
184
|
+
def apply_search_filter(self, qs, model):
|
|
175
185
|
"""Preparse search, search and return the filter and scores."""
|
|
176
186
|
try:
|
|
177
187
|
score_pairs, prev_pks, next_pks, scored_pks = self._get_search_scores(
|
|
178
|
-
|
|
188
|
+
model, qs
|
|
179
189
|
)
|
|
180
190
|
qs = self.annotate_search_score(qs, model, score_pairs, prev_pks, next_pks)
|
|
181
191
|
if score_pairs or scored_pks:
|
|
182
192
|
search_filter = self._get_search_query_filter(model, scored_pks)
|
|
183
193
|
qs = qs.filter(**search_filter)
|
|
184
|
-
self.cover_search_score_pairs = score_pairs
|
|
185
194
|
except Exception:
|
|
186
195
|
LOG.exception("Creating the search filter")
|
|
187
196
|
|
codex/views/browser/metadata.py
CHANGED
|
@@ -8,7 +8,9 @@ from drf_spectacular.utils import extend_schema
|
|
|
8
8
|
from rest_framework.exceptions import NotFound
|
|
9
9
|
from rest_framework.response import Response
|
|
10
10
|
|
|
11
|
-
from codex.librarian.importer.const import
|
|
11
|
+
from codex.librarian.importer.const import (
|
|
12
|
+
COMIC_M2M_FIELD_NAMES,
|
|
13
|
+
)
|
|
12
14
|
from codex.logger.logging import get_logger
|
|
13
15
|
from codex.models import AdminFlag, Comic
|
|
14
16
|
from codex.serializers.browser.metadata import MetadataSerializer
|
|
@@ -52,6 +54,11 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
52
54
|
}
|
|
53
55
|
)
|
|
54
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
|
+
|
|
55
62
|
def _get_comic_value_fields(self):
|
|
56
63
|
"""Include the path field for staff."""
|
|
57
64
|
fields = set(_COMIC_VALUE_FIELD_NAMES)
|
|
@@ -153,6 +160,8 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
153
160
|
if field_name in _GROUP_RELS:
|
|
154
161
|
group_by = "name" if field_name == "volume" else "sort_name"
|
|
155
162
|
intersection_qs = intersection_qs.group_by(group_by)
|
|
163
|
+
if field_name == "identifiers":
|
|
164
|
+
intersection_qs = intersection_qs.select_related("identifier_type")
|
|
156
165
|
intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
|
|
157
166
|
count=comic_pks_count
|
|
158
167
|
)
|
|
@@ -185,18 +194,18 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
185
194
|
def _get_optimized_m2m_query(key, qs):
|
|
186
195
|
# XXX The prefetch gets removed by field.set() :(
|
|
187
196
|
if key == "contributors":
|
|
188
|
-
|
|
197
|
+
qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
|
|
189
198
|
*_CONTRIBUTOR_RELATIONS
|
|
190
199
|
)
|
|
191
200
|
elif key == "story_arc_numbers":
|
|
192
|
-
|
|
201
|
+
qs = qs.prefetch_related("story_arc")
|
|
202
|
+
qs = qs.only("story_arc", "number")
|
|
193
203
|
elif key == "identifiers":
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
)
|
|
204
|
+
qs = qs.prefetch_related("identifier_type")
|
|
205
|
+
qs = qs.only("identifier_type", "nss", "url")
|
|
197
206
|
else:
|
|
198
|
-
|
|
199
|
-
return
|
|
207
|
+
qs = qs.only("name")
|
|
208
|
+
return qs
|
|
200
209
|
|
|
201
210
|
@classmethod
|
|
202
211
|
def _copy_m2m_intersections(cls, obj, m2m_intersections):
|
|
@@ -244,26 +253,17 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
244
253
|
# values dicts don't copy relations to the serializer. The values
|
|
245
254
|
# dict is necessary because of the folders view union in browser.py.
|
|
246
255
|
|
|
247
|
-
|
|
248
|
-
# TODO this looks redundant after set_browse_model
|
|
249
|
-
group = self.kwargs["group"]
|
|
250
|
-
raise NotFound(detail=f"Cannot get metadata for {group=}")
|
|
256
|
+
qs = self.get_filtered_queryset(self.model)
|
|
251
257
|
|
|
252
|
-
object_filter = self.get_query_filters_without_group(self.model) # type: ignore
|
|
253
|
-
pks = self.kwargs["pks"]
|
|
254
|
-
qs = self.model.objects.filter(object_filter, pk__in=pks)
|
|
255
|
-
qs = self.filter_by_annotations(qs, self.model, binary=True)
|
|
256
258
|
filtered_qs = qs
|
|
257
259
|
qs = self.annotate_order_aggregates(qs, self.model)
|
|
258
260
|
qs = self.annotate_card_aggregates(qs, self.model)
|
|
261
|
+
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
259
262
|
group_by = self.get_group_by()
|
|
260
263
|
qs = qs.group_by(group_by)
|
|
261
|
-
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
262
|
-
|
|
263
|
-
qs_list = self.re_cover_multi_groups(qs)
|
|
264
264
|
|
|
265
265
|
try:
|
|
266
|
-
obj =
|
|
266
|
+
obj = qs[0]
|
|
267
267
|
if not obj:
|
|
268
268
|
reason = "Empty obj"
|
|
269
269
|
raise ValueError(reason) # noqa TRY301
|
|
@@ -273,11 +273,6 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
273
273
|
m2m_intersections = self._query_m2m_intersections(filtered_qs)
|
|
274
274
|
return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
|
|
275
275
|
|
|
276
|
-
def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
|
|
277
|
-
"""Limited allowed nav groups for metadata."""
|
|
278
|
-
group = self.kwargs["group"]
|
|
279
|
-
self.valid_nav_groups = (group,)
|
|
280
|
-
|
|
281
276
|
@extend_schema(request=BrowserAnnotationsView.input_serializer_class)
|
|
282
277
|
def get(self, *_args, **_kwargs):
|
|
283
278
|
"""Get metadata for a filtered browse group."""
|
codex/views/const.py
CHANGED
|
@@ -64,6 +64,18 @@ GROUP_RELATION = MappingProxyType(
|
|
|
64
64
|
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
65
65
|
}
|
|
66
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
|
+
)
|
|
67
79
|
GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
|
|
68
80
|
MappingProxyType(
|
|
69
81
|
{
|