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,1141 +0,0 @@
1
- import re
2
- import warnings
3
- from datetime import datetime, timedelta
4
-
5
- from django.conf import settings
6
- from django.core.exceptions import ImproperlyConfigured
7
-
8
- from ... import haystack
9
- from ...haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
10
- from ...haystack.constants import (
11
- ALL_FIELD,
12
- DEFAULT_OPERATOR,
13
- DJANGO_CT,
14
- DJANGO_ID,
15
- FUZZY_MAX_EXPANSIONS,
16
- FUZZY_MIN_SIM,
17
- ID,
18
- )
19
- from ...haystack.exceptions import MissingDependency, MoreLikeThisError, SkipDocument
20
- from ...haystack.inputs import Clean, Exact, PythonData, Raw
21
- from ...haystack.models import SearchResult
22
- from ...haystack.utils import get_identifier, get_model_ct
23
- from ...haystack.utils import log as logging
24
- from ...haystack.utils.app_loading import haystack_get_model
25
-
26
- try:
27
- import elasticsearch
28
-
29
- if (1, 0, 0) <= elasticsearch.__version__ < (2, 0, 0):
30
- warnings.warn(
31
- "ElasticSearch 1.x support deprecated, will be removed in 4.0",
32
- DeprecationWarning,
33
- )
34
-
35
- try:
36
- # let's try this, for elasticsearch > 1.7.0
37
- from elasticsearch.helpers import bulk
38
- except ImportError:
39
- # let's try this, for elasticsearch <= 1.7.0
40
- from elasticsearch.helpers import bulk_index as bulk
41
- from elasticsearch.exceptions import NotFoundError
42
- except ImportError:
43
- raise MissingDependency(
44
- "The 'elasticsearch' backend requires the installation of 'elasticsearch'. Please refer to the documentation."
45
- )
46
-
47
-
48
- DATETIME_REGEX = re.compile(
49
- r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T"
50
- r"(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(\.\d+)?$"
51
- )
52
-
53
-
54
- class ElasticsearchSearchBackend(BaseSearchBackend):
55
- # Word reserved by Elasticsearch for special use.
56
- RESERVED_WORDS = ("AND", "NOT", "OR", "TO")
57
-
58
- # Characters reserved by Elasticsearch for special use.
59
- # The '\\' must come first, so as not to overwrite the other slash replacements.
60
- RESERVED_CHARACTERS = (
61
- "\\",
62
- "+",
63
- "-",
64
- "&&",
65
- "||",
66
- "!",
67
- "(",
68
- ")",
69
- "{",
70
- "}",
71
- "[",
72
- "]",
73
- "^",
74
- '"',
75
- "~",
76
- "*",
77
- "?",
78
- ":",
79
- "/",
80
- )
81
-
82
- # Settings to add an n-gram & edge n-gram analyzer.
83
- DEFAULT_SETTINGS = {
84
- "settings": {
85
- "analysis": {
86
- "analyzer": {
87
- "ngram_analyzer": {
88
- "type": "custom",
89
- "tokenizer": "standard",
90
- "filter": ["haystack_ngram", "lowercase"],
91
- },
92
- "edgengram_analyzer": {
93
- "type": "custom",
94
- "tokenizer": "standard",
95
- "filter": ["haystack_edgengram", "lowercase"],
96
- },
97
- },
98
- "tokenizer": {
99
- "haystack_ngram_tokenizer": {
100
- "type": "nGram",
101
- "min_gram": 3,
102
- "max_gram": 15,
103
- },
104
- "haystack_edgengram_tokenizer": {
105
- "type": "edgeNGram",
106
- "min_gram": 2,
107
- "max_gram": 15,
108
- "side": "front",
109
- },
110
- },
111
- "filter": {
112
- "haystack_ngram": {"type": "nGram", "min_gram": 3, "max_gram": 15},
113
- "haystack_edgengram": {
114
- "type": "edgeNGram",
115
- "min_gram": 2,
116
- "max_gram": 15,
117
- },
118
- },
119
- }
120
- }
121
- }
122
-
123
- def __init__(self, connection_alias, **connection_options):
124
- super().__init__(connection_alias, **connection_options)
125
-
126
- if "URL" not in connection_options:
127
- raise ImproperlyConfigured(
128
- "You must specify a 'URL' in your settings for connection '%s'."
129
- % connection_alias
130
- )
131
-
132
- if "INDEX_NAME" not in connection_options:
133
- raise ImproperlyConfigured(
134
- "You must specify a 'INDEX_NAME' in your settings for connection '%s'."
135
- % connection_alias
136
- )
137
-
138
- self.conn = elasticsearch.Elasticsearch(
139
- connection_options["URL"],
140
- timeout=self.timeout,
141
- **connection_options.get("KWARGS", {}),
142
- )
143
- self.index_name = connection_options["INDEX_NAME"]
144
- self.log = logging.getLogger("haystack")
145
- self.setup_complete = False
146
- self.existing_mapping = {}
147
-
148
- def _get_doc_type_option(self):
149
- return {
150
- "doc_type": "modelresult",
151
- }
152
-
153
- def _get_current_mapping(self, field_mapping):
154
- return {"modelresult": {"properties": field_mapping}}
155
-
156
- def setup(self):
157
- """
158
- Defers loading until needed.
159
- """
160
- # Get the existing mapping & cache it. We'll compare it
161
- # during the ``update`` & if it doesn't match, we'll put the new
162
- # mapping.
163
- try:
164
- self.existing_mapping = self.conn.indices.get_mapping(index=self.index_name)
165
- except NotFoundError:
166
- pass
167
- except Exception:
168
- if not self.silently_fail:
169
- raise
170
-
171
- unified_index = haystack.connections[self.connection_alias].get_unified_index()
172
- self.content_field_name, field_mapping = self.build_schema(
173
- unified_index.all_searchfields()
174
- )
175
- current_mapping = self._get_current_mapping(field_mapping)
176
-
177
- if current_mapping != self.existing_mapping:
178
- try:
179
- # Make sure the index is there first.
180
- self.conn.indices.create(
181
- index=self.index_name, body=self.DEFAULT_SETTINGS, ignore=400
182
- )
183
- self.conn.indices.put_mapping(
184
- index=self.index_name,
185
- body=current_mapping,
186
- **self._get_doc_type_option(),
187
- )
188
- self.existing_mapping = current_mapping
189
- except Exception:
190
- if not self.silently_fail:
191
- raise
192
-
193
- self.setup_complete = True
194
-
195
- def _prepare_object(self, index, obj):
196
- return index.full_prepare(obj)
197
-
198
- def update(self, index, iterable, commit=True):
199
- if not self.setup_complete:
200
- try:
201
- self.setup()
202
- except elasticsearch.TransportError as e:
203
- if not self.silently_fail:
204
- raise
205
-
206
- self.log.error(
207
- "Failed to add documents to Elasticsearch: %s", e, exc_info=True
208
- )
209
- return
210
-
211
- prepped_docs = []
212
-
213
- for obj in iterable:
214
- try:
215
- prepped_data = self._prepare_object(index, obj)
216
- final_data = {}
217
-
218
- # Convert the data to make sure it's happy.
219
- for key, value in prepped_data.items():
220
- final_data[key] = self._from_python(value)
221
- final_data["_id"] = final_data[ID]
222
-
223
- prepped_docs.append(final_data)
224
- except SkipDocument:
225
- self.log.debug("Indexing for object `%s` skipped", obj)
226
- except elasticsearch.TransportError as e:
227
- if not self.silently_fail:
228
- raise
229
-
230
- # We'll log the object identifier but won't include the actual object
231
- # to avoid the possibility of that generating encoding errors while
232
- # processing the log message:
233
- self.log.error(
234
- "%s while preparing object for update" % e.__class__.__name__,
235
- exc_info=True,
236
- extra={"data": {"index": index, "object": get_identifier(obj)}},
237
- )
238
-
239
- bulk(
240
- self.conn,
241
- prepped_docs,
242
- index=self.index_name,
243
- **self._get_doc_type_option(),
244
- )
245
-
246
- if commit:
247
- self.conn.indices.refresh(index=self.index_name)
248
-
249
- def remove(self, obj_or_string, commit=True):
250
- doc_id = get_identifier(obj_or_string)
251
-
252
- if not self.setup_complete:
253
- try:
254
- self.setup()
255
- except elasticsearch.TransportError as e:
256
- if not self.silently_fail:
257
- raise
258
-
259
- self.log.error(
260
- "Failed to remove document '%s' from Elasticsearch: %s",
261
- doc_id,
262
- e,
263
- exc_info=True,
264
- )
265
- return
266
-
267
- try:
268
- self.conn.delete(
269
- index=self.index_name,
270
- id=doc_id,
271
- ignore=404,
272
- **self._get_doc_type_option(),
273
- )
274
-
275
- if commit:
276
- self.conn.indices.refresh(index=self.index_name)
277
- except elasticsearch.TransportError as e:
278
- if not self.silently_fail:
279
- raise
280
-
281
- self.log.error(
282
- "Failed to remove document '%s' from Elasticsearch: %s",
283
- doc_id,
284
- e,
285
- exc_info=True,
286
- )
287
-
288
- def clear(self, models=None, commit=True):
289
- # We actually don't want to do this here, as mappings could be
290
- # very different.
291
- # if not self.setup_complete:
292
- # self.setup()
293
-
294
- if models is not None:
295
- assert isinstance(models, (list, tuple))
296
-
297
- try:
298
- if models is None:
299
- self.conn.indices.delete(index=self.index_name, ignore=404)
300
- self.setup_complete = False
301
- self.existing_mapping = {}
302
- else:
303
- models_to_delete = []
304
-
305
- for model in models:
306
- models_to_delete.append("%s:%s" % (DJANGO_CT, get_model_ct(model)))
307
-
308
- # Delete by query in Elasticsearch asssumes you're dealing with
309
- # a ``query`` root object. :/
310
- query = {
311
- "query": {"query_string": {"query": " OR ".join(models_to_delete)}}
312
- }
313
- self.conn.delete_by_query(
314
- index=self.index_name,
315
- body=query,
316
- **self._get_doc_type_option(),
317
- )
318
- except elasticsearch.TransportError as e:
319
- if not self.silently_fail:
320
- raise
321
-
322
- if models is not None:
323
- self.log.error(
324
- "Failed to clear Elasticsearch index of models '%s': %s",
325
- ",".join(models_to_delete),
326
- e,
327
- exc_info=True,
328
- )
329
- else:
330
- self.log.error(
331
- "Failed to clear Elasticsearch index: %s", e, exc_info=True
332
- )
333
-
334
- def build_search_kwargs(
335
- self,
336
- query_string,
337
- sort_by=None,
338
- start_offset=0,
339
- end_offset=None,
340
- fields="",
341
- highlight=False,
342
- facets=None,
343
- date_facets=None,
344
- query_facets=None,
345
- narrow_queries=None,
346
- spelling_query=None,
347
- within=None,
348
- dwithin=None,
349
- distance_point=None,
350
- models=None,
351
- limit_to_registered_models=None,
352
- result_class=None,
353
- **extra_kwargs
354
- ):
355
- index = haystack.connections[self.connection_alias].get_unified_index()
356
- content_field = index.document_field
357
-
358
- if query_string == "*:*":
359
- kwargs = {"query": {"match_all": {}}}
360
- else:
361
- kwargs = {
362
- "query": {
363
- "query_string": {
364
- "default_field": content_field,
365
- "default_operator": DEFAULT_OPERATOR,
366
- "query": query_string,
367
- "analyze_wildcard": True,
368
- "auto_generate_phrase_queries": True,
369
- "fuzzy_min_sim": FUZZY_MIN_SIM,
370
- "fuzzy_max_expansions": FUZZY_MAX_EXPANSIONS,
371
- }
372
- }
373
- }
374
-
375
- # so far, no filters
376
- filters = []
377
-
378
- if fields:
379
- if isinstance(fields, (list, set)):
380
- fields = " ".join(fields)
381
-
382
- kwargs["fields"] = fields
383
-
384
- if sort_by is not None:
385
- order_list = []
386
- for field, direction in sort_by:
387
- if field == "distance" and distance_point:
388
- # Do the geo-enabled sort.
389
- lng, lat = distance_point["point"].coords
390
- sort_kwargs = {
391
- "_geo_distance": {
392
- distance_point["field"]: [lng, lat],
393
- "order": direction,
394
- "unit": "km",
395
- }
396
- }
397
- else:
398
- if field == "distance":
399
- warnings.warn(
400
- "In order to sort by distance, you must call the '.distance(...)' method."
401
- )
402
-
403
- # Regular sorting.
404
- sort_kwargs = {field: {"order": direction}}
405
-
406
- order_list.append(sort_kwargs)
407
-
408
- kwargs["sort"] = order_list
409
-
410
- # From/size offsets don't seem to work right in Elasticsearch's DSL. :/
411
- # if start_offset is not None:
412
- # kwargs['from'] = start_offset
413
-
414
- # if end_offset is not None:
415
- # kwargs['size'] = end_offset - start_offset
416
-
417
- if highlight:
418
- # `highlight` can either be True or a dictionary containing custom parameters
419
- # which will be passed to the backend and may override our default settings:
420
-
421
- kwargs["highlight"] = {"fields": {content_field: {"store": "yes"}}}
422
-
423
- if isinstance(highlight, dict):
424
- kwargs["highlight"].update(highlight)
425
-
426
- if self.include_spelling:
427
- kwargs["suggest"] = {
428
- "suggest": {
429
- "text": spelling_query or query_string,
430
- "term": {
431
- # Using content_field here will result in suggestions of stemmed words.
432
- "field": ALL_FIELD,
433
- },
434
- }
435
- }
436
-
437
- if narrow_queries is None:
438
- narrow_queries = set()
439
-
440
- if facets is not None:
441
- kwargs.setdefault("facets", {})
442
-
443
- for facet_fieldname, extra_options in facets.items():
444
- facet_options = {"terms": {"field": facet_fieldname, "size": 100}}
445
- # Special cases for options applied at the facet level (not the terms level).
446
- if extra_options.pop("global_scope", False):
447
- # Renamed "global_scope" since "global" is a python keyword.
448
- facet_options["global"] = True
449
- if "facet_filter" in extra_options:
450
- facet_options["facet_filter"] = extra_options.pop("facet_filter")
451
- facet_options["terms"].update(extra_options)
452
- kwargs["facets"][facet_fieldname] = facet_options
453
-
454
- if date_facets is not None:
455
- kwargs.setdefault("facets", {})
456
-
457
- for facet_fieldname, value in date_facets.items():
458
- # Need to detect on gap_by & only add amount if it's more than one.
459
- interval = value.get("gap_by").lower()
460
-
461
- # Need to detect on amount (can't be applied on months or years).
462
- if value.get("gap_amount", 1) != 1 and interval not in (
463
- "month",
464
- "year",
465
- ):
466
- # Just the first character is valid for use.
467
- interval = "%s%s" % (value["gap_amount"], interval[:1])
468
-
469
- kwargs["facets"][facet_fieldname] = {
470
- "date_histogram": {"field": facet_fieldname, "interval": interval},
471
- "facet_filter": {
472
- "range": {
473
- facet_fieldname: {
474
- "from": self._from_python(value.get("start_date")),
475
- "to": self._from_python(value.get("end_date")),
476
- }
477
- }
478
- },
479
- }
480
-
481
- if query_facets is not None:
482
- kwargs.setdefault("facets", {})
483
-
484
- for facet_fieldname, value in query_facets:
485
- kwargs["facets"][facet_fieldname] = {
486
- "query": {"query_string": {"query": value}}
487
- }
488
-
489
- if limit_to_registered_models is None:
490
- limit_to_registered_models = getattr(
491
- settings, "HAYSTACK_LIMIT_TO_REGISTERED_MODELS", True
492
- )
493
-
494
- if models and len(models):
495
- model_choices = sorted(get_model_ct(model) for model in models)
496
- elif limit_to_registered_models:
497
- # Using narrow queries, limit the results to only models handled
498
- # with the current routers.
499
- model_choices = self.build_models_list()
500
- else:
501
- model_choices = []
502
-
503
- if len(model_choices) > 0:
504
- filters.append({"terms": {DJANGO_CT: model_choices}})
505
-
506
- for q in narrow_queries:
507
- filters.append(
508
- {"fquery": {"query": {"query_string": {"query": q}}, "_cache": True}}
509
- )
510
-
511
- if within is not None:
512
- from ...haystack.utils.geo import generate_bounding_box
513
-
514
- ((south, west), (north, east)) = generate_bounding_box(
515
- within["point_1"], within["point_2"]
516
- )
517
- within_filter = {
518
- "geo_bounding_box": {
519
- within["field"]: {
520
- "top_left": {"lat": north, "lon": west},
521
- "bottom_right": {"lat": south, "lon": east},
522
- }
523
- }
524
- }
525
- filters.append(within_filter)
526
-
527
- if dwithin is not None:
528
- lng, lat = dwithin["point"].coords
529
-
530
- # NB: the 1.0.0 release of elasticsearch introduce an
531
- # incompatible change on the distance filter formating
532
- if elasticsearch.VERSION >= (1, 0, 0):
533
- distance = "%(dist).6f%(unit)s" % {
534
- "dist": dwithin["distance"].km,
535
- "unit": "km",
536
- }
537
- else:
538
- distance = dwithin["distance"].km
539
-
540
- dwithin_filter = {
541
- "geo_distance": {
542
- "distance": distance,
543
- dwithin["field"]: {"lat": lat, "lon": lng},
544
- }
545
- }
546
- filters.append(dwithin_filter)
547
-
548
- # if we want to filter, change the query type to filteres
549
- if filters:
550
- kwargs["query"] = {"filtered": {"query": kwargs.pop("query")}}
551
- if len(filters) == 1:
552
- kwargs["query"]["filtered"]["filter"] = filters[0]
553
- else:
554
- kwargs["query"]["filtered"]["filter"] = {"bool": {"must": filters}}
555
-
556
- if extra_kwargs:
557
- kwargs.update(extra_kwargs)
558
-
559
- return kwargs
560
-
561
- @log_query
562
- def search(self, query_string, **kwargs):
563
- if len(query_string) == 0:
564
- return {"results": [], "hits": 0}
565
-
566
- if not self.setup_complete:
567
- self.setup()
568
-
569
- search_kwargs = self.build_search_kwargs(query_string, **kwargs)
570
- search_kwargs["from"] = kwargs.get("start_offset", 0)
571
-
572
- order_fields = set()
573
- for order in search_kwargs.get("sort", []):
574
- for key in order.keys():
575
- order_fields.add(key)
576
-
577
- geo_sort = "_geo_distance" in order_fields
578
-
579
- end_offset = kwargs.get("end_offset")
580
- start_offset = kwargs.get("start_offset", 0)
581
- if end_offset is not None and end_offset > start_offset:
582
- search_kwargs["size"] = end_offset - start_offset
583
-
584
- try:
585
- raw_results = self.conn.search(
586
- body=search_kwargs,
587
- index=self.index_name,
588
- _source=True,
589
- **self._get_doc_type_option(),
590
- )
591
- except elasticsearch.TransportError as e:
592
- if not self.silently_fail:
593
- raise
594
-
595
- self.log.error(
596
- "Failed to query Elasticsearch using '%s': %s",
597
- query_string,
598
- e,
599
- exc_info=True,
600
- )
601
- raw_results = {}
602
-
603
- return self._process_results(
604
- raw_results,
605
- highlight=kwargs.get("highlight"),
606
- result_class=kwargs.get("result_class", SearchResult),
607
- distance_point=kwargs.get("distance_point"),
608
- geo_sort=geo_sort,
609
- )
610
-
611
- def more_like_this(
612
- self,
613
- model_instance,
614
- additional_query_string=None,
615
- start_offset=0,
616
- end_offset=None,
617
- models=None,
618
- limit_to_registered_models=None,
619
- result_class=None,
620
- **kwargs
621
- ):
622
- from ...haystack import connections
623
-
624
- if not self.setup_complete:
625
- self.setup()
626
-
627
- # Deferred models will have a different class ("RealClass_Deferred_fieldname")
628
- # which won't be in our registry:
629
- model_klass = model_instance._meta.concrete_model
630
-
631
- index = (
632
- connections[self.connection_alias]
633
- .get_unified_index()
634
- .get_index(model_klass)
635
- )
636
- field_name = index.get_content_field()
637
- params = {}
638
-
639
- if start_offset is not None:
640
- params["search_from"] = start_offset
641
-
642
- if end_offset is not None:
643
- params["search_size"] = end_offset - start_offset
644
-
645
- doc_id = get_identifier(model_instance)
646
-
647
- try:
648
- raw_results = self.conn.mlt(
649
- index=self.index_name,
650
- id=doc_id,
651
- mlt_fields=[field_name],
652
- **self._get_doc_type_option(),
653
- **params,
654
- )
655
- except elasticsearch.TransportError as e:
656
- if not self.silently_fail:
657
- raise
658
-
659
- self.log.error(
660
- "Failed to fetch More Like This from Elasticsearch for document '%s': %s",
661
- doc_id,
662
- e,
663
- exc_info=True,
664
- )
665
- raw_results = {}
666
-
667
- return self._process_results(raw_results, result_class=result_class)
668
-
669
- def _process_hits(self, raw_results):
670
- return raw_results.get("hits", {}).get("total", 0)
671
-
672
- def _process_results(
673
- self,
674
- raw_results,
675
- highlight=False,
676
- result_class=None,
677
- distance_point=None,
678
- geo_sort=False,
679
- ):
680
- from ...haystack import connections
681
-
682
- results = []
683
- hits = self._process_hits(raw_results)
684
- facets = {}
685
- spelling_suggestion = None
686
-
687
- if result_class is None:
688
- result_class = SearchResult
689
-
690
- if self.include_spelling and "suggest" in raw_results:
691
- raw_suggest = raw_results["suggest"].get("suggest")
692
- if raw_suggest:
693
- spelling_suggestion = " ".join(
694
- [
695
- word["text"]
696
- if len(word["options"]) == 0
697
- else word["options"][0]["text"]
698
- for word in raw_suggest
699
- ]
700
- )
701
-
702
- if "facets" in raw_results:
703
- facets = {"fields": {}, "dates": {}, "queries": {}}
704
-
705
- # ES can return negative timestamps for pre-1970 data. Handle it.
706
- def from_timestamp(tm):
707
- if tm >= 0:
708
- return datetime.utcfromtimestamp(tm)
709
- else:
710
- return datetime(1970, 1, 1) + timedelta(seconds=tm)
711
-
712
- for facet_fieldname, facet_info in raw_results["facets"].items():
713
- if facet_info.get("_type", "terms") == "terms":
714
- facets["fields"][facet_fieldname] = [
715
- (individual["term"], individual["count"])
716
- for individual in facet_info["terms"]
717
- ]
718
- elif facet_info.get("_type", "terms") == "date_histogram":
719
- # Elasticsearch provides UTC timestamps with an extra three
720
- # decimals of precision, which datetime barfs on.
721
- facets["dates"][facet_fieldname] = [
722
- (from_timestamp(individual["time"] / 1000), individual["count"])
723
- for individual in facet_info["entries"]
724
- ]
725
- elif facet_info.get("_type", "terms") == "query":
726
- facets["queries"][facet_fieldname] = facet_info["count"]
727
-
728
- unified_index = connections[self.connection_alias].get_unified_index()
729
- indexed_models = unified_index.get_indexed_models()
730
- content_field = unified_index.document_field
731
-
732
- for raw_result in raw_results.get("hits", {}).get("hits", []):
733
- source = raw_result["_source"]
734
- app_label, model_name = source[DJANGO_CT].split(".")
735
- additional_fields = {}
736
- model = haystack_get_model(app_label, model_name)
737
-
738
- if model and model in indexed_models:
739
- index = source and unified_index.get_index(model)
740
- for key, value in source.items():
741
- string_key = str(key)
742
-
743
- if string_key in index.fields and hasattr(
744
- index.fields[string_key], "convert"
745
- ):
746
- additional_fields[string_key] = index.fields[
747
- string_key
748
- ].convert(value)
749
- else:
750
- additional_fields[string_key] = self._to_python(value)
751
-
752
- del additional_fields[DJANGO_CT]
753
- del additional_fields[DJANGO_ID]
754
-
755
- if "highlight" in raw_result:
756
- additional_fields["highlighted"] = raw_result["highlight"].get(
757
- content_field, ""
758
- )
759
-
760
- if distance_point:
761
- additional_fields["_point_of_origin"] = distance_point
762
-
763
- if geo_sort and raw_result.get("sort"):
764
- from django.contrib.gis.measure import Distance
765
-
766
- additional_fields["_distance"] = Distance(
767
- km=float(raw_result["sort"][0])
768
- )
769
- else:
770
- additional_fields["_distance"] = None
771
-
772
- result = result_class(
773
- app_label,
774
- model_name,
775
- source[DJANGO_ID],
776
- raw_result["_score"],
777
- **additional_fields,
778
- )
779
- results.append(result)
780
- else:
781
- hits -= 1
782
-
783
- return {
784
- "results": results,
785
- "hits": hits,
786
- "facets": facets,
787
- "spelling_suggestion": spelling_suggestion,
788
- }
789
-
790
- def _get_common_mapping(self):
791
- return {
792
- DJANGO_CT: {
793
- "type": "string",
794
- "index": "not_analyzed",
795
- "include_in_all": False,
796
- },
797
- DJANGO_ID: {
798
- "type": "string",
799
- "index": "not_analyzed",
800
- "include_in_all": False,
801
- },
802
- }
803
-
804
- def build_schema(self, fields):
805
- content_field_name = ""
806
- mapping = self._get_common_mapping()
807
-
808
- for _, field_class in fields.items():
809
- field_mapping = FIELD_MAPPINGS.get(
810
- field_class.field_type, DEFAULT_FIELD_MAPPING
811
- ).copy()
812
- if field_class.boost != 1.0:
813
- field_mapping["boost"] = field_class.boost
814
-
815
- if field_class.document is True:
816
- content_field_name = field_class.index_fieldname
817
-
818
- # Do this last to override `text` fields.
819
- if field_mapping["type"] == "string":
820
- if field_class.indexed is False or hasattr(field_class, "facet_for"):
821
- field_mapping["index"] = "not_analyzed"
822
- del field_mapping["analyzer"]
823
-
824
- mapping[field_class.index_fieldname] = field_mapping
825
-
826
- return (content_field_name, mapping)
827
-
828
- def _iso_datetime(self, value):
829
- """
830
- If value appears to be something datetime-like, return it in ISO format.
831
-
832
- Otherwise, return None.
833
- """
834
- if hasattr(value, "strftime"):
835
- if hasattr(value, "hour"):
836
- return value.isoformat()
837
- else:
838
- return "%sT00:00:00" % value.isoformat()
839
-
840
- def _from_python(self, value):
841
- """Convert more Python data types to ES-understandable JSON."""
842
- iso = self._iso_datetime(value)
843
- if iso:
844
- return iso
845
- elif isinstance(value, bytes):
846
- # TODO: Be stricter.
847
- return str(value, errors="replace")
848
- elif isinstance(value, set):
849
- return list(value)
850
- return value
851
-
852
- def _to_python(self, value):
853
- """Convert values from ElasticSearch to native Python values."""
854
- if isinstance(value, (int, float, complex, list, tuple, bool)):
855
- return value
856
-
857
- if isinstance(value, str):
858
- possible_datetime = DATETIME_REGEX.search(value)
859
-
860
- if possible_datetime:
861
- date_values = possible_datetime.groupdict()
862
-
863
- for dk, dv in date_values.items():
864
- date_values[dk] = int(dv)
865
-
866
- return datetime(
867
- date_values["year"],
868
- date_values["month"],
869
- date_values["day"],
870
- date_values["hour"],
871
- date_values["minute"],
872
- date_values["second"],
873
- )
874
-
875
- try:
876
- # This is slightly gross but it's hard to tell otherwise what the
877
- # string's original type might have been. Be careful who you trust.
878
- converted_value = eval(value)
879
-
880
- # Try to handle most built-in types.
881
- if isinstance(
882
- converted_value, (int, list, tuple, set, dict, float, complex)
883
- ):
884
- return converted_value
885
- except Exception:
886
- # If it fails (SyntaxError or its ilk) or we don't trust it,
887
- # continue on.
888
- pass
889
-
890
- return value
891
-
892
-
893
- # DRL_FIXME: Perhaps move to something where, if none of these
894
- # match, call a custom method on the form that returns, per-backend,
895
- # the right type of storage?
896
- DEFAULT_FIELD_MAPPING = {"type": "string", "analyzer": "snowball"}
897
- FIELD_MAPPINGS = {
898
- "edge_ngram": {"type": "string", "analyzer": "edgengram_analyzer"},
899
- "ngram": {"type": "string", "analyzer": "ngram_analyzer"},
900
- "date": {"type": "date"},
901
- "datetime": {"type": "date"},
902
- "location": {"type": "geo_point"},
903
- "boolean": {"type": "boolean"},
904
- "float": {"type": "float"},
905
- "long": {"type": "long"},
906
- "integer": {"type": "long"},
907
- }
908
-
909
-
910
- # Sucks that this is almost an exact copy of what's in the Solr backend,
911
- # but we can't import due to dependencies.
912
- class ElasticsearchSearchQuery(BaseSearchQuery):
913
- def matching_all_fragment(self):
914
- return "*:*"
915
-
916
- def build_query_fragment(self, field, filter_type, value):
917
- from ...haystack import connections
918
-
919
- query_frag = ""
920
-
921
- if not hasattr(value, "input_type_name"):
922
- # Handle when we've got a ``ValuesListQuerySet``...
923
- if hasattr(value, "values_list"):
924
- value = list(value)
925
-
926
- if isinstance(value, str):
927
- # It's not an ``InputType``. Assume ``Clean``.
928
- value = Clean(value)
929
- else:
930
- value = PythonData(value)
931
-
932
- # Prepare the query using the InputType.
933
- prepared_value = value.prepare(self)
934
-
935
- if not isinstance(prepared_value, (set, list, tuple)):
936
- # Then convert whatever we get back to what pysolr wants if needed.
937
- prepared_value = self.backend._from_python(prepared_value)
938
-
939
- # 'content' is a special reserved word, much like 'pk' in
940
- # Django's ORM layer. It indicates 'no special field'.
941
- if field == "content":
942
- index_fieldname = ""
943
- else:
944
- index_fieldname = "%s:" % connections[
945
- self._using
946
- ].get_unified_index().get_index_fieldname(field)
947
-
948
- filter_types = {
949
- "content": "%s",
950
- "contains": "*%s*",
951
- "endswith": "*%s",
952
- "startswith": "%s*",
953
- "exact": "%s",
954
- "gt": "{%s TO *}",
955
- "gte": "[%s TO *]",
956
- "lt": "{* TO %s}",
957
- "lte": "[* TO %s]",
958
- "fuzzy": "%s~",
959
- }
960
-
961
- if value.post_process is False:
962
- query_frag = prepared_value
963
- else:
964
- if filter_type in [
965
- "content",
966
- "contains",
967
- "startswith",
968
- "endswith",
969
- "fuzzy",
970
- ]:
971
- if value.input_type_name == "exact":
972
- query_frag = prepared_value
973
- else:
974
- # Iterate over terms & incorportate the converted form of each into the query.
975
- terms = []
976
-
977
- if isinstance(prepared_value, str):
978
- for possible_value in prepared_value.split(" "):
979
- terms.append(
980
- filter_types[filter_type]
981
- % self.backend._from_python(possible_value)
982
- )
983
- else:
984
- terms.append(
985
- filter_types[filter_type]
986
- % self.backend._from_python(prepared_value)
987
- )
988
-
989
- if len(terms) == 1:
990
- query_frag = terms[0]
991
- else:
992
- query_frag = "(%s)" % " AND ".join(terms)
993
- elif filter_type == "in":
994
- in_options = []
995
-
996
- if not prepared_value:
997
- query_frag = "(!*:*)"
998
- else:
999
- for possible_value in prepared_value:
1000
- in_options.append(
1001
- '"%s"' % self.backend._from_python(possible_value)
1002
- )
1003
- query_frag = "(%s)" % " OR ".join(in_options)
1004
-
1005
- elif filter_type == "range":
1006
- start = self.backend._from_python(prepared_value[0])
1007
- end = self.backend._from_python(prepared_value[1])
1008
- query_frag = '["%s" TO "%s"]' % (start, end)
1009
- elif filter_type == "exact":
1010
- if value.input_type_name == "exact":
1011
- query_frag = prepared_value
1012
- else:
1013
- prepared_value = Exact(prepared_value).prepare(self)
1014
- query_frag = filter_types[filter_type] % prepared_value
1015
- else:
1016
- if value.input_type_name != "exact":
1017
- prepared_value = Exact(prepared_value).prepare(self)
1018
-
1019
- query_frag = filter_types[filter_type] % prepared_value
1020
-
1021
- if len(query_frag) and not isinstance(value, Raw):
1022
- if not query_frag.startswith("(") and not query_frag.endswith(")"):
1023
- query_frag = "(%s)" % query_frag
1024
-
1025
- return "%s%s" % (index_fieldname, query_frag)
1026
-
1027
- def build_alt_parser_query(self, parser_name, query_string="", **kwargs):
1028
- if query_string:
1029
- kwargs["v"] = query_string
1030
-
1031
- kwarg_bits = []
1032
-
1033
- for key in sorted(kwargs.keys()):
1034
- if isinstance(kwargs[key], str) and " " in kwargs[key]:
1035
- kwarg_bits.append("%s='%s'" % (key, kwargs[key]))
1036
- else:
1037
- kwarg_bits.append("%s=%s" % (key, kwargs[key]))
1038
-
1039
- return "{!%s %s}" % (parser_name, " ".join(kwarg_bits))
1040
-
1041
- def build_params(self, spelling_query=None, **kwargs):
1042
- search_kwargs = {
1043
- "start_offset": self.start_offset,
1044
- "result_class": self.result_class,
1045
- }
1046
- order_by_list = None
1047
-
1048
- if self.order_by:
1049
- if order_by_list is None:
1050
- order_by_list = []
1051
-
1052
- for field in self.order_by:
1053
- direction = "asc"
1054
- if field.startswith("-"):
1055
- direction = "desc"
1056
- field = field[1:]
1057
- order_by_list.append((field, direction))
1058
-
1059
- search_kwargs["sort_by"] = order_by_list
1060
-
1061
- if self.date_facets:
1062
- search_kwargs["date_facets"] = self.date_facets
1063
-
1064
- if self.distance_point:
1065
- search_kwargs["distance_point"] = self.distance_point
1066
-
1067
- if self.dwithin:
1068
- search_kwargs["dwithin"] = self.dwithin
1069
-
1070
- if self.end_offset is not None:
1071
- search_kwargs["end_offset"] = self.end_offset
1072
-
1073
- if self.facets:
1074
- search_kwargs["facets"] = self.facets
1075
-
1076
- if self.fields:
1077
- search_kwargs["fields"] = self.fields
1078
-
1079
- if self.highlight:
1080
- search_kwargs["highlight"] = self.highlight
1081
-
1082
- if self.models:
1083
- search_kwargs["models"] = self.models
1084
-
1085
- if self.narrow_queries:
1086
- search_kwargs["narrow_queries"] = self.narrow_queries
1087
-
1088
- if self.query_facets:
1089
- search_kwargs["query_facets"] = self.query_facets
1090
-
1091
- if self.within:
1092
- search_kwargs["within"] = self.within
1093
-
1094
- if spelling_query:
1095
- search_kwargs["spelling_query"] = spelling_query
1096
- elif self.spelling_query:
1097
- search_kwargs["spelling_query"] = self.spelling_query
1098
-
1099
- return search_kwargs
1100
-
1101
- def run(self, spelling_query=None, **kwargs):
1102
- """Builds and executes the query. Returns a list of search results."""
1103
- final_query = self.build_query()
1104
- search_kwargs = self.build_params(spelling_query, **kwargs)
1105
-
1106
- if kwargs:
1107
- search_kwargs.update(kwargs)
1108
-
1109
- results = self.backend.search(final_query, **search_kwargs)
1110
- self._results = results.get("results", [])
1111
- self._hit_count = results.get("hits", 0)
1112
- self._facet_counts = self.post_process_facets(results)
1113
- self._spelling_suggestion = results.get("spelling_suggestion", None)
1114
-
1115
- def run_mlt(self, **kwargs):
1116
- """Builds and executes the query. Returns a list of search results."""
1117
- if self._more_like_this is False or self._mlt_instance is None:
1118
- raise MoreLikeThisError(
1119
- "No instance was provided to determine 'More Like This' results."
1120
- )
1121
-
1122
- additional_query_string = self.build_query()
1123
- search_kwargs = {
1124
- "start_offset": self.start_offset,
1125
- "result_class": self.result_class,
1126
- "models": self.models,
1127
- }
1128
-
1129
- if self.end_offset is not None:
1130
- search_kwargs["end_offset"] = self.end_offset - self.start_offset
1131
-
1132
- results = self.backend.more_like_this(
1133
- self._mlt_instance, additional_query_string, **search_kwargs
1134
- )
1135
- self._results = results.get("results", [])
1136
- self._hit_count = results.get("hits", 0)
1137
-
1138
-
1139
- class ElasticsearchSearchEngine(BaseEngine):
1140
- backend = ElasticsearchSearchBackend
1141
- query = ElasticsearchSearchQuery