codex 1.6.0a8__py3-none-any.whl → 1.6.0a9__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 (391) hide show
  1. codex/integrity.py +1 -2
  2. codex/librarian/importer/create_comics.py +3 -4
  3. codex/librarian/importer/create_covers.py +1 -3
  4. codex/librarian/importer/failed_imports.py +1 -2
  5. codex/librarian/importer/importerd.py +2 -2
  6. codex/librarian/importer/moved.py +4 -8
  7. codex/serializers/browser/mtime.py +21 -0
  8. codex/serializers/fields.py +8 -9
  9. codex/serializers/reader.py +5 -5
  10. codex/serializers/route.py +9 -4
  11. codex/static_root/assets/{VCheckbox-DI-DgMb4.8eeb1bd3e18b.js → VCheckbox-eNspVUje.da8282a08876.js} +1 -1
  12. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  13. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  14. codex/static_root/assets/{VCheckbox-DI-DgMb4.js → VCheckbox-eNspVUje.js} +1 -1
  15. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  16. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  17. codex/static_root/assets/{VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js → VCheckboxBtn-DLzF_WHB.9a47873b8548.js} +1 -1
  18. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  19. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  20. codex/static_root/assets/{VCheckboxBtn-B8Z4NA-t.js → VCheckboxBtn-DLzF_WHB.js} +1 -1
  21. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  22. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  23. codex/static_root/assets/{VCombobox-D1_0lCri.6ef85a29c2dc.js → VCombobox-BIO7lU_B.d607ada71b06.js} +1 -1
  24. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  25. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  26. codex/static_root/assets/{VCombobox-D1_0lCri.js → VCombobox-BIO7lU_B.js} +1 -1
  27. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  28. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  29. codex/static_root/assets/{VDialog-opmV0YZz.d77216e97599.js → VDialog-87AymJC5.4a46716358c0.js} +1 -1
  30. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  31. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  32. codex/static_root/assets/{VDialog-opmV0YZz.js → VDialog-87AymJC5.js} +1 -1
  33. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  34. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  35. codex/static_root/assets/{VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js → VExpansionPanels-CgA3shsd.9e524c73726a.js} +1 -1
  36. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  37. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  38. codex/static_root/assets/{VExpansionPanels-DX2dhUf5.js → VExpansionPanels-CgA3shsd.js} +1 -1
  39. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  40. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  41. codex/static_root/assets/{VRadioGroup-BGa6_q5a.d1a3ad7266bd.js → VRadioGroup-B--7K_v5.4a4e9c2d6c42.js} +1 -1
  42. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  43. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  44. codex/static_root/assets/{VRadioGroup-BGa6_q5a.js → VRadioGroup-B--7K_v5.js} +1 -1
  45. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  46. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  47. codex/static_root/assets/{VSelect-sIYW_-ud.7d40da0ec3ff.js → VSelect-BoJq222l.fc714657e214.js} +1 -1
  48. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  49. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  50. codex/static_root/assets/{VSelect-sIYW_-ud.js → VSelect-BoJq222l.js} +1 -1
  51. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  52. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  53. codex/static_root/assets/{VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js → VSelectionControl-CVQ9wkE2.c02186cceba6.js} +1 -1
  54. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  55. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  56. codex/static_root/assets/{VSelectionControl-C8DP9rgp.js → VSelectionControl-CVQ9wkE2.js} +1 -1
  57. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  58. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  59. codex/static_root/assets/{VSlideGroup-BRYhkLH8.3534ec94b2f1.js → VSlideGroup-Ce0j2BKK.85e64443a928.js} +1 -1
  60. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  61. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  62. codex/static_root/assets/{VSlideGroup-BRYhkLH8.js → VSlideGroup-Ce0j2BKK.js} +1 -1
  63. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  64. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  65. codex/static_root/assets/{VTable-D1esKtlw.52165930e3b9.js → VTable-CrWkhkiP.6d530eea0c09.js} +1 -1
  66. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  67. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  68. codex/static_root/assets/{VTable-D1esKtlw.js → VTable-CrWkhkiP.js} +1 -1
  69. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  70. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  71. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js +1 -0
  72. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  73. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  74. codex/static_root/assets/VTextField-CkWbil3K.js +1 -0
  75. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  76. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  77. codex/static_root/assets/{VWindowItem-B0KokcAk.ea94224aecb9.js → VWindowItem-7z55Y4lK.98eb7487e039.js} +1 -1
  78. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  79. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  80. codex/static_root/assets/{VWindowItem-B0KokcAk.js → VWindowItem-7z55Y4lK.js} +1 -1
  81. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  82. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  83. codex/static_root/assets/{admin-Y0bL21AB.42d4f0d68163.js → admin-D3OK784R.f69891726ec5.js} +1 -1
  84. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  85. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  86. codex/static_root/assets/{admin-Y0bL21AB.js → admin-D3OK784R.js} +1 -1
  87. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  88. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  89. codex/static_root/assets/{admin-drawer-panel-DNngWUEM.4e992e3fe86e.js → admin-drawer-panel-1pnfvY8O.5438f252970e.js} +11 -11
  90. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  91. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  92. codex/static_root/assets/{admin-drawer-panel-DNngWUEM.js → admin-drawer-panel-1pnfvY8O.js} +11 -11
  93. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  94. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  95. codex/static_root/assets/browser-BuU2x9y7.css +1 -0
  96. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  97. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  98. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +1 -0
  99. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  100. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  101. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +1 -0
  102. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  103. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  104. codex/static_root/assets/browser-CO9EYHIr.js +1 -0
  105. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  106. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  107. codex/static_root/assets/{change-password-dialog-CH4xbjJa.b749aa6ea07e.js → change-password-dialog-CjZdzaIk.188c765431ab.js} +1 -1
  108. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  109. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  110. codex/static_root/assets/{change-password-dialog-CH4xbjJa.js → change-password-dialog-CjZdzaIk.js} +1 -1
  111. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  112. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  113. codex/static_root/assets/{confirm-dialog-DkU0twE6.a20006aa5ba6.js → confirm-dialog-jwL7B98r.1cfd579cbcfe.js} +1 -1
  114. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  115. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  116. codex/static_root/assets/{confirm-dialog-DkU0twE6.js → confirm-dialog-jwL7B98r.js} +1 -1
  117. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  118. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  119. codex/static_root/assets/{datetime-column-BfW_j_3Q.6a614321a435.js → datetime-column-9w3GcKvo.f9172256f091.js} +1 -1
  120. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  121. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  122. codex/static_root/assets/{datetime-column-BfW_j_3Q.js → datetime-column-9w3GcKvo.js} +1 -1
  123. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  124. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  125. codex/static_root/assets/{filter-DjxN78cK.35673f6e49b1.js → filter-C-pghOri.eeb7c2ecf398.js} +1 -1
  126. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  127. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  128. codex/static_root/assets/{filter-DjxN78cK.js → filter-C-pghOri.js} +1 -1
  129. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  130. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  131. codex/static_root/assets/{flag-tab-D4ZNSbMR.e763a9937701.js → flag-tab-Da2DBNyz.ffbe5ca37e1c.js} +1 -1
  132. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  133. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  134. codex/static_root/assets/{flag-tab-D4ZNSbMR.js → flag-tab-Da2DBNyz.js} +1 -1
  135. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  136. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  137. codex/static_root/assets/{group-tab-DucpNoBs.a6f37ae940c0.js → group-tab-BONYWOc1.6ddf321a4a56.js} +1 -1
  138. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  139. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  140. codex/static_root/assets/{group-tab-DucpNoBs.js → group-tab-BONYWOc1.js} +1 -1
  141. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  142. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  143. codex/static_root/assets/{http-error-Cj0hTq-c.062a88ddd4dc.js → http-error-D2Jnc20C.32846bc3a43d.js} +1 -1
  144. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  145. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  146. codex/static_root/assets/{http-error-Cj0hTq-c.js → http-error-D2Jnc20C.js} +1 -1
  147. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  148. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  149. codex/static_root/assets/{library-tab-CzksdS3Q.566acdb45dec.js → library-tab-B419agXA.d4b778172aaa.js} +1 -1
  150. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  151. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  152. codex/static_root/assets/{library-tab-CzksdS3Q.js → library-tab-B419agXA.js} +1 -1
  153. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  154. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  155. codex/static_root/assets/{main-DX-k2724.js → main-CMQwJ9aG.9836fe81c80a.js} +4 -4
  156. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  157. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  158. codex/static_root/assets/{main-DX-k2724.f73d08e9ad64.js → main-CMQwJ9aG.js} +4 -4
  159. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  160. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  161. codex/static_root/assets/{pagination-toolbar-CQcR0a3T.js → pagination-toolbar-LTuj5U-G.a295defbcd2f.js} +1 -1
  162. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  163. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  164. codex/static_root/assets/{pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js → pagination-toolbar-LTuj5U-G.js} +1 -1
  165. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  166. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  167. codex/static_root/assets/{pdf-doc-GkMk4o5L.ef24c859a7a4.js → pdf-doc-8RVZqILx.2e7166dd26fb.js} +1 -1
  168. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  169. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  170. codex/static_root/assets/{pdf-doc-GkMk4o5L.js → pdf-doc-8RVZqILx.js} +1 -1
  171. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  172. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  173. codex/static_root/assets/{reader-Cl6mmQB_.js → reader-DNnK_Chr.114d0e321547.js} +2 -2
  174. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  175. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  176. codex/static_root/assets/{reader-Cl6mmQB_.7b9e7591954f.js → reader-DNnK_Chr.js} +2 -2
  177. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  178. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  179. codex/static_root/assets/{relation-chips-CZ8VvR4y.d4232f3c2518.js → relation-chips-PYua7aXv.428ba0b5861e.js} +1 -1
  180. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  181. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  182. codex/static_root/assets/{relation-chips-CZ8VvR4y.js → relation-chips-PYua7aXv.js} +1 -1
  183. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  184. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  185. codex/static_root/assets/{settings-drawer-DyS0A2pN.17a98928589f.js → settings-drawer-DbN6ISRH.d14f525d8eee.js} +2 -2
  186. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  187. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  188. codex/static_root/assets/{settings-drawer-DyS0A2pN.js → settings-drawer-DbN6ISRH.js} +2 -2
  189. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  190. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  191. codex/static_root/assets/{stats-tab-R3Ekobfm.70fa09a67518.js → stats-tab-DImNhH8O.d5026283841a.js} +1 -1
  192. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  193. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  194. codex/static_root/assets/{stats-tab-R3Ekobfm.js → stats-tab-DImNhH8O.js} +1 -1
  195. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  196. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  197. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js +1 -0
  198. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  199. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  200. codex/static_root/assets/task-tab-DPD53kjv.js +1 -0
  201. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  202. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  203. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +1 -0
  204. codex/static_root/assets/to-case-ehC9Ccyj.js +1 -0
  205. codex/static_root/assets/{unauthorized-BbXjAe5l.d48912d34a68.js → unauthorized-BOIx7Y18.ee3961abfccb.js} +1 -1
  206. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  207. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  208. codex/static_root/assets/{unauthorized-BbXjAe5l.js → unauthorized-BOIx7Y18.js} +1 -1
  209. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  210. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  211. codex/static_root/assets/{user-tab-DxITIl2d.d79d7f062930.js → user-tab-BABwfxLO.37dd0440fbdd.js} +1 -1
  212. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  213. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  214. codex/static_root/assets/{user-tab-DxITIl2d.js → user-tab-BABwfxLO.js} +1 -1
  215. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  216. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  217. codex/static_root/{manifest.16d52597ef50.json → manifest.cbea5663003a.json} +224 -224
  218. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  219. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  220. codex/static_root/manifest.json +224 -224
  221. codex/static_root/manifest.json.br +0 -0
  222. codex/static_root/manifest.json.gz +0 -0
  223. codex/static_root/staticfiles.json +1 -1
  224. codex/urls/api/v3.py +1 -1
  225. codex/views/admin/tasks.py +2 -5
  226. codex/views/bookmark.py +44 -40
  227. codex/views/browser/annotations.py +30 -45
  228. codex/views/browser/base.py +5 -4
  229. codex/views/browser/browser.py +7 -11
  230. codex/views/browser/filters/annotations.py +58 -14
  231. codex/views/browser/filters/bookmark.py +21 -5
  232. codex/views/browser/filters/group.py +7 -4
  233. codex/views/browser/mtime.py +72 -0
  234. codex/views/const.py +8 -0
  235. codex/views/reader/books.py +6 -8
  236. codex/views/reader/reader.py +24 -21
  237. {codex-1.6.0a8.dist-info → codex-1.6.0a9.dist-info}/METADATA +1 -1
  238. {codex-1.6.0a8.dist-info → codex-1.6.0a9.dist-info}/RECORD +241 -241
  239. codex/serializers/mtime.py +0 -25
  240. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  241. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  242. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  243. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  244. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  245. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  246. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  247. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  248. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  249. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  250. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  251. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  252. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  253. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  254. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  255. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  256. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  257. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  258. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  259. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  260. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  261. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  262. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  263. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  264. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  265. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  266. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  267. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  268. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  269. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  270. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  271. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  272. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  273. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  274. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  275. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  276. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  277. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  278. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  279. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  280. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +0 -1
  281. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  282. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  283. codex/static_root/assets/VTextField-BnGscKcY.js +0 -1
  284. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  285. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  286. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  287. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  288. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  289. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  290. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  291. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  292. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  293. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  294. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  295. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  296. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  297. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  298. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +0 -1
  299. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  300. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  301. codex/static_root/assets/browser-CMAtBCLx.css +0 -1
  302. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  303. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  304. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +0 -1
  305. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  306. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  307. codex/static_root/assets/browser-DoJk8uvb.js +0 -1
  308. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  309. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  310. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  311. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  312. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  313. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  314. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
  315. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  316. codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
  317. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  318. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  319. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  320. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  321. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  322. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  323. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  324. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  325. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  326. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  327. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  328. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  329. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  330. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  331. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  332. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  333. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  334. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  335. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  336. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  337. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  338. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  339. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  340. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  341. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  342. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  343. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  344. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  345. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  346. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  347. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  348. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  349. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  350. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  351. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  352. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  353. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  354. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  355. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  356. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  357. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  358. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  359. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  360. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  361. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  362. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  363. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  364. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  365. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  366. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  367. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  368. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  369. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  370. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +0 -1
  371. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  372. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  373. codex/static_root/assets/task-tab-BhLDK_7Q.js +0 -1
  374. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  375. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  376. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +0 -1
  377. codex/static_root/assets/to-case-BR2frjFx.js +0 -1
  378. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  379. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  380. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  381. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  382. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  383. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  384. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  385. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  386. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  387. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  388. codex/views/mtime.py +0 -72
  389. {codex-1.6.0a8.dist-info → codex-1.6.0a9.dist-info}/LICENSE +0 -0
  390. {codex-1.6.0a8.dist-info → codex-1.6.0a9.dist-info}/WHEEL +0 -0
  391. {codex-1.6.0a8.dist-info → codex-1.6.0a9.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.d790df01c20a.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/main-CMQwJ9aG.js": "assets/main-CMQwJ9aG.9836fe81c80a.js", "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/VCheckbox-eNspVUje.js": "assets/VCheckbox-eNspVUje.da8282a08876.js", "assets/pagination-toolbar-LTuj5U-G.js": "assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js", "assets/VTable-CrWkhkiP.js": "assets/VTable-CrWkhkiP.6d530eea0c09.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/materialdesignicons-webfont-PXm3-2wK.woff": "assets/materialdesignicons-webfont-PXm3-2wK.026b7ac9c43c.woff", "assets/VSlideGroup-Dk_1upHt.css": "assets/VSlideGroup-Dk_1upHt.2c836070ec75.css", "assets/VTable-DflzroDq.css": "assets/VTable-DflzroDq.160504d82195.css", "assets/settings-drawer-nZ9HvKSZ.css": "assets/settings-drawer-nZ9HvKSZ.01744e131806.css", "assets/main-Ztp855PW.css": "assets/main-Ztp855PW.d46d718fac45.css", "assets/admin-drawer-panel-1pnfvY8O.js": "assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js", "assets/task-tab-DPD53kjv.js": "assets/task-tab-DPD53kjv.e56a35ff1691.js", "assets/VRadioGroup-i2qP9Wu9.css": "assets/VRadioGroup-i2qP9Wu9.8e3741381961.css", "assets/change-password-dialog-HvIvmmIE.css": "assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css", "assets/pdf-doc-8RVZqILx.js": "assets/pdf-doc-8RVZqILx.2e7166dd26fb.js", "assets/VTextField-CkWbil3K.js": "assets/VTextField-CkWbil3K.2cac13161dfe.js", "assets/settings-drawer-DbN6ISRH.js": "assets/settings-drawer-DbN6ISRH.d14f525d8eee.js", "assets/user-tab-BABwfxLO.js": "assets/user-tab-BABwfxLO.37dd0440fbdd.js", "assets/copy-to-clipboard-DoUvDJVx.js": "assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js", "assets/VSelectionControl-CVQ9wkE2.js": "assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js", "assets/VWindowItem-ByIod57Y.css": "assets/VWindowItem-ByIod57Y.f7fb19752bf3.css", "assets/to-case-ehC9Ccyj.js": "assets/to-case-ehC9Ccyj.a7b8e25b391f.js", "assets/datetime-column-9w3GcKvo.js": "assets/datetime-column-9w3GcKvo.f9172256f091.js", "assets/stats-tab-DImNhH8O.js": "assets/stats-tab-DImNhH8O.d5026283841a.js", "assets/stats-tab-TsyG3wH_.css": "assets/stats-tab-TsyG3wH_.b93c080356fc.css", "assets/browser-BuU2x9y7.css": "assets/browser-BuU2x9y7.f67595d24664.css", "assets/reader-DNnK_Chr.js": "assets/reader-DNnK_Chr.114d0e321547.js", "assets/VWindowItem-7z55Y4lK.js": "assets/VWindowItem-7z55Y4lK.98eb7487e039.js", "assets/browser-CO9EYHIr.js": "assets/browser-CO9EYHIr.be49367c6a75.js", "assets/confirm-dialog-jwL7B98r.js": "assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js", "assets/library-tab-BN0SfPcH.css": "assets/library-tab-BN0SfPcH.913e236f625e.css", "assets/admin-D3OK784R.js": "assets/admin-D3OK784R.f69891726ec5.js", "assets/confirm-dialog-BmhedLCx.css": "assets/confirm-dialog-BmhedLCx.7c2103d319d4.css", "assets/group-tab-BONYWOc1.js": "assets/group-tab-BONYWOc1.6ddf321a4a56.js", "assets/materialdesignicons-webfont-B7mPwVP_.ttf": "assets/materialdesignicons-webfont-B7mPwVP_.6e435534bd35.ttf", "assets/reader-CCCfpM3x.css": "assets/reader-CCCfpM3x.1553e45bd775.css", "assets/change-password-dialog-CjZdzaIk.js": "assets/change-password-dialog-CjZdzaIk.188c765431ab.js", "assets/relation-chips-PYua7aXv.js": "assets/relation-chips-PYua7aXv.428ba0b5861e.js", "assets/VCheckboxBtn-DLzF_WHB.js": "assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js", "assets/VCombobox-BksEr4D_.css": "assets/VCombobox-BksEr4D_.73429bb75f42.css", "assets/filter-C-pghOri.js": "assets/filter-C-pghOri.eeb7c2ecf398.js", "assets/admin-drawer-panel-o87BwD38.css": "assets/admin-drawer-panel-o87BwD38.ce39427c8201.css", "assets/VSelect-BoJq222l.js": "assets/VSelect-BoJq222l.fc714657e214.js", "assets/pagination-toolbar-DWdp_kmz.css": "assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css", "assets/flag-tab-Da2DBNyz.js": "assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js", "assets/admin-B8Bp2Uih.css": "assets/admin-B8Bp2Uih.82c98714bcf1.css", "assets/http-error-D2Jnc20C.js": "assets/http-error-D2Jnc20C.32846bc3a43d.js", "assets/task-tab-1XiWn9xP.css": "assets/task-tab-1XiWn9xP.06cb8e1b794d.css", "assets/VDialog-87AymJC5.js": "assets/VDialog-87AymJC5.4a46716358c0.js", "assets/datetime-column-DeCthByU.css": "assets/datetime-column-DeCthByU.1f3bf499e063.css", "assets/materialdesignicons-webfont-CSr8KVlo.eot": "assets/materialdesignicons-webfont-CSr8KVlo.8ced95a05e3c.eot", "assets/library-tab-B419agXA.js": "assets/library-tab-B419agXA.d4b778172aaa.js", "assets/VSlideGroup-Ce0j2BKK.js": "assets/VSlideGroup-Ce0j2BKK.85e64443a928.js", "assets/VCombobox-BIO7lU_B.js": "assets/VCombobox-BIO7lU_B.d607ada71b06.js", "assets/VExpansionPanels-CgA3shsd.js": "assets/VExpansionPanels-CgA3shsd.9e524c73726a.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/materialdesignicons-webfont-Dp5v-WZN.woff2": "assets/materialdesignicons-webfont-Dp5v-WZN.1d7bcee1b302.woff2", "assets/VRadioGroup-B--7K_v5.js": "assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js", "assets/unauthorized-BOIx7Y18.js": "assets/unauthorized-BOIx7Y18.ee3961abfccb.js", "robots.txt": "robots.b6216d61c03e.txt", "manifest.json": "manifest.cbea5663003a.json"}, "version": "1.1", "hash": "469437147ea5"}
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
 
@@ -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
 
@@ -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):
@@ -104,9 +103,7 @@ class AdminLibrarianTaskView(AdminAPIView):
104
103
  "poll_force": WatchdogPollLibrariesTask(frozenset(), True),
