codex 1.6.0a7__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 (517) hide show
  1. codex/integrity.py +5 -6
  2. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +54 -76
  3. codex/librarian/importer/cache.py +162 -29
  4. codex/librarian/importer/const.py +15 -6
  5. codex/librarian/importer/create_comics.py +101 -17
  6. codex/librarian/importer/create_covers.py +96 -0
  7. codex/librarian/importer/create_fks.py +73 -170
  8. codex/librarian/importer/deleted.py +59 -46
  9. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  10. codex/librarian/importer/failed_imports.py +42 -64
  11. codex/librarian/importer/importer.py +96 -0
  12. codex/librarian/importer/importerd.py +71 -397
  13. codex/librarian/importer/init.py +265 -0
  14. codex/librarian/importer/link_comics.py +26 -81
  15. codex/librarian/importer/link_covers.py +73 -0
  16. codex/librarian/importer/moved.py +76 -104
  17. codex/librarian/importer/query_covers.py +52 -0
  18. codex/librarian/importer/query_fks.py +128 -172
  19. codex/librarian/importer/status.py +2 -39
  20. codex/librarian/importer/tasks.py +22 -13
  21. codex/librarian/janitor/cleanup.py +2 -2
  22. codex/librarian/janitor/janitor.py +10 -0
  23. codex/librarian/librariand.py +7 -1
  24. codex/migrations/0027_import_order_and_covers.py +345 -0
  25. codex/models/comic.py +1 -2
  26. codex/models/groups.py +3 -2
  27. codex/models/paths.py +0 -1
  28. codex/search/query.py +25 -2
  29. codex/search/writing.py +1 -1
  30. codex/serializers/browser/mixins.py +0 -18
  31. codex/serializers/browser/mtime.py +21 -0
  32. codex/serializers/browser/settings.py +2 -3
  33. codex/serializers/choices.py +10 -1
  34. codex/serializers/fields.py +8 -9
  35. codex/serializers/reader.py +5 -5
  36. codex/serializers/route.py +9 -4
  37. codex/settings/settings.py +2 -2
  38. codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-eNspVUje.da8282a08876.js} +1 -1
  39. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  40. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  41. codex/static_root/assets/{VCheckbox-BOUtyxuo.js → VCheckbox-eNspVUje.js} +1 -1
  42. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  43. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  44. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js → VCheckboxBtn-DLzF_WHB.9a47873b8548.js} +1 -1
  45. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  46. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  47. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.js → VCheckboxBtn-DLzF_WHB.js} +1 -1
  48. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  49. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  50. codex/static_root/assets/{VCombobox-DjkDXe33.22617ea193b1.js → VCombobox-BIO7lU_B.d607ada71b06.js} +1 -1
  51. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  52. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  53. codex/static_root/assets/{VCombobox-DjkDXe33.js → VCombobox-BIO7lU_B.js} +1 -1
  54. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  55. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  56. codex/static_root/assets/{VDialog-X0zn9AGX.0d89c749b9a6.js → VDialog-87AymJC5.4a46716358c0.js} +1 -1
  57. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  58. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  59. codex/static_root/assets/{VDialog-X0zn9AGX.js → VDialog-87AymJC5.js} +1 -1
  60. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  61. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  62. codex/static_root/assets/{VExpansionPanels-CRevojaF.43d65c777c58.js → VExpansionPanels-CgA3shsd.9e524c73726a.js} +1 -1
  63. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  64. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  65. codex/static_root/assets/{VExpansionPanels-CRevojaF.js → VExpansionPanels-CgA3shsd.js} +1 -1
  66. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  67. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  68. codex/static_root/assets/{VRadioGroup-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-B--7K_v5.4a4e9c2d6c42.js} +1 -1
  69. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  70. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  71. codex/static_root/assets/{VRadioGroup-8nrrxjDv.js → VRadioGroup-B--7K_v5.js} +1 -1
  72. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  73. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  74. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  75. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  76. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  77. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  78. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  79. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  80. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +1 -0
  81. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  82. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  83. codex/static_root/assets/VSelect-BoJq222l.js +1 -0
  84. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  85. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  86. codex/static_root/assets/{VSelectionControl-CGu0oz0K.3030b7a270d6.js → VSelectionControl-CVQ9wkE2.c02186cceba6.js} +1 -1
  87. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  88. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  89. codex/static_root/assets/{VSelectionControl-CGu0oz0K.js → VSelectionControl-CVQ9wkE2.js} +1 -1
  90. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  91. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  92. codex/static_root/assets/{VSlideGroup-D_oNvCOd.71a7101cdddd.js → VSlideGroup-Ce0j2BKK.85e64443a928.js} +1 -1
  93. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  94. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  95. codex/static_root/assets/{VSlideGroup-D_oNvCOd.js → VSlideGroup-Ce0j2BKK.js} +1 -1
  96. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  97. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  98. codex/static_root/assets/{VTable-C7pKI7gU.087912f706f5.js → VTable-CrWkhkiP.6d530eea0c09.js} +1 -1
  99. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  100. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  101. codex/static_root/assets/{VTable-C7pKI7gU.js → VTable-CrWkhkiP.js} +1 -1
  102. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  103. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  104. codex/static_root/assets/{VTextField-Bs8oq9mk.js → VTextField-CkWbil3K.2cac13161dfe.js} +1 -1
  105. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  106. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  107. codex/static_root/assets/{VTextField-Bs8oq9mk.7999fef12a03.js → VTextField-CkWbil3K.js} +1 -1
  108. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  109. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  110. codex/static_root/assets/{VWindowItem-iyZ1XOkP.409f7d292253.js → VWindowItem-7z55Y4lK.98eb7487e039.js} +1 -1
  111. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  112. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  113. codex/static_root/assets/{VWindowItem-iyZ1XOkP.js → VWindowItem-7z55Y4lK.js} +1 -1
  114. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  115. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  116. codex/static_root/assets/{admin-B0pCBjma.2407da79bd20.js → admin-D3OK784R.f69891726ec5.js} +1 -1
  117. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  118. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  119. codex/static_root/assets/{admin-B0pCBjma.js → admin-D3OK784R.js} +1 -1
  120. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  121. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  122. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-1pnfvY8O.5438f252970e.js} +11 -11
  123. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  124. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  125. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.js → admin-drawer-panel-1pnfvY8O.js} +11 -11
  126. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  127. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  128. codex/static_root/assets/browser-BuU2x9y7.css +1 -0
  129. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  130. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  131. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +1 -0
  132. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  133. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  134. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +1 -0
  135. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  136. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  137. codex/static_root/assets/browser-CO9EYHIr.js +1 -0
  138. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  139. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  140. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.930f4cae3cb0.js → change-password-dialog-CjZdzaIk.188c765431ab.js} +1 -1
  141. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  142. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  143. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.js → change-password-dialog-CjZdzaIk.js} +1 -1
  144. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  145. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  146. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js → confirm-dialog-jwL7B98r.1cfd579cbcfe.js} +1 -1
  147. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  148. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  149. codex/static_root/assets/{confirm-dialog-RP7JNStF.js → confirm-dialog-jwL7B98r.js} +1 -1
  150. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  151. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  152. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +1 -0
  153. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  154. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  155. codex/static_root/assets/datetime-column-9w3GcKvo.js +1 -0
  156. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  157. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  158. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  159. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  160. codex/static_root/assets/{filter-DAdGUt-1.3261cbcd50b5.js → filter-C-pghOri.eeb7c2ecf398.js} +1 -1
  161. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  162. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  163. codex/static_root/assets/{filter-DAdGUt-1.js → filter-C-pghOri.js} +1 -1
  164. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  165. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  166. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js +1 -0
  167. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  168. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  169. codex/static_root/assets/flag-tab-Da2DBNyz.js +1 -0
  170. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  171. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  172. codex/static_root/assets/{group-tab-DEwh4jfB.a95097b5e320.js → group-tab-BONYWOc1.6ddf321a4a56.js} +1 -1
  173. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  174. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  175. codex/static_root/assets/{group-tab-DEwh4jfB.js → group-tab-BONYWOc1.js} +1 -1
  176. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  177. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  178. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js +1 -0
  179. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  180. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  181. codex/static_root/assets/http-error-D2Jnc20C.js +1 -0
  182. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  183. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  184. codex/static_root/assets/{library-tab-DrXvD9B2.js → library-tab-B419agXA.d4b778172aaa.js} +1 -1
  185. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  186. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  187. codex/static_root/assets/{library-tab-DrXvD9B2.f765f9d3bae4.js → library-tab-B419agXA.js} +1 -1
  188. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  189. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  190. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +32 -0
  191. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  192. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  193. codex/static_root/assets/main-CMQwJ9aG.js +32 -0
  194. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  195. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  196. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  197. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  198. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  199. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  200. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  201. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  202. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +1 -0
  203. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  204. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  205. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +1 -0
  206. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  207. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  208. codex/static_root/assets/{pdf-doc-obrBwxa6.ec9bd13a5786.js → pdf-doc-8RVZqILx.2e7166dd26fb.js} +1 -1
  209. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  210. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  211. codex/static_root/assets/{pdf-doc-obrBwxa6.js → pdf-doc-8RVZqILx.js} +1 -1
  212. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  213. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  214. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  215. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  216. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  217. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  218. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  219. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  220. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js +2 -0
  221. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  222. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  223. codex/static_root/assets/reader-DNnK_Chr.js +2 -0
  224. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  225. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  226. codex/static_root/assets/{relation-chips-CZJcLjc8.1d7426dab654.js → relation-chips-PYua7aXv.428ba0b5861e.js} +1 -1
  227. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  228. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  229. codex/static_root/assets/{relation-chips-CZJcLjc8.js → relation-chips-PYua7aXv.js} +1 -1
  230. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  231. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  232. codex/static_root/assets/{settings-drawer-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DbN6ISRH.d14f525d8eee.js} +2 -2
  233. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  234. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  235. codex/static_root/assets/{settings-drawer-7iTnAdjf.js → settings-drawer-DbN6ISRH.js} +2 -2
  236. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  237. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  238. codex/static_root/assets/{stats-tab-CQsK1n8O.544bf50ffb22.js → stats-tab-DImNhH8O.d5026283841a.js} +1 -1
  239. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  240. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  241. codex/static_root/assets/{stats-tab-CQsK1n8O.js → stats-tab-DImNhH8O.js} +1 -1
  242. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  243. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  244. codex/static_root/assets/{task-tab-C60TLP6e.63aa929a4ed6.js → task-tab-DPD53kjv.e56a35ff1691.js} +1 -1
  245. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  246. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  247. codex/static_root/assets/{task-tab-C60TLP6e.js → task-tab-DPD53kjv.js} +1 -1
  248. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  249. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  250. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +1 -0
  251. codex/static_root/assets/to-case-ehC9Ccyj.js +1 -0
  252. codex/static_root/assets/{unauthorized-Bs7NSqea.c55650fac055.js → unauthorized-BOIx7Y18.ee3961abfccb.js} +1 -1
  253. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  254. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  255. codex/static_root/assets/{unauthorized-Bs7NSqea.js → unauthorized-BOIx7Y18.js} +1 -1
  256. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  257. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  258. codex/static_root/assets/{user-tab-ChuH7Atm.7e80cf1fb78e.js → user-tab-BABwfxLO.37dd0440fbdd.js} +1 -1
  259. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  260. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  261. codex/static_root/assets/{user-tab-ChuH7Atm.js → user-tab-BABwfxLO.js} +1 -1
  262. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  263. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  264. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  265. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  266. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  267. codex/static_root/js/choices-admin.json +1 -1
  268. codex/static_root/js/choices-admin.json.br +0 -0
  269. codex/static_root/js/choices-admin.json.gz +0 -0
  270. codex/static_root/js/{choices.079a01e0be1a.json → choices.a0aaaa6c7ef7.json} +1 -1
  271. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  272. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  273. codex/static_root/js/choices.json +1 -1
  274. codex/static_root/js/choices.json.br +0 -0
  275. codex/static_root/js/choices.json.gz +0 -0
  276. codex/static_root/{manifest.dad79c8475ae.json → manifest.cbea5663003a.json} +239 -239
  277. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  278. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  279. codex/static_root/manifest.json +239 -239
  280. codex/static_root/manifest.json.br +0 -0
  281. codex/static_root/manifest.json.gz +0 -0
  282. codex/static_root/staticfiles.json +1 -1
  283. codex/urls/api/browser.py +11 -2
  284. codex/urls/api/reader.py +1 -7
  285. codex/urls/api/v3.py +1 -1
  286. codex/urls/opds/binary.py +1 -1
  287. codex/views/admin/auth.py +0 -2
  288. codex/views/admin/tasks.py +10 -1
  289. codex/views/auth.py +2 -4
  290. codex/views/bookmark.py +44 -40
  291. codex/views/browser/annotations.py +38 -174
  292. codex/views/browser/base.py +6 -22
  293. codex/views/browser/breadcrumbs.py +16 -14
  294. codex/views/browser/browser.py +11 -20
  295. codex/views/browser/choices.py +15 -39
  296. codex/views/browser/cover.py +177 -0
  297. codex/views/browser/filters/annotations.py +63 -18
  298. codex/views/browser/filters/bookmark.py +21 -5
  299. codex/views/browser/filters/field.py +1 -2
  300. codex/views/browser/filters/group.py +19 -11
  301. codex/views/browser/filters/search.py +19 -10
  302. codex/views/browser/metadata.py +20 -25
  303. codex/views/browser/mtime.py +72 -0
  304. codex/views/const.py +20 -0
  305. codex/views/opds/binary.py +1 -1
  306. codex/views/opds/urls.py +2 -1
  307. codex/views/opds/util.py +2 -4
  308. codex/views/opds/v1/entry/links.py +18 -33
  309. codex/views/opds/v1/facets.py +2 -1
  310. codex/views/opds/v1/feed.py +42 -23
  311. codex/views/opds/v1/links.py +2 -1
  312. codex/views/opds/v2/feed.py +11 -9
  313. codex/views/opds/v2/links.py +2 -1
  314. codex/views/opds/v2/publications.py +13 -34
  315. codex/views/public.py +0 -2
  316. codex/views/reader/books.py +7 -9
  317. codex/views/reader/reader.py +25 -22
  318. codex/views/session.py +5 -3
  319. codex/views/util.py +32 -0
  320. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/METADATA +8 -7
  321. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/RECORD +324 -325
  322. codex/librarian/importer/update_comics.py +0 -85
  323. codex/migrations/0027_sort_name.py +0 -139
  324. codex/migrations/0028_custom_covers.py +0 -166
  325. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  326. codex/serializers/mtime.py +0 -25
  327. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  328. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  329. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  330. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  331. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  332. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  333. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  334. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  335. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  336. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  337. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  338. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  339. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  340. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  341. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  342. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  343. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  344. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  345. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  346. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  347. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  348. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  349. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  350. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  351. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +0 -1
  352. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  353. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  354. codex/static_root/assets/VSelect-DgfIKRnC.js +0 -1
  355. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  356. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  357. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  358. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  359. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  360. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  361. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  362. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  363. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  364. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  365. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  366. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  367. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  368. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  369. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  370. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  371. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  372. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  373. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  374. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  375. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  376. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  377. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  378. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  379. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  380. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  381. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  382. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  383. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  384. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  385. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  386. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  387. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  388. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  389. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  390. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  391. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +0 -1
  392. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  393. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  394. codex/static_root/assets/browser-DbyAjCNh.js +0 -1
  395. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  396. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  397. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +0 -1
  398. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  399. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  400. codex/static_root/assets/browser-VnzNj1t3.css +0 -1
  401. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  402. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  403. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  404. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  405. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  406. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  407. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
  408. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  409. codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
  410. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  411. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js +0 -1
  412. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  413. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  414. codex/static_root/assets/datetime-column-Cu3WYyPj.js +0 -1
  415. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  416. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  417. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  418. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  419. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  420. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  421. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  422. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  423. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  424. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  425. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +0 -1
  426. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  427. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  428. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +0 -1
  429. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  430. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  431. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  432. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  433. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  434. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  435. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +0 -1
  436. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  437. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  438. codex/static_root/assets/http-error-xs3mIL8U.js +0 -1
  439. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  440. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  441. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  442. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  443. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  444. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  445. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +0 -32
  446. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  447. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  448. codex/static_root/assets/main-Dy9Uqpmh.js +0 -32
  449. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  450. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  451. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +0 -1
  452. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  453. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  454. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +0 -1
  455. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  456. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  457. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css +0 -1
  458. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  459. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  460. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css +0 -1
  461. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  462. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  463. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  464. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  465. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  466. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  467. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +0 -2
  468. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  469. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  470. codex/static_root/assets/reader-BXKUaUeC.js +0 -2
  471. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  472. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  473. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css +0 -1
  474. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  475. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  476. codex/static_root/assets/reader-OXJ1KVaW.css +0 -1
  477. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  478. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  479. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  480. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  481. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  482. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  483. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  484. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  485. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  486. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  487. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  488. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  489. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  490. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  491. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  492. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  493. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  494. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  495. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +0 -1
  496. codex/static_root/assets/to-case-CR9beRR0.js +0 -1
  497. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  498. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  499. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  500. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  501. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  502. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  503. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  504. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  505. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  506. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  507. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  508. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  509. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  510. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  511. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  512. codex/views/cover.py +0 -76
  513. codex/views/mtime.py +0 -70
  514. codex/views/utils.py +0 -26
  515. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/LICENSE +0 -0
  516. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/WHEEL +0 -0
  517. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/entry_points.txt +0 -0
