codex 1.6.0a6__py3-none-any.whl → 1.6.0a8__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 (612) hide show
  1. codex/exceptions.py +1 -0
  2. codex/integrity.py +4 -4
  3. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +56 -86
  4. codex/librarian/importer/cache.py +180 -0
  5. codex/librarian/importer/const.py +15 -6
  6. codex/librarian/importer/create_comics.py +102 -17
  7. codex/librarian/importer/create_covers.py +98 -0
  8. codex/librarian/importer/create_fks.py +73 -170
  9. codex/librarian/importer/deleted.py +59 -46
  10. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  11. codex/librarian/importer/failed_imports.py +41 -62
  12. codex/librarian/importer/importer.py +96 -0
  13. codex/librarian/importer/importerd.py +71 -395
  14. codex/librarian/importer/init.py +265 -0
  15. codex/librarian/importer/link_comics.py +26 -81
  16. codex/librarian/importer/link_covers.py +73 -0
  17. codex/librarian/importer/moved.py +72 -96
  18. codex/librarian/importer/query_covers.py +52 -0
  19. codex/librarian/importer/query_fks.py +128 -172
  20. codex/librarian/importer/status.py +2 -39
  21. codex/librarian/importer/tasks.py +22 -13
  22. codex/librarian/janitor/cleanup.py +2 -2
  23. codex/librarian/janitor/janitor.py +11 -1
  24. codex/librarian/librariand.py +8 -2
  25. codex/migrations/0027_import_order_and_covers.py +345 -0
  26. codex/models/comic.py +1 -2
  27. codex/models/groups.py +3 -2
  28. codex/models/paths.py +0 -1
  29. codex/permissions.py +1 -1
  30. codex/search/query.py +25 -2
  31. codex/search/writing.py +1 -1
  32. codex/serializers/admin.py +1 -2
  33. codex/serializers/browser/filters.py +8 -101
  34. codex/serializers/browser/mixins.py +2 -3
  35. codex/serializers/browser/page.py +2 -2
  36. codex/serializers/browser/settings.py +4 -3
  37. codex/serializers/choices.py +10 -1
  38. codex/serializers/fields.py +123 -0
  39. codex/serializers/mtime.py +25 -0
  40. codex/serializers/opds/v2.py +7 -11
  41. codex/serializers/opds/v2_unused.py +5 -3
  42. codex/serializers/reader.py +5 -2
  43. codex/serializers/route.py +7 -3
  44. codex/settings/settings.py +2 -2
  45. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +1 -0
  46. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  47. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  48. codex/static_root/assets/VCheckbox-DI-DgMb4.js +1 -0
  49. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  50. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  51. codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js → VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js} +1 -1
  52. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  53. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  54. codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.js → VCheckboxBtn-B8Z4NA-t.js} +1 -1
  55. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  56. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  57. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +1 -0
  58. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  59. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  60. codex/static_root/assets/VCombobox-D1_0lCri.js +1 -0
  61. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  62. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  63. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js +1 -0
  64. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  65. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  66. codex/static_root/assets/VDialog-opmV0YZz.js +1 -0
  67. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  68. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  69. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +1 -0
  70. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  71. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  72. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +1 -0
  73. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  74. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  75. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js +1 -0
  76. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  77. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  78. codex/static_root/assets/VRadioGroup-BGa6_q5a.js +1 -0
  79. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  80. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  81. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  82. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  83. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  84. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  85. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  86. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  87. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +1 -0
  88. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  89. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  90. codex/static_root/assets/VSelect-sIYW_-ud.js +1 -0
  91. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  92. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  93. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js +1 -0
  94. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  95. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  96. codex/static_root/assets/VSelectionControl-C8DP9rgp.js +1 -0
  97. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  98. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  99. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +1 -0
  100. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  101. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  102. codex/static_root/assets/VSlideGroup-BRYhkLH8.js +1 -0
  103. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  104. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  105. codex/static_root/assets/{VTable-C0gAriFq.fa6e4b60d4ef.js → VTable-D1esKtlw.52165930e3b9.js} +1 -1
  106. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  107. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  108. codex/static_root/assets/{VTable-C0gAriFq.js → VTable-D1esKtlw.js} +1 -1
  109. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  110. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  111. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +1 -0
  112. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  113. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  114. codex/static_root/assets/VTextField-BnGscKcY.js +1 -0
  115. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  116. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  117. codex/static_root/assets/{VWindowItem-CFhKCj49.b2458002a937.js → VWindowItem-B0KokcAk.ea94224aecb9.js} +1 -1
  118. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  119. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  120. codex/static_root/assets/{VWindowItem-CFhKCj49.js → VWindowItem-B0KokcAk.js} +1 -1
  121. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  122. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  123. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +1 -0
  124. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  125. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  126. codex/static_root/assets/admin-Y0bL21AB.js +1 -0
  127. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  128. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  129. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +30 -0
  130. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  131. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  132. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +30 -0
  133. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  134. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  135. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
  136. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  137. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
  138. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
  139. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  140. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
  141. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +1 -0
  142. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  143. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  144. codex/static_root/assets/browser-CMAtBCLx.css +1 -0
  145. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  146. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  147. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +1 -0
  148. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  149. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  150. codex/static_root/assets/browser-DoJk8uvb.js +1 -0
  151. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  152. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  153. codex/static_root/assets/{change-password-dialog-JYM9aAR9.js → change-password-dialog-CH4xbjJa.b749aa6ea07e.js} +1 -1
  154. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  155. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  156. codex/static_root/assets/{change-password-dialog-JYM9aAR9.f71d378f4fa6.js → change-password-dialog-CH4xbjJa.js} +1 -1
  157. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  158. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  159. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.515858dd21e8.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
  160. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
  161. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  162. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.js → confirm-dialog-DkU0twE6.js} +1 -1
  163. codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
  164. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  165. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
  166. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  167. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
  168. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  169. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +1 -0
  170. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  171. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  172. codex/static_root/assets/datetime-column-BfW_j_3Q.js +1 -0
  173. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  174. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  175. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  176. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  177. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +1 -0
  178. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  179. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  180. codex/static_root/assets/filter-DjxN78cK.js +1 -0
  181. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  182. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  183. codex/static_root/assets/{flag-tab-Ci_XcDT0.a5ea640a08d6.js → flag-tab-D4ZNSbMR.e763a9937701.js} +1 -1
  184. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  185. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  186. codex/static_root/assets/{flag-tab-Ci_XcDT0.js → flag-tab-D4ZNSbMR.js} +1 -1
  187. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  188. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  189. codex/static_root/assets/{group-tab-DFma2R-E.879c37b15bbd.js → group-tab-DucpNoBs.a6f37ae940c0.js} +1 -1
  190. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  191. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  192. codex/static_root/assets/{group-tab-DFma2R-E.js → group-tab-DucpNoBs.js} +1 -1
  193. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  194. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  195. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js +1 -0
  196. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  197. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  198. codex/static_root/assets/http-error-Cj0hTq-c.js +1 -0
  199. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  200. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  201. codex/static_root/assets/{library-tab-V8DUGoHN.js → library-tab-CzksdS3Q.566acdb45dec.js} +1 -1
  202. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  203. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  204. codex/static_root/assets/{library-tab-V8DUGoHN.809c548ff347.js → library-tab-CzksdS3Q.js} +1 -1
  205. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  206. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  207. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +32 -0
  208. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  209. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  210. codex/static_root/assets/main-DX-k2724.js +32 -0
  211. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  212. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  213. codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
  214. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  215. codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
  216. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
  217. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  218. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
  219. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +1 -0
  220. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  221. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  222. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +1 -0
  223. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  224. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  225. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  226. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  227. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  228. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  229. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  230. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  231. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js +305 -0
  232. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  233. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  234. codex/static_root/assets/pdf-doc-GkMk4o5L.js +305 -0
  235. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  236. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  237. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  238. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  239. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  240. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  241. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  242. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  243. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +2 -0
  244. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  245. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  246. codex/static_root/assets/reader-Cl6mmQB_.js +2 -0
  247. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  248. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  249. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +1 -0
  250. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  251. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  252. codex/static_root/assets/relation-chips-CZ8VvR4y.js +1 -0
  253. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  254. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  255. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +2 -0
  256. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  257. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  258. codex/static_root/assets/settings-drawer-DyS0A2pN.js +2 -0
  259. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  260. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  261. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +1 -0
  262. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  263. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  264. codex/static_root/assets/stats-tab-R3Ekobfm.js +1 -0
  265. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  266. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  267. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +1 -0
  268. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  269. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  270. codex/static_root/assets/task-tab-BhLDK_7Q.js +1 -0
  271. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  272. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  273. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +1 -0
  274. codex/static_root/assets/to-case-BR2frjFx.js +1 -0
  275. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +1 -0
  276. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  277. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  278. codex/static_root/assets/unauthorized-BbXjAe5l.js +1 -0
  279. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  280. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  281. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js +1 -0
  282. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  283. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  284. codex/static_root/assets/user-tab-DxITIl2d.js +1 -0
  285. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  286. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  287. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  288. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  289. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  290. codex/static_root/js/choices-admin.json +1 -1
  291. codex/static_root/js/choices-admin.json.br +0 -0
  292. codex/static_root/js/choices-admin.json.gz +0 -0
  293. codex/static_root/js/choices.a0aaaa6c7ef7.json +1 -0
  294. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  295. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  296. codex/static_root/js/choices.json +1 -1
  297. codex/static_root/js/choices.json.br +0 -0
  298. codex/static_root/js/choices.json.gz +0 -0
  299. codex/static_root/manifest.16d52597ef50.json +629 -0
  300. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  301. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  302. codex/static_root/manifest.json +275 -288
  303. codex/static_root/manifest.json.br +0 -0
  304. codex/static_root/manifest.json.gz +0 -0
  305. codex/static_root/staticfiles.json +1 -1
  306. codex/urls/api/auth.py +2 -1
  307. codex/urls/api/browser.py +13 -4
  308. codex/urls/api/reader.py +3 -9
  309. codex/urls/api/v3.py +2 -0
  310. codex/urls/opds/binary.py +1 -1
  311. codex/util.py +10 -0
  312. codex/views/admin/api_key.py +2 -6
  313. codex/views/admin/auth.py +28 -0
  314. codex/views/admin/flag.py +2 -7
  315. codex/views/admin/group.py +2 -6
  316. codex/views/admin/library.py +7 -20
  317. codex/views/admin/stats.py +7 -66
  318. codex/views/admin/tasks.py +24 -11
  319. codex/views/admin/user.py +3 -9
  320. codex/views/auth.py +30 -53
  321. codex/views/bookmark.py +7 -11
  322. codex/views/browser/{browser_annotations.py → annotations.py} +90 -181
  323. codex/views/browser/base.py +23 -88
  324. codex/views/browser/breadcrumbs.py +202 -0
  325. codex/views/browser/browser.py +64 -342
  326. codex/views/browser/choices.py +22 -54
  327. codex/views/browser/cover.py +177 -0
  328. codex/views/browser/filters/annotations.py +24 -23
  329. codex/views/browser/filters/bookmark.py +9 -13
  330. codex/views/browser/filters/field.py +5 -5
  331. codex/views/browser/filters/group.py +18 -10
  332. codex/views/browser/filters/search.py +30 -18
  333. codex/views/browser/metadata.py +22 -31
  334. codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
  335. codex/views/browser/paginate.py +106 -0
  336. codex/views/browser/{session.py → settings.py} +4 -3
  337. codex/views/browser/title.py +48 -0
  338. codex/views/browser/validate.py +158 -0
  339. codex/views/const.py +109 -0
  340. codex/views/download.py +5 -8
  341. codex/views/frontend.py +4 -2
  342. codex/views/mtime.py +72 -0
  343. codex/views/opds/auth.py +17 -0
  344. codex/views/opds/authentication_v1.py +46 -45
  345. codex/views/opds/binary.py +5 -10
  346. codex/views/opds/const.py +13 -0
  347. codex/views/opds/urls.py +6 -4
  348. codex/views/opds/util.py +4 -12
  349. codex/views/opds/v1/entry/links.py +18 -33
  350. codex/views/opds/v1/facets.py +7 -7
  351. codex/views/opds/v1/feed.py +52 -40
  352. codex/views/opds/v1/links.py +7 -5
  353. codex/views/opds/v1/opensearch_v1.py +2 -6
  354. codex/views/opds/v2/feed.py +22 -23
  355. codex/views/opds/v2/links.py +4 -2
  356. codex/views/opds/v2/publications.py +34 -54
  357. codex/views/opds/v2/top_links.py +2 -2
  358. codex/views/public.py +36 -0
  359. codex/views/reader/arcs.py +101 -0
  360. codex/views/reader/books.py +199 -0
  361. codex/views/reader/page.py +3 -7
  362. codex/views/reader/reader.py +56 -301
  363. codex/views/reader/{session.py → settings.py} +4 -2
  364. codex/views/session.py +77 -103
  365. codex/views/settings.py +34 -0
  366. codex/views/template.py +3 -3
  367. codex/views/util.py +63 -0
  368. codex/views/version.py +2 -2
  369. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/METADATA +19 -14
  370. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/RECORD +373 -362
  371. codex/librarian/importer/covers.py +0 -47
  372. codex/librarian/importer/update_comics.py +0 -85
  373. codex/migrations/0027_sort_name.py +0 -139
  374. codex/migrations/0028_custom_covers.py +0 -166
  375. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  376. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js +0 -1
  377. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
  378. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
  379. codex/static_root/assets/VCheckbox-BS5fdM4M.js +0 -1
  380. codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
  381. codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
  382. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
  383. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
  384. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
  385. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
  386. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +0 -1
  387. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
  388. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
  389. codex/static_root/assets/VCombobox-jg7SI5mn.js +0 -1
  390. codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
  391. codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
  392. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js +0 -1
  393. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
  394. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
  395. codex/static_root/assets/VDialog-DiixpkQL.js +0 -1
  396. codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
  397. codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
  398. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js +0 -1
  399. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
  400. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
  401. codex/static_root/assets/VExpansionPanels-Db5mPFue.js +0 -1
  402. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
  403. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
  404. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js +0 -1
  405. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
  406. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
  407. codex/static_root/assets/VRadioGroup-DULWiMSI.js +0 -1
  408. codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
  409. codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
  410. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +0 -1
  411. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
  412. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
  413. codex/static_root/assets/VSelect-BnoCfCLY.js +0 -1
  414. codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
  415. codex/static_root/assets/VSelect-BnoCfCLY.js.gz +0 -0
  416. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  417. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  418. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  419. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  420. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  421. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  422. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js +0 -1
  423. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
  424. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
  425. codex/static_root/assets/VSelectionControl-BSL0qeL9.js +0 -1
  426. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
  427. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
  428. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js +0 -1
  429. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
  430. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
  431. codex/static_root/assets/VSlideGroup-BQtz8h9p.js +0 -1
  432. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
  433. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
  434. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
  435. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
  436. codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
  437. codex/static_root/assets/VTable-C0gAriFq.js.gz +0 -0
  438. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js +0 -1
  439. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
  440. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
  441. codex/static_root/assets/VTextField-Da_Hs7ls.js +0 -1
  442. codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
  443. codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
  444. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
  445. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
  446. codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
  447. codex/static_root/assets/VWindowItem-CFhKCj49.js.gz +0 -0
  448. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js +0 -1
  449. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
  450. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
  451. codex/static_root/assets/admin-BpGFsvOH.js +0 -1
  452. codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
  453. codex/static_root/assets/admin-BpGFsvOH.js.gz +0 -0
  454. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
  455. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
  456. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +0 -30
  457. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
  458. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
  459. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +0 -30
  460. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
  461. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
  462. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +0 -1
  463. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
  464. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
  465. codex/static_root/assets/browser-CRPvEdqv.js +0 -1
  466. codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
  467. codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
  468. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +0 -1
  469. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
  470. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
  471. codex/static_root/assets/browser-Zqc9cR6T.css +0 -1
  472. codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
  473. codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
  474. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
  475. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
  476. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
  477. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
  478. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
  479. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
  480. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
  481. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
  482. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js +0 -1
  483. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
  484. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
  485. codex/static_root/assets/datetime-column-BT1MSP82.js +0 -1
  486. codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
  487. codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
  488. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  489. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  490. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  491. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  492. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js +0 -1
  493. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
  494. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
  495. codex/static_root/assets/filter-frSNsexx.js +0 -1
  496. codex/static_root/assets/filter-frSNsexx.js.br +0 -0
  497. codex/static_root/assets/filter-frSNsexx.js.gz +0 -0
  498. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
  499. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
  500. codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
  501. codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
  502. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
  503. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
  504. codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
  505. codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
  506. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js +0 -1
  507. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
  508. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
  509. codex/static_root/assets/http-error-B3fQGxpt.js +0 -1
  510. codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
  511. codex/static_root/assets/http-error-B3fQGxpt.js.gz +0 -0
  512. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
  513. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  514. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  515. codex/static_root/assets/index-DUjeNg4x.js +0 -1
  516. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  517. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  518. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
  519. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
  520. codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
  521. codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
  522. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js +0 -32
  523. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
  524. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
  525. codex/static_root/assets/main-Cavvqkyp.js +0 -32
  526. codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
  527. codex/static_root/assets/main-Cavvqkyp.js.gz +0 -0
  528. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  529. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  530. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css +0 -1
  531. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
  532. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
  533. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css +0 -1
  534. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
  535. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
  536. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js +0 -1
  537. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
  538. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
  539. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +0 -1
  540. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
  541. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
  542. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js +0 -35
  543. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
  544. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
  545. codex/static_root/assets/pdf-doc-4PKLN-Z3.js +0 -35
  546. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
  547. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
  548. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js +0 -2
  549. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
  550. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
  551. codex/static_root/assets/reader-C39uGGxn.js +0 -2
  552. codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
  553. codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
  554. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css +0 -1
  555. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
  556. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
  557. codex/static_root/assets/reader-N2Ei_y2K.css +0 -1
  558. codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
  559. codex/static_root/assets/reader-N2Ei_y2K.css.gz +0 -0
  560. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js +0 -1
  561. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
  562. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
  563. codex/static_root/assets/relation-chips-CAC5ytqH.js +0 -1
  564. codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
  565. codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
  566. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js +0 -2
  567. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
  568. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
  569. codex/static_root/assets/settings-drawer-CT73ieiV.js +0 -2
  570. codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
  571. codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
  572. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js +0 -1
  573. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
  574. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
  575. codex/static_root/assets/stats-tab-C6qAnV6-.js +0 -1
  576. codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
  577. codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
  578. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +0 -1
  579. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
  580. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
  581. codex/static_root/assets/task-tab-JcG2vvgt.js +0 -1
  582. codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
  583. codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
  584. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
  585. codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
  586. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js +0 -1
  587. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
  588. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
  589. codex/static_root/assets/unauthorized-28bmiwAU.js +0 -1
  590. codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
  591. codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
  592. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js +0 -1
  593. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
  594. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
  595. codex/static_root/assets/user-tab-BOq4BMMc.js +0 -1
  596. codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
  597. codex/static_root/assets/user-tab-BOq4BMMc.js.gz +0 -0
  598. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  599. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  600. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  601. codex/static_root/js/choices.23fedac2b9ba.json +0 -1
  602. codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
  603. codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
  604. codex/static_root/manifest.ad9da9714e46.json +0 -642
  605. codex/static_root/manifest.ad9da9714e46.json.br +0 -0
  606. codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
  607. codex/views/browser/browser_breadcrumbs.py +0 -175
  608. codex/views/browser/const.py +0 -3
  609. codex/views/cover.py +0 -78
  610. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
  611. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
  612. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
