codex 1.7.3a2__py3-none-any.whl → 1.7.5__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/asgi.py +2 -1
- codex/choices/__init__.py +1 -0
- codex/{choices.py → choices/admin.py} +47 -146
- codex/choices/browser.py +85 -0
- codex/{choices_to_json.py → choices/choices_to_json.py} +25 -19
- codex/choices/notifications.py +16 -0
- codex/choices/reader.py +33 -0
- codex/db.py +2 -2
- codex/exceptions.py +7 -8
- codex/librarian/bookmark/bookmarkd.py +8 -4
- codex/librarian/bookmark/update.py +84 -49
- codex/librarian/covers/create.py +10 -8
- codex/librarian/covers/path.py +6 -5
- codex/librarian/covers/purge.py +4 -4
- codex/librarian/importer/aggregate.py +2 -2
- codex/librarian/importer/cache.py +1 -4
- codex/librarian/importer/create_comics.py +2 -2
- codex/librarian/importer/create_covers.py +1 -1
- codex/librarian/importer/create_fks.py +4 -3
- codex/librarian/importer/deleted.py +3 -3
- codex/librarian/importer/extract.py +56 -37
- codex/librarian/importer/failed_imports.py +1 -1
- codex/librarian/importer/importer.py +8 -3
- codex/librarian/importer/importerd.py +3 -3
- codex/librarian/importer/link_comics.py +2 -1
- codex/librarian/importer/moved.py +2 -7
- codex/librarian/importer/query_fks.py +1 -4
- codex/librarian/importer/tasks.py +1 -1
- codex/librarian/janitor/integrity.py +27 -20
- codex/librarian/janitor/janitor.py +1 -1
- codex/librarian/janitor/latest_version.py +1 -1
- codex/librarian/janitor/update.py +5 -2
- codex/librarian/janitor/vacuum.py +1 -1
- codex/librarian/librariand.py +3 -2
- codex/librarian/notifier/notifierd.py +2 -1
- codex/librarian/notifier/tasks.py +17 -3
- codex/librarian/search/optimize.py +1 -1
- codex/librarian/search/remove.py +1 -1
- codex/librarian/search/update.py +4 -4
- codex/librarian/telemeter/stats.py +7 -6
- codex/librarian/telemeter/telemeter.py +0 -1
- codex/librarian/watchdog/db_snapshot.py +3 -3
- codex/librarian/watchdog/dir_snapshot_diff.py +3 -4
- codex/librarian/watchdog/emitter.py +4 -4
- codex/librarian/watchdog/event_batcherd.py +2 -1
- codex/librarian/watchdog/events.py +1 -2
- codex/librarian/watchdog/observers.py +15 -10
- codex/logger/logging.py +1 -1
- codex/memory.py +3 -2
- codex/migrations/0001_init.py +1 -2
- codex/migrations/0014_pdf_issue_suffix_remove_cover_image_sort_name.py +1 -2
- codex/migrations/0018_rename_userbookmark_bookmark.py +1 -2
- codex/migrations/0028_telemeter.py +1 -5
- codex/models/base.py +3 -1
- codex/models/bookmark.py +4 -7
- codex/models/comic.py +2 -2
- codex/models/functions.py +12 -5
- codex/models/groups.py +1 -1
- codex/models/library.py +1 -1
- codex/models/paths.py +4 -4
- codex/models/query.py +8 -4
- codex/models/util.py +1 -1
- codex/permissions.py +1 -1
- codex/run.py +1 -1
- codex/serializers/admin/libraries.py +1 -3
- codex/serializers/admin/stats.py +4 -4
- codex/serializers/admin/tasks.py +12 -2
- codex/serializers/auth.py +4 -2
- codex/serializers/browser/choices.py +87 -9
- codex/serializers/browser/filters.py +9 -37
- codex/serializers/browser/metadata.py +2 -1
- codex/serializers/browser/mixins.py +7 -3
- codex/serializers/browser/mtime.py +3 -5
- codex/serializers/browser/page.py +5 -4
- codex/serializers/browser/settings.py +8 -6
- codex/serializers/fields/__init__.py +37 -0
- codex/serializers/fields/auth.py +45 -0
- codex/serializers/fields/browser.py +85 -0
- codex/serializers/fields/group.py +13 -0
- codex/serializers/fields/reader.py +22 -0
- codex/serializers/fields/sanitized.py +13 -0
- codex/serializers/fields/session.py +16 -0
- codex/serializers/fields/stats.py +36 -0
- codex/serializers/fields/vuetify.py +62 -0
- codex/serializers/models/base.py +1 -1
- codex/serializers/models/comic.py +2 -2
- codex/serializers/models/groups.py +0 -2
- codex/serializers/models/named.py +1 -1
- codex/serializers/models/pycountry.py +1 -1
- codex/serializers/opds/authentication.py +4 -2
- codex/serializers/opds/urls.py +2 -1
- codex/serializers/opds/v1.py +4 -3
- codex/serializers/opds/v2/feed.py +2 -1
- codex/serializers/opds/v2/links.py +23 -21
- codex/serializers/opds/v2/metadata.py +4 -8
- codex/serializers/opds/v2/progression.py +5 -3
- codex/serializers/opds/v2/publication.py +16 -18
- codex/serializers/opds/v2/unused.py +15 -8
- codex/serializers/reader.py +16 -14
- codex/serializers/redirect.py +2 -1
- codex/serializers/route.py +6 -4
- codex/serializers/settings.py +5 -3
- codex/serializers/versions.py +2 -1
- codex/settings/settings.py +4 -3
- codex/signals/django_signals.py +1 -1
- codex/startup.py +2 -2
- codex/static_root/assets/{VCheckbox-FaT6MGfu.f6732060f734.js → VCheckbox-CWBDr4kF.6da5bf3a4d90.js} +1 -1
- codex/static_root/assets/VCheckbox-CWBDr4kF.6da5bf3a4d90.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWBDr4kF.6da5bf3a4d90.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-FaT6MGfu.js → VCheckbox-CWBDr4kF.js} +1 -1
- codex/static_root/assets/VCheckbox-CWBDr4kF.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWBDr4kF.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CZ_P-qUM.847452d574cf.js → VCheckboxBtn-COaoh3uF.fc880a386827.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-COaoh3uF.fc880a386827.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-COaoh3uF.fc880a386827.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CZ_P-qUM.js → VCheckboxBtn-COaoh3uF.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-COaoh3uF.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-COaoh3uF.js.gz +0 -0
- codex/static_root/assets/{VCombobox-C8QLNlSl.6f431e19a453.js → VCombobox-DkOruNH0.f30f163ee632.js} +1 -1
- codex/static_root/assets/VCombobox-DkOruNH0.f30f163ee632.js.br +0 -0
- codex/static_root/assets/VCombobox-DkOruNH0.f30f163ee632.js.gz +0 -0
- codex/static_root/assets/{VCombobox-C8QLNlSl.js → VCombobox-DkOruNH0.js} +1 -1
- codex/static_root/assets/VCombobox-DkOruNH0.js.br +0 -0
- codex/static_root/assets/VCombobox-DkOruNH0.js.gz +0 -0
- codex/static_root/assets/{VDialog-RVLeW7je.c0c7eef71eec.js → VDialog-Dr7SuC2S.b6ec7df390f4.js} +1 -1
- codex/static_root/assets/VDialog-Dr7SuC2S.b6ec7df390f4.js.br +0 -0
- codex/static_root/assets/VDialog-Dr7SuC2S.b6ec7df390f4.js.gz +0 -0
- codex/static_root/assets/{VDialog-RVLeW7je.js → VDialog-Dr7SuC2S.js} +1 -1
- codex/static_root/assets/VDialog-Dr7SuC2S.js.br +0 -0
- codex/static_root/assets/VDialog-Dr7SuC2S.js.gz +0 -0
- codex/static_root/assets/{VDivider-D1Ot4vR2.9dbee744fb3c.js → VDivider-By4u2EsM.fd5ee9cce906.js} +1 -1
- codex/static_root/assets/VDivider-By4u2EsM.fd5ee9cce906.js.br +0 -0
- codex/static_root/assets/VDivider-By4u2EsM.fd5ee9cce906.js.gz +0 -0
- codex/static_root/assets/{VDivider-D1Ot4vR2.js → VDivider-By4u2EsM.js} +1 -1
- codex/static_root/assets/VDivider-By4u2EsM.js.br +0 -0
- codex/static_root/assets/VDivider-By4u2EsM.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DQilGHZk.43f06d0a45e0.js → VExpansionPanels-BhO9oj0a.e99bea455c78.js} +1 -1
- codex/static_root/assets/VExpansionPanels-BhO9oj0a.e99bea455c78.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-BhO9oj0a.e99bea455c78.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DQilGHZk.js → VExpansionPanels-BhO9oj0a.js} +1 -1
- codex/static_root/assets/VExpansionPanels-BhO9oj0a.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-BhO9oj0a.js.gz +0 -0
- codex/static_root/assets/VForm-XhA6T0Lc.70c9c6fcb9e3.js +1 -0
- codex/static_root/assets/VForm-XhA6T0Lc.70c9c6fcb9e3.js.br +0 -0
- codex/static_root/assets/VForm-XhA6T0Lc.70c9c6fcb9e3.js.gz +0 -0
- codex/static_root/assets/VForm-XhA6T0Lc.js +1 -0
- codex/static_root/assets/VForm-XhA6T0Lc.js.br +0 -0
- codex/static_root/assets/VForm-XhA6T0Lc.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-DoayJpcS.fe5e0e74ffee.js → VRadioGroup-C1WCG0rO.6ebc88e4f144.js} +1 -1
- codex/static_root/assets/VRadioGroup-C1WCG0rO.6ebc88e4f144.js.br +3 -0
- codex/static_root/assets/VRadioGroup-C1WCG0rO.6ebc88e4f144.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-DoayJpcS.js → VRadioGroup-C1WCG0rO.js} +1 -1
- codex/static_root/assets/VRadioGroup-C1WCG0rO.js.br +3 -0
- codex/static_root/assets/VRadioGroup-C1WCG0rO.js.gz +0 -0
- codex/static_root/assets/VSelect-Bbt1vrBg.ec45ee26818a.js +1 -0
- codex/static_root/assets/VSelect-Bbt1vrBg.ec45ee26818a.js.br +0 -0
- codex/static_root/assets/VSelect-Bbt1vrBg.ec45ee26818a.js.gz +0 -0
- codex/static_root/assets/VSelect-Bbt1vrBg.js +1 -0
- codex/static_root/assets/VSelect-Bbt1vrBg.js.br +0 -0
- codex/static_root/assets/VSelect-Bbt1vrBg.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-TaKCeZgb.ad6c96efe57c.js → VSelectionControl-BkZZAsWo.ba6eca944e2e.js} +1 -1
- codex/static_root/assets/VSelectionControl-BkZZAsWo.ba6eca944e2e.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BkZZAsWo.ba6eca944e2e.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-TaKCeZgb.js → VSelectionControl-BkZZAsWo.js} +1 -1
- codex/static_root/assets/VSelectionControl-BkZZAsWo.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BkZZAsWo.js.gz +0 -0
- codex/static_root/assets/{VTable-BfcOiEpa.77ef3973bb54.js → VTable-D7P2eIc2.5e94bf2a515b.js} +1 -1
- codex/static_root/assets/VTable-D7P2eIc2.5e94bf2a515b.js.br +0 -0
- codex/static_root/assets/VTable-D7P2eIc2.5e94bf2a515b.js.gz +0 -0
- codex/static_root/assets/{VTable-BfcOiEpa.js → VTable-D7P2eIc2.js} +1 -1
- codex/static_root/assets/VTable-D7P2eIc2.js.br +0 -0
- codex/static_root/assets/VTable-D7P2eIc2.js.gz +0 -0
- codex/static_root/assets/VWindowItem-Dm2szrjK.e77444188aa3.js +1 -0
- codex/static_root/assets/VWindowItem-Dm2szrjK.e77444188aa3.js.br +0 -0
- codex/static_root/assets/VWindowItem-Dm2szrjK.e77444188aa3.js.gz +0 -0
- codex/static_root/assets/VWindowItem-Dm2szrjK.js +1 -0
- codex/static_root/assets/VWindowItem-Dm2szrjK.js.br +0 -0
- codex/static_root/assets/VWindowItem-Dm2szrjK.js.gz +0 -0
- codex/static_root/assets/{admin-80eqCqtz.d6deb9edb8cb.js → admin-B4z2nA5P.da0c92500311.js} +1 -1
- codex/static_root/assets/admin-B4z2nA5P.da0c92500311.js.br +0 -0
- codex/static_root/assets/admin-B4z2nA5P.da0c92500311.js.gz +0 -0
- codex/static_root/assets/{admin-80eqCqtz.js → admin-B4z2nA5P.js} +1 -1
- codex/static_root/assets/admin-B4z2nA5P.js.br +0 -0
- codex/static_root/assets/admin-B4z2nA5P.js.gz +0 -0
- codex/static_root/assets/admin-BhW3PNO0.e444048e548d.js +1 -0
- codex/static_root/assets/admin-BhW3PNO0.e444048e548d.js.br +0 -0
- codex/static_root/assets/admin-BhW3PNO0.e444048e548d.js.gz +0 -0
- codex/static_root/assets/admin-BhW3PNO0.js +1 -0
- codex/static_root/assets/admin-BhW3PNO0.js.br +0 -0
- codex/static_root/assets/admin-BhW3PNO0.js.gz +0 -0
- codex/static_root/assets/{admin-menu-_aAGknKO.0c08c9bd2e7d.js → admin-menu-CL7S-xqR.92b6d84c4b70.js} +1 -1
- codex/static_root/assets/admin-menu-CL7S-xqR.92b6d84c4b70.js.br +0 -0
- codex/static_root/assets/admin-menu-CL7S-xqR.92b6d84c4b70.js.gz +0 -0
- codex/static_root/assets/{admin-menu-_aAGknKO.js → admin-menu-CL7S-xqR.js} +1 -1
- codex/static_root/assets/admin-menu-CL7S-xqR.js.br +0 -0
- codex/static_root/assets/admin-menu-CL7S-xqR.js.gz +0 -0
- codex/static_root/assets/{admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js → admin-settings-button-progress-BgBgdELw.52d052e38bc1.js} +1 -1
- codex/static_root/assets/admin-settings-button-progress-BgBgdELw.52d052e38bc1.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BgBgdELw.52d052e38bc1.js.gz +0 -0
- codex/static_root/assets/{admin-settings-button-progress-BqRFyhjf.js → admin-settings-button-progress-BgBgdELw.js} +1 -1
- codex/static_root/assets/admin-settings-button-progress-BgBgdELw.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BgBgdELw.js.gz +0 -0
- codex/static_root/assets/browser-Bi5ov5k8.a4af87c2a667.js +1 -0
- codex/static_root/assets/browser-Bi5ov5k8.a4af87c2a667.js.br +0 -0
- codex/static_root/assets/browser-Bi5ov5k8.a4af87c2a667.js.gz +0 -0
- codex/static_root/assets/browser-Bi5ov5k8.js +1 -0
- codex/static_root/assets/browser-Bi5ov5k8.js.br +0 -0
- codex/static_root/assets/browser-Bi5ov5k8.js.gz +0 -0
- codex/static_root/assets/{browser-chTZ1CM4.521193ab0710.css → browser-DNzQvgkY.aea8b7d7ca40.css} +1 -1
- codex/static_root/assets/browser-DNzQvgkY.aea8b7d7ca40.css.br +0 -0
- codex/static_root/assets/browser-DNzQvgkY.aea8b7d7ca40.css.gz +0 -0
- codex/static_root/assets/{browser-chTZ1CM4.css → browser-DNzQvgkY.css} +1 -1
- codex/static_root/assets/browser-DNzQvgkY.css.br +0 -0
- codex/static_root/assets/browser-DNzQvgkY.css.gz +0 -0
- codex/static_root/assets/{change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js → change-password-dialog-BKar2Bhb.3dbc2feb3c5a.js} +1 -1
- codex/static_root/assets/change-password-dialog-BKar2Bhb.3dbc2feb3c5a.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BKar2Bhb.3dbc2feb3c5a.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-1ZVP_Q2w.js → change-password-dialog-BKar2Bhb.js} +1 -1
- codex/static_root/assets/change-password-dialog-BKar2Bhb.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BKar2Bhb.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DqCey9Iq.e315d7f8f752.js → confirm-dialog-BoBL4OoS.abef9614fea9.js} +1 -1
- codex/static_root/assets/confirm-dialog-BoBL4OoS.abef9614fea9.js.br +0 -0
- codex/static_root/assets/confirm-dialog-BoBL4OoS.abef9614fea9.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DqCey9Iq.js → confirm-dialog-BoBL4OoS.js} +1 -1
- codex/static_root/assets/confirm-dialog-BoBL4OoS.js.br +0 -0
- codex/static_root/assets/confirm-dialog-BoBL4OoS.js.gz +0 -0
- codex/static_root/assets/{datetime-column-CBA8bYeO.17a31f189d66.js → datetime-column-BsiYRUKO.cadd764c06f4.js} +1 -1
- codex/static_root/assets/datetime-column-BsiYRUKO.cadd764c06f4.js.br +0 -0
- codex/static_root/assets/datetime-column-BsiYRUKO.cadd764c06f4.js.gz +0 -0
- codex/static_root/assets/{datetime-column-CBA8bYeO.js → datetime-column-BsiYRUKO.js} +1 -1
- codex/static_root/assets/datetime-column-BsiYRUKO.js.br +0 -0
- codex/static_root/assets/datetime-column-BsiYRUKO.js.gz +0 -0
- codex/static_root/assets/{filter-mnO3lLPo.659442401b16.js → filter-HnY0knto.07f807227dcc.js} +1 -1
- codex/static_root/assets/filter-HnY0knto.07f807227dcc.js.br +0 -0
- codex/static_root/assets/filter-HnY0knto.07f807227dcc.js.gz +0 -0
- codex/static_root/assets/{filter-mnO3lLPo.js → filter-HnY0knto.js} +1 -1
- codex/static_root/assets/filter-HnY0knto.js.br +0 -0
- codex/static_root/assets/filter-HnY0knto.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Bc677pNb.1b8a2f7c0dc3.js → flag-tab-CcS5pve2.a1df5d92e222.js} +1 -1
- codex/static_root/assets/flag-tab-CcS5pve2.a1df5d92e222.js.br +0 -0
- codex/static_root/assets/flag-tab-CcS5pve2.a1df5d92e222.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Bc677pNb.js → flag-tab-CcS5pve2.js} +1 -1
- codex/static_root/assets/flag-tab-CcS5pve2.js.br +0 -0
- codex/static_root/assets/flag-tab-CcS5pve2.js.gz +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css +1 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css.br +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css.gz +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.css +1 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.css.br +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.css.gz +0 -0
- codex/static_root/assets/{forwardRefs-DPRKg5wq.6f09e9d22fa6.js → forwardRefs-i80xmxX3.db393aeef392.js} +1 -1
- codex/static_root/assets/forwardRefs-i80xmxX3.db393aeef392.js.br +0 -0
- codex/static_root/assets/forwardRefs-i80xmxX3.db393aeef392.js.gz +0 -0
- codex/static_root/assets/{forwardRefs-DPRKg5wq.js → forwardRefs-i80xmxX3.js} +1 -1
- codex/static_root/assets/forwardRefs-i80xmxX3.js.br +0 -0
- codex/static_root/assets/forwardRefs-i80xmxX3.js.gz +0 -0
- codex/static_root/assets/group-tab-SY0gxmHW.f72e944c0ff1.js +1 -0
- codex/static_root/assets/group-tab-SY0gxmHW.f72e944c0ff1.js.br +0 -0
- codex/static_root/assets/group-tab-SY0gxmHW.f72e944c0ff1.js.gz +0 -0
- codex/static_root/assets/group-tab-SY0gxmHW.js +1 -0
- codex/static_root/assets/group-tab-SY0gxmHW.js.br +0 -0
- codex/static_root/assets/group-tab-SY0gxmHW.js.gz +0 -0
- codex/static_root/assets/http-error-EE8gtq5A.07e291d9d955.js +1 -0
- codex/static_root/assets/http-error-EE8gtq5A.07e291d9d955.js.br +0 -0
- codex/static_root/assets/http-error-EE8gtq5A.07e291d9d955.js.gz +0 -0
- codex/static_root/assets/http-error-EE8gtq5A.js +1 -0
- codex/static_root/assets/http-error-EE8gtq5A.js.br +0 -0
- codex/static_root/assets/http-error-EE8gtq5A.js.gz +0 -0
- codex/static_root/assets/{index-9nBCksDQ.4561d2608773.js → index-gVdawuuE.a940fadbac00.js} +1 -1
- codex/static_root/assets/index-gVdawuuE.a940fadbac00.js.br +0 -0
- codex/static_root/assets/index-gVdawuuE.a940fadbac00.js.gz +0 -0
- codex/static_root/assets/{index-9nBCksDQ.js → index-gVdawuuE.js} +1 -1
- codex/static_root/assets/index-gVdawuuE.js.br +0 -0
- codex/static_root/assets/index-gVdawuuE.js.gz +0 -0
- codex/static_root/assets/library-tab-BkDIAvyS.4b4c606b77d6.js +1 -0
- codex/static_root/assets/library-tab-BkDIAvyS.4b4c606b77d6.js.br +0 -0
- codex/static_root/assets/library-tab-BkDIAvyS.4b4c606b77d6.js.gz +0 -0
- codex/static_root/assets/library-tab-BkDIAvyS.js +1 -0
- codex/static_root/assets/library-tab-BkDIAvyS.js.br +0 -0
- codex/static_root/assets/library-tab-BkDIAvyS.js.gz +0 -0
- codex/static_root/assets/main-B5uflU6E.77f3fcd6873f.js +35 -0
- codex/static_root/assets/main-B5uflU6E.77f3fcd6873f.js.br +0 -0
- codex/static_root/assets/main-B5uflU6E.77f3fcd6873f.js.gz +0 -0
- codex/static_root/assets/main-B5uflU6E.js +35 -0
- codex/static_root/assets/main-B5uflU6E.js.br +0 -0
- codex/static_root/assets/main-B5uflU6E.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-wuQbceLs.111d9b9d4133.js +1 -0
- codex/static_root/assets/pager-full-pdf-wuQbceLs.111d9b9d4133.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-wuQbceLs.111d9b9d4133.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-wuQbceLs.js +1 -0
- codex/static_root/assets/pager-full-pdf-wuQbceLs.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-wuQbceLs.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js → pagination-toolbar-D3JQmDiD.8513a4db85f0.js} +1 -1
- codex/static_root/assets/pagination-toolbar-D3JQmDiD.8513a4db85f0.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-D3JQmDiD.8513a4db85f0.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-Dx5JwWG-.js → pagination-toolbar-D3JQmDiD.js} +1 -1
- codex/static_root/assets/pagination-toolbar-D3JQmDiD.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-D3JQmDiD.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-6ZE_HmLT.10d1e16abc78.js → pdf-doc-DRBtl9_2.225a6d89e842.js} +1 -1
- codex/static_root/assets/pdf-doc-DRBtl9_2.225a6d89e842.js.br +0 -0
- codex/static_root/assets/pdf-doc-DRBtl9_2.225a6d89e842.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-6ZE_HmLT.js → pdf-doc-DRBtl9_2.js} +1 -1
- codex/static_root/assets/pdf-doc-DRBtl9_2.js.br +0 -0
- codex/static_root/assets/pdf-doc-DRBtl9_2.js.gz +0 -0
- codex/static_root/assets/reader-DycvjN42.3e2b4f43235e.js +2 -0
- codex/static_root/assets/reader-DycvjN42.3e2b4f43235e.js.br +0 -0
- codex/static_root/assets/reader-DycvjN42.3e2b4f43235e.js.gz +0 -0
- codex/static_root/assets/reader-DycvjN42.js +2 -0
- codex/static_root/assets/reader-DycvjN42.js.br +0 -0
- codex/static_root/assets/reader-DycvjN42.js.gz +0 -0
- codex/static_root/assets/{relation-chips-C96hi5jm.js → relation-chips-CeHIEjvZ.04d302f4fa89.js} +1 -1
- codex/static_root/assets/relation-chips-CeHIEjvZ.04d302f4fa89.js.br +0 -0
- codex/static_root/assets/relation-chips-CeHIEjvZ.04d302f4fa89.js.gz +0 -0
- codex/static_root/assets/{relation-chips-C96hi5jm.9f07c9eb533a.js → relation-chips-CeHIEjvZ.js} +1 -1
- codex/static_root/assets/relation-chips-CeHIEjvZ.js.br +0 -0
- codex/static_root/assets/relation-chips-CeHIEjvZ.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DY8o-jF3.48dfaafaa00e.js +2 -0
- codex/static_root/assets/settings-drawer-DY8o-jF3.48dfaafaa00e.js.br +0 -0
- codex/static_root/assets/settings-drawer-DY8o-jF3.48dfaafaa00e.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DY8o-jF3.js +2 -0
- codex/static_root/assets/settings-drawer-DY8o-jF3.js.br +0 -0
- codex/static_root/assets/settings-drawer-DY8o-jF3.js.gz +0 -0
- codex/static_root/assets/ssrBoot-DRKt_yWP.3b5868abdd61.js +1 -0
- codex/static_root/assets/ssrBoot-DRKt_yWP.3b5868abdd61.js.br +0 -0
- codex/static_root/assets/ssrBoot-DRKt_yWP.3b5868abdd61.js.gz +0 -0
- codex/static_root/assets/ssrBoot-DRKt_yWP.js +1 -0
- codex/static_root/assets/ssrBoot-DRKt_yWP.js.br +0 -0
- codex/static_root/assets/ssrBoot-DRKt_yWP.js.gz +0 -0
- codex/static_root/assets/{stats-tab-CkT2ZGUQ.b704fa46dc88.js → stats-tab-C1XYGBKj.812896e1d09c.js} +1 -1
- codex/static_root/assets/stats-tab-C1XYGBKj.812896e1d09c.js.br +0 -0
- codex/static_root/assets/stats-tab-C1XYGBKj.812896e1d09c.js.gz +0 -0
- codex/static_root/assets/{stats-tab-CkT2ZGUQ.js → stats-tab-C1XYGBKj.js} +1 -1
- codex/static_root/assets/stats-tab-C1XYGBKj.js.br +0 -0
- codex/static_root/assets/stats-tab-C1XYGBKj.js.gz +0 -0
- codex/static_root/assets/task-tab-CW4DQ3KN.81812f9208e5.css +1 -0
- codex/static_root/assets/{task-tab-DfdUY9Rc.478e74041f80.css.br → task-tab-CW4DQ3KN.81812f9208e5.css.br} +1 -1
- codex/static_root/assets/task-tab-CW4DQ3KN.81812f9208e5.css.gz +0 -0
- codex/static_root/assets/task-tab-CW4DQ3KN.css +1 -0
- codex/static_root/assets/{task-tab-DfdUY9Rc.css.br → task-tab-CW4DQ3KN.css.br} +1 -1
- codex/static_root/assets/task-tab-CW4DQ3KN.css.gz +0 -0
- codex/static_root/assets/task-tab-lFRZ0NMd.6b90f40a1a9f.js +1 -0
- codex/static_root/assets/task-tab-lFRZ0NMd.6b90f40a1a9f.js.br +0 -0
- codex/static_root/assets/task-tab-lFRZ0NMd.6b90f40a1a9f.js.gz +0 -0
- codex/static_root/assets/task-tab-lFRZ0NMd.js +1 -0
- codex/static_root/assets/task-tab-lFRZ0NMd.js.br +0 -0
- codex/static_root/assets/task-tab-lFRZ0NMd.js.gz +0 -0
- codex/static_root/assets/unauthorized-Dld9cV8d.4e675fd9991c.js +1 -0
- codex/static_root/assets/unauthorized-Dld9cV8d.4e675fd9991c.js.br +0 -0
- codex/static_root/assets/unauthorized-Dld9cV8d.4e675fd9991c.js.gz +0 -0
- codex/static_root/assets/unauthorized-Dld9cV8d.js +1 -0
- codex/static_root/assets/unauthorized-Dld9cV8d.js.br +0 -0
- codex/static_root/assets/unauthorized-Dld9cV8d.js.gz +0 -0
- codex/static_root/assets/user-tab-CN9nBOMS.f88d8b1c8afe.js +1 -0
- codex/static_root/assets/user-tab-CN9nBOMS.f88d8b1c8afe.js.br +0 -0
- codex/static_root/assets/user-tab-CN9nBOMS.f88d8b1c8afe.js.gz +0 -0
- codex/static_root/assets/user-tab-CN9nBOMS.js +1 -0
- codex/static_root/assets/user-tab-CN9nBOMS.js.br +0 -0
- codex/static_root/assets/user-tab-CN9nBOMS.js.gz +0 -0
- codex/static_root/{manifest.225989a17f49.json → manifest.66d7229aa07a.json} +297 -277
- codex/static_root/manifest.66d7229aa07a.json.br +0 -0
- codex/static_root/manifest.66d7229aa07a.json.gz +0 -0
- codex/static_root/manifest.json +297 -277
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/status_controller.py +10 -13
- codex/urls/root.py +4 -3
- codex/views/admin/api_key.py +1 -4
- codex/views/admin/flag.py +2 -2
- codex/views/admin/group.py +2 -2
- codex/views/admin/library.py +4 -4
- codex/views/admin/stats.py +10 -10
- codex/views/admin/tasks.py +32 -13
- codex/views/admin/user.py +36 -10
- codex/views/auth.py +3 -8
- codex/views/bookmark.py +3 -3
- codex/views/browser/annotate/card.py +2 -2
- codex/views/browser/annotate/order.py +6 -7
- codex/views/browser/bookmark.py +3 -1
- codex/views/browser/breadcrumbs.py +6 -5
- codex/views/browser/browser.py +2 -2
- codex/views/browser/choices.py +7 -8
- codex/views/browser/cover.py +6 -3
- codex/views/browser/download.py +2 -1
- codex/views/browser/filters/bookmark.py +1 -1
- codex/views/browser/filters/field.py +1 -1
- codex/views/browser/filters/filter.py +6 -8
- codex/views/browser/filters/group.py +8 -8
- codex/views/browser/filters/search/field/expression.py +1 -2
- codex/views/browser/filters/search/field/optimize.py +1 -1
- codex/views/browser/filters/search/field/parse.py +2 -2
- codex/views/browser/filters/search/parse.py +4 -19
- codex/views/browser/group_mtime.py +1 -1
- codex/views/browser/metadata/copy_intersections.py +1 -1
- codex/views/browser/metadata/metadata.py +1 -1
- codex/views/browser/mtime.py +1 -1
- codex/views/browser/order_by.py +3 -5
- codex/views/browser/params.py +1 -1
- codex/views/browser/settings.py +1 -1
- codex/views/browser/validate.py +9 -6
- codex/views/const.py +0 -3
- codex/views/frontend.py +1 -1
- codex/views/mixins.py +3 -2
- codex/views/opds/authentication_v1.py +1 -1
- codex/views/opds/const.py +4 -4
- codex/views/opds/urls.py +1 -1
- codex/views/opds/util.py +4 -4
- codex/views/opds/v1/entry/entry.py +4 -2
- codex/views/opds/v1/entry/links.py +4 -5
- codex/views/opds/v1/facets.py +8 -4
- codex/views/opds/v1/feed.py +5 -5
- codex/views/opds/v1/links.py +11 -6
- codex/views/opds/v2/const.py +5 -2
- codex/views/opds/v2/feed.py +3 -3
- codex/views/opds/v2/links.py +18 -11
- codex/views/opds/v2/progression.py +8 -9
- codex/views/opds/v2/publications.py +10 -20
- codex/views/public.py +2 -2
- codex/views/reader/books.py +3 -2
- codex/views/reader/page.py +3 -3
- codex/views/reader/params.py +2 -2
- codex/views/reader/reader.py +6 -6
- codex/views/reader/settings.py +1 -1
- codex/views/session.py +10 -3
- codex/views/settings.py +2 -2
- codex/views/timezone.py +2 -2
- codex/views/util.py +10 -9
- codex/views/version.py +1 -1
- {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/METADATA +3 -1
- {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/RECORD +433 -415
- {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/WHEEL +1 -1
- codex/serializers/fields.py +0 -183
- codex/static_root/assets/VCheckbox-FaT6MGfu.f6732060f734.js.br +0 -0
- codex/static_root/assets/VCheckbox-FaT6MGfu.f6732060f734.js.gz +0 -0
- codex/static_root/assets/VCheckbox-FaT6MGfu.js.br +0 -0
- codex/static_root/assets/VCheckbox-FaT6MGfu.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.847452d574cf.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.847452d574cf.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.js.gz +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.6f431e19a453.js.br +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.6f431e19a453.js.gz +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.js.br +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.js.gz +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.c0c7eef71eec.js.br +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.c0c7eef71eec.js.gz +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.js.br +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.js.gz +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.9dbee744fb3c.js.br +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.9dbee744fb3c.js.gz +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.js.br +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.43f06d0a45e0.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.43f06d0a45e0.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.js.gz +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js +0 -1
- codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js.br +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js.gz +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.js +0 -1
- codex/static_root/assets/VForm-DNmY-qFJ.js.br +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.fe5e0e74ffee.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.fe5e0e74ffee.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.js.gz +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js +0 -1
- codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js.br +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js.gz +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.js +0 -1
- codex/static_root/assets/VSelect-BSLmHX_P.js.br +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.ad6c96efe57c.js.br +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.ad6c96efe57c.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.js.br +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.js.gz +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.77ef3973bb54.js.br +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.77ef3973bb54.js.gz +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.js.br +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js +0 -1
- codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js.br +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.js +0 -1
- codex/static_root/assets/VWindowItem-D8lWcbQY.js.br +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.js.gz +0 -0
- codex/static_root/assets/admin-80eqCqtz.d6deb9edb8cb.js.br +0 -0
- codex/static_root/assets/admin-80eqCqtz.d6deb9edb8cb.js.gz +0 -0
- codex/static_root/assets/admin-80eqCqtz.js.br +0 -0
- codex/static_root/assets/admin-80eqCqtz.js.gz +0 -0
- codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js +0 -1
- codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js.br +0 -0
- codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js.gz +0 -0
- codex/static_root/assets/admin-CBLHOrM9.js +0 -1
- codex/static_root/assets/admin-CBLHOrM9.js.br +0 -0
- codex/static_root/assets/admin-CBLHOrM9.js.gz +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.0c08c9bd2e7d.js.br +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.0c08c9bd2e7d.js.gz +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.js.br +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.js.gz +0 -0
- codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js +0 -1
- codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js.br +0 -0
- codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js.gz +0 -0
- codex/static_root/assets/browser-DAprXYuP.js +0 -1
- codex/static_root/assets/browser-DAprXYuP.js.br +0 -0
- codex/static_root/assets/browser-DAprXYuP.js.gz +0 -0
- codex/static_root/assets/browser-chTZ1CM4.521193ab0710.css.br +0 -0
- codex/static_root/assets/browser-chTZ1CM4.521193ab0710.css.gz +0 -0
- codex/static_root/assets/browser-chTZ1CM4.css.br +0 -0
- codex/static_root/assets/browser-chTZ1CM4.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js.br +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.js.br +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.e315d7f8f752.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.e315d7f8f752.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.js.gz +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.17a31f189d66.js.br +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.17a31f189d66.js.gz +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.js.br +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.js.gz +0 -0
- codex/static_root/assets/filter-mnO3lLPo.659442401b16.js.br +0 -0
- codex/static_root/assets/filter-mnO3lLPo.659442401b16.js.gz +0 -0
- codex/static_root/assets/filter-mnO3lLPo.js.br +0 -0
- codex/static_root/assets/filter-mnO3lLPo.js.gz +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.1b8a2f7c0dc3.js.br +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.1b8a2f7c0dc3.js.gz +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.js.br +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.js.gz +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css +0 -1
- codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css.br +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css.gz +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.css +0 -1
- codex/static_root/assets/flag-tab-glFvEgEl.css.br +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.css.gz +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.6f09e9d22fa6.js.br +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.6f09e9d22fa6.js.gz +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.js.br +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.js.gz +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js +0 -1
- codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js.br +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js.gz +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.js +0 -1
- codex/static_root/assets/group-tab-BVlBrMwo.js.br +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.js.gz +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js +0 -1
- codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js.br +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js.gz +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.js +0 -1
- codex/static_root/assets/http-error-C9-_aEBF.js.br +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.js.gz +0 -0
- codex/static_root/assets/index-9nBCksDQ.4561d2608773.js.br +0 -0
- codex/static_root/assets/index-9nBCksDQ.4561d2608773.js.gz +0 -0
- codex/static_root/assets/index-9nBCksDQ.js.br +0 -0
- codex/static_root/assets/index-9nBCksDQ.js.gz +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js +0 -1
- codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js.br +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js.gz +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.js +0 -1
- codex/static_root/assets/library-tab-BMe3dKzy.js.br +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.js.gz +0 -0
- codex/static_root/assets/main-BbNUiWEb.565b616eb122.js +0 -35
- codex/static_root/assets/main-BbNUiWEb.565b616eb122.js.br +0 -0
- codex/static_root/assets/main-BbNUiWEb.565b616eb122.js.gz +0 -0
- codex/static_root/assets/main-BbNUiWEb.js +0 -35
- codex/static_root/assets/main-BbNUiWEb.js.br +0 -0
- codex/static_root/assets/main-BbNUiWEb.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js +0 -1
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.js +0 -1
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.js.gz +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.10d1e16abc78.js.br +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.10d1e16abc78.js.gz +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.js.br +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.js.gz +0 -0
- codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js +0 -2
- codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js.br +0 -0
- codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js.gz +0 -0
- codex/static_root/assets/reader-DdPKDuh5.js +0 -2
- codex/static_root/assets/reader-DdPKDuh5.js.br +0 -0
- codex/static_root/assets/reader-DdPKDuh5.js.gz +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.9f07c9eb533a.js.br +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.9f07c9eb533a.js.gz +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.js.br +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js +0 -2
- codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js.br +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.js +0 -2
- codex/static_root/assets/settings-drawer-CqbT1rid.js.br +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.js.gz +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.b704fa46dc88.js.br +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.b704fa46dc88.js.gz +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.js.br +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.js.gz +0 -0
- codex/static_root/assets/task-tab-DfdUY9Rc.478e74041f80.css +0 -1
- codex/static_root/assets/task-tab-DfdUY9Rc.478e74041f80.css.gz +0 -0
- codex/static_root/assets/task-tab-DfdUY9Rc.css +0 -1
- codex/static_root/assets/task-tab-DfdUY9Rc.css.gz +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js +0 -1
- codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js.br +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js.gz +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.js +0 -1
- codex/static_root/assets/task-tab-a-DcU3Tj.js.br +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.js.gz +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js +0 -1
- codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js.br +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js.gz +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.js +0 -1
- codex/static_root/assets/unauthorized-BOl5YpL3.js.br +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.js.gz +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js +0 -1
- codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js.br +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js.gz +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.js +0 -1
- codex/static_root/assets/user-tab-BBDZvD8G.js.br +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.js.gz +0 -0
- codex/static_root/manifest.225989a17f49.json.br +0 -0
- codex/static_root/manifest.225989a17f49.json.gz +0 -0
- {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/LICENSE +0 -0
- {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/entry_points.txt +0 -0
|
@@ -5,6 +5,7 @@ from django.db.models.expressions import F
|
|
|
5
5
|
from django.db.models.functions import Now
|
|
6
6
|
from django.db.models.query import Q
|
|
7
7
|
|
|
8
|
+
from codex.choices.notifications import Notifications
|
|
8
9
|
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
9
10
|
from codex.librarian.notifier.tasks import NotifierTask
|
|
10
11
|
from codex.models import Bookmark, Comic
|
|
@@ -28,6 +29,35 @@ class BookmarkUpdate(GroupACLMixin):
|
|
|
28
29
|
|
|
29
30
|
# Used by Bookmarkd and view.bookmark.
|
|
30
31
|
|
|
32
|
+
@classmethod
|
|
33
|
+
def _get_existing_bookmarks_for_update(cls, auth_filter, comic_pks, updates):
|
|
34
|
+
# Get existing bookmarks
|
|
35
|
+
query_filter = Q(**auth_filter) & Q(comic__in=comic_pks)
|
|
36
|
+
existing_bookmarks = Bookmark.objects.filter(query_filter)
|
|
37
|
+
if updates.get("page") is not None:
|
|
38
|
+
existing_bookmarks = existing_bookmarks.annotate(
|
|
39
|
+
page_count=F("comic__page_count")
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
update_fields = (set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS) | {"updated_at"}
|
|
43
|
+
only_fields = (*update_fields, "pk")
|
|
44
|
+
|
|
45
|
+
existing_bookmarks = existing_bookmarks.only(*only_fields)
|
|
46
|
+
return existing_bookmarks, update_fields
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _prepare_bookmark_updates(cls, existing_bookmarks, updates):
|
|
50
|
+
# Prepare updates
|
|
51
|
+
update_bookmarks = []
|
|
52
|
+
for bm in existing_bookmarks:
|
|
53
|
+
cls._update_bookmarks_validate_page(bm, updates)
|
|
54
|
+
cls._update_bookmarks_validate_two_pages(bm, updates)
|
|
55
|
+
for key, value in updates.items():
|
|
56
|
+
setattr(bm, key, value)
|
|
57
|
+
bm.updated_at = Now()
|
|
58
|
+
update_bookmarks.append(bm)
|
|
59
|
+
return update_bookmarks
|
|
60
|
+
|
|
31
61
|
@staticmethod
|
|
32
62
|
def _update_bookmarks_validate_page(bm, updates):
|
|
33
63
|
"""Force bookmark page into valid range."""
|
|
@@ -44,9 +74,10 @@ class BookmarkUpdate(GroupACLMixin):
|
|
|
44
74
|
@staticmethod
|
|
45
75
|
def _update_bookmarks_validate_two_pages(bm, updates):
|
|
46
76
|
"""Force vertical view to not use two pages."""
|
|
47
|
-
rd = updates.get("reading_direction")
|
|
48
77
|
if bm.two_pages and bool(
|
|
49
|
-
{
|
|
78
|
+
{bm.reading_direction, updates.get("reading_direction")}.intersection(
|
|
79
|
+
_VERTICAL_READING_DIRECTIONS
|
|
80
|
+
)
|
|
50
81
|
):
|
|
51
82
|
updates["two_pages"] = None
|
|
52
83
|
|
|
@@ -54,41 +85,45 @@ class BookmarkUpdate(GroupACLMixin):
|
|
|
54
85
|
def _notify_library_changed(uid):
|
|
55
86
|
"""Notify one user that their library changed."""
|
|
56
87
|
group = f"user_{uid}"
|
|
57
|
-
task = NotifierTask(
|
|
88
|
+
task = NotifierTask(Notifications.BOOKMARK.value, group)
|
|
58
89
|
LIBRARIAN_QUEUE.put(task)
|
|
59
90
|
|
|
60
91
|
@classmethod
|
|
61
|
-
def _update_bookmarks(cls, auth_filter, comic_pks, updates
|
|
92
|
+
def _update_bookmarks(cls, auth_filter, comic_pks, updates) -> int:
|
|
62
93
|
"""Update existing bookmarks."""
|
|
63
94
|
count = 0
|
|
64
95
|
if not updates:
|
|
65
96
|
return count
|
|
66
|
-
group_acl_filter = cls.get_group_acl_filter(Bookmark, user)
|
|
67
|
-
query_filter = group_acl_filter & Q(**auth_filter) & Q(comic__in=comic_pks)
|
|
68
|
-
existing_bookmarks = Bookmark.objects.filter(query_filter)
|
|
69
|
-
if updates.get("page") is not None:
|
|
70
|
-
existing_bookmarks = existing_bookmarks.annotate(
|
|
71
|
-
page_count=F("comic__page_count")
|
|
72
|
-
)
|
|
73
97
|
|
|
74
|
-
update_fields =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
existing_bookmarks = existing_bookmarks.only(*only_fields)
|
|
79
|
-
update_bookmarks = []
|
|
80
|
-
for bm in existing_bookmarks:
|
|
81
|
-
cls._update_bookmarks_validate_page(bm, updates)
|
|
82
|
-
cls._update_bookmarks_validate_two_pages(bm, updates)
|
|
83
|
-
for key, value in updates.items():
|
|
84
|
-
setattr(bm, key, value)
|
|
85
|
-
bm.updated_at = Now()
|
|
86
|
-
update_bookmarks.append(bm)
|
|
98
|
+
existing_bookmarks, update_fields = cls._get_existing_bookmarks_for_update(
|
|
99
|
+
auth_filter, comic_pks, updates
|
|
100
|
+
)
|
|
101
|
+
update_bookmarks = cls._prepare_bookmark_updates(existing_bookmarks, updates)
|
|
87
102
|
count = len(update_bookmarks)
|
|
103
|
+
|
|
104
|
+
# Bulk update
|
|
88
105
|
if count:
|
|
89
106
|
Bookmark.objects.bulk_update(update_bookmarks, tuple(update_fields))
|
|
90
107
|
return count
|
|
91
108
|
|
|
109
|
+
@classmethod
|
|
110
|
+
def _get_comics_without_bookmarks(cls, auth_filter, comic_pks):
|
|
111
|
+
"""Get comics without bookmarks."""
|
|
112
|
+
exclude = {}
|
|
113
|
+
for key, value in auth_filter.items():
|
|
114
|
+
exclude["bookmark__" + key] = value
|
|
115
|
+
query_filter = Q(pk__in=comic_pks) & ~Q(**exclude)
|
|
116
|
+
return Comic.objects.filter(query_filter).only("pk")
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def _prepare_bookmark_creates(cls, create_bookmark_comics, auth_filter, updates):
|
|
120
|
+
# Prepare creates
|
|
121
|
+
create_bookmarks = []
|
|
122
|
+
for comic in create_bookmark_comics:
|
|
123
|
+
bm = Bookmark(comic=comic, **auth_filter, **updates)
|
|
124
|
+
create_bookmarks.append(bm)
|
|
125
|
+
return create_bookmarks
|
|
126
|
+
|
|
92
127
|
@staticmethod
|
|
93
128
|
def _create_bookmarks_validate_two_pages(updates):
|
|
94
129
|
"""Force vertical view to not use two pages."""
|
|
@@ -99,54 +134,54 @@ class BookmarkUpdate(GroupACLMixin):
|
|
|
99
134
|
updates.pop("two_pages", None)
|
|
100
135
|
|
|
101
136
|
@classmethod
|
|
102
|
-
def _create_bookmarks(cls, auth_filter, comic_pks, updates
|
|
137
|
+
def _create_bookmarks(cls, auth_filter, comic_pks, updates) -> int:
|
|
103
138
|
"""Create new bookmarks for comics that don't exist yet."""
|
|
104
139
|
count = 0
|
|
105
140
|
cls._create_bookmarks_validate_two_pages(updates)
|
|
106
141
|
if not updates:
|
|
107
142
|
return count
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for comic in create_bookmark_comics:
|
|
116
|
-
bm = Bookmark(comic=comic, **auth_filter, **updates)
|
|
117
|
-
create_bookmarks.append(bm)
|
|
143
|
+
|
|
144
|
+
create_bookmark_comics = cls._get_comics_without_bookmarks(
|
|
145
|
+
auth_filter, comic_pks
|
|
146
|
+
)
|
|
147
|
+
create_bookmarks = cls._prepare_bookmark_creates(
|
|
148
|
+
create_bookmark_comics, auth_filter, updates
|
|
149
|
+
)
|
|
118
150
|
count = len(create_bookmarks)
|
|
151
|
+
|
|
152
|
+
# Bulk create
|
|
119
153
|
if count:
|
|
120
154
|
Bookmark.objects.bulk_create(
|
|
121
155
|
create_bookmarks,
|
|
122
156
|
update_fields=tuple(_BOOKMARK_UPDATE_FIELDS),
|
|
123
|
-
unique_fields=Bookmark._meta.unique_together[0],
|
|
157
|
+
unique_fields=Bookmark._meta.unique_together[0],
|
|
124
158
|
)
|
|
125
159
|
return count
|
|
126
160
|
|
|
127
161
|
@classmethod
|
|
128
|
-
def _update_user_active(cls,
|
|
162
|
+
def _update_user_active(cls, auth_filter, log):
|
|
129
163
|
"""Update user active."""
|
|
130
164
|
# Offline because profile gets hit rapidly in succession.
|
|
131
165
|
try:
|
|
132
|
-
|
|
166
|
+
user = None
|
|
167
|
+
for key, value in auth_filter.items():
|
|
168
|
+
if key == "user":
|
|
169
|
+
user = User.objects.get(pk=value)
|
|
170
|
+
if user:
|
|
171
|
+
UserActive.objects.update_or_create(user=user)
|
|
172
|
+
break
|
|
173
|
+
except User.DoesNotExist:
|
|
174
|
+
pass
|
|
133
175
|
except Exception as exc:
|
|
134
|
-
reason = f"
|
|
176
|
+
reason = f"Update user activity {exc}"
|
|
135
177
|
log.warning(reason)
|
|
136
178
|
|
|
137
179
|
@classmethod
|
|
138
180
|
def update_bookmarks(cls, auth_filter, comic_pks, updates, log):
|
|
139
181
|
"""Update a user bookmark."""
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
user = User.objects.get(pk=value)
|
|
144
|
-
break
|
|
145
|
-
|
|
146
|
-
count = cls._update_bookmarks(auth_filter, comic_pks, updates, user)
|
|
147
|
-
count += cls._create_bookmarks(auth_filter, comic_pks, updates, user)
|
|
148
|
-
if user:
|
|
149
|
-
cls._update_user_active(user, log)
|
|
182
|
+
count = cls._update_bookmarks(auth_filter, comic_pks, updates)
|
|
183
|
+
count += cls._create_bookmarks(auth_filter, comic_pks, updates)
|
|
184
|
+
cls._update_user_active(auth_filter, log)
|
|
150
185
|
if count:
|
|
151
186
|
uid = next(iter(auth_filter.values()))
|
|
152
187
|
cls._notify_library_changed(uid)
|
codex/librarian/covers/create.py
CHANGED
|
@@ -32,7 +32,7 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
|
32
32
|
with Image.open(image_io) as cover_image:
|
|
33
33
|
cover_image.thumbnail(
|
|
34
34
|
_THUMBNAIL_SIZE,
|
|
35
|
-
Image.Resampling.LANCZOS,
|
|
35
|
+
Image.Resampling.LANCZOS,
|
|
36
36
|
reducing_gap=3.0,
|
|
37
37
|
)
|
|
38
38
|
cover_image.save(cover_thumb_buffer, "WEBP", method=6)
|
|
@@ -41,7 +41,8 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
|
41
41
|
|
|
42
42
|
@classmethod
|
|
43
43
|
def _get_comic_cover_image(cls, comic_path):
|
|
44
|
-
"""
|
|
44
|
+
"""
|
|
45
|
+
Create comic cover if none exists.
|
|
45
46
|
|
|
46
47
|
Return image thumb data or path to missing file thumb.
|
|
47
48
|
"""
|
|
@@ -59,8 +60,9 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
|
59
60
|
return f.read()
|
|
60
61
|
|
|
61
62
|
@classmethod
|
|
62
|
-
def create_cover_from_path(cls, pk, cover_path, log, librarian_queue, custom
|
|
63
|
-
"""
|
|
63
|
+
def create_cover_from_path(cls, pk, cover_path, log, librarian_queue, custom):
|
|
64
|
+
"""
|
|
65
|
+
Create cover for path.
|
|
64
66
|
|
|
65
67
|
Called from views/cover.
|
|
66
68
|
"""
|
|
@@ -95,7 +97,7 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
|
95
97
|
# zero length file is code for missing.
|
|
96
98
|
cover_path.touch()
|
|
97
99
|
|
|
98
|
-
def _bulk_create_comic_covers(self, pks, custom
|
|
100
|
+
def _bulk_create_comic_covers(self, pks, custom):
|
|
99
101
|
"""Create bulk comic covers."""
|
|
100
102
|
num_comics = len(pks)
|
|
101
103
|
if not num_comics:
|
|
@@ -115,7 +117,7 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
|
115
117
|
else:
|
|
116
118
|
# bulk contributor creates covers inline
|
|
117
119
|
data = self.create_cover_from_path(
|
|
118
|
-
pk, cover_path, self.log, self.librarian_queue
|
|
120
|
+
pk, cover_path, self.log, self.librarian_queue, custom=False
|
|
119
121
|
)
|
|
120
122
|
if data:
|
|
121
123
|
data.close()
|
|
@@ -134,6 +136,6 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
|
134
136
|
def create_all_covers(self):
|
|
135
137
|
"""Create all covers for all libraries."""
|
|
136
138
|
pks = CustomCover.objects.values_list("pk", flat=True)
|
|
137
|
-
self._bulk_create_comic_covers(pks, True)
|
|
139
|
+
self._bulk_create_comic_covers(pks, custom=True)
|
|
138
140
|
pks = Comic.objects.values_list("pk", flat=True)
|
|
139
|
-
self._bulk_create_comic_covers(pks, False)
|
|
141
|
+
self._bulk_create_comic_covers(pks, custom=False)
|
codex/librarian/covers/path.py
CHANGED
|
@@ -21,20 +21,21 @@ class CoverPathMixin:
|
|
|
21
21
|
"""Translate an integer into an efficient filesystem path."""
|
|
22
22
|
fnv = fnv1a_32(bytes(str(pk).zfill(cls._ZFILL), "utf-8"))
|
|
23
23
|
hex_str = format(fnv, f"0{cls._ZFILL}x")
|
|
24
|
-
parts = [
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
parts = [
|
|
25
|
+
hex_str[i : i + cls._PATH_STEP]
|
|
26
|
+
for i in range(0, len(hex_str), cls._PATH_STEP)
|
|
27
|
+
]
|
|
27
28
|
return Path("/".join(parts))
|
|
28
29
|
|
|
29
30
|
@classmethod
|
|
30
|
-
def get_cover_path(cls, pk, custom
|
|
31
|
+
def get_cover_path(cls, pk, custom):
|
|
31
32
|
"""Get cover path for comic pk."""
|
|
32
33
|
cover_path = cls._hex_path(pk)
|
|
33
34
|
root = cls.CUSTOM_COVERS_ROOT if custom else cls.COVERS_ROOT
|
|
34
35
|
return root / cover_path.with_suffix(".webp")
|
|
35
36
|
|
|
36
37
|
@classmethod
|
|
37
|
-
def get_cover_paths(cls, pks, custom
|
|
38
|
+
def get_cover_paths(cls, pks, custom):
|
|
38
39
|
"""Get cover paths for many comic pks."""
|
|
39
40
|
cover_paths = set()
|
|
40
41
|
for pk in pks:
|
codex/librarian/covers/purge.py
CHANGED
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
from codex.librarian.covers.create import CoverCreateThread
|
|
8
8
|
from codex.librarian.covers.status import CoverStatusTypes
|
|
9
|
-
from codex.librarian.notifier.tasks import
|
|
9
|
+
from codex.librarian.notifier.tasks import COVERS_CHANGED_TASK
|
|
10
10
|
from codex.models import Comic
|
|
11
11
|
from codex.models.paths import CustomCover
|
|
12
12
|
from codex.status import Status
|
|
@@ -67,9 +67,9 @@ class CoverPurgeThread(CoverCreateThread):
|
|
|
67
67
|
self.log.info("Removed entire comic cover cache.")
|
|
68
68
|
shutil.rmtree(self.CUSTOM_COVERS_ROOT)
|
|
69
69
|
self.log.info("Removed entire custom cover cache.")
|
|
70
|
-
except
|
|
70
|
+
except OSError as exc:
|
|
71
71
|
self.log.warning(exc)
|
|
72
|
-
librarian_queue.put(
|
|
72
|
+
librarian_queue.put(COVERS_CHANGED_TASK)
|
|
73
73
|
|
|
74
74
|
def _cleanup_orphan_covers(self, cover_class, cover_root, name):
|
|
75
75
|
"""Remove all orphan cover thumbs."""
|
|
@@ -77,7 +77,7 @@ class CoverPurgeThread(CoverCreateThread):
|
|
|
77
77
|
self.log.debug(f"Removing covers from missing {name}.")
|
|
78
78
|
self.status_controller.start_many(self._CLEANUP_STATUS_MAP)
|
|
79
79
|
pks = cover_class.objects.all().values_list("pk", flat=True)
|
|
80
|
-
db_cover_paths = self.get_cover_paths(pks)
|
|
80
|
+
db_cover_paths = self.get_cover_paths(pks, custom=False)
|
|
81
81
|
|
|
82
82
|
orphan_cover_paths = set()
|
|
83
83
|
for root, _, filenames in os.walk(cover_root):
|
|
@@ -222,10 +222,10 @@ class AggregateMetadataImporter(ExtractMetadataImporter):
|
|
|
222
222
|
if self.task.force_import_metadata:
|
|
223
223
|
import_metadata = True
|
|
224
224
|
else:
|
|
225
|
-
key = AdminFlag.FlagChoices.IMPORT_METADATA.value
|
|
225
|
+
key = AdminFlag.FlagChoices.IMPORT_METADATA.value
|
|
226
226
|
import_metadata = AdminFlag.objects.get(key=key).on
|
|
227
227
|
if not import_metadata:
|
|
228
|
-
self.log.
|
|
228
|
+
self.log.warning("Admin flag set to NOT import metadata.")
|
|
229
229
|
|
|
230
230
|
# Init metadata, extract and aggregate
|
|
231
231
|
self.metadata[MDS] = {}
|
|
@@ -73,7 +73,6 @@ class CacheUpdateImporter(InitImporter):
|
|
|
73
73
|
try:
|
|
74
74
|
log_list = []
|
|
75
75
|
for model in GROUP_MODELS:
|
|
76
|
-
# self.log.debug(f"Updating timestamps for {model.__name__}s...")
|
|
77
76
|
count = self._update_group_model(
|
|
78
77
|
force_update_group_map, model, start_time, log_list
|
|
79
78
|
)
|
|
@@ -85,9 +84,7 @@ class CacheUpdateImporter(InitImporter):
|
|
|
85
84
|
|
|
86
85
|
if total_count:
|
|
87
86
|
groups_log = ", ".join(log_list)
|
|
88
|
-
self.log.info(
|
|
89
|
-
f"Updated timestamps for {groups_log}."
|
|
90
|
-
)
|
|
87
|
+
self.log.info(f"Updated timestamps for {groups_log}.")
|
|
91
88
|
self.changed += total_count
|
|
92
89
|
finally:
|
|
93
90
|
self.status_controller.finish(status)
|
|
@@ -78,7 +78,7 @@ class CreateComicsImporter(LinkComicsImporter):
|
|
|
78
78
|
Comic.objects.bulk_update(update_comics, BULK_UPDATE_COMIC_FIELDS)
|
|
79
79
|
count = len(update_comics)
|
|
80
80
|
|
|
81
|
-
self._remove_covers(comic_pks, False)
|
|
81
|
+
self._remove_covers(comic_pks, custom=False)
|
|
82
82
|
self.log.debug(f"Purging covers for {len(comic_pks)} updated comics.")
|
|
83
83
|
if count:
|
|
84
84
|
self.log.info(f"Updated {count} comics.")
|
|
@@ -126,7 +126,7 @@ class CreateComicsImporter(LinkComicsImporter):
|
|
|
126
126
|
create_comics,
|
|
127
127
|
update_conflicts=True,
|
|
128
128
|
update_fields=BULK_CREATE_COMIC_FIELDS,
|
|
129
|
-
unique_fields=Comic._meta.unique_together[0],
|
|
129
|
+
unique_fields=Comic._meta.unique_together[0],
|
|
130
130
|
)
|
|
131
131
|
count = len(create_comics)
|
|
132
132
|
if count:
|
|
@@ -43,7 +43,7 @@ class CreateCoversImporter(CreateComicsImporter):
|
|
|
43
43
|
if LINK_COVER_PKS not in self.metadata:
|
|
44
44
|
self.metadata[LINK_COVER_PKS] = set()
|
|
45
45
|
self.metadata[LINK_COVER_PKS].update(update_cover_pks)
|
|
46
|
-
self._remove_covers(update_cover_pks, custom=True)
|
|
46
|
+
self._remove_covers(update_cover_pks, custom=True)
|
|
47
47
|
count = len(update_covers)
|
|
48
48
|
if status:
|
|
49
49
|
status.add_complete(count)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Create all missing comic foreign keys for an import.
|
|
2
3
|
|
|
3
4
|
So we may safely create the comics next.
|
|
4
5
|
"""
|
|
@@ -189,7 +190,7 @@ class CreateForeignKeysImporter(CreateCoversImporter):
|
|
|
189
190
|
create_folders,
|
|
190
191
|
update_conflicts=True,
|
|
191
192
|
update_fields=BULK_UPDATE_FOLDER_FIELDS,
|
|
192
|
-
unique_fields=Folder._meta.unique_together[0],
|
|
193
|
+
unique_fields=Folder._meta.unique_together[0],
|
|
193
194
|
)
|
|
194
195
|
count = len(create_folders)
|
|
195
196
|
status.add_complete(count)
|
|
@@ -296,7 +297,7 @@ class CreateForeignKeysImporter(CreateCoversImporter):
|
|
|
296
297
|
create_objs,
|
|
297
298
|
update_conflicts=True,
|
|
298
299
|
update_fields=CREATE_DICT_UPDATE_FIELDS[model],
|
|
299
|
-
unique_fields=model._meta.unique_together[0],
|
|
300
|
+
unique_fields=model._meta.unique_together[0],
|
|
300
301
|
)
|
|
301
302
|
count = len(create_objs)
|
|
302
303
|
if count:
|
|
@@ -13,7 +13,7 @@ from codex.status import Status
|
|
|
13
13
|
class DeletedImporter(CacheUpdateImporter):
|
|
14
14
|
"""Clean up database methods."""
|
|
15
15
|
|
|
16
|
-
def _remove_covers(self, delete_pks, custom
|
|
16
|
+
def _remove_covers(self, delete_pks, custom: bool):
|
|
17
17
|
task = CoverRemoveTask(delete_pks, custom)
|
|
18
18
|
self.librarian_queue.put(task)
|
|
19
19
|
|
|
@@ -34,7 +34,7 @@ class DeletedImporter(CacheUpdateImporter):
|
|
|
34
34
|
)
|
|
35
35
|
folders.delete()
|
|
36
36
|
|
|
37
|
-
self._remove_covers(delete_comic_pks)
|
|
37
|
+
self._remove_covers(delete_comic_pks, custom=False)
|
|
38
38
|
|
|
39
39
|
count = len(delete_comic_pks)
|
|
40
40
|
if count:
|
|
@@ -96,7 +96,7 @@ class DeletedImporter(CacheUpdateImporter):
|
|
|
96
96
|
delete_comic_pks = frozenset(delete_qs.values_list("pk", flat=True))
|
|
97
97
|
delete_qs.delete()
|
|
98
98
|
|
|
99
|
-
self._remove_covers(delete_comic_pks)
|
|
99
|
+
self._remove_covers(delete_comic_pks, custom=False)
|
|
100
100
|
|
|
101
101
|
count = len(delete_comic_pks)
|
|
102
102
|
if count:
|
|
@@ -9,8 +9,13 @@ from comicbox.box import Comicbox
|
|
|
9
9
|
from comicbox.box.computed import IDENTIFIERS_KEY
|
|
10
10
|
from comicbox.exceptions import UnsupportedArchiveTypeError
|
|
11
11
|
from comicbox.schemas.comicbox_mixin import CONTRIBUTORS_KEY
|
|
12
|
-
from django.db.models import
|
|
13
|
-
|
|
12
|
+
from django.db.models.fields import (
|
|
13
|
+
CharField,
|
|
14
|
+
DecimalField,
|
|
15
|
+
PositiveSmallIntegerField,
|
|
16
|
+
TextField,
|
|
17
|
+
)
|
|
18
|
+
from nh3 import clean
|
|
14
19
|
from rarfile import BadRarFile
|
|
15
20
|
|
|
16
21
|
from codex.librarian.importer.const import FIS, STORY_ARCS_METADATA_KEY
|
|
@@ -36,29 +41,39 @@ _MD_INVALID_KEYS = frozenset(
|
|
|
36
41
|
"updated_at",
|
|
37
42
|
)
|
|
38
43
|
)
|
|
39
|
-
_MD_VALID_KEYS =
|
|
40
|
-
(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
_MD_VALID_KEYS = tuple(
|
|
45
|
+
sorted(
|
|
46
|
+
frozenset(
|
|
47
|
+
(
|
|
48
|
+
*(
|
|
49
|
+
field.name
|
|
50
|
+
for field in Comic._meta.get_fields()
|
|
51
|
+
if field.name not in _MD_INVALID_KEYS
|
|
52
|
+
),
|
|
53
|
+
"story_arcs",
|
|
54
|
+
)
|
|
55
|
+
)
|
|
47
56
|
)
|
|
48
57
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_keys_by_field_type(field_type):
|
|
61
|
+
return tuple(
|
|
62
|
+
sorted(
|
|
63
|
+
frozenset(
|
|
64
|
+
field.name
|
|
65
|
+
for field in Comic._meta.get_fields()
|
|
66
|
+
if isinstance(field, field_type)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
_MD_CHAR_KEYS = _get_keys_by_field_type(CharField)
|
|
73
|
+
_MD_TEXT_KEYS = _get_keys_by_field_type(TextField)
|
|
74
|
+
_MD_STR_KEYS = tuple(sorted(_MD_CHAR_KEYS + _MD_TEXT_KEYS))
|
|
75
|
+
_MD_DECIMAL_KEYS = _get_keys_by_field_type(DecimalField)
|
|
76
|
+
_MD_PSI_KEYS = _get_keys_by_field_type(PositiveSmallIntegerField)
|
|
62
77
|
_PSI_MAX = 2**31 - 1
|
|
63
78
|
_SI_MIN = 2**15 * -1
|
|
64
79
|
_SI_MAX = 2**15 - 1
|
|
@@ -94,7 +109,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
94
109
|
|
|
95
110
|
@classmethod
|
|
96
111
|
def _clean_decimal(cls, value, field_name: str):
|
|
97
|
-
field: DecimalField = Comic._meta.get_field(field_name) # type: ignore
|
|
112
|
+
field: DecimalField = Comic._meta.get_field(field_name) # type: ignore[reportAssignmentType]
|
|
98
113
|
try:
|
|
99
114
|
quantize_str = Decimal(f"1e-{field.decimal_places}")
|
|
100
115
|
value = value.quantize(quantize_str, rounding=ROUND_DOWN)
|
|
@@ -145,11 +160,15 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
145
160
|
cls._assign_or_pop(obj, key, value)
|
|
146
161
|
|
|
147
162
|
@staticmethod
|
|
148
|
-
def
|
|
163
|
+
def _clean_strfield(model, field_name, value):
|
|
149
164
|
try:
|
|
150
|
-
field: CharField = model._meta.get_field(field_name) # type: ignore
|
|
151
165
|
if value:
|
|
152
|
-
value = value
|
|
166
|
+
value = value.strip()
|
|
167
|
+
field = model._meta.get_field(field_name) # type: ignore[reportAssignmentType]
|
|
168
|
+
max_length = getattr(field, "max_length", 0)
|
|
169
|
+
if max_length:
|
|
170
|
+
value = value[:max_length]
|
|
171
|
+
value = clean(value)
|
|
153
172
|
if not value:
|
|
154
173
|
value = None
|
|
155
174
|
except Exception:
|
|
@@ -157,10 +176,10 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
157
176
|
return value
|
|
158
177
|
|
|
159
178
|
@classmethod
|
|
160
|
-
def
|
|
161
|
-
for key in
|
|
179
|
+
def _clean_strfields(cls, md: dict[str, Any]) -> None:
|
|
180
|
+
for key in _MD_STR_KEYS:
|
|
162
181
|
value = md.get(key)
|
|
163
|
-
value = cls.
|
|
182
|
+
value = cls._clean_strfield(Comic, key, value)
|
|
164
183
|
cls._assign_or_pop(md, key, value)
|
|
165
184
|
|
|
166
185
|
@classmethod
|
|
@@ -182,10 +201,10 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
182
201
|
return
|
|
183
202
|
clean_contributors = {}
|
|
184
203
|
for role, persons in contributors.items():
|
|
185
|
-
clean_role = cls.
|
|
204
|
+
clean_role = cls._clean_strfield(ContributorRole, "name", role)
|
|
186
205
|
clean_persons = set()
|
|
187
206
|
for person in persons:
|
|
188
|
-
clean_person = cls.
|
|
207
|
+
clean_person = cls._clean_strfield(ContributorPerson, "name", person)
|
|
189
208
|
if clean_person:
|
|
190
209
|
clean_persons.add(clean_person)
|
|
191
210
|
clean_contributors[clean_role] = clean_persons
|
|
@@ -198,7 +217,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
198
217
|
return
|
|
199
218
|
clean_story_arcs = {}
|
|
200
219
|
for story_arc, number in story_arcs.items():
|
|
201
|
-
clean_story_arc = cls.
|
|
220
|
+
clean_story_arc = cls._clean_strfield(StoryArc, "name", story_arc)
|
|
202
221
|
if clean_story_arc:
|
|
203
222
|
clean_story_arcs[clean_story_arc] = number
|
|
204
223
|
cls._assign_or_pop(md, STORY_ARCS_METADATA_KEY, clean_story_arcs)
|
|
@@ -210,11 +229,11 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
210
229
|
return
|
|
211
230
|
clean_identifiers = {}
|
|
212
231
|
for nid, identifier in identifiers.items():
|
|
213
|
-
clean_id_type = cls.
|
|
232
|
+
clean_id_type = cls._clean_strfield(IdentifierType, "name", nid)
|
|
214
233
|
nss = identifier.get("nss")
|
|
215
|
-
clean_nss = cls.
|
|
234
|
+
clean_nss = cls._clean_strfield(Identifier, "nss", nss)
|
|
216
235
|
url = identifier.get("url")
|
|
217
|
-
clean_url = cls.
|
|
236
|
+
clean_url = cls._clean_strfield(Identifier, "url", url)
|
|
218
237
|
if clean_nss:
|
|
219
238
|
clean_identifier = {"nss": nss}
|
|
220
239
|
if clean_url:
|
|
@@ -230,7 +249,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
230
249
|
cls._clean_comic_positive_small_ints(md)
|
|
231
250
|
cls._clean_comic_small_ints(md)
|
|
232
251
|
cls._clean_comic_decimals(md)
|
|
233
|
-
cls.
|
|
252
|
+
cls._clean_strfields(md)
|
|
234
253
|
cls._clean_contributors(md)
|
|
235
254
|
cls._clean_story_arcs(md)
|
|
236
255
|
cls._clean_identifiers(md)
|
|
@@ -120,7 +120,7 @@ class FailedImportsImporter(DeletedImporter):
|
|
|
120
120
|
create_objs,
|
|
121
121
|
update_conflicts=True,
|
|
122
122
|
update_fields=_BULK_UPDATE_FAILED_IMPORT_FIELDS,
|
|
123
|
-
unique_fields=FailedImport._meta.unique_together[0],
|
|
123
|
+
unique_fields=FailedImport._meta.unique_together[0],
|
|
124
124
|
)
|
|
125
125
|
count = len(create_objs)
|
|
126
126
|
if count:
|
|
@@ -7,7 +7,10 @@ from humanize import naturaldelta
|
|
|
7
7
|
|
|
8
8
|
from codex.librarian.importer.moved import MovedImporter
|
|
9
9
|
from codex.librarian.importer.status import ImportStatusTypes
|
|
10
|
-
from codex.librarian.notifier.tasks import
|
|
10
|
+
from codex.librarian.notifier.tasks import (
|
|
11
|
+
FAILED_IMPORTS_CHANGED_TASK,
|
|
12
|
+
LIBRARY_CHANGED_TASK,
|
|
13
|
+
)
|
|
11
14
|
from codex.librarian.search.tasks import SearchIndexUpdateTask
|
|
12
15
|
from codex.librarian.tasks import DelayedTasks
|
|
13
16
|
|
|
@@ -43,12 +46,14 @@ class ComicImporter(MovedImporter):
|
|
|
43
46
|
|
|
44
47
|
# Wait to start the search index update in case more updates are incoming.
|
|
45
48
|
until = time() + 1
|
|
46
|
-
delayed_search_task = DelayedTasks(
|
|
49
|
+
delayed_search_task = DelayedTasks(
|
|
50
|
+
until, (SearchIndexUpdateTask(rebuild=False),)
|
|
51
|
+
)
|
|
47
52
|
self.librarian_queue.put(delayed_search_task)
|
|
48
53
|
else:
|
|
49
54
|
self.log.info("No updates neccissary.")
|
|
50
55
|
if new_failed_imports:
|
|
51
|
-
self.librarian_queue.put(
|
|
56
|
+
self.librarian_queue.put(FAILED_IMPORTS_CHANGED_TASK)
|
|
52
57
|
|
|
53
58
|
def apply(self):
|
|
54
59
|
"""Bulk import comics."""
|
|
@@ -35,7 +35,7 @@ class ComicImporterThread(QueuedThread):
|
|
|
35
35
|
import_comics = Comic.objects.filter(pk__in=task.pks).only("path", "library_id")
|
|
36
36
|
library_path_map = {}
|
|
37
37
|
for import_comic in import_comics:
|
|
38
|
-
library_id = import_comic.library_id # type: ignore
|
|
38
|
+
library_id = import_comic.library_id # type: ignore[reportAttributeAccessIssue]
|
|
39
39
|
if library_id not in library_path_map:
|
|
40
40
|
library_path_map[library_id] = set()
|
|
41
41
|
library_path_map[library_id].add(import_comic.path)
|
|
@@ -89,7 +89,7 @@ class ComicImporterThread(QueuedThread):
|
|
|
89
89
|
importer.bulk_folders_moved()
|
|
90
90
|
return True
|
|
91
91
|
|
|
92
|
-
def _adopt_orphan_folders(self, janitor
|
|
92
|
+
def _adopt_orphan_folders(self, janitor: bool):
|
|
93
93
|
"""Find orphan folders and move them into their correct place."""
|
|
94
94
|
status = Status(ImportStatusTypes.ADOPT_FOLDERS)
|
|
95
95
|
moved_status = Status(ImportStatusTypes.DIRS_MOVED)
|
|
@@ -118,6 +118,6 @@ class ComicImporterThread(QueuedThread):
|
|
|
118
118
|
elif isinstance(task, UpdateGroupsTask):
|
|
119
119
|
self._update_groups(task)
|
|
120
120
|
elif isinstance(task, AdoptOrphanFoldersTask):
|
|
121
|
-
self._adopt_orphan_folders(task.janitor)
|
|
121
|
+
self._adopt_orphan_folders(janitor=task.janitor)
|
|
122
122
|
else:
|
|
123
123
|
self.log.warning(f"Bad task sent to library updater {task}")
|