codex 1.6.0a6__py3-none-any.whl → 1.6.0a8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of codex might be problematic. Click here for more details.

Files changed (612) hide show
  1. codex/exceptions.py +1 -0
  2. codex/integrity.py +4 -4
  3. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +56 -86
  4. codex/librarian/importer/cache.py +180 -0
  5. codex/librarian/importer/const.py +15 -6
  6. codex/librarian/importer/create_comics.py +102 -17
  7. codex/librarian/importer/create_covers.py +98 -0
  8. codex/librarian/importer/create_fks.py +73 -170
  9. codex/librarian/importer/deleted.py +59 -46
  10. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  11. codex/librarian/importer/failed_imports.py +41 -62
  12. codex/librarian/importer/importer.py +96 -0
  13. codex/librarian/importer/importerd.py +71 -395
  14. codex/librarian/importer/init.py +265 -0
  15. codex/librarian/importer/link_comics.py +26 -81
  16. codex/librarian/importer/link_covers.py +73 -0
  17. codex/librarian/importer/moved.py +72 -96
  18. codex/librarian/importer/query_covers.py +52 -0
  19. codex/librarian/importer/query_fks.py +128 -172
  20. codex/librarian/importer/status.py +2 -39
  21. codex/librarian/importer/tasks.py +22 -13
  22. codex/librarian/janitor/cleanup.py +2 -2
  23. codex/librarian/janitor/janitor.py +11 -1
  24. codex/librarian/librariand.py +8 -2
  25. codex/migrations/0027_import_order_and_covers.py +345 -0
  26. codex/models/comic.py +1 -2
  27. codex/models/groups.py +3 -2
  28. codex/models/paths.py +0 -1
  29. codex/permissions.py +1 -1
  30. codex/search/query.py +25 -2
  31. codex/search/writing.py +1 -1
  32. codex/serializers/admin.py +1 -2
  33. codex/serializers/browser/filters.py +8 -101
  34. codex/serializers/browser/mixins.py +2 -3
  35. codex/serializers/browser/page.py +2 -2
  36. codex/serializers/browser/settings.py +4 -3
  37. codex/serializers/choices.py +10 -1
  38. codex/serializers/fields.py +123 -0
  39. codex/serializers/mtime.py +25 -0
  40. codex/serializers/opds/v2.py +7 -11
  41. codex/serializers/opds/v2_unused.py +5 -3
  42. codex/serializers/reader.py +5 -2
  43. codex/serializers/route.py +7 -3
  44. codex/settings/settings.py +2 -2
  45. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +1 -0
  46. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  47. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  48. codex/static_root/assets/VCheckbox-DI-DgMb4.js +1 -0
  49. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  50. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  51. codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js → VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js} +1 -1
  52. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  53. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  54. codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.js → VCheckboxBtn-B8Z4NA-t.js} +1 -1
  55. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  56. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  57. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +1 -0
  58. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  59. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  60. codex/static_root/assets/VCombobox-D1_0lCri.js +1 -0
  61. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  62. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  63. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js +1 -0
  64. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  65. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  66. codex/static_root/assets/VDialog-opmV0YZz.js +1 -0
  67. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  68. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  69. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +1 -0
  70. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  71. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  72. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +1 -0
  73. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  74. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  75. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js +1 -0
  76. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  77. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  78. codex/static_root/assets/VRadioGroup-BGa6_q5a.js +1 -0
  79. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  80. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  81. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  82. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  83. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  84. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  85. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  86. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  87. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +1 -0
  88. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  89. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  90. codex/static_root/assets/VSelect-sIYW_-ud.js +1 -0
  91. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  92. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  93. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js +1 -0
  94. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  95. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  96. codex/static_root/assets/VSelectionControl-C8DP9rgp.js +1 -0
  97. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  98. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  99. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +1 -0
  100. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  101. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  102. codex/static_root/assets/VSlideGroup-BRYhkLH8.js +1 -0
  103. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  104. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  105. codex/static_root/assets/{VTable-C0gAriFq.fa6e4b60d4ef.js → VTable-D1esKtlw.52165930e3b9.js} +1 -1
  106. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  107. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  108. codex/static_root/assets/{VTable-C0gAriFq.js → VTable-D1esKtlw.js} +1 -1
  109. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  110. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  111. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +1 -0
  112. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  113. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  114. codex/static_root/assets/VTextField-BnGscKcY.js +1 -0
  115. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  116. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  117. codex/static_root/assets/{VWindowItem-CFhKCj49.b2458002a937.js → VWindowItem-B0KokcAk.ea94224aecb9.js} +1 -1
  118. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  119. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  120. codex/static_root/assets/{VWindowItem-CFhKCj49.js → VWindowItem-B0KokcAk.js} +1 -1
  121. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  122. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  123. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +1 -0
  124. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  125. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  126. codex/static_root/assets/admin-Y0bL21AB.js +1 -0
  127. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  128. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  129. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +30 -0
  130. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  131. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  132. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +30 -0
  133. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  134. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  135. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
  136. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  137. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
  138. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
  139. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  140. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
  141. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +1 -0
  142. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  143. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  144. codex/static_root/assets/browser-CMAtBCLx.css +1 -0
  145. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  146. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  147. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +1 -0
  148. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  149. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  150. codex/static_root/assets/browser-DoJk8uvb.js +1 -0
  151. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  152. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  153. codex/static_root/assets/{change-password-dialog-JYM9aAR9.js → change-password-dialog-CH4xbjJa.b749aa6ea07e.js} +1 -1
  154. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  155. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  156. codex/static_root/assets/{change-password-dialog-JYM9aAR9.f71d378f4fa6.js → change-password-dialog-CH4xbjJa.js} +1 -1
  157. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  158. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  159. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.515858dd21e8.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
  160. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
  161. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  162. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.js → confirm-dialog-DkU0twE6.js} +1 -1
  163. codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
  164. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  165. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
  166. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  167. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
  168. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  169. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +1 -0
  170. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  171. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  172. codex/static_root/assets/datetime-column-BfW_j_3Q.js +1 -0
  173. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  174. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  175. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  176. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  177. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +1 -0
  178. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  179. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  180. codex/static_root/assets/filter-DjxN78cK.js +1 -0
  181. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  182. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  183. codex/static_root/assets/{flag-tab-Ci_XcDT0.a5ea640a08d6.js → flag-tab-D4ZNSbMR.e763a9937701.js} +1 -1
  184. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  185. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  186. codex/static_root/assets/{flag-tab-Ci_XcDT0.js → flag-tab-D4ZNSbMR.js} +1 -1
  187. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  188. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  189. codex/static_root/assets/{group-tab-DFma2R-E.879c37b15bbd.js → group-tab-DucpNoBs.a6f37ae940c0.js} +1 -1
  190. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  191. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  192. codex/static_root/assets/{group-tab-DFma2R-E.js → group-tab-DucpNoBs.js} +1 -1
  193. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  194. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  195. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js +1 -0
  196. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  197. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  198. codex/static_root/assets/http-error-Cj0hTq-c.js +1 -0
  199. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  200. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  201. codex/static_root/assets/{library-tab-V8DUGoHN.js → library-tab-CzksdS3Q.566acdb45dec.js} +1 -1
  202. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  203. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  204. codex/static_root/assets/{library-tab-V8DUGoHN.809c548ff347.js → library-tab-CzksdS3Q.js} +1 -1
  205. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  206. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  207. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +32 -0
  208. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  209. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  210. codex/static_root/assets/main-DX-k2724.js +32 -0
  211. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  212. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  213. codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
  214. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  215. codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
  216. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
  217. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  218. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
  219. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +1 -0
  220. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  221. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  222. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +1 -0
  223. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  224. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  225. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  226. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  227. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  228. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  229. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  230. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  231. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js +305 -0
  232. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  233. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  234. codex/static_root/assets/pdf-doc-GkMk4o5L.js +305 -0
  235. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  236. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  237. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  238. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  239. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  240. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  241. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  242. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  243. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +2 -0
  244. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  245. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  246. codex/static_root/assets/reader-Cl6mmQB_.js +2 -0
  247. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  248. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  249. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +1 -0
  250. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  251. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  252. codex/static_root/assets/relation-chips-CZ8VvR4y.js +1 -0
  253. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  254. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  255. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +2 -0
  256. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  257. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  258. codex/static_root/assets/settings-drawer-DyS0A2pN.js +2 -0
  259. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  260. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  261. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +1 -0
  262. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  263. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  264. codex/static_root/assets/stats-tab-R3Ekobfm.js +1 -0
  265. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  266. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  267. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +1 -0
  268. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  269. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  270. codex/static_root/assets/task-tab-BhLDK_7Q.js +1 -0
  271. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  272. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  273. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +1 -0
  274. codex/static_root/assets/to-case-BR2frjFx.js +1 -0
  275. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +1 -0
  276. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  277. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  278. codex/static_root/assets/unauthorized-BbXjAe5l.js +1 -0
  279. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  280. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  281. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js +1 -0
  282. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  283. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  284. codex/static_root/assets/user-tab-DxITIl2d.js +1 -0
  285. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  286. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  287. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  288. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  289. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  290. codex/static_root/js/choices-admin.json +1 -1
  291. codex/static_root/js/choices-admin.json.br +0 -0
  292. codex/static_root/js/choices-admin.json.gz +0 -0
  293. codex/static_root/js/choices.a0aaaa6c7ef7.json +1 -0
  294. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  295. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  296. codex/static_root/js/choices.json +1 -1
  297. codex/static_root/js/choices.json.br +0 -0
  298. codex/static_root/js/choices.json.gz +0 -0
  299. codex/static_root/manifest.16d52597ef50.json +629 -0
  300. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  301. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  302. codex/static_root/manifest.json +275 -288
  303. codex/static_root/manifest.json.br +0 -0
  304. codex/static_root/manifest.json.gz +0 -0
  305. codex/static_root/staticfiles.json +1 -1
  306. codex/urls/api/auth.py +2 -1
  307. codex/urls/api/browser.py +13 -4
  308. codex/urls/api/reader.py +3 -9
  309. codex/urls/api/v3.py +2 -0
  310. codex/urls/opds/binary.py +1 -1
  311. codex/util.py +10 -0
  312. codex/views/admin/api_key.py +2 -6
  313. codex/views/admin/auth.py +28 -0
  314. codex/views/admin/flag.py +2 -7
  315. codex/views/admin/group.py +2 -6
  316. codex/views/admin/library.py +7 -20
  317. codex/views/admin/stats.py +7 -66
  318. codex/views/admin/tasks.py +24 -11
  319. codex/views/admin/user.py +3 -9
  320. codex/views/auth.py +30 -53
  321. codex/views/bookmark.py +7 -11
  322. codex/views/browser/{browser_annotations.py → annotations.py} +90 -181
  323. codex/views/browser/base.py +23 -88
  324. codex/views/browser/breadcrumbs.py +202 -0
  325. codex/views/browser/browser.py +64 -342
  326. codex/views/browser/choices.py +22 -54
  327. codex/views/browser/cover.py +177 -0
  328. codex/views/browser/filters/annotations.py +24 -23
  329. codex/views/browser/filters/bookmark.py +9 -13
  330. codex/views/browser/filters/field.py +5 -5
  331. codex/views/browser/filters/group.py +18 -10
  332. codex/views/browser/filters/search.py +30 -18
  333. codex/views/browser/metadata.py +22 -31
  334. codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
  335. codex/views/browser/paginate.py +106 -0
  336. codex/views/browser/{session.py → settings.py} +4 -3
  337. codex/views/browser/title.py +48 -0
  338. codex/views/browser/validate.py +158 -0
  339. codex/views/const.py +109 -0
  340. codex/views/download.py +5 -8
  341. codex/views/frontend.py +4 -2
  342. codex/views/mtime.py +72 -0
  343. codex/views/opds/auth.py +17 -0
  344. codex/views/opds/authentication_v1.py +46 -45
  345. codex/views/opds/binary.py +5 -10
  346. codex/views/opds/const.py +13 -0
  347. codex/views/opds/urls.py +6 -4
  348. codex/views/opds/util.py +4 -12
  349. codex/views/opds/v1/entry/links.py +18 -33
  350. codex/views/opds/v1/facets.py +7 -7
  351. codex/views/opds/v1/feed.py +52 -40
  352. codex/views/opds/v1/links.py +7 -5
  353. codex/views/opds/v1/opensearch_v1.py +2 -6
  354. codex/views/opds/v2/feed.py +22 -23
  355. codex/views/opds/v2/links.py +4 -2
  356. codex/views/opds/v2/publications.py +34 -54
  357. codex/views/opds/v2/top_links.py +2 -2
  358. codex/views/public.py +36 -0
  359. codex/views/reader/arcs.py +101 -0
  360. codex/views/reader/books.py +199 -0
  361. codex/views/reader/page.py +3 -7
  362. codex/views/reader/reader.py +56 -301
  363. codex/views/reader/{session.py → settings.py} +4 -2
  364. codex/views/session.py +77 -103
  365. codex/views/settings.py +34 -0
  366. codex/views/template.py +3 -3
  367. codex/views/util.py +63 -0
  368. codex/views/version.py +2 -2
  369. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/METADATA +19 -14
  370. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/RECORD +373 -362
  371. codex/librarian/importer/covers.py +0 -47
  372. codex/librarian/importer/update_comics.py +0 -85
  373. codex/migrations/0027_sort_name.py +0 -139
  374. codex/migrations/0028_custom_covers.py +0 -166
  375. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  376. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js +0 -1
  377. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
  378. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
  379. codex/static_root/assets/VCheckbox-BS5fdM4M.js +0 -1
  380. codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
  381. codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
  382. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
  383. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
  384. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
  385. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
  386. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +0 -1
  387. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
  388. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
  389. codex/static_root/assets/VCombobox-jg7SI5mn.js +0 -1
  390. codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
  391. codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
  392. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js +0 -1
  393. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
  394. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
  395. codex/static_root/assets/VDialog-DiixpkQL.js +0 -1
  396. codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
  397. codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
  398. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js +0 -1
  399. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
  400. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
  401. codex/static_root/assets/VExpansionPanels-Db5mPFue.js +0 -1
  402. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
  403. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
  404. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js +0 -1
  405. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
  406. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
  407. codex/static_root/assets/VRadioGroup-DULWiMSI.js +0 -1
  408. codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
  409. codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
  410. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +0 -1
  411. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
  412. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
  413. codex/static_root/assets/VSelect-BnoCfCLY.js +0 -1
  414. codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
  415. codex/static_root/assets/VSelect-BnoCfCLY.js.gz +0 -0
  416. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  417. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  418. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  419. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  420. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  421. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  422. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js +0 -1
  423. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
  424. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
  425. codex/static_root/assets/VSelectionControl-BSL0qeL9.js +0 -1
  426. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
  427. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
  428. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js +0 -1
  429. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
  430. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
  431. codex/static_root/assets/VSlideGroup-BQtz8h9p.js +0 -1
  432. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
  433. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
  434. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
  435. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
  436. codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
  437. codex/static_root/assets/VTable-C0gAriFq.js.gz +0 -0
  438. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js +0 -1
  439. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
  440. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
  441. codex/static_root/assets/VTextField-Da_Hs7ls.js +0 -1
  442. codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
  443. codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
  444. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
  445. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
  446. codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
  447. codex/static_root/assets/VWindowItem-CFhKCj49.js.gz +0 -0
  448. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js +0 -1
  449. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
  450. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
  451. codex/static_root/assets/admin-BpGFsvOH.js +0 -1
  452. codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
  453. codex/static_root/assets/admin-BpGFsvOH.js.gz +0 -0
  454. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
  455. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
  456. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +0 -30
  457. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
  458. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
  459. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +0 -30
  460. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
  461. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
  462. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +0 -1
  463. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
  464. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
  465. codex/static_root/assets/browser-CRPvEdqv.js +0 -1
  466. codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
  467. codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
  468. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +0 -1
  469. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
  470. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
  471. codex/static_root/assets/browser-Zqc9cR6T.css +0 -1
  472. codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
  473. codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
  474. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
  475. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
  476. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
  477. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
  478. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
  479. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
  480. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
  481. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
  482. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js +0 -1
  483. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
  484. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
  485. codex/static_root/assets/datetime-column-BT1MSP82.js +0 -1
  486. codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
  487. codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
  488. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  489. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  490. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  491. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  492. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js +0 -1
  493. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
  494. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
  495. codex/static_root/assets/filter-frSNsexx.js +0 -1
  496. codex/static_root/assets/filter-frSNsexx.js.br +0 -0
  497. codex/static_root/assets/filter-frSNsexx.js.gz +0 -0
  498. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
  499. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
  500. codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
  501. codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
  502. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
  503. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
  504. codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
  505. codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
  506. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js +0 -1
  507. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
  508. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
  509. codex/static_root/assets/http-error-B3fQGxpt.js +0 -1
  510. codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
  511. codex/static_root/assets/http-error-B3fQGxpt.js.gz +0 -0
  512. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
  513. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  514. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  515. codex/static_root/assets/index-DUjeNg4x.js +0 -1
  516. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  517. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  518. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
  519. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
  520. codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
  521. codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
  522. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js +0 -32
  523. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
  524. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
  525. codex/static_root/assets/main-Cavvqkyp.js +0 -32
  526. codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
  527. codex/static_root/assets/main-Cavvqkyp.js.gz +0 -0
  528. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  529. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  530. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css +0 -1
  531. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
  532. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
  533. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css +0 -1
  534. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
  535. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
  536. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js +0 -1
  537. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
  538. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
  539. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +0 -1
  540. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
  541. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
  542. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js +0 -35
  543. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
  544. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
  545. codex/static_root/assets/pdf-doc-4PKLN-Z3.js +0 -35
  546. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
  547. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
  548. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js +0 -2
  549. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
  550. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
  551. codex/static_root/assets/reader-C39uGGxn.js +0 -2
  552. codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
  553. codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
  554. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css +0 -1
  555. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
  556. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
  557. codex/static_root/assets/reader-N2Ei_y2K.css +0 -1
  558. codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
  559. codex/static_root/assets/reader-N2Ei_y2K.css.gz +0 -0
  560. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js +0 -1
  561. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
  562. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
  563. codex/static_root/assets/relation-chips-CAC5ytqH.js +0 -1
  564. codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
  565. codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
  566. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js +0 -2
  567. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
  568. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
  569. codex/static_root/assets/settings-drawer-CT73ieiV.js +0 -2
  570. codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
  571. codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
  572. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js +0 -1
  573. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
  574. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
  575. codex/static_root/assets/stats-tab-C6qAnV6-.js +0 -1
  576. codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
  577. codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
  578. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +0 -1
  579. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
  580. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
  581. codex/static_root/assets/task-tab-JcG2vvgt.js +0 -1
  582. codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
  583. codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
  584. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
  585. codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
  586. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js +0 -1
  587. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
  588. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
  589. codex/static_root/assets/unauthorized-28bmiwAU.js +0 -1
  590. codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
  591. codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
  592. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js +0 -1
  593. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
  594. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
  595. codex/static_root/assets/user-tab-BOq4BMMc.js +0 -1
  596. codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
  597. codex/static_root/assets/user-tab-BOq4BMMc.js.gz +0 -0
  598. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  599. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  600. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  601. codex/static_root/js/choices.23fedac2b9ba.json +0 -1
  602. codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
  603. codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
  604. codex/static_root/manifest.ad9da9714e46.json +0 -642
  605. codex/static_root/manifest.ad9da9714e46.json.br +0 -0
  606. codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
  607. codex/views/browser/browser_breadcrumbs.py +0 -175
  608. codex/views/browser/const.py +0 -3
  609. codex/views/cover.py +0 -78
  610. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
  611. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
  612. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,106 @@
