codex 1.6.0a8__py3-none-any.whl → 1.6.0a10__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 (557) hide show
  1. codex/integrity.py +1 -2
  2. codex/librarian/importer/cache.py +46 -133
  3. codex/librarian/importer/create_comics.py +3 -4
  4. codex/librarian/importer/create_covers.py +1 -3
  5. codex/librarian/importer/create_fks.py +3 -1
  6. codex/librarian/importer/failed_imports.py +1 -2
  7. codex/librarian/importer/importer.py +1 -1
  8. codex/librarian/importer/importerd.py +7 -7
  9. codex/librarian/importer/moved.py +4 -8
  10. codex/librarian/importer/tasks.py +2 -2
  11. codex/librarian/janitor/janitor.py +0 -3
  12. codex/librarian/librariand.py +0 -2
  13. codex/librarian/notifier/notifierd.py +1 -1
  14. codex/librarian/notifier/tasks.py +4 -4
  15. codex/middleware.py +1 -2
  16. codex/migrations/0027_import_order_and_covers.py +1 -66
  17. codex/models/comic.py +0 -1
  18. codex/models/groups.py +0 -3
  19. codex/serializers/admin.py +41 -40
  20. codex/serializers/browser/choices.py +22 -6
  21. codex/serializers/browser/metadata.py +45 -1
  22. codex/serializers/browser/mtime.py +21 -0
  23. codex/serializers/browser/page.py +2 -4
  24. codex/serializers/browser/settings.py +34 -14
  25. codex/serializers/fields.py +27 -10
  26. codex/serializers/reader.py +16 -6
  27. codex/serializers/route.py +9 -4
  28. codex/serializers/settings.py +12 -0
  29. codex/settings/settings.py +10 -1
  30. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js +1 -0
  31. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.br +0 -0
  32. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.gz +0 -0
  33. codex/static_root/assets/VCheckbox-CY3rcqlS.js +1 -0
  34. codex/static_root/assets/VCheckbox-CY3rcqlS.js.br +0 -0
  35. codex/static_root/assets/VCheckbox-CY3rcqlS.js.gz +0 -0
  36. codex/static_root/assets/{VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js → VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js} +1 -1
  37. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.br +0 -0
  38. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.gz +0 -0
  39. codex/static_root/assets/{VCheckboxBtn-B8Z4NA-t.js → VCheckboxBtn-D2tPTt6P.js} +1 -1
  40. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.br +0 -0
  41. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.gz +0 -0
  42. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js +1 -0
  43. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.br +0 -0
  44. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.gz +0 -0
  45. codex/static_root/assets/VCombobox-aJr--Ou7.js +1 -0
  46. codex/static_root/assets/VCombobox-aJr--Ou7.js.br +0 -0
  47. codex/static_root/assets/VCombobox-aJr--Ou7.js.gz +0 -0
  48. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js +1 -0
  49. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.br +0 -0
  50. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.gz +0 -0
  51. codex/static_root/assets/VDialog-61pzfUB2.js +1 -0
  52. codex/static_root/assets/VDialog-61pzfUB2.js.br +0 -0
  53. codex/static_root/assets/VDialog-61pzfUB2.js.gz +0 -0
  54. codex/static_root/assets/{VDialog-C5Nxd_zP.css → VDialog-lQ13FnRR.1c801ad756b2.css} +1 -1
  55. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  56. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  57. codex/static_root/assets/{VDialog-C5Nxd_zP.d327f505ef0e.css → VDialog-lQ13FnRR.css} +1 -1
  58. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  59. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  60. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js +1 -0
  61. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.br +0 -0
  62. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.gz +0 -0
  63. codex/static_root/assets/VExpansionPanels-rewm0P4z.js +1 -0
  64. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.br +0 -0
  65. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.gz +0 -0
  66. codex/static_root/assets/{VRadioGroup-BGa6_q5a.d1a3ad7266bd.js → VRadioGroup-QFQbK1qo.750a42b1b814.js} +1 -1
  67. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.br +0 -0
  68. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.gz +0 -0
  69. codex/static_root/assets/{VRadioGroup-BGa6_q5a.js → VRadioGroup-QFQbK1qo.js} +1 -1
  70. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.br +0 -0
  71. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.gz +0 -0
  72. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js +1 -0
  73. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.br +0 -0
  74. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.gz +0 -0
  75. codex/static_root/assets/VSelect-Dixy6okt.js +1 -0
  76. codex/static_root/assets/VSelect-Dixy6okt.js.br +0 -0
  77. codex/static_root/assets/VSelect-Dixy6okt.js.gz +0 -0
  78. codex/static_root/assets/{VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js → VSelectionControl-MNmxKc3J.08055d00917e.js} +1 -1
  79. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.br +0 -0
  80. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.gz +0 -0
  81. codex/static_root/assets/{VSelectionControl-C8DP9rgp.js → VSelectionControl-MNmxKc3J.js} +1 -1
  82. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.br +0 -0
  83. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.gz +0 -0
  84. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js +1 -0
  85. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.br +0 -0
  86. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.gz +0 -0
  87. codex/static_root/assets/VSlideGroup-DZEV0FoV.js +1 -0
  88. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.br +0 -0
  89. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.gz +0 -0
  90. codex/static_root/assets/{VTable-D1esKtlw.52165930e3b9.js → VTable-lcnYg9Ee.d72927b13c2e.js} +1 -1
  91. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.br +0 -0
  92. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.gz +0 -0
  93. codex/static_root/assets/{VTable-D1esKtlw.js → VTable-lcnYg9Ee.js} +1 -1
  94. codex/static_root/assets/VTable-lcnYg9Ee.js.br +0 -0
  95. codex/static_root/assets/VTable-lcnYg9Ee.js.gz +0 -0
  96. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js +1 -0
  97. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.br +0 -0
  98. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.gz +0 -0
  99. codex/static_root/assets/VTextField-BkFrjhdV.js +1 -0
  100. codex/static_root/assets/VTextField-BkFrjhdV.js.br +0 -0
  101. codex/static_root/assets/VTextField-BkFrjhdV.js.gz +0 -0
  102. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js +1 -0
  103. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.br +0 -0
  104. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.gz +0 -0
  105. codex/static_root/assets/VWindowItem-C__tAqdc.js +1 -0
  106. codex/static_root/assets/VWindowItem-C__tAqdc.js.br +0 -0
  107. codex/static_root/assets/VWindowItem-C__tAqdc.js.gz +0 -0
  108. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js +1 -0
  109. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.br +0 -0
  110. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.gz +0 -0
  111. codex/static_root/assets/admin-CXES2HXe.js +1 -0
  112. codex/static_root/assets/admin-CXES2HXe.js.br +0 -0
  113. codex/static_root/assets/admin-CXES2HXe.js.gz +0 -0
  114. codex/static_root/assets/{admin-drawer-panel-o87BwD38.ce39427c8201.css → admin-drawer-panel-mne3FIj6.561b3f75fe65.css} +1 -1
  115. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
  116. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
  117. codex/static_root/assets/{admin-drawer-panel-o87BwD38.css → admin-drawer-panel-mne3FIj6.css} +1 -1
  118. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
  119. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
  120. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js +30 -0
  121. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.br +0 -0
  122. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.gz +0 -0
  123. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js +30 -0
  124. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.br +0 -0
  125. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.gz +0 -0
  126. codex/static_root/assets/browser-BuDeXGEe.css +1 -0
  127. codex/static_root/assets/browser-BuDeXGEe.css.br +0 -0
  128. codex/static_root/assets/browser-BuDeXGEe.css.gz +0 -0
  129. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css +1 -0
  130. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.br +0 -0
  131. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.gz +0 -0
  132. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js +1 -0
  133. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.br +0 -0
  134. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.gz +0 -0
  135. codex/static_root/assets/browser-s6mFzrK9.js +1 -0
  136. codex/static_root/assets/browser-s6mFzrK9.js.br +0 -0
  137. codex/static_root/assets/browser-s6mFzrK9.js.gz +0 -0
  138. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +1 -0
  139. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  140. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  141. codex/static_root/assets/change-password-dialog-0u6IJA87.css +1 -0
  142. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  143. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  144. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js +1 -0
  145. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.br +0 -0
  146. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.gz +0 -0
  147. codex/static_root/assets/change-password-dialog-Bn20jYzD.js +1 -0
  148. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.br +0 -0
  149. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.gz +0 -0
  150. codex/static_root/assets/{confirm-dialog-DkU0twE6.a20006aa5ba6.js → confirm-dialog-CWszioQE.859051c35dd9.js} +1 -1
  151. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.br +0 -0
  152. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.gz +0 -0
  153. codex/static_root/assets/{confirm-dialog-DkU0twE6.js → confirm-dialog-CWszioQE.js} +1 -1
  154. codex/static_root/assets/confirm-dialog-CWszioQE.js.br +0 -0
  155. codex/static_root/assets/confirm-dialog-CWszioQE.js.gz +0 -0
  156. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js +1 -0
  157. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.br +0 -0
  158. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.gz +0 -0
  159. codex/static_root/assets/datetime-column-BSdlLt-4.js +1 -0
  160. codex/static_root/assets/datetime-column-BSdlLt-4.js.br +0 -0
  161. codex/static_root/assets/datetime-column-BSdlLt-4.js.gz +0 -0
  162. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js +1 -0
  163. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.br +0 -0
  164. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.gz +0 -0
  165. codex/static_root/assets/filter-D3gaGf87.js +1 -0
  166. codex/static_root/assets/filter-D3gaGf87.js.br +0 -0
  167. codex/static_root/assets/filter-D3gaGf87.js.gz +0 -0
  168. codex/static_root/assets/{flag-tab-D4ZNSbMR.e763a9937701.js → flag-tab-8vzpdySX.a982c7f0776a.js} +1 -1
  169. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.br +0 -0
  170. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.gz +0 -0
  171. codex/static_root/assets/{flag-tab-D4ZNSbMR.js → flag-tab-8vzpdySX.js} +1 -1
  172. codex/static_root/assets/flag-tab-8vzpdySX.js.br +0 -0
  173. codex/static_root/assets/flag-tab-8vzpdySX.js.gz +0 -0
  174. codex/static_root/assets/{group-tab-DucpNoBs.a6f37ae940c0.js → group-tab-Cz9t_ECi.a9e8aea79254.js} +1 -1
  175. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.br +0 -0
  176. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.gz +0 -0
  177. codex/static_root/assets/{group-tab-DucpNoBs.js → group-tab-Cz9t_ECi.js} +1 -1
  178. codex/static_root/assets/group-tab-Cz9t_ECi.js.br +0 -0
  179. codex/static_root/assets/group-tab-Cz9t_ECi.js.gz +0 -0
  180. codex/static_root/assets/{http-error-Cj0hTq-c.062a88ddd4dc.js → http-error-Cqtszzf1.b31df3d9c29f.js} +1 -1
  181. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.br +0 -0
  182. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.gz +0 -0
  183. codex/static_root/assets/{http-error-Cj0hTq-c.js → http-error-Cqtszzf1.js} +1 -1
  184. codex/static_root/assets/http-error-Cqtszzf1.js.br +0 -0
  185. codex/static_root/assets/http-error-Cqtszzf1.js.gz +0 -0
  186. codex/static_root/assets/{library-tab-CzksdS3Q.566acdb45dec.js → library-tab-DMlnswUU.30f454e84184.js} +1 -1
  187. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.br +0 -0
  188. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.gz +0 -0
  189. codex/static_root/assets/{library-tab-CzksdS3Q.js → library-tab-DMlnswUU.js} +1 -1
  190. codex/static_root/assets/library-tab-DMlnswUU.js.br +0 -0
  191. codex/static_root/assets/library-tab-DMlnswUU.js.gz +0 -0
  192. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js +32 -0
  193. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.br +0 -0
  194. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.gz +0 -0
  195. codex/static_root/assets/main-vjXu_QYL.js +32 -0
  196. codex/static_root/assets/main-vjXu_QYL.js.br +0 -0
  197. codex/static_root/assets/main-vjXu_QYL.js.gz +0 -0
  198. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css +1 -0
  199. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.br +0 -0
  200. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.gz +0 -0
  201. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css +1 -0
  202. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.br +0 -0
  203. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.gz +0 -0
  204. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js +1 -0
  205. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.br +0 -0
  206. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.gz +0 -0
  207. codex/static_root/assets/pagination-toolbar-JW78pa0q.js +1 -0
  208. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.br +0 -0
  209. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.gz +0 -0
  210. codex/static_root/assets/{pdf-doc-GkMk4o5L.ef24c859a7a4.js → pdf-doc-BiamrjRZ.4c9be1ade43c.js} +1 -1
  211. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.br +0 -0
  212. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.gz +0 -0
  213. codex/static_root/assets/{pdf-doc-GkMk4o5L.js → pdf-doc-BiamrjRZ.js} +1 -1
  214. codex/static_root/assets/pdf-doc-BiamrjRZ.js.br +0 -0
  215. codex/static_root/assets/pdf-doc-BiamrjRZ.js.gz +0 -0
  216. codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-BpsctRsY.5d72e393911a.css} +1 -1
  217. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.br +0 -0
  218. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.gz +0 -0
  219. codex/static_root/assets/{reader-CCCfpM3x.css → reader-BpsctRsY.css} +1 -1
  220. codex/static_root/assets/reader-BpsctRsY.css.br +0 -0
  221. codex/static_root/assets/reader-BpsctRsY.css.gz +0 -0
  222. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js +2 -0
  223. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.br +0 -0
  224. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.gz +0 -0
  225. codex/static_root/assets/reader-CDk8oZzB.js +2 -0
  226. codex/static_root/assets/reader-CDk8oZzB.js.br +0 -0
  227. codex/static_root/assets/reader-CDk8oZzB.js.gz +0 -0
  228. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js +1 -0
  229. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.br +0 -0
  230. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.gz +0 -0
  231. codex/static_root/assets/relation-chips-C5acBjpy.js +1 -0
  232. codex/static_root/assets/relation-chips-C5acBjpy.js.br +0 -0
  233. codex/static_root/assets/relation-chips-C5acBjpy.js.gz +0 -0
  234. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js +2 -0
  235. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.br +0 -0
  236. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.gz +0 -0
  237. codex/static_root/assets/settings-drawer-D_JuRi7F.js +2 -0
  238. codex/static_root/assets/settings-drawer-D_JuRi7F.js.br +0 -0
  239. codex/static_root/assets/settings-drawer-D_JuRi7F.js.gz +0 -0
  240. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +1 -0
  241. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
  242. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
  243. codex/static_root/assets/stats-tab-B4yKuY0c.css +1 -0
  244. codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
  245. codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
  246. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js +1 -0
  247. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.br +0 -0
  248. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.gz +0 -0
  249. codex/static_root/assets/stats-tab-ClqRTk1z.js +1 -0
  250. codex/static_root/assets/stats-tab-ClqRTk1z.js.br +0 -0
  251. codex/static_root/assets/stats-tab-ClqRTk1z.js.gz +0 -0
  252. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js +1 -0
  253. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.br +0 -0
  254. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.gz +0 -0
  255. codex/static_root/assets/task-tab-DF9qa6LO.js +1 -0
  256. codex/static_root/assets/task-tab-DF9qa6LO.js.br +0 -0
  257. codex/static_root/assets/task-tab-DF9qa6LO.js.gz +0 -0
  258. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js +1 -0
  259. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js.br +0 -0
  260. codex/static_root/assets/to-case-CGbULRaE.js +1 -0
  261. codex/static_root/assets/to-case-CGbULRaE.js.br +0 -0
  262. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js +1 -0
  263. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.br +0 -0
  264. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.gz +0 -0
  265. codex/static_root/assets/unauthorized-BjKuLH6k.js +1 -0
  266. codex/static_root/assets/unauthorized-BjKuLH6k.js.br +0 -0
  267. codex/static_root/assets/unauthorized-BjKuLH6k.js.gz +0 -0
  268. codex/static_root/assets/{unauthorized-DHV6Q1n-.b7847b2872a1.css → unauthorized-CibJYD8o.4c2de98c2b53.css} +1 -1
  269. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
  270. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
  271. codex/static_root/assets/{unauthorized-DHV6Q1n-.css → unauthorized-CibJYD8o.css} +1 -1
  272. codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
  273. codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
  274. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js +1 -0
  275. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.br +0 -0
  276. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.gz +0 -0
  277. codex/static_root/assets/user-tab-Dpxe3ygA.js +1 -0
  278. codex/static_root/assets/user-tab-Dpxe3ygA.js.br +0 -0
  279. codex/static_root/assets/user-tab-Dpxe3ygA.js.gz +0 -0
  280. codex/static_root/js/choices-admin.3521e2a863df.json +1 -0
  281. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  282. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  283. codex/static_root/js/choices-admin.json +1 -1
  284. codex/static_root/js/choices-admin.json.br +0 -0
  285. codex/static_root/js/choices-admin.json.gz +0 -0
  286. codex/static_root/manifest.35e46a3ec8fa.json +622 -0
  287. codex/static_root/manifest.35e46a3ec8fa.json.br +0 -0
  288. codex/static_root/manifest.35e46a3ec8fa.json.gz +0 -0
  289. codex/static_root/manifest.json +245 -252
  290. codex/static_root/manifest.json.br +0 -0
  291. codex/static_root/manifest.json.gz +0 -0
  292. codex/static_root/staticfiles.json +1 -1
  293. codex/urls/api/v3.py +1 -1
  294. codex/views/admin/api_key.py +5 -2
  295. codex/views/admin/stats.py +13 -20
  296. codex/views/admin/tasks.py +3 -7
  297. codex/views/auth.py +1 -1
  298. codex/views/bookmark.py +69 -51
  299. codex/views/browser/annotations.py +32 -45
  300. codex/views/browser/base.py +25 -8
  301. codex/views/browser/breadcrumbs.py +5 -5
  302. codex/views/browser/browser.py +20 -32
  303. codex/views/browser/choices.py +18 -15
  304. codex/views/browser/cover.py +30 -66
  305. codex/views/browser/filters/annotations.py +66 -14
  306. codex/views/browser/filters/bookmark.py +21 -5
  307. codex/views/browser/filters/group.py +29 -17
  308. codex/views/browser/filters/search.py +3 -4
  309. codex/views/browser/metadata.py +86 -47
  310. codex/views/browser/mtime.py +61 -0
  311. codex/views/browser/order_by.py +1 -6
  312. codex/views/browser/settings.py +53 -0
  313. codex/views/browser/validate.py +3 -0
  314. codex/views/const.py +32 -1
  315. codex/views/opds/v1/feed.py +3 -5
  316. codex/views/opds/v2/feed.py +3 -3
  317. codex/views/reader/arcs.py +42 -51
  318. codex/views/reader/books.py +48 -66
  319. codex/views/reader/init.py +121 -0
  320. codex/views/reader/reader.py +8 -66
  321. codex/views/session.py +15 -11
  322. codex/views/settings.py +23 -10
  323. codex/views/util.py +2 -1
  324. codex/websockets/consumers.py +13 -9
  325. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/METADATA +1 -1
  326. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/RECORD +329 -325
  327. codex/serializers/mtime.py +0 -25
  328. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +0 -1
  329. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  330. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  331. codex/static_root/assets/VCheckbox-DI-DgMb4.js +0 -1
  332. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  333. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  334. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  335. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  336. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  337. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  338. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +0 -1
  339. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  340. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  341. codex/static_root/assets/VCombobox-D1_0lCri.js +0 -1
  342. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  343. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  344. codex/static_root/assets/VDialog-C5Nxd_zP.css.br +0 -0
  345. codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
  346. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
  347. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
  348. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js +0 -1
  349. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  350. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  351. codex/static_root/assets/VDialog-opmV0YZz.js +0 -1
  352. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  353. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  354. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +0 -1
  355. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  356. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  357. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +0 -1
  358. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  359. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  360. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  361. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  362. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  363. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  364. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +0 -1
  365. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  366. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  367. codex/static_root/assets/VSelect-sIYW_-ud.js +0 -1
  368. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  369. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  370. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  371. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  372. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  373. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  374. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +0 -1
  375. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  376. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  377. codex/static_root/assets/VSlideGroup-BRYhkLH8.js +0 -1
  378. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  379. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  380. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  381. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  382. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  383. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  384. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +0 -1
  385. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  386. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  387. codex/static_root/assets/VTextField-BnGscKcY.js +0 -1
  388. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  389. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  390. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js +0 -1
  391. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  392. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  393. codex/static_root/assets/VWindowItem-B0KokcAk.js +0 -1
  394. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  395. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  396. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +0 -1
  397. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  398. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  399. codex/static_root/assets/admin-Y0bL21AB.js +0 -1
  400. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  401. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  402. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +0 -30
  403. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  404. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  405. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +0 -30
  406. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  407. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  408. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  409. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.gz +0 -0
  410. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  411. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.gz +0 -0
  412. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +0 -1
  413. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  414. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  415. codex/static_root/assets/browser-CMAtBCLx.css +0 -1
  416. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  417. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  418. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +0 -1
  419. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  420. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  421. codex/static_root/assets/browser-DoJk8uvb.js +0 -1
  422. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  423. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  424. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js +0 -1
  425. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  426. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  427. codex/static_root/assets/change-password-dialog-CH4xbjJa.js +0 -1
  428. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  429. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  430. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css +0 -1
  431. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
  432. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
  433. codex/static_root/assets/change-password-dialog-HvIvmmIE.css +0 -1
  434. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
  435. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
  436. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
  437. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  438. codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
  439. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  440. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +0 -1
  441. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  442. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  443. codex/static_root/assets/datetime-column-BfW_j_3Q.js +0 -1
  444. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  445. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  446. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +0 -1
  447. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  448. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  449. codex/static_root/assets/filter-DjxN78cK.js +0 -1
  450. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  451. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  452. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  453. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  454. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  455. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  456. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  457. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  458. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  459. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  460. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  461. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  462. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  463. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  464. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  465. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  466. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  467. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  468. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +0 -32
  469. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  470. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  471. codex/static_root/assets/main-DX-k2724.js +0 -32
  472. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  473. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  474. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +0 -1
  475. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  476. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  477. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +0 -1
  478. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  479. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  480. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +0 -1
  481. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  482. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  483. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +0 -1
  484. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  485. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  486. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  487. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  488. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  489. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  490. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  491. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  492. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  493. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  494. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +0 -2
  495. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  496. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  497. codex/static_root/assets/reader-Cl6mmQB_.js +0 -2
  498. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  499. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  500. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +0 -1
  501. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  502. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  503. codex/static_root/assets/relation-chips-CZ8VvR4y.js +0 -1
  504. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  505. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  506. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +0 -2
  507. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  508. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  509. codex/static_root/assets/settings-drawer-DyS0A2pN.js +0 -2
  510. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  511. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  512. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +0 -1
  513. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  514. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  515. codex/static_root/assets/stats-tab-R3Ekobfm.js +0 -1
  516. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  517. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  518. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css +0 -1
  519. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +0 -2
  520. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
  521. codex/static_root/assets/stats-tab-TsyG3wH_.css +0 -1
  522. codex/static_root/assets/stats-tab-TsyG3wH_.css.br +0 -2
  523. codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
  524. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +0 -1
  525. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  526. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  527. codex/static_root/assets/task-tab-BhLDK_7Q.js +0 -1
  528. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  529. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  530. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +0 -1
  531. codex/static_root/assets/to-case-BR2frjFx.js +0 -1
  532. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +0 -1
  533. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  534. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  535. codex/static_root/assets/unauthorized-BbXjAe5l.js +0 -1
  536. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  537. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  538. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
  539. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
  540. codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
  541. codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
  542. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js +0 -1
  543. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  544. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  545. codex/static_root/assets/user-tab-DxITIl2d.js +0 -1
  546. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  547. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  548. codex/static_root/js/choices-admin.d790df01c20a.json +0 -1
  549. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  550. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  551. codex/static_root/manifest.16d52597ef50.json +0 -629
  552. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  553. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  554. codex/views/mtime.py +0 -72
  555. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/LICENSE +0 -0
  556. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/WHEEL +0 -0
  557. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/entry_points.txt +0 -0
