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
@@ -1,18 +1,13 @@
1
1
  """Views for browsing comic library."""
2
2
 
3
- from copy import deepcopy
4
- from math import ceil, floor, log10
5
- from os import sep
6
- from pathlib import Path
3
+ from math import floor, log10
7
4
  from types import MappingProxyType
8
- from typing import TYPE_CHECKING, ClassVar
5
+ from typing import TYPE_CHECKING
9
6
 
10
- from django.core.paginator import EmptyPage, Paginator
11
7
  from django.db.models import Max
12
8
  from drf_spectacular.utils import extend_schema
13
9
  from rest_framework.response import Response
14
10
 
15
- from codex.exceptions import SeeOtherRedirectError
16
11
  from codex.logger.logging import get_logger
17
12
  from codex.models import (
18
13
  AdminFlag,
@@ -27,10 +22,13 @@ from codex.models import (
27
22
  Volume,
28
23
  )
29
24
  from codex.serializers.browser.page import BrowserPageSerializer
30
- from codex.serializers.choices import DEFAULTS
31
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
32
- from codex.views.browser.browser_breadcrumbs import BrowserBreadcrumbsView
33
- from codex.views.browser.const import MAX_OBJ_PER_PAGE
25
+ from codex.util import max_none
26
+ from codex.views.browser.title import BrowserTitleView
27
+ from codex.views.const import (
28
+ COMIC_GROUP,
29
+ FOLDER_GROUP,
30
+ STORY_ARC_GROUP,
31
+ )
34
32
 
35
33
  if TYPE_CHECKING:
36
34
  from django.db.models.query import QuerySet
@@ -39,10 +37,9 @@ if TYPE_CHECKING:
39
37
  LOG = get_logger(__name__)
40
38
 
41
39
 
42
- class BrowserView(BrowserBreadcrumbsView):
40
+ class BrowserView(BrowserTitleView):
43
41
  """Browse comics with a variety of filters and sorts."""
44
42
 
45
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
46
43
  serializer_class = BrowserPageSerializer
47
44
 
48
45
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
@@ -51,7 +48,7 @@ class BrowserView(BrowserBreadcrumbsView):
51
48
  AdminFlag.FlagChoices.IMPORT_METADATA.value: "import_metadata",
52
49
  }
53
50
  )
54
-
51
+ TARGET = "browser"
55
52
  _GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
56
53
  type[BrowserGroupModel], tuple[str | None, ...]
57
54
  ] = MappingProxyType(
@@ -65,45 +62,43 @@ class BrowserView(BrowserBreadcrumbsView):
65
62
  StoryArc: (None,),
66
63
  }
67
64
  )
68
- DEFAULT_ROUTE = MappingProxyType(
69
- {
70
- "name": "browser",
71
- "params": deepcopy(DEFAULTS["breadcrumbs"][0]),
72
- }
73
- )
74
- _OPDS_M2M_RELS = (
75
- "characters",
76
- "genres",
77
- "locations",
78
- "series_groups",
79
- "story_arc_numbers",
80
- "story_arc_numbers__story_arc",
81
- "tags",
82
- "teams",
83
- "contributors",
84
- "contributors__role",
85
- "contributors__person",
86
- )
87
- TARGET = "browser"
88
65
 
89
66
  def __init__(self, *args, **kwargs):
90
67
  """Set params for the type checker."""
91
68
  super().__init__(*args, **kwargs)
92
69
  self.is_opds_2_acquisition = False
93
70
  self.group_query: QuerySet = Comic.objects.none()
94
- self.group_instance: BrowserGroupModel | None = None
95
71
  self.valid_nav_groups: tuple[str, ...] = ()
96
72
 
