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
@@ -0,0 +1,112 @@
1
+ """Base view for metadata annotations."""
2
+
3
+ from django.db.models import (
4
+ Case,
5
+ F,
6
+ Sum,
7
+ Value,
8
+ When,
9
+ )
10
+ from django.db.models.fields import BooleanField, PositiveSmallIntegerField
11
+ from django.db.models.functions import Least
12
+ from django.db.models.functions.comparison import Coalesce
13
+
14
+ from codex.logger.logging import get_logger
15
+ from codex.models import (
16
+ Comic,
17
+ )
18
+ from codex.models.functions import JsonGroupArray
19
+ from codex.views.browser.annotate.order import BrowserAnnotateOrderView
20
+
21
+ LOG = get_logger(__name__)
22
+
23
+
24
+ class BrowserAnnotateBookmarkView(BrowserAnnotateOrderView):
25
+ """Base class for views that need special metadata annotations."""
26
+
27
+ def _get_group_bookmark_page_annotation(
28
+ self, qs, bm_rel, bm_filter, page_rel, finished_rel
29
+ ):
30
+ """Get bookmark page subquery."""
31
+ finished_filter = {finished_rel: True}
32
+ prefix = "" if qs.model is Comic else self.rel_prefix
33
+ page_count = prefix + "page_count"
34
+ # Can't use a filtered relation for page & finished because of this
35
+ # page_count case.
36
+ bookmark_page_case = Case(
37
+ When(**{bm_rel: None}, then=0),
38
+ When(**finished_filter, then=page_count),
39
+ default=page_rel,
40
+ output_field=PositiveSmallIntegerField(),
41
+ )
42
+ return Sum(
43
+ bookmark_page_case,
44
+ default=0,
45
+ filter=bm_filter,
46
+ output_field=PositiveSmallIntegerField(),
47
+ distinct=True,
48
+ )
49
+
50
+ @classmethod
51
+ def _get_group_bookmark_finished_annotation(cls, qs, bm_filter, finished_rel):
52
+ """Get finished_count subquery."""
53
+ finished_count = Sum(
54
+ finished_rel,
55
+ default=0,
56
+ filter=bm_filter,
57
+ output_field=PositiveSmallIntegerField(),
58
+ # distinct breaks this sum and only returns one. idk why.
59
+ )
60
+ qs = qs.alias(finished_count=finished_count)
61
+
62
+ finished_aggregate = Case(
63
+ When(finished_count=F("child_count"), then=True),
64
+ When(finished_count=0, then=False),
65
+ default=None,
66
+ output_field=BooleanField(),
67
+ )
68
+ return qs, finished_aggregate
69
+
70
+ def annotate_bookmarks(self, qs):
71
+ """Hoist up bookmark annotations."""
72
+ bm_rel = self._get_bm_rel(qs.model)
73
+ bm_filter = self._get_my_bookmark_filter(bm_rel)
74
+ page_rel = f"{bm_rel}__page"
75
+ finished_rel = f"{bm_rel}__finished"
76
+
77
+ if qs.model is Comic:
78
+ bookmark_page = Sum(page_rel, filter=bm_filter, default=0)
79
+ finished_aggregate = Sum(finished_rel, filter=bm_filter, default=False)
80
+ else:
81
+ bookmark_page = self._get_group_bookmark_page_annotation(
82
+ qs, bm_rel, bm_filter, page_rel, finished_rel
83
+ )
84
+ qs, finished_aggregate = self._get_group_bookmark_finished_annotation(
85
+ qs, bm_filter, finished_rel
86
+ )
87
+
88
+ if (
89
+ (self.TARGET == "browser" and qs.model is Comic)
90
+ or self.is_opds_acquisition
91
+ or self.TARGET == "metadata"
92
+ ):
93
+ qs = qs.annotate(page=bookmark_page)
94
+ else:
95
+ qs = qs.alias(page=bookmark_page)
96
+
97
+ qs = qs.annotate(finished=finished_aggregate)
98
+
99
+ if not self.bmua_is_max:
100
+ mbmua = self.get_max_bookmark_updated_at_aggregate(qs.model, JsonGroupArray)
101
+ qs = qs.annotate(bookmark_updated_ats=mbmua)
102
+ return qs
103
+
104
+ def annotate_progress(self, qs):
105
+ """Compute progress for each member of a qs."""
106
+ # Requires bookmark and annotation hoisted from bookmarks.
107
+ # Requires page_count native to comic or aggregated
108
+ # Page counts can be null with metadata turned off.
109
+ # Least guard is for rare instances when bookmarks are set to
110
+ # invalid high values
111
+ progress = Least(Coalesce(F("page"), 0) * 100.0 / F("page_count"), Value(100.0))
112
+ return qs.annotate(progress=progress)
@@ -0,0 +1,61 @@
1
+ """Base view for metadata annotations."""
2
+
3
+ from types import MappingProxyType
4
+
5
+ from django.db.models import (
6
+ Value,
7
+ )
8
+ from django.db.models.aggregates import Count
9
+ from django.db.models.fields import CharField
10
+
11
+ from codex.logger.logging import get_logger
12
+ from codex.models.comic import Comic
13
+ from codex.models.functions import JsonGroupArray
14
+ from codex.models.groups import BrowserGroupModel, Imprint, Publisher, Series, Volume
15
+ from codex.views.browser.annotate.bookmark import BrowserAnnotateBookmarkView
16
+
17
+ _GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingProxyType(
18
+ {Publisher: "sort_name", Imprint: "sort_name", Series: "sort_name", Volume: "name"}
19
+ )
20
+ LOG = get_logger(__name__)
21
+
22
+
23
+ class BrowserAnnotateCardView(BrowserAnnotateBookmarkView):
24
+ """Base class for views that need special metadata annotations."""
25
+
26
+ def add_group_by(self, qs):
27
+ """Get the group by for the model."""
28
+ if group_by := _GROUP_BY.get(qs.model): # type: ignore
29
+ qs = qs.group_by(group_by)
30
+ return qs
31
+
32
+ def _annotate_child_count(self, qs):
33
+ """Annotate child chount for card."""
34
+ if qs.model is Comic:
35
+ return qs
36
+ rel = self.rel_prefix + "pk"
37
+ count_func = Count(rel, distinct=True)
38
+ ann = {"child_count": count_func}
39
+ if self.TARGET == "opds2":
40
+ if qs.model is not Comic:
41
+ qs = qs.alias(**ann)
42
+ else:
43
+ qs = qs.annotate(**ann)
44
+ return qs
45
+
46
+ def _annotate_group(self, qs):
47
+ """Annotate Group."""
48
+ value = "c" if qs.model is Comic else self.model_group # type: ignore
49
+ return qs.annotate(group=Value(value, CharField(max_length=1)))
50
+
51
+ def annotate_card_aggregates(self, qs):
52
+ """Annotate aggregates that appear the browser card."""
53
+ if qs.model is Comic:
54
+ # comic adds order_value for cards late
55
+ qs = self.annotate_order_value(qs)
56
+ qs = self._annotate_group(qs)
57
+ qs = self.annotate_group_names(qs)
58
+ qs = self._annotate_child_count(qs)
59
+ qs = self.annotate_bookmarks(qs)
60
+ qs = self.annotate_progress(qs)
61
+ return qs.annotate(updated_ats=JsonGroupArray("updated_at", distinct=True))
@@ -0,0 +1,247 @@
1
+ """Base view for metadata annotations."""
2
+
3
+ from os import sep
4
+ from types import MappingProxyType
5
+
6
+ from django.db.models import (
7
+ F,
8
+ FilteredRelation,
9
+ Q,
10
+ Value,
11
+ )
12
+ from django.db.models.aggregates import Avg, Max, Min, Sum
13
+ from django.db.models.fields import CharField
14
+ from django.db.models.functions import Reverse, Right, StrIndex
15
+
16
+ from codex.logger.logging import get_logger
17
+ from codex.models import (
18
+ Comic,
19
+ Folder,
20
+ StoryArc,
21
+ )
22
+ from codex.models.functions import ComicFTSRank, JsonGroupArray
23
+ from codex.views.browser.order_by import (
24
+ BrowserOrderByView,
25
+ )
26
+ from codex.views.const import (
27
+ NONE_INTEGERFIELD,
28
+ STORY_ARC_GROUP,
29
+ )
30
+ from codex.views.mixins import SharedAnnotationsMixin
31
+
32
+ _ORDER_AGGREGATE_FUNCS = MappingProxyType(
33
+ # These are annotated to order_value because they're simple relations
34
+ {
35
+ "age_rating": Avg,
36
+ "community_rating": Avg,
37
+ "created_at": Min,
38
+ "critical_rating": Avg,
39
+ "date": Min,
40
+ "page_count": Sum,
41
+ "size": Sum,
42
+ "updated_at": Min,
43
+ }
44
+ )
45
+ _ANNOTATED_ORDER_FIELDS = frozenset(
46
+ # These are annotated with their own functions
47
+ {
48
+ "filename",
49
+ "sort_name",
50
+ "search_score",
51
+ "bookmark_updated_at",
52
+ "story_arc_number",
53
+ }
54
+ )
55
+
56
+ LOG = get_logger(__name__)
57
+
58
+
59
+ class BrowserAnnotateOrderView(BrowserOrderByView, SharedAnnotationsMixin):
60
+ """Base class for views that need special metadata annotations."""
61
+
62
+ CARD_TARGETS = frozenset({"browser", "metadata"})
63
+ _OPDS_TARGETS = frozenset({"opds1", "opds2"})
64
+ _PAGE_COUNT_TARGETS = frozenset(CARD_TARGETS | _OPDS_TARGETS)
65
+ _COVER_AND_CARD_TARGETS = frozenset(CARD_TARGETS | {"cover"})
66
+
67
+ def __init__(self, *args, **kwargs):
68
+ """Set params for the type checker."""
69
+ super().__init__(*args, **kwargs)
70
+ self._order_agg_func: type[Min | Max] | None = None
71
+ self._is_opds_acquisition: bool | None = None
72
+ self._opds_acquisition_groups: frozenset[str] | None = None
73
+ self.bmua_is_max = False
74
+
75
+ @property
76
+ def opds_acquisition_groups(self):
77
+ """Memoize the opds acquisition groups."""
78
+ if self._opds_acquisition_groups is None:
79
+ groups = {"a", "f", "c"}
80
+ groups |= {*self.valid_nav_groups[-2:]}
81
+ self._opds_acquisition_groups = frozenset(groups)
82
+ return self._opds_acquisition_groups
83
+
84
+ @property
85
+ def is_opds_acquisition(self):
86
+ """Memoize if we're in an opds acquisition view."""
87
+ if self._is_opds_acquisition is None:
88
+ is_opds_acquisition = self.TARGET in self._OPDS_TARGETS
89
+ if is_opds_acquisition:
90
+ group = self.kwargs.get("group")
91
+ is_opds_acquisition &= group in self.opds_acquisition_groups
92
+ if is_opds_acquisition and group == "a":
93
+ pks = self.kwargs["pks"]
94
+ is_opds_acquisition &= bool(pks and 0 not in pks)
95
+ self._is_opds_acquisition = is_opds_acquisition
96
+ return self._is_opds_acquisition
97
+
98
+ @property
99
+ def order_agg_func(self):
100
+ """Get the order aggregate function."""
101
+ if self._order_agg_func is None:
102
+ order_reverse = self.params.get("order_reverse")
103
+ self._order_agg_func = Max if order_reverse else Min
104
+ return self._order_agg_func
105
+
106
+ def _alias_sort_names(self, qs):
107
+ """Annotate sort_name."""
108
+ if self.order_key != "sort_name" and not (
109
+ qs.model is StoryArc and self.order_key == "story_arc_number"
110
+ ):
111
+ return qs
112
+ pks = self.kwargs.get("pks")
113
+ show = MappingProxyType(self.params["show"]) # type: ignore
114
+ # TODO too many annotations for order?
115
+ # Move other annotations to card.
116
+ # eager publisher for imprint of course
117
+ sort_name_annotations = self.get_sort_name_annotations(
118
+ qs.model, self.model_group, pks, show
119
+ )
120
+ if sort_name_annotations:
121
+ qs = qs.alias(**sort_name_annotations)
122
+ if qs.model is Comic:
123
+ self._comic_sort_names = tuple(sort_name_annotations.keys())
124
+ return qs
125
+
126
+ def _alias_filename(self, qs):
127
+ """Calculate filename from path in the db."""
128
+ if self.order_key != "filename":
129
+ return qs
130
+ if qs.model is Folder:
131
+ filename = F("name")
132
+ else:
133
+ prefix = "" if qs.model == Comic else self.rel_prefix
134
+ path_rel = prefix + "path"
135
+ filename = self.order_agg_func(
136
+ Right(
137
+ path_rel,
138
+ StrIndex(Reverse(F(path_rel)), Value(sep)) - 1, # type: ignore
139
+ output_field=CharField(),
140
+ )
141
+ )
142
+ return qs.alias(filename=filename)
143
+
144
+ def _alias_story_arc_number(self, qs):
145
+ if self.order_key != "story_arc_number":
146
+ return qs
147
+
148
+ # Get story_arc__pk
149
+ group = self.kwargs["group"]
150
+ pks = self.kwargs["pks"]
151
+ if group == STORY_ARC_GROUP and pks:
152
+ story_arc_pks = pks
153
+ else:
154
+ story_arc_pks = self.params.get("filters", {}).get( # type: ignore
155
+ "story_arcs", ()
156
+ )
157
+
158
+ # If we have one annotate it.
159
+ if story_arc_pks:
160
+ rel = self.get_rel_prefix(qs.model) + "story_arc_numbers"
161
+ condition_rel = "pk" if qs.model is StoryArc else rel + "__story_arc"
162
+ condition = Q(**{f"{condition_rel}__in": story_arc_pks})
163
+ qs = qs.alias(
164
+ selected_story_arc_number=FilteredRelation(rel, condition=condition),
165
+ )
166
+ story_arc_number = self.order_agg_func("selected_story_arc_number__number")
167
+ else:
168
+ story_arc_number = NONE_INTEGERFIELD
169
+
170
+ return qs.alias(story_arc_number=story_arc_number)
171
+
172
+ def _annotate_page_count(self, qs):
173
+ """Hoist up total page_count of children."""
174
+ # Used for sorting and progress
175
+ if qs.model is Comic or (
176
+ self.order_key != "page_count"
177
+ and self.TARGET not in self._PAGE_COUNT_TARGETS
178
+ ):
179
+ return qs
180
+
181
+ rel = self.rel_prefix + "page_count"
182
+ page_count_sum = Sum(rel, distinct=True)
183
+ if self.TARGET == "browser":
184
+ qs = qs.alias(page_count=page_count_sum)
185
+ else:
186
+ qs = qs.annotate(page_count=page_count_sum)
187
+ return qs
188
+
189
+ def _annotate_bookmark_updated_at(self, qs):
190
+ if self.is_opds_acquisition or self.order_key == "bookmark_updated_at":
191
+ bmua_agg = self.get_max_bookmark_updated_at_aggregate(
192
+ qs.model, agg_func=self.order_agg_func
193
+ )
194
+ # This is used by annotate.bookmark to avoid a
195
+ # similar query.
196
+ self.bmua_is_max = self.order_agg_func is Max
197
+ qs = qs.annotate(bookmark_updated_at=bmua_agg)
198
+ # This is used by the serializer to compute mtime
199
+ return qs.annotate(bmua_is_max=Value(self.bmua_is_max))
200
+
201
+ def annotate_order_value(self, qs):
202
+ """Annotate a main key for sorting and browser card display."""
203
+ # Determine order func
204
+ if self.TARGET == "metadata":
205
+ return qs
206
+ if qs.model is Folder and self.order_key == "filename":
207
+ order_value = F("name")
208
+ elif qs.model is Comic or self.order_key in _ANNOTATED_ORDER_FIELDS:
209
+ # These are annotated in browser_annotaions
210
+ order_value = F(self.order_key)
211
+ else:
212
+ agg_func = _ORDER_AGGREGATE_FUNCS[self.order_key]
213
+ agg_func = self.order_agg_func if agg_func == Min else agg_func
214
+ field = self.rel_prefix + self.order_key
215
+ order_value = agg_func(field)
216
+
217
+ if self.TARGET == "browser":
218
+ qs = qs.annotate(order_value=order_value)
219
+ else:
220
+ qs = qs.alias(order_value=order_value)
221
+ return qs
222
+
223
+ def _annotate_search_scores(self, qs):
224
+ """Annotate Search Scores."""
225
+ if (
226
+ self.TARGET not in self._COVER_AND_CARD_TARGETS
227
+ or self.order_key != "search_score"
228
+ ):
229
+ return qs
230
+
231
+ # Rank is always the max of the relations, cannot aggregate?
232
+ # group by here fixes duplicates with story_arc, probably because it's a long relation
233
+ return qs.annotate(search_score=ComicFTSRank()).group_by("id")
234
+
235
+ def annotate_order_aggregates(self, qs):
236
+ """Annotate common aggregates between browser and metadata."""
237
+ qs = qs.annotate(ids=JsonGroupArray("id", distinct=True))
238
+ qs = self._annotate_search_scores(qs)
239
+ qs = self._alias_sort_names(qs)
240
+ qs = self._alias_filename(qs)
241
+ qs = self._alias_story_arc_number(qs)
242
+ qs = self._annotate_page_count(qs)
243
+ qs = self._annotate_bookmark_updated_at(qs)
244
+ if qs.model is not Comic:
245
+ # comic orders on indexed fields when it can
246
+ qs = self.annotate_order_value(qs)
247
+ return qs
@@ -2,18 +2,94 @@
2
2
 