105
104
  "janitor_nightly": JanitorNightlyTask(),
106
105
  "update_first_covers": UpdateGroupsFirstComic(),
107
- "force_update_first_covers": UpdateGroupsFirstComic(
108
- start_time=_EPOCH_START
109
- ),
106
+ "force_update_first_covers": UpdateGroupsFirstComic(start_time=EPOCH_START),
110
107
  "adopt_folders": AdoptOrphanFoldersTask(),
111
108
  }
112
109
  )
codex/views/bookmark.py CHANGED
@@ -1,5 +1,7 @@
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
 
@@ -16,38 +18,28 @@ from codex.views.const import GROUP_RELATION
16
18
 
17
19
  LOG = get_logger(__name__)
18
20
  VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
19
-
20
-
21
- class BookmarkBaseView(AuthFilterGenericAPIView):
22
- """Bookmark Updater."""
23
-
24
- _BOOKMARK_UPDATE_FIELDS = (
21
+ _BOOKMARK_UPDATE_FIELDS = frozenset(
22
+ {
25
23
  "page",
26
24
  "finished",
27
25
  "fit_to",
28
26
  "two_pages",
29
27
  "reading_direction",
30
- )
31
- _BOOKMARK_ONLY_FIELDS = (*_BOOKMARK_UPDATE_FIELDS, "pk", "comic")
32
- _COMIC_ONLY_FIELDS = ("pk", "page_count")
28
+ }
29
+ )
30
+ _COMIC_ONLY_FIELDS = ("pk", "page_count")
33
31
 
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
32
 
