codex 1.8.6__py3-none-any.whl → 1.8.9__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/choices/admin.py +1 -0
- codex/librarian/scribe/importer/const.py +7 -3
- codex/librarian/scribe/importer/create/comics.py +0 -3
- codex/librarian/scribe/importer/delete/comics.py +5 -2
- codex/librarian/scribe/importer/delete/folders.py +2 -1
- codex/librarian/scribe/importer/link/many_to_many.py +1 -0
- codex/librarian/scribe/importer/link/prepare.py +6 -1
- codex/librarian/scribe/importer/query/update_comics.py +22 -12
- codex/librarian/scribe/importer/read/aggregate_path.py +53 -13
- codex/librarian/scribe/importer/read/extract.py +16 -13
- codex/librarian/scribe/importer/search/prepare.py +1 -8
- codex/librarian/scribe/importer/search/sync_m2m.py +2 -1
- codex/librarian/scribe/importer/search/update.py +28 -86
- codex/librarian/scribe/{importer/search → search}/const.py +1 -13
- codex/librarian/scribe/search/prepare.py +162 -0
- codex/librarian/scribe/search/remove.py +22 -2
- codex/librarian/scribe/search/sync.py +47 -134
- codex/migrations/0035_fts_optmize.py +136 -0
- codex/models/comic.py +0 -16
- codex/models/functions.py +3 -1
- codex/serializers/admin/tasks.py +1 -1
- codex/settings/__init__.py +17 -1
- codex/static_root/assets/VCheckbox-BI6iNIZR.0d8e2dbdeb09.js +1 -0
- codex/static_root/assets/VCheckbox-BI6iNIZR.0d8e2dbdeb09.js.br +0 -0
- codex/static_root/assets/VCheckbox-BI6iNIZR.0d8e2dbdeb09.js.gz +0 -0
- codex/static_root/assets/VCheckbox-BI6iNIZR.js +1 -0
- codex/static_root/assets/VCheckbox-BI6iNIZR.js.br +0 -0
- codex/static_root/assets/VCheckbox-BI6iNIZR.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-BzgViWAb.1400a41af9da.js → VCheckboxBtn-D_jRtvPm.7d253b804bed.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-D_jRtvPm.7d253b804bed.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-D_jRtvPm.7d253b804bed.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-BzgViWAb.js → VCheckboxBtn-D_jRtvPm.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-D_jRtvPm.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-D_jRtvPm.js.gz +0 -0
- codex/static_root/assets/VCombobox-lKOmP-Yq.85384eec8906.js +1 -0
- codex/static_root/assets/VCombobox-lKOmP-Yq.85384eec8906.js.br +0 -0
- codex/static_root/assets/VCombobox-lKOmP-Yq.85384eec8906.js.gz +0 -0
- codex/static_root/assets/VCombobox-lKOmP-Yq.js +1 -0
- codex/static_root/assets/VCombobox-lKOmP-Yq.js.br +0 -0
- codex/static_root/assets/VCombobox-lKOmP-Yq.js.gz +0 -0
- codex/static_root/assets/VDialog-DwpHVF2o.f988d86f776c.js +1 -0
- codex/static_root/assets/VDialog-DwpHVF2o.f988d86f776c.js.br +0 -0
- codex/static_root/assets/VDialog-DwpHVF2o.f988d86f776c.js.gz +0 -0
- codex/static_root/assets/VDialog-DwpHVF2o.js +1 -0
- codex/static_root/assets/VDialog-DwpHVF2o.js.br +0 -0
- codex/static_root/assets/VDialog-DwpHVF2o.js.gz +0 -0
- codex/static_root/assets/{VDivider-CSRFyKQO.5f8e1ffafbaf.js → VDivider-CGh9hHPI.8fac42f0d85c.js} +1 -1
- codex/static_root/assets/VDivider-CGh9hHPI.8fac42f0d85c.js.br +0 -0
- codex/static_root/assets/VDivider-CGh9hHPI.8fac42f0d85c.js.gz +0 -0
- codex/static_root/assets/{VDivider-CSRFyKQO.js → VDivider-CGh9hHPI.js} +1 -1
- codex/static_root/assets/VDivider-CGh9hHPI.js.br +0 -0
- codex/static_root/assets/VDivider-CGh9hHPI.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-82Ocr2Kh.1ccaea91f07c.js → VExpansionPanels-DFd72CFw.b232fadc31db.js} +1 -1
- codex/static_root/assets/VExpansionPanels-DFd72CFw.b232fadc31db.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DFd72CFw.b232fadc31db.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-82Ocr2Kh.js → VExpansionPanels-DFd72CFw.js} +1 -1
- codex/static_root/assets/VExpansionPanels-DFd72CFw.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DFd72CFw.js.gz +0 -0
- codex/static_root/assets/{VForm-BaeKMRS9.be058cf1518f.js → VForm-C7W9kTbJ.02412068f578.js} +1 -1
- codex/static_root/assets/VForm-C7W9kTbJ.02412068f578.js.br +0 -0
- codex/static_root/assets/VForm-C7W9kTbJ.02412068f578.js.gz +0 -0
- codex/static_root/assets/{VForm-BaeKMRS9.js → VForm-C7W9kTbJ.js} +1 -1
- codex/static_root/assets/VForm-C7W9kTbJ.js.br +0 -0
- codex/static_root/assets/VForm-C7W9kTbJ.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-DeB10IRm.4ea874709ffb.js → VRadioGroup-E0wBXF2E.fd8052060d44.js} +1 -1
- codex/static_root/assets/VRadioGroup-E0wBXF2E.fd8052060d44.js.br +0 -0
- codex/static_root/assets/VRadioGroup-E0wBXF2E.fd8052060d44.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-DeB10IRm.js → VRadioGroup-E0wBXF2E.js} +1 -1
- codex/static_root/assets/VRadioGroup-E0wBXF2E.js.br +0 -0
- codex/static_root/assets/VRadioGroup-E0wBXF2E.js.gz +0 -0
- codex/static_root/assets/VSelect-DLKX5Mr6.d84e1c59fa18.js +1 -0
- codex/static_root/assets/VSelect-DLKX5Mr6.d84e1c59fa18.js.br +0 -0
- codex/static_root/assets/VSelect-DLKX5Mr6.d84e1c59fa18.js.gz +0 -0
- codex/static_root/assets/VSelect-DLKX5Mr6.js +1 -0
- codex/static_root/assets/VSelect-DLKX5Mr6.js.br +0 -0
- codex/static_root/assets/VSelect-DLKX5Mr6.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-B8hJUO0p.1dda62fe0677.js +1 -0
- codex/static_root/assets/VSelectionControl-B8hJUO0p.1dda62fe0677.js.br +0 -0
- codex/static_root/assets/VSelectionControl-B8hJUO0p.1dda62fe0677.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-B8hJUO0p.js +1 -0
- codex/static_root/assets/VSelectionControl-B8hJUO0p.js.br +0 -0
- codex/static_root/assets/VSelectionControl-B8hJUO0p.js.gz +0 -0
- codex/static_root/assets/VTable-BsI6FsWH.css +1 -0
- codex/static_root/assets/VTable-BsI6FsWH.css.br +0 -0
- codex/static_root/assets/VTable-BsI6FsWH.css.gz +0 -0
- codex/static_root/assets/VTable-BsI6FsWH.d4c54d44a5ab.css +1 -0
- codex/static_root/assets/VTable-BsI6FsWH.d4c54d44a5ab.css.br +0 -0
- codex/static_root/assets/VTable-BsI6FsWH.d4c54d44a5ab.css.gz +0 -0
- codex/static_root/assets/VTable-D0uqeonL.a9897896b818.js +1 -0
- codex/static_root/assets/VTable-D0uqeonL.a9897896b818.js.br +0 -0
- codex/static_root/assets/VTable-D0uqeonL.a9897896b818.js.gz +0 -0
- codex/static_root/assets/VTable-D0uqeonL.js +1 -0
- codex/static_root/assets/VTable-D0uqeonL.js.br +0 -0
- codex/static_root/assets/VTable-D0uqeonL.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-Dwb7oeu0.6a4505330db3.js → VWindowItem-DnxUM-gG.242f232488e3.js} +1 -1
- codex/static_root/assets/VWindowItem-DnxUM-gG.242f232488e3.js.br +0 -0
- codex/static_root/assets/VWindowItem-DnxUM-gG.242f232488e3.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-Dwb7oeu0.js → VWindowItem-DnxUM-gG.js} +1 -1
- codex/static_root/assets/VWindowItem-DnxUM-gG.js.br +0 -0
- codex/static_root/assets/VWindowItem-DnxUM-gG.js.gz +0 -0
- codex/static_root/assets/VWindowItem-QQWUVqMX.c22e707ac99c.css +1 -0
- codex/static_root/assets/VWindowItem-QQWUVqMX.c22e707ac99c.css.br +5 -0
- codex/static_root/assets/VWindowItem-QQWUVqMX.c22e707ac99c.css.gz +0 -0
- codex/static_root/assets/VWindowItem-QQWUVqMX.css +1 -0
- codex/static_root/assets/VWindowItem-QQWUVqMX.css.br +5 -0
- codex/static_root/assets/VWindowItem-QQWUVqMX.css.gz +0 -0
- codex/static_root/assets/{admin-mQ-F7brp.cc388b064f3a.js → admin-B9U01BEM.7c8747c54803.js} +1 -1
- codex/static_root/assets/admin-B9U01BEM.7c8747c54803.js.br +0 -0
- codex/static_root/assets/admin-B9U01BEM.7c8747c54803.js.gz +0 -0
- codex/static_root/assets/{admin-mQ-F7brp.js → admin-B9U01BEM.js} +1 -1
- codex/static_root/assets/admin-B9U01BEM.js.br +0 -0
- codex/static_root/assets/admin-B9U01BEM.js.gz +0 -0
- codex/static_root/assets/{admin-nYIgbfe8.d2acbae0b198.js → admin-DmYMMUnm.58b7b9f4dadf.js} +1 -1
- codex/static_root/assets/admin-DmYMMUnm.58b7b9f4dadf.js.br +0 -0
- codex/static_root/assets/admin-DmYMMUnm.58b7b9f4dadf.js.gz +0 -0
- codex/static_root/assets/{admin-nYIgbfe8.js → admin-DmYMMUnm.js} +1 -1
- codex/static_root/assets/admin-DmYMMUnm.js.br +0 -0
- codex/static_root/assets/admin-DmYMMUnm.js.gz +0 -0
- codex/static_root/assets/admin-menu-CuNW4Urn.25020f907bb8.js +1 -0
- codex/static_root/assets/admin-menu-CuNW4Urn.25020f907bb8.js.br +0 -0
- codex/static_root/assets/admin-menu-CuNW4Urn.25020f907bb8.js.gz +0 -0
- codex/static_root/assets/admin-menu-CuNW4Urn.js +1 -0
- codex/static_root/assets/admin-menu-CuNW4Urn.js.br +0 -0
- codex/static_root/assets/admin-menu-CuNW4Urn.js.gz +0 -0
- codex/static_root/assets/{admin-settings-button-progress-BBRW69l3.697026d00887.js → admin-settings-button-progress-Bzr-gkG2.b17f5bcba7bb.js} +1 -1
- codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.b17f5bcba7bb.js.br +3 -0
- codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.b17f5bcba7bb.js.gz +0 -0
- codex/static_root/assets/{admin-settings-button-progress-BBRW69l3.js → admin-settings-button-progress-Bzr-gkG2.js} +1 -1
- codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.js.br +3 -0
- codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.js.gz +0 -0
- codex/static_root/assets/browser-CIw7xQ-1.8d16d2b270d5.js +1 -0
- codex/static_root/assets/browser-CIw7xQ-1.8d16d2b270d5.js.br +0 -0
- codex/static_root/assets/browser-CIw7xQ-1.8d16d2b270d5.js.gz +0 -0
- codex/static_root/assets/browser-CIw7xQ-1.js +1 -0
- codex/static_root/assets/browser-CIw7xQ-1.js.br +0 -0
- codex/static_root/assets/browser-CIw7xQ-1.js.gz +0 -0
- codex/static_root/assets/{browser-Cdi7U5ku.bab9fccf0326.css → browser-TRzEFz_k.css} +1 -1
- codex/static_root/assets/browser-TRzEFz_k.css.br +0 -0
- codex/static_root/assets/browser-TRzEFz_k.css.gz +0 -0
- codex/static_root/assets/{browser-Cdi7U5ku.css → browser-TRzEFz_k.e8ae750c4a41.css} +1 -1
- codex/static_root/assets/browser-TRzEFz_k.e8ae750c4a41.css.br +0 -0
- codex/static_root/assets/browser-TRzEFz_k.e8ae750c4a41.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-CXaB7k3y.01788b2cb59e.js +1 -0
- codex/static_root/assets/change-password-dialog-CXaB7k3y.01788b2cb59e.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CXaB7k3y.01788b2cb59e.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-CXaB7k3y.js +1 -0
- codex/static_root/assets/change-password-dialog-CXaB7k3y.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CXaB7k3y.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-uoXMmYq3.0ec4c4eba588.js → confirm-dialog-gXeRF5bD.4ae6e7a9790a.js} +1 -1
- codex/static_root/assets/confirm-dialog-gXeRF5bD.4ae6e7a9790a.js.br +0 -0
- codex/static_root/assets/confirm-dialog-gXeRF5bD.4ae6e7a9790a.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-uoXMmYq3.js → confirm-dialog-gXeRF5bD.js} +1 -1
- codex/static_root/assets/confirm-dialog-gXeRF5bD.js.br +0 -0
- codex/static_root/assets/confirm-dialog-gXeRF5bD.js.gz +0 -0
- codex/static_root/assets/{datetime-column-GkTrMP1-.bf4adfaa4e02.js → datetime-column-C5ZBHxV-.35957e684ec2.js} +1 -1
- codex/static_root/assets/datetime-column-C5ZBHxV-.35957e684ec2.js.br +0 -0
- codex/static_root/assets/datetime-column-C5ZBHxV-.35957e684ec2.js.gz +0 -0
- codex/static_root/assets/{datetime-column-GkTrMP1-.js → datetime-column-C5ZBHxV-.js} +1 -1
- codex/static_root/assets/datetime-column-C5ZBHxV-.js.br +0 -0
- codex/static_root/assets/datetime-column-C5ZBHxV-.js.gz +0 -0
- codex/static_root/assets/{filter-CRnAKgPp.cc654b8f8155.js → filter-CQ83UjMF.7cd979df64da.js} +1 -1
- codex/static_root/assets/filter-CQ83UjMF.7cd979df64da.js.br +0 -0
- codex/static_root/assets/filter-CQ83UjMF.7cd979df64da.js.gz +0 -0
- codex/static_root/assets/{filter-CRnAKgPp.js → filter-CQ83UjMF.js} +1 -1
- codex/static_root/assets/filter-CQ83UjMF.js.br +0 -0
- codex/static_root/assets/filter-CQ83UjMF.js.gz +0 -0
- codex/static_root/assets/{flag-tab-BA4kiUXn.408cc0a71ec1.js → flag-tab-CN3Y0PZS.f5f7122a015e.js} +1 -1
- codex/static_root/assets/flag-tab-CN3Y0PZS.f5f7122a015e.js.br +0 -0
- codex/static_root/assets/flag-tab-CN3Y0PZS.f5f7122a015e.js.gz +0 -0
- codex/static_root/assets/{flag-tab-BA4kiUXn.js → flag-tab-CN3Y0PZS.js} +1 -1
- codex/static_root/assets/flag-tab-CN3Y0PZS.js.br +0 -0
- codex/static_root/assets/flag-tab-CN3Y0PZS.js.gz +0 -0
- codex/static_root/assets/{forwardRefs-B_sUWZn6.css → forwardRefs-C3Z4qL85.48fb131ece85.css} +1 -1
- codex/static_root/assets/forwardRefs-C3Z4qL85.48fb131ece85.css.br +0 -0
- codex/static_root/assets/forwardRefs-C3Z4qL85.48fb131ece85.css.gz +0 -0
- codex/static_root/assets/{forwardRefs-B_sUWZn6.5114f206ca9c.css → forwardRefs-C3Z4qL85.css} +1 -1
- codex/static_root/assets/forwardRefs-C3Z4qL85.css.br +0 -0
- codex/static_root/assets/forwardRefs-C3Z4qL85.css.gz +0 -0
- codex/static_root/assets/forwardRefs-DBxFdh0r.03d9552a8e4c.js +1 -0
- codex/static_root/assets/forwardRefs-DBxFdh0r.03d9552a8e4c.js.br +0 -0
- codex/static_root/assets/forwardRefs-DBxFdh0r.03d9552a8e4c.js.gz +0 -0
- codex/static_root/assets/forwardRefs-DBxFdh0r.js +1 -0
- codex/static_root/assets/forwardRefs-DBxFdh0r.js.br +0 -0
- codex/static_root/assets/forwardRefs-DBxFdh0r.js.gz +0 -0
- codex/static_root/assets/{group-tab-BD9iJZwB.b3e2b1b68d3a.js → group-tab-DMPL6FiK.82f53e5c353a.js} +1 -1
- codex/static_root/assets/group-tab-DMPL6FiK.82f53e5c353a.js.br +0 -0
- codex/static_root/assets/group-tab-DMPL6FiK.82f53e5c353a.js.gz +0 -0
- codex/static_root/assets/{group-tab-BD9iJZwB.js → group-tab-DMPL6FiK.js} +1 -1
- codex/static_root/assets/group-tab-DMPL6FiK.js.br +0 -0
- codex/static_root/assets/group-tab-DMPL6FiK.js.gz +0 -0
- codex/static_root/assets/http-error-DgJ8-SFZ.da2fe4d026ce.js +1 -0
- codex/static_root/assets/http-error-DgJ8-SFZ.da2fe4d026ce.js.br +0 -0
- codex/static_root/assets/http-error-DgJ8-SFZ.da2fe4d026ce.js.gz +0 -0
- codex/static_root/assets/http-error-DgJ8-SFZ.js +1 -0
- codex/static_root/assets/http-error-DgJ8-SFZ.js.br +0 -0
- codex/static_root/assets/http-error-DgJ8-SFZ.js.gz +0 -0
- codex/static_root/assets/{index-DCZ6v5x6.848976e7722c.js → index-CQ0ue0KC.b38e465abad4.js} +1 -1
- codex/static_root/assets/index-CQ0ue0KC.b38e465abad4.js.br +0 -0
- codex/static_root/assets/index-CQ0ue0KC.b38e465abad4.js.gz +0 -0
- codex/static_root/assets/{index-DCZ6v5x6.js → index-CQ0ue0KC.js} +1 -1
- codex/static_root/assets/index-CQ0ue0KC.js.br +0 -0
- codex/static_root/assets/index-CQ0ue0KC.js.gz +0 -0
- codex/static_root/assets/library-tab-CfuZgnol.880ba8bd0149.js +1 -0
- codex/static_root/assets/library-tab-CfuZgnol.880ba8bd0149.js.br +0 -0
- codex/static_root/assets/library-tab-CfuZgnol.880ba8bd0149.js.gz +0 -0
- codex/static_root/assets/library-tab-CfuZgnol.js +1 -0
- codex/static_root/assets/library-tab-CfuZgnol.js.br +0 -0
- codex/static_root/assets/library-tab-CfuZgnol.js.gz +0 -0
- codex/static_root/assets/{main-Bmi4BPiO.d75c8b55081b.js → main-G8n9PNCf.cad6fa53a574.js} +16 -16
- codex/static_root/assets/main-G8n9PNCf.cad6fa53a574.js.br +0 -0
- codex/static_root/assets/main-G8n9PNCf.cad6fa53a574.js.gz +0 -0
- codex/static_root/assets/{main-Bmi4BPiO.js → main-G8n9PNCf.js} +16 -16
- codex/static_root/assets/main-G8n9PNCf.js.br +0 -0
- codex/static_root/assets/main-G8n9PNCf.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BKZayLN3.4b724ad0595f.js +1 -0
- codex/static_root/assets/pager-full-pdf-BKZayLN3.4b724ad0595f.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BKZayLN3.4b724ad0595f.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BKZayLN3.js +1 -0
- codex/static_root/assets/pager-full-pdf-BKZayLN3.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BKZayLN3.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-0vkF7yjo.d32720f76776.js +1 -0
- codex/static_root/assets/pagination-toolbar-0vkF7yjo.d32720f76776.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-0vkF7yjo.d32720f76776.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-0vkF7yjo.js +1 -0
- codex/static_root/assets/pagination-toolbar-0vkF7yjo.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-0vkF7yjo.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-741OafNw.4f9155469274.css → pagination-toolbar-rUdD4-Qi.3a224c7806a5.css} +1 -1
- codex/static_root/assets/pagination-toolbar-rUdD4-Qi.3a224c7806a5.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-rUdD4-Qi.3a224c7806a5.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-741OafNw.css → pagination-toolbar-rUdD4-Qi.css} +1 -1
- codex/static_root/assets/pagination-toolbar-rUdD4-Qi.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-rUdD4-Qi.css.gz +0 -0
- codex/static_root/assets/{pdf-doc-DQBmNe4l.8dbdebf30643.js → pdf-doc-CHmFdwy6.4142a4f31d20.js} +1 -1
- codex/static_root/assets/pdf-doc-CHmFdwy6.4142a4f31d20.js.br +0 -0
- codex/static_root/assets/pdf-doc-CHmFdwy6.4142a4f31d20.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-DQBmNe4l.js → pdf-doc-CHmFdwy6.js} +1 -1
- codex/static_root/assets/pdf-doc-CHmFdwy6.js.br +0 -0
- codex/static_root/assets/pdf-doc-CHmFdwy6.js.gz +0 -0
- codex/static_root/assets/reader-YIWmNjj6.c96fc7891aec.js +2 -0
- codex/static_root/assets/reader-YIWmNjj6.c96fc7891aec.js.br +0 -0
- codex/static_root/assets/reader-YIWmNjj6.c96fc7891aec.js.gz +0 -0
- codex/static_root/assets/reader-YIWmNjj6.js +2 -0
- codex/static_root/assets/reader-YIWmNjj6.js.br +0 -0
- codex/static_root/assets/reader-YIWmNjj6.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CcFgPQOM.0b468249b62e.css → relation-chips-CpiT_4k-.b72d78f512e6.css} +1 -1
- codex/static_root/assets/relation-chips-CpiT_4k-.b72d78f512e6.css.br +0 -0
- codex/static_root/assets/relation-chips-CpiT_4k-.b72d78f512e6.css.gz +0 -0
- codex/static_root/assets/{relation-chips-CcFgPQOM.css → relation-chips-CpiT_4k-.css} +1 -1
- codex/static_root/assets/relation-chips-CpiT_4k-.css.br +0 -0
- codex/static_root/assets/relation-chips-CpiT_4k-.css.gz +0 -0
- codex/static_root/assets/relation-chips-DzoiGrOj.95f663c6c18d.js +1 -0
- codex/static_root/assets/relation-chips-DzoiGrOj.95f663c6c18d.js.br +0 -0
- codex/static_root/assets/relation-chips-DzoiGrOj.95f663c6c18d.js.gz +0 -0
- codex/static_root/assets/relation-chips-DzoiGrOj.js +1 -0
- codex/static_root/assets/relation-chips-DzoiGrOj.js.br +0 -0
- codex/static_root/assets/relation-chips-DzoiGrOj.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CUC-r4sg.e019f5467d25.js +2 -0
- codex/static_root/assets/settings-drawer-CUC-r4sg.e019f5467d25.js.br +0 -0
- codex/static_root/assets/settings-drawer-CUC-r4sg.e019f5467d25.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CUC-r4sg.js +2 -0
- codex/static_root/assets/settings-drawer-CUC-r4sg.js.br +0 -0
- codex/static_root/assets/settings-drawer-CUC-r4sg.js.gz +0 -0
- codex/static_root/assets/{ssrBoot-B6P0M2z2.38ab2f4bde57.js → ssrBoot-D7foQ-7h.31c07ef6f646.js} +1 -1
- codex/static_root/assets/ssrBoot-D7foQ-7h.31c07ef6f646.js.br +0 -0
- codex/static_root/assets/ssrBoot-D7foQ-7h.31c07ef6f646.js.gz +0 -0
- codex/static_root/assets/{ssrBoot-B6P0M2z2.js → ssrBoot-D7foQ-7h.js} +1 -1
- codex/static_root/assets/ssrBoot-D7foQ-7h.js.br +0 -0
- codex/static_root/assets/ssrBoot-D7foQ-7h.js.gz +0 -0
- codex/static_root/assets/{stats-tab-C5m62fu3.8482d96809e9.js → stats-tab-BSy-9tZm.2a7560872f5c.js} +1 -1
- codex/static_root/assets/stats-tab-BSy-9tZm.2a7560872f5c.js.br +0 -0
- codex/static_root/assets/stats-tab-BSy-9tZm.2a7560872f5c.js.gz +0 -0
- codex/static_root/assets/{stats-tab-C5m62fu3.js → stats-tab-BSy-9tZm.js} +1 -1
- codex/static_root/assets/stats-tab-BSy-9tZm.js.br +0 -0
- codex/static_root/assets/stats-tab-BSy-9tZm.js.gz +0 -0
- codex/static_root/assets/task-tab-Sp49fYRT.3a1aab3f3716.js +1 -0
- codex/static_root/assets/task-tab-Sp49fYRT.3a1aab3f3716.js.br +0 -0
- codex/static_root/assets/task-tab-Sp49fYRT.3a1aab3f3716.js.gz +0 -0
- codex/static_root/assets/task-tab-Sp49fYRT.js +1 -0
- codex/static_root/assets/task-tab-Sp49fYRT.js.br +0 -0
- codex/static_root/assets/task-tab-Sp49fYRT.js.gz +0 -0
- codex/static_root/assets/{unauthorized-DXoWE_v2.19c70a140f9b.js → unauthorized-BM6KzvCy.4c114d9693c1.js} +1 -1
- codex/static_root/assets/unauthorized-BM6KzvCy.4c114d9693c1.js.br +0 -0
- codex/static_root/assets/unauthorized-BM6KzvCy.4c114d9693c1.js.gz +0 -0
- codex/static_root/assets/{unauthorized-DXoWE_v2.js → unauthorized-BM6KzvCy.js} +1 -1
- codex/static_root/assets/unauthorized-BM6KzvCy.js.br +0 -0
- codex/static_root/assets/unauthorized-BM6KzvCy.js.gz +0 -0
- codex/static_root/assets/user-tab-yQ60jtVd.ad593cc6e6ea.js +1 -0
- codex/static_root/assets/user-tab-yQ60jtVd.ad593cc6e6ea.js.br +0 -0
- codex/static_root/assets/user-tab-yQ60jtVd.ad593cc6e6ea.js.gz +0 -0
- codex/static_root/assets/user-tab-yQ60jtVd.js +1 -0
- codex/static_root/assets/user-tab-yQ60jtVd.js.br +0 -0
- codex/static_root/assets/user-tab-yQ60jtVd.js.gz +0 -0
- codex/static_root/{manifest.db295ed1ca03.json → manifest.a059c4dc5443.json} +308 -308
- codex/static_root/manifest.a059c4dc5443.json.br +0 -0
- codex/static_root/manifest.a059c4dc5443.json.gz +0 -0
- codex/static_root/manifest.json +308 -308
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/views/browser/filters/filter.py +1 -1
- codex/views/browser/filters/search/field/column.py +3 -3
- codex/views/browser/filters/search/parse.py +15 -10
- {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/METADATA +5 -5
- {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/RECORD +307 -305
- codex/static_root/assets/VCheckbox--IZv20K5.121e571f623b.js +0 -1
- codex/static_root/assets/VCheckbox--IZv20K5.121e571f623b.js.br +0 -0
- codex/static_root/assets/VCheckbox--IZv20K5.121e571f623b.js.gz +0 -0
- codex/static_root/assets/VCheckbox--IZv20K5.js +0 -1
- codex/static_root/assets/VCheckbox--IZv20K5.js.br +0 -0
- codex/static_root/assets/VCheckbox--IZv20K5.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BzgViWAb.1400a41af9da.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BzgViWAb.1400a41af9da.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BzgViWAb.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BzgViWAb.js.gz +0 -0
- codex/static_root/assets/VCombobox-0VKjtXTP.f7c9bb8e7f77.js +0 -1
- codex/static_root/assets/VCombobox-0VKjtXTP.f7c9bb8e7f77.js.br +0 -0
- codex/static_root/assets/VCombobox-0VKjtXTP.f7c9bb8e7f77.js.gz +0 -0
- codex/static_root/assets/VCombobox-0VKjtXTP.js +0 -1
- codex/static_root/assets/VCombobox-0VKjtXTP.js.br +0 -0
- codex/static_root/assets/VCombobox-0VKjtXTP.js.gz +0 -0
- codex/static_root/assets/VDialog-Oz6-kM3S.2244675141c7.js +0 -1
- codex/static_root/assets/VDialog-Oz6-kM3S.2244675141c7.js.br +0 -0
- codex/static_root/assets/VDialog-Oz6-kM3S.2244675141c7.js.gz +0 -0
- codex/static_root/assets/VDialog-Oz6-kM3S.js +0 -1
- codex/static_root/assets/VDialog-Oz6-kM3S.js.br +0 -0
- codex/static_root/assets/VDialog-Oz6-kM3S.js.gz +0 -0
- codex/static_root/assets/VDivider-CSRFyKQO.5f8e1ffafbaf.js.br +0 -0
- codex/static_root/assets/VDivider-CSRFyKQO.5f8e1ffafbaf.js.gz +0 -0
- codex/static_root/assets/VDivider-CSRFyKQO.js.br +0 -0
- codex/static_root/assets/VDivider-CSRFyKQO.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-82Ocr2Kh.1ccaea91f07c.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-82Ocr2Kh.1ccaea91f07c.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-82Ocr2Kh.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-82Ocr2Kh.js.gz +0 -0
- codex/static_root/assets/VForm-BaeKMRS9.be058cf1518f.js.br +0 -0
- codex/static_root/assets/VForm-BaeKMRS9.be058cf1518f.js.gz +0 -0
- codex/static_root/assets/VForm-BaeKMRS9.js.br +0 -0
- codex/static_root/assets/VForm-BaeKMRS9.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DeB10IRm.4ea874709ffb.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DeB10IRm.4ea874709ffb.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DeB10IRm.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DeB10IRm.js.gz +0 -0
- codex/static_root/assets/VSelect-4ozzBEef.36f749b6f4ab.js +0 -1
- codex/static_root/assets/VSelect-4ozzBEef.36f749b6f4ab.js.br +0 -0
- codex/static_root/assets/VSelect-4ozzBEef.36f749b6f4ab.js.gz +0 -0
- codex/static_root/assets/VSelect-4ozzBEef.js +0 -1
- codex/static_root/assets/VSelect-4ozzBEef.js.br +0 -0
- codex/static_root/assets/VSelect-4ozzBEef.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D3h7FjSM.6cf4ba197995.js +0 -1
- codex/static_root/assets/VSelectionControl-D3h7FjSM.6cf4ba197995.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D3h7FjSM.6cf4ba197995.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D3h7FjSM.js +0 -1
- codex/static_root/assets/VSelectionControl-D3h7FjSM.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D3h7FjSM.js.gz +0 -0
- codex/static_root/assets/VTable-CQyv0wyO.1740239d481b.css +0 -1
- codex/static_root/assets/VTable-CQyv0wyO.1740239d481b.css.br +0 -0
- codex/static_root/assets/VTable-CQyv0wyO.1740239d481b.css.gz +0 -0
- codex/static_root/assets/VTable-CQyv0wyO.css +0 -1
- codex/static_root/assets/VTable-CQyv0wyO.css.br +0 -0
- codex/static_root/assets/VTable-CQyv0wyO.css.gz +0 -0
- codex/static_root/assets/VTable-Dj5EC5Aq.7abc9daf78be.js +0 -1
- codex/static_root/assets/VTable-Dj5EC5Aq.7abc9daf78be.js.br +0 -0
- codex/static_root/assets/VTable-Dj5EC5Aq.7abc9daf78be.js.gz +0 -0
- codex/static_root/assets/VTable-Dj5EC5Aq.js +0 -1
- codex/static_root/assets/VTable-Dj5EC5Aq.js.br +0 -0
- codex/static_root/assets/VTable-Dj5EC5Aq.js.gz +0 -0
- codex/static_root/assets/VWindowItem-ChYLiXSE.a9c679a2977e.css +0 -1
- codex/static_root/assets/VWindowItem-ChYLiXSE.a9c679a2977e.css.br +0 -0
- codex/static_root/assets/VWindowItem-ChYLiXSE.a9c679a2977e.css.gz +0 -0
- codex/static_root/assets/VWindowItem-ChYLiXSE.css +0 -1
- codex/static_root/assets/VWindowItem-ChYLiXSE.css.br +0 -0
- codex/static_root/assets/VWindowItem-ChYLiXSE.css.gz +0 -0
- codex/static_root/assets/VWindowItem-Dwb7oeu0.6a4505330db3.js.br +0 -0
- codex/static_root/assets/VWindowItem-Dwb7oeu0.6a4505330db3.js.gz +0 -0
- codex/static_root/assets/VWindowItem-Dwb7oeu0.js.br +0 -0
- codex/static_root/assets/VWindowItem-Dwb7oeu0.js.gz +0 -0
- codex/static_root/assets/admin-mQ-F7brp.cc388b064f3a.js.br +0 -0
- codex/static_root/assets/admin-mQ-F7brp.cc388b064f3a.js.gz +0 -0
- codex/static_root/assets/admin-mQ-F7brp.js.br +0 -0
- codex/static_root/assets/admin-mQ-F7brp.js.gz +0 -0
- codex/static_root/assets/admin-menu-CfGPKhvz.1e224da855f5.js +0 -1
- codex/static_root/assets/admin-menu-CfGPKhvz.1e224da855f5.js.br +0 -0
- codex/static_root/assets/admin-menu-CfGPKhvz.1e224da855f5.js.gz +0 -0
- codex/static_root/assets/admin-menu-CfGPKhvz.js +0 -1
- codex/static_root/assets/admin-menu-CfGPKhvz.js.br +0 -0
- codex/static_root/assets/admin-menu-CfGPKhvz.js.gz +0 -0
- codex/static_root/assets/admin-nYIgbfe8.d2acbae0b198.js.br +0 -0
- codex/static_root/assets/admin-nYIgbfe8.d2acbae0b198.js.gz +0 -0
- codex/static_root/assets/admin-nYIgbfe8.js.br +0 -0
- codex/static_root/assets/admin-nYIgbfe8.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BBRW69l3.697026d00887.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BBRW69l3.697026d00887.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BBRW69l3.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BBRW69l3.js.gz +0 -0
- codex/static_root/assets/browser-BzV_-1Jg.e39184a92bab.js +0 -1
- codex/static_root/assets/browser-BzV_-1Jg.e39184a92bab.js.br +0 -0
- codex/static_root/assets/browser-BzV_-1Jg.e39184a92bab.js.gz +0 -0
- codex/static_root/assets/browser-BzV_-1Jg.js +0 -1
- codex/static_root/assets/browser-BzV_-1Jg.js.br +0 -0
- codex/static_root/assets/browser-BzV_-1Jg.js.gz +0 -0
- codex/static_root/assets/browser-Cdi7U5ku.bab9fccf0326.css.br +0 -0
- codex/static_root/assets/browser-Cdi7U5ku.bab9fccf0326.css.gz +0 -0
- codex/static_root/assets/browser-Cdi7U5ku.css.br +0 -0
- codex/static_root/assets/browser-Cdi7U5ku.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DpqXgZ53.3f22c3114669.js +0 -1
- codex/static_root/assets/change-password-dialog-DpqXgZ53.3f22c3114669.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DpqXgZ53.3f22c3114669.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DpqXgZ53.js +0 -1
- codex/static_root/assets/change-password-dialog-DpqXgZ53.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DpqXgZ53.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-uoXMmYq3.0ec4c4eba588.js.br +0 -0
- codex/static_root/assets/confirm-dialog-uoXMmYq3.0ec4c4eba588.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-uoXMmYq3.js.br +0 -0
- codex/static_root/assets/confirm-dialog-uoXMmYq3.js.gz +0 -0
- codex/static_root/assets/datetime-column-GkTrMP1-.bf4adfaa4e02.js.br +0 -0
- codex/static_root/assets/datetime-column-GkTrMP1-.bf4adfaa4e02.js.gz +0 -0
- codex/static_root/assets/datetime-column-GkTrMP1-.js.br +0 -0
- codex/static_root/assets/datetime-column-GkTrMP1-.js.gz +0 -0
- codex/static_root/assets/filter-CRnAKgPp.cc654b8f8155.js.br +0 -0
- codex/static_root/assets/filter-CRnAKgPp.cc654b8f8155.js.gz +0 -0
- codex/static_root/assets/filter-CRnAKgPp.js.br +0 -0
- codex/static_root/assets/filter-CRnAKgPp.js.gz +0 -0
- codex/static_root/assets/flag-tab-BA4kiUXn.408cc0a71ec1.js.br +0 -0
- codex/static_root/assets/flag-tab-BA4kiUXn.408cc0a71ec1.js.gz +0 -0
- codex/static_root/assets/flag-tab-BA4kiUXn.js.br +0 -0
- codex/static_root/assets/flag-tab-BA4kiUXn.js.gz +0 -0
- codex/static_root/assets/forwardRefs-B_sUWZn6.5114f206ca9c.css.br +0 -0
- codex/static_root/assets/forwardRefs-B_sUWZn6.5114f206ca9c.css.gz +0 -0
- codex/static_root/assets/forwardRefs-B_sUWZn6.css.br +0 -0
- codex/static_root/assets/forwardRefs-B_sUWZn6.css.gz +0 -0
- codex/static_root/assets/forwardRefs-FxOC1qa_.6e14881eedf1.js +0 -1
- codex/static_root/assets/forwardRefs-FxOC1qa_.6e14881eedf1.js.br +0 -0
- codex/static_root/assets/forwardRefs-FxOC1qa_.6e14881eedf1.js.gz +0 -0
- codex/static_root/assets/forwardRefs-FxOC1qa_.js +0 -1
- codex/static_root/assets/forwardRefs-FxOC1qa_.js.br +0 -0
- codex/static_root/assets/forwardRefs-FxOC1qa_.js.gz +0 -0
- codex/static_root/assets/group-tab-BD9iJZwB.b3e2b1b68d3a.js.br +0 -0
- codex/static_root/assets/group-tab-BD9iJZwB.b3e2b1b68d3a.js.gz +0 -0
- codex/static_root/assets/group-tab-BD9iJZwB.js.br +0 -0
- codex/static_root/assets/group-tab-BD9iJZwB.js.gz +0 -0
- codex/static_root/assets/http-error-D-b5OfHC.e661fa6e0000.js +0 -1
- codex/static_root/assets/http-error-D-b5OfHC.e661fa6e0000.js.br +0 -2
- codex/static_root/assets/http-error-D-b5OfHC.e661fa6e0000.js.gz +0 -0
- codex/static_root/assets/http-error-D-b5OfHC.js +0 -1
- codex/static_root/assets/http-error-D-b5OfHC.js.br +0 -2
- codex/static_root/assets/http-error-D-b5OfHC.js.gz +0 -0
- codex/static_root/assets/index-DCZ6v5x6.848976e7722c.js.br +0 -0
- codex/static_root/assets/index-DCZ6v5x6.848976e7722c.js.gz +0 -0
- codex/static_root/assets/index-DCZ6v5x6.js.br +0 -0
- codex/static_root/assets/index-DCZ6v5x6.js.gz +0 -0
- codex/static_root/assets/library-tab-ju4WtRth.97949bf344d7.js +0 -1
- codex/static_root/assets/library-tab-ju4WtRth.97949bf344d7.js.br +0 -0
- codex/static_root/assets/library-tab-ju4WtRth.97949bf344d7.js.gz +0 -0
- codex/static_root/assets/library-tab-ju4WtRth.js +0 -1
- codex/static_root/assets/library-tab-ju4WtRth.js.br +0 -0
- codex/static_root/assets/library-tab-ju4WtRth.js.gz +0 -0
- codex/static_root/assets/main-Bmi4BPiO.d75c8b55081b.js.br +0 -0
- codex/static_root/assets/main-Bmi4BPiO.d75c8b55081b.js.gz +0 -0
- codex/static_root/assets/main-Bmi4BPiO.js.br +0 -0
- codex/static_root/assets/main-Bmi4BPiO.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BjSr3CzH.ffbe3d735455.js +0 -1
- codex/static_root/assets/pager-full-pdf-BjSr3CzH.ffbe3d735455.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BjSr3CzH.ffbe3d735455.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BjSr3CzH.js +0 -1
- codex/static_root/assets/pager-full-pdf-BjSr3CzH.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BjSr3CzH.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-741OafNw.4f9155469274.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-741OafNw.4f9155469274.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-741OafNw.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-741OafNw.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DpMVSiz_.86ce692ff9b4.js +0 -1
- codex/static_root/assets/pagination-toolbar-DpMVSiz_.86ce692ff9b4.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DpMVSiz_.86ce692ff9b4.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DpMVSiz_.js +0 -1
- codex/static_root/assets/pagination-toolbar-DpMVSiz_.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DpMVSiz_.js.gz +0 -0
- codex/static_root/assets/pdf-doc-DQBmNe4l.8dbdebf30643.js.br +0 -0
- codex/static_root/assets/pdf-doc-DQBmNe4l.8dbdebf30643.js.gz +0 -0
- codex/static_root/assets/pdf-doc-DQBmNe4l.js.br +0 -0
- codex/static_root/assets/pdf-doc-DQBmNe4l.js.gz +0 -0
- codex/static_root/assets/reader-BQVYc_LF.e327430a14a1.js +0 -2
- codex/static_root/assets/reader-BQVYc_LF.e327430a14a1.js.br +0 -0
- codex/static_root/assets/reader-BQVYc_LF.e327430a14a1.js.gz +0 -0
- codex/static_root/assets/reader-BQVYc_LF.js +0 -2
- codex/static_root/assets/reader-BQVYc_LF.js.br +0 -0
- codex/static_root/assets/reader-BQVYc_LF.js.gz +0 -0
- codex/static_root/assets/relation-chips-CcFgPQOM.0b468249b62e.css.br +0 -0
- codex/static_root/assets/relation-chips-CcFgPQOM.0b468249b62e.css.gz +0 -0
- codex/static_root/assets/relation-chips-CcFgPQOM.css.br +0 -0
- codex/static_root/assets/relation-chips-CcFgPQOM.css.gz +0 -0
- codex/static_root/assets/relation-chips-TRmABTTI.918cb21596f9.js +0 -1
- codex/static_root/assets/relation-chips-TRmABTTI.918cb21596f9.js.br +0 -0
- codex/static_root/assets/relation-chips-TRmABTTI.918cb21596f9.js.gz +0 -0
- codex/static_root/assets/relation-chips-TRmABTTI.js +0 -1
- codex/static_root/assets/relation-chips-TRmABTTI.js.br +0 -0
- codex/static_root/assets/relation-chips-TRmABTTI.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BoAnrKCI.46b6a030c81f.js +0 -2
- codex/static_root/assets/settings-drawer-BoAnrKCI.46b6a030c81f.js.br +0 -0
- codex/static_root/assets/settings-drawer-BoAnrKCI.46b6a030c81f.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BoAnrKCI.js +0 -2
- codex/static_root/assets/settings-drawer-BoAnrKCI.js.br +0 -0
- codex/static_root/assets/settings-drawer-BoAnrKCI.js.gz +0 -0
- codex/static_root/assets/ssrBoot-B6P0M2z2.38ab2f4bde57.js.br +0 -0
- codex/static_root/assets/ssrBoot-B6P0M2z2.38ab2f4bde57.js.gz +0 -0
- codex/static_root/assets/ssrBoot-B6P0M2z2.js.br +0 -0
- codex/static_root/assets/ssrBoot-B6P0M2z2.js.gz +0 -0
- codex/static_root/assets/stats-tab-C5m62fu3.8482d96809e9.js.br +0 -0
- codex/static_root/assets/stats-tab-C5m62fu3.8482d96809e9.js.gz +0 -0
- codex/static_root/assets/stats-tab-C5m62fu3.js.br +0 -0
- codex/static_root/assets/stats-tab-C5m62fu3.js.gz +0 -0
- codex/static_root/assets/task-tab-B3WOGyYT.508fce185a28.js +0 -1
- codex/static_root/assets/task-tab-B3WOGyYT.508fce185a28.js.br +0 -0
- codex/static_root/assets/task-tab-B3WOGyYT.508fce185a28.js.gz +0 -0
- codex/static_root/assets/task-tab-B3WOGyYT.js +0 -1
- codex/static_root/assets/task-tab-B3WOGyYT.js.br +0 -0
- codex/static_root/assets/task-tab-B3WOGyYT.js.gz +0 -0
- codex/static_root/assets/unauthorized-DXoWE_v2.19c70a140f9b.js.br +0 -0
- codex/static_root/assets/unauthorized-DXoWE_v2.19c70a140f9b.js.gz +0 -0
- codex/static_root/assets/unauthorized-DXoWE_v2.js.br +0 -0
- codex/static_root/assets/unauthorized-DXoWE_v2.js.gz +0 -0
- codex/static_root/assets/user-tab-DhsQxTkZ.8a8167f9f2c4.js +0 -1
- codex/static_root/assets/user-tab-DhsQxTkZ.8a8167f9f2c4.js.br +0 -0
- codex/static_root/assets/user-tab-DhsQxTkZ.8a8167f9f2c4.js.gz +0 -0
- codex/static_root/assets/user-tab-DhsQxTkZ.js +0 -1
- codex/static_root/assets/user-tab-DhsQxTkZ.js.br +0 -0
- codex/static_root/assets/user-tab-DhsQxTkZ.js.gz +0 -0
- codex/static_root/manifest.db295ed1ca03.json.br +0 -0
- codex/static_root/manifest.db295ed1ca03.json.gz +0 -0
- {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/WHEEL +0 -0
- {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/entry_points.txt +0 -0
- {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Prepare ComicFTS objects."""
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
|
|
6
|
+
from comicbox.enums.comicbox import IdSources
|
|
7
|
+
from comicbox.enums.maps.identifiers import ID_SOURCE_NAME_MAP
|
|
8
|
+
from django.db.models.functions.datetime import Now
|
|
9
|
+
|
|
10
|
+
from codex.librarian.status import Status
|
|
11
|
+
from codex.models.comic import ComicFTS
|
|
12
|
+
from codex.serializers.fields.browser import CountryField, LanguageField
|
|
13
|
+
|
|
14
|
+
_PYCOUNTRY_FIELDS = MappingProxyType(
|
|
15
|
+
{"country": CountryField(), "language": LanguageField()}
|
|
16
|
+
)
|
|
17
|
+
_COMIC_KEYS = (
|
|
18
|
+
"collection_title",
|
|
19
|
+
"country",
|
|
20
|
+
"language",
|
|
21
|
+
"name",
|
|
22
|
+
"review",
|
|
23
|
+
"summary",
|
|
24
|
+
"fts_publisher",
|
|
25
|
+
"fts_imprint",
|
|
26
|
+
"fts_series",
|
|
27
|
+
"fts_age_rating",
|
|
28
|
+
"fts_original_format",
|
|
29
|
+
"fts_scan_info",
|
|
30
|
+
"fts_tagger",
|
|
31
|
+
"fts_characters",
|
|
32
|
+
"fts_credits",
|
|
33
|
+
"fts_country",
|
|
34
|
+
"fts_genres",
|
|
35
|
+
"fts_sources",
|
|
36
|
+
"fts_language",
|
|
37
|
+
"fts_locations",
|
|
38
|
+
"fts_series_groups",
|
|
39
|
+
"fts_stories",
|
|
40
|
+
"fts_story_arcs",
|
|
41
|
+
"fts_tags",
|
|
42
|
+
"fts_teams",
|
|
43
|
+
"fts_universes",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SearchEntryPrepare:
|
|
48
|
+
"""Prepare ComicFTS objects."""
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _get_entry_str_value(entry: dict, key: str) -> str:
|
|
52
|
+
value = entry.get(key)
|
|
53
|
+
if not value:
|
|
54
|
+
return ""
|
|
55
|
+
if isinstance(value, tuple):
|
|
56
|
+
value = value[0]
|
|
57
|
+
if not value:
|
|
58
|
+
return ""
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _get_sources_fts_field(entry: dict) -> str:
|
|
63
|
+
sources = entry.get("sources", ())
|
|
64
|
+
if not sources:
|
|
65
|
+
return ""
|
|
66
|
+
if isinstance(sources, str):
|
|
67
|
+
sources = (sources,)
|
|
68
|
+
names = set()
|
|
69
|
+
for source_str in sources:
|
|
70
|
+
if not source_str:
|
|
71
|
+
continue
|
|
72
|
+
names.add(source_str)
|
|
73
|
+
with suppress(ValueError):
|
|
74
|
+
id_source = IdSources(source_str)
|
|
75
|
+
if long_name := ID_SOURCE_NAME_MAP.get(id_source):
|
|
76
|
+
names.add(long_name)
|
|
77
|
+
return ",".join(sorted(names))
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def _get_pycountry_fts_field(cls, entry, field_name) -> str:
|
|
81
|
+
iso_code = cls._get_entry_str_value(entry, field_name)
|
|
82
|
+
if not iso_code:
|
|
83
|
+
return ""
|
|
84
|
+
field = _PYCOUNTRY_FIELDS[field_name]
|
|
85
|
+
return ",".join((iso_code, field.to_representation(iso_code)))
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def _create_comicfts_entry_attributes(cls, entry, *, create: bool):
|
|
89
|
+
now = Now()
|
|
90
|
+
entry["updated_at"] = now
|
|
91
|
+
if create:
|
|
92
|
+
entry["created_at"] = now
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def _create_comicfts_entry_fks(cls, entry):
|
|
96
|
+
entry["country"] = cls._get_pycountry_fts_field(entry, "country")
|
|
97
|
+
entry["language"] = cls._get_pycountry_fts_field(entry, "language")
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def _create_comicfts_entry_m2ms(cls, entry, existing_values: dict | None):
|
|
101
|
+
if sources := cls._get_sources_fts_field(entry):
|
|
102
|
+
entry["sources"] = sources
|
|
103
|
+
if existing_values:
|
|
104
|
+
for field_name in tuple(existing_values.keys()):
|
|
105
|
+
if values := existing_values.get(field_name):
|
|
106
|
+
entry[field_name] = entry.get(field_name, ()) + values
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def prepare_import_fts_entry( # noqa: PLR0913
|
|
110
|
+
cls,
|
|
111
|
+
comic_id: int,
|
|
112
|
+
entry: dict,
|
|
113
|
+
existing_m2m_values: dict | None,
|
|
114
|
+
comicfts: ComicFTS | None,
|
|
115
|
+
obj_list: list[ComicFTS],
|
|
116
|
+
status: Status,
|
|
117
|
+
*,
|
|
118
|
+
create: bool,
|
|
119
|
+
):
|
|
120
|
+
"""Prepare ComicFTS object from import data."""
|
|
121
|
+
cls._create_comicfts_entry_m2ms(entry, existing_m2m_values)
|
|
122
|
+
cls._create_comicfts_entry_fks(entry)
|
|
123
|
+
for field_name in entry: # noqa: PLC0206
|
|
124
|
+
value = entry[field_name]
|
|
125
|
+
if isinstance(value, tuple):
|
|
126
|
+
entry[field_name] = ",".join(sorted(value))
|
|
127
|
+
|
|
128
|
+
cls._create_comicfts_entry_attributes(entry, create=create)
|
|
129
|
+
|
|
130
|
+
if comicfts:
|
|
131
|
+
for field_name, value in entry.items():
|
|
132
|
+
setattr(comicfts, field_name, value)
|
|
133
|
+
else:
|
|
134
|
+
entry["comic_id"] = comic_id
|
|
135
|
+
comicfts = ComicFTS(**entry)
|
|
136
|
+
|
|
137
|
+
obj_list.append(comicfts)
|
|
138
|
+
status.increment_complete()
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def prepare_sync_fts_entry(
|
|
142
|
+
cls,
|
|
143
|
+
comic: dict,
|
|
144
|
+
obj_list: list[ComicFTS],
|
|
145
|
+
*,
|
|
146
|
+
create: bool,
|
|
147
|
+
):
|
|
148
|
+
"""Prepare ComicFTS object from sync query data."""
|
|
149
|
+
entry = {
|
|
150
|
+
key.removeprefix("fts_"): comic.get(key, "")
|
|
151
|
+
for key in _COMIC_KEYS
|
|
152
|
+
if comic.get(key)
|
|
153
|
+
}
|
|
154
|
+
if sources := cls._get_sources_fts_field(entry):
|
|
155
|
+
entry["sources"] = sources
|
|
156
|
+
cls._create_comicfts_entry_fks(entry)
|
|
157
|
+
entry["universes"] = entry["universes"].strip(",")
|
|
158
|
+
cls._create_comicfts_entry_attributes(entry, create=create)
|
|
159
|
+
entry["comic_id"] = comic["id"]
|
|
160
|
+
|
|
161
|
+
comicfts: ComicFTS = ComicFTS(**entry)
|
|
162
|
+
obj_list.append(comicfts)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Search Index cleanup."""
|
|
2
2
|
|
|
3
|
+
from django.db.models import F, Max
|
|
4
|
+
|
|
3
5
|
from codex.librarian.scribe.search.optimize import SearchIndexerOptimize
|
|
4
6
|
from codex.librarian.scribe.search.status import (
|
|
5
7
|
SearchIndexCleanStatus,
|
|
6
8
|
SearchIndexClearStatus,
|
|
7
9
|
)
|
|
8
|
-
from codex.models.comic import ComicFTS
|
|
10
|
+
from codex.models.comic import Comic, ComicFTS
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class SearchIndexerRemove(SearchIndexerOptimize):
|
|
@@ -22,7 +24,9 @@ class SearchIndexerRemove(SearchIndexerOptimize):
|
|
|
22
24
|
"""Remove records not in the database from the index."""
|
|
23
25
|
self.status_controller.start(status)
|
|
24
26
|
self.log.debug("Finding stale records to remove...")
|
|
25
|
-
delete_comicfts = ComicFTS.objects.
|
|
27
|
+
delete_comicfts = ComicFTS.objects.exclude(
|
|
28
|
+
comic_id__in=Comic.objects.only("pk")
|
|
29
|
+
)
|
|
26
30
|
status.total = delete_comicfts.count()
|
|
27
31
|
self.status_controller.update(status)
|
|
28
32
|
if status.total:
|
|
@@ -42,3 +46,19 @@ class SearchIndexerRemove(SearchIndexerOptimize):
|
|
|
42
46
|
finally:
|
|
43
47
|
self.status_controller.finish(status)
|
|
44
48
|
return count
|
|
49
|
+
|
|
50
|
+
def remove_duplicate_records(self) -> int:
|
|
51
|
+
"""Remove duplicate FTS records."""
|
|
52
|
+
self.log.debug("Looking for duplicate search entries...")
|
|
53
|
+
duplicates = ComicFTS.objects.annotate(max_updated_at=Max("updated_at")).filter(
|
|
54
|
+
updated_at__lt=F("max_updated_at")
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
num_dupes = duplicates.count()
|
|
58
|
+
self.log.debug(f"Found {num_dupes} duplicate search entries")
|
|
59
|
+
count = num_dupes
|
|
60
|
+
if num_dupes:
|
|
61
|
+
count, _ = duplicates.delete()
|
|
62
|
+
if count:
|
|
63
|
+
self.log.info(f"Deleted {count} duplicate search entries")
|
|
64
|
+
return count
|
|
@@ -6,15 +6,16 @@ from time import time
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
from zoneinfo import ZoneInfo
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from django.db.models import Q
|
|
10
10
|
from django.db.models.aggregates import Max
|
|
11
11
|
from django.db.models.expressions import F, Value
|
|
12
12
|
from django.db.models.functions import Concat
|
|
13
|
-
from django.db.models.functions.datetime import Now
|
|
14
13
|
from django.db.models.query import QuerySet
|
|
15
14
|
from humanize import intcomma, naturaldelta
|
|
16
15
|
|
|
17
16
|
from codex.librarian.memory import get_mem_limit
|
|
17
|
+
from codex.librarian.scribe.search.const import COMICFTS_UPDATE_FIELDS
|
|
18
|
+
from codex.librarian.scribe.search.prepare import SearchEntryPrepare
|
|
18
19
|
from codex.librarian.scribe.search.remove import SearchIndexerRemove
|
|
19
20
|
from codex.librarian.scribe.search.status import (
|
|
20
21
|
SEARCH_INDEX_STATII,
|
|
@@ -26,54 +27,13 @@ from codex.librarian.scribe.search.status import (
|
|
|
26
27
|
from codex.models import Comic
|
|
27
28
|
from codex.models.comic import ComicFTS
|
|
28
29
|
from codex.models.functions import GroupConcat
|
|
29
|
-
from codex.serializers.fields.browser import CountryField, LanguageField, PyCountryField
|
|
30
30
|
from codex.settings import SEARCH_SYNC_BATCH_MEMORY_RATIO
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
33
|
from codex.librarian.status import Status
|
|
34
34
|
|
|
35
|
-
_COMICFTS_ATTRIBUTES = (
|
|
36
|
-
"collection_title",
|
|
37
|
-
"file_type",
|
|
38
|
-
"issue",
|
|
39
|
-
"name",
|
|
40
|
-
"notes",
|
|
41
|
-
"reading_direction",
|
|
42
|
-
"review",
|
|
43
|
-
"summary",
|
|
44
|
-
"updated_at",
|
|
45
|
-
)
|
|
46
|
-
_COMICFTS_FKS = (
|
|
47
|
-
"publisher",
|
|
48
|
-
"imprint",
|
|
49
|
-
"series",
|
|
50
|
-
"age_rating",
|
|
51
|
-
"country",
|
|
52
|
-
"language",
|
|
53
|
-
"original_format",
|
|
54
|
-
"scan_info",
|
|
55
|
-
"tagger",
|
|
56
|
-
)
|
|
57
|
-
_COMICFTS_M2MS = (
|
|
58
|
-
"characters",
|
|
59
|
-
"credits",
|
|
60
|
-
"genres",
|
|
61
|
-
"identifiers",
|
|
62
|
-
"locations",
|
|
63
|
-
"series_groups",
|
|
64
|
-
"sources",
|
|
65
|
-
"stories",
|
|
66
|
-
"story_arcs",
|
|
67
|
-
"tags",
|
|
68
|
-
"teams",
|
|
69
|
-
"universes",
|
|
70
|
-
)
|
|
71
|
-
_COMICFTS_UPDATE_FIELDS = (
|
|
72
|
-
*_COMICFTS_ATTRIBUTES,
|
|
73
|
-
*_COMICFTS_FKS,
|
|
74
|
-
*_COMICFTS_M2MS,
|
|
75
|
-
)
|
|
76
35
|
_MIN_UTC_DATE = datetime.min.replace(tzinfo=ZoneInfo("UTC"))
|
|
36
|
+
_ALL_FTS_COMIC_IDS_QUERY = Q(pk__in=ComicFTS.objects.values_list("comic_id", flat=True))
|
|
77
37
|
|
|
78
38
|
|
|
79
39
|
class SearchIndexerSync(SearchIndexerRemove):
|
|
@@ -171,23 +131,6 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
171
131
|
),
|
|
172
132
|
)
|
|
173
133
|
|
|
174
|
-
@staticmethod
|
|
175
|
-
def _get_pycountry_fts_field(field: PyCountryField, iso_code) -> str:
|
|
176
|
-
if not iso_code:
|
|
177
|
-
return ""
|
|
178
|
-
return ",".join((iso_code, field.to_representation(iso_code)))
|
|
179
|
-
|
|
180
|
-
@staticmethod
|
|
181
|
-
def _get_sources_fts_field(sources) -> str | None:
|
|
182
|
-
names = []
|
|
183
|
-
if not sources:
|
|
184
|
-
return None
|
|
185
|
-
for source in sources.split(","):
|
|
186
|
-
names.append(source)
|
|
187
|
-
if long_name := ID_SOURCE_NAME_MAP.get(source):
|
|
188
|
-
names.append(long_name)
|
|
189
|
-
return ",".join(names)
|
|
190
|
-
|
|
191
134
|
def _update_search_index_operate_get_status(
|
|
192
135
|
self, total_comics: int, chunk_human_size: str, *, create: bool
|
|
193
136
|
):
|
|
@@ -197,63 +140,6 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
197
140
|
subtitle = f"Chunks of {chunk_human_size}" if total_comics else ""
|
|
198
141
|
return status_class(total=total_comics, subtitle=subtitle)
|
|
199
142
|
|
|
200
|
-
def _create_comicfts_entry(
|
|
201
|
-
self,
|
|
202
|
-
comic,
|
|
203
|
-
obj_list: list[ComicFTS],
|
|
204
|
-
*,
|
|
205
|
-
create: bool,
|
|
206
|
-
):
|
|
207
|
-
country_field: PyCountryField = CountryField() # pyright: ignore[reportAssignmentType]
|
|
208
|
-
language_field: PyCountryField = LanguageField() # pyright: ignore[reportAssignmentType]
|
|
209
|
-
|
|
210
|
-
country = self._get_pycountry_fts_field(country_field, comic.fts_country)
|
|
211
|
-
language = self._get_pycountry_fts_field(language_field, comic.fts_language)
|
|
212
|
-
sources = self._get_sources_fts_field(comic.fts_sources)
|
|
213
|
-
|
|
214
|
-
now = Now()
|
|
215
|
-
comicfts = ComicFTS(
|
|
216
|
-
comic_id=comic.pk,
|
|
217
|
-
# Attributes
|
|
218
|
-
collection_title=comic.collection_title,
|
|
219
|
-
file_type=comic.file_type,
|
|
220
|
-
issue=comic.issue(),
|
|
221
|
-
name=comic.name,
|
|
222
|
-
notes=comic.notes,
|
|
223
|
-
reading_direction=comic.reading_direction,
|
|
224
|
-
review=comic.review,
|
|
225
|
-
summary=comic.summary,
|
|
226
|
-
updated_at=now,
|
|
227
|
-
# Group FKs
|
|
228
|
-
publisher=comic.fts_publisher,
|
|
229
|
-
imprint=comic.fts_imprint,
|
|
230
|
-
series=comic.fts_series,
|
|
231
|
-
# FKS
|
|
232
|
-
age_rating=comic.fts_age_rating,
|
|
233
|
-
country=country,
|
|
234
|
-
language=language,
|
|
235
|
-
original_format=comic.fts_original_format,
|
|
236
|
-
scan_info=comic.fts_scan_info,
|
|
237
|
-
tagger=comic.fts_tagger,
|
|
238
|
-
# ManyToMany
|
|
239
|
-
characters=comic.fts_characters,
|
|
240
|
-
credits=comic.fts_credits,
|
|
241
|
-
genres=comic.fts_genres,
|
|
242
|
-
identifiers=comic.fts_identifiers,
|
|
243
|
-
sources=sources, # comic.fts_sources,
|
|
244
|
-
locations=comic.fts_locations,
|
|
245
|
-
series_groups=comic.fts_series_groups,
|
|
246
|
-
stories=comic.fts_stories,
|
|
247
|
-
story_arcs=comic.fts_story_arcs,
|
|
248
|
-
tags=comic.fts_tags,
|
|
249
|
-
teams=comic.fts_teams,
|
|
250
|
-
universes=comic.fts_universes,
|
|
251
|
-
)
|
|
252
|
-
if create:
|
|
253
|
-
comicfts.created_at = now
|
|
254
|
-
|
|
255
|
-
obj_list.append(comicfts)
|
|
256
|
-
|
|
257
143
|
def _update_search_index_create_or_update(
|
|
258
144
|
self,
|
|
259
145
|
obj_list: list[ComicFTS],
|
|
@@ -272,11 +158,17 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
272
158
|
if create:
|
|
273
159
|
ComicFTS.objects.bulk_create(obj_list)
|
|
274
160
|
else:
|
|
275
|
-
ComicFTS.objects.bulk_update(obj_list,
|
|
161
|
+
ComicFTS.objects.bulk_update(obj_list, COMICFTS_UPDATE_FIELDS)
|
|
276
162
|
|
|
277
163
|
status.increment_complete(num_comic_fts)
|
|
278
164
|
self.status_controller.update(status, notify=True)
|
|
279
165
|
|
|
166
|
+
@staticmethod
|
|
167
|
+
def _get_operation_comics_query(qs, *, create: bool):
|
|
168
|
+
if create and not ComicFTS.objects.exists():
|
|
169
|
+
qs = Comic.objects.all()
|
|
170
|
+
return qs
|
|
171
|
+
|
|
280
172
|
def _update_search_index_operate(
|
|
281
173
|
self, comics_filtered_qs: QuerySet, *, create: bool
|
|
282
174
|
):
|
|
@@ -289,7 +181,10 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
289
181
|
|
|
290
182
|
verb = "create" if create else "update"
|
|
291
183
|
self.log.debug(f"Counting total search index entries to {verb}...")
|
|
292
|
-
total_comics =
|
|
184
|
+
total_comics = self._get_operation_comics_query(
|
|
185
|
+
comics_filtered_qs, create=create
|
|
186
|
+
)
|
|
187
|
+
total_comics = total_comics.count()
|
|
293
188
|
status = self._update_search_index_operate_get_status(
|
|
294
189
|
total_comics, chunk_human_size, create=create
|
|
295
190
|
)
|
|
@@ -300,23 +195,29 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
300
195
|
return total_comics
|
|
301
196
|
|
|
302
197
|
self.status_controller.start(status, notify=True)
|
|
303
|
-
|
|
198
|
+
start = 0
|
|
199
|
+
while start < total_comics:
|
|
304
200
|
obj_list = []
|
|
305
201
|
if self.abort_event.is_set():
|
|
306
202
|
break
|
|
307
203
|
# Not using standard iterator chunking to control memory and really
|
|
308
204
|
# do this in batches.
|
|
309
205
|
self.log.debug(
|
|
310
|
-
f"Preparing up to {
|
|
206
|
+
f"Preparing up to {chunk_human_size} comics for search indexing..."
|
|
207
|
+
)
|
|
208
|
+
# This query is supposed to get only comics that don't need creating but it doesn't
|
|
209
|
+
# So the start total exit assists it with that.
|
|
210
|
+
comics = self._get_operation_comics_query(
|
|
211
|
+
comics_filtered_qs, create=create
|
|
311
212
|
)
|
|
312
|
-
comics = comics_filtered_qs
|
|
313
|
-
comics = self._select_related_fts_query(comics)
|
|
314
213
|
comics = self._prefetch_related_fts_query(comics)
|
|
315
214
|
comics = comics.order_by("pk")
|
|
316
215
|
comics = comics[:search_index_batch_size]
|
|
317
216
|
comics = self._annotate_fts_query(comics)
|
|
318
|
-
for comic in comics:
|
|
319
|
-
|
|
217
|
+
for comic in comics.values():
|
|
218
|
+
SearchEntryPrepare.prepare_sync_fts_entry(
|
|
219
|
+
comic, obj_list, create=create
|
|
220
|
+
)
|
|
320
221
|
|
|
321
222
|
if not obj_list:
|
|
322
223
|
break
|
|
@@ -325,6 +226,7 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
325
226
|
status,
|
|
326
227
|
create=create,
|
|
327
228
|
)
|
|
229
|
+
start += search_index_batch_size
|
|
328
230
|
|
|
329
231
|
finally:
|
|
330
232
|
self.status_controller.finish(status)
|
|
@@ -332,25 +234,36 @@ class SearchIndexerSync(SearchIndexerRemove):
|
|
|
332
234
|
|
|
333
235
|
def _update_search_index_update(self):
|
|
334
236
|
"""Update out of date search entries."""
|
|
237
|
+
out_of_date_comics = Comic.objects.filter(_ALL_FTS_COMIC_IDS_QUERY)
|
|
238
|
+
self.log.debug("Looking for search index watermark...")
|
|
335
239
|
fts_watermark = ComicFTS.objects.aggregate(max=Max("updated_at"))["max"]
|
|
336
|
-
|
|
240
|
+
if fts_watermark:
|
|
241
|
+
since = fts_watermark
|
|
242
|
+
out_of_date_comics = out_of_date_comics.filter(updated_at__gt=fts_watermark)
|
|
243
|
+
else:
|
|
244
|
+
since = "the fracturing of the multiverse"
|
|
245
|
+
fts_watermark = fts_watermark or _MIN_UTC_DATE
|
|
337
246
|
self.log.info(f"Looking for search entries to update since {since}...")
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
)
|
|
247
|
+
count = out_of_date_comics.count()
|
|
248
|
+
self.log.debug(f"Found {count} comics with out of date search entries.")
|
|
249
|
+
|
|
342
250
|
return self._update_search_index_operate(out_of_date_comics, create=False)
|
|
343
251
|
|
|
344
252
|
def _update_search_index_create(self):
|
|
345
253
|
"""Create missing search entries."""
|
|
346
254
|
self.log.info("Looking for missing search entries to create...")
|
|
347
|
-
missing_comics = Comic.objects.
|
|
348
|
-
|
|
349
|
-
)
|
|
255
|
+
missing_comics = Comic.objects.all()
|
|
256
|
+
count = None if ComicFTS.objects.exists() else missing_comics.count()
|
|
257
|
+
missing_comics = missing_comics.exclude(_ALL_FTS_COMIC_IDS_QUERY)
|
|
258
|
+
if count is None:
|
|
259
|
+
count = missing_comics.count()
|
|
260
|
+
|
|
261
|
+
self.log.debug(f"Found {count} comics missing from the search index.")
|
|
350
262
|
return self._update_search_index_operate(missing_comics, create=True)
|
|
351
263
|
|
|
352
264
|
def _update_search_index(self, *, rebuild: bool):
|
|
353
265
|
"""Update or Rebuild the search index."""
|
|
266
|
+
self.log.debug("In update search index before init statii.")
|
|
354
267
|
start_time = time()
|
|
355
268
|
self._init_statuses(rebuild)
|
|
356
269
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Generated by Django 5.2.4 on 2025-07-21 15:05."""
|
|
2
|
+
|
|
3
|
+
from itertools import chain
|
|
4
|
+
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
from comicbox.enums.maps.identifiers import ID_SOURCE_NAME_MAP, get_id_source_by_alias
|
|
7
|
+
from django.db import migrations, models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _map_identifiers_to_canonical_names(apps):
|
|
11
|
+
id_source_model = apps.get_model("codex", "identifiersource")
|
|
12
|
+
identifier_source_map = {}
|
|
13
|
+
|
|
14
|
+
for source in id_source_model.objects.all():
|
|
15
|
+
if id_source_enum := get_id_source_by_alias(source.name, default=None):
|
|
16
|
+
canon_name = ID_SOURCE_NAME_MAP.get(id_source_enum, source.name)
|
|
17
|
+
if canon_name != source.name:
|
|
18
|
+
if canon_name not in identifier_source_map:
|
|
19
|
+
identifier_source_map[canon_name] = set()
|
|
20
|
+
identifier_source_map[canon_name] |= set(
|
|
21
|
+
source.identifier_set.values_list("pk", flat=True)
|
|
22
|
+
)
|
|
23
|
+
return identifier_source_map
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _prepare_canonical_id_sources(apps, identifier_source_map):
|
|
27
|
+
id_source_model = apps.get_model("codex", "identifiersource")
|
|
28
|
+
source_names = identifier_source_map.keys()
|
|
29
|
+
existing_id_sources = id_source_model.objects.filter(name__in=source_names).only(
|
|
30
|
+
"name"
|
|
31
|
+
)
|
|
32
|
+
existing_id_source_names = id_source_model.objects.filter(
|
|
33
|
+
name__in=source_names
|
|
34
|
+
).values_list("name", flat=True)
|
|
35
|
+
create_id_source_names = sorted(
|
|
36
|
+
frozenset(set(identifier_source_map.keys()) - set(existing_id_source_names))
|
|
37
|
+
)
|
|
38
|
+
return tuple(
|
|
39
|
+
id_source_model(name=name) for name in create_id_source_names
|
|
40
|
+
), existing_id_sources
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _create_link_map(identifier_source_map, sources):
|
|
44
|
+
link_map = {}
|
|
45
|
+
for id_source in chain(*sources):
|
|
46
|
+
for id_pk in identifier_source_map[id_source.name]:
|
|
47
|
+
link_map[id_pk] = id_source
|
|
48
|
+
return link_map
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _prepare_updatatable_identifiers(apps, identifier_source_map, sources):
|
|
52
|
+
link_map = _create_link_map(identifier_source_map, sources)
|
|
53
|
+
identifier_model = apps.get_model("codex", "identifier")
|
|
54
|
+
updateable_identifiers = identifier_model.objects.filter(pk__in=link_map.keys())
|
|
55
|
+
obj_list = []
|
|
56
|
+
for identifier in updateable_identifiers:
|
|
57
|
+
identifier.source = link_map[identifier.pk]
|
|
58
|
+
obj_list.append(identifier)
|
|
59
|
+
return tuple(obj_list)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _create_canonical_sources(apps):
|
|
63
|
+
print("Examining identifier sources for conversion to canonical sources...")
|
|
64
|
+
identifier_source_map = _map_identifiers_to_canonical_names(apps)
|
|
65
|
+
print("Preparing missing canonical identifiers sources for creation...")
|
|
66
|
+
obj_list, existing_id_sources = _prepare_canonical_id_sources(
|
|
67
|
+
apps, identifier_source_map
|
|
68
|
+
)
|
|
69
|
+
id_source_model = apps.get_model("codex", "identifiersource")
|
|
70
|
+
created_id_sources = id_source_model.objects.bulk_create(obj_list)
|
|
71
|
+
count = len(obj_list)
|
|
72
|
+
print(f"Created {count} canonical identifier sources")
|
|
73
|
+
return identifier_source_map, (existing_id_sources, created_id_sources)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _update_identifiers_with_canonical_sources(apps, identifier_source_map, sources):
|
|
77
|
+
print("Preparing identifiers for update with canonical sources...")
|
|
78
|
+
obj_list = _prepare_updatatable_identifiers(apps, identifier_source_map, sources)
|
|
79
|
+
identifier_model = apps.get_model("codex", "identifier")
|
|
80
|
+
identifier_model.objects.bulk_update(obj_list, fields=("source",))
|
|
81
|
+
count = len(obj_list)
|
|
82
|
+
print(f"Updated {count} identifiers with canonical sources.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _convert_identifier_sources(apps, _schema_editor):
|
|
86
|
+
identifier_source_map, sources = _create_canonical_sources(apps)
|
|
87
|
+
_update_identifiers_with_canonical_sources(apps, identifier_source_map, sources)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Migration(migrations.Migration):
|
|
91
|
+
"""Migrate db."""
|
|
92
|
+
|
|
93
|
+
dependencies = [
|
|
94
|
+
("codex", "0034_comicbox2"),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
operations = [
|
|
98
|
+
migrations.RunPython(_convert_identifier_sources),
|
|
99
|
+
migrations.SeparateDatabaseAndState(
|
|
100
|
+
database_operations=[
|
|
101
|
+
migrations.RunSQL(
|
|
102
|
+
sql="DROP TABLE IF EXISTS codex_comicfts;",
|
|
103
|
+
),
|
|
104
|
+
migrations.RunSQL(
|
|
105
|
+
sql="CREATE VIRTUAL TABLE codex_comicfts USING fts5(comic_id UNINDEXED, created_at UNINDEXED, updated_at UNINDEXED, publisher, imprint, series, name, collection_title, age_rating, country, language, original_format, review, scan_info, summary, tagger, characters, credits, genres, locations, roles, series_groups, stories, story_arcs, tags, teams, universes, sources)",
|
|
106
|
+
reverse_sql="DROP TABLE IF EXISTS codex_comicfts",
|
|
107
|
+
),
|
|
108
|
+
],
|
|
109
|
+
state_operations=[
|
|
110
|
+
migrations.DeleteModel(
|
|
111
|
+
name="ComicFTS",
|
|
112
|
+
),
|
|
113
|
+
migrations.CreateModel(
|
|
114
|
+
name="ComicFTS",
|
|
115
|
+
fields=[
|
|
116
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
117
|
+
("updated_at", models.DateTimeField(auto_now=True)),
|
|
118
|
+
(
|
|
119
|
+
"comic",
|
|
120
|
+
models.OneToOneField(
|
|
121
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
122
|
+
primary_key=True,
|
|
123
|
+
serialize=False,
|
|
124
|
+
to="codex.comic",
|
|
125
|
+
),
|
|
126
|
+
),
|
|
127
|
+
("body", models.TextField()),
|
|
128
|
+
],
|
|
129
|
+
options={
|
|
130
|
+
"get_latest_by": "updated_at",
|
|
131
|
+
"managed": False,
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
],
|
|
135
|
+
),
|
|
136
|
+
]
|
codex/models/comic.py
CHANGED
|
@@ -326,23 +326,12 @@ class Comic(WatchedPathBrowserGroup):
|
|
|
326
326
|
"""Most common text representation for logging."""
|
|
327
327
|
return self.get_title(self, volume=True, name=True, filename_fallback=True)
|
|
328
328
|
|
|
329
|
-
def issue(self) -> str:
|
|
330
|
-
"""Combine issue parts for search."""
|
|
331
|
-
res = ""
|
|
332
|
-
if self.issue_number is not None:
|
|
333
|
-
res += str(self.issue_number.normalize())
|
|
334
|
-
if self.issue_suffix:
|
|
335
|
-
res += self.issue_suffix
|
|
336
|
-
return res
|
|
337
|
-
|
|
338
329
|
|
|
339
330
|
class ComicFTS(BaseModel):
|
|
340
331
|
comic = OneToOneField(primary_key=True, to=Comic, on_delete=CASCADE)
|
|
341
332
|
# Attributes
|
|
342
333
|
collection_title = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
343
|
-
issue = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
344
334
|
name = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
345
|
-
notes = TextField(db_collation="nocase")
|
|
346
335
|
review = TextField(db_collation="nocase")
|
|
347
336
|
summary = TextField(db_collation="nocase")
|
|
348
337
|
# FK groups
|
|
@@ -352,7 +341,6 @@ class ComicFTS(BaseModel):
|
|
|
352
341
|
# FK
|
|
353
342
|
age_rating = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
354
343
|
country = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
355
|
-
file_type = CharField(db_collation="nocase", max_length=max_choices_len(FileType))
|
|
356
344
|
language = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
357
345
|
original_format = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
358
346
|
scan_info = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
@@ -361,12 +349,8 @@ class ComicFTS(BaseModel):
|
|
|
361
349
|
characters = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
362
350
|
credits = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
363
351
|
genres = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
364
|
-
identifiers = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
365
352
|
sources = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
366
353
|
locations = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
367
|
-
reading_direction = CharField(
|
|
368
|
-
db_collation="nocase", max_length=max_choices_len(ReadingDirection)
|
|
369
|
-
)
|
|
370
354
|
series_groups = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
371
355
|
stories = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
|
372
356
|
story_arcs = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
|
codex/models/functions.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from django.db.models.aggregates import Aggregate
|
|
4
4
|
from django.db.models.expressions import Func
|
|
5
|
-
from django.db.models.fields import CharField, FloatField
|
|
5
|
+
from django.db.models.fields import CharField, FloatField, TextField
|
|
6
6
|
from django.db.models.fields.json import JSONField
|
|
7
7
|
from django.db.models.fields.related import OneToOneField
|
|
8
8
|
from django.db.models.lookups import Lookup
|
|
@@ -55,12 +55,14 @@ class FTS5Match(Lookup):
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@CharField.register_lookup
|
|
58
|
+
@TextField.register_lookup
|
|
58
59
|
@CleaningCharField.register_lookup
|
|
59
60
|
@CleaningTextField.register_lookup
|
|
60
61
|
class Like(Lookup):
|
|
61
62
|
"""SQL LIKE lookup."""
|
|
62
63
|
|
|
63
64
|
lookup_name = "like"
|
|
65
|
+
prepare_rhs = False
|
|
64
66
|
|
|
65
67
|
@override
|
|
66
68
|
def as_sql(self, compiler, connection):
|