codex 1.6.0a5__py3-none-any.whl → 1.6.0a7__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 (564) hide show
  1. codex/exceptions.py +1 -0
  2. codex/librarian/covers/purge.py +1 -2
  3. codex/librarian/importer/aggregate_metadata.py +2 -10
  4. codex/librarian/importer/cache.py +47 -0
  5. codex/librarian/importer/const.py +8 -0
  6. codex/librarian/importer/deleted.py +45 -7
  7. codex/librarian/importer/importerd.py +11 -1
  8. codex/librarian/importer/update_comics.py +0 -2
  9. codex/librarian/janitor/janitor.py +1 -1
  10. codex/librarian/librariand.py +1 -1
  11. codex/migrations/0029_choices_adminflag_and_timestamp.py +44 -0
  12. codex/models/admin.py +0 -1
  13. codex/permissions.py +1 -1
  14. codex/serializers/admin.py +1 -2
  15. codex/serializers/browser/filters.py +8 -101
  16. codex/serializers/browser/mixins.py +20 -3
  17. codex/serializers/browser/page.py +2 -3
  18. codex/serializers/browser/settings.py +2 -0
  19. codex/serializers/fields.py +123 -0
  20. codex/serializers/mtime.py +25 -0
  21. codex/serializers/opds/v2.py +7 -11
  22. codex/serializers/opds/v2_unused.py +5 -3
  23. codex/serializers/reader.py +5 -2
  24. codex/serializers/route.py +7 -3
  25. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js +1 -0
  26. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  27. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  28. codex/static_root/assets/VCheckbox-BOUtyxuo.js +1 -0
  29. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  30. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  31. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js +1 -0
  32. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  33. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  34. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js +1 -0
  35. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  36. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  37. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js +1 -0
  38. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  39. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  40. codex/static_root/assets/VCombobox-DjkDXe33.js +1 -0
  41. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  42. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  43. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js +1 -0
  44. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  45. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  46. codex/static_root/assets/VDialog-X0zn9AGX.js +1 -0
  47. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  48. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  49. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js +1 -0
  50. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  51. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  52. codex/static_root/assets/VExpansionPanels-CRevojaF.js +1 -0
  53. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  54. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  55. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js +1 -0
  56. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  57. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  58. codex/static_root/assets/VRadioGroup-8nrrxjDv.js +1 -0
  59. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  60. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  61. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +1 -0
  62. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  63. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  64. codex/static_root/assets/VSelect-DgfIKRnC.js +1 -0
  65. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  66. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  67. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js +1 -0
  68. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  69. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  70. codex/static_root/assets/VSelectionControl-CGu0oz0K.js +1 -0
  71. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  72. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  73. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js +1 -0
  74. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  75. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  76. codex/static_root/assets/VSlideGroup-D_oNvCOd.js +1 -0
  77. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  78. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  79. codex/static_root/assets/{VTable-FKMTcdkF.6c1eed84d25e.js → VTable-C7pKI7gU.087912f706f5.js} +1 -1
  80. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  81. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  82. codex/static_root/assets/{VTable-FKMTcdkF.js → VTable-C7pKI7gU.js} +1 -1
  83. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  84. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  85. codex/static_root/assets/{VTextField-Dftcik85.js → VTextField-Bs8oq9mk.7999fef12a03.js} +1 -1
  86. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  87. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  88. codex/static_root/assets/{VTextField-Dftcik85.cb2f1dda87e1.js → VTextField-Bs8oq9mk.js} +1 -1
  89. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  90. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  91. codex/static_root/assets/{VWindowItem-CYCwnt3s.28025dc36c6a.js → VWindowItem-iyZ1XOkP.409f7d292253.js} +1 -1
  92. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  93. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  94. codex/static_root/assets/{VWindowItem-CYCwnt3s.js → VWindowItem-iyZ1XOkP.js} +1 -1
  95. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  96. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  97. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js +1 -0
  98. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  99. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  100. codex/static_root/assets/admin-B0pCBjma.js +1 -0
  101. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  102. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  103. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js +30 -0
  104. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  105. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  106. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js +30 -0
  107. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  108. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  109. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
  110. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  111. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
  112. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
  113. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  114. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
  115. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +1 -0
  116. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  117. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  118. codex/static_root/assets/browser-DbyAjCNh.js +1 -0
  119. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  120. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  121. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +1 -0
  122. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  123. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  124. codex/static_root/assets/browser-VnzNj1t3.css +1 -0
  125. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  126. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  127. codex/static_root/assets/{change-password-dialog-BIBG6Bk8.3583021c124a.js → change-password-dialog-DZ9dP8_F.930f4cae3cb0.js} +1 -1
  128. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  129. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  130. codex/static_root/assets/{change-password-dialog-BIBG6Bk8.js → change-password-dialog-DZ9dP8_F.js} +1 -1
  131. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  132. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  133. codex/static_root/assets/{confirm-dialog-DARLmXRf.53fc6938fb8c.js → confirm-dialog-RP7JNStF.a752a7c1e697.js} +1 -1
  134. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
  135. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  136. codex/static_root/assets/{confirm-dialog-DARLmXRf.js → confirm-dialog-RP7JNStF.js} +1 -1
  137. codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
  138. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  139. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
  140. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  141. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
  142. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  143. codex/static_root/assets/{datetime-column-LpLgWpkf.16ce3324970f.js → datetime-column-Cu3WYyPj.54b4c3817b74.js} +1 -1
  144. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  145. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  146. codex/static_root/assets/{datetime-column-LpLgWpkf.js → datetime-column-Cu3WYyPj.js} +1 -1
  147. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  148. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  149. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js +1 -0
  150. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  151. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  152. codex/static_root/assets/filter-DAdGUt-1.js +1 -0
  153. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  154. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  155. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +1 -0
  156. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  157. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  158. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +1 -0
  159. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  160. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  161. codex/static_root/assets/{group-tab-MK1lcjUU.98406d5babdc.js → group-tab-DEwh4jfB.a95097b5e320.js} +1 -1
  162. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  163. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  164. codex/static_root/assets/{group-tab-MK1lcjUU.js → group-tab-DEwh4jfB.js} +1 -1
  165. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  166. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  167. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +1 -0
  168. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  169. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  170. codex/static_root/assets/http-error-xs3mIL8U.js +1 -0
  171. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  172. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  173. codex/static_root/assets/{library-tab-DYeEBYlT.a2a2ef088d1c.js → library-tab-DrXvD9B2.f765f9d3bae4.js} +1 -1
  174. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  175. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  176. codex/static_root/assets/{library-tab-DYeEBYlT.js → library-tab-DrXvD9B2.js} +1 -1
  177. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  178. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  179. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +32 -0
  180. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  181. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  182. codex/static_root/assets/main-Dy9Uqpmh.js +32 -0
  183. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  184. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  185. codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
  186. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  187. codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
  188. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
  189. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  190. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
  191. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +1 -0
  192. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  193. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  194. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +1 -0
  195. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  196. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  197. codex/static_root/assets/{pagination-toolbar-ClZui9Zf.697d7ae11c21.css → pagination-toolbar-r6nbtWxg.4088ab042c0c.css} +1 -1
  198. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  199. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  200. codex/static_root/assets/{pagination-toolbar-ClZui9Zf.css → pagination-toolbar-r6nbtWxg.css} +1 -1
  201. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  202. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  203. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js +305 -0
  204. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  205. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  206. codex/static_root/assets/pdf-doc-obrBwxa6.js +305 -0
  207. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  208. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  209. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +2 -0
  210. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  211. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  212. codex/static_root/assets/reader-BXKUaUeC.js +2 -0
  213. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  214. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  215. codex/static_root/assets/{reader-CF3xt6JG.19fad7f62450.css → reader-OXJ1KVaW.a463b19d0508.css} +1 -1
  216. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  217. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  218. codex/static_root/assets/{reader-CF3xt6JG.css → reader-OXJ1KVaW.css} +1 -1
  219. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  220. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  221. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js +1 -0
  222. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  223. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  224. codex/static_root/assets/relation-chips-CZJcLjc8.js +1 -0
  225. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  226. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  227. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js +2 -0
  228. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  229. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  230. codex/static_root/assets/settings-drawer-7iTnAdjf.js +2 -0
  231. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  232. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  233. codex/static_root/assets/{settings-drawer-BJOfMtqA.6471a50fc05b.css → settings-drawer-nZ9HvKSZ.01744e131806.css} +1 -1
  234. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.br +0 -0
  235. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.gz +0 -0
  236. codex/static_root/assets/{settings-drawer-BJOfMtqA.css → settings-drawer-nZ9HvKSZ.css} +1 -1
  237. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.br +0 -0
  238. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.gz +0 -0
  239. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js +1 -0
  240. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  241. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  242. codex/static_root/assets/stats-tab-CQsK1n8O.js +1 -0
  243. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  244. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  245. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +1 -0
  246. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  247. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  248. codex/static_root/assets/task-tab-C60TLP6e.js +1 -0
  249. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  250. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  251. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +1 -0
  252. codex/static_root/assets/to-case-CR9beRR0.js +1 -0
  253. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js +1 -0
  254. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  255. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  256. codex/static_root/assets/unauthorized-Bs7NSqea.js +1 -0
  257. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  258. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  259. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js +1 -0
  260. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  261. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  262. codex/static_root/assets/user-tab-ChuH7Atm.js +1 -0
  263. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  264. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  265. codex/static_root/js/choices.079a01e0be1a.json +1 -0
  266. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  267. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  268. codex/static_root/js/choices.json +1 -1
  269. codex/static_root/js/choices.json.br +0 -0
  270. codex/static_root/js/choices.json.gz +0 -0
  271. codex/static_root/manifest.dad79c8475ae.json +629 -0
  272. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  273. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  274. codex/static_root/manifest.json +265 -278
  275. codex/static_root/manifest.json.br +0 -0
  276. codex/static_root/manifest.json.gz +0 -0
  277. codex/static_root/staticfiles.json +1 -1
  278. codex/urls/api/auth.py +2 -1
  279. codex/urls/api/browser.py +2 -2
  280. codex/urls/api/reader.py +2 -2
  281. codex/urls/api/v3.py +2 -0
  282. codex/util.py +10 -0
  283. codex/views/admin/api_key.py +2 -6
  284. codex/views/admin/auth.py +30 -0
  285. codex/views/admin/flag.py +2 -7
  286. codex/views/admin/group.py +2 -6
  287. codex/views/admin/library.py +7 -20
  288. codex/views/admin/stats.py +7 -66
  289. codex/views/admin/tasks.py +12 -11
  290. codex/views/admin/user.py +3 -9
  291. codex/views/auth.py +31 -52
  292. codex/views/bookmark.py +7 -11
  293. codex/views/browser/{browser_annotations.py → annotations.py} +119 -78
  294. codex/views/browser/base.py +24 -72
  295. codex/views/browser/breadcrumbs.py +200 -0
  296. codex/views/browser/browser.py +64 -344
  297. codex/views/browser/choices.py +25 -33
  298. codex/views/browser/filters/annotations.py +2 -2
  299. codex/views/browser/filters/bookmark.py +9 -13
  300. codex/views/browser/filters/field.py +9 -6
  301. codex/views/browser/filters/group.py +8 -5
  302. codex/views/browser/filters/search.py +11 -8
  303. codex/views/browser/metadata.py +3 -7
  304. codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
  305. codex/views/browser/paginate.py +106 -0
  306. codex/views/browser/{session.py → settings.py} +4 -3
  307. codex/views/browser/title.py +48 -0
  308. codex/views/browser/validate.py +158 -0
  309. codex/views/const.py +97 -0
  310. codex/views/cover.py +4 -6
  311. codex/views/download.py +5 -8
  312. codex/views/frontend.py +4 -2
  313. codex/views/mtime.py +70 -0
  314. codex/views/opds/auth.py +17 -0
  315. codex/views/opds/authentication_v1.py +46 -45
  316. codex/views/opds/binary.py +4 -9
  317. codex/views/opds/const.py +13 -0
  318. codex/views/opds/urls.py +5 -4
  319. codex/views/opds/util.py +2 -8
  320. codex/views/opds/v1/facets.py +6 -7
  321. codex/views/opds/v1/feed.py +10 -17
  322. codex/views/opds/v1/links.py +6 -5
  323. codex/views/opds/v1/opensearch_v1.py +2 -6
  324. codex/views/opds/v2/feed.py +12 -15
  325. codex/views/opds/v2/links.py +3 -2
  326. codex/views/opds/v2/publications.py +21 -20
  327. codex/views/opds/v2/top_links.py +2 -2
  328. codex/views/public.py +38 -0
  329. codex/views/reader/arcs.py +101 -0
  330. codex/views/reader/books.py +199 -0
  331. codex/views/reader/page.py +3 -7
  332. codex/views/reader/reader.py +53 -298
  333. codex/views/reader/{session.py → settings.py} +4 -2
  334. codex/views/session.py +74 -102
  335. codex/views/settings.py +34 -0
  336. codex/views/template.py +3 -3
  337. codex/views/util.py +31 -0
  338. codex/views/utils.py +26 -0
  339. codex/views/version.py +2 -2
  340. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/METADATA +12 -8
  341. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/RECORD +344 -330
  342. codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js +0 -1
  343. codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.br +0 -0
  344. codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.gz +0 -0
  345. codex/static_root/assets/VCheckbox-BF_99tgB.js +0 -1
  346. codex/static_root/assets/VCheckbox-BF_99tgB.js.br +0 -0
  347. codex/static_root/assets/VCheckbox-BF_99tgB.js.gz +0 -0
  348. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js +0 -1
  349. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.br +0 -0
  350. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.gz +0 -0
  351. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js +0 -1
  352. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.br +0 -0
  353. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.gz +0 -0
  354. codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js +0 -1
  355. codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.br +0 -0
  356. codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.gz +0 -0
  357. codex/static_root/assets/VCombobox-CTRWnOuM.js +0 -1
  358. codex/static_root/assets/VCombobox-CTRWnOuM.js.br +0 -0
  359. codex/static_root/assets/VCombobox-CTRWnOuM.js.gz +0 -0
  360. codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js +0 -1
  361. codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.br +0 -0
  362. codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.gz +0 -0
  363. codex/static_root/assets/VDialog-pzwzInEh.js +0 -1
  364. codex/static_root/assets/VDialog-pzwzInEh.js.br +0 -0
  365. codex/static_root/assets/VDialog-pzwzInEh.js.gz +0 -0
  366. codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js +0 -1
  367. codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.br +0 -0
  368. codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.gz +0 -0
  369. codex/static_root/assets/VExpansionPanels-CKSq1GpG.js +0 -1
  370. codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.br +0 -0
  371. codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.gz +0 -0
  372. codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js +0 -1
  373. codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.br +0 -0
  374. codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.gz +0 -0
  375. codex/static_root/assets/VRadioGroup-CW5GobGU.js +0 -1
  376. codex/static_root/assets/VRadioGroup-CW5GobGU.js.br +0 -0
  377. codex/static_root/assets/VRadioGroup-CW5GobGU.js.gz +0 -0
  378. codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js +0 -1
  379. codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.br +0 -0
  380. codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.gz +0 -0
  381. codex/static_root/assets/VSelect-ChOlLRyH.js +0 -1
  382. codex/static_root/assets/VSelect-ChOlLRyH.js.br +0 -0
  383. codex/static_root/assets/VSelect-ChOlLRyH.js.gz +0 -0
  384. codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js +0 -1
  385. codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.br +0 -0
  386. codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.gz +0 -0
  387. codex/static_root/assets/VSelectionControl-DKOhc2zB.js +0 -1
  388. codex/static_root/assets/VSelectionControl-DKOhc2zB.js.br +0 -0
  389. codex/static_root/assets/VSelectionControl-DKOhc2zB.js.gz +0 -0
  390. codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js +0 -1
  391. codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.br +0 -0
  392. codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.gz +0 -0
  393. codex/static_root/assets/VSlideGroup-zFz5_byd.js +0 -1
  394. codex/static_root/assets/VSlideGroup-zFz5_byd.js.br +0 -0
  395. codex/static_root/assets/VSlideGroup-zFz5_byd.js.gz +0 -0
  396. codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.br +0 -0
  397. codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.gz +0 -0
  398. codex/static_root/assets/VTable-FKMTcdkF.js.br +0 -0
  399. codex/static_root/assets/VTable-FKMTcdkF.js.gz +0 -0
  400. codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.br +0 -0
  401. codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.gz +0 -0
  402. codex/static_root/assets/VTextField-Dftcik85.js.br +0 -0
  403. codex/static_root/assets/VTextField-Dftcik85.js.gz +0 -0
  404. codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.br +0 -0
  405. codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.gz +0 -0
  406. codex/static_root/assets/VWindowItem-CYCwnt3s.js.br +0 -0
  407. codex/static_root/assets/VWindowItem-CYCwnt3s.js.gz +0 -0
  408. codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js +0 -1
  409. codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.br +0 -0
  410. codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.gz +0 -0
  411. codex/static_root/assets/admin-DH_DrB0F.js +0 -1
  412. codex/static_root/assets/admin-DH_DrB0F.js.br +0 -0
  413. codex/static_root/assets/admin-DH_DrB0F.js.gz +0 -0
  414. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js +0 -30
  415. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.br +0 -0
  416. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.gz +0 -0
  417. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js +0 -30
  418. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.br +0 -0
  419. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.gz +0 -0
  420. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
  421. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
  422. codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css +0 -1
  423. codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.br +0 -0
  424. codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.gz +0 -0
  425. codex/static_root/assets/browser-CpppBvim.css +0 -1
  426. codex/static_root/assets/browser-CpppBvim.css.br +0 -0
  427. codex/static_root/assets/browser-CpppBvim.css.gz +0 -0
  428. codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js +0 -1
  429. codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.br +0 -0
  430. codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.gz +0 -0
  431. codex/static_root/assets/browser-lKU7rqLd.js +0 -1
  432. codex/static_root/assets/browser-lKU7rqLd.js.br +0 -0
  433. codex/static_root/assets/browser-lKU7rqLd.js.gz +0 -0
  434. codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.br +0 -0
  435. codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.gz +0 -0
  436. codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.br +0 -0
  437. codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.gz +0 -0
  438. codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.br +0 -0
  439. codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.gz +0 -0
  440. codex/static_root/assets/confirm-dialog-DARLmXRf.js.br +0 -0
  441. codex/static_root/assets/confirm-dialog-DARLmXRf.js.gz +0 -0
  442. codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.br +0 -0
  443. codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.gz +0 -0
  444. codex/static_root/assets/datetime-column-LpLgWpkf.js.br +0 -0
  445. codex/static_root/assets/datetime-column-LpLgWpkf.js.gz +0 -0
  446. codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js +0 -1
  447. codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.br +0 -0
  448. codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.gz +0 -0
  449. codex/static_root/assets/filter-jRB1Ob6r.js +0 -1
  450. codex/static_root/assets/filter-jRB1Ob6r.js.br +0 -0
  451. codex/static_root/assets/filter-jRB1Ob6r.js.gz +0 -0
  452. codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js +0 -1
  453. codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.br +0 -0
  454. codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.gz +0 -0
  455. codex/static_root/assets/flag-tab-mYS2wg5e.js +0 -1
  456. codex/static_root/assets/flag-tab-mYS2wg5e.js.br +0 -0
  457. codex/static_root/assets/flag-tab-mYS2wg5e.js.gz +0 -0
  458. codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.br +0 -0
  459. codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.gz +0 -0
  460. codex/static_root/assets/group-tab-MK1lcjUU.js.br +0 -0
  461. codex/static_root/assets/group-tab-MK1lcjUU.js.gz +0 -0
  462. codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js +0 -1
  463. codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.br +0 -0
  464. codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.gz +0 -0
  465. codex/static_root/assets/http-error-HFeYnF0n.js +0 -1
  466. codex/static_root/assets/http-error-HFeYnF0n.js.br +0 -0
  467. codex/static_root/assets/http-error-HFeYnF0n.js.gz +0 -0
  468. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
  469. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  470. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  471. codex/static_root/assets/index-DUjeNg4x.js +0 -1
  472. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  473. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  474. codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.br +0 -0
  475. codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.gz +0 -0
  476. codex/static_root/assets/library-tab-DYeEBYlT.js.br +0 -0
  477. codex/static_root/assets/library-tab-DYeEBYlT.js.gz +0 -0
  478. codex/static_root/assets/main-gSW7TFDY.329223b148d4.js +0 -32
  479. codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.br +0 -0
  480. codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.gz +0 -0
  481. codex/static_root/assets/main-gSW7TFDY.js +0 -32
  482. codex/static_root/assets/main-gSW7TFDY.js.br +0 -0
  483. codex/static_root/assets/main-gSW7TFDY.js.gz +0 -0
  484. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  485. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  486. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
  487. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
  488. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
  489. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
  490. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js +0 -1
  491. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.br +0 -0
  492. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.gz +0 -0
  493. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js +0 -1
  494. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.br +0 -0
  495. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.gz +0 -0
  496. codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js +0 -35
  497. codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.br +0 -0
  498. codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.gz +0 -0
  499. codex/static_root/assets/pdf-doc-BTdS9hUz.js +0 -35
  500. codex/static_root/assets/pdf-doc-BTdS9hUz.js.br +0 -0
  501. codex/static_root/assets/pdf-doc-BTdS9hUz.js.gz +0 -0
  502. codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js +0 -2
  503. codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.br +0 -0
  504. codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.gz +0 -0
  505. codex/static_root/assets/reader-BtSTIETq.js +0 -2
  506. codex/static_root/assets/reader-BtSTIETq.js.br +0 -0
  507. codex/static_root/assets/reader-BtSTIETq.js.gz +0 -0
  508. codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.br +0 -0
  509. codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.gz +0 -0
  510. codex/static_root/assets/reader-CF3xt6JG.css.br +0 -0
  511. codex/static_root/assets/reader-CF3xt6JG.css.gz +0 -0
  512. codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js +0 -1
  513. codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.br +0 -0
  514. codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.gz +0 -0
  515. codex/static_root/assets/relation-chips-C4Ob7ySi.js +0 -1
  516. codex/static_root/assets/relation-chips-C4Ob7ySi.js.br +0 -0
  517. codex/static_root/assets/relation-chips-C4Ob7ySi.js.gz +0 -0
  518. codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.br +0 -0
  519. codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.gz +0 -0
  520. codex/static_root/assets/settings-drawer-BJOfMtqA.css.br +0 -0
  521. codex/static_root/assets/settings-drawer-BJOfMtqA.css.gz +0 -0
  522. codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js +0 -2
  523. codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.br +0 -0
  524. codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.gz +0 -0
  525. codex/static_root/assets/settings-drawer-xSctoxZn.js +0 -2
  526. codex/static_root/assets/settings-drawer-xSctoxZn.js.br +0 -0
  527. codex/static_root/assets/settings-drawer-xSctoxZn.js.gz +0 -0
  528. codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js +0 -1
  529. codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.br +0 -0
  530. codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.gz +0 -0
  531. codex/static_root/assets/stats-tab-C_uC_U7o.js +0 -1
  532. codex/static_root/assets/stats-tab-C_uC_U7o.js.br +0 -0
  533. codex/static_root/assets/stats-tab-C_uC_U7o.js.gz +0 -0
  534. codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js +0 -1
  535. codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.br +0 -0
  536. codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.gz +0 -0
  537. codex/static_root/assets/task-tab-VIpy1Ula.js +0 -1
  538. codex/static_root/assets/task-tab-VIpy1Ula.js.br +0 -0
  539. codex/static_root/assets/task-tab-VIpy1Ula.js.gz +0 -0
  540. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
  541. codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
  542. codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js +0 -1
  543. codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.br +0 -0
  544. codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.gz +0 -0
  545. codex/static_root/assets/unauthorized-kEuFL5br.js +0 -1
  546. codex/static_root/assets/unauthorized-kEuFL5br.js.br +0 -0
  547. codex/static_root/assets/unauthorized-kEuFL5br.js.gz +0 -0
  548. codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js +0 -1
  549. codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.br +0 -0
  550. codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.gz +0 -0
  551. codex/static_root/assets/user-tab-CZNFTTEI.js +0 -1
  552. codex/static_root/assets/user-tab-CZNFTTEI.js.br +0 -0
  553. codex/static_root/assets/user-tab-CZNFTTEI.js.gz +0 -0
  554. codex/static_root/js/choices.23fedac2b9ba.json +0 -1
  555. codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
  556. codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
  557. codex/static_root/manifest.6ef112683dae.json +0 -642
  558. codex/static_root/manifest.6ef112683dae.json.br +0 -0
  559. codex/static_root/manifest.6ef112683dae.json.gz +0 -0
  560. codex/views/browser/browser_breadcrumbs.py +0 -175
  561. codex/views/browser/const.py +0 -3
  562. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/LICENSE +0 -0
  563. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/WHEEL +0 -0
  564. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/entry_points.txt +0 -0