46
- def get_bookmark_search_kwargs(self, comic_filter):
33
+ class BookmarkBaseView(AuthFilterGenericAPIView):
34
+ """Bookmark Updater."""
35
+
36
+ def get_bookmark_search_kwargs(self, comic_filter=None):
47
37
  """Get the search kwargs for a user's authentication state."""
48
38
  search_kwargs = {}
49
- for key, value in comic_filter.items():
50
- search_kwargs[f"comic__{key}"] = value
39
+
40
+ if comic_filter:
41
+ for key, value in comic_filter.items():
42
+ search_kwargs[f"comic__{key}"] = value
51
43
 
52
44
  if self.request.user.is_authenticated:
53
45
  search_kwargs["user"] = self.request.user
@@ -55,7 +47,7 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
55
47
  if not self.request.session or not self.request.session.session_key:
56
48
  LOG.debug("no session, make one")
57
49
  self.request.session.save()
58
- search_kwargs["session_id"] = self.request.session.session_key
50
+ search_kwargs["session__session_key"] = self.request.session.session_key
59
51
  return search_kwargs
60
52
 
61
53
  @staticmethod
@@ -64,8 +56,9 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
64
56
  page = updates.get("page")
65
57
  if page is None:
66
58
  return
67
- page = max(min(page, bm.comic.max_page), 0)
68
- if page == bm.comic.max_page:
59
+ max_page = bm.page_count + 1
60
+ page = max(min(page, max_page), 0)
61
+ if page == max_page:
69
62
  # Auto finish on bookmark last page