3
3
  from contextlib import suppress
4
4
  from types import MappingProxyType
5
+ from typing import TYPE_CHECKING
5
6
 
6
7
  from codex.logger.logging import get_logger
8
+ from codex.models import (
9
+ BrowserGroupModel,
10
+ Comic,
11
+ Imprint,
12
+ Volume,
13
+ )
14
+ from codex.models.groups import Publisher
7
15
  from codex.views.browser.paginate import BrowserPaginateView
8
- from codex.views.const import FOLDER_GROUP, GROUP_NAME_MAP, GROUP_ORDER, STORY_ARC_GROUP
16
+ from codex.views.const import (
17
+ FOLDER_GROUP,
18
+ GROUP_MODEL_MAP,
19
+ GROUP_NAME_MAP,
20
+ GROUP_ORDER,
21
+ STORY_ARC_GROUP,
22
+ )
9
23
  from codex.views.util import Route
10
24
 
25
+ if TYPE_CHECKING:
26
+ from codex.models.groups import Folder
27
+
11
28
  LOG = get_logger(__name__)
29
+ _GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
30
+ type[BrowserGroupModel], tuple[str | None, ...]
31
+ ] = MappingProxyType(
32
+ {
33
+ Comic: ("series", "volume"),
34
+ Volume: ("series",),
35
+ Imprint: ("publisher",),
36
+ }
37
+ )
12
38
 