@@ -13,8 +13,10 @@ from codex.librarian.importer.const import (
13
13
  )
14
14
  from codex.logger.logging import get_logger
15
15
  from codex.models import AdminFlag, Comic
16
- from codex.serializers.browser.metadata import MetadataSerializer
16
+ from codex.models.functions import JsonGroupArray
17
+ from codex.serializers.browser.metadata import PREFETCH_PREFIX, MetadataSerializer
17
18
  from codex.views.browser.annotations import BrowserAnnotationsView
19
+ from codex.views.const import METADATA_GROUP_RELATION
18
20
 
19
21
  LOG = get_logger(__name__)
20
22
  _ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
@@ -54,6 +56,13 @@ class MetadataView(BrowserAnnotationsView):
54
56
  }
55
57
  )
56
58
 
59
+ def init_request(self):
60
+ """Initialize request."""
61
+ self.set_admin_flags()
62
+ self.parse_params()
63
+ self.set_model()
64
+ self.set_rel_prefix()
65
+
57
66
  def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
58
67
  """Limited allowed nav groups for metadata."""
59
68
  group = self.kwargs["group"]
@@ -79,6 +88,7 @@ class MetadataView(BrowserAnnotationsView):
79
88
  ):
80
89
  """Annotate the intersection of value and fk fields."""