70
63
  bm.finished = True
71
64
  updates["page"] = page
@@ -84,22 +77,30 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
84
77
  def _update_bookmarks(self, search_kwargs, updates):
85
78
  """Update existing bookmarks."""
86
79
  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)
80
+ update_fields = set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS
81
+ update_fields.add("updated_at")
82
+ only_fields = (*update_fields, "pk")
83
+ existing_bookmarks = Bookmark.objects.filter(group_acl_filter).filter(
84
+ **search_kwargs
92
85
  )
86
+ if updates.get("page") is not None:
87
+ existing_bookmarks = existing_bookmarks.annotate(
88
+ page_count=F("comic__page_count")
89
+ )
90
+ # only_fields = (*only_fields, "page_count")
91
+ existing_bookmarks = existing_bookmarks.only(*only_fields)
93
92
  update_bookmarks = []
94
93
  for bm in existing_bookmarks:
95
94
  self._update_bookmarks_validate_page(bm, updates)
96
95
  self._update_bookmarks_validate_two_pages(bm, updates)
97
96
  for key, value in updates.items():
98
97
  setattr(bm, key, value)
98
+ bm.updated_at = Now()
99
99
  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)
100
+ count = len(update_bookmarks)
101
+ if count:
102
+ Bookmark.objects.bulk_update(update_bookmarks, tuple(update_fields))
103
+ LOG.debug(f"Updated {count} bookmarks.")
103
104
 
