codex 1.6.0a7__py3-none-any.whl → 1.6.0a9__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 (517) hide show
  1. codex/integrity.py +5 -6
  2. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +54 -76
  3. codex/librarian/importer/cache.py +162 -29
  4. codex/librarian/importer/const.py +15 -6
  5. codex/librarian/importer/create_comics.py +101 -17
  6. codex/librarian/importer/create_covers.py +96 -0
  7. codex/librarian/importer/create_fks.py +73 -170
  8. codex/librarian/importer/deleted.py +59 -46
  9. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  10. codex/librarian/importer/failed_imports.py +42 -64
  11. codex/librarian/importer/importer.py +96 -0
  12. codex/librarian/importer/importerd.py +71 -397
  13. codex/librarian/importer/init.py +265 -0
  14. codex/librarian/importer/link_comics.py +26 -81
  15. codex/librarian/importer/link_covers.py +73 -0
  16. codex/librarian/importer/moved.py +76 -104
  17. codex/librarian/importer/query_covers.py +52 -0
  18. codex/librarian/importer/query_fks.py +128 -172
  19. codex/librarian/importer/status.py +2 -39
  20. codex/librarian/importer/tasks.py +22 -13
  21. codex/librarian/janitor/cleanup.py +2 -2
  22. codex/librarian/janitor/janitor.py +10 -0
  23. codex/librarian/librariand.py +7 -1
  24. codex/migrations/0027_import_order_and_covers.py +345 -0
  25. codex/models/comic.py +1 -2
  26. codex/models/groups.py +3 -2
  27. codex/models/paths.py +0 -1
  28. codex/search/query.py +25 -2
  29. codex/search/writing.py +1 -1
  30. codex/serializers/browser/mixins.py +0 -18
  31. codex/serializers/browser/mtime.py +21 -0
  32. codex/serializers/browser/settings.py +2 -3
  33. codex/serializers/choices.py +10 -1
  34. codex/serializers/fields.py +8 -9
  35. codex/serializers/reader.py +5 -5
  36. codex/serializers/route.py +9 -4
  37. codex/settings/settings.py +2 -2
  38. codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-eNspVUje.da8282a08876.js} +1 -1
  39. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  40. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  41. codex/static_root/assets/{VCheckbox-BOUtyxuo.js → VCheckbox-eNspVUje.js} +1 -1
  42. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  43. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  44. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js → VCheckboxBtn-DLzF_WHB.9a47873b8548.js} +1 -1
  45. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  46. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  47. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.js → VCheckboxBtn-DLzF_WHB.js} +1 -1
  48. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  49. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  50. codex/static_root/assets/{VCombobox-DjkDXe33.22617ea193b1.js → VCombobox-BIO7lU_B.d607ada71b06.js} +1 -1
  51. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  52. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  53. codex/static_root/assets/{VCombobox-DjkDXe33.js → VCombobox-BIO7lU_B.js} +1 -1
  54. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  55. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  56. codex/static_root/assets/{VDialog-X0zn9AGX.0d89c749b9a6.js → VDialog-87AymJC5.4a46716358c0.js} +1 -1
  57. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  58. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  59. codex/static_root/assets/{VDialog-X0zn9AGX.js → VDialog-87AymJC5.js} +1 -1
  60. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  61. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  62. codex/static_root/assets/{VExpansionPanels-CRevojaF.43d65c777c58.js → VExpansionPanels-CgA3shsd.9e524c73726a.js} +1 -1
  63. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  64. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  65. codex/static_root/assets/{VExpansionPanels-CRevojaF.js → VExpansionPanels-CgA3shsd.js} +1 -1
  66. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  67. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  68. codex/static_root/assets/{VRadioGroup-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-B--7K_v5.4a4e9c2d6c42.js} +1 -1
  69. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  70. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  71. codex/static_root/assets/{VRadioGroup-8nrrxjDv.js → VRadioGroup-B--7K_v5.js} +1 -1
  72. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  73. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  74. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  75. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  76. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  77. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  78. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  79. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  80. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +1 -0
  81. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  82. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  83. codex/static_root/assets/VSelect-BoJq222l.js +1 -0
  84. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  85. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  86. codex/static_root/assets/{VSelectionControl-CGu0oz0K.3030b7a270d6.js → VSelectionControl-CVQ9wkE2.c02186cceba6.js} +1 -1
  87. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  88. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  89. codex/static_root/assets/{VSelectionControl-CGu0oz0K.js → VSelectionControl-CVQ9wkE2.js} +1 -1
  90. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  91. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  92. codex/static_root/assets/{VSlideGroup-D_oNvCOd.71a7101cdddd.js → VSlideGroup-Ce0j2BKK.85e64443a928.js} +1 -1
  93. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  94. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  95. codex/static_root/assets/{VSlideGroup-D_oNvCOd.js → VSlideGroup-Ce0j2BKK.js} +1 -1
  96. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  97. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  98. codex/static_root/assets/{VTable-C7pKI7gU.087912f706f5.js → VTable-CrWkhkiP.6d530eea0c09.js} +1 -1
  99. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  100. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  101. codex/static_root/assets/{VTable-C7pKI7gU.js → VTable-CrWkhkiP.js} +1 -1
  102. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  103. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  104. codex/static_root/assets/{VTextField-Bs8oq9mk.js → VTextField-CkWbil3K.2cac13161dfe.js} +1 -1
  105. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  106. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  107. codex/static_root/assets/{VTextField-Bs8oq9mk.7999fef12a03.js → VTextField-CkWbil3K.js} +1 -1
  108. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  109. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  110. codex/static_root/assets/{VWindowItem-iyZ1XOkP.409f7d292253.js → VWindowItem-7z55Y4lK.98eb7487e039.js} +1 -1
  111. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  112. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  113. codex/static_root/assets/{VWindowItem-iyZ1XOkP.js → VWindowItem-7z55Y4lK.js} +1 -1
  114. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  115. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  116. codex/static_root/assets/{admin-B0pCBjma.2407da79bd20.js → admin-D3OK784R.f69891726ec5.js} +1 -1
  117. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  118. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  119. codex/static_root/assets/{admin-B0pCBjma.js → admin-D3OK784R.js} +1 -1
  120. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  121. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  122. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-1pnfvY8O.5438f252970e.js} +11 -11
  123. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  124. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  125. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.js → admin-drawer-panel-1pnfvY8O.js} +11 -11
  126. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  127. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  128. codex/static_root/assets/browser-BuU2x9y7.css +1 -0
  129. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  130. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  131. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +1 -0
  132. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  133. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  134. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +1 -0
  135. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  136. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  137. codex/static_root/assets/browser-CO9EYHIr.js +1 -0
  138. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  139. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  140. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.930f4cae3cb0.js → change-password-dialog-CjZdzaIk.188c765431ab.js} +1 -1
  141. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  142. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  143. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.js → change-password-dialog-CjZdzaIk.js} +1 -1
  144. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  145. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  146. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js → confirm-dialog-jwL7B98r.1cfd579cbcfe.js} +1 -1
  147. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  148. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  149. codex/static_root/assets/{confirm-dialog-RP7JNStF.js → confirm-dialog-jwL7B98r.js} +1 -1
  150. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  151. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  152. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +1 -0
  153. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  154. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  155. codex/static_root/assets/datetime-column-9w3GcKvo.js +1 -0
  156. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  157. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  158. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  159. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  160. codex/static_root/assets/{filter-DAdGUt-1.3261cbcd50b5.js → filter-C-pghOri.eeb7c2ecf398.js} +1 -1
  161. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  162. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  163. codex/static_root/assets/{filter-DAdGUt-1.js → filter-C-pghOri.js} +1 -1
  164. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  165. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  166. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js +1 -0
  167. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  168. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  169. codex/static_root/assets/flag-tab-Da2DBNyz.js +1 -0
  170. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  171. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  172. codex/static_root/assets/{group-tab-DEwh4jfB.a95097b5e320.js → group-tab-BONYWOc1.6ddf321a4a56.js} +1 -1
  173. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  174. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  175. codex/static_root/assets/{group-tab-DEwh4jfB.js → group-tab-BONYWOc1.js} +1 -1
  176. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  177. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  178. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js +1 -0
  179. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  180. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  181. codex/static_root/assets/http-error-D2Jnc20C.js +1 -0
  182. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  183. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  184. codex/static_root/assets/{library-tab-DrXvD9B2.js → library-tab-B419agXA.d4b778172aaa.js} +1 -1
  185. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  186. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  187. codex/static_root/assets/{library-tab-DrXvD9B2.f765f9d3bae4.js → library-tab-B419agXA.js} +1 -1
  188. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  189. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  190. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +32 -0
  191. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  192. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  193. codex/static_root/assets/main-CMQwJ9aG.js +32 -0
  194. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  195. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  196. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  197. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  198. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  199. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  200. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  201. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  202. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +1 -0
  203. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  204. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  205. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +1 -0
  206. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  207. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  208. codex/static_root/assets/{pdf-doc-obrBwxa6.ec9bd13a5786.js → pdf-doc-8RVZqILx.2e7166dd26fb.js} +1 -1
  209. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  210. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  211. codex/static_root/assets/{pdf-doc-obrBwxa6.js → pdf-doc-8RVZqILx.js} +1 -1
  212. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  213. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  214. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  215. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  216. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  217. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  218. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  219. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  220. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js +2 -0
  221. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  222. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  223. codex/static_root/assets/reader-DNnK_Chr.js +2 -0
  224. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  225. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  226. codex/static_root/assets/{relation-chips-CZJcLjc8.1d7426dab654.js → relation-chips-PYua7aXv.428ba0b5861e.js} +1 -1
  227. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  228. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  229. codex/static_root/assets/{relation-chips-CZJcLjc8.js → relation-chips-PYua7aXv.js} +1 -1
  230. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  231. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  232. codex/static_root/assets/{settings-drawer-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DbN6ISRH.d14f525d8eee.js} +2 -2
  233. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  234. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  235. codex/static_root/assets/{settings-drawer-7iTnAdjf.js → settings-drawer-DbN6ISRH.js} +2 -2
  236. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  237. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  238. codex/static_root/assets/{stats-tab-CQsK1n8O.544bf50ffb22.js → stats-tab-DImNhH8O.d5026283841a.js} +1 -1
  239. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  240. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  241. codex/static_root/assets/{stats-tab-CQsK1n8O.js → stats-tab-DImNhH8O.js} +1 -1
  242. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  243. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  244. codex/static_root/assets/{task-tab-C60TLP6e.63aa929a4ed6.js → task-tab-DPD53kjv.e56a35ff1691.js} +1 -1
  245. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  246. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  247. codex/static_root/assets/{task-tab-C60TLP6e.js → task-tab-DPD53kjv.js} +1 -1
  248. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  249. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  250. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +1 -0
  251. codex/static_root/assets/to-case-ehC9Ccyj.js +1 -0
  252. codex/static_root/assets/{unauthorized-Bs7NSqea.c55650fac055.js → unauthorized-BOIx7Y18.ee3961abfccb.js} +1 -1
  253. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  254. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  255. codex/static_root/assets/{unauthorized-Bs7NSqea.js → unauthorized-BOIx7Y18.js} +1 -1
  256. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  257. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  258. codex/static_root/assets/{user-tab-ChuH7Atm.7e80cf1fb78e.js → user-tab-BABwfxLO.37dd0440fbdd.js} +1 -1
  259. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  260. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  261. codex/static_root/assets/{user-tab-ChuH7Atm.js → user-tab-BABwfxLO.js} +1 -1
  262. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  263. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  264. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  265. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  266. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  267. codex/static_root/js/choices-admin.json +1 -1
  268. codex/static_root/js/choices-admin.json.br +0 -0
  269. codex/static_root/js/choices-admin.json.gz +0 -0
  270. codex/static_root/js/{choices.079a01e0be1a.json → choices.a0aaaa6c7ef7.json} +1 -1
  271. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  272. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  273. codex/static_root/js/choices.json +1 -1
  274. codex/static_root/js/choices.json.br +0 -0
  275. codex/static_root/js/choices.json.gz +0 -0
  276. codex/static_root/{manifest.dad79c8475ae.json → manifest.cbea5663003a.json} +239 -239
  277. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  278. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  279. codex/static_root/manifest.json +239 -239
  280. codex/static_root/manifest.json.br +0 -0
  281. codex/static_root/manifest.json.gz +0 -0
  282. codex/static_root/staticfiles.json +1 -1
  283. codex/urls/api/browser.py +11 -2
  284. codex/urls/api/reader.py +1 -7
  285. codex/urls/api/v3.py +1 -1
  286. codex/urls/opds/binary.py +1 -1
  287. codex/views/admin/auth.py +0 -2
  288. codex/views/admin/tasks.py +10 -1
  289. codex/views/auth.py +2 -4
  290. codex/views/bookmark.py +44 -40
  291. codex/views/browser/annotations.py +38 -174
  292. codex/views/browser/base.py +6 -22
  293. codex/views/browser/breadcrumbs.py +16 -14
  294. codex/views/browser/browser.py +11 -20
  295. codex/views/browser/choices.py +15 -39
  296. codex/views/browser/cover.py +177 -0
  297. codex/views/browser/filters/annotations.py +63 -18
  298. codex/views/browser/filters/bookmark.py +21 -5
  299. codex/views/browser/filters/field.py +1 -2
  300. codex/views/browser/filters/group.py +19 -11
  301. codex/views/browser/filters/search.py +19 -10
  302. codex/views/browser/metadata.py +20 -25
  303. codex/views/browser/mtime.py +72 -0
  304. codex/views/const.py +20 -0
  305. codex/views/opds/binary.py +1 -1
  306. codex/views/opds/urls.py +2 -1
  307. codex/views/opds/util.py +2 -4
  308. codex/views/opds/v1/entry/links.py +18 -33
  309. codex/views/opds/v1/facets.py +2 -1
  310. codex/views/opds/v1/feed.py +42 -23
  311. codex/views/opds/v1/links.py +2 -1
  312. codex/views/opds/v2/feed.py +11 -9
  313. codex/views/opds/v2/links.py +2 -1
  314. codex/views/opds/v2/publications.py +13 -34
  315. codex/views/public.py +0 -2
  316. codex/views/reader/books.py +7 -9
  317. codex/views/reader/reader.py +25 -22
  318. codex/views/session.py +5 -3
  319. codex/views/util.py +32 -0
  320. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/METADATA +8 -7
  321. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/RECORD +324 -325
  322. codex/librarian/importer/update_comics.py +0 -85
  323. codex/migrations/0027_sort_name.py +0 -139
  324. codex/migrations/0028_custom_covers.py +0 -166
  325. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  326. codex/serializers/mtime.py +0 -25
  327. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  328. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  329. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  330. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  331. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  332. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  333. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  334. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  335. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  336. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  337. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  338. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  339. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  340. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  341. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  342. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  343. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  344. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  345. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  346. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  347. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  348. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  349. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  350. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  351. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +0 -1
  352. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  353. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  354. codex/static_root/assets/VSelect-DgfIKRnC.js +0 -1
  355. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  356. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  357. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  358. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  359. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  360. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  361. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  362. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  363. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  364. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  365. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  366. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  367. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  368. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  369. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  370. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  371. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  372. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  373. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  374. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  375. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  376. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  377. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  378. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  379. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  380. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  381. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  382. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  383. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  384. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  385. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  386. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  387. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  388. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  389. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  390. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  391. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +0 -1
  392. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  393. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  394. codex/static_root/assets/browser-DbyAjCNh.js +0 -1
  395. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  396. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  397. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +0 -1
  398. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  399. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  400. codex/static_root/assets/browser-VnzNj1t3.css +0 -1
  401. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  402. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  403. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  404. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  405. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  406. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  407. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
  408. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  409. codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
  410. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  411. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js +0 -1
  412. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  413. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  414. codex/static_root/assets/datetime-column-Cu3WYyPj.js +0 -1
  415. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  416. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  417. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  418. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  419. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  420. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  421. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  422. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  423. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  424. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  425. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +0 -1
  426. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  427. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  428. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +0 -1
  429. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  430. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  431. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  432. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  433. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  434. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  435. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +0 -1
  436. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  437. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  438. codex/static_root/assets/http-error-xs3mIL8U.js +0 -1
  439. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  440. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  441. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  442. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  443. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  444. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  445. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +0 -32
  446. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  447. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  448. codex/static_root/assets/main-Dy9Uqpmh.js +0 -32
  449. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  450. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  451. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +0 -1
  452. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  453. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  454. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +0 -1
  455. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  456. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  457. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css +0 -1
  458. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  459. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  460. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css +0 -1
  461. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  462. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  463. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  464. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  465. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  466. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  467. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +0 -2
  468. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  469. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  470. codex/static_root/assets/reader-BXKUaUeC.js +0 -2
  471. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  472. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  473. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css +0 -1
  474. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  475. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  476. codex/static_root/assets/reader-OXJ1KVaW.css +0 -1
  477. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  478. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  479. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  480. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  481. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  482. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  483. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  484. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  485. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  486. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  487. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  488. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  489. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  490. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  491. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  492. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  493. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  494. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  495. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +0 -1
  496. codex/static_root/assets/to-case-CR9beRR0.js +0 -1
  497. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  498. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  499. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  500. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  501. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  502. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  503. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  504. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  505. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  506. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  507. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  508. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  509. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  510. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  511. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  512. codex/views/cover.py +0 -76
  513. codex/views/mtime.py +0 -70
  514. codex/views/utils.py +0 -26
  515. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/LICENSE +0 -0
  516. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/WHEEL +0 -0
  517. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/entry_points.txt +0 -0
