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,7 +2,6 @@
2
2
 
3
3
  from types import MappingProxyType
4
4
 
5
- from django.core.exceptions import FieldDoesNotExist
6
5
  from django.db.models import Count, IntegerField, Sum, Value
7
6
  from drf_spectacular.utils import extend_schema
8
7
  from rest_framework.exceptions import NotFound
@@ -13,8 +12,10 @@ from codex.librarian.importer.const import (
13
12
  )
14
13
  from codex.logger.logging import get_logger
15
14
  from codex.models import AdminFlag, Comic
16
- from codex.serializers.browser.metadata import MetadataSerializer
15
+ from codex.models.functions import JsonGroupArray
16
+ from codex.serializers.browser.metadata import PREFETCH_PREFIX, MetadataSerializer
17
17
  from codex.views.browser.annotations import BrowserAnnotationsView
18
+ from codex.views.const import METADATA_GROUP_RELATION
18
19
 
19
20
  LOG = get_logger(__name__)
20
21
  _ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
@@ -40,7 +41,21 @@ _COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_
40
41
  _PATH_GROUPS = frozenset({"c", "f"})
41
42
  _CONTRIBUTOR_RELATIONS = ("role", "person")
42
43
  _SUM_FIELDS = frozenset({"page_count", "size"})
43
- _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
44
+ _GROUP_RELS = MappingProxyType(
45
+ {
46
+ "i": ("publisher",),
47
+ "s": (
48
+ "publisher",
49
+ "imprint",
50
+ ),
51
+ "v": (
52
+ "publisher",
53
+ "imprint",
54
+ "series",
55
+ ),
56
+ "c": ("publisher", "imprint", "series", "volume"),
57
+ }
58
+ )
44
59
 
45
60
 
46
61
  class MetadataView(BrowserAnnotationsView):
@@ -54,6 +69,13 @@ class MetadataView(BrowserAnnotationsView):
54
69
  }
55
70
  )
56
71
 
72
+ def init_request(self):
73
+ """Initialize request."""
74
+ self.set_admin_flags()
75
+ self.parse_params()
76
+ self.set_model()
77
+ self.set_rel_prefix()
78
+
57
79
  def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
58
80
  """Limited allowed nav groups for metadata."""
59
81
  group = self.kwargs["group"]
@@ -79,6 +101,7 @@ class MetadataView(BrowserAnnotationsView):
79
101
  ):
80
102
  """Annotate the intersection of value and fk fields."""
81
103
  (filtered_qs, qs) = querysets
104
+
82
105
  for field in fields:
83
106
  # Annotate variant counts
84
107
  # If 1 variant, annotate value, otherwise None
@@ -133,6 +156,49 @@ class MetadataView(BrowserAnnotationsView):
133
156
  )
134
157
  return qs
135
158
 
159
+ def _query_groups(self):
160
+ """Query the through models to show group lists."""
161
+ groups = {}
162
+ if not self.model:
163
+ return groups
164
+ group = self.kwargs["group"]
165
+ rel = METADATA_GROUP_RELATION.get(group)
166
+ if not rel:
167
+ return groups
168
+ rel = rel + "__in"
169
+ pks = self.kwargs["pks"]
170
+ group_filter = {rel: pks}
171
+
172
+ for field_name in _GROUP_RELS.get(group, ()):
173
+ field = self.model._meta.get_field(field_name)
174
+ model = field.related_model
175
+ if not model:
176
+ continue
177
+
178
+ qs = model.objects.filter(**group_filter)
179
+ qs = qs.only("name").distinct()
180
+ group_by = self.get_group_by(model)
181
+ qs = qs.group_by(group_by)
182
+ qs = qs.annotate(ids=JsonGroupArray("id", distinct=True))
183
+ qs = qs.values("ids", "name")
184
+ groups[field_name] = qs
185
+ return groups
186
+
187
+ @staticmethod
188
+ def _get_optimized_m2m_query(key, qs):
189
+ if key == "contributors":
190
+ qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS)
191
+ qs = qs.only(*_CONTRIBUTOR_RELATIONS)
192
+ elif key == "story_arc_numbers":
193
+ qs = qs.select_related("story_arc")
194
+ qs = qs.only("story_arc", "number")
195
+ elif key == "identifiers":
196
+ qs = qs.select_related("identifier_type")
197
+ qs = qs.only("identifier_type", "nss", "url")
198
+ else:
199
+ qs = qs.only("name")
200
+ return qs
201
+
136
202
  def _query_m2m_intersections(self, filtered_qs):