104
105
  @staticmethod
105
106
  def _create_bookmarks_validate_two_pages(updates):
@@ -129,7 +130,7 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
129
130
  Comic.objects.filter(group_acl_filter)
130
131
  .filter(**comic_filter)
131
132
  .exclude(**exclude)
132
- .only(*self._COMIC_ONLY_FIELDS)
133
+ .only(*_COMIC_ONLY_FIELDS)
133
134
  )
134
135
  for comic in create_bookmark_comics:
135
136
  defaults = {"comic": comic}
@@ -144,11 +145,14 @@ class BookmarkBaseView(AuthFilterGenericAPIView):
144
145
  # This almost never happens. Possibly never.
145
146
  bm.finished = True
146
147
  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
- )
148
+ count = len(create_bookmarks)
149
+ if count:
150
+ Bookmark.objects.bulk_create(
151
+ create_bookmarks,
152
+ update_fields=tuple(_BOOKMARK_UPDATE_FIELDS),
153
+ unique_fields=Bookmark._meta.unique_together[0], # type: ignore
154
+ )
155
+ LOG.debug(f"Create {count} bookmarks.")
152
156
 
153
157
  def update_bookmarks(self, updates, comic_filter):
154
158
  """Update a user bookmark."""
@@ -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 (
@@ -227,24 +223,9 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
227
223
  value = "c" if model == Comic else self.model_group # type: ignore
228
224
  return qs.annotate(group=Value(value, CharField(max_length=1)))
229
225
 
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
226
  def _annotate_bookmarks(self, qs, model):
242
227
  """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)
228
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
248
229
 
249
230
  page_rel = f"{bm_rel}__page"
250
231
  finished_rel = f"{bm_rel}__finished"
@@ -285,7 +266,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
285
266
  output_field=PositiveSmallIntegerField(),
286
267
  distinct=True,
287
268
  )
288
- qs = qs.alias(finished_count=finished_count)
269
+ qs = qs.annotate(finished_count=finished_count)
289
270
  finished_aggregate = Case(
290
271
  When(finished_count=F("child_count"), then=True),
291
272
  When(finished_count=0, then=False),
@@ -301,8 +282,7 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
301
282
  qs = qs.alias(page=bookmark_page)
302
283
 
303
284
  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)
285
+ qs = qs.annotate(finished=finished_aggregate)
306
286
  return qs
307
287
 
308
288
  def _annotate_progress(self, qs):
@@ -319,14 +299,20 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
319
299
 
320
300
  def _annotate_mtime(self, qs, model):
321
301
  """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
- )
302
+ if self.is_bookmark_filtered:
303
+ sub_qs = self.get_filtered_queryset(model, bookmark_filter=False)
304
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
305
+ ua_rel = bm_rel + "__updated_at"
306
+ qs = qs.annotate(
307
+ bmu_max=Max(
308
+ sub_qs.filter(pk=OuterRef("pk")).values_list(ua_rel, flat=True),
309
+ default=NONE_DATETIMEFIELD,
310
+ filter=bm_filter,
311
+ )
312
+ )
313
+ mtime = Greatest(Coalesce("bmu_max", Value(EPOCH_START)), "updated_at")
314
+ else:
315
+ mtime = F("updated_at")
330
316
  return qs.annotate(mtime=mtime)
