codex 1.6.0a9__py3-none-any.whl → 1.6.0a11__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 (560) hide show
  1. codex/librarian/importer/cache.py +46 -133
  2. codex/librarian/importer/create_fks.py +3 -1
  3. codex/librarian/importer/importer.py +1 -1
  4. codex/librarian/importer/importerd.py +5 -5
  5. codex/librarian/importer/tasks.py +2 -2
  6. codex/librarian/janitor/janitor.py +0 -3
  7. codex/librarian/librariand.py +0 -2
  8. codex/librarian/notifier/notifierd.py +1 -1
  9. codex/librarian/notifier/tasks.py +4 -4
  10. codex/middleware.py +1 -2
  11. codex/migrations/0027_import_order_and_covers.py +1 -66
  12. codex/models/comic.py +0 -1
  13. codex/models/groups.py +0 -3
  14. codex/serializers/admin.py +41 -40
  15. codex/serializers/browser/choices.py +38 -10
  16. codex/serializers/browser/filters.py +2 -2
  17. codex/serializers/browser/metadata.py +45 -1
  18. codex/serializers/browser/mixins.py +35 -2
  19. codex/serializers/browser/page.py +2 -4
  20. codex/serializers/browser/settings.py +41 -14
  21. codex/serializers/choices.py +4 -0
  22. codex/serializers/fields.py +61 -1
  23. codex/serializers/models/pycountry.py +11 -51
  24. codex/serializers/reader.py +12 -1
  25. codex/serializers/route.py +1 -1
  26. codex/serializers/settings.py +12 -0
  27. codex/settings/settings.py +10 -1
  28. codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js +1 -0
  29. codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js.br +0 -0
  30. codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js.gz +0 -0
  31. codex/static_root/assets/VCheckbox-BwDyxhU7.js +1 -0
  32. codex/static_root/assets/VCheckbox-BwDyxhU7.js.br +0 -0
  33. codex/static_root/assets/VCheckbox-BwDyxhU7.js.gz +0 -0
  34. codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.9a47873b8548.js → VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js} +1 -1
  35. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js.br +0 -0
  36. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js.gz +0 -0
  37. codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.js → VCheckboxBtn-DVq5lDK4.js} +1 -1
  38. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.js.br +0 -0
  39. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.js.gz +0 -0
  40. codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js +1 -0
  41. codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js.br +0 -0
  42. codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js.gz +0 -0
  43. codex/static_root/assets/VCombobox-BASCOy0n.js +1 -0
  44. codex/static_root/assets/VCombobox-BASCOy0n.js.br +0 -0
  45. codex/static_root/assets/VCombobox-BASCOy0n.js.gz +0 -0
  46. codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js +1 -0
  47. codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js.br +0 -0
  48. codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js.gz +0 -0
  49. codex/static_root/assets/VDialog-DnbL_v_u.js +1 -0
  50. codex/static_root/assets/VDialog-DnbL_v_u.js.br +0 -0
  51. codex/static_root/assets/VDialog-DnbL_v_u.js.gz +0 -0
  52. codex/static_root/assets/{VDialog-C5Nxd_zP.css → VDialog-lQ13FnRR.1c801ad756b2.css} +1 -1
  53. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  54. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  55. codex/static_root/assets/{VDialog-C5Nxd_zP.d327f505ef0e.css → VDialog-lQ13FnRR.css} +1 -1
  56. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  57. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  58. codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js +1 -0
  59. codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js.br +0 -0
  60. codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js.gz +0 -0
  61. codex/static_root/assets/VExpansionPanels-B8nz8lN5.js +1 -0
  62. codex/static_root/assets/VExpansionPanels-B8nz8lN5.js.br +0 -0
  63. codex/static_root/assets/VExpansionPanels-B8nz8lN5.js.gz +0 -0
  64. codex/static_root/assets/{VRadioGroup-B--7K_v5.4a4e9c2d6c42.js → VRadioGroup-BgwJ_b94.55f84f3429b9.js} +1 -1
  65. codex/static_root/assets/VRadioGroup-BgwJ_b94.55f84f3429b9.js.br +0 -0
  66. codex/static_root/assets/VRadioGroup-BgwJ_b94.55f84f3429b9.js.gz +0 -0
  67. codex/static_root/assets/{VRadioGroup-B--7K_v5.js → VRadioGroup-BgwJ_b94.js} +1 -1
  68. codex/static_root/assets/VRadioGroup-BgwJ_b94.js.br +0 -0
  69. codex/static_root/assets/VRadioGroup-BgwJ_b94.js.gz +0 -0
  70. codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js +1 -0
  71. codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js.br +0 -0
  72. codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js.gz +0 -0
  73. codex/static_root/assets/VSelect-BoKpXxwe.js +1 -0
  74. codex/static_root/assets/VSelect-BoKpXxwe.js.br +0 -0
  75. codex/static_root/assets/VSelect-BoKpXxwe.js.gz +0 -0
  76. codex/static_root/assets/{VSelectionControl-CVQ9wkE2.c02186cceba6.js → VSelectionControl-BXJGg8No.0cb36d941d2a.js} +1 -1
  77. codex/static_root/assets/VSelectionControl-BXJGg8No.0cb36d941d2a.js.br +0 -0
  78. codex/static_root/assets/VSelectionControl-BXJGg8No.0cb36d941d2a.js.gz +0 -0
  79. codex/static_root/assets/{VSelectionControl-CVQ9wkE2.js → VSelectionControl-BXJGg8No.js} +1 -1
  80. codex/static_root/assets/VSelectionControl-BXJGg8No.js.br +0 -0
  81. codex/static_root/assets/VSelectionControl-BXJGg8No.js.gz +0 -0
  82. codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js +1 -0
  83. codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js.br +0 -0
  84. codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js.gz +0 -0
  85. codex/static_root/assets/VSlideGroup-C2TL5E34.js +1 -0
  86. codex/static_root/assets/VSlideGroup-C2TL5E34.js.br +0 -0
  87. codex/static_root/assets/VSlideGroup-C2TL5E34.js.gz +0 -0
  88. codex/static_root/assets/{VTable-CrWkhkiP.6d530eea0c09.js → VTable-C0FptljX.e4840b635c6e.js} +1 -1
  89. codex/static_root/assets/VTable-C0FptljX.e4840b635c6e.js.br +0 -0
  90. codex/static_root/assets/VTable-C0FptljX.e4840b635c6e.js.gz +0 -0
  91. codex/static_root/assets/{VTable-CrWkhkiP.js → VTable-C0FptljX.js} +1 -1
  92. codex/static_root/assets/VTable-C0FptljX.js.br +0 -0
  93. codex/static_root/assets/VTable-C0FptljX.js.gz +0 -0
  94. codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js +1 -0
  95. codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js.br +0 -0
  96. codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js.gz +0 -0
  97. codex/static_root/assets/VTextField-Aw8nKhY6.js +1 -0
  98. codex/static_root/assets/VTextField-Aw8nKhY6.js.br +0 -0
  99. codex/static_root/assets/VTextField-Aw8nKhY6.js.gz +0 -0
  100. codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js +1 -0
  101. codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js.br +0 -0
  102. codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js.gz +0 -0
  103. codex/static_root/assets/VWindowItem-D9j_u9MX.js +1 -0
  104. codex/static_root/assets/VWindowItem-D9j_u9MX.js.br +0 -0
  105. codex/static_root/assets/VWindowItem-D9j_u9MX.js.gz +0 -0
  106. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js +30 -0
  107. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js.br +0 -0
  108. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js.gz +0 -0
  109. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js +30 -0
  110. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js.br +0 -0
  111. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js.gz +0 -0
  112. codex/static_root/assets/{admin-drawer-panel-o87BwD38.ce39427c8201.css → admin-drawer-panel-mne3FIj6.561b3f75fe65.css} +1 -1
  113. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
  114. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
  115. codex/static_root/assets/{admin-drawer-panel-o87BwD38.css → admin-drawer-panel-mne3FIj6.css} +1 -1
  116. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
  117. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
  118. codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js +1 -0
  119. codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js.br +0 -0
  120. codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js.gz +0 -0
  121. codex/static_root/assets/admin-jf7zR2p_.js +1 -0
  122. codex/static_root/assets/admin-jf7zR2p_.js.br +0 -0
  123. codex/static_root/assets/admin-jf7zR2p_.js.gz +0 -0
  124. codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js +1 -0
  125. codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js.br +0 -0
  126. codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js.gz +0 -0
  127. codex/static_root/assets/browser-BKZuypzF.js +1 -0
  128. codex/static_root/assets/browser-BKZuypzF.js.br +0 -0
  129. codex/static_root/assets/browser-BKZuypzF.js.gz +0 -0
  130. codex/static_root/assets/browser-D7xci5SY.css +1 -0
  131. codex/static_root/assets/browser-D7xci5SY.css.br +0 -0
  132. codex/static_root/assets/browser-D7xci5SY.css.gz +0 -0
  133. codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css +1 -0
  134. codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css.br +0 -0
  135. codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css.gz +0 -0
  136. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +1 -0
  137. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  138. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  139. codex/static_root/assets/change-password-dialog-0u6IJA87.css +1 -0
  140. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  141. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  142. codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js +1 -0
  143. codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js.br +0 -0
  144. codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js.gz +0 -0
  145. codex/static_root/assets/change-password-dialog-Cc1e755p.js +1 -0
  146. codex/static_root/assets/change-password-dialog-Cc1e755p.js.br +0 -0
  147. codex/static_root/assets/change-password-dialog-Cc1e755p.js.gz +0 -0
  148. codex/static_root/assets/{confirm-dialog-jwL7B98r.1cfd579cbcfe.js → confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js} +1 -1
  149. codex/static_root/assets/confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js.br +0 -0
  150. codex/static_root/assets/confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js.gz +0 -0
  151. codex/static_root/assets/{confirm-dialog-jwL7B98r.js → confirm-dialog-Cyr7HAAz.js} +1 -1
  152. codex/static_root/assets/confirm-dialog-Cyr7HAAz.js.br +0 -0
  153. codex/static_root/assets/confirm-dialog-Cyr7HAAz.js.gz +0 -0
  154. codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js +1 -0
  155. codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js.br +0 -0
  156. codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js.gz +0 -0
  157. codex/static_root/assets/datetime-column-Cqi99Ze-.js +1 -0
  158. codex/static_root/assets/datetime-column-Cqi99Ze-.js.br +0 -0
  159. codex/static_root/assets/datetime-column-Cqi99Ze-.js.gz +0 -0
  160. codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js +1 -0
  161. codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js.br +0 -0
  162. codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js.gz +0 -0
  163. codex/static_root/assets/filter-CuocBoS2.js +1 -0
  164. codex/static_root/assets/filter-CuocBoS2.js.br +0 -0
  165. codex/static_root/assets/filter-CuocBoS2.js.gz +0 -0
  166. codex/static_root/assets/{flag-tab-Da2DBNyz.ffbe5ca37e1c.js → flag-tab-B5dRCBAt.e858f7ec9f55.js} +1 -1
  167. codex/static_root/assets/flag-tab-B5dRCBAt.e858f7ec9f55.js.br +0 -0
  168. codex/static_root/assets/flag-tab-B5dRCBAt.e858f7ec9f55.js.gz +0 -0
  169. codex/static_root/assets/{flag-tab-Da2DBNyz.js → flag-tab-B5dRCBAt.js} +1 -1
  170. codex/static_root/assets/flag-tab-B5dRCBAt.js.br +0 -0
  171. codex/static_root/assets/flag-tab-B5dRCBAt.js.gz +0 -0
  172. codex/static_root/assets/{group-tab-BONYWOc1.6ddf321a4a56.js → group-tab-_nT2o04v.c06032b254dc.js} +1 -1
  173. codex/static_root/assets/group-tab-_nT2o04v.c06032b254dc.js.br +0 -0
  174. codex/static_root/assets/group-tab-_nT2o04v.c06032b254dc.js.gz +0 -0
  175. codex/static_root/assets/{group-tab-BONYWOc1.js → group-tab-_nT2o04v.js} +1 -1
  176. codex/static_root/assets/group-tab-_nT2o04v.js.br +0 -0
  177. codex/static_root/assets/group-tab-_nT2o04v.js.gz +0 -0
  178. codex/static_root/assets/{http-error-D2Jnc20C.32846bc3a43d.js → http-error-CXo9qLiW.d5a337f399c9.js} +1 -1
  179. codex/static_root/assets/http-error-CXo9qLiW.d5a337f399c9.js.br +0 -0
  180. codex/static_root/assets/http-error-CXo9qLiW.d5a337f399c9.js.gz +0 -0
  181. codex/static_root/assets/{http-error-D2Jnc20C.js → http-error-CXo9qLiW.js} +1 -1
  182. codex/static_root/assets/http-error-CXo9qLiW.js.br +0 -0
  183. codex/static_root/assets/http-error-CXo9qLiW.js.gz +0 -0
  184. codex/static_root/assets/{library-tab-B419agXA.js → library-tab-lz5QMD4i.2f04b226ebe8.js} +1 -1
  185. codex/static_root/assets/library-tab-lz5QMD4i.2f04b226ebe8.js.br +0 -0
  186. codex/static_root/assets/library-tab-lz5QMD4i.2f04b226ebe8.js.gz +0 -0
  187. codex/static_root/assets/{library-tab-B419agXA.d4b778172aaa.js → library-tab-lz5QMD4i.js} +1 -1
  188. codex/static_root/assets/library-tab-lz5QMD4i.js.br +0 -0
  189. codex/static_root/assets/library-tab-lz5QMD4i.js.gz +0 -0
  190. codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js +32 -0
  191. codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js.br +0 -0
  192. codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js.gz +0 -0
  193. codex/static_root/assets/main-17i0EBCp.js +32 -0
  194. codex/static_root/assets/main-17i0EBCp.js.br +0 -0
  195. codex/static_root/assets/main-17i0EBCp.js.gz +0 -0
  196. codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js +1 -0
  197. codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js.br +0 -0
  198. codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js.gz +0 -0
  199. codex/static_root/assets/pagination-toolbar-BBvsIXTd.js +1 -0
  200. codex/static_root/assets/pagination-toolbar-BBvsIXTd.js.br +0 -0
  201. codex/static_root/assets/pagination-toolbar-BBvsIXTd.js.gz +0 -0
  202. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css +1 -0
  203. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css.br +0 -0
  204. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css.gz +0 -0
  205. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css +1 -0
  206. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css.br +0 -0
  207. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css.gz +0 -0
  208. codex/static_root/assets/{pdf-doc-8RVZqILx.2e7166dd26fb.js → pdf-doc-BpZdmj2g.93e86d9c7927.js} +1 -1
  209. codex/static_root/assets/pdf-doc-BpZdmj2g.93e86d9c7927.js.br +0 -0
  210. codex/static_root/assets/pdf-doc-BpZdmj2g.93e86d9c7927.js.gz +0 -0
  211. codex/static_root/assets/{pdf-doc-8RVZqILx.js → pdf-doc-BpZdmj2g.js} +1 -1
  212. codex/static_root/assets/pdf-doc-BpZdmj2g.js.br +0 -0
  213. codex/static_root/assets/pdf-doc-BpZdmj2g.js.gz +0 -0
  214. codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js +2 -0
  215. codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js.br +0 -0
  216. codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js.gz +0 -0
  217. codex/static_root/assets/reader-CRJUR2qj.js +2 -0
  218. codex/static_root/assets/reader-CRJUR2qj.js.br +0 -0
  219. codex/static_root/assets/reader-CRJUR2qj.js.gz +0 -0
  220. codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-CqXFa_gE.78267e9de0c2.css} +1 -1
  221. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.br +0 -0
  222. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.gz +0 -0
  223. codex/static_root/assets/{reader-CCCfpM3x.css → reader-CqXFa_gE.css} +1 -1
  224. codex/static_root/assets/reader-CqXFa_gE.css.br +0 -0
  225. codex/static_root/assets/reader-CqXFa_gE.css.gz +0 -0
  226. codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js +1 -0
  227. codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js.br +0 -0
  228. codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js.gz +0 -0
  229. codex/static_root/assets/relation-chips-NgDnInw_.js +1 -0
  230. codex/static_root/assets/relation-chips-NgDnInw_.js.br +0 -0
  231. codex/static_root/assets/relation-chips-NgDnInw_.js.gz +0 -0
  232. codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js +2 -0
  233. codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js.br +0 -0
  234. codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js.gz +0 -0
  235. codex/static_root/assets/settings-drawer-BaQT27kc.js +2 -0
  236. codex/static_root/assets/settings-drawer-BaQT27kc.js.br +0 -0
  237. codex/static_root/assets/settings-drawer-BaQT27kc.js.gz +0 -0
  238. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +1 -0
  239. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
  240. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
  241. codex/static_root/assets/stats-tab-B4yKuY0c.css +1 -0
  242. codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
  243. codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
  244. codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js +1 -0
  245. codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js.br +0 -0
  246. codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js.gz +0 -0
  247. codex/static_root/assets/stats-tab-DoHb54IU.js +1 -0
  248. codex/static_root/assets/stats-tab-DoHb54IU.js.br +0 -0
  249. codex/static_root/assets/stats-tab-DoHb54IU.js.gz +0 -0
  250. codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js +1 -0
  251. codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js.br +0 -0
  252. codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js.gz +0 -0
  253. codex/static_root/assets/task-tab-B88ucu1U.js +1 -0
  254. codex/static_root/assets/task-tab-B88ucu1U.js.br +0 -0
  255. codex/static_root/assets/task-tab-B88ucu1U.js.gz +0 -0
  256. codex/static_root/assets/to-case-B7MjLIw-.08fc8412de69.js +1 -0
  257. codex/static_root/assets/to-case-B7MjLIw-.08fc8412de69.js.br +0 -0
  258. codex/static_root/assets/to-case-B7MjLIw-.js +1 -0
  259. codex/static_root/assets/to-case-B7MjLIw-.js.br +0 -0
  260. codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js +1 -0
  261. codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js.br +0 -0
  262. codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js.gz +0 -0
  263. codex/static_root/assets/unauthorized-AnKtlgGy.js +1 -0
  264. codex/static_root/assets/unauthorized-AnKtlgGy.js.br +0 -0
  265. codex/static_root/assets/unauthorized-AnKtlgGy.js.gz +0 -0
  266. codex/static_root/assets/{unauthorized-DHV6Q1n-.b7847b2872a1.css → unauthorized-CibJYD8o.4c2de98c2b53.css} +1 -1
  267. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
  268. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
  269. codex/static_root/assets/{unauthorized-DHV6Q1n-.css → unauthorized-CibJYD8o.css} +1 -1
  270. codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
  271. codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
  272. codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js +1 -0
  273. codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js.br +0 -0
  274. codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js.gz +0 -0
  275. codex/static_root/assets/user-tab-BxtZ2u7W.js +1 -0
  276. codex/static_root/assets/user-tab-BxtZ2u7W.js.br +0 -0
  277. codex/static_root/assets/user-tab-BxtZ2u7W.js.gz +0 -0
  278. codex/static_root/js/choices-admin.3521e2a863df.json +1 -0
  279. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  280. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  281. codex/static_root/js/choices-admin.json +1 -1
  282. codex/static_root/js/choices-admin.json.br +0 -0
  283. codex/static_root/js/choices-admin.json.gz +0 -0
  284. codex/static_root/js/{choices.a0aaaa6c7ef7.json → choices.2f6b0359e1ef.json} +1 -1
  285. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  286. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  287. codex/static_root/js/choices.json +1 -1
  288. codex/static_root/js/choices.json.br +0 -0
  289. codex/static_root/js/choices.json.gz +0 -0
  290. codex/static_root/manifest.3851c028b0cb.json +622 -0
  291. codex/static_root/manifest.3851c028b0cb.json.br +0 -0
  292. codex/static_root/manifest.3851c028b0cb.json.gz +0 -0
  293. codex/static_root/manifest.json +245 -252
  294. codex/static_root/manifest.json.br +0 -0
  295. codex/static_root/manifest.json.gz +0 -0
  296. codex/static_root/staticfiles.json +1 -1
  297. codex/util.py +8 -6
  298. codex/views/admin/api_key.py +5 -2
  299. codex/views/admin/stats.py +13 -20
  300. codex/views/admin/tasks.py +2 -3
  301. codex/views/auth.py +1 -1
  302. codex/views/bookmark.py +49 -29
  303. codex/views/browser/annotations.py +11 -45
  304. codex/views/browser/base.py +22 -6
  305. codex/views/browser/breadcrumbs.py +5 -5
  306. codex/views/browser/browser.py +39 -27
  307. codex/views/browser/choices.py +22 -37
  308. codex/views/browser/cover.py +55 -65
  309. codex/views/browser/filters/annotations.py +33 -10
  310. codex/views/browser/filters/group.py +24 -15
  311. codex/views/browser/filters/search.py +3 -4
  312. codex/views/browser/metadata.py +98 -49
  313. codex/views/browser/mtime.py +3 -14
  314. codex/views/browser/order_by.py +1 -6
  315. codex/views/browser/settings.py +53 -0
  316. codex/views/browser/validate.py +3 -0
  317. codex/views/const.py +25 -2
  318. codex/views/opds/v1/feed.py +3 -5
  319. codex/views/opds/v2/feed.py +3 -3
  320. codex/views/reader/arcs.py +42 -51
  321. codex/views/reader/books.py +47 -63
  322. codex/views/reader/init.py +125 -0
  323. codex/views/reader/reader.py +9 -70
  324. codex/views/session.py +18 -12
  325. codex/views/settings.py +23 -10
  326. codex/views/util.py +2 -1
  327. codex/websockets/consumers.py +14 -10
  328. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/METADATA +1 -1
  329. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/RECORD +332 -328
  330. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js +0 -1
  331. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  332. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  333. codex/static_root/assets/VCheckbox-eNspVUje.js +0 -1
  334. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  335. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  336. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  337. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  338. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  339. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  340. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js +0 -1
  341. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  342. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  343. codex/static_root/assets/VCombobox-BIO7lU_B.js +0 -1
  344. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  345. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  346. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js +0 -1
  347. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  348. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  349. codex/static_root/assets/VDialog-87AymJC5.js +0 -1
  350. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  351. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  352. codex/static_root/assets/VDialog-C5Nxd_zP.css.br +0 -0
  353. codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
  354. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
  355. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
  356. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js +0 -1
  357. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  358. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  359. codex/static_root/assets/VExpansionPanels-CgA3shsd.js +0 -1
  360. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  361. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  362. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  363. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  364. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  365. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  366. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +0 -1
  367. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  368. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  369. codex/static_root/assets/VSelect-BoJq222l.js +0 -1
  370. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  371. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  372. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  373. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  374. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  375. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  376. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js +0 -1
  377. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  378. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  379. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js +0 -1
  380. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  381. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  382. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  383. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  384. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  385. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  386. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js +0 -1
  387. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  388. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  389. codex/static_root/assets/VTextField-CkWbil3K.js +0 -1
  390. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  391. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  392. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js +0 -1
  393. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  394. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  395. codex/static_root/assets/VWindowItem-7z55Y4lK.js +0 -1
  396. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  397. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  398. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js +0 -1
  399. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  400. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  401. codex/static_root/assets/admin-D3OK784R.js +0 -1
  402. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  403. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  404. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js +0 -30
  405. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  406. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  407. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js +0 -30
  408. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  409. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  410. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  411. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.gz +0 -0
  412. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  413. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.gz +0 -0
  414. codex/static_root/assets/browser-BuU2x9y7.css +0 -1
  415. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  416. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  417. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +0 -1
  418. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  419. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  420. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +0 -1
  421. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  422. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  423. codex/static_root/assets/browser-CO9EYHIr.js +0 -1
  424. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  425. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  426. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js +0 -1
  427. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  428. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  429. codex/static_root/assets/change-password-dialog-CjZdzaIk.js +0 -1
  430. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  431. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  432. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css +0 -1
  433. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
  434. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
  435. codex/static_root/assets/change-password-dialog-HvIvmmIE.css +0 -1
  436. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
  437. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
  438. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  439. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  440. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  441. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  442. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +0 -1
  443. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  444. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  445. codex/static_root/assets/datetime-column-9w3GcKvo.js +0 -1
  446. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  447. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  448. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js +0 -1
  449. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  450. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  451. codex/static_root/assets/filter-C-pghOri.js +0 -1
  452. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  453. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  454. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  455. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  456. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  457. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  458. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  459. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  460. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  461. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  462. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  463. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  464. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  465. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  466. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  467. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  468. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  469. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  470. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +0 -32
  471. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  472. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  473. codex/static_root/assets/main-CMQwJ9aG.js +0 -32
  474. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  475. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  476. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +0 -1
  477. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  478. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  479. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +0 -1
  480. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  481. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  482. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +0 -1
  483. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  484. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  485. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +0 -1
  486. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  487. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  488. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  489. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  490. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  491. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  492. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  493. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  494. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  495. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  496. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js +0 -2
  497. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  498. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  499. codex/static_root/assets/reader-DNnK_Chr.js +0 -2
  500. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  501. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  502. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js +0 -1
  503. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  504. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  505. codex/static_root/assets/relation-chips-PYua7aXv.js +0 -1
  506. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  507. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  508. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js +0 -2
  509. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  510. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  511. codex/static_root/assets/settings-drawer-DbN6ISRH.js +0 -2
  512. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  513. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  514. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js +0 -1
  515. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  516. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  517. codex/static_root/assets/stats-tab-DImNhH8O.js +0 -1
  518. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  519. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  520. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css +0 -1
  521. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +0 -2
  522. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
  523. codex/static_root/assets/stats-tab-TsyG3wH_.css +0 -1
  524. codex/static_root/assets/stats-tab-TsyG3wH_.css.br +0 -2
  525. codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
  526. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js +0 -1
  527. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  528. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  529. codex/static_root/assets/task-tab-DPD53kjv.js +0 -1
  530. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  531. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  532. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +0 -1
  533. codex/static_root/assets/to-case-ehC9Ccyj.js +0 -1
  534. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js +0 -1
  535. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  536. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  537. codex/static_root/assets/unauthorized-BOIx7Y18.js +0 -1
  538. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  539. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  540. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
  541. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
  542. codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
  543. codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
  544. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js +0 -1
  545. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  546. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  547. codex/static_root/assets/user-tab-BABwfxLO.js +0 -1
  548. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  549. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  550. codex/static_root/js/choices-admin.d790df01c20a.json +0 -1
  551. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  552. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  553. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  554. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  555. codex/static_root/manifest.cbea5663003a.json +0 -629
  556. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  557. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  558. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/LICENSE +0 -0
  559. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/WHEEL +0 -0
  560. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/entry_points.txt +0 -0
