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
@@ -1,171 +1,84 @@
1
1
  """Update Groups timestamp for cover cache busting."""
2
2
 
3
- import os
4
-
5
3
  from django.db.models.aggregates import Count
6
4
  from django.db.models.functions.datetime import Now
7
5
  from django.db.models.query import Q
8
6
 
9
7
  from codex.librarian.importer.init import InitImporter
10
8
  from codex.librarian.importer.status import ImportStatusTypes
11
- from codex.models import Comic, Folder, Imprint, Publisher, Series, StoryArc, Volume
9
+ from codex.models import StoryArc, Volume
12
10
  from codex.status import Status
11
+ from codex.views.const import GROUP_MODELS
13
12
 
14
- _FIRST_COVER_MODEL_UPDATE_ORDER = (Volume, Series, Imprint, Publisher, Folder, StoryArc)
15
- _UPDATE_FIELDS = ("first_comic", "updated_at")
13
+ _UPDATE_FIELDS = ("updated_at",)
16
14
 
17
15
 
18
16
  class CacheUpdateImporter(InitImporter):
19
17
  """Update Groups timestamp for cover cache busting."""
20
18
 
21
19
  @staticmethod
22
- def _get_folder_batches(model_qs):
23
- """Create batches of folders by folder depth level."""
24
- # Collect all related folders
25
- folder_pks = model_qs.values_list("pk", flat=True)
26
- all_folders = (
27
- Folder.objects.filter(Q(folder__pk__in=folder_pks) | Q(pk__in=folder_pks))
28
- .select_related("first_comic")
29
- .only("path", *_UPDATE_FIELDS)
30
- )
31
-
32
- # Create a map of each folder by path depth level
33
- batch_map = {}
34
- for folder in all_folders:
35
- depth = folder.path.count(os.sep)
36
- if depth not in batch_map:
37
- batch_map[depth] = set()
38
- batch_map[depth].add(folder)
39
-
40
- # Order batches by reverse depth level to update bottom up.
41
- batches = []
42
- for _, folders in sorted(batch_map.items(), reverse=True):
43
- batches.append(folders)
44
-
45
- return tuple(batches)
20
+ def _get_update_filter(model, start_time, force_update_group_map):
21
+ # Get groups with comics updated during this import
22
+ rel = "storyarcnumber__" if model == StoryArc else ""
23
+ updated_at_rel = rel + "comic__updated_at__gt"
24
+ updated_filter = {updated_at_rel: start_time}
25
+ update_filter = Q(**updated_filter)
46
26
 
47
- @staticmethod
48
- def _update_first_cover_volume(obj):
49
- qs = Comic.objects.filter(volume=obj)
50
- qs = qs.order_by("issue_number", "issue_suffix", "sort_name").only("pk")
51
- obj.first_comic = qs.first()
27
+ # Get groups with custom covers updated during this import
28
+ if model != Volume:
29
+ update_filter |= Q(custom_cover__updated_at__gt=start_time)
52
30
 
53
- @staticmethod
54
- def _update_first_cover_group(obj, model, field):
55
- qs = model.objects.filter(**{field: obj})
56
- order_by = "name" if model == Volume else "sort_name"
57
- qs = qs.select_related("first_comic").order_by(order_by).only("first_comic")
58
- obj.first_comic = qs.first().first_comic
31
+ # Get groups to be force updated (usually those with deleted children)
32
+ if pks := force_update_group_map.get(model):
33
+ update_filter |= Q(pk__in=pks)
59
34
 
60
- @staticmethod
61
- def _update_first_cover_story_arc(obj):
62
- qs = Comic.objects.filter(story_arc_numbers__story_arc=obj)
63
- qs = qs.order_by("story_arc_numbers__number", "date").only("pk")
64
- obj.first_comic = qs.first()
35
+ return update_filter
65
36
 
66
37
  @staticmethod