81
90
  (filtered_qs, qs) = querysets
91
+
82
92
  for field in fields:
83
93
  # Annotate variant counts
84
94
  # If 1 variant, annotate value, otherwise None
@@ -133,6 +143,52 @@ class MetadataView(BrowserAnnotationsView):
133
143
  )
134
144
  return qs
135
145
 
146
+ def _query_groups(self):
147
+ """Query the through models to show group lists."""
148
+ groups = {}
149
+ if not self.model:
150
+ return groups
151
+ group = self.kwargs["group"]
152
+ rel = METADATA_GROUP_RELATION.get(group)
153
+ if not rel:
154
+ return groups
155
+ rel = rel + "__in"
156
+ pks = self.kwargs["pks"]
157
+ group_filter = {rel: pks}
158
+
159
+ for field_name in _GROUP_RELS:
160
+ try:
161
+ field = self.model._meta.get_field(field_name)
162
+ except FieldDoesNotExist:
163
+ continue
164
+ model = field.related_model
165
+ if not model:
166
+ continue
167
+
168
+ qs = model.objects.filter(**group_filter)
169
+ qs = qs.only("name").distinct()
170
+ group_by = self.get_group_by(model)
171
+ qs = qs.group_by(group_by)
172
+ qs = qs.annotate(ids=JsonGroupArray("id", distinct=True))
173
+ qs = qs.values("ids", "name")
174
+ groups[field_name] = qs
175
+ return groups
176
+
177
+ @staticmethod
178
+ def _get_optimized_m2m_query(key, qs):
179
+ if key == "contributors":
180
+ qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS)
181
+ qs = qs.only(*_CONTRIBUTOR_RELATIONS)
182
+ elif key == "story_arc_numbers":
183
+ qs = qs.select_related("story_arc")
184
+ qs = qs.only("story_arc", "number")
185
+ elif key == "identifiers":
186
+ qs = qs.select_related("identifier_type")
187
+ qs = qs.only("identifier_type", "nss", "url")
188
+ else:
189
+ qs = qs.only("name")
190
+ return qs
191
+
136
192
  def _query_m2m_intersections(self, filtered_qs):