@@ -16,9 +16,6 @@ from codex.models import (
16
16
  Folder,
17
17
  Imprint,
18
18
  Library,
19
- Publisher,
20
- Series,
21
- StoryArc,
22
19
  Volume,
23
20
  )
24
21
  from codex.serializers.browser.page import BrowserPageSerializer
@@ -26,6 +23,7 @@ from codex.views.browser.title import BrowserTitleView
26
23
  from codex.views.const import (
27
24
  COMIC_GROUP,
28
25
  FOLDER_GROUP,
26
+ NONE_DATETIMEFIELD,
29
27
  STORY_ARC_GROUP,
30
28
  )
31
29
 
@@ -34,6 +32,15 @@ if TYPE_CHECKING:
34
32
 
35
33
 
36
34
  LOG = get_logger(__name__)
35
+ _GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
36
+ type[BrowserGroupModel], tuple[str | None, ...]
37
+ ] = MappingProxyType(
38
+ {
39
+ Comic: ("series", "volume"),
40
+ Volume: ("series",),
41
+ Imprint: ("publisher",),
42
+ }
43
+ )
37
44
 
38
45
 
39
46
  class BrowserView(BrowserTitleView):
@@ -48,19 +55,6 @@ class BrowserView(BrowserTitleView):
48
55
  }
