codex 1.7.0a0__py3-none-any.whl → 1.7.0a2__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/choices.py +424 -0
- codex/choices_to_json.py +122 -0
- codex/db.py +31 -15
- codex/exceptions.py +2 -3
- codex/librarian/bookmark/__init__.py +1 -0
- codex/librarian/bookmark/bookmarkd.py +38 -0
- codex/librarian/bookmark/tasks.py +19 -0
- codex/librarian/bookmark/update.py +154 -0
- codex/librarian/covers/coverd.py +3 -4
- codex/librarian/covers/create.py +8 -7
- codex/librarian/covers/path.py +1 -2
- codex/librarian/covers/purge.py +2 -2
- codex/librarian/janitor/integrity.py +1 -0
- codex/librarian/librariand.py +5 -0
- codex/librarian/watchdog/emitter.py +21 -19
- codex/librarian/watchdog/event_batcherd.py +0 -1
- codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +4 -0
- codex/models/admin.py +0 -5
- codex/models/functions.py +6 -5
- codex/models/query.py +6 -0
- codex/run.py +11 -9
- codex/serializers/admin/tasks.py +2 -2
- codex/serializers/browser/mixins.py +18 -14
- codex/serializers/browser/settings.py +4 -4
- codex/serializers/fields.py +9 -5
- codex/startup.py +6 -4
- codex/static_root/assets/VCheckbox-CCOzQpCw.a65ad95bd21a.js +1 -0
- codex/static_root/assets/VCheckbox-CCOzQpCw.a65ad95bd21a.js.br +0 -0
- codex/static_root/assets/VCheckbox-CCOzQpCw.a65ad95bd21a.js.gz +0 -0
- codex/static_root/assets/VCheckbox-CCOzQpCw.js +1 -0
- codex/static_root/assets/VCheckbox-CCOzQpCw.js.br +0 -0
- codex/static_root/assets/VCheckbox-CCOzQpCw.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-DnBH3a96.876fda35b85a.js +1 -0
- codex/static_root/assets/VCheckboxBtn-DnBH3a96.876fda35b85a.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DnBH3a96.876fda35b85a.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-DnBH3a96.js +1 -0
- codex/static_root/assets/VCheckboxBtn-DnBH3a96.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DnBH3a96.js.gz +0 -0
- codex/static_root/assets/VCombobox-DwwoR4yO.e7a4db7ab7f0.js +1 -0
- codex/static_root/assets/VCombobox-DwwoR4yO.e7a4db7ab7f0.js.br +0 -0
- codex/static_root/assets/VCombobox-DwwoR4yO.e7a4db7ab7f0.js.gz +0 -0
- codex/static_root/assets/VCombobox-DwwoR4yO.js +1 -0
- codex/static_root/assets/VCombobox-DwwoR4yO.js.br +0 -0
- codex/static_root/assets/VCombobox-DwwoR4yO.js.gz +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css +1 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css.br +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css.gz +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css +1 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.br +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.gz +0 -0
- codex/static_root/assets/VDialog-DbN2FfmF.b2843f79d90f.js +1 -0
- codex/static_root/assets/VDialog-DbN2FfmF.b2843f79d90f.js.br +0 -0
- codex/static_root/assets/VDialog-DbN2FfmF.b2843f79d90f.js.gz +0 -0
- codex/static_root/assets/VDialog-DbN2FfmF.js +1 -0
- codex/static_root/assets/VDialog-DbN2FfmF.js.br +0 -0
- codex/static_root/assets/VDialog-DbN2FfmF.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CBPnoy7O.212f4cad7d78.js +1 -0
- codex/static_root/assets/VExpansionPanels-CBPnoy7O.212f4cad7d78.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CBPnoy7O.212f4cad7d78.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CBPnoy7O.js +1 -0
- codex/static_root/assets/VExpansionPanels-CBPnoy7O.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CBPnoy7O.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-C7Zu_Eup.b42735892aec.js +1 -0
- codex/static_root/assets/VRadioGroup-C7Zu_Eup.b42735892aec.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C7Zu_Eup.b42735892aec.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-C7Zu_Eup.js +1 -0
- codex/static_root/assets/VRadioGroup-C7Zu_Eup.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C7Zu_Eup.js.gz +0 -0
- codex/static_root/assets/VSelect-BTy7SKk0.dee138de5dd0.js +1 -0
- codex/static_root/assets/VSelect-BTy7SKk0.dee138de5dd0.js.br +0 -0
- codex/static_root/assets/VSelect-BTy7SKk0.dee138de5dd0.js.gz +0 -0
- codex/static_root/assets/VSelect-BTy7SKk0.js +1 -0
- codex/static_root/assets/VSelect-BTy7SKk0.js.br +0 -0
- codex/static_root/assets/VSelect-BTy7SKk0.js.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +1 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css +1 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
- codex/static_root/assets/VSelectionControl-B42mmzLA.bf64d259ddef.js +1 -0
- codex/static_root/assets/VSelectionControl-B42mmzLA.bf64d259ddef.js.br +0 -0
- codex/static_root/assets/VSelectionControl-B42mmzLA.bf64d259ddef.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-B42mmzLA.js +1 -0
- codex/static_root/assets/VSelectionControl-B42mmzLA.js.br +0 -0
- codex/static_root/assets/VSelectionControl-B42mmzLA.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +1 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css +1 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtfHoWCn.33e2bd94e34a.js +1 -0
- codex/static_root/assets/VSlideGroup-BtfHoWCn.33e2bd94e34a.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BtfHoWCn.33e2bd94e34a.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtfHoWCn.js +1 -0
- codex/static_root/assets/VSlideGroup-BtfHoWCn.js.br +0 -0
- codex/static_root/assets/VSlideGroup-BtfHoWCn.js.gz +0 -0
- codex/static_root/assets/VTable-R3X0iPjV.ee07d9c2ca5f.js +1 -0
- codex/static_root/assets/VTable-R3X0iPjV.ee07d9c2ca5f.js.br +0 -0
- codex/static_root/assets/VTable-R3X0iPjV.ee07d9c2ca5f.js.gz +0 -0
- codex/static_root/assets/VTable-R3X0iPjV.js +1 -0
- codex/static_root/assets/VTable-R3X0iPjV.js.br +0 -0
- codex/static_root/assets/VTable-R3X0iPjV.js.gz +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css +1 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.br +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.gz +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css +1 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css.br +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css.gz +0 -0
- codex/static_root/assets/VTextField-D9pqgz_R.47beb9e7e547.js +1 -0
- codex/static_root/assets/VTextField-D9pqgz_R.47beb9e7e547.js.br +0 -0
- codex/static_root/assets/VTextField-D9pqgz_R.47beb9e7e547.js.gz +0 -0
- codex/static_root/assets/VTextField-D9pqgz_R.js +1 -0
- codex/static_root/assets/VTextField-D9pqgz_R.js.br +0 -0
- codex/static_root/assets/VTextField-D9pqgz_R.js.gz +0 -0
- codex/static_root/assets/VWindowItem-DHTDdYfK.f6bf6b893442.js +1 -0
- codex/static_root/assets/VWindowItem-DHTDdYfK.f6bf6b893442.js.br +0 -0
- codex/static_root/assets/VWindowItem-DHTDdYfK.f6bf6b893442.js.gz +0 -0
- codex/static_root/assets/VWindowItem-DHTDdYfK.js +1 -0
- codex/static_root/assets/VWindowItem-DHTDdYfK.js.br +0 -0
- codex/static_root/assets/VWindowItem-DHTDdYfK.js.gz +0 -0
- codex/static_root/assets/admin-CIWyRJnH.dde0ee029f08.js +1 -0
- codex/static_root/assets/admin-CIWyRJnH.dde0ee029f08.js.br +0 -0
- codex/static_root/assets/admin-CIWyRJnH.dde0ee029f08.js.gz +0 -0
- codex/static_root/assets/admin-CIWyRJnH.js +1 -0
- codex/static_root/assets/admin-CIWyRJnH.js.br +0 -0
- codex/static_root/assets/admin-CIWyRJnH.js.gz +0 -0
- codex/static_root/assets/{admin-CWEjaFr5.7d4523387384.css → admin-DJ7pIXDQ.css} +1 -1
- codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
- codex/static_root/assets/{admin-CWEjaFr5.css → admin-DJ7pIXDQ.e2a77af73ef2.css} +1 -1
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css +1 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.br +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.gz +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css +1 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css.br +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css.gz +0 -0
- codex/static_root/assets/admin-menu-BHOUwxhf.1ed26b58fb9f.js +1 -0
- codex/static_root/assets/admin-menu-BHOUwxhf.1ed26b58fb9f.js.br +0 -0
- codex/static_root/assets/admin-menu-BHOUwxhf.1ed26b58fb9f.js.gz +0 -0
- codex/static_root/assets/admin-menu-BHOUwxhf.js +1 -0
- codex/static_root/assets/admin-menu-BHOUwxhf.js.br +0 -0
- codex/static_root/assets/admin-menu-BHOUwxhf.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.85a213c96423.js +1 -0
- codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.85a213c96423.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.85a213c96423.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.js +1 -0
- codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.2548c49a853e.css +1 -0
- codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.css +1 -0
- codex/static_root/assets/browser-6T4uwUjJ.7dd4a7f74df6.js +1 -0
- codex/static_root/assets/browser-6T4uwUjJ.7dd4a7f74df6.js.br +0 -0
- codex/static_root/assets/browser-6T4uwUjJ.7dd4a7f74df6.js.gz +0 -0
- codex/static_root/assets/browser-6T4uwUjJ.js +1 -0
- codex/static_root/assets/browser-6T4uwUjJ.js.br +0 -0
- codex/static_root/assets/browser-6T4uwUjJ.js.gz +0 -0
- codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css +1 -0
- codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.br +0 -0
- codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.gz +0 -0
- codex/static_root/assets/browser-Br5CorL4.css +1 -0
- codex/static_root/assets/browser-Br5CorL4.css.br +0 -0
- codex/static_root/assets/browser-Br5CorL4.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BfiQBpUn.48db4c906920.js +1 -0
- codex/static_root/assets/change-password-dialog-BfiQBpUn.48db4c906920.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BfiQBpUn.48db4c906920.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BfiQBpUn.js +1 -0
- codex/static_root/assets/change-password-dialog-BfiQBpUn.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BfiQBpUn.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css +1 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.br +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css +1 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css.br +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css.gz +0 -0
- codex/static_root/assets/confirm-dialog-B7w6z2rf.472011c06253.css +1 -0
- codex/static_root/assets/confirm-dialog-B7w6z2rf.css +1 -0
- codex/static_root/assets/confirm-dialog-Iyuhxfyi.cf28325e58b7.js +1 -0
- codex/static_root/assets/confirm-dialog-Iyuhxfyi.cf28325e58b7.js.br +0 -0
- codex/static_root/assets/confirm-dialog-Iyuhxfyi.cf28325e58b7.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-Iyuhxfyi.js +1 -0
- codex/static_root/assets/confirm-dialog-Iyuhxfyi.js.br +0 -0
- codex/static_root/assets/confirm-dialog-Iyuhxfyi.js.gz +0 -0
- codex/static_root/assets/datetime-column-Dnxh9sRh.b6a92a6bcce9.js +1 -0
- codex/static_root/assets/datetime-column-Dnxh9sRh.b6a92a6bcce9.js.br +0 -0
- codex/static_root/assets/datetime-column-Dnxh9sRh.b6a92a6bcce9.js.gz +0 -0
- codex/static_root/assets/datetime-column-Dnxh9sRh.js +1 -0
- codex/static_root/assets/datetime-column-Dnxh9sRh.js.br +0 -0
- codex/static_root/assets/datetime-column-Dnxh9sRh.js.gz +0 -0
- codex/static_root/assets/filter-CmOjoKbH.ea79c4727d92.js +1 -0
- codex/static_root/assets/filter-CmOjoKbH.ea79c4727d92.js.br +0 -0
- codex/static_root/assets/filter-CmOjoKbH.ea79c4727d92.js.gz +0 -0
- codex/static_root/assets/filter-CmOjoKbH.js +1 -0
- codex/static_root/assets/filter-CmOjoKbH.js.br +0 -0
- codex/static_root/assets/filter-CmOjoKbH.js.gz +0 -0
- codex/static_root/assets/flag-tab-1PeNvDA9.2fc60db57915.js +1 -0
- codex/static_root/assets/flag-tab-1PeNvDA9.2fc60db57915.js.br +0 -0
- codex/static_root/assets/flag-tab-1PeNvDA9.2fc60db57915.js.gz +0 -0
- codex/static_root/assets/flag-tab-1PeNvDA9.js +1 -0
- codex/static_root/assets/flag-tab-1PeNvDA9.js.br +0 -0
- codex/static_root/assets/flag-tab-1PeNvDA9.js.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +1 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css +1 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css +1 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.br +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.gz +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css +1 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css.br +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css.gz +0 -0
- codex/static_root/assets/group-tab-Zl_C7M7Z.dbeed7f2d08f.js +1 -0
- codex/static_root/assets/group-tab-Zl_C7M7Z.dbeed7f2d08f.js.br +0 -0
- codex/static_root/assets/group-tab-Zl_C7M7Z.dbeed7f2d08f.js.gz +0 -0
- codex/static_root/assets/group-tab-Zl_C7M7Z.js +1 -0
- codex/static_root/assets/group-tab-Zl_C7M7Z.js.br +0 -0
- codex/static_root/assets/group-tab-Zl_C7M7Z.js.gz +0 -0
- codex/static_root/assets/http-error-YNrrsTPg.73e9978dc0ce.js +1 -0
- codex/static_root/assets/http-error-YNrrsTPg.73e9978dc0ce.js.br +0 -0
- codex/static_root/assets/http-error-YNrrsTPg.73e9978dc0ce.js.gz +0 -0
- codex/static_root/assets/http-error-YNrrsTPg.js +1 -0
- codex/static_root/assets/http-error-YNrrsTPg.js.br +0 -0
- codex/static_root/assets/http-error-YNrrsTPg.js.gz +0 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css +1 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.br +0 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.gz +0 -0
- codex/static_root/assets/index-CL_6g4tJ.css +1 -0
- codex/static_root/assets/index-CL_6g4tJ.css.br +0 -0
- codex/static_root/assets/index-CL_6g4tJ.css.gz +0 -0
- codex/static_root/assets/index-j5K-C_JC.65c2dc5af141.js +1 -0
- codex/static_root/assets/index-j5K-C_JC.65c2dc5af141.js.br +0 -0
- codex/static_root/assets/index-j5K-C_JC.65c2dc5af141.js.gz +0 -0
- codex/static_root/assets/index-j5K-C_JC.js +1 -0
- codex/static_root/assets/index-j5K-C_JC.js.br +0 -0
- codex/static_root/assets/index-j5K-C_JC.js.gz +0 -0
- codex/static_root/assets/library-tab-CxKs8G-K.c6bfd6d53b17.js +1 -0
- codex/static_root/assets/library-tab-CxKs8G-K.c6bfd6d53b17.js.br +0 -0
- codex/static_root/assets/library-tab-CxKs8G-K.c6bfd6d53b17.js.gz +0 -0
- codex/static_root/assets/library-tab-CxKs8G-K.js +1 -0
- codex/static_root/assets/library-tab-CxKs8G-K.js.br +0 -0
- codex/static_root/assets/library-tab-CxKs8G-K.js.gz +0 -0
- codex/static_root/assets/{library-tab-Bh7ErAk7.7c44200ee727.css → library-tab-Wew8YOLv.0c1780a6b68e.css} +1 -1
- codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.br +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.gz +0 -0
- codex/static_root/assets/{library-tab-Bh7ErAk7.css → library-tab-Wew8YOLv.css} +1 -1
- codex/static_root/assets/library-tab-Wew8YOLv.css.br +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.css.gz +0 -0
- codex/static_root/assets/main-H14uUxuN.cc1511f0f146.js +35 -0
- codex/static_root/assets/main-H14uUxuN.cc1511f0f146.js.br +0 -0
- codex/static_root/assets/main-H14uUxuN.cc1511f0f146.js.gz +0 -0
- codex/static_root/assets/main-H14uUxuN.js +35 -0
- codex/static_root/assets/main-H14uUxuN.js.br +0 -0
- codex/static_root/assets/main-H14uUxuN.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-DcRg28qt.ca9cdb9619ff.js +1 -0
- codex/static_root/assets/pager-full-pdf-DcRg28qt.ca9cdb9619ff.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-DcRg28qt.ca9cdb9619ff.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-DcRg28qt.js +1 -0
- codex/static_root/assets/pager-full-pdf-DcRg28qt.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-DcRg28qt.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-BgoV4zqc.ab5fa1be17a3.js +1 -0
- codex/static_root/assets/pagination-toolbar-BgoV4zqc.ab5fa1be17a3.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-BgoV4zqc.ab5fa1be17a3.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-BgoV4zqc.js +1 -0
- codex/static_root/assets/pagination-toolbar-BgoV4zqc.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-BgoV4zqc.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BcRNpoaW.882f6e967869.js → pdf-doc-BbuJd_VS.225d8590d020.js} +3 -3
- codex/static_root/assets/pdf-doc-BbuJd_VS.225d8590d020.js.br +0 -0
- codex/static_root/assets/pdf-doc-BbuJd_VS.225d8590d020.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BcRNpoaW.js → pdf-doc-BbuJd_VS.js} +3 -3
- codex/static_root/assets/pdf-doc-BbuJd_VS.js.br +0 -0
- codex/static_root/assets/pdf-doc-BbuJd_VS.js.gz +0 -0
- codex/static_root/assets/reader-DORea4Al.d8e756bb0027.js +2 -0
- codex/static_root/assets/reader-DORea4Al.d8e756bb0027.js.br +0 -0
- codex/static_root/assets/reader-DORea4Al.d8e756bb0027.js.gz +0 -0
- codex/static_root/assets/reader-DORea4Al.js +2 -0
- codex/static_root/assets/reader-DORea4Al.js.br +0 -0
- codex/static_root/assets/reader-DORea4Al.js.gz +0 -0
- codex/static_root/assets/{reader-DveZZr9x.7b21b9f8f628.css → reader-OpKdTZ_B.css} +1 -1
- codex/static_root/assets/reader-OpKdTZ_B.css.br +0 -0
- codex/static_root/assets/reader-OpKdTZ_B.css.gz +0 -0
- codex/static_root/assets/{reader-DveZZr9x.css → reader-OpKdTZ_B.ebd1512b7014.css} +1 -1
- codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.br +0 -0
- codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.gz +0 -0
- codex/static_root/assets/relation-chips-kqofGdNg.0c7a53962f2b.js +1 -0
- codex/static_root/assets/relation-chips-kqofGdNg.0c7a53962f2b.js.br +0 -0
- codex/static_root/assets/relation-chips-kqofGdNg.0c7a53962f2b.js.gz +0 -0
- codex/static_root/assets/relation-chips-kqofGdNg.js +1 -0
- codex/static_root/assets/relation-chips-kqofGdNg.js.br +0 -0
- codex/static_root/assets/relation-chips-kqofGdNg.js.gz +0 -0
- codex/static_root/assets/settings-drawer-C-hgtHJt.0eaf7e00ee9b.js +2 -0
- codex/static_root/assets/settings-drawer-C-hgtHJt.0eaf7e00ee9b.js.br +0 -0
- codex/static_root/assets/settings-drawer-C-hgtHJt.0eaf7e00ee9b.js.gz +0 -0
- codex/static_root/assets/settings-drawer-C-hgtHJt.js +2 -0
- codex/static_root/assets/settings-drawer-C-hgtHJt.js.br +0 -0
- codex/static_root/assets/settings-drawer-C-hgtHJt.js.gz +0 -0
- codex/static_root/assets/{stats-tab-DpfGsVX3.1cb66f2649a9.css → stats-tab-BAYTqQNg.5f715428c394.css} +1 -1
- codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.br +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.gz +0 -0
- codex/static_root/assets/{stats-tab-DpfGsVX3.css → stats-tab-BAYTqQNg.css} +1 -1
- codex/static_root/assets/stats-tab-BAYTqQNg.css.br +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.css.gz +0 -0
- codex/static_root/assets/stats-tab-C7iJCFa5.f1039eba03c2.js +1 -0
- codex/static_root/assets/stats-tab-C7iJCFa5.f1039eba03c2.js.br +0 -0
- codex/static_root/assets/stats-tab-C7iJCFa5.f1039eba03c2.js.gz +0 -0
- codex/static_root/assets/stats-tab-C7iJCFa5.js +1 -0
- codex/static_root/assets/stats-tab-C7iJCFa5.js.br +0 -0
- codex/static_root/assets/stats-tab-C7iJCFa5.js.gz +0 -0
- codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css +1 -0
- codex/static_root/assets/{task-tab-1XiWn9xP.06cb8e1b794d.css.br → task-tab--cXi48kg.5dfd6890a1c2.css.br} +1 -1
- codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css.gz +0 -0
- codex/static_root/assets/task-tab--cXi48kg.css +1 -0
- codex/static_root/assets/{task-tab-1XiWn9xP.css.br → task-tab--cXi48kg.css.br} +1 -1
- codex/static_root/assets/task-tab--cXi48kg.css.gz +0 -0
- codex/static_root/assets/task-tab-Drfz5uaT.0b387dcd4f5a.js +1 -0
- codex/static_root/assets/task-tab-Drfz5uaT.0b387dcd4f5a.js.br +0 -0
- codex/static_root/assets/task-tab-Drfz5uaT.0b387dcd4f5a.js.gz +0 -0
- codex/static_root/assets/task-tab-Drfz5uaT.js +1 -0
- codex/static_root/assets/task-tab-Drfz5uaT.js.br +0 -0
- codex/static_root/assets/task-tab-Drfz5uaT.js.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +1 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +4 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.css +1 -0
- codex/static_root/assets/transition-5LRFTPo5.css.br +4 -0
- codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
- codex/static_root/assets/transition-CQVFPpmZ.ba35e475243a.js +1 -0
- codex/static_root/assets/transition-CQVFPpmZ.ba35e475243a.js.br +0 -0
- codex/static_root/assets/transition-CQVFPpmZ.ba35e475243a.js.gz +0 -0
- codex/static_root/assets/transition-CQVFPpmZ.js +1 -0
- codex/static_root/assets/transition-CQVFPpmZ.js.br +0 -0
- codex/static_root/assets/transition-CQVFPpmZ.js.gz +0 -0
- codex/static_root/assets/unauthorized-Cfxq6rru.968d6e6e57bd.js +1 -0
- codex/static_root/assets/unauthorized-Cfxq6rru.968d6e6e57bd.js.br +0 -0
- codex/static_root/assets/unauthorized-Cfxq6rru.968d6e6e57bd.js.gz +0 -0
- codex/static_root/assets/unauthorized-Cfxq6rru.js +1 -0
- codex/static_root/assets/unauthorized-Cfxq6rru.js.br +0 -0
- codex/static_root/assets/unauthorized-Cfxq6rru.js.gz +0 -0
- codex/static_root/assets/user-tab-icKcK_Qd.c29142ed5212.js +1 -0
- codex/static_root/assets/user-tab-icKcK_Qd.c29142ed5212.js.br +0 -0
- codex/static_root/assets/user-tab-icKcK_Qd.c29142ed5212.js.gz +0 -0
- codex/static_root/assets/user-tab-icKcK_Qd.js +1 -0
- codex/static_root/assets/user-tab-icKcK_Qd.js.br +0 -0
- codex/static_root/assets/user-tab-icKcK_Qd.js.gz +0 -0
- codex/static_root/manifest.3eee2b4822b7.json +712 -0
- codex/static_root/manifest.3eee2b4822b7.json.br +0 -0
- codex/static_root/manifest.3eee2b4822b7.json.gz +0 -0
- codex/static_root/manifest.json +377 -300
- 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 +5 -6
- codex/threads.py +1 -1
- codex/urls/api/auth.py +1 -1
- codex/urls/api/browser.py +1 -1
- codex/views/admin/library.py +18 -13
- codex/views/admin/stats.py +23 -16
- codex/views/auth.py +42 -77
- codex/views/bookmark.py +16 -153
- codex/views/browser/annotate/__init__.py +1 -0
- codex/views/browser/annotate/bookmark.py +112 -0
- codex/views/browser/annotate/card.py +61 -0
- codex/views/browser/annotate/order.py +247 -0
- codex/views/browser/breadcrumbs.py +90 -11
- codex/views/browser/browser.py +44 -110
- codex/views/browser/choices.py +15 -18
- codex/views/browser/cover.py +10 -20
- codex/views/browser/filters/bookmark.py +18 -19
- codex/views/browser/filters/field.py +8 -3
- codex/views/browser/filters/filter.py +69 -0
- codex/views/browser/filters/group.py +9 -6
- codex/views/browser/filters/search/fts.py +5 -19
- codex/views/browser/filters/search/parse.py +54 -24
- codex/views/browser/group_mtime.py +83 -0
- codex/views/browser/metadata/__init__.py +1 -0
- codex/views/browser/metadata/annotate.py +109 -0
- codex/views/browser/metadata/copy_intersections.py +90 -0
- codex/views/browser/metadata/metadata.py +83 -0
- codex/views/browser/metadata/query_intersections.py +112 -0
- codex/views/browser/mtime.py +2 -10
- codex/views/browser/order_by.py +22 -22
- codex/views/browser/page_in_bounds.py +2 -2
- codex/views/browser/paginate.py +9 -15
- codex/views/browser/params.py +64 -0
- codex/views/browser/title.py +1 -6
- codex/views/browser/validate.py +68 -33
- codex/views/download.py +1 -1
- codex/views/mixins.py +27 -15
- codex/views/opds/const.py +2 -3
- codex/views/opds/urls.py +2 -2
- codex/views/opds/util.py +2 -3
- codex/views/opds/v1/entry/links.py +3 -1
- codex/views/opds/v1/facets.py +65 -12
- codex/views/opds/v1/feed.py +5 -64
- codex/views/opds/v1/links.py +5 -4
- codex/views/opds/v2/feed.py +1 -28
- codex/views/opds/v2/links.py +26 -1
- codex/views/opds/v2/publications.py +5 -6
- codex/views/opds/v2/top_links.py +3 -6
- codex/views/public.py +2 -2
- codex/views/reader/arcs.py +0 -3
- codex/views/reader/books.py +57 -41
- codex/views/reader/page.py +10 -4
- codex/views/reader/{init.py → params.py} +37 -31
- codex/views/reader/reader.py +1 -2
- codex/views/session.py +43 -54
- codex/views/timezone.py +54 -0
- codex/views/version.py +1 -1
- {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/METADATA +1 -1
- {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/RECORD +417 -383
- codex/serializers/choices.py +0 -196
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js +0 -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-CWiqcZ7a.js +0 -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-Dk3iB62X.bc66d5351baa.js +0 -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-Dk3iB62X.js +0 -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-D1_KU27A.b1d30587a973.js +0 -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-D1_KU27A.js +0 -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-38np3TSA.5a0cb7a9e56d.css +0 -1
- codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
- codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
- codex/static_root/assets/VDialog-38np3TSA.css +0 -1
- codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
- codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js +0 -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-GbxvFMMN.js +0 -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-Cywv_bEj.a762e63858e5.js +0 -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-Cywv_bEj.js +0 -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-C0etmxpZ.0eb3009a6300.js +0 -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-C0etmxpZ.js +0 -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-Ca2bIOso.6613989e4c45.css +0 -1
- codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css +0 -1
- codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js +0 -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-txrnRiNJ.js +0 -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-CY6RmcvW.7e8824224f6c.js +0 -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-CY6RmcvW.js +0 -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-Dk_1upHt.2c836070ec75.css +0 -1
- codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.br +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css +0 -1
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css.br +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js +0 -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-DtEQMwY1.js +0 -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-2af995xo.6c5bf36631b2.js +0 -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-2af995xo.js +0 -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-Crc3gdNh.39995ccf4d52.css +0 -1
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
- codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js +0 -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-G6CMj7yO.js +0 -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-CzoGd6Ul.721e5da11ad4.js +0 -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-CzoGd6Ul.js +0 -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-CWEjaFr5.7d4523387384.css.br +0 -0
- codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
- codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
- codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js +0 -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-CsPAIQeG.js +0 -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-BAT7GA64.73a064628c19.js +0 -30
- 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-BAT7GA64.js +0 -30
- 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/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +0 -1
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +0 -1
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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-BmhedLCx.7c2103d319d4.css +0 -1
- codex/static_root/assets/confirm-dialog-BmhedLCx.css +0 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js +0 -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-CbW3hlOp.js +0 -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-uSqKZU37.b157b0743db5.js +0 -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-uSqKZU37.js +0 -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-CBl5KEsB.845a81573c2e.js +0 -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-CBl5KEsB.js +0 -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 +0 -1
- 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 +0 -1
- 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/flag-tab-DBqpLKi7.css +0 -1
- codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +0 -1
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js +0 -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-BOW9kigw.js +0 -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/group-tab-CBKuS3Iv.7e4746634782.css +0 -1
- codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.br +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.gz +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css +0 -1
- codex/static_root/assets/group-tab-CBKuS3Iv.css.br +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css.gz +0 -0
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js +0 -1
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +0 -4
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
- codex/static_root/assets/http-error-C6yuLApo.js +0 -1
- codex/static_root/assets/http-error-C6yuLApo.js.br +0 -4
- codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
- codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +0 -2
- codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
- codex/static_root/assets/library-tab-Bh7ErAk7.css.br +0 -2
- codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js +0 -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-D3WOtpE0.js +0 -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 +0 -6
- 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 +0 -6
- 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 +0 -1
- 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 +0 -1
- 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-JH3Kbffr.451171f7ac33.js +0 -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-JH3Kbffr.js +0 -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-BcRNpoaW.882f6e967869.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
- 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 +0 -2
- 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 +0 -2
- 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.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -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-3tTlR5QH.71f18bc92adc.js +0 -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-3tTlR5QH.js +0 -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-BdyFhA_r.151f4c3061ed.js +0 -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-BdyFhA_r.js +0 -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 +0 -1
- 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 +0 -1
- 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-DpfGsVX3.1cb66f2649a9.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
- 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-1XiWn9xP.06cb8e1b794d.css +0 -1
- codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css.gz +0 -0
- codex/static_root/assets/task-tab-1XiWn9xP.css +0 -1
- codex/static_root/assets/task-tab-1XiWn9xP.css.gz +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js +0 -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-C8D0qRP_.js +0 -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 +0 -1
- 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 +0 -1
- codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js +0 -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-BU31Z92y.js +0 -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 +0 -1
- 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 +0 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/choices.2f6b0359e1ef.json +0 -1
- codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
- codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
- codex/static_root/js/choices.json +0 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/manifest.45cb46e19686.json +0 -635
- codex/static_root/manifest.45cb46e19686.json.br +0 -0
- codex/static_root/manifest.45cb46e19686.json.gz +0 -0
- codex/views/browser/annotations.py +0 -339
- codex/views/browser/base.py +0 -139
- codex/views/browser/filters/annotations.py +0 -125
- codex/views/browser/metadata.py +0 -332
- {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/LICENSE +0 -0
- {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/WHEEL +0 -0
- {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/entry_points.txt +0 -0
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"""Search Filters Methods."""
|
|
2
2
|
|
|
3
|
-
from django.db.models.query import Q
|
|
4
|
-
|
|
5
3
|
from codex.logger.logging import get_logger
|
|
6
|
-
from codex.models import Comic, StoryArc
|
|
7
4
|
from codex.views.browser.filters.search.field.filter import BrowserFieldQueryFilter
|
|
8
5
|
|
|
9
6
|
LOG = get_logger(__name__)
|
|
@@ -12,27 +9,16 @@ LOG = get_logger(__name__)
|
|
|
12
9
|
class BrowserFTSFilter(BrowserFieldQueryFilter):
|
|
13
10
|
"""Search Filters Methods."""
|
|
14
11
|
|
|
15
|
-
@staticmethod
|
|
16
|
-
def _get_fts_filter(model, text):
|
|
17
|
-
"""Get the filter dict."""
|
|
18
|
-
prefix = (
|
|
19
|
-
""
|
|
20
|
-
if model == Comic
|
|
21
|
-
else "storyarcnumber__comic__"
|
|
22
|
-
if model == StoryArc
|
|
23
|
-
else "comic__"
|
|
24
|
-
)
|
|
25
|
-
# Custom Lookup defined in codex.models
|
|
26
|
-
rel = prefix + "comicfts__match"
|
|
27
|
-
return {rel: text}
|
|
28
|
-
|
|
29
12
|
def get_fts_filter(self, model, text):
|
|
30
13
|
"""Perform the search and return the scores as a dict."""
|
|
31
14
|
fts_filter = {}
|
|
32
15
|
try:
|
|
33
16
|
if text:
|
|
34
|
-
|
|
17
|
+
rel = self.get_rel_prefix(model)
|
|
18
|
+
# Custom lookup defined in codex.models
|
|
19
|
+
rel += "comicfts__match"
|
|
20
|
+
fts_filter[rel] = text
|
|
35
21
|
except Exception:
|
|
36
22
|
LOG.exception("Getting Full Text Search Filter.")
|
|
37
23
|
self.search_error = "Error creating full text search filter"
|
|
38
|
-
return
|
|
24
|
+
return fts_filter
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""Search Filters Methods."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
|
|
6
|
+
from django.db.models.query import Q
|
|
4
7
|
|
|
5
8
|
from codex.logger.logging import get_logger
|
|
9
|
+
from codex.models import AdminFlag
|
|
6
10
|
from codex.models.comic import ComicFTS
|
|
7
11
|
from codex.views.browser.filters.search.aliases import ALIAS_FIELD_MAP
|
|
8
12
|
from codex.views.browser.filters.search.fts import BrowserFTSFilter
|
|
@@ -56,24 +60,43 @@ _TOKEN_RE = re.compile(_TOKEN_REXP, flags=re.IGNORECASE)
|
|
|
56
60
|
class SearchFilterView(BrowserFTSFilter):
|
|
57
61
|
"""Search Query Parser."""
|
|
58
62
|
|
|
63
|
+
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
|
|
64
|
+
{
|
|
65
|
+
AdminFlag.FlagChoices.FOLDER_VIEW.value: "folder_view",
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
59
69
|
def __init__(self, *args, **kwargs):
|
|
60
70
|
"""Initialize search variables."""
|
|
61
71
|
super().__init__(*args, **kwargs)
|
|
72
|
+
self._admin_flags: MappingProxyType[str, bool] | None = None
|
|
62
73
|
self.fts_mode = False
|
|
63
74
|
self.search_mode = False
|
|
64
75
|
self.search_error = ""
|
|
65
76
|
|
|
77
|
+
@property
|
|
78
|
+
def admin_flags(self) -> MappingProxyType[str, bool]:
|
|
79
|
+
"""Set browser relevant admin flags."""
|
|
80
|
+
if self._admin_flags is None:
|
|
81
|
+
if self.ADMIN_FLAG_VALUE_KEY_MAP:
|
|
82
|
+
admin_pairs = AdminFlag.objects.filter(
|
|
83
|
+
key__in=self.ADMIN_FLAG_VALUE_KEY_MAP.keys()
|
|
84
|
+
).values_list("key", "on")
|
|
85
|
+
else:
|
|
86
|
+
admin_pairs = ()
|
|
87
|
+
admin_flags = {}
|
|
88
|
+
for key, on in admin_pairs:
|
|
89
|
+
export_key = self.ADMIN_FLAG_VALUE_KEY_MAP[key]
|
|
90
|
+
admin_flags[export_key] = on
|
|
91
|
+
self._admin_flags = MappingProxyType(admin_flags)
|
|
92
|
+
return self._admin_flags
|
|
93
|
+
|
|
66
94
|
def _is_path_column_allowed(self):
|
|
67
95
|
"""Is path column allowed."""
|
|
68
|
-
|
|
69
|
-
if not "folder_view" not in self.admin_flags: # type: ignore
|
|
70
|
-
# Ensure admin flags for Cover View
|
|
71
|
-
self.set_admin_flags() # type: ignore
|
|
72
|
-
return bool(self.admin_flags.get("folder_view")) # type: ignore
|
|
73
|
-
return True
|
|
96
|
+
return self.is_admin or bool(self.admin_flags["folder_view"])
|
|
74
97
|
|
|
75
98
|
@staticmethod
|
|
76
|
-
def _is_column_operators_used(exp):
|
|
99
|
+
def _is_column_operators_used(exp) -> bool:
|
|
77
100
|
"""Detect column expression operators, but not inside quotes."""
|
|
78
101
|
for match in _COLUMN_EXPRESSION_OPERATORS_RE.finditer(exp):
|
|
79
102
|
if match.group("star"):
|
|
@@ -149,7 +172,7 @@ class SearchFilterView(BrowserFTSFilter):
|
|
|
149
172
|
field_exclude_q_list = []
|
|
150
173
|
for preop, field_token_pairs in field_tokens_dict.items():
|
|
151
174
|
if preop == "not":
|
|
152
|
-
exclude_q_list,
|
|
175
|
+
exclude_q_list, include_q_list = self.get_search_field_filters(
|
|
153
176
|
model, field_token_pairs
|
|
154
177
|
)
|
|
155
178
|
else:
|
|
@@ -157,37 +180,44 @@ class SearchFilterView(BrowserFTSFilter):
|
|
|
157
180
|
# XXX cannot do OR queries with MATCH, it decontextualizes MATCH somehow.
|
|
158
181
|
if preop == "or":
|
|
159
182
|
self.search_error = "OR preceding column tokens with operator expressions will act as AND"
|
|
160
|
-
|
|
183
|
+
include_q_list, exclude_q_list = self.get_search_field_filters(
|
|
161
184
|
model, field_token_pairs
|
|
162
185
|
)
|
|
163
|
-
field_filter_q_list +=
|
|
186
|
+
field_filter_q_list += include_q_list
|
|
164
187
|
field_exclude_q_list += exclude_q_list
|
|
165
|
-
fts_filter = self.get_fts_filter(model, fts_text)
|
|
166
|
-
return field_exclude_q_list, field_filter_q_list, fts_filter
|
|
167
188
|
|
|
168
|
-
|
|
189
|
+
fts_filter_dict = self.get_fts_filter(model, fts_text)
|
|
190
|
+
if fts_filter_dict:
|
|
191
|
+
self.fts_mode = True
|
|
192
|
+
fts_q = Q(**fts_filter_dict)
|
|
193
|
+
else:
|
|
194
|
+
fts_q = Q()
|
|
195
|
+
|
|
196
|
+
return field_filter_q_list, field_exclude_q_list, fts_q
|
|
197
|
+
|
|
198
|
+
def _create_search_filter(self, filter_list):
|
|
169
199
|
"""Apply search filter lists. Separate filter clauses are employed for m2m searches."""
|
|
200
|
+
combined_q = Q()
|
|
170
201
|
for q in filter_list:
|
|
171
202
|
if not q:
|
|
172
203
|
continue
|
|
173
|
-
|
|
204
|
+
combined_q &= q
|
|
174
205
|
self.search_mode = True
|
|
175
206
|
|
|
176
|
-
return
|
|
207
|
+
return combined_q
|
|
177
208
|
|
|
178
|
-
def
|
|
209
|
+
def get_search_filters(self, model):
|
|
179
210
|
"""Preparse search, search and return the filter and scores."""
|
|
211
|
+
include_q = Q()
|
|
212
|
+
exclude_q = Q()
|
|
213
|
+
fts_q = Q()
|
|
180
214
|
try:
|
|
181
|
-
|
|
215
|
+
field_filter_q_list, field_exclude_q_list, fts_q = (
|
|
182
216
|
self._create_search_filters(model)
|
|
183
217
|
)
|
|
184
|
-
|
|
185
218
|
# Apply filters
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if fts_filter:
|
|
189
|
-
qs = qs.filter(fts_filter)
|
|
190
|
-
self.search_mode = self.fts_mode = True
|
|
219
|
+
include_q = self._create_search_filter(field_filter_q_list)
|
|
220
|
+
exclude_q = self._create_search_filter(field_exclude_q_list)
|
|
191
221
|
|
|
192
222
|
except Exception as exc:
|
|
193
223
|
msg = "Creating search filters"
|
|
@@ -195,7 +225,7 @@ class SearchFilterView(BrowserFTSFilter):
|
|
|
195
225
|
msg = f"{msg} - {exc}"
|
|
196
226
|
self.search_error = msg
|
|
197
227
|
|
|
198
|
-
return
|
|
228
|
+
return include_q, exclude_q, fts_q
|
|
199
229
|
|
|
200
230
|
def _is_search_results_limited(self) -> bool:
|
|
201
231
|
"""Get search result limit from params."""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Group Mtime Function."""
|
|
2
|
+
|
|
3
|
+
from logging import DEBUG, WARNING
|
|
4
|
+
|
|
5
|
+
from django.db.models.aggregates import Aggregate, Max
|
|
6
|
+
from django.db.models.functions import Greatest
|
|
7
|
+
from django.db.utils import OperationalError
|
|
8
|
+
|
|
9
|
+
from codex.logger.logging import get_logger
|
|
10
|
+
from codex.models.functions import JsonGroupArray
|
|
11
|
+
from codex.views.browser.filters.filter import BrowserFilterView
|
|
12
|
+
from codex.views.const import EPOCH_START, EPOCH_START_DATETIMEFIELD, NONE_DATETIMEFIELD
|
|
13
|
+
|
|
14
|
+
_FTS5_PREFIX = "fts5: "
|
|
15
|
+
LOG = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BrowserGroupMtimeView(BrowserFilterView):
|
|
19
|
+
"""Annotations that also filter."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
"""Initialize memoized values."""
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
self._is_bookmark_filtered: bool | None = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def is_bookmark_filtered(self):
|
|
28
|
+
"""Is bookmark filter in effect."""
|
|
29
|
+
if self._is_bookmark_filtered is None:
|
|
30
|
+
self._is_bookmark_filtered = bool(
|
|
31
|
+
self.params.get("filters", {}).get("bookmark")
|
|
32
|
+
)
|
|
33
|
+
return self._is_bookmark_filtered
|
|
34
|
+
|
|
35
|
+
def _handle_operational_error(self, err):
|
|
36
|
+
msg = err.args[0] if err.args else ""
|
|
37
|
+
if msg.startswith(_FTS5_PREFIX):
|
|
38
|
+
level = DEBUG
|
|
39
|
+
self.search_error = msg.removeprefix(_FTS5_PREFIX)
|
|
40
|
+
else:
|
|
41
|
+
level = WARNING
|
|
42
|
+
msg = str(err)
|
|
43
|
+
LOG.log(level, f"Query Error: {msg}")
|
|
44
|
+
|
|
45
|
+
def get_max_bookmark_updated_at_aggregate(
|
|
46
|
+
self, model, agg_func: type[Aggregate] = Max, default=NONE_DATETIMEFIELD
|
|
47
|
+
):
|
|
48
|
+
"""Get filtered maximum bookmark updated_at relation."""
|
|
49
|
+
bm_rel = self._get_bm_rel(model)
|
|
50
|
+
bm_filter = self._get_my_bookmark_filter(bm_rel)
|
|
51
|
+
bmua_rel = f"{bm_rel}__updated_at"
|
|
52
|
+
args = {"default": default, "filter": bm_filter}
|
|
53
|
+
if agg_func is JsonGroupArray:
|
|
54
|
+
args["distinct"] = True
|
|
55
|
+
return agg_func(bmua_rel, **args)
|
|
56
|
+
|
|
57
|
+
def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
|
|
58
|
+
"""Get a filtered mtime for browser pages and mtime checker."""
|
|
59
|
+
qs = self.get_filtered_queryset(
|
|
60
|
+
model,
|
|
61
|
+
group=group,
|
|
62
|
+
pks=pks,
|
|
63
|
+
page_mtime=page_mtime,
|
|
64
|
+
bookmark_filter=self.is_bookmark_filtered,
|
|
65
|
+
)
|
|
66
|
+
mua = Max("updated_at", default=EPOCH_START_DATETIMEFIELD)
|
|
67
|
+
mbua = self.get_max_bookmark_updated_at_aggregate(
|
|
68
|
+
model, default=EPOCH_START_DATETIMEFIELD
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
qs = qs.annotate(max=Greatest(mua, mbua))
|
|
73
|
+
# Forcing inner joins makes search work
|
|
74
|
+
# Can't run demote_joins on aggregate.
|
|
75
|
+
qs = self.force_inner_joins(qs)
|
|
76
|
+
first = qs.first()
|
|
77
|
+
mtime = first.max if first else EPOCH_START
|
|
78
|
+
except OperationalError as exc:
|
|
79
|
+
self._handle_operational_error(exc)
|
|
80
|
+
mtime = None
|
|
81
|
+
if mtime == NotImplemented:
|
|
82
|
+
mtime = None
|
|
83
|
+
return mtime
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Metadata View."""
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Metadata Annotations."""
|
|
2
|
+
|
|
3
|
+
from django.db.models import Count, IntegerField, Sum, Value
|
|
4
|
+
|
|
5
|
+
from codex.logger.logging import get_logger
|
|
6
|
+
from codex.models import Comic
|
|
7
|
+
from codex.views.browser.annotate.card import BrowserAnnotateCardView
|
|
8
|
+
|
|
9
|
+
LOG = get_logger(__name__)
|
|
10
|
+
_ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
|
|
11
|
+
_COMIC_VALUE_FIELDS_CONFLICTING = frozenset(
|
|
12
|
+
{
|
|
13
|
+
"created_at",
|
|
14
|
+
"name",
|
|
15
|
+
"path",
|
|
16
|
+
"updated_at",
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
_DISABLED_VALUE_FIELD_NAMES = frozenset(
|
|
20
|
+
{"id", "pk", "sort_name", "stat"} | _COMIC_VALUE_FIELDS_CONFLICTING
|
|
21
|
+
)
|
|
22
|
+
_COMIC_VALUE_FIELD_NAMES = frozenset(
|
|
23
|
+
# contains path
|
|
24
|
+
field.name
|
|
25
|
+
for field in Comic._meta.get_fields()
|
|
26
|
+
if not field.is_relation and field.name not in _DISABLED_VALUE_FIELD_NAMES
|
|
27
|
+
)
|
|
28
|
+
_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
|
|
29
|
+
_COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_count"})
|
|
30
|
+
_PATH_GROUPS = frozenset({"c", "f"})
|
|
31
|
+
_SUM_FIELDS = frozenset({"page_count", "size"})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MetadataAnnotateView(BrowserAnnotateCardView):
|
|
35
|
+
"""Metadata Annotations."""
|
|
36
|
+
|
|
37
|
+
def _get_comic_value_fields(self):
|
|
38
|
+
"""Include the path field for staff."""
|
|
39
|
+
fields = set(_COMIC_VALUE_FIELD_NAMES)
|
|
40
|
+
group = self.kwargs["group"]
|
|
41
|
+
if (
|
|
42
|
+
not (self.is_admin and self.admin_flags["folder_view"])
|
|
43
|
+
or group not in _PATH_GROUPS
|
|
44
|
+
):
|
|
45
|
+
fields -= _ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS
|
|
46
|
+
return tuple(fields)
|
|
47
|
+
|
|
48
|
+
def _intersection_annotate(
|
|
49
|
+
self,
|
|
50
|
+
querysets,
|
|
51
|
+
fields,
|
|
52
|
+
related_suffix="",
|
|
53
|
+
annotation_prefix="",
|
|
54
|
+
):
|
|
55
|
+
"""Annotate the intersection of value and fk fields."""
|
|
56
|
+
(filtered_qs, qs) = querysets
|
|
57
|
+
|
|
58
|
+
for field in fields:
|
|
59
|
+
# Annotate variant counts
|
|
60
|
+
# If 1 variant, annotate value, otherwise None
|
|
61
|
+
ann_field = annotation_prefix + field.replace("__", "_")
|
|
62
|
+
|
|
63
|
+
full_field = self.rel_prefix + field
|
|
64
|
+
|
|
65
|
+
if field in _SUM_FIELDS:
|
|
66
|
+
val = Sum(full_field)
|
|
67
|
+
else:
|
|
68
|
+
# group_by makes the filter work, but prevents its
|
|
69
|
+
# use as a subquery in the big query.
|
|
70
|
+
sq = filtered_qs.alias(
|
|
71
|
+
filter_count=Count(full_field, distinct=True)
|
|
72
|
+
).filter(filter_count=1)
|
|
73
|
+
sq = self.add_group_by(sq)
|
|
74
|
+
sq = self.force_inner_joins(sq)
|
|
75
|
+
sq = sq.values_list(full_field + related_suffix, flat=True)
|
|
76
|
+
try:
|
|
77
|
+
val = sq[0]
|
|
78
|
+
except IndexError:
|
|
79
|
+
val = None
|
|
80
|
+
if field.endswith("count"):
|
|
81
|
+
output_field = IntegerField()
|
|
82
|
+
else:
|
|
83
|
+
output_field = Comic._meta.get_field(field).__class__()
|
|
84
|
+
val = Value(val, output_field)
|
|
85
|
+
qs = qs.annotate(**{ann_field: val})
|
|
86
|
+
|
|
87
|
+
return filtered_qs, qs
|
|
88
|
+
|
|
89
|
+
def _annotate_values_and_fks(self, qs, filtered_qs):
|
|
90
|
+
"""Annotate comic values and comic foreign key values."""
|
|
91
|
+
# Simple Values
|
|
92
|
+
querysets = (filtered_qs, qs)
|
|
93
|
+
if qs.model is not Comic:
|
|
94
|
+
comic_value_fields = self._get_comic_value_fields()
|
|
95
|
+
querysets = self._intersection_annotate(querysets, comic_value_fields)
|
|
96
|
+
|
|
97
|
+
# Conflicting Simple Values
|
|
98
|
+
querysets = self._intersection_annotate(
|
|
99
|
+
querysets,
|
|
100
|
+
_COMIC_VALUE_FIELDS_CONFLICTING,
|
|
101
|
+
annotation_prefix=_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Foreign Keys with special count values
|
|
105
|
+
_, qs = self._intersection_annotate(
|
|
106
|
+
querysets,
|
|
107
|
+
_COMIC_RELATED_VALUE_FIELDS,
|
|
108
|
+
)
|
|
109
|
+
return qs
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Copy Intersections Into Comic Fields."""
|
|
2
|
+
|
|
3
|
+
from codex.logger.logging import get_logger
|
|
4
|
+
from codex.models.comic import Comic
|
|
5
|
+
from codex.serializers.browser.metadata import PREFETCH_PREFIX
|
|
6
|
+
from codex.views.browser.metadata.query_intersections import (
|
|
7
|
+
MetadataQueryIntersectionsView,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
LOG = get_logger(__name__)
|
|
11
|
+
_COMIC_VALUE_FIELDS_CONFLICTING = frozenset(
|
|
12
|
+
{
|
|
13
|
+
"created_at",
|
|
14
|
+
"name",
|
|
15
|
+
"path",
|
|
16
|
+
"updated_at",
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
_COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
|
|
20
|
+
_PATH_GROUPS = frozenset({"c", "f"})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MetadataCopyIntersectionsView(MetadataQueryIntersectionsView):
|
|
24
|
+
"""Copy Intersections Into Comic Fields."""
|
|
25
|
+
|
|
26
|
+
def _path_security(self, obj):
|
|
27
|
+
"""Secure filesystem information for acl situation."""
|
|
28
|
+
group = self.kwargs["group"]
|
|
29
|
+
is_path_group = group in _PATH_GROUPS
|
|
30
|
+
if is_path_group:
|
|
31
|
+
if self.is_admin:
|
|
32
|
+
return
|
|
33
|
+
if self.admin_flags["folder_view"]:
|
|
34
|
+
obj.path = obj.search_path()
|
|
35
|
+
else:
|
|
36
|
+
obj.path = ""
|
|
37
|
+
|
|
38
|
+
def _highlight_current_group(self, obj):
|
|
39
|
+
"""Values for highlighting the current group."""
|
|
40
|
+
if self.model and self.model is not Comic:
|
|
41
|
+
# move the name of the group to the correct field
|
|
42
|
+
field = self.model.__name__.lower() + "_list"
|
|
43
|
+
group_list = self.model.objects.filter(pk__in=obj.ids).values("name")
|
|
44
|
+
setattr(obj, field, group_list)
|
|
45
|
+
obj.name = None
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def _copy_m2m_intersections(cls, obj, m2m_intersections):
|
|
49
|
+
"""Copy the m2m intersections into the object."""
|
|
50
|
+
# XXX It might even be faster to copy everything to a dict and not use the obj.
|
|
51
|
+
for key, qs in m2m_intersections.items():
|
|
52
|
+
serializer_key = (
|
|
53
|
+
f"{PREFETCH_PREFIX}{key}"
|
|
54
|
+
if key in ("identifiers", "contributors", "story_arc_numbers")
|
|
55
|
+
else key
|
|
56
|
+
)
|
|
57
|
+
if hasattr(obj, serializer_key):
|
|
58
|
+
# real db fields need to use their special set method.
|
|
59
|
+
field = getattr(obj, serializer_key)
|
|
60
|
+
field.set(
|
|
61
|
+
qs,
|
|
62
|
+
clear=True,
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
# fake db field is just a queryset attached.
|
|
66
|
+
setattr(obj, serializer_key, qs)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _copy_groups(obj, groups):
|
|
70
|
+
for field, group_qs in groups.items():
|
|
71
|
+
setattr(obj, field + "_list", group_qs)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _copy_conflicting_simple_fields(obj):
|
|
75
|
+
for field in _COMIC_VALUE_FIELDS_CONFLICTING:
|
|
76
|
+
"""Copy conflicting fields over naturral fields."""
|
|
77
|
+
conflict_field = _COMIC_VALUE_FIELDS_CONFLICTING_PREFIX + field
|
|
78
|
+
val = getattr(obj, conflict_field)
|
|
79
|
+
setattr(obj, field, val)
|
|
80
|
+
|
|
81
|
+
def copy_intersections_into_comic_fields(self, obj, groups, m2m_intersections):
|
|
82
|
+
"""Copy a bunch of values that i couldn't fit cleanly in the main queryset."""
|
|
83
|
+
self._path_security(obj)
|
|
84
|
+
self._highlight_current_group(obj)
|
|
85
|
+
self._copy_groups(obj, groups)
|
|
86
|
+
self._copy_m2m_intersections(obj, m2m_intersections)
|
|
87
|
+
if self.model is not Comic:
|
|
88
|
+
self._copy_conflicting_simple_fields(obj)
|
|
89
|
+
|
|
90
|
+
return obj
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Aggregate Group and Comic Metadata View."""
|
|
2
|
+
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
|
|
5
|
+
from drf_spectacular.utils import extend_schema
|
|
6
|
+
from rest_framework.exceptions import NotFound
|
|
7
|
+
from rest_framework.response import Response
|
|
8
|
+
|
|
9
|
+
from codex.logger.logging import get_logger
|
|
10
|
+
from codex.models import AdminFlag
|
|
11
|
+
from codex.serializers.browser.metadata import MetadataSerializer
|
|
12
|
+
from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
|
|
13
|
+
from codex.views.browser.metadata.copy_intersections import (
|
|
14
|
+
MetadataCopyIntersectionsView,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
LOG = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MetadataView(MetadataCopyIntersectionsView):
|
|
21
|
+
"""Aggregate Group and Comic Metadata View."""
|
|
22
|
+
|
|
23
|
+
serializer_class = MetadataSerializer
|
|
24
|
+
input_serializer_class = BrowserFilterChoicesInputSerilalizer
|
|
25
|
+
TARGET = "metadata"
|
|
26
|
+
ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
|
|
27
|
+
{
|
|
28
|
+
AdminFlag.FlagChoices.FOLDER_VIEW.value: "folder_view",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _get_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
|
|
33
|
+
"""Limited allowed nav groups for metadata."""
|
|
34
|
+
# Overrides method in browser.validate
|
|
35
|
+
group = self.kwargs["group"]
|
|
36
|
+
return (group,)
|
|
37
|
+
|
|
38
|
+
def _raise_not_found(self, exc=None):
|
|
39
|
+
"""Raise an exception if the object is not found."""
|
|
40
|
+
pks = self.kwargs["pks"]
|
|
41
|
+
group = self.kwargs["group"]
|
|
42
|
+
detail = f"Filtered metadata for {group}/{pks} not found"
|
|
43
|
+
raise NotFound(detail=detail) from exc
|
|
44
|
+
|
|
45
|
+
def get_object(self):
|
|
46
|
+
"""Create a comic-like object from the current browser group."""
|
|
47
|
+
# Comic model goes through the same code path as groups because
|
|
48
|
+
# values dicts don't copy relations to the serializer. The values
|
|
49
|
+
# dict is necessary because of the folders view union in browser.py.
|
|
50
|
+
|
|
51
|
+
qs = self.get_filtered_queryset(self.model)
|
|
52
|
+
filtered_qs = qs
|
|
53
|
+
|
|
54
|
+
# Annotate
|
|
55
|
+
qs = self.annotate_order_aggregates(qs)
|
|
56
|
+
qs = self.annotate_card_aggregates(qs)
|
|
57
|
+
qs = self._annotate_values_and_fks(qs, filtered_qs)
|
|
58
|
+
qs = self.add_group_by(qs)
|
|
59
|
+
qs = self.force_inner_joins(qs)
|
|
60
|
+
|
|
61
|
+
# Get Object
|
|
62
|
+
try:
|
|
63
|
+
obj = qs[0]
|
|
64
|
+
if not obj:
|
|
65
|
+
reason = "Empty obj"
|
|
66
|
+
raise ValueError(reason) # noqa TRY301
|
|
67
|
+
except (IndexError, ValueError) as exc:
|
|
68
|
+
return self._raise_not_found(exc)
|
|
69
|
+
|
|
70
|
+
# Hacks to add to object after query
|
|
71
|
+
groups, m2m_intersections = self.query_intersections(filtered_qs)
|
|
72
|
+
return self.copy_intersections_into_comic_fields(obj, groups, m2m_intersections)
|
|
73
|
+
|
|
74
|
+
@extend_schema(parameters=[input_serializer_class])
|
|
75
|
+
def get(self, *_args, **_kwargs):
|
|
76
|
+
"""Get metadata for a filtered browse group."""
|
|
77
|
+
# Init
|
|
78
|
+
try:
|
|
79
|
+
obj = self.get_object()
|
|
80
|
+
serializer = self.get_serializer(obj)
|
|
81
|
+
return Response(serializer.data)
|
|
82
|
+
except Exception:
|
|
83
|
+
LOG.exception(f"Getting metadata {self.kwargs}")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Metadata query fk & m2m intersections."""
|
|
2
|
+
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
|
|
5
|
+
from django.db.models import Count
|
|
6
|
+
|
|
7
|
+
from codex.librarian.importer.const import COMIC_M2M_FIELD_NAMES
|
|
8
|
+
from codex.logger.logging import get_logger
|
|
9
|
+
from codex.models import Comic
|
|
10
|
+
from codex.models.functions import JsonGroupArray
|
|
11
|
+
from codex.views.browser.metadata.annotate import MetadataAnnotateView
|
|
12
|
+
from codex.views.const import METADATA_GROUP_RELATION
|
|
13
|
+
|
|
14
|
+
LOG = get_logger(__name__)
|
|
15
|
+
_CONTRIBUTOR_RELATIONS = ("role", "person")
|
|
16
|
+
_GROUP_RELS = MappingProxyType(
|
|
17
|
+
{
|
|
18
|
+
"i": ("publisher",),
|
|
19
|
+
"s": (
|
|
20
|
+
"publisher",
|
|
21
|
+
"imprint",
|
|
22
|
+
),
|
|
23
|
+
"v": (
|
|
24
|
+
"publisher",
|
|
25
|
+
"imprint",
|
|
26
|
+
"series",
|
|
27
|
+
),
|
|
28
|
+
"c": ("publisher", "imprint", "series", "volume"),
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MetadataQueryIntersectionsView(MetadataAnnotateView):
|
|
34
|
+
"""Metadata query fk & m2m intersections."""
|
|
35
|
+
|
|
36
|
+
def _query_groups(self):
|
|
37
|
+
"""Query the through models to show group lists."""
|
|
38
|
+
groups = {}
|
|
39
|
+
if not self.model:
|
|
40
|
+
return groups
|
|
41
|
+
group = self.kwargs["group"]
|
|
42
|
+
rel = METADATA_GROUP_RELATION.get(group)
|
|
43
|
+
if not rel:
|
|
44
|
+
return groups
|
|
45
|
+
rel = rel + "__in"
|
|
46
|
+
pks = self.kwargs["pks"]
|
|
47
|
+
group_filter = {rel: pks}
|
|
48
|
+
|
|
49
|
+
for field_name in _GROUP_RELS.get(group, ()):
|
|
50
|
+
field = self.model._meta.get_field(field_name)
|
|
51
|
+
model = field.related_model
|
|
52
|
+
if not model:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
qs = model.objects.filter(**group_filter)
|
|
56
|
+
qs = qs.only("name").distinct()
|
|
57
|
+
qs = qs.group_by("name")
|
|
58
|
+
qs = qs.annotate(ids=JsonGroupArray("id", distinct=True))
|
|
59
|
+
qs = qs.values("ids", "name")
|
|
60
|
+
groups[field_name] = qs
|
|
61
|
+
return groups
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _get_optimized_m2m_query(key, qs):
|
|
65
|
+
if key == "contributors":
|
|
66
|
+
qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS)
|
|
67
|
+
qs = qs.only(*_CONTRIBUTOR_RELATIONS)
|
|
68
|
+
elif key == "story_arc_numbers":
|
|
69
|
+
qs = qs.select_related("story_arc")
|
|
70
|
+
qs = qs.only("story_arc", "number")
|
|
71
|
+
elif key == "identifiers":
|
|
72
|
+
qs = qs.select_related("identifier_type")
|
|
73
|
+
qs = qs.only("identifier_type", "nss", "url")
|
|
74
|
+
else:
|
|
75
|
+
qs = qs.only("name")
|
|
76
|
+
return qs
|
|
77
|
+
|
|
78
|
+
def _get_m2m_intersection_query(self, field_name, comic_pks, comic_pks_count):
|
|
79
|
+
"""Get intersection query for one field."""
|
|
80
|
+
model = Comic._meta.get_field(field_name).related_model
|
|
81
|
+
if not model:
|
|
82
|
+
reason = f"No model found for comic field: {field_name}"
|
|
83
|
+
raise ValueError(reason)
|
|
84
|
+
|
|
85
|
+
intersection_qs = model.objects.filter(comic__pk__in=comic_pks)
|
|
86
|
+
intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
|
|
87
|
+
count=comic_pks_count
|
|
88
|
+
)
|
|
89
|
+
return self._get_optimized_m2m_query(field_name, intersection_qs)
|
|
90
|
+
|
|
91
|
+
def _query_m2m_intersections(self, filtered_qs):
|
|
92
|
+
"""Query the through models to figure out m2m intersections."""
|
|
93
|
+
# Speed ok, but still does a query per m2m model
|
|
94
|
+
m2m_intersections = {}
|
|
95
|
+
pk_field = self.rel_prefix + "pk"
|
|
96
|
+
comic_pks = filtered_qs.distinct().values_list(pk_field, flat=True)
|
|
97
|
+
# In fts mode the join doesn't work for the query.
|
|
98
|
+
# Evaluating it now is probably faster than running the filter for every m2m anyway.
|
|
99
|
+
comic_pks = set(comic_pks)
|
|
100
|
+
comic_pks_count = len(comic_pks)
|
|
101
|
+
|
|
102
|
+
for field_name in COMIC_M2M_FIELD_NAMES:
|
|
103
|
+
m2m_intersections[field_name] = self._get_m2m_intersection_query(
|
|
104
|
+
field_name, comic_pks, comic_pks_count
|
|
105
|
+
)
|
|
106
|
+
return m2m_intersections
|
|
107
|
+
|
|
108
|
+
def query_intersections(self, filtered_qs):
|
|
109
|
+
"""Query complex intersections."""
|
|
110
|
+
groups = self._query_groups()
|
|
111
|
+
m2m_intersections = self._query_m2m_intersections(filtered_qs)
|
|
112
|
+
return groups, m2m_intersections
|