1
+ """Browser pagination."""
2
+
3
+ from math import ceil
4
+
5
+ from django.core.paginator import EmptyPage, Paginator
6
+
7
+ from codex.logger.logging import get_logger
8
+ from codex.models import (
9
+ Comic,
10
+ Folder,
11
+ )
12
+ from codex.views.browser.annotations import BrowserAnnotationsView
13
+ from codex.views.const import (
14
+ MAX_OBJ_PER_PAGE,
15
+ )
16
+
17
+ LOG = get_logger(__name__)
18
+
19
+
20
+ class BrowserPaginateView(BrowserAnnotationsView):
21
+ """Browser pagination."""
22
+
23
+ def _check_page_in_bounds(self, total_count):
24
+ """Redirect page out of bounds."""
25
+ pks = self.kwargs.get("pks")
26
+ page = self.kwargs.get("page", 1)
27
+ num_pages = ceil(total_count / MAX_OBJ_PER_PAGE)
28
+ if (not pks and page == 1) or (page >= 1 and page <= num_pages):
29
+ # Don't redirect if on the root page for the group.
30
+ # Or page within valid range.
31
+ return num_pages
32
+
33
+ # Adjust route mask for redirect
34
+ group = self.kwargs.get("group")
35
+ if num_pages and page > 1:
36
+ # Try page 1 first.
37
+ new_page = num_pages if num_pages and page > num_pages else 1
38
+ pks = pks if pks else "0"
39
+ else:
40
+ # Redirect to root for the group.
41
+ pks = "0"
42
+ new_page = 1
43
+
44
+ reason = f"{page=} does not exist!"
45
+ route_changes = {"group": group, "pks": pks, "page": new_page}
46
+ return self.raise_redirect(reason, route_mask=route_changes)
47
+
48
+ def _paginate_section(self, model, qs, page):
49
+ """Paginate a group or Comic section."""
50
+ orphans = 0 if self.model_group == "f" or self.params.get("q") else 5
51
+ paginator = Paginator(qs, MAX_OBJ_PER_PAGE, orphans=orphans)
52
+ try:
53
+ qs = paginator.page(page).object_list
54
+ except EmptyPage:
55
+ if model != Folder:
56
+ model_name = self.model.__name__ if self.model else "NO_MODEL"
57
+ LOG.warning(f"No {model_name}s on page {page}")
58
+ qs = model.objects.none()
59
+
60
+ return qs
61
+
62
+ def _paginate_groups(self, group_qs, total_group_count):
63
+ """Paginate the group object list before books."""
64
+ if total_group_count:
65
+ page = self.kwargs.get("page", 1)
66
+ page_group_qs = self._paginate_section(self.model, group_qs, page)
67
+ else:
68
+ page_group_qs = self.model.objects.none() # type: ignore
69
+ return page_group_qs
70
+
71
+ def _paginate_books(self, book_qs, total_group_count, page_obj_count):
72
+ """Paginate the book object list based on how many group/folders are showing."""
73
+ if page_obj_count >= MAX_OBJ_PER_PAGE:
74
+ # No books for this page
75
+ page_book_qs = Comic.objects.none()
76
+ else:
77
+ group_remainder = total_group_count % MAX_OBJ_PER_PAGE
78
+ if page_obj_count:
79
+ # There are books after the groups on the same page
80
+ # Manually add books without the paginator
81
+ book_limit = MAX_OBJ_PER_PAGE - group_remainder
82
+ page_book_qs = book_qs[:book_limit]
83
+ else:
84
+ # There are books after the groups on a new page
85
+ if group_remainder:
86
+ book_offset = MAX_OBJ_PER_PAGE - group_remainder
87
+ page_book_qs = book_qs[book_offset:]
88
+ else:
89
+ page_book_qs = book_qs
90
+
91
+ num_group_and_mixed_pages = ceil(total_group_count / MAX_OBJ_PER_PAGE)
92
+ book_only_page = self.kwargs.get("page", 1) - num_group_and_mixed_pages
93
+ page_book_qs = self._paginate_section(
94
+ Comic, page_book_qs, book_only_page
95
+ )
96
+ return page_book_qs
97
+
98
+ def paginate(self, group_qs, book_qs, group_count, book_count):
99
+ """Paginate the queryset into a group and book object lists."""
100
+ num_pages = self._check_page_in_bounds(group_count + book_count)
101
+ page_group_qs = self._paginate_groups(group_qs, group_count)
102
+ page_group_count = page_group_qs.count()
103
+ page_book_qs = self._paginate_books(book_qs, group_count, page_group_count)
104
+ page_book_count = page_book_qs.count()
105
+
106
+ return page_group_qs, page_book_qs, num_pages, page_group_count, page_book_count
@@ -1,12 +1,13 @@
1
1
  """Browser session view."""