49
56
  )
50
57
  TARGET = "browser"
51
- _GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
52
- type[BrowserGroupModel], tuple[str | None, ...]
53
- ] = MappingProxyType(
54
- {
55
- Comic: ("series", "volume"),
56
- Volume: ("series",),
57
- Series: (None,),
58
- Imprint: ("publisher",),
59
- Publisher: (None,),
60
- Folder: ("parent_folder",),
61
- StoryArc: (None,),
62
- }
63
- )
64
58
 
65
59
  def __init__(self, *args, **kwargs):
66
60
  """Set params for the type checker."""
@@ -75,7 +69,7 @@ class BrowserView(BrowserTitleView):
75
69
  if pks and self.group_class:
76
70
  try:
77
71
  select_related: tuple[str | None, ...] = (
78
- self._GROUP_INSTANCE_SELECT_RELATED[self.group_class]
72
+ _GROUP_INSTANCE_SELECT_RELATED.get(self.group_class, (None,))
79
73
  )
80
74
  self.group_query = self.group_class.objects.select_related(
81
75
  *select_related
@@ -156,13 +150,23 @@ class BrowserView(BrowserTitleView):
156
150
  count = 0
157
151
  return qs, count
158
152
 
159
- def _get_page_mtime(self):
160
- if not self.is_bookmark_filtered and self.group_instance:
161
- # Nice optimization if we can get get it.
162
- return self.group_instance.updated_at
153
+ def _requery_max_bookmark_updated_at(self, group_qs):
154
+ """Get max updated at without bookmark filter and aware of multi-groups."""
155
+ if not self.is_bookmark_filtered:
156
+ return group_qs
163
157
 
164
- group_model = self.group_class if self.group_class else self.model
165
- return self.get_group_mtime(group_model)
158
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(self.model)
159
+ bm_updated_at_rel = f"{bm_rel}__updated_at"
160
+ max_bmua = Max(bm_updated_at_rel, default=NONE_DATETIMEFIELD, filter=bm_filter)
161
+
162
+ group_list = []
163
+ for group in group_qs:
164
+ qs = self.get_filtered_queryset(
165
+ self.model, bookmark_filter=False, group_filter=False
166
+ )
167
+ group.max_bookmark_updated_at = qs.aggregate(max=max_bmua)["max"]
168
+ group_list.append(group)
169
+ return group_list
166
170
 
167
171
  @staticmethod
168
172
  def _get_zero_pad(book_qs):
@@ -175,6 +179,14 @@ class BrowserView(BrowserTitleView):
175
179
  zero_pad += floor(log10(issue_number_max))
176
180
  return zero_pad
177
181
 
182
+ def _get_page_mtime(self):
183
+ if not self.is_bookmark_filtered and self.group_instance:
184
+ # Nice optimization if we can get get it.
185
+ return self.group_instance.updated_at
186
+
187
+ group_model = self.group_class if self.group_class else self.model
188
+ return self.get_group_mtime(group_model, page_mtime=True)
189
+
178
190
  def _get_group_and_books(self):
179
191
  """Create the main queries with filters, annotation and pagination."""
180
192
  group_qs, group_count = self._get_group_queryset()
@@ -185,6 +197,7 @@ class BrowserView(BrowserTitleView):
185
197
  )
