codex 1.6.0a9__py3-none-any.whl → 1.6.0a11__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 (560) hide show
  1. codex/librarian/importer/cache.py +46 -133
  2. codex/librarian/importer/create_fks.py +3 -1
  3. codex/librarian/importer/importer.py +1 -1
  4. codex/librarian/importer/importerd.py +5 -5
  5. codex/librarian/importer/tasks.py +2 -2
  6. codex/librarian/janitor/janitor.py +0 -3
  7. codex/librarian/librariand.py +0 -2
  8. codex/librarian/notifier/notifierd.py +1 -1
  9. codex/librarian/notifier/tasks.py +4 -4
  10. codex/middleware.py +1 -2
  11. codex/migrations/0027_import_order_and_covers.py +1 -66
  12. codex/models/comic.py +0 -1
  13. codex/models/groups.py +0 -3
  14. codex/serializers/admin.py +41 -40
  15. codex/serializers/browser/choices.py +38 -10
  16. codex/serializers/browser/filters.py +2 -2
  17. codex/serializers/browser/metadata.py +45 -1
  18. codex/serializers/browser/mixins.py +35 -2
  19. codex/serializers/browser/page.py +2 -4
  20. codex/serializers/browser/settings.py +41 -14
  21. codex/serializers/choices.py +4 -0
  22. codex/serializers/fields.py +61 -1
  23. codex/serializers/models/pycountry.py +11 -51
  24. codex/serializers/reader.py +12 -1
  25. codex/serializers/route.py +1 -1
  26. codex/serializers/settings.py +12 -0
  27. codex/settings/settings.py +10 -1
  28. codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js +1 -0
  29. codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js.br +0 -0
  30. codex/static_root/assets/VCheckbox-BwDyxhU7.831b00df198a.js.gz +0 -0
  31. codex/static_root/assets/VCheckbox-BwDyxhU7.js +1 -0
  32. codex/static_root/assets/VCheckbox-BwDyxhU7.js.br +0 -0
  33. codex/static_root/assets/VCheckbox-BwDyxhU7.js.gz +0 -0
  34. codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.9a47873b8548.js → VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js} +1 -1
  35. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js.br +0 -0
  36. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.5e6464c8d80a.js.gz +0 -0
  37. codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.js → VCheckboxBtn-DVq5lDK4.js} +1 -1
  38. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.js.br +0 -0
  39. codex/static_root/assets/VCheckboxBtn-DVq5lDK4.js.gz +0 -0
  40. codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js +1 -0
  41. codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js.br +0 -0
  42. codex/static_root/assets/VCombobox-BASCOy0n.7801caa5e192.js.gz +0 -0
  43. codex/static_root/assets/VCombobox-BASCOy0n.js +1 -0
  44. codex/static_root/assets/VCombobox-BASCOy0n.js.br +0 -0
  45. codex/static_root/assets/VCombobox-BASCOy0n.js.gz +0 -0
  46. codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js +1 -0
  47. codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js.br +0 -0
  48. codex/static_root/assets/VDialog-DnbL_v_u.0bd289bda8e6.js.gz +0 -0
  49. codex/static_root/assets/VDialog-DnbL_v_u.js +1 -0
  50. codex/static_root/assets/VDialog-DnbL_v_u.js.br +0 -0
  51. codex/static_root/assets/VDialog-DnbL_v_u.js.gz +0 -0
  52. codex/static_root/assets/{VDialog-C5Nxd_zP.css → VDialog-lQ13FnRR.1c801ad756b2.css} +1 -1
  53. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  54. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  55. codex/static_root/assets/{VDialog-C5Nxd_zP.d327f505ef0e.css → VDialog-lQ13FnRR.css} +1 -1
  56. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  57. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  58. codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js +1 -0
  59. codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js.br +0 -0
  60. codex/static_root/assets/VExpansionPanels-B8nz8lN5.56e5adec4f62.js.gz +0 -0
  61. codex/static_root/assets/VExpansionPanels-B8nz8lN5.js +1 -0
  62. codex/static_root/assets/VExpansionPanels-B8nz8lN5.js.br +0 -0
  63. codex/static_root/assets/VExpansionPanels-B8nz8lN5.js.gz +0 -0
  64. codex/static_root/assets/{VRadioGroup-B--7K_v5.4a4e9c2d6c42.js → VRadioGroup-BgwJ_b94.55f84f3429b9.js} +1 -1
  65. codex/static_root/assets/VRadioGroup-BgwJ_b94.55f84f3429b9.js.br +0 -0
  66. codex/static_root/assets/VRadioGroup-BgwJ_b94.55f84f3429b9.js.gz +0 -0
  67. codex/static_root/assets/{VRadioGroup-B--7K_v5.js → VRadioGroup-BgwJ_b94.js} +1 -1
  68. codex/static_root/assets/VRadioGroup-BgwJ_b94.js.br +0 -0
  69. codex/static_root/assets/VRadioGroup-BgwJ_b94.js.gz +0 -0
  70. codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js +1 -0
  71. codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js.br +0 -0
  72. codex/static_root/assets/VSelect-BoKpXxwe.a88f065dfe3b.js.gz +0 -0
  73. codex/static_root/assets/VSelect-BoKpXxwe.js +1 -0
  74. codex/static_root/assets/VSelect-BoKpXxwe.js.br +0 -0
  75. codex/static_root/assets/VSelect-BoKpXxwe.js.gz +0 -0
  76. codex/static_root/assets/{VSelectionControl-CVQ9wkE2.c02186cceba6.js → VSelectionControl-BXJGg8No.0cb36d941d2a.js} +1 -1
  77. codex/static_root/assets/VSelectionControl-BXJGg8No.0cb36d941d2a.js.br +0 -0
  78. codex/static_root/assets/VSelectionControl-BXJGg8No.0cb36d941d2a.js.gz +0 -0
  79. codex/static_root/assets/{VSelectionControl-CVQ9wkE2.js → VSelectionControl-BXJGg8No.js} +1 -1
  80. codex/static_root/assets/VSelectionControl-BXJGg8No.js.br +0 -0
  81. codex/static_root/assets/VSelectionControl-BXJGg8No.js.gz +0 -0
  82. codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js +1 -0
  83. codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js.br +0 -0
  84. codex/static_root/assets/VSlideGroup-C2TL5E34.775adeb5a973.js.gz +0 -0
  85. codex/static_root/assets/VSlideGroup-C2TL5E34.js +1 -0
  86. codex/static_root/assets/VSlideGroup-C2TL5E34.js.br +0 -0
  87. codex/static_root/assets/VSlideGroup-C2TL5E34.js.gz +0 -0
  88. codex/static_root/assets/{VTable-CrWkhkiP.6d530eea0c09.js → VTable-C0FptljX.e4840b635c6e.js} +1 -1
  89. codex/static_root/assets/VTable-C0FptljX.e4840b635c6e.js.br +0 -0
  90. codex/static_root/assets/VTable-C0FptljX.e4840b635c6e.js.gz +0 -0
  91. codex/static_root/assets/{VTable-CrWkhkiP.js → VTable-C0FptljX.js} +1 -1
  92. codex/static_root/assets/VTable-C0FptljX.js.br +0 -0
  93. codex/static_root/assets/VTable-C0FptljX.js.gz +0 -0
  94. codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js +1 -0
  95. codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js.br +0 -0
  96. codex/static_root/assets/VTextField-Aw8nKhY6.428732ac22d7.js.gz +0 -0
  97. codex/static_root/assets/VTextField-Aw8nKhY6.js +1 -0
  98. codex/static_root/assets/VTextField-Aw8nKhY6.js.br +0 -0
  99. codex/static_root/assets/VTextField-Aw8nKhY6.js.gz +0 -0
  100. codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js +1 -0
  101. codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js.br +0 -0
  102. codex/static_root/assets/VWindowItem-D9j_u9MX.828acf3dee55.js.gz +0 -0
  103. codex/static_root/assets/VWindowItem-D9j_u9MX.js +1 -0
  104. codex/static_root/assets/VWindowItem-D9j_u9MX.js.br +0 -0
  105. codex/static_root/assets/VWindowItem-D9j_u9MX.js.gz +0 -0
  106. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js +30 -0
  107. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js.br +0 -0
  108. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.af7e8490f0e3.js.gz +0 -0
  109. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js +30 -0
  110. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js.br +0 -0
  111. codex/static_root/assets/admin-drawer-panel-Dmn4wEY3.js.gz +0 -0
  112. codex/static_root/assets/{admin-drawer-panel-o87BwD38.ce39427c8201.css → admin-drawer-panel-mne3FIj6.561b3f75fe65.css} +1 -1
  113. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
  114. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
  115. codex/static_root/assets/{admin-drawer-panel-o87BwD38.css → admin-drawer-panel-mne3FIj6.css} +1 -1
  116. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
  117. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
  118. codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js +1 -0
  119. codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js.br +0 -0
  120. codex/static_root/assets/admin-jf7zR2p_.4741813376d2.js.gz +0 -0
  121. codex/static_root/assets/admin-jf7zR2p_.js +1 -0
  122. codex/static_root/assets/admin-jf7zR2p_.js.br +0 -0
  123. codex/static_root/assets/admin-jf7zR2p_.js.gz +0 -0
  124. codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js +1 -0
  125. codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js.br +0 -0
  126. codex/static_root/assets/browser-BKZuypzF.86e5333bd7d6.js.gz +0 -0
  127. codex/static_root/assets/browser-BKZuypzF.js +1 -0
  128. codex/static_root/assets/browser-BKZuypzF.js.br +0 -0
  129. codex/static_root/assets/browser-BKZuypzF.js.gz +0 -0
  130. codex/static_root/assets/browser-D7xci5SY.css +1 -0
  131. codex/static_root/assets/browser-D7xci5SY.css.br +0 -0
  132. codex/static_root/assets/browser-D7xci5SY.css.gz +0 -0
  133. codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css +1 -0
  134. codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css.br +0 -0
  135. codex/static_root/assets/browser-D7xci5SY.fa305e14005c.css.gz +0 -0
  136. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +1 -0
  137. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  138. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  139. codex/static_root/assets/change-password-dialog-0u6IJA87.css +1 -0
  140. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  141. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  142. codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js +1 -0
  143. codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js.br +0 -0
  144. codex/static_root/assets/change-password-dialog-Cc1e755p.b29d200537bf.js.gz +0 -0
  145. codex/static_root/assets/change-password-dialog-Cc1e755p.js +1 -0
  146. codex/static_root/assets/change-password-dialog-Cc1e755p.js.br +0 -0
  147. codex/static_root/assets/change-password-dialog-Cc1e755p.js.gz +0 -0
  148. codex/static_root/assets/{confirm-dialog-jwL7B98r.1cfd579cbcfe.js → confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js} +1 -1
  149. codex/static_root/assets/confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js.br +0 -0
  150. codex/static_root/assets/confirm-dialog-Cyr7HAAz.8ab1d8b6241a.js.gz +0 -0
  151. codex/static_root/assets/{confirm-dialog-jwL7B98r.js → confirm-dialog-Cyr7HAAz.js} +1 -1
  152. codex/static_root/assets/confirm-dialog-Cyr7HAAz.js.br +0 -0
  153. codex/static_root/assets/confirm-dialog-Cyr7HAAz.js.gz +0 -0
  154. codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js +1 -0
  155. codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js.br +0 -0
  156. codex/static_root/assets/datetime-column-Cqi99Ze-.49d0ce340183.js.gz +0 -0
  157. codex/static_root/assets/datetime-column-Cqi99Ze-.js +1 -0
  158. codex/static_root/assets/datetime-column-Cqi99Ze-.js.br +0 -0
  159. codex/static_root/assets/datetime-column-Cqi99Ze-.js.gz +0 -0
  160. codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js +1 -0
  161. codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js.br +0 -0
  162. codex/static_root/assets/filter-CuocBoS2.885d1b8d44cc.js.gz +0 -0
  163. codex/static_root/assets/filter-CuocBoS2.js +1 -0
  164. codex/static_root/assets/filter-CuocBoS2.js.br +0 -0
  165. codex/static_root/assets/filter-CuocBoS2.js.gz +0 -0
  166. codex/static_root/assets/{flag-tab-Da2DBNyz.ffbe5ca37e1c.js → flag-tab-B5dRCBAt.e858f7ec9f55.js} +1 -1
  167. codex/static_root/assets/flag-tab-B5dRCBAt.e858f7ec9f55.js.br +0 -0
  168. codex/static_root/assets/flag-tab-B5dRCBAt.e858f7ec9f55.js.gz +0 -0
  169. codex/static_root/assets/{flag-tab-Da2DBNyz.js → flag-tab-B5dRCBAt.js} +1 -1
  170. codex/static_root/assets/flag-tab-B5dRCBAt.js.br +0 -0
  171. codex/static_root/assets/flag-tab-B5dRCBAt.js.gz +0 -0
  172. codex/static_root/assets/{group-tab-BONYWOc1.6ddf321a4a56.js → group-tab-_nT2o04v.c06032b254dc.js} +1 -1
  173. codex/static_root/assets/group-tab-_nT2o04v.c06032b254dc.js.br +0 -0
  174. codex/static_root/assets/group-tab-_nT2o04v.c06032b254dc.js.gz +0 -0
  175. codex/static_root/assets/{group-tab-BONYWOc1.js → group-tab-_nT2o04v.js} +1 -1
  176. codex/static_root/assets/group-tab-_nT2o04v.js.br +0 -0
  177. codex/static_root/assets/group-tab-_nT2o04v.js.gz +0 -0
  178. codex/static_root/assets/{http-error-D2Jnc20C.32846bc3a43d.js → http-error-CXo9qLiW.d5a337f399c9.js} +1 -1
  179. codex/static_root/assets/http-error-CXo9qLiW.d5a337f399c9.js.br +0 -0
  180. codex/static_root/assets/http-error-CXo9qLiW.d5a337f399c9.js.gz +0 -0
  181. codex/static_root/assets/{http-error-D2Jnc20C.js → http-error-CXo9qLiW.js} +1 -1
  182. codex/static_root/assets/http-error-CXo9qLiW.js.br +0 -0
  183. codex/static_root/assets/http-error-CXo9qLiW.js.gz +0 -0
  184. codex/static_root/assets/{library-tab-B419agXA.js → library-tab-lz5QMD4i.2f04b226ebe8.js} +1 -1
  185. codex/static_root/assets/library-tab-lz5QMD4i.2f04b226ebe8.js.br +0 -0
  186. codex/static_root/assets/library-tab-lz5QMD4i.2f04b226ebe8.js.gz +0 -0
  187. codex/static_root/assets/{library-tab-B419agXA.d4b778172aaa.js → library-tab-lz5QMD4i.js} +1 -1
  188. codex/static_root/assets/library-tab-lz5QMD4i.js.br +0 -0
  189. codex/static_root/assets/library-tab-lz5QMD4i.js.gz +0 -0
  190. codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js +32 -0
  191. codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js.br +0 -0
  192. codex/static_root/assets/main-17i0EBCp.7093c3f755f1.js.gz +0 -0
  193. codex/static_root/assets/main-17i0EBCp.js +32 -0
  194. codex/static_root/assets/main-17i0EBCp.js.br +0 -0
  195. codex/static_root/assets/main-17i0EBCp.js.gz +0 -0
  196. codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js +1 -0
  197. codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js.br +0 -0
  198. codex/static_root/assets/pagination-toolbar-BBvsIXTd.ad891da6617a.js.gz +0 -0
  199. codex/static_root/assets/pagination-toolbar-BBvsIXTd.js +1 -0
  200. codex/static_root/assets/pagination-toolbar-BBvsIXTd.js.br +0 -0
  201. codex/static_root/assets/pagination-toolbar-BBvsIXTd.js.gz +0 -0
  202. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css +1 -0
  203. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css.br +0 -0
  204. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.css.gz +0 -0
  205. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css +1 -0
  206. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css.br +0 -0
  207. codex/static_root/assets/pagination-toolbar-B_zL7BQQ.d1311bd41edc.css.gz +0 -0
  208. codex/static_root/assets/{pdf-doc-8RVZqILx.2e7166dd26fb.js → pdf-doc-BpZdmj2g.93e86d9c7927.js} +1 -1
  209. codex/static_root/assets/pdf-doc-BpZdmj2g.93e86d9c7927.js.br +0 -0
  210. codex/static_root/assets/pdf-doc-BpZdmj2g.93e86d9c7927.js.gz +0 -0
  211. codex/static_root/assets/{pdf-doc-8RVZqILx.js → pdf-doc-BpZdmj2g.js} +1 -1
  212. codex/static_root/assets/pdf-doc-BpZdmj2g.js.br +0 -0
  213. codex/static_root/assets/pdf-doc-BpZdmj2g.js.gz +0 -0
  214. codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js +2 -0
  215. codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js.br +0 -0
  216. codex/static_root/assets/reader-CRJUR2qj.55d626f0f9fa.js.gz +0 -0
  217. codex/static_root/assets/reader-CRJUR2qj.js +2 -0
  218. codex/static_root/assets/reader-CRJUR2qj.js.br +0 -0
  219. codex/static_root/assets/reader-CRJUR2qj.js.gz +0 -0
  220. codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-CqXFa_gE.78267e9de0c2.css} +1 -1
  221. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.br +0 -0
  222. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.gz +0 -0
  223. codex/static_root/assets/{reader-CCCfpM3x.css → reader-CqXFa_gE.css} +1 -1
  224. codex/static_root/assets/reader-CqXFa_gE.css.br +0 -0
  225. codex/static_root/assets/reader-CqXFa_gE.css.gz +0 -0
  226. codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js +1 -0
  227. codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js.br +0 -0
  228. codex/static_root/assets/relation-chips-NgDnInw_.3b5ba16f68f0.js.gz +0 -0
  229. codex/static_root/assets/relation-chips-NgDnInw_.js +1 -0
  230. codex/static_root/assets/relation-chips-NgDnInw_.js.br +0 -0
  231. codex/static_root/assets/relation-chips-NgDnInw_.js.gz +0 -0
  232. codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js +2 -0
  233. codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js.br +0 -0
  234. codex/static_root/assets/settings-drawer-BaQT27kc.e43fc1527b66.js.gz +0 -0
  235. codex/static_root/assets/settings-drawer-BaQT27kc.js +2 -0
  236. codex/static_root/assets/settings-drawer-BaQT27kc.js.br +0 -0
  237. codex/static_root/assets/settings-drawer-BaQT27kc.js.gz +0 -0
  238. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +1 -0
  239. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
  240. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
  241. codex/static_root/assets/stats-tab-B4yKuY0c.css +1 -0
  242. codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
  243. codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
  244. codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js +1 -0
  245. codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js.br +0 -0
  246. codex/static_root/assets/stats-tab-DoHb54IU.30d7deea7b9f.js.gz +0 -0
  247. codex/static_root/assets/stats-tab-DoHb54IU.js +1 -0
  248. codex/static_root/assets/stats-tab-DoHb54IU.js.br +0 -0
  249. codex/static_root/assets/stats-tab-DoHb54IU.js.gz +0 -0
  250. codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js +1 -0
  251. codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js.br +0 -0
  252. codex/static_root/assets/task-tab-B88ucu1U.4f240e3d82c4.js.gz +0 -0
  253. codex/static_root/assets/task-tab-B88ucu1U.js +1 -0
  254. codex/static_root/assets/task-tab-B88ucu1U.js.br +0 -0
  255. codex/static_root/assets/task-tab-B88ucu1U.js.gz +0 -0
  256. codex/static_root/assets/to-case-B7MjLIw-.08fc8412de69.js +1 -0
  257. codex/static_root/assets/to-case-B7MjLIw-.08fc8412de69.js.br +0 -0
  258. codex/static_root/assets/to-case-B7MjLIw-.js +1 -0
  259. codex/static_root/assets/to-case-B7MjLIw-.js.br +0 -0
  260. codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js +1 -0
  261. codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js.br +0 -0
  262. codex/static_root/assets/unauthorized-AnKtlgGy.62ee74d4373c.js.gz +0 -0
  263. codex/static_root/assets/unauthorized-AnKtlgGy.js +1 -0
  264. codex/static_root/assets/unauthorized-AnKtlgGy.js.br +0 -0
  265. codex/static_root/assets/unauthorized-AnKtlgGy.js.gz +0 -0
  266. codex/static_root/assets/{unauthorized-DHV6Q1n-.b7847b2872a1.css → unauthorized-CibJYD8o.4c2de98c2b53.css} +1 -1
  267. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
  268. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
  269. codex/static_root/assets/{unauthorized-DHV6Q1n-.css → unauthorized-CibJYD8o.css} +1 -1
  270. codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
  271. codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
  272. codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js +1 -0
  273. codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js.br +0 -0
  274. codex/static_root/assets/user-tab-BxtZ2u7W.161cc8f0dcce.js.gz +0 -0
  275. codex/static_root/assets/user-tab-BxtZ2u7W.js +1 -0
  276. codex/static_root/assets/user-tab-BxtZ2u7W.js.br +0 -0
  277. codex/static_root/assets/user-tab-BxtZ2u7W.js.gz +0 -0
  278. codex/static_root/js/choices-admin.3521e2a863df.json +1 -0
  279. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  280. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  281. codex/static_root/js/choices-admin.json +1 -1
  282. codex/static_root/js/choices-admin.json.br +0 -0
  283. codex/static_root/js/choices-admin.json.gz +0 -0
  284. codex/static_root/js/{choices.a0aaaa6c7ef7.json → choices.2f6b0359e1ef.json} +1 -1
  285. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  286. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  287. codex/static_root/js/choices.json +1 -1
  288. codex/static_root/js/choices.json.br +0 -0
  289. codex/static_root/js/choices.json.gz +0 -0
  290. codex/static_root/manifest.3851c028b0cb.json +622 -0
  291. codex/static_root/manifest.3851c028b0cb.json.br +0 -0
  292. codex/static_root/manifest.3851c028b0cb.json.gz +0 -0
  293. codex/static_root/manifest.json +245 -252
  294. codex/static_root/manifest.json.br +0 -0
  295. codex/static_root/manifest.json.gz +0 -0
  296. codex/static_root/staticfiles.json +1 -1
  297. codex/util.py +8 -6
  298. codex/views/admin/api_key.py +5 -2
  299. codex/views/admin/stats.py +13 -20
  300. codex/views/admin/tasks.py +2 -3
  301. codex/views/auth.py +1 -1
  302. codex/views/bookmark.py +49 -29
  303. codex/views/browser/annotations.py +11 -45
  304. codex/views/browser/base.py +22 -6
  305. codex/views/browser/breadcrumbs.py +5 -5
  306. codex/views/browser/browser.py +39 -27
  307. codex/views/browser/choices.py +22 -37
  308. codex/views/browser/cover.py +55 -65
  309. codex/views/browser/filters/annotations.py +33 -10
  310. codex/views/browser/filters/group.py +24 -15
  311. codex/views/browser/filters/search.py +3 -4
  312. codex/views/browser/metadata.py +98 -49
  313. codex/views/browser/mtime.py +3 -14
  314. codex/views/browser/order_by.py +1 -6
  315. codex/views/browser/settings.py +53 -0
  316. codex/views/browser/validate.py +3 -0
  317. codex/views/const.py +25 -2
  318. codex/views/opds/v1/feed.py +3 -5
  319. codex/views/opds/v2/feed.py +3 -3
  320. codex/views/reader/arcs.py +42 -51
  321. codex/views/reader/books.py +47 -63
  322. codex/views/reader/init.py +125 -0
  323. codex/views/reader/reader.py +9 -70
  324. codex/views/session.py +18 -12
  325. codex/views/settings.py +23 -10
  326. codex/views/util.py +2 -1
  327. codex/websockets/consumers.py +14 -10
  328. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/METADATA +1 -1
  329. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/RECORD +332 -328
  330. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js +0 -1
  331. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  332. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  333. codex/static_root/assets/VCheckbox-eNspVUje.js +0 -1
  334. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  335. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  336. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  337. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  338. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  339. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  340. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js +0 -1
  341. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  342. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  343. codex/static_root/assets/VCombobox-BIO7lU_B.js +0 -1
  344. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  345. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  346. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js +0 -1
  347. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  348. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  349. codex/static_root/assets/VDialog-87AymJC5.js +0 -1
  350. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  351. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  352. codex/static_root/assets/VDialog-C5Nxd_zP.css.br +0 -0
  353. codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
  354. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
  355. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
  356. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js +0 -1
  357. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  358. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  359. codex/static_root/assets/VExpansionPanels-CgA3shsd.js +0 -1
  360. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  361. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  362. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  363. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  364. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  365. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  366. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +0 -1
  367. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  368. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  369. codex/static_root/assets/VSelect-BoJq222l.js +0 -1
  370. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  371. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  372. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  373. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  374. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  375. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  376. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js +0 -1
  377. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  378. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  379. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js +0 -1
  380. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  381. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  382. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  383. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  384. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  385. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  386. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js +0 -1
  387. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  388. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  389. codex/static_root/assets/VTextField-CkWbil3K.js +0 -1
  390. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  391. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  392. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js +0 -1
  393. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  394. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  395. codex/static_root/assets/VWindowItem-7z55Y4lK.js +0 -1
  396. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  397. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  398. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js +0 -1
  399. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  400. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  401. codex/static_root/assets/admin-D3OK784R.js +0 -1
  402. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  403. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  404. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js +0 -30
  405. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  406. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  407. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js +0 -30
  408. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  409. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  410. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  411. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.gz +0 -0
  412. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  413. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.gz +0 -0
  414. codex/static_root/assets/browser-BuU2x9y7.css +0 -1
  415. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  416. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  417. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +0 -1
  418. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  419. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  420. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +0 -1
  421. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  422. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  423. codex/static_root/assets/browser-CO9EYHIr.js +0 -1
  424. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  425. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  426. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js +0 -1
  427. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  428. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  429. codex/static_root/assets/change-password-dialog-CjZdzaIk.js +0 -1
  430. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  431. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  432. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css +0 -1
  433. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
  434. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
  435. codex/static_root/assets/change-password-dialog-HvIvmmIE.css +0 -1
  436. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
  437. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
  438. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  439. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  440. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  441. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  442. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +0 -1
  443. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  444. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  445. codex/static_root/assets/datetime-column-9w3GcKvo.js +0 -1
  446. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  447. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  448. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js +0 -1
  449. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  450. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  451. codex/static_root/assets/filter-C-pghOri.js +0 -1
  452. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  453. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  454. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  455. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  456. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  457. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  458. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  459. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  460. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  461. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  462. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  463. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  464. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  465. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  466. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  467. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  468. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  469. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  470. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +0 -32
  471. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  472. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  473. codex/static_root/assets/main-CMQwJ9aG.js +0 -32
  474. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  475. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  476. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +0 -1
  477. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  478. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  479. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +0 -1
  480. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  481. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  482. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +0 -1
  483. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  484. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  485. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +0 -1
  486. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  487. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  488. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  489. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  490. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  491. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  492. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  493. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  494. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  495. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  496. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js +0 -2
  497. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  498. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  499. codex/static_root/assets/reader-DNnK_Chr.js +0 -2
  500. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  501. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  502. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js +0 -1
  503. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  504. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  505. codex/static_root/assets/relation-chips-PYua7aXv.js +0 -1
  506. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  507. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  508. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js +0 -2
  509. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  510. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  511. codex/static_root/assets/settings-drawer-DbN6ISRH.js +0 -2
  512. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  513. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  514. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js +0 -1
  515. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  516. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  517. codex/static_root/assets/stats-tab-DImNhH8O.js +0 -1
  518. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  519. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  520. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css +0 -1
  521. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +0 -2
  522. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
  523. codex/static_root/assets/stats-tab-TsyG3wH_.css +0 -1
  524. codex/static_root/assets/stats-tab-TsyG3wH_.css.br +0 -2
  525. codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
  526. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js +0 -1
  527. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  528. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  529. codex/static_root/assets/task-tab-DPD53kjv.js +0 -1
  530. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  531. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  532. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +0 -1
  533. codex/static_root/assets/to-case-ehC9Ccyj.js +0 -1
  534. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js +0 -1
  535. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  536. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  537. codex/static_root/assets/unauthorized-BOIx7Y18.js +0 -1
  538. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  539. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  540. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
  541. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
  542. codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
  543. codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
  544. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js +0 -1
  545. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  546. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  547. codex/static_root/assets/user-tab-BABwfxLO.js +0 -1
  548. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  549. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  550. codex/static_root/js/choices-admin.d790df01c20a.json +0 -1
  551. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  552. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  553. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  554. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  555. codex/static_root/manifest.cbea5663003a.json +0 -629
  556. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  557. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  558. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/LICENSE +0 -0
  559. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/WHEEL +0 -0
  560. {codex-1.6.0a9.dist-info → codex-1.6.0a11.dist-info}/entry_points.txt +0 -0
