codex 1.6.0a8__py3-none-any.whl → 1.6.0a10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of codex might be problematic. Click here for more details.

Files changed (557) hide show
  1. codex/integrity.py +1 -2
  2. codex/librarian/importer/cache.py +46 -133
  3. codex/librarian/importer/create_comics.py +3 -4
  4. codex/librarian/importer/create_covers.py +1 -3
  5. codex/librarian/importer/create_fks.py +3 -1
  6. codex/librarian/importer/failed_imports.py +1 -2
  7. codex/librarian/importer/importer.py +1 -1
  8. codex/librarian/importer/importerd.py +7 -7
  9. codex/librarian/importer/moved.py +4 -8
  10. codex/librarian/importer/tasks.py +2 -2
  11. codex/librarian/janitor/janitor.py +0 -3
  12. codex/librarian/librariand.py +0 -2
  13. codex/librarian/notifier/notifierd.py +1 -1
  14. codex/librarian/notifier/tasks.py +4 -4
  15. codex/middleware.py +1 -2
  16. codex/migrations/0027_import_order_and_covers.py +1 -66
  17. codex/models/comic.py +0 -1
  18. codex/models/groups.py +0 -3
  19. codex/serializers/admin.py +41 -40
  20. codex/serializers/browser/choices.py +22 -6
  21. codex/serializers/browser/metadata.py +45 -1
  22. codex/serializers/browser/mtime.py +21 -0
  23. codex/serializers/browser/page.py +2 -4
  24. codex/serializers/browser/settings.py +34 -14
  25. codex/serializers/fields.py +27 -10
  26. codex/serializers/reader.py +16 -6
  27. codex/serializers/route.py +9 -4
  28. codex/serializers/settings.py +12 -0
  29. codex/settings/settings.py +10 -1
  30. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js +1 -0
  31. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.br +0 -0
  32. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.gz +0 -0
  33. codex/static_root/assets/VCheckbox-CY3rcqlS.js +1 -0
  34. codex/static_root/assets/VCheckbox-CY3rcqlS.js.br +0 -0
  35. codex/static_root/assets/VCheckbox-CY3rcqlS.js.gz +0 -0
  36. codex/static_root/assets/{VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js → VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js} +1 -1
  37. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.br +0 -0
  38. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.gz +0 -0
  39. codex/static_root/assets/{VCheckboxBtn-B8Z4NA-t.js → VCheckboxBtn-D2tPTt6P.js} +1 -1
  40. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.br +0 -0
  41. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.gz +0 -0
  42. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js +1 -0
  43. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.br +0 -0
  44. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.gz +0 -0
  45. codex/static_root/assets/VCombobox-aJr--Ou7.js +1 -0
  46. codex/static_root/assets/VCombobox-aJr--Ou7.js.br +0 -0
  47. codex/static_root/assets/VCombobox-aJr--Ou7.js.gz +0 -0
  48. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js +1 -0
  49. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.br +0 -0
  50. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.gz +0 -0
  51. codex/static_root/assets/VDialog-61pzfUB2.js +1 -0
  52. codex/static_root/assets/VDialog-61pzfUB2.js.br +0 -0
  53. codex/static_root/assets/VDialog-61pzfUB2.js.gz +0 -0
  54. codex/static_root/assets/{VDialog-C5Nxd_zP.css → VDialog-lQ13FnRR.1c801ad756b2.css} +1 -1
  55. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  56. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  57. codex/static_root/assets/{VDialog-C5Nxd_zP.d327f505ef0e.css → VDialog-lQ13FnRR.css} +1 -1
  58. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  59. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  60. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js +1 -0
  61. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.br +0 -0
  62. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.gz +0 -0
  63. codex/static_root/assets/VExpansionPanels-rewm0P4z.js +1 -0
  64. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.br +0 -0
  65. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.gz +0 -0
  66. codex/static_root/assets/{VRadioGroup-BGa6_q5a.d1a3ad7266bd.js → VRadioGroup-QFQbK1qo.750a42b1b814.js} +1 -1
  67. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.br +0 -0
  68. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.gz +0 -0
  69. codex/static_root/assets/{VRadioGroup-BGa6_q5a.js → VRadioGroup-QFQbK1qo.js} +1 -1
  70. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.br +0 -0
  71. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.gz +0 -0
  72. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js +1 -0
  73. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.br +0 -0
  74. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.gz +0 -0
  75. codex/static_root/assets/VSelect-Dixy6okt.js +1 -0
  76. codex/static_root/assets/VSelect-Dixy6okt.js.br +0 -0
  77. codex/static_root/assets/VSelect-Dixy6okt.js.gz +0 -0
  78. codex/static_root/assets/{VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js → VSelectionControl-MNmxKc3J.08055d00917e.js} +1 -1
  79. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.br +0 -0
  80. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.gz +0 -0
  81. codex/static_root/assets/{VSelectionControl-C8DP9rgp.js → VSelectionControl-MNmxKc3J.js} +1 -1
  82. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.br +0 -0
  83. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.gz +0 -0
  84. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js +1 -0
  85. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.br +0 -0
  86. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.gz +0 -0
  87. codex/static_root/assets/VSlideGroup-DZEV0FoV.js +1 -0
  88. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.br +0 -0
  89. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.gz +0 -0
  90. codex/static_root/assets/{VTable-D1esKtlw.52165930e3b9.js → VTable-lcnYg9Ee.d72927b13c2e.js} +1 -1
  91. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.br +0 -0
  92. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.gz +0 -0
  93. codex/static_root/assets/{VTable-D1esKtlw.js → VTable-lcnYg9Ee.js} +1 -1
  94. codex/static_root/assets/VTable-lcnYg9Ee.js.br +0 -0
  95. codex/static_root/assets/VTable-lcnYg9Ee.js.gz +0 -0
  96. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js +1 -0
  97. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.br +0 -0
  98. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.gz +0 -0
  99. codex/static_root/assets/VTextField-BkFrjhdV.js +1 -0
  100. codex/static_root/assets/VTextField-BkFrjhdV.js.br +0 -0
  101. codex/static_root/assets/VTextField-BkFrjhdV.js.gz +0 -0
  102. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js +1 -0
  103. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.br +0 -0
  104. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.gz +0 -0
  105. codex/static_root/assets/VWindowItem-C__tAqdc.js +1 -0
  106. codex/static_root/assets/VWindowItem-C__tAqdc.js.br +0 -0
  107. codex/static_root/assets/VWindowItem-C__tAqdc.js.gz +0 -0
  108. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js +1 -0
  109. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.br +0 -0
  110. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.gz +0 -0
  111. codex/static_root/assets/admin-CXES2HXe.js +1 -0
  112. codex/static_root/assets/admin-CXES2HXe.js.br +0 -0
  113. codex/static_root/assets/admin-CXES2HXe.js.gz +0 -0
  114. codex/static_root/assets/{admin-drawer-panel-o87BwD38.ce39427c8201.css → admin-drawer-panel-mne3FIj6.561b3f75fe65.css} +1 -1
  115. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
  116. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
  117. codex/static_root/assets/{admin-drawer-panel-o87BwD38.css → admin-drawer-panel-mne3FIj6.css} +1 -1
  118. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
  119. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
  120. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js +30 -0
  121. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.br +0 -0
  122. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.gz +0 -0
  123. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js +30 -0
  124. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.br +0 -0
  125. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.gz +0 -0
  126. codex/static_root/assets/browser-BuDeXGEe.css +1 -0
  127. codex/static_root/assets/browser-BuDeXGEe.css.br +0 -0
  128. codex/static_root/assets/browser-BuDeXGEe.css.gz +0 -0
  129. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css +1 -0
  130. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.br +0 -0
  131. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.gz +0 -0
  132. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js +1 -0
  133. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.br +0 -0
  134. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.gz +0 -0
  135. codex/static_root/assets/browser-s6mFzrK9.js +1 -0
  136. codex/static_root/assets/browser-s6mFzrK9.js.br +0 -0
  137. codex/static_root/assets/browser-s6mFzrK9.js.gz +0 -0
  138. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +1 -0
  139. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  140. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  141. codex/static_root/assets/change-password-dialog-0u6IJA87.css +1 -0
  142. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  143. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  144. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js +1 -0
  145. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.br +0 -0
  146. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.gz +0 -0
  147. codex/static_root/assets/change-password-dialog-Bn20jYzD.js +1 -0
  148. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.br +0 -0
  149. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.gz +0 -0
  150. codex/static_root/assets/{confirm-dialog-DkU0twE6.a20006aa5ba6.js → confirm-dialog-CWszioQE.859051c35dd9.js} +1 -1
  151. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.br +0 -0
  152. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.gz +0 -0
  153. codex/static_root/assets/{confirm-dialog-DkU0twE6.js → confirm-dialog-CWszioQE.js} +1 -1
  154. codex/static_root/assets/confirm-dialog-CWszioQE.js.br +0 -0
  155. codex/static_root/assets/confirm-dialog-CWszioQE.js.gz +0 -0
  156. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js +1 -0
  157. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.br +0 -0
  158. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.gz +0 -0
  159. codex/static_root/assets/datetime-column-BSdlLt-4.js +1 -0
  160. codex/static_root/assets/datetime-column-BSdlLt-4.js.br +0 -0
  161. codex/static_root/assets/datetime-column-BSdlLt-4.js.gz +0 -0
  162. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js +1 -0
  163. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.br +0 -0
  164. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.gz +0 -0
  165. codex/static_root/assets/filter-D3gaGf87.js +1 -0
  166. codex/static_root/assets/filter-D3gaGf87.js.br +0 -0
  167. codex/static_root/assets/filter-D3gaGf87.js.gz +0 -0
  168. codex/static_root/assets/{flag-tab-D4ZNSbMR.e763a9937701.js → flag-tab-8vzpdySX.a982c7f0776a.js} +1 -1
  169. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.br +0 -0
  170. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.gz +0 -0
  171. codex/static_root/assets/{flag-tab-D4ZNSbMR.js → flag-tab-8vzpdySX.js} +1 -1
  172. codex/static_root/assets/flag-tab-8vzpdySX.js.br +0 -0
  173. codex/static_root/assets/flag-tab-8vzpdySX.js.gz +0 -0
  174. codex/static_root/assets/{group-tab-DucpNoBs.a6f37ae940c0.js → group-tab-Cz9t_ECi.a9e8aea79254.js} +1 -1
  175. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.br +0 -0
  176. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.gz +0 -0
  177. codex/static_root/assets/{group-tab-DucpNoBs.js → group-tab-Cz9t_ECi.js} +1 -1
  178. codex/static_root/assets/group-tab-Cz9t_ECi.js.br +0 -0
  179. codex/static_root/assets/group-tab-Cz9t_ECi.js.gz +0 -0
  180. codex/static_root/assets/{http-error-Cj0hTq-c.062a88ddd4dc.js → http-error-Cqtszzf1.b31df3d9c29f.js} +1 -1
  181. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.br +0 -0
  182. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.gz +0 -0
  183. codex/static_root/assets/{http-error-Cj0hTq-c.js → http-error-Cqtszzf1.js} +1 -1
  184. codex/static_root/assets/http-error-Cqtszzf1.js.br +0 -0
  185. codex/static_root/assets/http-error-Cqtszzf1.js.gz +0 -0
  186. codex/static_root/assets/{library-tab-CzksdS3Q.566acdb45dec.js → library-tab-DMlnswUU.30f454e84184.js} +1 -1
  187. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.br +0 -0
  188. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.gz +0 -0
  189. codex/static_root/assets/{library-tab-CzksdS3Q.js → library-tab-DMlnswUU.js} +1 -1
  190. codex/static_root/assets/library-tab-DMlnswUU.js.br +0 -0
  191. codex/static_root/assets/library-tab-DMlnswUU.js.gz +0 -0
  192. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js +32 -0
  193. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.br +0 -0
  194. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.gz +0 -0
  195. codex/static_root/assets/main-vjXu_QYL.js +32 -0
  196. codex/static_root/assets/main-vjXu_QYL.js.br +0 -0
  197. codex/static_root/assets/main-vjXu_QYL.js.gz +0 -0
  198. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css +1 -0
  199. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.br +0 -0
  200. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.gz +0 -0
  201. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css +1 -0
  202. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.br +0 -0
  203. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.gz +0 -0
  204. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js +1 -0
  205. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.br +0 -0
  206. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.gz +0 -0
  207. codex/static_root/assets/pagination-toolbar-JW78pa0q.js +1 -0
  208. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.br +0 -0
  209. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.gz +0 -0
  210. codex/static_root/assets/{pdf-doc-GkMk4o5L.ef24c859a7a4.js → pdf-doc-BiamrjRZ.4c9be1ade43c.js} +1 -1
  211. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.br +0 -0
  212. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.gz +0 -0
  213. codex/static_root/assets/{pdf-doc-GkMk4o5L.js → pdf-doc-BiamrjRZ.js} +1 -1
  214. codex/static_root/assets/pdf-doc-BiamrjRZ.js.br +0 -0
  215. codex/static_root/assets/pdf-doc-BiamrjRZ.js.gz +0 -0
  216. codex/static_root/assets/{reader-CCCfpM3x.1553e45bd775.css → reader-BpsctRsY.5d72e393911a.css} +1 -1
  217. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.br +0 -0
  218. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.gz +0 -0
  219. codex/static_root/assets/{reader-CCCfpM3x.css → reader-BpsctRsY.css} +1 -1
  220. codex/static_root/assets/reader-BpsctRsY.css.br +0 -0
  221. codex/static_root/assets/reader-BpsctRsY.css.gz +0 -0
  222. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js +2 -0
  223. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.br +0 -0
  224. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.gz +0 -0
  225. codex/static_root/assets/reader-CDk8oZzB.js +2 -0
  226. codex/static_root/assets/reader-CDk8oZzB.js.br +0 -0
  227. codex/static_root/assets/reader-CDk8oZzB.js.gz +0 -0
  228. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js +1 -0
  229. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.br +0 -0
  230. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.gz +0 -0
  231. codex/static_root/assets/relation-chips-C5acBjpy.js +1 -0
  232. codex/static_root/assets/relation-chips-C5acBjpy.js.br +0 -0
  233. codex/static_root/assets/relation-chips-C5acBjpy.js.gz +0 -0
  234. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js +2 -0
  235. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.br +0 -0
  236. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.gz +0 -0
  237. codex/static_root/assets/settings-drawer-D_JuRi7F.js +2 -0
  238. codex/static_root/assets/settings-drawer-D_JuRi7F.js.br +0 -0
  239. codex/static_root/assets/settings-drawer-D_JuRi7F.js.gz +0 -0
  240. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +1 -0
  241. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
  242. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
  243. codex/static_root/assets/stats-tab-B4yKuY0c.css +1 -0
  244. codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
  245. codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
  246. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js +1 -0
  247. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.br +0 -0
  248. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.gz +0 -0
  249. codex/static_root/assets/stats-tab-ClqRTk1z.js +1 -0
  250. codex/static_root/assets/stats-tab-ClqRTk1z.js.br +0 -0
  251. codex/static_root/assets/stats-tab-ClqRTk1z.js.gz +0 -0
  252. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js +1 -0
  253. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.br +0 -0
  254. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.gz +0 -0
  255. codex/static_root/assets/task-tab-DF9qa6LO.js +1 -0
  256. codex/static_root/assets/task-tab-DF9qa6LO.js.br +0 -0
  257. codex/static_root/assets/task-tab-DF9qa6LO.js.gz +0 -0
  258. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js +1 -0
  259. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js.br +0 -0
  260. codex/static_root/assets/to-case-CGbULRaE.js +1 -0
  261. codex/static_root/assets/to-case-CGbULRaE.js.br +0 -0
  262. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js +1 -0
  263. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.br +0 -0
  264. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.gz +0 -0
  265. codex/static_root/assets/unauthorized-BjKuLH6k.js +1 -0
  266. codex/static_root/assets/unauthorized-BjKuLH6k.js.br +0 -0
  267. codex/static_root/assets/unauthorized-BjKuLH6k.js.gz +0 -0
  268. codex/static_root/assets/{unauthorized-DHV6Q1n-.b7847b2872a1.css → unauthorized-CibJYD8o.4c2de98c2b53.css} +1 -1
  269. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
  270. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
  271. codex/static_root/assets/{unauthorized-DHV6Q1n-.css → unauthorized-CibJYD8o.css} +1 -1
  272. codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
  273. codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
  274. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js +1 -0
  275. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.br +0 -0
  276. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.gz +0 -0
  277. codex/static_root/assets/user-tab-Dpxe3ygA.js +1 -0
  278. codex/static_root/assets/user-tab-Dpxe3ygA.js.br +0 -0
  279. codex/static_root/assets/user-tab-Dpxe3ygA.js.gz +0 -0
  280. codex/static_root/js/choices-admin.3521e2a863df.json +1 -0
  281. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  282. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  283. codex/static_root/js/choices-admin.json +1 -1
  284. codex/static_root/js/choices-admin.json.br +0 -0
  285. codex/static_root/js/choices-admin.json.gz +0 -0
  286. codex/static_root/manifest.35e46a3ec8fa.json +622 -0
  287. codex/static_root/manifest.35e46a3ec8fa.json.br +0 -0
  288. codex/static_root/manifest.35e46a3ec8fa.json.gz +0 -0
  289. codex/static_root/manifest.json +245 -252
  290. codex/static_root/manifest.json.br +0 -0
  291. codex/static_root/manifest.json.gz +0 -0
  292. codex/static_root/staticfiles.json +1 -1
  293. codex/urls/api/v3.py +1 -1
  294. codex/views/admin/api_key.py +5 -2
  295. codex/views/admin/stats.py +13 -20
  296. codex/views/admin/tasks.py +3 -7
  297. codex/views/auth.py +1 -1
  298. codex/views/bookmark.py +69 -51
  299. codex/views/browser/annotations.py +32 -45
  300. codex/views/browser/base.py +25 -8
  301. codex/views/browser/breadcrumbs.py +5 -5
  302. codex/views/browser/browser.py +20 -32
  303. codex/views/browser/choices.py +18 -15
  304. codex/views/browser/cover.py +30 -66
  305. codex/views/browser/filters/annotations.py +66 -14
  306. codex/views/browser/filters/bookmark.py +21 -5
  307. codex/views/browser/filters/group.py +29 -17
  308. codex/views/browser/filters/search.py +3 -4
  309. codex/views/browser/metadata.py +86 -47
  310. codex/views/browser/mtime.py +61 -0
  311. codex/views/browser/order_by.py +1 -6
  312. codex/views/browser/settings.py +53 -0
  313. codex/views/browser/validate.py +3 -0
  314. codex/views/const.py +32 -1
  315. codex/views/opds/v1/feed.py +3 -5
  316. codex/views/opds/v2/feed.py +3 -3
  317. codex/views/reader/arcs.py +42 -51
  318. codex/views/reader/books.py +48 -66
  319. codex/views/reader/init.py +121 -0
  320. codex/views/reader/reader.py +8 -66
  321. codex/views/session.py +15 -11
  322. codex/views/settings.py +23 -10
  323. codex/views/util.py +2 -1
  324. codex/websockets/consumers.py +13 -9
  325. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/METADATA +1 -1
  326. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/RECORD +329 -325
  327. codex/serializers/mtime.py +0 -25
  328. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +0 -1
  329. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  330. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  331. codex/static_root/assets/VCheckbox-DI-DgMb4.js +0 -1
  332. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  333. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  334. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  335. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  336. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  337. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  338. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +0 -1
  339. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  340. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  341. codex/static_root/assets/VCombobox-D1_0lCri.js +0 -1
  342. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  343. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  344. codex/static_root/assets/VDialog-C5Nxd_zP.css.br +0 -0
  345. codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
  346. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
  347. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
  348. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js +0 -1
  349. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  350. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  351. codex/static_root/assets/VDialog-opmV0YZz.js +0 -1
  352. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  353. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  354. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +0 -1
  355. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  356. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  357. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +0 -1
  358. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  359. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  360. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  361. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  362. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  363. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  364. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +0 -1
  365. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  366. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  367. codex/static_root/assets/VSelect-sIYW_-ud.js +0 -1
  368. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  369. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  370. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  371. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  372. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  373. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  374. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +0 -1
  375. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  376. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  377. codex/static_root/assets/VSlideGroup-BRYhkLH8.js +0 -1
  378. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  379. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  380. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  381. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  382. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  383. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  384. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +0 -1
  385. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  386. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  387. codex/static_root/assets/VTextField-BnGscKcY.js +0 -1
  388. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  389. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  390. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js +0 -1
  391. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  392. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  393. codex/static_root/assets/VWindowItem-B0KokcAk.js +0 -1
  394. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  395. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  396. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +0 -1
  397. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  398. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  399. codex/static_root/assets/admin-Y0bL21AB.js +0 -1
  400. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  401. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  402. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +0 -30
  403. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  404. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  405. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +0 -30
  406. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  407. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  408. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  409. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.gz +0 -0
  410. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  411. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.gz +0 -0
  412. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +0 -1
  413. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  414. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  415. codex/static_root/assets/browser-CMAtBCLx.css +0 -1
  416. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  417. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  418. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +0 -1
  419. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  420. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  421. codex/static_root/assets/browser-DoJk8uvb.js +0 -1
  422. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  423. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  424. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js +0 -1
  425. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  426. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  427. codex/static_root/assets/change-password-dialog-CH4xbjJa.js +0 -1
  428. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  429. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  430. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css +0 -1
  431. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
  432. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
  433. codex/static_root/assets/change-password-dialog-HvIvmmIE.css +0 -1
  434. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
  435. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
  436. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
  437. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  438. codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
  439. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  440. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +0 -1
  441. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  442. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  443. codex/static_root/assets/datetime-column-BfW_j_3Q.js +0 -1
  444. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  445. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  446. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +0 -1
  447. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  448. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  449. codex/static_root/assets/filter-DjxN78cK.js +0 -1
  450. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  451. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  452. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  453. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  454. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  455. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  456. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  457. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  458. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  459. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  460. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  461. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  462. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  463. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  464. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  465. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  466. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  467. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  468. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +0 -32
  469. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  470. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  471. codex/static_root/assets/main-DX-k2724.js +0 -32
  472. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  473. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  474. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +0 -1
  475. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  476. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  477. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +0 -1
  478. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  479. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  480. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +0 -1
  481. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  482. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  483. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +0 -1
  484. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  485. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  486. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  487. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  488. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  489. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  490. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  491. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  492. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  493. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  494. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +0 -2
  495. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  496. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  497. codex/static_root/assets/reader-Cl6mmQB_.js +0 -2
  498. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  499. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  500. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +0 -1
  501. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  502. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  503. codex/static_root/assets/relation-chips-CZ8VvR4y.js +0 -1
  504. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  505. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  506. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +0 -2
  507. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  508. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  509. codex/static_root/assets/settings-drawer-DyS0A2pN.js +0 -2
  510. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  511. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  512. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +0 -1
  513. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  514. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  515. codex/static_root/assets/stats-tab-R3Ekobfm.js +0 -1
  516. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  517. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  518. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css +0 -1
  519. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +0 -2
  520. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
  521. codex/static_root/assets/stats-tab-TsyG3wH_.css +0 -1
  522. codex/static_root/assets/stats-tab-TsyG3wH_.css.br +0 -2
  523. codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
  524. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +0 -1
  525. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  526. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  527. codex/static_root/assets/task-tab-BhLDK_7Q.js +0 -1
  528. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  529. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  530. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +0 -1
  531. codex/static_root/assets/to-case-BR2frjFx.js +0 -1
  532. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +0 -1
  533. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  534. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  535. codex/static_root/assets/unauthorized-BbXjAe5l.js +0 -1
  536. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  537. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  538. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
  539. codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
  540. codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
  541. codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
  542. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js +0 -1
  543. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  544. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  545. codex/static_root/assets/user-tab-DxITIl2d.js +0 -1
  546. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  547. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  548. codex/static_root/js/choices-admin.d790df01c20a.json +0 -1
  549. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  550. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  551. codex/static_root/manifest.16d52597ef50.json +0 -629
  552. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  553. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  554. codex/views/mtime.py +0 -72
  555. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/LICENSE +0 -0
  556. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/WHEEL +0 -0
  557. {codex-1.6.0a8.dist-info → codex-1.6.0a10.dist-info}/entry_points.txt +0 -0