186
198
  if page_group_count:
187
199
  group_qs = self.annotate_card_aggregates(group_qs, self.model)
200
+ group_qs = self._requery_max_bookmark_updated_at(group_qs)
188
201
  if page_book_count:
189
202
  zero_pad = self._get_zero_pad(book_qs)
190
203
  book_qs = self.annotate_card_aggregates(book_qs, Comic)
@@ -205,7 +218,7 @@ class BrowserView(BrowserTitleView):
205
218
  )
206
219
 
207
220
  # get additional context
208
- parent_breadcrumbs = self.get_parent_breadcrumbs()
221
+ breadcrumbs = self.get_breadcrumbs()
209
222
  title = self.get_browser_page_title()
210
223
  # needs to happen after pagination
211
224
  # runs obj list query twice :/
@@ -214,7 +227,7 @@ class BrowserView(BrowserTitleView):
214
227
  # construct final data structure
215
228
  return MappingProxyType(
216
229
  {
217
- "breadcrumbs": parent_breadcrumbs,
230
+ "breadcrumbs": breadcrumbs,
218
231
  "title": title,
219
232
  "model_group": self.model_group,
220
233
  "groups": group_qs,
@@ -228,11 +241,10 @@ class BrowserView(BrowserTitleView):
228
241
  }
229
242
  )