137
203
  """Query the through models to figure out m2m intersections."""
138
204
  # Speed ok, but still does a query per m2m model
@@ -140,31 +206,17 @@ class MetadataView(BrowserAnnotationsView):
140
206
  pk_field = self.rel_prefix + "pk"
141
207
  comic_pks = filtered_qs.values_list(pk_field, flat=True)
142
208
  comic_pks_count = comic_pks.count()
143
- for field_name in COMIC_M2M_FIELD_NAMES | _GROUP_RELS:
144
- try:
145
- if (
146
- field_name in _GROUP_RELS
147
- and self.model
148
- and not self.model._meta.get_field(field_name)
149
- ):
150
- continue
151
- except FieldDoesNotExist:
152
- continue
153
-
209
+ for field_name in COMIC_M2M_FIELD_NAMES:
154
210
  model = Comic._meta.get_field(field_name).related_model
155
211
  if not model:
156
212
  reason = f"No model found for comic field: {field_name}"
157
213
  raise ValueError(reason)
158
214
 
159
215
  intersection_qs = model.objects.filter(comic__pk__in=comic_pks)
160
- if field_name in _GROUP_RELS:
161
- group_by = "name" if field_name == "volume" else "sort_name"
162
- intersection_qs = intersection_qs.group_by(group_by)
163
- if field_name == "identifiers":
164
- intersection_qs = intersection_qs.select_related("identifier_type")
165
216
  intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
166
217
  count=comic_pks_count
167
218
  )
219
+ intersection_qs = self._get_optimized_m2m_query(field_name, intersection_qs)
168
220
 
169
221
  m2m_intersections[field_name] = intersection_qs
170
222
  return m2m_intersections
@@ -185,42 +237,37 @@ class MetadataView(BrowserAnnotationsView):
185
237
  """Values for highlighting the current group."""
186
238
  if self.model and not self.is_model_comic:
187
239
  # move the name of the group to the correct field
188
- group_field = self.model.__name__.lower()
240
+ field = self.model.__name__.lower() + "_list"
189
241
  group_obj = {"pk": obj.pk, "name": obj.name}
190
- setattr(obj, group_field, group_obj)
242
+ group_list = [group_obj]
243
+ setattr(obj, field, group_list)
191
244
  obj.name = None
192
245
 
193
- @staticmethod
194
- def _get_optimized_m2m_query(key, qs):
195
- # XXX The prefetch gets removed by field.set() :(
196
- if key == "contributors":
197
- qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
198
- *_CONTRIBUTOR_RELATIONS
199
- )
200
- elif key == "story_arc_numbers":
201
- qs = qs.prefetch_related("story_arc")
202
- qs = qs.only("story_arc", "number")
203
- elif key == "identifiers":
204
- qs = qs.prefetch_related("identifier_type")
205
- qs = qs.only("identifier_type", "nss", "url")
206
- else:
207
- qs = qs.only("name")
208
- return qs
209
-
210
246
  @classmethod
211
247
  def _copy_m2m_intersections(cls, obj, m2m_intersections):
212
248
  """Copy the m2m intersections into the object."""
213
- for key, value in m2m_intersections.items():
214
- optimized_qs = cls._get_optimized_m2m_query(key, value)
215
- if hasattr(obj, key) and key not in _GROUP_RELS:
249
+ # XXX It might even be faster to copy everything to a dict and not use the obj.
250
+ for key, qs in m2m_intersections.items():
251
+ serializer_key = (
252
+ f"{PREFETCH_PREFIX}{key}"
253
+ if key in ("identifiers", "contributors", "story_arc_numbers")
254
+ else key
255
+ )
256
+ if hasattr(obj, serializer_key):
216
257
  # real db fields need to use their special set method.
217
- field = getattr(obj, key)
218
- field.set(optimized_qs)
258
+ field = getattr(obj, serializer_key)
259
+ field.set(
260
+ qs,
261
+ clear=True,
262
+ )
219
263
  else:
220
264
  # fake db field is just a queryset attached.
221
- if key in _GROUP_RELS:
222
- optimized_qs = optimized_qs[0] if len(optimized_qs) else None
223
- setattr(obj, key, optimized_qs)
265
+ setattr(obj, serializer_key, qs)
266
+
267
+ @staticmethod
268
+ def _copy_groups(obj, groups):
269
+ for field, group_qs in groups.items():
270
+ setattr(obj, field + "_list", group_qs)
224
271
 
