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
@@ -3,15 +3,14 @@
3
3
  from types import MappingProxyType
4
4
 
5
5
  from drf_spectacular.utils import extend_schema
6
- from rest_framework.authentication import BasicAuthentication, SessionAuthentication
7
6
  from rest_framework.response import Response
8
7
 
9
8
  from codex.logger.logging import get_logger
10
9
  from codex.models import AdminFlag
11
10
  from codex.serializers.opds.v2 import OPDS2FeedSerializer
12
11
  from codex.views.browser.browser import BrowserView
13
- from codex.views.browser.const import MAX_OBJ_PER_PAGE
14
- from codex.views.const import FALSY
12
+ from codex.views.const import FALSY, MAX_OBJ_PER_PAGE
13
+ from codex.views.opds.auth import OPDSAuthMixin
15
14
  from codex.views.opds.const import BLANK_TITLE
16
15
  from codex.views.opds.v2.const import (
17
16
  FACETS,
@@ -22,22 +21,20 @@ from codex.views.opds.v2.const import (
22
21
  NavigationGroup,
23
22
  )
24
23
  from codex.views.opds.v2.links import HrefData, LinkData
25
- from codex.views.opds.v2.publications import PublicationMixin
26
- from codex.views.opds.v2.top_links import TopLinksMixin
24
+ from codex.views.opds.v2.publications import OPDS2PublicationView
27
25
 
28
26
  LOG = get_logger(__name__)
29
27
 
30
28
 
31
- class OPDS2FeedView(PublicationMixin, TopLinksMixin):
29
+ class OPDS2FeedView(OPDSAuthMixin, OPDS2PublicationView):
32
30
  """OPDS 2.0 Feed."""
33
31
 
34
32
  DEFAULT_ROUTE = MappingProxyType(
35
- {**TopLinksMixin.DEFAULT_ROUTE, "name": "opds:v2:feed"}
33
+ {**OPDS2PublicationView.DEFAULT_ROUTE, "name": "opds:v2:feed"}
36
34
  )
37
35
  TARGET = "opds2"
38
36
  throttle_scope = "opds"
39
37
 
40
- authentication_classes = (SessionAuthentication, BasicAuthentication)
41
38
  serializer_class = OPDS2FeedSerializer
42
39
 
43
40
  def _title(self, browser_title):
@@ -55,11 +52,11 @@ class OPDS2FeedView(PublicationMixin, TopLinksMixin):
55
52
  result = BLANK_TITLE
56
53
  return result
57
54
 
58
- def _detect_user_agent(self):
59
- """Hacks for individual clients."""
60
- user_agent = self.request.headers.get("User-Agent")
61
- if not user_agent:
62
- return
55
+ # def _detect_user_agent(self):
56
+ # """Hacks for individual clients."""
57
+ # user_agent = self.request.headers.get("User-Agent")
58
+ # if not user_agent:
59
+ # return
63
60
 
64
61
  @staticmethod
65
62
  def _is_allowed(link_spec):
@@ -178,32 +175,34 @@ class OPDS2FeedView(PublicationMixin, TopLinksMixin):
178
175
 
179
176
  def get_object(self):
180
177
  """Get the browser page and serialize it for this subclass."""
181
- browser_page = super().get_object()
182
-
178
+ group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
179
+ self._get_group_and_books()
180
+ )
181
+ title = self.get_browser_page_title()
183
182
  # convert browser_page into opds page
184
183
 
185
184
  # instance vars
186
- self.is_aq_feed = browser_page.get("model_group") in ("c", "f")
187
- self.num_pages = browser_page["num_pages"]
185
+ self.is_aq_feed = self.model_group in ("c", "f")
186
+ self.num_pages = num_pages
188
187
 
189
188
  # opds page
190
- title = self._title(browser_page.get("browser_title"))
191
- number_of_items = browser_page["total_count"]
189
+ title = self._title(title)
190
+ number_of_items = total_count
192
191
  current_page = self.kwargs.get("page")
193
192
  up_route = self.get_last_route()