230
243
 
231
- @extend_schema(request=BrowserTitleView.input_serializer_class)
244
+ @extend_schema(parameters=[BrowserTitleView.input_serializer_class])
232
245
  def get(self, *_args, **_kwargs):
233
246
  """Get browser settings."""
234
247
  self.init_request()
235
248
  data = self.get_object()
236
249
  serializer = self.get_serializer(data)
237
- self.save_params_to_session(self.params)
238
250
  return Response(serializer.data)
@@ -2,7 +2,6 @@
2
2
 
3
3
  from types import MappingProxyType
4
4
 
5
- import pycountry
6
5
  from caseconverter import snakecase
7
6
  from django.db.models import QuerySet
8
7
  from drf_spectacular.utils import extend_schema
@@ -15,12 +14,14 @@ from codex.models import (
15
14
  StoryArc,
16
15
  )
17
16
  from codex.models.named import IdentifierType
18
- from codex.serializers.browser.choices import CHOICES_SERIALIZER_CLASS_MAP
17
+ from codex.serializers.browser.choices import (
18
+ BrowserChoicesFilterSerializer,
19
+ )
19
20
  from codex.serializers.browser.filters import (
20
21
  BrowserFilterChoicesSerializer,
21
- CharListField,
22
22
  )
23
- from codex.serializers.models.pycountry import PyCountrySerializer
23
+ from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
24
+ from codex.serializers.choices import DUMMY_NULL_NAME, VUETIFY_NULL_CODE
24
25
  from codex.views.browser.filters.annotations import (
25
26
  BrowserAnnotationsFilterView,
26
27
  )
