codex 1.6.19__py3-none-any.whl → 1.7.0a0__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 (576) hide show
  1. codex/db.py +119 -0
  2. codex/librarian/covers/purge.py +1 -1
  3. codex/librarian/importer/const.py +7 -1
  4. codex/librarian/importer/create_fks.py +5 -2
  5. codex/librarian/importer/importerd.py +17 -12
  6. codex/librarian/importer/moved.py +44 -13
  7. codex/librarian/importer/tasks.py +2 -0
  8. codex/librarian/janitor/cleanup.py +21 -8
  9. codex/librarian/janitor/integrity.py +369 -0
  10. codex/librarian/janitor/janitor.py +50 -15
  11. codex/librarian/janitor/latest_version.py +2 -1
  12. codex/librarian/janitor/status.py +5 -0
  13. codex/librarian/janitor/tasks.py +37 -0
  14. codex/librarian/janitor/update.py +1 -1
  15. codex/librarian/librariand.py +32 -24
  16. codex/librarian/search/optimize.py +43 -0
  17. codex/librarian/search/remove.py +18 -46
  18. codex/librarian/search/searchd.py +14 -17
  19. codex/librarian/search/status.py +2 -1
  20. codex/librarian/search/tasks.py +4 -9
  21. codex/librarian/search/update.py +223 -301
  22. codex/librarian/watchdog/observers.py +2 -1
  23. codex/migrations/0029_comicfts.py +58 -0
  24. codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +198 -0
  25. codex/models/admin.py +15 -6
  26. codex/models/base.py +9 -0
  27. codex/models/bookmark.py +3 -3
  28. codex/models/comic.py +68 -16
  29. codex/models/functions.py +59 -2
  30. codex/models/groups.py +6 -1
  31. codex/models/library.py +1 -3
  32. codex/models/paths.py +9 -3
  33. codex/serializers/browser/page.py +2 -0
  34. codex/settings/settings.py +16 -17
  35. codex/signals/django_signals.py +4 -10
  36. codex/startup.py +1 -35
  37. codex/static_root/assets/{VCheckbox-Cko1sQK-.170726b3b8a1.js → VCheckbox-CWiqcZ7a.3feebd47daab.js} +1 -1
  38. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.br +0 -0
  39. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.gz +0 -0
  40. codex/static_root/assets/{VCheckbox-Cko1sQK-.js → VCheckbox-CWiqcZ7a.js} +1 -1
  41. codex/static_root/assets/VCheckbox-CWiqcZ7a.js.br +0 -0
  42. codex/static_root/assets/VCheckbox-CWiqcZ7a.js.gz +0 -0
  43. codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js → VCheckboxBtn-Dk3iB62X.bc66d5351baa.js} +1 -1
  44. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.br +0 -0
  45. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.gz +0 -0
  46. codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.js → VCheckboxBtn-Dk3iB62X.js} +1 -1
  47. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.br +0 -0
  48. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.gz +0 -0
  49. codex/static_root/assets/{VCombobox-Bjp6lffE.js → VCombobox-D1_KU27A.b1d30587a973.js} +1 -1
  50. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.br +0 -0
  51. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.gz +0 -0
  52. codex/static_root/assets/{VCombobox-Bjp6lffE.caf59fba486e.js → VCombobox-D1_KU27A.js} +1 -1
  53. codex/static_root/assets/VCombobox-D1_KU27A.js.br +0 -0
  54. codex/static_root/assets/VCombobox-D1_KU27A.js.gz +0 -0
  55. codex/static_root/assets/{VDialog-BkpVGB70.6eea3ca762a5.js → VDialog-GbxvFMMN.594fb3a39ced.js} +1 -1
  56. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.br +0 -0
  57. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.gz +0 -0
  58. codex/static_root/assets/{VDialog-BkpVGB70.js → VDialog-GbxvFMMN.js} +1 -1
  59. codex/static_root/assets/VDialog-GbxvFMMN.js.br +0 -0
  60. codex/static_root/assets/VDialog-GbxvFMMN.js.gz +0 -0
  61. codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js → VExpansionPanels-Cywv_bEj.a762e63858e5.js} +1 -1
  62. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.br +0 -0
  63. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.gz +0 -0
  64. codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.js → VExpansionPanels-Cywv_bEj.js} +1 -1
  65. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.br +0 -0
  66. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.gz +0 -0
  67. codex/static_root/assets/{VRadioGroup-D3Py5BfQ.a4f25edb86d3.js → VRadioGroup-C0etmxpZ.0eb3009a6300.js} +1 -1
  68. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.br +0 -0
  69. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.gz +0 -0
  70. codex/static_root/assets/{VRadioGroup-D3Py5BfQ.js → VRadioGroup-C0etmxpZ.js} +1 -1
  71. codex/static_root/assets/VRadioGroup-C0etmxpZ.js.br +0 -0
  72. codex/static_root/assets/VRadioGroup-C0etmxpZ.js.gz +0 -0
  73. codex/static_root/assets/{VSelect-DtvZsYZz.e98745d858eb.js → VSelect-txrnRiNJ.a5625760018c.js} +1 -1
  74. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.br +0 -0
  75. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.gz +0 -0
  76. codex/static_root/assets/{VSelect-DtvZsYZz.js → VSelect-txrnRiNJ.js} +1 -1
  77. codex/static_root/assets/VSelect-txrnRiNJ.js.br +0 -0
  78. codex/static_root/assets/VSelect-txrnRiNJ.js.gz +0 -0
  79. codex/static_root/assets/{VSelectionControl-D6kmykQW.1e1fda62ceba.js → VSelectionControl-CY6RmcvW.7e8824224f6c.js} +1 -1
  80. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.br +0 -0
  81. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.gz +0 -0
  82. codex/static_root/assets/{VSelectionControl-D6kmykQW.js → VSelectionControl-CY6RmcvW.js} +1 -1
  83. codex/static_root/assets/VSelectionControl-CY6RmcvW.js.br +0 -0
  84. codex/static_root/assets/VSelectionControl-CY6RmcvW.js.gz +0 -0
  85. codex/static_root/assets/{VSlideGroup-UGZWxpF5.360bdefcd215.js → VSlideGroup-DtEQMwY1.6c72325635ed.js} +1 -1
  86. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.br +0 -0
  87. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.gz +0 -0
  88. codex/static_root/assets/{VSlideGroup-UGZWxpF5.js → VSlideGroup-DtEQMwY1.js} +1 -1
  89. codex/static_root/assets/VSlideGroup-DtEQMwY1.js.br +0 -0
  90. codex/static_root/assets/VSlideGroup-DtEQMwY1.js.gz +0 -0
  91. codex/static_root/assets/{VTable-DXH_yQ0o.1149f56ad566.js → VTable-2af995xo.6c5bf36631b2.js} +1 -1
  92. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.br +0 -0
  93. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.gz +0 -0
  94. codex/static_root/assets/{VTable-DXH_yQ0o.js → VTable-2af995xo.js} +1 -1
  95. codex/static_root/assets/VTable-2af995xo.js.br +0 -0
  96. codex/static_root/assets/VTable-2af995xo.js.gz +0 -0
  97. codex/static_root/assets/{VTextField-Blqy56_L.26d8761e0155.js → VTextField-G6CMj7yO.657a130dd302.js} +1 -1
  98. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.br +0 -0
  99. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.gz +0 -0
  100. codex/static_root/assets/{VTextField-Blqy56_L.js → VTextField-G6CMj7yO.js} +1 -1
  101. codex/static_root/assets/VTextField-G6CMj7yO.js.br +0 -0
  102. codex/static_root/assets/VTextField-G6CMj7yO.js.gz +0 -0
  103. codex/static_root/assets/{VWindowItem-C8_ovV7e.afbc08414765.js → VWindowItem-CzoGd6Ul.721e5da11ad4.js} +1 -1
  104. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.br +0 -0
  105. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.gz +0 -0
  106. codex/static_root/assets/{VWindowItem-C8_ovV7e.js → VWindowItem-CzoGd6Ul.js} +1 -1
  107. codex/static_root/assets/VWindowItem-CzoGd6Ul.js.br +0 -0
  108. codex/static_root/assets/VWindowItem-CzoGd6Ul.js.gz +0 -0
  109. codex/static_root/assets/{admin-DTRn8bBs.d031ea42d8d0.js → admin-CsPAIQeG.5175467148a3.js} +1 -1
  110. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.br +0 -0
  111. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.gz +0 -0
  112. codex/static_root/assets/{admin-DTRn8bBs.js → admin-CsPAIQeG.js} +1 -1
  113. codex/static_root/assets/admin-CsPAIQeG.js.br +0 -0
  114. codex/static_root/assets/admin-CsPAIQeG.js.gz +0 -0
  115. codex/static_root/assets/{admin-drawer-panel-BaXFOcru.b1a44b05f482.js → admin-drawer-panel-BAT7GA64.73a064628c19.js} +11 -11
  116. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.br +0 -0
  117. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.gz +0 -0
  118. codex/static_root/assets/{admin-drawer-panel-BaXFOcru.js → admin-drawer-panel-BAT7GA64.js} +11 -11
  119. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.br +0 -0
  120. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.gz +0 -0
  121. codex/static_root/assets/browser-C18l93yc.923860009ef4.js +1 -0
  122. codex/static_root/assets/browser-C18l93yc.923860009ef4.js.br +0 -0
  123. codex/static_root/assets/browser-C18l93yc.923860009ef4.js.gz +0 -0
  124. codex/static_root/assets/browser-C18l93yc.js +1 -0
  125. codex/static_root/assets/browser-C18l93yc.js.br +0 -0
  126. codex/static_root/assets/browser-C18l93yc.js.gz +0 -0
  127. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css +1 -0
  128. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.br +0 -0
  129. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.gz +0 -0
  130. codex/static_root/assets/browser-DaAqlBYO.css +1 -0
  131. codex/static_root/assets/browser-DaAqlBYO.css.br +0 -0
  132. codex/static_root/assets/browser-DaAqlBYO.css.gz +0 -0
  133. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js +1 -0
  134. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.br +0 -0
  135. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.gz +0 -0
  136. codex/static_root/assets/change-password-dialog-BazJdNs9.js +1 -0
  137. codex/static_root/assets/change-password-dialog-BazJdNs9.js.br +0 -0
  138. codex/static_root/assets/change-password-dialog-BazJdNs9.js.gz +0 -0
  139. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css +1 -0
  140. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.br +0 -0
  141. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.gz +0 -0
  142. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css +1 -0
  143. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.br +0 -0
  144. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.gz +0 -0
  145. codex/static_root/assets/{confirm-dialog-D3_s2DQy.9dde66036c72.js → confirm-dialog-CbW3hlOp.9dd39b443c31.js} +1 -1
  146. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.br +0 -0
  147. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.gz +0 -0
  148. codex/static_root/assets/{confirm-dialog-D3_s2DQy.js → confirm-dialog-CbW3hlOp.js} +1 -1
  149. codex/static_root/assets/confirm-dialog-CbW3hlOp.js.br +0 -0
  150. codex/static_root/assets/confirm-dialog-CbW3hlOp.js.gz +0 -0
  151. codex/static_root/assets/{datetime-column-BxC1Li9e.3dc314b3295d.js → datetime-column-uSqKZU37.b157b0743db5.js} +1 -1
  152. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.br +0 -0
  153. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.gz +0 -0
  154. codex/static_root/assets/{datetime-column-BxC1Li9e.js → datetime-column-uSqKZU37.js} +1 -1
  155. codex/static_root/assets/datetime-column-uSqKZU37.js.br +0 -0
  156. codex/static_root/assets/datetime-column-uSqKZU37.js.gz +0 -0
  157. codex/static_root/assets/{filter-Bzk7x841.d0c3b602e62d.js → filter-CBl5KEsB.845a81573c2e.js} +1 -1
  158. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.br +0 -0
  159. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.gz +0 -0
  160. codex/static_root/assets/{filter-Bzk7x841.js → filter-CBl5KEsB.js} +1 -1
  161. codex/static_root/assets/filter-CBl5KEsB.js.br +0 -0
  162. codex/static_root/assets/filter-CBl5KEsB.js.gz +0 -0
  163. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js +1 -0
  164. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.br +0 -0
  165. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.gz +0 -0
  166. codex/static_root/assets/flag-tab-CT8pqM4r.js +1 -0
  167. codex/static_root/assets/flag-tab-CT8pqM4r.js.br +0 -0
  168. codex/static_root/assets/flag-tab-CT8pqM4r.js.gz +0 -0
  169. codex/static_root/assets/{group-tab-5esH4lwX.a81fdd258117.js → group-tab-BOW9kigw.7e5007596367.js} +1 -1
  170. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.br +0 -0
  171. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.gz +0 -0
  172. codex/static_root/assets/{group-tab-5esH4lwX.js → group-tab-BOW9kigw.js} +1 -1
  173. codex/static_root/assets/group-tab-BOW9kigw.js.br +0 -0
  174. codex/static_root/assets/group-tab-BOW9kigw.js.gz +0 -0
  175. codex/static_root/assets/{http-error-BTOBfcGY.52ced6f28420.js → http-error-C6yuLApo.45cf99eb7a70.js} +1 -1
  176. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +4 -0
  177. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
  178. codex/static_root/assets/{http-error-BTOBfcGY.js → http-error-C6yuLApo.js} +1 -1
  179. codex/static_root/assets/http-error-C6yuLApo.js.br +4 -0
  180. codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
  181. codex/static_root/assets/{library-tab-DiPEdOF7.ece2d642a9dc.js → library-tab-D3WOtpE0.9af34e6e7235.js} +1 -1
  182. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.br +0 -0
  183. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.gz +0 -0
  184. codex/static_root/assets/{library-tab-DiPEdOF7.js → library-tab-D3WOtpE0.js} +1 -1
  185. codex/static_root/assets/library-tab-D3WOtpE0.js.br +0 -0
  186. codex/static_root/assets/library-tab-D3WOtpE0.js.gz +0 -0
  187. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js +6 -0
  188. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.br +0 -0
  189. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.gz +0 -0
  190. codex/static_root/assets/main-CmjU2oTp.js +6 -0
  191. codex/static_root/assets/main-CmjU2oTp.js.br +0 -0
  192. codex/static_root/assets/main-CmjU2oTp.js.gz +0 -0
  193. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js +1 -0
  194. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.br +0 -0
  195. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.gz +0 -0
  196. codex/static_root/assets/pager-full-pdf-BH54_5f6.js +1 -0
  197. codex/static_root/assets/pager-full-pdf-BH54_5f6.js.br +0 -0
  198. codex/static_root/assets/pager-full-pdf-BH54_5f6.js.gz +0 -0
  199. codex/static_root/assets/{pagination-toolbar-C45-onwe.4722195bb60b.css → pagination-toolbar-DGrxp2OD.css} +1 -1
  200. codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.br +0 -0
  201. codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.gz +0 -0
  202. codex/static_root/assets/{pagination-toolbar-C45-onwe.css → pagination-toolbar-DGrxp2OD.f960a624f04f.css} +1 -1
  203. codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.br +0 -0
  204. codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.gz +0 -0
  205. codex/static_root/assets/{pagination-toolbar-CkIzuyx1.00c354a82e6e.js → pagination-toolbar-JH3Kbffr.451171f7ac33.js} +1 -1
  206. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.br +0 -0
  207. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.gz +0 -0
  208. codex/static_root/assets/{pagination-toolbar-CkIzuyx1.js → pagination-toolbar-JH3Kbffr.js} +1 -1
  209. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.br +0 -0
  210. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.gz +0 -0
  211. codex/static_root/assets/{pdf-doc-BjlRPuf4.0f056fa321b6.js → pdf-doc-BcRNpoaW.882f6e967869.js} +1 -1
  212. codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.br +0 -0
  213. codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
  214. codex/static_root/assets/{pdf-doc-BjlRPuf4.js → pdf-doc-BcRNpoaW.js} +1 -1
  215. codex/static_root/assets/pdf-doc-BcRNpoaW.js.br +0 -0
  216. codex/static_root/assets/pdf-doc-BcRNpoaW.js.gz +0 -0
  217. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js +2 -0
  218. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.br +0 -0
  219. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.gz +0 -0
  220. codex/static_root/assets/reader-DIPRg2VB.js +2 -0
  221. codex/static_root/assets/reader-DIPRg2VB.js.br +0 -0
  222. codex/static_root/assets/reader-DIPRg2VB.js.gz +0 -0
  223. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css +1 -0
  224. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.br +0 -0
  225. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -0
  226. codex/static_root/assets/reader-DveZZr9x.css +1 -0
  227. codex/static_root/assets/reader-DveZZr9x.css.br +0 -0
  228. codex/static_root/assets/reader-DveZZr9x.css.gz +0 -0
  229. codex/static_root/assets/{relation-chips-CUSatZET.js → relation-chips-3tTlR5QH.71f18bc92adc.js} +1 -1
  230. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.br +0 -0
  231. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.gz +0 -0
  232. codex/static_root/assets/{relation-chips-CUSatZET.a58031ff8141.js → relation-chips-3tTlR5QH.js} +1 -1
  233. codex/static_root/assets/relation-chips-3tTlR5QH.js.br +0 -0
  234. codex/static_root/assets/relation-chips-3tTlR5QH.js.gz +0 -0
  235. codex/static_root/assets/{settings-drawer-BSHTAsJ9.js → settings-drawer-BdyFhA_r.151f4c3061ed.js} +2 -2
  236. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.br +0 -0
  237. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.gz +0 -0
  238. codex/static_root/assets/{settings-drawer-BSHTAsJ9.1d07ce084fc9.js → settings-drawer-BdyFhA_r.js} +2 -2
  239. codex/static_root/assets/settings-drawer-BdyFhA_r.js.br +0 -0
  240. codex/static_root/assets/settings-drawer-BdyFhA_r.js.gz +0 -0
  241. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js +1 -0
  242. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.br +0 -0
  243. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.gz +0 -0
  244. codex/static_root/assets/stats-tab-DeKjwvmh.js +1 -0
  245. codex/static_root/assets/stats-tab-DeKjwvmh.js.br +0 -0
  246. codex/static_root/assets/stats-tab-DeKjwvmh.js.gz +0 -0
  247. codex/static_root/assets/{stats-tab-BXGisn5N.9b7a71e7fe28.css → stats-tab-DpfGsVX3.1cb66f2649a9.css} +1 -1
  248. codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.br +0 -0
  249. codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
  250. codex/static_root/assets/{stats-tab-BXGisn5N.css → stats-tab-DpfGsVX3.css} +1 -1
  251. codex/static_root/assets/stats-tab-DpfGsVX3.css.br +0 -0
  252. codex/static_root/assets/stats-tab-DpfGsVX3.css.gz +0 -0
  253. codex/static_root/assets/{task-tab-NORFAa9p.97e07e366ddd.js → task-tab-C8D0qRP_.d4ab174c69a3.js} +1 -1
  254. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.br +0 -0
  255. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.gz +0 -0
  256. codex/static_root/assets/{task-tab-NORFAa9p.js → task-tab-C8D0qRP_.js} +1 -1
  257. codex/static_root/assets/task-tab-C8D0qRP_.js.br +0 -0
  258. codex/static_root/assets/task-tab-C8D0qRP_.js.gz +0 -0
  259. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js +1 -0
  260. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.br +0 -0
  261. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.gz +0 -0
  262. codex/static_root/assets/unauthorized-BHgBRF4z.js +1 -0
  263. codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
  264. codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
  265. codex/static_root/assets/{unauthorized-muDsv-rC.css → unauthorized-BjhIc97q.728511c92cd0.css} +1 -1
  266. codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.br +0 -0
  267. codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.gz +0 -0
  268. codex/static_root/assets/{unauthorized-muDsv-rC.2166ec8b3a32.css → unauthorized-BjhIc97q.css} +1 -1
  269. codex/static_root/assets/unauthorized-BjhIc97q.css.br +0 -0
  270. codex/static_root/assets/unauthorized-BjhIc97q.css.gz +0 -0
  271. codex/static_root/assets/{user-tab-CHbyN1EW.e7206db08680.js → user-tab-BU31Z92y.35613c713faf.js} +1 -1
  272. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.br +0 -0
  273. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.gz +0 -0
  274. codex/static_root/assets/{user-tab-CHbyN1EW.js → user-tab-BU31Z92y.js} +1 -1
  275. codex/static_root/assets/user-tab-BU31Z92y.js.br +0 -0
  276. codex/static_root/assets/user-tab-BU31Z92y.js.gz +0 -0
  277. codex/static_root/js/choices-admin.8aa64c911203.json +1 -0
  278. codex/static_root/js/choices-admin.8aa64c911203.json.br +0 -0
  279. codex/static_root/js/choices-admin.8aa64c911203.json.gz +0 -0
  280. codex/static_root/js/choices-admin.json +1 -1
  281. codex/static_root/js/choices-admin.json.br +0 -0
  282. codex/static_root/js/choices-admin.json.gz +0 -0
  283. codex/static_root/manifest.45cb46e19686.json +635 -0
  284. codex/static_root/manifest.45cb46e19686.json.br +0 -0
  285. codex/static_root/manifest.45cb46e19686.json.gz +0 -0
  286. codex/static_root/manifest.json +248 -248
  287. codex/static_root/manifest.json.br +0 -0
  288. codex/static_root/manifest.json.gz +0 -0
  289. codex/static_root/staticfiles.json +1 -1
  290. codex/status_controller.py +10 -4
  291. codex/views/admin/tasks.py +46 -40
  292. codex/views/auth.py +15 -4
  293. codex/views/browser/annotations.py +94 -51
  294. codex/views/browser/base.py +1 -2
  295. codex/views/browser/browser.py +52 -21
  296. codex/views/browser/cover.py +8 -4
  297. codex/views/browser/filters/annotations.py +28 -6
  298. codex/views/browser/filters/search/__init__.py +1 -0
  299. codex/views/browser/filters/search/aliases.py +81 -0
  300. codex/views/browser/filters/search/field/__init__.py +1 -0
  301. codex/views/browser/filters/search/field/column.py +53 -0
  302. codex/views/browser/filters/search/field/expression.py +177 -0
  303. codex/views/browser/filters/search/field/filter.py +70 -0
  304. codex/views/browser/filters/search/field/optimize.py +71 -0
  305. codex/views/browser/filters/search/field/parse.py +187 -0
  306. codex/views/browser/filters/search/fts.py +38 -0
  307. codex/views/browser/filters/search/parse.py +220 -0
  308. codex/views/browser/order_by.py +8 -2
  309. codex/views/browser/page_in_bounds.py +88 -0
  310. codex/views/browser/paginate.py +28 -113
  311. codex/views/const.py +0 -1
  312. codex/views/opds/v1/feed.py +2 -1
  313. codex/views/opds/v2/feed.py +2 -1
  314. codex/views/opds/v2/links.py +1 -1
  315. codex/views/reader/books.py +1 -1
  316. codex/views/reader/page.py +3 -3
  317. {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/METADATA +18 -10
  318. {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/RECORD +321 -372
  319. codex/_vendor/__init__.py +0 -0
  320. codex/_vendor/django_haystack-3.2.1.dist-info/AUTHORS +0 -127
  321. codex/_vendor/django_haystack-3.2.1.dist-info/INSTALLER +0 -1
  322. codex/_vendor/django_haystack-3.2.1.dist-info/LICENSE +0 -31
  323. codex/_vendor/django_haystack-3.2.1.dist-info/METADATA +0 -98
  324. codex/_vendor/django_haystack-3.2.1.dist-info/RECORD +0 -97
  325. codex/_vendor/django_haystack-3.2.1.dist-info/REQUESTED +0 -0
  326. codex/_vendor/django_haystack-3.2.1.dist-info/WHEEL +0 -5
  327. codex/_vendor/django_haystack-3.2.1.dist-info/top_level.txt +0 -1
  328. codex/_vendor/haystack/__init__.py +0 -80
  329. codex/_vendor/haystack/admin.py +0 -163
  330. codex/_vendor/haystack/apps.py +0 -31
  331. codex/_vendor/haystack/backends/__init__.py +0 -1109
  332. codex/_vendor/haystack/backends/elasticsearch2_backend.py +0 -390
  333. codex/_vendor/haystack/backends/elasticsearch5_backend.py +0 -483
  334. codex/_vendor/haystack/backends/elasticsearch7_backend.py +0 -586
  335. codex/_vendor/haystack/backends/elasticsearch_backend.py +0 -1141
  336. codex/_vendor/haystack/backends/simple_backend.py +0 -132
  337. codex/_vendor/haystack/backends/solr_backend.py +0 -991
  338. codex/_vendor/haystack/backends/whoosh_backend.py +0 -1104
  339. codex/_vendor/haystack/constants.py +0 -57
  340. codex/_vendor/haystack/exceptions.py +0 -57
  341. codex/_vendor/haystack/fields.py +0 -562
  342. codex/_vendor/haystack/forms.py +0 -135
  343. codex/_vendor/haystack/generic_views.py +0 -141
  344. codex/_vendor/haystack/indexes.py +0 -548
  345. codex/_vendor/haystack/inputs.py +0 -169
  346. codex/_vendor/haystack/management/__init__.py +0 -0
  347. codex/_vendor/haystack/management/commands/__init__.py +0 -0
  348. codex/_vendor/haystack/management/commands/build_solr_schema.py +0 -187
  349. codex/_vendor/haystack/management/commands/clear_index.py +0 -67
  350. codex/_vendor/haystack/management/commands/haystack_info.py +0 -22
  351. codex/_vendor/haystack/management/commands/rebuild_index.py +0 -65
  352. codex/_vendor/haystack/management/commands/update_index.py +0 -438
  353. codex/_vendor/haystack/manager.py +0 -105
  354. codex/_vendor/haystack/models.py +0 -257
  355. codex/_vendor/haystack/panels.py +0 -89
  356. codex/_vendor/haystack/query.py +0 -771
  357. codex/_vendor/haystack/routers.py +0 -14
  358. codex/_vendor/haystack/signals.py +0 -88
  359. codex/_vendor/haystack/templates/panels/haystack.html +0 -33
  360. codex/_vendor/haystack/templates/search_configuration/schema.xml +0 -1056
  361. codex/_vendor/haystack/templates/search_configuration/solrconfig.xml +0 -1446
  362. codex/_vendor/haystack/templatetags/__init__.py +0 -0
  363. codex/_vendor/haystack/templatetags/highlight.py +0 -131
  364. codex/_vendor/haystack/templatetags/more_like_this.py +0 -119
  365. codex/_vendor/haystack/urls.py +0 -5
  366. codex/_vendor/haystack/utils/__init__.py +0 -84
  367. codex/_vendor/haystack/utils/app_loading.py +0 -34
  368. codex/_vendor/haystack/utils/geo.py +0 -71
  369. codex/_vendor/haystack/utils/highlighting.py +0 -165
  370. codex/_vendor/haystack/utils/loading.py +0 -374
  371. codex/_vendor/haystack/utils/log.py +0 -21
  372. codex/_vendor/haystack/version.py +0 -16
  373. codex/_vendor/haystack/views.py +0 -253
  374. codex/integrity.py +0 -492
  375. codex/librarian/search/merge.py +0 -86
  376. codex/librarian/search/version.py +0 -63
  377. codex/search/__init__.py +0 -1
  378. codex/search/backend.py +0 -524
  379. codex/search/backend_search.py +0 -229
  380. codex/search/engine.py +0 -33
  381. codex/search/indexes.py +0 -79
  382. codex/search/query.py +0 -67
  383. codex/search/writing.py +0 -180
  384. codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.br +0 -0
  385. codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.gz +0 -0
  386. codex/static_root/assets/VCheckbox-Cko1sQK-.js.br +0 -0
  387. codex/static_root/assets/VCheckbox-Cko1sQK-.js.gz +0 -0
  388. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.br +0 -0
  389. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.gz +0 -0
  390. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.br +0 -0
  391. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.gz +0 -0
  392. codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.br +0 -0
  393. codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.gz +0 -0
  394. codex/static_root/assets/VCombobox-Bjp6lffE.js.br +0 -0
  395. codex/static_root/assets/VCombobox-Bjp6lffE.js.gz +0 -0
  396. codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.br +0 -0
  397. codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.gz +0 -0
  398. codex/static_root/assets/VDialog-BkpVGB70.js.br +0 -0
  399. codex/static_root/assets/VDialog-BkpVGB70.js.gz +0 -0
  400. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.br +0 -0
  401. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.gz +0 -0
  402. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.br +0 -0
  403. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.gz +0 -0
  404. codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.br +0 -0
  405. codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.gz +0 -0
  406. codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.br +0 -0
  407. codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.gz +0 -0
  408. codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.br +0 -0
  409. codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.gz +0 -0
  410. codex/static_root/assets/VSelect-DtvZsYZz.js.br +0 -0
  411. codex/static_root/assets/VSelect-DtvZsYZz.js.gz +0 -0
  412. codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.br +0 -0
  413. codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.gz +0 -0
  414. codex/static_root/assets/VSelectionControl-D6kmykQW.js.br +0 -0
  415. codex/static_root/assets/VSelectionControl-D6kmykQW.js.gz +0 -0
  416. codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.br +0 -0
  417. codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.gz +0 -0
  418. codex/static_root/assets/VSlideGroup-UGZWxpF5.js.br +0 -0
  419. codex/static_root/assets/VSlideGroup-UGZWxpF5.js.gz +0 -0
  420. codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.br +0 -0
  421. codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.gz +0 -0
  422. codex/static_root/assets/VTable-DXH_yQ0o.js.br +0 -0
  423. codex/static_root/assets/VTable-DXH_yQ0o.js.gz +0 -0
  424. codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.br +0 -0
  425. codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.gz +0 -0
  426. codex/static_root/assets/VTextField-Blqy56_L.js.br +0 -0
  427. codex/static_root/assets/VTextField-Blqy56_L.js.gz +0 -0
  428. codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.br +0 -0
  429. codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.gz +0 -0
  430. codex/static_root/assets/VWindowItem-C8_ovV7e.js.br +0 -0
  431. codex/static_root/assets/VWindowItem-C8_ovV7e.js.gz +0 -0
  432. codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.br +0 -0
  433. codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.gz +0 -0
  434. codex/static_root/assets/admin-DTRn8bBs.js.br +0 -0
  435. codex/static_root/assets/admin-DTRn8bBs.js.gz +0 -0
  436. codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.br +0 -0
  437. codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.gz +0 -0
  438. codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.br +0 -0
  439. codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.gz +0 -0
  440. codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js +0 -1
  441. codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.br +0 -0
  442. codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.gz +0 -0
  443. codex/static_root/assets/browser-BBpUyGmz.js +0 -1
  444. codex/static_root/assets/browser-BBpUyGmz.js.br +0 -0
  445. codex/static_root/assets/browser-BBpUyGmz.js.gz +0 -0
  446. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css +0 -1
  447. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.br +0 -0
  448. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.gz +0 -0
  449. codex/static_root/assets/browser-Binc3n9H.css +0 -1
  450. codex/static_root/assets/browser-Binc3n9H.css.br +0 -0
  451. codex/static_root/assets/browser-Binc3n9H.css.gz +0 -0
  452. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css +0 -1
  453. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.br +0 -0
  454. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.gz +0 -0
  455. codex/static_root/assets/change-password-dialog-Cebek1-c.css +0 -1
  456. codex/static_root/assets/change-password-dialog-Cebek1-c.css.br +0 -0
  457. codex/static_root/assets/change-password-dialog-Cebek1-c.css.gz +0 -0
  458. codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js +0 -1
  459. codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.br +0 -0
  460. codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.gz +0 -0
  461. codex/static_root/assets/change-password-dialog-DlHoLhDF.js +0 -1
  462. codex/static_root/assets/change-password-dialog-DlHoLhDF.js.br +0 -0
  463. codex/static_root/assets/change-password-dialog-DlHoLhDF.js.gz +0 -0
  464. codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.br +0 -0
  465. codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.gz +0 -0
  466. codex/static_root/assets/confirm-dialog-D3_s2DQy.js.br +0 -0
  467. codex/static_root/assets/confirm-dialog-D3_s2DQy.js.gz +0 -0
  468. codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.br +0 -0
  469. codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.gz +0 -0
  470. codex/static_root/assets/datetime-column-BxC1Li9e.js.br +0 -0
  471. codex/static_root/assets/datetime-column-BxC1Li9e.js.gz +0 -0
  472. codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.br +0 -0
  473. codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.gz +0 -0
  474. codex/static_root/assets/filter-Bzk7x841.js.br +0 -0
  475. codex/static_root/assets/filter-Bzk7x841.js.gz +0 -0
  476. codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js +0 -1
  477. codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.br +0 -0
  478. codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.gz +0 -0
  479. codex/static_root/assets/flag-tab-C2bI5zq3.js +0 -1
  480. codex/static_root/assets/flag-tab-C2bI5zq3.js.br +0 -0
  481. codex/static_root/assets/flag-tab-C2bI5zq3.js.gz +0 -0
  482. codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.br +0 -0
  483. codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.gz +0 -0
  484. codex/static_root/assets/group-tab-5esH4lwX.js.br +0 -0
  485. codex/static_root/assets/group-tab-5esH4lwX.js.gz +0 -0
  486. codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.br +0 -0
  487. codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.gz +0 -0
  488. codex/static_root/assets/http-error-BTOBfcGY.js.br +0 -0
  489. codex/static_root/assets/http-error-BTOBfcGY.js.gz +0 -0
  490. codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.br +0 -0
  491. codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.gz +0 -0
  492. codex/static_root/assets/library-tab-DiPEdOF7.js.br +0 -0
  493. codex/static_root/assets/library-tab-DiPEdOF7.js.gz +0 -0
  494. codex/static_root/assets/main-BPIPjzsc.66def8991e38.js +0 -6
  495. codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.br +0 -0
  496. codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.gz +0 -0
  497. codex/static_root/assets/main-BPIPjzsc.js +0 -6
  498. codex/static_root/assets/main-BPIPjzsc.js.br +0 -0
  499. codex/static_root/assets/main-BPIPjzsc.js.gz +0 -0
  500. codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js +0 -1
  501. codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.br +0 -0
  502. codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.gz +0 -0
  503. codex/static_root/assets/pager-full-pdf-CiyC6Al9.js +0 -1
  504. codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.br +0 -0
  505. codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.gz +0 -0
  506. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.br +0 -0
  507. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.gz +0 -0
  508. codex/static_root/assets/pagination-toolbar-C45-onwe.css.br +0 -0
  509. codex/static_root/assets/pagination-toolbar-C45-onwe.css.gz +0 -0
  510. codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.br +0 -0
  511. codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.gz +0 -0
  512. codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.br +0 -0
  513. codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.gz +0 -0
  514. codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.br +0 -0
  515. codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.gz +0 -0
  516. codex/static_root/assets/pdf-doc-BjlRPuf4.js.br +0 -0
  517. codex/static_root/assets/pdf-doc-BjlRPuf4.js.gz +0 -0
  518. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css +0 -1
  519. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.br +0 -0
  520. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.gz +0 -0
  521. codex/static_root/assets/reader-_stAWL-4.css +0 -1
  522. codex/static_root/assets/reader-_stAWL-4.css.br +0 -0
  523. codex/static_root/assets/reader-_stAWL-4.css.gz +0 -0
  524. codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js +0 -2
  525. codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.br +0 -0
  526. codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.gz +0 -0
  527. codex/static_root/assets/reader-ucxnLi2K.js +0 -2
  528. codex/static_root/assets/reader-ucxnLi2K.js.br +0 -0
  529. codex/static_root/assets/reader-ucxnLi2K.js.gz +0 -0
  530. codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.br +0 -0
  531. codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.gz +0 -0
  532. codex/static_root/assets/relation-chips-CUSatZET.js.br +0 -0
  533. codex/static_root/assets/relation-chips-CUSatZET.js.gz +0 -0
  534. codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.br +0 -0
  535. codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.gz +0 -0
  536. codex/static_root/assets/settings-drawer-BSHTAsJ9.js.br +0 -0
  537. codex/static_root/assets/settings-drawer-BSHTAsJ9.js.gz +0 -0
  538. codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.br +0 -0
  539. codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.gz +0 -0
  540. codex/static_root/assets/stats-tab-BXGisn5N.css.br +0 -0
  541. codex/static_root/assets/stats-tab-BXGisn5N.css.gz +0 -0
  542. codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js +0 -1
  543. codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.br +0 -0
  544. codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.gz +0 -0
  545. codex/static_root/assets/stats-tab-DKXVB2Cc.js +0 -1
  546. codex/static_root/assets/stats-tab-DKXVB2Cc.js.br +0 -0
  547. codex/static_root/assets/stats-tab-DKXVB2Cc.js.gz +0 -0
  548. codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.br +0 -0
  549. codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.gz +0 -0
  550. codex/static_root/assets/task-tab-NORFAa9p.js.br +0 -0
  551. codex/static_root/assets/task-tab-NORFAa9p.js.gz +0 -0
  552. codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js +0 -1
  553. codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.br +0 -0
  554. codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.gz +0 -0
  555. codex/static_root/assets/unauthorized-RHaQZRfr.js +0 -1
  556. codex/static_root/assets/unauthorized-RHaQZRfr.js.br +0 -0
  557. codex/static_root/assets/unauthorized-RHaQZRfr.js.gz +0 -0
  558. codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.br +0 -0
  559. codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.gz +0 -0
  560. codex/static_root/assets/unauthorized-muDsv-rC.css.br +0 -0
  561. codex/static_root/assets/unauthorized-muDsv-rC.css.gz +0 -0
  562. codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.br +0 -0
  563. codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.gz +0 -0
  564. codex/static_root/assets/user-tab-CHbyN1EW.js.br +0 -0
  565. codex/static_root/assets/user-tab-CHbyN1EW.js.gz +0 -0
  566. codex/static_root/js/choices-admin.1a20a5648f20.json +0 -1
  567. codex/static_root/js/choices-admin.1a20a5648f20.json.br +0 -0
  568. codex/static_root/js/choices-admin.1a20a5648f20.json.gz +0 -0
  569. codex/static_root/manifest.0fe31a6cb99f.json +0 -635
  570. codex/static_root/manifest.0fe31a6cb99f.json.br +0 -0
  571. codex/static_root/manifest.0fe31a6cb99f.json.gz +0 -0
  572. codex/templates/search/indexes/codex/comic_text.txt +0 -47
  573. codex/views/browser/filters/search.py +0 -196
  574. {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/LICENSE +0 -0
  575. {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/WHEEL +0 -0
  576. {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,198 @@
1
+ """Generated by Django 5.1.1 on 2024-09-12 00:13."""
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ """Migrate db."""
8
+
9
+ dependencies = [
10
+ ("codex", "0029_comicfts"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="adminflag",
16
+ name="key",
17
+ field=models.CharField(
18
+ choices=[
19
+ ("FV", "Folder View"),
20
+ ("RG", "Registration"),
21
+ ("NU", "Non Users"),
22
+ ("AU", "Auto Update"),
23
+ ("IM", "Import Metadata"),
24
+ ("ST", "Send Telemetry"),
25
+ ],
26
+ db_index=True,
27
+ max_length=2,
28
+ ),
29
+ ),
30
+ migrations.AlterField(
31
+ model_name="comic",
32
+ name="day",
33
+ field=models.PositiveSmallIntegerField(db_index=True, null=True),
34
+ ),
35
+ migrations.AlterField(
36
+ model_name="comic",
37
+ name="file_type",
38
+ field=models.CharField(
39
+ blank=True,
40
+ choices=[
41
+ ("CBZ", "Cbz"),
42
+ ("CBR", "Cbr"),
43
+ ("CBT", "Cbt"),
44
+ ("PDF", "Pdf"),
45
+ ],
46
+ db_collation="nocase",
47
+ db_index=True,
48
+ default="",
49
+ max_length=3,
50
+ ),
51
+ ),
52
+ migrations.AlterField(
53
+ model_name="comic",
54
+ name="issue_suffix",
55
+ field=models.CharField(
56
+ db_collation="nocase", db_index=True, default="", max_length=16
57
+ ),
58
+ ),
59
+ migrations.AlterField(
60
+ model_name="comic",
61
+ name="month",
62
+ field=models.PositiveSmallIntegerField(db_index=True, null=True),
63
+ ),
64
+ migrations.AlterField(
65
+ model_name="comic",
66
+ name="notes",
67
+ field=models.TextField(db_collation="nocase", default=""),
68
+ ),
69
+ migrations.AlterField(
70
+ model_name="comic",
71
+ name="reading_direction",
72
+ field=models.CharField(
73
+ choices=[
74
+ ("ltr", "Ltr"),
75
+ ("rtl", "Rtl"),
76
+ ("ttb", "Ttb"),
77
+ ("btt", "Btt"),
78
+ ],
79
+ db_collation="nocase",
80
+ db_index=True,
81
+ default="ltr",
82
+ max_length=3,
83
+ ),
84
+ ),
85
+ migrations.AlterField(
86
+ model_name="comic",
87
+ name="review",
88
+ field=models.TextField(db_collation="nocase", default=""),
89
+ ),
90
+ migrations.AlterField(
91
+ model_name="comic",
92
+ name="sort_name",
93
+ field=models.CharField(
94
+ db_collation="nocase", db_index=True, default="", max_length=128
95
+ ),
96
+ ),
97
+ migrations.AlterField(
98
+ model_name="comic",
99
+ name="summary",
100
+ field=models.TextField(db_collation="nocase", default=""),
101
+ ),
102
+ migrations.AlterField(
103
+ model_name="customcover",
104
+ name="sort_name",
105
+ field=models.CharField(
106
+ db_collation="nocase", db_index=True, default="", max_length=128
107
+ ),
108
+ ),
109
+ migrations.AlterField(
110
+ model_name="folder",
111
+ name="sort_name",
112
+ field=models.CharField(
113
+ db_collation="nocase", db_index=True, default="", max_length=128
114
+ ),
115
+ ),
116
+ migrations.AlterField(
117
+ model_name="imprint",
118
+ name="sort_name",
119
+ field=models.CharField(
120
+ db_collation="nocase", db_index=True, default="", max_length=128
121
+ ),
122
+ ),
123
+ migrations.AlterField(
124
+ model_name="librarianstatus",
125
+ name="status_type",
126
+ field=models.CharField(
127
+ choices=[
128
+ ("CCC", "Create Covers"),
129
+ ("CCD", "Purge Covers"),
130
+ ("CFO", "Find Orphan"),
131
+ ("IDM", "Dirs Moved"),
132
+ ("IFM", "Files Moved"),
133
+ ("ITR", "Aggregate Tags"),
134
+ ("ITQ", "Query Missing Fks"),
135
+ ("ITC", "Create Fks"),
136
+ ("IDU", "Dirs Modified"),
137
+ ("IFU", "Files Modified"),
138
+ ("IFC", "Files Created"),
139
+ ("IMQ", "Query M2M Fields"),
140
+ ("IMC", "Link M2M Fields"),
141
+ ("IDD", "Dirs Deleted"),
142
+ ("IFD", "Files Deleted"),
143
+ ("IFI", "Failed Imports"),
144
+ ("ICQ", "Query Missing Covers"),
145
+ ("ICM", "Covers Moved"),
146
+ ("ICU", "Covers Modified"),
147
+ ("ICC", "Covers Created"),
148
+ ("ICD", "Covers Deleted"),
149
+ ("ICL", "Covers Link"),
150
+ ("IGU", "Group Update"),
151
+ ("IAF", "Adopt Folders"),
152
+ ("JTD", "Cleanup Fk"),
153
+ ("JLV", "Codex Latest Version"),
154
+ ("JCU", "Codex Update"),
155
+ ("JCR", "Codex Restart"),
156
+ ("JCS", "Codex Stop"),
157
+ ("JDO", "Db Optimize"),
158
+ ("JDB", "Db Backup"),
159
+ ("JSD", "Cleanup Sessions"),
160
+ ("JCD", "Cleanup Covers"),
161
+ ("JCB", "Cleanup Bookmarks"),
162
+ ("JIF", "Integrity Fk"),
163
+ ("JIC", "Integrity Check"),
164
+ ("JFC", "Fts Integrity Check"),
165
+ ("JFR", "Fts Rebuild"),
166
+ ("SIX", "Search Index Clear"),
167
+ ("SIU", "Search Index Update"),
168
+ ("SIC", "Search Index Create"),
169
+ ("SID", "Search Index Remove"),
170
+ ("SIO", "Search Index Optimize"),
171
+ ("WPO", "Poll"),
172
+ ],
173
+ db_index=True,
174
+ max_length=3,
175
+ ),
176
+ ),
177
+ migrations.AlterField(
178
+ model_name="publisher",
179
+ name="sort_name",
180
+ field=models.CharField(
181
+ db_collation="nocase", db_index=True, default="", max_length=128
182
+ ),
183
+ ),
184
+ migrations.AlterField(
185
+ model_name="series",
186
+ name="sort_name",
187
+ field=models.CharField(
188
+ db_collation="nocase", db_index=True, default="", max_length=128
189
+ ),
190
+ ),
191
+ migrations.AlterField(
192
+ model_name="storyarc",
193
+ name="sort_name",
194
+ field=models.CharField(
195
+ db_collation="nocase", db_index=True, default="", max_length=128
196
+ ),
197
+ ),
198
+ ]
codex/models/admin.py CHANGED
@@ -22,7 +22,7 @@ from codex.librarian.importer.status import ImportStatusTypes
22
22
  from codex.librarian.janitor.status import JanitorStatusTypes
23
23
  from codex.librarian.search.status import SearchIndexStatusTypes
24
24
  from codex.librarian.watchdog.status import WatchdogStatusTypes
25
- from codex.models.base import MAX_FIELD_LEN, MAX_NAME_LEN, BaseModel
25
+ from codex.models.base import MAX_FIELD_LEN, MAX_NAME_LEN, BaseModel, max_choices_len
26
26
 
27
27
  __all__ = ("AdminFlag", "LibrarianStatus", "Timestamp", "UserActive")
28
28
 
@@ -37,13 +37,16 @@ class AdminFlag(BaseModel):
37
37
  REGISTRATION = "RG"
38
38
  NON_USERS = "NU"
39
39
  AUTO_UPDATE = "AU"
40
- SEARCH_INDEX_OPTIMIZE = "SO"
41
40
  IMPORT_METADATA = "IM"
42
41
  SEND_TELEMETRY = "ST"
43
42
 
44
43
  FALSE_DEFAULTS = frozenset({FlagChoices.AUTO_UPDATE})
45
44
 
46
- key = CharField(db_index=True, max_length=2, choices=FlagChoices.choices)
45
+ key = CharField(
46
+ db_index=True,
47
+ max_length=max_choices_len(FlagChoices),
48
+ choices=FlagChoices.choices,
49
+ )
47
50
  on = BooleanField(default=True)
48
51
 
49
52
  class Meta(BaseModel.Meta):
@@ -55,7 +58,7 @@ class AdminFlag(BaseModel):
55
58
  class LibrarianStatus(BaseModel):
56
59
  """Active Library Tasks."""
57
60
 
58
- CHOICES = (
61
+ CHOICES = tuple(
59
62
  CoverStatusTypes.choices
60
63
  + ImportStatusTypes.choices
61
64
  + JanitorStatusTypes.choices
@@ -63,7 +66,9 @@ class LibrarianStatus(BaseModel):
63
66
  + WatchdogStatusTypes.choices
64
67
  )
65
68
 
66
- status_type = CharField(db_index=True, max_length=3, choices=CHOICES)
69
+ status_type = CharField(
70
+ db_index=True, max_length=max_choices_len(CHOICES), choices=CHOICES
71
+ )
67
72
  preactive = BooleanField(default=False)
68
73
  complete = PositiveSmallIntegerField(null=True, default=None)
69
74
  total = PositiveSmallIntegerField(null=True, default=None)
@@ -89,7 +94,11 @@ class Timestamp(BaseModel):
89
94
  API_KEY = "AP", _("API Key")
90
95
  TELEMETER_SENT = "TS", _("Telemeter Sent")
91
96
 
92
- key = CharField(db_index=True, max_length=2, choices=TimestampChoices.choices)
97
+ key = CharField(
98
+ db_index=True,
99
+ max_length=max_choices_len(TimestampChoices),
100
+ choices=TimestampChoices.choices,
101
+ )
93
102
  version = CharField(max_length=MAX_FIELD_LEN, default="")
94
103
 
95
104
  @classmethod
codex/models/base.py CHANGED
@@ -7,8 +7,17 @@ from codex.models.query import GroupByManager
7
7
 
8
8
  __all__ = ("BaseModel",)
9
9
 
10
+ MAX_PATH_LEN = 4095
10
11
  MAX_NAME_LEN = 128
11
12
  MAX_FIELD_LEN = 32
13
+ MAX_ISSUE_SUFFIX_LEN = 16
14
+
15
+
16
+ def max_choices_len(choices):
17
+ """Return the maximum possible size for a Choice's key."""
18
+ if not isinstance(choices, tuple):
19
+ choices = choices.choices
20
+ return max(len(choice[0]) for choice in choices)
12
21
 
13
22
 
14
23
  class BaseModel(Model):
codex/models/bookmark.py CHANGED
@@ -11,7 +11,7 @@ from django.db.models import (
11
11
  PositiveSmallIntegerField,
12
12
  )
13
13
 
14
- from codex.models.base import BaseModel
14
+ from codex.models.base import BaseModel, max_choices_len
15
15
  from codex.models.comic import Comic, ReadingDirection
16
16
 
17
17
  __all__ = ("Bookmark", "cascade_if_user_null")
@@ -73,14 +73,14 @@ class Bookmark(BaseModel):
73
73
  blank=True,
74
74
  choices=FitTo.choices,
75
75
  default="",
76
- max_length=1,
76
+ max_length=max_choices_len(FitTo),
77
77
  )
78
78
  two_pages = BooleanField(default=None, null=True)
79
79
  reading_direction = CharField(
80
80
  blank=True,
81
81
  choices=ReadingDirection.choices,
82
82
  default="",
83
- max_length=3,
83
+ max_length=max_choices_len(ReadingDirection),
84
84
  )
85
85
 
86
86
  class Meta(BaseModel.Meta):
codex/models/comic.py CHANGED
@@ -17,11 +17,18 @@ from django.db.models import (
17
17
  DecimalField,
18
18
  ForeignKey,
19
19
  ManyToManyField,
20
+ OneToOneField,
20
21
  PositiveIntegerField,
21
22
  PositiveSmallIntegerField,
22
23
  TextField,
23
24
  )
24
25
 
26
+ from codex.models.base import (
27
+ MAX_ISSUE_SUFFIX_LEN,
28
+ MAX_NAME_LEN,
29
+ BaseModel,
30
+ max_choices_len,
31
+ )
25
32
  from codex.models.groups import (
26
33
  Folder,
27
34
  Imprint,
@@ -61,16 +68,17 @@ class ReadingDirection(Choices):
61
68
  BTT = ReadingDirectionEnum.BTT.value
62
69
 
63
70
 
64
- class Comic(WatchedPathBrowserGroup):
65
- """Comic metadata."""
71
+ class FileType(Choices):
72
+ """Identifiers for file formats."""
73
+
74
+ CBZ = "CBZ"
75
+ CBR = "CBR"
76
+ CBT = "CBT"
77
+ PDF = "PDF"
66
78
 
67
- class FileType(Choices):
68
- """Identifiers for file formats."""
69
79
 
70
- CBZ = "CBZ"
71
- CBR = "CBR"
72
- CBT = "CBT"
73
- PDF = "PDF"
80
+ class Comic(WatchedPathBrowserGroup):
81
+ """Comic metadata."""
74
82
 
75
83
  _ORDERING = (
76
84
  "issue_number",
@@ -92,7 +100,12 @@ class Comic(WatchedPathBrowserGroup):
92
100
  issue_number = DecimalField(
93
101
  db_index=True, decimal_places=2, max_digits=10, null=True
94
102
  )
95
- issue_suffix = CharField(db_index=True, max_length=16, default="")
103
+ issue_suffix = CharField(
104
+ db_index=True,
105
+ max_length=MAX_ISSUE_SUFFIX_LEN,
106
+ default="",
107
+ db_collation="nocase",
108
+ )
96
109
  # Group FKs
97
110
  volume = ForeignKey(Volume, db_index=True, on_delete=CASCADE)
98
111
  series = ForeignKey(Series, db_index=True, on_delete=CASCADE)
@@ -111,13 +124,13 @@ class Comic(WatchedPathBrowserGroup):
111
124
 
112
125
  # Date
113
126
  year = PositiveSmallIntegerField(db_index=True, null=True)
114
- month = PositiveSmallIntegerField(null=True)
115
- day = PositiveSmallIntegerField(null=True)
127
+ month = PositiveSmallIntegerField(db_index=True, null=True)
128
+ day = PositiveSmallIntegerField(db_index=True, null=True)
116
129
 
117
130
  # Text
118
- summary = TextField(default="")
119
- review = TextField(default="")
120
- notes = TextField(default="")
131
+ summary = TextField(default="", db_collation="nocase")
132
+ review = TextField(default="", db_collation="nocase")
133
+ notes = TextField(default="", db_collation="nocase")
121
134
 
122
135
  # Ratings
123
136
  community_rating = DecimalField(
@@ -133,7 +146,8 @@ class Comic(WatchedPathBrowserGroup):
133
146
  db_index=True,
134
147
  choices=ReadingDirection.choices,
135
148
  default=ReadingDirectionEnum.LTR.value,
136
- max_length=3,
149
+ max_length=max_choices_len(ReadingDirection),
150
+ db_collation="nocase",
137
151
  )
138
152
 
139
153
  # Misc
@@ -168,10 +182,12 @@ class Comic(WatchedPathBrowserGroup):
168
182
  folders = ManyToManyField(Folder)
169
183
  size = PositiveIntegerField(db_index=True)
170
184
  file_type = CharField(
185
+ db_index=True,
171
186
  choices=FileType.choices,
172
- max_length=3,
187
+ max_length=max_choices_len(FileType),
173
188
  blank=True,
174
189
  default="",
190
+ db_collation="nocase",
175
191
  )
176
192
 
177
193
  # Not useful
@@ -287,3 +303,39 @@ class Comic(WatchedPathBrowserGroup):
287
303
  if self.issue_suffix:
288
304
  res += self.issue_suffix
289
305
  return res
306
+
307
+
308
+ class ComicFTS(BaseModel):
309
+ comic = OneToOneField(primary_key=True, to=Comic, on_delete=CASCADE)
310
+ publisher = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
311
+ imprint = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
312
+ series = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
313
+ volume = PositiveIntegerField()
314
+ issue = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
315
+ name = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
316
+ age_rating = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
317
+ country = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
318
+ language = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
319
+ notes = TextField(db_collation="nocase")
320
+ original_format = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
321
+ review = TextField(db_collation="nocase")
322
+ scan_info = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
323
+ summary = TextField(db_collation="nocase")
324
+ tagger = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
325
+ characters = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
326
+ contributors = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
327
+ genres = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
328
+ locations = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
329
+ series_groups = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
330
+ stories = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
331
+ story_arcs = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
332
+ tags = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
333
+ teams = CharField(db_collation="nocase", max_length=MAX_NAME_LEN)
334
+
335
+ reading_direction = CharField(
336
+ db_collation="nocase", max_length=max_choices_len(ReadingDirection)
337
+ )
338
+ file_type = CharField(db_collation="nocase", max_length=max_choices_len(FileType))
339
+
340
+ class Meta(BaseModel.Meta):
341
+ managed = False
codex/models/functions.py CHANGED
@@ -1,12 +1,69 @@
1
1
  """Custom Django DB functions."""
2
2
 
3
- from django.db.models import Aggregate, JSONField
3
+ from django.db.models.aggregates import Aggregate
4
+ from django.db.models.expressions import Func
5
+ from django.db.models.fields import CharField, FloatField, TextField
6
+ from django.db.models.fields.json import JSONField
7
+ from django.db.models.fields.related import OneToOneField
8
+ from django.db.models.lookups import Lookup
4
9
 
5
10
 
6
11
  class JsonGroupArray(Aggregate):
7
- """Sqlite3 json_group_array() function."""
12
+ """Sqlite3 JSON_GROUP_ARRAY function."""
8
13
 
9
14
  allow_distinct = True
10
15
  function = "JSON_GROUP_ARRAY"
11
16
  name = "JsonGroupArray"
12
17
  output_field = JSONField() # type: ignore
18
+
19
+
20
+ class GroupConcat(Aggregate):
21
+ """Sqlite3 GROUP_CONCAT."""
22
+
23
+ # Defaults to " " separator which is all I need for now.
24
+
25
+ allow_distinct = True
26
+ function = "GROUP_CONCAT"
27
+ name = "GroupConcat"
28
+ output_field = CharField() # type: ignore
29
+
30
+
31
+ @OneToOneField.register_lookup
32
+ class FTS5Match(Lookup):
33
+ """Sqlite3 FTS5 MATCH lookup."""
34
+
35
+ lookup_name = "match"
36
+
37
+ def as_sql(self, compiler, connection):
38
+ """Generate MATCH sql."""
39
+ lhs, lhs_params = self.process_lhs(compiler, connection)
40
+ rhs, rhs_params = self.process_rhs(compiler, connection)
41
+ # MATCH works on the table itself not the one_to_one rel.
42
+ fts_table = lhs.split(".")[0]
43
+ params = lhs_params + rhs_params
44
+ sql = f"{fts_table} MATCH {rhs}"
45
+ return sql, params
46
+
47
+
48
+ @CharField.register_lookup
49
+ @TextField.register_lookup
50
+ class Like(Lookup):
51
+ """SQL LIKE lookup."""
52
+
53
+ lookup_name = "like"
54
+
55
+ def as_sql(self, compiler, connection):
56
+ """Generate LIKE sql."""
57
+ lhs, lhs_params = self.process_lhs(compiler, connection)
58
+ rhs, rhs_params = self.process_rhs(compiler, connection)
59
+ params = lhs_params + rhs_params
60
+ sql = f"{lhs} LIKE {rhs}"
61
+ return sql, params
62
+
63
+
64
+ class ComicFTSRank(Func):
65
+ """Sqlite3 FTS5 inverse rank lookup."""
66
+
67
+ function = "rank"
68
+ template = '("codex_comicfts"."rank" * -1)'
69
+ output_field = FloatField() # type: ignore
codex/models/groups.py CHANGED
@@ -21,7 +21,12 @@ class BrowserGroupModel(BaseModel):
21
21
  PARENT = ""
22
22
 
23
23
  name = CharField(db_index=True, max_length=MAX_NAME_LEN, default=DEFAULT_NAME)
24
- sort_name = CharField(db_index=True, max_length=MAX_NAME_LEN, default=DEFAULT_NAME)
24
+ sort_name = CharField(
25
+ db_index=True,
26
+ max_length=MAX_NAME_LEN,
27
+ default=DEFAULT_NAME,
28
+ db_collation="nocase",
29
+ )
25
30
  custom_cover = ForeignKey(
26
31
  CustomCover, on_delete=SET_DEFAULT, null=True, default=None
27
32
  )
codex/models/library.py CHANGED
@@ -15,12 +15,10 @@ from django.db.models import (
15
15
  )
16
16
  from django.utils.translation import gettext_lazy as _
17
17
 
18
- from codex.models.base import BaseModel
18
+ from codex.models.base import MAX_PATH_LEN, BaseModel
19
19
 
20
20
  __all__ = ("Library", "validate_dir_exists")
21
21
 
22
- MAX_PATH_LEN = 4095
23
-
24
22
 
25
23
  def validate_dir_exists(path):
26
24
  """Validate that a library exists."""
codex/models/paths.py CHANGED
@@ -6,7 +6,7 @@ from types import MappingProxyType
6
6
  from django.db.models import CASCADE, CharField, ForeignKey, JSONField
7
7
  from django.db.models.enums import Choices
8
8
 
9
- from codex.models.base import MAX_NAME_LEN, BaseModel
9
+ from codex.models.base import MAX_NAME_LEN, BaseModel, max_choices_len
10
10
  from codex.models.library import MAX_PATH_LEN, Library
11
11
  from codex.models.util import get_sort_name
12
12
 
@@ -101,8 +101,14 @@ class CustomCover(WatchedPath):
101
101
  )
102
102
 
103
103
  parent_folder = None
104
- group = CharField(max_length=1, db_index=True, choices=GroupChoice.choices)
105
- sort_name = CharField(max_length=MAX_NAME_LEN, db_index=True, default="")
104
+ group = CharField(
105
+ max_length=max_choices_len(GroupChoice),
106
+ db_index=True,
107
+ choices=GroupChoice.choices,
108
+ )
109
+ sort_name = CharField(
110
+ max_length=MAX_NAME_LEN, db_index=True, default="", db_collation="nocase"
111
+ )
106
112
 
107
113
  def _set_group_and_sort_name(self):
108
114
  """Set group and sort_name from path."""
@@ -60,4 +60,6 @@ class BrowserPageSerializer(Serializer):
60
60
  num_pages = IntegerField(read_only=True)
61
61
  groups = BrowserCardSerializer(allow_empty=True, read_only=True, many=True)
62
62
  books = BrowserCardSerializer(allow_empty=True, read_only=True, many=True)
63
+ fts = BooleanField(read_only=True)
64
+ search_error = CharField(read_only=True)
63
65
  mtime = TimestampField(read_only=True)
@@ -13,7 +13,6 @@ import logging
13
13
  from logging import WARN, getLogger
14
14
  from os import environ
15
15
  from pathlib import Path
16
- from sys import maxsize
17
16
  from types import MappingProxyType
18
17
 
19
18
  from codex.settings.hypercorn import load_hypercorn_config
@@ -25,12 +24,20 @@ from codex.settings.whitenoise import immutable_file_test
25
24
  # Undocumented Environment Variables #
26
25
  ######################################
27
26
  # SECURITY WARNING: don't run with debug turned on in production!
28
- DEBUG = bool(environ.get("DEBUG", "").lower() not in {"0", "false", ""})
27
+ FALSY = {None, "", "false", "0", False}
28
+
29
+
30
+ def not_falsy_env(name):
31
+ """Return a boolean environment envs mindful of falsy values."""
32
+ return bool(environ.get(name, "").lower() not in FALSY)
33
+
34
+
35
+ DEBUG = not_falsy_env("DEBUG")
29
36
  BUILD = environ.get("BUILD", False)
30
37
  # Slow query middleware
31
38
  # limit in seconds
32
39
  SLOW_QUERY_LIMIT = float(environ.get("CODEX_SLOW_QUERY_LIMIT", 0.5))
33
- LOG_RESPONSE_TIME = bool(environ.get("CODEX_LOG_RESPONSE_TIME", False))
40
+ LOG_RESPONSE_TIME = not_falsy_env("CODEX_LOG_RESPONSE_TIME")
34
41
  # Search indexing memory controls
35
42
  MMAP_RATIO = int(environ.get("CODEX_MMAP_RATIO", 240))
36
43
  WRITER_MEMORY_PERCENT = float(environ.get("CODEX_WRITER_MEMORY_PERCENT", 0.6))
@@ -43,6 +50,7 @@ MAX_CHUNK_SIZE = int(environ.get("CODEX_MAX_CHUNK_SIZE", 1000))
43
50
  # count is a proxy, but it works. 990 errors sometimes.
44
51
  FILTER_BATCH_SIZE = int(environ.get("CODEX_FILTER_BATCH_SIZE", 900))
45
52
  VITE_HOST = environ.get("VITE_HOST")
53
+ SEARCH_INDEX_BATCH_SIZE = int(environ.get("CODEX_SEARCH_INDEX_BATCH_SIZE", 10000))
46
54
 
47
55
  ####################################
48
56
  # Documented Environment Variables #
@@ -50,8 +58,7 @@ VITE_HOST = environ.get("VITE_HOST")
50
58
  LOGLEVEL = environ.get("LOGLEVEL", logging.DEBUG if DEBUG else logging.INFO)
51
59
  TZ = environ.get("TIMEZONE", environ.get("TZ"))
52
60
  CONFIG_PATH = Path(environ.get("CODEX_CONFIG_DIR", Path.cwd() / "config"))
53
- RESET_ADMIN = bool(environ.get("CODEX_RESET_ADMIN"))
54
- SKIP_INTEGRITY_CHECK = bool(environ.get("CODEX_SKIP_INTEGRITY_CHECK"))
61
+ RESET_ADMIN = not_falsy_env("CODEX_RESET_ADMIN")
55
62
  LOG_DIR = Path(environ.get("CODEX_LOG_DIR", CONFIG_PATH / "logs"))
56
63
  LOG_TO_CONSOLE = environ.get("CODEX_LOG_TO_CONSOLE") != "0"
57
64
  LOG_TO_FILE = environ.get("CODEX_LOG_TO_FILE") != "0"
@@ -59,6 +66,10 @@ THROTTLE_ANON = int(environ.get("CODEX_THROTTLE_ANON", 0))
59
66
  THROTTLE_USER = int(environ.get("CODEX_THROTTLE_USER", 0))
60
67
  THROTTLE_OPDS = int(environ.get("CODEX_THROTTLE_OPDS", 0))
61
68
  THROTTLE_OPENSEARCH = int(environ.get("CODEX_THROTTLE_OPENSEARCH", 0))
69
+ FIX_FOREIGN_KEYS = not_falsy_env("CODEX_FIX_FOREIGN_KEYS")
70
+ INTEGRITY_CHECK = environ.get("CODEX_INTEGRITY_CHECK", False)
71
+ FTS_INTEGRITY_CHECK = not_falsy_env("CODEX_FTS_INTEGRITY_CHECK")
72
+ FTS_REBUILD = not_falsy_env("CODEX_FTS_REBUILD")
62
73
 
63
74
  # Base paths
64
75
  BASE_DIR = Path(__file__).resolve().parent.parent.parent
@@ -101,7 +112,6 @@ INSTALLED_APPS = [
101
112
  "django.contrib.contenttypes",
102
113
  "django.contrib.sessions",
103
114
  "django.contrib.messages",
104
- "codex._vendor.haystack",
105
115
  ]
106
116
 
107
117
  if DEBUG:
@@ -357,17 +367,6 @@ CACHES = {
357
367
 
358
368
  INTERNAL_IPS = ("127.0.0.1",)
359
369
 
360
- SEARCH_INDEX_PATH = CONFIG_PATH / "whoosh_index"
361
- SEARCH_INDEX_PATH.mkdir(exist_ok=True, parents=True)
362
- SEARCH_INDEX_UUID_PATH = SEARCH_INDEX_PATH / "codex_db.uuid"
363
- HAYSTACK_CONNECTIONS = {
364
- "default": {
365
- "ENGINE": "codex.search.engine.CodexSearchEngine",
366
- "PATH": str(SEARCH_INDEX_PATH),
367
- "BATCH_SIZE": maxsize, # use whoosh multiprocessing not haystack's
368
- },
369
- }
370
- HAYSTACK_LOGGING = False
371
370
  CHANNEL_LAYERS = {
372
371
  "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"},
373
372
  }