@@ -2,18 +2,17 @@
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django.db.models import F, IntegerField, Value
5
+ from django.db.models import F
6
6
 
7
7
  from codex.models import Bookmark, Comic
8
8
  from codex.views.bookmark import BookmarkBaseView
9
- from codex.views.const import FOLDER_GROUP
9
+ from codex.views.const import FOLDER_GROUP, NONE_INTEGERFIELD
10
10
  from codex.views.mixins import SharedAnnotationsMixin
11
- from codex.views.session import SessionView
11
+ from codex.views.reader.init import ReaderInitView
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Mapping
15
15
 
16
- _MIN_CRUMB_WALKBACK_LEN = 3
17
16
  _SETTINGS_ATTRS = ("fit_to", "two_pages", "reading_direction")
18
17
  _COMIC_FIELDS = (
19
18
  "file_type",
@@ -25,60 +24,40 @@ _COMIC_FIELDS = (
25
24
  "reading_direction",
26
25
  "updated_at",
27
26
  )
28
- _SHOW = {
29
- "series": True,
30
- "volume": True,
31
- }
32
27
 
33
28
 
34
- class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
29
+ class ReaderBooksView(BookmarkBaseView, ReaderInitView, SharedAnnotationsMixin):
35
30
  """Get Books methods."""
36
31
 
37
- def get_series_pks_from_breadcrumbs(self):
38
- """Get Multi-Group pks from the breadcrumbs."""
39
- if self.series_pks:
40
- return self.series_pks
41
- breadcrumbs = self.get_from_session(
42
- "breadcrumbs", session_key=self.BROWSER_SESSION_KEY
43
- )
44
- if breadcrumbs:
45
- crumb = breadcrumbs[-1]
46
- crumb_group = crumb.get("group")
47
- if crumb_group == "v" and len(breadcrumbs) >= _MIN_CRUMB_WALKBACK_LEN:
48
- crumb = breadcrumbs[-2]
49
- crumb_group = crumb.get("group")
50
- if crumb_group == "s":
51
- self.series_pks = crumb.get("pks", ())
52
-
53
- return self.series_pks
54
-
55
- def _get_reader_arc_pks(
56
- self, arc, arc_pk_select_related, prefetch_related, arc_pk_rel
32
+ def _get_reader_arc_pks( # noqa: PLR0913
33
+ self, arc, arc_pk_select_related, prefetch_related, arc_pk_rel, arc_group
57
34
  ):
58
35
  """Get the nav filter."""
59
- arc_pks = arc.get("pks", ())
36
+ if arc_pks := arc.get("pks", ()):
37
+ return arc_pks
38
+
39
+ comic_pk = self.kwargs["pk"]
40
+ try:
41
+ arc_pk_qs = Comic.objects.filter(pk=comic_pk)
42
+ arc_pk_qs = arc_pk_qs.select_related(*arc_pk_select_related)
43
+ arc_pk_qs = arc_pk_qs.prefetch_related(*prefetch_related)
44
+ arc_pk_qs = arc_pk_qs.values_list(arc_pk_rel, flat=True)
45
+ arc_pk = arc_pk_qs.first()
46
+ except IndexError:
47
+ arc_pk = 0
48
+
49
+ _, multi_arc_pks = self.get_group_pks_from_breadcrumbs([arc_group])
50
+ if not arc_pk or arc_pk in multi_arc_pks:
51
+ arc_pks = multi_arc_pks
52
+
60
53
  if not arc_pks:
61
- comic_pk = self.kwargs["pk"]
62
- try:
63
- arc_pk_qs = Comic.objects.filter(pk=comic_pk)
64
- arc_pk_qs = arc_pk_qs.select_related(*arc_pk_select_related)
65
- arc_pk_qs = arc_pk_qs.prefetch_related(*prefetch_related)
66
- arc_pk = arc_pk_qs.values_list(arc_pk_rel, flat=True)[0]
67
- except IndexError:
68
- arc_pk = 0
69
-
70
- if arc_pk_rel == "series__pk":
71
- multi_arc_pks = self.get_series_pks_from_breadcrumbs()
72
- if not arc_pk or arc_pk in multi_arc_pks:
73
- arc_pks = multi_arc_pks
74
- if not arc_pks:
75
- arc_pks = (arc_pk,)
54
+ arc_pks = (arc_pk,)
76
55
 
77
56
  return arc_pks
78
57
 
79
58
  def _get_comics_list(self):
80
59
  """Get the reader naviation group filter."""
81
- select_related = ("series",)
60
+ select_related = ("series", "volume")
82
61
  prefetch_related = ()
83
62
 
84
63
  arc: Mapping = self.params.get("arc", {}) # type: ignore
@@ -91,31 +70,35 @@ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
91
70
  arc_pk_rel = "story_arc_numbers__story_arc__pk"
92
71
  prefetch_related = (*prefetch_related, "story_arc_numbers__story_arc")
93
72
  arc_index = F("story_arc_numbers__number")
94
- ordering = ("arc_index", "date")
73
+ ordering = ("arc_index", "date", "pk")
95
74
  arc_pk_select_related = ()
96
75
  elif arc_group == FOLDER_GROUP:
97
76
  # folder mode
98
77
  rel = "parent_folder"
99
78
  fields = (*_COMIC_FIELDS, "parent_folder")
100
79
  arc_pk_rel = "parent_folder__pk"
101
- select_related = (*select_related, "parent_folder")
102
- arc_index = Value(None, IntegerField())
80
+ arc_index = NONE_INTEGERFIELD
103
81
  ordering = ("path", "pk")
104
82
  arc_pk_select_related = ("parent_folder",)
83
+ elif arc_group == "v":
84
+ # volume
85
+ rel = "volume"
86
+ fields = _COMIC_FIELDS
87
+ arc_pk_rel = "volume__pk"
88
+ arc_index = NONE_INTEGERFIELD
89
+ ordering = ()
90
+ arc_pk_select_related = ("volume",)
105
91
  else:
106
- # browser mode.
92
+ # series
107
93
  rel = "series"
108
94
  fields = _COMIC_FIELDS
109
95
  arc_pk_rel = "series__pk"
110
- arc_index = Value(None, IntegerField())
96
+ arc_index = NONE_INTEGERFIELD
111
97
  ordering = ()
112
98
  arc_pk_select_related = ("series",)
113
99
 
114
100
  arc_pks = self._get_reader_arc_pks(
115
- arc,
116
- arc_pk_select_related,
117
- prefetch_related,
118
- arc_pk_rel,
101
+ arc, arc_pk_select_related, prefetch_related, arc_pk_rel, arc_group
119
102
  )
120
103
  nav_filter = {f"{rel}__in": arc_pks}
121
104
  group_acl_filter = self.get_group_acl_filter(Comic)
@@ -136,9 +119,11 @@ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
136
119
  .annotate(mtime=F("updated_at"))
137
120
  )
138
121
  qs = self.annotate_group_names(qs, Comic)
139
- if arc_group == "s":
122
+ if arc_group in ("v", "s"):
123
+ show = self.params["show"]
124
+ model_group = "i" if arc_group == "s" else "s"
140
125
  qs, comic_sort_names = self.alias_sort_names(
141
- qs, Comic, pks=arc_pks, model_group="i", show=_SHOW
126
+ qs, Comic, pks=arc_pks, model_group=model_group, show=show
142
127
  )
143
128
  ordering = (
144
129
  *comic_sort_names,
@@ -146,7 +131,8 @@ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
146
131
  "issue_suffix",
147
132
  "sort_name",
148
133
  )
149
- return qs.order_by(*ordering), arc_group
134
+ qs = qs.order_by(*ordering)
135
+ return qs, arc_group
150
136
 
151
137
  def _append_with_settings(self, book, bookmark_filter):
152
138
  """Append bookmark to book list."""
@@ -183,9 +169,9 @@ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
183
169
  prev_book, bookmark_filter
184
170
  )