2
2
 
3
3
  from codex.serializers.browser.settings import BrowserSettingsSerializer
4
- from codex.views.session import BrowserSessionViewBase, SessionViewBase
4
+ from codex.views.settings import SettingsView
5
5
 
6
6
 
7
- class BrowserSessionView(BrowserSessionViewBase, SessionViewBase):
7
+ class BrowserSettingsView(SettingsView):
8
8
  """Get Browser Settings."""
9
9
 
10
10
  # Put Browser Settings is normally done through BrowserView.get()
11
-
12
11
  serializer_class = BrowserSettingsSerializer # type: ignore
12
+
13
+ SESSION_KEY = SettingsView.BROWSER_SESSION_KEY
@@ -0,0 +1,48 @@
1
+ """Browser title."""
2
+
3
+ from codex.models import BrowserGroupModel, Comic, Volume
4
+ from codex.views.browser.breadcrumbs import BrowserBreadcrumbsView
5
+
6
+
7
+ class BrowserTitleView(BrowserBreadcrumbsView):
8
+ """Browser title methods."""
9
+
10
+ def __init__(self, *args, **kwargs):
11
+ """Set params for the type checker."""
12
+ super().__init__(*args, **kwargs)
13
+ self.group_instance: BrowserGroupModel | None = None
14
+
15
+ def _get_root_group_name(self):
16
+ if not self.model:
17
+ reason = "No model set in browser"
18
+ raise ValueError(reason)
19
+ plural = self.model._meta.verbose_name_plural
20
+ if not plural:
21
+ reason = f"No plural name for {self.model}"
22
+ raise ValueError(reason)
23
+ return plural.capitalize(), 0
24
+
25
+ def _get_group_name(self):
26
+ group_count = 0
27
+ if gi := self.group_instance:
28
+ if isinstance(gi, Volume):
29
+ group_count = gi.series.volume_count
30
+ elif isinstance(gi, Comic):
31
+ group_count = gi.volume.issue_count
32
+ group_name = gi.name
33
+ else:
34
+ group_name = ""
35
+ return group_name, group_count
36
+
37
+ def get_browser_page_title(self):
38
+ """Get the proper title for this browse level."""
39
+ pks = self.kwargs.get("pks")
40
+ if not pks:
41
+ group_name, group_count = self._get_root_group_name()
42
+ else:
43
+ group_name, group_count = self._get_group_name()
44
+
45
+ return {
46
+ "group_name": group_name,
47
+ "group_count": group_count,
48
+ }
@@ -0,0 +1,158 @@
1
+ """Browser Settings and URL Validation."""
2
+
3
+ from copy import deepcopy
4
+ from types import MappingProxyType
5
+
6
+ from codex.exceptions import SeeOtherRedirectError
7
+ from codex.logger.logging import get_logger
8
+ from codex.serializers.choices import DEFAULTS
9
+ from codex.views.browser.base import BrowserBaseView
10
+ from codex.views.const import (
11
+ COMIC_GROUP,
12
+ FOLDER_GROUP,
13
+ ROOT_GROUP,
14
+ STORY_ARC_GROUP,
15
+ )
16
+
17
+ LOG = get_logger(__name__)
18
+
19
+
20
+ class BrowserValidateView(BrowserBaseView):
21
+ """Browser Settings and URL Validation."""
22
+
23
+ DEFAULT_ROUTE = MappingProxyType(
24
+ {
25
+ "name": "browser",
26
+ "params": deepcopy(DEFAULTS["breadcrumbs"][0]),
27
+ }
28
+ )
29
+
30
+ def raise_redirect(self, reason, route_mask=None, settings_mask=None):
31
+ """Redirect the client to a valid group url."""
32
+ route = deepcopy(dict(self.DEFAULT_ROUTE))
33
+ if route_mask:
34
+ route["params"].update(route_mask) # type: ignore
35
+ settings = deepcopy(dict(self.params))
36
+ if settings_mask:
37
+ settings.update(settings_mask)
38
+ detail = {"route": route, "settings": settings, "reason": reason}
39
+ raise SeeOtherRedirectError(detail=detail)
40
+
41
+ def _get_valid_browse_top_groups(self):
42
+ """Get valid top groups for the current settings.
43
+
44
+ Valid top groups are determined by the Browser Settings.
45
+ """
46
+ valid_top_groups = []
47
+
48
+ show = self.params["show"]
49
+ for nav_group, allowed in show.items():
50
+ if allowed:
51
+ valid_top_groups.append(nav_group)
52
+ # Issues is always a valid top group
53
+ valid_top_groups += [COMIC_GROUP]
54
+
55
+ return valid_top_groups
56
+
57
+ def _validate_top_group(self, valid_top_groups):
58
+ nav_group = self.kwargs.get("group")
59
+ top_group = self.params.get("top_group")
60
+ if top_group not in valid_top_groups:
61
+ reason = f"top_group {top_group} not in valid nav groups, changed to "
62
+ if nav_group in valid_top_groups:
63
+ valid_top_group = nav_group
64
+ reason += "nav group: "
65
+ else:
66
+ valid_top_group = valid_top_groups[0]
67
+ reason += "first valid top group "
68
+ reason += valid_top_group
69
+ pks = self.kwargs.get("pks", ())
70
+ page = self.kwargs["page"]
71
+ route = {"group": nav_group, "pks": pks, "page": page}
72
+ settings_mask = {"top_group": valid_top_group}
73
+ self.raise_redirect(reason, route, settings_mask)
74
+
75
+ def set_valid_browse_nav_groups(self, valid_top_groups):
76
+ """Get valid nav groups for the current settings.
77
+
78
+ Valid nav groups are the top group and below that are also
79
+ enabled in browser settings.
80
+
81
+ May always navigate to root 'r' nav group.
82
+ """
83
+ top_group = self.params["top_group"]
84
+ nav_group = self.kwargs["group"]
85
+ valid_nav_groups = [ROOT_GROUP]
86
+
87
+ for possible_index, possible_nav_group in enumerate(valid_top_groups):
88
+ if top_group == possible_nav_group:
89
+ # all the nav groups past this point,
90
+ # 'c' is obscured by the web reader url, but valid for opds
91
+ tail_top_groups = valid_top_groups[possible_index:]
92
+ valid_nav_groups += tail_top_groups
93
+
94
+ if nav_group not in valid_nav_groups:
95
+ reason = (
96
+ f"Nav group {nav_group} unavailable, "
97
+ f"redirect to {ROOT_GROUP}"
98
+ )
99
+ self.raise_redirect(reason)
100
+ break
101
+ self.valid_nav_groups = tuple(valid_nav_groups)
102
+
103
+ def _validate_folder_settings(self):
104
+ """Check that all the view variables for folder mode are set right."""
105
+ # Check folder view admin flag
106
+ if not self.admin_flags["folder_view"]:
107
+ reason = "folder view disabled"
108
+ valid_top_groups = self._get_valid_browse_top_groups()
109
+ settings_mask = {"top_group": valid_top_groups[0]}
110
+ self.raise_redirect(reason, settings_mask=settings_mask)
111
+
112
+ valid_top_groups = (FOLDER_GROUP,)
113
+ self._validate_top_group(valid_top_groups)
114
+ self.valid_nav_groups = valid_top_groups
115
+
116
+ def _validate_browser_group_settings(self):
117
+ """Check that all the view variables for browser mode are set right."""
118
+ # Validate Browser top_group
119
+ # Change top_group if its not in the valid top groups
120
+ valid_top_groups = self._get_valid_browse_top_groups()
121
+ self._validate_top_group(valid_top_groups)
122
+
123
+ # Validate pks
124
+ nav_group = self.kwargs["group"]
125
+ pks = self.kwargs["pks"]
126
+ if nav_group == ROOT_GROUP and (pks and 0 not in pks):
127
+ # r never has pks
128
+ reason = f"Redirect r with {pks=} to pks 0"
129
+ self.raise_redirect(reason)
130
+
131
+ # Validate Browser nav_group
132
+ # Redirect if nav group is wrong
133
+ self.set_valid_browse_nav_groups(valid_top_groups)
134
+
135
+ def _validate_story_arc_settings(self):
136
+ """Validate story arc settings."""
137
+ valid_top_groups = (STORY_ARC_GROUP,)
138
+ self._validate_top_group(valid_top_groups)
139
+ self.valid_nav_groups = valid_top_groups
140
+
141
+ def validate_settings(self):
142
+ """Validate group and top group settings."""
143
+ group = self.kwargs["group"]
144
+ if group == FOLDER_GROUP:
145
+ self._validate_folder_settings()
146
+ elif group == STORY_ARC_GROUP:
147
+ self._validate_story_arc_settings()
148
+ else:
149
+ self._validate_browser_group_settings()
150
+
151
+ # Validate order
152
+ if self.order_key == "filename" and not self.admin_flags["folder_view"]:
153
+ pks = self.kwargs["pks"]
154
+ page = self.kwargs["page"]
155
+ route_changes = {"group": group, "pks": pks, "page": page}
156
+ reason = "order by filename not allowed by admin flag."
157
+ settings_mask = {"order_by": "sort_name"}
158
+ self.raise_redirect(reason, route_changes, settings_mask)
codex/views/const.py CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  from types import MappingProxyType
4
4
 
