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.

Files changed (531) hide show
  1. codex/choices/admin.py +1 -0
  2. codex/librarian/scribe/importer/const.py +7 -3
  3. codex/librarian/scribe/importer/create/comics.py +0 -3
  4. codex/librarian/scribe/importer/delete/comics.py +5 -2
  5. codex/librarian/scribe/importer/delete/folders.py +2 -1
  6. codex/librarian/scribe/importer/link/many_to_many.py +1 -0
  7. codex/librarian/scribe/importer/link/prepare.py +6 -1
  8. codex/librarian/scribe/importer/query/update_comics.py +22 -12
  9. codex/librarian/scribe/importer/read/aggregate_path.py +53 -13
  10. codex/librarian/scribe/importer/read/extract.py +16 -13
  11. codex/librarian/scribe/importer/search/prepare.py +1 -8
  12. codex/librarian/scribe/importer/search/sync_m2m.py +2 -1
  13. codex/librarian/scribe/importer/search/update.py +28 -86
  14. codex/librarian/scribe/{importer/search → search}/const.py +1 -13
  15. codex/librarian/scribe/search/prepare.py +162 -0
  16. codex/librarian/scribe/search/remove.py +22 -2
  17. codex/librarian/scribe/search/sync.py +47 -134
  18. codex/migrations/0035_fts_optmize.py +136 -0
  19. codex/models/comic.py +0 -16
  20. codex/models/functions.py +3 -1
  21. codex/serializers/admin/tasks.py +1 -1
  22. codex/settings/__init__.py +17 -1
  23. codex/static_root/assets/VCheckbox-BI6iNIZR.0d8e2dbdeb09.js +1 -0
  24. codex/static_root/assets/VCheckbox-BI6iNIZR.0d8e2dbdeb09.js.br +0 -0
  25. codex/static_root/assets/VCheckbox-BI6iNIZR.0d8e2dbdeb09.js.gz +0 -0
  26. codex/static_root/assets/VCheckbox-BI6iNIZR.js +1 -0
  27. codex/static_root/assets/VCheckbox-BI6iNIZR.js.br +0 -0
  28. codex/static_root/assets/VCheckbox-BI6iNIZR.js.gz +0 -0
  29. codex/static_root/assets/{VCheckboxBtn-BzgViWAb.1400a41af9da.js → VCheckboxBtn-D_jRtvPm.7d253b804bed.js} +1 -1
  30. codex/static_root/assets/VCheckboxBtn-D_jRtvPm.7d253b804bed.js.br +0 -0
  31. codex/static_root/assets/VCheckboxBtn-D_jRtvPm.7d253b804bed.js.gz +0 -0
  32. codex/static_root/assets/{VCheckboxBtn-BzgViWAb.js → VCheckboxBtn-D_jRtvPm.js} +1 -1
  33. codex/static_root/assets/VCheckboxBtn-D_jRtvPm.js.br +0 -0
  34. codex/static_root/assets/VCheckboxBtn-D_jRtvPm.js.gz +0 -0
  35. codex/static_root/assets/VCombobox-lKOmP-Yq.85384eec8906.js +1 -0
  36. codex/static_root/assets/VCombobox-lKOmP-Yq.85384eec8906.js.br +0 -0
  37. codex/static_root/assets/VCombobox-lKOmP-Yq.85384eec8906.js.gz +0 -0
  38. codex/static_root/assets/VCombobox-lKOmP-Yq.js +1 -0
  39. codex/static_root/assets/VCombobox-lKOmP-Yq.js.br +0 -0
  40. codex/static_root/assets/VCombobox-lKOmP-Yq.js.gz +0 -0
  41. codex/static_root/assets/VDialog-DwpHVF2o.f988d86f776c.js +1 -0
  42. codex/static_root/assets/VDialog-DwpHVF2o.f988d86f776c.js.br +0 -0
  43. codex/static_root/assets/VDialog-DwpHVF2o.f988d86f776c.js.gz +0 -0
  44. codex/static_root/assets/VDialog-DwpHVF2o.js +1 -0
  45. codex/static_root/assets/VDialog-DwpHVF2o.js.br +0 -0
  46. codex/static_root/assets/VDialog-DwpHVF2o.js.gz +0 -0
  47. codex/static_root/assets/{VDivider-CSRFyKQO.5f8e1ffafbaf.js → VDivider-CGh9hHPI.8fac42f0d85c.js} +1 -1
  48. codex/static_root/assets/VDivider-CGh9hHPI.8fac42f0d85c.js.br +0 -0
  49. codex/static_root/assets/VDivider-CGh9hHPI.8fac42f0d85c.js.gz +0 -0
  50. codex/static_root/assets/{VDivider-CSRFyKQO.js → VDivider-CGh9hHPI.js} +1 -1
  51. codex/static_root/assets/VDivider-CGh9hHPI.js.br +0 -0
  52. codex/static_root/assets/VDivider-CGh9hHPI.js.gz +0 -0
  53. codex/static_root/assets/{VExpansionPanels-82Ocr2Kh.1ccaea91f07c.js → VExpansionPanels-DFd72CFw.b232fadc31db.js} +1 -1
  54. codex/static_root/assets/VExpansionPanels-DFd72CFw.b232fadc31db.js.br +0 -0
  55. codex/static_root/assets/VExpansionPanels-DFd72CFw.b232fadc31db.js.gz +0 -0
  56. codex/static_root/assets/{VExpansionPanels-82Ocr2Kh.js → VExpansionPanels-DFd72CFw.js} +1 -1
  57. codex/static_root/assets/VExpansionPanels-DFd72CFw.js.br +0 -0
  58. codex/static_root/assets/VExpansionPanels-DFd72CFw.js.gz +0 -0
  59. codex/static_root/assets/{VForm-BaeKMRS9.be058cf1518f.js → VForm-C7W9kTbJ.02412068f578.js} +1 -1
  60. codex/static_root/assets/VForm-C7W9kTbJ.02412068f578.js.br +0 -0
  61. codex/static_root/assets/VForm-C7W9kTbJ.02412068f578.js.gz +0 -0
  62. codex/static_root/assets/{VForm-BaeKMRS9.js → VForm-C7W9kTbJ.js} +1 -1
  63. codex/static_root/assets/VForm-C7W9kTbJ.js.br +0 -0
  64. codex/static_root/assets/VForm-C7W9kTbJ.js.gz +0 -0
  65. codex/static_root/assets/{VRadioGroup-DeB10IRm.4ea874709ffb.js → VRadioGroup-E0wBXF2E.fd8052060d44.js} +1 -1
  66. codex/static_root/assets/VRadioGroup-E0wBXF2E.fd8052060d44.js.br +0 -0
  67. codex/static_root/assets/VRadioGroup-E0wBXF2E.fd8052060d44.js.gz +0 -0
  68. codex/static_root/assets/{VRadioGroup-DeB10IRm.js → VRadioGroup-E0wBXF2E.js} +1 -1
  69. codex/static_root/assets/VRadioGroup-E0wBXF2E.js.br +0 -0
  70. codex/static_root/assets/VRadioGroup-E0wBXF2E.js.gz +0 -0
  71. codex/static_root/assets/VSelect-DLKX5Mr6.d84e1c59fa18.js +1 -0
  72. codex/static_root/assets/VSelect-DLKX5Mr6.d84e1c59fa18.js.br +0 -0
  73. codex/static_root/assets/VSelect-DLKX5Mr6.d84e1c59fa18.js.gz +0 -0
  74. codex/static_root/assets/VSelect-DLKX5Mr6.js +1 -0
  75. codex/static_root/assets/VSelect-DLKX5Mr6.js.br +0 -0
  76. codex/static_root/assets/VSelect-DLKX5Mr6.js.gz +0 -0
  77. codex/static_root/assets/VSelectionControl-B8hJUO0p.1dda62fe0677.js +1 -0
  78. codex/static_root/assets/VSelectionControl-B8hJUO0p.1dda62fe0677.js.br +0 -0
  79. codex/static_root/assets/VSelectionControl-B8hJUO0p.1dda62fe0677.js.gz +0 -0
  80. codex/static_root/assets/VSelectionControl-B8hJUO0p.js +1 -0
  81. codex/static_root/assets/VSelectionControl-B8hJUO0p.js.br +0 -0
  82. codex/static_root/assets/VSelectionControl-B8hJUO0p.js.gz +0 -0
  83. codex/static_root/assets/VTable-BsI6FsWH.css +1 -0
  84. codex/static_root/assets/VTable-BsI6FsWH.css.br +0 -0
  85. codex/static_root/assets/VTable-BsI6FsWH.css.gz +0 -0
  86. codex/static_root/assets/VTable-BsI6FsWH.d4c54d44a5ab.css +1 -0
  87. codex/static_root/assets/VTable-BsI6FsWH.d4c54d44a5ab.css.br +0 -0
  88. codex/static_root/assets/VTable-BsI6FsWH.d4c54d44a5ab.css.gz +0 -0
  89. codex/static_root/assets/VTable-D0uqeonL.a9897896b818.js +1 -0
  90. codex/static_root/assets/VTable-D0uqeonL.a9897896b818.js.br +0 -0
  91. codex/static_root/assets/VTable-D0uqeonL.a9897896b818.js.gz +0 -0
  92. codex/static_root/assets/VTable-D0uqeonL.js +1 -0
  93. codex/static_root/assets/VTable-D0uqeonL.js.br +0 -0
  94. codex/static_root/assets/VTable-D0uqeonL.js.gz +0 -0
  95. codex/static_root/assets/{VWindowItem-Dwb7oeu0.6a4505330db3.js → VWindowItem-DnxUM-gG.242f232488e3.js} +1 -1
  96. codex/static_root/assets/VWindowItem-DnxUM-gG.242f232488e3.js.br +0 -0
  97. codex/static_root/assets/VWindowItem-DnxUM-gG.242f232488e3.js.gz +0 -0
  98. codex/static_root/assets/{VWindowItem-Dwb7oeu0.js → VWindowItem-DnxUM-gG.js} +1 -1
  99. codex/static_root/assets/VWindowItem-DnxUM-gG.js.br +0 -0
  100. codex/static_root/assets/VWindowItem-DnxUM-gG.js.gz +0 -0
  101. codex/static_root/assets/VWindowItem-QQWUVqMX.c22e707ac99c.css +1 -0
  102. codex/static_root/assets/VWindowItem-QQWUVqMX.c22e707ac99c.css.br +5 -0
  103. codex/static_root/assets/VWindowItem-QQWUVqMX.c22e707ac99c.css.gz +0 -0
  104. codex/static_root/assets/VWindowItem-QQWUVqMX.css +1 -0
  105. codex/static_root/assets/VWindowItem-QQWUVqMX.css.br +5 -0
  106. codex/static_root/assets/VWindowItem-QQWUVqMX.css.gz +0 -0
  107. codex/static_root/assets/{admin-mQ-F7brp.cc388b064f3a.js → admin-B9U01BEM.7c8747c54803.js} +1 -1
  108. codex/static_root/assets/admin-B9U01BEM.7c8747c54803.js.br +0 -0
  109. codex/static_root/assets/admin-B9U01BEM.7c8747c54803.js.gz +0 -0
  110. codex/static_root/assets/{admin-mQ-F7brp.js → admin-B9U01BEM.js} +1 -1
  111. codex/static_root/assets/admin-B9U01BEM.js.br +0 -0
  112. codex/static_root/assets/admin-B9U01BEM.js.gz +0 -0
  113. codex/static_root/assets/{admin-nYIgbfe8.d2acbae0b198.js → admin-DmYMMUnm.58b7b9f4dadf.js} +1 -1
  114. codex/static_root/assets/admin-DmYMMUnm.58b7b9f4dadf.js.br +0 -0
  115. codex/static_root/assets/admin-DmYMMUnm.58b7b9f4dadf.js.gz +0 -0
  116. codex/static_root/assets/{admin-nYIgbfe8.js → admin-DmYMMUnm.js} +1 -1
  117. codex/static_root/assets/admin-DmYMMUnm.js.br +0 -0
  118. codex/static_root/assets/admin-DmYMMUnm.js.gz +0 -0
  119. codex/static_root/assets/admin-menu-CuNW4Urn.25020f907bb8.js +1 -0
  120. codex/static_root/assets/admin-menu-CuNW4Urn.25020f907bb8.js.br +0 -0
  121. codex/static_root/assets/admin-menu-CuNW4Urn.25020f907bb8.js.gz +0 -0
  122. codex/static_root/assets/admin-menu-CuNW4Urn.js +1 -0
  123. codex/static_root/assets/admin-menu-CuNW4Urn.js.br +0 -0
  124. codex/static_root/assets/admin-menu-CuNW4Urn.js.gz +0 -0
  125. codex/static_root/assets/{admin-settings-button-progress-BBRW69l3.697026d00887.js → admin-settings-button-progress-Bzr-gkG2.b17f5bcba7bb.js} +1 -1
  126. codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.b17f5bcba7bb.js.br +3 -0
  127. codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.b17f5bcba7bb.js.gz +0 -0
  128. codex/static_root/assets/{admin-settings-button-progress-BBRW69l3.js → admin-settings-button-progress-Bzr-gkG2.js} +1 -1
  129. codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.js.br +3 -0
  130. codex/static_root/assets/admin-settings-button-progress-Bzr-gkG2.js.gz +0 -0
  131. codex/static_root/assets/browser-CIw7xQ-1.8d16d2b270d5.js +1 -0
  132. codex/static_root/assets/browser-CIw7xQ-1.8d16d2b270d5.js.br +0 -0
  133. codex/static_root/assets/browser-CIw7xQ-1.8d16d2b270d5.js.gz +0 -0
  134. codex/static_root/assets/browser-CIw7xQ-1.js +1 -0
  135. codex/static_root/assets/browser-CIw7xQ-1.js.br +0 -0
  136. codex/static_root/assets/browser-CIw7xQ-1.js.gz +0 -0
  137. codex/static_root/assets/{browser-Cdi7U5ku.bab9fccf0326.css → browser-TRzEFz_k.css} +1 -1
  138. codex/static_root/assets/browser-TRzEFz_k.css.br +0 -0
  139. codex/static_root/assets/browser-TRzEFz_k.css.gz +0 -0
  140. codex/static_root/assets/{browser-Cdi7U5ku.css → browser-TRzEFz_k.e8ae750c4a41.css} +1 -1
  141. codex/static_root/assets/browser-TRzEFz_k.e8ae750c4a41.css.br +0 -0
  142. codex/static_root/assets/browser-TRzEFz_k.e8ae750c4a41.css.gz +0 -0
  143. codex/static_root/assets/change-password-dialog-CXaB7k3y.01788b2cb59e.js +1 -0
  144. codex/static_root/assets/change-password-dialog-CXaB7k3y.01788b2cb59e.js.br +0 -0
  145. codex/static_root/assets/change-password-dialog-CXaB7k3y.01788b2cb59e.js.gz +0 -0
  146. codex/static_root/assets/change-password-dialog-CXaB7k3y.js +1 -0
  147. codex/static_root/assets/change-password-dialog-CXaB7k3y.js.br +0 -0
  148. codex/static_root/assets/change-password-dialog-CXaB7k3y.js.gz +0 -0
  149. codex/static_root/assets/{confirm-dialog-uoXMmYq3.0ec4c4eba588.js → confirm-dialog-gXeRF5bD.4ae6e7a9790a.js} +1 -1
  150. codex/static_root/assets/confirm-dialog-gXeRF5bD.4ae6e7a9790a.js.br +0 -0
  151. codex/static_root/assets/confirm-dialog-gXeRF5bD.4ae6e7a9790a.js.gz +0 -0
  152. codex/static_root/assets/{confirm-dialog-uoXMmYq3.js → confirm-dialog-gXeRF5bD.js} +1 -1
  153. codex/static_root/assets/confirm-dialog-gXeRF5bD.js.br +0 -0
  154. codex/static_root/assets/confirm-dialog-gXeRF5bD.js.gz +0 -0
  155. codex/static_root/assets/{datetime-column-GkTrMP1-.bf4adfaa4e02.js → datetime-column-C5ZBHxV-.35957e684ec2.js} +1 -1
  156. codex/static_root/assets/datetime-column-C5ZBHxV-.35957e684ec2.js.br +0 -0
  157. codex/static_root/assets/datetime-column-C5ZBHxV-.35957e684ec2.js.gz +0 -0
  158. codex/static_root/assets/{datetime-column-GkTrMP1-.js → datetime-column-C5ZBHxV-.js} +1 -1
  159. codex/static_root/assets/datetime-column-C5ZBHxV-.js.br +0 -0
  160. codex/static_root/assets/datetime-column-C5ZBHxV-.js.gz +0 -0
  161. codex/static_root/assets/{filter-CRnAKgPp.cc654b8f8155.js → filter-CQ83UjMF.7cd979df64da.js} +1 -1
  162. codex/static_root/assets/filter-CQ83UjMF.7cd979df64da.js.br +0 -0
  163. codex/static_root/assets/filter-CQ83UjMF.7cd979df64da.js.gz +0 -0
  164. codex/static_root/assets/{filter-CRnAKgPp.js → filter-CQ83UjMF.js} +1 -1
  165. codex/static_root/assets/filter-CQ83UjMF.js.br +0 -0
  166. codex/static_root/assets/filter-CQ83UjMF.js.gz +0 -0
  167. codex/static_root/assets/{flag-tab-BA4kiUXn.408cc0a71ec1.js → flag-tab-CN3Y0PZS.f5f7122a015e.js} +1 -1
  168. codex/static_root/assets/flag-tab-CN3Y0PZS.f5f7122a015e.js.br +0 -0
  169. codex/static_root/assets/flag-tab-CN3Y0PZS.f5f7122a015e.js.gz +0 -0
  170. codex/static_root/assets/{flag-tab-BA4kiUXn.js → flag-tab-CN3Y0PZS.js} +1 -1
  171. codex/static_root/assets/flag-tab-CN3Y0PZS.js.br +0 -0
  172. codex/static_root/assets/flag-tab-CN3Y0PZS.js.gz +0 -0
  173. codex/static_root/assets/{forwardRefs-B_sUWZn6.css → forwardRefs-C3Z4qL85.48fb131ece85.css} +1 -1
  174. codex/static_root/assets/forwardRefs-C3Z4qL85.48fb131ece85.css.br +0 -0
  175. codex/static_root/assets/forwardRefs-C3Z4qL85.48fb131ece85.css.gz +0 -0
  176. codex/static_root/assets/{forwardRefs-B_sUWZn6.5114f206ca9c.css → forwardRefs-C3Z4qL85.css} +1 -1
  177. codex/static_root/assets/forwardRefs-C3Z4qL85.css.br +0 -0
  178. codex/static_root/assets/forwardRefs-C3Z4qL85.css.gz +0 -0
  179. codex/static_root/assets/forwardRefs-DBxFdh0r.03d9552a8e4c.js +1 -0
  180. codex/static_root/assets/forwardRefs-DBxFdh0r.03d9552a8e4c.js.br +0 -0
  181. codex/static_root/assets/forwardRefs-DBxFdh0r.03d9552a8e4c.js.gz +0 -0
  182. codex/static_root/assets/forwardRefs-DBxFdh0r.js +1 -0
  183. codex/static_root/assets/forwardRefs-DBxFdh0r.js.br +0 -0
  184. codex/static_root/assets/forwardRefs-DBxFdh0r.js.gz +0 -0
  185. codex/static_root/assets/{group-tab-BD9iJZwB.b3e2b1b68d3a.js → group-tab-DMPL6FiK.82f53e5c353a.js} +1 -1
  186. codex/static_root/assets/group-tab-DMPL6FiK.82f53e5c353a.js.br +0 -0
  187. codex/static_root/assets/group-tab-DMPL6FiK.82f53e5c353a.js.gz +0 -0
  188. codex/static_root/assets/{group-tab-BD9iJZwB.js → group-tab-DMPL6FiK.js} +1 -1
  189. codex/static_root/assets/group-tab-DMPL6FiK.js.br +0 -0
  190. codex/static_root/assets/group-tab-DMPL6FiK.js.gz +0 -0
  191. codex/static_root/assets/http-error-DgJ8-SFZ.da2fe4d026ce.js +1 -0
  192. codex/static_root/assets/http-error-DgJ8-SFZ.da2fe4d026ce.js.br +0 -0
  193. codex/static_root/assets/http-error-DgJ8-SFZ.da2fe4d026ce.js.gz +0 -0
  194. codex/static_root/assets/http-error-DgJ8-SFZ.js +1 -0
  195. codex/static_root/assets/http-error-DgJ8-SFZ.js.br +0 -0
  196. codex/static_root/assets/http-error-DgJ8-SFZ.js.gz +0 -0
  197. codex/static_root/assets/{index-DCZ6v5x6.848976e7722c.js → index-CQ0ue0KC.b38e465abad4.js} +1 -1
  198. codex/static_root/assets/index-CQ0ue0KC.b38e465abad4.js.br +0 -0
  199. codex/static_root/assets/index-CQ0ue0KC.b38e465abad4.js.gz +0 -0
  200. codex/static_root/assets/{index-DCZ6v5x6.js → index-CQ0ue0KC.js} +1 -1
  201. codex/static_root/assets/index-CQ0ue0KC.js.br +0 -0
  202. codex/static_root/assets/index-CQ0ue0KC.js.gz +0 -0
  203. codex/static_root/assets/library-tab-CfuZgnol.880ba8bd0149.js +1 -0
  204. codex/static_root/assets/library-tab-CfuZgnol.880ba8bd0149.js.br +0 -0
  205. codex/static_root/assets/library-tab-CfuZgnol.880ba8bd0149.js.gz +0 -0
  206. codex/static_root/assets/library-tab-CfuZgnol.js +1 -0
  207. codex/static_root/assets/library-tab-CfuZgnol.js.br +0 -0
  208. codex/static_root/assets/library-tab-CfuZgnol.js.gz +0 -0
  209. codex/static_root/assets/{main-Bmi4BPiO.d75c8b55081b.js → main-G8n9PNCf.cad6fa53a574.js} +16 -16
  210. codex/static_root/assets/main-G8n9PNCf.cad6fa53a574.js.br +0 -0
  211. codex/static_root/assets/main-G8n9PNCf.cad6fa53a574.js.gz +0 -0
  212. codex/static_root/assets/{main-Bmi4BPiO.js → main-G8n9PNCf.js} +16 -16
  213. codex/static_root/assets/main-G8n9PNCf.js.br +0 -0
  214. codex/static_root/assets/main-G8n9PNCf.js.gz +0 -0
  215. codex/static_root/assets/pager-full-pdf-BKZayLN3.4b724ad0595f.js +1 -0
  216. codex/static_root/assets/pager-full-pdf-BKZayLN3.4b724ad0595f.js.br +0 -0
  217. codex/static_root/assets/pager-full-pdf-BKZayLN3.4b724ad0595f.js.gz +0 -0
  218. codex/static_root/assets/pager-full-pdf-BKZayLN3.js +1 -0
  219. codex/static_root/assets/pager-full-pdf-BKZayLN3.js.br +0 -0
  220. codex/static_root/assets/pager-full-pdf-BKZayLN3.js.gz +0 -0
  221. codex/static_root/assets/pagination-toolbar-0vkF7yjo.d32720f76776.js +1 -0
  222. codex/static_root/assets/pagination-toolbar-0vkF7yjo.d32720f76776.js.br +0 -0
  223. codex/static_root/assets/pagination-toolbar-0vkF7yjo.d32720f76776.js.gz +0 -0
  224. codex/static_root/assets/pagination-toolbar-0vkF7yjo.js +1 -0
  225. codex/static_root/assets/pagination-toolbar-0vkF7yjo.js.br +0 -0
  226. codex/static_root/assets/pagination-toolbar-0vkF7yjo.js.gz +0 -0
  227. codex/static_root/assets/{pagination-toolbar-741OafNw.4f9155469274.css → pagination-toolbar-rUdD4-Qi.3a224c7806a5.css} +1 -1
  228. codex/static_root/assets/pagination-toolbar-rUdD4-Qi.3a224c7806a5.css.br +0 -0
  229. codex/static_root/assets/pagination-toolbar-rUdD4-Qi.3a224c7806a5.css.gz +0 -0
  230. codex/static_root/assets/{pagination-toolbar-741OafNw.css → pagination-toolbar-rUdD4-Qi.css} +1 -1
  231. codex/static_root/assets/pagination-toolbar-rUdD4-Qi.css.br +0 -0
  232. codex/static_root/assets/pagination-toolbar-rUdD4-Qi.css.gz +0 -0
  233. codex/static_root/assets/{pdf-doc-DQBmNe4l.8dbdebf30643.js → pdf-doc-CHmFdwy6.4142a4f31d20.js} +1 -1
  234. codex/static_root/assets/pdf-doc-CHmFdwy6.4142a4f31d20.js.br +0 -0
  235. codex/static_root/assets/pdf-doc-CHmFdwy6.4142a4f31d20.js.gz +0 -0
  236. codex/static_root/assets/{pdf-doc-DQBmNe4l.js → pdf-doc-CHmFdwy6.js} +1 -1
  237. codex/static_root/assets/pdf-doc-CHmFdwy6.js.br +0 -0
  238. codex/static_root/assets/pdf-doc-CHmFdwy6.js.gz +0 -0
  239. codex/static_root/assets/reader-YIWmNjj6.c96fc7891aec.js +2 -0
  240. codex/static_root/assets/reader-YIWmNjj6.c96fc7891aec.js.br +0 -0
  241. codex/static_root/assets/reader-YIWmNjj6.c96fc7891aec.js.gz +0 -0
  242. codex/static_root/assets/reader-YIWmNjj6.js +2 -0
  243. codex/static_root/assets/reader-YIWmNjj6.js.br +0 -0
  244. codex/static_root/assets/reader-YIWmNjj6.js.gz +0 -0
  245. codex/static_root/assets/{relation-chips-CcFgPQOM.0b468249b62e.css → relation-chips-CpiT_4k-.b72d78f512e6.css} +1 -1
  246. codex/static_root/assets/relation-chips-CpiT_4k-.b72d78f512e6.css.br +0 -0
  247. codex/static_root/assets/relation-chips-CpiT_4k-.b72d78f512e6.css.gz +0 -0
  248. codex/static_root/assets/{relation-chips-CcFgPQOM.css → relation-chips-CpiT_4k-.css} +1 -1
  249. codex/static_root/assets/relation-chips-CpiT_4k-.css.br +0 -0
  250. codex/static_root/assets/relation-chips-CpiT_4k-.css.gz +0 -0
  251. codex/static_root/assets/relation-chips-DzoiGrOj.95f663c6c18d.js +1 -0
  252. codex/static_root/assets/relation-chips-DzoiGrOj.95f663c6c18d.js.br +0 -0
  253. codex/static_root/assets/relation-chips-DzoiGrOj.95f663c6c18d.js.gz +0 -0
  254. codex/static_root/assets/relation-chips-DzoiGrOj.js +1 -0
  255. codex/static_root/assets/relation-chips-DzoiGrOj.js.br +0 -0
  256. codex/static_root/assets/relation-chips-DzoiGrOj.js.gz +0 -0
  257. codex/static_root/assets/settings-drawer-CUC-r4sg.e019f5467d25.js +2 -0
  258. codex/static_root/assets/settings-drawer-CUC-r4sg.e019f5467d25.js.br +0 -0
  259. codex/static_root/assets/settings-drawer-CUC-r4sg.e019f5467d25.js.gz +0 -0
  260. codex/static_root/assets/settings-drawer-CUC-r4sg.js +2 -0
  261. codex/static_root/assets/settings-drawer-CUC-r4sg.js.br +0 -0
  262. codex/static_root/assets/settings-drawer-CUC-r4sg.js.gz +0 -0
  263. codex/static_root/assets/{ssrBoot-B6P0M2z2.38ab2f4bde57.js → ssrBoot-D7foQ-7h.31c07ef6f646.js} +1 -1
  264. codex/static_root/assets/ssrBoot-D7foQ-7h.31c07ef6f646.js.br +0 -0
  265. codex/static_root/assets/ssrBoot-D7foQ-7h.31c07ef6f646.js.gz +0 -0
  266. codex/static_root/assets/{ssrBoot-B6P0M2z2.js → ssrBoot-D7foQ-7h.js} +1 -1
  267. codex/static_root/assets/ssrBoot-D7foQ-7h.js.br +0 -0
  268. codex/static_root/assets/ssrBoot-D7foQ-7h.js.gz +0 -0
  269. codex/static_root/assets/{stats-tab-C5m62fu3.8482d96809e9.js → stats-tab-BSy-9tZm.2a7560872f5c.js} +1 -1
  270. codex/static_root/assets/stats-tab-BSy-9tZm.2a7560872f5c.js.br +0 -0
  271. codex/static_root/assets/stats-tab-BSy-9tZm.2a7560872f5c.js.gz +0 -0
  272. codex/static_root/assets/{stats-tab-C5m62fu3.js → stats-tab-BSy-9tZm.js} +1 -1
  273. codex/static_root/assets/stats-tab-BSy-9tZm.js.br +0 -0
  274. codex/static_root/assets/stats-tab-BSy-9tZm.js.gz +0 -0
  275. codex/static_root/assets/task-tab-Sp49fYRT.3a1aab3f3716.js +1 -0
  276. codex/static_root/assets/task-tab-Sp49fYRT.3a1aab3f3716.js.br +0 -0
  277. codex/static_root/assets/task-tab-Sp49fYRT.3a1aab3f3716.js.gz +0 -0
  278. codex/static_root/assets/task-tab-Sp49fYRT.js +1 -0
  279. codex/static_root/assets/task-tab-Sp49fYRT.js.br +0 -0
  280. codex/static_root/assets/task-tab-Sp49fYRT.js.gz +0 -0
  281. codex/static_root/assets/{unauthorized-DXoWE_v2.19c70a140f9b.js → unauthorized-BM6KzvCy.4c114d9693c1.js} +1 -1
  282. codex/static_root/assets/unauthorized-BM6KzvCy.4c114d9693c1.js.br +0 -0
  283. codex/static_root/assets/unauthorized-BM6KzvCy.4c114d9693c1.js.gz +0 -0
  284. codex/static_root/assets/{unauthorized-DXoWE_v2.js → unauthorized-BM6KzvCy.js} +1 -1
  285. codex/static_root/assets/unauthorized-BM6KzvCy.js.br +0 -0
  286. codex/static_root/assets/unauthorized-BM6KzvCy.js.gz +0 -0
  287. codex/static_root/assets/user-tab-yQ60jtVd.ad593cc6e6ea.js +1 -0
  288. codex/static_root/assets/user-tab-yQ60jtVd.ad593cc6e6ea.js.br +0 -0
  289. codex/static_root/assets/user-tab-yQ60jtVd.ad593cc6e6ea.js.gz +0 -0
  290. codex/static_root/assets/user-tab-yQ60jtVd.js +1 -0
  291. codex/static_root/assets/user-tab-yQ60jtVd.js.br +0 -0
  292. codex/static_root/assets/user-tab-yQ60jtVd.js.gz +0 -0
  293. codex/static_root/{manifest.db295ed1ca03.json → manifest.a059c4dc5443.json} +308 -308
  294. codex/static_root/manifest.a059c4dc5443.json.br +0 -0
  295. codex/static_root/manifest.a059c4dc5443.json.gz +0 -0
  296. codex/static_root/manifest.json +308 -308
  297. codex/static_root/manifest.json.br +0 -0
  298. codex/static_root/manifest.json.gz +0 -0
  299. codex/static_root/staticfiles.json +1 -1
  300. codex/views/browser/filters/filter.py +1 -1
  301. codex/views/browser/filters/search/field/column.py +3 -3
  302. codex/views/browser/filters/search/parse.py +15 -10
  303. {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/METADATA +5 -5
  304. {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/RECORD +307 -305
  305. codex/static_root/assets/VCheckbox--IZv20K5.121e571f623b.js +0 -1
  306. codex/static_root/assets/VCheckbox--IZv20K5.121e571f623b.js.br +0 -0
  307. codex/static_root/assets/VCheckbox--IZv20K5.121e571f623b.js.gz +0 -0
  308. codex/static_root/assets/VCheckbox--IZv20K5.js +0 -1
  309. codex/static_root/assets/VCheckbox--IZv20K5.js.br +0 -0
  310. codex/static_root/assets/VCheckbox--IZv20K5.js.gz +0 -0
  311. codex/static_root/assets/VCheckboxBtn-BzgViWAb.1400a41af9da.js.br +0 -0
  312. codex/static_root/assets/VCheckboxBtn-BzgViWAb.1400a41af9da.js.gz +0 -0
  313. codex/static_root/assets/VCheckboxBtn-BzgViWAb.js.br +0 -0
  314. codex/static_root/assets/VCheckboxBtn-BzgViWAb.js.gz +0 -0
  315. codex/static_root/assets/VCombobox-0VKjtXTP.f7c9bb8e7f77.js +0 -1
  316. codex/static_root/assets/VCombobox-0VKjtXTP.f7c9bb8e7f77.js.br +0 -0
  317. codex/static_root/assets/VCombobox-0VKjtXTP.f7c9bb8e7f77.js.gz +0 -0
  318. codex/static_root/assets/VCombobox-0VKjtXTP.js +0 -1
  319. codex/static_root/assets/VCombobox-0VKjtXTP.js.br +0 -0
  320. codex/static_root/assets/VCombobox-0VKjtXTP.js.gz +0 -0
  321. codex/static_root/assets/VDialog-Oz6-kM3S.2244675141c7.js +0 -1
  322. codex/static_root/assets/VDialog-Oz6-kM3S.2244675141c7.js.br +0 -0
  323. codex/static_root/assets/VDialog-Oz6-kM3S.2244675141c7.js.gz +0 -0
  324. codex/static_root/assets/VDialog-Oz6-kM3S.js +0 -1
  325. codex/static_root/assets/VDialog-Oz6-kM3S.js.br +0 -0
  326. codex/static_root/assets/VDialog-Oz6-kM3S.js.gz +0 -0
  327. codex/static_root/assets/VDivider-CSRFyKQO.5f8e1ffafbaf.js.br +0 -0
  328. codex/static_root/assets/VDivider-CSRFyKQO.5f8e1ffafbaf.js.gz +0 -0
  329. codex/static_root/assets/VDivider-CSRFyKQO.js.br +0 -0
  330. codex/static_root/assets/VDivider-CSRFyKQO.js.gz +0 -0
  331. codex/static_root/assets/VExpansionPanels-82Ocr2Kh.1ccaea91f07c.js.br +0 -0
  332. codex/static_root/assets/VExpansionPanels-82Ocr2Kh.1ccaea91f07c.js.gz +0 -0
  333. codex/static_root/assets/VExpansionPanels-82Ocr2Kh.js.br +0 -0
  334. codex/static_root/assets/VExpansionPanels-82Ocr2Kh.js.gz +0 -0
  335. codex/static_root/assets/VForm-BaeKMRS9.be058cf1518f.js.br +0 -0
  336. codex/static_root/assets/VForm-BaeKMRS9.be058cf1518f.js.gz +0 -0
  337. codex/static_root/assets/VForm-BaeKMRS9.js.br +0 -0
  338. codex/static_root/assets/VForm-BaeKMRS9.js.gz +0 -0
  339. codex/static_root/assets/VRadioGroup-DeB10IRm.4ea874709ffb.js.br +0 -0
  340. codex/static_root/assets/VRadioGroup-DeB10IRm.4ea874709ffb.js.gz +0 -0
  341. codex/static_root/assets/VRadioGroup-DeB10IRm.js.br +0 -0
  342. codex/static_root/assets/VRadioGroup-DeB10IRm.js.gz +0 -0
  343. codex/static_root/assets/VSelect-4ozzBEef.36f749b6f4ab.js +0 -1
  344. codex/static_root/assets/VSelect-4ozzBEef.36f749b6f4ab.js.br +0 -0
  345. codex/static_root/assets/VSelect-4ozzBEef.36f749b6f4ab.js.gz +0 -0
  346. codex/static_root/assets/VSelect-4ozzBEef.js +0 -1
  347. codex/static_root/assets/VSelect-4ozzBEef.js.br +0 -0
  348. codex/static_root/assets/VSelect-4ozzBEef.js.gz +0 -0
  349. codex/static_root/assets/VSelectionControl-D3h7FjSM.6cf4ba197995.js +0 -1
  350. codex/static_root/assets/VSelectionControl-D3h7FjSM.6cf4ba197995.js.br +0 -0
  351. codex/static_root/assets/VSelectionControl-D3h7FjSM.6cf4ba197995.js.gz +0 -0
  352. codex/static_root/assets/VSelectionControl-D3h7FjSM.js +0 -1
  353. codex/static_root/assets/VSelectionControl-D3h7FjSM.js.br +0 -0
  354. codex/static_root/assets/VSelectionControl-D3h7FjSM.js.gz +0 -0
  355. codex/static_root/assets/VTable-CQyv0wyO.1740239d481b.css +0 -1
  356. codex/static_root/assets/VTable-CQyv0wyO.1740239d481b.css.br +0 -0
  357. codex/static_root/assets/VTable-CQyv0wyO.1740239d481b.css.gz +0 -0
  358. codex/static_root/assets/VTable-CQyv0wyO.css +0 -1
  359. codex/static_root/assets/VTable-CQyv0wyO.css.br +0 -0
  360. codex/static_root/assets/VTable-CQyv0wyO.css.gz +0 -0
  361. codex/static_root/assets/VTable-Dj5EC5Aq.7abc9daf78be.js +0 -1
  362. codex/static_root/assets/VTable-Dj5EC5Aq.7abc9daf78be.js.br +0 -0
  363. codex/static_root/assets/VTable-Dj5EC5Aq.7abc9daf78be.js.gz +0 -0
  364. codex/static_root/assets/VTable-Dj5EC5Aq.js +0 -1
  365. codex/static_root/assets/VTable-Dj5EC5Aq.js.br +0 -0
  366. codex/static_root/assets/VTable-Dj5EC5Aq.js.gz +0 -0
  367. codex/static_root/assets/VWindowItem-ChYLiXSE.a9c679a2977e.css +0 -1
  368. codex/static_root/assets/VWindowItem-ChYLiXSE.a9c679a2977e.css.br +0 -0
  369. codex/static_root/assets/VWindowItem-ChYLiXSE.a9c679a2977e.css.gz +0 -0
  370. codex/static_root/assets/VWindowItem-ChYLiXSE.css +0 -1
  371. codex/static_root/assets/VWindowItem-ChYLiXSE.css.br +0 -0
  372. codex/static_root/assets/VWindowItem-ChYLiXSE.css.gz +0 -0
  373. codex/static_root/assets/VWindowItem-Dwb7oeu0.6a4505330db3.js.br +0 -0
  374. codex/static_root/assets/VWindowItem-Dwb7oeu0.6a4505330db3.js.gz +0 -0
  375. codex/static_root/assets/VWindowItem-Dwb7oeu0.js.br +0 -0
  376. codex/static_root/assets/VWindowItem-Dwb7oeu0.js.gz +0 -0
  377. codex/static_root/assets/admin-mQ-F7brp.cc388b064f3a.js.br +0 -0
  378. codex/static_root/assets/admin-mQ-F7brp.cc388b064f3a.js.gz +0 -0
  379. codex/static_root/assets/admin-mQ-F7brp.js.br +0 -0
  380. codex/static_root/assets/admin-mQ-F7brp.js.gz +0 -0
  381. codex/static_root/assets/admin-menu-CfGPKhvz.1e224da855f5.js +0 -1
  382. codex/static_root/assets/admin-menu-CfGPKhvz.1e224da855f5.js.br +0 -0
  383. codex/static_root/assets/admin-menu-CfGPKhvz.1e224da855f5.js.gz +0 -0
  384. codex/static_root/assets/admin-menu-CfGPKhvz.js +0 -1
  385. codex/static_root/assets/admin-menu-CfGPKhvz.js.br +0 -0
  386. codex/static_root/assets/admin-menu-CfGPKhvz.js.gz +0 -0
  387. codex/static_root/assets/admin-nYIgbfe8.d2acbae0b198.js.br +0 -0
  388. codex/static_root/assets/admin-nYIgbfe8.d2acbae0b198.js.gz +0 -0
  389. codex/static_root/assets/admin-nYIgbfe8.js.br +0 -0
  390. codex/static_root/assets/admin-nYIgbfe8.js.gz +0 -0
  391. codex/static_root/assets/admin-settings-button-progress-BBRW69l3.697026d00887.js.br +0 -0
  392. codex/static_root/assets/admin-settings-button-progress-BBRW69l3.697026d00887.js.gz +0 -0
  393. codex/static_root/assets/admin-settings-button-progress-BBRW69l3.js.br +0 -0
  394. codex/static_root/assets/admin-settings-button-progress-BBRW69l3.js.gz +0 -0
  395. codex/static_root/assets/browser-BzV_-1Jg.e39184a92bab.js +0 -1
  396. codex/static_root/assets/browser-BzV_-1Jg.e39184a92bab.js.br +0 -0
  397. codex/static_root/assets/browser-BzV_-1Jg.e39184a92bab.js.gz +0 -0
  398. codex/static_root/assets/browser-BzV_-1Jg.js +0 -1
  399. codex/static_root/assets/browser-BzV_-1Jg.js.br +0 -0
  400. codex/static_root/assets/browser-BzV_-1Jg.js.gz +0 -0
  401. codex/static_root/assets/browser-Cdi7U5ku.bab9fccf0326.css.br +0 -0
  402. codex/static_root/assets/browser-Cdi7U5ku.bab9fccf0326.css.gz +0 -0
  403. codex/static_root/assets/browser-Cdi7U5ku.css.br +0 -0
  404. codex/static_root/assets/browser-Cdi7U5ku.css.gz +0 -0
  405. codex/static_root/assets/change-password-dialog-DpqXgZ53.3f22c3114669.js +0 -1
  406. codex/static_root/assets/change-password-dialog-DpqXgZ53.3f22c3114669.js.br +0 -0
  407. codex/static_root/assets/change-password-dialog-DpqXgZ53.3f22c3114669.js.gz +0 -0
  408. codex/static_root/assets/change-password-dialog-DpqXgZ53.js +0 -1
  409. codex/static_root/assets/change-password-dialog-DpqXgZ53.js.br +0 -0
  410. codex/static_root/assets/change-password-dialog-DpqXgZ53.js.gz +0 -0
  411. codex/static_root/assets/confirm-dialog-uoXMmYq3.0ec4c4eba588.js.br +0 -0
  412. codex/static_root/assets/confirm-dialog-uoXMmYq3.0ec4c4eba588.js.gz +0 -0
  413. codex/static_root/assets/confirm-dialog-uoXMmYq3.js.br +0 -0
  414. codex/static_root/assets/confirm-dialog-uoXMmYq3.js.gz +0 -0
  415. codex/static_root/assets/datetime-column-GkTrMP1-.bf4adfaa4e02.js.br +0 -0
  416. codex/static_root/assets/datetime-column-GkTrMP1-.bf4adfaa4e02.js.gz +0 -0
  417. codex/static_root/assets/datetime-column-GkTrMP1-.js.br +0 -0
  418. codex/static_root/assets/datetime-column-GkTrMP1-.js.gz +0 -0
  419. codex/static_root/assets/filter-CRnAKgPp.cc654b8f8155.js.br +0 -0
  420. codex/static_root/assets/filter-CRnAKgPp.cc654b8f8155.js.gz +0 -0
  421. codex/static_root/assets/filter-CRnAKgPp.js.br +0 -0
  422. codex/static_root/assets/filter-CRnAKgPp.js.gz +0 -0
  423. codex/static_root/assets/flag-tab-BA4kiUXn.408cc0a71ec1.js.br +0 -0
  424. codex/static_root/assets/flag-tab-BA4kiUXn.408cc0a71ec1.js.gz +0 -0
  425. codex/static_root/assets/flag-tab-BA4kiUXn.js.br +0 -0
  426. codex/static_root/assets/flag-tab-BA4kiUXn.js.gz +0 -0
  427. codex/static_root/assets/forwardRefs-B_sUWZn6.5114f206ca9c.css.br +0 -0
  428. codex/static_root/assets/forwardRefs-B_sUWZn6.5114f206ca9c.css.gz +0 -0
  429. codex/static_root/assets/forwardRefs-B_sUWZn6.css.br +0 -0
  430. codex/static_root/assets/forwardRefs-B_sUWZn6.css.gz +0 -0
  431. codex/static_root/assets/forwardRefs-FxOC1qa_.6e14881eedf1.js +0 -1
  432. codex/static_root/assets/forwardRefs-FxOC1qa_.6e14881eedf1.js.br +0 -0
  433. codex/static_root/assets/forwardRefs-FxOC1qa_.6e14881eedf1.js.gz +0 -0
  434. codex/static_root/assets/forwardRefs-FxOC1qa_.js +0 -1
  435. codex/static_root/assets/forwardRefs-FxOC1qa_.js.br +0 -0
  436. codex/static_root/assets/forwardRefs-FxOC1qa_.js.gz +0 -0
  437. codex/static_root/assets/group-tab-BD9iJZwB.b3e2b1b68d3a.js.br +0 -0
  438. codex/static_root/assets/group-tab-BD9iJZwB.b3e2b1b68d3a.js.gz +0 -0
  439. codex/static_root/assets/group-tab-BD9iJZwB.js.br +0 -0
  440. codex/static_root/assets/group-tab-BD9iJZwB.js.gz +0 -0
  441. codex/static_root/assets/http-error-D-b5OfHC.e661fa6e0000.js +0 -1
  442. codex/static_root/assets/http-error-D-b5OfHC.e661fa6e0000.js.br +0 -2
  443. codex/static_root/assets/http-error-D-b5OfHC.e661fa6e0000.js.gz +0 -0
  444. codex/static_root/assets/http-error-D-b5OfHC.js +0 -1
  445. codex/static_root/assets/http-error-D-b5OfHC.js.br +0 -2
  446. codex/static_root/assets/http-error-D-b5OfHC.js.gz +0 -0
  447. codex/static_root/assets/index-DCZ6v5x6.848976e7722c.js.br +0 -0
  448. codex/static_root/assets/index-DCZ6v5x6.848976e7722c.js.gz +0 -0
  449. codex/static_root/assets/index-DCZ6v5x6.js.br +0 -0
  450. codex/static_root/assets/index-DCZ6v5x6.js.gz +0 -0
  451. codex/static_root/assets/library-tab-ju4WtRth.97949bf344d7.js +0 -1
  452. codex/static_root/assets/library-tab-ju4WtRth.97949bf344d7.js.br +0 -0
  453. codex/static_root/assets/library-tab-ju4WtRth.97949bf344d7.js.gz +0 -0
  454. codex/static_root/assets/library-tab-ju4WtRth.js +0 -1
  455. codex/static_root/assets/library-tab-ju4WtRth.js.br +0 -0
  456. codex/static_root/assets/library-tab-ju4WtRth.js.gz +0 -0
  457. codex/static_root/assets/main-Bmi4BPiO.d75c8b55081b.js.br +0 -0
  458. codex/static_root/assets/main-Bmi4BPiO.d75c8b55081b.js.gz +0 -0
  459. codex/static_root/assets/main-Bmi4BPiO.js.br +0 -0
  460. codex/static_root/assets/main-Bmi4BPiO.js.gz +0 -0
  461. codex/static_root/assets/pager-full-pdf-BjSr3CzH.ffbe3d735455.js +0 -1
  462. codex/static_root/assets/pager-full-pdf-BjSr3CzH.ffbe3d735455.js.br +0 -0
  463. codex/static_root/assets/pager-full-pdf-BjSr3CzH.ffbe3d735455.js.gz +0 -0
  464. codex/static_root/assets/pager-full-pdf-BjSr3CzH.js +0 -1
  465. codex/static_root/assets/pager-full-pdf-BjSr3CzH.js.br +0 -0
  466. codex/static_root/assets/pager-full-pdf-BjSr3CzH.js.gz +0 -0
  467. codex/static_root/assets/pagination-toolbar-741OafNw.4f9155469274.css.br +0 -0
  468. codex/static_root/assets/pagination-toolbar-741OafNw.4f9155469274.css.gz +0 -0
  469. codex/static_root/assets/pagination-toolbar-741OafNw.css.br +0 -0
  470. codex/static_root/assets/pagination-toolbar-741OafNw.css.gz +0 -0
  471. codex/static_root/assets/pagination-toolbar-DpMVSiz_.86ce692ff9b4.js +0 -1
  472. codex/static_root/assets/pagination-toolbar-DpMVSiz_.86ce692ff9b4.js.br +0 -0
  473. codex/static_root/assets/pagination-toolbar-DpMVSiz_.86ce692ff9b4.js.gz +0 -0
  474. codex/static_root/assets/pagination-toolbar-DpMVSiz_.js +0 -1
  475. codex/static_root/assets/pagination-toolbar-DpMVSiz_.js.br +0 -0
  476. codex/static_root/assets/pagination-toolbar-DpMVSiz_.js.gz +0 -0
  477. codex/static_root/assets/pdf-doc-DQBmNe4l.8dbdebf30643.js.br +0 -0
  478. codex/static_root/assets/pdf-doc-DQBmNe4l.8dbdebf30643.js.gz +0 -0
  479. codex/static_root/assets/pdf-doc-DQBmNe4l.js.br +0 -0
  480. codex/static_root/assets/pdf-doc-DQBmNe4l.js.gz +0 -0
  481. codex/static_root/assets/reader-BQVYc_LF.e327430a14a1.js +0 -2
  482. codex/static_root/assets/reader-BQVYc_LF.e327430a14a1.js.br +0 -0
  483. codex/static_root/assets/reader-BQVYc_LF.e327430a14a1.js.gz +0 -0
  484. codex/static_root/assets/reader-BQVYc_LF.js +0 -2
  485. codex/static_root/assets/reader-BQVYc_LF.js.br +0 -0
  486. codex/static_root/assets/reader-BQVYc_LF.js.gz +0 -0
  487. codex/static_root/assets/relation-chips-CcFgPQOM.0b468249b62e.css.br +0 -0
  488. codex/static_root/assets/relation-chips-CcFgPQOM.0b468249b62e.css.gz +0 -0
  489. codex/static_root/assets/relation-chips-CcFgPQOM.css.br +0 -0
  490. codex/static_root/assets/relation-chips-CcFgPQOM.css.gz +0 -0
  491. codex/static_root/assets/relation-chips-TRmABTTI.918cb21596f9.js +0 -1
  492. codex/static_root/assets/relation-chips-TRmABTTI.918cb21596f9.js.br +0 -0
  493. codex/static_root/assets/relation-chips-TRmABTTI.918cb21596f9.js.gz +0 -0
  494. codex/static_root/assets/relation-chips-TRmABTTI.js +0 -1
  495. codex/static_root/assets/relation-chips-TRmABTTI.js.br +0 -0
  496. codex/static_root/assets/relation-chips-TRmABTTI.js.gz +0 -0
  497. codex/static_root/assets/settings-drawer-BoAnrKCI.46b6a030c81f.js +0 -2
  498. codex/static_root/assets/settings-drawer-BoAnrKCI.46b6a030c81f.js.br +0 -0
  499. codex/static_root/assets/settings-drawer-BoAnrKCI.46b6a030c81f.js.gz +0 -0
  500. codex/static_root/assets/settings-drawer-BoAnrKCI.js +0 -2
  501. codex/static_root/assets/settings-drawer-BoAnrKCI.js.br +0 -0
  502. codex/static_root/assets/settings-drawer-BoAnrKCI.js.gz +0 -0
  503. codex/static_root/assets/ssrBoot-B6P0M2z2.38ab2f4bde57.js.br +0 -0
  504. codex/static_root/assets/ssrBoot-B6P0M2z2.38ab2f4bde57.js.gz +0 -0
  505. codex/static_root/assets/ssrBoot-B6P0M2z2.js.br +0 -0
  506. codex/static_root/assets/ssrBoot-B6P0M2z2.js.gz +0 -0
  507. codex/static_root/assets/stats-tab-C5m62fu3.8482d96809e9.js.br +0 -0
  508. codex/static_root/assets/stats-tab-C5m62fu3.8482d96809e9.js.gz +0 -0
  509. codex/static_root/assets/stats-tab-C5m62fu3.js.br +0 -0
  510. codex/static_root/assets/stats-tab-C5m62fu3.js.gz +0 -0
  511. codex/static_root/assets/task-tab-B3WOGyYT.508fce185a28.js +0 -1
  512. codex/static_root/assets/task-tab-B3WOGyYT.508fce185a28.js.br +0 -0
  513. codex/static_root/assets/task-tab-B3WOGyYT.508fce185a28.js.gz +0 -0
  514. codex/static_root/assets/task-tab-B3WOGyYT.js +0 -1
  515. codex/static_root/assets/task-tab-B3WOGyYT.js.br +0 -0
  516. codex/static_root/assets/task-tab-B3WOGyYT.js.gz +0 -0
  517. codex/static_root/assets/unauthorized-DXoWE_v2.19c70a140f9b.js.br +0 -0
  518. codex/static_root/assets/unauthorized-DXoWE_v2.19c70a140f9b.js.gz +0 -0
  519. codex/static_root/assets/unauthorized-DXoWE_v2.js.br +0 -0
  520. codex/static_root/assets/unauthorized-DXoWE_v2.js.gz +0 -0
  521. codex/static_root/assets/user-tab-DhsQxTkZ.8a8167f9f2c4.js +0 -1
  522. codex/static_root/assets/user-tab-DhsQxTkZ.8a8167f9f2c4.js.br +0 -0
  523. codex/static_root/assets/user-tab-DhsQxTkZ.8a8167f9f2c4.js.gz +0 -0
  524. codex/static_root/assets/user-tab-DhsQxTkZ.js +0 -1
  525. codex/static_root/assets/user-tab-DhsQxTkZ.js.br +0 -0
  526. codex/static_root/assets/user-tab-DhsQxTkZ.js.gz +0 -0
  527. codex/static_root/manifest.db295ed1ca03.json.br +0 -0
  528. codex/static_root/manifest.db295ed1ca03.json.gz +0 -0
  529. {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/WHEEL +0 -0
  530. {codex-1.8.6.dist-info → codex-1.8.9.dist-info}/entry_points.txt +0 -0
  531. {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.filter(comic__isnull=True)
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 comicbox.enums.maps.identifiers import ID_SOURCE_NAME_MAP
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, _COMICFTS_UPDATE_FIELDS)
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 = comics_filtered_qs.count()
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
- while True:
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 {search_index_batch_size} comics for search indexing..."
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
- self._create_comicfts_entry(comic, obj_list, create=create)
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
- since = fts_watermark or "the fracturing of the multiverse"
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
- fts_watermark = fts_watermark or _MIN_UTC_DATE
339
- out_of_date_comics = Comic.objects.filter(
340
- pk__in=ComicFTS.objects.values("comic_id"), updated_at__gt=fts_watermark
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.exclude(
348
- pk__in=ComicFTS.objects.values("comic_id")
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):