97
- def _raise_redirect(self, reason, route_mask=None, settings_mask=None):
98
- """Redirect the client to a valid group url."""
99
- route = deepcopy(dict(self.DEFAULT_ROUTE))
100
- if route_mask:
101
- route["params"].update(route_mask) # type: ignore
102
- settings = deepcopy(dict(self.params))
103
- if settings_mask:
104
- settings.update(settings_mask)
105
- detail = {"route": route, "settings": settings, "reason": reason}
106
- raise SeeOtherRedirectError(detail=detail)
73
+ def _set_group_query_and_instance(self):
74
+ """Create group_class instance."""
75
+ pks = self.kwargs.get("pks")
76
+ if pks and self.group_class:
77
+ try:
78
+ select_related: tuple[str | None, ...] = (
79
+ self._GROUP_INSTANCE_SELECT_RELATED[self.group_class]
80
+ )
81
+ self.group_query = self.group_class.objects.select_related(
82
+ *select_related
83
+ ).filter(pk__in=pks)
84
+ except self.group_class.DoesNotExist:
85
+ group = self.kwargs.get("group")
86
+ page = self.kwargs.get("page")
87
+ if group == "r" and not pks and page == 1:
88
+ self.group_query = self.group_class.objects.none()
89
+ else:
90
+ reason = f"{group}__in={pks} does not exist!"
91
+ self.raise_redirect(reason, route_mask={"group": group})
92
+ elif self.group_class:
93
+ self.group_query = self.group_class.objects.none()
94
+ else:
95
+ self.group_query = Comic.objects.none()
96
+ self.group_instance = self.group_query.first()
97
+
98
+ def init_request(self):
99
+ """Initialize request."""
100
+ super().init_request()
101
+ self._set_group_query_and_instance()
107
102
 
108
103
  def get_model_group(self):
109
104
  """Get the group of the models to browse."""
@@ -111,21 +106,22 @@ class BrowserView(BrowserBreadcrumbsView):
111
106
  # A valid nav group or 'c'
112
107
  # the child of the current nav group or 'c'
113
108
  group = self.kwargs["group"]
114
- if group == self.FOLDER_GROUP:
109
+ if group == FOLDER_GROUP:
115
110
  return group
116
- if group == self.STORY_ARC_GROUP:
111
+ if group == STORY_ARC_GROUP:
117
112
  pks = self.kwargs.get("pks")
118
- return self.COMIC_GROUP if pks else group
113
+ return COMIC_GROUP if pks else group
119
114
  if group == self.valid_nav_groups[-1]:
120
115
  # special case for lowest valid group
121
- return self.COMIC_GROUP
116
+ return COMIC_GROUP
122
117
  return self.valid_nav_groups[self.valid_nav_groups.index(group) + 1]
123
118
 
119
+ ################
120
+ # MAIN QUERIES #
121
+ ################
124
122
  def _get_common_queryset(self, model):
125
123
  """Create queryset common to group & books."""
126
- object_filter = self.get_query_filters(model, False)
127
- qs = model.objects.filter(object_filter)
128
- qs = self.filter_by_annotations(qs, model)
124
+ qs = self.get_filtered_queryset(model)
129
125
  count_group_by = self.get_group_by(model)
130
126
  count = qs.group_by(count_group_by).count()
131
127
  if count:
@@ -161,167 +157,16 @@ class BrowserView(BrowserBreadcrumbsView):
161
157
  count = 0
162
158
  return qs, count
163
159
 