Binary file
Binary file
@@ -1 +1 @@
1
- {"paths": {"rest_framework/docs/js/api.js": "rest_framework/docs/js/api.18a5ba8a1bd8.js", "rest_framework/docs/js/jquery.json-view.min.js": "rest_framework/docs/js/jquery.json-view.min.b7c2d6981377.js", "rest_framework/docs/js/highlight.pack.js": "rest_framework/docs/js/highlight.pack.479b5f21dcba.js", "rest_framework/docs/img/favicon.ico": "rest_framework/docs/img/favicon.5195b4d0f3eb.ico", "rest_framework/docs/img/grid.png": "rest_framework/docs/img/grid.a4b938cf382b.png", "rest_framework/docs/css/jquery.json-view.min.css": "rest_framework/docs/css/jquery.json-view.min.a2e6beeb6710.css", "rest_framework/docs/css/highlight.css": "rest_framework/docs/css/highlight.e0e4d973c6d7.css", "rest_framework/docs/css/base.css": "rest_framework/docs/css/base.e630f8f4990e.css", "rest_framework/js/ajax-form.js": "rest_framework/js/ajax-form.4e1cdcb7acab.js", "rest_framework/js/csrf.js": "rest_framework/js/csrf.455080a7b2ce.js", "rest_framework/js/prettify-min.js": "rest_framework/js/prettify-min.709bfcc456c6.js", "rest_framework/js/jquery-3.7.1.min.js": "rest_framework/js/jquery-3.7.1.min.2c872dbe60f4.js", "rest_framework/js/default.js": "rest_framework/js/default.5b08897dbdc3.js", "rest_framework/js/bootstrap.min.js": "rest_framework/js/bootstrap.min.2f34b630ffe3.js", "rest_framework/js/coreapi-0.1.1.js": "rest_framework/js/coreapi-0.1.1.8851fb9336c9.js", "rest_framework/js/load-ajax-form.js": "rest_framework/js/load-ajax-form.8cdb3a9f3466.js", "rest_framework/fonts/fontawesome-webfont.woff": "rest_framework/fonts/fontawesome-webfont.3293616ec0c6.woff", "rest_framework/fonts/glyphicons-halflings-regular.eot": "rest_framework/fonts/glyphicons-halflings-regular.f4769f9bdb74.eot", "rest_framework/fonts/glyphicons-halflings-regular.ttf": "rest_framework/fonts/glyphicons-halflings-regular.e18bbf611f2a.ttf", "rest_framework/fonts/fontawesome-webfont.ttf": "rest_framework/fonts/fontawesome-webfont.dcb26c7239d8.ttf", "rest_framework/fonts/glyphicons-halflings-regular.woff2": "rest_framework/fonts/glyphicons-halflings-regular.448c34a56d69.woff2", "rest_framework/fonts/fontawesome-webfont.svg": "rest_framework/fonts/fontawesome-webfont.83e37a11f9d7.svg", "rest_framework/fonts/glyphicons-halflings-regular.woff": "rest_framework/fonts/glyphicons-halflings-regular.fa2772327f55.woff", "rest_framework/fonts/glyphicons-halflings-regular.svg": "rest_framework/fonts/glyphicons-halflings-regular.08eda92397ae.svg", "rest_framework/fonts/fontawesome-webfont.eot": "rest_framework/fonts/fontawesome-webfont.8b27bc96115c.eot", "rest_framework/img/glyphicons-halflings-white.png": "rest_framework/img/glyphicons-halflings-white.9bbc6e960299.png", "rest_framework/img/glyphicons-halflings.png": "rest_framework/img/glyphicons-halflings.90233c9067e9.png", "rest_framework/img/grid.png": "rest_framework/img/grid.a4b938cf382b.png", "rest_framework/css/bootstrap.min.css.map": "rest_framework/css/bootstrap.min.css.cafbda9c0e9e.map", "rest_framework/css/default.css": "rest_framework/css/default.789dfb5732d7.css", "rest_framework/css/bootstrap.min.css": "rest_framework/css/bootstrap.min.f17d4516b026.css", "rest_framework/css/prettify.css": "rest_framework/css/prettify.a987f72342ee.css", "rest_framework/css/bootstrap-theme.min.css": "rest_framework/css/bootstrap-theme.min.1d4b05b397c3.css", "rest_framework/css/bootstrap-tweaks.css": "rest_framework/css/bootstrap-tweaks.ee4ee6acf9eb.css", "rest_framework/css/font-awesome-4.0.3.css": "rest_framework/css/font-awesome-4.0.3.c1e1ea213abf.css", "rest_framework/css/bootstrap-theme.min.css.map": "rest_framework/css/bootstrap-theme.min.css.51806092cc05.map", "img/publisher.svg": "img/publisher.82d378fcd601.svg", "img/logo-32.webp": "img/logo-32.5688944495bf.webp", "img/logo.svg": "img/logo.0f5344a028ca.svg", "img/story-arc.svg": "img/story-arc.ca10014355ef.svg", "img/missing-cover-165.webp": "img/missing-cover-165.0743171f2148.webp", "img/series.svg": "img/series.ec8a889b8f95.svg", "img/logo-maskable-180.webp": "img/logo-maskable-180.c58d966aa64e.webp", "img/missing-cover.svg": "img/missing-cover.ddb038cc2c56.svg", "img/folder.svg": "img/folder.f28936a3701d.svg", "img/volume.svg": "img/volume.814e4c79461a.svg", "img/logo-maskable.svg": "img/logo-maskable.8f7de7f1c6a8.svg", "img/imprint.svg": "img/imprint.7c2adebb76d6.svg", "pwa/offline.html": "pwa/offline.7bfaf9f94bf9.html", "js/choices.json": "js/choices.a0aaaa6c7ef7.json", "js/choices-admin.json": "js/choices-admin.d790df01c20a.json", "assets/relation-chips-Bsc-gUxE.css": "assets/relation-chips-Bsc-gUxE.98e65384c3c9.css", "assets/to-case-BR2frjFx.js": "assets/to-case-BR2frjFx.87de5d7a0503.js", "assets/http-error-Cj0hTq-c.js": "assets/http-error-Cj0hTq-c.062a88ddd4dc.js", "assets/group-tab-CBKuS3Iv.css": "assets/group-tab-CBKuS3Iv.7e4746634782.css", "assets/http-error-DKulXuTl.css": "assets/http-error-DKulXuTl.95e42988d5d5.css", "assets/library-tab-CzksdS3Q.js": "assets/library-tab-CzksdS3Q.566acdb45dec.js", "assets/VSelect-ARDhJiQK.css": "assets/VSelect-ARDhJiQK.372c098fb475.css", "assets/flag-tab-BNwbLfrN.css": "assets/flag-tab-BNwbLfrN.b755a99fff7e.css", "assets/VWindowItem-B0KokcAk.js": "assets/VWindowItem-B0KokcAk.ea94224aecb9.js", "assets/VSelectionControl-BFq--28I.css": "assets/VSelectionControl-BFq--28I.9687c77461fc.css", "assets/pdf-doc-CMzaSV_K.css": "assets/pdf-doc-CMzaSV_K.8cd4b3e65f9a.css", "assets/unauthorized-DHV6Q1n-.css": "assets/unauthorized-DHV6Q1n-.b7847b2872a1.css", "assets/VCheckbox-DI-DgMb4.js": "assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js", "assets/group-tab-DucpNoBs.js": "assets/group-tab-DucpNoBs.a6f37ae940c0.js", "assets/materialdesignicons-webfont-PXm3-2wK.woff": "assets/materialdesignicons-webfont-PXm3-2wK.026b7ac9c43c.woff", "assets/stats-tab-R3Ekobfm.js": "assets/stats-tab-R3Ekobfm.70fa09a67518.js", "assets/VSlideGroup-Dk_1upHt.css": "assets/VSlideGroup-Dk_1upHt.2c836070ec75.css", "assets/pagination-toolbar-CQcR0a3T.js": "assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js", "assets/change-password-dialog-CH4xbjJa.js": "assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js", "assets/VTable-DflzroDq.css": "assets/VTable-DflzroDq.160504d82195.css", "assets/settings-drawer-nZ9HvKSZ.css": "assets/settings-drawer-nZ9HvKSZ.01744e131806.css", "assets/VSelect-sIYW_-ud.js": "assets/VSelect-sIYW_-ud.7d40da0ec3ff.js", "assets/main-Ztp855PW.css": "assets/main-Ztp855PW.d46d718fac45.css", "assets/VSlideGroup-BRYhkLH8.js": "assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js", "assets/VRadioGroup-i2qP9Wu9.css": "assets/VRadioGroup-i2qP9Wu9.8e3741381961.css", "assets/task-tab-BhLDK_7Q.js": "assets/task-tab-BhLDK_7Q.28ff51b3ed01.js", "assets/change-password-dialog-HvIvmmIE.css": "assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css", "assets/browser-CMAtBCLx.css": "assets/browser-CMAtBCLx.17a277ce43a9.css", "assets/copy-to-clipboard-DoUvDJVx.js": "assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js", "assets/browser-DoJk8uvb.js": "assets/browser-DoJk8uvb.2c97a3858932.js", "assets/VWindowItem-ByIod57Y.css": "assets/VWindowItem-ByIod57Y.f7fb19752bf3.css", "assets/user-tab-DxITIl2d.js": "assets/user-tab-DxITIl2d.d79d7f062930.js", "assets/stats-tab-TsyG3wH_.css": "assets/stats-tab-TsyG3wH_.b93c080356fc.css", "assets/flag-tab-D4ZNSbMR.js": "assets/flag-tab-D4ZNSbMR.e763a9937701.js", "assets/VTable-D1esKtlw.js": "assets/VTable-D1esKtlw.52165930e3b9.js", "assets/library-tab-BN0SfPcH.css": "assets/library-tab-BN0SfPcH.913e236f625e.css", "assets/VCombobox-D1_0lCri.js": "assets/VCombobox-D1_0lCri.6ef85a29c2dc.js", "assets/confirm-dialog-BmhedLCx.css": "assets/confirm-dialog-BmhedLCx.7c2103d319d4.css", "assets/pdf-doc-GkMk4o5L.js": "assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js", "assets/materialdesignicons-webfont-B7mPwVP_.ttf": "assets/materialdesignicons-webfont-B7mPwVP_.6e435534bd35.ttf", "assets/reader-CCCfpM3x.css": "assets/reader-CCCfpM3x.1553e45bd775.css", "assets/VCombobox-BksEr4D_.css": "assets/VCombobox-BksEr4D_.73429bb75f42.css", "assets/admin-Y0bL21AB.js": "assets/admin-Y0bL21AB.42d4f0d68163.js", "assets/admin-drawer-panel-o87BwD38.css": "assets/admin-drawer-panel-o87BwD38.ce39427c8201.css", "assets/settings-drawer-DyS0A2pN.js": "assets/settings-drawer-DyS0A2pN.17a98928589f.js", "assets/pagination-toolbar-DWdp_kmz.css": "assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css", "assets/reader-Cl6mmQB_.js": "assets/reader-Cl6mmQB_.7b9e7591954f.js", "assets/VRadioGroup-BGa6_q5a.js": "assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js", "assets/main-DX-k2724.js": "assets/main-DX-k2724.f73d08e9ad64.js", "assets/admin-B8Bp2Uih.css": "assets/admin-B8Bp2Uih.82c98714bcf1.css", "assets/VSelectionControl-C8DP9rgp.js": "assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js", "assets/confirm-dialog-DkU0twE6.js": "assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js", "assets/task-tab-1XiWn9xP.css": "assets/task-tab-1XiWn9xP.06cb8e1b794d.css", "assets/VTextField-BnGscKcY.js": "assets/VTextField-BnGscKcY.54032adf3a4b.js", "assets/VCheckboxBtn-B8Z4NA-t.js": "assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js", "assets/datetime-column-DeCthByU.css": "assets/datetime-column-DeCthByU.1f3bf499e063.css", "assets/materialdesignicons-webfont-CSr8KVlo.eot": "assets/materialdesignicons-webfont-CSr8KVlo.8ced95a05e3c.eot", "assets/datetime-column-BfW_j_3Q.js": "assets/datetime-column-BfW_j_3Q.6a614321a435.js", "assets/filter-DjxN78cK.js": "assets/filter-DjxN78cK.35673f6e49b1.js", "assets/relation-chips-CZ8VvR4y.js": "assets/relation-chips-CZ8VvR4y.d4232f3c2518.js", "assets/VExpansionPanels-DX2dhUf5.js": "assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js", "assets/VExpansionPanels-DsEXVcCv.css": "assets/VExpansionPanels-DsEXVcCv.20b475255e66.css", "assets/VDialog-C5Nxd_zP.css": "assets/VDialog-C5Nxd_zP.d327f505ef0e.css", "assets/VCheckbox-CMKdyl0R.css": "assets/VCheckbox-CMKdyl0R.016ed8d222c4.css", "assets/VTextField-BssVoDhB.css": "assets/VTextField-BssVoDhB.4cc388b4aa3c.css", "assets/VDialog-opmV0YZz.js": "assets/VDialog-opmV0YZz.d77216e97599.js", "assets/materialdesignicons-webfont-Dp5v-WZN.woff2": "assets/materialdesignicons-webfont-Dp5v-WZN.1d7bcee1b302.woff2", "assets/unauthorized-BbXjAe5l.js": "assets/unauthorized-BbXjAe5l.d48912d34a68.js", "assets/admin-drawer-panel-DNngWUEM.js": "assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js", "robots.txt": "robots.b6216d61c03e.txt", "manifest.json": "manifest.16d52597ef50.json"}, "version": "1.1", "hash": "1cdc90da430d"}
1
+ {"paths": {"rest_framework/docs/js/api.js": "rest_framework/docs/js/api.18a5ba8a1bd8.js", "rest_framework/docs/js/jquery.json-view.min.js": "rest_framework/docs/js/jquery.json-view.min.b7c2d6981377.js", "rest_framework/docs/js/highlight.pack.js": "rest_framework/docs/js/highlight.pack.479b5f21dcba.js", "rest_framework/docs/img/favicon.ico": "rest_framework/docs/img/favicon.5195b4d0f3eb.ico", "rest_framework/docs/img/grid.png": "rest_framework/docs/img/grid.a4b938cf382b.png", "rest_framework/docs/css/jquery.json-view.min.css": "rest_framework/docs/css/jquery.json-view.min.a2e6beeb6710.css", "rest_framework/docs/css/highlight.css": "rest_framework/docs/css/highlight.e0e4d973c6d7.css", "rest_framework/docs/css/base.css": "rest_framework/docs/css/base.e630f8f4990e.css", "rest_framework/js/ajax-form.js": "rest_framework/js/ajax-form.4e1cdcb7acab.js", "rest_framework/js/csrf.js": "rest_framework/js/csrf.455080a7b2ce.js", "rest_framework/js/prettify-min.js": "rest_framework/js/prettify-min.709bfcc456c6.js", "rest_framework/js/jquery-3.7.1.min.js": "rest_framework/js/jquery-3.7.1.min.2c872dbe60f4.js", "rest_framework/js/default.js": "rest_framework/js/default.5b08897dbdc3.js", "rest_framework/js/bootstrap.min.js": "rest_framework/js/bootstrap.min.2f34b630ffe3.js", "rest_framework/js/coreapi-0.1.1.js": "rest_framework/js/coreapi-0.1.1.8851fb9336c9.js", "rest_framework/js/load-ajax-form.js": "rest_framework/js/load-ajax-form.8cdb3a9f3466.js", "rest_framework/fonts/fontawesome-webfont.woff": "rest_framework/fonts/fontawesome-webfont.3293616ec0c6.woff", "rest_framework/fonts/glyphicons-halflings-regular.eot": "rest_framework/fonts/glyphicons-halflings-regular.f4769f9bdb74.eot", "rest_framework/fonts/glyphicons-halflings-regular.ttf": "rest_framework/fonts/glyphicons-halflings-regular.e18bbf611f2a.ttf", "rest_framework/fonts/fontawesome-webfont.ttf": "rest_framework/fonts/fontawesome-webfont.dcb26c7239d8.ttf", "rest_framework/fonts/glyphicons-halflings-regular.woff2": "rest_framework/fonts/glyphicons-halflings-regular.448c34a56d69.woff2", "rest_framework/fonts/fontawesome-webfont.svg": "rest_framework/fonts/fontawesome-webfont.83e37a11f9d7.svg", "rest_framework/fonts/glyphicons-halflings-regular.woff": "rest_framework/fonts/glyphicons-halflings-regular.fa2772327f55.woff", "rest_framework/fonts/glyphicons-halflings-regular.svg": "rest_framework/fonts/glyphicons-halflings-regular.08eda92397ae.svg", "rest_framework/fonts/fontawesome-webfont.eot": "rest_framework/fonts/fontawesome-webfont.8b27bc96115c.eot", "rest_framework/img/glyphicons-halflings-white.png": "rest_framework/img/glyphicons-halflings-white.9bbc6e960299.png", "rest_framework/img/glyphicons-halflings.png": "rest_framework/img/glyphicons-halflings.90233c9067e9.png", "rest_framework/img/grid.png": "rest_framework/img/grid.a4b938cf382b.png", "rest_framework/css/bootstrap.min.css.map": "rest_framework/css/bootstrap.min.css.cafbda9c0e9e.map", "rest_framework/css/default.css": "rest_framework/css/default.789dfb5732d7.css", "rest_framework/css/bootstrap.min.css": "rest_framework/css/bootstrap.min.f17d4516b026.css", "rest_framework/css/prettify.css": "rest_framework/css/prettify.a987f72342ee.css", "rest_framework/css/bootstrap-theme.min.css": "rest_framework/css/bootstrap-theme.min.1d4b05b397c3.css", "rest_framework/css/bootstrap-tweaks.css": "rest_framework/css/bootstrap-tweaks.ee4ee6acf9eb.css", "rest_framework/css/font-awesome-4.0.3.css": "rest_framework/css/font-awesome-4.0.3.c1e1ea213abf.css", "rest_framework/css/bootstrap-theme.min.css.map": "rest_framework/css/bootstrap-theme.min.css.51806092cc05.map", "img/publisher.svg": "img/publisher.82d378fcd601.svg", "img/logo-32.webp": "img/logo-32.5688944495bf.webp", "img/logo.svg": "img/logo.0f5344a028ca.svg", "img/story-arc.svg": "img/story-arc.ca10014355ef.svg", "img/missing-cover-165.webp": "img/missing-cover-165.0743171f2148.webp", "img/series.svg": "img/series.ec8a889b8f95.svg", "img/logo-maskable-180.webp": "img/logo-maskable-180.c58d966aa64e.webp", "img/missing-cover.svg": "img/missing-cover.ddb038cc2c56.svg", "img/folder.svg": "img/folder.f28936a3701d.svg", "img/volume.svg": "img/volume.814e4c79461a.svg", "img/logo-maskable.svg": "img/logo-maskable.8f7de7f1c6a8.svg", "img/imprint.svg": "img/imprint.7c2adebb76d6.svg", "pwa/offline.html": "pwa/offline.7bfaf9f94bf9.html", "js/choices.json": "js/choices.a0aaaa6c7ef7.json", "js/choices-admin.json": "js/choices-admin.3521e2a863df.json", "assets/relation-chips-Bsc-gUxE.css": "assets/relation-chips-Bsc-gUxE.98e65384c3c9.css", "assets/group-tab-CBKuS3Iv.css": "assets/group-tab-CBKuS3Iv.7e4746634782.css", "assets/http-error-DKulXuTl.css": "assets/http-error-DKulXuTl.95e42988d5d5.css", "assets/VSelect-ARDhJiQK.css": "assets/VSelect-ARDhJiQK.372c098fb475.css", "assets/flag-tab-BNwbLfrN.css": "assets/flag-tab-BNwbLfrN.b755a99fff7e.css", "assets/VSelect-Dixy6okt.js": "assets/VSelect-Dixy6okt.5d53830daa8a.js", "assets/browser-s6mFzrK9.js": "assets/browser-s6mFzrK9.0d2200b451af.js", "assets/settings-drawer-D_JuRi7F.js": "assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js", "assets/change-password-dialog-Bn20jYzD.js": "assets/change-password-dialog-Bn20jYzD.593c5af1732b.js", "assets/VSelectionControl-BFq--28I.css": "assets/VSelectionControl-BFq--28I.9687c77461fc.css", "assets/pdf-doc-CMzaSV_K.css": "assets/pdf-doc-CMzaSV_K.8cd4b3e65f9a.css", "assets/VSelectionControl-MNmxKc3J.js": "assets/VSelectionControl-MNmxKc3J.08055d00917e.js", "assets/materialdesignicons-webfont-PXm3-2wK.woff": "assets/materialdesignicons-webfont-PXm3-2wK.026b7ac9c43c.woff", "assets/VSlideGroup-Dk_1upHt.css": "assets/VSlideGroup-Dk_1upHt.2c836070ec75.css", "assets/stats-tab-ClqRTk1z.js": "assets/stats-tab-ClqRTk1z.c759ace65a87.js", "assets/admin-drawer-panel-mne3FIj6.css": "assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css", "assets/VTable-DflzroDq.css": "assets/VTable-DflzroDq.160504d82195.css", "assets/settings-drawer-nZ9HvKSZ.css": "assets/settings-drawer-nZ9HvKSZ.01744e131806.css", "assets/VDialog-lQ13FnRR.css": "assets/VDialog-lQ13FnRR.1c801ad756b2.css", "assets/relation-chips-C5acBjpy.js": "assets/relation-chips-C5acBjpy.dc67d4528868.js", "assets/main-Ztp855PW.css": "assets/main-Ztp855PW.d46d718fac45.css", "assets/reader-BpsctRsY.css": "assets/reader-BpsctRsY.5d72e393911a.css", "assets/VTable-lcnYg9Ee.js": "assets/VTable-lcnYg9Ee.d72927b13c2e.js", "assets/reader-CDk8oZzB.js": "assets/reader-CDk8oZzB.ad62eebda920.js", "assets/VRadioGroup-QFQbK1qo.js": "assets/VRadioGroup-QFQbK1qo.750a42b1b814.js", "assets/VRadioGroup-i2qP9Wu9.css": "assets/VRadioGroup-i2qP9Wu9.8e3741381961.css", "assets/unauthorized-BjKuLH6k.js": "assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js", "assets/VWindowItem-C__tAqdc.js": "assets/VWindowItem-C__tAqdc.9791c128c3b7.js", "assets/group-tab-Cz9t_ECi.js": "assets/group-tab-Cz9t_ECi.a9e8aea79254.js", "assets/VTextField-BkFrjhdV.js": "assets/VTextField-BkFrjhdV.b02653a9b3b7.js", "assets/VSlideGroup-DZEV0FoV.js": "assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js", "assets/datetime-column-BSdlLt-4.js": "assets/datetime-column-BSdlLt-4.27ec2df67a0b.js", "assets/change-password-dialog-0u6IJA87.css": "assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css", "assets/VCombobox-aJr--Ou7.js": "assets/VCombobox-aJr--Ou7.f290ccf2ce25.js", "assets/user-tab-Dpxe3ygA.js": "assets/user-tab-Dpxe3ygA.7d90174631f0.js", "assets/copy-to-clipboard-DoUvDJVx.js": "assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js", "assets/library-tab-DMlnswUU.js": "assets/library-tab-DMlnswUU.30f454e84184.js", "assets/VWindowItem-ByIod57Y.css": "assets/VWindowItem-ByIod57Y.f7fb19752bf3.css", "assets/main-vjXu_QYL.js": "assets/main-vjXu_QYL.548bd4c38799.js", "assets/pagination-toolbar-DGdMiBRs.css": "assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css", "assets/pagination-toolbar-JW78pa0q.js": "assets/pagination-toolbar-JW78pa0q.91024effdab9.js", "assets/flag-tab-8vzpdySX.js": "assets/flag-tab-8vzpdySX.a982c7f0776a.js", "assets/admin-drawer-panel-rrNKi_GE.js": "assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js", "assets/VDialog-61pzfUB2.js": "assets/VDialog-61pzfUB2.33f73beb5224.js", "assets/confirm-dialog-CWszioQE.js": "assets/confirm-dialog-CWszioQE.859051c35dd9.js", "assets/VCheckbox-CY3rcqlS.js": "assets/VCheckbox-CY3rcqlS.de5154294920.js", "assets/library-tab-BN0SfPcH.css": "assets/library-tab-BN0SfPcH.913e236f625e.css", "assets/VExpansionPanels-rewm0P4z.js": "assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js", "assets/confirm-dialog-BmhedLCx.css": "assets/confirm-dialog-BmhedLCx.7c2103d319d4.css", "assets/materialdesignicons-webfont-B7mPwVP_.ttf": "assets/materialdesignicons-webfont-B7mPwVP_.6e435534bd35.ttf", "assets/VCombobox-BksEr4D_.css": "assets/VCombobox-BksEr4D_.73429bb75f42.css", "assets/to-case-CGbULRaE.js": "assets/to-case-CGbULRaE.4c80644f460d.js", "assets/admin-CXES2HXe.js": "assets/admin-CXES2HXe.9e48a9bf84e0.js", "assets/pdf-doc-BiamrjRZ.js": "assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js", "assets/admin-B8Bp2Uih.css": "assets/admin-B8Bp2Uih.82c98714bcf1.css", "assets/task-tab-1XiWn9xP.css": "assets/task-tab-1XiWn9xP.06cb8e1b794d.css", "assets/stats-tab-B4yKuY0c.css": "assets/stats-tab-B4yKuY0c.a5728396494b.css", "assets/browser-BuDeXGEe.css": "assets/browser-BuDeXGEe.f60854183b45.css", "assets/datetime-column-DeCthByU.css": "assets/datetime-column-DeCthByU.1f3bf499e063.css", "assets/materialdesignicons-webfont-CSr8KVlo.eot": "assets/materialdesignicons-webfont-CSr8KVlo.8ced95a05e3c.eot", "assets/task-tab-DF9qa6LO.js": "assets/task-tab-DF9qa6LO.0b32cafde4ad.js", "assets/http-error-Cqtszzf1.js": "assets/http-error-Cqtszzf1.b31df3d9c29f.js", "assets/VCheckboxBtn-D2tPTt6P.js": "assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js", "assets/VExpansionPanels-DsEXVcCv.css": "assets/VExpansionPanels-DsEXVcCv.20b475255e66.css", "assets/unauthorized-CibJYD8o.css": "assets/unauthorized-CibJYD8o.4c2de98c2b53.css", "assets/VCheckbox-CMKdyl0R.css": "assets/VCheckbox-CMKdyl0R.016ed8d222c4.css", "assets/filter-D3gaGf87.js": "assets/filter-D3gaGf87.1f14dbfd9929.js", "assets/VTextField-BssVoDhB.css": "assets/VTextField-BssVoDhB.4cc388b4aa3c.css", "assets/materialdesignicons-webfont-Dp5v-WZN.woff2": "assets/materialdesignicons-webfont-Dp5v-WZN.1d7bcee1b302.woff2", "robots.txt": "robots.b6216d61c03e.txt", "manifest.json": "manifest.35e46a3ec8fa.json"}, "version": "1.1", "hash": "3eaef7cc048e"}
codex/urls/api/v3.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from django.urls import include, path
4
4
  from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