@@ -2,6 +2,11 @@
2
2
 
3
3
  from codex.librarian.covers.status import CoverStatusTypes
4
4
  from codex.librarian.covers.tasks import CoverRemoveOrphansTask
5
+ from codex.librarian.importer.status import ImportStatusTypes
6
+ from codex.librarian.importer.tasks import (
7
+ AdoptOrphanFoldersTask,
8
+ UpdateGroupsFirstComic,
9
+ )
5
10
  from codex.librarian.janitor.cleanup import TOTAL_NUM_FK_CLASSES, CleanupMixin
6
11
  from codex.librarian.janitor.failed_imports import UpdateFailedImportsMixin
7
12
  from codex.librarian.janitor.status import JanitorStatusTypes
@@ -36,8 +41,11 @@ _JANITOR_STATII = (
36
41
  Status(JanitorStatusTypes.DB_OPTIMIZE),
37
42
  Status(JanitorStatusTypes.DB_BACKUP),
38
43
  Status(JanitorStatusTypes.CODEX_UPDATE),
44
+ Status(ImportStatusTypes.GROUP_UPDATE),
39
45
  Status(CoverStatusTypes.FIND_ORPHAN),
40
46
  Status(CoverStatusTypes.PURGE_COVERS),
47
+ Status(ImportStatusTypes.ADOPT_FOLDERS),
48
+ Status(ImportStatusTypes.DIRS_MOVED),
41
49
  Status(SearchIndexStatusTypes.SEARCH_INDEX_UPDATE),
42
50
  Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
43
51
  Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE),