225
272
  @staticmethod
226
273
  def _copy_conflicting_simple_fields(obj):
@@ -230,10 +277,11 @@ class MetadataView(BrowserAnnotationsView):
230
277
  val = getattr(obj, conflict_field)
231
278
  setattr(obj, field, val)
232
279
 
233
- def _copy_annotations_into_comic_fields(self, obj, m2m_intersections):
280
+ def _copy_annotations_into_comic_fields(self, obj, groups, m2m_intersections):
234
281
  """Copy a bunch of values that i couldn't fit cleanly in the main queryset."""
235
282
  self._path_security(obj)
236
283
  self._highlight_current_group(obj)
284
+ self._copy_groups(obj, groups)
237
285
  self._copy_m2m_intersections(obj, m2m_intersections)
238
286
  if not self.is_model_comic:
239
287
  self._copy_conflicting_simple_fields(obj)
@@ -270,10 +318,11 @@ class MetadataView(BrowserAnnotationsView):
270
318
  except (IndexError, ValueError) as exc:
271
319
  self._raise_not_found(exc)
272
320
 
321
+ groups = self._query_groups()
273
322
  m2m_intersections = self._query_m2m_intersections(filtered_qs)
274
- return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
323
+ return self._copy_annotations_into_comic_fields(obj, groups, m2m_intersections) # type: ignore
275
324
 
276
- @extend_schema(request=BrowserAnnotationsView.input_serializer_class)
325
+ @extend_schema(parameters=[BrowserAnnotationsView.input_serializer_class])
277
326
  def get(self, *_args, **_kwargs):
278
327
  """Get metadata for a filtered browse group."""
279
328
  # Init
@@ -1,7 +1,6 @@
1
1
  """Get the mtimes for the submitted groups."""
2
2
 
3
- from types import MappingProxyType
4
-
3
+ from drf_spectacular.utils import extend_schema
5
4
  from rest_framework.response import Response
6
5
 
7
6
  from codex.logger.logging import get_logger
@@ -21,24 +20,13 @@ class MtimeView(BrowserAnnotationsFilterView):
21
20
  serializer_class = MtimeSerializer
22
21
 
23
22
  REPARSE_JSON_FIELDS = frozenset({"groups", "filters"})
23
+ TARGET = "mtime"
24
24
 
25
25
  def __init__(self, *args, **kwargs) -> None:
26
26
  """Initialize."""
27
27
  super().__init__(*args, **kwargs)
28
28
  self.init_bookmark_data()
29
29
 
30
- def parse_params(self):
31
- """Parse GET params."""
32
- try:
33
- validated_data = super().parse_params()
34
- params = dict(self.params)
35
- params["groups"] = validated_data["groups"] # type: ignore
36
- self.params = MappingProxyType(params)
37
-
38
- except Exception:
39
- LOG.exception("parse")
40
- raise
41
-
42
30
  def _get_group_mtime(self, item):
43
31
  """Get one group's mtimes."""
44
32
  group = item["group"]
@@ -59,6 +47,7 @@ class MtimeView(BrowserAnnotationsFilterView):
59
47
  max_mtime = max_none(max_mtime, mtime)
60
48
  return max_mtime
61
49
 
50
+ @extend_schema(parameters=[GroupsMtimeSerializer])
62
51
  def get(self, *args, **kwargs):
63
52
  """Get the mtimes for the submitted groups."""
64
53
  # Parse Request
@@ -6,7 +6,6 @@ from codex.models import Comic
6
6
  from codex.views.browser.filters.annotations import (
7
7
  BrowserAnnotationsFilterView,
8
8
  )
9
- from codex.views.const import FOLDER_GROUP
10
9
 
11
10
 
12
11
  class BrowserOrderByView(BrowserAnnotationsFilterView):
@@ -14,11 +13,7 @@ class BrowserOrderByView(BrowserAnnotationsFilterView):
14
13
 
15
14
  def set_order_key(self):
16
15
  """Get the default order key for the view."""
17
- order_key: str = self.params.get("order_by", "") # type: ignore
18
- if not order_key:
19
- group = self.kwargs.get("group")
20
- order_key = "filename" if group == FOLDER_GROUP else "sort_name"
21
- self.order_key: str = order_key
16
+ self.order_key: str = self.params["order_by"]
22
17
  order_reverse = self.params.get("order_reverse")