67
- def _update_first_cover_folder(obj):
68
- first_comic = (
69
- Comic.objects.filter(parent_folder=obj)
70
- .order_by("path")
71
- .only("path")
72
- .first()
73
- )
74
- first_folder = (
75
- Folder.objects.filter(parent_folder=obj)
76
- .select_related("first_comic")
77
- .order_by("path")
78
- .only("first_comic", "path")
79
- .first()
80
- )
81
- if first_comic and not first_folder:
82
- obj.first_comic = first_comic
83
- elif first_folder and not first_comic:
84
- obj.first_comic = first_folder.first_comic
85
- elif first_folder and first_comic:
86
- if first_comic and first_comic.path < first_folder.path:
87
- obj.first_comic = first_comic
88
- else:
89
- obj.first_comic = first_folder.first_comic
90
-
91
- @classmethod
92
- def _update_first_cover(cls, model, obj):
93
- if model == Volume:
94
- cls._update_first_cover_volume(obj)
95
- elif model == Series:
96
- cls._update_first_cover_group(obj, Volume, "series")
97
- elif model == Imprint:
98
- cls._update_first_cover_group(obj, Series, "imprint")
99
- elif model == Publisher:
100
- cls._update_first_cover_group(obj, Imprint, "publisher")
101
- elif model == StoryArc:
102
- cls._update_first_cover_story_arc(obj)
103
- elif model == Folder:
104
- cls._update_first_cover_folder(obj)
38
+ def _add_child_count_filter(qs, model):
39
+ """Filter out groups with no comics."""
40
+ rel_prefix = "storyarcnumber__" if model == StoryArc else ""
41
+ rel_prefix += "comic"
42
+ qs = qs.alias(child_count=Count(f"{rel_prefix}__pk", distinct=True))
43
+ return qs.filter(child_count__gt=0)
105
44
 
106
45
  @classmethod