@@ -66,6 +74,8 @@ class Janitor(CleanupMixin, UpdateMixin, VacuumMixin, UpdateFailedImportsMixin):
66
74
  JanitorVacuumTask(),
67
75
  JanitorBackupTask(),
68
76
  JanitorUpdateTask(force=False),
77
+ AdoptOrphanFoldersTask(),
78
+ UpdateGroupsFirstComic(),
69
79
  CoverRemoveOrphansTask(),
70
80
  SearchIndexUpdateTask(False),
71
81
  SearchIndexMergeTask(optimize),
@@ -11,7 +11,11 @@ from codex.librarian.covers.coverd import CoverThread
11
11
  from codex.librarian.covers.tasks import CoverTask
12
12
  from codex.librarian.delayed_taskd import DelayedTasksThread
13
13
  from codex.librarian.importer.importerd import ComicImporterThread
14
- from codex.librarian.importer.tasks import AdoptOrphanFoldersTask, ImportTask
14
+ from codex.librarian.importer.tasks import (
15
+ AdoptOrphanFoldersTask,
16
+ ImportTask,
17
+ UpdateGroupsFirstComic,
18
+ )
15
19
  from codex.librarian.janitor.janitor import Janitor
16
20
  from codex.librarian.janitor.janitord import JanitorThread
