codex 1.7.2__py3-none-any.whl → 1.7.3a0__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 (697) hide show
  1. codex/choices.py +13 -5
  2. codex/librarian/bookmark/bookmarkd.py +4 -5
  3. codex/librarian/bookmark/tasks.py +1 -1
  4. codex/librarian/bookmark/update.py +15 -29
  5. codex/librarian/cron/crond.py +1 -1
  6. codex/librarian/importer/query_fks.py +2 -2
  7. codex/librarian/janitor/janitor.py +11 -1
  8. codex/librarian/janitor/log_compress.py +77 -0
  9. codex/librarian/janitor/status.py +1 -0
  10. codex/librarian/janitor/tasks.py +5 -0
  11. codex/librarian/librariand.py +18 -13
  12. codex/librarian/search/update.py +8 -4
  13. codex/migrations/0031_adminflag_banner.py +50 -0
  14. codex/models/admin.py +4 -3
  15. codex/serializers/admin/flags.py +1 -1
  16. codex/serializers/auth.py +1 -0
  17. codex/serializers/opds/v2/__init__.py +1 -0
  18. codex/serializers/opds/v2/facet.py +13 -0
  19. codex/serializers/opds/v2/feed.py +35 -0
  20. codex/serializers/opds/v2/links.py +73 -0
  21. codex/serializers/opds/v2/metadata.py +22 -0
  22. codex/serializers/opds/v2/progression.py +63 -0
  23. codex/serializers/opds/v2/publication.py +78 -0
  24. codex/serializers/opds/{v2_unused.py → v2/unused.py} +1 -1
  25. codex/settings/settings.py +1 -0
  26. codex/static_root/assets/VCheckbox-pHjSEgdV.176f3c1f4191.js +1 -0
  27. codex/static_root/assets/VCheckbox-pHjSEgdV.176f3c1f4191.js.br +0 -0
  28. codex/static_root/assets/VCheckbox-pHjSEgdV.176f3c1f4191.js.gz +0 -0
  29. codex/static_root/assets/VCheckbox-pHjSEgdV.js +1 -0
  30. codex/static_root/assets/VCheckbox-pHjSEgdV.js.br +0 -0
  31. codex/static_root/assets/VCheckbox-pHjSEgdV.js.gz +0 -0
  32. codex/static_root/assets/VCheckboxBtn-DBKqrZGq.467e75859b62.js +1 -0
  33. codex/static_root/assets/VCheckboxBtn-DBKqrZGq.467e75859b62.js.br +0 -0
  34. codex/static_root/assets/VCheckboxBtn-DBKqrZGq.467e75859b62.js.gz +0 -0
  35. codex/static_root/assets/VCheckboxBtn-DBKqrZGq.js +1 -0
  36. codex/static_root/assets/VCheckboxBtn-DBKqrZGq.js.br +0 -0
  37. codex/static_root/assets/VCheckboxBtn-DBKqrZGq.js.gz +0 -0
  38. codex/static_root/assets/VCombobox-Bg7uOTbF.2fc6073b789f.js +1 -0
  39. codex/static_root/assets/VCombobox-Bg7uOTbF.2fc6073b789f.js.br +0 -0
  40. codex/static_root/assets/VCombobox-Bg7uOTbF.2fc6073b789f.js.gz +0 -0
  41. codex/static_root/assets/VCombobox-Bg7uOTbF.js +1 -0
  42. codex/static_root/assets/VCombobox-Bg7uOTbF.js.br +0 -0
  43. codex/static_root/assets/VCombobox-Bg7uOTbF.js.gz +0 -0
  44. codex/static_root/assets/VDialog-Dyd05WHA.b07aca713774.css +1 -0
  45. codex/static_root/assets/VDialog-Dyd05WHA.b07aca713774.css.br +0 -0
  46. codex/static_root/assets/VDialog-Dyd05WHA.b07aca713774.css.gz +0 -0
  47. codex/static_root/assets/VDialog-Dyd05WHA.css +1 -0
  48. codex/static_root/assets/VDialog-Dyd05WHA.css.br +0 -0
  49. codex/static_root/assets/VDialog-Dyd05WHA.css.gz +0 -0
  50. codex/static_root/assets/VDialog-IrbBYFLM.91a058d572c4.js +1 -0
  51. codex/static_root/assets/VDialog-IrbBYFLM.91a058d572c4.js.br +0 -0
  52. codex/static_root/assets/VDialog-IrbBYFLM.91a058d572c4.js.gz +0 -0
  53. codex/static_root/assets/VDialog-IrbBYFLM.js +1 -0
  54. codex/static_root/assets/VDialog-IrbBYFLM.js.br +0 -0
  55. codex/static_root/assets/VDialog-IrbBYFLM.js.gz +0 -0
  56. codex/static_root/assets/VDivider-CuRohbVQ.3c12b040aed7.js +1 -0
  57. codex/static_root/assets/VDivider-CuRohbVQ.3c12b040aed7.js.br +0 -0
  58. codex/static_root/assets/VDivider-CuRohbVQ.3c12b040aed7.js.gz +0 -0
  59. codex/static_root/assets/VDivider-CuRohbVQ.js +1 -0
  60. codex/static_root/assets/VDivider-CuRohbVQ.js.br +0 -0
  61. codex/static_root/assets/VDivider-CuRohbVQ.js.gz +0 -0
  62. codex/static_root/assets/VDivider-DE8MJPGz.3d2e17799e5c.css +1 -0
  63. codex/static_root/assets/VDivider-DE8MJPGz.3d2e17799e5c.css.br +0 -0
  64. codex/static_root/assets/VDivider-DE8MJPGz.3d2e17799e5c.css.gz +0 -0
  65. codex/static_root/assets/VDivider-DE8MJPGz.css +1 -0
  66. codex/static_root/assets/VDivider-DE8MJPGz.css.br +0 -0
  67. codex/static_root/assets/VDivider-DE8MJPGz.css.gz +0 -0
  68. codex/static_root/assets/VExpansionPanels-b0xWLH75.bff6ea8b4055.js +1 -0
  69. codex/static_root/assets/VExpansionPanels-b0xWLH75.bff6ea8b4055.js.br +0 -0
  70. codex/static_root/assets/VExpansionPanels-b0xWLH75.bff6ea8b4055.js.gz +0 -0
  71. codex/static_root/assets/VExpansionPanels-b0xWLH75.js +1 -0
  72. codex/static_root/assets/VExpansionPanels-b0xWLH75.js.br +0 -0
  73. codex/static_root/assets/VExpansionPanels-b0xWLH75.js.gz +0 -0
  74. codex/static_root/assets/VForm-B4hkqnS8.afbd5cf0908a.js +1 -0
  75. codex/static_root/assets/VForm-B4hkqnS8.afbd5cf0908a.js.br +0 -0
  76. codex/static_root/assets/VForm-B4hkqnS8.afbd5cf0908a.js.gz +0 -0
  77. codex/static_root/assets/VForm-B4hkqnS8.js +1 -0
  78. codex/static_root/assets/VForm-B4hkqnS8.js.br +0 -0
  79. codex/static_root/assets/VForm-B4hkqnS8.js.gz +0 -0
  80. codex/static_root/assets/VForm-v1Gmu6E1.138c0b89b6fc.css +1 -0
  81. codex/static_root/assets/VForm-v1Gmu6E1.138c0b89b6fc.css.br +0 -0
  82. codex/static_root/assets/VForm-v1Gmu6E1.138c0b89b6fc.css.gz +0 -0
  83. codex/static_root/assets/VForm-v1Gmu6E1.css +1 -0
  84. codex/static_root/assets/VForm-v1Gmu6E1.css.br +0 -0
  85. codex/static_root/assets/VForm-v1Gmu6E1.css.gz +0 -0
  86. codex/static_root/assets/VRadioGroup-Cbayik6d.034f0f275265.js +1 -0
  87. codex/static_root/assets/VRadioGroup-Cbayik6d.034f0f275265.js.br +0 -0
  88. codex/static_root/assets/VRadioGroup-Cbayik6d.034f0f275265.js.gz +0 -0
  89. codex/static_root/assets/VRadioGroup-Cbayik6d.js +1 -0
  90. codex/static_root/assets/VRadioGroup-Cbayik6d.js.br +0 -0
  91. codex/static_root/assets/VRadioGroup-Cbayik6d.js.gz +0 -0
  92. codex/static_root/assets/VSelect-8MFoaOUz.6309f7bf17bb.css +1 -0
  93. codex/static_root/assets/VSelect-8MFoaOUz.6309f7bf17bb.css.br +0 -0
  94. codex/static_root/assets/VSelect-8MFoaOUz.6309f7bf17bb.css.gz +0 -0
  95. codex/static_root/assets/VSelect-8MFoaOUz.css +1 -0
  96. codex/static_root/assets/VSelect-8MFoaOUz.css.br +0 -0
  97. codex/static_root/assets/VSelect-8MFoaOUz.css.gz +0 -0
  98. codex/static_root/assets/VSelect-BsuB-2P1.abe187c8b53f.js +1 -0
  99. codex/static_root/assets/VSelect-BsuB-2P1.abe187c8b53f.js.br +0 -0
  100. codex/static_root/assets/VSelect-BsuB-2P1.abe187c8b53f.js.gz +0 -0
  101. codex/static_root/assets/VSelect-BsuB-2P1.js +1 -0
  102. codex/static_root/assets/VSelect-BsuB-2P1.js.br +0 -0
  103. codex/static_root/assets/VSelect-BsuB-2P1.js.gz +0 -0
  104. codex/static_root/assets/VSelectionControl-DLxcLoTI.cc18de4e83f6.css +1 -0
  105. codex/static_root/assets/VSelectionControl-DLxcLoTI.cc18de4e83f6.css.br +0 -0
  106. codex/static_root/assets/VSelectionControl-DLxcLoTI.cc18de4e83f6.css.gz +0 -0
  107. codex/static_root/assets/VSelectionControl-DLxcLoTI.css +1 -0
  108. codex/static_root/assets/VSelectionControl-DLxcLoTI.css.br +0 -0
  109. codex/static_root/assets/VSelectionControl-DLxcLoTI.css.gz +0 -0
  110. codex/static_root/assets/VSelectionControl-L41n5bnX.f4674bb62425.js +1 -0
  111. codex/static_root/assets/VSelectionControl-L41n5bnX.f4674bb62425.js.br +0 -0
  112. codex/static_root/assets/VSelectionControl-L41n5bnX.f4674bb62425.js.gz +0 -0
  113. codex/static_root/assets/VSelectionControl-L41n5bnX.js +1 -0
  114. codex/static_root/assets/VSelectionControl-L41n5bnX.js.br +0 -0
  115. codex/static_root/assets/VSelectionControl-L41n5bnX.js.gz +0 -0
  116. codex/static_root/assets/VTable-CYv4gUUt.76e4658abeb3.js +1 -0
  117. codex/static_root/assets/VTable-CYv4gUUt.76e4658abeb3.js.br +0 -0
  118. codex/static_root/assets/VTable-CYv4gUUt.76e4658abeb3.js.gz +0 -0
  119. codex/static_root/assets/VTable-CYv4gUUt.js +1 -0
  120. codex/static_root/assets/VTable-CYv4gUUt.js.br +0 -0
  121. codex/static_root/assets/VTable-CYv4gUUt.js.gz +0 -0
  122. codex/static_root/assets/VWindowItem-f8UkxXa_.1d3e9e78fc9b.js +1 -0
  123. codex/static_root/assets/VWindowItem-f8UkxXa_.1d3e9e78fc9b.js.br +0 -0
  124. codex/static_root/assets/VWindowItem-f8UkxXa_.1d3e9e78fc9b.js.gz +0 -0
  125. codex/static_root/assets/VWindowItem-f8UkxXa_.js +1 -0
  126. codex/static_root/assets/VWindowItem-f8UkxXa_.js.br +0 -0
  127. codex/static_root/assets/VWindowItem-f8UkxXa_.js.gz +0 -0
  128. codex/static_root/assets/admin-BmuWJcAB.b0b253aefd88.js +1 -0
  129. codex/static_root/assets/admin-BmuWJcAB.b0b253aefd88.js.br +0 -0
  130. codex/static_root/assets/admin-BmuWJcAB.b0b253aefd88.js.gz +0 -0
  131. codex/static_root/assets/admin-BmuWJcAB.js +1 -0
  132. codex/static_root/assets/admin-BmuWJcAB.js.br +0 -0
  133. codex/static_root/assets/admin-BmuWJcAB.js.gz +0 -0
  134. codex/static_root/assets/admin-Izb24SXM.504ea9d9515c.js +1 -0
  135. codex/static_root/assets/admin-Izb24SXM.504ea9d9515c.js.br +0 -0
  136. codex/static_root/assets/admin-Izb24SXM.504ea9d9515c.js.gz +0 -0
  137. codex/static_root/assets/admin-Izb24SXM.js +1 -0
  138. codex/static_root/assets/admin-Izb24SXM.js.br +0 -0
  139. codex/static_root/assets/admin-Izb24SXM.js.gz +0 -0
  140. codex/static_root/assets/admin-dm3bSP7O.ae1234cc0beb.css +1 -0
  141. codex/static_root/assets/admin-dm3bSP7O.ae1234cc0beb.css.br +0 -0
  142. codex/static_root/assets/admin-dm3bSP7O.ae1234cc0beb.css.gz +0 -0
  143. codex/static_root/assets/admin-dm3bSP7O.css +1 -0
  144. codex/static_root/assets/admin-dm3bSP7O.css.br +0 -0
  145. codex/static_root/assets/admin-dm3bSP7O.css.gz +0 -0
  146. codex/static_root/assets/admin-menu-C712OV-9.ecd1f2af4cf4.js +1 -0
  147. codex/static_root/assets/admin-menu-C712OV-9.ecd1f2af4cf4.js.br +0 -0
  148. codex/static_root/assets/admin-menu-C712OV-9.ecd1f2af4cf4.js.gz +0 -0
  149. codex/static_root/assets/admin-menu-C712OV-9.js +1 -0
  150. codex/static_root/assets/admin-menu-C712OV-9.js.br +0 -0
  151. codex/static_root/assets/admin-menu-C712OV-9.js.gz +0 -0
  152. codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.2536580dece1.js +1 -0
  153. codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.2536580dece1.js.br +0 -0
  154. codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.2536580dece1.js.gz +0 -0
  155. codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.js +1 -0
  156. codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.js.br +0 -0
  157. codex/static_root/assets/admin-settings-button-progress-Clv9yQ7G.js.gz +0 -0
  158. codex/static_root/assets/browser-CReWGhn_.521193ab0710.css +1 -0
  159. codex/static_root/assets/browser-CReWGhn_.521193ab0710.css.br +0 -0
  160. codex/static_root/assets/browser-CReWGhn_.521193ab0710.css.gz +0 -0
  161. codex/static_root/assets/browser-CReWGhn_.css +1 -0
  162. codex/static_root/assets/browser-CReWGhn_.css.br +0 -0
  163. codex/static_root/assets/browser-CReWGhn_.css.gz +0 -0
  164. codex/static_root/assets/browser-DiAN9Ipo.a5b62a3cbe1d.js +1 -0
  165. codex/static_root/assets/browser-DiAN9Ipo.a5b62a3cbe1d.js.br +0 -0
  166. codex/static_root/assets/browser-DiAN9Ipo.a5b62a3cbe1d.js.gz +0 -0
  167. codex/static_root/assets/browser-DiAN9Ipo.js +1 -0
  168. codex/static_root/assets/browser-DiAN9Ipo.js.br +0 -0
  169. codex/static_root/assets/browser-DiAN9Ipo.js.gz +0 -0
  170. codex/static_root/assets/change-password-dialog-BZPmAOML.73fdc23e13a4.js +1 -0
  171. codex/static_root/assets/change-password-dialog-BZPmAOML.73fdc23e13a4.js.br +0 -0
  172. codex/static_root/assets/change-password-dialog-BZPmAOML.73fdc23e13a4.js.gz +0 -0
  173. codex/static_root/assets/change-password-dialog-BZPmAOML.js +1 -0
  174. codex/static_root/assets/change-password-dialog-BZPmAOML.js.br +0 -0
  175. codex/static_root/assets/change-password-dialog-BZPmAOML.js.gz +0 -0
  176. codex/static_root/assets/confirm-dialog-CBLQOik_.f53acd0c622a.js +1 -0
  177. codex/static_root/assets/confirm-dialog-CBLQOik_.f53acd0c622a.js.br +0 -0
  178. codex/static_root/assets/confirm-dialog-CBLQOik_.f53acd0c622a.js.gz +0 -0
  179. codex/static_root/assets/confirm-dialog-CBLQOik_.js +1 -0
  180. codex/static_root/assets/confirm-dialog-CBLQOik_.js.br +0 -0
  181. codex/static_root/assets/confirm-dialog-CBLQOik_.js.gz +0 -0
  182. codex/static_root/assets/datetime-column-CU19rCbi.9edc0d2da736.js +1 -0
  183. codex/static_root/assets/datetime-column-CU19rCbi.9edc0d2da736.js.br +1 -0
  184. codex/static_root/assets/datetime-column-CU19rCbi.9edc0d2da736.js.gz +0 -0
  185. codex/static_root/assets/datetime-column-CU19rCbi.js +1 -0
  186. codex/static_root/assets/datetime-column-CU19rCbi.js.br +1 -0
  187. codex/static_root/assets/datetime-column-CU19rCbi.js.gz +0 -0
  188. codex/static_root/assets/{filter-ud5mPZpn.0f0a537959aa.js → filter-CZ7kkBLq.171b4fcba146.js} +1 -1
  189. codex/static_root/assets/filter-CZ7kkBLq.171b4fcba146.js.br +0 -0
  190. codex/static_root/assets/filter-CZ7kkBLq.171b4fcba146.js.gz +0 -0
  191. codex/static_root/assets/{filter-ud5mPZpn.js → filter-CZ7kkBLq.js} +1 -1
  192. codex/static_root/assets/filter-CZ7kkBLq.js.br +0 -0
  193. codex/static_root/assets/filter-CZ7kkBLq.js.gz +0 -0
  194. codex/static_root/assets/flag-tab-DHGfv0Q3.2b7ef394b850.css +1 -0
  195. codex/static_root/assets/flag-tab-DHGfv0Q3.2b7ef394b850.css.br +0 -0
  196. codex/static_root/assets/flag-tab-DHGfv0Q3.2b7ef394b850.css.gz +0 -0
  197. codex/static_root/assets/flag-tab-DHGfv0Q3.css +1 -0
  198. codex/static_root/assets/flag-tab-DHGfv0Q3.css.br +0 -0
  199. codex/static_root/assets/flag-tab-DHGfv0Q3.css.gz +0 -0
  200. codex/static_root/assets/flag-tab-kcBN36xe.631c48ab75fd.js +1 -0
  201. codex/static_root/assets/flag-tab-kcBN36xe.631c48ab75fd.js.br +0 -0
  202. codex/static_root/assets/flag-tab-kcBN36xe.631c48ab75fd.js.gz +0 -0
  203. codex/static_root/assets/flag-tab-kcBN36xe.js +1 -0
  204. codex/static_root/assets/flag-tab-kcBN36xe.js.br +0 -0
  205. codex/static_root/assets/flag-tab-kcBN36xe.js.gz +0 -0
  206. codex/static_root/assets/forwardRefs-DNAemYUk.06a315272ed9.js +1 -0
  207. codex/static_root/assets/forwardRefs-DNAemYUk.06a315272ed9.js.br +0 -0
  208. codex/static_root/assets/forwardRefs-DNAemYUk.06a315272ed9.js.gz +0 -0
  209. codex/static_root/assets/forwardRefs-DNAemYUk.js +1 -0
  210. codex/static_root/assets/forwardRefs-DNAemYUk.js.br +0 -0
  211. codex/static_root/assets/forwardRefs-DNAemYUk.js.gz +0 -0
  212. codex/static_root/assets/forwardRefs-zKOZsbbr.9b56b4459240.css +1 -0
  213. codex/static_root/assets/forwardRefs-zKOZsbbr.9b56b4459240.css.br +0 -0
  214. codex/static_root/assets/forwardRefs-zKOZsbbr.9b56b4459240.css.gz +0 -0
  215. codex/static_root/assets/forwardRefs-zKOZsbbr.css +1 -0
  216. codex/static_root/assets/forwardRefs-zKOZsbbr.css.br +0 -0
  217. codex/static_root/assets/forwardRefs-zKOZsbbr.css.gz +0 -0
  218. codex/static_root/assets/group-tab-D9gwAdJc.60fb0ffa734c.js +1 -0
  219. codex/static_root/assets/group-tab-D9gwAdJc.60fb0ffa734c.js.br +0 -0
  220. codex/static_root/assets/group-tab-D9gwAdJc.60fb0ffa734c.js.gz +0 -0
  221. codex/static_root/assets/group-tab-D9gwAdJc.js +1 -0
  222. codex/static_root/assets/group-tab-D9gwAdJc.js.br +0 -0
  223. codex/static_root/assets/group-tab-D9gwAdJc.js.gz +0 -0
  224. codex/static_root/assets/http-error-DEJmFobt.8c13689ae574.css +1 -0
  225. codex/static_root/assets/http-error-DEJmFobt.8c13689ae574.css.br +0 -0
  226. codex/static_root/assets/http-error-DEJmFobt.8c13689ae574.css.gz +0 -0
  227. codex/static_root/assets/http-error-DEJmFobt.css +1 -0
  228. codex/static_root/assets/http-error-DEJmFobt.css.br +0 -0
  229. codex/static_root/assets/http-error-DEJmFobt.css.gz +0 -0
  230. codex/static_root/assets/http-error-DmFqD2Xz.593d626b5f1b.js +1 -0
  231. codex/static_root/assets/http-error-DmFqD2Xz.593d626b5f1b.js.br +0 -0
  232. codex/static_root/assets/http-error-DmFqD2Xz.593d626b5f1b.js.gz +0 -0
  233. codex/static_root/assets/http-error-DmFqD2Xz.js +1 -0
  234. codex/static_root/assets/http-error-DmFqD2Xz.js.br +0 -0
  235. codex/static_root/assets/http-error-DmFqD2Xz.js.gz +0 -0
  236. codex/static_root/assets/index-hvGLIBbY.30ce8cca0c6d.js +1 -0
  237. codex/static_root/assets/index-hvGLIBbY.30ce8cca0c6d.js.br +0 -0
  238. codex/static_root/assets/index-hvGLIBbY.30ce8cca0c6d.js.gz +0 -0
  239. codex/static_root/assets/index-hvGLIBbY.js +1 -0
  240. codex/static_root/assets/index-hvGLIBbY.js.br +0 -0
  241. codex/static_root/assets/index-hvGLIBbY.js.gz +0 -0
  242. codex/static_root/assets/library-tab-CF-RAEgP.a0ad816fb994.js +1 -0
  243. codex/static_root/assets/library-tab-CF-RAEgP.a0ad816fb994.js.br +0 -0
  244. codex/static_root/assets/library-tab-CF-RAEgP.a0ad816fb994.js.gz +0 -0
  245. codex/static_root/assets/library-tab-CF-RAEgP.js +1 -0
  246. codex/static_root/assets/library-tab-CF-RAEgP.js.br +0 -0
  247. codex/static_root/assets/library-tab-CF-RAEgP.js.gz +0 -0
  248. codex/static_root/assets/{library-tab-Cat0MAK5.5911f022188e.css → library-tab-Ce6qaeCV.75baedb7e976.css} +1 -1
  249. codex/static_root/assets/library-tab-Ce6qaeCV.75baedb7e976.css.br +0 -0
  250. codex/static_root/assets/library-tab-Ce6qaeCV.75baedb7e976.css.gz +0 -0
  251. codex/static_root/assets/{library-tab-Cat0MAK5.css → library-tab-Ce6qaeCV.css} +1 -1
  252. codex/static_root/assets/library-tab-Ce6qaeCV.css.br +0 -0
  253. codex/static_root/assets/library-tab-Ce6qaeCV.css.gz +0 -0
  254. codex/static_root/assets/main-Dv8e0bMI.bd74f1ba1627.js +35 -0
  255. codex/static_root/assets/main-Dv8e0bMI.bd74f1ba1627.js.br +0 -0
  256. codex/static_root/assets/main-Dv8e0bMI.bd74f1ba1627.js.gz +0 -0
  257. codex/static_root/assets/main-Dv8e0bMI.js +35 -0
  258. codex/static_root/assets/main-Dv8e0bMI.js.br +0 -0
  259. codex/static_root/assets/main-Dv8e0bMI.js.gz +0 -0
  260. codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.04a4f5c451b4.js +1 -0
  261. codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.04a4f5c451b4.js.br +0 -0
  262. codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.04a4f5c451b4.js.gz +0 -0
  263. codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.js +1 -0
  264. codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.js.br +0 -0
  265. codex/static_root/assets/pager-full-pdf-BXd9Y5oQ.js.gz +0 -0
  266. codex/static_root/assets/pagination-toolbar-DSe0TkMm.css +1 -0
  267. codex/static_root/assets/pagination-toolbar-DSe0TkMm.css.br +0 -0
  268. codex/static_root/assets/pagination-toolbar-DSe0TkMm.css.gz +0 -0
  269. codex/static_root/assets/pagination-toolbar-DSe0TkMm.dcb0eba65827.css +1 -0
  270. codex/static_root/assets/pagination-toolbar-DSe0TkMm.dcb0eba65827.css.br +0 -0
  271. codex/static_root/assets/pagination-toolbar-DSe0TkMm.dcb0eba65827.css.gz +0 -0
  272. codex/static_root/assets/pagination-toolbar-D_whea1o.9bbd927058f1.js +1 -0
  273. codex/static_root/assets/pagination-toolbar-D_whea1o.9bbd927058f1.js.br +0 -0
  274. codex/static_root/assets/pagination-toolbar-D_whea1o.9bbd927058f1.js.gz +0 -0
  275. codex/static_root/assets/pagination-toolbar-D_whea1o.js +1 -0
  276. codex/static_root/assets/pagination-toolbar-D_whea1o.js.br +0 -0
  277. codex/static_root/assets/pagination-toolbar-D_whea1o.js.gz +0 -0
  278. codex/static_root/assets/pdf-doc-dnr5fHbU.fa1d7ed9255b.js +348 -0
  279. codex/static_root/assets/pdf-doc-dnr5fHbU.fa1d7ed9255b.js.br +0 -0
  280. codex/static_root/assets/pdf-doc-dnr5fHbU.fa1d7ed9255b.js.gz +0 -0
  281. codex/static_root/assets/pdf-doc-dnr5fHbU.js +348 -0
  282. codex/static_root/assets/pdf-doc-dnr5fHbU.js.br +0 -0
  283. codex/static_root/assets/pdf-doc-dnr5fHbU.js.gz +0 -0
  284. codex/static_root/assets/{reader-Dd2KmdCN.115d4bfe9280.css → reader-B3hgta6g.2ed32f08e906.css} +1 -1
  285. codex/static_root/assets/reader-B3hgta6g.2ed32f08e906.css.br +0 -0
  286. codex/static_root/assets/reader-B3hgta6g.2ed32f08e906.css.gz +0 -0
  287. codex/static_root/assets/{reader-Dd2KmdCN.css → reader-B3hgta6g.css} +1 -1
  288. codex/static_root/assets/reader-B3hgta6g.css.br +0 -0
  289. codex/static_root/assets/reader-B3hgta6g.css.gz +0 -0
  290. codex/static_root/assets/reader-Cn-ra_Dv.f38c0b419dec.js +2 -0
  291. codex/static_root/assets/reader-Cn-ra_Dv.f38c0b419dec.js.br +0 -0
  292. codex/static_root/assets/reader-Cn-ra_Dv.f38c0b419dec.js.gz +0 -0
  293. codex/static_root/assets/reader-Cn-ra_Dv.js +2 -0
  294. codex/static_root/assets/reader-Cn-ra_Dv.js.br +0 -0
  295. codex/static_root/assets/reader-Cn-ra_Dv.js.gz +0 -0
  296. codex/static_root/assets/relation-chips-fOPxWAMU.78e5fe70c014.js +1 -0
  297. codex/static_root/assets/relation-chips-fOPxWAMU.78e5fe70c014.js.br +0 -0
  298. codex/static_root/assets/relation-chips-fOPxWAMU.78e5fe70c014.js.gz +0 -0
  299. codex/static_root/assets/relation-chips-fOPxWAMU.js +1 -0
  300. codex/static_root/assets/relation-chips-fOPxWAMU.js.br +0 -0
  301. codex/static_root/assets/relation-chips-fOPxWAMU.js.gz +0 -0
  302. codex/static_root/assets/settings-drawer-1UEq8JY1.d95cee9aefb5.js +2 -0
  303. codex/static_root/assets/settings-drawer-1UEq8JY1.d95cee9aefb5.js.br +0 -0
  304. codex/static_root/assets/settings-drawer-1UEq8JY1.d95cee9aefb5.js.gz +0 -0
  305. codex/static_root/assets/settings-drawer-1UEq8JY1.js +2 -0
  306. codex/static_root/assets/settings-drawer-1UEq8JY1.js.br +0 -0
  307. codex/static_root/assets/settings-drawer-1UEq8JY1.js.gz +0 -0
  308. codex/static_root/assets/settings-drawer-CqJ4gXLq.75a6ea969e71.css +1 -0
  309. codex/static_root/assets/settings-drawer-CqJ4gXLq.75a6ea969e71.css.br +0 -0
  310. codex/static_root/assets/settings-drawer-CqJ4gXLq.75a6ea969e71.css.gz +0 -0
  311. codex/static_root/assets/settings-drawer-CqJ4gXLq.css +1 -0
  312. codex/static_root/assets/settings-drawer-CqJ4gXLq.css.br +0 -0
  313. codex/static_root/assets/settings-drawer-CqJ4gXLq.css.gz +0 -0
  314. codex/static_root/assets/stats-tab-0ptRCeKw.5514d3508e37.js +1 -0
  315. codex/static_root/assets/stats-tab-0ptRCeKw.5514d3508e37.js.br +0 -0
  316. codex/static_root/assets/stats-tab-0ptRCeKw.5514d3508e37.js.gz +0 -0
  317. codex/static_root/assets/stats-tab-0ptRCeKw.js +1 -0
  318. codex/static_root/assets/stats-tab-0ptRCeKw.js.br +0 -0
  319. codex/static_root/assets/stats-tab-0ptRCeKw.js.gz +0 -0
  320. codex/static_root/assets/task-tab-CG9_bi6Z.ad9f647f8e5f.js +1 -0
  321. codex/static_root/assets/task-tab-CG9_bi6Z.ad9f647f8e5f.js.br +0 -0
  322. codex/static_root/assets/task-tab-CG9_bi6Z.ad9f647f8e5f.js.gz +0 -0
  323. codex/static_root/assets/task-tab-CG9_bi6Z.js +1 -0
  324. codex/static_root/assets/task-tab-CG9_bi6Z.js.br +0 -0
  325. codex/static_root/assets/task-tab-CG9_bi6Z.js.gz +0 -0
  326. codex/static_root/assets/unauthorized-VnIXUO1w.c5536d9dae75.css +1 -0
  327. codex/static_root/assets/unauthorized-VnIXUO1w.c5536d9dae75.css.br +0 -0
  328. codex/static_root/assets/unauthorized-VnIXUO1w.c5536d9dae75.css.gz +0 -0
  329. codex/static_root/assets/unauthorized-VnIXUO1w.css +1 -0
  330. codex/static_root/assets/unauthorized-VnIXUO1w.css.br +0 -0
  331. codex/static_root/assets/unauthorized-VnIXUO1w.css.gz +0 -0
  332. codex/static_root/assets/unauthorized-vG1Lh0m_.0c4bae81ee09.js +1 -0
  333. codex/static_root/assets/unauthorized-vG1Lh0m_.0c4bae81ee09.js.br +0 -0
  334. codex/static_root/assets/unauthorized-vG1Lh0m_.0c4bae81ee09.js.gz +0 -0
  335. codex/static_root/assets/unauthorized-vG1Lh0m_.js +1 -0
  336. codex/static_root/assets/unauthorized-vG1Lh0m_.js.br +0 -0
  337. codex/static_root/assets/unauthorized-vG1Lh0m_.js.gz +0 -0
  338. codex/static_root/assets/user-tab-BOxWlpY7.1a94280a799b.js +1 -0
  339. codex/static_root/assets/user-tab-BOxWlpY7.1a94280a799b.js.br +0 -0
  340. codex/static_root/assets/user-tab-BOxWlpY7.1a94280a799b.js.gz +0 -0
  341. codex/static_root/assets/user-tab-BOxWlpY7.js +1 -0
  342. codex/static_root/assets/user-tab-BOxWlpY7.js.br +0 -0
  343. codex/static_root/assets/user-tab-BOxWlpY7.js.gz +0 -0
  344. codex/static_root/manifest.9b3479a6a4a6.json +735 -0
  345. codex/static_root/manifest.9b3479a6a4a6.json.br +0 -0
  346. codex/static_root/manifest.9b3479a6a4a6.json.gz +0 -0
  347. codex/static_root/manifest.json +365 -342
  348. codex/static_root/manifest.json.br +0 -0
  349. codex/static_root/manifest.json.gz +0 -0
  350. codex/static_root/staticfiles.json +1 -1
  351. codex/templates/index.html +1 -1
  352. codex/threads.py +8 -3
  353. codex/urls/api/admin.py +1 -0
  354. codex/urls/api/browser.py +1 -1
  355. codex/urls/opds/v2.py +6 -0
  356. codex/views/admin/flag.py +5 -1
  357. codex/views/admin/tasks.py +2 -0
  358. codex/views/bookmark.py +53 -41
  359. codex/views/browser/annotate/bookmark.py +6 -4
  360. codex/views/browser/annotate/card.py +1 -16
  361. codex/views/browser/annotate/order.py +45 -18
  362. codex/views/browser/bookmark.py +53 -0
  363. codex/views/browser/download.py +2 -0
  364. codex/views/browser/filters/bookmark.py +4 -32
  365. codex/views/browser/group_mtime.py +2 -2
  366. codex/views/browser/metadata/annotate.py +1 -1
  367. codex/views/browser/metadata/metadata.py +1 -1
  368. codex/views/browser/order_by.py +6 -1
  369. codex/views/frontend.py +19 -4
  370. codex/views/mixins.py +16 -28
  371. codex/views/opds/const.py +2 -0
  372. codex/views/opds/util.py +2 -2
  373. codex/views/opds/v1/entry/links.py +1 -2
  374. codex/views/opds/v2/feed.py +3 -2
  375. codex/views/opds/v2/href.py +60 -0
  376. codex/views/opds/v2/links.py +13 -47
  377. codex/views/opds/v2/progression.py +137 -0
  378. codex/views/opds/v2/publications.py +21 -1
  379. codex/views/opds/v2/top_links.py +2 -1
  380. codex/views/public.py +11 -4
  381. codex/views/reader/books.py +19 -8
  382. codex/views/reader/page.py +5 -5
  383. {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/METADATA +21 -26
  384. {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/RECORD +387 -370
  385. codex/serializers/opds/v2.py +0 -178
  386. codex/static_root/assets/VCheckbox-DLMzIII1.1ff8fc2d9a21.js +0 -1
  387. codex/static_root/assets/VCheckbox-DLMzIII1.1ff8fc2d9a21.js.br +0 -0
  388. codex/static_root/assets/VCheckbox-DLMzIII1.1ff8fc2d9a21.js.gz +0 -0
  389. codex/static_root/assets/VCheckbox-DLMzIII1.js +0 -1
  390. codex/static_root/assets/VCheckbox-DLMzIII1.js.br +0 -0
  391. codex/static_root/assets/VCheckbox-DLMzIII1.js.gz +0 -0
  392. codex/static_root/assets/VCheckboxBtn-BzoQl8XC.094679a3624b.js +0 -1
  393. codex/static_root/assets/VCheckboxBtn-BzoQl8XC.094679a3624b.js.br +0 -0
  394. codex/static_root/assets/VCheckboxBtn-BzoQl8XC.094679a3624b.js.gz +0 -0
  395. codex/static_root/assets/VCheckboxBtn-BzoQl8XC.js +0 -1
  396. codex/static_root/assets/VCheckboxBtn-BzoQl8XC.js.br +0 -0
  397. codex/static_root/assets/VCheckboxBtn-BzoQl8XC.js.gz +0 -0
  398. codex/static_root/assets/VCombobox-CBY8K5CP.fcf2221a9edd.js +0 -1
  399. codex/static_root/assets/VCombobox-CBY8K5CP.fcf2221a9edd.js.br +0 -0
  400. codex/static_root/assets/VCombobox-CBY8K5CP.fcf2221a9edd.js.gz +0 -0
  401. codex/static_root/assets/VCombobox-CBY8K5CP.js +0 -1
  402. codex/static_root/assets/VCombobox-CBY8K5CP.js.br +0 -0
  403. codex/static_root/assets/VCombobox-CBY8K5CP.js.gz +0 -0
  404. codex/static_root/assets/VDialog-DXQz0Ylp.7b23d86dbd0d.css +0 -1
  405. codex/static_root/assets/VDialog-DXQz0Ylp.7b23d86dbd0d.css.br +0 -0
  406. codex/static_root/assets/VDialog-DXQz0Ylp.7b23d86dbd0d.css.gz +0 -0
  407. codex/static_root/assets/VDialog-DXQz0Ylp.css +0 -1
  408. codex/static_root/assets/VDialog-DXQz0Ylp.css.br +0 -0
  409. codex/static_root/assets/VDialog-DXQz0Ylp.css.gz +0 -0
  410. codex/static_root/assets/VDialog-Db4tJYqR.e21a79a5030e.js +0 -1
  411. codex/static_root/assets/VDialog-Db4tJYqR.e21a79a5030e.js.br +0 -0
  412. codex/static_root/assets/VDialog-Db4tJYqR.e21a79a5030e.js.gz +0 -0
  413. codex/static_root/assets/VDialog-Db4tJYqR.js +0 -1
  414. codex/static_root/assets/VDialog-Db4tJYqR.js.br +0 -0
  415. codex/static_root/assets/VDialog-Db4tJYqR.js.gz +0 -0
  416. codex/static_root/assets/VExpansionPanels-DN4yGbOt.163e59e38865.js +0 -1
  417. codex/static_root/assets/VExpansionPanels-DN4yGbOt.163e59e38865.js.br +0 -0
  418. codex/static_root/assets/VExpansionPanels-DN4yGbOt.163e59e38865.js.gz +0 -0
  419. codex/static_root/assets/VExpansionPanels-DN4yGbOt.js +0 -1
  420. codex/static_root/assets/VExpansionPanels-DN4yGbOt.js.br +0 -0
  421. codex/static_root/assets/VExpansionPanels-DN4yGbOt.js.gz +0 -0
  422. codex/static_root/assets/VRadioGroup-D2-PWEfs.ce7644863c25.js +0 -1
  423. codex/static_root/assets/VRadioGroup-D2-PWEfs.ce7644863c25.js.br +0 -0
  424. codex/static_root/assets/VRadioGroup-D2-PWEfs.ce7644863c25.js.gz +0 -0
  425. codex/static_root/assets/VRadioGroup-D2-PWEfs.js +0 -1
  426. codex/static_root/assets/VRadioGroup-D2-PWEfs.js.br +0 -0
  427. codex/static_root/assets/VRadioGroup-D2-PWEfs.js.gz +0 -0
  428. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +0 -1
  429. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
  430. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
  431. codex/static_root/assets/VSelect-DLu8bpcs.css +0 -1
  432. codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
  433. codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
  434. codex/static_root/assets/VSelect-DqXJ23Pc.a1360cbed050.js +0 -1
  435. codex/static_root/assets/VSelect-DqXJ23Pc.a1360cbed050.js.br +0 -0
  436. codex/static_root/assets/VSelect-DqXJ23Pc.a1360cbed050.js.gz +0 -0
  437. codex/static_root/assets/VSelect-DqXJ23Pc.js +0 -1
  438. codex/static_root/assets/VSelect-DqXJ23Pc.js.br +0 -0
  439. codex/static_root/assets/VSelect-DqXJ23Pc.js.gz +0 -0
  440. codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css +0 -1
  441. codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css.br +0 -0
  442. codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css.gz +0 -0
  443. codex/static_root/assets/VSelectionControl-CiViEYoC.css +0 -1
  444. codex/static_root/assets/VSelectionControl-CiViEYoC.css.br +0 -0
  445. codex/static_root/assets/VSelectionControl-CiViEYoC.css.gz +0 -0
  446. codex/static_root/assets/VSelectionControl-CxhDNufT.dd7ca99567fc.js +0 -1
  447. codex/static_root/assets/VSelectionControl-CxhDNufT.dd7ca99567fc.js.br +0 -0
  448. codex/static_root/assets/VSelectionControl-CxhDNufT.dd7ca99567fc.js.gz +0 -0
  449. codex/static_root/assets/VSelectionControl-CxhDNufT.js +0 -1
  450. codex/static_root/assets/VSelectionControl-CxhDNufT.js.br +0 -0
  451. codex/static_root/assets/VSelectionControl-CxhDNufT.js.gz +0 -0
  452. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +0 -1
  453. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
  454. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
  455. codex/static_root/assets/VSlideGroup-BtXPhMOT.css +0 -1
  456. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
  457. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
  458. codex/static_root/assets/VSlideGroup-DRbthp6q.7365c5542d47.js +0 -1
  459. codex/static_root/assets/VSlideGroup-DRbthp6q.7365c5542d47.js.br +0 -0
  460. codex/static_root/assets/VSlideGroup-DRbthp6q.7365c5542d47.js.gz +0 -0
  461. codex/static_root/assets/VSlideGroup-DRbthp6q.js +0 -1
  462. codex/static_root/assets/VSlideGroup-DRbthp6q.js.br +0 -0
  463. codex/static_root/assets/VSlideGroup-DRbthp6q.js.gz +0 -0
  464. codex/static_root/assets/VTable-Cbq-yJos.c884706cac21.js +0 -1
  465. codex/static_root/assets/VTable-Cbq-yJos.c884706cac21.js.br +0 -0
  466. codex/static_root/assets/VTable-Cbq-yJos.c884706cac21.js.gz +0 -0
  467. codex/static_root/assets/VTable-Cbq-yJos.js +0 -1
  468. codex/static_root/assets/VTable-Cbq-yJos.js.br +0 -0
  469. codex/static_root/assets/VTable-Cbq-yJos.js.gz +0 -0
  470. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css +0 -1
  471. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
  472. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
  473. codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
  474. codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
  475. codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
  476. codex/static_root/assets/VTextField-X5yAw35c.155e276dd747.js +0 -1
  477. codex/static_root/assets/VTextField-X5yAw35c.155e276dd747.js.br +0 -0
  478. codex/static_root/assets/VTextField-X5yAw35c.155e276dd747.js.gz +0 -0
  479. codex/static_root/assets/VTextField-X5yAw35c.js +0 -1
  480. codex/static_root/assets/VTextField-X5yAw35c.js.br +0 -0
  481. codex/static_root/assets/VTextField-X5yAw35c.js.gz +0 -0
  482. codex/static_root/assets/VWindowItem-CRqhVh-9.cd73d01e0591.js +0 -1
  483. codex/static_root/assets/VWindowItem-CRqhVh-9.cd73d01e0591.js.br +0 -0
  484. codex/static_root/assets/VWindowItem-CRqhVh-9.cd73d01e0591.js.gz +0 -0
  485. codex/static_root/assets/VWindowItem-CRqhVh-9.js +0 -1
  486. codex/static_root/assets/VWindowItem-CRqhVh-9.js.br +0 -0
  487. codex/static_root/assets/VWindowItem-CRqhVh-9.js.gz +0 -0
  488. codex/static_root/assets/admin-BFvQwinN.6cfff21f780a.js +0 -1
  489. codex/static_root/assets/admin-BFvQwinN.6cfff21f780a.js.br +0 -0
  490. codex/static_root/assets/admin-BFvQwinN.6cfff21f780a.js.gz +0 -0
  491. codex/static_root/assets/admin-BFvQwinN.js +0 -1
  492. codex/static_root/assets/admin-BFvQwinN.js.br +0 -0
  493. codex/static_root/assets/admin-BFvQwinN.js.gz +0 -0
  494. codex/static_root/assets/admin-DJ7pIXDQ.css +0 -1
  495. codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
  496. codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
  497. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css +0 -1
  498. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
  499. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
  500. codex/static_root/assets/admin-menu-Jfmp_Yd8.39fa7a18b3b4.js +0 -1
  501. codex/static_root/assets/admin-menu-Jfmp_Yd8.39fa7a18b3b4.js.br +0 -0
  502. codex/static_root/assets/admin-menu-Jfmp_Yd8.39fa7a18b3b4.js.gz +0 -0
  503. codex/static_root/assets/admin-menu-Jfmp_Yd8.js +0 -1
  504. codex/static_root/assets/admin-menu-Jfmp_Yd8.js.br +0 -0
  505. codex/static_root/assets/admin-menu-Jfmp_Yd8.js.gz +0 -0
  506. codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.cbf73c71330d.js +0 -1
  507. codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.cbf73c71330d.js.br +0 -0
  508. codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.cbf73c71330d.js.gz +0 -0
  509. codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.js +0 -1
  510. codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.js.br +0 -0
  511. codex/static_root/assets/admin-settings-button-progress-BAk-B1sR.js.gz +0 -0
  512. codex/static_root/assets/browser-CctlSJji.css +0 -1
  513. codex/static_root/assets/browser-CctlSJji.css.br +0 -0
  514. codex/static_root/assets/browser-CctlSJji.css.gz +0 -0
  515. codex/static_root/assets/browser-CctlSJji.d42d796476d2.css +0 -1
  516. codex/static_root/assets/browser-CctlSJji.d42d796476d2.css.br +0 -0
  517. codex/static_root/assets/browser-CctlSJji.d42d796476d2.css.gz +0 -0
  518. codex/static_root/assets/browser-DJnEeVHz.a78c47516b08.js +0 -1
  519. codex/static_root/assets/browser-DJnEeVHz.a78c47516b08.js.br +0 -0
  520. codex/static_root/assets/browser-DJnEeVHz.a78c47516b08.js.gz +0 -0
  521. codex/static_root/assets/browser-DJnEeVHz.js +0 -1
  522. codex/static_root/assets/browser-DJnEeVHz.js.br +0 -0
  523. codex/static_root/assets/browser-DJnEeVHz.js.gz +0 -0
  524. codex/static_root/assets/change-password-dialog-DJtOQw7K.be7fa5ec653d.js +0 -1
  525. codex/static_root/assets/change-password-dialog-DJtOQw7K.be7fa5ec653d.js.br +0 -0
  526. codex/static_root/assets/change-password-dialog-DJtOQw7K.be7fa5ec653d.js.gz +0 -0
  527. codex/static_root/assets/change-password-dialog-DJtOQw7K.js +0 -1
  528. codex/static_root/assets/change-password-dialog-DJtOQw7K.js.br +0 -0
  529. codex/static_root/assets/change-password-dialog-DJtOQw7K.js.gz +0 -0
  530. codex/static_root/assets/confirm-dialog-CME4gKSZ.1606233e784c.js +0 -1
  531. codex/static_root/assets/confirm-dialog-CME4gKSZ.1606233e784c.js.br +0 -0
  532. codex/static_root/assets/confirm-dialog-CME4gKSZ.1606233e784c.js.gz +0 -0
  533. codex/static_root/assets/confirm-dialog-CME4gKSZ.js +0 -1
  534. codex/static_root/assets/confirm-dialog-CME4gKSZ.js.br +0 -0
  535. codex/static_root/assets/confirm-dialog-CME4gKSZ.js.gz +0 -0
  536. codex/static_root/assets/datetime-column-D_F30OLW.09cdce0ef96e.js +0 -1
  537. codex/static_root/assets/datetime-column-D_F30OLW.09cdce0ef96e.js.br +0 -2
  538. codex/static_root/assets/datetime-column-D_F30OLW.09cdce0ef96e.js.gz +0 -0
  539. codex/static_root/assets/datetime-column-D_F30OLW.js +0 -1
  540. codex/static_root/assets/datetime-column-D_F30OLW.js.br +0 -2
  541. codex/static_root/assets/datetime-column-D_F30OLW.js.gz +0 -0
  542. codex/static_root/assets/filter-ud5mPZpn.0f0a537959aa.js.br +0 -0
  543. codex/static_root/assets/filter-ud5mPZpn.0f0a537959aa.js.gz +0 -0
  544. codex/static_root/assets/filter-ud5mPZpn.js.br +0 -0
  545. codex/static_root/assets/filter-ud5mPZpn.js.gz +0 -0
  546. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +0 -1
  547. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
  548. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
  549. codex/static_root/assets/flag-tab-CqWovuBu.css +0 -1
  550. codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
  551. codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
  552. codex/static_root/assets/flag-tab-k2g6u_vi.3e1da9318af9.js +0 -1
  553. codex/static_root/assets/flag-tab-k2g6u_vi.3e1da9318af9.js.br +0 -0
  554. codex/static_root/assets/flag-tab-k2g6u_vi.3e1da9318af9.js.gz +0 -0
  555. codex/static_root/assets/flag-tab-k2g6u_vi.js +0 -1
  556. codex/static_root/assets/flag-tab-k2g6u_vi.js.br +0 -0
  557. codex/static_root/assets/flag-tab-k2g6u_vi.js.gz +0 -0
  558. codex/static_root/assets/group-tab-C6Ykf03Z.ff0cc204013a.js +0 -1
  559. codex/static_root/assets/group-tab-C6Ykf03Z.ff0cc204013a.js.br +0 -0
  560. codex/static_root/assets/group-tab-C6Ykf03Z.ff0cc204013a.js.gz +0 -0
  561. codex/static_root/assets/group-tab-C6Ykf03Z.js +0 -1
  562. codex/static_root/assets/group-tab-C6Ykf03Z.js.br +0 -0
  563. codex/static_root/assets/group-tab-C6Ykf03Z.js.gz +0 -0
  564. codex/static_root/assets/http-error-Ctcws1xe.cfd378a792d2.css +0 -1
  565. codex/static_root/assets/http-error-Ctcws1xe.cfd378a792d2.css.br +0 -0
  566. codex/static_root/assets/http-error-Ctcws1xe.cfd378a792d2.css.gz +0 -0
  567. codex/static_root/assets/http-error-Ctcws1xe.css +0 -1
  568. codex/static_root/assets/http-error-Ctcws1xe.css.br +0 -0
  569. codex/static_root/assets/http-error-Ctcws1xe.css.gz +0 -0
  570. codex/static_root/assets/http-error-DQBHK8v5.aea50b1327fd.js +0 -1
  571. codex/static_root/assets/http-error-DQBHK8v5.aea50b1327fd.js.br +0 -0
  572. codex/static_root/assets/http-error-DQBHK8v5.aea50b1327fd.js.gz +0 -0
  573. codex/static_root/assets/http-error-DQBHK8v5.js +0 -1
  574. codex/static_root/assets/http-error-DQBHK8v5.js.br +0 -0
  575. codex/static_root/assets/http-error-DQBHK8v5.js.gz +0 -0
  576. codex/static_root/assets/index-ClGiHwWc.0717bacf5135.js +0 -1
  577. codex/static_root/assets/index-ClGiHwWc.0717bacf5135.js.br +0 -0
  578. codex/static_root/assets/index-ClGiHwWc.0717bacf5135.js.gz +0 -0
  579. codex/static_root/assets/index-ClGiHwWc.js +0 -1
  580. codex/static_root/assets/index-ClGiHwWc.js.br +0 -0
  581. codex/static_root/assets/index-ClGiHwWc.js.gz +0 -0
  582. codex/static_root/assets/library-tab-BpinbqAu.53fa2625e727.js +0 -1
  583. codex/static_root/assets/library-tab-BpinbqAu.53fa2625e727.js.br +0 -0
  584. codex/static_root/assets/library-tab-BpinbqAu.53fa2625e727.js.gz +0 -0
  585. codex/static_root/assets/library-tab-BpinbqAu.js +0 -1
  586. codex/static_root/assets/library-tab-BpinbqAu.js.br +0 -0
  587. codex/static_root/assets/library-tab-BpinbqAu.js.gz +0 -0
  588. codex/static_root/assets/library-tab-Cat0MAK5.5911f022188e.css.br +0 -0
  589. codex/static_root/assets/library-tab-Cat0MAK5.5911f022188e.css.gz +0 -0
  590. codex/static_root/assets/library-tab-Cat0MAK5.css.br +0 -0
  591. codex/static_root/assets/library-tab-Cat0MAK5.css.gz +0 -0
  592. codex/static_root/assets/main-BVKrDunc.1c6f8cdc3afb.js +0 -35
  593. codex/static_root/assets/main-BVKrDunc.1c6f8cdc3afb.js.br +0 -0
  594. codex/static_root/assets/main-BVKrDunc.1c6f8cdc3afb.js.gz +0 -0
  595. codex/static_root/assets/main-BVKrDunc.js +0 -35
  596. codex/static_root/assets/main-BVKrDunc.js.br +0 -0
  597. codex/static_root/assets/main-BVKrDunc.js.gz +0 -0
  598. codex/static_root/assets/pager-full-pdf-C1ZSyXfG.f2e95b19621c.js +0 -1
  599. codex/static_root/assets/pager-full-pdf-C1ZSyXfG.f2e95b19621c.js.br +0 -0
  600. codex/static_root/assets/pager-full-pdf-C1ZSyXfG.f2e95b19621c.js.gz +0 -0
  601. codex/static_root/assets/pager-full-pdf-C1ZSyXfG.js +0 -1
  602. codex/static_root/assets/pager-full-pdf-C1ZSyXfG.js.br +0 -0
  603. codex/static_root/assets/pager-full-pdf-C1ZSyXfG.js.gz +0 -0
  604. codex/static_root/assets/pagination-toolbar-Cx_9nh6D.546121fa7a0d.css +0 -1
  605. codex/static_root/assets/pagination-toolbar-Cx_9nh6D.546121fa7a0d.css.br +0 -0
  606. codex/static_root/assets/pagination-toolbar-Cx_9nh6D.546121fa7a0d.css.gz +0 -0
  607. codex/static_root/assets/pagination-toolbar-Cx_9nh6D.css +0 -1
  608. codex/static_root/assets/pagination-toolbar-Cx_9nh6D.css.br +0 -0
  609. codex/static_root/assets/pagination-toolbar-Cx_9nh6D.css.gz +0 -0
  610. codex/static_root/assets/pagination-toolbar-Diad0EdD.140911bf86af.js +0 -1
  611. codex/static_root/assets/pagination-toolbar-Diad0EdD.140911bf86af.js.br +0 -0
  612. codex/static_root/assets/pagination-toolbar-Diad0EdD.140911bf86af.js.gz +0 -0
  613. codex/static_root/assets/pagination-toolbar-Diad0EdD.js +0 -1
  614. codex/static_root/assets/pagination-toolbar-Diad0EdD.js.br +0 -0
  615. codex/static_root/assets/pagination-toolbar-Diad0EdD.js.gz +0 -0
  616. codex/static_root/assets/pdf-doc-b-GyNR6z.c9d18f0d36ed.js +0 -305
  617. codex/static_root/assets/pdf-doc-b-GyNR6z.c9d18f0d36ed.js.br +0 -0
  618. codex/static_root/assets/pdf-doc-b-GyNR6z.c9d18f0d36ed.js.gz +0 -0
  619. codex/static_root/assets/pdf-doc-b-GyNR6z.js +0 -305
  620. codex/static_root/assets/pdf-doc-b-GyNR6z.js.br +0 -0
  621. codex/static_root/assets/pdf-doc-b-GyNR6z.js.gz +0 -0
  622. codex/static_root/assets/reader-2up6WEuE.c56ad81d14c6.js +0 -2
  623. codex/static_root/assets/reader-2up6WEuE.c56ad81d14c6.js.br +0 -0
  624. codex/static_root/assets/reader-2up6WEuE.c56ad81d14c6.js.gz +0 -0
  625. codex/static_root/assets/reader-2up6WEuE.js +0 -2
  626. codex/static_root/assets/reader-2up6WEuE.js.br +0 -0
  627. codex/static_root/assets/reader-2up6WEuE.js.gz +0 -0
  628. codex/static_root/assets/reader-Dd2KmdCN.115d4bfe9280.css.br +0 -0
  629. codex/static_root/assets/reader-Dd2KmdCN.115d4bfe9280.css.gz +0 -0
  630. codex/static_root/assets/reader-Dd2KmdCN.css.br +0 -0
  631. codex/static_root/assets/reader-Dd2KmdCN.css.gz +0 -0
  632. codex/static_root/assets/relation-chips-piYv1IcD.f8a2ae79de89.js +0 -1
  633. codex/static_root/assets/relation-chips-piYv1IcD.f8a2ae79de89.js.br +0 -0
  634. codex/static_root/assets/relation-chips-piYv1IcD.f8a2ae79de89.js.gz +0 -0
  635. codex/static_root/assets/relation-chips-piYv1IcD.js +0 -1
  636. codex/static_root/assets/relation-chips-piYv1IcD.js.br +0 -0
  637. codex/static_root/assets/relation-chips-piYv1IcD.js.gz +0 -0
  638. codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css +0 -1
  639. codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css.br +0 -0
  640. codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css.gz +0 -0
  641. codex/static_root/assets/settings-drawer-DKncO_vX.css +0 -1
  642. codex/static_root/assets/settings-drawer-DKncO_vX.css.br +0 -0
  643. codex/static_root/assets/settings-drawer-DKncO_vX.css.gz +0 -0
  644. codex/static_root/assets/settings-drawer-yVoY-WCS.1240cc47a246.js +0 -2
  645. codex/static_root/assets/settings-drawer-yVoY-WCS.1240cc47a246.js.br +0 -0
  646. codex/static_root/assets/settings-drawer-yVoY-WCS.1240cc47a246.js.gz +0 -0
  647. codex/static_root/assets/settings-drawer-yVoY-WCS.js +0 -2
  648. codex/static_root/assets/settings-drawer-yVoY-WCS.js.br +0 -0
  649. codex/static_root/assets/settings-drawer-yVoY-WCS.js.gz +0 -0
  650. codex/static_root/assets/stats-tab-bqSPLvTp.05106cb03ef0.js +0 -1
  651. codex/static_root/assets/stats-tab-bqSPLvTp.05106cb03ef0.js.br +0 -0
  652. codex/static_root/assets/stats-tab-bqSPLvTp.05106cb03ef0.js.gz +0 -0
  653. codex/static_root/assets/stats-tab-bqSPLvTp.js +0 -1
  654. codex/static_root/assets/stats-tab-bqSPLvTp.js.br +0 -0
  655. codex/static_root/assets/stats-tab-bqSPLvTp.js.gz +0 -0
  656. codex/static_root/assets/task-tab-DWPKaGPT.da4df28dde5c.js +0 -1
  657. codex/static_root/assets/task-tab-DWPKaGPT.da4df28dde5c.js.br +0 -0
  658. codex/static_root/assets/task-tab-DWPKaGPT.da4df28dde5c.js.gz +0 -0
  659. codex/static_root/assets/task-tab-DWPKaGPT.js +0 -1
  660. codex/static_root/assets/task-tab-DWPKaGPT.js.br +0 -0
  661. codex/static_root/assets/task-tab-DWPKaGPT.js.gz +0 -0
  662. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +0 -1
  663. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +0 -4
  664. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
  665. codex/static_root/assets/transition-5LRFTPo5.css +0 -1
  666. codex/static_root/assets/transition-5LRFTPo5.css.br +0 -4
  667. codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
  668. codex/static_root/assets/transition-DWVQ2EZ3.0bdfaa37b937.js +0 -1
  669. codex/static_root/assets/transition-DWVQ2EZ3.0bdfaa37b937.js.br +0 -0
  670. codex/static_root/assets/transition-DWVQ2EZ3.0bdfaa37b937.js.gz +0 -0
  671. codex/static_root/assets/transition-DWVQ2EZ3.js +0 -1
  672. codex/static_root/assets/transition-DWVQ2EZ3.js.br +0 -0
  673. codex/static_root/assets/transition-DWVQ2EZ3.js.gz +0 -0
  674. codex/static_root/assets/unauthorized-CEGD20tR.064b0993a944.js +0 -1
  675. codex/static_root/assets/unauthorized-CEGD20tR.064b0993a944.js.br +0 -0
  676. codex/static_root/assets/unauthorized-CEGD20tR.064b0993a944.js.gz +0 -0
  677. codex/static_root/assets/unauthorized-CEGD20tR.js +0 -1
  678. codex/static_root/assets/unauthorized-CEGD20tR.js.br +0 -0
  679. codex/static_root/assets/unauthorized-CEGD20tR.js.gz +0 -0
  680. codex/static_root/assets/unauthorized-DyojW2ic.css +0 -1
  681. codex/static_root/assets/unauthorized-DyojW2ic.css.br +0 -0
  682. codex/static_root/assets/unauthorized-DyojW2ic.css.gz +0 -0
  683. codex/static_root/assets/unauthorized-DyojW2ic.feefa7628a23.css +0 -1
  684. codex/static_root/assets/unauthorized-DyojW2ic.feefa7628a23.css.br +0 -0
  685. codex/static_root/assets/unauthorized-DyojW2ic.feefa7628a23.css.gz +0 -0
  686. codex/static_root/assets/user-tab-EsokzMCB.b0acc46b56a3.js +0 -1
  687. codex/static_root/assets/user-tab-EsokzMCB.b0acc46b56a3.js.br +0 -0
  688. codex/static_root/assets/user-tab-EsokzMCB.b0acc46b56a3.js.gz +0 -0
  689. codex/static_root/assets/user-tab-EsokzMCB.js +0 -1
  690. codex/static_root/assets/user-tab-EsokzMCB.js.br +0 -0
  691. codex/static_root/assets/user-tab-EsokzMCB.js.gz +0 -0
  692. codex/static_root/manifest.4a38de3db1c5.json +0 -712
  693. codex/static_root/manifest.4a38de3db1c5.json.br +0 -0
  694. codex/static_root/manifest.4a38de3db1c5.json.gz +0 -0
  695. {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/LICENSE +0 -0
  696. {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/WHEEL +0 -0
  697. {codex-1.7.2.dist-info → codex-1.7.3a0.dist-info}/entry_points.txt +0 -0
codex/choices.py CHANGED
@@ -12,17 +12,18 @@ BROWSER_BOOKMARK_FILTER_CHOICES = MappingProxyType(
12
12
  )
13
13
  BROWSER_ORDER_BY_CHOICES = MappingProxyType(
14
14
  {
15
+ "created_at": "Added Time",
15
16
  "age_rating": "Age Rating",
16
- "bookmark_updated_at": "Last Read",
17
+ "child_count": "Child Count",
17
18
  "community_rating": "Community Rating",
18
- "created_at": "Added Time",
19
19
  "critical_rating": "Critical Rating",
20
- "date": "Publish Date",
21
20
  "filename": "Filename",
22
- "page_count": "Page Count",
23
- "search_score": "Search Score",
24
21
  "size": "File Size",
22
+ "bookmark_updated_at": "Last Read",
25
23
  "sort_name": "Name",
24
+ "page_count": "Page Count",
25
+ "date": "Publish Date",
26
+ "search_score": "Search Score",
26
27
  "story_arc_number": "Story Arc Number",
27
28
  "updated_at": "Updated Time",
28
29
  }
@@ -116,6 +117,7 @@ ADMIN_FLAG_CHOICES = MappingProxyType(
116
117
  "NU": "Non Users",
117
118
  "RG": "Registration",
118
119
  "ST": "Send Stats",
120
+ "BT": "Banner Text",
119
121
  }
120
122
  )
121
123
 
@@ -159,6 +161,7 @@ ADMIN_STATUS_TITLES = MappingProxyType(
159
161
  "JLV": "Check Codex Latest Version",
160
162
  "JSD": "Cleanup Expired Sessions",
161
163
  "JTD": "Cleanup Stale Tags",
164
+ "JCL": "Compress Logs",
162
165
  "SIC": "Search Index Create",
163
166
  "SID": "Search Index Remove Stale Records",
164
167
  "SIO": "Search Index Optimize",
@@ -374,6 +377,11 @@ ADMIN_TASK_GROUPS = MappingProxyType(
374
377
  "title": "Clear Librarian Statuses",
375
378
  "desc": "Mark all Librarian tasks finished.",
376
379
  },
380
+ {
381
+ "value": "compress_logs",
382
+ "title": "Compress Old Logs",
383
+ "desc": "Compress old Codex logs. Runs nightly.",
384
+ },
377
385
  {
378
386
  "value": "janitor_nightly",
379
387
  "title": "Run Nightly Maintenance",
@@ -14,7 +14,7 @@ class BookmarkThread(AggregateMessageQueuedThread, BookmarkUpdate):
14
14
  def aggregate_items(self, item):
15
15
  """Aggregate bookmark updates."""
16
16
  if isinstance(item, BookmarkUpdateTask):
17
- key = (tuple(item.auth_filter.items()), tuple(item.comic_filter.items()))
17
+ key = (tuple(item.auth_filter.items()), item.comic_pks)
18
18
  if key not in self.cache:
19
19
  self.cache[key] = {}
20
20
  self.cache[key].update(item.updates)
@@ -26,11 +26,10 @@ class BookmarkThread(AggregateMessageQueuedThread, BookmarkUpdate):
26
26
  cleanup = set()
27
27
  for filters, updates in self.cache.items():
28
28
  try:
29
- auth_filter, comic_filter = filters
29
+ auth_filter, comic_pks = filters
30
30
  auth_filter = dict(auth_filter)
31
- comic_filter = dict(comic_filter)
32
- self.update_bookmarks(auth_filter, comic_filter, updates, self.log)
33
- self.log.debug(f"updated {auth_filter | comic_filter}")
31
+ self.update_bookmarks(auth_filter, comic_pks, updates, self.log)
32
+ self.log.debug(f"updated {auth_filter} pk__in={comic_pks}")
34
33
  cleanup.add(filters)
35
34
  except Exception:
36
35
  self.log.exception("Updating bookmarks")
@@ -15,5 +15,5 @@ class BookmarkUpdateTask(BookmarkTask):
15
15
  """Bookmark a page."""
16
16
 
17
17
  auth_filter: Mapping[str, int | str]
18
- comic_filter: Mapping[str, Any]
18
+ comic_pks: tuple[int]
19
19
  updates: Mapping[str, Any]
@@ -10,9 +10,8 @@ from codex.librarian.notifier.tasks import NotifierTask
10
10
  from codex.models import Bookmark, Comic
11
11
  from codex.models.admin import UserActive
12
12
  from codex.views.auth import GroupACLMixin
13
- from codex.views.mixins import BookmarkSearchMixin
14
13
 
15
- VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
14
+ _VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
16
15
  _BOOKMARK_UPDATE_FIELDS = frozenset(
17
16
  {
18
17
  "page",
@@ -22,10 +21,9 @@ _BOOKMARK_UPDATE_FIELDS = frozenset(
22
21
  "reading_direction",
23
22
  }
24
23
  )
25
- _COMIC_ONLY_FIELDS = ("pk", "page_count")
26
24
 
27
25
 
28
- class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
26
+ class BookmarkUpdate(GroupACLMixin):
29
27
  """Update Bookmarks."""
30
28
 
31
29
  # Used by Bookmarkd and view.bookmark.
@@ -47,10 +45,8 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
47
45
  def _update_bookmarks_validate_two_pages(bm, updates):
48
46
  """Force vertical view to not use two pages."""
49
47
  rd = updates.get("reading_direction")
50
- if (
51
- bm.two_pages
52
- and rd in VERTICAL_READING_DIRECTIONS
53
- or bm.reading_direction in VERTICAL_READING_DIRECTIONS
48
+ if bm.two_pages and bool(
49
+ {rd, bm.reading_direction}.intersection(_VERTICAL_READING_DIRECTIONS)
54
50
  ):
55
51
  updates["two_pages"] = None
56
52
 
@@ -62,13 +58,13 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
62
58
  LIBRARIAN_QUEUE.put(task)
63
59
 
64
60
  @classmethod
65
- def _update_bookmarks(cls, search_kwargs, updates, user) -> int:
61
+ def _update_bookmarks(cls, auth_filter, comic_pks, updates, user) -> int:
66
62
  """Update existing bookmarks."""
67
63
  count = 0
68
64
  if not updates:
69
65
  return count
70
66
  group_acl_filter = cls.get_group_acl_filter(Bookmark, user)
71
- query_filter = group_acl_filter & Q(**search_kwargs)
67
+ query_filter = group_acl_filter & Q(**auth_filter) & Q(comic__in=comic_pks)
72
68
  existing_bookmarks = Bookmark.objects.filter(query_filter)
73
69
  if updates.get("page") is not None:
74
70
  existing_bookmarks = existing_bookmarks.annotate(
@@ -98,12 +94,12 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
98
94
  """Force vertical view to not use two pages."""
99
95
  if (
100
96
  updates.get("two_pages")
101
- and updates.get("reading_direction") in VERTICAL_READING_DIRECTIONS
97
+ and updates.get("reading_direction") in _VERTICAL_READING_DIRECTIONS
102
98
  ):
103
99
  updates.pop("two_pages", None)
104
100
 
105
101
  @classmethod
106
- def _create_bookmarks(cls, comic_filter, search_kwargs, updates, user) -> int:
102
+ def _create_bookmarks(cls, auth_filter, comic_pks, updates, user) -> int:
107
103
  """Create new bookmarks for comics that don't exist yet."""
108
104
  count = 0
109
105
  cls._create_bookmarks_validate_two_pages(updates)
@@ -112,20 +108,12 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
112
108
  create_bookmarks = []
113
109
  group_acl_filter = cls.get_group_acl_filter(Comic, user)
114
110
  exclude = {}
115
- for key, value in search_kwargs.items():
111
+ for key, value in auth_filter.items():
116
112
  exclude["bookmark__" + key] = value
117
- query_filter = group_acl_filter & Q(**comic_filter) & ~Q(**exclude)
118
- create_bookmark_comics = Comic.objects.filter(query_filter).only(
119
- *_COMIC_ONLY_FIELDS
120
- )
113
+ query_filter = group_acl_filter & Q(pk__in=comic_pks) & ~Q(**exclude)
114
+ create_bookmark_comics = Comic.objects.filter(query_filter).only("pk")
121
115
  for comic in create_bookmark_comics:
122
- defaults = {"comic": comic}
123
- defaults.update(updates)
124
- for key, value in search_kwargs.items():
125
- if not key.startswith("comic"):
126
- defaults[key] = value
127
-
128
- bm = Bookmark(**defaults)
116
+ bm = Bookmark(comic=comic, **auth_filter, **updates)
129
117
  create_bookmarks.append(bm)
130
118
  count = len(create_bookmarks)
131
119
  if count:
@@ -147,18 +135,16 @@ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
147
135
  log.warning(reason)
148
136
 
149
137
  @classmethod
150
- def update_bookmarks(cls, auth_filter, comic_filter, updates, log):
138
+ def update_bookmarks(cls, auth_filter, comic_pks, updates, log):
151
139
  """Update a user bookmark."""
152
- search_kwargs = cls.get_bookmark_search_kwargs(auth_filter, comic_filter)
153
-
154
140
  user = None
155
141
  for key, value in auth_filter.items():
156
142
  if key == "user":
157
143
  user = User.objects.get(pk=value)
158
144
  break
159
145
 
160
- count = cls._update_bookmarks(search_kwargs, updates, user)
161
- count += cls._create_bookmarks(comic_filter, search_kwargs, updates, user)
146
+ count = cls._update_bookmarks(auth_filter, comic_pks, updates, user)
147
+ count += cls._create_bookmarks(auth_filter, comic_pks, updates, user)
162
148
  if user:
163
149
  cls._update_user_active(user, log)
164
150
  if count:
@@ -28,7 +28,7 @@ class CronThread(NamedThread):
28
28
  self._stop_event = Event()
29
29
  self._cond = Condition()
30
30
  self._task_times = ()
31
- super().__init__(*args, name=self.__class__.__name__, daemon=True, **kwargs)
31
+ super().__init__(*args, daemon=True, **kwargs)
32
32
 
33
33
  def _create_task_times(self):
34
34
  task_times = {}
@@ -96,7 +96,7 @@ class QueryForeignKeysImporter(QueryCustomCoversImporter):
96
96
  if extra_fields:
97
97
  fields += extra_fields
98
98
  flat = len(fields) == 1 and fk_cls != Publisher
99
- qs = fk_cls.objects.filter(fk_filter).values_list(*fields, flat=flat)
99
+ qs = fk_cls.objects.filter(fk_filter).distinct().values_list(*fields, flat=flat)
100
100
  return frozenset(qs)
101
101
 
102
102
  def _query_create_metadata( # noqa: PLR0913
@@ -449,7 +449,7 @@ class QueryForeignKeysImporter(QueryCustomCoversImporter):
449
449
 
450
450
  # Build combined query object from the value_filter
451
451
  model = DICT_MODEL_FIELD_MODEL_MAP[field_name]
452
- if model == Identifier:
452
+ if model in (Identifier, Contributor):
453
453
  for filter_and_prefix, value_filter in query_filter_map.items():
454
454
  self._query_create_metadata(
455
455
  model,
@@ -10,6 +10,7 @@ from codex.librarian.janitor.cleanup import TOTAL_NUM_FK_CLASSES, CleanupMixin
10
10
  from codex.librarian.janitor.failed_imports import UpdateFailedImportsMixin
11
11
  from codex.librarian.janitor.integrity import IntegrityMixin
12
12
  from codex.librarian.janitor.latest_version import LatestVersionMixin
13
+ from codex.librarian.janitor.log_compress import LogCompressMixin
13
14
  from codex.librarian.janitor.status import JanitorStatusTypes
14
15
  from codex.librarian.janitor.tasks import (
15
16
  ForceUpdateAllFailedImportsTask,
@@ -20,6 +21,7 @@ from codex.librarian.janitor.tasks import (
20
21
  JanitorCleanupBookmarksTask,
21
22
  JanitorCleanupSessionsTask,
22
23
  JanitorClearStatusTask,
24
+ JanitorCompressOldLogs,
23
25
  JanitorForeignKeyCheck,
24
26
  JanitorFTSIntegrityCheck,
25
27
  JanitorFTSRebuildTask,
@@ -61,6 +63,7 @@ _JANITOR_STATII = (
61
63
  Status(SearchIndexStatusTypes.SEARCH_INDEX_UPDATE),
62
64
  Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
63
65
  Status(SearchIndexStatusTypes.SEARCH_INDEX_OPTIMIZE),
66
+ Status(JanitorStatusTypes.COMPRESS_LOGS),
64
67
  )
65
68
 
66
69
 
@@ -71,6 +74,7 @@ class Janitor(
71
74
  UpdateFailedImportsMixin,
72
75
  VacuumMixin,
73
76
  IntegrityMixin,
77
+ LogCompressMixin,
74
78
  ):
75
79
  """Janitor inline task runner."""
76
80
 
@@ -149,9 +153,15 @@ class Janitor(
149
153
  for next_task in next_tasks:
150
154
  self.librarian_queue.put(next_task)
151
155
  case JanitorSearchOptimizeFinishedTask():
152
- next_tasks = (JanitorVacuumTask(), JanitorBackupTask())
156
+ next_tasks = (
157
+ JanitorVacuumTask(),
158
+ JanitorBackupTask(),
159
+ JanitorCompressOldLogs(),
160
+ )
153
161
  for next_task in next_tasks:
154
162
  self.librarian_queue.put(next_task)
163
+ case JanitorCompressOldLogs():
164
+ self.log_compress()
155
165
  case _:
156
166
  self.log.warning(f"Janitor received unknown task {task}")
157
167
  except Exception:
@@ -0,0 +1,77 @@
1
+ """Rotate logs."""
2
+
3
+ from dataclasses import dataclass
4
+ from lzma import LZMAFile
5
+ from pathlib import Path
6
+
7
+ from humanize import naturalsize
8
+
9
+ from codex.librarian.janitor.status import JanitorStatusTypes
10
+ from codex.settings.settings import LOG_DIR
11
+ from codex.status import Status
12
+ from codex.worker_base import WorkerBaseMixin
13
+
14
+ _LOG_GLOB = "codex.log.*"
15
+
16
+
17
+ @dataclass
18
+ class LogStats:
19
+ """Collect stats for compressed logs."""
20
+
21
+ count = 0
22
+ old_size = 0
23
+ new_size = 0
24
+
25
+
26
+ class LogCompressMixin(WorkerBaseMixin):
27
+ """Rotate logs with janitor."""
28
+
29
+ @staticmethod
30
+ def _get_old_log_paths():
31
+ """Get old log paths to compress and sort them by reverse age."""
32
+ old_log_paths = {}
33
+ for path_str in LOG_DIR.glob(_LOG_GLOB):
34
+ old_path = Path(path_str)
35
+ try:
36
+ log_number = int(old_path.suffix[1:])
37
+ if log_number == 1:
38
+ continue
39
+ except ValueError:
40
+ continue
41
+ old_log_paths[log_number] = old_path
42
+ return dict(sorted(old_log_paths.items(), reverse=True)).values()
43
+
44
+ @staticmethod
45
+ def _rotate_log(old_path, stats):
46
+ """Rotate one log file."""
47
+ new_path = Path(str(old_path) + ".xz")
48
+ if new_path.exists():
49
+ return
50
+ with (
51
+ LZMAFile(new_path, mode="w", preset=9) as log_archive,
52
+ old_path.open("r") as log_file,
53
+ ):
54
+ data = log_file.read()
55
+ log_archive.write(data.encode())
56
+ stats.count += 1
57
+ stats.old_size += old_path.stat().st_size
58
+ stats.new_size += new_path.stat().st_size
59
+ if new_path.exists():
60
+ old_path.unlink(missing_ok=True)
61
+
62
+ def log_compress(self):
63
+ """Compress old log files and report on savings."""
64
+ status = Status(JanitorStatusTypes.COMPRESS_LOGS)
65
+ try:
66
+ self.status_controller.start(status)
67
+ old_log_paths = self._get_old_log_paths()
68
+ stats = LogStats()
69
+ for old_path in old_log_paths:
70
+ self._rotate_log(old_path, stats)
71
+ if stats.count:
72
+ saved = naturalsize(stats.old_size - stats.new_size)
73
+ self.log.info(f"Compressed logs. Saved {saved}.")
74
+ else:
75
+ self.log.debug("No logs compressed.")
76
+ finally:
77
+ self.status_controller.finish(status)
@@ -20,3 +20,4 @@ class JanitorStatusTypes(Choices):
20
20
  INTEGRITY_CHECK = "JIC"
21
21
  FTS_INTEGRITY_CHECK = "JFC"
22
22
  FTS_REBUILD = "JFR"
23
+ COMPRESS_LOGS = "JCL"
@@ -107,3 +107,8 @@ class JanitorAdoptOrphanFoldersFinishedTask(JanitorTask):
107
107
  @dataclass
108
108
  class JanitorSearchOptimizeFinishedTask(JanitorTask):
109
109
  """Signals to the Janitor that the Search Optimize task is finished."""
110
+
111
+
112
+ @dataclass
113
+ class JanitorCompressOldLogs(JanitorTask):
114
+ """Compress old logs."""
@@ -42,6 +42,7 @@ from codex.librarian.watchdog.tasks import (
42
42
  WatchdogSyncTask,
43
43
  )
44
44
  from codex.logger_base import LoggerBaseMixin
45
+ from codex.settings.settings import SET_PROC_TITLE
45
46
 
46
47
 
47
48
  class LibrarianDaemon(Process, LoggerBaseMixin):
@@ -121,7 +122,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
121
122
  case DelayedTasks():
122
123
  self._threads.delayed_tasks_thread.queue.put(task)
123
124
  case LibrarianShutdownTask():
124
- self.log.info(f"Shutting down {self.__class__.__name__}...")
125
+ self.log.info(f"Shutting down {self.name}...")
125
126
  self.run_loop = False
126
127
  case _:
127
128
  self.log.warning(f"Unhandled Librarian task: {task}")
@@ -150,34 +151,38 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
150
151
 
151
152
  def _start_threads(self):
152
153
  """Start all librarian's threads."""
153
- self.log.debug(f"{self.__class__.__name__} starting all threads.")
154
+ self.log.debug(f"{self.name} starting all threads.")
154
155
  for thread in self._threads:
155
156
  thread.start()
156
- self.log.info(f"{self.__class__.__name__} started all threads.")
157
+ self.log.info(f"{self.name} started all threads.")
157
158
 
158
159
  def _startup(self):
159
160
  """Initialize threads."""
161
+ if SET_PROC_TITLE:
162
+ from setproctitle import setproctitle
163
+
164
+ setproctitle("Codex" + self.name)
160
165
  self.init_logger(self.log_queue)
161
- self.log.debug(f"Started {self.__class__.__name__}.")
166
+ self.log.debug(f"Started {self.name}.")
162
167
  self.janitor = Janitor(self.log_queue, self.queue)
163
168
  self._create_threads() # can't do this in init.
164
169
  self._start_threads()
165
170
  self.run_loop = True
166
- self.log.info(f"{self.__class__.__name__} ready for tasks.")
171
+ self.log.info(f"{self.name} ready for tasks.")
167
172
 
168
173
  def _stop_threads(self):
169
174
  """Stop all librarian's threads."""
170
- self.log.debug(f"{self.__class__.__name__} stopping all threads...")
175
+ self.log.debug(f"{self.name} stopping all threads...")
171
176
  for thread in self._reversed_threads:
172
177
  thread.stop()
173
- self.log.debug(f"{self.__class__.__name__} stopped all threads.")
178
+ self.log.debug(f"{self.name} stopped all threads.")
174
179
 
175
180
  def _join_threads(self):
176
181
  """Join all librarian threads."""
177
- self.log.debug(f"{self.__class__.__name__} joining all threads...")
182
+ self.log.debug(f"{self.name} joining all threads...")
178
183
  for thread in self._reversed_threads:
179
184
  thread.join()
180
- self.log.info(f"{self.__class__.__name__} joined all threads.")
185
+ self.log.info(f"{self.name} joined all threads.")
181
186
 
182
187
  def _shutdown(self):
183
188
  """Shutdown threads and queues."""
@@ -188,7 +193,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
188
193
  self.queue.get_nowait()
189
194
  self.queue.close()
190
195
  self.queue.join_thread()
191
- self.log.info(f"{self.__class__.__name__} finished.")
196
+ self.log.info(f"{self.name} finished.")
192
197
  self.log_queue.close()
193
198
  self.log_queue.join_thread()
194
199
 
@@ -205,11 +210,11 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
205
210
  task = self.queue.get()
206
211
  self._process_task(task)
207
212
  except Exception:
208
- self.log.exception(f"In {self.__class__.__name__} loop")
213
+ self.log.exception(f"In {self.name} loop")
209
214
  except Exception:
210
- self.log.exception(f"{self.__class__.__name__} crashed.")
215
+ self.log.exception(f"{self.name} crashed.")
211
216
  except KeyboardInterrupt:
212
- self.log.debug(f"{self.__class__.__name__} Keyboard interrupt")
217
+ self.log.debug(f"{self.name} Keyboard interrupt")
213
218
  finally:
214
219
  self._shutdown()
215
220
 
@@ -114,7 +114,7 @@ class FTSUpdateMixin(RemoveMixin):
114
114
  return ""
115
115
  return ",".join((iso_code, field_instance.to_representation(iso_code)))
116
116
 
117
- def _get_comicts_fts_list(
117
+ def _get_comics_fts_list(
118
118
  self, comics, country_field, language_field, obj_list, create
119
119
  ):
120
120
  """Prepare a batch of search entries."""
@@ -157,6 +157,7 @@ class FTSUpdateMixin(RemoveMixin):
157
157
  if create:
158
158
  comicfts.created_at = now
159
159
  obj_list.append(comicfts)
160
+ self.log.debug(f"{len(obj_list)}/{comics.count()} entries prepped.")
160
161
 
161
162
  def _get_comicfts_list(self, comics, create=False):
162
163
  """Create a ComicFTS object for bulk_create or bulk_update."""
@@ -164,7 +165,7 @@ class FTSUpdateMixin(RemoveMixin):
164
165
  language_field = LanguageField()
165
166
  obj_list = []
166
167
  comics = self._annotate_fts_query(comics)
167
- self._get_comicts_fts_list(
168
+ self._get_comics_fts_list(
168
169
  comics, country_field, language_field, obj_list, create
169
170
  )
170
171
  return obj_list
@@ -205,11 +206,14 @@ class FTSUpdateMixin(RemoveMixin):
205
206
  batch_start = time()
206
207
  batch_to = batch_from + SEARCH_INDEX_BATCH_SIZE
207
208
  comics_batch = comics_qs[batch_from:batch_to]
208
-
209
+ self.log.debug(
210
+ f"Checking {comics_batch.count()}/{count} comics for search index operations..."
211
+ )
209
212
  if self.abort_event.is_set():
210
213
  break
211
214
  operate_comicfts = self._get_comicfts_list(comics_batch, create)
212
215
  operate_comicfts_count = len(operate_comicfts)
216
+ self.log.debug(f"{verb}ing {operate_comicfts_count} search entries...")
213
217
  if self.abort_event.is_set():
214
218
  break
215
219
  if create:
@@ -223,7 +227,7 @@ class FTSUpdateMixin(RemoveMixin):
223
227
  batch_time = time() - batch_start
224
228
  eps = round(count / batch_time)
225
229
  self.log.debug(
226
- f"{verb} {count}/{operate_comicfts_count} search entries in {batch_time}, {eps} per second."
230
+ f"{verb} {operate_comicfts_count}/{count} search entries in {batch_time}, {eps} per second."
227
231
  )
228
232
  batch_from = batch_to
229
233
  finally:
@@ -0,0 +1,50 @@
1
+ """Generated by Django 5.1.2 on 2024-10-22 03:09."""
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ """Migrate db."""
8
+
9
+ dependencies = [
10
+ ("codex", "0030_nocase_collation_day_month_indexes_status_types"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name="adminflag",
16
+ name="value",
17
+ field=models.CharField(blank=True, default="", max_length=32),
18
+ ),
19
+ migrations.AlterField(
20
+ model_name="adminflag",
21
+ name="key",
22
+ field=models.CharField(
23
+ choices=[
24
+ ("FV", "Folder View"),
25
+ ("RG", "Registration"),
26
+ ("NU", "Non Users"),
27
+ ("AU", "Auto Update"),
28
+ ("IM", "Import Metadata"),
29
+ ("ST", "Send Telemetry"),
30
+ ("BT", "Banner Text"),
31
+ ],
32
+ db_index=True,
33
+ max_length=2,
34
+ ),
35
+ ),
36
+ migrations.AlterField(
37
+ model_name="timestamp",
38
+ name="key",
39
+ field=models.CharField(
40
+ choices=[
41
+ ("AP", "API Key"),
42
+ ("VR", "Codex Version"),
43
+ ("JA", "Janitor"),
44
+ ("TS", "Telemeter Sent"),
45
+ ],
46
+ db_index=True,
47
+ max_length=2,
48
+ ),
49
+ ),
50
+ ]
codex/models/admin.py CHANGED
@@ -39,6 +39,7 @@ class AdminFlag(BaseModel):
39
39
  AUTO_UPDATE = "AU"
40
40
  IMPORT_METADATA = "IM"
41
41
  SEND_TELEMETRY = "ST"
42
+ BANNER_TEXT = "BT"
42
43
 
43
44
  FALSE_DEFAULTS = frozenset({FlagChoices.AUTO_UPDATE})
44
45
 
@@ -48,6 +49,7 @@ class AdminFlag(BaseModel):
48
49
  choices=FlagChoices.choices,
49
50
  )
50
51
  on = BooleanField(default=True)
52
+ value = CharField(max_length=32, default="", blank=True)
51
53
 
52
54
  class Meta(BaseModel.Meta):
53
55
  """Constraints."""
@@ -88,10 +90,9 @@ class Timestamp(BaseModel):
88
90
  class TimestampChoices(TextChoices):
89
91
  """Choices for Timestamps."""
90
92
 
91
- JANITOR = "JA", _("Janitor")
92
- CODEX_VERSION = "VR", _("Codex Version")
93
- SEARCH_INDEX_UUID = "SI", _("Search Index UUID")
94
93
  API_KEY = "AP", _("API Key")
94
+ CODEX_VERSION = "VR", _("Codex Version")
95
+ JANITOR = "JA", _("Janitor")
95
96
  TELEMETER_SENT = "TS", _("Telemeter Sent")
96
97
 
97
98
  key = CharField(
@@ -11,5 +11,5 @@ class AdminFlagSerializer(BaseModelSerializer):
11
11
  """Specify Model."""
12
12
 
13
13
  model = AdminFlag
14
- fields = ("key", "on")
14
+ fields = ("key", "on", "value")
15
15
  read_only_fields = ("key",)
codex/serializers/auth.py CHANGED
@@ -78,3 +78,4 @@ class AuthAdminFlagsSerializer(Serializer):
78
78
 
79
79
  non_users = BooleanField(read_only=True)
80
80
  registration = BooleanField(read_only=True)
81
+ banner_text = CharField(read_only=True)
@@ -0,0 +1 @@
1
+ """OPDS v2.0 Serializers."""
@@ -0,0 +1,13 @@
1
+ """OPDS v2.0 Facet Serializer."""
2
+
3
+ from rest_framework.serializers import Serializer
4
+
5
+ from codex.serializers.opds.v2.links import OPDS2LinkListField
6
+ from codex.serializers.opds.v2.metadata import OPDS2MetadataSerializer
7
+
8
+
9
+ class OPDS2FacetSerializer(Serializer):
10
+ """Facets."""
11
+
12
+ metadata = OPDS2MetadataSerializer(read_only=True)
13
+ links = OPDS2LinkListField(read_only=True)
@@ -0,0 +1,35 @@
1
+ """OPDS v2.0 Feed Serializer."""
2
+
3
+ from rest_framework.fields import (
4
+ ListField,
5
+ )
6
+ from rest_framework.serializers import Serializer
7
+
8
+ from codex.serializers.opds.v2.facet import OPDS2FacetSerializer
9
+ from codex.serializers.opds.v2.links import OPDS2LinkListField
10
+ from codex.serializers.opds.v2.publication import (
11
+ OPDS2MetadataSerializer,
12
+ OPDS2PublicationSerializer,
13
+ )
14
+
15
+
16
+ class OPDS2GroupSerializer(Serializer):
17
+ """Group."""
18
+
19
+ metadata = OPDS2MetadataSerializer(read_only=True)
20
+ links = OPDS2LinkListField(read_only=True, required=False)
21
+ publications = ListField(
22
+ child=OPDS2PublicationSerializer(), read_only=True, required=False
23
+ )
24
+ navigation = OPDS2LinkListField(read_only=True, required=False)
25
+
26
+
27
+ class OPDS2FeedSerializer(OPDS2GroupSerializer):
28
+ """Feed.
29
+
30
+ https://drafts.opds.io/schema/feed.schema.json
31
+ https://readium.org/webpub-manifest/schema/subcollection.schema.json
32
+ """
33
+
34
+ facets = ListField(child=OPDS2FacetSerializer(), read_only=True, required=False)
35
+ groups = ListField(child=OPDS2GroupSerializer(), read_only=True, required=False)