331
317
 
332
318
  def annotate_card_aggregates(self, qs, model):
@@ -336,7 +322,6 @@ class BrowserAnnotationsView(BrowserOrderByView, SharedAnnotationsMixin):
336
322
  qs = self._annotate_order_value(qs, model)
337
323
  qs = self._annotate_group(qs, model)
338
324
  qs = self.annotate_group_names(qs, model)
339
- qs = self._annotate_child_count(qs, model)
340
325
  qs = self._annotate_bookmarks(qs, model)
341
326
  qs = self._annotate_progress(qs)
342
327
  return self._annotate_mtime(qs, model)
@@ -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,6 +29,7 @@ class BrowserBaseView(SearchFilterView):
31
29
  input_serializer_class = BrowserSettingsSerializer
32
30
 
33
31
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType({})
32
+ REPARSE_JSON_FIELDS = frozenset({"filters", "show"})
34
33
 
35
34
  _GET_JSON_KEYS = frozenset({"filters", "show"})
36
35
 
@@ -39,7 +38,6 @@ class BrowserBaseView(SearchFilterView):
39
38
  super().__init__(*args, **kwargs)
40
39
  self._is_admin: bool = False
41
40
  self.params: MappingProxyType[str, Any] = MappingProxyType({})
42
- self.bm_annotation_data = {}
43
41
  self.rel_prefix: str = ""