17
21
  from codex.librarian.janitor.tasks import JanitorTask
@@ -70,9 +74,11 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
70
74
  self.broadcast_queue = broadcast_queue
71
75
  startup_tasks = (
72
76
  AdoptOrphanFoldersTask(),
77
+ UpdateGroupsFirstComic(),
73
78
  WatchdogSyncTask(),
74
79
  SearchIndexRebuildIfDBChangedTask(),
75
80
  )
81
+
76
82
  for task in startup_tasks:
77
83
  self.queue.put(task)
78
84
  self.search_indexer_abort_event = Manager().Event()
@@ -0,0 +1,345 @@
1
+ """Generated by Django 5.0.6 on 2024-06-06 02:22."""
2
+
3
+ import os
4
+ from contextlib import suppress
5
+ from pathlib import Path
6
+
7
+ import django.db.models.deletion
8
+ from django.db import migrations, models
9
+
10
+ from codex.models.util import get_sort_name
11
+
12
+ CONFIG_PATH = Path(os.environ.get("CODEX_CONFIG_DIR", Path.cwd() / "config"))
13
+ COVER_ROOT = CONFIG_PATH / "cache" / "covers"
14
+ _GROUP_MODEL_NAMES = (
15
+ "publisher",
16
+ "imprint",
17
+ "series",
18
+ "storyarc",
19
+ "folder",
20
+ "comic",
21
+ )
22
+
23
+
24
+ def _set_sort_name(obj):
25
+ """Create sort_name for model."""
26
+ obj.sort_name = get_sort_name(obj.name)
27
+
28
+
29
+ def _generate_sort_name(apps, _schema_editor):
30
+ """Update new sort_name field."""
31
+ for model_name in _GROUP_MODEL_NAMES:
32
+ model = apps.get_model("codex", model_name)
33
+ update_fields = ("sort_name",)
34
+ only_fields = ("name", *update_fields)
35
+ update_groups = []
36
+ objs = model.objects.only(*only_fields)
37
+ for obj in objs:
38
+ _set_sort_name(obj)
39
+ update_groups.append(obj)
40
+ if update_groups:
41
+ model.objects.bulk_update(update_groups, update_fields)
42
+ print(f"\tUpdated sort_name for {len(update_groups)} {model_name}s")
43
+
44
+
45
+ def _remove_cover_symlinks(_apps, _schema_editor):
46
+ """Remove old missing cover symlinks."""
47
+ # Handled programmatically now.
48
+ count = 0
49
+ for dirpath_str, _, filenames in os.walk(COVER_ROOT):
50
+ dirpath = Path(dirpath_str)
51
+ for filename in filenames:
52
+ full_path = dirpath / filename
53
+ if full_path.is_symlink():
54
+ with suppress(OSError):
55
+ full_path.unlink(missing_ok=True)
56
+ count += 1
57
+ if count:
58
+ print(f"Removed {count} missing-cover symlinks from cover cache.")
59
+
60
+
61
+ class Migration(migrations.Migration):
62
+ """Migrate db."""
63
+
64
+ dependencies = [
65
+ ("codex", "0026_comicbox_1"),
66
+ ]
67
+
68
+ operations = [
69
+ migrations.AddField(
70
+ model_name="comic",
71
+ name="sort_name",
72
+ field=models.CharField(db_index=True, default="", max_length=128),
73
+ ),
74
+ migrations.AddField(
75
+ model_name="folder",
76
+ name="first_comic",
77
+ field=models.ForeignKey(
78
+ default=None,
79
+ null=True,
80
+ on_delete=django.db.models.deletion.SET_DEFAULT,
81
+ related_name="+",
82
+ to="codex.comic",
83
+ ),
84
+ ),
85
+ migrations.AddField(
86
+ model_name="folder",
87
+ name="sort_name",
88
+ field=models.CharField(db_index=True, default="", max_length=128),
89
+ ),
90
+ migrations.AddField(
91
+ model_name="imprint",
92
+ name="first_comic",
93
+ field=models.ForeignKey(
94
+ default=None,
95
+ null=True,
96
+ on_delete=django.db.models.deletion.SET_DEFAULT,
97
+ related_name="+",
98
+ to="codex.comic",
99
+ ),
100
+ ),
101
+ migrations.AddField(
102
+ model_name="imprint",
103
+ name="sort_name",
104
+ field=models.CharField(db_index=True, default="", max_length=128),
105
+ ),
106
+ migrations.AddField(
107
+ model_name="library",
108
+ name="covers_only",
109
+ field=models.BooleanField(db_index=True, default=False),
110
+ ),
111
+ migrations.AddField(
112
+ model_name="publisher",
113
+ name="first_comic",
114
+ field=models.ForeignKey(
115
+ default=None,
116
+ null=True,
117
+ on_delete=django.db.models.deletion.SET_DEFAULT,
118
+ related_name="+",
119
+ to="codex.comic",
120
+ ),
121
+ ),
122
+ migrations.AddField(
123
+ model_name="publisher",
124
+ name="sort_name",
125
+ field=models.CharField(db_index=True, default="", max_length=128),
126
+ ),
127
+ migrations.AddField(
128
+ model_name="series",
129
+ name="first_comic",
130
+ field=models.ForeignKey(
131
+ default=None,
132
+ null=True,
133
+ on_delete=django.db.models.deletion.SET_DEFAULT,
134
+ related_name="+",
135
+ to="codex.comic",
136
+ ),
137
+ ),
138
+ migrations.AddField(
139
+ model_name="series",
140
+ name="sort_name",
141
+ field=models.CharField(db_index=True, default="", max_length=128),
142
+ ),
143
+ migrations.AddField(
144
+ model_name="storyarc",
145
+ name="first_comic",
146
+ field=models.ForeignKey(
147
+ default=None,
148
+ null=True,
149
+ on_delete=django.db.models.deletion.SET_DEFAULT,
150
+ related_name="+",
151
+ to="codex.comic",
152
+ ),
153
+ ),
154
+ migrations.AddField(
155
+ model_name="storyarc",
156
+ name="sort_name",
157
+ field=models.CharField(db_index=True, default="", max_length=128),
158
+ ),
159
+ migrations.AddField(
160
+ model_name="volume",
161
+ name="first_comic",
162
+ field=models.ForeignKey(
163
+ default=None,
164
+ null=True,
165
+ on_delete=django.db.models.deletion.SET_DEFAULT,
166
+ related_name="+",
167
+ to="codex.comic",
168
+ ),
169
+ ),
170
+ migrations.AlterField(
171
+ model_name="adminflag",
172
+ name="key",
173
+ field=models.CharField(
174
+ choices=[
175
+ ("FV", "Folder View"),
176
+ ("RG", "Registration"),
177
+ ("NU", "Non Users"),
178
+ ("AU", "Auto Update"),
179
+ ("SO", "Search Index Optimize"),
180
+ ("IM", "Import Metadata"),
181
+ ],
182
+ db_index=True,
183
+ max_length=2,
184
+ ),
185
+ ),
186
+ migrations.AlterField(
187
+ model_name="librarianstatus",
188
+ name="status_type",
189
+ field=models.CharField(
190
+ choices=[
191
+ ("CCC", "Create Covers"),
192
+ ("CCD", "Purge Covers"),
193
+ ("CFO", "Find Orphan"),
194
+ ("IDM", "Dirs Moved"),
195
+ ("IFM", "Files Moved"),
196
+ ("ITR", "Aggregate Tags"),
197
+ ("ITQ", "Query Missing Fks"),
198
+ ("ITC", "Create Fks"),
199
+ ("IDU", "Dirs Modified"),
200
+ ("IFU", "Files Modified"),
201
+ ("IFC", "Files Created"),
202
+ ("IMQ", "Query M2M Fields"),
203
+ ("IMC", "Link M2M Fields"),
204
+ ("IDD", "Dirs Deleted"),
205
+ ("IFD", "Files Deleted"),
206
+ ("IFI", "Failed Imports"),
207
+ ("ICQ", "Query Missing Covers"),
208
+ ("ICM", "Covers Moved"),
209
+ ("ICU", "Covers Modified"),
210
+ ("ICC", "Covers Created"),
211
+ ("ICD", "Covers Deleted"),
212
+ ("ICL", "Covers Link"),
213
+ ("IGU", "Group Update"),
214
+ ("JTD", "Cleanup Fk"),
215
+ ("JCU", "Codex Update"),
216
+ ("JCR", "Codex Restart"),
217
+ ("JCS", "Codex Stop"),
218
+ ("JDO", "Db Optimize"),
219
+ ("JDB", "Db Backup"),
220
+ ("JSD", "Cleanup Sessions"),
221
+ ("JCD", "Cleanup Covers"),
222
+ ("SIX", "Search Index Clear"),
223
+ ("SIU", "Search Index Update"),
224
+ ("SID", "Search Index Remove"),
225
+ ("SIM", "Search Index Merge"),
226
+ ("WPO", "Poll"),
227
+ ],
228
+ db_index=True,
229
+ max_length=3,
230
+ ),
231
+ ),
232
+ migrations.AlterField(
233
+ model_name="timestamp",
234
+ name="key",
235
+ field=models.CharField(
236
+ choices=[
237
+ ("JA", "Janitor"),
238
+ ("VR", "Codex Version"),
239
+ ("SI", "Search Index UUID"),
240
+ ("AP", "API Key"),
241
+ ],
242
+ db_index=True,
243
+ max_length=2,
244
+ ),
245
+ ),
246
+ migrations.CreateModel(
247
+ name="CustomCover",
248
+ fields=[
249
+ (
250
+ "id",
251
+ models.AutoField(
252
+ auto_created=True,
253
+ primary_key=True,
254
+ serialize=False,
255
+ verbose_name="ID",
256
+ ),
257
+ ),
258
+ ("created_at", models.DateTimeField(auto_now_add=True)),
259
+ ("updated_at", models.DateTimeField(auto_now=True)),
260
+ ("path", models.CharField(db_index=True, max_length=4095)),
261
+ ("stat", models.JSONField(null=True)),
262
+ (
263
+ "group",
264
+ models.CharField(
265
+ choices=[
266
+ ("p", "P"),
267
+ ("i", "I"),
268
+ ("s", "S"),
269
+ ("a", "A"),
270
+ ("f", "F"),
271
+ ],
272
+ db_index=True,
273
+ max_length=1,
274
+ ),
275
+ ),
276
+ (
277
+ "sort_name",
278
+ models.CharField(db_index=True, default="", max_length=128),
279
+ ),
280
+ (
281
+ "library",
282
+ models.ForeignKey(
283
+ on_delete=django.db.models.deletion.CASCADE, to="codex.library"
284
+ ),
285
+ ),
286
+ ],
287
+ options={
288
+ "get_latest_by": "updated_at",
289
+ "abstract": False,
290
+ "unique_together": {("library", "path")},
291
+ },
292
+ ),
293
+ migrations.AddField(
294
+ model_name="folder",
295
+ name="custom_cover",
296
+ field=models.ForeignKey(
297
+ default=None,
298
+ null=True,
299
+ on_delete=django.db.models.deletion.SET_DEFAULT,
300
+ to="codex.customcover",
301
+ ),
302
+ ),
303
+ migrations.AddField(
304
+ model_name="imprint",
305
+ name="custom_cover",
306
+ field=models.ForeignKey(
307
+ default=None,
308
+ null=True,
309
+ on_delete=django.db.models.deletion.SET_DEFAULT,
310
+ to="codex.customcover",
311
+ ),
312
+ ),
313
+ migrations.AddField(
314
+ model_name="publisher",
315
+ name="custom_cover",
316
+ field=models.ForeignKey(
317
+ default=None,
318
+ null=True,
319
+ on_delete=django.db.models.deletion.SET_DEFAULT,
320
+ to="codex.customcover",
321
+ ),
322
+ ),
323
+ migrations.AddField(
324
+ model_name="series",
325
+ name="custom_cover",
326
+ field=models.ForeignKey(
327
+ default=None,
328
+ null=True,
329
+ on_delete=django.db.models.deletion.SET_DEFAULT,
330
+ to="codex.customcover",
331
+ ),
332
+ ),
333
+ migrations.AddField(
334
+ model_name="storyarc",
335
+ name="custom_cover",
336
+ field=models.ForeignKey(
337
+ default=None,
338
+ null=True,
339
+ on_delete=django.db.models.deletion.SET_DEFAULT,
340
+ to="codex.customcover",
341
+ ),
342
+ ),
343
+ migrations.RunPython(_generate_sort_name),
344
+ migrations.RunPython(_remove_cover_symlinks),
345
+ ]
codex/models/comic.py CHANGED
@@ -176,12 +176,11 @@ class Comic(WatchedPathBrowserGroup):
176
176
 