5
5
 
6
- from codex.views.mtime import MtimeView
6
+ from codex.views.browser.mtime import MtimeView
7
7
  from codex.views.opds.urls import OPDSURLsView
8
8
  from codex.views.version import VersionView
9
9
 
@@ -14,8 +14,11 @@ class AdminAPIKey(AdminGenericAPIView):
14
14
  serializer_class = APIKeySerializer
15
15
  input_serializer_class = None
16
16
 
17
- @extend_schema(request=input_serializer_class)
18
- def post(self, *_args, **_kwargs):
17
+ @extend_schema(
18
+ # parameters=[input_serializer_class],
19
+ request=input_serializer_class
20
+ )
21
+ def put(self, *_args, **_kwargs):
19
22
  """Regenerate the API Key."""
20
23
  ts = Timestamp.objects.get(key=Timestamp.TimestampChoices.API_KEY.value)
21
24
  ts.save_uuid_version()
@@ -21,9 +21,18 @@ from codex.permissions import HasAPIKeyOrIsAdminUser
21
21
  from codex.serializers.admin import AdminStatsRequestSerializer, AdminStatsSerializer
22
22
  from codex.version import VERSION
23
23
  from codex.views.admin.auth import AdminGenericAPIView
24
- from codex.views.const import CONFIG_MODELS, GROUP_MODELS, METADATA_MODELS
24
+ from codex.views.const import CONFIG_MODELS, METADATA_MODELS, STATS_GROUP_MODELS
25
25
 