185
171
  # create extra current book attrs:
186
- if book.arc_index is None: # type: ignore
187
- book.arc_index = index + 1 # type: ignore
188
- book.filename = book.get_filename() # type: ignore
172
+ if book.arc_index is None:
173
+ book.arc_index = index + 1
174
+ book.filename = book.get_filename()
189
175
  book.arc_group = arc_group
190
176
  book.arc_count = comics.count()
191
177
  books["current"] = self._append_with_settings(book, bookmark_filter)
@@ -193,5 +179,3 @@ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
193
179
  # Haven't matched yet, so set the previous comic
194
180
  prev_book = book
195
181
  return books
196
-
197
- #####
@@ -0,0 +1,125 @@
1
+ """Views for reading comic books."""
2
+
3
+ from copy import deepcopy
4
+ from types import MappingProxyType
5
+
6
+ from codex.logger.logging import get_logger
7
+ from codex.models import Comic
8
+ from codex.serializers.reader import (
9
+ ReaderViewInputSerializer,
10
+ )
11
+ from codex.views.const import FOLDER_GROUP, GROUP_RELATION, STORY_ARC_GROUP
12
+ from codex.views.session import SessionView
13
+ from codex.views.util import reparse_json_query_params
14
+
15
+ LOG = get_logger(__name__)
16
+ VALID_ARC_GROUPS = frozenset({"s", "v", "f", "a"})
17
+ _JSON_KEYS = frozenset({"arc", "breadcrumbs", "show"})
18
+ _BROWSER_SESSION_DEFAULTS = SessionView.SESSION_DEFAULTS[
19
+ SessionView.BROWSER_SESSION_KEY
20
+ ]
21
+ _DEFAULT_PARAMS = {
22
+ "breadcrumbs": _BROWSER_SESSION_DEFAULTS["breadcrumbs"],
23
+ "show": _BROWSER_SESSION_DEFAULTS["show"],
24
+ "top_group": _BROWSER_SESSION_DEFAULTS["top_group"],
25
+ }
26
+
27
+
28
+ class ReaderInitView(SessionView):
29
+ """Reader initialization."""
30
+
31
+ input_serializer_class = ReaderViewInputSerializer
32
+
33
+ def __init__(self, *args, **kwargs):
34
+ """Initialize instance vars."""
35
+ super().__init__(*args, **kwargs)
36
+ self.group_pks: dict[str, tuple[int, ...]] = {}
37
+
38
+ def _set_group_pks_from_breadcrumbs(self, groups, breadcrumbs):
39
+ """Set Multi-Group pks from breadcrumbs to the cache."""
40
+ index = len(breadcrumbs) - 1
41
+ min_index = max(index - 1, 0) if FOLDER_GROUP in groups else 0
42
+ while index > min_index:
43
+ crumb = breadcrumbs[index]
44
+ crumb_group = crumb.get("group")
45
+ if crumb_group in groups:
46
+ pks = crumb.get("pks", ())
47
+ self.group_pks[crumb_group] = pks
48
+ break
49
+ index -= 1
50
+ else:
51
+ crumb_group = ""
52
+ pks = ()
53
+ for group in groups:
54
+ self.group_pks[group] = pks
55
+ return crumb_group, pks
56
+
57
+ def get_group_pks_from_breadcrumbs(self, groups):
58
+ """Get Multi-Group pks from the breadcrumbs."""
59
+ # Return cached values
60
+ for group in groups:
61
+ if pks := self.group_pks.get(group):
62
+ return group, pks
63
+
64
+ # Validate input
65
+ breadcrumbs: tuple = self.params.get("breadcrumbs") # type: ignore
66
+ if not breadcrumbs:
67
+ return "", ()
68
+
69
+ return self._set_group_pks_from_breadcrumbs(groups, breadcrumbs)
70
+
71
+ def _ensure_arc_contains_comic(self, params):
72
+ """Arc sanity check."""
73
+ arc = params.get("arc")
74
+ if not arc:
75
+ return
76
+ pk = self.kwargs["pk"]
77
+ arc_group = arc.get("group")
78
+ arc_pks = arc.get("pks")
79
+ valid = False
80
+ if rel := GROUP_RELATION.get(arc_group):
81
+ arc_filter = {rel: arc_pks}
82
+ valid = Comic.objects.filter(pk=pk, **arc_filter).exists()
83
+ if not valid:
84
+ LOG.warning(f"Invalid arc {arc} submitted to reader for comic {pk}.")
85
+ params.pop("arc", None)
86
+
87
+ def _ensure_arc(self, params):
88
+ """arc.group validation."""
89
+ # Can't be in the serializer
90
+ arc = params.get("arc", {})
91
+
92
+ if arc.get("group") not in VALID_ARC_GROUPS:
93
+ top_group = params["top_group"]
94
+ if top_group in (FOLDER_GROUP, STORY_ARC_GROUP):
95
+ search_groups = (top_group,)
96
+ else:
97
+ search_groups = ("v", "s")
98
+
99
+ group, pks = self.get_group_pks_from_breadcrumbs(search_groups)
100
+ if not group:
101
+ group = "s"
102
+
103
+ arc["group"] = group
104
+ if pks:
105
+ arc["pks"] = pks
106
+
107
+ params["arc"] = arc
108
+
109
+ def _parse_params(self):
110
+ try:
111
+ data = self.request.GET
112
+ data = reparse_json_query_params(self.request.GET, _JSON_KEYS)
113
+ serializer = self.input_serializer_class(data=data)
114
+ serializer.is_valid(raise_exception=True)
115
+
116
+ params = deepcopy(_DEFAULT_PARAMS)
117
+ if serializer.validated_data:
118
+ params.update(serializer.validated_data) # type: ignore
119
+ self._ensure_arc_contains_comic(params)
120
+ self._ensure_arc(params)
121
+
122
+ self.params = MappingProxyType(params)
123
+ except Exception:
124
+ LOG.exception("validate")
125
+ raise
@@ -1,43 +1,30 @@
1
1
  """Views for reading comic books."""