@@ -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,35 @@
1
1
  """Annotations used by a filter."""
2
2
 
3
3
  from django.db.models.aggregates import Count
4
- from django.db.models.expressions import Value
5
- from django.db.models.fields import PositiveSmallIntegerField
6
4
 
7
5
  from codex.models.comic import Comic
8
- from codex.views.browser.base import BrowserBaseView
6
+ from codex.views.browser.filters.bookmark import BookmarkFilterMixin
7
+ from codex.views.browser.validate import BrowserValidateView
9
8
 
10
9
 
11
- class BrowserAnnotationsFilterView(BrowserBaseView):
10
+ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
12
11
  """Annotations that also filter."""
13
12
 
14
- ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
13
+ def get_query_filters(self, model):
14
+ """Return all the filters except the group filter."""
15
+ object_filter = self.get_group_acl_filter(model)
16
+ object_filter &= self.get_bookmark_filter(model)
17
+ object_filter &= self.get_comic_field_filter(model)
18
+ object_filter &= self.get_group_filter(model)
19
+ return object_filter
15
20
 
16
- def _annotate_child_count(self, qs, model):
17
- """Annotate Child Count."""
21
+ def _filter_by_child_count(self, qs, model):
18
22
  if model == Comic:
19
- child_count_sum = self.ONE_INTEGERFIELD
20
- else:
21
- child_count_sum = Count(self.rel_prefix + "pk", distinct=True)
22
- if self.TARGET == "opds2":
23
- if model != Comic:
24
- qs = qs.alias(child_count=child_count_sum)
25
- else:
26
- qs = qs.annotate(child_count=child_count_sum)
27
- if model != Comic:
28
- qs = qs.filter(child_count__gt=0)
29
- return qs
30
-
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)
23
+ return qs
24
+
25
+ rel = self.rel_prefix + "pk"
26
+ qs = qs.alias(pre_search_child_count=Count(rel, distinct=True))
27
+ return qs.filter(pre_search_child_count__gt=0)
28
+
29
+ def get_filtered_queryset(self, model):
30
+ """Get a filtered queryset for the model."""
31
+ object_filter = self.get_query_filters(model)
32
+
33
+ qs = model.objects.filter(object_filter)
34
+ qs = self._filter_by_child_count(qs, model)
35
+ return self.apply_search_filter(qs, model)
@@ -2,20 +2,16 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.serializers.choices import CHOICES
6
-
7
5
 