177
177
  # Not useful
178
178
  custom_cover = None
179
+ first_comic = None
179
180
 
180
181
  class Meta(WatchedPathBrowserGroup.Meta):
181
182
  """Constraints."""
182
183
 
183
- # TODO see if this inherits properly
184
- # unique_together = ("library", "path")
185
184
  verbose_name = "Issue"
186
185
 
187
186
  def _set_date(self):
codex/models/groups.py CHANGED
@@ -25,6 +25,9 @@ class BrowserGroupModel(BaseModel):
25
25
  custom_cover = ForeignKey(
26
26
  CustomCover, on_delete=SET_DEFAULT, null=True, default=None
27
27
  )
28
+ first_comic = ForeignKey(
29
+ "Comic", on_delete=SET_DEFAULT, null=True, default=None, related_name="+"
30
+ )
28
31
 
29
32
  def set_sort_name(self):
30
33
  """Create sort_name for model."""
@@ -128,14 +131,12 @@ class WatchedPathBrowserGroup(BrowserGroupModel, WatchedPath):
128
131
 
129
132
  def presave(self):
130
133
  """Fix multiple inheritance presave."""
131
- # TODO test presave calling bgm & stat
132
134
  super().presave()
133
135
  WatchedPath.presave(self)
134
136
 
135
137
  class Meta(WatchedPath.Meta): # type: ignore
136
138
  """Use Mixin Meta."""
137
139
 
138
- # TODO see if this does unique properly.
139
140
  abstract = True
140
141
 
141
142
 
codex/models/paths.py CHANGED
@@ -66,7 +66,6 @@ class WatchedPath(BaseModel):
66
66
  class FailedImport(WatchedPath):
67
67
  """Failed Comic Imports. Displayed in Admin Panel."""
68
68
 
69
- # TODO rename reason
70
69
  name = CharField(db_index=True, max_length=MAX_NAME_LEN, default="")
71
70
 
72
71
  def set_reason(self, exc):
codex/search/query.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from codex._vendor.haystack.backends.whoosh_backend import (
4
4
  WhooshSearchQuery,
5
5
  )
6
+ from codex._vendor.haystack.constants import DJANGO_ID
6
7
  from codex._vendor.haystack.query import SearchQuerySet
7
8
 
8
9
 
@@ -12,7 +13,8 @@ class CodexSearchQuery(WhooshSearchQuery):
12
13
  def __init__(self, *args, **kwargs):
13
14
  """Give CodexSearchQuery a reverse attribute."""
14
15
  super().__init__(*args, **kwargs)
15
- self._reverse = False
16
+ self._reverse: bool = False
17
+ self._filter_comic_ids: frozenset | None = None
16
18
 