137
193
  """Query the through models to figure out m2m intersections."""
138
194
  # Speed ok, but still does a query per m2m model
@@ -140,31 +196,17 @@ class MetadataView(BrowserAnnotationsView):
140
196
  pk_field = self.rel_prefix + "pk"
141
197
  comic_pks = filtered_qs.values_list(pk_field, flat=True)
142
198
  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
-
199
+ for field_name in COMIC_M2M_FIELD_NAMES:
154
200
  model = Comic._meta.get_field(field_name).related_model
155
201
  if not model:
156
202
  reason = f"No model found for comic field: {field_name}"
157
203
  raise ValueError(reason)
158
204
 
159
205
  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
206
  intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
166
207
  count=comic_pks_count
167
208
  )
209
+ intersection_qs = self._get_optimized_m2m_query(field_name, intersection_qs)
168
210
 
169
211
  m2m_intersections[field_name] = intersection_qs
170
212
  return m2m_intersections
@@ -185,42 +227,37 @@ class MetadataView(BrowserAnnotationsView):
185
227
  """Values for highlighting the current group."""
186
228
  if self.model and not self.is_model_comic:
187
229
  # move the name of the group to the correct field
188
- group_field = self.model.__name__.lower()
230
+ field = self.model.__name__.lower() + "_list"
189
231
  group_obj = {"pk": obj.pk, "name": obj.name}