8
6
  class BookmarkFilterMixin:
9
7
  """BookmarkFilter view methods."""
10
8
 
11
- _BOOKMARK_FILTERS = frozenset(set(CHOICES["bookmarkFilter"].keys()) - {"ALL"})
12
-
13
9
  def get_bm_rel(self, model):
14
10
  """Create bookmark relation."""
15
11
  rel_prefix = self.get_rel_prefix(model) # type: ignore
16
12
  return rel_prefix + "bookmark"
17
13
 
18
- def _get_my_bookmark_filter(self, bm_rel):
14
+ def get_my_bookmark_filter(self, bm_rel):
19
15
  """Get a filter for my session or user defined bookmarks."""
20
16
  if self.request.user.is_authenticated: # type: ignore
21
17
  my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
@@ -28,11 +24,15 @@ class BookmarkFilterMixin:
28
24
 
29
25
  def get_bookmark_filter(self, model):
30
26
  """Build bookmark query."""
31
- choice = self.params["filters"].get("bookmark", "ALL") # type: ignore
32
- if choice in self._BOOKMARK_FILTERS:
27
+ choice = self.params["filters"].get("bookmark", "") # type: ignore
28
+ if choice:
33
29
  bm_rel = self.get_bm_rel(model)
34
- my_bookmark_filter = self._get_my_bookmark_filter(bm_rel)
35
- if choice in ("UNREAD", "IN_PROGRESS"):
30
+ my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
31
+ if choice == "READ":
32
+ bookmark_filter = my_bookmark_filter & Q(
33
+ **{f"{bm_rel}__finished": True}
34
+ )
35
+ else:
36
36
  my_not_finished_filter = my_bookmark_filter & Q(
37
37
  **{f"{bm_rel}__finished__in": (False, None)}
38
38
  )
@@ -42,10 +42,6 @@ class BookmarkFilterMixin:
42
42
  bookmark_filter = my_not_finished_filter & Q(
43
43
  **{f"{bm_rel}__page__gt": 0}
44
44
  )
45
- else: # READ
46
- bookmark_filter = my_bookmark_filter & Q(
47
- **{f"{bm_rel}__finished": True}
48
- )
49
45
  else:
50
46
  bookmark_filter = Q()
51
47
  return bookmark_filter
@@ -5,18 +5,18 @@ 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.session import BrowserSessionViewBase
8
+ from codex.views.browser.filters.group import GroupFilterView
9
9
 
10
10
  _FILTER_REL_MAP = MappingProxyType(
11
11
  {
12
- BrowserSessionViewBase.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
13
- BrowserSessionViewBase.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
14
- BrowserSessionViewBase.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
12
+ GroupFilterView.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
13
+ GroupFilterView.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
14
+ GroupFilterView.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
15
15
  }
16
16
  )
17
17
 
18
18
 
19
- class ComicFieldFilter(BrowserSessionViewBase):
19
+ class ComicFieldFilterView(GroupFilterView):
20
20
  """Comic field filters."""
21
21
 
22
22
  def _filter_by_comic_field(self, field, rel_prefix):
@@ -2,25 +2,33 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.views.auth import GroupACLMixin
5
+ from codex.models import Comic
6
+ from codex.views.const import FOLDER_GROUP, GROUP_MODEL_MAP, GROUP_RELATION
7
+ from codex.views.session import SessionView
6
8
 
7
9
 
8
- class GroupFilterMixin(GroupACLMixin):
10
+ class GroupFilterView(SessionView):
9
11
  """Group Filters."""
10
12
 
11
- def get_group_filter(self, choices):
13
+ SESSION_KEY = SessionView.BROWSER_SESSION_KEY
14
+
15
+ def get_group_filter(self, model):
12
16
  """Get filter for the displayed group."""
13
17
  group = self.kwargs["group"] # type: ignore
14
18
  pks = self.kwargs["pks"] # type: ignore
15
19
  if pks: # type: ignore
16
- group_relation = "comic__" if choices else ""
17
- if choices and group == self.FOLDER_GROUP:
18
- group_relation += "folders"
20
+ if model == GROUP_MODEL_MAP.get(group):
21
+ # metadata only
22
+ rel = "pk"
23
+ elif model == Comic and group == FOLDER_GROUP:
24
+ # choices & covers
25
+ rel = "folders"
19
26
  else:
20
- group_relation += self.GROUP_RELATION[group]
21
- group_relation += "__in"
22
- group_filter_dict = {group_relation: pks} # type: ignore
23
- elif group == self.FOLDER_GROUP:
27
+ rel = GROUP_RELATION[group]
28
+
29
+ rel += "__in"
30
+ group_filter_dict = {rel: pks} # type: ignore
31
+ elif group == FOLDER_GROUP:
24
32
  group_filter_dict = {"parent_folder": None}
25
33
  else:
26
34
  group_filter_dict = {}
@@ -9,12 +9,13 @@ from codex.logger.logging import get_logger
9
9
  from codex.models import Comic
10
10
  from codex.search.query import CodexSearchQuerySet
11
11
  from codex.settings.settings import DEBUG
12
- from codex.views.browser.const import MAX_OBJ_PER_PAGE
12
+ from codex.views.browser.filters.field import ComicFieldFilterView
13
+ from codex.views.const import MAX_OBJ_PER_PAGE
13
14
 
14
15
  LOG = get_logger(__name__)
15
- SEARCH_SCORE_MAX = 100.0
16
- SEARCH_SCORE_MIN = 0.001
17
- SEARCH_SCORE_EMPTY = 0.0
16
+ _SEARCH_SCORE_MAX = 100.0
17
+ _SEARCH_SCORE_MIN = 0.001
18
+ _SEARCH_SCORE_EMPTY = 0.0
18
19
 
19
20
 
20
21
  @dataclass
@@ -26,9 +27,11 @@ class SearchScorePks:
26
27
  next_pks: tuple[int, ...] = ()
27
28
 
28
29
 
29
- class SearchFilterMixin:
30
+ class SearchFilterView(ComicFieldFilterView):
30
31
  """Search Filters Methods."""
31
32
 
33
+ TARGET = ""
34
+
32
35
  def _is_search_results_limited(self) -> bool:
33
36
  """Get search result limit from params."""
34
37
  # user = self.request.user # type: ignore
@@ -114,7 +117,7 @@ class SearchFilterMixin:
114
117
 
115
118
  return tuple(scores_pairs), tuple(prev_pks), tuple(next_pks), tuple(scored_pks)
116
119
 
117
- def _get_search_scores(self, binary=False):
120
+ def _get_search_scores(self, model, qs):
118
121
  """Perform the search and return the scores as a dict."""
119
122
  text = self.params.get("q", "") # type: ignore
120
123
  if not text:
@@ -127,13 +130,21 @@ class SearchFilterMixin:
127
130
  try:
128
131
  sqs = CodexSearchQuerySet()
129
132
  sqs = sqs.auto_query(text) # .filter(score__gt=0.0)
130
- binary = binary or self.params.get("order_by") != "search_score" # type: ignore
131
- if binary:
132
- scored_pks = self._get_binary_search_scores(sqs)
133
- else:
134
- score_pairs, prev_pks, next_pks, scored_pks = (
135
- 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"
136
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
+ )
137
148
  except MemoryError:
138
149
  LOG.warning("Search engine needs more memory, results truncated.")
139
150
  except Exception:
@@ -154,10 +165,12 @@ class SearchFilterMixin:
154
165
  when = {prefix + "pk": pk, "then": score}
155
166
  whens.append(When(**when))
156
167
  if prev_pks:
157
- whens.append(When(pk__in=prev_pks, then=SEARCH_SCORE_MAX))
168
+ whens.append(When(pk__in=prev_pks, then=_SEARCH_SCORE_MAX))
158
169
  if next_pks:
159
- whens.append(When(pk__in=next_pks, then=SEARCH_SCORE_MIN))
160
- search_score = self.order_agg_func(Case(*whens, default=SEARCH_SCORE_EMPTY)) # type: ignore
170
+ whens.append(When(pk__in=next_pks, then=_SEARCH_SCORE_MIN))
171
+ search_score = self.order_agg_func( # type: ignore
172
+ Case(*whens, default=_SEARCH_SCORE_EMPTY)
173
+ )
161
174
  else:
162
175
  search_score = Value(0.0)
163
176
  return qs.annotate(search_score=search_score)
@@ -168,17 +181,16 @@ class SearchFilterMixin:
168
181
  rel = prefix + "pk__in"
169
182
  return {rel: scored_pks}
170
183
 
171
- def apply_search_filter(self, qs, model, binary=False):
184
+ def apply_search_filter(self, qs, model):
172
185
  """Preparse search, search and return the filter and scores."""
173
186
  try:
174
187
  score_pairs, prev_pks, next_pks, scored_pks = self._get_search_scores(
175
- binary
188
+ model, qs
176
189
  )
177
190
  qs = self.annotate_search_score(qs, model, score_pairs, prev_pks, next_pks)
178
191
  if score_pairs or scored_pks:
179
192
  search_filter = self._get_search_query_filter(model, scored_pks)
180
193
  qs = qs.filter(**search_filter)
181
- self.cover_search_score_pairs = score_pairs
182
194
  except Exception:
183
195
  LOG.exception("Creating the search filter")
184
196
 
@@ -1,7 +1,6 @@
1
1
  """View for marking comics read and unread."""
2
2
 
3
3
  from types import MappingProxyType
4
- from typing import ClassVar
5
4
 
6
5
  from django.core.exceptions import FieldDoesNotExist
7
6
  from django.db.models import Count, IntegerField, Sum, Value
@@ -9,12 +8,13 @@ from drf_spectacular.utils import extend_schema
9
8
  from rest_framework.exceptions import NotFound
10
9
  from rest_framework.response import Response
11
10
 
12
- from codex.librarian.importer.const import COMIC_M2M_FIELD_NAMES
11
+ from codex.librarian.importer.const import (
12
+ COMIC_M2M_FIELD_NAMES,
13
+ )
13
14
  from codex.logger.logging import get_logger
14
15
  from codex.models import AdminFlag, Comic
15
16
  from codex.serializers.browser.metadata import MetadataSerializer
16
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
17
- from codex.views.browser.browser_annotations import BrowserAnnotationsView
17
+ from codex.views.browser.annotations import BrowserAnnotationsView
18
18
 
19
19
  LOG = get_logger(__name__)
20
20
  _ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
@@ -37,7 +37,7 @@ _COMIC_VALUE_FIELD_NAMES = frozenset(
37
37
  )
38
38
  _COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
39
39
  _COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_count"})
40
- _PATH_GROUPS = ("c", "f")
40
+ _PATH_GROUPS = frozenset({"c", "f"})
41
41
  _CONTRIBUTOR_RELATIONS = ("role", "person")
42
42
  _SUM_FIELDS = frozenset({"page_count", "size"})
43
43
  _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
@@ -46,7 +46,6 @@ _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
46
46
  class MetadataView(BrowserAnnotationsView):
47
47
  """Comic metadata."""
48
48
 
49
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
50
49
  serializer_class = MetadataSerializer
51
50
  TARGET = "metadata"
52
51
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
@@ -55,6 +54,11 @@ class MetadataView(BrowserAnnotationsView):
55
54
  }
56
55
  )
57
56
 
57
+ def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
58
+ """Limited allowed nav groups for metadata."""
59
+ group = self.kwargs["group"]
60
+ self.valid_nav_groups = (group,)
61
+
58
62
  def _get_comic_value_fields(self):
59
63
  """Include the path field for staff."""
60
64
  fields = set(_COMIC_VALUE_FIELD_NAMES)
@@ -156,6 +160,8 @@ class MetadataView(BrowserAnnotationsView):
156
160
  if field_name in _GROUP_RELS:
157
161
  group_by = "name" if field_name == "volume" else "sort_name"
158
162
  intersection_qs = intersection_qs.group_by(group_by)
163
+ if field_name == "identifiers":
164
+ intersection_qs = intersection_qs.select_related("identifier_type")
159
165
  intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
160
166
  count=comic_pks_count
161
167
  )
@@ -188,18 +194,18 @@ class MetadataView(BrowserAnnotationsView):
188
194
  def _get_optimized_m2m_query(key, qs):
189
195
  # XXX The prefetch gets removed by field.set() :(
190
196
  if key == "contributors":
191
- optimized_qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
197
+ qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
192
198
  *_CONTRIBUTOR_RELATIONS
193
199
  )
194
200
  elif key == "story_arc_numbers":
195
- optimized_qs = qs.prefetch_related("story_arc").only("story_arc", "number")
201
+ qs = qs.prefetch_related("story_arc")
202
+ qs = qs.only("story_arc", "number")
196
203
  elif key == "identifiers":
197
- optimized_qs = qs.prefetch_related("identifier_type").only(
198
- "identifier_type", "nss", "url"
199
- )
204
+ qs = qs.prefetch_related("identifier_type")
205
+ qs = qs.only("identifier_type", "nss", "url")
200
206
  else:
201
- optimized_qs = qs.only("name")
202
- return optimized_qs
207
+ qs = qs.only("name")
208
+ return qs
203
209
 
204
210
  @classmethod
205
211
  def _copy_m2m_intersections(cls, obj, m2m_intersections):
@@ -247,27 +253,17 @@ class MetadataView(BrowserAnnotationsView):
247
253
  # values dicts don't copy relations to the serializer. The values
248
254
  # dict is necessary because of the folders view union in browser.py.
249
255
 
250
- if self.model is None:
251
- # TODO this looks redundant after set_browse_model
252
- group = self.kwargs["group"]
253
- raise NotFound(detail=f"Cannot get metadata for {group=}")
256
+ qs = self.get_filtered_queryset(self.model)
254
257
 
255
- object_filter = self.get_query_filters_without_group(self.model) # type: ignore
256
- pks = self.kwargs["pks"]
257
- qs = self.model.objects.filter(object_filter, pk__in=pks)
258
- qs = self.filter_by_annotations(qs, self.model, binary=True)
259
258
  filtered_qs = qs
260
259
  qs = self.annotate_order_aggregates(qs, self.model)
261
260
  qs = self.annotate_card_aggregates(qs, self.model)
262
- qs = self.annotate_for_metadata(qs, self.model)
261
+ qs = self._annotate_values_and_fks(qs, filtered_qs)
263
262
  group_by = self.get_group_by()
264
263
  qs = qs.group_by(group_by)
265
- qs = self._annotate_values_and_fks(qs, filtered_qs)
266
-
267
- qs_list = self.re_cover_multi_groups(qs)
268
264
 
269
265
  try:
270
- obj = qs_list[0]
266
+ obj = qs[0]
271
267
  if not obj:
272
268
  reason = "Empty obj"
273
269
  raise ValueError(reason) # noqa TRY301
@@ -277,11 +273,6 @@ class MetadataView(BrowserAnnotationsView):
277
273
  m2m_intersections = self._query_m2m_intersections(filtered_qs)
278
274
  return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
279
275
 
280
- def set_valid_browse_nav_groups(self, _valid_top_groups):
281
- """Limited allowed nav groups for metadata."""
282
- group = self.kwargs["group"]
283
- self.valid_nav_groups = (group,)
284
-
285
276
  @extend_schema(request=BrowserAnnotationsView.input_serializer_class)
286
277
  def get(self, *_args, **_kwargs):
287
278
  """Get metadata for a filtered browse group."""
@@ -6,22 +6,18 @@ from codex.models import Comic
6
6
  from codex.views.browser.filters.annotations import (
7
7
  BrowserAnnotationsFilterView,
8
8
  )
9
+ from codex.views.const import FOLDER_GROUP
9
10
 
10
11
 
11
12
  class BrowserOrderByView(BrowserAnnotationsFilterView):
12
13
  """Base class for views that need ordering."""
13
14
 
14
- def __init__(self, *args, **kwargs):
15
- """Set params for the type checker."""
16
- super().__init__(*args, **kwargs)
17
- self.order_key: str = ""
18
-
19
15
  def set_order_key(self):
20
16
  """Get the default order key for the view."""
21
17
  order_key: str = self.params.get("order_by", "") # type: ignore
22
18
  if not order_key:
23
19
  group = self.kwargs.get("group")
24
- order_key = "filename" if group == self.FOLDER_GROUP else "sort_name"
20
+ order_key = "filename" if group == FOLDER_GROUP else "sort_name"
25
21
  self.order_key: str = order_key
26
22
  order_reverse = self.params.get("order_reverse")
27
23
  self.order_agg_func = Max if order_reverse else Min