codex 1.6.0a9__py3-none-any.whl → 1.6.0a11__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/librarian/importer/cache.py +46 -133
- codex/librarian/importer/create_fks.py +3 -1
- codex/librarian/importer/importer.py +1 -1
- codex/librarian/importer/importerd.py +5 -5
- codex/librarian/importer/tasks.py +2 -2
- codex/librarian/janitor/janitor.py +0 -3
- codex/librarian/librariand.py +0 -2
- codex/librarian/notifier/notifierd.py +1 -1
- codex/librarian/notifier/tasks.py +4 -4
- codex/middleware.py +1 -2
- codex/migrations/0027_import_order_and_covers.py +1 -66
- codex/models/comic.py +0 -1
- codex/models/groups.py +0 -3
- codex/serializers/admin.py +41 -40
- codex/serializers/browser/choices.py +38 -10
- codex/serializers/browser/filters.py +2 -2
- codex/serializers/browser/metadata.py +45 -1
- codex/serializers/browser/mixins.py +35 -2
- codex/serializers/browser/page.py +2 -4
- codex/serializers/browser/settings.py +41 -14
- codex/serializers/choices.py +4 -0
- codex/serializers/fields.py +61 -1
- codex/serializers/models/pycountry.py +11 -51
- codex/serializers/reader.py +12 -1
- codex/serializers/route.py +1 -1
- codex/serializers/settings.py +12 -0
- codex/settings/settings.py +10 -1
- codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js +1 -0
- codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js.br +0 -0
- codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BwDyxhU7.js +1 -0
- codex/static_root/assets/VCheckbox-BwDyxhU7.js.br +0 -0
- codex/static_root/assets/VCheckbox-BwDyxhU7.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.9a47873b8548.js → VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.js → VCheckboxBtn-DVq5lDK4.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-DVq5lDK4.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DVq5lDK4.js.gz +0 -0
- codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js +1 -0
- codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js.br +0 -0
- codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js.gz +0 -0
- codex/static_root/assets/VCombobox-BASCOy0n.js +1 -0
- codex/static_root/assets/VCombobox-BASCOy0n.js.br +0 -0
- codex/static_root/assets/VCombobox-BASCOy0n.js.gz +0 -0
- codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js +1 -0
- codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js.br +0 -0
- codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js.gz +0 -0
- codex/static_root/assets/VDialog-DnbL_v_u.js +1 -0
- codex/static_root/assets/VDialog-DnbL_v_u.js.br +0 -0
- codex/static_root/assets/VDialog-DnbL_v_u.js.gz +0 -0
- codex/static_root/assets/{VDialog-C5Nxd_zP.css → VDialog-lQ13FnRR.1c801ad756b2.css} +1 -1
- codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
- codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
- codex/static_root/assets/{VDialog-C5Nxd_zP.d327f505ef0e.css → VDialog-lQ13FnRR.css} +1 -1
- codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
- codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
- codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js +1 -0
- codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-B8nz8lN5.js +1 -0
- codex/static_root/assets/VExpansionPanels-B8nz8lN5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-B8nz8lN5.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-B--7K_v5.4a4e9c2d6c42.js → VRadioGroup-BgwJ_b94.55f84f3429b9.js} +1 -1
- codex/static_root/assets/VRadioGroup-BgwJ_b94.55f84f3429b9.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BgwJ_b94.55f84f3429b9.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-B--7K_v5.js → VRadioGroup-BgwJ_b94.js} +1 -1
- codex/static_root/assets/VRadioGroup-BgwJ_b94.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BgwJ_b94.js.gz +0 -0
- codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js +1 -0
- codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js.br +0 -0
- codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js.gz +0 -0
- codex/static_root/assets/VSelect-BoKpXxwe.js +1 -0
- codex/static_root/assets/VSelect-BoKpXxwe.js.br +0 -0
- codex/static_root/assets/VSelect-BoKpXxwe.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-CVQ9wkE2.c02186cceba6.js → VSelectionControl-BXJGg8No.0cb36d941d2a.js} +1 -1
- codex/static_root/assets/VSelectionControl-BXJGg8No.0cb36d941d2a.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BXJGg8No.0cb36d941d2a.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-CVQ9wkE2.js → VSelectionControl-BXJGg8No.js} +1 -1
- codex/static_root/assets/VSelectionControl-BXJGg8No.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BXJGg8No.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js +1 -0
- codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js.br +0 -0
- codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-C2TL5E34.js +1 -0
- codex/static_root/assets/VSlideGroup-C2TL5E34.js.br +0 -0
- codex/static_root/assets/VSlideGroup-C2TL5E34.js.gz +0 -0
- codex/static_root/assets/{VTable-CrWkhkiP.6d530eea0c09.js → VTable-C0FptljX.e4840b635c6e.js} +1 -1
- codex/static_root/assets/VTable-C0FptljX.e4840b635c6e.js.br +0 -0
- codex/static_root/assets/VTable-C0FptljX.e4840b635c6e.js.gz +0 -0
- codex/static_root/assets/{VTable-CrWkhkiP.js → VTable-C0FptljX.js} +1 -1
- codex/static_root/assets/VTable-C0FptljX.js.br +0 -0
- codex/static_root/assets/VTable-C0FptljX.js.gz +0 -0
- codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js +1 -0
- codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js.br +0 -0
- codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js.gz +0 -0
- codex/static_root/assets/VTextField-Aw8nKhY6.js +1 -0
- codex/static_root/assets/VTextField-Aw8nKhY6.js.br +0 -0
- codex/static_root/assets/VTextField-Aw8nKhY6.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js +1 -0
- codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js.br +0 -0
- codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D9j_u9MX.js +1 -0
- codex/static_root/assets/VWindowItem-D9j_u9MX.js.br +0 -0
- codex/static_root/assets/VWindowItem-D9j_u9MX.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js +30 -0
- codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js +30 -0
- codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-o87BwD38.ce39427c8201.css → admin-drawer-panel-mne3FIj6.561b3f75fe65.css} +1 -1
- codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-o87BwD38.css → admin-drawer-panel-mne3FIj6.css} +1 -1
- codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
- codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js +1 -0
- codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js.br +0 -0
- codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js.gz +0 -0
- codex/static_root/assets/admin-jf7zR2p_.js +1 -0
- codex/static_root/assets/admin-jf7zR2p_.js.br +0 -0
- codex/static_root/assets/admin-jf7zR2p_.js.gz +0 -0
- codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js +1 -0
- codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js.br +0 -0
- codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js.gz +0 -0
- codex/static_root/assets/browser-BKZuypzF.js +1 -0
- codex/static_root/assets/browser-BKZuypzF.js.br +0 -0
- codex/static_root/assets/browser-BKZuypzF.js.gz +0 -0
- codex/static_root/assets/browser-D7xci5SY.css +1 -0
- codex/static_root/assets/browser-D7xci5SY.css.br +0 -0
- codex/static_root/assets/browser-D7xci5SY.css.gz +0 -0
- codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css +1 -0
- codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css.br +0 -0
- codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +1 -0
- codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
- codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-0u6IJA87.css +1 -0
- codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
- codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js +1 -0
- codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js.br +0 -0
- codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cc1e755p.js +1 -0
- codex/static_root/assets/change-password-dialog-Cc1e755p.js.br +0 -0
- codex/static_root/assets/change-password-dialog-Cc1e755p.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-jwL7B98r.1cfd579cbcfe.js → confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js} +1 -1
- codex/static_root/assets/confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js.br +0 -0
- codex/static_root/assets/confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-jwL7B98r.js → confirm-dialog-Cyr7HAAz.js} +1 -1
- codex/static_root/assets/confirm-dialog-Cyr7HAAz.js.br +0 -0
- codex/static_root/assets/confirm-dialog-Cyr7HAAz.js.gz +0 -0
- codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js +1 -0
- codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js.br +0 -0
- codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js.gz +0 -0
- codex/static_root/assets/datetime-column-Cqi99Ze-.js +1 -0
- codex/static_root/assets/datetime-column-Cqi99Ze-.js.br +0 -0
- codex/static_root/assets/datetime-column-Cqi99Ze-.js.gz +0 -0
- codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js +1 -0
- codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js.br +0 -0
- codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js.gz +0 -0
- codex/static_root/assets/filter-CuocBoS2.js +1 -0
- codex/static_root/assets/filter-CuocBoS2.js.br +0 -0
- codex/static_root/assets/filter-CuocBoS2.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Da2DBNyz.ffbe5ca37e1c.js → flag-tab-B5dRCBAt.e858f7ec9f55.js} +1 -1
- codex/static_root/assets/flag-tab-B5dRCBAt.e858f7ec9f55.js.br +0 -0
- codex/static_root/assets/flag-tab-B5dRCBAt.e858f7ec9f55.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Da2DBNyz.js → flag-tab-B5dRCBAt.js} +1 -1
- codex/static_root/assets/flag-tab-B5dRCBAt.js.br +0 -0
- codex/static_root/assets/flag-tab-B5dRCBAt.js.gz +0 -0
- codex/static_root/assets/{group-tab-BONYWOc1.6ddf321a4a56.js → group-tab-_nT2o04v.c06032b254dc.js} +1 -1
- codex/static_root/assets/group-tab-_nT2o04v.c06032b254dc.js.br +0 -0
- codex/static_root/assets/group-tab-_nT2o04v.c06032b254dc.js.gz +0 -0
- codex/static_root/assets/{group-tab-BONYWOc1.js → group-tab-_nT2o04v.js} +1 -1
- codex/static_root/assets/group-tab-_nT2o04v.js.br +0 -0
- codex/static_root/assets/group-tab-_nT2o04v.js.gz +0 -0
- codex/static_root/assets/{http-error-D2Jnc20C.32846bc3a43d.js → http-error-CXo9qLiW.d5a337f399c9.js} +1 -1
- codex/static_root/assets/http-error-CXo9qLiW.d5a337f399c9.js.br +0 -0
- codex/static_root/assets/http-error-CXo9qLiW.d5a337f399c9.js.gz +0 -0
- codex/static_root/assets/{http-error-D2Jnc20C.js → http-error-CXo9qLiW.js} +1 -1
- codex/static_root/assets/http-error-CXo9qLiW.js.br +0 -0
- codex/static_root/assets/http-error-CXo9qLiW.js.gz +0 -0
- codex/static_root/assets/{library-tab-B419agXA.js → library-tab-lz5QMD4i.2f04b226ebe8.js} +1 -1
- codex/static_root/assets/library-tab-lz5QMD4i.2f04b226ebe8.js.br +0 -0
- codex/static_root/assets/library-tab-lz5QMD4i.2f04b226ebe8.js.gz +0 -0
- codex/static_root/assets/{library-tab-B419agXA.d4b778172aaa.js → library-tab-lz5QMD4i.js} +1 -1
- codex/static_root/assets/library-tab-lz5QMD4i.js.br +0 -0
- codex/static_root/assets/library-tab-lz5QMD4i.js.gz +0 -0
- codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js +32 -0
- codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js.br +0 -0
- codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js.gz +0 -0
- codex/static_root/assets/main-17i0EBCp.js +32 -0
- codex/static_root/assets/main-17i0EBCp.js.br +0 -0
- codex/static_root/assets/main-17i0EBCp.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js +1 -0
- codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-BBvsIXTd.js +1 -0
- codex/static_root/assets/pagination-toolbar-BBvsIXTd.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-BBvsIXTd.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css +1 -0
- codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css +1 -0
- codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css.gz +0 -0
- codex/static_root/assets/{pdf-doc-8RVZqILx.2e7166dd26fb.js → pdf-doc-BpZdmj2g.93e86d9c7927.js} +1 -1
- codex/static_root/assets/pdf-doc-BpZdmj2g.93e86d9c7927.js.br +0 -0
- codex/static_root/assets/pdf-doc-BpZdmj2g.93e86d9c7927.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-8RVZqILx.js → pdf-doc-BpZdmj2g.js} +1 -1
- codex/static_root/assets/pdf-doc-BpZdmj2g.js.br +0 -0
- codex/static_root/assets/pdf-doc-BpZdmj2g.js.gz +0 -0
- codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js +2 -0
- codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js.br +0 -0
- codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js.gz +0 -0
- codex/static_root/assets/reader-CRJUR2qj.js +2 -0
- codex/static_root/assets/reader-CRJUR2qj.js.br +0 -0
- codex/static_root/assets/reader-CRJUR2qj.js.gz +0 -0
- codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-CqXFa_gE.78267e9de0c2.css} +1 -1
- codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.br +0 -0
- codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.gz +0 -0
- codex/static_root/assets/{reader-CCCfpM3x.css → reader-CqXFa_gE.css} +1 -1
- codex/static_root/assets/reader-CqXFa_gE.css.br +0 -0
- codex/static_root/assets/reader-CqXFa_gE.css.gz +0 -0
- codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js +1 -0
- codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js.br +0 -0
- codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js.gz +0 -0
- codex/static_root/assets/relation-chips-NgDnInw_.js +1 -0
- codex/static_root/assets/relation-chips-NgDnInw_.js.br +0 -0
- codex/static_root/assets/relation-chips-NgDnInw_.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js +2 -0
- codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js.br +0 -0
- codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BaQT27kc.js +2 -0
- codex/static_root/assets/settings-drawer-BaQT27kc.js.br +0 -0
- codex/static_root/assets/settings-drawer-BaQT27kc.js.gz +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +1 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.css +1 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
- codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
- codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js +1 -0
- codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js.br +0 -0
- codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js.gz +0 -0
- codex/static_root/assets/stats-tab-DoHb54IU.js +1 -0
- codex/static_root/assets/stats-tab-DoHb54IU.js.br +0 -0
- codex/static_root/assets/stats-tab-DoHb54IU.js.gz +0 -0
- codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js +1 -0
- codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js.br +0 -0
- codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js.gz +0 -0
- codex/static_root/assets/task-tab-B88ucu1U.js +1 -0
- codex/static_root/assets/task-tab-B88ucu1U.js.br +0 -0
- codex/static_root/assets/task-tab-B88ucu1U.js.gz +0 -0
- codex/static_root/assets/to-case-B7MjLIw-.08fc8412de69.js +1 -0
- codex/static_root/assets/to-case-B7MjLIw-.08fc8412de69.js.br +0 -0
- codex/static_root/assets/to-case-B7MjLIw-.js +1 -0
- codex/static_root/assets/to-case-B7MjLIw-.js.br +0 -0
- codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js +1 -0
- codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js.br +0 -0
- codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js.gz +0 -0
- codex/static_root/assets/unauthorized-AnKtlgGy.js +1 -0
- codex/static_root/assets/unauthorized-AnKtlgGy.js.br +0 -0
- codex/static_root/assets/unauthorized-AnKtlgGy.js.gz +0 -0
- codex/static_root/assets/{unauthorized-DHV6Q1n-.b7847b2872a1.css → unauthorized-CibJYD8o.4c2de98c2b53.css} +1 -1
- codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
- codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
- codex/static_root/assets/{unauthorized-DHV6Q1n-.css → unauthorized-CibJYD8o.css} +1 -1
- codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
- codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
- codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js +1 -0
- codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js.br +0 -0
- codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js.gz +0 -0
- codex/static_root/assets/user-tab-BxtZ2u7W.js +1 -0
- codex/static_root/assets/user-tab-BxtZ2u7W.js.br +0 -0
- codex/static_root/assets/user-tab-BxtZ2u7W.js.gz +0 -0
- codex/static_root/js/choices-admin.3521e2a863df.json +1 -0
- codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
- codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
- codex/static_root/js/choices-admin.json +1 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/{choices.a0aaaa6c7ef7.json → choices.2f6b0359e1ef.json} +1 -1
- codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
- codex/static_root/js/choices.2f6b0359e1ef.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.3851c028b0cb.json +622 -0
- codex/static_root/manifest.3851c028b0cb.json.br +0 -0
- codex/static_root/manifest.3851c028b0cb.json.gz +0 -0
- codex/static_root/manifest.json +245 -252
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/util.py +8 -6
- codex/views/admin/api_key.py +5 -2
- codex/views/admin/stats.py +13 -20
- codex/views/admin/tasks.py +2 -3
- codex/views/auth.py +1 -1
- codex/views/bookmark.py +49 -29
- codex/views/browser/annotations.py +11 -45
- codex/views/browser/base.py +22 -6
- codex/views/browser/breadcrumbs.py +5 -5
- codex/views/browser/browser.py +39 -27
- codex/views/browser/choices.py +22 -37
- codex/views/browser/cover.py +55 -65
- codex/views/browser/filters/annotations.py +33 -10
- codex/views/browser/filters/group.py +24 -15
- codex/views/browser/filters/search.py +3 -4
- codex/views/browser/metadata.py +98 -49
- codex/views/browser/mtime.py +3 -14
- codex/views/browser/order_by.py +1 -6
- codex/views/browser/settings.py +53 -0
- codex/views/browser/validate.py +3 -0
- codex/views/const.py +25 -2
- codex/views/opds/v1/feed.py +3 -5
- codex/views/opds/v2/feed.py +3 -3
- codex/views/reader/arcs.py +42 -51
- codex/views/reader/books.py +47 -63
- codex/views/reader/init.py +125 -0
- codex/views/reader/reader.py +9 -70
- codex/views/session.py +18 -12
- codex/views/settings.py +23 -10
- codex/views/util.py +2 -1
- codex/websockets/consumers.py +14 -10
- {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/METADATA +1 -1
- {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/RECORD +332 -328
- codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js +0 -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-eNspVUje.js +0 -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-DLzF_WHB.9a47873b8548.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
- 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-BIO7lU_B.d607ada71b06.js +0 -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-BIO7lU_B.js +0 -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-87AymJC5.4a46716358c0.js +0 -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-87AymJC5.js +0 -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/VDialog-C5Nxd_zP.css.br +0 -0
- codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
- codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
- codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js +0 -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-CgA3shsd.js +0 -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-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-B--7K_v5.js.br +0 -0
- codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
- codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +0 -1
- 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 +0 -1
- 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-CVQ9wkE2.c02186cceba6.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
- 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-Ce0j2BKK.85e64443a928.js +0 -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-Ce0j2BKK.js +0 -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-CrWkhkiP.6d530eea0c09.js.br +0 -0
- codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
- 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-CkWbil3K.2cac13161dfe.js +0 -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-CkWbil3K.js +0 -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-7z55Y4lK.98eb7487e039.js +0 -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-7z55Y4lK.js +0 -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-D3OK784R.f69891726ec5.js +0 -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-D3OK784R.js +0 -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-1pnfvY8O.5438f252970e.js +0 -30
- 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-1pnfvY8O.js +0 -30
- 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/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-o87BwD38.css.gz +0 -0
- codex/static_root/assets/browser-BuU2x9y7.css +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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-CjZdzaIk.188c765431ab.js +0 -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-CjZdzaIk.js +0 -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/change-password-dialog-HvIvmmIE.73be0c8a8278.css +0 -1
- codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
- codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-HvIvmmIE.css +0 -1
- codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
- codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
- 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-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 +0 -1
- 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 +0 -1
- 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/filter-C-pghOri.eeb7c2ecf398.js +0 -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-C-pghOri.js +0 -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.br +0 -0
- codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -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-BONYWOc1.6ddf321a4a56.js.br +0 -0
- codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
- 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.br +0 -0
- codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -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-B419agXA.d4b778172aaa.js.br +0 -0
- codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
- 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 +0 -32
- 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 +0 -32
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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-8RVZqILx.2e7166dd26fb.js.br +0 -0
- codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
- 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.br +0 -0
- codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -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 +0 -2
- 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 +0 -2
- 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-PYua7aXv.428ba0b5861e.js +0 -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-PYua7aXv.js +0 -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-DbN6ISRH.d14f525d8eee.js +0 -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-DbN6ISRH.js +0 -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-DImNhH8O.d5026283841a.js +0 -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-DImNhH8O.js +0 -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/stats-tab-TsyG3wH_.b93c080356fc.css +0 -1
- codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +0 -2
- codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
- codex/static_root/assets/stats-tab-TsyG3wH_.css +0 -1
- codex/static_root/assets/stats-tab-TsyG3wH_.css.br +0 -2
- codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
- codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js +0 -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-DPD53kjv.js +0 -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 +0 -1
- codex/static_root/assets/to-case-ehC9Ccyj.js +0 -1
- codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js +0 -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-BOIx7Y18.js +0 -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/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
- codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
- codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
- codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
- codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js +0 -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-BABwfxLO.js +0 -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 +0 -1
- 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.a0aaaa6c7ef7.json.br +0 -0
- codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
- codex/static_root/manifest.cbea5663003a.json +0 -629
- codex/static_root/manifest.cbea5663003a.json.br +0 -0
- codex/static_root/manifest.cbea5663003a.json.gz +0 -0
- {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/LICENSE +0 -0
- {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/WHEEL +0 -0
- {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/entry_points.txt +0 -0
codex/views/browser/browser.py
CHANGED
|
@@ -16,9 +16,6 @@ from codex.models import (
|
|
|
16
16
|
Folder,
|
|
17
17
|
Imprint,
|
|
18
18
|
Library,
|
|
19
|
-
Publisher,
|
|
20
|
-
Series,
|
|
21
|
-
StoryArc,
|
|
22
19
|
Volume,
|
|
23
20
|
)
|
|
24
21
|
from codex.serializers.browser.page import BrowserPageSerializer
|
|
@@ -26,6 +23,7 @@ from codex.views.browser.title import BrowserTitleView
|
|
|
26
23
|
from codex.views.const import (
|
|
27
24
|
COMIC_GROUP,
|
|
28
25
|
FOLDER_GROUP,
|
|
26
|
+
NONE_DATETIMEFIELD,
|
|
29
27
|
STORY_ARC_GROUP,
|
|
30
28
|
)
|
|
31
29
|
|
|
@@ -34,6 +32,15 @@ if TYPE_CHECKING:
|
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
LOG = get_logger(__name__)
|
|
35
|
+
_GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
|
|
36
|
+
type[BrowserGroupModel], tuple[str | None, ...]
|
|
37
|
+
] = MappingProxyType(
|
|
38
|
+
{
|
|
39
|
+
Comic: ("series", "volume"),
|
|
40
|
+
Volume: ("series",),
|
|
41
|
+
Imprint: ("publisher",),
|
|
42
|
+
}
|
|
43
|
+
)
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
class BrowserView(BrowserTitleView):
|
|
@@ -48,19 +55,6 @@ class BrowserView(BrowserTitleView):
|
|
|
48
55
|
}
|
|
49
56
|
)
|
|
50
57
|
TARGET = "browser"
|
|
51
|
-
_GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
|
|
52
|
-
type[BrowserGroupModel], tuple[str | None, ...]
|
|
53
|
-
] = MappingProxyType(
|
|
54
|
-
{
|
|
55
|
-
Comic: ("series", "volume"),
|
|
56
|
-
Volume: ("series",),
|
|
57
|
-
Series: (None,),
|
|
58
|
-
Imprint: ("publisher",),
|
|
59
|
-
Publisher: (None,),
|
|
60
|
-
Folder: ("parent_folder",),
|
|
61
|
-
StoryArc: (None,),
|
|
62
|
-
}
|
|
63
|
-
)
|
|
64
58
|
|
|
65
59
|
def __init__(self, *args, **kwargs):
|
|
66
60
|
"""Set params for the type checker."""
|
|
@@ -75,7 +69,7 @@ class BrowserView(BrowserTitleView):
|
|
|
75
69
|
if pks and self.group_class:
|
|
76
70
|
try:
|
|
77
71
|
select_related: tuple[str | None, ...] = (
|
|
78
|
-
|
|
72
|
+
_GROUP_INSTANCE_SELECT_RELATED.get(self.group_class, (None,))
|
|
79
73
|
)
|
|
80
74
|
self.group_query = self.group_class.objects.select_related(
|
|
81
75
|
*select_related
|
|
@@ -156,13 +150,23 @@ class BrowserView(BrowserTitleView):
|
|
|
156
150
|
count = 0
|
|
157
151
|
return qs, count
|
|
158
152
|
|
|
159
|
-
def
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return
|
|
153
|
+
def _requery_max_bookmark_updated_at(self, group_qs):
|
|
154
|
+
"""Get max updated at without bookmark filter and aware of multi-groups."""
|
|
155
|
+
if not self.is_bookmark_filtered:
|
|
156
|
+
return group_qs
|
|
163
157
|
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(self.model)
|
|
159
|
+
bm_updated_at_rel = f"{bm_rel}__updated_at"
|
|
160
|
+
max_bmua = Max(bm_updated_at_rel, default=NONE_DATETIMEFIELD, filter=bm_filter)
|
|
161
|
+
|
|
162
|
+
group_list = []
|
|
163
|
+
for group in group_qs:
|
|
164
|
+
qs = self.get_filtered_queryset(
|
|
165
|
+
self.model, bookmark_filter=False, group_filter=False
|
|
166
|
+
)
|
|
167
|
+
group.max_bookmark_updated_at = qs.aggregate(max=max_bmua)["max"]
|
|
168
|
+
group_list.append(group)
|
|
169
|
+
return group_list
|
|
166
170
|
|
|
167
171
|
@staticmethod
|
|
168
172
|
def _get_zero_pad(book_qs):
|
|
@@ -175,6 +179,14 @@ class BrowserView(BrowserTitleView):
|
|
|
175
179
|
zero_pad += floor(log10(issue_number_max))
|
|
176
180
|
return zero_pad
|
|
177
181
|
|
|
182
|
+
def _get_page_mtime(self):
|
|
183
|
+
if not self.is_bookmark_filtered and self.group_instance:
|
|
184
|
+
# Nice optimization if we can get get it.
|
|
185
|
+
return self.group_instance.updated_at
|
|
186
|
+
|
|
187
|
+
group_model = self.group_class if self.group_class else self.model
|
|
188
|
+
return self.get_group_mtime(group_model, page_mtime=True)
|
|
189
|
+
|
|
178
190
|
def _get_group_and_books(self):
|
|
179
191
|
"""Create the main queries with filters, annotation and pagination."""
|
|
180
192
|
group_qs, group_count = self._get_group_queryset()
|
|
@@ -185,6 +197,7 @@ class BrowserView(BrowserTitleView):
|
|
|
185
197
|
)
|
|
186
198
|
if page_group_count:
|
|
187
199
|
group_qs = self.annotate_card_aggregates(group_qs, self.model)
|
|
200
|
+
group_qs = self._requery_max_bookmark_updated_at(group_qs)
|
|
188
201
|
if page_book_count:
|
|
189
202
|
zero_pad = self._get_zero_pad(book_qs)
|
|
190
203
|
book_qs = self.annotate_card_aggregates(book_qs, Comic)
|
|
@@ -205,7 +218,7 @@ class BrowserView(BrowserTitleView):
|
|
|
205
218
|
)
|
|
206
219
|
|
|
207
220
|
# get additional context
|
|
208
|
-
|
|
221
|
+
breadcrumbs = self.get_breadcrumbs()
|
|
209
222
|
title = self.get_browser_page_title()
|
|
210
223
|
# needs to happen after pagination
|
|
211
224
|
# runs obj list query twice :/
|
|
@@ -214,7 +227,7 @@ class BrowserView(BrowserTitleView):
|
|
|
214
227
|
# construct final data structure
|
|
215
228
|
return MappingProxyType(
|
|
216
229
|
{
|
|
217
|
-
"breadcrumbs":
|
|
230
|
+
"breadcrumbs": breadcrumbs,
|
|
218
231
|
"title": title,
|
|
219
232
|
"model_group": self.model_group,
|
|
220
233
|
"groups": group_qs,
|
|
@@ -228,11 +241,10 @@ class BrowserView(BrowserTitleView):
|
|
|
228
241
|
}
|
|
229
242
|
)
|
|
230
243
|
|
|
231
|
-
@extend_schema(
|
|
244
|
+
@extend_schema(parameters=[BrowserTitleView.input_serializer_class])
|
|
232
245
|
def get(self, *_args, **_kwargs):
|
|
233
246
|
"""Get browser settings."""
|
|
234
247
|
self.init_request()
|
|
235
248
|
data = self.get_object()
|
|
236
249
|
serializer = self.get_serializer(data)
|
|
237
|
-
self.save_params_to_session(self.params)
|
|
238
250
|
return Response(serializer.data)
|
codex/views/browser/choices.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
|
|
5
|
-
import pycountry
|
|
6
5
|
from caseconverter import snakecase
|
|
7
6
|
from django.db.models import QuerySet
|
|
8
7
|
from drf_spectacular.utils import extend_schema
|
|
@@ -15,12 +14,14 @@ from codex.models import (
|
|
|
15
14
|
StoryArc,
|
|
16
15
|
)
|
|
17
16
|
from codex.models.named import IdentifierType
|
|
18
|
-
from codex.serializers.browser.choices import
|
|
17
|
+
from codex.serializers.browser.choices import (
|
|
18
|
+
BrowserChoicesFilterSerializer,
|
|
19
|
+
)
|
|
19
20
|
from codex.serializers.browser.filters import (
|
|
20
21
|
BrowserFilterChoicesSerializer,
|
|
21
|
-
CharListField,
|
|
22
22
|
)
|
|
23
|
-
from codex.serializers.
|
|
23
|
+
from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
|
|
24
|
+
from codex.serializers.choices import DUMMY_NULL_NAME, VUETIFY_NULL_CODE
|
|
24
25
|
from codex.views.browser.filters.annotations import (
|
|
25
26
|
BrowserAnnotationsFilterView,
|
|
26
27
|
)
|
|
@@ -50,15 +51,21 @@ _BACK_REL_MAP = MappingProxyType(
|
|
|
50
51
|
IdentifierType: "identifier__",
|
|
51
52
|
}
|
|
52
53
|
)
|
|
53
|
-
_NULL_NAMED_ROW = MappingProxyType({"pk":
|
|
54
|
+
_NULL_NAMED_ROW = MappingProxyType({"pk": VUETIFY_NULL_CODE, "name": DUMMY_NULL_NAME})
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
57
58
|
"""Get choices for filter dialog."""
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
input_serializer_class = BrowserFilterChoicesInputSerilalizer
|
|
60
61
|
TARGET = "choices"
|
|
61
62
|
|
|
63
|
+
def init_request(self):
|
|
64
|
+
"""Initialieze request."""
|
|
65
|
+
self.parse_params()
|
|
66
|
+
self.set_model()
|
|
67
|
+
self.set_rel_prefix()
|
|
68
|
+
|
|
62
69
|
@staticmethod
|
|
63
70
|
def get_field_choices_query(comic_qs, field_name):
|
|
64
71
|
"""Get distinct values for the field."""
|
|
@@ -97,12 +104,12 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
|
97
104
|
"""Get the comic subquery use for the choices."""
|
|
98
105
|
return self.get_filtered_queryset(Comic)
|
|
99
106
|
|
|
100
|
-
@extend_schema(
|
|
107
|
+
@extend_schema(parameters=[input_serializer_class])
|
|
101
108
|
def get(self, *_args, **_kwargs):
|
|
102
109
|
"""Return choices."""
|
|
103
110
|
self.init_request()
|
|
104
111
|
obj = self.get_object()
|
|
105
|
-
serializer = self.get_serializer(obj
|
|
112
|
+
serializer = self.get_serializer(obj)
|
|
106
113
|
return Response(serializer.data)
|
|
107
114
|
|
|
108
115
|
|
|
@@ -160,28 +167,7 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
|
|
|
160
167
|
class BrowserChoicesView(BrowserChoicesViewBase):
|
|
161
168
|
"""Get choices for filter dialog."""
|
|
162
169
|
|
|
163
|
-
|
|
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()
|
|
167
|
-
|
|
168
|
-
def _get_field_choices(self, comic_qs, field_name):
|
|
169
|
-
"""Create a pk:name object for fields without tables."""
|
|
170
|
-
qs = self.get_field_choices_query(comic_qs, field_name)
|
|
171
|
-
|
|
172
|
-
if field_name == "country":
|
|
173
|
-
lookup = pycountry.countries
|
|
174
|
-
elif field_name == "language":
|
|
175
|
-
lookup = pycountry.languages
|
|
176
|
-
else:
|
|
177
|
-
lookup = None
|
|
178
|
-
|
|
179
|
-
choices = []
|
|
180
|
-
for val in qs:
|
|
181
|
-
name = PyCountrySerializer.lookup_name(lookup, val) if lookup else val
|
|
182
|
-
choices.append({"pk": val, "name": name})
|
|
183
|
-
|
|
184
|
-
return choices
|
|
170
|
+
serializer_class = BrowserChoicesFilterSerializer
|
|
185
171
|
|
|
186
172
|
def _get_m2m_field_choices(self, model, comic_qs, rel):
|
|
187
173
|
"""Get choices with nulls where there are nulls."""
|
|
@@ -202,11 +188,6 @@ class BrowserChoicesView(BrowserChoicesViewBase):
|
|
|
202
188
|
field_name = self.kwargs.get("field_name", "")
|
|
203
189
|
return snakecase(field_name)
|
|
204
190
|
|
|
205
|
-
def get_serializer_class(self): # type: ignore
|
|
206
|
-
"""Dynamic serializer class."""
|
|
207
|
-
field_name = self._get_field_name()
|
|
208
|
-
return CHOICES_SERIALIZER_CLASS_MAP.get(field_name, CharListField)
|
|
209
|
-
|
|
210
191
|
def get_object(self):
|
|
211
192
|
"""Return choices with more than one choice."""
|
|
212
193
|
qs = super().get_object()
|
|
@@ -216,5 +197,9 @@ class BrowserChoicesView(BrowserChoicesViewBase):
|
|
|
216
197
|
if m2m_model:
|
|
217
198
|
choices = self._get_m2m_field_choices(m2m_model, qs, rel)
|
|
218
199
|
else:
|
|
219
|
-
choices = self.
|
|
220
|
-
|
|
200
|
+
choices = self.get_field_choices_query(qs, field_name)
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"field_name": field_name,
|
|
204
|
+
"choices": choices,
|
|
205
|
+
}
|
codex/views/browser/cover.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Comic cover thumbnail view."""
|
|
2
2
|
|
|
3
|
+
from django.db.models.query import Q
|
|
3
4
|
from django.http import HttpResponse
|
|
4
5
|
from drf_spectacular.types import OpenApiTypes
|
|
5
6
|
from drf_spectacular.utils import extend_schema
|
|
@@ -9,11 +10,12 @@ from codex.librarian.covers.create import CoverCreateMixin
|
|
|
9
10
|
from codex.librarian.covers.path import CoverPathMixin
|
|
10
11
|
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
11
12
|
from codex.logger.logging import get_logger
|
|
12
|
-
from codex.models import Comic,
|
|
13
|
+
from codex.models import Comic, Volume
|
|
13
14
|
from codex.models.paths import CustomCover
|
|
14
|
-
from codex.serializers.
|
|
15
|
+
from codex.serializers.browser.settings import BrowserCoverInputSerializer
|
|
15
16
|
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
16
17
|
from codex.views.const import (
|
|
18
|
+
CUSTOM_COVER_GROUP_RELATION,
|
|
17
19
|
GROUP_RELATION,
|
|
18
20
|
MISSING_COVER_FN,
|
|
19
21
|
MISSING_COVER_NAME_MAP,
|
|
@@ -39,9 +41,34 @@ class WEBPRenderer(BaseRenderer):
|
|
|
39
41
|
class CoverView(BrowserAnnotationsView):
|
|
40
42
|
"""ComicCover View."""
|
|
41
43
|
|
|
44
|
+
input_serializer_class = BrowserCoverInputSerializer
|
|
42
45
|
renderer_classes = (WEBPRenderer,)
|
|
43
46
|
content_type = "image/webp"
|
|
44
47
|
TARGET = "cover"
|
|
48
|
+
REPARSE_JSON_FIELDS = frozenset(
|
|
49
|
+
BrowserAnnotationsView.REPARSE_JSON_FIELDS | {"parent"}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def get_group_filter(self, group=None, pks=None, page_mtime=False):
|
|
53
|
+
"""Get group filter for First Cover View."""
|
|
54
|
+
if self.params.get("dynamic_covers"):
|
|
55
|
+
return super().get_group_filter(group=group, pks=pks, page_mtime=page_mtime)
|
|
56
|
+
|
|
57
|
+
# First cover group filter relies on sort names to look outside the browser supplied pks
|
|
58
|
+
# For multi_groups not in the browser query.
|
|
59
|
+
pks = self.kwargs["pks"]
|
|
60
|
+
name_rel = "name" if self.model == Volume else "sort_name"
|
|
61
|
+
qs = self.model.objects.filter(pk__in=pks) # type: ignore
|
|
62
|
+
sort_names = qs.values_list(name_rel, flat=True).distinct()
|
|
63
|
+
model_rel = GROUP_RELATION[self.model_group]
|
|
64
|
+
group_filter = {f"{model_rel}__{name_rel}__in": sort_names}
|
|
65
|
+
|
|
66
|
+
parent = self.params["parent"]
|
|
67
|
+
parent_pks = parent.get("pks", ())
|
|
68
|
+
if parent_pks:
|
|
69
|
+
parent_rel = GROUP_RELATION[parent["group"]]
|
|
70
|
+
group_filter[f"{parent_rel}__pk__in"] = parent_pks
|
|
71
|
+
return Q(**group_filter)
|
|
45
72
|
|
|
46
73
|
def get_model_group(self):
|
|
47
74
|
"""Return the url group."""
|
|
@@ -56,39 +83,21 @@ class CoverView(BrowserAnnotationsView):
|
|
|
56
83
|
pks = self.kwargs["pks"]
|
|
57
84
|
return pks[0], False
|
|
58
85
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
86
|
+
def _get_custom_cover(self):
|
|
87
|
+
"""Get Custom Cover."""
|
|
88
|
+
if self.model == Volume or not self.params.get("custom_covers"):
|
|
89
|
+
return None
|
|
90
|
+
group = self.kwargs["group"]
|
|
91
|
+
group_rel = CUSTOM_COVER_GROUP_RELATION[group]
|
|
92
|
+
pks = self.kwargs["pks"]
|
|
93
|
+
comic_filter = {f"{group_rel}__in": pks}
|
|
94
|
+
qs = CustomCover.objects.filter(**comic_filter)
|
|
95
|
+
qs = qs.only("pk")
|
|
96
|
+
return qs.first()
|
|
78
97
|
|
|
79
98
|
def _get_dynamic_cover(self):
|
|
80
99
|
"""Get dynamic cover."""
|
|
81
100
|
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
101
|
comic_qs = self.get_filtered_queryset(Comic)
|
|
93
102
|
comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
|
|
94
103
|
comic_qs = self.add_order_by(comic_qs, Comic)
|
|
@@ -99,40 +108,15 @@ class CoverView(BrowserAnnotationsView):
|
|
|
99
108
|
cover_pk = comic.pk if comic else 0
|
|
100
109
|
return cover_pk, False
|
|
101
110
|
|
|
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
111
|
def _get_cover_pk(self) -> tuple[int, bool]:
|
|
129
112
|
"""Get Cover Pk queryset for comic queryset."""
|
|
130
113
|
if self.model == Comic:
|
|
131
114
|
cover_pk, custom = self._get_comic_cover()
|
|
132
|
-
elif self.
|
|
133
|
-
cover_pk
|
|
115
|
+
elif custom_cover := self._get_custom_cover():
|
|
116
|
+
cover_pk = custom_cover.pk
|
|
117
|
+
custom = True
|
|
134
118
|
else:
|
|
135
|
-
cover_pk, custom = self.
|
|
119
|
+
cover_pk, custom = self._get_dynamic_cover()
|
|
136
120
|
return cover_pk, custom
|
|
137
121
|
|
|
138
122
|
def _get_missing_cover_path(self):
|
|
@@ -168,10 +152,16 @@ class CoverView(BrowserAnnotationsView):
|
|
|
168
152
|
thumb_image_data = f.read()
|
|
169
153
|
return thumb_image_data, content_type
|
|
170
154
|
|
|
171
|
-
@extend_schema(
|
|
155
|
+
@extend_schema(
|
|
156
|
+
parameters=[BrowserAnnotationsView.input_serializer_class],
|
|
157
|
+
responses={(200, content_type): OpenApiTypes.BINARY},
|
|
158
|
+
)
|
|
172
159
|
def get(self, *args, **kwargs): # type: ignore
|
|
173
160
|
"""Get comic cover."""
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
161
|
+
try:
|
|
162
|
+
self.init_request()
|
|
163
|
+
pk, custom = self._get_cover_pk()
|
|
164
|
+
thumb_image_data, content_type = self._get_cover_data(pk, custom)
|
|
165
|
+
return HttpResponse(thumb_image_data, content_type=content_type)
|
|
166
|
+
except Exception:
|
|
167
|
+
LOG.exception("get")
|
|
@@ -7,7 +7,7 @@ from codex.models.comic import Comic
|
|
|
7
7
|
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
8
8
|
from codex.views.browser.validate import BrowserValidateView
|
|
9
9
|
from codex.views.const import (
|
|
10
|
-
|
|
10
|
+
EPOCH_START_DATETIMEFIELD,
|
|
11
11
|
ONE_INTEGERFIELD,
|
|
12
12
|
)
|
|
13
13
|
|
|
@@ -20,13 +20,17 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
|
20
20
|
super().__init__(*args, **kwargs)
|
|
21
21
|
self.init_bookmark_data()
|
|
22
22
|
|
|
23
|
-
def _get_query_filters(
|
|
23
|
+
def _get_query_filters( # noqa: PLR0913
|
|
24
|
+
self, model, group, pks, bookmark_filter, page_mtime=False, group_filter=True
|
|
25
|
+
):
|
|
24
26
|
"""Return all the filters except the group filter."""
|
|
27
|
+
# TODO all these ifs and flags are awful
|
|
25
28
|
object_filter = self.get_group_acl_filter(model)
|
|
26
|
-
|
|
29
|
+
if group_filter:
|
|
30
|
+
object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
|
|
27
31
|
object_filter &= self.get_comic_field_filter(model)
|
|
28
32
|
if bookmark_filter:
|
|
29
|
-
# not needed
|
|
33
|
+
# not needed when used by outerrefl OuterRef is applied next
|
|
30
34
|
object_filter &= self.get_bookmark_filter(model)
|
|
31
35
|
return object_filter
|
|
32
36
|
|
|
@@ -45,17 +49,36 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
|
45
49
|
qs = qs.filter(**{f"{ann_name}__gt": 0})
|
|
46
50
|
return qs
|
|
47
51
|
|
|
48
|
-
def get_filtered_queryset(
|
|
52
|
+
def get_filtered_queryset( # noqa: PLR0913
|
|
53
|
+
self,
|
|
54
|
+
model,
|
|
55
|
+
group=None,
|
|
56
|
+
pks=None,
|
|
57
|
+
bookmark_filter=True,
|
|
58
|
+
page_mtime=False,
|
|
59
|
+
group_filter=True,
|
|
60
|
+
):
|
|
49
61
|
"""Get a filtered queryset for the model."""
|
|
50
|
-
object_filter = self._get_query_filters(
|
|
62
|
+
object_filter = self._get_query_filters(
|
|
63
|
+
model,
|
|
64
|
+
group,
|
|
65
|
+
pks,
|
|
66
|
+
bookmark_filter,
|
|
67
|
+
page_mtime=page_mtime,
|
|
68
|
+
group_filter=group_filter,
|
|
69
|
+
)
|
|
51
70
|
qs = model.objects.filter(object_filter)
|
|
52
71
|
qs = self.apply_search_filter(qs, model)
|
|
53
72
|
return self._filter_by_child_count(qs, model)
|
|
54
73
|
|
|
55
|
-
def get_group_mtime(self, model, group=None, pks=None):
|
|
74
|
+
def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
|
|
56
75
|
"""Get a filtered mtime for browser pages and mtime checker."""
|
|
57
76
|
qs = self.get_filtered_queryset(
|
|
58
|
-
model,
|
|
77
|
+
model,
|
|
78
|
+
group=group,
|
|
79
|
+
pks=pks,
|
|
80
|
+
bookmark_filter=self.is_bookmark_filtered,
|
|
81
|
+
page_mtime=page_mtime,
|
|
59
82
|
)
|
|
60
83
|
qs = qs.annotate(max_updated_at=Max("updated_at"))
|
|
61
84
|
|
|
@@ -65,12 +88,12 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
|
65
88
|
ua_rel = bm_rel + "__updated_at"
|
|
66
89
|
mbua = Max(ua_rel)
|
|
67
90
|
else:
|
|
68
|
-
mbua =
|
|
91
|
+
mbua = EPOCH_START_DATETIMEFIELD
|
|
69
92
|
qs = qs.annotate(max_bookmark_updated_at=mbua)
|
|
70
93
|
|
|
71
94
|
mtime = qs.aggregate(
|
|
72
95
|
max=Greatest(
|
|
73
|
-
Coalesce("max_bookmark_updated_at",
|
|
96
|
+
Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFIELD),
|
|
74
97
|
"max_updated_at",
|
|
75
98
|
)
|
|
76
99
|
)["max"]
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db.models import Q
|
|
4
4
|
|
|
5
|
-
from codex.
|
|
6
|
-
|
|
5
|
+
from codex.views.const import (
|
|
6
|
+
FILTER_ONLY_GROUP_RELATION,
|
|
7
|
+
FOLDER_GROUP,
|
|
8
|
+
GROUP_RELATION,
|
|
9
|
+
)
|
|
7
10
|
from codex.views.session import SessionView
|
|
8
11
|
|
|
9
12
|
|
|
@@ -12,25 +15,31 @@ class GroupFilterView(SessionView):
|
|
|
12
15
|
|
|
13
16
|
SESSION_KEY = SessionView.BROWSER_SESSION_KEY
|
|
14
17
|
|
|
15
|
-
def
|
|
18
|
+
def _get_rel_for_pks(self, group, page_mtime=False):
|
|
19
|
+
"""Get the relation from the model to the pks."""
|
|
20
|
+
# XXX these TARGET refs might be better as subclass get rel methods.
|
|
21
|
+
target: str = self.TARGET # type: ignore
|
|
22
|
+
if target in ("metadata", "mtime") or page_mtime:
|
|
23
|
+
# metadata, mtime, browser.page_mtime
|
|
24
|
+
rel = "pk"
|
|
25
|
+
elif target in ("choices", "cover"):
|
|
26
|
+
rel = FILTER_ONLY_GROUP_RELATION[group]
|
|
27
|
+
else:
|
|
28
|
+
rel = GROUP_RELATION[group]
|
|
29
|
+
|
|
30
|
+
rel += "__in"
|
|
31
|
+
return rel
|
|
32
|
+
|
|
33
|
+
def get_group_filter(self, group=None, pks=None, page_mtime=False):
|
|
16
34
|
"""Get filter for the displayed group."""
|
|
17
35
|
if group is None:
|
|
18
36
|
group = self.kwargs["group"]
|
|
19
37
|
if pks is None:
|
|
20
38
|
pks = self.kwargs["pks"]
|
|
21
39
|
|
|
22
|
-
if pks and 0 not in pks:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rel = "pk"
|
|
26
|
-
elif model == Comic and group == FOLDER_GROUP:
|
|
27
|
-
# choices & covers
|
|
28
|
-
rel = "folders"
|
|
29
|
-
else:
|
|
30
|
-
rel = GROUP_RELATION[group]
|
|
31
|
-
|
|
32
|
-
rel += "__in"
|
|
33
|
-
group_filter_dict = {rel: pks} # type: ignore
|
|
40
|
+
if pks and 0 not in pks:
|
|
41
|
+
rel = self._get_rel_for_pks(group, page_mtime)
|
|
42
|
+
group_filter_dict = {rel: pks}
|
|
34
43
|
elif group == FOLDER_GROUP:
|
|
35
44
|
group_filter_dict = {"parent_folder": None}
|
|
36
45
|
else:
|
|
@@ -135,11 +135,10 @@ class SearchFilterView(ComicFieldFilterView):
|
|
|
135
135
|
if comic_ids:
|
|
136
136
|
# Prefilter comic ids. If nothing is allowed, don't search.
|
|
137
137
|
sqs = sqs.filter_comic_ids(comic_ids)
|
|
138
|
-
|
|
138
|
+
if (
|
|
139
139
|
self.TARGET in frozenset({"cover", "choices"})
|
|
140
|
-
or self.params.get("order_by", "") != "search_score"
|
|
141
|
-
)
|
|
142
|
-
if binary:
|
|
140
|
+
or self.params.get("order_by", "") != "search_score" # type: ignore
|
|
141
|
+
):
|
|
143
142
|
scored_pks = self._get_binary_search_scores(sqs)
|
|
144
143
|
else:
|
|
145
144
|
score_pairs, prev_pks, next_pks, scored_pks = (
|