190
- setattr(obj, group_field, group_obj)
232
+ group_list = [group_obj]
233
+ setattr(obj, field, group_list)
191
234
  obj.name = None
192
235
 
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
236
  @classmethod
211
237
  def _copy_m2m_intersections(cls, obj, m2m_intersections):
212
238
  """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:
239
+ # XXX It might even be faster to copy everything to a dict and not use the obj.
240
+ for key, qs in m2m_intersections.items():
241
+ serializer_key = (
242
+ f"{PREFETCH_PREFIX}{key}"
243
+ if key in ("identifiers", "contributors", "story_arc_numbers")
244
+ else key
245
+ )
246
+ if hasattr(obj, serializer_key):
216
247
  # real db fields need to use their special set method.
217
- field = getattr(obj, key)
218
- field.set(optimized_qs)
248
+ field = getattr(obj, serializer_key)
249
+ field.set(
250
+ qs,
251
+ clear=True,
252
+ )
219
253
  else:
220
254
  # 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)
255
+ setattr(obj, serializer_key, qs)
256
+
257
+ @staticmethod
258
+ def _copy_groups(obj, groups):
259
+ for field, group_qs in groups.items():
260
+ setattr(obj, field + "_list", group_qs)
224
261
 
225
262
  @staticmethod
226
263
  def _copy_conflicting_simple_fields(obj):
@@ -230,10 +267,11 @@ class MetadataView(BrowserAnnotationsView):
230
267
  val = getattr(obj, conflict_field)
231
268
  setattr(obj, field, val)
232
269
 
233
- def _copy_annotations_into_comic_fields(self, obj, m2m_intersections):
270
+ def _copy_annotations_into_comic_fields(self, obj, groups, m2m_intersections):
234
271
  """Copy a bunch of values that i couldn't fit cleanly in the main queryset."""