13
39
 
14
40
  class BrowserBreadcrumbsView(BrowserPaginateView):
15
41
  """Browser breadcrumbs calculations."""
16
42
 
43
+ def __init__(self, *args, **kwargs):
44
+ """Set params for the type checker."""
45
+ super().__init__(*args, **kwargs)
46
+ self._group_instance: BrowserGroupModel | None | int = 0
47
+
48
+ def _get_group_query(self, model):
49
+ """Get the group query for the group instance."""
50
+ pks = self.kwargs.get("pks")
51
+ qs = model.objects.filter(pk__in=pks)
52
+ if select_related := _GROUP_INSTANCE_SELECT_RELATED.get(model):
53
+ qs = qs.select_related(*select_related)
54
+ order_by = "name" if model is Volume else "sort_name"
55
+ return qs.order_by(order_by)
56
+
57
+ def _handle_group_query_missing_model(self, model):
58
+ """Handle a missing model for the group instance."""
59
+ group = self.kwargs.get("group")
60
+ pks = self.kwargs.get("pks")
61
+ page = self.kwargs.get("page")
62
+ if group == "r" and not pks and page == 1:
63
+ group_query = model.objects.none()
64
+ else:
65
+ reason = f"{group}__in={pks} does not exist!"
66
+ settings_mask = {"breadcrumbs": []}
67
+ self.raise_redirect(
68
+ reason,
69
+ route_mask={"group": group},
70
+ settings_mask=settings_mask,
71
+ )
72
+ return group_query
73
+
74
+ @property
75
+ def group_instance(self) -> BrowserGroupModel | None:
76
+ """Memoize group instance for getting group names & counts."""
77
+ if self._group_instance == 0:
78
+ group = self.kwargs.get("group")
79
+ model = GROUP_MODEL_MAP[group]
80
+ pks = self.kwargs.get("pks")
81
+ if model and pks and 0 not in pks:
82
+ try:
83
+ group_query = self._get_group_query(model)
84
+ except model.DoesNotExist:
85
+ group_query = self._handle_group_query_missing_model(model)
86
+ else:
87
+ if not model:
88
+ model = Publisher
89
+ group_query = model.objects.none()
90
+ self._group_instance = group_query.first()
91
+ return self._group_instance # type: ignore
92
+
17
93
  def _init_breadcrumbs(self, valid_groups):
