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.

Files changed (715) hide show
  1. codex/choices.py +424 -0
  2. codex/choices_to_json.py +122 -0
  3. codex/db.py +31 -15
  4. codex/exceptions.py +2 -3
  5. codex/librarian/bookmark/__init__.py +1 -0
  6. codex/librarian/bookmark/bookmarkd.py +38 -0
  7. codex/librarian/bookmark/tasks.py +19 -0
  8. codex/librarian/bookmark/update.py +154 -0
  9. codex/librarian/covers/coverd.py +3 -4
  10. codex/librarian/covers/create.py +8 -7
  11. codex/librarian/covers/path.py +1 -2
  12. codex/librarian/covers/purge.py +2 -2
  13. codex/librarian/janitor/integrity.py +1 -0
  14. codex/librarian/librariand.py +5 -0
  15. codex/librarian/watchdog/emitter.py +21 -19
  16. codex/librarian/watchdog/event_batcherd.py +0 -1
  17. codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +4 -0
  18. codex/models/admin.py +0 -5
  19. codex/models/functions.py +6 -5
  20. codex/models/query.py +6 -0
  21. codex/run.py +11 -9
  22. codex/serializers/admin/tasks.py +2 -2
  23. codex/serializers/browser/mixins.py +18 -14
  24. codex/serializers/browser/settings.py +4 -4
  25. codex/serializers/fields.py +9 -5
  26. codex/startup.py +6 -4
  27. codex/static_root/assets/VCheckbox-CCOzQpCw.a65ad95bd21a.js +1 -0
  28. codex/static_root/assets/VCheckbox-CCOzQpCw.a65ad95bd21a.js.br +0 -0
  29. codex/static_root/assets/VCheckbox-CCOzQpCw.a65ad95bd21a.js.gz +0 -0
  30. codex/static_root/assets/VCheckbox-CCOzQpCw.js +1 -0
  31. codex/static_root/assets/VCheckbox-CCOzQpCw.js.br +0 -0
  32. codex/static_root/assets/VCheckbox-CCOzQpCw.js.gz +0 -0
  33. codex/static_root/assets/VCheckboxBtn-DnBH3a96.876fda35b85a.js +1 -0
  34. codex/static_root/assets/VCheckboxBtn-DnBH3a96.876fda35b85a.js.br +0 -0
  35. codex/static_root/assets/VCheckboxBtn-DnBH3a96.876fda35b85a.js.gz +0 -0
  36. codex/static_root/assets/VCheckboxBtn-DnBH3a96.js +1 -0
  37. codex/static_root/assets/VCheckboxBtn-DnBH3a96.js.br +0 -0
  38. codex/static_root/assets/VCheckboxBtn-DnBH3a96.js.gz +0 -0
  39. codex/static_root/assets/VCombobox-DwwoR4yO.e7a4db7ab7f0.js +1 -0
  40. codex/static_root/assets/VCombobox-DwwoR4yO.e7a4db7ab7f0.js.br +0 -0
  41. codex/static_root/assets/VCombobox-DwwoR4yO.e7a4db7ab7f0.js.gz +0 -0
  42. codex/static_root/assets/VCombobox-DwwoR4yO.js +1 -0
  43. codex/static_root/assets/VCombobox-DwwoR4yO.js.br +0 -0
  44. codex/static_root/assets/VCombobox-DwwoR4yO.js.gz +0 -0
  45. codex/static_root/assets/VDialog-CSvrKKb2.css +1 -0
  46. codex/static_root/assets/VDialog-CSvrKKb2.css.br +0 -0
  47. codex/static_root/assets/VDialog-CSvrKKb2.css.gz +0 -0
  48. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css +1 -0
  49. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.br +0 -0
  50. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.gz +0 -0
  51. codex/static_root/assets/VDialog-DbN2FfmF.b2843f79d90f.js +1 -0
  52. codex/static_root/assets/VDialog-DbN2FfmF.b2843f79d90f.js.br +0 -0
  53. codex/static_root/assets/VDialog-DbN2FfmF.b2843f79d90f.js.gz +0 -0
  54. codex/static_root/assets/VDialog-DbN2FfmF.js +1 -0
  55. codex/static_root/assets/VDialog-DbN2FfmF.js.br +0 -0
  56. codex/static_root/assets/VDialog-DbN2FfmF.js.gz +0 -0
  57. codex/static_root/assets/VExpansionPanels-CBPnoy7O.212f4cad7d78.js +1 -0
  58. codex/static_root/assets/VExpansionPanels-CBPnoy7O.212f4cad7d78.js.br +0 -0
  59. codex/static_root/assets/VExpansionPanels-CBPnoy7O.212f4cad7d78.js.gz +0 -0
  60. codex/static_root/assets/VExpansionPanels-CBPnoy7O.js +1 -0
  61. codex/static_root/assets/VExpansionPanels-CBPnoy7O.js.br +0 -0
  62. codex/static_root/assets/VExpansionPanels-CBPnoy7O.js.gz +0 -0
  63. codex/static_root/assets/VRadioGroup-C7Zu_Eup.b42735892aec.js +1 -0
  64. codex/static_root/assets/VRadioGroup-C7Zu_Eup.b42735892aec.js.br +0 -0
  65. codex/static_root/assets/VRadioGroup-C7Zu_Eup.b42735892aec.js.gz +0 -0
  66. codex/static_root/assets/VRadioGroup-C7Zu_Eup.js +1 -0
  67. codex/static_root/assets/VRadioGroup-C7Zu_Eup.js.br +0 -0
  68. codex/static_root/assets/VRadioGroup-C7Zu_Eup.js.gz +0 -0
  69. codex/static_root/assets/VSelect-BTy7SKk0.dee138de5dd0.js +1 -0
  70. codex/static_root/assets/VSelect-BTy7SKk0.dee138de5dd0.js.br +0 -0
  71. codex/static_root/assets/VSelect-BTy7SKk0.dee138de5dd0.js.gz +0 -0
  72. codex/static_root/assets/VSelect-BTy7SKk0.js +1 -0
  73. codex/static_root/assets/VSelect-BTy7SKk0.js.br +0 -0
  74. codex/static_root/assets/VSelect-BTy7SKk0.js.gz +0 -0
  75. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +1 -0
  76. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
  77. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
  78. codex/static_root/assets/VSelect-DLu8bpcs.css +1 -0
  79. codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
  80. codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
  81. codex/static_root/assets/VSelectionControl-B42mmzLA.bf64d259ddef.js +1 -0
  82. codex/static_root/assets/VSelectionControl-B42mmzLA.bf64d259ddef.js.br +0 -0
  83. codex/static_root/assets/VSelectionControl-B42mmzLA.bf64d259ddef.js.gz +0 -0
  84. codex/static_root/assets/VSelectionControl-B42mmzLA.js +1 -0
  85. codex/static_root/assets/VSelectionControl-B42mmzLA.js.br +0 -0
  86. codex/static_root/assets/VSelectionControl-B42mmzLA.js.gz +0 -0
  87. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +1 -0
  88. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
  89. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
  90. codex/static_root/assets/VSlideGroup-BtXPhMOT.css +1 -0
  91. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
  92. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
  93. codex/static_root/assets/VSlideGroup-BtfHoWCn.33e2bd94e34a.js +1 -0
  94. codex/static_root/assets/VSlideGroup-BtfHoWCn.33e2bd94e34a.js.br +0 -0
  95. codex/static_root/assets/VSlideGroup-BtfHoWCn.33e2bd94e34a.js.gz +0 -0
  96. codex/static_root/assets/VSlideGroup-BtfHoWCn.js +1 -0
  97. codex/static_root/assets/VSlideGroup-BtfHoWCn.js.br +0 -0
  98. codex/static_root/assets/VSlideGroup-BtfHoWCn.js.gz +0 -0
  99. codex/static_root/assets/VTable-R3X0iPjV.ee07d9c2ca5f.js +1 -0
  100. codex/static_root/assets/VTable-R3X0iPjV.ee07d9c2ca5f.js.br +0 -0
  101. codex/static_root/assets/VTable-R3X0iPjV.ee07d9c2ca5f.js.gz +0 -0
  102. codex/static_root/assets/VTable-R3X0iPjV.js +1 -0
  103. codex/static_root/assets/VTable-R3X0iPjV.js.br +0 -0
  104. codex/static_root/assets/VTable-R3X0iPjV.js.gz +0 -0
  105. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css +1 -0
  106. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.br +0 -0
  107. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.gz +0 -0
  108. codex/static_root/assets/VTextField-BnCEPZIq.css +1 -0
  109. codex/static_root/assets/VTextField-BnCEPZIq.css.br +0 -0
  110. codex/static_root/assets/VTextField-BnCEPZIq.css.gz +0 -0
  111. codex/static_root/assets/VTextField-D9pqgz_R.47beb9e7e547.js +1 -0
  112. codex/static_root/assets/VTextField-D9pqgz_R.47beb9e7e547.js.br +0 -0
  113. codex/static_root/assets/VTextField-D9pqgz_R.47beb9e7e547.js.gz +0 -0
  114. codex/static_root/assets/VTextField-D9pqgz_R.js +1 -0
  115. codex/static_root/assets/VTextField-D9pqgz_R.js.br +0 -0
  116. codex/static_root/assets/VTextField-D9pqgz_R.js.gz +0 -0
  117. codex/static_root/assets/VWindowItem-DHTDdYfK.f6bf6b893442.js +1 -0
  118. codex/static_root/assets/VWindowItem-DHTDdYfK.f6bf6b893442.js.br +0 -0
  119. codex/static_root/assets/VWindowItem-DHTDdYfK.f6bf6b893442.js.gz +0 -0
  120. codex/static_root/assets/VWindowItem-DHTDdYfK.js +1 -0
  121. codex/static_root/assets/VWindowItem-DHTDdYfK.js.br +0 -0
  122. codex/static_root/assets/VWindowItem-DHTDdYfK.js.gz +0 -0
  123. codex/static_root/assets/admin-CIWyRJnH.dde0ee029f08.js +1 -0
  124. codex/static_root/assets/admin-CIWyRJnH.dde0ee029f08.js.br +0 -0
  125. codex/static_root/assets/admin-CIWyRJnH.dde0ee029f08.js.gz +0 -0
  126. codex/static_root/assets/admin-CIWyRJnH.js +1 -0
  127. codex/static_root/assets/admin-CIWyRJnH.js.br +0 -0
  128. codex/static_root/assets/admin-CIWyRJnH.js.gz +0 -0
  129. codex/static_root/assets/{admin-CWEjaFr5.7d4523387384.css → admin-DJ7pIXDQ.css} +1 -1
  130. codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
  131. codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
  132. codex/static_root/assets/{admin-CWEjaFr5.css → admin-DJ7pIXDQ.e2a77af73ef2.css} +1 -1
  133. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
  134. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
  135. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css +1 -0
  136. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.br +0 -0
  137. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.gz +0 -0
  138. codex/static_root/assets/admin-menu-B9bzD6hV.css +1 -0
  139. codex/static_root/assets/admin-menu-B9bzD6hV.css.br +0 -0
  140. codex/static_root/assets/admin-menu-B9bzD6hV.css.gz +0 -0
  141. codex/static_root/assets/admin-menu-BHOUwxhf.1ed26b58fb9f.js +1 -0
  142. codex/static_root/assets/admin-menu-BHOUwxhf.1ed26b58fb9f.js.br +0 -0
  143. codex/static_root/assets/admin-menu-BHOUwxhf.1ed26b58fb9f.js.gz +0 -0
  144. codex/static_root/assets/admin-menu-BHOUwxhf.js +1 -0
  145. codex/static_root/assets/admin-menu-BHOUwxhf.js.br +0 -0
  146. codex/static_root/assets/admin-menu-BHOUwxhf.js.gz +0 -0
  147. codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.85a213c96423.js +1 -0
  148. codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.85a213c96423.js.br +0 -0
  149. codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.85a213c96423.js.gz +0 -0
  150. codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.js +1 -0
  151. codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.js.br +0 -0
  152. codex/static_root/assets/admin-settings-button-progress-DHIl0C8k.js.gz +0 -0
  153. codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.2548c49a853e.css +1 -0
  154. codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.css +1 -0
  155. codex/static_root/assets/browser-6T4uwUjJ.7dd4a7f74df6.js +1 -0
  156. codex/static_root/assets/browser-6T4uwUjJ.7dd4a7f74df6.js.br +0 -0
  157. codex/static_root/assets/browser-6T4uwUjJ.7dd4a7f74df6.js.gz +0 -0
  158. codex/static_root/assets/browser-6T4uwUjJ.js +1 -0
  159. codex/static_root/assets/browser-6T4uwUjJ.js.br +0 -0
  160. codex/static_root/assets/browser-6T4uwUjJ.js.gz +0 -0
  161. codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css +1 -0
  162. codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.br +0 -0
  163. codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.gz +0 -0
  164. codex/static_root/assets/browser-Br5CorL4.css +1 -0
  165. codex/static_root/assets/browser-Br5CorL4.css.br +0 -0
  166. codex/static_root/assets/browser-Br5CorL4.css.gz +0 -0
  167. codex/static_root/assets/change-password-dialog-BfiQBpUn.48db4c906920.js +1 -0
  168. codex/static_root/assets/change-password-dialog-BfiQBpUn.48db4c906920.js.br +0 -0
  169. codex/static_root/assets/change-password-dialog-BfiQBpUn.48db4c906920.js.gz +0 -0
  170. codex/static_root/assets/change-password-dialog-BfiQBpUn.js +1 -0
  171. codex/static_root/assets/change-password-dialog-BfiQBpUn.js.br +0 -0
  172. codex/static_root/assets/change-password-dialog-BfiQBpUn.js.gz +0 -0
  173. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css +1 -0
  174. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.br +0 -0
  175. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.gz +0 -0
  176. codex/static_root/assets/change-password-dialog-DrIXdKUC.css +1 -0
  177. codex/static_root/assets/change-password-dialog-DrIXdKUC.css.br +0 -0
  178. codex/static_root/assets/change-password-dialog-DrIXdKUC.css.gz +0 -0
  179. codex/static_root/assets/confirm-dialog-B7w6z2rf.472011c06253.css +1 -0
  180. codex/static_root/assets/confirm-dialog-B7w6z2rf.css +1 -0
  181. codex/static_root/assets/confirm-dialog-Iyuhxfyi.cf28325e58b7.js +1 -0
  182. codex/static_root/assets/confirm-dialog-Iyuhxfyi.cf28325e58b7.js.br +0 -0
  183. codex/static_root/assets/confirm-dialog-Iyuhxfyi.cf28325e58b7.js.gz +0 -0
  184. codex/static_root/assets/confirm-dialog-Iyuhxfyi.js +1 -0
  185. codex/static_root/assets/confirm-dialog-Iyuhxfyi.js.br +0 -0
  186. codex/static_root/assets/confirm-dialog-Iyuhxfyi.js.gz +0 -0
  187. codex/static_root/assets/datetime-column-Dnxh9sRh.b6a92a6bcce9.js +1 -0
  188. codex/static_root/assets/datetime-column-Dnxh9sRh.b6a92a6bcce9.js.br +0 -0
  189. codex/static_root/assets/datetime-column-Dnxh9sRh.b6a92a6bcce9.js.gz +0 -0
  190. codex/static_root/assets/datetime-column-Dnxh9sRh.js +1 -0
  191. codex/static_root/assets/datetime-column-Dnxh9sRh.js.br +0 -0
  192. codex/static_root/assets/datetime-column-Dnxh9sRh.js.gz +0 -0
  193. codex/static_root/assets/filter-CmOjoKbH.ea79c4727d92.js +1 -0
  194. codex/static_root/assets/filter-CmOjoKbH.ea79c4727d92.js.br +0 -0
  195. codex/static_root/assets/filter-CmOjoKbH.ea79c4727d92.js.gz +0 -0
  196. codex/static_root/assets/filter-CmOjoKbH.js +1 -0
  197. codex/static_root/assets/filter-CmOjoKbH.js.br +0 -0
  198. codex/static_root/assets/filter-CmOjoKbH.js.gz +0 -0
  199. codex/static_root/assets/flag-tab-1PeNvDA9.2fc60db57915.js +1 -0
  200. codex/static_root/assets/flag-tab-1PeNvDA9.2fc60db57915.js.br +0 -0
  201. codex/static_root/assets/flag-tab-1PeNvDA9.2fc60db57915.js.gz +0 -0
  202. codex/static_root/assets/flag-tab-1PeNvDA9.js +1 -0
  203. codex/static_root/assets/flag-tab-1PeNvDA9.js.br +0 -0
  204. codex/static_root/assets/flag-tab-1PeNvDA9.js.gz +0 -0
  205. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +1 -0
  206. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
  207. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
  208. codex/static_root/assets/flag-tab-CqWovuBu.css +1 -0
  209. codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
  210. codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
  211. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css +1 -0
  212. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.br +0 -0
  213. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.gz +0 -0
  214. codex/static_root/assets/group-tab-DkIN1Heu.css +1 -0
  215. codex/static_root/assets/group-tab-DkIN1Heu.css.br +0 -0
  216. codex/static_root/assets/group-tab-DkIN1Heu.css.gz +0 -0
  217. codex/static_root/assets/group-tab-Zl_C7M7Z.dbeed7f2d08f.js +1 -0
  218. codex/static_root/assets/group-tab-Zl_C7M7Z.dbeed7f2d08f.js.br +0 -0
  219. codex/static_root/assets/group-tab-Zl_C7M7Z.dbeed7f2d08f.js.gz +0 -0
  220. codex/static_root/assets/group-tab-Zl_C7M7Z.js +1 -0
  221. codex/static_root/assets/group-tab-Zl_C7M7Z.js.br +0 -0
  222. codex/static_root/assets/group-tab-Zl_C7M7Z.js.gz +0 -0
  223. codex/static_root/assets/http-error-YNrrsTPg.73e9978dc0ce.js +1 -0
  224. codex/static_root/assets/http-error-YNrrsTPg.73e9978dc0ce.js.br +0 -0
  225. codex/static_root/assets/http-error-YNrrsTPg.73e9978dc0ce.js.gz +0 -0
  226. codex/static_root/assets/http-error-YNrrsTPg.js +1 -0
  227. codex/static_root/assets/http-error-YNrrsTPg.js.br +0 -0
  228. codex/static_root/assets/http-error-YNrrsTPg.js.gz +0 -0
  229. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css +1 -0
  230. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.br +0 -0
  231. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.gz +0 -0
  232. codex/static_root/assets/index-CL_6g4tJ.css +1 -0
  233. codex/static_root/assets/index-CL_6g4tJ.css.br +0 -0
  234. codex/static_root/assets/index-CL_6g4tJ.css.gz +0 -0
  235. codex/static_root/assets/index-j5K-C_JC.65c2dc5af141.js +1 -0
  236. codex/static_root/assets/index-j5K-C_JC.65c2dc5af141.js.br +0 -0
  237. codex/static_root/assets/index-j5K-C_JC.65c2dc5af141.js.gz +0 -0
  238. codex/static_root/assets/index-j5K-C_JC.js +1 -0
  239. codex/static_root/assets/index-j5K-C_JC.js.br +0 -0
  240. codex/static_root/assets/index-j5K-C_JC.js.gz +0 -0
  241. codex/static_root/assets/library-tab-CxKs8G-K.c6bfd6d53b17.js +1 -0
  242. codex/static_root/assets/library-tab-CxKs8G-K.c6bfd6d53b17.js.br +0 -0
  243. codex/static_root/assets/library-tab-CxKs8G-K.c6bfd6d53b17.js.gz +0 -0
  244. codex/static_root/assets/library-tab-CxKs8G-K.js +1 -0
  245. codex/static_root/assets/library-tab-CxKs8G-K.js.br +0 -0
  246. codex/static_root/assets/library-tab-CxKs8G-K.js.gz +0 -0
  247. codex/static_root/assets/{library-tab-Bh7ErAk7.7c44200ee727.css → library-tab-Wew8YOLv.0c1780a6b68e.css} +1 -1
  248. codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.br +0 -0
  249. codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.gz +0 -0
  250. codex/static_root/assets/{library-tab-Bh7ErAk7.css → library-tab-Wew8YOLv.css} +1 -1
  251. codex/static_root/assets/library-tab-Wew8YOLv.css.br +0 -0
  252. codex/static_root/assets/library-tab-Wew8YOLv.css.gz +0 -0
  253. codex/static_root/assets/main-H14uUxuN.cc1511f0f146.js +35 -0
  254. codex/static_root/assets/main-H14uUxuN.cc1511f0f146.js.br +0 -0
  255. codex/static_root/assets/main-H14uUxuN.cc1511f0f146.js.gz +0 -0
  256. codex/static_root/assets/main-H14uUxuN.js +35 -0
  257. codex/static_root/assets/main-H14uUxuN.js.br +0 -0
  258. codex/static_root/assets/main-H14uUxuN.js.gz +0 -0
  259. codex/static_root/assets/pager-full-pdf-DcRg28qt.ca9cdb9619ff.js +1 -0
  260. codex/static_root/assets/pager-full-pdf-DcRg28qt.ca9cdb9619ff.js.br +0 -0
  261. codex/static_root/assets/pager-full-pdf-DcRg28qt.ca9cdb9619ff.js.gz +0 -0
  262. codex/static_root/assets/pager-full-pdf-DcRg28qt.js +1 -0
  263. codex/static_root/assets/pager-full-pdf-DcRg28qt.js.br +0 -0
  264. codex/static_root/assets/pager-full-pdf-DcRg28qt.js.gz +0 -0
  265. codex/static_root/assets/pagination-toolbar-BgoV4zqc.ab5fa1be17a3.js +1 -0
  266. codex/static_root/assets/pagination-toolbar-BgoV4zqc.ab5fa1be17a3.js.br +0 -0
  267. codex/static_root/assets/pagination-toolbar-BgoV4zqc.ab5fa1be17a3.js.gz +0 -0
  268. codex/static_root/assets/pagination-toolbar-BgoV4zqc.js +1 -0
  269. codex/static_root/assets/pagination-toolbar-BgoV4zqc.js.br +0 -0
  270. codex/static_root/assets/pagination-toolbar-BgoV4zqc.js.gz +0 -0
  271. codex/static_root/assets/{pdf-doc-BcRNpoaW.882f6e967869.js → pdf-doc-BbuJd_VS.225d8590d020.js} +3 -3
  272. codex/static_root/assets/pdf-doc-BbuJd_VS.225d8590d020.js.br +0 -0
  273. codex/static_root/assets/pdf-doc-BbuJd_VS.225d8590d020.js.gz +0 -0
  274. codex/static_root/assets/{pdf-doc-BcRNpoaW.js → pdf-doc-BbuJd_VS.js} +3 -3
  275. codex/static_root/assets/pdf-doc-BbuJd_VS.js.br +0 -0
  276. codex/static_root/assets/pdf-doc-BbuJd_VS.js.gz +0 -0
  277. codex/static_root/assets/reader-DORea4Al.d8e756bb0027.js +2 -0
  278. codex/static_root/assets/reader-DORea4Al.d8e756bb0027.js.br +0 -0
  279. codex/static_root/assets/reader-DORea4Al.d8e756bb0027.js.gz +0 -0
  280. codex/static_root/assets/reader-DORea4Al.js +2 -0
  281. codex/static_root/assets/reader-DORea4Al.js.br +0 -0
  282. codex/static_root/assets/reader-DORea4Al.js.gz +0 -0
  283. codex/static_root/assets/{reader-DveZZr9x.7b21b9f8f628.css → reader-OpKdTZ_B.css} +1 -1
  284. codex/static_root/assets/reader-OpKdTZ_B.css.br +0 -0
  285. codex/static_root/assets/reader-OpKdTZ_B.css.gz +0 -0
  286. codex/static_root/assets/{reader-DveZZr9x.css → reader-OpKdTZ_B.ebd1512b7014.css} +1 -1
  287. codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.br +0 -0
  288. codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.gz +0 -0
  289. codex/static_root/assets/relation-chips-kqofGdNg.0c7a53962f2b.js +1 -0
  290. codex/static_root/assets/relation-chips-kqofGdNg.0c7a53962f2b.js.br +0 -0
  291. codex/static_root/assets/relation-chips-kqofGdNg.0c7a53962f2b.js.gz +0 -0
  292. codex/static_root/assets/relation-chips-kqofGdNg.js +1 -0
  293. codex/static_root/assets/relation-chips-kqofGdNg.js.br +0 -0
  294. codex/static_root/assets/relation-chips-kqofGdNg.js.gz +0 -0
  295. codex/static_root/assets/settings-drawer-C-hgtHJt.0eaf7e00ee9b.js +2 -0
  296. codex/static_root/assets/settings-drawer-C-hgtHJt.0eaf7e00ee9b.js.br +0 -0
  297. codex/static_root/assets/settings-drawer-C-hgtHJt.0eaf7e00ee9b.js.gz +0 -0
  298. codex/static_root/assets/settings-drawer-C-hgtHJt.js +2 -0
  299. codex/static_root/assets/settings-drawer-C-hgtHJt.js.br +0 -0
  300. codex/static_root/assets/settings-drawer-C-hgtHJt.js.gz +0 -0
  301. codex/static_root/assets/{stats-tab-DpfGsVX3.1cb66f2649a9.css → stats-tab-BAYTqQNg.5f715428c394.css} +1 -1
  302. codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.br +0 -0
  303. codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.gz +0 -0
  304. codex/static_root/assets/{stats-tab-DpfGsVX3.css → stats-tab-BAYTqQNg.css} +1 -1
  305. codex/static_root/assets/stats-tab-BAYTqQNg.css.br +0 -0
  306. codex/static_root/assets/stats-tab-BAYTqQNg.css.gz +0 -0
  307. codex/static_root/assets/stats-tab-C7iJCFa5.f1039eba03c2.js +1 -0
  308. codex/static_root/assets/stats-tab-C7iJCFa5.f1039eba03c2.js.br +0 -0
  309. codex/static_root/assets/stats-tab-C7iJCFa5.f1039eba03c2.js.gz +0 -0
  310. codex/static_root/assets/stats-tab-C7iJCFa5.js +1 -0
  311. codex/static_root/assets/stats-tab-C7iJCFa5.js.br +0 -0
  312. codex/static_root/assets/stats-tab-C7iJCFa5.js.gz +0 -0
  313. codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css +1 -0
  314. codex/static_root/assets/{task-tab-1XiWn9xP.06cb8e1b794d.css.br → task-tab--cXi48kg.5dfd6890a1c2.css.br} +1 -1
  315. codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css.gz +0 -0
  316. codex/static_root/assets/task-tab--cXi48kg.css +1 -0
  317. codex/static_root/assets/{task-tab-1XiWn9xP.css.br → task-tab--cXi48kg.css.br} +1 -1
  318. codex/static_root/assets/task-tab--cXi48kg.css.gz +0 -0
  319. codex/static_root/assets/task-tab-Drfz5uaT.0b387dcd4f5a.js +1 -0
  320. codex/static_root/assets/task-tab-Drfz5uaT.0b387dcd4f5a.js.br +0 -0
  321. codex/static_root/assets/task-tab-Drfz5uaT.0b387dcd4f5a.js.gz +0 -0
  322. codex/static_root/assets/task-tab-Drfz5uaT.js +1 -0
  323. codex/static_root/assets/task-tab-Drfz5uaT.js.br +0 -0
  324. codex/static_root/assets/task-tab-Drfz5uaT.js.gz +0 -0
  325. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +1 -0
  326. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +4 -0
  327. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
  328. codex/static_root/assets/transition-5LRFTPo5.css +1 -0
  329. codex/static_root/assets/transition-5LRFTPo5.css.br +4 -0
  330. codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
  331. codex/static_root/assets/transition-CQVFPpmZ.ba35e475243a.js +1 -0
  332. codex/static_root/assets/transition-CQVFPpmZ.ba35e475243a.js.br +0 -0
  333. codex/static_root/assets/transition-CQVFPpmZ.ba35e475243a.js.gz +0 -0
  334. codex/static_root/assets/transition-CQVFPpmZ.js +1 -0
  335. codex/static_root/assets/transition-CQVFPpmZ.js.br +0 -0
  336. codex/static_root/assets/transition-CQVFPpmZ.js.gz +0 -0
  337. codex/static_root/assets/unauthorized-Cfxq6rru.968d6e6e57bd.js +1 -0
  338. codex/static_root/assets/unauthorized-Cfxq6rru.968d6e6e57bd.js.br +0 -0
  339. codex/static_root/assets/unauthorized-Cfxq6rru.968d6e6e57bd.js.gz +0 -0
  340. codex/static_root/assets/unauthorized-Cfxq6rru.js +1 -0
  341. codex/static_root/assets/unauthorized-Cfxq6rru.js.br +0 -0
  342. codex/static_root/assets/unauthorized-Cfxq6rru.js.gz +0 -0
  343. codex/static_root/assets/user-tab-icKcK_Qd.c29142ed5212.js +1 -0
  344. codex/static_root/assets/user-tab-icKcK_Qd.c29142ed5212.js.br +0 -0
  345. codex/static_root/assets/user-tab-icKcK_Qd.c29142ed5212.js.gz +0 -0
  346. codex/static_root/assets/user-tab-icKcK_Qd.js +1 -0
  347. codex/static_root/assets/user-tab-icKcK_Qd.js.br +0 -0
  348. codex/static_root/assets/user-tab-icKcK_Qd.js.gz +0 -0
  349. codex/static_root/manifest.3eee2b4822b7.json +712 -0
  350. codex/static_root/manifest.3eee2b4822b7.json.br +0 -0
  351. codex/static_root/manifest.3eee2b4822b7.json.gz +0 -0
  352. codex/static_root/manifest.json +377 -300
  353. codex/static_root/manifest.json.br +0 -0
  354. codex/static_root/manifest.json.gz +0 -0
  355. codex/static_root/staticfiles.json +1 -1
  356. codex/status_controller.py +5 -6
  357. codex/threads.py +1 -1
  358. codex/urls/api/auth.py +1 -1
  359. codex/urls/api/browser.py +1 -1
  360. codex/views/admin/library.py +18 -13
  361. codex/views/admin/stats.py +23 -16
  362. codex/views/auth.py +42 -77
  363. codex/views/bookmark.py +16 -153
  364. codex/views/browser/annotate/__init__.py +1 -0
  365. codex/views/browser/annotate/bookmark.py +112 -0
  366. codex/views/browser/annotate/card.py +61 -0
  367. codex/views/browser/annotate/order.py +247 -0
  368. codex/views/browser/breadcrumbs.py +90 -11
  369. codex/views/browser/browser.py +44 -110
  370. codex/views/browser/choices.py +15 -18
  371. codex/views/browser/cover.py +10 -20
  372. codex/views/browser/filters/bookmark.py +18 -19
  373. codex/views/browser/filters/field.py +8 -3
  374. codex/views/browser/filters/filter.py +69 -0
  375. codex/views/browser/filters/group.py +9 -6
  376. codex/views/browser/filters/search/fts.py +5 -19
  377. codex/views/browser/filters/search/parse.py +54 -24
  378. codex/views/browser/group_mtime.py +83 -0
  379. codex/views/browser/metadata/__init__.py +1 -0
  380. codex/views/browser/metadata/annotate.py +109 -0
  381. codex/views/browser/metadata/copy_intersections.py +90 -0
  382. codex/views/browser/metadata/metadata.py +83 -0
  383. codex/views/browser/metadata/query_intersections.py +112 -0
  384. codex/views/browser/mtime.py +2 -10
  385. codex/views/browser/order_by.py +22 -22
  386. codex/views/browser/page_in_bounds.py +2 -2
  387. codex/views/browser/paginate.py +9 -15
  388. codex/views/browser/params.py +64 -0
  389. codex/views/browser/title.py +1 -6
  390. codex/views/browser/validate.py +68 -33
  391. codex/views/download.py +1 -1
  392. codex/views/mixins.py +27 -15
  393. codex/views/opds/const.py +2 -3
  394. codex/views/opds/urls.py +2 -2
  395. codex/views/opds/util.py +2 -3
  396. codex/views/opds/v1/entry/links.py +3 -1
  397. codex/views/opds/v1/facets.py +65 -12
  398. codex/views/opds/v1/feed.py +5 -64
  399. codex/views/opds/v1/links.py +5 -4
  400. codex/views/opds/v2/feed.py +1 -28
  401. codex/views/opds/v2/links.py +26 -1
  402. codex/views/opds/v2/publications.py +5 -6
  403. codex/views/opds/v2/top_links.py +3 -6
  404. codex/views/public.py +2 -2
  405. codex/views/reader/arcs.py +0 -3
  406. codex/views/reader/books.py +57 -41
  407. codex/views/reader/page.py +10 -4
  408. codex/views/reader/{init.py → params.py} +37 -31
  409. codex/views/reader/reader.py +1 -2
  410. codex/views/session.py +43 -54
  411. codex/views/timezone.py +54 -0
  412. codex/views/version.py +1 -1
  413. {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/METADATA +1 -1
  414. {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/RECORD +417 -383
  415. codex/serializers/choices.py +0 -196
  416. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js +0 -1
  417. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.br +0 -0
  418. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.gz +0 -0
  419. codex/static_root/assets/VCheckbox-CWiqcZ7a.js +0 -1
  420. codex/static_root/assets/VCheckbox-CWiqcZ7a.js.br +0 -0
  421. codex/static_root/assets/VCheckbox-CWiqcZ7a.js.gz +0 -0
  422. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js +0 -1
  423. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.br +0 -0
  424. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.gz +0 -0
  425. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js +0 -1
  426. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.br +0 -0
  427. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.gz +0 -0
  428. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js +0 -1
  429. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.br +0 -0
  430. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.gz +0 -0
  431. codex/static_root/assets/VCombobox-D1_KU27A.js +0 -1
  432. codex/static_root/assets/VCombobox-D1_KU27A.js.br +0 -0
  433. codex/static_root/assets/VCombobox-D1_KU27A.js.gz +0 -0
  434. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css +0 -1
  435. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
  436. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
  437. codex/static_root/assets/VDialog-38np3TSA.css +0 -1
  438. codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
  439. codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
  440. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js +0 -1
  441. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.br +0 -0
  442. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.gz +0 -0
  443. codex/static_root/assets/VDialog-GbxvFMMN.js +0 -1
  444. codex/static_root/assets/VDialog-GbxvFMMN.js.br +0 -0
  445. codex/static_root/assets/VDialog-GbxvFMMN.js.gz +0 -0
  446. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js +0 -1
  447. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.br +0 -0
  448. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.gz +0 -0
  449. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js +0 -1
  450. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.br +0 -0
  451. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.gz +0 -0
  452. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js +0 -1
  453. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.br +0 -0
  454. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.gz +0 -0
  455. codex/static_root/assets/VRadioGroup-C0etmxpZ.js +0 -1
  456. codex/static_root/assets/VRadioGroup-C0etmxpZ.js.br +0 -0
  457. codex/static_root/assets/VRadioGroup-C0etmxpZ.js.gz +0 -0
  458. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css +0 -1
  459. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
  460. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
  461. codex/static_root/assets/VSelect-Ca2bIOso.css +0 -1
  462. codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
  463. codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
  464. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js +0 -1
  465. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.br +0 -0
  466. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.gz +0 -0
  467. codex/static_root/assets/VSelect-txrnRiNJ.js +0 -1
  468. codex/static_root/assets/VSelect-txrnRiNJ.js.br +0 -0
  469. codex/static_root/assets/VSelect-txrnRiNJ.js.gz +0 -0
  470. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js +0 -1
  471. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.br +0 -0
  472. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.gz +0 -0
  473. codex/static_root/assets/VSelectionControl-CY6RmcvW.js +0 -1
  474. codex/static_root/assets/VSelectionControl-CY6RmcvW.js.br +0 -0
  475. codex/static_root/assets/VSelectionControl-CY6RmcvW.js.gz +0 -0
  476. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css +0 -1
  477. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.br +0 -0
  478. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.gz +0 -0
  479. codex/static_root/assets/VSlideGroup-Dk_1upHt.css +0 -1
  480. codex/static_root/assets/VSlideGroup-Dk_1upHt.css.br +0 -0
  481. codex/static_root/assets/VSlideGroup-Dk_1upHt.css.gz +0 -0
  482. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js +0 -1
  483. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.br +0 -0
  484. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.gz +0 -0
  485. codex/static_root/assets/VSlideGroup-DtEQMwY1.js +0 -1
  486. codex/static_root/assets/VSlideGroup-DtEQMwY1.js.br +0 -0
  487. codex/static_root/assets/VSlideGroup-DtEQMwY1.js.gz +0 -0
  488. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js +0 -1
  489. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.br +0 -0
  490. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.gz +0 -0
  491. codex/static_root/assets/VTable-2af995xo.js +0 -1
  492. codex/static_root/assets/VTable-2af995xo.js.br +0 -0
  493. codex/static_root/assets/VTable-2af995xo.js.gz +0 -0
  494. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css +0 -1
  495. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
  496. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
  497. codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
  498. codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
  499. codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
  500. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js +0 -1
  501. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.br +0 -0
  502. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.gz +0 -0
  503. codex/static_root/assets/VTextField-G6CMj7yO.js +0 -1
  504. codex/static_root/assets/VTextField-G6CMj7yO.js.br +0 -0
  505. codex/static_root/assets/VTextField-G6CMj7yO.js.gz +0 -0
  506. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js +0 -1
  507. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.br +0 -0
  508. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.gz +0 -0
  509. codex/static_root/assets/VWindowItem-CzoGd6Ul.js +0 -1
  510. codex/static_root/assets/VWindowItem-CzoGd6Ul.js.br +0 -0
  511. codex/static_root/assets/VWindowItem-CzoGd6Ul.js.gz +0 -0
  512. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.br +0 -0
  513. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
  514. codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
  515. codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
  516. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js +0 -1
  517. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.br +0 -0
  518. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.gz +0 -0
  519. codex/static_root/assets/admin-CsPAIQeG.js +0 -1
  520. codex/static_root/assets/admin-CsPAIQeG.js.br +0 -0
  521. codex/static_root/assets/admin-CsPAIQeG.js.gz +0 -0
  522. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js +0 -30
  523. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.br +0 -0
  524. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.gz +0 -0
  525. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js +0 -30
  526. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.br +0 -0
  527. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.gz +0 -0
  528. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +0 -1
  529. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
  530. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
  531. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +0 -1
  532. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
  533. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.gz +0 -0
  534. codex/static_root/assets/browser-C18l93yc.923860009ef4.js +0 -1
  535. codex/static_root/assets/browser-C18l93yc.923860009ef4.js.br +0 -0
  536. codex/static_root/assets/browser-C18l93yc.923860009ef4.js.gz +0 -0
  537. codex/static_root/assets/browser-C18l93yc.js +0 -1
  538. codex/static_root/assets/browser-C18l93yc.js.br +0 -0
  539. codex/static_root/assets/browser-C18l93yc.js.gz +0 -0
  540. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css +0 -1
  541. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.br +0 -0
  542. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.gz +0 -0
  543. codex/static_root/assets/browser-DaAqlBYO.css +0 -1
  544. codex/static_root/assets/browser-DaAqlBYO.css.br +0 -0
  545. codex/static_root/assets/browser-DaAqlBYO.css.gz +0 -0
  546. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js +0 -1
  547. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.br +0 -0
  548. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.gz +0 -0
  549. codex/static_root/assets/change-password-dialog-BazJdNs9.js +0 -1
  550. codex/static_root/assets/change-password-dialog-BazJdNs9.js.br +0 -0
  551. codex/static_root/assets/change-password-dialog-BazJdNs9.js.gz +0 -0
  552. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css +0 -1
  553. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.br +0 -0
  554. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.gz +0 -0
  555. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css +0 -1
  556. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.br +0 -0
  557. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.gz +0 -0
  558. codex/static_root/assets/confirm-dialog-BmhedLCx.7c2103d319d4.css +0 -1
  559. codex/static_root/assets/confirm-dialog-BmhedLCx.css +0 -1
  560. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js +0 -1
  561. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.br +0 -0
  562. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.gz +0 -0
  563. codex/static_root/assets/confirm-dialog-CbW3hlOp.js +0 -1
  564. codex/static_root/assets/confirm-dialog-CbW3hlOp.js.br +0 -0
  565. codex/static_root/assets/confirm-dialog-CbW3hlOp.js.gz +0 -0
  566. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js +0 -1
  567. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.br +0 -0
  568. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.gz +0 -0
  569. codex/static_root/assets/datetime-column-uSqKZU37.js +0 -1
  570. codex/static_root/assets/datetime-column-uSqKZU37.js.br +0 -0
  571. codex/static_root/assets/datetime-column-uSqKZU37.js.gz +0 -0
  572. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js +0 -1
  573. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.br +0 -0
  574. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.gz +0 -0
  575. codex/static_root/assets/filter-CBl5KEsB.js +0 -1
  576. codex/static_root/assets/filter-CBl5KEsB.js.br +0 -0
  577. codex/static_root/assets/filter-CBl5KEsB.js.gz +0 -0
  578. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js +0 -1
  579. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.br +0 -0
  580. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.gz +0 -0
  581. codex/static_root/assets/flag-tab-CT8pqM4r.js +0 -1
  582. codex/static_root/assets/flag-tab-CT8pqM4r.js.br +0 -0
  583. codex/static_root/assets/flag-tab-CT8pqM4r.js.gz +0 -0
  584. codex/static_root/assets/flag-tab-DBqpLKi7.css +0 -1
  585. codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
  586. codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
  587. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +0 -1
  588. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
  589. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
  590. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js +0 -1
  591. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.br +0 -0
  592. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.gz +0 -0
  593. codex/static_root/assets/group-tab-BOW9kigw.js +0 -1
  594. codex/static_root/assets/group-tab-BOW9kigw.js.br +0 -0
  595. codex/static_root/assets/group-tab-BOW9kigw.js.gz +0 -0
  596. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css +0 -1
  597. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.br +0 -0
  598. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.gz +0 -0
  599. codex/static_root/assets/group-tab-CBKuS3Iv.css +0 -1
  600. codex/static_root/assets/group-tab-CBKuS3Iv.css.br +0 -0
  601. codex/static_root/assets/group-tab-CBKuS3Iv.css.gz +0 -0
  602. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js +0 -1
  603. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +0 -4
  604. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
  605. codex/static_root/assets/http-error-C6yuLApo.js +0 -1
  606. codex/static_root/assets/http-error-C6yuLApo.js.br +0 -4
  607. codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
  608. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +0 -2
  609. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
  610. codex/static_root/assets/library-tab-Bh7ErAk7.css.br +0 -2
  611. codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
  612. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js +0 -1
  613. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.br +0 -0
  614. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.gz +0 -0
  615. codex/static_root/assets/library-tab-D3WOtpE0.js +0 -1
  616. codex/static_root/assets/library-tab-D3WOtpE0.js.br +0 -0
  617. codex/static_root/assets/library-tab-D3WOtpE0.js.gz +0 -0
  618. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js +0 -6
  619. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.br +0 -0
  620. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.gz +0 -0
  621. codex/static_root/assets/main-CmjU2oTp.js +0 -6
  622. codex/static_root/assets/main-CmjU2oTp.js.br +0 -0
  623. codex/static_root/assets/main-CmjU2oTp.js.gz +0 -0
  624. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js +0 -1
  625. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.br +0 -0
  626. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.gz +0 -0
  627. codex/static_root/assets/pager-full-pdf-BH54_5f6.js +0 -1
  628. codex/static_root/assets/pager-full-pdf-BH54_5f6.js.br +0 -0
  629. codex/static_root/assets/pager-full-pdf-BH54_5f6.js.gz +0 -0
  630. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js +0 -1
  631. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.br +0 -0
  632. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.gz +0 -0
  633. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js +0 -1
  634. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.br +0 -0
  635. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.gz +0 -0
  636. codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.br +0 -0
  637. codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
  638. codex/static_root/assets/pdf-doc-BcRNpoaW.js.br +0 -0
  639. codex/static_root/assets/pdf-doc-BcRNpoaW.js.gz +0 -0
  640. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js +0 -2
  641. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.br +0 -0
  642. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.gz +0 -0
  643. codex/static_root/assets/reader-DIPRg2VB.js +0 -2
  644. codex/static_root/assets/reader-DIPRg2VB.js.br +0 -0
  645. codex/static_root/assets/reader-DIPRg2VB.js.gz +0 -0
  646. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.br +0 -0
  647. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -0
  648. codex/static_root/assets/reader-DveZZr9x.css.br +0 -0
  649. codex/static_root/assets/reader-DveZZr9x.css.gz +0 -0
  650. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js +0 -1
  651. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.br +0 -0
  652. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.gz +0 -0
  653. codex/static_root/assets/relation-chips-3tTlR5QH.js +0 -1
  654. codex/static_root/assets/relation-chips-3tTlR5QH.js.br +0 -0
  655. codex/static_root/assets/relation-chips-3tTlR5QH.js.gz +0 -0
  656. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js +0 -2
  657. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.br +0 -0
  658. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.gz +0 -0
  659. codex/static_root/assets/settings-drawer-BdyFhA_r.js +0 -2
  660. codex/static_root/assets/settings-drawer-BdyFhA_r.js.br +0 -0
  661. codex/static_root/assets/settings-drawer-BdyFhA_r.js.gz +0 -0
  662. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js +0 -1
  663. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.br +0 -0
  664. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.gz +0 -0
  665. codex/static_root/assets/stats-tab-DeKjwvmh.js +0 -1
  666. codex/static_root/assets/stats-tab-DeKjwvmh.js.br +0 -0
  667. codex/static_root/assets/stats-tab-DeKjwvmh.js.gz +0 -0
  668. codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.br +0 -0
  669. codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
  670. codex/static_root/assets/stats-tab-DpfGsVX3.css.br +0 -0
  671. codex/static_root/assets/stats-tab-DpfGsVX3.css.gz +0 -0
  672. codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css +0 -1
  673. codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css.gz +0 -0
  674. codex/static_root/assets/task-tab-1XiWn9xP.css +0 -1
  675. codex/static_root/assets/task-tab-1XiWn9xP.css.gz +0 -0
  676. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js +0 -1
  677. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.br +0 -0
  678. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.gz +0 -0
  679. codex/static_root/assets/task-tab-C8D0qRP_.js +0 -1
  680. codex/static_root/assets/task-tab-C8D0qRP_.js.br +0 -0
  681. codex/static_root/assets/task-tab-C8D0qRP_.js.gz +0 -0
  682. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js +0 -1
  683. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.br +0 -0
  684. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.gz +0 -0
  685. codex/static_root/assets/unauthorized-BHgBRF4z.js +0 -1
  686. codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
  687. codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
  688. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js +0 -1
  689. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.br +0 -0
  690. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.gz +0 -0
  691. codex/static_root/assets/user-tab-BU31Z92y.js +0 -1
  692. codex/static_root/assets/user-tab-BU31Z92y.js.br +0 -0
  693. codex/static_root/assets/user-tab-BU31Z92y.js.gz +0 -0
  694. codex/static_root/js/choices-admin.8aa64c911203.json +0 -1
  695. codex/static_root/js/choices-admin.8aa64c911203.json.br +0 -0
  696. codex/static_root/js/choices-admin.8aa64c911203.json.gz +0 -0
  697. codex/static_root/js/choices-admin.json +0 -1
  698. codex/static_root/js/choices-admin.json.br +0 -0
  699. codex/static_root/js/choices-admin.json.gz +0 -0
  700. codex/static_root/js/choices.2f6b0359e1ef.json +0 -1
  701. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  702. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  703. codex/static_root/js/choices.json +0 -1
  704. codex/static_root/js/choices.json.br +0 -0
  705. codex/static_root/js/choices.json.gz +0 -0
  706. codex/static_root/manifest.45cb46e19686.json +0 -635
  707. codex/static_root/manifest.45cb46e19686.json.br +0 -0
  708. codex/static_root/manifest.45cb46e19686.json.gz +0 -0
  709. codex/views/browser/annotations.py +0 -339
  710. codex/views/browser/base.py +0 -139
  711. codex/views/browser/filters/annotations.py +0 -125
  712. codex/views/browser/metadata.py +0 -332
  713. {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/LICENSE +0 -0
  714. {codex-1.7.0a0.dist-info → codex-1.7.0a2.dist-info}/WHEEL +0 -0
  715. {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
- fts_filter = self._get_fts_filter(model, text)
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 Q(**fts_filter)
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
- if not self.is_admin(): # type: ignore
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, filter_q_list = self.get_search_field_filters(
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
- filter_q_list, exclude_q_list = self.get_search_field_filters(
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 += 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
- def _apply_search_filter_list(self, qs, filter_list, exclude):
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
- qs = qs.exclude(q) if exclude else qs.filter(q)
204
+ combined_q &= q
174
205
  self.search_mode = True
175
206
 
176
- return qs
207
+ return combined_q
177
208
 
178
- def apply_search_filter(self, qs, model):
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
- field_exclude_q_list, field_filter_q_list, fts_filter = (
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
- qs = self._apply_search_filter_list(qs, field_exclude_q_list, True)
187
- qs = self._apply_search_filter_list(qs, field_filter_q_list, False)
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 qs
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