26
26
  LOG = get_logger(__name__)
27
+ _KEY_MODELS_MAP = MappingProxyType(
28
+ {
29
+ "config": CONFIG_MODELS,
30
+ "groups": STATS_GROUP_MODELS,
31
+ "metadata": METADATA_MODELS,
32
+ }
33
+ )
34
+ _DOCKERENV_PATH = Path("/.dockerenv")
35
+ _CGROUP_PATH = Path("/proc/self/cgroup")
27
36
 
28
37
 
29
38
  class AdminStatsView(AdminGenericAPIView):
@@ -33,24 +42,11 @@ class AdminStatsView(AdminGenericAPIView):
33
42
  serializer_class = AdminStatsSerializer
34
43
  input_serializer_class = AdminStatsRequestSerializer
35
44
 
36
- _KEY_MODELS_MAP = MappingProxyType(
37
- {
38
- "config": CONFIG_MODELS,
39
- "groups": GROUP_MODELS,
40
- "metadata": METADATA_MODELS,
41
- }
42
- )
43
- _DOCKERENV_PATH = Path("/.dockerenv")
44
- _CGROUP_PATH = Path("/proc/self/cgroup")
45
-
46
45
  @classmethod
47
46
  def _is_docker(cls):
48
47
  """Test if we're in a docker container."""