23
18
  self.order_agg_func = Max if order_reverse else Min
24
19
 
@@ -1,6 +1,7 @@
1
1
  """Browser session view."""
2
2
 
3
3
  from codex.serializers.browser.settings import BrowserSettingsSerializer
4
+ from codex.views.const import GROUP_ORDER
4
5
  from codex.views.settings import SettingsView
5
6
 
6
7
 
@@ -11,3 +12,55 @@ class BrowserSettingsView(SettingsView):
11
12
  serializer_class = BrowserSettingsSerializer # type: ignore
12
13
 
13
14
  SESSION_KEY = SettingsView.BROWSER_SESSION_KEY
15
+
16
+ @staticmethod
17
+ def _strip_breadcrumb_names(params):
18
+ """Remove names from breadcrumbs."""
19
+ breadcrumbs = params.get("breadcrumbs")
20
+ if not breadcrumbs:
21
+ return params
22
+ nameless_breadcrumbs = []
23
+ for crumb in breadcrumbs:
24
+ crumb.pop("name", None)
25
+ nameless_breadcrumbs.append(crumb)
26
+ params["breadcrumbs"] = nameless_breadcrumbs
27
+ return params
28
+
29
+ @staticmethod
30
+ def _validate_browse_top_group(params, group, top_group):
31
+ """Validate top group for browse groups."""
32
+ show = params["show"]
33
+ if group == "r" or (
34
+ show.get(top_group)
35
+ and GROUP_ORDER.index(top_group) < GROUP_ORDER.index(group)
36
+ ):
37
+ return
38
+
39
+ for show_group, on in show.items():
40
+ if on:
41
+ params["top_group"] = show_group
42
+ break
43
+ else:
44
+ params["top_group"] = "c"
45
+
46
+ @classmethod
47
+ def _validate_top_group(cls, params, group, top_group):
48
+ """Validate top group."""
49
+ if group == top_group:
50
+ return
51
+
52
+ if group in ("f", "a"):
53
+ params["top_group"] = group
54
+ else:
55
+ cls._validate_browse_top_group(params, group, top_group)
56
+
57
+ def validate_settings_get(self, validated_data, params):
58
+ """Change bad settings."""
59
+ top_group = params["top_group"]
60
+ group = validated_data.get("group", "r") if validated_data else "r"
61
+ self._validate_top_group(params, group, top_group)
62
+
63
+ if not validated_data.get("breadcrumb_names", True):
64
+ params = self._strip_breadcrumb_names(params)
65
+
66
+ return params
@@ -15,6 +15,7 @@ from codex.views.const import (
15
15
  )
16
16
 
17
17
  LOG = get_logger(__name__)
18
+ _NO_TOP_GROUP_VALIDATE = frozenset({"metadata"})
18
19
 
19
20
 
20
21
  class BrowserValidateView(BrowserBaseView):
@@ -55,6 +56,8 @@ class BrowserValidateView(BrowserBaseView):
55
56
  return valid_top_groups
56
57
 
57
58
  def _validate_top_group(self, valid_top_groups):
59
+ # if self.TARGET in _NO_TOP_GROUP_VALIDATE:
60
+ # return
58
61
  nav_group = self.kwargs.get("group")
59
62
  top_group = self.params.get("top_group")
60
63
  if top_group not in valid_top_groups:
codex/views/const.py CHANGED
@@ -67,6 +67,25 @@ GROUP_RELATION = MappingProxyType(
67
67
  STORY_ARC_GROUP: "story_arc_numbers__story_arc",
68
68
  }
69
69
  )
70
+ FILTER_ONLY_GROUP_RELATION = MappingProxyType(
71
+ {
72
+ **GROUP_NAME_MAP,
73
+ FOLDER_GROUP: "folders",
74
+ STORY_ARC_GROUP: "story_arc_numbers__story_arc",
75
+ }
76
+ )
77
+ METADATA_GROUP_RELATION = MappingProxyType(
78
+ {
79
+ **GROUP_NAME_MAP,
80
+ COMIC_GROUP: "pk",
81
+ # Possible to list all groups in a non group view, but could get crazy.
82
+ # FOLDER_GROUP: "comic__folder",
83
+ # STORY_ARC_GROUP: "comic__story_arc_number__story_arc"
84
+ }
85
+ )
86
+ CUSTOM_COVER_GROUP_RELATION = MappingProxyType(
87
+ {**GROUP_NAME_MAP, FOLDER_GROUP: "folder", STORY_ARC_GROUP: "storyarc"}
88
+ )
70
89
  GROUP_ORDER = "rpisv"