235
272
  self._path_security(obj)
236
273
  self._highlight_current_group(obj)
274
+ self._copy_groups(obj, groups)
237
275
  self._copy_m2m_intersections(obj, m2m_intersections)
238
276
  if not self.is_model_comic:
239
277
  self._copy_conflicting_simple_fields(obj)
@@ -270,10 +308,11 @@ class MetadataView(BrowserAnnotationsView):
270
308
  except (IndexError, ValueError) as exc:
271
309
  self._raise_not_found(exc)
272
310
 
311
+ groups = self._query_groups()
273
312
  m2m_intersections = self._query_m2m_intersections(filtered_qs)
274
- return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
313
+ return self._copy_annotations_into_comic_fields(obj, groups, m2m_intersections) # type: ignore
275
314
 
276
- @extend_schema(request=BrowserAnnotationsView.input_serializer_class)
315
+ @extend_schema(parameters=[BrowserAnnotationsView.input_serializer_class])
277
316
  def get(self, *_args, **_kwargs):
278
317
  """Get metadata for a filtered browse group."""
279
318
  # Init
@@ -0,0 +1,61 @@
1
+ """Get the mtimes for the submitted groups."""
2
+
3
+ from drf_spectacular.utils import extend_schema
4
+ from rest_framework.response import Response
5
+
6
+ from codex.logger.logging import get_logger
7
+ from codex.models.groups import Publisher
8
+ from codex.serializers.browser.mtime import GroupsMtimeSerializer, MtimeSerializer
9
+ from codex.util import max_none
10
+ from codex.views.browser.filters.annotations import BrowserAnnotationsFilterView
11
+ from codex.views.const import GROUP_MODEL_MAP
12
+
13
+ LOG = get_logger(__name__)
14
+
15
+
16
+ class MtimeView(BrowserAnnotationsFilterView):
17
+ """Get the mtimes for the submitted groups."""
18
+
19
+ input_serializer_class = GroupsMtimeSerializer
20
+ serializer_class = MtimeSerializer
21
+
22
+ REPARSE_JSON_FIELDS = frozenset({"groups", "filters"})
23
+ TARGET = "mtime"
24
+
25
+ def __init__(self, *args, **kwargs) -> None:
26
+ """Initialize."""
27
+ super().__init__(*args, **kwargs)
28
+ self.init_bookmark_data()
29
+
30
+ def _get_group_mtime(self, item):
31
+ """Get one group's mtimes."""
32
+ group = item["group"]
33
+ pks = item["pks"]
34
+
35
+ model = GROUP_MODEL_MAP[group]
36
+ if not model:
37
+ model = Publisher
38
+
39
+ return self.get_group_mtime(model, group, pks)
40
+
41
+ def get_max_groups_mtime(self):
42
+ """Get max mtime for all groups."""
43
+ max_mtime = None
44
+
45
+ for item in self.params["groups"]: # type: ignore
46
+ mtime = self._get_group_mtime(item)
47
+ max_mtime = max_none(max_mtime, mtime)
48
+ return max_mtime
49
+
50
+ @extend_schema(parameters=[GroupsMtimeSerializer])
51
+ def get(self, *args, **kwargs):
52
+ """Get the mtimes for the submitted groups."""
53
+ # Parse Request
54
+ self.parse_params()
55
+
56
+ max_mtime = self.get_max_groups_mtime()
57
+
58
+ # Serialize Response
59
+ result = {"max_mtime": max_mtime}
60
+ serializer = self.get_serializer(result)
61
+ return Response(serializer.data)
@@ -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
@@ -1,9 +1,12 @@
1
1
  """Common view constants."""