164
- def _set_group_query_and_instance(self):
165
- """Create group_class instance."""
166
- pks = self.kwargs.get("pks")
167
- if pks and self.group_class:
168
- try:
169
- select_related: tuple[str | None, ...] = (
170
- self._GROUP_INSTANCE_SELECT_RELATED[self.group_class]
171
- )
172
- self.group_query = self.group_class.objects.select_related(
173
- *select_related
174
- ).filter(pk__in=pks)
175
- except self.group_class.DoesNotExist:
176
- group = self.kwargs.get("group")
177
- page = self.kwargs.get("page")
178
- if group == "r" and not pks and page == 1:
179
- self.group_query = self.group_class.objects.none()
180
- else:
181
- reason = f"{group}__in={pks} does not exist!"
182
- self._raise_redirect(reason, route_mask={"group": group})
183
- elif self.group_class:
184
- self.group_query = self.group_class.objects.none()
185
- else:
186
- self.group_query = Comic.objects.none()
187
- self.group_instance = self.group_query.first()
188
-
189
- def _get_root_group_name(self):
190
- if not self.model:
191
- reason = "No model set in browser"
192
- raise ValueError(reason)
193
- plural = self.model._meta.verbose_name_plural
194
- if not plural:
195
- reason = f"No plural name for {self.model}"
196
- raise ValueError(reason)
197
- return plural.capitalize()
198
-
199
- def _get_folder_parent_name(self):
200
- """Get title for a folder browser page."""
201
- group_instance: Folder = self.group_instance # type: ignore
202
- if not group_instance.path:
203
- return ""
204
-
205
- parent_path = Path(group_instance.path).parent
206
- if not self.is_admin():
207
- try:
208
- # remove library path for not admins
209
- library_path_parent = Path(group_instance.library.path).parent
210
- parent_path = parent_path.relative_to(library_path_parent)
211
- except ValueError:
212
- parent_path = ""
213
-
214
- parent_path_str = str(parent_path)
215
- parent_path_str = parent_path_str.removeprefix(".")
216
- if parent_path_str and parent_path_str[0] != sep:
217
- parent_path_str = sep + parent_path_str
218
- return parent_path_str
219
-
220
- def _get_browser_page_title(self):
221
- """Get the proper title for this browse level."""
222
- group_count = 0
223
- group_name = ""
224
- pks = self.kwargs.get("pks")
225
- if not pks:
226
- group_name = self._get_root_group_name()
227
- else:
228
- group_instance = self.group_instance
229
- if group_instance:
230
- if isinstance(group_instance, Volume):
231
- group_count = group_instance.series.volume_count
232
- elif isinstance(group_instance, Comic):
233
- group_count = group_instance.volume.issue_count
234
- group_name = group_instance.name
235
-
236
- return {
237
- "group_name": group_name,
238
- "group_count": group_count,
239
- }
240
-
241
- def _check_page_in_bounds(self, total_count):
242
- """Redirect page out of bounds."""
243
- pks = self.kwargs.get("pks")
244
- page = self.kwargs.get("page", 1)
245
- num_pages = ceil(total_count / MAX_OBJ_PER_PAGE)
246
- if (not pks and page == 1) or (page >= 1 and page <= num_pages):
247
- # Don't redirect if on the root page for the group.
248
- # Or page within valid range.
249
- return num_pages
250
-
251
- # Adjust route mask for redirect
252
- group = self.kwargs.get("group")
253
- if num_pages and page > 1:
254
- # Try page 1 first.
255
- new_page = num_pages if num_pages and page > num_pages else 1
256
- pks = pks if pks else "0"
257
- else:
258
- # Redirect to root for the group.
259
- pks = "0"
260
- new_page = 1
261
-
262
- reason = f"{page=} does not exist!"
263
- route_changes = {"group": group, "pks": pks, "page": new_page}
264
- return self._raise_redirect(reason, route_mask=route_changes)
265
-
266
- def _paginate_section(self, model, qs, page):
267
- """Paginate a group or Comic section."""
268
- orphans = 0 if self.model_group == "f" or self.params.get("q") else 5
269
- paginator = Paginator(qs, MAX_OBJ_PER_PAGE, orphans=orphans)
270
- try:
271
- qs = paginator.page(page).object_list
272
- except EmptyPage:
273
- if model != Folder:
274
- model_name = self.model.__name__ if self.model else "NO_MODEL"
275
- LOG.warning(f"No {model_name}s on page {page}")
276
- qs = model.objects.none()
277
-
278
- return qs
279
-
280
- def _paginate_groups(self, group_qs, total_group_count):
281
- """Paginate the group object list before books."""
282
- if total_group_count:
283
- page = self.kwargs.get("page", 1)
284
- page_group_qs = self._paginate_section(self.model, group_qs, page)
160
+ def _get_page_mtime(self, group_qs):
161
+ if self.group_instance:
162
+ page_updated_at_max = self.group_instance.updated_at
285
163
  else:
286
- page_group_qs = self.model.objects.none() # type: ignore
287
- return page_group_qs
164
+ page_updated_at_max = group_qs.aggregate(max=Max("updated_at"))["max"]
288
165
 
