codex 1.6.0a8__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/integrity.py +1 -2
- codex/librarian/importer/cache.py +46 -133
- codex/librarian/importer/create_comics.py +3 -4
- codex/librarian/importer/create_covers.py +1 -3
- codex/librarian/importer/create_fks.py +3 -1
- codex/librarian/importer/failed_imports.py +1 -2
- codex/librarian/importer/importer.py +1 -1
- codex/librarian/importer/importerd.py +7 -7
- codex/librarian/importer/moved.py +4 -8
- 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/mtime.py +21 -0
- codex/serializers/browser/page.py +2 -4
- codex/serializers/browser/settings.py +34 -14
- codex/serializers/fields.py +27 -10
- codex/serializers/reader.py +16 -6
- codex/serializers/route.py +9 -4
- 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-B8Z4NA-t.3d8921f98ad9.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-B8Z4NA-t.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-BGa6_q5a.d1a3ad7266bd.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-BGa6_q5a.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-C8DP9rgp.ef80e8e3dfc1.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-C8DP9rgp.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-D1esKtlw.52165930e3b9.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-D1esKtlw.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-DkU0twE6.a20006aa5ba6.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-DkU0twE6.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-D4ZNSbMR.e763a9937701.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-D4ZNSbMR.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-DucpNoBs.a6f37ae940c0.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-DucpNoBs.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-Cj0hTq-c.062a88ddd4dc.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-Cj0hTq-c.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-CzksdS3Q.566acdb45dec.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-CzksdS3Q.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-GkMk4o5L.ef24c859a7a4.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-GkMk4o5L.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/urls/api/v3.py +1 -1
- codex/views/admin/api_key.py +5 -2
- codex/views/admin/stats.py +13 -20
- codex/views/admin/tasks.py +3 -7
- codex/views/auth.py +1 -1
- codex/views/bookmark.py +69 -51
- codex/views/browser/annotations.py +32 -45
- codex/views/browser/base.py +25 -8
- codex/views/browser/breadcrumbs.py +5 -5
- codex/views/browser/browser.py +20 -32
- codex/views/browser/choices.py +18 -15
- codex/views/browser/cover.py +30 -66
- codex/views/browser/filters/annotations.py +66 -14
- codex/views/browser/filters/bookmark.py +21 -5
- codex/views/browser/filters/group.py +29 -17
- codex/views/browser/filters/search.py +3 -4
- codex/views/browser/metadata.py +86 -47
- codex/views/browser/mtime.py +61 -0
- 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 +32 -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 +48 -66
- codex/views/reader/init.py +121 -0
- codex/views/reader/reader.py +8 -66
- 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.0a8.dist-info → codex-1.6.0a10.dist-info}/METADATA +1 -1
- {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/RECORD +329 -325
- codex/serializers/mtime.py +0 -25
- codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +0 -1
- codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.js +0 -1
- codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
- codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +0 -1
- codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.js +0 -1
- codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
- codex/static_root/assets/VDialog-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/VDialog-opmV0YZz.d77216e97599.js +0 -1
- codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.js +0 -1
- codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
- codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +0 -1
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +0 -1
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +0 -1
- codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.js +0 -1
- codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
- codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
- codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +0 -1
- codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.js +0 -1
- codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
- codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
- codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
- codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
- codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +0 -1
- codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.js +0 -1
- codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
- codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
- codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js +0 -1
- codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
- codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
- codex/static_root/assets/VWindowItem-B0KokcAk.js +0 -1
- codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
- codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
- codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +0 -1
- codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
- codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
- codex/static_root/assets/admin-Y0bL21AB.js +0 -1
- codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
- codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +0 -30
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +0 -30
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
- codex/static_root/assets/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-CMAtBCLx.17a277ce43a9.css +0 -1
- codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
- codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
- codex/static_root/assets/browser-CMAtBCLx.css +0 -1
- codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
- codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
- codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +0 -1
- codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
- codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
- codex/static_root/assets/browser-DoJk8uvb.js +0 -1
- codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
- codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js +0 -1
- codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-CH4xbjJa.js +0 -1
- codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
- codex/static_root/assets/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-DkU0twE6.a20006aa5ba6.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +0 -1
- codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.js +0 -1
- codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
- codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
- codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +0 -1
- codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
- codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
- codex/static_root/assets/filter-DjxN78cK.js +0 -1
- codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
- codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
- codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
- codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
- codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
- codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
- codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
- codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
- codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
- codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
- codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
- codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
- codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
- codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
- codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
- codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +0 -32
- codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
- codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
- codex/static_root/assets/main-DX-k2724.js +0 -32
- codex/static_root/assets/main-DX-k2724.js.br +0 -0
- codex/static_root/assets/main-DX-k2724.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +0 -1
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +0 -1
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +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/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
- codex/static_root/assets/pdf-doc-GkMk4o5L.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-Cl6mmQB_.7b9e7591954f.js +0 -2
- codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.js +0 -2
- codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
- codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +0 -1
- codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.js +0 -1
- codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
- codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +0 -2
- codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.js +0 -2
- codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
- codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +0 -1
- codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.js +0 -1
- codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
- codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
- codex/static_root/assets/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-BhLDK_7Q.28ff51b3ed01.js +0 -1
- codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.js +0 -1
- codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
- codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
- codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +0 -1
- codex/static_root/assets/to-case-BR2frjFx.js +0 -1
- codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +0 -1
- codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.js +0 -1
- codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
- codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
- codex/static_root/assets/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-DxITIl2d.d79d7f062930.js +0 -1
- codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.js +0 -1
- codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
- codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
- codex/static_root/js/choices-admin.d790df01c20a.json +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.16d52597ef50.json +0 -629
- codex/static_root/manifest.16d52597ef50.json.br +0 -0
- codex/static_root/manifest.16d52597ef50.json.gz +0 -0
- codex/views/mtime.py +0 -72
- {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/LICENSE +0 -0
- {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/WHEEL +0 -0
- {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/entry_points.txt +0 -0
|
@@ -18,10 +18,10 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
18
18
|
"""Load breadcrumbs and determine if they should be searched for a graft."""
|
|
19
19
|
breadcrumbs: list[Route] = list(self.params.get("breadcrumbs", []))
|
|
20
20
|
old_breadcrumbs = [Route(**crumb) for crumb in breadcrumbs]
|
|
21
|
-
|
|
22
|
-
if
|
|
21
|
+
invalid = not old_breadcrumbs or old_breadcrumbs[-1].group not in valid_groups
|
|
22
|
+
if invalid:
|
|
23
23
|
old_breadcrumbs = []
|
|
24
|
-
return old_breadcrumbs,
|
|
24
|
+
return old_breadcrumbs, invalid
|
|
25
25
|
|
|
26
26
|
def _breadcrumbs_save(self, breadcrumbs):
|
|
27
27
|
"""Save the breadcrumbs to params."""
|
|
@@ -184,7 +184,7 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
184
184
|
|
|
185
185
|
return tuple(new_breadcrumbs), changed
|
|
186
186
|
|
|
187
|
-
def
|
|
187
|
+
def get_breadcrumbs(
|
|
188
188
|
self,
|
|
189
189
|
) -> tuple[Route, ...]:
|
|
190
190
|
"""Graft or create breadcrumbs by browser mode."""
|
|
@@ -199,4 +199,4 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
|
|
|
199
199
|
if changed:
|
|
200
200
|
self._breadcrumbs_save(breadcrumbs)
|
|
201
201
|
|
|
202
|
-
return breadcrumbs
|
|
202
|
+
return breadcrumbs
|
codex/views/browser/browser.py
CHANGED
|
@@ -16,13 +16,9 @@ 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
|
|
25
|
-
from codex.util import max_none
|
|
26
22
|
from codex.views.browser.title import BrowserTitleView
|
|
27
23
|
from codex.views.const import (
|
|
28
24
|
COMIC_GROUP,
|
|
@@ -35,6 +31,15 @@ if TYPE_CHECKING:
|
|
|
35
31
|
|
|
36
32
|
|
|
37
33
|
LOG = get_logger(__name__)
|
|
34
|
+
_GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
|
|
35
|
+
type[BrowserGroupModel], tuple[str | None, ...]
|
|
36
|
+
] = MappingProxyType(
|
|
37
|
+
{
|
|
38
|
+
Comic: ("series", "volume"),
|
|
39
|
+
Volume: ("series",),
|
|
40
|
+
Imprint: ("publisher",),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
class BrowserView(BrowserTitleView):
|
|
@@ -49,19 +54,6 @@ class BrowserView(BrowserTitleView):
|
|
|
49
54
|
}
|
|
50
55
|
)
|
|
51
56
|
TARGET = "browser"
|
|
52
|
-
_GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
|
|
53
|
-
type[BrowserGroupModel], tuple[str | None, ...]
|
|
54
|
-
] = MappingProxyType(
|
|
55
|
-
{
|
|
56
|
-
Comic: ("series", "volume"),
|
|
57
|
-
Volume: ("series",),
|
|
58
|
-
Series: (None,),
|
|
59
|
-
Imprint: ("publisher",),
|
|
60
|
-
Publisher: (None,),
|
|
61
|
-
Folder: ("parent_folder",),
|
|
62
|
-
StoryArc: (None,),
|
|
63
|
-
}
|
|
64
|
-
)
|
|
65
57
|
|
|
66
58
|
def __init__(self, *args, **kwargs):
|
|
67
59
|
"""Set params for the type checker."""
|
|
@@ -76,7 +68,7 @@ class BrowserView(BrowserTitleView):
|
|
|
76
68
|
if pks and self.group_class:
|
|
77
69
|
try:
|
|
78
70
|
select_related: tuple[str | None, ...] = (
|
|
79
|
-
|
|
71
|
+
_GROUP_INSTANCE_SELECT_RELATED.get(self.group_class, (None,))
|
|
80
72
|
)
|
|
81
73
|
self.group_query = self.group_class.objects.select_related(
|
|
82
74
|
*select_related
|
|
@@ -157,16 +149,13 @@ class BrowserView(BrowserTitleView):
|
|
|
157
149
|
count = 0
|
|
158
150
|
return qs, count
|
|
159
151
|
|
|
160
|
-
def _get_page_mtime(self
|
|
161
|
-
if self.group_instance:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
page_updated_at_max = group_qs.aggregate(max=Max("updated_at"))["max"]
|
|
165
|
-
|
|
166
|
-
agg_func = self.get_bookmark_updated_at_aggregate(self.model, True)
|
|
167
|
-
page_bookmark_updated_at = group_qs.aggregate(max=agg_func)["max"]
|
|
152
|
+
def _get_page_mtime(self):
|
|
153
|
+
if not self.is_bookmark_filtered and self.group_instance:
|
|
154
|
+
# Nice optimization if we can get get it.
|
|
155
|
+
return self.group_instance.updated_at
|
|
168
156
|
|
|
169
|
-
|
|
157
|
+
group_model = self.group_class if self.group_class else self.model
|
|
158
|
+
return self.get_group_mtime(group_model, page_mtime=True)
|
|
170
159
|
|
|
171
160
|
@staticmethod
|
|
172
161
|
def _get_zero_pad(book_qs):
|
|
@@ -182,7 +171,6 @@ class BrowserView(BrowserTitleView):
|
|
|
182
171
|
def _get_group_and_books(self):
|
|
183
172
|
"""Create the main queries with filters, annotation and pagination."""
|
|
184
173
|
group_qs, group_count = self._get_group_queryset()
|
|
185
|
-
mtime = self._get_page_mtime(group_qs)
|
|
186
174
|
book_qs, book_count = self._get_book_queryset()
|
|
187
175
|
# Paginate
|
|
188
176
|
group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
|
|
@@ -200,6 +188,7 @@ class BrowserView(BrowserTitleView):
|
|
|
200
188
|
# print(group_qs.query)
|
|
201
189
|
|
|
202
190
|
total_count = page_group_count + page_book_count
|
|
191
|
+
mtime = self._get_page_mtime()
|
|
203
192
|
return group_qs, book_qs, num_pages, total_count, zero_pad, mtime
|
|
204
193
|
|
|
205
194
|
def get_object(self):
|
|
@@ -209,7 +198,7 @@ class BrowserView(BrowserTitleView):
|
|
|
209
198
|
)
|
|
210
199
|
|
|
211
200
|
# get additional context
|
|
212
|
-
|
|
201
|
+
breadcrumbs = self.get_breadcrumbs()
|
|
213
202
|
title = self.get_browser_page_title()
|
|
214
203
|
# needs to happen after pagination
|
|
215
204
|
# runs obj list query twice :/
|
|
@@ -218,7 +207,7 @@ class BrowserView(BrowserTitleView):
|
|
|
218
207
|
# construct final data structure
|
|
219
208
|
return MappingProxyType(
|
|
220
209
|
{
|
|
221
|
-
"breadcrumbs":
|
|
210
|
+
"breadcrumbs": breadcrumbs,
|
|
222
211
|
"title": title,
|
|
223
212
|
"model_group": self.model_group,
|
|
224
213
|
"groups": group_qs,
|
|
@@ -232,11 +221,10 @@ class BrowserView(BrowserTitleView):
|
|
|
232
221
|
}
|
|
233
222
|
)
|
|
234
223
|
|
|
235
|
-
@extend_schema(
|
|
224
|
+
@extend_schema(parameters=[BrowserTitleView.input_serializer_class])
|
|
236
225
|
def get(self, *_args, **_kwargs):
|
|
237
226
|
"""Get browser settings."""
|
|
238
227
|
self.init_request()
|
|
239
228
|
data = self.get_object()
|
|
240
229
|
serializer = self.get_serializer(data)
|
|
241
|
-
self.save_params_to_session(self.params)
|
|
242
230
|
return Response(serializer.data)
|
codex/views/browser/choices.py
CHANGED
|
@@ -15,11 +15,13 @@ from codex.models import (
|
|
|
15
15
|
StoryArc,
|
|
16
16
|
)
|
|
17
17
|
from codex.models.named import IdentifierType
|
|
18
|
-
from codex.serializers.browser.choices import
|
|
18
|
+
from codex.serializers.browser.choices import (
|
|
19
|
+
BrowserChoicesFilterSerializer,
|
|
20
|
+
)
|
|
19
21
|
from codex.serializers.browser.filters import (
|
|
20
22
|
BrowserFilterChoicesSerializer,
|
|
21
|
-
CharListField,
|
|
22
23
|
)
|
|
24
|
+
from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
|
|
23
25
|
from codex.serializers.models.pycountry import PyCountrySerializer
|
|
24
26
|
from codex.views.browser.filters.annotations import (
|
|
25
27
|
BrowserAnnotationsFilterView,
|
|
@@ -56,9 +58,15 @@ _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
|
|
|
56
58
|
class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
57
59
|
"""Get choices for filter dialog."""
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
input_serializer_class = BrowserFilterChoicesInputSerilalizer
|
|
60
62
|
TARGET = "choices"
|
|
61
63
|
|
|
64
|
+
def init_request(self):
|
|
65
|
+
"""Initialieze request."""
|
|
66
|
+
self.parse_params()
|
|
67
|
+
self.set_model()
|
|
68
|
+
self.set_rel_prefix()
|
|
69
|
+
|
|
62
70
|
@staticmethod
|
|
63
71
|
def get_field_choices_query(comic_qs, field_name):
|
|
64
72
|
"""Get distinct values for the field."""
|
|
@@ -97,12 +105,12 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
|
|
|
97
105
|
"""Get the comic subquery use for the choices."""
|
|
98
106
|
return self.get_filtered_queryset(Comic)
|
|
99
107
|
|
|
100
|
-
@extend_schema(
|
|
108
|
+
@extend_schema(parameters=[input_serializer_class])
|
|
101
109
|
def get(self, *_args, **_kwargs):
|
|
102
110
|
"""Return choices."""
|
|
103
111
|
self.init_request()
|
|
104
112
|
obj = self.get_object()
|
|
105
|
-
serializer = self.get_serializer(obj
|
|
113
|
+
serializer = self.get_serializer(obj)
|
|
106
114
|
return Response(serializer.data)
|
|
107
115
|
|
|
108
116
|
|
|
@@ -160,10 +168,7 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
|
|
|
160
168
|
class BrowserChoicesView(BrowserChoicesViewBase):
|
|
161
169
|
"""Get choices for filter dialog."""
|
|
162
170
|
|
|
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()
|
|
171
|
+
serializer_class = BrowserChoicesFilterSerializer
|
|
167
172
|
|
|
168
173
|
def _get_field_choices(self, comic_qs, field_name):
|
|
169
174
|
"""Create a pk:name object for fields without tables."""
|
|
@@ -202,11 +207,6 @@ class BrowserChoicesView(BrowserChoicesViewBase):
|
|
|
202
207
|
field_name = self.kwargs.get("field_name", "")
|
|
203
208
|
return snakecase(field_name)
|
|
204
209
|
|
|
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
210
|
def get_object(self):
|
|
211
211
|
"""Return choices with more than one choice."""
|
|
212
212
|
qs = super().get_object()
|
|
@@ -217,4 +217,7 @@ class BrowserChoicesView(BrowserChoicesViewBase):
|
|
|
217
217
|
choices = self._get_m2m_field_choices(m2m_model, qs, rel)
|
|
218
218
|
else:
|
|
219
219
|
choices = self._get_field_choices(qs, rel)
|
|
220
|
-
return
|
|
220
|
+
return {
|
|
221
|
+
"field_name": field_name,
|
|
222
|
+
"choices": choices,
|
|
223
|
+
}
|
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")
|
|
@@ -1,35 +1,87 @@
|
|
|
1
1
|
"""Annotations used by a filter."""
|
|
2
2
|
|
|
3
|
-
from django.db.models.aggregates import Count
|
|
3
|
+
from django.db.models.aggregates import Count, Max
|
|
4
|
+
from django.db.models.functions import Coalesce, Greatest
|
|
4
5
|
|
|
5
6
|
from codex.models.comic import Comic
|
|
6
7
|
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
7
8
|
from codex.views.browser.validate import BrowserValidateView
|
|
9
|
+
from codex.views.const import (
|
|
10
|
+
EPOCH_START_DATETIMEFILED,
|
|
11
|
+
ONE_INTEGERFIELD,
|
|
12
|
+
)
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
|
|
11
16
|
"""Annotations that also filter."""
|
|
12
17
|
|
|
13
|
-
def
|
|
18
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
19
|
+
"""Initialize."""
|
|
20
|
+
super().__init__(*args, **kwargs)
|
|
21
|
+
self.init_bookmark_data()
|
|
22
|
+
|
|
23
|
+
def _get_query_filters(self, model, group, pks, bookmark_filter, page_mtime=False): # noqa: PLR0913
|
|
14
24
|
"""Return all the filters except the group filter."""
|
|
15
25
|
object_filter = self.get_group_acl_filter(model)
|
|
16
|
-
object_filter &= self.
|
|
26
|
+
object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
|
|
17
27
|
object_filter &= self.get_comic_field_filter(model)
|
|
18
|
-
|
|
28
|
+
if bookmark_filter:
|
|
29
|
+
# not needed because OuterRef is applied next
|
|
30
|
+
object_filter &= self.get_bookmark_filter(model)
|
|
19
31
|
return object_filter
|
|
20
32
|
|
|
21
33
|
def _filter_by_child_count(self, qs, model):
|
|
22
|
-
|
|
23
|
-
return qs
|
|
24
|
-
|
|
34
|
+
"""Filter group by child count."""
|
|
25
35
|
rel = self.rel_prefix + "pk"
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
ann_name = "child_count"
|
|
37
|
+
count_func = ONE_INTEGERFIELD if model == Comic else Count(rel, distinct=True)
|
|
38
|
+
ann = {ann_name: count_func}
|
|
39
|
+
if self.TARGET == "opds2":
|
|
40
|
+
if model != Comic:
|
|
41
|
+
qs = qs.alias(**ann)
|
|
42
|
+
else:
|
|
43
|
+
qs = qs.annotate(**ann)
|
|
44
|
+
if model != Comic:
|
|
45
|
+
qs = qs.filter(**{f"{ann_name}__gt": 0})
|
|
46
|
+
return qs
|
|
28
47
|
|
|
29
|
-
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
|
+
):
|
|
30
51
|
"""Get a filtered queryset for the model."""
|
|
31
|
-
object_filter = self.
|
|
32
|
-
|
|
52
|
+
object_filter = self._get_query_filters(
|
|
53
|
+
model, group, pks, bookmark_filter, page_mtime=page_mtime
|
|
54
|
+
)
|
|
33
55
|
qs = model.objects.filter(object_filter)
|
|
34
|
-
qs = self.
|
|
35
|
-
return self.
|
|
56
|
+
qs = self.apply_search_filter(qs, model)
|
|
57
|
+
return self._filter_by_child_count(qs, model)
|
|
58
|
+
|
|
59
|
+
def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
|
|
60
|
+
"""Get a filtered mtime for browser pages and mtime checker."""
|
|
61
|
+
qs = self.get_filtered_queryset(
|
|
62
|
+
model,
|
|
63
|
+
group=group,
|
|
64
|
+
pks=pks,
|
|
65
|
+
bookmark_filter=self.is_bookmark_filtered,
|
|
66
|
+
page_mtime=page_mtime,
|
|
67
|
+
)
|
|
68
|
+
qs = qs.annotate(max_updated_at=Max("updated_at"))
|
|
69
|
+
|
|
70
|
+
if self.is_bookmark_filtered:
|
|
71
|
+
bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
|
|
72
|
+
qs = qs.filter(bm_filter)
|
|
73
|
+
ua_rel = bm_rel + "__updated_at"
|
|
74
|
+
mbua = Max(ua_rel)
|
|
75
|
+
else:
|
|
76
|
+
mbua = EPOCH_START_DATETIMEFILED
|
|
77
|
+
qs = qs.annotate(max_bookmark_updated_at=mbua)
|
|
78
|
+
|
|
79
|
+
mtime = qs.aggregate(
|
|
80
|
+
max=Greatest(
|
|
81
|
+
Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFILED),
|
|
82
|
+
"max_updated_at",
|
|
83
|
+
)
|
|
84
|
+
)["max"]
|
|
85
|
+
if mtime == NotImplemented:
|
|
86
|
+
mtime = None
|
|
87
|
+
return mtime
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
"""Bookmark filter view methods."""
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from django.db.models import Q
|
|
4
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from codex.models import BrowserGroupModel
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
class BookmarkFilterMixin:
|
|
7
12
|
"""BookmarkFilter view methods."""
|
|
8
13
|
|
|
9
|
-
def
|
|
14
|
+
def init_bookmark_data(self):
|
|
15
|
+
"""Initialize the bm_annotation_data."""
|
|
16
|
+
self._bm_annotation_data: dict[BrowserGroupModel, tuple[str, Q]] = {}
|
|
17
|
+
|
|
18
|
+
def _get_bm_rel(self, model):
|
|
10
19
|
"""Create bookmark relation."""
|
|
11
20
|
rel_prefix = self.get_rel_prefix(model) # type: ignore
|
|
12
21
|
return rel_prefix + "bookmark"
|
|
13
22
|
|
|
14
|
-
def
|
|
23
|
+
def _get_my_bookmark_filter(self, bm_rel):
|
|
15
24
|
"""Get a filter for my session or user defined bookmarks."""
|
|
16
25
|
if self.request.user.is_authenticated: # type: ignore
|
|
17
26
|
my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
|
|
@@ -22,12 +31,19 @@ class BookmarkFilterMixin:
|
|
|
22
31
|
}
|
|
23
32
|
return Q(**my_bookmarks_kwargs)
|
|
24
33
|
|
|
34
|
+
def get_bookmark_rel_and_filter(self, model):
|
|
35
|
+
"""Get the bookmark rel and filter once."""
|
|
36
|
+
if model not in self._bm_annotation_data:
|
|
37
|
+
bm_rel = self._get_bm_rel(model)
|
|
38
|
+
bm_filter = self._get_my_bookmark_filter(bm_rel)
|
|
39
|
+
self._bm_annotation_data[model] = (bm_rel, bm_filter)
|
|
40
|
+
return self._bm_annotation_data[model]
|
|
41
|
+
|
|
25
42
|
def get_bookmark_filter(self, model):
|
|
26
43
|
"""Build bookmark query."""
|
|
27
|
-
choice = self.params
|
|
44
|
+
choice: str = self.params.get("filters", {}).get("bookmark", "") # type: ignore
|
|
28
45
|
if choice:
|
|
29
|
-
bm_rel = self.
|
|
30
|
-
my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
|
|
46
|
+
bm_rel, my_bookmark_filter = self.get_bookmark_rel_and_filter(model)
|
|
31
47
|
if choice == "READ":
|
|
32
48
|
bookmark_filter = my_bookmark_filter & Q(
|
|
33
49
|
**{f"{bm_rel}__finished": True}
|
|
@@ -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,22 +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
|
-
group
|
|
18
|
-
|
|
19
|
-
if pks
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rel = "folders"
|
|
26
|
-
else:
|
|
27
|
-
rel = GROUP_RELATION[group]
|
|
28
|
-
|
|
29
|
-
rel += "__in"
|
|
30
|
-
group_filter_dict = {rel: pks} # type: ignore
|
|
35
|
+
if group is None:
|
|
36
|
+
group = self.kwargs["group"]
|
|
37
|
+
if pks is None:
|
|
38
|
+
pks = self.kwargs["pks"]
|
|
39
|
+
|
|
40
|
+
if pks and 0 not in pks:
|
|
41
|
+
rel = self._get_rel_for_pks(group, page_mtime)
|
|
42
|
+
group_filter_dict = {rel: pks}
|
|
31
43
|
elif group == FOLDER_GROUP:
|
|
32
44
|
group_filter_dict = {"parent_folder": None}
|
|
33
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 = (
|