2
2
 
3
+ from datetime import datetime, timezone
3
4
  from types import MappingProxyType
4
5
 
5
6
  from django.contrib.auth.models import Group, User
6
7
  from django.contrib.sessions.models import Session
8
+ from django.db.models.expressions import Value
9
+ from django.db.models.fields import DateTimeField, PositiveSmallIntegerField
7
10
 
8
11
  from codex.models import (
9
12
  AgeRating,
@@ -64,6 +67,25 @@ GROUP_RELATION = MappingProxyType(
64
67
  STORY_ARC_GROUP: "story_arc_numbers__story_arc",
65
68
  }
66
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
+ )
67
89
  GROUP_ORDER = "rpisv"
68
90
  MODEL_REL_MAP = MappingProxyType(
69
91
  {
@@ -95,8 +117,12 @@ GROUP_MODELS = (
95
117
  Imprint,
96
118
  Series,
97
119
  Volume,
98
- Comic,
99
120
  Folder,
121
+ StoryArc,
122
+ )
123
+ STATS_GROUP_MODELS = (
124
+ *GROUP_MODELS,
125
+ Comic,
100
126
  )
101
127
  METADATA_MODELS = (
102
128
  AgeRating,
@@ -127,3 +153,8 @@ CONFIG_MODELS = (
127
153
  )
128
154
  MAX_OBJ_PER_PAGE = 100
129
155
  GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
156
+ EPOCH_START = datetime.fromtimestamp(0, tz=timezone.utc)
157
+ ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
158
+ NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
159
+ NONE_DATETIMEFIELD = Value(None, DateTimeField())
160
+ EPOCH_START_DATETIMEFILED = 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: