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
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
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Get the mtimes for the submitted groups."""
|
|
2
|
+
|
|
3
|
+
from drf_spectacular.utils import extend_schema
|
|
4
|
+
from rest_framework.response import Response
|
|
5
|
+
|
|
6
|
+
from codex.logger.logging import get_logger
|
|
7
|
+
from codex.models.groups import Publisher
|
|
8
|
+
from codex.serializers.browser.mtime import GroupsMtimeSerializer, MtimeSerializer
|
|
9
|
+
from codex.util import max_none
|
|
10
|
+
from codex.views.browser.filters.annotations import BrowserAnnotationsFilterView
|
|
11
|
+
from codex.views.const import GROUP_MODEL_MAP
|
|
12
|
+
|
|
13
|
+
LOG = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MtimeView(BrowserAnnotationsFilterView):
|
|
17
|
+
"""Get the mtimes for the submitted groups."""
|
|
18
|
+
|
|
19
|
+
input_serializer_class = GroupsMtimeSerializer
|
|
20
|
+
serializer_class = MtimeSerializer
|
|
21
|
+
|
|
22
|
+
REPARSE_JSON_FIELDS = frozenset({"groups", "filters"})
|
|
23
|
+
TARGET = "mtime"
|
|
24
|
+
|
|
25
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
26
|
+
"""Initialize."""
|
|
27
|
+
super().__init__(*args, **kwargs)
|
|
28
|
+
self.init_bookmark_data()
|
|
29
|
+
|
|
30
|
+
def _get_group_mtime(self, item):
|
|
31
|
+
"""Get one group's mtimes."""
|
|
32
|
+
group = item["group"]
|
|
33
|
+
pks = item["pks"]
|
|
34
|
+
|
|
35
|
+
model = GROUP_MODEL_MAP[group]
|
|
36
|
+
if not model:
|
|
37
|
+
model = Publisher
|
|
38
|
+
|
|
39
|
+
return self.get_group_mtime(model, group, pks)
|
|
40
|
+
|
|
41
|
+
def get_max_groups_mtime(self):
|
|
42
|
+
"""Get max mtime for all groups."""
|
|
43
|
+
max_mtime = None
|
|
44
|
+
|
|
45
|
+
for item in self.params["groups"]: # type: ignore
|
|
46
|
+
mtime = self._get_group_mtime(item)
|
|
47
|
+
max_mtime = max_none(max_mtime, mtime)
|
|
48
|
+
return max_mtime
|
|
49
|
+
|
|
50
|
+
@extend_schema(parameters=[GroupsMtimeSerializer])
|
|
51
|
+
def get(self, *args, **kwargs):
|
|
52
|
+
"""Get the mtimes for the submitted groups."""
|
|
53
|
+
# Parse Request
|
|
54
|
+
self.parse_params()
|
|
55
|
+
|
|
56
|
+
max_mtime = self.get_max_groups_mtime()
|
|
57
|
+
|
|
58
|
+
# Serialize Response
|
|
59
|
+
result = {"max_mtime": max_mtime}
|
|
60
|
+
serializer = self.get_serializer(result)
|
|
61
|
+
return Response(serializer.data)
|
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
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Common view constants."""
|
|
2
2
|
|
|
3
|
+
from datetime import datetime, timezone
|
|
3
4
|
from types import MappingProxyType
|
|
4
5
|
|
|
5
6
|
from django.contrib.auth.models import Group, User
|
|
6
7
|
from django.contrib.sessions.models import Session
|
|
8
|
+
from django.db.models.expressions import Value
|
|
9
|
+
from django.db.models.fields import DateTimeField, PositiveSmallIntegerField
|
|
7
10
|
|
|
8
11
|
from codex.models import (
|
|
9
12
|
AgeRating,
|
|
@@ -64,6 +67,25 @@ GROUP_RELATION = MappingProxyType(
|
|
|
64
67
|
STORY_ARC_GROUP: "story_arc_numbers__story_arc",
|
|
65
68
|
}
|
|
66
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
|
+
)
|
|
67
89
|
GROUP_ORDER = "rpisv"
|
|
68
90
|
MODEL_REL_MAP = MappingProxyType(
|
|
69
91
|
{
|
|
@@ -95,8 +117,12 @@ GROUP_MODELS = (
|
|
|
95
117
|
Imprint,
|
|
96
118
|
Series,
|
|
97
119
|
Volume,
|
|
98
|
-
Comic,
|
|
99
120
|
Folder,
|
|
121
|
+
StoryArc,
|
|
122
|
+
)
|
|
123
|
+
STATS_GROUP_MODELS = (
|
|
124
|
+
*GROUP_MODELS,
|
|
125
|
+
Comic,
|
|
100
126
|
)
|
|
101
127
|
METADATA_MODELS = (
|
|
102
128
|
AgeRating,
|
|
@@ -127,3 +153,8 @@ CONFIG_MODELS = (
|
|
|
127
153
|
)
|
|
128
154
|
MAX_OBJ_PER_PAGE = 100
|
|
129
155
|
GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
|
|
156
|
+
EPOCH_START = datetime.fromtimestamp(0, tz=timezone.utc)
|
|
157
|
+
ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
|
|
158
|
+
NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
|
|
159
|
+
NONE_DATETIMEFIELD = Value(None, DateTimeField())
|
|
160
|
+
EPOCH_START_DATETIMEFILED = Value(EPOCH_START)
|
codex/views/opds/v1/feed.py
CHANGED
|
@@ -11,10 +11,10 @@ from rest_framework.throttling import ScopedRateThrottle
|
|
|
11
11
|
from codex.librarian.importer.tasks import LazyImportComicsTask
|
|
12
12
|
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
13
13
|
from codex.logger.logging import get_logger
|
|
14
|
+
from codex.serializers.browser.settings import OPDSSettingsSerializer
|
|
14
15
|
from codex.serializers.opds.v1 import (
|
|
15
16
|
OPDS1TemplateSerializer,
|
|
16
17
|
)
|
|
17
|
-
from codex.views.browser.browser import BrowserView
|
|
18
18
|
from codex.views.const import FALSY, MAX_OBJ_PER_PAGE
|
|
19
19
|
from codex.views.opds.auth import OPDSTemplateView
|
|
20
20
|
from codex.views.opds.const import BLANK_TITLE, MimeType
|
|
@@ -56,6 +56,7 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
|
|
|
56
56
|
|
|
57
57
|
template_name = "opds_v1/index.xml"
|
|
58
58
|
serializer_class = OPDS1TemplateSerializer
|
|
59
|
+
input_serializer_class = OPDSSettingsSerializer
|
|
59
60
|
throttle_classes = (ScopedRateThrottle,)
|
|
60
61
|
throttle_scope = "opds"
|
|
61
62
|
TARGET = "opds1"
|
|
@@ -254,10 +255,7 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
|
|
|
254
255
|
self._set_user_agent_variables()
|
|
255
256
|
self.skip_order_facets |= self.kwargs.get("group") == "c"
|
|
256
257
|
|
|
257
|
-
@extend_schema(
|
|
258
|
-
request=BrowserView.input_serializer_class,
|
|
259
|
-
parameters=[BrowserView.input_serializer_class],
|
|
260
|
-
)
|
|
258
|
+
@extend_schema(parameters=[input_serializer_class])
|
|
261
259
|
def get(self, *_args, **_kwargs):
|
|
262
260
|
"""Get the feed."""
|
|
263
261
|
self.init_request()
|
codex/views/opds/v2/feed.py
CHANGED
|
@@ -7,8 +7,8 @@ from rest_framework.response import Response
|
|
|
7
7
|
|
|
8
8
|
from codex.logger.logging import get_logger
|
|
9
9
|
from codex.models import AdminFlag
|
|
10
|
+
from codex.serializers.browser.settings import OPDSSettingsSerializer
|
|
10
11
|
from codex.serializers.opds.v2 import OPDS2FeedSerializer
|
|
11
|
-
from codex.views.browser.browser import BrowserView
|
|
12
12
|
from codex.views.const import FALSY, MAX_OBJ_PER_PAGE
|
|
13
13
|
from codex.views.opds.auth import OPDSAuthMixin
|
|
14
14
|
from codex.views.opds.const import BLANK_TITLE
|
|
@@ -36,6 +36,7 @@ class OPDS2FeedView(OPDSAuthMixin, OPDS2PublicationView):
|
|
|
36
36
|
throttle_scope = "opds"
|
|
37
37
|
|
|
38
38
|
serializer_class = OPDS2FeedSerializer
|
|
39
|
+
input_serializer_class = OPDSSettingsSerializer
|
|
39
40
|
|
|
40
41
|
def _title(self, browser_title):
|
|
41
42
|
"""Create the feed title."""
|
|
@@ -233,8 +234,7 @@ class OPDS2FeedView(OPDSAuthMixin, OPDS2PublicationView):
|
|
|
233
234
|
# self._detect_user_agent()
|
|
234
235
|
|
|
235
236
|
@extend_schema(
|
|
236
|
-
|
|
237
|
-
parameters=[BrowserView.input_serializer_class],
|
|
237
|
+
parameters=[input_serializer_class],
|
|
238
238
|
)
|
|
239
239
|
def get(self, *_args, **_kwargs):
|
|
240
240
|
"""Get the feed."""
|
codex/views/reader/arcs.py
CHANGED
|
@@ -4,7 +4,7 @@ from types import MappingProxyType
|
|
|
4
4
|
|
|
5
5
|
from codex.models import AdminFlag
|
|
6
6
|
from codex.util import max_none
|
|
7
|
-
from codex.views.const import FOLDER_GROUP
|
|
7
|
+
from codex.views.const import FOLDER_GROUP, STORY_ARC_GROUP
|
|
8
8
|
from codex.views.reader.books import ReaderBooksView
|
|
9
9
|
|
|
10
10
|
|
|
@@ -16,6 +16,22 @@ class ReaderArcsView(ReaderBooksView):
|
|
|
16
16
|
super().__init__(*args, **kwargs)
|
|
17
17
|
self.params: MappingProxyType = MappingProxyType({})
|
|
18
18
|
|
|
19
|
+
def _get_group_arc(self, book, attr):
|
|
20
|
+
"""Create the volume arc."""
|
|
21
|
+
group = getattr(book, attr, None)
|
|
22
|
+
if not group:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
_, arc_pks = self.get_group_pks_from_breadcrumbs([group])
|
|
26
|
+
if group.pk not in arc_pks:
|
|
27
|
+
arc_pks = (group.pk,)
|
|
28
|
+
return {
|
|
29
|
+
"group": attr[0],
|
|
30
|
+
"pks": arc_pks,
|
|
31
|
+
"name": group.name,
|
|
32
|
+
"mtime": group.updated_at,
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
def _get_folder_arc(self, book):
|
|
20
36
|
"""Create the folder arc."""
|
|
21
37
|
efv_flag = (
|
|
@@ -23,39 +39,16 @@ class ReaderArcsView(ReaderBooksView):
|
|
|
23
39
|
.get(key=AdminFlag.FlagChoices.FOLDER_VIEW.value)
|
|
24
40
|
.on
|
|
25
41
|
)
|
|
42
|
+
if not efv_flag:
|
|
43
|
+
return None
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
else:
|
|
36
|
-
folder_arc = None
|
|
37
|
-
return folder_arc
|
|
38
|
-
|
|
39
|
-
def _get_series_arc(self, book):
|
|
40
|
-
"""Create the series arc."""
|
|
41
|
-
series = book.series
|
|
42
|
-
if series:
|
|
43
|
-
arc_pks = self.get_series_pks_from_breadcrumbs()
|
|
44
|
-
if book.series.pk not in arc_pks:
|
|
45
|
-
arc_pks = (book.series.pk,)
|
|
46
|
-
arc = (
|
|
47
|
-
{
|
|
48
|
-
"group": "s",
|
|
49
|
-
"pks": arc_pks,
|
|
50
|
-
"name": series.name,
|
|
51
|
-
"mtime": series.updated_at,
|
|
52
|
-
}
|
|
53
|
-
if series
|
|
54
|
-
else None
|
|
55
|
-
)
|
|
56
|
-
else:
|
|
57
|
-
arc = None
|
|
58
|
-
return arc
|
|
45
|
+
folder = book.parent_folder
|
|
46
|
+
return {
|
|
47
|
+
"group": FOLDER_GROUP,
|
|
48
|
+
"pks": (folder.pk,),
|
|
49
|
+
"name": folder.name,
|
|
50
|
+
"mtime": folder.updated_at,
|
|
51
|
+
}
|
|
59
52
|
|
|
60
53
|
def _get_story_arcs(self, book):
|
|
61
54
|
"""Create story arcs."""
|
|
@@ -74,26 +67,24 @@ class ReaderArcsView(ReaderBooksView):
|
|
|
74
67
|
def get_arcs(self, book):
|
|
75
68
|
"""Get all series/folder/story arcs."""
|
|
76
69
|
# create top arcs
|
|
77
|
-
|
|
78
|
-
series_arc = self._get_series_arc(book)
|
|
79
|
-
|
|
80
|
-
# order top arcs
|
|
70
|
+
arcs = []
|
|
81
71
|
top_group = self.params.get("top_group")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
else:
|
|
72
|
+
arc = None
|
|
73
|
+
if series_arc := self._get_group_arc(book, "series"):
|
|
74
|
+
arcs.append(series_arc)
|
|
86
75
|
arc = series_arc
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if
|
|
92
|
-
arcs.append(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
76
|
+
if volume_arc := self._get_group_arc(book, "volume"):
|
|
77
|
+
arcs.append(volume_arc)
|
|
78
|
+
if not arc:
|
|
79
|
+
arc = volume_arc
|
|
80
|
+
if folder_arc := self._get_folder_arc(book):
|
|
81
|
+
arcs.append(folder_arc)
|
|
82
|
+
if top_group == FOLDER_GROUP:
|
|
83
|
+
arc = folder_arc
|
|
84
|
+
if story_arcs := self._get_story_arcs(book):
|
|
85
|
+
arcs = [*arcs, *story_arcs]
|
|
86
|
+
if top_group == STORY_ARC_GROUP:
|
|
87
|
+
arc = story_arcs[0]
|
|
97
88
|
|
|
98
89
|
max_mtime = None
|
|
99
90
|
for arc in arcs:
|