289
- def _paginate_books(self, book_qs, total_group_count, page_obj_count):
290
- """Paginate the book object list based on how many group/folders are showing."""
291
- if page_obj_count >= MAX_OBJ_PER_PAGE:
292
- # No books for this page
293
- page_book_qs = Comic.objects.none()
294
- else:
295
- group_remainder = total_group_count % MAX_OBJ_PER_PAGE
296
- if page_obj_count:
297
- # There are books after the groups on the same page
298
- # Manually add books without the paginator
299
- book_limit = MAX_OBJ_PER_PAGE - group_remainder
300
- page_book_qs = book_qs[:book_limit]
301
- else:
302
- # There are books after the groups on a new page
303
- if group_remainder:
304
- book_offset = MAX_OBJ_PER_PAGE - group_remainder
305
- page_book_qs = book_qs[book_offset:]
306
- else:
307
- page_book_qs = book_qs
166
+ agg_func = self.get_bookmark_updated_at_aggregate(self.model, True)
167
+ page_bookmark_updated_at = group_qs.aggregate(max=agg_func)["max"]
308
168
 
309
- num_group_and_mixed_pages = ceil(total_group_count / MAX_OBJ_PER_PAGE)
310
- book_only_page = self.kwargs.get("page", 1) - num_group_and_mixed_pages
311
- page_book_qs = self._paginate_section(
312
- Comic, page_book_qs, book_only_page
313
- )
314
- return page_book_qs
315
-
316
- def _paginate(self, group_qs, book_qs, group_count, book_count):
317
- """Paginate the queryset into a group and book object lists."""
318
- num_pages = self._check_page_in_bounds(group_count + book_count)
319
- page_group_qs = self._paginate_groups(group_qs, group_count)
320
- page_group_count = page_group_qs.count()
321
- page_book_qs = self._paginate_books(book_qs, group_count, page_group_count)
322
- page_book_count = page_book_qs.count()
323
-
324
- return page_group_qs, page_book_qs, num_pages, page_group_count, page_book_count
169
+ return max_none(page_updated_at_max, page_bookmark_updated_at)
325
170
 
326
171
  @staticmethod
327
172
  def _get_zero_pad(book_qs):
@@ -337,10 +182,11 @@ class BrowserView(BrowserBreadcrumbsView):
337
182
  def _get_group_and_books(self):
338
183
  """Create the main queries with filters, annotation and pagination."""
339
184
  group_qs, group_count = self._get_group_queryset()
185
+ mtime = self._get_page_mtime(group_qs)
340
186
  book_qs, book_count = self._get_book_queryset()
341
187
  # Paginate
342
- group_qs, book_qs, num_pages, page_group_count, page_book_count = (
343
- self._paginate(group_qs, book_qs, group_count, book_count)
188
+ group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
189
+ group_qs, book_qs, group_count, book_count
344
190
  )
345
191
  if page_group_count:
346
192
  group_qs = self.annotate_card_aggregates(group_qs, self.model)
@@ -353,19 +199,18 @@ class BrowserView(BrowserBreadcrumbsView):
353
199
  # print(group_qs.explain())
354
200
  # print(group_qs.query)
355
201
 
356
- recovered_group_list = self.re_cover_multi_groups(group_qs)
357
202
  total_count = page_group_count + page_book_count
358
- return recovered_group_list, book_qs, num_pages, total_count, zero_pad
203
+ return group_qs, book_qs, num_pages, total_count, zero_pad, mtime
359
204
 
360
205
  def get_object(self):
361
206
  """Validate settings and get the querysets."""
362
- group_list, book_qs, num_pages, total_count, zero_pad = (
207
+ group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
363
208
  self._get_group_and_books()
364
209
  )
365
210
 
366
211
  # get additional context
367
212
  parent_breadcrumbs = self.get_parent_breadcrumbs()
368
- title = self._get_browser_page_title()
213
+ title = self.get_browser_page_title()
369
214
  # needs to happen after pagination
370
215
  # runs obj list query twice :/
371
216
  libraries_exist = Library.objects.filter(covers_only=False).exists()
@@ -376,141 +221,18 @@ class BrowserView(BrowserBreadcrumbsView):
376
221
  "breadcrumbs": parent_breadcrumbs,
377
222
  "title": title,
378
223
  "model_group": self.model_group,
379
- "groups": group_list,
224
+ "groups": group_qs,
380
225
  "books": book_qs,
381
226
  "zero_pad": zero_pad,