44
42
  self.model: type[BrowserGroupModel] | None = None
45
43
  self.group_class: type[BrowserGroupModel] | None = None
@@ -76,7 +74,9 @@ class BrowserBaseView(SearchFilterView):
76
74
 
77
75
  def _parse_query_params(self):
78
76
  """Parse GET query parameters: filter object & snake case."""
79
- query_params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
77
+ query_params = reparse_json_query_params(
78
+ self.request.GET, self.REPARSE_JSON_FIELDS
79
+ )
80
80
  if "q" not in query_params and (query := query_params.get("query")):
81
81
  # parse query param for opds v2
82
82
  query_params["q"] = query
@@ -89,6 +89,7 @@ class BrowserBaseView(SearchFilterView):
89
89
  serializer.is_valid(raise_exception=True)
90
90
  self.load_params_from_session(serializer.validated_data)
91
91
  self.is_bookmark_filtered = bool(self.params.get("filters", {}).get("bookmark"))
92
+ return serializer.validated_data
92
93
 
93
94
  def set_order_key(self):
94
95
  """Unused until browser."""
@@ -22,7 +22,6 @@ from codex.models import (
22
22
  Volume,
23
23
  )
24
24
  from codex.serializers.browser.page import BrowserPageSerializer
25
- from codex.util import max_none
26
25
  from codex.views.browser.title import BrowserTitleView
27
26
  from codex.views.const import (
28
27
  COMIC_GROUP,
@@ -157,16 +156,13 @@ class BrowserView(BrowserTitleView):
157
156
  count = 0
158
157
  return qs, count
159
158
 
160
- def _get_page_mtime(self, group_qs):
161
- if self.group_instance:
162
- page_updated_at_max = self.group_instance.updated_at
163
- else:
164
- page_updated_at_max = group_qs.aggregate(max=Max("updated_at"))["max"]
165
-
166
- agg_func = self.get_bookmark_updated_at_aggregate(self.model, True)
167
- page_bookmark_updated_at = group_qs.aggregate(max=agg_func)["max"]
159
+ def _get_page_mtime(self):
160
+ if not self.is_bookmark_filtered and self.group_instance:
161
+ # Nice optimization if we can get get it.
162
+ return self.group_instance.updated_at
168
163
 
169
- return max_none(page_updated_at_max, page_bookmark_updated_at)
164
+ group_model = self.group_class if self.group_class else self.model
165
+ return self.get_group_mtime(group_model)
170
166
 
171
167
  @staticmethod
172
168
  def _get_zero_pad(book_qs):
@@ -182,7 +178,6 @@ class BrowserView(BrowserTitleView):
182
178
  def _get_group_and_books(self):
183
179
  """Create the main queries with filters, annotation and pagination."""
184
180
  group_qs, group_count = self._get_group_queryset()
185
- mtime = self._get_page_mtime(group_qs)
186
181
  book_qs, book_count = self._get_book_queryset()
187
182
  # Paginate
188
183
  group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
@@ -200,6 +195,7 @@ class BrowserView(BrowserTitleView):
200
195
  # print(group_qs.query)
201
196
 
202
197
  total_count = page_group_count + page_book_count
198
+ mtime = self._get_page_mtime()
203
199
  return group_qs, book_qs, num_pages, total_count, zero_pad, mtime
204
200
 
205
201
  def get_object(self):
@@ -1,35 +1,79 @@
1
1
  """Annotations used by a filter."""
2
2
 
3
- from django.db.models.aggregates import Count
3
+ from django.db.models.aggregates import Count, Max
4
+ from django.db.models.functions import Coalesce, Greatest
4
5
 
5
6
  from codex.models.comic import Comic
6
7
  from codex.views.browser.filters.bookmark import BookmarkFilterMixin
7
8
  from codex.views.browser.validate import BrowserValidateView
9
+ from codex.views.const import (
10
+ EPOCH_START_DATETIMEFILED,
11
+ ONE_INTEGERFIELD,
12
+ )
8
13
 
9
14
 
10
15
  class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
11
16
  """Annotations that also filter."""
12
17
 
13
- def get_query_filters(self, model):
18
+ def __init__(self, *args, **kwargs) -> None:
19
+ """Initialize."""
20
+ super().__init__(*args, **kwargs)
21
+ self.init_bookmark_data()
22
+
23
+ def _get_query_filters(self, model, group, pks, bookmark_filter):
14
24
  """Return all the filters except the group filter."""
15
25
  object_filter = self.get_group_acl_filter(model)
16
- object_filter &= self.get_bookmark_filter(model)
26
+ object_filter &= self.get_group_filter(model, group, pks)
17
27
  object_filter &= self.get_comic_field_filter(model)
18
- object_filter &= self.get_group_filter(model)
28
+ if bookmark_filter:
29
+ # not needed because OuterRef is applied next
30
+ object_filter &= self.get_bookmark_filter(model)
19
31
  return object_filter
20
32
 
21
33
  def _filter_by_child_count(self, qs, model):
22
- if model == Comic:
23
- return qs
24
-
34
+ """Filter group by child count."""
25
35
  rel = self.rel_prefix + "pk"
26
- qs = qs.alias(pre_search_child_count=Count(rel, distinct=True))
27
- return qs.filter(pre_search_child_count__gt=0)
36
+ ann_name = "child_count"
37
+ count_func = ONE_INTEGERFIELD if model == Comic else Count(rel, distinct=True)
38
+ ann = {ann_name: count_func}
39
+ if self.TARGET == "opds2":
40
+ if model != Comic:
41
+ qs = qs.alias(**ann)
42
+ else:
43
+ qs = qs.annotate(**ann)
44
+ if model != Comic:
45
+ qs = qs.filter(**{f"{ann_name}__gt": 0})
46
+ return qs
28
47
 
29
- def get_filtered_queryset(self, model):
48
+ def get_filtered_queryset(self, model, group=None, pks=None, bookmark_filter=True):
30
49
  """Get a filtered queryset for the model."""
31
- object_filter = self.get_query_filters(model)
32
-
50
+ object_filter = self._get_query_filters(model, group, pks, bookmark_filter)
33
51
  qs = model.objects.filter(object_filter)
34
- qs = self._filter_by_child_count(qs, model)
35
- return self.apply_search_filter(qs, model)
52
+ qs = self.apply_search_filter(qs, model)
53
+ return self._filter_by_child_count(qs, model)
54
+
55
+ def get_group_mtime(self, model, group=None, pks=None):
56
+ """Get a filtered mtime for browser pages and mtime checker."""
57
+ qs = self.get_filtered_queryset(
58
+ model, group=group, pks=pks, bookmark_filter=self.is_bookmark_filtered
59
+ )
60
+ qs = qs.annotate(max_updated_at=Max("updated_at"))
61
+
62
+ if self.is_bookmark_filtered:
63
+ bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
64
+ qs = qs.filter(bm_filter)
65
+ ua_rel = bm_rel + "__updated_at"
66
+ mbua = Max(ua_rel)
67
+ else:
68
+ mbua = EPOCH_START_DATETIMEFILED
69
+ qs = qs.annotate(max_bookmark_updated_at=mbua)
70
+
71
+ mtime = qs.aggregate(
72
+ max=Greatest(
73
+ Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFILED),
74
+ "max_updated_at",
75
+ )
76
+ )["max"]
77
+ if mtime == NotImplemented:
78
+ mtime = None
79
+ return mtime
@@ -1,17 +1,26 @@
1
1
  """Bookmark filter view methods."""
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.db.models import Q
4
6
 