17
19
  def clean(self, query_fragment):
18
20
  """Optimize to noop because RESERVED_ consts are empty in the backend."""
@@ -22,9 +24,23 @@ class CodexSearchQuery(WhooshSearchQuery):
22
24
  """Invert the reverse attribute."""
23
25
  self._reverse = not self._reverse
24
26
 
27
+ def filter_comic_ids(self, ids):
28
+ """Set a filter for the query."""
29
+ self._filter_comic_ids = frozenset(ids)
30
+
25
31
  def get_results(self, **kwargs):
26
32
  """Send custom get results the reverse flag."""
27
- return super().get_results(reverse=self._reverse, **kwargs)
33
+ custom_params = {}
34
+ if self._reverse:
35
+ custom_params["reverse"] = self._reverse
36
+
37
+ if self._filter_comic_ids:
38
+ filter_query = " OR ".join(
39
+ [f"{DJANGO_ID}:{pk}" for pk in self._filter_comic_ids]
40
+ )
41
+ custom_params["narrow_queries"] = {filter_query}
42
+
43
+ return super().get_results(**custom_params, **kwargs)
28
44
 
29
45
 
30
46
  class CodexSearchQuerySet(SearchQuerySet):
@@ -36,9 +52,16 @@ class CodexSearchQuerySet(SearchQuerySet):
36
52
  clone.query.order_reverse() # type: ignore
37
53
  return clone
38
54
 
55
+ def filter_comic_ids(self, ids):
56
+ """Filter search by comic ids."""
57
+ clone = self._clone()
58
+ clone.query.filter_comic_ids(ids) # type: ignore
59
+ return clone
60
+
39
61
  def values_list(self, *args, **kwargs):
40
62
  """Copy reverse setting into CodexSearchQuery."""
41
63
  clone = super().values_list(*args, **kwargs)
42
64
  if clone.query and self.query:
43
65
  clone.query._reverse = self.query._reverse # noqa: SLF001
66
+ clone.query._filter_comic_ids = self.query._filter_comic_ids # noqa: SLF001
44
67
  return clone
codex/search/writing.py CHANGED
@@ -16,7 +16,7 @@ class CodexWriter(BufferedWriter):
16
16
  """MP safe Buffered Writer that locks the index writer much more granularly.
17
17
 
18
18
  XXX The period and limit args are ignored and this writer behaves more like the
19
- AsycnWriter. The AsyncWriter could possibly be extended to not lock until commit
19
+ AsycWriter. The AsyncWriter could possibly be extended to not lock until commit
20
20
  time like this one and use less code.
21
21
  """
22
22
 
@@ -13,14 +13,6 @@ from codex.serializers.browser.filters import IntListField
13
13
  from codex.serializers.fields import TimestampField
14
14
 
15
15
 
16
- class BrowserCover(Serializer):
17
- """Browser Cover."""
18
-
19
- custom = BooleanField(read_only=True, default=False)
20
- pk = IntegerField(read_only=True, default=0)
21
- mtime = TimestampField(read_only=True, default=0)
22
-
23
-
24
16
  class BrowserAggregateSerializerMixin(Serializer):
25
17
  """Mixin for browser, opds & metadata serializers."""
26
18
 
@@ -30,7 +22,6 @@ class BrowserAggregateSerializerMixin(Serializer):
30
22
  # Aggregate Annotations
31
23
  child_count = IntegerField(read_only=True)