5
+ from django.contrib.auth.models import Group, User
6
+ from django.contrib.sessions.models import Session
7
+
8
+ from codex.models import (
9
+ AgeRating,
10
+ BrowserGroupModel,
11
+ Character,
12
+ Comic,
13
+ Contributor,
14
+ ContributorPerson,
15
+ ContributorRole,
16
+ Country,
17
+ Folder,
18
+ Genre,
19
+ Identifier,
20
+ IdentifierType,
21
+ Imprint,
22
+ Language,
23
+ Library,
24
+ Location,
25
+ OriginalFormat,
26
+ Publisher,
27
+ ScanInfo,
28
+ Series,
29
+ SeriesGroup,
30
+ StoryArc,
31
+ StoryArcNumber,
32
+ Tag,
33
+ Tagger,
34
+ Team,
35
+ Volume,
36
+ )
5
37
  from codex.settings.settings import CODEX_PATH
6
38
 
7
39
  FALSY = {None, "", "false", "0", False}
@@ -18,3 +50,80 @@ MISSING_COVER_NAME_MAP = MappingProxyType(
18
50
  )
19
51
  MISSING_COVER_FN = "missing-cover-165.webp"
20
52
  MISSING_COVER_PATH = STATIC_IMG_PATH / MISSING_COVER_FN