382
227
  "num_pages": num_pages,
383
228
  "total_count": total_count,
384
229
  "admin_flags": self.admin_flags,
385
230
  "libraries_exist": libraries_exist,
231
+ "mtime": mtime,
386
232
  }
387
233
  )
388
234
 
389
- def _get_valid_browse_top_groups(self):
390
- """Get valid top groups for the current settings.
391
-
392
- Valid top groups are determined by the Browser Settings.
393
- """
394
- valid_top_groups = []
395
-
396
- show = self.params["show"]
397
- for nav_group, allowed in show.items():
398
- if allowed:
399
- valid_top_groups.append(nav_group)
400
- # Issues is always a valid top group
401
- valid_top_groups += [self.COMIC_GROUP]
402
-
403
- return valid_top_groups
404
-
405
- def _validate_top_group(self, valid_top_groups):
406
- nav_group = self.kwargs.get("group")
407
- top_group = self.params.get("top_group")
408
- if top_group not in valid_top_groups:
409
- reason = f"top_group {top_group} not in valid nav groups, changed to "
410
- if nav_group in valid_top_groups:
411
- valid_top_group = nav_group
412
- reason += "nav group: "
413
- else:
414
- valid_top_group = valid_top_groups[0]
415
- reason += "first valid top group "
416
- reason += valid_top_group
417
- pks = self.kwargs.get("pks", ())
418
- page = self.kwargs["page"]
419
- route = {"group": nav_group, "pks": pks, "page": page}
420
- settings_mask = {"top_group": valid_top_group}
421
- self._raise_redirect(reason, route, settings_mask)
422
-
423
- def set_valid_browse_nav_groups(self, valid_top_groups):
424
- """Get valid nav groups for the current settings.
425
-
426
- Valid nav groups are the top group and below that are also
427
- enabled in browser settings.
428
-
429
- May always navigate to root 'r' nav group.
430
- """
431
- top_group = self.params["top_group"]
432
- nav_group = self.kwargs["group"]
433
- valid_nav_groups = [self.ROOT_GROUP]
434
-
435
- for possible_index, possible_nav_group in enumerate(valid_top_groups):
436
- if top_group == possible_nav_group:
437
- # all the nav groups past this point,
438
- # 'c' is obscured by the web reader url, but valid for opds
439
- tail_top_groups = valid_top_groups[possible_index:]
440
- valid_nav_groups += tail_top_groups
441
-
442
- if nav_group not in valid_nav_groups:
443
- reason = (
444
- f"Nav group {nav_group} unavailable, "
445
- f"redirect to {self.ROOT_GROUP}"
446
- )
447
- self._raise_redirect(reason)
448
- break
449
- self.valid_nav_groups = tuple(valid_nav_groups)
450
-
451
- def _validate_folder_settings(self):
452
- """Check that all the view variables for folder mode are set right."""
453
- # Check folder view admin flag
454
- if not self.admin_flags["folder_view"]:
455
- reason = "folder view disabled"
456
- valid_top_groups = self._get_valid_browse_top_groups()
457
- settings_mask = {"top_group": valid_top_groups[0]}
458
- self._raise_redirect(reason, settings_mask=settings_mask)
459
-
460
- valid_top_groups = (self.FOLDER_GROUP,)
461
- self._validate_top_group(valid_top_groups)
462
- self.valid_nav_groups = valid_top_groups
463
-
464
- def _validate_browser_group_settings(self):
465
- """Check that all the view variables for browser mode are set right."""
466
- # Validate Browser top_group
467
- # Change top_group if its not in the valid top groups
468
- valid_top_groups = self._get_valid_browse_top_groups()
469
- self._validate_top_group(valid_top_groups)
470
-
471
- # Validate pks
472
- nav_group = self.kwargs["group"]
473
- pks = self.kwargs["pks"]
474
- if nav_group == self.ROOT_GROUP and (pks and 0 not in pks):
475
- # r never has pks
476
- reason = f"Redirect r with {pks=} to pks 0"
477
- self._raise_redirect(reason)
478
-
479
- # Validate Browser nav_group
480
- # Redirect if nav group is wrong
481
- self.set_valid_browse_nav_groups(valid_top_groups)
482
-
483
- def _validate_story_arc_settings(self):
484
- """Validate story arc settings."""
485
- valid_top_groups = (self.STORY_ARC_GROUP,)
486
- self._validate_top_group(valid_top_groups)
487
- self.valid_nav_groups = valid_top_groups
488
-
489
- def validate_settings(self):
490
- """Validate group and top group settings."""
491
- group = self.kwargs["group"]
492
- if group == self.FOLDER_GROUP:
493
- self._validate_folder_settings()
494
- elif group == self.STORY_ARC_GROUP:
495
- self._validate_story_arc_settings()
496
- else:
497
- self._validate_browser_group_settings()
498
-
499
- # Validate order
500
- if self.order_key == "filename" and not self.admin_flags["folder_view"]:
501
- pks = self.kwargs["pks"]
502
- page = self.kwargs["page"]
503
- route_changes = {"group": group, "pks": pks, "page": page}
504
- reason = "order by filename not allowed by admin flag."
505
- settings_mask = {"order_by": "sort_name"}
506
- self._raise_redirect(reason, route_changes, settings_mask)
507
-
508
- def init_request(self):
509
- """Initialize request."""
510
- super().init_request()
511
- self._set_group_query_and_instance()
512
-
513
- @extend_schema(request=BrowserBreadcrumbsView.input_serializer_class)
235
+ @extend_schema(request=BrowserTitleView.input_serializer_class)
514
236
  def get(self, *_args, **_kwargs):