32
24
  mtime = TimestampField(read_only=True)
33
- cover = BrowserCover(read_only=True)
34
25
 
35
26
  # Bookmark annotations
36
27
  page = IntegerField(read_only=True)
@@ -39,12 +30,3 @@ class BrowserAggregateSerializerMixin(Serializer):
39
30
  progress = DecimalField(
40
31
  max_digits=5, decimal_places=2, read_only=True, coerce_to_string=False
41
32
  )
42
-
43
- def to_representation(self, instance):
44
- """Nest Cover Object."""
45
- instance.cover = {
46
- "custom": instance.cover_custom,
47
- "pk": instance.cover_pk,
48
- "mtime": instance.cover_mtime,
49
- }
50
- return super().to_representation(instance)
@@ -0,0 +1,21 @@
1
+ """Group Mtimes."""
2
+
3
+ from rest_framework.serializers import CharField, Serializer
4
+
5
+ from codex.serializers.browser.settings import BrowserSettingsFilterSerializer
6
+ from codex.serializers.fields import TimestampField
7
+ from codex.serializers.route import SimpleRouteSerializer
8
+
9
+
10
+ class GroupsMtimeSerializer(Serializer):
11
+ """Groups Mtimes."""
12
+
13
+ filters = BrowserSettingsFilterSerializer(required=False)
14
+ q = CharField(allow_blank=True, required=False)
15
+ groups = SimpleRouteSerializer(many=True, required=True)
16
+
17
+
18
+ class MtimeSerializer(Serializer):
19
+ """Max mtime for all submitted groups."""
20
+
21
+ max_mtime = TimestampField(read_only=True)
@@ -37,9 +37,8 @@ class BrowserSettingsSerializer(Serializer):
37
37
  q = CharField(allow_blank=True, required=False)
38
38
  query = CharField(allow_blank=True, required=False) # OPDS 2.0
39
39
  show = BrowserSettingsShowGroupFlagsSerializer(required=False)
40
- cover_style = ChoiceField(
41
- choices=tuple(CHOICES["coverStyle"].keys()), required=False
42
- )
40
+ dynamic_covers = BooleanField(required=False)
41
+ custom_covers = BooleanField(required=False)
43
42
  # search_results_limit = IntegerField(required=False)
44
43
  twenty_four_hour_time = BooleanField(required=False)
45
44
  top_group = ChoiceField(choices=tuple(CHOICES["topGroup"].keys()), required=False)
@@ -49,7 +49,16 @@ _CHOICES_FN_RE = {
49
49
  _CHOICES_MODULE_NAME: create_choices_fn_regexes(_CHOICES_MODULE_NAME),
50
50
  _ADMIN_CHOICES_MODULE_NAME: create_choices_fn_regexes(_ADMIN_CHOICES_MODULE_NAME),
51
51
  }
52
- _SINGLE_VALUE_KEYS = frozenset({"orderReverse", "q", "searchResultsLimit"})
52
+ _SINGLE_VALUE_KEYS = frozenset(
53
+ {
54
+ "customCovers",
55
+ "dynamicCovers",
56
+ "orderReverse",
57
+ "q",
58
+ "searchResultsLimit",
59
+ "twentyFourHourTime",
60
+ }
61
+ )
53
62
 
54
63
  # Exports
55
64
  CHOICES = {}
@@ -1,8 +1,7 @@
1
1
  """Custom fields."""
2
2
 
3
3
  from abc import ABC
4
- from datetime import datetime
5
- from math import floor
4
+ from datetime import datetime, timezone
6
5
 
7
6
  from django.core.exceptions import ValidationError
8
7
  from django.utils.translation import gettext_lazy as _
@@ -23,15 +22,15 @@ VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
23
22
  class TimestampField(IntegerField):
24
23
  """IntegerTimestampField."""
25
24
 
26
- def to_representation(self, value):
27
- """Convert to int from datetime, or castable."""
25
+ def to_representation(self, value) -> int:
26
+ """Convert to Jascript millisecond int timestamp from datetime, or castable."""
28
27
  if isinstance(value, datetime):
29
- value = floor(value.timestamp())
30
- return int(value)
28
+ value = value.timestamp()
29
+ return int(float(value) * 1000)
31
30
 
32
- def to_internal_value(self, data):
33
- """Convert from castable, likely string."""
34
- return int(data)
31
+ def to_internal_value(self, data) -> datetime: # type: ignore
32
+ """Convert from castable, likely string to datetime."""
33
+ return datetime.fromtimestamp(float(data) / 1000, tz=timezone.utc)
35
34
 
36
35
 
37
36
  def validate_decades(decades):
@@ -11,7 +11,7 @@ from rest_framework.serializers import (
11
11
 
12
12
  from codex.models import Bookmark
13
13
  from codex.serializers.fields import TimestampField
14
- from codex.serializers.route import RouteSerializer
14
+ from codex.serializers.route import RouteSerializer, SimpleRouteSerializer
15
15
 
16
16
 
17
17
  class ReaderSettingsSerializer(Serializer):
@@ -38,12 +38,12 @@ class ReaderComicSerializer(Serializer):
38
38
  mtime = TimestampField(read_only=True)
39
39
 
40
40
 
41
- class ReaderArcSerializer(RouteSerializer):
41
+ class ReaderArcSerializer(SimpleRouteSerializer):
42
42
  """Information about the current Arc."""
43
43
 
44
- page = None
45
- index = IntegerField(read_only=True, required=False)
46
- count = IntegerField(read_only=True, required=False)
44
+ name = CharField(required=False)
45
+ index = IntegerField(required=False)
46
+ count = IntegerField(required=False)
47
47
  mtime = TimestampField(read_only=True)
48
48
 
49
49