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
|
@@ -7,7 +7,6 @@ from django.db.models import (
|
|
|
7
7
|
Avg,
|
|
8
8
|
BooleanField,
|
|
9
9
|
Case,
|
|
10
|
-
DateTimeField,
|
|
11
10
|
F,
|
|
12
11
|
FilteredRelation,
|
|
13
12
|
Max,
|
|
@@ -17,16 +16,16 @@ from django.db.models import (
|
|
|
17
16
|
Value,
|
|
18
17
|
When,
|
|
19
18
|
)
|
|
19
|
+
from django.db.models.expressions import OuterRef
|
|
20
20
|
from django.db.models.fields import CharField, PositiveSmallIntegerField
|
|
21
|
-
from django.db.models.functions import Least, Reverse, Right, StrIndex
|
|
21
|
+
from django.db.models.functions import Coalesce, Least, Reverse, Right, StrIndex
|
|
22
|
+
from django.db.models.functions.comparison import Greatest
|
|
22
23
|
|
|
24
|
+
from codex.logger.logging import get_logger
|
|
23
25
|
from codex.models import (
|
|
24
26
|
BrowserGroupModel,
|
|
25
27
|
Comic,
|
|
26
28
|
Folder,
|
|
27
|
-
Imprint,
|
|
28
|
-
Publisher,
|
|
29
|
-
Series,
|
|
30
29
|
StoryArc,
|
|
31
30
|
Volume,
|
|
32
31
|
)
|
|
@@ -34,21 +33,14 @@ from codex.models.functions import JsonGroupArray
|
|
|
34
33
|
from codex.views.browser.order_by import (
|
|
35
34
|
BrowserOrderByView,
|
|
36
35
|
)
|
|
37
|
-
from codex.views.const import
|
|
36
|
+
from codex.views.const import (
|
|
37
|
+
EPOCH_START,
|
|
38
|
+
NONE_DATETIMEFIELD,
|
|
39
|
+
NONE_INTEGERFIELD,
|
|
40
|
+
STORY_ARC_GROUP,
|
|
41
|
+
)
|
|
38
42
|
from codex.views.mixins import SharedAnnotationsMixin
|
|
39
43
|
|
|
40
|
-
_REL_MAP = MappingProxyType(
|
|
41
|
-
{
|
|
42
|
-
Publisher: "publisher",
|
|
43
|
-
Imprint: "imprint",
|
|
44
|
-
Series: "series",
|
|
45
|
-
Volume: "volume",
|
|
46
|
-
Folder: "parent_folder",
|
|
47
|
-
StoryArc: "story_arc_numbers__story_arc",
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
_NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
|
|
51
|
-
_NONE_DATETIMEFIELD = Value(None, DateTimeField())
|
|
52
44
|
_ORDER_AGGREGATE_FUNCS = MappingProxyType(
|
|
53
45
|
# These are annotated to order_value because they're simple relations
|
|
54
46
|
{
|
|
@@ -76,6 +68,8 @@ _ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingPro
|
|
|
76
68
|
{Comic: "id", Volume: "name"}
|
|
77
69
|
)
|
|
78
70
|
|
|
71
|
+
LOG = get_logger(__name__)
|
|
72
|
+
|
|
79
73
|
|
|
80
74
|
class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
81
75
|
"""Base class for views that need special metadata annotations."""
|
|
@@ -86,7 +80,6 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
86
80
|
self.is_opds_1_acquisition = False
|
|
87
81
|
self.comic_sort_names = ()
|
|
88
82
|
self.bm_annotataion_data: dict[BrowserGroupModel, tuple[str, dict]] = {}
|
|
89
|
-
self.cover_search_score_pairs: tuple[tuple[int, float], ...] = ()
|
|
90
83
|
|
|
91
84
|
def get_group_by(self, model=None):
|
|
92
85
|
"""Get the group by for the model."""
|
|
@@ -150,7 +143,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
150
143
|
)
|
|
151
144
|
story_arc_number = self.order_agg_func("selected_story_arc_number__number")
|
|
152
145
|
else:
|
|
153
|
-
story_arc_number =
|
|
146
|
+
story_arc_number = NONE_INTEGERFIELD
|
|
154
147
|
|
|
155
148
|
return qs.alias(story_arc_number=story_arc_number)
|
|
156
149
|
|
|
@@ -170,17 +163,11 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
170
163
|
|
|
171
164
|
def get_bookmark_updated_at_aggregate(self, model, always_max=False):
|
|
172
165
|
"""Get The aggregate function for relevant bookmark.updated_at."""
|
|
173
|
-
bm_rel = self.
|
|
174
|
-
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
175
|
-
self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
|
|
166
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
|
|
176
167
|
|
|
177
|
-
|
|
168
|
+
bm_updated_at_rel = f"{bm_rel}__updated_at"
|
|
178
169
|
agg_func = Max if always_max else self.order_agg_func
|
|
179
|
-
return agg_func(
|
|
180
|
-
updated_at_rel,
|
|
181
|
-
default=_NONE_DATETIMEFIELD,
|
|
182
|
-
filter=bm_filter,
|
|
183
|
-
)
|
|
170
|
+
return agg_func(bm_updated_at_rel, default=NONE_DATETIMEFIELD, filter=bm_filter)
|
|
184
171
|
|
|
185
172
|
def _annotate_bookmark_updated_at(self, qs, model):
|
|
186
173
|
if (
|
|
@@ -231,54 +218,6 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
231
218
|
qs = self._annotate_order_value(qs, model)
|
|
232
219
|
return qs
|
|
233
220
|
|
|
234
|
-
def _annotate_cover_pk(self, qs, model):
|
|
235
|
-
"""Annotate the query set for the coverpath for the sort."""
|
|
236
|
-
# Select comics for the children by an outer ref for annotation
|
|
237
|
-
# Order the descendant comics by the sort argumentst
|
|
238
|
-
cover_style = self.params.get("cover_style")
|
|
239
|
-
if model == Comic:
|
|
240
|
-
default_cover_pk = F("pk")
|
|
241
|
-
default_cover_mtime = F("updated_at")
|
|
242
|
-
elif cover_style == "i":
|
|
243
|
-
default_cover_pk = None
|
|
244
|
-
default_cover_mtime = None
|
|
245
|
-
elif cover_style == "f":
|
|
246
|
-
# At this point it's only been filtered.
|
|
247
|
-
default_cover_pk = F(self.rel_prefix + "pk")
|
|
248
|
-
default_cover_mtime = F(self.rel_prefix + "updated_at")
|
|
249
|
-
else: # cover_style == "d"
|
|
250
|
-
default_cover_pk = F(self.rel_prefix + "pk")
|
|
251
|
-
if self.is_bookmark_filtered:
|
|
252
|
-
default_cover_mtime = Case(
|
|
253
|
-
When(bookmark_updated_at__gt=F(self.rel_prefix + "updated_at")),
|
|
254
|
-
then=F("bookmark_updated_at"),
|
|
255
|
-
default=F(self.rel_prefix + "updated_at"),
|
|
256
|
-
)
|
|
257
|
-
else:
|
|
258
|
-
default_cover_mtime = F(self.rel_prefix + "updated_at")
|
|
259
|
-
|
|
260
|
-
if model in (Volume, Comic):
|
|
261
|
-
cover_custom = Value(False)
|
|
262
|
-
cover_pk = default_cover_pk
|
|
263
|
-
cover_mtime = default_cover_mtime
|
|
264
|
-
else:
|
|
265
|
-
cover_custom = F("custom_cover")
|
|
266
|
-
cover_pk = Case(
|
|
267
|
-
When(custom_cover__isnull=False, then=F("custom_cover__pk")),
|
|
268
|
-
default=default_cover_pk,
|
|
269
|
-
)
|
|
270
|
-
cover_mtime = Case(
|
|
271
|
-
When(custom_cover__isnull=False, then=F("custom_cover__updated_at")),
|
|
272
|
-
default=default_cover_mtime,
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
return qs.annotate(
|
|
276
|
-
cover_custom=cover_custom,
|
|
277
|
-
cover_pk=cover_pk,
|
|
278
|
-
cover_mtime=cover_mtime,
|
|
279
|
-
cover_pks=JsonGroupArray("cover_pk", distinct=True),
|
|
280
|
-
)
|
|
281
|
-
|
|
282
221
|
def _annotate_group(self, qs, model):
|
|
283
222
|
"""Annotate Group."""
|
|
284
223
|
value = "c" if model == Comic else self.model_group # type: ignore
|
|
@@ -286,11 +225,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
286
225
|
|
|
287
226
|
def _annotate_bookmarks(self, qs, model):
|
|
288
227
|
"""Hoist up bookmark annotations."""
|
|
289
|
-
|
|
290
|
-
bm_rel, bm_filter = self.bm_annotation_data[model] # type: ignore
|
|
291
|
-
else:
|
|
292
|
-
bm_rel = self.get_bm_rel(model)
|
|
293
|
-
bm_filter = self.get_my_bookmark_filter(bm_rel)
|
|
228
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
|
|
294
229
|
|
|
295
230
|
page_rel = f"{bm_rel}__page"
|
|
296
231
|
finished_rel = f"{bm_rel}__finished"
|
|
@@ -331,7 +266,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
331
266
|
output_field=PositiveSmallIntegerField(),
|
|
332
267
|
distinct=True,
|
|
333
268
|
)
|
|
334
|
-
qs = qs.
|
|
269
|
+
qs = qs.annotate(finished_count=finished_count)
|
|
335
270
|
finished_aggregate = Case(
|
|
336
271
|
When(finished_count=F("child_count"), then=True),
|
|
337
272
|
When(finished_count=0, then=False),
|
|
@@ -339,20 +274,15 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
339
274
|
output_field=BooleanField(),
|
|
340
275
|
)
|
|
341
276
|
|
|
342
|
-
if (
|
|
343
|
-
self.
|
|
344
|
-
or self.is_model_comic
|
|
345
|
-
and self.TARGET == "browser"
|
|
277
|
+
if self.is_opds_1_acquisition or (
|
|
278
|
+
self.is_model_comic and self.TARGET == "browser"
|
|
346
279
|
):
|
|
347
280
|
qs = qs.annotate(page=bookmark_page)
|
|
348
|
-
|
|
281
|
+
elif self.TARGET in frozenset({"metadata", "browser"}):
|
|
349
282
|
qs = qs.alias(page=bookmark_page)
|
|
350
283
|
|
|
351
284
|
if self.TARGET in frozenset({"metadata", "browser"}):
|
|
352
|
-
qs.annotate(finished=finished_aggregate)
|
|
353
|
-
else:
|
|
354
|
-
# Only used for progress
|
|
355
|
-
qs.alias(finished=finished_aggregate)
|
|
285
|
+
qs = qs.annotate(finished=finished_aggregate)
|
|
356
286
|
return qs
|
|
357
287
|
|
|
358
288
|
def _annotate_progress(self, qs):
|
|
@@ -367,19 +297,26 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
367
297
|
progress = Case(When(page_count__gt=0, then=then), default=0.0)
|
|
368
298
|
return qs.annotate(progress=progress)
|
|
369
299
|
|
|
370
|
-
def _annotate_mtime(self, qs):
|
|
300
|
+
def _annotate_mtime(self, qs, model):
|
|
371
301
|
"""Annotations mtime."""
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
302
|
+
if self.is_bookmark_filtered:
|
|
303
|
+
sub_qs = self.get_filtered_queryset(model, bookmark_filter=False)
|
|
304
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
|
|
305
|
+
ua_rel = bm_rel + "__updated_at"
|
|
306
|
+
qs = qs.annotate(
|
|
307
|
+
bmu_max=Max(
|
|
308
|
+
sub_qs.filter(pk=OuterRef("pk")).values_list(ua_rel, flat=True),
|
|
309
|
+
default=NONE_DATETIMEFIELD,
|
|
310
|
+
filter=bm_filter,
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
mtime = Greatest(Coalesce("bmu_max", Value(EPOCH_START)), "updated_at")
|
|
314
|
+
else:
|
|
315
|
+
mtime = F("updated_at")
|
|
378
316
|
return qs.annotate(mtime=mtime)
|
|
379
317
|
|
|
380
318
|
def annotate_card_aggregates(self, qs, model):
|
|
381
319
|
"""Annotate aggregates that appear the browser card."""
|
|
382
|
-
qs = self._annotate_cover_pk(qs, model)
|
|
383
320
|
if model == Comic:
|
|
384
321
|
# comic adds order_value for cards late
|
|
385
322
|
qs = self._annotate_order_value(qs, model)
|
|
@@ -387,77 +324,4 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
|
|
|
387
324
|
qs = self.annotate_group_names(qs, model)
|
|
388
325
|
qs = self._annotate_bookmarks(qs, model)
|
|
389
326
|
qs = self._annotate_progress(qs)
|
|
390
|
-
return self._annotate_mtime(qs)
|
|
391
|
-
|
|
392
|
-
def _get_cover_pk_query(self, cover_pks, group_ids):
|
|
393
|
-
"""Get Cover Pk queryset for comic queryset."""
|
|
394
|
-
cover_style = self.params.get("cover_style")
|
|
395
|
-
if cover_style == "d":
|
|
396
|
-
# DYNAMIC COVERS
|
|
397
|
-
comic_qs = Comic.objects.filter(pk__in=cover_pks)
|
|
398
|
-
comic_qs = self.annotate_search_score(
|
|
399
|
-
comic_qs, Comic, self.cover_search_score_pairs
|
|
400
|
-
)
|
|
401
|
-
comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
|
|
402
|
-
comic_qs = self.add_order_by(comic_qs, Comic) # type: ignore
|
|
403
|
-
else: # "f"
|
|
404
|
-
# FIRST COVERS
|
|
405
|
-
# The only filter at all is the model relation.
|
|
406
|
-
group_rel = _REL_MAP[self.model] # type: ignore
|
|
407
|
-
comic_qs = Comic.objects.filter(**{f"{group_rel}__in": group_ids})
|
|
408
|
-
|
|
409
|
-
# Order generated irrespective of browser filters and order
|
|
410
|
-
if self.kwargs["group"] == "f":
|
|
411
|
-
order_key = "filename"
|
|
412
|
-
elif self.kwargs["group"] == "a":
|
|
413
|
-
order_key = "story_arc_number"
|
|
414
|
-
else:
|
|
415
|
-
order_key = "sort_name"
|
|
416
|
-
|
|
417
|
-
model_group = self.model_group # type: ignore
|
|
418
|
-
show = MappingProxyType(self.params["show"]) # type: ignore
|
|
419
|
-
model_group = self.kwargs["group"]
|
|
420
|
-
comic_qs, comic_sort_names = self.alias_sort_names(
|
|
421
|
-
comic_qs, Comic, cover_pks, model_group, show
|
|
422
|
-
)
|
|
423
|
-
comic_qs = self.add_order_by(
|
|
424
|
-
comic_qs,
|
|
425
|
-
Comic,
|
|
426
|
-
order_key=order_key,
|
|
427
|
-
do_reverse=False,
|
|
428
|
-
comic_sort_names=comic_sort_names,
|
|
429
|
-
)
|
|
430
|
-
comic_qs = comic_qs.group_by("id")
|
|
431
|
-
return comic_qs.only("pk", "updated_at")
|
|
432
|
-
|
|
433
|
-
def re_cover_multi_groups(self, group_qs):
|
|
434
|
-
"""Python hack to re-cover groups collapsed with group_by."""
|
|
435
|
-
# This would be better in the main query but OuterRef can't access annotations.
|
|
436
|
-
# So cover_pks aggregates all covers from multi groups like ids does.
|
|
437
|
-
cover_style = self.params.get("cover_style")
|
|
438
|
-
if self.is_model_comic or cover_style == "i":
|
|
439
|
-
return group_qs
|
|
440
|
-
|
|
441
|
-
recovered_group_list = []
|
|
442
|
-
for group in group_qs:
|
|
443
|
-
cover_pks = group.cover_pks
|
|
444
|
-
if len(cover_pks) == 1:
|
|
445
|
-
cover_pk = cover_pks[0]
|
|
446
|
-
else:
|
|
447
|
-
comic_qs = self._get_cover_pk_query(cover_pks, group.ids)
|
|
448
|
-
# print(comic_qs.explain())
|
|
449
|
-
# print(comic_qs.query)
|
|
450
|
-
cover = comic_qs[0]
|
|
451
|
-
cover_pk = cover.pk
|
|
452
|
-
|
|
453
|
-
if len(group.ids) == 1:
|
|
454
|
-
cover_mtime = group.updated_at
|
|
455
|
-
else:
|
|
456
|
-
cover_mtime = group.__class__.objects.filter(
|
|
457
|
-
pk__in=group.ids
|
|
458
|
-
).aggregate(updated_at=Max("updated_at"))["updated_at"]
|
|
459
|
-
|
|
460
|
-
group.cover_mtime = cover_mtime
|
|
461
|
-
group.cover_pk = cover_pk
|
|
462
|
-
recovered_group_list.append(group)
|
|
463
|
-
return recovered_group_list
|
|
327
|
+
return self._annotate_mtime(qs, model)
|
codex/views/browser/base.py
CHANGED
|
@@ -4,7 +4,6 @@ from types import MappingProxyType
|
|
|
4
4
|
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
6
|
from django.contrib.auth.models import User
|
|
7
|
-
from django.db.models import Q
|
|
8
7
|
from django.db.models.aggregates import Max, Min
|
|
9
8
|
from rest_framework.exceptions import NotFound
|
|
10
9
|
|
|
@@ -12,20 +11,17 @@ from codex.logger.logging import get_logger
|
|
|
12
11
|
from codex.models import (
|
|
13
12
|
AdminFlag,
|
|
14
13
|
Comic,
|
|
15
|
-
Folder,
|
|
16
14
|
)
|
|
17
15
|
from codex.serializers.browser.settings import BrowserSettingsSerializer
|
|
18
16
|
from codex.views.browser.filters.search import SearchFilterView
|
|
19
17
|
from codex.views.const import GROUP_MODEL_MAP, ROOT_GROUP
|
|
20
|
-
from codex.views.
|
|
18
|
+
from codex.views.util import reparse_json_query_params
|
|
21
19
|
|
|
22
20
|
LOG = get_logger(__name__)
|
|
23
21
|
|
|
24
22
|
if TYPE_CHECKING:
|
|
25
23
|
from codex.models.groups import BrowserGroupModel
|
|
26
24
|
|
|
27
|
-
_REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
|
|
28
|
-
|
|
29
25
|
|
|
30
26
|
class BrowserBaseView(SearchFilterView):
|
|
31
27
|
"""Browse comics with a variety of filters and sorts."""
|
|
@@ -33,16 +29,15 @@ class BrowserBaseView(SearchFilterView):
|
|
|
33
29
|
input_serializer_class = BrowserSettingsSerializer
|
|
34
30
|
|
|
35
31
|
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType({})
|
|
32
|
+
REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
|
|
36
33
|
|
|
37
34
|
_GET_JSON_KEYS = frozenset({"filters", "show"})
|
|
38
|
-
TARGET = ""
|
|
39
35
|
|
|
40
36
|
def __init__(self, *args, **kwargs):
|
|
41
37
|
"""Set params for the type checker."""
|
|
42
38
|
super().__init__(*args, **kwargs)
|
|
43
39
|
self._is_admin: bool = False
|
|
44
40
|
self.params: MappingProxyType[str, Any] = MappingProxyType({})
|
|
45
|
-
self.bm_annotation_data = {}
|
|
46
41
|
self.rel_prefix: str = ""
|
|
47
42
|
self.model: type[BrowserGroupModel] | None = None
|
|
48
43
|
self.group_class: type[BrowserGroupModel] | None = None
|
|
@@ -77,23 +72,11 @@ class BrowserBaseView(SearchFilterView):
|
|
|
77
72
|
group = self.params["top_group"]
|
|
78
73
|
return group
|
|
79
74
|
|
|
80
|
-
def get_query_filters_without_group(self, model, cover=False):
|
|
81
|
-
"""Return all the filters except the group filter."""
|
|
82
|
-
add_acl = not (cover and self.model == Folder) # type: ignore
|
|
83
|
-
object_filter = self.get_group_acl_filter(model) if add_acl else Q()
|
|
84
|
-
object_filter &= self.get_bookmark_filter(model)
|
|
85
|
-
object_filter &= self.get_comic_field_filter(model)
|
|
86
|
-
return object_filter
|
|
87
|
-
|
|
88
|
-
def get_query_filters(self, model, choices=False):
|
|
89
|
-
"""Return the main object filter and the one for aggregates."""
|
|
90
|
-
object_filter = self.get_query_filters_without_group(model)
|
|
91
|
-
object_filter &= self.get_group_filter(choices)
|
|
92
|
-
return object_filter
|
|
93
|
-
|
|
94
75
|
def _parse_query_params(self):
|
|
95
76
|
"""Parse GET query parameters: filter object & snake case."""
|
|
96
|
-
query_params = reparse_json_query_params(
|
|
77
|
+
query_params = reparse_json_query_params(
|
|
78
|
+
self.request.GET, self.REPARSE_JSON_FIELDS
|
|
79
|
+
)
|
|
97
80
|
if "q" not in query_params and (query := query_params.get("query")):
|
|
98
81
|
# parse query param for opds v2
|
|
99
82
|
query_params["q"] = query
|
|
@@ -106,6 +89,7 @@ class BrowserBaseView(SearchFilterView):
|
|
|
106
89
|
serializer.is_valid(raise_exception=True)
|
|
107
90
|
self.load_params_from_session(serializer.validated_data)
|
|
108
91
|
self.is_bookmark_filtered = bool(self.params.get("filters", {}).get("bookmark"))
|
|
92
|
+
return serializer.validated_data
|
|
109
93
|
|
|
110
94
|
def set_order_key(self):
|
|
111
95
|
"""Unused until browser."""
|
|
@@ -5,13 +5,11 @@ from types import MappingProxyType
|
|
|
5
5
|
|
|
6
6
|
from codex.logger.logging import get_logger
|
|
7
7
|
from codex.views.browser.paginate import BrowserPaginateView
|
|
8
|
-
from codex.views.const import FOLDER_GROUP, GROUP_NAME_MAP, STORY_ARC_GROUP
|
|
8
|
+
from codex.views.const import FOLDER_GROUP, GROUP_NAME_MAP, GROUP_ORDER, STORY_ARC_GROUP
|
|
9
9
|
from codex.views.util import Route
|
|
10
10
|
|
|
11
11
|
LOG = get_logger(__name__)
|
|
12
12
|
|
|
13
|
-
_GROUP_ORDER = "rpisv" # TODO move to const
|
|
14
|
-
|
|
15
13
|
|
|
16
14
|
class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
17
15
|
"""Browser breadcrumbs calculations."""
|
|
@@ -69,9 +67,11 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
69
67
|
|
|
70
68
|
reversed_breadcrumbs = list(reversed(old_breadcrumbs))
|
|
71
69
|
|
|
70
|
+
pks = self.kwargs["pks"]
|
|
71
|
+
page = self.kwargs["page"]
|
|
72
72
|
folder = self.group_instance # type: ignore
|
|
73
|
-
name = folder.name if folder else "
|
|
74
|
-
group_crumb = Route(FOLDER_GROUP,
|
|
73
|
+
name = folder.name if folder and pks else ""
|
|
74
|
+
group_crumb = Route(FOLDER_GROUP, pks, page, name)
|
|
75
75
|
new_breadcrumbs = []
|
|
76
76
|
|
|
77
77
|
while True:
|
|
@@ -89,11 +89,13 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
89
89
|
break
|
|
90
90
|
|
|
91
91
|
# parent next
|
|
92
|
+
if not folder:
|
|
93
|
+
break
|
|
92
94
|
folder = folder.parent_folder
|
|
93
95
|
if folder:
|
|
94
96
|
group_crumb = Route(FOLDER_GROUP, (folder.pk,), 1, name=folder.name)
|
|
95
97
|
else:
|
|
96
|
-
group_crumb = Route(FOLDER_GROUP, (), 1, name="
|
|
98
|
+
group_crumb = Route(FOLDER_GROUP, (), 1, name="")
|
|
97
99
|
|
|
98
100
|
breadcrumbs = new_breadcrumbs
|
|
99
101
|
|
|
@@ -105,12 +107,12 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
105
107
|
if not gi:
|
|
106
108
|
pks = ()
|
|
107
109
|
page = 1
|
|
108
|
-
name = "
|
|
110
|
+
name = ""
|
|
109
111
|
if group == self.kwargs["group"]:
|
|
110
112
|
# create self crumb
|
|
111
113
|
pks = self.kwargs["pks"]
|
|
112
114
|
page = self.kwargs["page"]
|
|
113
|
-
name = gi.name if gi else "
|
|
115
|
+
name = gi.name if gi else ""
|
|
114
116
|
else:
|
|
115
117
|
page = 1
|
|
116
118
|
if (attr := GROUP_NAME_MAP.get(group)) and (
|
|
@@ -120,7 +122,7 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
120
122
|
name = parent_group.name
|
|
121
123
|
else:
|
|
122
124
|
pks = ()
|
|
123
|
-
name = "
|
|
125
|
+
name = ""
|
|
124
126
|
|
|
125
127
|
return Route(group, pks, page, name)
|
|
126
128
|
|
|
@@ -147,29 +149,29 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
147
149
|
|
|
148
150
|
def _breadcrumbs_graft_or_create_group(self) -> tuple[tuple[Route, ...], bool]:
|
|
149
151
|
"""Graft or create browse group breadcrumbs."""
|
|
150
|
-
old_breadcrumbs, changed = self._init_breadcrumbs(
|
|
152
|
+
old_breadcrumbs, changed = self._init_breadcrumbs(GROUP_ORDER)
|
|
151
153
|
|
|
152
154
|
vng = self.valid_nav_groups # type: ignore
|
|
153
155
|
test_groups = tuple(reversed(vng[:-1]))
|
|
154
156
|
new_breadcrumbs = []
|
|
155
157
|
level = done = False
|
|
156
158
|
try:
|
|
157
|
-
browser_group_index =
|
|
159
|
+
browser_group_index = GROUP_ORDER.index(self.kwargs["group"])
|
|
158
160
|
except ValueError:
|
|
159
161
|
browser_group_index = -1
|
|
160
162
|
|
|
161
163
|
for group in test_groups:
|
|
162
164
|
try:
|
|
163
165
|
with suppress(ValueError):
|
|
164
|
-
level = level or
|
|
166
|
+
level = level or GROUP_ORDER.index(group) <= browser_group_index
|
|
165
167
|
if level:
|
|
166
168
|
done, changed = self._breadcrumbs_graft_or_create_group_crumb(
|
|
167
169
|
group, old_breadcrumbs, new_breadcrumbs, changed
|
|
168
170
|
)
|
|
169
171
|
try:
|
|
170
|
-
if old_breadcrumbs and
|
|
172
|
+
if old_breadcrumbs and GROUP_ORDER.index(
|
|
171
173
|
old_breadcrumbs[-1].group
|
|
172
|
-
) >=
|
|
174
|
+
) >= GROUP_ORDER.index(group):
|
|
173
175
|
# Trim old_breadcrumbs to match to group
|
|
174
176
|
old_breadcrumbs.pop(-1)
|
|
175
177
|
except ValueError:
|
codex/views/browser/browser.py
CHANGED
|
@@ -22,7 +22,6 @@ from codex.models import (
|
|
|
22
22
|
Volume,
|
|
23
23
|
)
|
|
24
24
|
from codex.serializers.browser.page import BrowserPageSerializer
|
|
25
|
-
from codex.util import max_none
|
|
26
25
|
from codex.views.browser.title import BrowserTitleView
|
|
27
26
|
from codex.views.const import (
|
|
28
27
|
COMIC_GROUP,
|
|
@@ -121,9 +120,7 @@ class BrowserView(BrowserTitleView):
|
|
|
121
120
|
################
|
|
122
121
|
def _get_common_queryset(self, model):
|
|
123
122
|
"""Create queryset common to group & books."""
|
|
124
|
-
|
|
125
|
-
qs = model.objects.filter(object_filter)
|
|
126
|
-
qs = self.filter_by_annotations(qs, model)
|
|
123
|
+
qs = self.get_filtered_queryset(model)
|
|
127
124
|
count_group_by = self.get_group_by(model)
|
|
128
125
|
count = qs.group_by(count_group_by).count()
|
|
129
126
|
if count:
|
|
@@ -159,16 +156,13 @@ class BrowserView(BrowserTitleView):
|
|
|
159
156
|
count = 0
|
|
160
157
|
return qs, count
|
|
161
158
|
|
|
162
|
-
def _get_page_mtime(self
|
|
163
|
-
if self.group_instance:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
page_updated_at_max = group_qs.aggregate(max=Max("updated_at"))["max"]
|
|
167
|
-
|
|
168
|
-
agg_func = self.get_bookmark_updated_at_aggregate(self.model, True)
|
|
169
|
-
page_bookmark_updated_at = group_qs.aggregate(max=agg_func)["max"]
|
|
159
|
+
def _get_page_mtime(self):
|
|
160
|
+
if not self.is_bookmark_filtered and self.group_instance:
|
|
161
|
+
# Nice optimization if we can get get it.
|
|
162
|
+
return self.group_instance.updated_at
|
|
170
163
|
|
|
171
|
-
|
|
164
|
+
group_model = self.group_class if self.group_class else self.model
|
|
165
|
+
return self.get_group_mtime(group_model)
|
|
172
166
|
|
|
173
167
|
@staticmethod
|
|
174
168
|
def _get_zero_pad(book_qs):
|
|
@@ -189,9 +183,6 @@ class BrowserView(BrowserTitleView):
|
|
|
189
183
|
group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
|
|
190
184
|
group_qs, book_qs, group_count, book_count
|
|
191
185
|
)
|
|
192
|
-
mtime = self._get_page_mtime(
|
|
193
|
-
group_qs
|
|
194
|
-
) # TODO should this be after paginate or not?
|
|
195
186
|
if page_group_count:
|
|
196
187
|
group_qs = self.annotate_card_aggregates(group_qs, self.model)
|
|
197
188
|
if page_book_count:
|
|
@@ -203,13 +194,13 @@ class BrowserView(BrowserTitleView):
|
|
|
203
194
|
# print(group_qs.explain())
|
|
204
195
|
# print(group_qs.query)
|
|
205
196
|
|
|
206
|
-
recovered_group_list = self.re_cover_multi_groups(group_qs)
|
|
207
197
|
total_count = page_group_count + page_book_count
|
|
208
|
-
|
|
198
|
+
mtime = self._get_page_mtime()
|
|
199
|
+
return group_qs, book_qs, num_pages, total_count, zero_pad, mtime
|
|
209
200
|
|
|
210
201
|
def get_object(self):
|
|
211
202
|
"""Validate settings and get the querysets."""
|
|
212
|
-
|
|
203
|
+
group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
|
|
213
204
|
self._get_group_and_books()
|
|
214
205
|
)
|
|
215
206
|
|
|
@@ -226,7 +217,7 @@ class BrowserView(BrowserTitleView):
|
|
|
226
217
|
"breadcrumbs": parent_breadcrumbs,
|
|
227
218
|
"title": title,
|
|
228
219
|
"model_group": self.model_group,
|
|
229
|
-
"groups":
|
|
220
|
+
"groups": group_qs,
|
|
230
221
|
"books": book_qs,
|
|
231
222
|
"zero_pad": zero_pad,
|
|
232
223
|
"num_pages": num_pages,
|