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
@@ -12,12 +12,7 @@ from codex.logger.logging import get_logger
12
12
  from codex.models import (
13
13
  Comic,
14
14
  ContributorPerson,
15
- Folder,
16
- Imprint,
17
- Publisher,
18
- Series,
19
15
  StoryArc,
20
- Volume,
21
16
  )
22
17
  from codex.models.named import IdentifierType
23
18
  from codex.serializers.browser.choices import CHOICES_SERIALIZER_CLASS_MAP
@@ -48,7 +43,6 @@ _FIELD_TO_REL_MODEL_MAP = MappingProxyType(
48
43
  ),
49
44
  }
50
45
  )
51
- _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
52
46
  _BACK_REL_MAP = MappingProxyType(
53
47
  {
54
48
  ContributorPerson: "contributor__",
@@ -56,24 +50,15 @@ _BACK_REL_MAP = MappingProxyType(
56
50
  IdentifierType: "identifier__",
57
51
  }
58
52
  )
59
- _CLASS_REL_MAP = MappingProxyType(
60
- {
61
- Publisher: "publisher",
62
- Imprint: "imprint",
63
- Series: "series",
64
- Volume: "volume",
65
- Comic: "pk",
66
- Folder: "parent_folder",
67
- StoryArc: "story_arc_numbers__story_arc",
68
- IdentifierType: "identifiers__identifier_type",
69
- }
70
- )
71
- _SERIALIZER_MANY = False
53
+ _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
72
54
 
73
55
 
74
56
  class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
75
57
  """Get choices for filter dialog."""
76
58
 
59
+ SERIALIZER_MANY = False
60
+ TARGET = "choices"
61
+
77
62
  @staticmethod
78
63
  def get_field_choices_query(comic_qs, field_name):
79
64
  """Get distinct values for the field."""
@@ -85,21 +70,13 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
85
70
 
86
71
  def get_m2m_field_query(self, model, comic_qs: QuerySet):
87
72
  """Get distinct m2m value objects for the relation."""
88
- if self.model is None:
89
- LOG.error("No model to make filter choices!")
90
- m2m_filter = {}
91
- else:
92
- model_rel: str = _CLASS_REL_MAP[self.model] # type: ignore
93
- back_rel = _BACK_REL_MAP.get(model, "")
94
- back_rel += "comic__"
95
- back_rel += model_rel
96
- back_rel += "__in"
97
- m2m_filter = {back_rel: comic_qs}
73
+ back_rel = _BACK_REL_MAP.get(model, "")
74
+ m2m_filter = {f"{back_rel}comic__in": comic_qs}
98
75
  return model.objects.filter(**m2m_filter).values("pk", "name").distinct()
99
76
 
100
77
  @staticmethod
101
78
  def does_m2m_null_exist(comic_qs, rel):
102
- """Get if null values exists for an m2m field."""
79
+ """Get if comics exist in the filter without values exists for an m2m field."""
103
80
  return comic_qs.filter(**{f"{rel}__isnull": True}).exists()
104
81
 
105
82
  def get_rel_and_model(self, field_name):
@@ -114,22 +91,18 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
114
91
  rel = field_name
115
92
  model = remote_field.model if remote_field else None
116
93
 
117
- rel = self.rel_prefix + rel
118
-
119
94
  return rel, model
120
95
 
121
96
  def get_object(self):
122
97
  """Get the comic subquery use for the choices."""
123
- object_filter = self.get_query_filters(self.model, choices=True)
124
- qs = self.model.objects.filter(object_filter) # type: ignore
125
- return self.filter_by_annotations(qs, self.model, binary=True)
98
+ return self.get_filtered_queryset(Comic)
126
99
 
127
100
  @extend_schema(request=BrowserAnnotationsFilterView.input_serializer_class)
128
101
  def get(self, *_args, **_kwargs):
129
102
  """Return choices."""
130
103
  self.init_request()
131
104
  obj = self.get_object()
132
- serializer = self.get_serializer(obj, many=_SERIALIZER_MANY)
105
+ serializer = self.get_serializer(obj, many=self.SERIALIZER_MANY)
133
106
  return Response(serializer.data)
134
107
 
135
108
 
@@ -162,6 +135,7 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
162
135
  def get_object(self):
163
136
  """Get choice counts."""
164
137
  qs = super().get_object()
138
+ filters = self.params.get("filters", {})
165
139
  data = {}
166
140
  for field_name in self.serializer_class().get_fields(): # type: ignore
167
141
  if field_name == "story_arcs" and self.model == StoryArc:
@@ -174,9 +148,9 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
174
148
  else:
175
149
  count = self._get_field_choices_count(qs, rel)
176
150
 
177
- filters = self.params.get("filters", {})
178
151
  try:
179
- flag = count > 1 or field_name in filters # type: ignore
152
+ is_filter_set = bool(filters.get(field_name))
153
+ flag = is_filter_set or count > 1 # type: ignore
180
154
  except TypeError:
181
155
  flag = False
182
156
  data[field_name] = flag
@@ -187,7 +161,9 @@ class BrowserChoicesView(BrowserChoicesViewBase):
187
161
  """Get choices for filter dialog."""
188
162
 
189
163
  # serializer_class = Dynamic class determined in get()
190
- _SERIALIZER_MANY = True
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()
191
167
 
192
168
  def _get_field_choices(self, comic_qs, field_name):
193
169
  """Create a pk:name object for fields without tables."""
@@ -0,0 +1,177 @@
1
+ """Comic cover thumbnail view."""
2
+
3
+ from django.http import HttpResponse
4
+ from drf_spectacular.types import OpenApiTypes
5
+ from drf_spectacular.utils import extend_schema
6
+ from rest_framework.renderers import BaseRenderer
7
+
8
+ from codex.librarian.covers.create import CoverCreateMixin
9
+ from codex.librarian.covers.path import CoverPathMixin
10
+ from codex.librarian.mp_queue import LIBRARIAN_QUEUE
11
+ from codex.logger.logging import get_logger
12
+ from codex.models import Comic, Folder, Volume
13
+ from codex.models.paths import CustomCover
14
+ from codex.serializers.choices import DEFAULTS
15
+ from codex.views.browser.annotations import BrowserAnnotationsView
16
+ from codex.views.const import (
17
+ GROUP_RELATION,
18
+ MISSING_COVER_FN,
19
+ MISSING_COVER_NAME_MAP,
20
+ STATIC_IMG_PATH,
21
+ )
22
+
23
+ LOG = get_logger(__name__)
24
+
25
+
26
+ class WEBPRenderer(BaseRenderer):
27
+ """Render WEBP images."""
28
+
29
+ media_type = "image/webp"
30
+ format = "webp"
31
+ charset = None
32
+ render_style = "binary"
33
+
34
+ def render(self, data, *_args, **_kwargs):
35
+ """Return raw data."""
36
+ return data
37
+
38
+
39
+ class CoverView(BrowserAnnotationsView):
40
+ """ComicCover View."""
41
+
42
+ renderer_classes = (WEBPRenderer,)
43
+ content_type = "image/webp"
44
+ TARGET = "cover"
45
+
46
+ def get_model_group(self):
47
+ """Return the url group."""
48
+ return self.kwargs["group"]
49
+
50
+ def init_request(self):
51
+ """Initialize request."""
52
+ self.parse_params()
53
+ self.set_model()
54
+
55
+ def _get_comic_cover(self):
56
+ pks = self.kwargs["pks"]
57
+ return pks[0], False
58
+
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
78
+
79
+ def _get_dynamic_cover(self):
80
+ """Get dynamic cover."""
81
+ 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
+ comic_qs = self.get_filtered_queryset(Comic)
93
+ comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
94
+ comic_qs = self.add_order_by(comic_qs, Comic)
95
+ comic_qs = comic_qs.only("pk")
96
+ group_by = self.get_group_by(Comic)
97
+ comic_qs.group_by(group_by)
98
+ comic = comic_qs.first()
99
+ cover_pk = comic.pk if comic else 0
100
+ return cover_pk, False
101
+
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
+ def _get_cover_pk(self) -> tuple[int, bool]:
129
+ """Get Cover Pk queryset for comic queryset."""
130
+ if self.model == Comic:
131
+ cover_pk, custom = self._get_comic_cover()
132
+ elif self._use_dynamic_cover():
133
+ cover_pk, custom = self._get_dynamic_cover()
134
+ else:
135
+ cover_pk, custom = self._get_first_cover()
136
+ return cover_pk, custom
137
+
138
+ def _get_missing_cover_path(self):
139
+ """Get the missing cover, which is a default svg if fetched for a group."""
140
+ group: str = self.kwargs["group"]
141
+ cover_name = MISSING_COVER_NAME_MAP.get(group)
142
+ if cover_name:
143
+ cover_fn = cover_name + ".svg"
144
+ content_type = "image/svg+xml"
145
+ else:
146
+ cover_fn = MISSING_COVER_FN
147
+ content_type = "image/webp"
148
+ cover_path = STATIC_IMG_PATH / cover_fn
149
+ return cover_path, content_type
150
+
151
+ def _get_cover_data(self, pk, custom):
152
+ thumb_image_data = None
153
+ content_type = "image/webp"
154
+
155
+ cover_path = CoverPathMixin.get_cover_path(pk, custom)
156
+ if not cover_path.exists():
157
+ thumb_image_data = CoverCreateMixin.create_cover_from_path(
158
+ pk, cover_path, LOG, LIBRARIAN_QUEUE, custom
159
+ )
160
+ if not thumb_image_data:
161
+ cover_path, content_type = self._get_missing_cover_path()
162
+ elif cover_path.stat().st_size == 0:
163
+ cover_path, content_type = self._get_missing_cover_path()
164
+
165
+ if not thumb_image_data:
166
+ # if not thumb_image_data:
167
+ with cover_path.open("rb") as f:
168
+ thumb_image_data = f.read()
169
+ return thumb_image_data, content_type
170
+
171
+ @extend_schema(responses={(200, content_type): OpenApiTypes.BINARY})
172
+ def get(self, *args, **kwargs): # type: ignore
173
+ """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)
@@ -1,34 +1,79 @@
1
1
  """Annotations used by a filter."""
2
2
 
3
- from django.db.models.aggregates import Count
4
- from django.db.models.expressions import Value
5
- from django.db.models.fields import PositiveSmallIntegerField
3
+ from django.db.models.aggregates import Count, Max
4
+ from django.db.models.functions import Coalesce, Greatest
6
5
 
7
6
  from codex.models.comic import Comic
7
+ from codex.views.browser.filters.bookmark import BookmarkFilterMixin
8
8
  from codex.views.browser.validate import BrowserValidateView
9
+ from codex.views.const import (
10
+ EPOCH_START_DATETIMEFILED,
11
+ ONE_INTEGERFIELD,
12
+ )
9
13
 
10
14
 
11
- class BrowserAnnotationsFilterView(BrowserValidateView):
15
+ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
12
16
  """Annotations that also filter."""
13
17
 
14
- ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
18
+ def __init__(self, *args, **kwargs) -> None:
19
+ """Initialize."""
20
+ super().__init__(*args, **kwargs)
21
+ self.init_bookmark_data()
15
22
 
16
- def _annotate_child_count(self, qs, model):
17
- """Annotate Child Count."""
18
- if model == Comic:
19
- child_count_sum = self.ONE_INTEGERFIELD
20
- else:
21
- child_count_sum = Count(self.rel_prefix + "pk", distinct=True)
23
+ def _get_query_filters(self, model, group, pks, bookmark_filter):
24
+ """Return all the filters except the group filter."""
25
+ object_filter = self.get_group_acl_filter(model)
26
+ object_filter &= self.get_group_filter(model, group, pks)
27
+ object_filter &= self.get_comic_field_filter(model)
28
+ if bookmark_filter:
29
+ # not needed because OuterRef is applied next
30
+ object_filter &= self.get_bookmark_filter(model)
31
+ return object_filter
32
+
33
+ def _filter_by_child_count(self, qs, model):
34
+ """Filter group by child count."""
35
+ rel = self.rel_prefix + "pk"
36
+ ann_name = "child_count"
37
+ count_func = ONE_INTEGERFIELD if model == Comic else Count(rel, distinct=True)
38
+ ann = {ann_name: count_func}
22
39
  if self.TARGET == "opds2":
23
40
  if model != Comic:
24
- qs = qs.alias(child_count=child_count_sum)
41
+ qs = qs.alias(**ann)
25
42
  else:
26
- qs = qs.annotate(child_count=child_count_sum)
43
+ qs = qs.annotate(**ann)
27
44
  if model != Comic:
28
- qs = qs.filter(child_count__gt=0)
45
+ qs = qs.filter(**{f"{ann_name}__gt": 0})
29
46
  return qs
30
47
 
31
- def filter_by_annotations(self, qs, model, binary=False):
32
- """Filter queryset by annotations."""
33
- qs = self._annotate_child_count(qs, model)
34
- return self.apply_search_filter(qs, model, binary=binary)
48
+ def get_filtered_queryset(self, model, group=None, pks=None, bookmark_filter=True):
49
+ """Get a filtered queryset for the model."""
50
+ object_filter = self._get_query_filters(model, group, pks, bookmark_filter)
51
+ qs = model.objects.filter(object_filter)
52
+ qs = self.apply_search_filter(qs, model)
53
+ return self._filter_by_child_count(qs, model)
54
+
55
+ def get_group_mtime(self, model, group=None, pks=None):
56
+ """Get a filtered mtime for browser pages and mtime checker."""
57
+ qs = self.get_filtered_queryset(
58
+ model, group=group, pks=pks, bookmark_filter=self.is_bookmark_filtered
59
+ )
60
+ qs = qs.annotate(max_updated_at=Max("updated_at"))
61
+
62
+ if self.is_bookmark_filtered:
63
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
64
+ qs = qs.filter(bm_filter)
65
+ ua_rel = bm_rel + "__updated_at"
66
+ mbua = Max(ua_rel)
67
+ else:
68
+ mbua = EPOCH_START_DATETIMEFILED
69
+ qs = qs.annotate(max_bookmark_updated_at=mbua)
70
+
71
+ mtime = qs.aggregate(
72
+ max=Greatest(
73
+ Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFILED),
74
+ "max_updated_at",
75
+ )
76
+ )["max"]
77
+ if mtime == NotImplemented:
78
+ mtime = None
79
+ return mtime
@@ -1,17 +1,26 @@
1
1
  """Bookmark filter view methods."""
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.db.models import Q
4
6
 
7
+ if TYPE_CHECKING:
8
+ from codex.models import BrowserGroupModel
9
+
5
10
 
6
11
  class BookmarkFilterMixin:
7
12
  """BookmarkFilter view methods."""
8
13
 
9
- def get_bm_rel(self, model):
14
+ def init_bookmark_data(self):
15
+ """Initialize the bm_annotation_data."""
16
+ self._bm_annotation_data: dict[BrowserGroupModel, tuple[str, Q]] = {}
17
+
18
+ def _get_bm_rel(self, model):
10
19
  """Create bookmark relation."""
11
20
  rel_prefix = self.get_rel_prefix(model) # type: ignore
12
21
  return rel_prefix + "bookmark"
13
22
 
14
- def get_my_bookmark_filter(self, bm_rel):
23
+ def _get_my_bookmark_filter(self, bm_rel):
15
24
  """Get a filter for my session or user defined bookmarks."""
16
25
  if self.request.user.is_authenticated: # type: ignore
17
26
  my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
@@ -22,12 +31,19 @@ class BookmarkFilterMixin:
22
31
  }
23
32
  return Q(**my_bookmarks_kwargs)
24
33
 
34
+ def get_bookmark_rel_and_filter(self, model):
35
+ """Get the bookmark rel and filter once."""
36
+ if model not in self._bm_annotation_data:
37
+ bm_rel = self._get_bm_rel(model)
38
+ bm_filter = self._get_my_bookmark_filter(bm_rel)
39
+ self._bm_annotation_data[model] = (bm_rel, bm_filter)
40
+ return self._bm_annotation_data[model]
41
+
25
42
  def get_bookmark_filter(self, model):
26
43
  """Build bookmark query."""
27
- choice = self.params["filters"].get("bookmark", "") # type: ignore
44
+ choice: str = self.params.get("filters", {}).get("bookmark", "") # type: ignore
28
45
  if choice:
29
- bm_rel = self.get_bm_rel(model)
30
- my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
46
+ bm_rel, my_bookmark_filter = self.get_bookmark_rel_and_filter(model)
31
47
  if choice == "READ":
32
48
  bookmark_filter = my_bookmark_filter & Q(
33
49
  **{f"{bm_rel}__finished": True}
@@ -5,7 +5,6 @@ from types import MappingProxyType
5
5
  from django.db.models import Q
6
6
 
7
7
  from codex.models.comic import Comic
8
- from codex.views.browser.filters.bookmark import BookmarkFilterMixin
9
8
  from codex.views.browser.filters.group import GroupFilterView
10
9
 
11
10
  _FILTER_REL_MAP = MappingProxyType(
@@ -17,7 +16,7 @@ _FILTER_REL_MAP = MappingProxyType(
17
16
  )
18
17
 
19
18
 
20
- class ComicFieldFilterView(GroupFilterView, BookmarkFilterMixin):
19
+ class ComicFieldFilterView(GroupFilterView):
21
20
  """Comic field filters."""
22
21
 
23
22
  def _filter_by_comic_field(self, field, rel_prefix):
@@ -2,7 +2,8 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.views.const import FOLDER_GROUP, GROUP_RELATION
5
+ from codex.models import Comic
6
+ from codex.views.const import FOLDER_GROUP, GROUP_MODEL_MAP, GROUP_RELATION
6
7
  from codex.views.session import SessionView
7
8
 
8
9
 
@@ -11,18 +12,25 @@ class GroupFilterView(SessionView):
11
12
 
12
13
  SESSION_KEY = SessionView.BROWSER_SESSION_KEY
13
14
 
14
- def get_group_filter(self, choices):
15
+ def get_group_filter(self, model, group=None, pks=None):
15
16
  """Get filter for the displayed group."""
16
- group = self.kwargs["group"] # type: ignore
17
- pks = self.kwargs["pks"] # type: ignore
18
- if pks: # type: ignore
19
- group_relation = "comic__" if choices else ""
20
- if choices and group == FOLDER_GROUP:
21
- group_relation += "folders"
17
+ if group is None:
18
+ group = self.kwargs["group"]
19
+ if pks is None:
20
+ pks = self.kwargs["pks"]
21
+
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"
22
29
  else:
23
- group_relation += GROUP_RELATION[group]
24
- group_relation += "__in"
25
- group_filter_dict = {group_relation: pks} # type: ignore
30
+ rel = GROUP_RELATION[group]
31
+
32
+ rel += "__in"
33
+ group_filter_dict = {rel: pks} # type: ignore
26
34
  elif group == FOLDER_GROUP:
27
35
  group_filter_dict = {"parent_folder": None}
28
36
  else:
@@ -30,6 +30,8 @@ class SearchScorePks:
30
30
  class SearchFilterView(ComicFieldFilterView):
31
31
  """Search Filters Methods."""
32
32
 
33
+ TARGET = ""
34
+
33
35
  def _is_search_results_limited(self) -> bool:
34
36
  """Get search result limit from params."""
35
37
  # user = self.request.user # type: ignore
@@ -115,7 +117,7 @@ class SearchFilterView(ComicFieldFilterView):
115
117
 
116
118
  return tuple(scores_pairs), tuple(prev_pks), tuple(next_pks), tuple(scored_pks)
117
119
 
118
- def _get_search_scores(self, binary=False):
120
+ def _get_search_scores(self, model, qs):
119
121
  """Perform the search and return the scores as a dict."""
120
122
  text = self.params.get("q", "") # type: ignore
121
123
  if not text:
@@ -128,13 +130,21 @@ class SearchFilterView(ComicFieldFilterView):
128
130
  try:
129
131
  sqs = CodexSearchQuerySet()
130
132
  sqs = sqs.auto_query(text) # .filter(score__gt=0.0)
131
- binary = binary or self.params.get("order_by") != "search_score" # type: ignore
132
- if binary:
133
- scored_pks = self._get_binary_search_scores(sqs)
134
- else:
135
- score_pairs, prev_pks, next_pks, scored_pks = (
136
- self._get_browser_search_scores(sqs)
133
+ prefix = "" if model == Comic else self.rel_prefix # type: ignore
134
+ comic_ids = qs.values_list(prefix + "pk", flat=True)
135
+ if comic_ids:
136
+ # Prefilter comic ids. If nothing is allowed, don't search.
137
+ sqs = sqs.filter_comic_ids(comic_ids)
138
+ binary = (
139
+ self.TARGET in frozenset({"cover", "choices"})
140
+ or self.params.get("order_by", "") != "search_score"
137
141
  )
142
+ if binary:
143
+ scored_pks = self._get_binary_search_scores(sqs)
144
+ else:
145
+ score_pairs, prev_pks, next_pks, scored_pks = (
146
+ self._get_browser_search_scores(sqs)
147
+ )
138
148
  except MemoryError:
139
149
  LOG.warning("Search engine needs more memory, results truncated.")
140
150
  except Exception:
@@ -171,17 +181,16 @@ class SearchFilterView(ComicFieldFilterView):
171
181
  rel = prefix + "pk__in"
172
182
  return {rel: scored_pks}
173
183
 
174
- def apply_search_filter(self, qs, model, binary=False):
184
+ def apply_search_filter(self, qs, model):
175
185
  """Preparse search, search and return the filter and scores."""
176
186
  try:
177
187
  score_pairs, prev_pks, next_pks, scored_pks = self._get_search_scores(
178
- binary
188
+ model, qs
179
189
  )
180
190
  qs = self.annotate_search_score(qs, model, score_pairs, prev_pks, next_pks)
181
191
  if score_pairs or scored_pks:
182
192
  search_filter = self._get_search_query_filter(model, scored_pks)
183
193
  qs = qs.filter(**search_filter)
184
- self.cover_search_score_pairs = score_pairs
185
194
  except Exception:
186
195
  LOG.exception("Creating the search filter")
187
196