codex/exceptions.py CHANGED
@@ -30,6 +30,7 @@ class SeeOtherRedirectError(APIException):
30
30
  route: dict = detail.get("route", {}) # type: ignore
31
31
  params: dict = route.get("params", DEFAULTS["breadcrumbs"][0])
32
32
  # Save route in server format for redirect reverse
33
+ params.pop("name", None)
33
34
  self.route_kwargs = params
34
35
  # Serialize route for detail
35
36
  serializer = RouteSerializer(params)
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from codex.librarian.covers.path import CoverPathMixin
8
8
  from codex.librarian.covers.status import CoverStatusTypes
9
9
  from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
10
- from codex.models import Comic, Timestamp
10
+ from codex.models import Comic
11
11
  from codex.models.paths import CustomCover
12
12
  from codex.status import Status
13
13
 
@@ -69,7 +69,6 @@ class CoverPurgeMixin(CoverPathMixin):
69
69
  self.log.info("Removed entire custom cover cache.")
70
70
  except Exception as exc:
71
71
  self.log.warning(exc)
72
- Timestamp.touch(Timestamp.TimestampChoices.COVERS)
73
72
  librarian_queue.put(LIBRARY_CHANGED_TASK)
74
73
 
75
74
  def _cleanup_orphan_covers(self, cover_class, cover_root, name):
@@ -28,6 +28,7 @@ from codex.librarian.importer.status import ImportStatusTypes, status_notify
28
28
  from codex.models import Imprint, Publisher, Series, Volume
29
29
  from codex.models.admin import AdminFlag
30
30
  from codex.status import Status
31
+ from codex.util import max_none
31
32
 
32
33
 
33
34
  class AggregateMetadataMixin(CleanMetadataMixin):
@@ -182,22 +183,13 @@ class AggregateMetadataMixin(CleanMetadataMixin):
182
183
  all_fks[field] = set()
183
184
  all_fks[field].add(name)
184
185
 
185
- @staticmethod
186
- def _none_max(a, b):
187
- """None aware math.max."""
188
- if a is not None and b is not None:
189
- return max(a, b)
190
- if a is None:
191
- return b
192
- return a
193
-
194
186
  @classmethod
195
187
  def _set_max_group_count(cls, common_args, group_class, index, count_key):
196
188
  """Assign the maximum group count number."""
197
189
  all_fks, group_tree, group_md = common_args
198
190
  group_name = group_tree[0:index]
199
191
  try:
200
- count = cls._none_max(
192
+ count = max_none(
201
193
  all_fks[GROUP_TREES][Series].get(group_name),
202
194
  group_md.get(count_key),
203
195
  )
@@ -0,0 +1,47 @@
1
+ """Update Groups timestamp for cover cache busting."""
2
+
3
+ from django.db.models.functions import Now
4
+ from django.db.models.query import Q
5
+
6
+ from codex.models import StoryArc
7
+
8
+
9
+ class CacheMixin:
10
+ """Update Groups timestamp for cover cache busting."""
11
+
12
+ def update_groups_with_changed_comics(self, start_time, deleted_comic_groups):
13
+ """Update timestamps for each group for cover cache busting."""
14
+ total_count = 0
15
+ now = Now()
16
+
17
+ log_list = []
18
+ for model, pks in deleted_comic_groups.items():
19
+ rel = "story_arc_number__" if model == StoryArc else ""
20
+ updated_at_rel = rel + "comic__updated_at__gt"
21
+ updated_filter = {updated_at_rel: start_time}
22
+ model_qs = (
23
+ model.objects.filter(Q(**updated_filter) | Q(pk__in=pks))
24
+ .distinct()
25
+ .only("updated_at")
26
+ )
27
+
28
+ updated = []
29
+ for obj in model_qs:
30
+ obj.updated_at = now
31
+ updated.append(obj)
32
+
33
+ count = len(updated)
34
+ if count:
35
+ model.objects.bulk_update(updated, ["updated_at"])
36
+ log_list.append(f"{count} {model.__name__}s.")
37
+ total_count += count
38
+ if total_count:
39
+ groups_log = ", ".join(log_list)
40
+ self.log.debug( # type: ignore
41
+ f"Updated {groups_log} timestamps for browser cache busting."
42
+ )
43
+ self.log.info( # type: ignore
44
+ f"Updated {total_count} group timestamps for browser cache busting."
45
+ )
46
+
47
+ return total_count
@@ -219,3 +219,11 @@ COMIC_M2M_FIELD_NAMES = frozenset(
219
219
  for field in Comic._meta.get_fields()
220
220
  if field.many_to_many and field.name != "folders"
221
221
  )
222
+ COMIC_GROUP_FIELD_NAMES = (
223
+ "publisher",
224
+ "imprint",
225
+ "series",
226
+ "volume",
227
+ "story_arc_numbers",
228
+ "folders",
229
+ )
@@ -1,9 +1,11 @@
1
1
  """Clean up the database after moves or imports."""
2
2
 
3
3
  from codex.librarian.covers.tasks import CoverRemoveTask
4
+ from codex.librarian.importer.const import COMIC_GROUP_FIELD_NAMES
4
5
  from codex.librarian.importer.status import ImportStatusTypes, status_notify
5
- from codex.models import Comic, Folder
6
+ from codex.models import Comic, Folder, StoryArc
6
7
  from codex.models.paths import CustomCover
8
+ from codex.settings.settings import MAX_CHUNK_SIZE
7
9
  from codex.threads import QueuedThread
8
10
 
9
11
 
@@ -37,14 +39,47 @@ class DeletedMixin(QueuedThread):
37
39
  )
38
40
  return count
39
41
 
42
+ @staticmethod
43
+ def _populate_deleted_comic_groups(delete_qs, deleted_comic_groups):
44
+ """Populate changed groups for cover timestamp updater."""
45
+ comics_deleted_qs = delete_qs.only(*COMIC_GROUP_FIELD_NAMES).prefetch_related(
46
+ "story_arc_numbers__story_arc"
47
+ )
48
+ for comic in comics_deleted_qs.iterator(chunk_size=MAX_CHUNK_SIZE):
49
+ for field_name in COMIC_GROUP_FIELD_NAMES:
50
+ if field_name == "story_arc_numbers":
51
+ for san in comic.story_arc_numbers.select_related("story_arc").only(
52
+ "story_arc"
53
+ ):
54
+ deleted_comic_groups[StoryArc].add(san.story_arc.pk)
55
+ elif field_name == "folders":
56
+ for folder in comic.folders.only("pk"):
57
+ deleted_comic_groups[Folder].add(folder.pk)
58
+ else:
59
+ related_model = comic._meta.get_field(field_name).related_model
60
+ related_id = getattr(comic, field_name).pk
61
+ deleted_comic_groups[related_model].add(related_id)
62
+
63
+ @staticmethod
64
+ def _init_deleted_comic_groups(deleted_comic_groups):
65
+ """Init deleted_comic_groups, used later even if no deletes."""
66
+ for field_name in COMIC_GROUP_FIELD_NAMES:
67
+ related_model = Comic._meta.get_field(field_name).related_model
68
+ deleted_comic_groups[related_model] = set()
69
+
40
70
  @status_notify(status_type=ImportStatusTypes.FILES_DELETED, updates=False)
41
- def _bulk_comics_deleted(self, delete_comic_paths, library, **kwargs):
71
+ def _bulk_comics_deleted(
72
+ self, delete_comic_paths, library, deleted_comic_groups, **kwargs
73
+ ):
42
74
  """Bulk delete comics found missing from the filesystem."""
43
75
  if not delete_comic_paths:
44
76
  return 0
45
- comics = Comic.objects.filter(library=library, path__in=delete_comic_paths)
46
- delete_comic_pks = frozenset(comics.values_list("pk", flat=True))
47
- comics.delete()
77
+ delete_qs = Comic.objects.filter(library=library, path__in=delete_comic_paths)
78
+
79
+ self._populate_deleted_comic_groups(delete_qs, deleted_comic_groups)
80
+
81
+ delete_comic_pks = frozenset(delete_qs.values_list("pk", flat=True))
82
+ delete_qs.delete()
48
83
 
49
84
  self._remove_covers(delete_comic_pks)
50
85
 
@@ -73,12 +108,15 @@ class DeletedMixin(QueuedThread):
73
108
 
74
109
  return count
75
110
 
76
- def delete(self, library, task):
111
+ def delete(self, library, task, deleted_comic_groups):
77
112
  """Delete files and folders."""
78
113
  count = self._bulk_folders_deleted(task.dirs_deleted, library)
79
114
  task.dirs_deleted = None
80
115
 
81
- count += self._bulk_comics_deleted(task.files_deleted, library)
116
+ self._init_deleted_comic_groups(deleted_comic_groups)
117
+ count += self._bulk_comics_deleted(
118
+ task.files_deleted, library, deleted_comic_groups
119
+ )
82
120
  task.files_deleted = None
83
121
 
84
122
  count += self._bulk_covers_deleted(task.covers_deleted, library)
@@ -5,9 +5,11 @@ from pathlib import Path
5
5
  from time import sleep, time
6
6
 
7
7
  from django.core.cache import cache
8
+ from django.utils.timezone import now
8
9
  from humanize import naturaldelta
9
10
 
10
11
  from codex.librarian.importer.aggregate_metadata import AggregateMetadataMixin
12
+ from codex.librarian.importer.cache import CacheMixin
11
13
  from codex.librarian.importer.const import FIS, FKS, M2M_MDS, MDS
12
14
  from codex.librarian.importer.deleted import DeletedMixin
13
15
  from codex.librarian.importer.failed_imports import FailedImportsMixin
@@ -35,6 +37,7 @@ class ComicImporterThread(
35
37
  UpdateComicsMixin,
36
38
  FailedImportsMixin,
37
39
  MovedMixin,
40
+ CacheMixin,
38
41
  ):
39
42
  """A worker to handle all bulk database updates."""
40
43
 