@@ -50,15 +51,21 @@ _BACK_REL_MAP = MappingProxyType(
50
51
  IdentifierType: "identifier__",
51
52
  }
52
53
  )
53
- _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
54
+ _NULL_NAMED_ROW = MappingProxyType({"pk": VUETIFY_NULL_CODE, "name": DUMMY_NULL_NAME})
54
55
 
55
56
 
56
57
  class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
57
58
  """Get choices for filter dialog."""
58
59
 
59
- SERIALIZER_MANY = False
60
+ input_serializer_class = BrowserFilterChoicesInputSerilalizer
60
61
  TARGET = "choices"
61
62
 
63
+ def init_request(self):
64
+ """Initialieze request."""
65
+ self.parse_params()
66
+ self.set_model()
67
+ self.set_rel_prefix()
68
+
62
69
  @staticmethod
63
70
  def get_field_choices_query(comic_qs, field_name):
64
71
  """Get distinct values for the field."""
@@ -97,12 +104,12 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
97
104
  """Get the comic subquery use for the choices."""
98
105
  return self.get_filtered_queryset(Comic)
99
106
 
100
- @extend_schema(request=BrowserAnnotationsFilterView.input_serializer_class)
107
+ @extend_schema(parameters=[input_serializer_class])
101
108
  def get(self, *_args, **_kwargs):
102
109
  """Return choices."""
103
110
  self.init_request()
104
111
  obj = self.get_object()
105
- serializer = self.get_serializer(obj, many=self.SERIALIZER_MANY)
112
+ serializer = self.get_serializer(obj)
106
113
  return Response(serializer.data)
107
114
 
108
115
 
@@ -160,28 +167,7 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
160
167
  class BrowserChoicesView(BrowserChoicesViewBase):
161
168
  """Get choices for filter dialog."""
162
169
 
163
- # serializer_class = Dynamic class determined in get()
164
- SERIALIZER_MANY = True
165
- # HACK get_serializer(data, many=True) requires this to be set for the debug API view
166
- queryset = Comic.objects.none()
167
-
168
- def _get_field_choices(self, comic_qs, field_name):
169
- """Create a pk:name object for fields without tables."""
170
- qs = self.get_field_choices_query(comic_qs, field_name)
171
-
172
- if field_name == "country":
173
- lookup = pycountry.countries
174
- elif field_name == "language":
175
- lookup = pycountry.languages
176
- else:
177
- lookup = None
178
-
179
- choices = []
180
- for val in qs:
181
- name = PyCountrySerializer.lookup_name(lookup, val) if lookup else val
182
- choices.append({"pk": val, "name": name})
183
-
184
- return choices
170
+ serializer_class = BrowserChoicesFilterSerializer
185
171
 
186
172
  def _get_m2m_field_choices(self, model, comic_qs, rel):
187
173
  """Get choices with nulls where there are nulls."""
@@ -202,11 +188,6 @@ class BrowserChoicesView(BrowserChoicesViewBase):
202
188
  field_name = self.kwargs.get("field_name", "")
203
189
  return snakecase(field_name)
204
190
 
205
- def get_serializer_class(self): # type: ignore
206
- """Dynamic serializer class."""
207
- field_name = self._get_field_name()
208
- return CHOICES_SERIALIZER_CLASS_MAP.get(field_name, CharListField)
209
-
210
191
  def get_object(self):
211
192
  """Return choices with more than one choice."""
212
193
  qs = super().get_object()
@@ -216,5 +197,9 @@ class BrowserChoicesView(BrowserChoicesViewBase):
216
197
  if m2m_model:
217
198
  choices = self._get_m2m_field_choices(m2m_model, qs, rel)
218
199
  else:
219
- choices = self._get_field_choices(qs, rel)
220
- return choices
200
+ choices = self.get_field_choices_query(qs, field_name)
201
+
202
+ return {
203
+ "field_name": field_name,
204
+ "choices": choices,
205
+ }
@@ -1,5 +1,6 @@
1
1
  """Comic cover thumbnail view."""
2
2
 
3
+ from django.db.models.query import Q
3
4
  from django.http import HttpResponse
4
5
  from drf_spectacular.types import OpenApiTypes
5
6
  from drf_spectacular.utils import extend_schema
@@ -9,11 +10,12 @@ from codex.librarian.covers.create import CoverCreateMixin
9
10
  from codex.librarian.covers.path import CoverPathMixin
10
11
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
11
12
  from codex.logger.logging import get_logger
12
- from codex.models import Comic, Folder, Volume
13
+ from codex.models import Comic, Volume
13
14
  from codex.models.paths import CustomCover
14
- from codex.serializers.choices import DEFAULTS
15
+ from codex.serializers.browser.settings import BrowserCoverInputSerializer
15
16
  from codex.views.browser.annotations import BrowserAnnotationsView
