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
@@ -1,991 +0,0 @@
1
- import warnings
2
-
3
- from django.conf import settings
4
- from django.core.exceptions import ImproperlyConfigured
5
-
6
- from ... import haystack
7
- from ...haystack.backends import (
8
- BaseEngine,
9
- BaseSearchBackend,
10
- BaseSearchQuery,
11
- EmptyResults,
12
- log_query,
13
- )
14
- from ...haystack.constants import DJANGO_CT, DJANGO_ID, ID
15
- from ...haystack.exceptions import MissingDependency, MoreLikeThisError, SkipDocument
16
- from ...haystack.inputs import Clean, Exact, PythonData, Raw
17
- from ...haystack.models import SearchResult
18
- from ...haystack.utils import get_identifier, get_model_ct
19
- from ...haystack.utils import log as logging
20
- from ...haystack.utils.app_loading import haystack_get_model
21
-
22
- try:
23
- from pysolr import Solr, SolrError
24
- except ImportError:
25
- raise MissingDependency(
26
- "The 'solr' backend requires the installation of 'pysolr'. Please refer to the documentation."
27
- )
28
-
29
-
30
- class SolrSearchBackend(BaseSearchBackend):
31
- # Word reserved by Solr for special use.
32
- RESERVED_WORDS = ("AND", "NOT", "OR", "TO")
33
-
34
- # Characters reserved by Solr for special use.
35
- # The '\\' must come first, so as not to overwrite the other slash replacements.
36
- RESERVED_CHARACTERS = (
37
- "\\",
38
- "+",
39
- "-",
40
- "&&",
41
- "||",
42
- "!",
43
- "(",
44
- ")",
45
- "{",
46
- "}",
47
- "[",
48
- "]",
49
- "^",
50
- '"',
51
- "~",
52
- "*",
53
- "?",
54
- ":",
55
- "/",
56
- )
57
-
58
- def __init__(self, connection_alias, **connection_options):
59
- super().__init__(connection_alias, **connection_options)
60
-
61
- if "URL" not in connection_options:
62
- raise ImproperlyConfigured(
63
- "You must specify a 'URL' in your settings for connection '%s'."
64
- % connection_alias
65
- )
66
-
67
- self.collate = connection_options.get("COLLATE_SPELLING", True)
68
-
69
- # Support to `date_facet` on Solr >= 6.6. Olders set `date`
70
- self.date_facet_field = connection_options.get("DATE_FACET_FIELD", "range")
71
-
72
- self.conn = Solr(
73
- connection_options["URL"],
74
- timeout=self.timeout,
75
- **connection_options.get("KWARGS", {})
76
- )
77
- self.log = logging.getLogger("haystack")
78
-
79
- def update(self, index, iterable, commit=True):
80
- docs = []
81
-
82
- for obj in iterable:
83
- try:
84
- docs.append(index.full_prepare(obj))
85
- except SkipDocument:
86
- self.log.debug("Indexing for object `%s` skipped", obj)
87
- except UnicodeDecodeError:
88
- if not self.silently_fail:
89
- raise
90
-
91
- # We'll log the object identifier but won't include the actual object
92
- # to avoid the possibility of that generating encoding errors while
93
- # processing the log message:
94
- self.log.error(
95
- "UnicodeDecodeError while preparing object for update",
96
- exc_info=True,
97
- extra={"data": {"index": index, "object": get_identifier(obj)}},
98
- )
99
-
100
- if len(docs) > 0:
101
- try:
102
- self.conn.add(docs, commit=commit, boost=index.get_field_weights())
103
- except (IOError, SolrError) as e:
104
- if not self.silently_fail:
105
- raise
106
-
107
- self.log.error("Failed to add documents to Solr: %s", e, exc_info=True)
108
-
109
- def remove(self, obj_or_string, commit=True):
110
- solr_id = get_identifier(obj_or_string)
111
-
112
- try:
113
- kwargs = {"commit": commit, "id": solr_id}
114
- self.conn.delete(**kwargs)
115
- except (IOError, SolrError) as e:
116
- if not self.silently_fail:
117
- raise
118
-
119
- self.log.error(
120
- "Failed to remove document '%s' from Solr: %s",
121
- solr_id,
122
- e,
123
- exc_info=True,
124
- )
125
-
126
- def clear(self, models=None, commit=True):
127
- if models is not None:
128
- assert isinstance(models, (list, tuple))
129
-
130
- try:
131
- if models is None:
132
- # *:* matches all docs in Solr
133
- self.conn.delete(q="*:*", commit=commit)
134
- else:
135
- models_to_delete = []
136
-
137
- for model in models:
138
- models_to_delete.append("%s:%s" % (DJANGO_CT, get_model_ct(model)))
139
-
140
- self.conn.delete(q=" OR ".join(models_to_delete), commit=commit)
141
-
142
- if commit:
143
- # Run an optimize post-clear. http://wiki.apache.org/solr/FAQ#head-9aafb5d8dff5308e8ea4fcf4b71f19f029c4bb99
144
- self.conn.optimize()
145
- except (IOError, SolrError) as e:
146
- if not self.silently_fail:
147
- raise
148
-
149
- if models is not None:
150
- self.log.error(
151
- "Failed to clear Solr index of models '%s': %s",
152
- ",".join(models_to_delete),
153
- e,
154
- exc_info=True,
155
- )
156
- else:
157
- self.log.error("Failed to clear Solr index: %s", e, exc_info=True)
158
-
159
- @log_query
160
- def search(self, query_string, **kwargs):
161
- if len(query_string) == 0:
162
- return {"results": [], "hits": 0}
163
-
164
- search_kwargs = self.build_search_kwargs(query_string, **kwargs)
165
-
166
- try:
167
- raw_results = self.conn.search(query_string, **search_kwargs)
168
- except (IOError, SolrError) as e:
169
- if not self.silently_fail:
170
- raise
171
-
172
- self.log.error(
173
- "Failed to query Solr using '%s': %s", query_string, e, exc_info=True
174
- )
175
- raw_results = EmptyResults()
176
-
177
- return self._process_results(
178
- raw_results,
179
- highlight=kwargs.get("highlight"),
180
- result_class=kwargs.get("result_class", SearchResult),
181
- distance_point=kwargs.get("distance_point"),
182
- )
183
-
184
- def build_search_kwargs(
185
- self,
186
- query_string,
187
- sort_by=None,
188
- start_offset=0,
189
- end_offset=None,
190
- fields="",
191
- highlight=False,
192
- facets=None,
193
- date_facets=None,
194
- query_facets=None,
195
- narrow_queries=None,
196
- spelling_query=None,
197
- within=None,
198
- dwithin=None,
199
- distance_point=None,
200
- models=None,
201
- limit_to_registered_models=None,
202
- result_class=None,
203
- stats=None,
204
- collate=None,
205
- **extra_kwargs
206
- ):
207
-
208
- index = haystack.connections[self.connection_alias].get_unified_index()
209
-
210
- kwargs = {"fl": "* score", "df": index.document_field}
211
-
212
- if fields:
213
- if isinstance(fields, (list, set)):
214
- fields = " ".join(fields)
215
-
216
- kwargs["fl"] = fields
217
-
218
- if sort_by is not None:
219
- if sort_by in ["distance asc", "distance desc"] and distance_point:
220
- # Do the geo-enabled sort.
221
- lng, lat = distance_point["point"].coords
222
- kwargs["sfield"] = distance_point["field"]
223
- kwargs["pt"] = "%s,%s" % (lat, lng)
224
-
225
- if sort_by == "distance asc":
226
- kwargs["sort"] = "geodist() asc"
227
- else:
228
- kwargs["sort"] = "geodist() desc"
229
- else:
230
- if sort_by.startswith("distance "):
231
- warnings.warn(
232
- "In order to sort by distance, you must call the '.distance(...)' method."
233
- )
234
-
235
- # Regular sorting.
236
- kwargs["sort"] = sort_by
237
-
238
- if start_offset is not None:
239
- kwargs["start"] = start_offset
240
-
241
- if end_offset is not None:
242
- kwargs["rows"] = end_offset - start_offset
243
-
244
- if highlight:
245
- # `highlight` can either be True or a dictionary containing custom parameters
246
- # which will be passed to the backend and may override our default settings:
247
-
248
- kwargs["hl"] = "true"
249
- kwargs["hl.fragsize"] = "200"
250
-
251
- if isinstance(highlight, dict):
252
- # autoprefix highlighter options with 'hl.', all of them start with it anyway
253
- # this makes option dicts shorter: {'maxAnalyzedChars': 42}
254
- # and lets some of options be used as keyword arguments: `.highlight(preserveMulti=False)`
255
- kwargs.update(
256
- {
257
- key if key.startswith("hl.") else ("hl." + key): highlight[key]
258
- for key in highlight.keys()
259
- }
260
- )
261
-
262
- if collate is None:
263
- collate = self.collate
264
- if self.include_spelling is True:
265
- kwargs["spellcheck"] = "true"
266
- kwargs["spellcheck.collate"] = str(collate).lower()
267
- kwargs["spellcheck.count"] = 1
268
-
269
- if spelling_query:
270
- kwargs["spellcheck.q"] = spelling_query
271
-
272
- if facets is not None:
273
- kwargs["facet"] = "on"
274
- kwargs["facet.field"] = facets.keys()
275
-
276
- for facet_field, options in facets.items():
277
- for key, value in options.items():
278
- kwargs[
279
- "f.%s.facet.%s" % (facet_field, key)
280
- ] = self.conn._from_python(value)
281
-
282
- if date_facets is not None:
283
- kwargs["facet"] = "on"
284
- kwargs["facet.%s" % self.date_facet_field] = date_facets.keys()
285
- kwargs["facet.%s.other" % self.date_facet_field] = "none"
286
-
287
- for key, value in date_facets.items():
288
- kwargs[
289
- "f.%s.facet.%s.start" % (key, self.date_facet_field)
290
- ] = self.conn._from_python(value.get("start_date"))
291
- kwargs[
292
- "f.%s.facet.%s.end" % (key, self.date_facet_field)
293
- ] = self.conn._from_python(value.get("end_date"))
294
- gap_by_string = value.get("gap_by").upper()
295
- gap_string = "%d%s" % (value.get("gap_amount"), gap_by_string)
296
-
297
- if value.get("gap_amount") != 1:
298
- gap_string += "S"
299
-
300
- kwargs[
301
- "f.%s.facet.%s.gap" % (key, self.date_facet_field)
302
- ] = "+%s/%s" % (
303
- gap_string,
304
- gap_by_string,
305
- )
306
-
307
- if query_facets is not None:
308
- kwargs["facet"] = "on"
309
- kwargs["facet.query"] = [
310
- "%s:%s" % (field, value) for field, value in query_facets
311
- ]
312
-
313
- if limit_to_registered_models is None:
314
- limit_to_registered_models = getattr(
315
- settings, "HAYSTACK_LIMIT_TO_REGISTERED_MODELS", True
316
- )
317
-
318
- if models and len(models):
319
- model_choices = sorted(get_model_ct(model) for model in models)
320
- elif limit_to_registered_models:
321
- # Using narrow queries, limit the results to only models handled
322
- # with the current routers.
323
- model_choices = self.build_models_list()
324
- else:
325
- model_choices = []
326
-
327
- if len(model_choices) > 0:
328
- if narrow_queries is None:
329
- narrow_queries = set()
330
-
331
- narrow_queries.add("%s:(%s)" % (DJANGO_CT, " OR ".join(model_choices)))
332
-
333
- if narrow_queries is not None:
334
- kwargs["fq"] = list(narrow_queries)
335
-
336
- if stats:
337
- kwargs["stats"] = "true"
338
-
339
- for k in stats.keys():
340
- kwargs["stats.field"] = k
341
-
342
- for facet in stats[k]:
343
- kwargs["f.%s.stats.facet" % k] = facet
344
-
345
- if within is not None:
346
- from ...haystack.utils.geo import generate_bounding_box
347
-
348
- kwargs.setdefault("fq", [])
349
- ((min_lat, min_lng), (max_lat, max_lng)) = generate_bounding_box(
350
- within["point_1"], within["point_2"]
351
- )
352
- # Bounding boxes are min, min TO max, max. Solr's wiki was *NOT*
353
- # very clear on this.
354
- bbox = "%s:[%s,%s TO %s,%s]" % (
355
- within["field"],
356
- min_lat,
357
- min_lng,
358
- max_lat,
359
- max_lng,
360
- )
361
- kwargs["fq"].append(bbox)
362
-
363
- if dwithin is not None:
364
- kwargs.setdefault("fq", [])
365
- lng, lat = dwithin["point"].coords
366
- geofilt = "{!geofilt pt=%s,%s sfield=%s d=%s}" % (
367
- lat,
368
- lng,
369
- dwithin["field"],
370
- dwithin["distance"].km,
371
- )
372
- kwargs["fq"].append(geofilt)
373
-
374
- # Check to see if the backend should try to include distances
375
- # (Solr 4.X+) in the results.
376
- if self.distance_available and distance_point:
377
- # In early testing, you can't just hand Solr 4.X a proper bounding box
378
- # & request distances. To enable native distance would take calculating
379
- # a center point & a radius off the user-provided box, which kinda
380
- # sucks. We'll avoid it for now, since Solr 4.x's release will be some
381
- # time yet.
382
- # kwargs['fl'] += ' _dist_:geodist()'
383
- pass
384
-
385
- if extra_kwargs:
386
- kwargs.update(extra_kwargs)
387
-
388
- return kwargs
389
-
390
- def more_like_this(
391
- self,
392
- model_instance,
393
- additional_query_string=None,
394
- start_offset=0,
395
- end_offset=None,
396
- models=None,
397
- limit_to_registered_models=None,
398
- result_class=None,
399
- **kwargs
400
- ):
401
- from ...haystack import connections
402
-
403
- # Deferred models will have a different class ("RealClass_Deferred_fieldname")
404
- # which won't be in our registry:
405
- model_klass = model_instance._meta.concrete_model
406
-
407
- index = (
408
- connections[self.connection_alias]
409
- .get_unified_index()
410
- .get_index(model_klass)
411
- )
412
- field_name = index.get_content_field()
413
- params = {"fl": "*,score"}
414
-
415
- if start_offset is not None:
416
- params["start"] = start_offset
417
-
418
- if end_offset is not None:
419
- params["rows"] = end_offset
420
-
421
- narrow_queries = set()
422
-
423
- if limit_to_registered_models is None:
424
- limit_to_registered_models = getattr(
425
- settings, "HAYSTACK_LIMIT_TO_REGISTERED_MODELS", True
426
- )
427
-
428
- if models and len(models):
429
- model_choices = sorted(get_model_ct(model) for model in models)
430
- elif limit_to_registered_models:
431
- # Using narrow queries, limit the results to only models handled
432
- # with the current routers.
433
- model_choices = self.build_models_list()
434
- else:
435
- model_choices = []
436
-
437
- if len(model_choices) > 0:
438
- if narrow_queries is None:
439
- narrow_queries = set()
440
-
441
- narrow_queries.add("%s:(%s)" % (DJANGO_CT, " OR ".join(model_choices)))
442
-
443
- if additional_query_string:
444
- narrow_queries.add(additional_query_string)
445
-
446
- if narrow_queries:
447
- params["fq"] = list(narrow_queries)
448
-
449
- query = "%s:%s" % (ID, get_identifier(model_instance))
450
-
451
- try:
452
- raw_results = self.conn.more_like_this(query, field_name, **params)
453
- except (IOError, SolrError) as e:
454
- if not self.silently_fail:
455
- raise
456
-
457
- self.log.error(
458
- "Failed to fetch More Like This from Solr for document '%s': %s",
459
- query,
460
- e,
461
- exc_info=True,
462
- )
463
- raw_results = EmptyResults()
464
-
465
- return self._process_results(raw_results, result_class=result_class)
466
-
467
- def _process_results(
468
- self, raw_results, highlight=False, result_class=None, distance_point=None
469
- ):
470
- from ...haystack import connections
471
-
472
- results = []
473
- hits = raw_results.hits
474
- facets = {}
475
- stats = {}
476
- spelling_suggestion = spelling_suggestions = None
477
-
478
- if result_class is None:
479
- result_class = SearchResult
480
-
481
- if hasattr(raw_results, "stats"):
482
- stats = raw_results.stats.get("stats_fields", {})
483
-
484
- if hasattr(raw_results, "facets"):
485
- facets = {
486
- "fields": raw_results.facets.get("facet_fields", {}),
487
- "dates": raw_results.facets.get("facet_dates", {}),
488
- "queries": raw_results.facets.get("facet_queries", {}),
489
- "ranges": raw_results.facets.get("facet_ranges", {}),
490
- }
491
-
492
- for key in ["fields"]:
493
- for facet_field in facets[key]:
494
- # Convert to a two-tuple, as Solr's json format returns a list of
495
- # pairs.
496
- facets[key][facet_field] = list(
497
- zip(
498
- facets[key][facet_field][::2],
499
- facets[key][facet_field][1::2],
500
- )
501
- )
502
-
503
- for key in ["ranges"]:
504
- for facet_field in facets[key]:
505
- # Convert to a two-tuple, as Solr's json format returns a list of
506
- # pairs.
507
- facets[key][facet_field] = list(
508
- zip(
509
- facets[key][facet_field]["counts"][::2],
510
- facets[key][facet_field]["counts"][1::2],
511
- )
512
- )
513
-
514
- if self.include_spelling and hasattr(raw_results, "spellcheck"):
515
- try:
516
- spelling_suggestions = self.extract_spelling_suggestions(raw_results)
517
- except Exception as exc:
518
- self.log.error(
519
- "Error extracting spelling suggestions: %s",
520
- exc,
521
- exc_info=True,
522
- extra={"data": {"spellcheck": raw_results.spellcheck}},
523
- )
524
-
525
- if not self.silently_fail:
526
- raise
527
-
528
- spelling_suggestions = None
529
-
530
- if spelling_suggestions:
531
- # Maintain compatibility with older versions of Haystack which returned a single suggestion:
532
- spelling_suggestion = spelling_suggestions[-1]
533
- assert isinstance(spelling_suggestion, str)
534
- else:
535
- spelling_suggestion = None
536
-
537
- unified_index = connections[self.connection_alias].get_unified_index()
538
- indexed_models = unified_index.get_indexed_models()
539
-
540
- for raw_result in raw_results.docs:
541
- app_label, model_name = raw_result[DJANGO_CT].split(".")
542
- additional_fields = {}
543
- model = haystack_get_model(app_label, model_name)
544
-
545
- if model and model in indexed_models:
546
- index = unified_index.get_index(model)
547
- index_field_map = index.field_map
548
- for key, value in raw_result.items():
549
- string_key = str(key)
550
- # re-map key if alternate name used
551
- if string_key in index_field_map:
552
- string_key = index_field_map[key]
553
-
554
- if string_key in index.fields and hasattr(
555
- index.fields[string_key], "convert"
556
- ):
557
- additional_fields[string_key] = index.fields[
558
- string_key
559
- ].convert(value)
560
- else:
561
- additional_fields[string_key] = self.conn._to_python(value)
562
-
563
- del additional_fields[DJANGO_CT]
564
- del additional_fields[DJANGO_ID]
565
- del additional_fields["score"]
566
-
567
- if raw_result[ID] in getattr(raw_results, "highlighting", {}):
568
- additional_fields["highlighted"] = raw_results.highlighting[
569
- raw_result[ID]
570
- ]
571
-
572
- if distance_point:
573
- additional_fields["_point_of_origin"] = distance_point
574
-
575
- if raw_result.get("__dist__"):
576
- from django.contrib.gis.measure import Distance
577
-
578
- additional_fields["_distance"] = Distance(
579
- km=float(raw_result["__dist__"])
580
- )
581
- else:
582
- additional_fields["_distance"] = None
583
-
584
- result = result_class(
585
- app_label,
586
- model_name,
587
- raw_result[DJANGO_ID],
588
- raw_result["score"],
589
- **additional_fields
590
- )
591
- results.append(result)
592
- else:
593
- hits -= 1
594
-
595
- return {
596
- "results": results,
597
- "hits": hits,
598
- "stats": stats,
599
- "facets": facets,
600
- "spelling_suggestion": spelling_suggestion,
601
- "spelling_suggestions": spelling_suggestions,
602
- }
603
-
604
- def extract_spelling_suggestions(self, raw_results):
605
- # There are many different formats for Legacy, 6.4, and 6.5 e.g.
606
- # https://issues.apache.org/jira/browse/SOLR-3029 and depending on the
607
- # version and configuration the response format may be a dict of dicts,
608
- # a list of dicts, or a list of strings.
609
-
610
- collations = raw_results.spellcheck.get("collations", None)
611
- suggestions = raw_results.spellcheck.get("suggestions", None)
612
-
613
- # We'll collect multiple suggestions here. For backwards
614
- # compatibility with older versions of Haystack we'll still return
615
- # only a single suggestion but in the future we can expose all of
616
- # them.
617
-
618
- spelling_suggestions = []
619
-
620
- if collations:
621
- if isinstance(collations, dict):
622
- # Solr 6.5
623
- collation_values = collations["collation"]
624
- if isinstance(collation_values, str):
625
- collation_values = [collation_values]
626
- elif isinstance(collation_values, dict):
627
- # spellcheck.collateExtendedResults changes the format to a dictionary:
628
- collation_values = [collation_values["collationQuery"]]
629
- elif isinstance(collations[1], dict):
630
- # Solr 6.4
631
- collation_values = collations
632
- else:
633
- # Older versions of Solr
634
- collation_values = collations[-1:]
635
-
636
- for i in collation_values:
637
- # Depending on the options the values are either simple strings or dictionaries:
638
- spelling_suggestions.append(
639
- i["collationQuery"] if isinstance(i, dict) else i
640
- )
641
- elif suggestions:
642
- if isinstance(suggestions, dict):
643
- for i in suggestions.values():
644
- for j in i["suggestion"]:
645
- if isinstance(j, dict):
646
- spelling_suggestions.append(j["word"])
647
- else:
648
- spelling_suggestions.append(j)
649
- elif isinstance(suggestions[0], str) and isinstance(suggestions[1], dict):
650
- # Solr 6.4 uses a list of paired (word, dictionary) pairs:
651
- for suggestion in suggestions:
652
- if isinstance(suggestion, dict):
653
- for i in suggestion["suggestion"]:
654
- if isinstance(i, dict):
655
- spelling_suggestions.append(i["word"])
656
- else:
657
- spelling_suggestions.append(i)
658
- else:
659
- # Legacy Solr
660
- spelling_suggestions.append(suggestions[-1])
661
-
662
- return spelling_suggestions
663
-
664
- def build_schema(self, fields):
665
- content_field_name = ""
666
- schema_fields = []
667
-
668
- for _, field_class in fields.items():
669
- field_data = {
670
- "field_name": field_class.index_fieldname,
671
- "type": "text_en",
672
- "indexed": "true",
673
- "stored": "true",
674
- "multi_valued": "false",
675
- }
676
-
677
- if field_class.document is True:
678
- content_field_name = field_class.index_fieldname
679
-
680
- # DRL_FIXME: Perhaps move to something where, if none of these
681
- # checks succeed, call a custom method on the form that
682
- # returns, per-backend, the right type of storage?
683
- if field_class.field_type in ["date", "datetime"]:
684
- field_data["type"] = "date"
685
- elif field_class.field_type == "integer":
686
- field_data["type"] = "long"
687
- elif field_class.field_type == "float":
688
- field_data["type"] = "float"
689
- elif field_class.field_type == "boolean":
690
- field_data["type"] = "boolean"
691
- elif field_class.field_type == "ngram":
692
- field_data["type"] = "ngram"
693
- elif field_class.field_type == "edge_ngram":
694
- field_data["type"] = "edge_ngram"
695
- elif field_class.field_type == "location":
696
- field_data["type"] = "location"
697
-
698
- if field_class.is_multivalued:
699
- field_data["multi_valued"] = "true"
700
-
701
- if field_class.stored is False:
702
- field_data["stored"] = "false"
703
-
704
- # Do this last to override `text` fields.
705
- if field_class.indexed is False:
706
- field_data["indexed"] = "false"
707
-
708
- # If it's text and not being indexed, we probably don't want
709
- # to do the normal lowercase/tokenize/stemming/etc. dance.
710
- if field_data["type"] == "text_en":
711
- field_data["type"] = "string"
712
-
713
- # If it's a ``FacetField``, make sure we don't postprocess it.
714
- if hasattr(field_class, "facet_for"):
715
- # If it's text, it ought to be a string.
716
- if field_data["type"] == "text_en":
717
- field_data["type"] = "string"
718
-
719
- schema_fields.append(field_data)
720
-
721
- return (content_field_name, schema_fields)
722
-
723
- def extract_file_contents(self, file_obj, **kwargs):
724
- """Extract text and metadata from a structured file (PDF, MS Word, etc.)
725
-
726
- Uses the Solr ExtractingRequestHandler, which is based on Apache Tika.
727
- See the Solr wiki for details:
728
-
729
- http://wiki.apache.org/solr/ExtractingRequestHandler
730
-
731
- Due to the way the ExtractingRequestHandler is implemented it completely
732
- replaces the normal Haystack indexing process with several unfortunate
733
- restrictions: only one file per request, the extracted data is added to
734
- the index with no ability to modify it, etc. To simplify the process and
735
- allow for more advanced use we'll run using the extract-only mode to
736
- return the extracted data without adding it to the index so we can then
737
- use it within Haystack's normal templating process.
738
-
739
- Returns None if metadata cannot be extracted; otherwise returns a
740
- dictionary containing at least two keys:
741
-
742
- :contents:
743
- Extracted full-text content, if applicable
744
- :metadata:
745
- key:value pairs of text strings
746
- """
747
-
748
- try:
749
- return self.conn.extract(file_obj, **kwargs)
750
- except Exception as e:
751
- self.log.warning(
752
- "Unable to extract file contents: %s",
753
- e,
754
- exc_info=True,
755
- extra={"data": {"file": file_obj}},
756
- )
757
- return None
758
-
759
-
760
- class SolrSearchQuery(BaseSearchQuery):
761
- def matching_all_fragment(self):
762
- return "*:*"
763
-
764
- def build_query_fragment(self, field, filter_type, value):
765
- from ...haystack import connections
766
-
767
- query_frag = ""
768
-
769
- if not hasattr(value, "input_type_name"):
770
- # Handle when we've got a ``ValuesListQuerySet``...
771
- if hasattr(value, "values_list"):
772
- value = list(value)
773
-
774
- if isinstance(value, str):
775
- # It's not an ``InputType``. Assume ``Clean``.
776
- value = Clean(value)
777
- else:
778
- value = PythonData(value)
779
-
780
- # Prepare the query using the InputType.
781
- prepared_value = value.prepare(self)
782
-
783
- if not isinstance(prepared_value, (set, list, tuple)):
784
- # Then convert whatever we get back to what pysolr wants if needed.
785
- prepared_value = self.backend.conn._from_python(prepared_value)
786
-
787
- # 'content' is a special reserved word, much like 'pk' in
788
- # Django's ORM layer. It indicates 'no special field'.
789
- if field == "content":
790
- index_fieldname = ""
791
- else:
792
- index_fieldname = "%s:" % connections[
793
- self._using
794
- ].get_unified_index().get_index_fieldname(field)
795
-
796
- filter_types = {
797
- "content": "%s",
798
- "contains": "*%s*",
799
- "endswith": "*%s",
800
- "startswith": "%s*",
801
- "exact": "%s",
802
- "gt": "{%s TO *}",
803
- "gte": "[%s TO *]",
804
- "lt": "{* TO %s}",
805
- "lte": "[* TO %s]",
806
- "fuzzy": "%s~",
807
- }
808
-
809
- if value.post_process is False:
810
- query_frag = prepared_value
811
- else:
812
- if filter_type in [
813
- "content",
814
- "contains",
815
- "startswith",
816
- "endswith",
817
- "fuzzy",
818
- ]:
819
- if value.input_type_name == "exact":
820
- query_frag = prepared_value
821
- else:
822
- # Iterate over terms & incorportate the converted form of each into the query.
823
- terms = []
824
-
825
- for possible_value in prepared_value.split(" "):
826
- terms.append(
827
- filter_types[filter_type]
828
- % self.backend.conn._from_python(possible_value)
829
- )
830
-
831
- if len(terms) == 1:
832
- query_frag = terms[0]
833
- else:
834
- query_frag = "(%s)" % " AND ".join(terms)
835
- elif filter_type == "in":
836
- in_options = []
837
-
838
- if not prepared_value:
839
- query_frag = "(!*:*)"
840
- else:
841
- for possible_value in prepared_value:
842
- in_options.append(
843
- '"%s"' % self.backend.conn._from_python(possible_value)
844
- )
845
-
846
- query_frag = "(%s)" % " OR ".join(in_options)
847
- elif filter_type == "range":
848
- start = self.backend.conn._from_python(prepared_value[0])
849
- end = self.backend.conn._from_python(prepared_value[1])
850
- query_frag = '["%s" TO "%s"]' % (start, end)
851
- elif filter_type == "exact":
852
- if value.input_type_name == "exact":
853
- query_frag = prepared_value
854
- else:
855
- prepared_value = Exact(prepared_value).prepare(self)
856
- query_frag = filter_types[filter_type] % prepared_value
857
- else:
858
- if value.input_type_name != "exact":
859
- prepared_value = Exact(prepared_value).prepare(self)
860
-
861
- query_frag = filter_types[filter_type] % prepared_value
862
-
863
- if len(query_frag) and not isinstance(value, Raw):
864
- if not query_frag.startswith("(") and not query_frag.endswith(")"):
865
- query_frag = "(%s)" % query_frag
866
-
867
- return "%s%s" % (index_fieldname, query_frag)
868
-
869
- def build_alt_parser_query(self, parser_name, query_string="", **kwargs):
870
- if query_string:
871
- query_string = Clean(query_string).prepare(self)
872
-
873
- kwarg_bits = []
874
-
875
- for key in sorted(kwargs.keys()):
876
- if isinstance(kwargs[key], str) and " " in kwargs[key]:
877
- kwarg_bits.append("%s='%s'" % (key, kwargs[key]))
878
- else:
879
- kwarg_bits.append("%s=%s" % (key, kwargs[key]))
880
-
881
- return '_query_:"{!%s %s}%s"' % (
882
- parser_name,
883
- Clean(" ".join(kwarg_bits)),
884
- query_string,
885
- )
886
-
887
- def build_params(self, spelling_query=None, **kwargs):
888
- search_kwargs = {
889
- "start_offset": self.start_offset,
890
- "result_class": self.result_class,
891
- }
892
- order_by_list = None
893
-
894
- if self.order_by:
895
- if order_by_list is None:
896
- order_by_list = []
897
-
898
- for order_by in self.order_by:
899
- if order_by.startswith("-"):
900
- order_by_list.append("%s desc" % order_by[1:])
901
- else:
902
- order_by_list.append("%s asc" % order_by)
903
-
904
- search_kwargs["sort_by"] = ", ".join(order_by_list)
905
-
906
- if self.date_facets:
907
- search_kwargs["date_facets"] = self.date_facets
908
-
909
- if self.distance_point:
910
- search_kwargs["distance_point"] = self.distance_point
911
-
912
- if self.dwithin:
913
- search_kwargs["dwithin"] = self.dwithin
914
-
915
- if self.end_offset is not None:
916
- search_kwargs["end_offset"] = self.end_offset
917
-
918
- if self.facets:
919
- search_kwargs["facets"] = self.facets
920
-
921
- if self.fields:
922
- search_kwargs["fields"] = self.fields
923
-
924
- if self.highlight:
925
- search_kwargs["highlight"] = self.highlight
926
-
927
- if self.models:
928
- search_kwargs["models"] = self.models
929
-
930
- if self.narrow_queries:
931
- search_kwargs["narrow_queries"] = self.narrow_queries
932
-
933
- if self.query_facets:
934
- search_kwargs["query_facets"] = self.query_facets
935
-
936
- if self.within:
937
- search_kwargs["within"] = self.within
938
-
939
- if spelling_query:
940
- search_kwargs["spelling_query"] = spelling_query
941
- elif self.spelling_query:
942
- search_kwargs["spelling_query"] = self.spelling_query
943
-
944
- if self.stats:
945
- search_kwargs["stats"] = self.stats
946
-
947
- return search_kwargs
948
-
949
- def run(self, spelling_query=None, **kwargs):
950
- """Builds and executes the query. Returns a list of search results."""
951
- final_query = self.build_query()
952
- search_kwargs = self.build_params(spelling_query, **kwargs)
953
-
954
- if kwargs:
955
- search_kwargs.update(kwargs)
956
-
957
- results = self.backend.search(final_query, **search_kwargs)
958
-
959
- self._results = results.get("results", [])
960
- self._hit_count = results.get("hits", 0)
961
- self._facet_counts = self.post_process_facets(results)
962
- self._stats = results.get("stats", {})
963
- self._spelling_suggestion = results.get("spelling_suggestion", None)
964
-
965
- def run_mlt(self, **kwargs):
966
- """Builds and executes the query. Returns a list of search results."""
967
- if self._more_like_this is False or self._mlt_instance is None:
968
- raise MoreLikeThisError(
969
- "No instance was provided to determine 'More Like This' results."
970
- )
971
-
972
- additional_query_string = self.build_query()
973
- search_kwargs = {
974
- "start_offset": self.start_offset,
975
- "result_class": self.result_class,
976
- "models": self.models,
977
- }
978
-
979
- if self.end_offset is not None:
980
- search_kwargs["end_offset"] = self.end_offset - self.start_offset
981
-
982
- results = self.backend.more_like_this(
983
- self._mlt_instance, additional_query_string, **search_kwargs
984
- )
985
- self._results = results.get("results", [])
986
- self._hit_count = results.get("hits", 0)
987
-
988
-
989
- class SolrEngine(BaseEngine):
990
- backend = SolrSearchBackend
991
- query = SolrSearchQuery