2
2
 
3
- from types import MappingProxyType
4
-
5
3
  from comicbox.box import Comicbox
6
4
  from django.urls import reverse
7
- from drf_spectacular.types import OpenApiTypes
8
- from drf_spectacular.utils import OpenApiParameter, extend_schema
9
- from rest_framework.exceptions import NotFound, ValidationError
5
+ from drf_spectacular.utils import extend_schema
6
+ from rest_framework.exceptions import NotFound
10
7
  from rest_framework.response import Response
11
8
 
12
9
  from codex.librarian.importer.tasks import LazyImportComicsTask
13
10
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
14
11
  from codex.logger.logging import get_logger
15
- from codex.serializers.reader import ReaderArcSerializer, ReaderComicsSerializer
12
+ from codex.serializers.reader import ReaderComicsSerializer, ReaderViewInputSerializer
16
13
  from codex.serializers.redirect import ReaderRedirectSerializer
17
14
  from codex.views.reader.arcs import ReaderArcsView
18
- from codex.views.util import pop_name, reparse_json_query_params
15
+ from codex.views.util import pop_name
19
16
 
20
17
  LOG = get_logger(__name__)
21
- _VALID_ARC_GROUPS = frozenset({"f", "s", "a"})
22
- _JSON_KEYS = frozenset({"arc"})
23
18
 