7
+ if TYPE_CHECKING:
8
+ from codex.models import BrowserGroupModel
9
+
5
10
 
6
11
  class BookmarkFilterMixin:
7
12
  """BookmarkFilter view methods."""
8
13
 
9
- def get_bm_rel(self, model):
14
+ def init_bookmark_data(self):
15
+ """Initialize the bm_annotation_data."""
16
+ self._bm_annotation_data: dict[BrowserGroupModel, tuple[str, Q]] = {}
17
+
18
+ def _get_bm_rel(self, model):
10
19
  """Create bookmark relation."""
11
20
  rel_prefix = self.get_rel_prefix(model) # type: ignore
12
21
  return rel_prefix + "bookmark"
13
22
 
14
- def get_my_bookmark_filter(self, bm_rel):
23
+ def _get_my_bookmark_filter(self, bm_rel):
15
24
  """Get a filter for my session or user defined bookmarks."""
16
25
  if self.request.user.is_authenticated: # type: ignore
17
26
  my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
@@ -22,12 +31,19 @@ class BookmarkFilterMixin:
22
31
  }
23
32
  return Q(**my_bookmarks_kwargs)
24
33
 
34
+ def get_bookmark_rel_and_filter(self, model):
35
+ """Get the bookmark rel and filter once."""
36
+ if model not in self._bm_annotation_data:
37
+ bm_rel = self._get_bm_rel(model)
38
+ bm_filter = self._get_my_bookmark_filter(bm_rel)
39
+ self._bm_annotation_data[model] = (bm_rel, bm_filter)
40
+ return self._bm_annotation_data[model]
41
+
25
42
  def get_bookmark_filter(self, model):
26
43
  """Build bookmark query."""
27
- choice = self.params["filters"].get("bookmark", "") # type: ignore
44
+ choice: str = self.params.get("filters", {}).get("bookmark", "") # type: ignore
28
45
  if choice:
29
- bm_rel = self.get_bm_rel(model)
30
- my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
46
+ bm_rel, my_bookmark_filter = self.get_bookmark_rel_and_filter(model)
31
47
  if choice == "READ":
32
48
  bookmark_filter = my_bookmark_filter & Q(
33
49
  **{f"{bm_rel}__finished": True}