@@ -322,6 +325,7 @@ class ComicImporterThread(
322
325
  library = Library.objects.get(pk=task.library_id)
323
326
  try:
324
327
  self._init_apply(library, task)
328
+ import_start_time = now()
325
329
 
326
330
  changed: int = 0
327
331
  link_cover_pks: set[int] = set()
@@ -379,7 +383,13 @@ class ComicImporterThread(
379
383
  library, fis, bool(task.files_deleted)
380
384
  )
381
385
 
382
- changed += self.delete(library, task)
386
+ deleted_comic_groups = {}
387
+ changed += self.delete(library, task, deleted_comic_groups)
388
+
389
+ self.update_groups_with_changed_comics(
390
+ import_start_time, deleted_comic_groups
391
+ )
392
+
383
393
  if changed:
384
394
  cache.clear()
385
395
  finally:
@@ -11,7 +11,6 @@ from codex.librarian.importer.link_comics import LinkComicsMixin
11
11
  from codex.librarian.importer.status import ImportStatusTypes, status_notify
12
12
  from codex.models import (
13
13
  Comic,
14
- Timestamp,
15
14
  )
16
15
 
17
16
 
@@ -74,7 +73,6 @@ class UpdateComicsMixin(LinkComicsMixin):
74
73
  self.log.debug(f"Bulk updating {len(update_comics)} comics.")
75
74
  try:
76
75
  Comic.objects.bulk_update(update_comics, BULK_UPDATE_COMIC_FIELDS)
77
- Timestamp.touch(Timestamp.TimestampChoices.COVERS)
78
76
  count = len(update_comics)
79
77
 
80
78
  self._remove_covers(comic_pks, False) # type: ignore
@@ -76,7 +76,7 @@ class Janitor(CleanupMixin, UpdateMixin, VacuumMixin, UpdateFailedImportsMixin):
76
76
  except Exception:
77
77
  self.log.exception(f"In {self.__class__.__name__}")
78
78
 
79
- def run(self, task):
79
+ def run(self, task): # noqa: PLR0912,C901
80
80
  """Run Janitor tasks as the librarian process directly."""
81
81
  try:
82
82
  match task:
@@ -77,7 +77,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
77
77
  self.queue.put(task)
78
78
  self.search_indexer_abort_event = Manager().Event()
79
79
 
80
- def _process_task(self, task):
80
+ def _process_task(self, task): # noqa: PLR0912,C901
81
81
  """Process an individual task popped off the queue."""
82
82
  match task:
83
83
  case CoverTask():
@@ -0,0 +1,44 @@
1
+ """Generated by Django 5.0.6 on 2024-05-25 21:19."""
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ """Migrate db."""
8
+
9
+ dependencies = [
10
+ ("codex", "0028_custom_covers"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="adminflag",
16
+ name="key",
17
+ field=models.CharField(
18
+ choices=[
19
+ ("FV", "Folder View"),
20
+ ("RG", "Registration"),
21
+ ("NU", "Non Users"),
22
+ ("AU", "Auto Update"),
23
+ ("SO", "Search Index Optimize"),
24
+ ("IM", "Import Metadata"),
25
+ ],
26
+ db_index=True,
27
+ max_length=2,
28
+ ),
29
+ ),
30
+ migrations.AlterField(
31
+ model_name="timestamp",
32
+ name="key",
33
+ field=models.CharField(
34
+ choices=[
35
+ ("JA", "Janitor"),
36
+ ("VR", "Codex Version"),
37
+ ("SI", "Search Index UUID"),
38
+ ("AP", "API Key"),
39
+ ],
40
+ db_index=True,
41
+ max_length=2,
42
+ ),
43
+ ),
44
+ ]
codex/models/admin.py CHANGED
@@ -82,7 +82,6 @@ class Timestamp(BaseModel):
82
82
  class TimestampChoices(TextChoices):
83
83
  """Choices for Timestamps."""
84
84
 
85
- COVERS = "CV", _("Covers")
86
85
  JANITOR = "JA", _("Janitor")
87
86
  CODEX_VERSION = "VR", _("Codex Version")
88
87
  SEARCH_INDEX_UUID = "SI", _("Search Index UUID")
codex/permissions.py CHANGED
@@ -8,7 +8,7 @@ from codex.models import Timestamp
8
8
  class HasAPIKeyOrIsAdminUser(BasePermission):
9
9
  """Does the request have the current api key."""
10
10
 
11
- def has_permission(self, request, view):
11
+ def has_permission(self, request, view): # type: ignore
12
12
  """Test the request api key against the database."""
13
13
  data = request.GET if request.method == "GET" else request.POST
14
14
  api_key = data.get("apiKey")
@@ -173,8 +173,7 @@ class AdminFolderSerializer(Serializer):
173
173
  def validate_show_hidden(self, show_hidden):
174
174
  """Snakecase the showHidden field."""
175
175
  return (
176
- show_hidden == "true"
177
- or self.initial_data.get("showHidden", False) == "true"
176
+ show_hidden == "true" or self.initial_data.get("showHidden") == "true" # type: ignore
178
177
  )
179
178
 
180
179
 
@@ -1,23 +1,19 @@
1
1
  """Browser Settings Filter Serializers."""
2
2
 
3
- from abc import ABC
4
-
5
- from django.core.exceptions import ValidationError
6
- from django.utils.translation import gettext_lazy as _
7
- from rest_framework.fields import Field
8
3
  from rest_framework.serializers import (
9
4
  BooleanField,
10
- CharField,
11
5
  ChoiceField,
12
- FloatField,
13
- IntegerField,
14
- ListField,
15
6
  Serializer,
16
7
  )
17
8
 
18
- from codex.serializers.choices import CHOICES, VUETIFY_NULL_CODE
19
-
20
- VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
9
+ from codex.serializers.choices import CHOICES
10
+ from codex.serializers.fields import (
11
+ BooleanListField,
12
+ CharListField,
13
+ DecadeListField,
14
+ FloatListField,
15
+ IntListField,
16
+ )
21
17
 
22
18
 
23
19
  class BrowserFilterChoicesSerializer(Serializer):
@@ -47,95 +43,6 @@ class BrowserFilterChoicesSerializer(Serializer):
47
43
  year = BooleanField(read_only=True)
48
44
 
49
45
 
50
- def validate_decades(decades):
51
- """Validate decades."""
52
- if not decades:
53
- return
54
- # * We don't need a whole db call just to be perfectly accurate
55
- # * -1s are decoded back into None before validation
56
- exists = False
57
- for decade in decades:
58
- if decade is None or decade % 10 == 0:
59
- exists = True
60
- break
61
- if not exists:
62
- raise ValidationError(_(f"Invalid decade in: {decades}"))
63
-
64
-
65
- def validate_int_null(values):
66
- """Use a special code for null.
67
-
68
- Because if a vuetify component has a null key it changes it to the
69
- array index.
70
- """
71
- for index, value in enumerate(values):
72
- if value == VUETIFY_NULL_CODE:
73
- values[index] = None
74
- return values
75
-
76
-
77
- def validate_str_null(values):
78
- """Use a special code for null.
79
-
80
- Because if a vuetify component has a null key it changes it to the
81
- array index. This is the version for CharFields.
82
- """
83
- for index, value in enumerate(values):
84
- if value == VUETIFY_NULL_CODE_STR:
85
- values[index] = None
86
- return values
87
-
88
-
89
- class FilterListField(ListField, ABC):
90
- """Filter List field with custom arguments."""
91
-
92
- CHILD_CLASS = Field
93
- VALIDATORS = ()
94
-
95
- def __init__(self, *args, **kwargs):
96
- """Apply the subclass's arguments."""
97
- super().__init__(
98
- *args,
99
- child=self.CHILD_CLASS(allow_null=True, **kwargs),
100
- required=False,
101
- validators=self.VALIDATORS,
102
- )
103
-
104
-
105
- class FloatListField(FilterListField):
106
- """Decimal List Field with validation."""
107
-
108
- CHILD_CLASS = FloatField # type: ignore
109
- VALIDATORS = (validate_int_null,)
110
-
111
-
112
- class IntListField(FilterListField):
113
- """Integer List Field with validation."""
114
-
115
- CHILD_CLASS = IntegerField # type: ignore
116
- VALIDATORS = (validate_int_null,)
117
-
118
-
119
- class DecadeListField(IntListField):
120
- """Integer List Field with validation for decades."""
121
-
122
- VALIDATORS = (validate_int_null, validate_decades)
123
-
124
-
125
- class CharListField(FilterListField):
126
- """Char List Field with validation."""
127
-
128
- CHILD_CLASS = CharField # type: ignore
129
- VALIDATORS = (validate_str_null,)
130
-
131
-
132
- class BooleanListField(FilterListField):
133
- """Bool List Field with validation."""
134
-
135
- CHILD_CLASS = BooleanField # type: ignore
136
- VALIDATORS = (validate_int_null,)
137
-
138
-
139
46
  class BrowserSettingsFilterSerializer(Serializer):
140
47
  """Filter values for settings."""
141
48
 
@@ -10,6 +10,15 @@ from rest_framework.serializers import (
10
10
  )
11
11
 
12
12
  from codex.serializers.browser.filters import IntListField
13
+ from codex.serializers.fields import TimestampField
14
+
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)
13
22
 