71
90
  MODEL_REL_MAP = MappingProxyType(
72
91
  {
@@ -98,8 +117,12 @@ GROUP_MODELS = (
98
117
  Imprint,
99
118
  Series,
100
119
  Volume,
101
- Comic,
102
120
  Folder,
121
+ StoryArc,
122
+ )
123
+ STATS_GROUP_MODELS = (
124
+ *GROUP_MODELS,
125
+ Comic,
103
126
  )
104
127
  METADATA_MODELS = (
105
128
  AgeRating,
@@ -134,4 +157,4 @@ EPOCH_START = datetime.fromtimestamp(0, tz=timezone.utc)
134
157
  ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
135
158
  NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
136
159
  NONE_DATETIMEFIELD = Value(None, DateTimeField())
137
- EPOCH_START_DATETIMEFILED = Value(EPOCH_START)
160
+ EPOCH_START_DATETIMEFIELD = Value(EPOCH_START)
@@ -11,10 +11,10 @@ from rest_framework.throttling import ScopedRateThrottle
11
11
  from codex.librarian.importer.tasks import LazyImportComicsTask
12
12
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
13
13
  from codex.logger.logging import get_logger
14
+ from codex.serializers.browser.settings import OPDSSettingsSerializer
14
15
  from codex.serializers.opds.v1 import (
15
16
  OPDS1TemplateSerializer,
16
17
  )
17
- from codex.views.browser.browser import BrowserView
18
18
  from codex.views.const import FALSY, MAX_OBJ_PER_PAGE
19
19
  from codex.views.opds.auth import OPDSTemplateView
20
20
  from codex.views.opds.const import BLANK_TITLE, MimeType
@@ -56,6 +56,7 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
56
56
 
57
57
  template_name = "opds_v1/index.xml"
58
58
  serializer_class = OPDS1TemplateSerializer
59
+ input_serializer_class = OPDSSettingsSerializer
59
60
  throttle_classes = (ScopedRateThrottle,)
60
61
  throttle_scope = "opds"
61
62
  TARGET = "opds1"
@@ -254,10 +255,7 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
254
255
  self._set_user_agent_variables()
255
256
  self.skip_order_facets |= self.kwargs.get("group") == "c"
256
257
 
257
- @extend_schema(
258
- request=BrowserView.input_serializer_class,
259
- parameters=[BrowserView.input_serializer_class],
260
- )
258
+ @extend_schema(parameters=[input_serializer_class])
261
259
  def get(self, *_args, **_kwargs):
262
260
  """Get the feed."""
263
261
  self.init_request()
@@ -7,8 +7,8 @@ from rest_framework.response import Response
7
7
 
8
8
  from codex.logger.logging import get_logger
9
9
  from codex.models import AdminFlag
10
+ from codex.serializers.browser.settings import OPDSSettingsSerializer
10
11
  from codex.serializers.opds.v2 import OPDS2FeedSerializer
11
- from codex.views.browser.browser import BrowserView
12
12
  from codex.views.const import FALSY, MAX_OBJ_PER_PAGE
13
13
  from codex.views.opds.auth import OPDSAuthMixin
14
14
  from codex.views.opds.const import BLANK_TITLE
@@ -36,6 +36,7 @@ class OPDS2FeedView(OPDSAuthMixin, OPDS2PublicationView):
36
36
  throttle_scope = "opds"
37
37
 
38
38
  serializer_class = OPDS2FeedSerializer
39
+ input_serializer_class = OPDSSettingsSerializer
39
40
 
40
41
  def _title(self, browser_title):
41
42
  """Create the feed title."""
@@ -233,8 +234,7 @@ class OPDS2FeedView(OPDSAuthMixin, OPDS2PublicationView):
233
234
  # self._detect_user_agent()
234
235
 
235
236
  @extend_schema(
236
- request=BrowserView.input_serializer_class,
237
- parameters=[BrowserView.input_serializer_class],
237
+ parameters=[input_serializer_class],
238
238
  )
239
239
  def get(self, *_args, **_kwargs):
240
240
  """Get the feed."""
@@ -4,7 +4,7 @@ from types import MappingProxyType
4
4
 
5
5
  from codex.models import AdminFlag
6
6
  from codex.util import max_none
7
- from codex.views.const import FOLDER_GROUP
7
+ from codex.views.const import FOLDER_GROUP, STORY_ARC_GROUP
8
8
  from codex.views.reader.books import ReaderBooksView
9
9
 
10
10
 
@@ -16,6 +16,22 @@ class ReaderArcsView(ReaderBooksView):
16
16
  super().__init__(*args, **kwargs)
17
17
  self.params: MappingProxyType = MappingProxyType({})
18
18
 
19
+ def _get_group_arc(self, book, attr):
20
+ """Create the volume arc."""
21
+ group = getattr(book, attr, None)
22
+ if not group:
23
+ return None
24
+
25
+ _, arc_pks = self.get_group_pks_from_breadcrumbs([group])
26
+ if group.pk not in arc_pks:
27
+ arc_pks = (group.pk,)
28
+ return {
29
+ "group": attr[0],
30
+ "pks": arc_pks,
31
+ "name": group.name,
32
+ "mtime": group.updated_at,
33
+ }
34
+
19
35
  def _get_folder_arc(self, book):
20
36
  """Create the folder arc."""
21
37
  efv_flag = (
@@ -23,39 +39,16 @@ class ReaderArcsView(ReaderBooksView):
23
39
  .get(key=AdminFlag.FlagChoices.FOLDER_VIEW.value)
24
40
  .on
25
41
  )
42
+ if not efv_flag:
43
+ return None
26
44
 
27
- if efv_flag:
28
- folder = book.parent_folder
29
- folder_arc = {
30
- "group": FOLDER_GROUP,
31
- "pks": (folder.pk,),
32
- "name": folder.name,
33
- "mtime": folder.updated_at,
34
- }
35
- else:
36
- folder_arc = None
37
- return folder_arc
38
-
39
- def _get_series_arc(self, book):
40
- """Create the series arc."""
41
- series = book.series
42
- if series:
43
- arc_pks = self.get_series_pks_from_breadcrumbs()
44
- if book.series.pk not in arc_pks:
45
- arc_pks = (book.series.pk,)
46
- arc = (
47
- {
48
- "group": "s",
49
- "pks": arc_pks,
50
- "name": series.name,
51
- "mtime": series.updated_at,
52
- }
53
- if series
54
- else None
55
- )
56
- else:
57
- arc = None
58
- return arc
45
+ folder = book.parent_folder
46
+ return {
47
+ "group": FOLDER_GROUP,
48
+ "pks": (folder.pk,),
49
+ "name": folder.name,
50
+ "mtime": folder.updated_at,
51
+ }
59
52
 
60
53
  def _get_story_arcs(self, book):
61
54
  """Create story arcs."""
@@ -74,26 +67,24 @@ class ReaderArcsView(ReaderBooksView):
74
67
  def get_arcs(self, book):
75
68
  """Get all series/folder/story arcs."""
76
69
  # create top arcs
77
- folder_arc = self._get_folder_arc(book)
78
- series_arc = self._get_series_arc(book)
79
-
80
- # order top arcs
70
+ arcs = []
81
71
  top_group = self.params.get("top_group")
82
- if top_group == FOLDER_GROUP and folder_arc:
83
- arc = folder_arc
84
- other_arc = series_arc
85
- else:
72
+ arc = None
73
+ if series_arc := self._get_group_arc(book, "series"):
74
+ arcs.append(series_arc)
86
75
  arc = series_arc
87
- other_arc = folder_arc
88
-
89
- arcs = []
90
- arcs.append(arc)
91
- if other_arc:
92
- arcs.append(other_arc)
93
-
94
- # story arcs
95
- sas = self._get_story_arcs(book)
96
- arcs += sas
76
+ if volume_arc := self._get_group_arc(book, "volume"):
77
+ arcs.append(volume_arc)
78
+ if not arc:
79
+ arc = volume_arc
80
+ if folder_arc := self._get_folder_arc(book):
81
+ arcs.append(folder_arc)
82
+ if top_group == FOLDER_GROUP:
83
+ arc = folder_arc
84
+ if story_arcs := self._get_story_arcs(book):
85
+ arcs = [*arcs, *story_arcs]
86
+ if top_group == STORY_ARC_GROUP:
87
+ arc = story_arcs[0]
97
88
 
98
89
  max_mtime = None
99
90
  for arc in arcs: