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
codex/views/auth.py CHANGED
@@ -1,25 +1,19 @@
1
1
  """Views authorization."""
2
2
 
3
- from types import MappingProxyType
4
-
5
- from django.conf import settings
6
3
  from django.contrib.auth.models import AnonymousUser
7
4
  from django.db.models import Q
8
5
  from drf_spectacular.utils import extend_schema
9
6
  from rest_framework.generics import GenericAPIView
10
- from rest_framework.mixins import RetrieveModelMixin
11
7
  from rest_framework.permissions import IsAuthenticated
12
8
  from rest_framework.response import Response
9
+ from rest_framework.views import APIView
13
10
 
14
11
  from codex.logger.logging import get_logger
15
12
  from codex.models import AdminFlag, Comic, Folder, StoryArc, UserActive
16
- from codex.serializers.auth import AuthAdminFlagsSerializer, TimezoneSerializer
17
- from codex.serializers.choices import CHOICES
13
+ from codex.serializers.auth import TimezoneSerializer
18
14
  from codex.serializers.mixins import OKSerializer
19
- from codex.views.const import GROUP_NAME_MAP
20
15
 
21
16
  LOG = get_logger(__name__)
22
- NULL_USER = {"pk": None, "username": None, "is_staff": False}
23
17
 
24
18
 
25
19
  class IsAuthenticatedOrEnabledNonUsers(IsAuthenticated):
@@ -37,20 +31,33 @@ class IsAuthenticatedOrEnabledNonUsers(IsAuthenticated):
37
31
  return super().has_permission(request, view)
38
32
 
39
33
 
40
- class TimezoneView(GenericAPIView):
34
+ class AuthMixin:
35
+ """General Auth Policy."""
36
+
37
+ permission_classes = (IsAuthenticatedOrEnabledNonUsers,)
38
+
39
+
40
+ class AuthAPIView(AuthMixin, APIView):
41
+ """Auth Policy APIView."""
42
+
43
+
44
+ class AuthGenericAPIView(AuthMixin, GenericAPIView):
45
+ """Auth Policy GenericAPIView."""
46
+
47
+
48
+ class TimezoneView(AuthGenericAPIView):
41
49
  """User info."""
42
50
 
43
51
  input_serializer_class = TimezoneSerializer
44
52
  serializer_class = OKSerializer
45
53
 
46
- _AUTH_USER_MODEL_TYPE = type(settings.AUTH_USER_MODEL)
47
-
48
54
  @extend_schema(request=input_serializer_class)
49
55
  def post(self, request, *args, **kwargs):
50
56
  """Get the user info for the current user."""
51
- serializer = self.input_serializer_class(data=self.request.data)
57
+ data = self.request.data # type: ignore
58
+ serializer = self.input_serializer_class(data=data)
52
59
  serializer.is_valid(raise_exception=True)
53
- request.session["django_timezone"] = serializer.validated_data["timezone"]
60
+ request.session["django_timezone"] = serializer.validated_data["timezone"] # type: ignore
54
61
  request.session.save()
55
62
  user = self.request.user
56
63
  if user.is_authenticated:
@@ -59,49 +66,11 @@ class TimezoneView(GenericAPIView):
59
66
  return Response(serializer.data)
60
67
 
61
68
 
62
- class AdminFlagsView(GenericAPIView, RetrieveModelMixin):
63
- """Get admin flags relevant to auth."""
64
-
65
- _ADMIN_FLAG_KEYS = frozenset(
66
- (
67
- AdminFlag.FlagChoices.NON_USERS.value,
68
- AdminFlag.FlagChoices.REGISTRATION.value,
69
- )
70
- )
71
-
72
- serializer_class = AuthAdminFlagsSerializer
73
- queryset = AdminFlag.objects.filter(key__in=_ADMIN_FLAG_KEYS).only("key", "on")
74
-
75
- def get_object(self):
76
- """Get admin flags."""
77
- flags = {}
78
- for obj in self.get_queryset(): # type: ignore
79
- name = CHOICES["admin"]["adminFlags"][obj.key].lower().replace(" ", "_")
80
- flags[name] = obj.on
81
- return flags
82
-
83
- def get(self, request, *args, **kwargs):
84
- """Get admin flags relevant to auth."""
85
- return self.retrieve(request, *args, **kwargs)
86
-
87
-
88
69
  class GroupACLMixin:
89
70
  """Filter group ACLS for views."""
90
71
 
91
- ROOT_GROUP = "r"
92
- FOLDER_GROUP = "f"
93
- STORY_ARC_GROUP = "a"
94
- COMIC_GROUP = "c"
95
- GROUP_RELATION = MappingProxyType(
96
- {
97
- **GROUP_NAME_MAP,
98
- COMIC_GROUP: "pk",
99
- FOLDER_GROUP: "parent_folder",
100
- STORY_ARC_GROUP: "story_arc_numbers__story_arc",
101
- }
102
- )
103
-
104
- def get_rel_prefix(self, model):
72
+ @staticmethod
73
+ def get_rel_prefix(model):
105
74
  """Return the relation prfiex for most fields."""
106
75
  prefix = ""
107
76
  if model != Comic:
@@ -133,3 +102,11 @@ class GroupACLMixin:
133
102
  query |= auth_query
134
103
 
135
104
  return query
105
+
106
+
107
+ class AuthFilterGenericAPIView(AuthGenericAPIView, GroupACLMixin):
108
+ """Auth Enabled GenericAPIView."""
109
+
110
+
111
+ class AuthFilterAPIView(AuthAPIView, GroupACLMixin):
112
+ """Auth Enabled APIView."""
codex/views/bookmark.py CHANGED
@@ -1,9 +1,6 @@
1
1
  """Bookmark views."""
2
2
 
3
- from typing import ClassVar
4
-
5
3
  from drf_spectacular.utils import extend_schema
6
- from rest_framework.generics import GenericAPIView
7
4
  from rest_framework.response import Response
8
5
 
9
6
  from codex.logger.logging import get_logger
@@ -12,13 +9,16 @@ from codex.serializers.models.bookmark import (
12
9
  BookmarkFinishedSerializer,
13
10
  BookmarkSerializer,
14
11
  )
15
- from codex.views.auth import GroupACLMixin, IsAuthenticatedOrEnabledNonUsers
12
+ from codex.views.auth import (
13
+ AuthFilterGenericAPIView,
14
+ )
15
+ from codex.views.const import GROUP_RELATION
16
16
 
17
17
  LOG = get_logger(__name__)
18
18
  VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
19
19
 
20
20
 
21
- class BookmarkBaseView(GenericAPIView, GroupACLMixin):
21
+ class BookmarkBaseView(AuthFilterGenericAPIView):
22
22
  """Bookmark Updater."""
23
23
 
24
24
  _BOOKMARK_UPDATE_FIELDS = (
@@ -160,12 +160,11 @@ class BookmarkBaseView(GenericAPIView, GroupACLMixin):
160
160
  class BookmarkView(BookmarkBaseView):
161
161
  """User Bookmark View."""
162
162
 
163
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type:ignore
164
163
  serializer_class = BookmarkSerializer
165
164
 
166
165
  def _validate(self, serializer_class):
167
166
  """Validate and translate the submitted data."""
168
- data = self.request.data
167
+ data = self.request.data # type: ignore
169
168
  if serializer_class:
170
169
  serializer = serializer_class(data=data, partial=True)
171
170
  else:
@@ -184,10 +183,7 @@ class BookmarkView(BookmarkBaseView):
184
183
 
185
184
  pks = self.kwargs.get("pks")
186
185
 
187
- if group == "f":
188
- relation = "folders__in"
189
- else:
190
- relation = self.GROUP_RELATION[group] + "__in"
186
+ relation = "folders__in" if group == "f" else GROUP_RELATION[group] + "__in"
191
187
  comic_filter = {relation: pks}
192
188
 
193
189
  self.update_bookmarks(updates, comic_filter=comic_filter)
@@ -17,82 +17,73 @@ from django.db.models import (
17
17
  Value,
18
18
  When,
19
19
  )
20
+ from django.db.models.aggregates import Count
20
21
  from django.db.models.fields import CharField, PositiveSmallIntegerField
21
22
  from django.db.models.functions import Least, Reverse, Right, StrIndex
22
23
 
24
+ from codex.logger.logging import get_logger
23
25
  from codex.models import (
24
26
  BrowserGroupModel,
25
27
  Comic,
26
28
  Folder,
27
- Imprint,
28
- Publisher,
29
- Series,
30
29
  StoryArc,
31
30
  Volume,
32
31
  )
33
32
  from codex.models.functions import JsonGroupArray
34
- from codex.views.browser.browser_order_by import (
33
+ from codex.views.browser.order_by import (
35
34
  BrowserOrderByView,
36
35
  )
36
+ from codex.views.const import STORY_ARC_GROUP
37
37
  from codex.views.mixins import SharedAnnotationsMixin
38
38
 
39
- _REL_MAP = MappingProxyType(
39
+ _NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
40
+ _NONE_DATETIMEFIELD = Value(None, DateTimeField())
41
+ _ORDER_AGGREGATE_FUNCS = MappingProxyType(
42
+ # These are annotated to order_value because they're simple relations
40
43
  {
41
- Publisher: "publisher",
42
- Imprint: "imprint",
43
- Series: "series",
44
- Volume: "volume",
45
- Folder: "parent_folder",
46
- StoryArc: "story_arc_numbers__story_arc",
44
+ "age_rating": Avg,
45
+ "community_rating": Avg,
46
+ "created_at": Min,
47
+ "critical_rating": Avg,
48
+ "date": Min,
49
+ "page_count": Sum,
50
+ "size": Sum,
51
+ "updated_at": Min,
47
52
  }
48
53
  )
54
+ _ANNOTATED_ORDER_FIELDS = frozenset(
55
+ # These are annotated with their own functions
56
+ {
57
+ "filename",
58
+ "sort_name",
59
+ "search_score",
60
+ "bookmark_updated_at",
61
+ "story_arc_number",
62
+ }
63
+ )
64
+ _ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingProxyType(
65
+ {Comic: "id", Volume: "name"}
66
+ )
67
+ _ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
68
+
69
+ LOG = get_logger(__name__)
49
70
 
50
71
 
51
72
  class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
52
73
  """Base class for views that need special metadata annotations."""
53
74
 
54
- _NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
55
- _NONE_DATETIMEFIELD = Value(None, DateTimeField())
56
- _ORDER_AGGREGATE_FUNCS = MappingProxyType(
57
- # These are annotated to order_value because they're simple relations
58
- {
59
- "age_rating": Avg,
60
- "community_rating": Avg,
61
- "created_at": Min,
62
- "critical_rating": Avg,
63
- "date": Min,
64
- "page_count": Sum,
65
- "size": Sum,
66
- "updated_at": Min,
67
- }
68
- )
69
- _ANNOTATED_ORDER_FIELDS = frozenset(
70
- # These are annotated with their own functions
71
- {
72
- "filename",
73
- "sort_name",
74
- "search_score",
75
- "bookmark_updated_at",
76
- "story_arc_number",
77
- }
78
- )
79
- _ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = (
80
- MappingProxyType({Comic: "id", Volume: "name"})
81
- )
82
-
83
75
  def __init__(self, *args, **kwargs):
84
76
  """Set params for the type checker."""
85
77
  super().__init__(*args, **kwargs)
86
78
  self.is_opds_1_acquisition = False
87
79
  self.comic_sort_names = ()
88
80
  self.bm_annotataion_data: dict[BrowserGroupModel, tuple[str, dict]] = {}
89
- self.cover_search_score_pairs: tuple[tuple[int, float], ...] = ()
90
81
 
91
82
  def get_group_by(self, model=None):
92
83
  """Get the group by for the model."""
93
84
  if model is None:
94
85
  model = self.model
95
- return self._ALTERNATE_GROUP_BY.get(model, "sort_name") # type: ignore
86
+ return _ALTERNATE_GROUP_BY.get(model, "sort_name") # type: ignore
96
87
 
97
88
  def _alias_sort_names(self, qs, model):
98
89
  """Annotate sort_name."""
@@ -133,7 +124,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
133
124
  # Get story_arc__pk
134
125
  group = self.kwargs["group"]
135
126
  pks = self.kwargs["pks"]
136
- if group == self.STORY_ARC_GROUP and pks:
127
+ if group == STORY_ARC_GROUP and pks:
137
128
  story_arc_pks = pks
138
129
  else:
139
130
  story_arc_pks = self.params.get("filters", {}).get( # type: ignore
@@ -150,7 +141,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
150
141
  )
151
142
  story_arc_number = self.order_agg_func("selected_story_arc_number__number")
152
143
  else:
153
- story_arc_number = self._NONE_INTEGERFIELD
144
+ story_arc_number = _NONE_INTEGERFIELD
154
145
 
155
146
  return qs.alias(story_arc_number=story_arc_number)
156
147
 
@@ -168,19 +159,30 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
168
159
  qs = qs.annotate(page_count=page_count_sum)
169
160
  return qs
170
161
 
171
- def _annotate_bookmark_updated_at(self, qs, model):
172
- if self.is_opds_1_acquisition or self.order_key == "bookmark_updated_at":
173
- bm_rel = self.get_bm_rel(model)
174
- bm_filter = self._get_my_bookmark_filter(bm_rel)
175
- self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
162
+ def get_bookmark_updated_at_aggregate(self, model, always_max=False):
163
+ """Get The aggregate function for relevant bookmark.updated_at."""
164
+ bm_rel = self.get_bm_rel(model)
165
+ bm_filter = self.get_my_bookmark_filter(bm_rel)
166
+ self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
167
+
168
+ updated_at_rel = f"{bm_rel}__updated_at"
169
+ agg_func = Max if always_max else self.order_agg_func
170
+ return agg_func(
171
+ updated_at_rel,
172
+ default=_NONE_DATETIMEFIELD,
173
+ filter=bm_filter,
174
+ )
176
175
 
177
- updated_at_rel = f"{bm_rel}__updated_at"
178
- bookmark_updated_at_aggregate = self.order_agg_func(
179
- updated_at_rel,
180
- default=self._NONE_DATETIMEFIELD,
181
- filter=bm_filter,
176
+ def _annotate_bookmark_updated_at(self, qs, model):
177
+ if (
178
+ self.is_opds_1_acquisition
179
+ or self.order_key == "bookmark_updated_at"
180
+ or self.is_bookmark_filtered
181
+ ):
182
+ bookmark_updated_at_aggregate = self.get_bookmark_updated_at_aggregate(
183
+ model
182
184
  )
183
- if self.TARGET == "opds1":
185
+ if self.TARGET == "opds1" or self.is_bookmark_filtered:
184
186
  qs = qs.annotate(bookmark_updated_at=bookmark_updated_at_aggregate)
185
187
  else:
186
188
  qs = qs.alias(bookmark_updated_at=bookmark_updated_at_aggregate)
@@ -191,11 +193,11 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
191
193
  # Determine order func
192
194
  if model == Folder and self.order_key == "filename":
193
195
  order_value = F("name")
194
- elif model == Comic or self.order_key in self._ANNOTATED_ORDER_FIELDS:
196
+ elif model == Comic or self.order_key in _ANNOTATED_ORDER_FIELDS:
195
197
  # These are annotated in browser_annotaions
196
198
  order_value = F(self.order_key)
197
199
  else:
198
- agg_func = self._ORDER_AGGREGATE_FUNCS[self.order_key]
200
+ agg_func = _ORDER_AGGREGATE_FUNCS[self.order_key]
199
201
  agg_func = self.order_agg_func if agg_func == Min else agg_func
200
202
  field = self.rel_prefix + self.order_key
201
203
  order_value = agg_func(field)
@@ -220,56 +222,29 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
220
222
  qs = self._annotate_order_value(qs, model)
221
223
  return qs
222
224
 
223
- def _annotate_cover_pk(self, qs, model):
224
- """Annotate the query set for the coverpath for the sort."""
225
- # Select comics for the children by an outer ref for annotation
226
- # Order the descendant comics by the sort argumentst
227
- cover_style = self.params.get("cover_style")
228
- if model == Comic:
229
- default_cover_pk = F("pk")
230
- default_cover_mtime = F("updated_at")
231
- elif cover_style == "i":
232
- default_cover_pk = None
233
- default_cover_mtime = None
234
- else:
235
- # At this point it's only been filtered.
236
- default_cover_pk = F(self.rel_prefix + "pk")
237
- default_cover_mtime = F(self.rel_prefix + "updated_at")
238
-
239
- if model in (Volume, Comic):
240
- cover_custom = Value(False)
241
- cover_pk = default_cover_pk
242
- cover_mtime = default_cover_mtime
243
- else:
244
- cover_custom = F("custom_cover")
245
- cover_pk = Case(
246
- When(custom_cover__isnull=False, then=F("custom_cover__pk")),
247
- default=default_cover_pk,
248
- )
249
- cover_mtime = Case(
250
- When(custom_cover__isnull=False, then=F("custom_cover__updated_at")),
251
- default=default_cover_mtime,
252
- )
253
-
254
- return qs.annotate(
255
- cover_custom=cover_custom,
256
- cover_pk=cover_pk,
257
- cover_mtime=cover_mtime,
258
- cover_pks=JsonGroupArray("cover_pk", distinct=True),
259
- )
260
-
261
225
  def _annotate_group(self, qs, model):
262
226
  """Annotate Group."""
263
227
  value = "c" if model == Comic else self.model_group # type: ignore
264
228
  return qs.annotate(group=Value(value, CharField(max_length=1)))
265
229
 
230
+ def _annotate_child_count(self, qs, model):
231
+ """Annotate Child Count."""
232
+ if self.TARGET == "opds2":
233
+ return qs
234
+
235
+ if model == Comic:
236
+ child_count_sum = _ONE_INTEGERFIELD
237
+ else:
238
+ child_count_sum = Count(self.rel_prefix + "pk", distinct=True)
239
+ return qs.annotate(child_count=child_count_sum)
240
+
266
241
  def _annotate_bookmarks(self, qs, model):
267
242
  """Hoist up bookmark annotations."""
268
243
  if model in self.bm_annotation_data: # type: ignore
269
244
  bm_rel, bm_filter = self.bm_annotation_data[model] # type: ignore
270
245
  else:
271
246
  bm_rel = self.get_bm_rel(model)
272
- bm_filter = self._get_my_bookmark_filter(bm_rel)
247
+ bm_filter = self.get_my_bookmark_filter(bm_rel)
273
248
 
274
249
  page_rel = f"{bm_rel}__page"
275
250
  finished_rel = f"{bm_rel}__finished"
@@ -318,20 +293,16 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
318
293
  output_field=BooleanField(),
319
294
  )
320
295
 
321
- if (
322
- self.is_opds_1_acquisition
323
- or self.is_model_comic
324
- and self.TARGET == "browser"
296
+ if self.is_opds_1_acquisition or (
297
+ self.is_model_comic and self.TARGET == "browser"
325
298
  ):
326
299
  qs = qs.annotate(page=bookmark_page)
327
- else:
300
+ elif self.TARGET in frozenset({"metadata", "browser"}):
328
301
  qs = qs.alias(page=bookmark_page)
329
302
 
330
303
  if self.TARGET in frozenset({"metadata", "browser"}):
304
+ # Only used for progress and progress is only for metadata and browser anyway
331
305
  qs.annotate(finished=finished_aggregate)
332
- else:
333
- # Only used for progress
334
- qs.alias(finished=finished_aggregate)
335
306
  return qs
336
307
 
337
308
  def _annotate_progress(self, qs):
@@ -346,88 +317,26 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
346
317
  progress = Case(When(page_count__gt=0, then=then), default=0.0)
347
318
  return qs.annotate(progress=progress)
348
319
 
320
+ def _annotate_mtime(self, qs, model):
321
+ """Annotations mtime."""
322
+ bm_rel = self.get_bm_rel(model)
323
+ bm_ua_rel = bm_rel + "__updated_at"
324
+ when_filter = {bm_ua_rel + "__gt": F("updated_at")}
325
+ mtime = Case(
326
+ When(**when_filter),
327
+ then=F(bm_ua_rel),
328
+ default=F("updated_at"),
329
+ )
330
+ return qs.annotate(mtime=mtime)
331
+
349
332
  def annotate_card_aggregates(self, qs, model):
350
333
  """Annotate aggregates that appear the browser card."""
351
- qs = self._annotate_cover_pk(qs, model)
352
334
  if model == Comic:
353
335
  # comic adds order_value for cards late
354
336
  qs = self._annotate_order_value(qs, model)
355
337
  qs = self._annotate_group(qs, model)
356
338
  qs = self.annotate_group_names(qs, model)
339
+ qs = self._annotate_child_count(qs, model)
357
340
  qs = self._annotate_bookmarks(qs, model)
358
- return self._annotate_progress(qs)
359
-
360
- def annotate_for_metadata(self, qs, _model):
361
- """Annotations only for metadata."""
362
- return qs.annotate(mtime=Max("updated_at"))
363
-
364
- def _get_cover_pk_query(self, cover_pks, group_ids):
365
- """Get Cover Pk queryset for comic queryset."""
366
- cover_style = self.params.get("cover_style")
367
- if cover_style == "d":
368
- # DYNAMIC COVERS
369
- comic_qs = Comic.objects.filter(pk__in=cover_pks)
370
- comic_qs = self.annotate_search_score(
371
- comic_qs, Comic, self.cover_search_score_pairs
372
- )
373
- comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
374
- comic_qs = self.add_order_by(comic_qs, Comic) # type: ignore
375
- else: # "f"
376
- # FIRST COVERS
377
- # The only filter at all is the model relation.
378
- group_rel = _REL_MAP[self.model] # type: ignore
379
- comic_qs = Comic.objects.filter(**{f"{group_rel}__in": group_ids})
380
-
381
- # Order generated irrespective of browser filters and order
382
- if self.kwargs["group"] == "f":
383
- order_key = "filename"
384
- elif self.kwargs["group"] == "a":
385
- order_key = "story_arc_number"
386
- else:
387
- order_key = "sort_name"
388
-
389
- model_group = self.model_group # type: ignore
390
- show = MappingProxyType(self.params["show"]) # type: ignore
391
- model_group = self.kwargs["group"]
392
- comic_qs, comic_sort_names = self.alias_sort_names(
393
- comic_qs, Comic, cover_pks, model_group, show
394
- )
395
- comic_qs = self.add_order_by(
396
- comic_qs,
397
- Comic,
398
- order_key=order_key,
399
- do_reverse=False,
400
- comic_sort_names=comic_sort_names,
401
- )
402
- comic_qs = comic_qs.group_by("id")
403
- return comic_qs.only("pk", "updated_at")
404
-
405
- def re_cover_multi_groups(self, group_qs):
406
- """Python hack to re-cover groups collapsed with group_by."""
407
- # This would be better in the main query but OuterRef can't access annotations.
408
- # So cover_pks aggregates all covers from multi groups like ids does.
409
- if self.is_model_comic or self.params.get("cover_style") == "i":
410
- return group_qs
411
-
412
- recovered_group_list = []
413
- for group in group_qs:
414
- cover_pks = group.cover_pks
415
- if len(cover_pks) == 1:
416
- cover_pk = cover_pks[0]
417
- cover_mtime = group.updated_at
418
- else:
419
- comic_qs = self._get_cover_pk_query(cover_pks, group.ids)
420
- # print(comic_qs.explain())
421
- # print(comic_qs.query)
422
- cover = comic_qs[0]
423
- cover_pk = cover.pk
424
- if len(group.ids) == 1:
425
- cover_mtime = group.updated_at
426
- else:
427
- cover_mtime = group.__class__.objects.filter(
428
- pk__in=group.ids
429
- ).aggregate(updated_at=Max("updated_at"))["updated_at"]
430
- group.cover_mtime = cover_mtime
431
- group.cover_pk = cover_pk
432
- recovered_group_list.append(group)
433
- return recovered_group_list
341
+ qs = self._annotate_progress(qs)
342
+ return self._annotate_mtime(qs, model)