49
48
  try:
50
- return (
51
- cls._DOCKERENV_PATH.is_file()
52
- or "docker" in cls._CGROUP_PATH.read_text()
53
- )
49
+ return _DOCKERENV_PATH.is_file() or "docker" in _CGROUP_PATH.read_text()
54
50
  except Exception:
55
51
  return False
56
52
 
@@ -66,7 +62,7 @@ class AdminStatsView(AdminGenericAPIView):
66
62
  def _get_models(self, key):
67
63
  """Get models from request params."""
68
64
  request_model_list = self.request.GET.get(key)
69
- all_models = self._KEY_MODELS_MAP[key]
65
+ all_models = _KEY_MODELS_MAP[key]
70
66
  if request_model_list:
71
67
  models = []
72
68
  for model_name in request_model_list.split(","):
@@ -176,10 +172,7 @@ class AdminStatsView(AdminGenericAPIView):
176
172
  self._get_metadata(obj)
177
173
  return obj
178
174
 
179
- @extend_schema(
180
- parameters=[input_serializer_class],
181
- request=input_serializer_class,
182
- )
175
+ @extend_schema(parameters=[input_serializer_class])
183
176
  def get(self, *_args, **_kwargs):
184
177
  """Get the stats object and serialize it."""
185
178
  input_serializer = self.input_serializer_class(data=self.request.GET)