18
94
  """Load breadcrumbs and determine if they should be searched for a graft."""
19
95
  breadcrumbs: list[Route] = list(self.params.get("breadcrumbs", []))
@@ -27,7 +103,8 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
27
103
  """Save the breadcrumbs to params."""
28
104
  params = dict(self.params)
29
105
  params["breadcrumbs"] = tuple(crumb.dict() for crumb in breadcrumbs)
30
- self.params = MappingProxyType(params)
106
+ # HACK rewriting params
107
+ self._params = MappingProxyType(params)
31
108
 
32
109
  def _breadcrumbs_graft_or_create_story_arc(self) -> tuple[tuple[Route, ...], bool]:
33
110
  """Graft or create story_arc breadcrumbs."""
@@ -35,7 +112,7 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
35
112
 
36
113
  pks = self.kwargs["pks"]
37
114
  page = self.kwargs["page"]
38
- gi = self.group_instance # type: ignore
115
+ gi = self.group_instance
39
116
  name = gi.name if gi else ""
40
117
  group_crumb = Route(STORY_ARC_GROUP, pks, page, name)
41
118
 
@@ -69,7 +146,7 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
69
146
 
70
147
  pks = self.kwargs["pks"]
71
148
  page = self.kwargs["page"]
72
- folder = self.group_instance # type: ignore
149
+ folder: Folder | None = self.group_instance # type: ignore
73
150
  name = folder.name if folder and pks else ""