24
19
 
25
- class ReaderView(
26
- ReaderArcsView,
27
- ):
20
+ class ReaderView(ReaderArcsView):
28
21
  """Get info for displaying comic pages."""
29
22
 
30
23
  serializer_class = ReaderComicsSerializer
31
- input_serializer_class = ReaderArcSerializer
32
24
 
33
25
  SESSION_KEY = "reader"
34
26
  TARGET = "reader"
35
27
 
36
- def __init__(self, *args, **kwargs):
37
- """Initialize instance vars."""
38
- super().__init__(*args, **kwargs)
39
- self.series_pks: tuple[int, ...] = ()
40
-
41
28
  def _raise_not_found(self):
42
29
  """Raise not found exception."""
43
30
  pk = self.kwargs.get("pk")
@@ -48,48 +35,6 @@ class ReaderView(
48
35
  }
49
36
  raise NotFound(detail=detail)
50
37
 
51
- def _validate_params(self, arc, top_group):
52
- """arc.group validation."""
53
- # Can't be in the serializer
54
- arc_group = arc.get("group", "")
55
- if not arc_group:
56
- series_pks = self.get_series_pks_from_breadcrumbs()
57
- if series_pks:
58
- arc_group = "s"
59
- arc["pks"] = series_pks
60
- if not arc_group:
61
- arc_group = top_group
62
- if arc_group not in _VALID_ARC_GROUPS:
63
- arc_group = None
64
-
65
- def _parse_params(self):
66
- data = self.request.GET
67
- arc = {}
68
- data = reparse_json_query_params(self.request.GET, _JSON_KEYS)
69
- arc_data = data.get("arc", [])
70
- if arc_data:
71
- serializer = self.input_serializer_class(data=arc_data)
72
- try:
73
- serializer.is_valid()
74
- arc: dict = serializer.validated_data # type: ignore
75
- except ValidationError:
76
- pass
77
-
78
- # From Browser Session
79
- top_group = self.get_from_session(
80
- "top_group", "s", session_key=self.BROWSER_SESSION_KEY
81
- )
82
-
83
- self._validate_params(arc, top_group)
84
-
85
- params = MappingProxyType(
86
- {
87
- "arc": arc,
88
- "top_group": top_group,
89
- }
90
- )
91
- self.params = params
92
-
93
38
  @staticmethod