@@ -1,6 +1,5 @@
1
1
  """Librarian Status View."""
2
2
 
3
- from datetime import datetime, timezone
4
3
  from types import MappingProxyType
5
4
  from typing import TYPE_CHECKING
6
5
 
@@ -14,7 +13,7 @@ from codex.librarian.covers.tasks import (
14
13
  )
15
14
  from codex.librarian.importer.tasks import (
16
15
  AdoptOrphanFoldersTask,
17
- UpdateGroupsFirstComic,
16
+ UpdateGroupsTask,
18
17
  )
19
18
  from codex.librarian.janitor.tasks import (
20
19
  ForceUpdateAllFailedImportsTask,
@@ -49,12 +48,12 @@ from codex.serializers.admin import AdminLibrarianTaskSerializer
49
48
  from codex.serializers.mixins import OKSerializer
50
49
  from codex.serializers.models.admin import LibrarianStatusSerializer
51
50
  from codex.views.admin.auth import AdminAPIView, AdminReadOnlyModelViewSet
51
+ from codex.views.const import EPOCH_START
52
52
 
53
53
  if TYPE_CHECKING:
54
54
  from collections.abc import Mapping
55
55
 
56
56
  LOG = get_logger(__name__)
57
- _EPOCH_START = datetime.fromtimestamp(0, tz=timezone.utc)
58
57
 
59
58
 
60
59
  class AdminLibrarianStatusViewSet(AdminReadOnlyModelViewSet):
@@ -103,10 +102,7 @@ class AdminLibrarianTaskView(AdminAPIView):
103
102
  "poll": WatchdogPollLibrariesTask(frozenset(), False),
104
103
  "poll_force": WatchdogPollLibrariesTask(frozenset(), True),
105
104
  "janitor_nightly": JanitorNightlyTask(),
106
- "update_first_covers": UpdateGroupsFirstComic(),
107
- "force_update_first_covers": UpdateGroupsFirstComic(
108
- start_time=_EPOCH_START
109
- ),
105
+ "force_update_groups": UpdateGroupsTask(start_time=EPOCH_START),
110
106
  "adopt_folders": AdoptOrphanFoldersTask(),
111
107
  }
112
108
  )
codex/views/auth.py CHANGED
@@ -52,7 +52,7 @@ class TimezoneView(AuthGenericAPIView):
52
52
  serializer_class = OKSerializer
53
53
 
54
54
  @extend_schema(request=input_serializer_class)
55
- def post(self, request, *args, **kwargs):
55
+ def put(self, request, *args, **kwargs):
56
56
  """Get the user info for the current user."""
57
57
  data = self.request.data # type: ignore
58
58
  serializer = self.input_serializer_class(data=data)
codex/views/bookmark.py CHANGED
@@ -1,8 +1,12 @@
1
1
  """Bookmark views."""
2
2
 
3
+ from django.db.models.expressions import F
4
+ from django.db.models.functions import Now
3
5
  from drf_spectacular.utils import extend_schema
4
6
  from rest_framework.response import Response
5
7
 