53
+
54
+
55
+ ROOT_GROUP = "r"
56
+ FOLDER_GROUP = "f"
57
+ STORY_ARC_GROUP = "a"
58
+ COMIC_GROUP = "c"
59
+ GROUP_RELATION = MappingProxyType(
60
+ {
61
+ **GROUP_NAME_MAP,
62
+ COMIC_GROUP: "pk",
63
+ FOLDER_GROUP: "parent_folder",
64
+ STORY_ARC_GROUP: "story_arc_numbers__story_arc",
65
+ }
66
+ )
67
+ GROUP_ORDER = "rpisv"
68
+ MODEL_REL_MAP = MappingProxyType(
69
+ {
70
+ Publisher: "publisher",
71
+ Imprint: "imprint",
72
+ Series: "series",
73
+ Volume: "volume",
74
+ Folder: "parent_folder",
75
+ StoryArc: "story_arc_numbers__story_arc",
76
+ Comic: "pk",
77
+ }
78
+ )
79
+ GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
80
+ MappingProxyType(
81
+ {
82
+ ROOT_GROUP: None,
83
+ "p": Publisher,
84
+ "i": Imprint,
85
+ "s": Series,
86
+ "v": Volume,
87
+ COMIC_GROUP: Comic,
88
+ FOLDER_GROUP: Folder,
89
+ STORY_ARC_GROUP: StoryArc,
90
+ }
91
+ )
92
+ )
93
+ GROUP_MODELS = (
94
+ Publisher,
95
+ Imprint,
96
+ Series,
97
+ Volume,
98
+ Comic,
99
+ Folder,
100
+ )
101
+ METADATA_MODELS = (
102
+ AgeRating,
103
+ Character,
104
+ Country,
105
+ Genre,
106
+ Identifier,
107
+ IdentifierType,
108
+ Language,
109
+ Location,
110
+ OriginalFormat,
111
+ SeriesGroup,
112
+ ScanInfo,
113
+ StoryArc,
114
+ StoryArcNumber,
115
+ Team,
116
+ Tag,
117
+ Tagger,
118
+ Contributor,
119
+ ContributorPerson,
120
+ ContributorRole,
121
+ )
122
+ CONFIG_MODELS = (
123
+ Library,
124
+ User,
125
+ Group,
126
+ Session,
127
+ )
128
+ MAX_OBJ_PER_PAGE = 100
129
+ GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
codex/views/download.py CHANGED
@@ -1,24 +1,21 @@
1
1
  """Download a comic archive."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import ClassVar
5
4
 
6
5
  from django.http import FileResponse, Http404
7
6
  from drf_spectacular.types import OpenApiTypes
8
7
  from drf_spectacular.utils import extend_schema
9
- from rest_framework.views import APIView
10
8
 
11
9
  from codex.models import Comic
12
- from codex.views.auth import GroupACLMixin, IsAuthenticatedOrEnabledNonUsers
10
+ from codex.views.auth import AuthFilterAPIView
13
11
 
14
12
 
15
- class DownloadView(APIView, GroupACLMixin):
13
+ class DownloadView(AuthFilterAPIView):
16
14
  """Return the comic archive file as an attachment."""
17
15
 
18
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
19
16
  content_type = "application/vnd.comicbook+zip"
20
17
 
21
- AS_ATTACHMENT = True
18
+ _AS_ATTACHMENT = True
22
19
 
23
20
  @extend_schema(responses={(200, content_type): OpenApiTypes.BINARY})
24
21
  def get(self, *_args, **kwargs):
@@ -48,7 +45,7 @@ class DownloadView(APIView, GroupACLMixin):
48
45
  filename = comic.get_filename()
49
46
  return FileResponse(
50
47
  comic_file,
51
- as_attachment=self.AS_ATTACHMENT,
48
+ as_attachment=self._AS_ATTACHMENT,
52
49
  content_type=content_type,
53
50
  filename=filename,
54
51
  )
@@ -57,4 +54,4 @@ class DownloadView(APIView, GroupACLMixin):
57
54
  class FileView(DownloadView):
58
55
  """View a comic in the browser."""
59
56
 
60
- AS_ATTACHMENT = False
57
+ _AS_ATTACHMENT = False
codex/views/frontend.py CHANGED
@@ -6,12 +6,14 @@ from rest_framework.renderers import TemplateHTMLRenderer
6
6
  from rest_framework.response import Response
7
7
 
8
8
  from codex.serializers.route import RouteSerializer
9
- from codex.views.session import BrowserSessionViewBase
9
+ from codex.views.session import SessionView
10
10
 
11
11
 
12
- class IndexView(BrowserSessionViewBase):
12
+ class IndexView(SessionView):
13
13
  """The main app."""
14
14
 
15
+ SESSION_KEY = SessionView.BROWSER_SESSION_KEY
16
+
15
17
  renderer_classes: ClassVar[list] = [TemplateHTMLRenderer] # type: ignore
16
18
  template_name = "index.html"
17
19
 
codex/views/mtime.py ADDED
@@ -0,0 +1,72 @@
1
+ """Get the mtimes for the submitted groups."""
2
+
3
+ from django.db.models import Max
4
+ from rest_framework.response import Response
5
+
6
+ from codex.models.groups import Publisher
7
+ from codex.serializers.mtime import GroupsMtimeSerializer, MtimeSerializer
8
+ from codex.util import max_none
9
+ from codex.views.auth import AuthGenericAPIView
10
+ from codex.views.browser.filters.bookmark import BookmarkFilterMixin
11
+ from codex.views.const import GROUP_MODEL_MAP
12
+ from codex.views.util import reparse_json_query_params
13
+
14
+ _REPARSE_JSON_FIELDS = frozenset({"groups"})
15
+
16
+
17
+ class MtimeView(AuthGenericAPIView, BookmarkFilterMixin):
18
+ """Get the mtimes for the submitted groups."""
19
+
20
+ serializer_class = GroupsMtimeSerializer
21
+ response_serializer_class = MtimeSerializer
22
+
23
+ def _parse_params(self):
24
+ params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
25
+ self.groups = params.get("groups", "")
26
+ self.use_bookmark_filter = self.request.GET.get("use_bookmark_filter", False)
27
+
28
+ def _get_group_mtime(self, group, pks):
29
+ """Get one group's mtimes."""
30
+ model = GROUP_MODEL_MAP[group]
31
+ if not model:
32
+ model = Publisher
33
+
34
+ qs = model.objects.all()
35
+
36
+ if pks and 0 not in pks:
37
+ qs = qs.filter(pk__in=pks)
38
+ updated_at_max = qs.aggregate(max=Max("updated_at"))["max"]
39
+
40
+ if self.use_bookmark_filter:
41
+ bm_rel = self.get_bm_rel(model)
42
+ bm_filter = self.get_my_bookmark_filter(bm_rel)
43
+ qs = qs.filter(bm_filter)
44
+ bookmark_updated_at_max = qs.aggregate(max=(Max(f"{bm_rel}__updated_at")))[
45
+ "max"
46
+ ]
47
+ updated_at_max = max_none(updated_at_max, bookmark_updated_at_max)
48
+
49
+ return updated_at_max
50
+
51
+ def get_max_groups_mtime(self):
52
+ """Get max mtime for all groups."""
53
+ max_mtime = None
54
+
55
+ for item in self.groups:
56
+ group = item["group"]
57
+ pks = tuple(int(pk) for pk in item["pks"].split(","))
58
+ mtime = self._get_group_mtime(group, pks)
59
+ max_mtime = max_none(max_mtime, mtime)
60
+ return max_mtime
61
+
62
+ def get(self, *args, **kwargs):
63
+ """Get the mtimes for the submitted groups."""
64
+ # Parse Request
65
+ self._parse_params()
66
+
67
+ max_mtime = self.get_max_groups_mtime()
68
+
69
+ # Serialize Response
70
+ result = {"max_mtime": max_mtime}
71
+ serializer = self.response_serializer_class(result)
72
+ return Response(serializer.data)
@@ -0,0 +1,17 @@
1
+ """OPDS Authentican mixin."""
2
+
3
+ from rest_framework.authentication import BasicAuthentication, SessionAuthentication
4
+
5
+ from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
6
+ from codex.views.template import CodexXMLTemplateView
7
+
8
+
9
+ class OPDSAuthMixin:
10
+ """Add Basic Auth."""
11
+
12
+ authentication_classes = (SessionAuthentication, BasicAuthentication)
13
+ permission_classes = (IsAuthenticatedOrEnabledNonUsers,)
14
+
15
+
16
+ class OPDSTemplateView(OPDSAuthMixin, CodexXMLTemplateView):
17
+ """XML Template view with OPDSAuth."""