16
17
  from codex.views.const import (
18
+ CUSTOM_COVER_GROUP_RELATION,
17
19
  GROUP_RELATION,
18
20
  MISSING_COVER_FN,
19
21
  MISSING_COVER_NAME_MAP,
@@ -39,9 +41,34 @@ class WEBPRenderer(BaseRenderer):
39
41
  class CoverView(BrowserAnnotationsView):
40
42
  """ComicCover View."""
41
43
 
44
+ input_serializer_class = BrowserCoverInputSerializer
42
45
  renderer_classes = (WEBPRenderer,)
43
46
  content_type = "image/webp"
44
47
  TARGET = "cover"
48
+ REPARSE_JSON_FIELDS = frozenset(
49
+ BrowserAnnotationsView.REPARSE_JSON_FIELDS | {"parent"}
50
+ )
51
+
52
+ def get_group_filter(self, group=None, pks=None, page_mtime=False):
53
+ """Get group filter for First Cover View."""
54
+ if self.params.get("dynamic_covers"):
55
+ return super().get_group_filter(group=group, pks=pks, page_mtime=page_mtime)
56
+
57
+ # First cover group filter relies on sort names to look outside the browser supplied pks
58
+ # For multi_groups not in the browser query.
59
+ pks = self.kwargs["pks"]
60
+ name_rel = "name" if self.model == Volume else "sort_name"
61
+ qs = self.model.objects.filter(pk__in=pks) # type: ignore
62
+ sort_names = qs.values_list(name_rel, flat=True).distinct()
63
+ model_rel = GROUP_RELATION[self.model_group]
64
+ group_filter = {f"{model_rel}__{name_rel}__in": sort_names}
65
+
66
+ parent = self.params["parent"]
67
+ parent_pks = parent.get("pks", ())
68
+ if parent_pks:
69
+ parent_rel = GROUP_RELATION[parent["group"]]
70
+ group_filter[f"{parent_rel}__pk__in"] = parent_pks
71
+ return Q(**group_filter)
45
72
 
46
73
  def get_model_group(self):
47
74
  """Return the url group."""
@@ -56,39 +83,21 @@ class CoverView(BrowserAnnotationsView):
56
83
  pks = self.kwargs["pks"]
57
84
  return pks[0], False
58
85
 
59
- def _use_dynamic_cover(self):
60
- if not self.params.get("dynamic_covers"):
61
- return False
62
- filters = self.params.get("filters", {})
63
- for key, value in filters.items():
64
- if key == "bookmark" and value != DEFAULTS["bookmarkFilter"]:
65
- return True
66
- if value:
67
- return True
68
-
69
- order_by = self.params.get("order_by")
70
- default_order_by = "filename" if self.model == Folder else "sort_name"
71
- if order_by != default_order_by:
72
- return True
73
-
74
- if self.params.get("order_reverse"):
75
- return True
76
-
77
- return False
86
+ def _get_custom_cover(self):
87
+ """Get Custom Cover."""
88
+ if self.model == Volume or not self.params.get("custom_covers"):
89
+ return None
90
+ group = self.kwargs["group"]
91
+ group_rel = CUSTOM_COVER_GROUP_RELATION[group]
92
+ pks = self.kwargs["pks"]
93
+ comic_filter = {f"{group_rel}__in": pks}
94
+ qs = CustomCover.objects.filter(**comic_filter)
95
+ qs = qs.only("pk")
96
+ return qs.first()
78
97
 
79
98
  def _get_dynamic_cover(self):
80
99
  """Get dynamic cover."""
81
100
  self.set_order_key()
82
- pks = self.kwargs["pks"]
83
-
84
- if self.model != Volume and self.params.get("custom_covers"):
85
- group = self.kwargs["group"]
86
- group_rel = "folder" if self.model == Folder else GROUP_RELATION[group] # type: ignore
87
- comic_filter = {f"{group_rel}__in": pks}
88
- custom_cover = CustomCover.objects.filter(**comic_filter).only("pk").first()
89
- if custom_cover:
90
- return custom_cover.pk, True
91
-
92
101
  comic_qs = self.get_filtered_queryset(Comic)
93
102
  comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
94
103
  comic_qs = self.add_order_by(comic_qs, Comic)
@@ -99,40 +108,15 @@ class CoverView(BrowserAnnotationsView):
99
108
  cover_pk = comic.pk if comic else 0
100
109
  return cover_pk, False
101
110
 
102
- def _get_first_cover(self):
103
- """Get first cover."""
104
- pks = self.kwargs["pks"]
105
- order_by = (
106
- "path"
107
- if self.model == Folder
108
- else "name"
109
- if self.model == Volume
110
- else "sort_name"
111
- )
112
- group_qs = self.model.objects.filter(pk__in=pks) # type: ignore
113
- group_qs = group_qs.order_by(order_by)
114
- select_related = ["first_comic"]
115
- custom_covers = self.params.get("custom_covers")
116
- if custom_covers and self.model != Volume:
117
- select_related.append("custom_cover")
118
- group_qs = group_qs.select_related(*select_related)
119
- group_qs = group_qs.group_by("id") # type: ignore
120
- group_obj = group_qs.first()
121
- if custom_covers and self.model != Volume and group_obj.custom_cover:
122
- return group_obj.custom_cover.pk, True
123
- cover_pk = (
124
- group_obj.first_comic.pk if group_obj and group_obj.first_comic else 0
125
- )
126
- return cover_pk, False
127
-
128
111
  def _get_cover_pk(self) -> tuple[int, bool]:
129
112
  """Get Cover Pk queryset for comic queryset."""
130
113
  if self.model == Comic:
131
114
  cover_pk, custom = self._get_comic_cover()
132
- elif self._use_dynamic_cover():
133
- cover_pk, custom = self._get_dynamic_cover()
115
+ elif custom_cover := self._get_custom_cover():
116
+ cover_pk = custom_cover.pk
117
+ custom = True
134
118
  else:
135
- cover_pk, custom = self._get_first_cover()
119
+ cover_pk, custom = self._get_dynamic_cover()
136
120
  return cover_pk, custom
137
121
 
138
122
  def _get_missing_cover_path(self):
@@ -168,10 +152,16 @@ class CoverView(BrowserAnnotationsView):
168
152
  thumb_image_data = f.read()
169
153
  return thumb_image_data, content_type
170
154
 
171
- @extend_schema(responses={(200, content_type): OpenApiTypes.BINARY})
155
+ @extend_schema(
156
+ parameters=[BrowserAnnotationsView.input_serializer_class],
157
+ responses={(200, content_type): OpenApiTypes.BINARY},
158
+ )
172
159
  def get(self, *args, **kwargs): # type: ignore
173
160
  """Get comic cover."""
174
- self.init_request()
175
- pk, custom = self._get_cover_pk()
176
- thumb_image_data, content_type = self._get_cover_data(pk, custom)
177
- return HttpResponse(thumb_image_data, content_type=content_type)
161
+ try:
162
+ self.init_request()
163
+ pk, custom = self._get_cover_pk()
164
+ thumb_image_data, content_type = self._get_cover_data(pk, custom)
165
+ return HttpResponse(thumb_image_data, content_type=content_type)
166
+ except Exception:
167
+ LOG.exception("get")
@@ -7,7 +7,7 @@ from codex.models.comic import Comic
7
7
  from codex.views.browser.filters.bookmark import BookmarkFilterMixin
8
8
  from codex.views.browser.validate import BrowserValidateView
9
9
  from codex.views.const import (
10
- EPOCH_START_DATETIMEFILED,
10
+ EPOCH_START_DATETIMEFIELD,
11
11
  ONE_INTEGERFIELD,
12
12
  )
13
13
 
@@ -20,13 +20,17 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
20
20
  super().__init__(*args, **kwargs)
21
21
  self.init_bookmark_data()
22
22
 
23
- def _get_query_filters(self, model, group, pks, bookmark_filter):
23
+ def _get_query_filters( # noqa: PLR0913
24
+ self, model, group, pks, bookmark_filter, page_mtime=False, group_filter=True
25
+ ):
24
26
  """Return all the filters except the group filter."""
27
+ # TODO all these ifs and flags are awful
25
28
  object_filter = self.get_group_acl_filter(model)
26
- object_filter &= self.get_group_filter(model, group, pks)
29
+ if group_filter:
30
+ object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
27
31
  object_filter &= self.get_comic_field_filter(model)
28
32
  if bookmark_filter:
29
- # not needed because OuterRef is applied next
33
+ # not needed when used by outerrefl OuterRef is applied next
30
34
  object_filter &= self.get_bookmark_filter(model)
31
35
  return object_filter
32
36
 
@@ -45,17 +49,36 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
45
49
  qs = qs.filter(**{f"{ann_name}__gt": 0})
46
50
  return qs
47
51
 
48
- def get_filtered_queryset(self, model, group=None, pks=None, bookmark_filter=True):
52
+ def get_filtered_queryset( # noqa: PLR0913
53
+ self,
54
+ model,
55
+ group=None,
56
+ pks=None,
57
+ bookmark_filter=True,
58
+ page_mtime=False,
59
+ group_filter=True,
60
+ ):
49
61
  """Get a filtered queryset for the model."""
50
- object_filter = self._get_query_filters(model, group, pks, bookmark_filter)
62
+ object_filter = self._get_query_filters(
63
+ model,
64
+ group,
65
+ pks,
66
+ bookmark_filter,
67
+ page_mtime=page_mtime,
68
+ group_filter=group_filter,
69
+ )
51
70
  qs = model.objects.filter(object_filter)
52
71
  qs = self.apply_search_filter(qs, model)
53
72
  return self._filter_by_child_count(qs, model)
54
73
 
55
- def get_group_mtime(self, model, group=None, pks=None):
74
+ def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
56
75
  """Get a filtered mtime for browser pages and mtime checker."""
57
76
  qs = self.get_filtered_queryset(
58
- model, group=group, pks=pks, bookmark_filter=self.is_bookmark_filtered
77
+ model,
78
+ group=group,
79
+ pks=pks,
80
+ bookmark_filter=self.is_bookmark_filtered,
81
+ page_mtime=page_mtime,
59
82
  )
60
83
  qs = qs.annotate(max_updated_at=Max("updated_at"))
61
84
 
@@ -65,12 +88,12 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
65
88
  ua_rel = bm_rel + "__updated_at"
66
89
  mbua = Max(ua_rel)
67
90
  else:
68
- mbua = EPOCH_START_DATETIMEFILED
91
+ mbua = EPOCH_START_DATETIMEFIELD
69
92
  qs = qs.annotate(max_bookmark_updated_at=mbua)
70
93
 
71
94
  mtime = qs.aggregate(
72
95
  max=Greatest(
73
- Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFILED),
96
+ Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFIELD),
74
97
  "max_updated_at",
75
98
  )