107
- def _update_group_first_comic(
108
- cls, force_update_group_map, model, start_time, log_list
109
- ):
110
- rel = "storyarcnumber__" if model == StoryArc else ""
111
- updated_at_rel = rel + "comic__updated_at__gt"
112
- updated_filter = {updated_at_rel: start_time}
113
- filter_query = (
114
- Q(**updated_filter)
115
- | Q(first_comic__isnull=True)
116
- | Q(updated_at__gt=start_time)
46
+ def _update_group_model(cls, force_update_group_map, model, start_time, log_list):
47
+ """Update a single group model."""
48
+ update_filter = cls._get_update_filter(
49
+ model, start_time, force_update_group_map
117
50
  )
118
- if model != Volume:
119
- filter_query |= Q(custom_cover__updated_at__gt=start_time)
120
- pks = force_update_group_map.get(model)
121
- if pks:
122
- filter_query |= Q(pk__in=pks)
123
- model_qs = model.objects.filter(filter_query)
51
+ qs = model.objects.filter(update_filter)
52
+ qs = cls._add_child_count_filter(qs, model)
124
53
 
125
- # only update those with comics
126
- rel_prefix = "storyarcnumber__" if model == StoryArc else ""
127
- rel_prefix += "comic"
128
- model_qs = model_qs.alias(child_count=Count(f"{rel_prefix}__pk", distinct=True))
129
- model_qs = model_qs.filter(child_count__gt=0)
130
- model_qs = model_qs.distinct()
131
- if model == Folder:
132
- batches = cls._get_folder_batches(model_qs)
133
- else:
134
- model_qs.select_related("first_comic").only(*_UPDATE_FIELDS)
135
- batches = (model_qs,)
54
+ qs = qs.distinct()
55
+ qs = qs.only(*_UPDATE_FIELDS)
136
56
 
137
- total_count = 0
138
- for model_qs in batches:
139
- updated = []
140
- for obj in model_qs:
141
- cls._update_first_cover(model, obj)
142
- obj.updated_at = Now()
143
- updated.append(obj)
144
-
145
- count = len(updated)
146
- if count:
147
- model.objects.bulk_update(updated, _UPDATE_FIELDS)
148
- total_count += count
149
- if total_count:
150
- log_list.append(f"{total_count} {model.__name__}s")
151
- return total_count
57
+ updated = []
58
+ for obj in qs:
59
+ obj.updated_at = Now()
60
+ updated.append(obj)
61
+
62
+ count = len(updated)
63
+ if count:
64
+ model.objects.bulk_update(updated, _UPDATE_FIELDS)
65
+ log_list.append(f"{count} {model.__name__}s")
66
+ return count
152
67
 
153
- def update_all_groups_first_comics(self, force_update_group_map, start_time):
68
+ def update_all_groups(self, force_update_group_map, start_time):
154
69
  """Update timestamps for each group for cover cache busting."""
155
70
  total_count = 0
156
71
  status = Status(ImportStatusTypes.GROUP_UPDATE)
157
72
  self.status_controller.start(status)
158
73
  try:
159
74
  log_list = []
160
- for model in _FIRST_COVER_MODEL_UPDATE_ORDER:
161
- # self.log.debug(f"Updating first covers for {model.__name__}s...")
162
- count = self._update_group_first_comic(
75
+ for model in GROUP_MODELS:
76
+ # self.log.debug(f"Updating timestamps for {model.__name__}s...")
77
+ count = self._update_group_model(
163
78
  force_update_group_map, model, start_time, log_list
164
79
  )
165
80
  if count:
166
- self.log.debug(
167
- f"Updated {count} first covers for {model.__name__}s"
168
- )
81
+ self.log.debug(f"Updated {count} {model.__name__}s timestamps.")
169
82
  status.add_complete(count)
170
83
  self.status_controller.update(status, notify=False)
171
84
  total_count += count
@@ -173,7 +86,7 @@ class CacheUpdateImporter(InitImporter):
173
86
  if total_count:
174
87
  groups_log = ", ".join(log_list)
175
88
  self.log.info( # type: ignore
176
- f"Updated first covers for {groups_log}."
89
+ f"Updated timestamps for {groups_log}."
177
90
  )
178
91
  self.changed += total_count
179
92
  finally:
@@ -211,8 +211,10 @@ class CreateForeignKeysImporter(CreateCoversImporter):
211
211
  folder_path_dict[path_length].add(path)
212
212
 
213
213
  # create each depth level first to ensure we can assign parents
214
- for paths in sorted(folder_path_dict.values()):
214
+ for depth_level in sorted(folder_path_dict):
215
+ paths = sorted(folder_path_dict[depth_level])
215
216
  count += self._bulk_folders_create_depth_level(paths, status)
217
+ self.log.debug(f"Created {count} Folders at depth level {depth_level}")
216
218
 
217
219
  if count:
218
220
  self.log.info(f"Created {count} Folders.")
@@ -88,7 +88,7 @@ class ComicImporter(MovedImporter):
88
88
 
89
89
  deleted_comic_groups = self.delete()
90
90
 
91
- self.update_all_groups_first_comics(deleted_comic_groups, self.start_time)
91
+ self.update_all_groups(deleted_comic_groups, self.start_time)
92
92
 
93
93
  finally:
94
94
  self._finish_apply_status()
@@ -8,7 +8,7 @@ from codex.librarian.importer.tasks import (
8
8
  AdoptOrphanFoldersTask,
9
9
  ImportDBDiffTask,
10
10
  LazyImportComicsTask,
11
- UpdateGroupsFirstComic,
11
+ UpdateGroupsTask,
12
12
  )
13
13
  from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
14
14
  from codex.models import Comic, Folder, Library
@@ -89,13 +89,13 @@ class ComicImporterThread(QueuedThread):
89
89
 
90
90
  self.status_controller.finish_many((moved_status, status))
91
91
 
92
- def _update_groups_first_comic(self, task):
92
+ def _update_groups(self, task):
93
93
  pks = Library.objects.filter(covers_only=False).values_list("pk", flat=True)
94
94
  start_time = task.start_time if task.start_time else timezone.now()
95
95
  for pk in pks:
96
96
  task = ImportDBDiffTask(library_id=pk)
97
97
  importer = self._create_importer(task)
98
- importer.update_all_groups_first_comics({}, start_time)
98
+ importer.update_all_groups({}, start_time)
99
99
  self.librarian_queue.put(LIBRARY_CHANGED_TASK)
100
100
 
101
101
  def process_item(self, item):
@@ -107,7 +107,7 @@ class ComicImporterThread(QueuedThread):
107
107
  self._lazy_import_metadata(task)
108
108
  elif isinstance(task, AdoptOrphanFoldersTask):
109
109
  self._adopt_orphan_folders()
110
- elif isinstance(task, UpdateGroupsFirstComic):
111
- self._update_groups_first_comic(task)
110
+ elif isinstance(task, UpdateGroupsTask):
111
+ self._update_groups(task)
112
112
  else:
113
113
  self.log.warning(f"Bad task sent to library updater {task}")
@@ -47,7 +47,7 @@ class AdoptOrphanFoldersTask(ImportTask):
47
47
 
48
48
 
49
49
  @dataclass
50
- class UpdateGroupsFirstComic(ImportTask):
51
- """Force the update of the first comics."""
50
+ class UpdateGroupsTask(ImportTask):
51
+ """Force the update of group timestamp."""
52
52
 
53
53
  start_time: datetime | None = None
@@ -5,7 +5,6 @@ from codex.librarian.covers.tasks import CoverRemoveOrphansTask
5
5
  from codex.librarian.importer.status import ImportStatusTypes
6
6
  from codex.librarian.importer.tasks import (
7
7
  AdoptOrphanFoldersTask,
8
- UpdateGroupsFirstComic,
9
8
  )
10
9
  from codex.librarian.janitor.cleanup import TOTAL_NUM_FK_CLASSES, CleanupMixin
11
10
  from codex.librarian.janitor.failed_imports import UpdateFailedImportsMixin
@@ -41,7 +40,6 @@ _JANITOR_STATII = (
41
40
  Status(JanitorStatusTypes.DB_OPTIMIZE),
42
41
  Status(JanitorStatusTypes.DB_BACKUP),
43
42
  Status(JanitorStatusTypes.CODEX_UPDATE),
44
- Status(ImportStatusTypes.GROUP_UPDATE),
45
43
  Status(CoverStatusTypes.FIND_ORPHAN),
46
44
  Status(CoverStatusTypes.PURGE_COVERS),
47
45
  Status(ImportStatusTypes.ADOPT_FOLDERS),
@@ -75,7 +73,6 @@ class Janitor(CleanupMixin, UpdateMixin, VacuumMixin, UpdateFailedImportsMixin):
75
73
  JanitorBackupTask(),
76
74
  JanitorUpdateTask(force=False),
77
75
  AdoptOrphanFoldersTask(),
78
- UpdateGroupsFirstComic(),
79
76
  CoverRemoveOrphansTask(),
80
77
  SearchIndexUpdateTask(False),
81
78
  SearchIndexMergeTask(optimize),
@@ -14,7 +14,6 @@ from codex.librarian.importer.importerd import ComicImporterThread
14
14
  from codex.librarian.importer.tasks import (
15
15
  AdoptOrphanFoldersTask,
16
16
  ImportTask,
17
- UpdateGroupsFirstComic,
18
17
  )
19
18
  from codex.librarian.janitor.janitor import Janitor
20
19
  from codex.librarian.janitor.janitord import JanitorThread
@@ -74,7 +73,6 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
74
73
  self.broadcast_queue = broadcast_queue
75
74
  startup_tasks = (
76
75
  AdoptOrphanFoldersTask(),
77
- UpdateGroupsFirstComic(),
78
76
  WatchdogSyncTask(),
79
77
  SearchIndexRebuildIfDBChangedTask(),
80
78
  )
@@ -22,7 +22,7 @@ class NotifierThread(AggregateMessageQueuedThread):
22
22
  and do a group_send with it's message.
23
23
  """
24
24
  item = {
25
- "group": task.group.name,
25
+ "group": task.group,
26
26
  "message": {
27
27
  "type": "send_text",
28
28
  "text": task.text,
@@ -10,9 +10,9 @@ class NotifierTask:
10
10
  """Handle with the Notifier."""
11
11
 
12
12
  text: str
13
- group: ChannelGroups
13
+ group: str
14
14
 
15
15
 
16
- LIBRARY_CHANGED_TASK = NotifierTask("LIBRARY_CHANGED", ChannelGroups.ALL)
17
- LIBRARIAN_STATUS_TASK = NotifierTask("LIBRARIAN_STATUS", ChannelGroups.ADMIN)
18
- FAILED_IMPORTS_TASK = NotifierTask("FAILED_IMPORTS", ChannelGroups.ADMIN)
16
+ LIBRARY_CHANGED_TASK = NotifierTask("LIBRARY_CHANGED", ChannelGroups.ALL.name)
17
+ LIBRARIAN_STATUS_TASK = NotifierTask("LIBRARIAN_STATUS", ChannelGroups.ADMIN.name)
18
+ FAILED_IMPORTS_TASK = NotifierTask("FAILED_IMPORTS", ChannelGroups.ADMIN.name)
codex/middleware.py CHANGED
@@ -22,8 +22,7 @@ class TimezoneMiddleware:
22
22
 
23
23
  def __call__(self, request):
24
24
  """Fix timeszone from the django session."""
25
- tzname = request.session.get("django_timezone")
26
- if tzname:
25
+ if tzname := request.session.get("django_timezone"):
27
26
  timezone.activate(tzname)
28
27
  else:
29
28
  timezone.deactivate()
@@ -71,33 +71,11 @@ class Migration(migrations.Migration):
71
71
  name="sort_name",
72
72
  field=models.CharField(db_index=True, default="", max_length=128),
73
73
  ),
74
- migrations.AddField(
75
- model_name="folder",
76
- name="first_comic",
77
- field=models.ForeignKey(
78
- default=None,
79
- null=True,
80
- on_delete=django.db.models.deletion.SET_DEFAULT,
81
- related_name="+",
82
- to="codex.comic",
83
- ),
84
- ),
85
74
  migrations.AddField(
86
75
  model_name="folder",
87
76
  name="sort_name",
88
77
  field=models.CharField(db_index=True, default="", max_length=128),
89
78
  ),
90
- migrations.AddField(
91
- model_name="imprint",
92
- name="first_comic",
93
- field=models.ForeignKey(
94
- default=None,
95
- null=True,
96
- on_delete=django.db.models.deletion.SET_DEFAULT,
97
- related_name="+",
98
- to="codex.comic",
99
- ),
100
- ),
101
79
  migrations.AddField(
102
80
  model_name="imprint",
103
81
  name="sort_name",
@@ -108,65 +86,21 @@ class Migration(migrations.Migration):
108
86
  name="covers_only",
109
87
  field=models.BooleanField(db_index=True, default=False),
110
88
  ),
111
- migrations.AddField(
112
- model_name="publisher",
113
- name="first_comic",
114
- field=models.ForeignKey(
115
- default=None,
116
- null=True,
117
- on_delete=django.db.models.deletion.SET_DEFAULT,
118
- related_name="+",
119
- to="codex.comic",
120
- ),
121
- ),
122
89
  migrations.AddField(
123
90
  model_name="publisher",
124
91
  name="sort_name",
125
92
  field=models.CharField(db_index=True, default="", max_length=128),
126
93
  ),
127
- migrations.AddField(
128
- model_name="series",
129
- name="first_comic",
130
- field=models.ForeignKey(
131
- default=None,
132
- null=True,
133
- on_delete=django.db.models.deletion.SET_DEFAULT,
134
- related_name="+",
135
- to="codex.comic",
136
- ),
137
- ),
138
94
  migrations.AddField(
139
95
  model_name="series",
140
96
  name="sort_name",
141
97
  field=models.CharField(db_index=True, default="", max_length=128),
142
98
  ),
143
- migrations.AddField(
144
- model_name="storyarc",
145
- name="first_comic",
146
- field=models.ForeignKey(
147
- default=None,
148
- null=True,
149
- on_delete=django.db.models.deletion.SET_DEFAULT,
150
- related_name="+",
151
- to="codex.comic",
152
- ),
153
- ),
154
99
  migrations.AddField(
155
100
  model_name="storyarc",
156
101
  name="sort_name",
157
102
  field=models.CharField(db_index=True, default="", max_length=128),
158
103
  ),
159
- migrations.AddField(
160
- model_name="volume",
161
- name="first_comic",
162
- field=models.ForeignKey(
163
- default=None,
164
- null=True,
165
- on_delete=django.db.models.deletion.SET_DEFAULT,
166
- related_name="+",
167
- to="codex.comic",
168
- ),
169
- ),
170
104
  migrations.AlterField(
171
105
  model_name="adminflag",
172
106
  name="key",
@@ -211,6 +145,7 @@ class Migration(migrations.Migration):
211
145
  ("ICD", "Covers Deleted"),
212
146
  ("ICL", "Covers Link"),
213
147
  ("IGU", "Group Update"),
148
+ ("IAF", "Adopt Folders"),
214
149
  ("JTD", "Cleanup Fk"),
215
150
  ("JCU", "Codex Update"),
216
151
  ("JCR", "Codex Restart"),
codex/models/comic.py CHANGED
@@ -176,7 +176,6 @@ class Comic(WatchedPathBrowserGroup):
176
176
 
177
177
  # Not useful
178
178
  custom_cover = None
179
- first_comic = None
180
179
 
181
180
  class Meta(WatchedPathBrowserGroup.Meta):
182
181
  """Constraints."""
codex/models/groups.py CHANGED
@@ -25,9 +25,6 @@ class BrowserGroupModel(BaseModel):
25
25
  custom_cover = ForeignKey(
26
26
  CustomCover, on_delete=SET_DEFAULT, null=True, default=None
27
27
  )
28
- first_comic = ForeignKey(
29
- "Comic", on_delete=SET_DEFAULT, null=True, default=None, related_name="+"
30
- )
31
28
 
32
29
  def set_sort_name(self):
33
30
  """Create sort_name for model."""
@@ -181,67 +181,68 @@ class AdminGroupSerializer(Serializer):
181
181
  """Group Counts."""
182
182
 
183
183
  publishers_count = IntegerField(required=False, read_only=True)
184
- imprints_count = IntegerField(required=False)
185
- series_count = IntegerField(required=False)
186
- volumes_count = IntegerField(required=False)
187
- issues_count = IntegerField(required=False)
188
- folders_count = IntegerField(required=False)
184
+ imprints_count = IntegerField(required=False, read_only=True)
185
+ series_count = IntegerField(required=False, read_only=True)
186
+ volumes_count = IntegerField(required=False, read_only=True)
187
+ issues_count = IntegerField(required=False, read_only=True)
188
+ folders_count = IntegerField(required=False, read_only=True)
189
+ story_arcs_count = IntegerField(required=False, read_only=True)
189
190
 
190
191
 
191
192
  class AdminFileTypeSerializer(Serializer):
192
193
  """File Type Counts."""
193
194
 
194
- pdf_count = IntegerField(required=False)
195
- cbz_count = IntegerField(required=False)
196
- cbr_count = IntegerField(required=False)
197
- cbt_count = IntegerField(required=False)
198
- unknown_count = IntegerField(required=False)
195
+ pdf_count = IntegerField(required=False, read_only=True)
196
+ cbz_count = IntegerField(required=False, read_only=True)
197
+ cbr_count = IntegerField(required=False, read_only=True)
198
+ cbt_count = IntegerField(required=False, read_only=True)
199
+ unknown_count = IntegerField(required=False, read_only=True)
199
200
 
200
201
 
201
202
  class AdminComicMetadataSerializer(Serializer):
202
203
  """Metadata Counts."""
203
204
 
204
- age_ratings_count = IntegerField(required=False)
205
- characters_count = IntegerField(required=False)
206
- contributors_count = IntegerField(required=False)
207
- contributor_persons_count = IntegerField(required=False)
208
- contributor_roles_count = IntegerField(required=False)
209
- countries_count = IntegerField(required=False)
210
- genres_count = IntegerField(required=False)
211
- identifiers_count = IntegerField(required=False)
212
- identifier_types_count = IntegerField(required=False)
213
- languages_count = IntegerField(required=False)
214
- locations_count = IntegerField(required=False)
215
- original_formats_count = IntegerField(required=False)
216
- series_groups_count = IntegerField(required=False)
217
- scan_infos_count = IntegerField(required=False)
205
+ age_ratings_count = IntegerField(required=False, read_only=True)
206
+ characters_count = IntegerField(required=False, read_only=True)
207
+ contributors_count = IntegerField(required=False, read_only=True)
208
+ contributor_persons_count = IntegerField(required=False, read_only=True)
209
+ contributor_roles_count = IntegerField(required=False, read_only=True)
210
+ countries_count = IntegerField(required=False, read_only=True)
211
+ genres_count = IntegerField(required=False, read_only=True)
212
+ identifiers_count = IntegerField(required=False, read_only=True)
213
+ identifier_types_count = IntegerField(required=False, read_only=True)
214
+ languages_count = IntegerField(required=False, read_only=True)
215
+ locations_count = IntegerField(required=False, read_only=True)
216
+ original_formats_count = IntegerField(required=False, read_only=True)
217
+ series_groups_count = IntegerField(required=False, read_only=True)
218
+ scan_infos_count = IntegerField(required=False, read_only=True)
218
219
  story_arcs_count = IntegerField(required=False)
219
- story_arc_numbers_count = IntegerField(required=False)
220
- tags_count = IntegerField(required=False)
221
- taggers_count = IntegerField(required=False)
222
- teams_count = IntegerField(required=False)
220
+ story_arc_numbers_count = IntegerField(required=False, read_only=True)
221
+ tags_count = IntegerField(required=False, read_only=True)
222
+ taggers_count = IntegerField(required=False, read_only=True)
223
+ teams_count = IntegerField(required=False, read_only=True)
223
224
 
224
225
 
225
226
  class AdminConfigSerializer(Serializer):
226
227
  """Config Information."""
227
228
 
228
- api_key = CharField(required=False)
229
- groups_count = IntegerField(required=False)
230
- libraries_count = IntegerField(required=False)
231
- sessions_anon_count = IntegerField(required=False)
232
- sessions_count = IntegerField(required=False)
233
- users_count = IntegerField(required=False)
229
+ api_key = CharField(required=False, read_only=True)
230
+ groups_count = IntegerField(required=False, read_only=True)
231
+ libraries_count = IntegerField(required=False, read_only=True)
232
+ sessions_anon_count = IntegerField(required=False, read_only=True)
233
+ sessions_count = IntegerField(required=False, read_only=True)
234
+ users_count = IntegerField(required=False, read_only=True)
234
235
 
235
236
 
236
237
  class AdminPlatformSerializer(Serializer):
237
238
  """Platform Information."""
238
239
 
239
- docker = BooleanField()
240
- machine = CharField()
241
- system = CharField()
242
- system_release = CharField()
243
- python = CharField()
244
- codex = CharField()
240
+ docker = BooleanField(read_only=True)
241
+ machine = CharField(read_only=True)
242
+ system = CharField(read_only=True)
243
+ system_release = CharField(read_only=True)
244
+ python = CharField(read_only=True)
245
+ codex = CharField(read_only=True)
245
246
 
246
247
 
247
248
  class AdminStatsSerializer(Serializer):
@@ -2,6 +2,7 @@
2
2
 
3
3
  from types import MappingProxyType
4
4
 
5
+ from rest_framework.fields import SerializerMethodField
5
6
  from rest_framework.serializers import (
6
7
  BooleanField,
7
8
  CharField,
@@ -10,10 +11,11 @@ from rest_framework.serializers import (
10
11
  IntegerField,
11
12
  Serializer,
12
13
  )
14
+ from rest_framework.utils.serializer_helpers import ReturnList
13
15
 
14
- from codex.serializers.browser.filters import (
16
+ from codex.serializers.browser.filters import BrowserSettingsFilterSerializer
17
+ from codex.serializers.fields import (
15
18
  BooleanListField,
16
- BrowserSettingsFilterSerializer,
17
19
  CharListField,
18
20
  DecadeListField,
19
21
  FloatListField,
@@ -56,11 +58,25 @@ _CHOICES_TYPE_SERIALIZER_MAP = MappingProxyType(
56
58
  IntListField: BrowserChoicesIntPkSerializer,
57
59
  }
58
60
  )
59
-
60
- _BSFS = BrowserSettingsFilterSerializer
61
- CHOICES_SERIALIZER_CLASS_MAP = MappingProxyType(
61
+ _CHOICES_SERIALIZER_CLASS_MAP = MappingProxyType(
62
62
  {
63
63
  name: _CHOICES_TYPE_SERIALIZER_MAP[field.__class__]
64
- for name, field in _BSFS().fields.items() # type: ignore
64
+ for name, field in BrowserSettingsFilterSerializer().fields.items() # type: ignore
65
65
  }
66
66
  )
67
+
68
+
69
+ class BrowserChoicesFilterSerializer(Serializer):
70
+ """Dynamic Serializer response by field type."""
71
+
72
+ choices = SerializerMethodField(read_only=True)
73
+
74
+ def get_choices(self, obj) -> ReturnList:
75
+ """Dynamic Serializer response by field type."""
76
+ key = obj.get("field_name", "")
77
+ choices = obj.get("choices", [])
78
+ serializer_class = _CHOICES_SERIALIZER_CLASS_MAP.get(
79
+ key, BrowserChoicesCharPkSerializer
80
+ )
81
+ serializer = serializer_class(choices, many=True)
82
+ return serializer.data # type: ignore