74
151
  group_crumb = Route(FOLDER_GROUP, pks, page, name)
75
152
  new_breadcrumbs = []
@@ -91,11 +168,13 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
91
168
  # parent next
92
169
  if not folder:
93
170
  break
94
- folder = folder.parent_folder
95
- if folder:
96
- group_crumb = Route(FOLDER_GROUP, (folder.pk,), 1, name=folder.name)
171
+ if folder := folder.parent_folder:
172
+ parent_pks = (folder.pk,)
173
+ parent_name = folder.name
97
174
  else:
98
- group_crumb = Route(FOLDER_GROUP, (), 1, name="")
175
+ parent_pks = ()
176
+ parent_name = ""
177
+ group_crumb = Route(FOLDER_GROUP, parent_pks, 1, parent_name)
99
178
 
100
179
  breadcrumbs = new_breadcrumbs
101
180
 
@@ -103,16 +182,16 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
103
182
 
104
183
  def _get_breadcrumbs_group_crumb(self, group):
105
184
  """Create the crumb for this group."""
106
- gi = self.group_instance # type: ignore
185
+ gi = self.group_instance
107
186
  if not gi:
108
187
  pks = ()
109
188
  page = 1
110
189
  name = ""
111
- if group == self.kwargs["group"]:
190
+ elif group == self.kwargs["group"]:
112
191
  # create self crumb
113
192
  pks = self.kwargs["pks"]
114
193
  page = self.kwargs["page"]
115
- name = gi.name if gi else ""
194
+ name = gi.name
116
195
  else:
117
196
  page = 1
118
197
  if (attr := GROUP_NAME_MAP.get(group)) and (