76
99
  )["max"]
@@ -2,8 +2,11 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.models import Comic
6
- from codex.views.const import FOLDER_GROUP, GROUP_MODEL_MAP, GROUP_RELATION
5
+ from codex.views.const import (
6
+ FILTER_ONLY_GROUP_RELATION,
7
+ FOLDER_GROUP,
8
+ GROUP_RELATION,
9
+ )
7
10
  from codex.views.session import SessionView
8
11
 
9
12
 
@@ -12,25 +15,31 @@ class GroupFilterView(SessionView):
12
15
 
13
16
  SESSION_KEY = SessionView.BROWSER_SESSION_KEY
14
17
 
15
- def get_group_filter(self, model, group=None, pks=None):
18
+ def _get_rel_for_pks(self, group, page_mtime=False):
19
+ """Get the relation from the model to the pks."""
20
+ # XXX these TARGET refs might be better as subclass get rel methods.
21
+ target: str = self.TARGET # type: ignore
22
+ if target in ("metadata", "mtime") or page_mtime:
23
+ # metadata, mtime, browser.page_mtime
24
+ rel = "pk"
25
+ elif target in ("choices", "cover"):
26
+ rel = FILTER_ONLY_GROUP_RELATION[group]
27
+ else:
28
+ rel = GROUP_RELATION[group]
29
+
30
+ rel += "__in"
31
+ return rel
32
+
33
+ def get_group_filter(self, group=None, pks=None, page_mtime=False):
16
34
  """Get filter for the displayed group."""
17
35
  if group is None:
18
36
  group = self.kwargs["group"]
19
37
  if pks is None:
20
38
  pks = self.kwargs["pks"]
21
39
 
22
- if pks and 0 not in pks: # type: ignore
23
- if model == GROUP_MODEL_MAP.get(group):
24
- # metadata only
25
- rel = "pk"
26
- elif model == Comic and group == FOLDER_GROUP:
27
- # choices & covers
28
- rel = "folders"
29
- else:
30
- rel = GROUP_RELATION[group]
31
-
32
- rel += "__in"
33
- group_filter_dict = {rel: pks} # type: ignore
40
+ if pks and 0 not in pks:
41
+ rel = self._get_rel_for_pks(group, page_mtime)
42
+ group_filter_dict = {rel: pks}
34
43
  elif group == FOLDER_GROUP:
35
44
  group_filter_dict = {"parent_folder": None}
36
45
  else:
@@ -135,11 +135,10 @@ class SearchFilterView(ComicFieldFilterView):
135
135
  if comic_ids:
136
136
  # Prefilter comic ids. If nothing is allowed, don't search.
137
137
  sqs = sqs.filter_comic_ids(comic_ids)
138
- binary = (
138
+ if (
139
139
  self.TARGET in frozenset({"cover", "choices"})
140
- or self.params.get("order_by", "") != "search_score"
141
- )
142
- if binary:
140
+ or self.params.get("order_by", "") != "search_score" # type: ignore
141
+ ):
143
142
  scored_pks = self._get_binary_search_scores(sqs)
144
143
  else:
145
144
  score_pairs, prev_pks, next_pks, scored_pks = (