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.
- codex/db.py +119 -0
- codex/librarian/covers/purge.py +1 -1
- codex/librarian/importer/const.py +7 -1
- codex/librarian/importer/create_fks.py +5 -2
- codex/librarian/importer/importerd.py +17 -12
- codex/librarian/importer/moved.py +44 -13
- codex/librarian/importer/tasks.py +2 -0
- codex/librarian/janitor/cleanup.py +21 -8
- codex/librarian/janitor/integrity.py +369 -0
- codex/librarian/janitor/janitor.py +50 -15
- codex/librarian/janitor/latest_version.py +2 -1
- codex/librarian/janitor/status.py +5 -0
- codex/librarian/janitor/tasks.py +37 -0
- codex/librarian/janitor/update.py +1 -1
- codex/librarian/librariand.py +32 -24
- codex/librarian/search/optimize.py +43 -0
- codex/librarian/search/remove.py +18 -46
- codex/librarian/search/searchd.py +14 -17
- codex/librarian/search/status.py +2 -1
- codex/librarian/search/tasks.py +4 -9
- codex/librarian/search/update.py +223 -301
- codex/librarian/watchdog/observers.py +2 -1
- codex/migrations/0029_comicfts.py +58 -0
- codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +198 -0
- codex/models/admin.py +15 -6
- codex/models/base.py +9 -0
- codex/models/bookmark.py +3 -3
- codex/models/comic.py +68 -16
- codex/models/functions.py +59 -2
- codex/models/groups.py +6 -1
- codex/models/library.py +1 -3
- codex/models/paths.py +9 -3
- codex/serializers/browser/page.py +2 -0
- codex/settings/settings.py +16 -17
- codex/signals/django_signals.py +4 -10
- codex/startup.py +1 -35
- codex/static_root/assets/{VCheckbox-Cko1sQK-.170726b3b8a1.js → VCheckbox-CWiqcZ7a.3feebd47daab.js} +1 -1
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-Cko1sQK-.js → VCheckbox-CWiqcZ7a.js} +1 -1
- codex/static_root/assets/VCheckbox-CWiqcZ7a.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWiqcZ7a.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js → VCheckboxBtn-Dk3iB62X.bc66d5351baa.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.js → VCheckboxBtn-Dk3iB62X.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.gz +0 -0
- codex/static_root/assets/{VCombobox-Bjp6lffE.js → VCombobox-D1_KU27A.b1d30587a973.js} +1 -1
- codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.gz +0 -0
- codex/static_root/assets/{VCombobox-Bjp6lffE.caf59fba486e.js → VCombobox-D1_KU27A.js} +1 -1
- codex/static_root/assets/VCombobox-D1_KU27A.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_KU27A.js.gz +0 -0
- codex/static_root/assets/{VDialog-BkpVGB70.6eea3ca762a5.js → VDialog-GbxvFMMN.594fb3a39ced.js} +1 -1
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.br +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.gz +0 -0
- codex/static_root/assets/{VDialog-BkpVGB70.js → VDialog-GbxvFMMN.js} +1 -1
- codex/static_root/assets/VDialog-GbxvFMMN.js.br +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js → VExpansionPanels-Cywv_bEj.a762e63858e5.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.js → VExpansionPanels-Cywv_bEj.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-D3Py5BfQ.a4f25edb86d3.js → VRadioGroup-C0etmxpZ.0eb3009a6300.js} +1 -1
- codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-D3Py5BfQ.js → VRadioGroup-C0etmxpZ.js} +1 -1
- codex/static_root/assets/VRadioGroup-C0etmxpZ.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C0etmxpZ.js.gz +0 -0
- codex/static_root/assets/{VSelect-DtvZsYZz.e98745d858eb.js → VSelect-txrnRiNJ.a5625760018c.js} +1 -1
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.br +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.gz +0 -0
- codex/static_root/assets/{VSelect-DtvZsYZz.js → VSelect-txrnRiNJ.js} +1 -1
- codex/static_root/assets/VSelect-txrnRiNJ.js.br +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-D6kmykQW.1e1fda62ceba.js → VSelectionControl-CY6RmcvW.7e8824224f6c.js} +1 -1
- codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-D6kmykQW.js → VSelectionControl-CY6RmcvW.js} +1 -1
- codex/static_root/assets/VSelectionControl-CY6RmcvW.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CY6RmcvW.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-UGZWxpF5.360bdefcd215.js → VSlideGroup-DtEQMwY1.6c72325635ed.js} +1 -1
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-UGZWxpF5.js → VSlideGroup-DtEQMwY1.js} +1 -1
- codex/static_root/assets/VSlideGroup-DtEQMwY1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.js.gz +0 -0
- codex/static_root/assets/{VTable-DXH_yQ0o.1149f56ad566.js → VTable-2af995xo.6c5bf36631b2.js} +1 -1
- codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.br +0 -0
- codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.gz +0 -0
- codex/static_root/assets/{VTable-DXH_yQ0o.js → VTable-2af995xo.js} +1 -1
- codex/static_root/assets/VTable-2af995xo.js.br +0 -0
- codex/static_root/assets/VTable-2af995xo.js.gz +0 -0
- codex/static_root/assets/{VTextField-Blqy56_L.26d8761e0155.js → VTextField-G6CMj7yO.657a130dd302.js} +1 -1
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.br +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.gz +0 -0
- codex/static_root/assets/{VTextField-Blqy56_L.js → VTextField-G6CMj7yO.js} +1 -1
- codex/static_root/assets/VTextField-G6CMj7yO.js.br +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-C8_ovV7e.afbc08414765.js → VWindowItem-CzoGd6Ul.721e5da11ad4.js} +1 -1
- codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.br +0 -0
- codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-C8_ovV7e.js → VWindowItem-CzoGd6Ul.js} +1 -1
- codex/static_root/assets/VWindowItem-CzoGd6Ul.js.br +0 -0
- codex/static_root/assets/VWindowItem-CzoGd6Ul.js.gz +0 -0
- codex/static_root/assets/{admin-DTRn8bBs.d031ea42d8d0.js → admin-CsPAIQeG.5175467148a3.js} +1 -1
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.br +0 -0
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.gz +0 -0
- codex/static_root/assets/{admin-DTRn8bBs.js → admin-CsPAIQeG.js} +1 -1
- codex/static_root/assets/admin-CsPAIQeG.js.br +0 -0
- codex/static_root/assets/admin-CsPAIQeG.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BaXFOcru.b1a44b05f482.js → admin-drawer-panel-BAT7GA64.73a064628c19.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BaXFOcru.js → admin-drawer-panel-BAT7GA64.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js +1 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js.br +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.js +1 -0
- codex/static_root/assets/browser-C18l93yc.js.br +0 -0
- codex/static_root/assets/browser-C18l93yc.js.gz +0 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css +1 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.br +0 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.gz +0 -0
- codex/static_root/assets/browser-DaAqlBYO.css +1 -0
- codex/static_root/assets/browser-DaAqlBYO.css.br +0 -0
- codex/static_root/assets/browser-DaAqlBYO.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js +1 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js +1 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css +1 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css +1 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.gz +0 -0
- codex/static_root/assets/{confirm-dialog-D3_s2DQy.9dde66036c72.js → confirm-dialog-CbW3hlOp.9dd39b443c31.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-D3_s2DQy.js → confirm-dialog-CbW3hlOp.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BxC1Li9e.3dc314b3295d.js → datetime-column-uSqKZU37.b157b0743db5.js} +1 -1
- codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.br +0 -0
- codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BxC1Li9e.js → datetime-column-uSqKZU37.js} +1 -1
- codex/static_root/assets/datetime-column-uSqKZU37.js.br +0 -0
- codex/static_root/assets/datetime-column-uSqKZU37.js.gz +0 -0
- codex/static_root/assets/{filter-Bzk7x841.d0c3b602e62d.js → filter-CBl5KEsB.845a81573c2e.js} +1 -1
- codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.br +0 -0
- codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.gz +0 -0
- codex/static_root/assets/{filter-Bzk7x841.js → filter-CBl5KEsB.js} +1 -1
- codex/static_root/assets/filter-CBl5KEsB.js.br +0 -0
- codex/static_root/assets/filter-CBl5KEsB.js.gz +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js +1 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.br +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.gz +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js +1 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js.br +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js.gz +0 -0
- codex/static_root/assets/{group-tab-5esH4lwX.a81fdd258117.js → group-tab-BOW9kigw.7e5007596367.js} +1 -1
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.br +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.gz +0 -0
- codex/static_root/assets/{group-tab-5esH4lwX.js → group-tab-BOW9kigw.js} +1 -1
- codex/static_root/assets/group-tab-BOW9kigw.js.br +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.js.gz +0 -0
- codex/static_root/assets/{http-error-BTOBfcGY.52ced6f28420.js → http-error-C6yuLApo.45cf99eb7a70.js} +1 -1
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +4 -0
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
- codex/static_root/assets/{http-error-BTOBfcGY.js → http-error-C6yuLApo.js} +1 -1
- codex/static_root/assets/http-error-C6yuLApo.js.br +4 -0
- codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
- codex/static_root/assets/{library-tab-DiPEdOF7.ece2d642a9dc.js → library-tab-D3WOtpE0.9af34e6e7235.js} +1 -1
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.br +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.gz +0 -0
- codex/static_root/assets/{library-tab-DiPEdOF7.js → library-tab-D3WOtpE0.js} +1 -1
- codex/static_root/assets/library-tab-D3WOtpE0.js.br +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.js.gz +0 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js +6 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.br +0 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.gz +0 -0
- codex/static_root/assets/main-CmjU2oTp.js +6 -0
- codex/static_root/assets/main-CmjU2oTp.js.br +0 -0
- codex/static_root/assets/main-CmjU2oTp.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js +1 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js +1 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-C45-onwe.4722195bb60b.css → pagination-toolbar-DGrxp2OD.css} +1 -1
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-C45-onwe.css → pagination-toolbar-DGrxp2OD.f960a624f04f.css} +1 -1
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-CkIzuyx1.00c354a82e6e.js → pagination-toolbar-JH3Kbffr.451171f7ac33.js} +1 -1
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-CkIzuyx1.js → pagination-toolbar-JH3Kbffr.js} +1 -1
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BjlRPuf4.0f056fa321b6.js → pdf-doc-BcRNpoaW.882f6e967869.js} +1 -1
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BjlRPuf4.js → pdf-doc-BcRNpoaW.js} +1 -1
- codex/static_root/assets/pdf-doc-BcRNpoaW.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.js.gz +0 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js +2 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.br +0 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.gz +0 -0
- codex/static_root/assets/reader-DIPRg2VB.js +2 -0
- codex/static_root/assets/reader-DIPRg2VB.js.br +0 -0
- codex/static_root/assets/reader-DIPRg2VB.js.gz +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css +1 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -0
- codex/static_root/assets/reader-DveZZr9x.css +1 -0
- codex/static_root/assets/reader-DveZZr9x.css.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.css.gz +0 -0
- codex/static_root/assets/{relation-chips-CUSatZET.js → relation-chips-3tTlR5QH.71f18bc92adc.js} +1 -1
- codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.br +0 -0
- codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CUSatZET.a58031ff8141.js → relation-chips-3tTlR5QH.js} +1 -1
- codex/static_root/assets/relation-chips-3tTlR5QH.js.br +0 -0
- codex/static_root/assets/relation-chips-3tTlR5QH.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BSHTAsJ9.js → settings-drawer-BdyFhA_r.151f4c3061ed.js} +2 -2
- codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.br +0 -0
- codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BSHTAsJ9.1d07ce084fc9.js → settings-drawer-BdyFhA_r.js} +2 -2
- codex/static_root/assets/settings-drawer-BdyFhA_r.js.br +0 -0
- codex/static_root/assets/settings-drawer-BdyFhA_r.js.gz +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js +1 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.br +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.gz +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js +1 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js.br +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js.gz +0 -0
- codex/static_root/assets/{stats-tab-BXGisn5N.9b7a71e7fe28.css → stats-tab-DpfGsVX3.1cb66f2649a9.css} +1 -1
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
- codex/static_root/assets/{stats-tab-BXGisn5N.css → stats-tab-DpfGsVX3.css} +1 -1
- codex/static_root/assets/stats-tab-DpfGsVX3.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.css.gz +0 -0
- codex/static_root/assets/{task-tab-NORFAa9p.97e07e366ddd.js → task-tab-C8D0qRP_.d4ab174c69a3.js} +1 -1
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.br +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.gz +0 -0
- codex/static_root/assets/{task-tab-NORFAa9p.js → task-tab-C8D0qRP_.js} +1 -1
- codex/static_root/assets/task-tab-C8D0qRP_.js.br +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.js.gz +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js +1 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.gz +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js +1 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
- codex/static_root/assets/{unauthorized-muDsv-rC.css → unauthorized-BjhIc97q.728511c92cd0.css} +1 -1
- codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.br +0 -0
- codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.gz +0 -0
- codex/static_root/assets/{unauthorized-muDsv-rC.2166ec8b3a32.css → unauthorized-BjhIc97q.css} +1 -1
- codex/static_root/assets/unauthorized-BjhIc97q.css.br +0 -0
- codex/static_root/assets/unauthorized-BjhIc97q.css.gz +0 -0
- codex/static_root/assets/{user-tab-CHbyN1EW.e7206db08680.js → user-tab-BU31Z92y.35613c713faf.js} +1 -1
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.br +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.gz +0 -0
- codex/static_root/assets/{user-tab-CHbyN1EW.js → user-tab-BU31Z92y.js} +1 -1
- codex/static_root/assets/user-tab-BU31Z92y.js.br +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.js.gz +0 -0
- codex/static_root/js/choices-admin.8aa64c911203.json +1 -0
- codex/static_root/js/choices-admin.8aa64c911203.json.br +0 -0
- codex/static_root/js/choices-admin.8aa64c911203.json.gz +0 -0
- codex/static_root/js/choices-admin.json +1 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/manifest.45cb46e19686.json +635 -0
- codex/static_root/manifest.45cb46e19686.json.br +0 -0
- codex/static_root/manifest.45cb46e19686.json.gz +0 -0
- codex/static_root/manifest.json +248 -248
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/status_controller.py +10 -4
- codex/views/admin/tasks.py +46 -40
- codex/views/auth.py +15 -4
- codex/views/browser/annotations.py +94 -51
- codex/views/browser/base.py +1 -2
- codex/views/browser/browser.py +52 -21
- codex/views/browser/cover.py +8 -4
- codex/views/browser/filters/annotations.py +28 -6
- codex/views/browser/filters/search/__init__.py +1 -0
- codex/views/browser/filters/search/aliases.py +81 -0
- codex/views/browser/filters/search/field/__init__.py +1 -0
- codex/views/browser/filters/search/field/column.py +53 -0
- codex/views/browser/filters/search/field/expression.py +177 -0
- codex/views/browser/filters/search/field/filter.py +70 -0
- codex/views/browser/filters/search/field/optimize.py +71 -0
- codex/views/browser/filters/search/field/parse.py +187 -0
- codex/views/browser/filters/search/fts.py +38 -0
- codex/views/browser/filters/search/parse.py +220 -0
- codex/views/browser/order_by.py +8 -2
- codex/views/browser/page_in_bounds.py +88 -0
- codex/views/browser/paginate.py +28 -113
- codex/views/const.py +0 -1
- codex/views/opds/v1/feed.py +2 -1
- codex/views/opds/v2/feed.py +2 -1
- codex/views/opds/v2/links.py +1 -1
- codex/views/reader/books.py +1 -1
- codex/views/reader/page.py +3 -3
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/METADATA +18 -10
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/RECORD +321 -372
- codex/_vendor/__init__.py +0 -0
- codex/_vendor/django_haystack-3.2.1.dist-info/AUTHORS +0 -127
- codex/_vendor/django_haystack-3.2.1.dist-info/INSTALLER +0 -1
- codex/_vendor/django_haystack-3.2.1.dist-info/LICENSE +0 -31
- codex/_vendor/django_haystack-3.2.1.dist-info/METADATA +0 -98
- codex/_vendor/django_haystack-3.2.1.dist-info/RECORD +0 -97
- codex/_vendor/django_haystack-3.2.1.dist-info/REQUESTED +0 -0
- codex/_vendor/django_haystack-3.2.1.dist-info/WHEEL +0 -5
- codex/_vendor/django_haystack-3.2.1.dist-info/top_level.txt +0 -1
- codex/_vendor/haystack/__init__.py +0 -80
- codex/_vendor/haystack/admin.py +0 -163
- codex/_vendor/haystack/apps.py +0 -31
- codex/_vendor/haystack/backends/__init__.py +0 -1109
- codex/_vendor/haystack/backends/elasticsearch2_backend.py +0 -390
- codex/_vendor/haystack/backends/elasticsearch5_backend.py +0 -483
- codex/_vendor/haystack/backends/elasticsearch7_backend.py +0 -586
- codex/_vendor/haystack/backends/elasticsearch_backend.py +0 -1141
- codex/_vendor/haystack/backends/simple_backend.py +0 -132
- codex/_vendor/haystack/backends/solr_backend.py +0 -991
- codex/_vendor/haystack/backends/whoosh_backend.py +0 -1104
- codex/_vendor/haystack/constants.py +0 -57
- codex/_vendor/haystack/exceptions.py +0 -57
- codex/_vendor/haystack/fields.py +0 -562
- codex/_vendor/haystack/forms.py +0 -135
- codex/_vendor/haystack/generic_views.py +0 -141
- codex/_vendor/haystack/indexes.py +0 -548
- codex/_vendor/haystack/inputs.py +0 -169
- codex/_vendor/haystack/management/__init__.py +0 -0
- codex/_vendor/haystack/management/commands/__init__.py +0 -0
- codex/_vendor/haystack/management/commands/build_solr_schema.py +0 -187
- codex/_vendor/haystack/management/commands/clear_index.py +0 -67
- codex/_vendor/haystack/management/commands/haystack_info.py +0 -22
- codex/_vendor/haystack/management/commands/rebuild_index.py +0 -65
- codex/_vendor/haystack/management/commands/update_index.py +0 -438
- codex/_vendor/haystack/manager.py +0 -105
- codex/_vendor/haystack/models.py +0 -257
- codex/_vendor/haystack/panels.py +0 -89
- codex/_vendor/haystack/query.py +0 -771
- codex/_vendor/haystack/routers.py +0 -14
- codex/_vendor/haystack/signals.py +0 -88
- codex/_vendor/haystack/templates/panels/haystack.html +0 -33
- codex/_vendor/haystack/templates/search_configuration/schema.xml +0 -1056
- codex/_vendor/haystack/templates/search_configuration/solrconfig.xml +0 -1446
- codex/_vendor/haystack/templatetags/__init__.py +0 -0
- codex/_vendor/haystack/templatetags/highlight.py +0 -131
- codex/_vendor/haystack/templatetags/more_like_this.py +0 -119
- codex/_vendor/haystack/urls.py +0 -5
- codex/_vendor/haystack/utils/__init__.py +0 -84
- codex/_vendor/haystack/utils/app_loading.py +0 -34
- codex/_vendor/haystack/utils/geo.py +0 -71
- codex/_vendor/haystack/utils/highlighting.py +0 -165
- codex/_vendor/haystack/utils/loading.py +0 -374
- codex/_vendor/haystack/utils/log.py +0 -21
- codex/_vendor/haystack/version.py +0 -16
- codex/_vendor/haystack/views.py +0 -253
- codex/integrity.py +0 -492
- codex/librarian/search/merge.py +0 -86
- codex/librarian/search/version.py +0 -63
- codex/search/__init__.py +0 -1
- codex/search/backend.py +0 -524
- codex/search/backend_search.py +0 -229
- codex/search/engine.py +0 -33
- codex/search/indexes.py +0 -79
- codex/search/query.py +0 -67
- codex/search/writing.py +0 -180
- codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.br +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.gz +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.js.br +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.br +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.js.br +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.js.gz +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.br +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.gz +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.js.br +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.gz +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.br +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.gz +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.js.br +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.br +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.js.br +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.js.gz +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.br +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.gz +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.js.br +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.js.gz +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.br +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.gz +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.js.br +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.br +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.js.br +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.js.gz +0 -0
- codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.br +0 -0
- codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.gz +0 -0
- codex/static_root/assets/admin-DTRn8bBs.js.br +0 -0
- codex/static_root/assets/admin-DTRn8bBs.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.gz +0 -0
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js +0 -1
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.br +0 -0
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.gz +0 -0
- codex/static_root/assets/browser-BBpUyGmz.js +0 -1
- codex/static_root/assets/browser-BBpUyGmz.js.br +0 -0
- codex/static_root/assets/browser-BBpUyGmz.js.gz +0 -0
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css +0 -1
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.br +0 -0
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.gz +0 -0
- codex/static_root/assets/browser-Binc3n9H.css +0 -1
- codex/static_root/assets/browser-Binc3n9H.css.br +0 -0
- codex/static_root/assets/browser-Binc3n9H.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css +0 -1
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.css +0 -1
- codex/static_root/assets/change-password-dialog-Cebek1-c.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js +0 -1
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js +0 -1
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.br +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.js.br +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.js.gz +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.br +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.gz +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.js.br +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.js.gz +0 -0
- codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.br +0 -0
- codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.gz +0 -0
- codex/static_root/assets/filter-Bzk7x841.js.br +0 -0
- codex/static_root/assets/filter-Bzk7x841.js.gz +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js +0 -1
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.br +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.gz +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.js +0 -1
- codex/static_root/assets/flag-tab-C2bI5zq3.js.br +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.js.gz +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.br +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.gz +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.js.br +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.js.gz +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.br +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.gz +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.js.br +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.js.gz +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.br +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.gz +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.js.br +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.js.gz +0 -0
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js +0 -6
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.br +0 -0
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.gz +0 -0
- codex/static_root/assets/main-BPIPjzsc.js +0 -6
- codex/static_root/assets/main-BPIPjzsc.js.br +0 -0
- codex/static_root/assets/main-BPIPjzsc.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js +0 -1
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js +0 -1
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.br +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.js.br +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.js.gz +0 -0
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css +0 -1
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.br +0 -0
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.gz +0 -0
- codex/static_root/assets/reader-_stAWL-4.css +0 -1
- codex/static_root/assets/reader-_stAWL-4.css.br +0 -0
- codex/static_root/assets/reader-_stAWL-4.css.gz +0 -0
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js +0 -2
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.br +0 -0
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.gz +0 -0
- codex/static_root/assets/reader-ucxnLi2K.js +0 -2
- codex/static_root/assets/reader-ucxnLi2K.js.br +0 -0
- codex/static_root/assets/reader-ucxnLi2K.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.br +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.js.br +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.br +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.js.br +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.js.gz +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.br +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.gz +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.css.br +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.css.gz +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js +0 -1
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.br +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.gz +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.js +0 -1
- codex/static_root/assets/stats-tab-DKXVB2Cc.js.br +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.js.gz +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.br +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.gz +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.js.br +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.js.gz +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js +0 -1
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.br +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.gz +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.js +0 -1
- codex/static_root/assets/unauthorized-RHaQZRfr.js.br +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.js.gz +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.br +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.gz +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.css.br +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.css.gz +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.br +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.gz +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.js.br +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.js.gz +0 -0
- codex/static_root/js/choices-admin.1a20a5648f20.json +0 -1
- codex/static_root/js/choices-admin.1a20a5648f20.json.br +0 -0
- codex/static_root/js/choices-admin.1a20a5648f20.json.gz +0 -0
- codex/static_root/manifest.0fe31a6cb99f.json +0 -635
- codex/static_root/manifest.0fe31a6cb99f.json.br +0 -0
- codex/static_root/manifest.0fe31a6cb99f.json.gz +0 -0
- codex/templates/search/indexes/codex/comic_text.txt +0 -47
- codex/views/browser/filters/search.py +0 -196
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/LICENSE +0 -0
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/WHEEL +0 -0
- {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
|