94
39
  def _lazy_metadata(current, prev_book, next_book):
95
40
  """Get reader metadata from comicbox if it's not in the db."""
@@ -117,7 +62,6 @@ class ReaderView(
117
62
  def get_object(self):
118
63
  """Get the previous and next comics in a group or story arc."""
119
64
  # Books
120
- self.last_route = self.get_last_route()
121
65
  books = self.get_book_collection()
122
66
 
123
67
  current = books.get("current")
@@ -144,7 +88,9 @@ class ReaderView(
144
88
  arc["pks"] = (current.arc_pk,) # type: ignore
145
89
  arc["index"] = current.arc_index # type:ignore
146
90
  arc["count"] = current.arc_count # type: ignore
147
- close_route = pop_name(self.last_route)
91
+
92
+ close_route = self.get_last_route()
93
+ close_route = pop_name(close_route)
148
94
 
149
95
  return {
150
96
  "books": books,
@@ -154,14 +100,7 @@ class ReaderView(
154
100
  "mtime": mtime,
155
101
  }
156
102
 
157
- @extend_schema(
158
- parameters=[
159
- OpenApiParameter(
160
- "arc[group]", OpenApiTypes.STR, enum=sorted(_VALID_ARC_GROUPS)
161
- ),
162
- OpenApiParameter("arc[pks]", OpenApiTypes.STR),
163
- ]
164
- )
103
+ @extend_schema(parameters=[ReaderViewInputSerializer])
165
104
  def get(self, *args, **kwargs):
166
105
  """Get the book info."""
167
106
  self._parse_params()
codex/views/session.py CHANGED
@@ -69,8 +69,10 @@ class SessionView(AuthFilterGenericAPIView, ABC):
69
69
  "top_group": DEFAULTS["topGroup"],
70
70
  },
71
71
  READER_SESSION_KEY: {
72
+ "finish_on_last_page": DEFAULTS["finishOnLastPage"],
72
73
  "fit_to": DEFAULTS["fitTo"],
73
- "two_pages": False,
74
+ "two_pages": DEFAULTS["twoPages"],
75
+ "read_rtl_in_reverse": DEFAULTS["readRtlInReverse"],
74
76
  "reading_direction": DEFAULTS["readingDirection"],
75
77
  },
76
78
  }
@@ -102,17 +104,6 @@ class SessionView(AuthFilterGenericAPIView, ABC):
102
104
 
103
105
  return last_route
104
106
 
105
- def load_params_from_session(self, validated_data=None, session_key=None):
106
- """Load params from the session with defaults."""
107
- session_key = session_key if session_key else self.SESSION_KEY
108
- params = deepcopy(dict(self.SESSION_DEFAULTS[session_key]))
109
- session_params = self.request.session.get(session_key)
110
- if session_params:
111
- params.update(session_params)
112
- if validated_data:
113
- params.update(validated_data)
114
- self.params = MappingProxyType(params)
115
-
116
107
  @classmethod
117
108
  def _get_source_values_or_set_defaults(cls, defaults_dict, source_dict, data):
118
109
  """Recursively copy source_dict values into data or use defaults."""
@@ -128,6 +119,21 @@ class SessionView(AuthFilterGenericAPIView, ABC):
128
119
  )