@@ -7,7 +7,6 @@ from django.db.models import (
7
7
  Avg,
8
8
  BooleanField,
9
9
  Case,
10
- DateTimeField,
11
10
  F,
12
11
  FilteredRelation,
13
12
  Max,
@@ -17,16 +16,16 @@ from django.db.models import (
17
16
  Value,
18
17
  When,
19
18
  )
19
+ from django.db.models.expressions import OuterRef
20
20
  from django.db.models.fields import CharField, PositiveSmallIntegerField
21
- from django.db.models.functions import Least, Reverse, Right, StrIndex
21
+ from django.db.models.functions import Coalesce, Least, Reverse, Right, StrIndex
22
+ from django.db.models.functions.comparison import Greatest
22
23
 
24
+ from codex.logger.logging import get_logger
23
25
  from codex.models import (
24
26
  BrowserGroupModel,
25
27
  Comic,
26
28
  Folder,
27
- Imprint,
28
- Publisher,
29
- Series,
30
29
  StoryArc,
31
30
  Volume,
32
31
  )
@@ -34,21 +33,14 @@ from codex.models.functions import JsonGroupArray
34
33
  from codex.views.browser.order_by import (
35
34
  BrowserOrderByView,
36
35
  )
37
- from codex.views.const import STORY_ARC_GROUP
36
+ from codex.views.const import (
37
+ EPOCH_START,
38
+ NONE_DATETIMEFIELD,
39
+ NONE_INTEGERFIELD,
40
+ STORY_ARC_GROUP,
41
+ )
38
42
  from codex.views.mixins import SharedAnnotationsMixin
39
43
 
40
- _REL_MAP = MappingProxyType(
41
- {
42
- Publisher: "publisher",
43
- Imprint: "imprint",
44
- Series: "series",
45
- Volume: "volume",
46
- Folder: "parent_folder",
47
- StoryArc: "story_arc_numbers__story_arc",
48
- }
49
- )
50
- _NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
51
- _NONE_DATETIMEFIELD = Value(None, DateTimeField())
52
44
  _ORDER_AGGREGATE_FUNCS = MappingProxyType(
53
45
  # These are annotated to order_value because they're simple relations
54
46
  {
@@ -76,6 +68,8 @@ _ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingPro
76
68
  {Comic: "id", Volume: "name"}
77
69
  )
78
70
 
71
+ LOG = get_logger(__name__)
72
+
79
73
 
80
74
  class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
81
75
  """Base class for views that need special metadata annotations."""
@@ -86,7 +80,6 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
86
80
  self.is_opds_1_acquisition = False
87
81
  self.comic_sort_names = ()
88
82
  self.bm_annotataion_data: dict[BrowserGroupModel, tuple[str, dict]] = {}
89
- self.cover_search_score_pairs: tuple[tuple[int, float], ...] = ()
90
83
 
91
84
  def get_group_by(self, model=None):
92
85
  """Get the group by for the model."""
@@ -150,7 +143,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
150
143
  )
151
144
  story_arc_number = self.order_agg_func("selected_story_arc_number__number")
152
145
  else:
153
- story_arc_number = _NONE_INTEGERFIELD
146
+ story_arc_number = NONE_INTEGERFIELD
154
147
 
155
148
  return qs.alias(story_arc_number=story_arc_number)
156
149
 
@@ -170,17 +163,11 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
170
163
 
171
164
  def get_bookmark_updated_at_aggregate(self, model, always_max=False):
172
165
  """Get The aggregate function for relevant bookmark.updated_at."""
173
- bm_rel = self.get_bm_rel(model)
174
- bm_filter = self.get_my_bookmark_filter(bm_rel)
175
- self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
166
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
176
167
 
177
- updated_at_rel = f"{bm_rel}__updated_at"
168
+ bm_updated_at_rel = f"{bm_rel}__updated_at"
178
169
  agg_func = Max if always_max else self.order_agg_func
179
- return agg_func(
180
- updated_at_rel,
181
- default=_NONE_DATETIMEFIELD,
182
- filter=bm_filter,
183
- )
170
+ return agg_func(bm_updated_at_rel, default=NONE_DATETIMEFIELD, filter=bm_filter)
184
171
 
185
172
  def _annotate_bookmark_updated_at(self, qs, model):
186
173
  if (
@@ -231,54 +218,6 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
231
218
  qs = self._annotate_order_value(qs, model)
232
219
  return qs
233
220
 
234
- def _annotate_cover_pk(self, qs, model):
235
- """Annotate the query set for the coverpath for the sort."""
236
- # Select comics for the children by an outer ref for annotation
237
- # Order the descendant comics by the sort argumentst
238
- cover_style = self.params.get("cover_style")
239
- if model == Comic:
240
- default_cover_pk = F("pk")
241
- default_cover_mtime = F("updated_at")
242
- elif cover_style == "i":
243
- default_cover_pk = None
244
- default_cover_mtime = None
245
- elif cover_style == "f":
246
- # At this point it's only been filtered.
247
- default_cover_pk = F(self.rel_prefix + "pk")
248
- default_cover_mtime = F(self.rel_prefix + "updated_at")
249
- else: # cover_style == "d"
250
- default_cover_pk = F(self.rel_prefix + "pk")
251
- if self.is_bookmark_filtered:
252
- default_cover_mtime = Case(
253
- When(bookmark_updated_at__gt=F(self.rel_prefix + "updated_at")),
254
- then=F("bookmark_updated_at"),
255
- default=F(self.rel_prefix + "updated_at"),
256
- )
257
- else:
258
- default_cover_mtime = F(self.rel_prefix + "updated_at")
259
-
260
- if model in (Volume, Comic):
261
- cover_custom = Value(False)
262
- cover_pk = default_cover_pk
263
- cover_mtime = default_cover_mtime
264
- else:
265
- cover_custom = F("custom_cover")
266
- cover_pk = Case(
267
- When(custom_cover__isnull=False, then=F("custom_cover__pk")),
268
- default=default_cover_pk,
269
- )
270
- cover_mtime = Case(
271
- When(custom_cover__isnull=False, then=F("custom_cover__updated_at")),
272
- default=default_cover_mtime,
273
- )
274
-
275
- return qs.annotate(
276
- cover_custom=cover_custom,
277
- cover_pk=cover_pk,
278
- cover_mtime=cover_mtime,
279
- cover_pks=JsonGroupArray("cover_pk", distinct=True),
280
- )
281
-
282
221
  def _annotate_group(self, qs, model):
283
222
  """Annotate Group."""
284
223
  value = "c" if model == Comic else self.model_group # type: ignore
@@ -286,11 +225,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
286
225
 
287
226
  def _annotate_bookmarks(self, qs, model):
288
227
  """Hoist up bookmark annotations."""
289
- if model in self.bm_annotation_data: # type: ignore
290
- bm_rel, bm_filter = self.bm_annotation_data[model] # type: ignore
291
- else:
292
- bm_rel = self.get_bm_rel(model)
293
- bm_filter = self.get_my_bookmark_filter(bm_rel)
228
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
294
229
 
295
230
  page_rel = f"{bm_rel}__page"
296
231
  finished_rel = f"{bm_rel}__finished"
@@ -331,7 +266,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
331
266
  output_field=PositiveSmallIntegerField(),
332
267
  distinct=True,
333
268
  )
334
- qs = qs.alias(finished_count=finished_count)
269
+ qs = qs.annotate(finished_count=finished_count)
335
270
  finished_aggregate = Case(
336
271
  When(finished_count=F("child_count"), then=True),
337
272
  When(finished_count=0, then=False),
@@ -339,20 +274,15 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
339
274
  output_field=BooleanField(),
340
275
  )
341
276
 
342
- if (
343
- self.is_opds_1_acquisition
344
- or self.is_model_comic
345
- and self.TARGET == "browser"
277
+ if self.is_opds_1_acquisition or (
278
+ self.is_model_comic and self.TARGET == "browser"
346
279
  ):
347
280
  qs = qs.annotate(page=bookmark_page)
348
- else:
281
+ elif self.TARGET in frozenset({"metadata", "browser"}):
349
282
  qs = qs.alias(page=bookmark_page)
350
283
 
351
284
  if self.TARGET in frozenset({"metadata", "browser"}):
352
- qs.annotate(finished=finished_aggregate)
353
- else:
354
- # Only used for progress
355
- qs.alias(finished=finished_aggregate)
285
+ qs = qs.annotate(finished=finished_aggregate)
356
286
  return qs
357
287
 
358
288
  def _annotate_progress(self, qs):
@@ -367,19 +297,26 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
367
297
  progress = Case(When(page_count__gt=0, then=then), default=0.0)
368
298
  return qs.annotate(progress=progress)
369
299
 
370
- def _annotate_mtime(self, qs):
300
+ def _annotate_mtime(self, qs, model):
371
301
  """Annotations mtime."""
372
- when_filter = {self.rel_prefix + "bookmark__updated_at__gt": F("updated_at")}
373
- mtime = Case(
374
- When(**when_filter),
375
- then=F(self.rel_prefix + "bookmark__updated_at"),
376
- default=F("updated_at"),
377
- )
302
+ if self.is_bookmark_filtered:
303
+ sub_qs = self.get_filtered_queryset(model, bookmark_filter=False)
304
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
305
+ ua_rel = bm_rel + "__updated_at"
306
+ qs = qs.annotate(
307
+ bmu_max=Max(
308
+ sub_qs.filter(pk=OuterRef("pk")).values_list(ua_rel, flat=True),
309
+ default=NONE_DATETIMEFIELD,
310
+ filter=bm_filter,
311
+ )
312
+ )
313
+ mtime = Greatest(Coalesce("bmu_max", Value(EPOCH_START)), "updated_at")
314
+ else:
315
+ mtime = F("updated_at")
378
316
  return qs.annotate(mtime=mtime)
379
317
 
380
318
  def annotate_card_aggregates(self, qs, model):
381
319
  """Annotate aggregates that appear the browser card."""
382
- qs = self._annotate_cover_pk(qs, model)
383
320
  if model == Comic:
384
321
  # comic adds order_value for cards late
385
322
  qs = self._annotate_order_value(qs, model)
@@ -387,77 +324,4 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
387
324
  qs = self.annotate_group_names(qs, model)
388
325
  qs = self._annotate_bookmarks(qs, model)
389
326
  qs = self._annotate_progress(qs)
390
- return self._annotate_mtime(qs)
391
-
392
- def _get_cover_pk_query(self, cover_pks, group_ids):
393
- """Get Cover Pk queryset for comic queryset."""
394
- cover_style = self.params.get("cover_style")
395
- if cover_style == "d":
396
- # DYNAMIC COVERS
397
- comic_qs = Comic.objects.filter(pk__in=cover_pks)
398
- comic_qs = self.annotate_search_score(
399
- comic_qs, Comic, self.cover_search_score_pairs
400
- )
401
- comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
402
- comic_qs = self.add_order_by(comic_qs, Comic) # type: ignore
403
- else: # "f"
404
- # FIRST COVERS
405
- # The only filter at all is the model relation.
406
- group_rel = _REL_MAP[self.model] # type: ignore
407
- comic_qs = Comic.objects.filter(**{f"{group_rel}__in": group_ids})
408
-
409
- # Order generated irrespective of browser filters and order
410
- if self.kwargs["group"] == "f":
411
- order_key = "filename"
412
- elif self.kwargs["group"] == "a":
413
- order_key = "story_arc_number"
414
- else:
415
- order_key = "sort_name"
416
-
417
- model_group = self.model_group # type: ignore
418
- show = MappingProxyType(self.params["show"]) # type: ignore
419
- model_group = self.kwargs["group"]
420
- comic_qs, comic_sort_names = self.alias_sort_names(
421
- comic_qs, Comic, cover_pks, model_group, show
422
- )
423
- comic_qs = self.add_order_by(
424
- comic_qs,
425
- Comic,
426
- order_key=order_key,
427
- do_reverse=False,
428
- comic_sort_names=comic_sort_names,
429
- )
430
- comic_qs = comic_qs.group_by("id")
431
- return comic_qs.only("pk", "updated_at")
432
-
433
- def re_cover_multi_groups(self, group_qs):
434
- """Python hack to re-cover groups collapsed with group_by."""
435
- # This would be better in the main query but OuterRef can't access annotations.
436
- # So cover_pks aggregates all covers from multi groups like ids does.
437
- cover_style = self.params.get("cover_style")
438
- if self.is_model_comic or cover_style == "i":
439
- return group_qs
440
-
441
- recovered_group_list = []
442
- for group in group_qs:
443
- cover_pks = group.cover_pks
444
- if len(cover_pks) == 1:
445
- cover_pk = cover_pks[0]
446
- else:
447
- comic_qs = self._get_cover_pk_query(cover_pks, group.ids)
448
- # print(comic_qs.explain())
449
- # print(comic_qs.query)
450
- cover = comic_qs[0]
451
- cover_pk = cover.pk
452
-
453
- if len(group.ids) == 1:
454
- cover_mtime = group.updated_at
455
- else:
456
- cover_mtime = group.__class__.objects.filter(
457
- pk__in=group.ids
458
- ).aggregate(updated_at=Max("updated_at"))["updated_at"]
459
-
460
- group.cover_mtime = cover_mtime
461
- group.cover_pk = cover_pk
462
- recovered_group_list.append(group)
463
- return recovered_group_list
327
+ return self._annotate_mtime(qs, model)
@@ -4,7 +4,6 @@ from types import MappingProxyType
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from django.contrib.auth.models import User
7
- from django.db.models import Q
8
7
  from django.db.models.aggregates import Max, Min
9
8
  from rest_framework.exceptions import NotFound
10
9
 
@@ -12,20 +11,17 @@ from codex.logger.logging import get_logger
12
11
  from codex.models import (
13
12
  AdminFlag,
14
13
  Comic,
15
- Folder,
16
14
  )
17
15
  from codex.serializers.browser.settings import BrowserSettingsSerializer
18
16
  from codex.views.browser.filters.search import SearchFilterView
19
17
  from codex.views.const import GROUP_MODEL_MAP, ROOT_GROUP
20
- from codex.views.utils import reparse_json_query_params
18
+ from codex.views.util import reparse_json_query_params
21
19
 
22
20
  LOG = get_logger(__name__)
23
21
 
24
22
  if TYPE_CHECKING:
25
23
  from codex.models.groups import BrowserGroupModel
26
24
 
27
- _REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
28
-
29
25
 
30
26
  class BrowserBaseView(SearchFilterView):
31
27
  """Browse comics with a variety of filters and sorts."""
@@ -33,16 +29,15 @@ class BrowserBaseView(SearchFilterView):
33
29
  input_serializer_class = BrowserSettingsSerializer
34
30
 
35
31
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType({})
32
+ REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
36
33
 
37
34
  _GET_JSON_KEYS = frozenset({"filters", "show"})
38
- TARGET = ""
39
35
 
40
36
  def __init__(self, *args, **kwargs):
41
37
  """Set params for the type checker."""
42
38
  super().__init__(*args, **kwargs)
43
39
  self._is_admin: bool = False
44
40
  self.params: MappingProxyType[str, Any] = MappingProxyType({})
45
- self.bm_annotation_data = {}
46
41
  self.rel_prefix: str = ""
47
42
  self.model: type[BrowserGroupModel] | None = None
48
43
  self.group_class: type[BrowserGroupModel] | None = None
@@ -77,23 +72,11 @@ class BrowserBaseView(SearchFilterView):
77
72
  group = self.params["top_group"]
78
73
  return group
79
74
 
80
- def get_query_filters_without_group(self, model, cover=False):
81
- """Return all the filters except the group filter."""
82
- add_acl = not (cover and self.model == Folder) # type: ignore
83
- object_filter = self.get_group_acl_filter(model) if add_acl else Q()
84
- object_filter &= self.get_bookmark_filter(model)
85
- object_filter &= self.get_comic_field_filter(model)
86
- return object_filter
87
-
88
- def get_query_filters(self, model, choices=False):
89
- """Return the main object filter and the one for aggregates."""
90
- object_filter = self.get_query_filters_without_group(model)
91
- object_filter &= self.get_group_filter(choices)
92
- return object_filter
93
-
94
75
  def _parse_query_params(self):
95
76
  """Parse GET query parameters: filter object & snake case."""
96
- query_params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
77
+ query_params = reparse_json_query_params(
78
+ self.request.GET, self.REPARSE_JSON_FIELDS
79
+ )
97
80
  if "q" not in query_params and (query := query_params.get("query")):
98
81
  # parse query param for opds v2
99
82
  query_params["q"] = query
@@ -106,6 +89,7 @@ class BrowserBaseView(SearchFilterView):
106
89
  serializer.is_valid(raise_exception=True)
107
90
  self.load_params_from_session(serializer.validated_data)
108
91
  self.is_bookmark_filtered = bool(self.params.get("filters", {}).get("bookmark"))
92
+ return serializer.validated_data
109
93
 
110
94
  def set_order_key(self):
111
95
  """Unused until browser."""
@@ -5,13 +5,11 @@ from types import MappingProxyType
5
5
 
6
6
  from codex.logger.logging import get_logger
7
7
  from codex.views.browser.paginate import BrowserPaginateView
8
- from codex.views.const import FOLDER_GROUP, GROUP_NAME_MAP, STORY_ARC_GROUP
8
+ from codex.views.const import FOLDER_GROUP, GROUP_NAME_MAP, GROUP_ORDER, STORY_ARC_GROUP
9
9
  from codex.views.util import Route
10
10
 
11
11
  LOG = get_logger(__name__)
12
12
 
13
- _GROUP_ORDER = "rpisv" # TODO move to const
14
-
15
13
 
16
14
  class BrowserBreadcrumbsView(BrowserPaginateView):
17
15
  """Browser breadcrumbs calculations."""
@@ -69,9 +67,11 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
69
67
 
70
68
  reversed_breadcrumbs = list(reversed(old_breadcrumbs))
71
69
 
70
+ pks = self.kwargs["pks"]
71
+ page = self.kwargs["page"]
72
72
  folder = self.group_instance # type: ignore
73
- name = folder.name if folder else "All"
74
- group_crumb = Route(FOLDER_GROUP, self.kwargs["pks"], self.kwargs["page"], name)
73
+ name = folder.name if folder and pks else ""
74
+ group_crumb = Route(FOLDER_GROUP, pks, page, name)
75
75
  new_breadcrumbs = []
76
76
 
77
77
  while True:
@@ -89,11 +89,13 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
89
89
  break
90
90
 
91
91
  # parent next
92
+ if not folder:
93
+ break
92
94
  folder = folder.parent_folder
93
95
  if folder:
94
96
  group_crumb = Route(FOLDER_GROUP, (folder.pk,), 1, name=folder.name)
95
97
  else:
96
- group_crumb = Route(FOLDER_GROUP, (), 1, name="All")
98
+ group_crumb = Route(FOLDER_GROUP, (), 1, name="")
97
99
 
98
100
  breadcrumbs = new_breadcrumbs
99
101
 
@@ -105,12 +107,12 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
105
107
  if not gi:
106
108
  pks = ()
107
109
  page = 1
108
- name = "All"
110
+ name = ""
109
111
  if group == self.kwargs["group"]:
110
112
  # create self crumb
111
113
  pks = self.kwargs["pks"]
112
114
  page = self.kwargs["page"]
113
- name = gi.name if gi else "All"
115
+ name = gi.name if gi else ""
114
116
  else:
115
117
  page = 1
116
118
  if (attr := GROUP_NAME_MAP.get(group)) and (
@@ -120,7 +122,7 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
120
122
  name = parent_group.name
121
123
  else:
122
124
  pks = ()
123
- name = "All"
125
+ name = ""
124
126
 
125
127
  return Route(group, pks, page, name)
126
128
 
@@ -147,29 +149,29 @@ class BrowserBreadcrumbsView(BrowserPaginateView):
147
149
 
148
150
  def _breadcrumbs_graft_or_create_group(self) -> tuple[tuple[Route, ...], bool]:
149
151
  """Graft or create browse group breadcrumbs."""
150
- old_breadcrumbs, changed = self._init_breadcrumbs(_GROUP_ORDER)
152
+ old_breadcrumbs, changed = self._init_breadcrumbs(GROUP_ORDER)
151
153
 
152
154
  vng = self.valid_nav_groups # type: ignore
153
155
  test_groups = tuple(reversed(vng[:-1]))
154
156
  new_breadcrumbs = []
155
157
  level = done = False
156
158
  try:
157
- browser_group_index = _GROUP_ORDER.index(self.kwargs["group"])
159
+ browser_group_index = GROUP_ORDER.index(self.kwargs["group"])
158
160
  except ValueError:
159
161
  browser_group_index = -1
160
162
 
161
163
  for group in test_groups:
162
164
  try:
163
165
  with suppress(ValueError):
164
- level = level or _GROUP_ORDER.index(group) <= browser_group_index
166
+ level = level or GROUP_ORDER.index(group) <= browser_group_index
165
167
  if level:
166
168
  done, changed = self._breadcrumbs_graft_or_create_group_crumb(
167
169
  group, old_breadcrumbs, new_breadcrumbs, changed
168
170
  )
169
171
  try:
170
- if old_breadcrumbs and _GROUP_ORDER.index(
172
+ if old_breadcrumbs and GROUP_ORDER.index(
171
173
  old_breadcrumbs[-1].group
172
- ) >= _GROUP_ORDER.index(group):
174
+ ) >= GROUP_ORDER.index(group):
173
175
  # Trim old_breadcrumbs to match to group
174
176
  old_breadcrumbs.pop(-1)
175
177
  except ValueError:
@@ -22,7 +22,6 @@ from codex.models import (
22
22
  Volume,
23
23
  )
24
24
  from codex.serializers.browser.page import BrowserPageSerializer
25
- from codex.util import max_none
26
25
  from codex.views.browser.title import BrowserTitleView
27
26
  from codex.views.const import (
28
27
  COMIC_GROUP,
@@ -121,9 +120,7 @@ class BrowserView(BrowserTitleView):
121
120
  ################
122
121
  def _get_common_queryset(self, model):
123
122
  """Create queryset common to group & books."""
124
- object_filter = self.get_query_filters(model, False)
125
- qs = model.objects.filter(object_filter)
126
- qs = self.filter_by_annotations(qs, model)
123
+ qs = self.get_filtered_queryset(model)
127
124
  count_group_by = self.get_group_by(model)
128
125
  count = qs.group_by(count_group_by).count()
129
126
  if count:
@@ -159,16 +156,13 @@ class BrowserView(BrowserTitleView):
159
156
  count = 0
160
157
  return qs, count
161
158
 
162
- def _get_page_mtime(self, group_qs):
163
- if self.group_instance:
164
- page_updated_at_max = self.group_instance.updated_at
165
- else:
166
- page_updated_at_max = group_qs.aggregate(max=Max("updated_at"))["max"]
167
-
168
- agg_func = self.get_bookmark_updated_at_aggregate(self.model, True)
169
- page_bookmark_updated_at = group_qs.aggregate(max=agg_func)["max"]
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
170
163
 
171
- return max_none(page_updated_at_max, page_bookmark_updated_at)
164
+ group_model = self.group_class if self.group_class else self.model
165
+ return self.get_group_mtime(group_model)
172
166
 
173
167
  @staticmethod
174
168
  def _get_zero_pad(book_qs):
@@ -189,9 +183,6 @@ class BrowserView(BrowserTitleView):
189
183
  group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
190
184
  group_qs, book_qs, group_count, book_count
191
185
  )
192
- mtime = self._get_page_mtime(
193
- group_qs
194
- ) # TODO should this be after paginate or not?
195
186
  if page_group_count:
196
187
  group_qs = self.annotate_card_aggregates(group_qs, self.model)
197
188
  if page_book_count:
@@ -203,13 +194,13 @@ class BrowserView(BrowserTitleView):
203
194
  # print(group_qs.explain())
204
195
  # print(group_qs.query)
205
196
 
206
- recovered_group_list = self.re_cover_multi_groups(group_qs)
207
197
  total_count = page_group_count + page_book_count
208
- return recovered_group_list, book_qs, num_pages, total_count, zero_pad, mtime
198
+ mtime = self._get_page_mtime()
199
+ return group_qs, book_qs, num_pages, total_count, zero_pad, mtime
209
200
 
210
201
  def get_object(self):
211
202
  """Validate settings and get the querysets."""
212
- group_list, book_qs, num_pages, total_count, zero_pad, mtime = (
203
+ group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
213
204
  self._get_group_and_books()
214
205
  )
215
206
 
@@ -226,7 +217,7 @@ class BrowserView(BrowserTitleView):
226
217
  "breadcrumbs": parent_breadcrumbs,
227
218
  "title": title,
228
219
  "model_group": self.model_group,
229
- "groups": group_list,
220
+ "groups": group_qs,
230
221
  "books": book_qs,
231
222
  "zero_pad": zero_pad,
232
223
  "num_pages": num_pages,