14
23
 
15
24
  class BrowserAggregateSerializerMixin(Serializer):
@@ -20,9 +29,8 @@ class BrowserAggregateSerializerMixin(Serializer):
20
29
 
21
30
  # Aggregate Annotations
22
31
  child_count = IntegerField(read_only=True)
23
- cover_pk = IntegerField(read_only=True)
24
- cover_mtime = DateTimeField(format="%s", read_only=True)
25
- cover_custom = BooleanField(read_only=True)
32
+ mtime = TimestampField(read_only=True)
33
+ cover = BrowserCover(read_only=True)
26
34
 
27
35
  # Bookmark annotations
28
36
  page = IntegerField(read_only=True)
@@ -31,3 +39,12 @@ class BrowserAggregateSerializerMixin(Serializer):
31
39
  progress = DecimalField(
32
40
  max_digits=5, decimal_places=2, read_only=True, coerce_to_string=False
33
41
  )
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)
@@ -3,7 +3,6 @@
3
3
  from rest_framework.serializers import (
4
4
  BooleanField,
5
5
  CharField,
6
- DateTimeField,
7
6
  DecimalField,
8
7
  IntegerField,
9
8
  ListSerializer,
@@ -13,6 +12,7 @@ from rest_framework.serializers import (
13
12
  from codex.serializers.browser.mixins import (
14
13
  BrowserAggregateSerializerMixin,
15
14
  )
15
+ from codex.serializers.fields import TimestampField
16
16
  from codex.serializers.route import RouteSerializer
17
17
 
18
18
 
@@ -34,7 +34,6 @@ class BrowserCardSerializer(BrowserAggregateSerializerMixin):
34
34
  order_value = CharField(read_only=True)
35
35
  page_count = IntegerField(read_only=True)
36
36
  reading_direction = CharField(read_only=True)
37
- mtime = DateTimeField(format="%s", read_only=True)
38
37
 
39
38
 
40
39
  class BrowserAdminFlagsSerializer(Serializer):
@@ -57,10 +56,10 @@ class BrowserPageSerializer(Serializer):
57
56
  admin_flags = BrowserAdminFlagsSerializer(read_only=True)
58
57
  breadcrumbs = ListSerializer(child=RouteSerializer())
59
58
  title = BrowserTitleSerializer(read_only=True)
60
- covers_timestamp = IntegerField(read_only=True)
61
59
  zero_pad = IntegerField(read_only=True)
62
60
  libraries_exist = BooleanField(read_only=True)
63
61
  model_group = CharField(read_only=True)
64
62
  num_pages = IntegerField(read_only=True)
65
63
  groups = BrowserCardSerializer(allow_empty=True, read_only=True, many=True)
66
64
  books = BrowserCardSerializer(allow_empty=True, read_only=True, many=True)
65
+ mtime = TimestampField(read_only=True)
@@ -10,6 +10,7 @@ from rest_framework.serializers import (
10
10
 
11
11
  from codex.serializers.browser.filters import BrowserSettingsFilterSerializer
12
12
  from codex.serializers.choices import CHOICES, VUETIFY_NULL_CODE
13
+ from codex.serializers.fields import TimestampField
13
14
 
14
15
  VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
15
16
 
@@ -44,3 +45,4 @@ class BrowserSettingsSerializer(Serializer):
44
45
  top_group = ChoiceField(choices=tuple(CHOICES["topGroup"].keys()), required=False)
45
46
  opds_metadata = BooleanField(required=False)
46
47
  limit = IntegerField(required=False)
48
+ mtime = TimestampField(read_only=True)