codex 1.6.0a9__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 (547) 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 +22 -6
  16. codex/serializers/browser/metadata.py +45 -1
  17. codex/serializers/browser/page.py +2 -4
  18. codex/serializers/browser/settings.py +34 -14
  19. codex/serializers/fields.py +19 -1
  20. codex/serializers/reader.py +11 -1
  21. codex/serializers/route.py +1 -1
  22. codex/serializers/settings.py +12 -0
  23. codex/settings/settings.py +10 -1
  24. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js +1 -0
  25. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.br +0 -0
  26. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.gz +0 -0
  27. codex/static_root/assets/VCheckbox-CY3rcqlS.js +1 -0
  28. codex/static_root/assets/VCheckbox-CY3rcqlS.js.br +0 -0
  29. codex/static_root/assets/VCheckbox-CY3rcqlS.js.gz +0 -0
  30. codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.9a47873b8548.js → VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js} +1 -1
  31. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.br +0 -0
  32. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.gz +0 -0
  33. codex/static_root/assets/{VCheckboxBtn-DLzF_WHB.js → VCheckboxBtn-D2tPTt6P.js} +1 -1
  34. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.br +0 -0
  35. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.gz +0 -0
  36. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js +1 -0
  37. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.br +0 -0
  38. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.gz +0 -0
  39. codex/static_root/assets/VCombobox-aJr--Ou7.js +1 -0
  40. codex/static_root/assets/VCombobox-aJr--Ou7.js.br +0 -0
  41. codex/static_root/assets/VCombobox-aJr--Ou7.js.gz +0 -0
  42. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js +1 -0
  43. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.br +0 -0
  44. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.gz +0 -0
  45. codex/static_root/assets/VDialog-61pzfUB2.js +1 -0
  46. codex/static_root/assets/VDialog-61pzfUB2.js.br +0 -0
  47. codex/static_root/assets/VDialog-61pzfUB2.js.gz +0 -0
  48. codex/static_root/assets/{VDialog-C5Nxd_zP.css → VDialog-lQ13FnRR.1c801ad756b2.css} +1 -1
  49. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  50. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  51. codex/static_root/assets/{VDialog-C5Nxd_zP.d327f505ef0e.css → VDialog-lQ13FnRR.css} +1 -1
  52. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  53. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  54. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js +1 -0
  55. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.br +0 -0
  56. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.gz +0 -0
  57. codex/static_root/assets/VExpansionPanels-rewm0P4z.js +1 -0
  58. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.br +0 -0
  59. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.gz +0 -0
  60. codex/static_root/assets/{VRadioGroup-B--7K_v5.4a4e9c2d6c42.js → VRadioGroup-QFQbK1qo.750a42b1b814.js} +1 -1
  61. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.br +0 -0
  62. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.gz +0 -0
  63. codex/static_root/assets/{VRadioGroup-B--7K_v5.js → VRadioGroup-QFQbK1qo.js} +1 -1
  64. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.br +0 -0
  65. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.gz +0 -0
  66. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js +1 -0
  67. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.br +0 -0
  68. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.gz +0 -0
  69. codex/static_root/assets/VSelect-Dixy6okt.js +1 -0
  70. codex/static_root/assets/VSelect-Dixy6okt.js.br +0 -0
  71. codex/static_root/assets/VSelect-Dixy6okt.js.gz +0 -0
  72. codex/static_root/assets/{VSelectionControl-CVQ9wkE2.c02186cceba6.js → VSelectionControl-MNmxKc3J.08055d00917e.js} +1 -1
  73. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.br +0 -0
  74. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.gz +0 -0
  75. codex/static_root/assets/{VSelectionControl-CVQ9wkE2.js → VSelectionControl-MNmxKc3J.js} +1 -1
  76. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.br +0 -0
  77. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.gz +0 -0
  78. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js +1 -0
  79. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.br +0 -0
  80. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.gz +0 -0
  81. codex/static_root/assets/VSlideGroup-DZEV0FoV.js +1 -0
  82. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.br +0 -0
  83. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.gz +0 -0
  84. codex/static_root/assets/{VTable-CrWkhkiP.6d530eea0c09.js → VTable-lcnYg9Ee.d72927b13c2e.js} +1 -1
  85. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.br +0 -0
  86. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.gz +0 -0
  87. codex/static_root/assets/{VTable-CrWkhkiP.js → VTable-lcnYg9Ee.js} +1 -1
  88. codex/static_root/assets/VTable-lcnYg9Ee.js.br +0 -0
  89. codex/static_root/assets/VTable-lcnYg9Ee.js.gz +0 -0
  90. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js +1 -0
  91. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.br +0 -0
  92. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.gz +0 -0
  93. codex/static_root/assets/VTextField-BkFrjhdV.js +1 -0
  94. codex/static_root/assets/VTextField-BkFrjhdV.js.br +0 -0
  95. codex/static_root/assets/VTextField-BkFrjhdV.js.gz +0 -0
  96. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js +1 -0
  97. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.br +0 -0
  98. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.gz +0 -0
  99. codex/static_root/assets/VWindowItem-C__tAqdc.js +1 -0
  100. codex/static_root/assets/VWindowItem-C__tAqdc.js.br +0 -0
  101. codex/static_root/assets/VWindowItem-C__tAqdc.js.gz +0 -0
  102. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js +1 -0
  103. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.br +0 -0
  104. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.gz +0 -0
  105. codex/static_root/assets/admin-CXES2HXe.js +1 -0
  106. codex/static_root/assets/admin-CXES2HXe.js.br +0 -0
  107. codex/static_root/assets/admin-CXES2HXe.js.gz +0 -0
  108. codex/static_root/assets/{admin-drawer-panel-o87BwD38.ce39427c8201.css → admin-drawer-panel-mne3FIj6.561b3f75fe65.css} +1 -1
  109. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
  110. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
  111. codex/static_root/assets/{admin-drawer-panel-o87BwD38.css → admin-drawer-panel-mne3FIj6.css} +1 -1
  112. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
  113. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
  114. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js +30 -0
  115. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.br +0 -0
  116. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.gz +0 -0
  117. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js +30 -0
  118. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.br +0 -0
  119. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.gz +0 -0
  120. codex/static_root/assets/browser-BuDeXGEe.css +1 -0
  121. codex/static_root/assets/browser-BuDeXGEe.css.br +0 -0
  122. codex/static_root/assets/browser-BuDeXGEe.css.gz +0 -0
  123. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css +1 -0
  124. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.br +0 -0
  125. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.gz +0 -0
  126. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js +1 -0
  127. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.br +0 -0
  128. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.gz +0 -0
  129. codex/static_root/assets/browser-s6mFzrK9.js +1 -0
  130. codex/static_root/assets/browser-s6mFzrK9.js.br +0 -0
  131. codex/static_root/assets/browser-s6mFzrK9.js.gz +0 -0
  132. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +1 -0
  133. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  134. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  135. codex/static_root/assets/change-password-dialog-0u6IJA87.css +1 -0
  136. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  137. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  138. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js +1 -0
  139. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.br +0 -0
  140. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.gz +0 -0
  141. codex/static_root/assets/change-password-dialog-Bn20jYzD.js +1 -0
  142. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.br +0 -0
  143. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.gz +0 -0
  144. codex/static_root/assets/{confirm-dialog-jwL7B98r.1cfd579cbcfe.js → confirm-dialog-CWszioQE.859051c35dd9.js} +1 -1
  145. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.br +0 -0
  146. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.gz +0 -0
  147. codex/static_root/assets/{confirm-dialog-jwL7B98r.js → confirm-dialog-CWszioQE.js} +1 -1
  148. codex/static_root/assets/confirm-dialog-CWszioQE.js.br +0 -0
  149. codex/static_root/assets/confirm-dialog-CWszioQE.js.gz +0 -0
  150. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js +1 -0
  151. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.br +0 -0
  152. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.gz +0 -0
  153. codex/static_root/assets/datetime-column-BSdlLt-4.js +1 -0
  154. codex/static_root/assets/datetime-column-BSdlLt-4.js.br +0 -0
  155. codex/static_root/assets/datetime-column-BSdlLt-4.js.gz +0 -0
  156. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js +1 -0
  157. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.br +0 -0
  158. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.gz +0 -0
  159. codex/static_root/assets/filter-D3gaGf87.js +1 -0
  160. codex/static_root/assets/filter-D3gaGf87.js.br +0 -0
  161. codex/static_root/assets/filter-D3gaGf87.js.gz +0 -0
  162. codex/static_root/assets/{flag-tab-Da2DBNyz.ffbe5ca37e1c.js → flag-tab-8vzpdySX.a982c7f0776a.js} +1 -1
  163. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.br +0 -0
  164. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.gz +0 -0
  165. codex/static_root/assets/{flag-tab-Da2DBNyz.js → flag-tab-8vzpdySX.js} +1 -1
  166. codex/static_root/assets/flag-tab-8vzpdySX.js.br +0 -0
  167. codex/static_root/assets/flag-tab-8vzpdySX.js.gz +0 -0
  168. codex/static_root/assets/{group-tab-BONYWOc1.6ddf321a4a56.js → group-tab-Cz9t_ECi.a9e8aea79254.js} +1 -1
  169. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.br +0 -0
  170. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.gz +0 -0
  171. codex/static_root/assets/{group-tab-BONYWOc1.js → group-tab-Cz9t_ECi.js} +1 -1
  172. codex/static_root/assets/group-tab-Cz9t_ECi.js.br +0 -0
  173. codex/static_root/assets/group-tab-Cz9t_ECi.js.gz +0 -0
  174. codex/static_root/assets/{http-error-D2Jnc20C.32846bc3a43d.js → http-error-Cqtszzf1.b31df3d9c29f.js} +1 -1
  175. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.br +0 -0
  176. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.gz +0 -0
  177. codex/static_root/assets/{http-error-D2Jnc20C.js → http-error-Cqtszzf1.js} +1 -1
  178. codex/static_root/assets/http-error-Cqtszzf1.js.br +0 -0
  179. codex/static_root/assets/http-error-Cqtszzf1.js.gz +0 -0
  180. codex/static_root/assets/{library-tab-B419agXA.js → library-tab-DMlnswUU.30f454e84184.js} +1 -1
  181. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.br +0 -0
  182. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.gz +0 -0
  183. codex/static_root/assets/{library-tab-B419agXA.d4b778172aaa.js → library-tab-DMlnswUU.js} +1 -1
  184. codex/static_root/assets/library-tab-DMlnswUU.js.br +0 -0
  185. codex/static_root/assets/library-tab-DMlnswUU.js.gz +0 -0
  186. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js +32 -0
  187. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.br +0 -0
  188. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.gz +0 -0
  189. codex/static_root/assets/main-vjXu_QYL.js +32 -0
  190. codex/static_root/assets/main-vjXu_QYL.js.br +0 -0
  191. codex/static_root/assets/main-vjXu_QYL.js.gz +0 -0
  192. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css +1 -0
  193. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.br +0 -0
  194. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.gz +0 -0
  195. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css +1 -0
  196. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.br +0 -0
  197. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.gz +0 -0
  198. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js +1 -0
  199. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.br +0 -0
  200. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.gz +0 -0
  201. codex/static_root/assets/pagination-toolbar-JW78pa0q.js +1 -0
  202. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.br +0 -0
  203. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.gz +0 -0
  204. codex/static_root/assets/{pdf-doc-8RVZqILx.2e7166dd26fb.js → pdf-doc-BiamrjRZ.4c9be1ade43c.js} +1 -1
  205. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.br +0 -0
  206. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.gz +0 -0
  207. codex/static_root/assets/{pdf-doc-8RVZqILx.js → pdf-doc-BiamrjRZ.js} +1 -1
  208. codex/static_root/assets/pdf-doc-BiamrjRZ.js.br +0 -0
  209. codex/static_root/assets/pdf-doc-BiamrjRZ.js.gz +0 -0
  210. codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-BpsctRsY.5d72e393911a.css} +1 -1
  211. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.br +0 -0
  212. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.gz +0 -0
  213. codex/static_root/assets/{reader-CCCfpM3x.css → reader-BpsctRsY.css} +1 -1
  214. codex/static_root/assets/reader-BpsctRsY.css.br +0 -0
  215. codex/static_root/assets/reader-BpsctRsY.css.gz +0 -0
  216. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js +2 -0
  217. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.br +0 -0
  218. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.gz +0 -0
  219. codex/static_root/assets/reader-CDk8oZzB.js +2 -0
  220. codex/static_root/assets/reader-CDk8oZzB.js.br +0 -0
  221. codex/static_root/assets/reader-CDk8oZzB.js.gz +0 -0
  222. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js +1 -0
  223. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.br +0 -0
  224. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.gz +0 -0
  225. codex/static_root/assets/relation-chips-C5acBjpy.js +1 -0
  226. codex/static_root/assets/relation-chips-C5acBjpy.js.br +0 -0
  227. codex/static_root/assets/relation-chips-C5acBjpy.js.gz +0 -0
  228. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js +2 -0
  229. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.br +0 -0
  230. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.gz +0 -0
  231. codex/static_root/assets/settings-drawer-D_JuRi7F.js +2 -0
  232. codex/static_root/assets/settings-drawer-D_JuRi7F.js.br +0 -0
  233. codex/static_root/assets/settings-drawer-D_JuRi7F.js.gz +0 -0
  234. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +1 -0
  235. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
  236. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
  237. codex/static_root/assets/stats-tab-B4yKuY0c.css +1 -0
  238. codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
  239. codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
  240. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js +1 -0
  241. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.br +0 -0
  242. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.gz +0 -0
  243. codex/static_root/assets/stats-tab-ClqRTk1z.js +1 -0
  244. codex/static_root/assets/stats-tab-ClqRTk1z.js.br +0 -0
  245. codex/static_root/assets/stats-tab-ClqRTk1z.js.gz +0 -0
  246. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js +1 -0
  247. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.br +0 -0
  248. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.gz +0 -0
  249. codex/static_root/assets/task-tab-DF9qa6LO.js +1 -0
  250. codex/static_root/assets/task-tab-DF9qa6LO.js.br +0 -0
  251. codex/static_root/assets/task-tab-DF9qa6LO.js.gz +0 -0
  252. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js +1 -0
  253. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js.br +0 -0
  254. codex/static_root/assets/to-case-CGbULRaE.js +1 -0
  255. codex/static_root/assets/to-case-CGbULRaE.js.br +0 -0
  256. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js +1 -0
  257. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.br +0 -0
  258. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.gz +0 -0
  259. codex/static_root/assets/unauthorized-BjKuLH6k.js +1 -0
  260. codex/static_root/assets/unauthorized-BjKuLH6k.js.br +0 -0
  261. codex/static_root/assets/unauthorized-BjKuLH6k.js.gz +0 -0
  262. codex/static_root/assets/{unauthorized-DHV6Q1n-.b7847b2872a1.css → unauthorized-CibJYD8o.4c2de98c2b53.css} +1 -1
  263. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
  264. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
  265. codex/static_root/assets/{unauthorized-DHV6Q1n-.css → unauthorized-CibJYD8o.css} +1 -1
  266. codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
  267. codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
  268. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js +1 -0
  269. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.br +0 -0
  270. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.gz +0 -0
  271. codex/static_root/assets/user-tab-Dpxe3ygA.js +1 -0
  272. codex/static_root/assets/user-tab-Dpxe3ygA.js.br +0 -0
  273. codex/static_root/assets/user-tab-Dpxe3ygA.js.gz +0 -0
  274. codex/static_root/js/choices-admin.3521e2a863df.json +1 -0
  275. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  276. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  277. codex/static_root/js/choices-admin.json +1 -1
  278. codex/static_root/js/choices-admin.json.br +0 -0
  279. codex/static_root/js/choices-admin.json.gz +0 -0
  280. codex/static_root/manifest.35e46a3ec8fa.json +622 -0
  281. codex/static_root/manifest.35e46a3ec8fa.json.br +0 -0
  282. codex/static_root/manifest.35e46a3ec8fa.json.gz +0 -0
  283. codex/static_root/manifest.json +245 -252
  284. codex/static_root/manifest.json.br +0 -0
  285. codex/static_root/manifest.json.gz +0 -0
  286. codex/static_root/staticfiles.json +1 -1
  287. codex/views/admin/api_key.py +5 -2
  288. codex/views/admin/stats.py +13 -20
  289. codex/views/admin/tasks.py +2 -3
  290. codex/views/auth.py +1 -1
  291. codex/views/bookmark.py +30 -16
  292. codex/views/browser/annotations.py +2 -0
  293. codex/views/browser/base.py +22 -6
  294. codex/views/browser/breadcrumbs.py +5 -5
  295. codex/views/browser/browser.py +14 -22
  296. codex/views/browser/choices.py +18 -15
  297. codex/views/browser/cover.py +30 -66
  298. codex/views/browser/filters/annotations.py +14 -6
  299. codex/views/browser/filters/group.py +24 -15
  300. codex/views/browser/filters/search.py +3 -4
  301. codex/views/browser/metadata.py +86 -47
  302. codex/views/browser/mtime.py +3 -14
  303. codex/views/browser/order_by.py +1 -6
  304. codex/views/browser/settings.py +53 -0
  305. codex/views/browser/validate.py +3 -0
  306. codex/views/const.py +24 -1
  307. codex/views/opds/v1/feed.py +3 -5
  308. codex/views/opds/v2/feed.py +3 -3
  309. codex/views/reader/arcs.py +42 -51
  310. codex/views/reader/books.py +47 -63
  311. codex/views/reader/init.py +121 -0
  312. codex/views/reader/reader.py +9 -70
  313. codex/views/session.py +15 -11
  314. codex/views/settings.py +23 -10
  315. codex/views/util.py +2 -1
  316. codex/websockets/consumers.py +13 -9
  317. {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/METADATA +1 -1
  318. {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/RECORD +321 -317
  319. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js +0 -1
  320. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  321. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  322. codex/static_root/assets/VCheckbox-eNspVUje.js +0 -1
  323. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  324. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  325. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  326. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  327. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  328. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  329. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js +0 -1
  330. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  331. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  332. codex/static_root/assets/VCombobox-BIO7lU_B.js +0 -1
  333. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  334. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  335. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js +0 -1
  336. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  337. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  338. codex/static_root/assets/VDialog-87AymJC5.js +0 -1
  339. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  340. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  341. codex/static_root/assets/VDialog-C5Nxd_zP.css.br +0 -0
  342. codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
  343. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
  344. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
  345. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js +0 -1
  346. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  347. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  348. codex/static_root/assets/VExpansionPanels-CgA3shsd.js +0 -1
  349. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  350. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  351. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  352. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  353. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  354. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  355. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +0 -1
  356. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  357. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  358. codex/static_root/assets/VSelect-BoJq222l.js +0 -1
  359. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  360. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  361. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  362. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  363. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  364. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  365. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js +0 -1
  366. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  367. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  368. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js +0 -1
  369. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  370. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  371. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  372. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  373. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  374. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  375. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js +0 -1
  376. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  377. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  378. codex/static_root/assets/VTextField-CkWbil3K.js +0 -1
  379. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  380. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  381. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js +0 -1
  382. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  383. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  384. codex/static_root/assets/VWindowItem-7z55Y4lK.js +0 -1
  385. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  386. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  387. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js +0 -1
  388. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  389. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  390. codex/static_root/assets/admin-D3OK784R.js +0 -1
  391. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  392. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  393. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js +0 -30
  394. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  395. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  396. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js +0 -30
  397. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  398. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  399. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  400. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.gz +0 -0
  401. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  402. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.gz +0 -0
  403. codex/static_root/assets/browser-BuU2x9y7.css +0 -1
  404. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  405. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  406. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +0 -1
  407. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  408. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  409. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +0 -1
  410. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  411. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  412. codex/static_root/assets/browser-CO9EYHIr.js +0 -1
  413. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  414. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  415. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js +0 -1
  416. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  417. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  418. codex/static_root/assets/change-password-dialog-CjZdzaIk.js +0 -1
  419. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  420. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  421. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css +0 -1
  422. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
  423. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
  424. codex/static_root/assets/change-password-dialog-HvIvmmIE.css +0 -1
  425. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
  426. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
  427. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  428. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  429. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  430. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  431. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +0 -1
  432. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  433. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  434. codex/static_root/assets/datetime-column-9w3GcKvo.js +0 -1
  435. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  436. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  437. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js +0 -1
  438. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  439. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  440. codex/static_root/assets/filter-C-pghOri.js +0 -1
  441. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  442. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  443. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  444. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  445. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  446. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  447. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  448. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  449. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  450. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  451. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  452. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  453. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  454. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  455. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  456. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  457. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  458. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  459. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +0 -32
  460. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  461. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  462. codex/static_root/assets/main-CMQwJ9aG.js +0 -32
  463. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  464. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  465. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +0 -1
  466. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  467. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  468. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +0 -1
  469. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  470. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  471. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +0 -1
  472. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  473. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  474. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +0 -1
  475. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  476. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  477. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  478. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  479. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  480. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  481. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  482. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  483. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  484. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  485. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js +0 -2
  486. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  487. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  488. codex/static_root/assets/reader-DNnK_Chr.js +0 -2
  489. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  490. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  491. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js +0 -1
  492. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  493. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  494. codex/static_root/assets/relation-chips-PYua7aXv.js +0 -1
  495. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  496. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  497. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js +0 -2
  498. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  499. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  500. codex/static_root/assets/settings-drawer-DbN6ISRH.js +0 -2
  501. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  502. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  503. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js +0 -1
  504. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  505. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  506. codex/static_root/assets/stats-tab-DImNhH8O.js +0 -1
  507. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  508. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  509. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css +0 -1
  510. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +0 -2
  511. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
  512. codex/static_root/assets/stats-tab-TsyG3wH_.css +0 -1
  513. codex/static_root/assets/stats-tab-TsyG3wH_.css.br +0 -2
  514. codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
  515. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js +0 -1
  516. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  517. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  518. codex/static_root/assets/task-tab-DPD53kjv.js +0 -1
  519. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  520. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  521. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +0 -1
  522. codex/static_root/assets/to-case-ehC9Ccyj.js +0 -1
  523. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js +0 -1
  524. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  525. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  526. codex/static_root/assets/unauthorized-BOIx7Y18.js +0 -1
  527. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  528. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  529. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
  530. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
  531. codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
  532. codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
  533. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js +0 -1
  534. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  535. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  536. codex/static_root/assets/user-tab-BABwfxLO.js +0 -1
  537. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  538. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  539. codex/static_root/js/choices-admin.d790df01c20a.json +0 -1
  540. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  541. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  542. codex/static_root/manifest.cbea5663003a.json +0 -629
  543. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  544. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  545. {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/LICENSE +0 -0
  546. {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/WHEEL +0 -0
  547. {codex-1.6.0a9.dist-info → codex-1.6.0a10.dist-info}/entry_points.txt +0 -0
@@ -9,12 +9,12 @@ from codex.librarian.covers.create import CoverCreateMixin
9
9
  from codex.librarian.covers.path import CoverPathMixin
10
10
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
11
11
  from codex.logger.logging import get_logger
12
- from codex.models import Comic, Folder, Volume
12
+ from codex.models import Comic, Volume
13
13
  from codex.models.paths import CustomCover
14
- from codex.serializers.choices import DEFAULTS
14
+ from codex.serializers.browser.settings import BrowserCoverInputSerializer
15
15
  from codex.views.browser.annotations import BrowserAnnotationsView
16
16
  from codex.views.const import (
17
- GROUP_RELATION,
17
+ CUSTOM_COVER_GROUP_RELATION,
18
18
  MISSING_COVER_FN,
19
19
  MISSING_COVER_NAME_MAP,
20
20
  STATIC_IMG_PATH,
@@ -39,6 +39,7 @@ class WEBPRenderer(BaseRenderer):
39
39
  class CoverView(BrowserAnnotationsView):
40
40
  """ComicCover View."""
41
41
 
42
+ input_serializer_class = BrowserCoverInputSerializer
42
43
  renderer_classes = (WEBPRenderer,)
43
44
  content_type = "image/webp"
44
45
  TARGET = "cover"
@@ -56,39 +57,21 @@ class CoverView(BrowserAnnotationsView):
56
57
  pks = self.kwargs["pks"]
57
58
  return pks[0], False
58
59
 
59
- def _use_dynamic_cover(self):
60
- if not self.params.get("dynamic_covers"):
61
- return False
62
- filters = self.params.get("filters", {})
63
- for key, value in filters.items():
64
- if key == "bookmark" and value != DEFAULTS["bookmarkFilter"]:
65
- return True
66
- if value:
67
- return True
68
-
69
- order_by = self.params.get("order_by")
70
- default_order_by = "filename" if self.model == Folder else "sort_name"
71
- if order_by != default_order_by:
72
- return True
73
-
74
- if self.params.get("order_reverse"):
75
- return True
76
-
77
- return False
60
+ def _get_custom_cover(self):
61
+ """Get Custom Cover."""
62
+ if self.model == Volume or not self.params.get("custom_covers"):
63
+ return None
64
+ group = self.kwargs["group"]
65
+ group_rel = CUSTOM_COVER_GROUP_RELATION[group]
66
+ pks = self.kwargs["pks"]
67
+ comic_filter = {f"{group_rel}__in": pks}
68
+ qs = CustomCover.objects.filter(**comic_filter)
69
+ qs = qs.only("pk")
70
+ return qs.first()
78
71
 
79
72
  def _get_dynamic_cover(self):
80
73
  """Get dynamic cover."""
81
74
  self.set_order_key()
82
- pks = self.kwargs["pks"]
83
-
84
- if self.model != Volume and self.params.get("custom_covers"):
85
- group = self.kwargs["group"]
86
- group_rel = "folder" if self.model == Folder else GROUP_RELATION[group] # type: ignore
87
- comic_filter = {f"{group_rel}__in": pks}
88
- custom_cover = CustomCover.objects.filter(**comic_filter).only("pk").first()
89
- if custom_cover:
90
- return custom_cover.pk, True
91
-
92
75
  comic_qs = self.get_filtered_queryset(Comic)
93
76
  comic_qs = self.annotate_order_aggregates(comic_qs, Comic)
94
77
  comic_qs = self.add_order_by(comic_qs, Comic)
@@ -99,40 +82,15 @@ class CoverView(BrowserAnnotationsView):
99
82
  cover_pk = comic.pk if comic else 0
100
83
  return cover_pk, False
101
84
 
102
- def _get_first_cover(self):
103
- """Get first cover."""
104
- pks = self.kwargs["pks"]
105
- order_by = (
106
- "path"
107
- if self.model == Folder
108
- else "name"
109
- if self.model == Volume
110
- else "sort_name"
111
- )
112
- group_qs = self.model.objects.filter(pk__in=pks) # type: ignore
113
- group_qs = group_qs.order_by(order_by)
114
- select_related = ["first_comic"]
115
- custom_covers = self.params.get("custom_covers")
116
- if custom_covers and self.model != Volume:
117
- select_related.append("custom_cover")
118
- group_qs = group_qs.select_related(*select_related)
119
- group_qs = group_qs.group_by("id") # type: ignore
120
- group_obj = group_qs.first()
121
- if custom_covers and self.model != Volume and group_obj.custom_cover:
122
- return group_obj.custom_cover.pk, True
123
- cover_pk = (
124
- group_obj.first_comic.pk if group_obj and group_obj.first_comic else 0
125
- )
126
- return cover_pk, False
127
-
128
85
  def _get_cover_pk(self) -> tuple[int, bool]:
129
86
  """Get Cover Pk queryset for comic queryset."""
130
87
  if self.model == Comic:
131
88
  cover_pk, custom = self._get_comic_cover()
132
- elif self._use_dynamic_cover():
133
- cover_pk, custom = self._get_dynamic_cover()
89
+ elif custom_cover := self._get_custom_cover():
90
+ cover_pk = custom_cover.pk
91
+ custom = True
134
92
  else:
135
- cover_pk, custom = self._get_first_cover()
93
+ cover_pk, custom = self._get_dynamic_cover()
136
94
  return cover_pk, custom
137
95
 
138
96
  def _get_missing_cover_path(self):
@@ -168,10 +126,16 @@ class CoverView(BrowserAnnotationsView):
168
126
  thumb_image_data = f.read()
169
127
  return thumb_image_data, content_type
170
128
 
171
- @extend_schema(responses={(200, content_type): OpenApiTypes.BINARY})
129
+ @extend_schema(
130
+ parameters=[BrowserAnnotationsView.input_serializer_class],
131
+ responses={(200, content_type): OpenApiTypes.BINARY},
132
+ )
172
133
  def get(self, *args, **kwargs): # type: ignore
173
134
  """Get comic cover."""
174
- self.init_request()
175
- pk, custom = self._get_cover_pk()
176
- thumb_image_data, content_type = self._get_cover_data(pk, custom)
177
- return HttpResponse(thumb_image_data, content_type=content_type)
135
+ try:
136
+ self.init_request()
137
+ pk, custom = self._get_cover_pk()
138
+ thumb_image_data, content_type = self._get_cover_data(pk, custom)
139
+ return HttpResponse(thumb_image_data, content_type=content_type)
140
+ except Exception:
141
+ LOG.exception("get")
@@ -20,10 +20,10 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
20
20
  super().__init__(*args, **kwargs)
21
21
  self.init_bookmark_data()
22
22
 
23
- def _get_query_filters(self, model, group, pks, bookmark_filter):
23
+ def _get_query_filters(self, model, group, pks, bookmark_filter, page_mtime=False): # noqa: PLR0913
24
24
  """Return all the filters except the group filter."""
25
25
  object_filter = self.get_group_acl_filter(model)
26
- object_filter &= self.get_group_filter(model, group, pks)
26
+ object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
27
27
  object_filter &= self.get_comic_field_filter(model)
28
28
  if bookmark_filter:
29
29
  # not needed because OuterRef is applied next
@@ -45,17 +45,25 @@ class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
45
45
  qs = qs.filter(**{f"{ann_name}__gt": 0})
46
46
  return qs
47
47
 
48
- def get_filtered_queryset(self, model, group=None, pks=None, bookmark_filter=True):
48
+ def get_filtered_queryset( # noqa: PLR0913
49
+ self, model, group=None, pks=None, bookmark_filter=True, page_mtime=False
50
+ ):
49
51
  """Get a filtered queryset for the model."""
50
- object_filter = self._get_query_filters(model, group, pks, bookmark_filter)
52
+ object_filter = self._get_query_filters(
53
+ model, group, pks, bookmark_filter, page_mtime=page_mtime
54
+ )
51
55
  qs = model.objects.filter(object_filter)
52
56
  qs = self.apply_search_filter(qs, model)
53
57
  return self._filter_by_child_count(qs, model)
54
58
 
55
- def get_group_mtime(self, model, group=None, pks=None):
59
+ def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
56
60
  """Get a filtered mtime for browser pages and mtime checker."""
57
61
  qs = self.get_filtered_queryset(
58
- model, group=group, pks=pks, bookmark_filter=self.is_bookmark_filtered
62
+ model,
63
+ group=group,
64
+ pks=pks,
65
+ bookmark_filter=self.is_bookmark_filtered,
66
+ page_mtime=page_mtime,
59
67
  )
60
68
  qs = qs.annotate(max_updated_at=Max("updated_at"))
61
69
 
@@ -2,8 +2,11 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.models import Comic
6
- from codex.views.const import FOLDER_GROUP, GROUP_MODEL_MAP, GROUP_RELATION
5
+ from codex.views.const import (
6
+ FILTER_ONLY_GROUP_RELATION,
7
+ FOLDER_GROUP,
8
+ GROUP_RELATION,
9
+ )
7
10
  from codex.views.session import SessionView
8
11
 
9
12
 
@@ -12,25 +15,31 @@ class GroupFilterView(SessionView):
12
15
 
13
16
  SESSION_KEY = SessionView.BROWSER_SESSION_KEY
14
17
 
15
- def get_group_filter(self, model, group=None, pks=None):
18
+ def _get_rel_for_pks(self, group, page_mtime=False):
19
+ """Get the relation from the model to the pks."""
20
+ # XXX these TARGET refs might be better as subclass get rel methods.
21
+ target: str = self.TARGET # type: ignore
22
+ if target in ("metadata", "mtime") or page_mtime:
23
+ # metadata, mtime, browser.page_mtime
24
+ rel = "pk"
25
+ elif target in ("choices", "cover"):
26
+ rel = FILTER_ONLY_GROUP_RELATION[group]
27
+ else:
28
+ rel = GROUP_RELATION[group]
29
+
30
+ rel += "__in"
31
+ return rel
32
+
33
+ def get_group_filter(self, group=None, pks=None, page_mtime=False):
16
34
  """Get filter for the displayed group."""
17
35
  if group is None:
18
36
  group = self.kwargs["group"]
19
37
  if pks is None:
20
38
  pks = self.kwargs["pks"]
21
39
 
22
- if pks and 0 not in pks: # type: ignore
23
- if model == GROUP_MODEL_MAP.get(group):
24
- # metadata only
25
- rel = "pk"
26
- elif model == Comic and group == FOLDER_GROUP:
27
- # choices & covers
28
- rel = "folders"
29
- else:
30
- rel = GROUP_RELATION[group]
31
-
32
- rel += "__in"
33
- group_filter_dict = {rel: pks} # type: ignore
40
+ if pks and 0 not in pks:
41
+ rel = self._get_rel_for_pks(group, page_mtime)
42
+ group_filter_dict = {rel: pks}
34
43
  elif group == FOLDER_GROUP:
35
44
  group_filter_dict = {"parent_folder": None}
36
45
  else:
@@ -135,11 +135,10 @@ class SearchFilterView(ComicFieldFilterView):
135
135
  if comic_ids:
136
136
  # Prefilter comic ids. If nothing is allowed, don't search.
137
137
  sqs = sqs.filter_comic_ids(comic_ids)
138
- binary = (
138
+ if (
139
139
  self.TARGET in frozenset({"cover", "choices"})
140
- or self.params.get("order_by", "") != "search_score"
141
- )
142
- if binary:
140
+ or self.params.get("order_by", "") != "search_score" # type: ignore
141
+ ):
143
142
  scored_pks = self._get_binary_search_scores(sqs)
144
143
  else:
145
144
  score_pairs, prev_pks, next_pks, scored_pks = (
@@ -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
@@ -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,