515
237
  """Get browser settings."""
516
238
  self.init_request()
@@ -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
  import pycountry
7
6
  from caseconverter import snakecase
@@ -13,12 +12,7 @@ from codex.logger.logging import get_logger
13
12
  from codex.models import (
14
13
  Comic,
15
14
  ContributorPerson,
16
- Folder,
17
- Imprint,
18
- Publisher,
19
- Series,
20
15
  StoryArc,
21
- Volume,
22
16
  )
23
17
  from codex.models.named import IdentifierType
24
18
  from codex.serializers.browser.choices import CHOICES_SERIALIZER_CLASS_MAP
@@ -27,7 +21,6 @@ from codex.serializers.browser.filters import (
27
21
  CharListField,
28
22
  )
29
23
  from codex.serializers.models.pycountry import PyCountrySerializer
30
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
31
24
  from codex.views.browser.filters.annotations import (
32
25
  BrowserAnnotationsFilterView,
33
26
  )
@@ -50,37 +43,21 @@ _FIELD_TO_REL_MODEL_MAP = MappingProxyType(
50
43
  ),
51
44
  }
52
45
  )
46
+ _BACK_REL_MAP = MappingProxyType(
47
+ {
48
+ ContributorPerson: "contributor__",
49
+ StoryArc: "storyarcnumber__",
50
+ IdentifierType: "identifier__",
51
+ }
52
+ )
53
+ _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
53
54
 
54
55
 
55
56
  class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
56
57
  """Get choices for filter dialog."""
57
58
 
58
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
59
-
60
- _CONTRIBUTORS_PERSON_REL = "contributors__person"
61
- _STORY_ARC_REL = "story_arc_numbers__story_arc"
62
- _IDENTIFIERS_REL = "identifiers__identifier_type"
63
- _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
64
- _BACK_REL_MAP = MappingProxyType(
65
- {
66
- ContributorPerson: "contributor__",
67
- StoryArc: "storyarcnumber__",
68
- IdentifierType: "identifier__",
69
- }
70
- )
71
- _REL_MAP = MappingProxyType(
72
- {
73
- Publisher: "publisher",
74
- Imprint: "imprint",
75
- Series: "series",
76
- Volume: "volume",
77
- Comic: "pk",
78
- Folder: "parent_folder",
79
- StoryArc: "story_arc_numbers__story_arc",
80
- IdentifierType: "identifiers__identifier_type",
81
- }
82
- )
83
- _SERIALIZER_MANY = False
59
+ SERIALIZER_MANY = False
60
+ TARGET = "choices"
84
61
 
85
62
  @staticmethod
86
63
  def get_field_choices_query(comic_qs, field_name):
@@ -93,21 +70,13 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
93
70
 