129
120
  return result
130
121
 
122
+ def load_params_from_session(self, session_key=None, only=None):
123
+ """Get session settings with defaults."""
124
+ if not session_key:
125
+ session_key = self.SESSION_KEY
126
+ if only:
127
+ defaults = {}
128
+ for key in only:
129
+ if key:
130
+ defaults[key] = self.SESSION_DEFAULTS[session_key][key]
131
+ else:
132
+ defaults = self.SESSION_DEFAULTS[session_key]
133
+
134
+ session = self.request.session.get(session_key, defaults)
135
+ return self._get_source_values_or_set_defaults(defaults, session, {})
136
+
131
137
  def save_params_to_session(self, params): # reader session & browser final
132
138
  """Save the session from params with defaults for missing values."""
133
139
  try:
codex/views/settings.py CHANGED
@@ -5,30 +5,43 @@ from abc import ABC
5
5
  from drf_spectacular.utils import extend_schema
6
6
  from rest_framework.response import Response
7
7
 
8
+ from codex.serializers.settings import SettingsSerializer
8
9
  from codex.views.session import SessionView
10
+ from codex.views.util import reparse_json_query_params
11
+
12
+ _GET_JSON_PARAMS = frozenset({"only"})
9
13
 
10
14
 
11
15
  class SettingsView(SessionView, ABC):