194
193
  links = self.get_links(up_route)
195
194
  facets = self._get_facets()
196
195
 
197
196
  # opds groups
198
- page_groups = browser_page.get("groups")
199
- page_books = browser_page.get("books")
200
- zero_pad = browser_page["zero_pad"]
197
+ page_groups = group_qs
198
+ page_books = book_qs
201
199
  groups = self._get_groups(page_groups, page_books, title, zero_pad)
202
200
 
203
201
  return MappingProxyType(
204
202
  {
205
203
  "metadata": {
206
204
  "title": title,
205
+ "modified": mtime,
207
206
  "number_of_items": number_of_items,
208
207
  "items_per_page": MAX_OBJ_PER_PAGE,
209
208
  "current_page": current_page,
@@ -224,7 +223,7 @@ class OPDS2FeedView(PublicationMixin, TopLinksMixin):
224
223
  acquisition_groups = frozenset(self.valid_nav_groups[-2:])
225
224
  self.is_opds_2_acquisition = group in acquisition_groups
226
225
  self.is_opds_metadata = (
227
- self.request.query_params.get("opdsMetadata", "").lower() not in FALSY
226
+ self.request.GET.get("opdsMetadata", "").lower() not in FALSY
228
227
  )
229
228
 
230
229
  def init_request(self):
@@ -10,6 +10,7 @@ from codex.views.browser.browser import BrowserView
10
10
  from codex.views.const import FALSY
11
11
  from codex.views.opds.const import MimeType, Rel
12
12
  from codex.views.opds.util import update_href_query_params
13
+ from codex.views.util import pop_name
13
14
 
14
15
 
15
16
  @dataclass
@@ -39,7 +40,7 @@ class LinkData:
39
40
  num_items: int | None = None
40
41
 
41
42
 
42
- class LinksMixin(BrowserView):
43
+ class OPDS2LinksView(BrowserView):
43
44
  """Links methods for OPDS 2.0 Feed."""
44
45
 
45
46
  num_pages = 0 # For pyright. Overwritten every run.
@@ -58,7 +59,7 @@ class LinksMixin(BrowserView):
58
59
  elif hasattr(self, "request"):
59
60
  # if request link and not init static links
60
61
  href = update_href_query_params(
61
- href, self.request.query_params, new_query_params=data.query_params
62
+ href, self.request.GET, new_query_params=data.query_params
62
63
  )
63
64
  return href
64
65
 
@@ -69,6 +70,7 @@ class LinksMixin(BrowserView):
69
70
  if "page" in kwargs and not self._href_page_validate(kwargs, data):
70
71
  return None
71
72
 
73
+ kwargs = pop_name(kwargs)
72
74
  href = reverse(url_name, kwargs=kwargs)
73
75
  return self._href_update_query_params(href, data)
74
76
 
@@ -1,69 +1,47 @@
1
1
  """Publication Methods for OPDS v2.0 feed."""
2
2
 
3
+ from datetime import datetime
4
+ from math import floor
3
5
  from types import MappingProxyType
4
6
  from urllib.parse import quote_plus
5
7
 
6
- from django.contrib.staticfiles.storage import staticfiles_storage
7
-
8
8
  from codex.librarian.covers.create import CoverCreateMixin
9
9
  from codex.models import Comic
10
10
  from codex.views.opds.const import AUTHOR_ROLES, MimeType, Rel
11
11
  from codex.views.opds.util import get_contributors, get_m2m_objects
12
- from codex.views.opds.v2.links import HrefData, LinkData, LinksMixin
13
-
14
-
15
- class PublicationMixin(LinksMixin):
12
+ from codex.views.opds.v2.top_links import HrefData, LinkData, OPDS2TopLinksView
13
+
14
+ _MD_CONTRIBUTOR_MAP = MappingProxyType(
15
+ {
16
+ "author": AUTHOR_ROLES,
17
+ # "translator": {},
18
+ "editor": {"Editor"},
19
+ "artist": {"CoverArtist"},
20
+ # "illustrator": {},
21
+ "letterer": {"Letterer"},
22
+ "penciller": {"Penciller"},
23
+ "colorist": {"Colorist"},
24
+ "inker": {"Inker"},
25
+ }
26
+ )
27
+ _CONTRIBUTOR_ROLES = frozenset({x for s in _MD_CONTRIBUTOR_MAP.values() for x in s})
28
+
29
+
30
+ class OPDS2PublicationView(OPDS2TopLinksView):
16
31
  """Publication Methods for OPDS 2.0 feed."""
17
32
 
18
- _MD_CONTRIBUTOR_MAP = MappingProxyType(
19
- {
20
- "author": AUTHOR_ROLES,
21
- # "translator": {},
22
- "editor": {"Editor"},
23
- "artist": {"CoverArtist"},
24
- # "illustrator": {},
25
- "letterer": {"Letterer"},
26
- "penciller": {"Penciller"},
27
- "colorist": {"Colorist"},
28
- "inker": {"Inker"},
29
- }
30
- )
31
- _CONTRIBUTOR_ROLES = frozenset({x for s in _MD_CONTRIBUTOR_MAP.values() for x in s})
32
33
  is_opds_metadata = False
33
34
 
34
- def _get_big_image_link(self, obj, cover_pk):
35
- if cover_pk:
36
- mime_type = MimeType.JPEG
37
- url_name = "opds:bin:page"
38
- href = None
39
- min_page = 0
40
- max_page = obj.max_page or 1
41
- else:
42
- mime_type = MimeType.WEBP
43
- href = staticfiles_storage.url("img/missing_cover.webp")
44
- url_name = None
45
- min_page = None
46
- max_page = None
47
-
48
- kwargs = {"pk": obj.pk, "page": 0}
49
- href_data = HrefData(
50
- kwargs,
51
- absolute_query_params=True,
52
- url_name=url_name,
53
- min_page=min_page,
54
- max_page=max_page,
55
- )
56
- link_data = LinkData(Rel.IMAGE, href_data, href=href, mime_type=mime_type)
57
- return self.link(link_data)
58
-
59
35
  def _images(self, obj):
60
36
  # Images
61
- cover_pk = obj.cover_pk
62
-
63
- kwargs = {"pk": cover_pk}
64
- href_data = HrefData(
65
- kwargs, url_name="opds:bin:cover", absolute_query_params=True
66
- )
37
+ kwargs = {"group": obj.group, "pks": obj.ids}
38
+ ts = floor(datetime.timestamp(obj.updated_at))
39
+ query_params = {
40
+ "customCovers": True,
41
+ "dynamicCovers": False,
42
+ "ts": ts,
43
+ }
44
+ href_data = HrefData(kwargs, query_params, True, "opds:bin:cover")
67
45
  link_data = LinkData(
68
46
  Rel.THUMBNAIL,
69
47
  href_data,
@@ -74,7 +52,9 @@ class PublicationMixin(LinksMixin):
74
52
 
75
53
  cover_link = self.link(link_data)
76
54
 
77
- image_link = self._get_big_image_link(obj, cover_pk)
55
+ link_data.rel = Rel.IMAGE
56
+
57
+ image_link = self.link(link_data)
78
58
 
79
59
  return [
80
60
  cover_link,
@@ -106,9 +86,9 @@ class PublicationMixin(LinksMixin):
106
86
 
107
87
  def _publication_extended_metadata(self, md, obj):
108
88
  """Publication m2m metadata only on the metadata alternate link."""
109
- for key, roles in self._MD_CONTRIBUTOR_MAP.items():
89
+ for key, roles in _MD_CONTRIBUTOR_MAP.items():
110
90
  self._add_contributors(md, obj.ids, key, roles)
111
- if contributors := get_contributors(obj.ids, self._CONTRIBUTOR_ROLES, True):
91
+ if contributors := get_contributors(obj.ids, _CONTRIBUTOR_ROLES, True):
112
92
  md["contributor"] = contributors
113
93
 
114
94
  if m2m_objs := get_m2m_objects(obj.ids):
@@ -1,10 +1,10 @@
1
1
  """OPDS v2.0 top links section methods."""
2
2
 
3
3
  from codex.views.opds.const import MimeType, Rel
4
- from codex.views.opds.v2.links import HrefData, LinkData, LinksMixin
4
+ from codex.views.opds.v2.links import HrefData, LinkData, OPDS2LinksView
5
5
 
6
6
 
7
- class TopLinksMixin(LinksMixin):
7
+ class OPDS2TopLinksView(OPDS2LinksView):
8
8
  """OPDS 2.0 top links section methods."""
9
9
 
10
10
  def _link_auth(self):
codex/views/public.py ADDED
@@ -0,0 +1,36 @@
1
+ """Public non-authenticated views."""
2
+
3
+ from rest_framework.generics import GenericAPIView
4
+ from rest_framework.mixins import RetrieveModelMixin
5
+
6
+ from codex.logger.logging import get_logger
7
+ from codex.models import AdminFlag
8
+ from codex.serializers.auth import AuthAdminFlagsSerializer
9
+ from codex.serializers.choices import CHOICES
10
+
11
+ LOG = get_logger(__name__)
12
+ _ADMIN_FLAG_KEYS = frozenset(
13
+ {
14
+ AdminFlag.FlagChoices.NON_USERS.value,
15
+ AdminFlag.FlagChoices.REGISTRATION.value,
16
+ }
17
+ )
18
+
19
+
20
+ class AdminFlagsView(GenericAPIView, RetrieveModelMixin):
21
+ """Get admin flags relevant to auth."""
22
+
23
+ serializer_class = AuthAdminFlagsSerializer
24
+ queryset = AdminFlag.objects.filter(key__in=_ADMIN_FLAG_KEYS).only("key", "on")
25
+
26
+ def get_object(self):
27
+ """Get admin flags."""
28
+ flags = {}
29
+ for obj in self.get_queryset(): # type: ignore
30
+ name = CHOICES["admin"]["adminFlags"][obj.key].lower().replace(" ", "_")
31
+ flags[name] = obj.on
32
+ return flags
33
+
34
+ def get(self, request, *args, **kwargs):
35
+ """Get admin flags relevant to auth."""
36
+ return self.retrieve(request, *args, **kwargs)
@@ -0,0 +1,101 @@
1
+ """Reader get Arcs methods."""
2
+
3
+ from types import MappingProxyType
4
+
5
+ from codex.models import AdminFlag
6
+ from codex.util import max_none
7
+ from codex.views.const import FOLDER_GROUP
8
+ from codex.views.reader.books import ReaderBooksView
9
+
10
+
11
+ class ReaderArcsView(ReaderBooksView):
12
+ """Reader get Arcs methods."""
13
+
14
+ def __init__(self, *args, **kwargs):
15
+ """Initialize instance vars."""
16
+ super().__init__(*args, **kwargs)
17
+ self.params: MappingProxyType = MappingProxyType({})
18
+
19
+ def _get_folder_arc(self, book):
20
+ """Create the folder arc."""
21
+ efv_flag = (
22
+ AdminFlag.objects.only("on")
23
+ .get(key=AdminFlag.FlagChoices.FOLDER_VIEW.value)
24
+ .on
25
+ )
26
+
27
+ if efv_flag:
28
+ folder = book.parent_folder
29
+ folder_arc = {
30
+ "group": FOLDER_GROUP,
31
+ "pks": (folder.pk,),
32
+ "name": folder.name,
33
+ "mtime": folder.updated_at,
34
+ }
35
+ else:
36
+ folder_arc = None
37
+ return folder_arc
38
+
39
+ def _get_series_arc(self, book):
40
+ """Create the series arc."""
41
+ series = book.series
42
+ if series:
43
+ arc_pks = self.get_series_pks_from_breadcrumbs()
44
+ if book.series.pk not in arc_pks:
45
+ arc_pks = (book.series.pk,)
46
+ arc = (
47
+ {
48
+ "group": "s",
49
+ "pks": arc_pks,
50
+ "name": series.name,
51
+ "mtime": series.updated_at,
52
+ }
53
+ if series
54
+ else None
55
+ )
56
+ else:
57
+ arc = None
58
+ return arc
59
+
60
+ def _get_story_arcs(self, book):
61
+ """Create story arcs."""
62
+ sas = []
63
+ for san in book.story_arc_numbers.all():
64
+ sa = san.story_arc
65
+ arc = {
66
+ "group": "a",
67
+ "pks": (sa.pk,),
68
+ "name": sa.name,
69
+ "mtime": sa.updated_at,
70
+ }
71
+ sas.append(arc)
72
+ return sorted(sas, key=lambda x: x["name"])
73
+
74
+ def get_arcs(self, book):
75
+ """Get all series/folder/story arcs."""
76
+ # create top arcs
77
+ folder_arc = self._get_folder_arc(book)
78
+ series_arc = self._get_series_arc(book)
79
+
80
+ # order top arcs
81
+ top_group = self.params.get("top_group")
82
+ if top_group == FOLDER_GROUP and folder_arc:
83
+ arc = folder_arc
84
+ other_arc = series_arc
85
+ else:
86
+ arc = series_arc
87
+ other_arc = folder_arc
88
+
89
+ arcs = []
90
+ arcs.append(arc)
91
+ if other_arc:
92
+ arcs.append(other_arc)
93
+
94
+ # story arcs
95
+ sas = self._get_story_arcs(book)
96
+ arcs += sas
97
+
98
+ max_mtime = None
99
+ for arc in arcs:
100
+ max_mtime = max_none(max_mtime, arc["mtime"])
101
+ return arcs, max_mtime
@@ -0,0 +1,199 @@
1
+ """Get Books methods."""
2
+
3
+ from copy import deepcopy
4
+ from typing import TYPE_CHECKING
5
+
6
+ from django.db.models import F, IntegerField, Value
7
+
8
+ from codex.models import Bookmark, Comic
9
+ from codex.views.bookmark import BookmarkBaseView
10
+ from codex.views.const import FOLDER_GROUP
11
+ from codex.views.mixins import SharedAnnotationsMixin
12
+ from codex.views.session import SessionView
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Mapping
16
+
17
+ _MIN_CRUMB_WALKBACK_LEN = 3
18
+ _SETTINGS_ATTRS = ("fit_to", "two_pages", "reading_direction")
19
+ _COMIC_FIELDS = (
20
+ "file_type",
21
+ "issue_number",
22
+ "issue_suffix",
23
+ "page_count",
24
+ "series",
25
+ "volume",
26
+ "reading_direction",
27
+ "updated_at",
28
+ )
29
+
30
+
31
+ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
32
+ """Get Books methods."""
33
+
34
+ def get_series_pks_from_breadcrumbs(self):
35
+ """Get Multi-Group pks from the breadcrumbs."""
36
+ if self.series_pks:
37
+ return self.series_pks
38
+ breadcrumbs = self.get_from_session(
39
+ "breadcrumbs", session_key=self.BROWSER_SESSION_KEY
40
+ )
41
+ if breadcrumbs:
42
+ crumb = breadcrumbs[-1]
43
+ crumb_group = crumb.get("group")
44
+ if crumb_group == "v" and len(breadcrumbs) >= _MIN_CRUMB_WALKBACK_LEN:
45
+ crumb = breadcrumbs[-2]
46
+ crumb_group = crumb.get("group")
47
+ if crumb_group == "s":
48
+ self.series_pks = crumb.get("pks", ())
49
+
50
+ return self.series_pks
51
+
52
+ def _get_reader_arc_pks(
53
+ self, arc, arc_pk_select_related, prefetch_related, arc_pk_rel
54
+ ):
55
+ """Get the nav filter."""
56
+ arc_pks = arc.get("pks", ())
57
+ if not arc_pks:
58
+ comic_pk = self.kwargs["pk"]
59
+ try:
60
+ arc_pk_qs = Comic.objects.filter(pk=comic_pk)
61
+ arc_pk_qs = arc_pk_qs.select_related(*arc_pk_select_related)
62
+ arc_pk_qs = arc_pk_qs.prefetch_related(*prefetch_related)
63
+ arc_pk = arc_pk_qs.values_list(arc_pk_rel, flat=True)[0]
64
+ except IndexError:
65
+ arc_pk = 0
66
+
67
+ if arc_pk_rel == "series__pk":
68
+ multi_arc_pks = self.get_series_pks_from_breadcrumbs()
69
+ if not arc_pk or arc_pk in multi_arc_pks:
70
+ arc_pks = multi_arc_pks
71
+ if not arc_pks:
72
+ arc_pks = (arc_pk,)
73
+
74
+ return arc_pks
75
+
76
+ def _get_comics_list(self):
77
+ """Get the reader naviation group filter."""
78
+ select_related = ("series",)
79
+ prefetch_related = ()
80
+
81
+ arc: Mapping = self.params.get("arc", {}) # type: ignore
82
+
83
+ arc_group = arc.get("group", "s")
84
+ if arc_group == "a":
85
+ # for story arcs
86
+ rel = "story_arc_numbers__story_arc"
87
+ fields = _COMIC_FIELDS
88
+ arc_pk_rel = "story_arc_numbers__story_arc__pk"
89
+ prefetch_related = (*prefetch_related, "story_arc_numbers__story_arc")
90
+ arc_index = F("story_arc_numbers__number")
91
+ ordering = ("arc_index", "date")
92
+ arc_pk_select_related = ()
93
+ elif arc_group == FOLDER_GROUP:
94
+ # folder mode
95
+ rel = "parent_folder"
96
+ fields = (*_COMIC_FIELDS, "parent_folder")
97
+ arc_pk_rel = "parent_folder__pk"
98
+ select_related = (*select_related, "parent_folder")
99
+ arc_index = Value(None, IntegerField())
100
+ ordering = ("path", "pk")
101
+ arc_pk_select_related = ("parent_folder",)
102
+ else:
103
+ # browser mode.
104
+ rel = "series"
105
+ fields = _COMIC_FIELDS
106
+ arc_pk_rel = "series__pk"
107
+ arc_index = Value(None, IntegerField())
108
+ ordering = ()
109
+ arc_pk_select_related = ("series",)
110
+
111
+ arc_pks = self._get_reader_arc_pks(
112
+ arc,
113
+ arc_pk_select_related,
114
+ prefetch_related,
115
+ arc_pk_rel,
116
+ )
117
+ nav_filter = {f"{rel}__in": arc_pks}
118
+ group_acl_filter = self.get_group_acl_filter(Comic)
119
+
120
+ qs = (
121
+ Comic.objects.filter(group_acl_filter)
122
+ .filter(**nav_filter)
123
+ .select_related(*select_related)
124
+ .prefetch_related(*prefetch_related)
125
+ .only(*fields)
126
+ .annotate(
127
+ issue_count=F("volume__issue_count"),
128
+ )
129
+ .annotate(
130
+ arc_pk=F(arc_pk_rel),
131
+ arc_index=arc_index,
132
+ )
133
+ .annotate(mtime=F("updated_at"))
134
+ )
135
+ qs = self.annotate_group_names(qs, Comic)
136
+ if arc_group == "s":
137
+ show = deepcopy(
138
+ self.get_from_session("show", session_key=self.BROWSER_SESSION_KEY)
139
+ )
140
+ show.pop("p", None)
141
+ show.pop("i", None)
142
+ qs, comic_sort_names = self.alias_sort_names(
143
+ qs, Comic, pks=arc_pks, model_group="i", show=show
144
+ )
145
+ ordering = (
146
+ *comic_sort_names,
147
+ "issue_number",
148
+ "issue_suffix",
149
+ "sort_name",
150
+ )
151
+ return qs.order_by(*ordering), arc_group
152
+
153
+ def _append_with_settings(self, book, bookmark_filter):
154
+ """Append bookmark to book list."""
155
+ book.settings = (
156
+ Bookmark.objects.filter(**bookmark_filter, comic=book)
157
+ .only(*_SETTINGS_ATTRS)
158
+ .first()
159
+ )
160
+ return book
161
+
162
+ def get_book_collection(self):
163
+ """Get the -1, +1 window around the current issue.
164
+
165
+ Uses iteration in python. There are some complicated ways of
166
+ doing this with __gt[0] & __lt[0] in the db, but I think they
167
+ might be even more expensive.
168
+
169
+ Yields 1 to 3 books
170
+ """
171
+ comics, arc_group = self._get_comics_list()
172
+ bookmark_filter = self.get_bookmark_filter()
173
+ books = {}
174
+ prev_book = None
175
+ pk = self.kwargs.get("pk")
176
+ for index, book in enumerate(comics):
177
+ if books:
178
+ # after match set next comic and break
179
+ books["next"] = self._append_with_settings(book, bookmark_filter)
180
+ break
181
+ if book.pk == pk:
182
+ # first match. set previous and current comic
183
+ if prev_book:
184
+ books["prev"] = self._append_with_settings(
185
+ prev_book, bookmark_filter
186
+ )
187
+ # create extra current book attrs:
188
+ if book.arc_index is None: # type: ignore
189
+ book.arc_index = index + 1 # type: ignore
190
+ book.filename = book.get_filename() # type: ignore
191
+ book.arc_group = arc_group
192
+ book.arc_count = comics.count()
193
+ books["current"] = self._append_with_settings(book, bookmark_filter)
194
+ else:
195
+ # Haven't matched yet, so set the previous comic
196
+ prev_book = book
197
+ return books
198
+
199
+ #####
@@ -1,7 +1,5 @@
1
1
  """Views for reading comic books."""
2
2
 
3
- from typing import ClassVar
4
-
5
3
  from comicbox.box import Comicbox
6
4
  from django.http import HttpResponse
7
5
  from drf_spectacular.types import OpenApiTypes
@@ -11,12 +9,11 @@ from rest_framework.negotiation import BaseContentNegotiation
11
9
 
12
10
  from codex.logger.logging import get_logger
13
11
  from codex.models import Comic
14
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
15
12
  from codex.views.bookmark import BookmarkBaseView
16
13
  from codex.views.const import FALSY
17
14
 
18
15
  LOG = get_logger(__name__)
19
- PDF_MIME_TYPE = "application/pdf"
16
+ _PDF_MIME_TYPE = "application/pdf"
20
17
 
21
18
 
22
19
  class IgnoreClientContentNegotiation(BaseContentNegotiation):
@@ -43,7 +40,6 @@ class ReaderPageView(BookmarkBaseView):
43
40
  X_MOZ_PRE_HEADERS = frozenset({"prefetch", "preload", "prerender", "subresource"})
44
41
  content_type = "image/jpeg"
45
42
  content_negotiation_class = IgnoreClientContentNegotiation # type: ignore
46
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type:ignore
47
43
 
48
44
  def _update_bookmark(self):
49
45
  """Update the bookmark if the bookmark param was passed."""
@@ -67,7 +63,7 @@ class ReaderPageView(BookmarkBaseView):
67
63
  page = self.kwargs.get("page")
68
64
  to_pixmap = self.request.GET.get("pixmap", "").lower() not in FALSY
69
65
  if comic.file_type == Comic.FileType.PDF.value and not to_pixmap:
70
- content_type = PDF_MIME_TYPE
66
+ content_type = _PDF_MIME_TYPE
71
67
  else:
72
68
  content_type = self.content_type
73
69
  with Comicbox(comic.path) as cb:
@@ -81,7 +77,7 @@ class ReaderPageView(BookmarkBaseView):
81
77
  ],
82
78
  responses={
83
79
  (200, content_type): OpenApiTypes.BINARY,
84
- (200, PDF_MIME_TYPE): OpenApiTypes.BINARY,
80
+ (200, _PDF_MIME_TYPE): OpenApiTypes.BINARY,
85
81
  },
86
82
  )
87
83
  def get(self, *_args, **_kwargs):