94
71
  def get_m2m_field_query(self, model, comic_qs: QuerySet):
95
72
  """Get distinct m2m value objects for the relation."""
96
- if self.model is None:
97
- LOG.error("No model to make filter choices!")
98
- m2m_filter = {}
99
- else:
100
- model_rel: str = self._REL_MAP[self.model] # type: ignore
101
- back_rel = self._BACK_REL_MAP.get(model, "")
102
- back_rel += "comic__"
103
- back_rel += model_rel
104
- back_rel += "__in"
105
- m2m_filter = {back_rel: comic_qs}
73
+ back_rel = _BACK_REL_MAP.get(model, "")
74
+ m2m_filter = {f"{back_rel}comic__in": comic_qs}
106
75
  return model.objects.filter(**m2m_filter).values("pk", "name").distinct()
107
76
 
108
77
  @staticmethod
109
78
  def does_m2m_null_exist(comic_qs, rel):
110
- """Get if null values exists for an m2m field."""
79
+ """Get if comics exist in the filter without values exists for an m2m field."""
111
80
  return comic_qs.filter(**{f"{rel}__isnull": True}).exists()
112
81
 
113
82
  def get_rel_and_model(self, field_name):
@@ -122,22 +91,18 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
122
91
  rel = field_name
123
92
  model = remote_field.model if remote_field else None
124
93
 
125
- rel = self.rel_prefix + rel
126
-
127
94
  return rel, model
128
95
 
129
96
  def get_object(self):
130
97
  """Get the comic subquery use for the choices."""
131
- object_filter = self.get_query_filters(self.model, choices=True)
132
- qs = self.model.objects.filter(object_filter) # type: ignore
133
- return self.filter_by_annotations(qs, self.model, binary=True)
98
+ return self.get_filtered_queryset(Comic)
134
99
 
135
100
  @extend_schema(request=BrowserAnnotationsFilterView.input_serializer_class)
136
101
  def get(self, *_args, **_kwargs):
137
102
  """Return choices."""
138
103
  self.init_request()
139
104
  obj = self.get_object()
140
- serializer = self.get_serializer(obj, many=self._SERIALIZER_MANY)
105
+ serializer = self.get_serializer(obj, many=self.SERIALIZER_MANY)
141
106
  return Response(serializer.data)
142
107
 
143
108
 
@@ -170,6 +135,7 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
170
135
  def get_object(self):
171
136
  """Get choice counts."""
172
137
  qs = super().get_object()
138
+ filters = self.params.get("filters", {})
173
139
  data = {}
174
140
  for field_name in self.serializer_class().get_fields(): # type: ignore
175
141
  if field_name == "story_arcs" and self.model == StoryArc:
@@ -182,9 +148,9 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
182
148
  else:
183
149
  count = self._get_field_choices_count(qs, rel)
184
150
 
185
- filters = self.params.get("filters", {})
186
151
  try:
187
- flag = count > 1 or field_name in filters # type: ignore
152
+ is_filter_set = bool(filters.get(field_name))
153
+ flag = is_filter_set or count > 1 # type: ignore
188
154
  except TypeError:
189
155
  flag = False
190
156
  data[field_name] = flag
@@ -195,7 +161,9 @@ class BrowserChoicesView(BrowserChoicesViewBase):
195
161
  """Get choices for filter dialog."""
196
162
 
197
163
  # serializer_class = Dynamic class determined in get()
198
- _SERIALIZER_MANY = True
164
+ SERIALIZER_MANY = True
165
+ # HACK get_serializer(data, many=True) requires this to be set for the debug API view
166
+ queryset = Comic.objects.none()
199
167
 
200
168
  def _get_field_choices(self, comic_qs, field_name):
201
169
  """Create a pk:name object for fields without tables."""
@@ -225,7 +193,7 @@ class BrowserChoicesView(BrowserChoicesViewBase):
225
193
  # rows. :(
226
194
  if self.does_m2m_null_exist(comic_qs, rel):
227
195
  choices = list(qs)
228
- choices.append(self._NULL_NAMED_ROW)
196
+ choices.append(_NULL_NAMED_ROW)
229
197
  else:
230
198
  choices = qs
231
199
  return choices