8
+ from codex.librarian.mp_queue import LIBRARIAN_QUEUE
9
+ from codex.librarian.notifier.tasks import NotifierTask
6
10
  from codex.logger.logging import get_logger
7
11
  from codex.models import Bookmark, Comic
8
12
  from codex.serializers.models.bookmark import (
@@ -12,42 +16,32 @@ from codex.serializers.models.bookmark import (
12
16
  from codex.views.auth import (
13
17
  AuthFilterGenericAPIView,
14
18
  )
15
- from codex.views.const import GROUP_RELATION
19
+ from codex.views.const import FOLDER_GROUP, GROUP_RELATION
16
20
 
17
21
  LOG = get_logger(__name__)
18
22
  VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
19
-
20
-
21
- class BookmarkBaseView(AuthFilterGenericAPIView):
22
- """Bookmark Updater."""
23
-
24
- _BOOKMARK_UPDATE_FIELDS = (
23
+ _BOOKMARK_UPDATE_FIELDS = frozenset(
24
+ {
25
25
  "page",
26
26
  "finished",
27
27
  "fit_to",
28
28
  "two_pages",
29
29
  "reading_direction",
30
- )
31
- _BOOKMARK_ONLY_FIELDS = (*_BOOKMARK_UPDATE_FIELDS, "pk", "comic")
32
- _COMIC_ONLY_FIELDS = ("pk", "page_count")
30
+ }
31
+ )
32
+ _COMIC_ONLY_FIELDS = ("pk", "page_count")
33
33
 
34
- def get_bookmark_filter(self):
35
- """Get search kwargs for the reader."""
36
- search_kwargs = {}
37
- if self.request.user.is_authenticated:
38
- search_kwargs["user"] = self.request.user
39
- else:
40
- if not self.request.session or not self.request.session.session_key:
41
- LOG.debug("no session, make one")
42
- self.request.session.save()
43
- search_kwargs["session_id"] = self.request.session.session_key
44
- return search_kwargs
45
34
 
46
- def get_bookmark_search_kwargs(self, comic_filter):
35
+ class BookmarkBaseView(AuthFilterGenericAPIView):
36
+ """Bookmark Updater."""
37
+
38
+ def get_bookmark_search_kwargs(self, comic_filter=None):
47
39
  """Get the search kwargs for a user's authentication state."""
48
40
  search_kwargs = {}
49
- for key, value in comic_filter.items():
50
- search_kwargs[f"comic__{key}"] = value
41
+
42
+ if comic_filter:
43
+ for key, value in comic_filter.items():
44
+ search_kwargs[f"comic__{key}"] = value
51
45
 
52
46
  if self.request.user.is_authenticated:
53
47
  search_kwargs["user"] = self.request.user
@@ -64,8 +58,9 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
64
58
  page = updates.get("page")
65
59
  if page is None:
66
60
  return
67
- page = max(min(page, bm.comic.max_page), 0)
68
- if page == bm.comic.max_page:
61
+ max_page = bm.page_count + 1
62
+ page = max(min(page, max_page), 0)
63
+ if page == max_page:
69
64
  # Auto finish on bookmark last page
70
65
  bm.finished = True
71
66
  updates["page"] = page
@@ -81,25 +76,45 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
81
76
  ):
82
77
  updates["two_pages"] = None
83
78
 
79
+ def notify_library_changed(self):
80
+ """Notify one user that their library changed."""
81
+ if user := self.request.user:
82
+ uid = user.id # type: ignore
83
+ else:
84
+ uid = self.request.session.session_key
85
+ group = f"user_{uid}"
86
+ task = NotifierTask("LIBRARY_CHANGED", group)
87
+ LIBRARIAN_QUEUE.put(task)
88
+
84
89
  def _update_bookmarks(self, search_kwargs, updates):
85
90
  """Update existing bookmarks."""
91
+ if not updates:
92
+ return 0
86
93
  group_acl_filter = self.get_group_acl_filter(Bookmark)
87
- existing_bookmarks = (
88
- Bookmark.objects.filter(group_acl_filter)
89
- .filter(**search_kwargs)
90
- .select_related("comic")
91
- .only(*self._BOOKMARK_ONLY_FIELDS)
94
+ update_fields = set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS
95
+ update_fields.add("updated_at")
96
+ only_fields = (*update_fields, "pk")
97
+ existing_bookmarks = Bookmark.objects.filter(group_acl_filter).filter(
98
+ **search_kwargs
92
99
  )
100
+ if updates.get("page") is not None:
101
+ existing_bookmarks = existing_bookmarks.annotate(
102
+ page_count=F("comic__page_count")
103
+ )
104
+ existing_bookmarks = existing_bookmarks.only(*only_fields)
93
105
  update_bookmarks = []
94
106
  for bm in existing_bookmarks:
95
107
  self._update_bookmarks_validate_page(bm, updates)
96
108
  self._update_bookmarks_validate_two_pages(bm, updates)
97
109
  for key, value in updates.items():
98
110
  setattr(bm, key, value)
111
+ bm.updated_at = Now()
99
112
  update_bookmarks.append(bm)
100
- if update_bookmarks:
101
- update_fields = list(self._BOOKMARK_UPDATE_FIELDS | updates.keys())
102
- Bookmark.objects.bulk_update(update_bookmarks, update_fields)
113
+ count = len(update_bookmarks)
114
+ if count:
115
+ Bookmark.objects.bulk_update(update_bookmarks, tuple(update_fields))
116
+ return count
117
+ # LOG.debug(f"Updated {count} bookmarks.")
103
118
 
104
119
  @staticmethod
105
120
  def _create_bookmarks_validate_two_pages(updates):
@@ -114,22 +129,17 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
114
129
  """Create new bookmarks for comics that don't exist yet."""
115
130
  self._create_bookmarks_validate_two_pages(updates)
116
131
  if not updates:
117
- return
132
+ return 0
118
133
  create_bookmarks = []
119
134
  group_acl_filter = self.get_group_acl_filter(Comic)
135
+ exclude = {}
120
136
  for key, value in search_kwargs.items():
121
- exclude = {"bookmark__" + key: value}
122
- break
123
- else:
124
- reason = (
125
- f"No user or session_id found for creating bookmarks: {search_kwargs}"
126
- )
127
- raise ValueError(reason)
137
+ exclude["bookmark__" + key] = value
128
138
  create_bookmark_comics = (
129
139
  Comic.objects.filter(group_acl_filter)
130
140
  .filter(**comic_filter)
131
141
  .exclude(**exclude)
132
- .only(*self._COMIC_ONLY_FIELDS)
142
+ .only(*_COMIC_ONLY_FIELDS)
133
143
  )
134
144
  for comic in create_bookmark_comics:
135
145
  defaults = {"comic": comic}
@@ -144,17 +154,23 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
144
154
  # This almost never happens. Possibly never.
145
155
  bm.finished = True
146
156
  create_bookmarks.append(bm)
147
- Bookmark.objects.bulk_create(
148
- create_bookmarks,
149
- update_fields=self._BOOKMARK_UPDATE_FIELDS,
150
- unique_fields=Bookmark._meta.unique_together[0], # type: ignore
151
- )
157
+ count = len(create_bookmarks)
158
+ if count:
159
+ Bookmark.objects.bulk_create(
160
+ create_bookmarks,
161
+ update_fields=tuple(_BOOKMARK_UPDATE_FIELDS),
162
+ unique_fields=Bookmark._meta.unique_together[0], # type: ignore
163
+ )
164
+ # LOG.debug(f"Create {count} bookmarks.")
165
+ return count
152
166
 
153
167
  def update_bookmarks(self, updates, comic_filter):
154
168
  """Update a user bookmark."""
155
169
  search_kwargs = self.get_bookmark_search_kwargs(comic_filter)
156
- self._update_bookmarks(search_kwargs, updates)
157
- self._create_bookmarks(comic_filter, search_kwargs, updates)
170
+ count = self._update_bookmarks(search_kwargs, updates)
171
+ count += self._create_bookmarks(comic_filter, search_kwargs, updates)
172
+ if count and "finished" in updates:
173
+ self.notify_library_changed()
158
174
 
159
175
 
160
176
  class BookmarkView(BookmarkBaseView):
@@ -183,7 +199,9 @@ class BookmarkView(BookmarkBaseView):
183
199
 
184
200
  pks = self.kwargs.get("pks")
185
201
 
186
- relation = "folders__in" if group == "f" else GROUP_RELATION[group] + "__in"
202
+ relation = (
203
+ "folders__in" if group == FOLDER_GROUP else GROUP_RELATION[group] + "__in"
204
+ )
187
205
  comic_filter = {relation: pks}
188
206
 
189
207
  self.update_bookmarks(updates, comic_filter=comic_filter)
@@ -7,7 +7,6 @@ from django.db.models import (
7
7
  Avg,
8
8
  BooleanField,
9
9
  Case,
10
- DateTimeField,
11
10
  F,
12
11
  FilteredRelation,
13
12
  Max,
@@ -17,9 +16,10 @@ from django.db.models import (
17
16
  Value,
18
17
  When,
19
18
  )
20
- from django.db.models.aggregates import Count
19
+ from django.db.models.expressions import OuterRef
21
20
  from django.db.models.fields import CharField, PositiveSmallIntegerField
22
- from django.db.models.functions import Least, Reverse, Right, StrIndex
21
+ from django.db.models.functions import Coalesce, Least, Reverse, Right, StrIndex
22
+ from django.db.models.functions.comparison import Greatest
23
23
 
24
24
  from codex.logger.logging import get_logger
25
25
  from codex.models import (
@@ -33,11 +33,14 @@ from codex.models.functions import JsonGroupArray
33
33
  from codex.views.browser.order_by import (
34
34
  BrowserOrderByView,
35
35
  )
36
- from codex.views.const import STORY_ARC_GROUP
36
+ from codex.views.const import (
37
+ EPOCH_START,
38
+ NONE_DATETIMEFIELD,
39
+ NONE_INTEGERFIELD,
40
+ STORY_ARC_GROUP,
41
+ )
37
42
  from codex.views.mixins import SharedAnnotationsMixin
38
43
 
39
- _NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
40
- _NONE_DATETIMEFIELD = Value(None, DateTimeField())
41
44
  _ORDER_AGGREGATE_FUNCS = MappingProxyType(
42
45
  # These are annotated to order_value because they're simple relations
43
46
  {
@@ -64,7 +67,6 @@ _ANNOTATED_ORDER_FIELDS = frozenset(
64
67
  _ALTERNATE_GROUP_BY: MappingProxyType[type[BrowserGroupModel], str] = MappingProxyType(
65
68
  {Comic: "id", Volume: "name"}
66
69
  )
67
- _ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
68
70
 
69
71
  LOG = get_logger(__name__)
70
72
 
@@ -141,7 +143,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
141
143
  )
142
144
  story_arc_number = self.order_agg_func("selected_story_arc_number__number")
143
145
  else:
144
- story_arc_number = _NONE_INTEGERFIELD
146
+ story_arc_number = NONE_INTEGERFIELD
145
147
 
146
148
  return qs.alias(story_arc_number=story_arc_number)
147
149
 
@@ -161,17 +163,11 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
161
163
 
162
164
  def get_bookmark_updated_at_aggregate(self, model, always_max=False):
163
165
  """Get The aggregate function for relevant bookmark.updated_at."""
164
- bm_rel = self.get_bm_rel(model)
165
- bm_filter = self.get_my_bookmark_filter(bm_rel)
166
- self.bm_annotation_data[model] = bm_rel, bm_filter # type: ignore
166
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
167
167
 
168
- updated_at_rel = f"{bm_rel}__updated_at"
168
+ bm_updated_at_rel = f"{bm_rel}__updated_at"
169
169
  agg_func = Max if always_max else self.order_agg_func
170
- return agg_func(
171
- updated_at_rel,
172
- default=_NONE_DATETIMEFIELD,
173
- filter=bm_filter,
174
- )
170
+ return agg_func(bm_updated_at_rel, default=NONE_DATETIMEFIELD, filter=bm_filter)
175
171
 
176
172
  def _annotate_bookmark_updated_at(self, qs, model):
177
173
  if (
@@ -191,6 +187,8 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
191
187
  def _annotate_order_value(self, qs, model):
192
188
  """Annotate a main key for sorting and browser card display."""
193
189
  # Determine order func
190
+ if self.TARGET == "metadata":
191
+ return qs
194
192
  if model == Folder and self.order_key == "filename":
195
193
  order_value = F("name")
196
194
  elif model == Comic or self.order_key in _ANNOTATED_ORDER_FIELDS:
@@ -227,24 +225,9 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
227
225
  value = "c" if model == Comic else self.model_group # type: ignore
228
226
  return qs.annotate(group=Value(value, CharField(max_length=1)))
229
227
 
230
- def _annotate_child_count(self, qs, model):
231
- """Annotate Child Count."""
232
- if self.TARGET == "opds2":
233
- return qs
234
-
235
- if model == Comic:
236
- child_count_sum = _ONE_INTEGERFIELD
237
- else:
238
- child_count_sum = Count(self.rel_prefix + "pk", distinct=True)
239
- return qs.annotate(child_count=child_count_sum)
240
-
241
228
  def _annotate_bookmarks(self, qs, model):
242
229
  """Hoist up bookmark annotations."""
243
- if model in self.bm_annotation_data: # type: ignore
244
- bm_rel, bm_filter = self.bm_annotation_data[model] # type: ignore
245
- else:
246
- bm_rel = self.get_bm_rel(model)
247
- bm_filter = self.get_my_bookmark_filter(bm_rel)
230
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
248
231
 
249
232
  page_rel = f"{bm_rel}__page"
250
233
  finished_rel = f"{bm_rel}__finished"
@@ -285,7 +268,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
285
268
  output_field=PositiveSmallIntegerField(),
286
269
  distinct=True,
287
270
  )
288
- qs = qs.alias(finished_count=finished_count)
271
+ qs = qs.annotate(finished_count=finished_count)
289
272
  finished_aggregate = Case(
290
273
  When(finished_count=F("child_count"), then=True),
291
274
  When(finished_count=0, then=False),
@@ -301,8 +284,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
301
284
  qs = qs.alias(page=bookmark_page)
302
285
 
303
286
  if self.TARGET in frozenset({"metadata", "browser"}):
304
- # Only used for progress and progress is only for metadata and browser anyway
305
- qs.annotate(finished=finished_aggregate)
287
+ qs = qs.annotate(finished=finished_aggregate)
306
288
  return qs
307
289
 
308
290
  def _annotate_progress(self, qs):
@@ -319,14 +301,20 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
319
301
 
320
302
  def _annotate_mtime(self, qs, model):
321
303
  """Annotations mtime."""
322
- bm_rel = self.get_bm_rel(model)
323
- bm_ua_rel = bm_rel + "__updated_at"
324
- when_filter = {bm_ua_rel + "__gt": F("updated_at")}
325
- mtime = Case(
326
- When(**when_filter),
327
- then=F(bm_ua_rel),
328
- default=F("updated_at"),
329
- )
304
+ if self.is_bookmark_filtered:
305
+ sub_qs = self.get_filtered_queryset(model, bookmark_filter=False)
306
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
307
+ ua_rel = bm_rel + "__updated_at"
308
+ qs = qs.annotate(
309
+ bmu_max=Max(
310
+ sub_qs.filter(pk=OuterRef("pk")).values_list(ua_rel, flat=True),
311
+ default=NONE_DATETIMEFIELD,
312
+ filter=bm_filter,
313
+ )
314
+ )
315
+ mtime = Greatest(Coalesce("bmu_max", Value(EPOCH_START)), "updated_at")
316
+ else:
317
+ mtime = F("updated_at")
330
318
  return qs.annotate(mtime=mtime)
331
319
 
332
320
  def annotate_card_aggregates(self, qs, model):
@@ -336,7 +324,6 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
336
324
  qs = self._annotate_order_value(qs, model)
337
325
  qs = self._annotate_group(qs, model)
338
326
  qs = self.annotate_group_names(qs, model)
339
- qs = self._annotate_child_count(qs, model)
340
327
  qs = self._annotate_bookmarks(qs, model)
341
328
  qs = self._annotate_progress(qs)
342
329
  return self._annotate_mtime(qs, model)
@@ -14,7 +14,7 @@ from codex.models import (
14
14
  )
15
15
  from codex.serializers.browser.settings import BrowserSettingsSerializer
16
16
  from codex.views.browser.filters.search import SearchFilterView
17
- from codex.views.const import GROUP_MODEL_MAP, ROOT_GROUP
17
+ from codex.views.const import FOLDER_GROUP, GROUP_MODEL_MAP, ROOT_GROUP, STORY_ARC_GROUP
18
18
  from codex.views.util import reparse_json_query_params
19
19
 
20
20
  LOG = get_logger(__name__)
@@ -22,8 +22,6 @@ LOG = get_logger(__name__)
22
22
  if TYPE_CHECKING:
23
23
  from codex.models.groups import BrowserGroupModel
24
24
 
25
- _REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
26
-
27
25
 
28
26
  class BrowserBaseView(SearchFilterView):
29
27
  """Browse comics with a variety of filters and sorts."""
@@ -31,15 +29,13 @@ class BrowserBaseView(SearchFilterView):
31
29
  input_serializer_class = BrowserSettingsSerializer
32
30
 
33
31
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType({})
34
-
35
- _GET_JSON_KEYS = frozenset({"filters", "show"})
32
+ REPARSE_JSON_FIELDS = frozenset({"breadcrumbs", "filters", "show"})
36
33
 
37
34
  def __init__(self, *args, **kwargs):
38
35
  """Set params for the type checker."""
39
36
  super().__init__(*args, **kwargs)
40
37
  self._is_admin: bool = False
41
38
  self.params: MappingProxyType[str, Any] = MappingProxyType({})
42
- self.bm_annotation_data = {}
43
39
  self.rel_prefix: str = ""
44
40
  self.model: type[BrowserGroupModel] | None = None
45
41
  self.group_class: type[BrowserGroupModel] | None = None
@@ -76,7 +72,9 @@ class BrowserBaseView(SearchFilterView):
76
72
 
77
73
  def _parse_query_params(self):
78
74
  """Parse GET query parameters: filter object & snake case."""
79
- query_params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
75
+ query_params = reparse_json_query_params(
76
+ self.request.GET, self.REPARSE_JSON_FIELDS
77
+ )
80
78
  if "q" not in query_params and (query := query_params.get("query")):
81
79
  # parse query param for opds v2
82
80
  query_params["q"] = query
@@ -87,7 +85,26 @@ class BrowserBaseView(SearchFilterView):
87
85
  data = self._parse_query_params()
88
86
  serializer = self.input_serializer_class(data=data)
89
87
  serializer.is_valid(raise_exception=True)
90
- self.load_params_from_session(serializer.validated_data)
88
+
89
+ params: dict[str, Any] = {}
90
+ defaults = self.SESSION_DEFAULTS[self.SESSION_KEY]
91
+ params.update(defaults)
92
+ group = self.kwargs.get("group")
93
+ if group:
94
+ # order_by has a dynamic group based default
95
+ order_defaults = {
96
+ "order_by": "filename"
97
+ if group == FOLDER_GROUP
98
+ else "story_arc_number"
99
+ if group == STORY_ARC_GROUP
100
+ else "sort_name"
101
+ }
102
+ params.update(order_defaults)
103
+ validated_data = serializer.validated_data
104
+
105
+ if validated_data:
106
+ params.update(validated_data) # type: ignore
107
+ self.params = MappingProxyType(params)
91
108
  self.is_bookmark_filtered = bool(self.params.get("filters", {}).get("bookmark"))
92
109
 
93
110
  def set_order_key(self):