codex 1.6.0a9__py3-none-any.whl → 1.6.0a10__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 +22 -6
- codex/serializers/browser/metadata.py +45 -1
- codex/serializers/browser/page.py +2 -4
- codex/serializers/browser/settings.py +34 -14
- codex/serializers/fields.py +19 -1
- codex/serializers/reader.py +11 -1
- codex/serializers/route.py +1 -1
- codex/serializers/settings.py +12 -0
- codex/settings/settings.py +10 -1
- codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js +1 -0
- codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.br +0 -0
- codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.gz +0 -0
- codex/static_root/assets/VCheckbox-CY3rcqlS.js +1 -0
- codex/static_root/assets/VCheckbox-CY3rcqlS.js.br +0 -0
- codex/static_root/assets/VCheckbox-CY3rcqlS.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.9a47873b8548.js → VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.js → VCheckboxBtn-D2tPTt6P.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.gz +0 -0
- codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js +1 -0
- codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.br +0 -0
- codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.gz +0 -0
- codex/static_root/assets/VCombobox-aJr--Ou7.js +1 -0
- codex/static_root/assets/VCombobox-aJr--Ou7.js.br +0 -0
- codex/static_root/assets/VCombobox-aJr--Ou7.js.gz +0 -0
- codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js +1 -0
- codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.br +0 -0
- codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.gz +0 -0
- codex/static_root/assets/VDialog-61pzfUB2.js +1 -0
- codex/static_root/assets/VDialog-61pzfUB2.js.br +0 -0
- codex/static_root/assets/VDialog-61pzfUB2.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-rewm0P4z.5c7680f815b6.js +1 -0
- codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-rewm0P4z.js +1 -0
- codex/static_root/assets/VExpansionPanels-rewm0P4z.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-rewm0P4z.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-B--7K_v5.4a4e9c2d6c42.js → VRadioGroup-QFQbK1qo.750a42b1b814.js} +1 -1
- codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.br +0 -0
- codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-B--7K_v5.js → VRadioGroup-QFQbK1qo.js} +1 -1
- codex/static_root/assets/VRadioGroup-QFQbK1qo.js.br +0 -0
- codex/static_root/assets/VRadioGroup-QFQbK1qo.js.gz +0 -0
- codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js +1 -0
- codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.br +0 -0
- codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.gz +0 -0
- codex/static_root/assets/VSelect-Dixy6okt.js +1 -0
- codex/static_root/assets/VSelect-Dixy6okt.js.br +0 -0
- codex/static_root/assets/VSelect-Dixy6okt.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-CVQ9wkE2.c02186cceba6.js → VSelectionControl-MNmxKc3J.08055d00917e.js} +1 -1
- codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.br +0 -0
- codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-CVQ9wkE2.js → VSelectionControl-MNmxKc3J.js} +1 -1
- codex/static_root/assets/VSelectionControl-MNmxKc3J.js.br +0 -0
- codex/static_root/assets/VSelectionControl-MNmxKc3J.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js +1 -0
- codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-DZEV0FoV.js +1 -0
- codex/static_root/assets/VSlideGroup-DZEV0FoV.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DZEV0FoV.js.gz +0 -0
- codex/static_root/assets/{VTable-CrWkhkiP.6d530eea0c09.js → VTable-lcnYg9Ee.d72927b13c2e.js} +1 -1
- codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.br +0 -0
- codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.gz +0 -0
- codex/static_root/assets/{VTable-CrWkhkiP.js → VTable-lcnYg9Ee.js} +1 -1
- codex/static_root/assets/VTable-lcnYg9Ee.js.br +0 -0
- codex/static_root/assets/VTable-lcnYg9Ee.js.gz +0 -0
- codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js +1 -0
- codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.br +0 -0
- codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.gz +0 -0
- codex/static_root/assets/VTextField-BkFrjhdV.js +1 -0
- codex/static_root/assets/VTextField-BkFrjhdV.js.br +0 -0
- codex/static_root/assets/VTextField-BkFrjhdV.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js +1 -0
- codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.br +0 -0
- codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C__tAqdc.js +1 -0
- codex/static_root/assets/VWindowItem-C__tAqdc.js.br +0 -0
- codex/static_root/assets/VWindowItem-C__tAqdc.js.gz +0 -0
- codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js +1 -0
- codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.br +0 -0
- codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.gz +0 -0
- codex/static_root/assets/admin-CXES2HXe.js +1 -0
- codex/static_root/assets/admin-CXES2HXe.js.br +0 -0
- codex/static_root/assets/admin-CXES2HXe.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-drawer-panel-rrNKi_GE.e2b034db7934.js +30 -0
- codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js +30 -0
- codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.gz +0 -0
- codex/static_root/assets/browser-BuDeXGEe.css +1 -0
- codex/static_root/assets/browser-BuDeXGEe.css.br +0 -0
- codex/static_root/assets/browser-BuDeXGEe.css.gz +0 -0
- codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css +1 -0
- codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.br +0 -0
- codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.gz +0 -0
- codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js +1 -0
- codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.br +0 -0
- codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.gz +0 -0
- codex/static_root/assets/browser-s6mFzrK9.js +1 -0
- codex/static_root/assets/browser-s6mFzrK9.js.br +0 -0
- codex/static_root/assets/browser-s6mFzrK9.js.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-Bn20jYzD.593c5af1732b.js +1 -0
- codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.br +0 -0
- codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-Bn20jYzD.js +1 -0
- codex/static_root/assets/change-password-dialog-Bn20jYzD.js.br +0 -0
- codex/static_root/assets/change-password-dialog-Bn20jYzD.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-jwL7B98r.1cfd579cbcfe.js → confirm-dialog-CWszioQE.859051c35dd9.js} +1 -1
- codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-jwL7B98r.js → confirm-dialog-CWszioQE.js} +1 -1
- codex/static_root/assets/confirm-dialog-CWszioQE.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CWszioQE.js.gz +0 -0
- codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js +1 -0
- codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.br +0 -0
- codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.gz +0 -0
- codex/static_root/assets/datetime-column-BSdlLt-4.js +1 -0
- codex/static_root/assets/datetime-column-BSdlLt-4.js.br +0 -0
- codex/static_root/assets/datetime-column-BSdlLt-4.js.gz +0 -0
- codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js +1 -0
- codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.br +0 -0
- codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.gz +0 -0
- codex/static_root/assets/filter-D3gaGf87.js +1 -0
- codex/static_root/assets/filter-D3gaGf87.js.br +0 -0
- codex/static_root/assets/filter-D3gaGf87.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Da2DBNyz.ffbe5ca37e1c.js → flag-tab-8vzpdySX.a982c7f0776a.js} +1 -1
- codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.br +0 -0
- codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Da2DBNyz.js → flag-tab-8vzpdySX.js} +1 -1
- codex/static_root/assets/flag-tab-8vzpdySX.js.br +0 -0
- codex/static_root/assets/flag-tab-8vzpdySX.js.gz +0 -0
- codex/static_root/assets/{group-tab-BONYWOc1.6ddf321a4a56.js → group-tab-Cz9t_ECi.a9e8aea79254.js} +1 -1
- codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.br +0 -0
- codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.gz +0 -0
- codex/static_root/assets/{group-tab-BONYWOc1.js → group-tab-Cz9t_ECi.js} +1 -1
- codex/static_root/assets/group-tab-Cz9t_ECi.js.br +0 -0
- codex/static_root/assets/group-tab-Cz9t_ECi.js.gz +0 -0
- codex/static_root/assets/{http-error-D2Jnc20C.32846bc3a43d.js → http-error-Cqtszzf1.b31df3d9c29f.js} +1 -1
- codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.br +0 -0
- codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.gz +0 -0
- codex/static_root/assets/{http-error-D2Jnc20C.js → http-error-Cqtszzf1.js} +1 -1
- codex/static_root/assets/http-error-Cqtszzf1.js.br +0 -0
- codex/static_root/assets/http-error-Cqtszzf1.js.gz +0 -0
- codex/static_root/assets/{library-tab-B419agXA.js → library-tab-DMlnswUU.30f454e84184.js} +1 -1
- codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.br +0 -0
- codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.gz +0 -0
- codex/static_root/assets/{library-tab-B419agXA.d4b778172aaa.js → library-tab-DMlnswUU.js} +1 -1
- codex/static_root/assets/library-tab-DMlnswUU.js.br +0 -0
- codex/static_root/assets/library-tab-DMlnswUU.js.gz +0 -0
- codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js +32 -0
- codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.br +0 -0
- codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.gz +0 -0
- codex/static_root/assets/main-vjXu_QYL.js +32 -0
- codex/static_root/assets/main-vjXu_QYL.js.br +0 -0
- codex/static_root/assets/main-vjXu_QYL.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css +1 -0
- codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DGdMiBRs.css +1 -0
- codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js +1 -0
- codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-JW78pa0q.js +1 -0
- codex/static_root/assets/pagination-toolbar-JW78pa0q.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JW78pa0q.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-8RVZqILx.2e7166dd26fb.js → pdf-doc-BiamrjRZ.4c9be1ade43c.js} +1 -1
- codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.br +0 -0
- codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-8RVZqILx.js → pdf-doc-BiamrjRZ.js} +1 -1
- codex/static_root/assets/pdf-doc-BiamrjRZ.js.br +0 -0
- codex/static_root/assets/pdf-doc-BiamrjRZ.js.gz +0 -0
- codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-BpsctRsY.5d72e393911a.css} +1 -1
- codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.br +0 -0
- codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.gz +0 -0
- codex/static_root/assets/{reader-CCCfpM3x.css → reader-BpsctRsY.css} +1 -1
- codex/static_root/assets/reader-BpsctRsY.css.br +0 -0
- codex/static_root/assets/reader-BpsctRsY.css.gz +0 -0
- codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js +2 -0
- codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.br +0 -0
- codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.gz +0 -0
- codex/static_root/assets/reader-CDk8oZzB.js +2 -0
- codex/static_root/assets/reader-CDk8oZzB.js.br +0 -0
- codex/static_root/assets/reader-CDk8oZzB.js.gz +0 -0
- codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js +1 -0
- codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.br +0 -0
- codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.gz +0 -0
- codex/static_root/assets/relation-chips-C5acBjpy.js +1 -0
- codex/static_root/assets/relation-chips-C5acBjpy.js.br +0 -0
- codex/static_root/assets/relation-chips-C5acBjpy.js.gz +0 -0
- codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js +2 -0
- codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.br +0 -0
- codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.gz +0 -0
- codex/static_root/assets/settings-drawer-D_JuRi7F.js +2 -0
- codex/static_root/assets/settings-drawer-D_JuRi7F.js.br +0 -0
- codex/static_root/assets/settings-drawer-D_JuRi7F.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-ClqRTk1z.c759ace65a87.js +1 -0
- codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.br +0 -0
- codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.gz +0 -0
- codex/static_root/assets/stats-tab-ClqRTk1z.js +1 -0
- codex/static_root/assets/stats-tab-ClqRTk1z.js.br +0 -0
- codex/static_root/assets/stats-tab-ClqRTk1z.js.gz +0 -0
- codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js +1 -0
- codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.br +0 -0
- codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.gz +0 -0
- codex/static_root/assets/task-tab-DF9qa6LO.js +1 -0
- codex/static_root/assets/task-tab-DF9qa6LO.js.br +0 -0
- codex/static_root/assets/task-tab-DF9qa6LO.js.gz +0 -0
- codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js +1 -0
- codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js.br +0 -0
- codex/static_root/assets/to-case-CGbULRaE.js +1 -0
- codex/static_root/assets/to-case-CGbULRaE.js.br +0 -0
- codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js +1 -0
- codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.br +0 -0
- codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.gz +0 -0
- codex/static_root/assets/unauthorized-BjKuLH6k.js +1 -0
- codex/static_root/assets/unauthorized-BjKuLH6k.js.br +0 -0
- codex/static_root/assets/unauthorized-BjKuLH6k.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-Dpxe3ygA.7d90174631f0.js +1 -0
- codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.br +0 -0
- codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.gz +0 -0
- codex/static_root/assets/user-tab-Dpxe3ygA.js +1 -0
- codex/static_root/assets/user-tab-Dpxe3ygA.js.br +0 -0
- codex/static_root/assets/user-tab-Dpxe3ygA.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/manifest.35e46a3ec8fa.json +622 -0
- codex/static_root/manifest.35e46a3ec8fa.json.br +0 -0
- codex/static_root/manifest.35e46a3ec8fa.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/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 +30 -16
- codex/views/browser/annotations.py +2 -0
- codex/views/browser/base.py +22 -6
- codex/views/browser/breadcrumbs.py +5 -5
- codex/views/browser/browser.py +14 -22
- codex/views/browser/choices.py +18 -15
- codex/views/browser/cover.py +30 -66
- codex/views/browser/filters/annotations.py +14 -6
- codex/views/browser/filters/group.py +24 -15
- codex/views/browser/filters/search.py +3 -4
- codex/views/browser/metadata.py +86 -47
- 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 +24 -1
- 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 +121 -0
- codex/views/reader/reader.py +9 -70
- codex/views/session.py +15 -11
- codex/views/settings.py +23 -10
- codex/views/util.py +2 -1
- codex/websockets/consumers.py +13 -9
- {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/METADATA +1 -1
- {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/RECORD +321 -317
- 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/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.0a10.dist-info}/LICENSE +0 -0
- {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/WHEEL +0 -0
- {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/entry_points.txt +0 -0
codex/views/browser/cover.py
CHANGED
|
@@ -9,12 +9,12 @@ from codex.librarian.covers.create import CoverCreateMixin
|
|
|
9
9
|
from codex.librarian.covers.path import CoverPathMixin
|
|
10
10
|
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
11
11
|
from codex.logger.logging import get_logger
|
|
12
|
-
from codex.models import Comic,
|
|
12
|
+
from codex.models import Comic, Volume
|
|
13
13
|
from codex.models.paths import CustomCover
|
|
14
|
-
from codex.serializers.
|
|
14
|
+
from codex.serializers.browser.settings import BrowserCoverInputSerializer
|
|
15
15
|
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
16
16
|
from codex.views.const import (
|
|
17
|
-
|
|
17
|
+
CUSTOM_COVER_GROUP_RELATION,
|
|
18
18
|
MISSING_COVER_FN,
|
|
19
19
|
MISSING_COVER_NAME_MAP,
|
|
20
20
|
STATIC_IMG_PATH,
|
|
@@ -39,6 +39,7 @@ class WEBPRenderer(BaseRenderer):
|
|
|
39
39
|
class CoverView(BrowserAnnotationsView):
|
|
40
40
|
"""ComicCover View."""
|
|
41
41
|
|
|
42
|
+
input_serializer_class = BrowserCoverInputSerializer
|
|
42
43
|
renderer_classes = (WEBPRenderer,)
|
|
43
44
|
content_type = "image/webp"
|
|
44
45
|
TARGET = "cover"
|
|
@@ -56,39 +57,21 @@ class CoverView(BrowserAnnotationsView):
|
|
|
56
57
|
pks = self.kwargs["pks"]
|
|
57
58
|
return pks[0], False
|
|
58
59
|
|
|
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
|
|
60
|
+
def _get_custom_cover(self):
|
|
61
|
+
"""Get Custom Cover."""
|
|
62
|
+
if self.model == Volume or not self.params.get("custom_covers"):
|
|
63
|
+
return None
|
|
64
|
+
group = self.kwargs["group"]
|
|
65
|
+
group_rel = CUSTOM_COVER_GROUP_RELATION[group]
|
|
66
|
+
pks = self.kwargs["pks"]
|
|
67
|
+
comic_filter = {f"{group_rel}__in": pks}
|
|
68
|
+
qs = CustomCover.objects.filter(**comic_filter)
|
|
69
|
+
qs = qs.only("pk")
|
|
70
|
+
return qs.first()
|
|
78
71
|
|
|
79
72
|
def _get_dynamic_cover(self):
|
|
80
73
|
"""Get dynamic cover."""
|
|
81
74
|
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
75
|
comic_qs = self.get_filtered_queryset(Comic)
|
|
93
76
|
comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
|
|
94
77
|
comic_qs = self.add_order_by(comic_qs, Comic)
|
|
@@ -99,40 +82,15 @@ class CoverView(BrowserAnnotationsView):
|
|
|
99
82
|
cover_pk = comic.pk if comic else 0
|
|
100
83
|
return cover_pk, False
|
|
101
84
|
|
|
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
85
|
def _get_cover_pk(self) -> tuple[int, bool]:
|
|
129
86
|
"""Get Cover Pk queryset for comic queryset."""
|
|
130
87
|
if self.model == Comic:
|
|
131
88
|
cover_pk, custom = self._get_comic_cover()
|
|
132
|
-
elif self.
|
|
133
|
-
cover_pk
|
|
89
|
+
elif custom_cover := self._get_custom_cover():
|
|
90
|
+
cover_pk = custom_cover.pk
|
|
91
|
+
custom = True
|
|
134
92
|
else:
|
|
135
|
-
cover_pk, custom = self.
|
|
93
|
+
cover_pk, custom = self._get_dynamic_cover()
|
|
136
94
|
return cover_pk, custom
|
|
137
95
|
|
|
138
96
|
def _get_missing_cover_path(self):
|
|
@@ -168,10 +126,16 @@ class CoverView(BrowserAnnotationsView):
|
|
|
168
126
|
thumb_image_data = f.read()
|
|
169
127
|
return thumb_image_data, content_type
|
|
170
128
|
|
|
171
|
-
@extend_schema(
|
|
129
|
+
@extend_schema(
|
|
130
|
+
parameters=[BrowserAnnotationsView.input_serializer_class],
|
|
131
|
+
responses={(200, content_type): OpenApiTypes.BINARY},
|
|
132
|
+
)
|
|
172
133
|
def get(self, *args, **kwargs): # type: ignore
|
|
173
134
|
"""Get comic cover."""
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
135
|
+
try:
|
|
136
|
+
self.init_request()
|
|
137
|
+
pk, custom = self._get_cover_pk()
|
|
138
|
+
thumb_image_data, content_type = self._get_cover_data(pk, custom)
|
|
139
|
+
return HttpResponse(thumb_image_data, content_type=content_type)
|
|
140
|
+
except Exception:
|
|
141
|
+
LOG.exception("get")
|
|
@@ -20,10 +20,10 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
|
20
20
|
super().__init__(*args, **kwargs)
|
|
21
21
|
self.init_bookmark_data()
|
|
22
22
|
|
|
23
|
-
def _get_query_filters(self, model, group, pks, bookmark_filter):
|
|
23
|
+
def _get_query_filters(self, model, group, pks, bookmark_filter, page_mtime=False): # noqa: PLR0913
|
|
24
24
|
"""Return all the filters except the group filter."""
|
|
25
25
|
object_filter = self.get_group_acl_filter(model)
|
|
26
|
-
object_filter &= self.get_group_filter(
|
|
26
|
+
object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
|
|
27
27
|
object_filter &= self.get_comic_field_filter(model)
|
|
28
28
|
if bookmark_filter:
|
|
29
29
|
# not needed because OuterRef is applied next
|
|
@@ -45,17 +45,25 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
|
45
45
|
qs = qs.filter(**{f"{ann_name}__gt": 0})
|
|
46
46
|
return qs
|
|
47
47
|
|
|
48
|
-
def get_filtered_queryset(
|
|
48
|
+
def get_filtered_queryset( # noqa: PLR0913
|
|
49
|
+
self, model, group=None, pks=None, bookmark_filter=True, page_mtime=False
|
|
50
|
+
):
|
|
49
51
|
"""Get a filtered queryset for the model."""
|
|
50
|
-
object_filter = self._get_query_filters(
|
|
52
|
+
object_filter = self._get_query_filters(
|
|
53
|
+
model, group, pks, bookmark_filter, page_mtime=page_mtime
|
|
54
|
+
)
|
|
51
55
|
qs = model.objects.filter(object_filter)
|
|
52
56
|
qs = self.apply_search_filter(qs, model)
|
|
53
57
|
return self._filter_by_child_count(qs, model)
|
|
54
58
|
|
|
55
|
-
def get_group_mtime(self, model, group=None, pks=None):
|
|
59
|
+
def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
|
|
56
60
|
"""Get a filtered mtime for browser pages and mtime checker."""
|
|
57
61
|
qs = self.get_filtered_queryset(
|
|
58
|
-
model,
|
|
62
|
+
model,
|
|
63
|
+
group=group,
|
|
64
|
+
pks=pks,
|
|
65
|
+
bookmark_filter=self.is_bookmark_filtered,
|
|
66
|
+
page_mtime=page_mtime,
|
|
59
67
|
)
|
|
60
68
|
qs = qs.annotate(max_updated_at=Max("updated_at"))
|
|
61
69
|
|
|
@@ -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 = (
|
codex/views/browser/metadata.py
CHANGED
|
@@ -13,8 +13,10 @@ from codex.librarian.importer.const import (
|
|
|
13
13
|
)
|
|
14
14
|
from codex.logger.logging import get_logger
|
|
15
15
|
from codex.models import AdminFlag, Comic
|
|
16
|
-
from codex.
|
|
16
|
+
from codex.models.functions import JsonGroupArray
|
|
17
|
+
from codex.serializers.browser.metadata import PREFETCH_PREFIX, MetadataSerializer
|
|
17
18
|
from codex.views.browser.annotations import BrowserAnnotationsView
|
|
19
|
+
from codex.views.const import METADATA_GROUP_RELATION
|
|
18
20
|
|
|
19
21
|
LOG = get_logger(__name__)
|
|
20
22
|
_ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
|
|
@@ -54,6 +56,13 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
54
56
|
}
|
|
55
57
|
)
|
|
56
58
|
|
|
59
|
+
def init_request(self):
|
|
60
|
+
"""Initialize request."""
|
|
61
|
+
self.set_admin_flags()
|
|
62
|
+
self.parse_params()
|
|
63
|
+
self.set_model()
|
|
64
|
+
self.set_rel_prefix()
|
|
65
|
+
|
|
57
66
|
def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
|
|
58
67
|
"""Limited allowed nav groups for metadata."""
|
|
59
68
|
group = self.kwargs["group"]
|
|
@@ -79,6 +88,7 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
79
88
|
):
|
|
80
89
|
"""Annotate the intersection of value and fk fields."""
|
|
81
90
|
(filtered_qs, qs) = querysets
|
|
91
|
+
|
|
82
92
|
for field in fields:
|
|
83
93
|
# Annotate variant counts
|
|
84
94
|
# If 1 variant, annotate value, otherwise None
|
|
@@ -133,6 +143,52 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
133
143
|
)
|
|
134
144
|
return qs
|
|
135
145
|
|
|
146
|
+
def _query_groups(self):
|
|
147
|
+
"""Query the through models to show group lists."""
|
|
148
|
+
groups = {}
|
|
149
|
+
if not self.model:
|
|
150
|
+
return groups
|
|
151
|
+
group = self.kwargs["group"]
|
|
152
|
+
rel = METADATA_GROUP_RELATION.get(group)
|
|
153
|
+
if not rel:
|
|
154
|
+
return groups
|
|
155
|
+
rel = rel + "__in"
|
|
156
|
+
pks = self.kwargs["pks"]
|
|
157
|
+
group_filter = {rel: pks}
|
|
158
|
+
|
|
159
|
+
for field_name in _GROUP_RELS:
|
|
160
|
+
try:
|
|
161
|
+
field = self.model._meta.get_field(field_name)
|
|
162
|
+
except FieldDoesNotExist:
|
|
163
|
+
continue
|
|
164
|
+
model = field.related_model
|
|
165
|
+
if not model:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
qs = model.objects.filter(**group_filter)
|
|
169
|
+
qs = qs.only("name").distinct()
|
|
170
|
+
group_by = self.get_group_by(model)
|
|
171
|
+
qs = qs.group_by(group_by)
|
|
172
|
+
qs = qs.annotate(ids=JsonGroupArray("id", distinct=True))
|
|
173
|
+
qs = qs.values("ids", "name")
|
|
174
|
+
groups[field_name] = qs
|
|
175
|
+
return groups
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def _get_optimized_m2m_query(key, qs):
|
|
179
|
+
if key == "contributors":
|
|
180
|
+
qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS)
|
|
181
|
+
qs = qs.only(*_CONTRIBUTOR_RELATIONS)
|
|
182
|
+
elif key == "story_arc_numbers":
|
|
183
|
+
qs = qs.select_related("story_arc")
|
|
184
|
+
qs = qs.only("story_arc", "number")
|
|
185
|
+
elif key == "identifiers":
|
|
186
|
+
qs = qs.select_related("identifier_type")
|
|
187
|
+
qs = qs.only("identifier_type", "nss", "url")
|
|
188
|
+
else:
|
|
189
|
+
qs = qs.only("name")
|
|
190
|
+
return qs
|
|
191
|
+
|
|
136
192
|
def _query_m2m_intersections(self, filtered_qs):
|
|
137
193
|
"""Query the through models to figure out m2m intersections."""
|
|
138
194
|
# Speed ok, but still does a query per m2m model
|
|
@@ -140,31 +196,17 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
140
196
|
pk_field = self.rel_prefix + "pk"
|
|
141
197
|
comic_pks = filtered_qs.values_list(pk_field, flat=True)
|
|
142
198
|
comic_pks_count = comic_pks.count()
|
|
143
|
-
for field_name in COMIC_M2M_FIELD_NAMES
|
|
144
|
-
try:
|
|
145
|
-
if (
|
|
146
|
-
field_name in _GROUP_RELS
|
|
147
|
-
and self.model
|
|
148
|
-
and not self.model._meta.get_field(field_name)
|
|
149
|
-
):
|
|
150
|
-
continue
|
|
151
|
-
except FieldDoesNotExist:
|
|
152
|
-
continue
|
|
153
|
-
|
|
199
|
+
for field_name in COMIC_M2M_FIELD_NAMES:
|
|
154
200
|
model = Comic._meta.get_field(field_name).related_model
|
|
155
201
|
if not model:
|
|
156
202
|
reason = f"No model found for comic field: {field_name}"
|
|
157
203
|
raise ValueError(reason)
|
|
158
204
|
|
|
159
205
|
intersection_qs = model.objects.filter(comic__pk__in=comic_pks)
|
|
160
|
-
if field_name in _GROUP_RELS:
|
|
161
|
-
group_by = "name" if field_name == "volume" else "sort_name"
|
|
162
|
-
intersection_qs = intersection_qs.group_by(group_by)
|
|
163
|
-
if field_name == "identifiers":
|
|
164
|
-
intersection_qs = intersection_qs.select_related("identifier_type")
|
|
165
206
|
intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
|
|
166
207
|
count=comic_pks_count
|
|
167
208
|
)
|
|
209
|
+
intersection_qs = self._get_optimized_m2m_query(field_name, intersection_qs)
|
|
168
210
|
|
|
169
211
|
m2m_intersections[field_name] = intersection_qs
|
|
170
212
|
return m2m_intersections
|
|
@@ -185,42 +227,37 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
185
227
|
"""Values for highlighting the current group."""
|
|
186
228
|
if self.model and not self.is_model_comic:
|
|
187
229
|
# move the name of the group to the correct field
|
|
188
|
-
|
|
230
|
+
field = self.model.__name__.lower() + "_list"
|
|
189
231
|
group_obj = {"pk": obj.pk, "name": obj.name}
|
|
190
|
-
|
|
232
|
+
group_list = [group_obj]
|
|
233
|
+
setattr(obj, field, group_list)
|
|
191
234
|
obj.name = None
|
|
192
235
|
|
|
193
|
-
@staticmethod
|
|
194
|
-
def _get_optimized_m2m_query(key, qs):
|
|
195
|
-
# XXX The prefetch gets removed by field.set() :(
|
|
196
|
-
if key == "contributors":
|
|
197
|
-
qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
|
|
198
|
-
*_CONTRIBUTOR_RELATIONS
|
|
199
|
-
)
|
|
200
|
-
elif key == "story_arc_numbers":
|
|
201
|
-
qs = qs.prefetch_related("story_arc")
|
|
202
|
-
qs = qs.only("story_arc", "number")
|
|
203
|
-
elif key == "identifiers":
|
|
204
|
-
qs = qs.prefetch_related("identifier_type")
|
|
205
|
-
qs = qs.only("identifier_type", "nss", "url")
|
|
206
|
-
else:
|
|
207
|
-
qs = qs.only("name")
|
|
208
|
-
return qs
|
|
209
|
-
|
|
210
236
|
@classmethod
|
|
211
237
|
def _copy_m2m_intersections(cls, obj, m2m_intersections):
|
|
212
238
|
"""Copy the m2m intersections into the object."""
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
239
|
+
# XXX It might even be faster to copy everything to a dict and not use the obj.
|
|
240
|
+
for key, qs in m2m_intersections.items():
|
|
241
|
+
serializer_key = (
|
|
242
|
+
f"{PREFETCH_PREFIX}{key}"
|
|
243
|
+
if key in ("identifiers", "contributors", "story_arc_numbers")
|
|
244
|
+
else key
|
|
245
|
+
)
|
|
246
|
+
if hasattr(obj, serializer_key):
|
|
216
247
|
# real db fields need to use their special set method.
|
|
217
|
-
field = getattr(obj,
|
|
218
|
-
field.set(
|
|
248
|
+
field = getattr(obj, serializer_key)
|
|
249
|
+
field.set(
|
|
250
|
+
qs,
|
|
251
|
+
clear=True,
|
|
252
|
+
)
|
|
219
253
|
else:
|
|
220
254
|
# fake db field is just a queryset attached.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
255
|
+
setattr(obj, serializer_key, qs)
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _copy_groups(obj, groups):
|
|
259
|
+
for field, group_qs in groups.items():
|
|
260
|
+
setattr(obj, field + "_list", group_qs)
|
|
224
261
|
|
|
225
262
|
@staticmethod
|
|
226
263
|
def _copy_conflicting_simple_fields(obj):
|
|
@@ -230,10 +267,11 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
230
267
|
val = getattr(obj, conflict_field)
|
|
231
268
|
setattr(obj, field, val)
|
|
232
269
|
|
|
233
|
-
def _copy_annotations_into_comic_fields(self, obj, m2m_intersections):
|
|
270
|
+
def _copy_annotations_into_comic_fields(self, obj, groups, m2m_intersections):
|
|
234
271
|
"""Copy a bunch of values that i couldn't fit cleanly in the main queryset."""
|
|
235
272
|
self._path_security(obj)
|
|
236
273
|
self._highlight_current_group(obj)
|
|
274
|
+
self._copy_groups(obj, groups)
|
|
237
275
|
self._copy_m2m_intersections(obj, m2m_intersections)
|
|
238
276
|
if not self.is_model_comic:
|
|
239
277
|
self._copy_conflicting_simple_fields(obj)
|
|
@@ -270,10 +308,11 @@ class MetadataView(BrowserAnnotationsView):
|
|
|
270
308
|
except (IndexError, ValueError) as exc:
|
|
271
309
|
self._raise_not_found(exc)
|
|
272
310
|
|
|
311
|
+
groups = self._query_groups()
|
|
273
312
|
m2m_intersections = self._query_m2m_intersections(filtered_qs)
|
|
274
|
-
return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
|
|
313
|
+
return self._copy_annotations_into_comic_fields(obj, groups, m2m_intersections) # type: ignore
|
|
275
314
|
|
|
276
|
-
@extend_schema(
|
|
315
|
+
@extend_schema(parameters=[BrowserAnnotationsView.input_serializer_class])
|
|
277
316
|
def get(self, *_args, **_kwargs):
|
|
278
317
|
"""Get metadata for a filtered browse group."""
|
|
279
318
|
# Init
|
codex/views/browser/mtime.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Get the mtimes for the submitted groups."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
3
|
+
from drf_spectacular.utils import extend_schema
|
|
5
4
|
from rest_framework.response import Response
|
|
6
5
|
|
|
7
6
|
from codex.logger.logging import get_logger
|
|
@@ -21,24 +20,13 @@ class MtimeView(BrowserAnnotationsFilterView):
|
|
|
21
20
|
serializer_class = MtimeSerializer
|
|
22
21
|
|
|
23
22
|
REPARSE_JSON_FIELDS = frozenset({"groups", "filters"})
|
|
23
|
+
TARGET = "mtime"
|
|
24
24
|
|
|
25
25
|
def __init__(self, *args, **kwargs) -> None:
|
|
26
26
|
"""Initialize."""
|
|
27
27
|
super().__init__(*args, **kwargs)
|
|
28
28
|
self.init_bookmark_data()
|
|
29
29
|
|
|
30
|
-
def parse_params(self):
|
|
31
|
-
"""Parse GET params."""
|
|
32
|
-
try:
|
|
33
|
-
validated_data = super().parse_params()
|
|
34
|
-
params = dict(self.params)
|
|
35
|
-
params["groups"] = validated_data["groups"] # type: ignore
|
|
36
|
-
self.params = MappingProxyType(params)
|
|
37
|
-
|
|
38
|
-
except Exception:
|
|
39
|
-
LOG.exception("parse")
|
|
40
|
-
raise
|
|
41
|
-
|
|
42
30
|
def _get_group_mtime(self, item):
|
|
43
31
|
"""Get one group's mtimes."""
|
|
44
32
|
group = item["group"]
|
|
@@ -59,6 +47,7 @@ class MtimeView(BrowserAnnotationsFilterView):
|
|
|
59
47
|
max_mtime = max_none(max_mtime, mtime)
|
|
60
48
|
return max_mtime
|
|
61
49
|
|
|
50
|
+
@extend_schema(parameters=[GroupsMtimeSerializer])
|
|
62
51
|
def get(self, *args, **kwargs):
|
|
63
52
|
"""Get the mtimes for the submitted groups."""
|
|
64
53
|
# Parse Request
|
codex/views/browser/order_by.py
CHANGED
|
@@ -6,7 +6,6 @@ from codex.models import Comic
|
|
|
6
6
|
from codex.views.browser.filters.annotations import (
|
|
7
7
|
BrowserAnnotationsFilterView,
|
|
8
8
|
)
|
|
9
|
-
from codex.views.const import FOLDER_GROUP
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class BrowserOrderByView(BrowserAnnotationsFilterView):
|
|
@@ -14,11 +13,7 @@ class BrowserOrderByView(BrowserAnnotationsFilterView):
|
|
|
14
13
|
|
|
15
14
|
def set_order_key(self):
|
|
16
15
|
"""Get the default order key for the view."""
|
|
17
|
-
order_key: str = self.params
|
|
18
|
-
if not order_key:
|
|
19
|
-
group = self.kwargs.get("group")
|
|
20
|
-
order_key = "filename" if group == FOLDER_GROUP else "sort_name"
|
|
21
|
-
self.order_key: str = order_key
|
|
16
|
+
self.order_key: str = self.params["order_by"]
|
|
22
17
|
order_reverse = self.params.get("order_reverse")
|
|
23
18
|
self.order_agg_func = Max if order_reverse else Min
|
|
24
19
|
|
codex/views/browser/settings.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Browser session view."""
|
|
2
2
|
|
|
3
3
|
from codex.serializers.browser.settings import BrowserSettingsSerializer
|
|
4
|
+
from codex.views.const import GROUP_ORDER
|
|
4
5
|
from codex.views.settings import SettingsView
|
|
5
6
|
|
|
6
7
|
|
|
@@ -11,3 +12,55 @@ class BrowserSettingsView(SettingsView):
|
|
|
11
12
|
serializer_class = BrowserSettingsSerializer # type: ignore
|
|
12
13
|
|
|
13
14
|
SESSION_KEY = SettingsView.BROWSER_SESSION_KEY
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def _strip_breadcrumb_names(params):
|
|
18
|
+
"""Remove names from breadcrumbs."""
|
|
19
|
+
breadcrumbs = params.get("breadcrumbs")
|
|
20
|
+
if not breadcrumbs:
|
|
21
|
+
return params
|
|
22
|
+
nameless_breadcrumbs = []
|
|
23
|
+
for crumb in breadcrumbs:
|
|
24
|
+
crumb.pop("name", None)
|
|
25
|
+
nameless_breadcrumbs.append(crumb)
|
|
26
|
+
params["breadcrumbs"] = nameless_breadcrumbs
|
|
27
|
+
return params
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _validate_browse_top_group(params, group, top_group):
|
|
31
|
+
"""Validate top group for browse groups."""
|
|
32
|
+
show = params["show"]
|
|
33
|
+
if group == "r" or (
|
|
34
|
+
show.get(top_group)
|
|
35
|
+
and GROUP_ORDER.index(top_group) < GROUP_ORDER.index(group)
|
|
36
|
+
):
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
for show_group, on in show.items():
|
|
40
|
+
if on:
|
|
41
|
+
params["top_group"] = show_group
|
|
42
|
+
break
|
|
43
|
+
else:
|
|
44
|
+
params["top_group"] = "c"
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def _validate_top_group(cls, params, group, top_group):
|
|
48
|
+
"""Validate top group."""
|
|
49
|
+
if group == top_group:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
if group in ("f", "a"):
|
|
53
|
+
params["top_group"] = group
|
|
54
|
+
else:
|
|
55
|
+
cls._validate_browse_top_group(params, group, top_group)
|
|
56
|
+
|
|
57
|
+
def validate_settings_get(self, validated_data, params):
|
|
58
|
+
"""Change bad settings."""
|
|
59
|
+
top_group = params["top_group"]
|
|
60
|
+
group = validated_data.get("group", "r") if validated_data else "r"
|
|
61
|
+
self._validate_top_group(params, group, top_group)
|
|
62
|
+
|
|
63
|
+
if not validated_data.get("breadcrumb_names", True):
|
|
64
|
+
params = self._strip_breadcrumb_names(params)
|
|
65
|
+
|
|
66
|
+
return params
|
codex/views/browser/validate.py
CHANGED
|
@@ -15,6 +15,7 @@ from codex.views.const import (
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
LOG = get_logger(__name__)
|
|
18
|
+
_NO_TOP_GROUP_VALIDATE = frozenset({"metadata"})
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class BrowserValidateView(BrowserBaseView):
|
|
@@ -55,6 +56,8 @@ class BrowserValidateView(BrowserBaseView):
|
|
|
55
56
|
return valid_top_groups
|
|
56
57
|
|
|
57
58
|
def _validate_top_group(self, valid_top_groups):
|
|
59
|
+
# if self.TARGET in _NO_TOP_GROUP_VALIDATE:
|
|
60
|
+
# return
|
|
58
61
|
nav_group = self.kwargs.get("group")
|
|
59
62
|
top_group = self.params.get("top_group")
|
|
60
63
|
if top_group not in valid_top_groups:
|
codex/views/const.py
CHANGED
|
@@ -67,6 +67,25 @@ GROUP_RELATION = MappingProxyType(
|
|
|
67
67
|
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
68
68
|
}
|
|
69
69
|
)
|
|
70
|
+
FILTER_ONLY_GROUP_RELATION = MappingProxyType(
|
|
71
|
+
{
|
|
72
|
+
**GROUP_NAME_MAP,
|
|
73
|
+
FOLDER_GROUP: "folders",
|
|
74
|
+
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
METADATA_GROUP_RELATION = MappingProxyType(
|
|
78
|
+
{
|
|
79
|
+
**GROUP_NAME_MAP,
|
|
80
|
+
COMIC_GROUP: "pk",
|
|
81
|
+
# Possible to list all groups in a non group view, but could get crazy.
|
|
82
|
+
# FOLDER_GROUP: "comic__folder",
|
|
83
|
+
# STORY_ARC_GROUP: "comic__story_arc_number__story_arc"
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
CUSTOM_COVER_GROUP_RELATION = MappingProxyType(
|
|
87
|
+
{**GROUP_NAME_MAP, FOLDER_GROUP: "folder", STORY_ARC_GROUP: "storyarc"}
|
|
88
|
+
)
|
|
70
89
|
GROUP_ORDER = "rpisv"
|
|
71
90
|
MODEL_REL_MAP = MappingProxyType(
|
|
72
91
|
{
|
|
@@ -98,8 +117,12 @@ GROUP_MODELS = (
|
|
|
98
117
|
Imprint,
|
|
99
118
|
Series,
|
|
100
119
|
Volume,
|
|
101
|
-
Comic,
|
|
102
120
|
Folder,
|
|
121
|
+
StoryArc,
|
|
122
|
+
)
|
|
123
|
+
STATS_GROUP_MODELS = (
|
|
124
|
+
*GROUP_MODELS,
|
|
125
|
+
Comic,
|
|
103
126
|
)
|
|
104
127
|
METADATA_MODELS = (
|
|
105
128
|
AgeRating,
|