12
16
  """Settings View."""
13
17
 
18
+ input_serializer_class = SettingsSerializer
19
+
20
+ def validate_settings_get(self, _validated_data, params: dict) -> dict:
21
+ """Change bad settings."""
22
+ return params
23
+
24
+ @extend_schema(parameters=[SettingsSerializer])
14
25
  def get(self, *args, **kwargs):
15
26
  """Get session settings."""
16
- defaults = self.SESSION_DEFAULTS[self.SESSION_KEY]
17
- session = self.request.session.get(self.SESSION_KEY, defaults)
18
- params = self._get_source_values_or_set_defaults(defaults, session, {})
19
- data = {}
20
- for key, filter_name in params.get("filters", {}).items():
21
- if filter_name:
22
- data[key] = filter_name
23
- params["filters"] = data
27
+ data = self.request.GET
28
+ data = reparse_json_query_params(data, _GET_JSON_PARAMS)
29
+ serializer = self.input_serializer_class(data=data)
30
+ serializer.is_valid(raise_exception=True)
31
+ validated_data = serializer.validated_data
32
+ only = validated_data.get("only") if validated_data else None # type: ignore
33
+ params = self.load_params_from_session(only=only)
34
+ params = self.validate_settings_get(validated_data, params)
24
35
  serializer = self.get_serializer(params)
25
36
  return Response(serializer.data)
26
37
 
27
38
  @extend_schema(responses=None)
28
- def put(self, *args, **kwargs):
39
+ def patch(self, *args, **kwargs):
29
40
  """Update session settings."""
30
41
  data = self.request.data # type: ignore
31
42
  serializer = self.get_serializer(data=data)
32
43
  serializer.is_valid(raise_exception=True)
33
- self.save_params_to_session(serializer.validated_data)
44
+ params = self.load_params_from_session()
45
+ params.update(serializer.validated_data)
46
+ self.save_params_to_session(params)
34
47
  return Response()
codex/views/util.py CHANGED
@@ -26,6 +26,7 @@ class Route(dict):
26
26
  bool(cmp is not None)
27
27
  and (self.group == cmp.group)
28
28
  and (set(self.pks) == set(cmp.pks))
29
+ and (self.page == cmp.page)
29
30
  )
30
31
 
31
32
  def __and__(self, cmp):
@@ -33,7 +34,7 @@ class Route(dict):
33
34
  return (
34
35
  bool(cmp is not None)
35
36
  and (self.group == cmp.group)
36
- and (set(self.pks) & set(cmp.pks))
37
+ and (self.pks == cmp.pks or (set(self.pks) & set(cmp.pks)))
37
38
  )
38
39
 
39
40
  dict = asdict
@@ -14,24 +14,28 @@ LOG = get_logger(__name__)
14
14
  class NotifierConsumer(AsyncWebsocketConsumer):
15
15
  """Base Notifier Consumer."""
16
16
 
17
- @staticmethod
18
- def _get_groups(user):
17
+ def _get_groups(self):
19
18
  """Dynamic groups by user type."""
20
19
  groups = [ChannelGroups.ALL.name]
21
- if user.is_staff:
20
+ user = self.scope.get("user") if self.scope else None
21
+ user_channel = None
22
+ if user:
23
+ user_channel = f"user_{user.id}"
24
+ else:
25
+ session = self.scope.get("session")
26
+ if session:
27
+ user_channel = f"user_{session.session_key}"
28
+ if user_channel:
29
+ groups += [user_channel]
30
+ if user and user.is_staff:
22
31
  groups += [ChannelGroups.ADMIN.name]
23
32
  return groups
24
33
 
25
34
  async def websocket_connect(self, message):
26
35
  """Authorize with user and connect websocket to groups."""
27
36
  # Authorization
28
- user = self.scope.get("user") if self.scope else None
29
- if not user:
30
- LOG.warning("Websockets not available for unregistered users.")
31
- return
32
-
33
37
  # Set groups for user.
34
- self.groups = self._get_groups(user)
38
+ self.groups = self._get_groups()
35
39
 
36
40
  await super().websocket_connect(message)
37
41
  LOG.debug(f"Websocket connected to {self.groups}")
@@ -44,6 +48,6 @@ class NotifierConsumer(AsyncWebsocketConsumer):
44
48
  """Send message to client."""
45
49
  text = event.get("text")
46
50
  if not text:
47
- LOG.warning("No text in message")
51
+ LOG.warning(f"No text in websockets message: {event}")
48
52
  return
49
53
  await self.send(text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codex
3
- Version: 1.6.0a9
3
+ Version: 1.6.0a11
4
4
  Summary: A comic archive web server.
5
5
  Home-page: https://github.com/ajslater/codex
6
6
  License: GPL-3.0-only