codex 1.6.0a7__py3-none-any.whl → 1.6.0a9__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 +5 -6
- 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 +101 -17
- codex/librarian/importer/create_covers.py +96 -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 +42 -64
- 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 +76 -104
- 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/mtime.py +21 -0
- codex/serializers/browser/settings.py +2 -3
- codex/serializers/choices.py +10 -1
- codex/serializers/fields.py +8 -9
- codex/serializers/reader.py +5 -5
- codex/serializers/route.py +9 -4
- codex/settings/settings.py +2 -2
- codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-eNspVUje.da8282a08876.js} +1 -1
- codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
- codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-BOUtyxuo.js → VCheckbox-eNspVUje.js} +1 -1
- codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
- codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js → VCheckboxBtn-DLzF_WHB.9a47873b8548.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.js → VCheckboxBtn-DLzF_WHB.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
- codex/static_root/assets/{VCombobox-DjkDXe33.22617ea193b1.js → VCombobox-BIO7lU_B.d607ada71b06.js} +1 -1
- codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
- codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
- codex/static_root/assets/{VCombobox-DjkDXe33.js → VCombobox-BIO7lU_B.js} +1 -1
- codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
- codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
- codex/static_root/assets/{VDialog-X0zn9AGX.0d89c749b9a6.js → VDialog-87AymJC5.4a46716358c0.js} +1 -1
- codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
- codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
- codex/static_root/assets/{VDialog-X0zn9AGX.js → VDialog-87AymJC5.js} +1 -1
- codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
- codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-CRevojaF.43d65c777c58.js → VExpansionPanels-CgA3shsd.9e524c73726a.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-CRevojaF.js → VExpansionPanels-CgA3shsd.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-B--7K_v5.4a4e9c2d6c42.js} +1 -1
- codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
- codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-8nrrxjDv.js → VRadioGroup-B--7K_v5.js} +1 -1
- codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
- codex/static_root/assets/VRadioGroup-B--7K_v5.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-BoJq222l.fc714657e214.js +1 -0
- codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
- codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
- codex/static_root/assets/VSelect-BoJq222l.js +1 -0
- codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
- codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-CGu0oz0K.3030b7a270d6.js → VSelectionControl-CVQ9wkE2.c02186cceba6.js} +1 -1
- codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-CGu0oz0K.js → VSelectionControl-CVQ9wkE2.js} +1 -1
- codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-D_oNvCOd.71a7101cdddd.js → VSlideGroup-Ce0j2BKK.85e64443a928.js} +1 -1
- codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
- codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-D_oNvCOd.js → VSlideGroup-Ce0j2BKK.js} +1 -1
- codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
- codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
- codex/static_root/assets/{VTable-C7pKI7gU.087912f706f5.js → VTable-CrWkhkiP.6d530eea0c09.js} +1 -1
- codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
- codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
- codex/static_root/assets/{VTable-C7pKI7gU.js → VTable-CrWkhkiP.js} +1 -1
- codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
- codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
- codex/static_root/assets/{VTextField-Bs8oq9mk.js → VTextField-CkWbil3K.2cac13161dfe.js} +1 -1
- codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
- codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
- codex/static_root/assets/{VTextField-Bs8oq9mk.7999fef12a03.js → VTextField-CkWbil3K.js} +1 -1
- codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
- codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-iyZ1XOkP.409f7d292253.js → VWindowItem-7z55Y4lK.98eb7487e039.js} +1 -1
- codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
- codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-iyZ1XOkP.js → VWindowItem-7z55Y4lK.js} +1 -1
- codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
- codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
- codex/static_root/assets/{admin-B0pCBjma.2407da79bd20.js → admin-D3OK784R.f69891726ec5.js} +1 -1
- codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
- codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
- codex/static_root/assets/{admin-B0pCBjma.js → admin-D3OK784R.js} +1 -1
- codex/static_root/assets/admin-D3OK784R.js.br +0 -0
- codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-1pnfvY8O.5438f252970e.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.js → admin-drawer-panel-1pnfvY8O.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
- codex/static_root/assets/browser-BuU2x9y7.css +1 -0
- codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
- codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
- codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +1 -0
- codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
- codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
- codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +1 -0
- codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
- codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
- codex/static_root/assets/browser-CO9EYHIr.js +1 -0
- codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
- codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-DZ9dP8_F.930f4cae3cb0.js → change-password-dialog-CjZdzaIk.188c765431ab.js} +1 -1
- codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-DZ9dP8_F.js → change-password-dialog-CjZdzaIk.js} +1 -1
- codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js → confirm-dialog-jwL7B98r.1cfd579cbcfe.js} +1 -1
- codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
- codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-RP7JNStF.js → confirm-dialog-jwL7B98r.js} +1 -1
- codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
- codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
- codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +1 -0
- codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
- codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
- codex/static_root/assets/datetime-column-9w3GcKvo.js +1 -0
- codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
- codex/static_root/assets/datetime-column-9w3GcKvo.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-C-pghOri.eeb7c2ecf398.js} +1 -1
- codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
- codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
- codex/static_root/assets/{filter-DAdGUt-1.js → filter-C-pghOri.js} +1 -1
- codex/static_root/assets/filter-C-pghOri.js.br +0 -0
- codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js +1 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.js +1 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
- codex/static_root/assets/{group-tab-DEwh4jfB.a95097b5e320.js → group-tab-BONYWOc1.6ddf321a4a56.js} +1 -1
- codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
- codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
- codex/static_root/assets/{group-tab-DEwh4jfB.js → group-tab-BONYWOc1.js} +1 -1
- codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
- codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
- codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js +1 -0
- codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
- codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
- codex/static_root/assets/http-error-D2Jnc20C.js +1 -0
- codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
- codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
- codex/static_root/assets/{library-tab-DrXvD9B2.js → library-tab-B419agXA.d4b778172aaa.js} +1 -1
- codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
- codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
- codex/static_root/assets/{library-tab-DrXvD9B2.f765f9d3bae4.js → library-tab-B419agXA.js} +1 -1
- codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
- codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
- codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +32 -0
- codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
- codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
- codex/static_root/assets/main-CMQwJ9aG.js +32 -0
- codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
- codex/static_root/assets/main-CMQwJ9aG.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/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +1 -0
- codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +1 -0
- codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-obrBwxa6.ec9bd13a5786.js → pdf-doc-8RVZqILx.2e7166dd26fb.js} +1 -1
- codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
- codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-obrBwxa6.js → pdf-doc-8RVZqILx.js} +1 -1
- codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
- codex/static_root/assets/pdf-doc-8RVZqILx.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-DNnK_Chr.114d0e321547.js +2 -0
- codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
- codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
- codex/static_root/assets/reader-DNnK_Chr.js +2 -0
- codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
- codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CZJcLjc8.1d7426dab654.js → relation-chips-PYua7aXv.428ba0b5861e.js} +1 -1
- codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
- codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CZJcLjc8.js → relation-chips-PYua7aXv.js} +1 -1
- codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
- codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DbN6ISRH.d14f525d8eee.js} +2 -2
- codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-7iTnAdjf.js → settings-drawer-DbN6ISRH.js} +2 -2
- codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
- codex/static_root/assets/{stats-tab-CQsK1n8O.544bf50ffb22.js → stats-tab-DImNhH8O.d5026283841a.js} +1 -1
- codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
- codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
- codex/static_root/assets/{stats-tab-CQsK1n8O.js → stats-tab-DImNhH8O.js} +1 -1
- codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
- codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
- codex/static_root/assets/{task-tab-C60TLP6e.63aa929a4ed6.js → task-tab-DPD53kjv.e56a35ff1691.js} +1 -1
- codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
- codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
- codex/static_root/assets/{task-tab-C60TLP6e.js → task-tab-DPD53kjv.js} +1 -1
- codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
- codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
- codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +1 -0
- codex/static_root/assets/to-case-ehC9Ccyj.js +1 -0
- codex/static_root/assets/{unauthorized-Bs7NSqea.c55650fac055.js → unauthorized-BOIx7Y18.ee3961abfccb.js} +1 -1
- codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
- codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
- codex/static_root/assets/{unauthorized-Bs7NSqea.js → unauthorized-BOIx7Y18.js} +1 -1
- codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
- codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
- codex/static_root/assets/{user-tab-ChuH7Atm.7e80cf1fb78e.js → user-tab-BABwfxLO.37dd0440fbdd.js} +1 -1
- codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
- codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
- codex/static_root/assets/{user-tab-ChuH7Atm.js → user-tab-BABwfxLO.js} +1 -1
- codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
- codex/static_root/assets/user-tab-BABwfxLO.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.cbea5663003a.json} +239 -239
- codex/static_root/manifest.cbea5663003a.json.br +0 -0
- codex/static_root/manifest.cbea5663003a.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/api/v3.py +1 -1
- codex/urls/opds/binary.py +1 -1
- codex/views/admin/auth.py +0 -2
- codex/views/admin/tasks.py +10 -1
- codex/views/auth.py +2 -4
- codex/views/bookmark.py +44 -40
- codex/views/browser/annotations.py +38 -174
- codex/views/browser/base.py +6 -22
- codex/views/browser/breadcrumbs.py +16 -14
- codex/views/browser/browser.py +11 -20
- codex/views/browser/choices.py +15 -39
- codex/views/browser/cover.py +177 -0
- codex/views/browser/filters/annotations.py +63 -18
- codex/views/browser/filters/bookmark.py +21 -5
- codex/views/browser/filters/field.py +1 -2
- codex/views/browser/filters/group.py +19 -11
- codex/views/browser/filters/search.py +19 -10
- codex/views/browser/metadata.py +20 -25
- codex/views/browser/mtime.py +72 -0
- codex/views/const.py +20 -0
- 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 +7 -9
- codex/views/reader/reader.py +25 -22
- codex/views/session.py +5 -3
- codex/views/util.py +32 -0
- {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/METADATA +8 -7
- {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/RECORD +324 -325
- 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/serializers/mtime.py +0 -25
- 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.br +0 -0
- codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
- 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.br +0 -0
- codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +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.br +0 -0
- codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
- 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/mtime.py +0 -70
- codex/views/utils.py +0 -26
- {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/LICENSE +0 -0
- {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/WHEEL +0 -0
- {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/entry_points.txt +0 -0
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,79 @@
|
|
|
1
1
|
"""Annotations used by a filter."""
|
|
2
2
|
|
|
3
|
-
from django.db.models.aggregates import Count
|
|
4
|
-
from django.db.models.
|
|
5
|
-
from django.db.models.fields import PositiveSmallIntegerField
|
|
3
|
+
from django.db.models.aggregates import Count, Max
|
|
4
|
+
from django.db.models.functions import Coalesce, Greatest
|
|
6
5
|
|
|
7
6
|
from codex.models.comic import Comic
|
|
7
|
+
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
8
8
|
from codex.views.browser.validate import BrowserValidateView
|
|
9
|
+
from codex.views.const import (
|
|
10
|
+
EPOCH_START_DATETIMEFILED,
|
|
11
|
+
ONE_INTEGERFIELD,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
class BrowserAnnotationsFilterView(BrowserValidateView):
|
|
15
|
+
class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
12
16
|
"""Annotations that also filter."""
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
19
|
+
"""Initialize."""
|
|
20
|
+
super().__init__(*args, **kwargs)
|
|
21
|
+
self.init_bookmark_data()
|
|
15
22
|
|
|
16
|
-
def
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
def _get_query_filters(self, model, group, pks, bookmark_filter):
|
|
24
|
+
"""Return all the filters except the group filter."""
|
|
25
|
+
object_filter = self.get_group_acl_filter(model)
|
|
26
|
+
object_filter &= self.get_group_filter(model, group, pks)
|
|
27
|
+
object_filter &= self.get_comic_field_filter(model)
|
|
28
|
+
if bookmark_filter:
|
|
29
|
+
# not needed because OuterRef is applied next
|
|
30
|
+
object_filter &= self.get_bookmark_filter(model)
|
|
31
|
+
return object_filter
|
|
32
|
+
|
|
33
|
+
def _filter_by_child_count(self, qs, model):
|
|
34
|
+
"""Filter group by child count."""
|
|
35
|
+
rel = self.rel_prefix + "pk"
|
|
36
|
+
ann_name = "child_count"
|
|
37
|
+
count_func = ONE_INTEGERFIELD if model == Comic else Count(rel, distinct=True)
|
|
38
|
+
ann = {ann_name: count_func}
|
|
22
39
|
if self.TARGET == "opds2":
|
|
23
40
|
if model != Comic:
|
|
24
|
-
qs = qs.alias(
|
|
41
|
+
qs = qs.alias(**ann)
|
|
25
42
|
else:
|
|
26
|
-
qs = qs.annotate(
|
|
43
|
+
qs = qs.annotate(**ann)
|
|
27
44
|
if model != Comic:
|
|
28
|
-
qs = qs.filter(
|
|
45
|
+
qs = qs.filter(**{f"{ann_name}__gt": 0})
|
|
29
46
|
return qs
|
|
30
47
|
|
|
31
|
-
def
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
def get_filtered_queryset(self, model, group=None, pks=None, bookmark_filter=True):
|
|
49
|
+
"""Get a filtered queryset for the model."""
|
|
50
|
+
object_filter = self._get_query_filters(model, group, pks, bookmark_filter)
|
|
51
|
+
qs = model.objects.filter(object_filter)
|
|
52
|
+
qs = self.apply_search_filter(qs, model)
|
|
53
|
+
return self._filter_by_child_count(qs, model)
|
|
54
|
+
|
|
55
|
+
def get_group_mtime(self, model, group=None, pks=None):
|
|
56
|
+
"""Get a filtered mtime for browser pages and mtime checker."""
|
|
57
|
+
qs = self.get_filtered_queryset(
|
|
58
|
+
model, group=group, pks=pks, bookmark_filter=self.is_bookmark_filtered
|
|
59
|
+
)
|
|
60
|
+
qs = qs.annotate(max_updated_at=Max("updated_at"))
|
|
61
|
+
|
|
62
|
+
if self.is_bookmark_filtered:
|
|
63
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
|
|
64
|
+
qs = qs.filter(bm_filter)
|
|
65
|
+
ua_rel = bm_rel + "__updated_at"
|
|
66
|
+
mbua = Max(ua_rel)
|
|
67
|
+
else:
|
|
68
|
+
mbua = EPOCH_START_DATETIMEFILED
|
|
69
|
+
qs = qs.annotate(max_bookmark_updated_at=mbua)
|
|
70
|
+
|
|
71
|
+
mtime = qs.aggregate(
|
|
72
|
+
max=Greatest(
|
|
73
|
+
Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFILED),
|
|
74
|
+
"max_updated_at",
|
|
75
|
+
)
|
|
76
|
+
)["max"]
|
|
77
|
+
if mtime == NotImplemented:
|
|
78
|
+
mtime = None
|
|
79
|
+
return mtime
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
"""Bookmark filter view methods."""
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from django.db.models import Q
|
|
4
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from codex.models import BrowserGroupModel
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
class BookmarkFilterMixin:
|
|
7
12
|
"""BookmarkFilter view methods."""
|
|
8
13
|
|
|
9
|
-
def
|
|
14
|
+
def init_bookmark_data(self):
|
|
15
|
+
"""Initialize the bm_annotation_data."""
|
|
16
|
+
self._bm_annotation_data: dict[BrowserGroupModel, tuple[str, Q]] = {}
|
|
17
|
+
|
|
18
|
+
def _get_bm_rel(self, model):
|
|
10
19
|
"""Create bookmark relation."""
|
|
11
20
|
rel_prefix = self.get_rel_prefix(model) # type: ignore
|
|
12
21
|
return rel_prefix + "bookmark"
|
|
13
22
|
|
|
14
|
-
def
|
|
23
|
+
def _get_my_bookmark_filter(self, bm_rel):
|
|
15
24
|
"""Get a filter for my session or user defined bookmarks."""
|
|
16
25
|
if self.request.user.is_authenticated: # type: ignore
|
|
17
26
|
my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
|
|
@@ -22,12 +31,19 @@ class BookmarkFilterMixin:
|
|
|
22
31
|
}
|
|
23
32
|
return Q(**my_bookmarks_kwargs)
|
|
24
33
|
|
|
34
|
+
def get_bookmark_rel_and_filter(self, model):
|
|
35
|
+
"""Get the bookmark rel and filter once."""
|
|
36
|
+
if model not in self._bm_annotation_data:
|
|
37
|
+
bm_rel = self._get_bm_rel(model)
|
|
38
|
+
bm_filter = self._get_my_bookmark_filter(bm_rel)
|
|
39
|
+
self._bm_annotation_data[model] = (bm_rel, bm_filter)
|
|
40
|
+
return self._bm_annotation_data[model]
|
|
41
|
+
|
|
25
42
|
def get_bookmark_filter(self, model):
|
|
26
43
|
"""Build bookmark query."""
|
|
27
|
-
choice = self.params
|
|
44
|
+
choice: str = self.params.get("filters", {}).get("bookmark", "") # type: ignore
|
|
28
45
|
if choice:
|
|
29
|
-
bm_rel = self.
|
|
30
|
-
my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
|
|
46
|
+
bm_rel, my_bookmark_filter = self.get_bookmark_rel_and_filter(model)
|
|
31
47
|
if choice == "READ":
|
|
32
48
|
bookmark_filter = my_bookmark_filter & Q(
|
|
33
49
|
**{f"{bm_rel}__finished": True}
|
|
@@ -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,25 @@ 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, group=None, pks=None):
|
|
15
16
|
"""Get filter for the displayed group."""
|
|
16
|
-
group
|
|
17
|
-
|
|
18
|
-
if pks
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
if group is None:
|
|
18
|
+
group = self.kwargs["group"]
|
|
19
|
+
if pks is None:
|
|
20
|
+
pks = self.kwargs["pks"]
|
|
21
|
+
|
|
22
|
+
if pks and 0 not in pks: # type: ignore
|
|
23
|
+
if model == GROUP_MODEL_MAP.get(group):
|
|
24
|
+
# metadata only
|
|
25
|
+
rel = "pk"
|
|
26
|
+
elif model == Comic and group == FOLDER_GROUP:
|
|
27
|
+
# choices & covers
|
|
28
|
+
rel = "folders"
|
|
22
29
|
else:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
rel = GROUP_RELATION[group]
|
|
31
|
+
|
|
32
|
+
rel += "__in"
|
|
33
|
+
group_filter_dict = {rel: pks} # type: ignore
|
|
26
34
|
elif group == FOLDER_GROUP:
|
|
27
35
|
group_filter_dict = {"parent_folder": None}
|
|
28
36
|
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
|
|