codex 1.7.3a2__py3-none-any.whl → 1.7.5__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 (632) hide show
  1. codex/asgi.py +2 -1
  2. codex/choices/__init__.py +1 -0
  3. codex/{choices.py → choices/admin.py} +47 -146
  4. codex/choices/browser.py +85 -0
  5. codex/{choices_to_json.py → choices/choices_to_json.py} +25 -19
  6. codex/choices/notifications.py +16 -0
  7. codex/choices/reader.py +33 -0
  8. codex/db.py +2 -2
  9. codex/exceptions.py +7 -8
  10. codex/librarian/bookmark/bookmarkd.py +8 -4
  11. codex/librarian/bookmark/update.py +84 -49
  12. codex/librarian/covers/create.py +10 -8
  13. codex/librarian/covers/path.py +6 -5
  14. codex/librarian/covers/purge.py +4 -4
  15. codex/librarian/importer/aggregate.py +2 -2
  16. codex/librarian/importer/cache.py +1 -4
  17. codex/librarian/importer/create_comics.py +2 -2
  18. codex/librarian/importer/create_covers.py +1 -1
  19. codex/librarian/importer/create_fks.py +4 -3
  20. codex/librarian/importer/deleted.py +3 -3
  21. codex/librarian/importer/extract.py +56 -37
  22. codex/librarian/importer/failed_imports.py +1 -1
  23. codex/librarian/importer/importer.py +8 -3
  24. codex/librarian/importer/importerd.py +3 -3
  25. codex/librarian/importer/link_comics.py +2 -1
  26. codex/librarian/importer/moved.py +2 -7
  27. codex/librarian/importer/query_fks.py +1 -4
  28. codex/librarian/importer/tasks.py +1 -1
  29. codex/librarian/janitor/integrity.py +27 -20
  30. codex/librarian/janitor/janitor.py +1 -1
  31. codex/librarian/janitor/latest_version.py +1 -1
  32. codex/librarian/janitor/update.py +5 -2
  33. codex/librarian/janitor/vacuum.py +1 -1
  34. codex/librarian/librariand.py +3 -2
  35. codex/librarian/notifier/notifierd.py +2 -1
  36. codex/librarian/notifier/tasks.py +17 -3
  37. codex/librarian/search/optimize.py +1 -1
  38. codex/librarian/search/remove.py +1 -1
  39. codex/librarian/search/update.py +4 -4
  40. codex/librarian/telemeter/stats.py +7 -6
  41. codex/librarian/telemeter/telemeter.py +0 -1
  42. codex/librarian/watchdog/db_snapshot.py +3 -3
  43. codex/librarian/watchdog/dir_snapshot_diff.py +3 -4
  44. codex/librarian/watchdog/emitter.py +4 -4
  45. codex/librarian/watchdog/event_batcherd.py +2 -1
  46. codex/librarian/watchdog/events.py +1 -2
  47. codex/librarian/watchdog/observers.py +15 -10
  48. codex/logger/logging.py +1 -1
  49. codex/memory.py +3 -2
  50. codex/migrations/0001_init.py +1 -2
  51. codex/migrations/0014_pdf_issue_suffix_remove_cover_image_sort_name.py +1 -2
  52. codex/migrations/0018_rename_userbookmark_bookmark.py +1 -2
  53. codex/migrations/0028_telemeter.py +1 -5
  54. codex/models/base.py +3 -1
  55. codex/models/bookmark.py +4 -7
  56. codex/models/comic.py +2 -2
  57. codex/models/functions.py +12 -5
  58. codex/models/groups.py +1 -1
  59. codex/models/library.py +1 -1
  60. codex/models/paths.py +4 -4
  61. codex/models/query.py +8 -4
  62. codex/models/util.py +1 -1
  63. codex/permissions.py +1 -1
  64. codex/run.py +1 -1
  65. codex/serializers/admin/libraries.py +1 -3
  66. codex/serializers/admin/stats.py +4 -4
  67. codex/serializers/admin/tasks.py +12 -2
  68. codex/serializers/auth.py +4 -2
  69. codex/serializers/browser/choices.py +87 -9
  70. codex/serializers/browser/filters.py +9 -37
  71. codex/serializers/browser/metadata.py +2 -1
  72. codex/serializers/browser/mixins.py +7 -3
  73. codex/serializers/browser/mtime.py +3 -5
  74. codex/serializers/browser/page.py +5 -4
  75. codex/serializers/browser/settings.py +8 -6
  76. codex/serializers/fields/__init__.py +37 -0
  77. codex/serializers/fields/auth.py +45 -0
  78. codex/serializers/fields/browser.py +85 -0
  79. codex/serializers/fields/group.py +13 -0
  80. codex/serializers/fields/reader.py +22 -0
  81. codex/serializers/fields/sanitized.py +13 -0
  82. codex/serializers/fields/session.py +16 -0
  83. codex/serializers/fields/stats.py +36 -0
  84. codex/serializers/fields/vuetify.py +62 -0
  85. codex/serializers/models/base.py +1 -1
  86. codex/serializers/models/comic.py +2 -2
  87. codex/serializers/models/groups.py +0 -2
  88. codex/serializers/models/named.py +1 -1
  89. codex/serializers/models/pycountry.py +1 -1
  90. codex/serializers/opds/authentication.py +4 -2
  91. codex/serializers/opds/urls.py +2 -1
  92. codex/serializers/opds/v1.py +4 -3
  93. codex/serializers/opds/v2/feed.py +2 -1
  94. codex/serializers/opds/v2/links.py +23 -21
  95. codex/serializers/opds/v2/metadata.py +4 -8
  96. codex/serializers/opds/v2/progression.py +5 -3
  97. codex/serializers/opds/v2/publication.py +16 -18
  98. codex/serializers/opds/v2/unused.py +15 -8
  99. codex/serializers/reader.py +16 -14
  100. codex/serializers/redirect.py +2 -1
  101. codex/serializers/route.py +6 -4
  102. codex/serializers/settings.py +5 -3
  103. codex/serializers/versions.py +2 -1
  104. codex/settings/settings.py +4 -3
  105. codex/signals/django_signals.py +1 -1
  106. codex/startup.py +2 -2
  107. codex/static_root/assets/{VCheckbox-FaT6MGfu.f6732060f734.js → VCheckbox-CWBDr4kF.6da5bf3a4d90.js} +1 -1
  108. codex/static_root/assets/VCheckbox-CWBDr4kF.6da5bf3a4d90.js.br +0 -0
  109. codex/static_root/assets/VCheckbox-CWBDr4kF.6da5bf3a4d90.js.gz +0 -0
  110. codex/static_root/assets/{VCheckbox-FaT6MGfu.js → VCheckbox-CWBDr4kF.js} +1 -1
  111. codex/static_root/assets/VCheckbox-CWBDr4kF.js.br +0 -0
  112. codex/static_root/assets/VCheckbox-CWBDr4kF.js.gz +0 -0
  113. codex/static_root/assets/{VCheckboxBtn-CZ_P-qUM.847452d574cf.js → VCheckboxBtn-COaoh3uF.fc880a386827.js} +1 -1
  114. codex/static_root/assets/VCheckboxBtn-COaoh3uF.fc880a386827.js.br +0 -0
  115. codex/static_root/assets/VCheckboxBtn-COaoh3uF.fc880a386827.js.gz +0 -0
  116. codex/static_root/assets/{VCheckboxBtn-CZ_P-qUM.js → VCheckboxBtn-COaoh3uF.js} +1 -1
  117. codex/static_root/assets/VCheckboxBtn-COaoh3uF.js.br +0 -0
  118. codex/static_root/assets/VCheckboxBtn-COaoh3uF.js.gz +0 -0
  119. codex/static_root/assets/{VCombobox-C8QLNlSl.6f431e19a453.js → VCombobox-DkOruNH0.f30f163ee632.js} +1 -1
  120. codex/static_root/assets/VCombobox-DkOruNH0.f30f163ee632.js.br +0 -0
  121. codex/static_root/assets/VCombobox-DkOruNH0.f30f163ee632.js.gz +0 -0
  122. codex/static_root/assets/{VCombobox-C8QLNlSl.js → VCombobox-DkOruNH0.js} +1 -1
  123. codex/static_root/assets/VCombobox-DkOruNH0.js.br +0 -0
  124. codex/static_root/assets/VCombobox-DkOruNH0.js.gz +0 -0
  125. codex/static_root/assets/{VDialog-RVLeW7je.c0c7eef71eec.js → VDialog-Dr7SuC2S.b6ec7df390f4.js} +1 -1
  126. codex/static_root/assets/VDialog-Dr7SuC2S.b6ec7df390f4.js.br +0 -0
  127. codex/static_root/assets/VDialog-Dr7SuC2S.b6ec7df390f4.js.gz +0 -0
  128. codex/static_root/assets/{VDialog-RVLeW7je.js → VDialog-Dr7SuC2S.js} +1 -1
  129. codex/static_root/assets/VDialog-Dr7SuC2S.js.br +0 -0
  130. codex/static_root/assets/VDialog-Dr7SuC2S.js.gz +0 -0
  131. codex/static_root/assets/{VDivider-D1Ot4vR2.9dbee744fb3c.js → VDivider-By4u2EsM.fd5ee9cce906.js} +1 -1
  132. codex/static_root/assets/VDivider-By4u2EsM.fd5ee9cce906.js.br +0 -0
  133. codex/static_root/assets/VDivider-By4u2EsM.fd5ee9cce906.js.gz +0 -0
  134. codex/static_root/assets/{VDivider-D1Ot4vR2.js → VDivider-By4u2EsM.js} +1 -1
  135. codex/static_root/assets/VDivider-By4u2EsM.js.br +0 -0
  136. codex/static_root/assets/VDivider-By4u2EsM.js.gz +0 -0
  137. codex/static_root/assets/{VExpansionPanels-DQilGHZk.43f06d0a45e0.js → VExpansionPanels-BhO9oj0a.e99bea455c78.js} +1 -1
  138. codex/static_root/assets/VExpansionPanels-BhO9oj0a.e99bea455c78.js.br +0 -0
  139. codex/static_root/assets/VExpansionPanels-BhO9oj0a.e99bea455c78.js.gz +0 -0
  140. codex/static_root/assets/{VExpansionPanels-DQilGHZk.js → VExpansionPanels-BhO9oj0a.js} +1 -1
  141. codex/static_root/assets/VExpansionPanels-BhO9oj0a.js.br +0 -0
  142. codex/static_root/assets/VExpansionPanels-BhO9oj0a.js.gz +0 -0
  143. codex/static_root/assets/VForm-XhA6T0Lc.70c9c6fcb9e3.js +1 -0
  144. codex/static_root/assets/VForm-XhA6T0Lc.70c9c6fcb9e3.js.br +0 -0
  145. codex/static_root/assets/VForm-XhA6T0Lc.70c9c6fcb9e3.js.gz +0 -0
  146. codex/static_root/assets/VForm-XhA6T0Lc.js +1 -0
  147. codex/static_root/assets/VForm-XhA6T0Lc.js.br +0 -0
  148. codex/static_root/assets/VForm-XhA6T0Lc.js.gz +0 -0
  149. codex/static_root/assets/{VRadioGroup-DoayJpcS.fe5e0e74ffee.js → VRadioGroup-C1WCG0rO.6ebc88e4f144.js} +1 -1
  150. codex/static_root/assets/VRadioGroup-C1WCG0rO.6ebc88e4f144.js.br +3 -0
  151. codex/static_root/assets/VRadioGroup-C1WCG0rO.6ebc88e4f144.js.gz +0 -0
  152. codex/static_root/assets/{VRadioGroup-DoayJpcS.js → VRadioGroup-C1WCG0rO.js} +1 -1
  153. codex/static_root/assets/VRadioGroup-C1WCG0rO.js.br +3 -0
  154. codex/static_root/assets/VRadioGroup-C1WCG0rO.js.gz +0 -0
  155. codex/static_root/assets/VSelect-Bbt1vrBg.ec45ee26818a.js +1 -0
  156. codex/static_root/assets/VSelect-Bbt1vrBg.ec45ee26818a.js.br +0 -0
  157. codex/static_root/assets/VSelect-Bbt1vrBg.ec45ee26818a.js.gz +0 -0
  158. codex/static_root/assets/VSelect-Bbt1vrBg.js +1 -0
  159. codex/static_root/assets/VSelect-Bbt1vrBg.js.br +0 -0
  160. codex/static_root/assets/VSelect-Bbt1vrBg.js.gz +0 -0
  161. codex/static_root/assets/{VSelectionControl-TaKCeZgb.ad6c96efe57c.js → VSelectionControl-BkZZAsWo.ba6eca944e2e.js} +1 -1
  162. codex/static_root/assets/VSelectionControl-BkZZAsWo.ba6eca944e2e.js.br +0 -0
  163. codex/static_root/assets/VSelectionControl-BkZZAsWo.ba6eca944e2e.js.gz +0 -0
  164. codex/static_root/assets/{VSelectionControl-TaKCeZgb.js → VSelectionControl-BkZZAsWo.js} +1 -1
  165. codex/static_root/assets/VSelectionControl-BkZZAsWo.js.br +0 -0
  166. codex/static_root/assets/VSelectionControl-BkZZAsWo.js.gz +0 -0
  167. codex/static_root/assets/{VTable-BfcOiEpa.77ef3973bb54.js → VTable-D7P2eIc2.5e94bf2a515b.js} +1 -1
  168. codex/static_root/assets/VTable-D7P2eIc2.5e94bf2a515b.js.br +0 -0
  169. codex/static_root/assets/VTable-D7P2eIc2.5e94bf2a515b.js.gz +0 -0
  170. codex/static_root/assets/{VTable-BfcOiEpa.js → VTable-D7P2eIc2.js} +1 -1
  171. codex/static_root/assets/VTable-D7P2eIc2.js.br +0 -0
  172. codex/static_root/assets/VTable-D7P2eIc2.js.gz +0 -0
  173. codex/static_root/assets/VWindowItem-Dm2szrjK.e77444188aa3.js +1 -0
  174. codex/static_root/assets/VWindowItem-Dm2szrjK.e77444188aa3.js.br +0 -0
  175. codex/static_root/assets/VWindowItem-Dm2szrjK.e77444188aa3.js.gz +0 -0
  176. codex/static_root/assets/VWindowItem-Dm2szrjK.js +1 -0
  177. codex/static_root/assets/VWindowItem-Dm2szrjK.js.br +0 -0
  178. codex/static_root/assets/VWindowItem-Dm2szrjK.js.gz +0 -0
  179. codex/static_root/assets/{admin-80eqCqtz.d6deb9edb8cb.js → admin-B4z2nA5P.da0c92500311.js} +1 -1
  180. codex/static_root/assets/admin-B4z2nA5P.da0c92500311.js.br +0 -0
  181. codex/static_root/assets/admin-B4z2nA5P.da0c92500311.js.gz +0 -0
  182. codex/static_root/assets/{admin-80eqCqtz.js → admin-B4z2nA5P.js} +1 -1
  183. codex/static_root/assets/admin-B4z2nA5P.js.br +0 -0
  184. codex/static_root/assets/admin-B4z2nA5P.js.gz +0 -0
  185. codex/static_root/assets/admin-BhW3PNO0.e444048e548d.js +1 -0
  186. codex/static_root/assets/admin-BhW3PNO0.e444048e548d.js.br +0 -0
  187. codex/static_root/assets/admin-BhW3PNO0.e444048e548d.js.gz +0 -0
  188. codex/static_root/assets/admin-BhW3PNO0.js +1 -0
  189. codex/static_root/assets/admin-BhW3PNO0.js.br +0 -0
  190. codex/static_root/assets/admin-BhW3PNO0.js.gz +0 -0
  191. codex/static_root/assets/{admin-menu-_aAGknKO.0c08c9bd2e7d.js → admin-menu-CL7S-xqR.92b6d84c4b70.js} +1 -1
  192. codex/static_root/assets/admin-menu-CL7S-xqR.92b6d84c4b70.js.br +0 -0
  193. codex/static_root/assets/admin-menu-CL7S-xqR.92b6d84c4b70.js.gz +0 -0
  194. codex/static_root/assets/{admin-menu-_aAGknKO.js → admin-menu-CL7S-xqR.js} +1 -1
  195. codex/static_root/assets/admin-menu-CL7S-xqR.js.br +0 -0
  196. codex/static_root/assets/admin-menu-CL7S-xqR.js.gz +0 -0
  197. codex/static_root/assets/{admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js → admin-settings-button-progress-BgBgdELw.52d052e38bc1.js} +1 -1
  198. codex/static_root/assets/admin-settings-button-progress-BgBgdELw.52d052e38bc1.js.br +0 -0
  199. codex/static_root/assets/admin-settings-button-progress-BgBgdELw.52d052e38bc1.js.gz +0 -0
  200. codex/static_root/assets/{admin-settings-button-progress-BqRFyhjf.js → admin-settings-button-progress-BgBgdELw.js} +1 -1
  201. codex/static_root/assets/admin-settings-button-progress-BgBgdELw.js.br +0 -0
  202. codex/static_root/assets/admin-settings-button-progress-BgBgdELw.js.gz +0 -0
  203. codex/static_root/assets/browser-Bi5ov5k8.a4af87c2a667.js +1 -0
  204. codex/static_root/assets/browser-Bi5ov5k8.a4af87c2a667.js.br +0 -0
  205. codex/static_root/assets/browser-Bi5ov5k8.a4af87c2a667.js.gz +0 -0
  206. codex/static_root/assets/browser-Bi5ov5k8.js +1 -0
  207. codex/static_root/assets/browser-Bi5ov5k8.js.br +0 -0
  208. codex/static_root/assets/browser-Bi5ov5k8.js.gz +0 -0
  209. codex/static_root/assets/{browser-chTZ1CM4.521193ab0710.css → browser-DNzQvgkY.aea8b7d7ca40.css} +1 -1
  210. codex/static_root/assets/browser-DNzQvgkY.aea8b7d7ca40.css.br +0 -0
  211. codex/static_root/assets/browser-DNzQvgkY.aea8b7d7ca40.css.gz +0 -0
  212. codex/static_root/assets/{browser-chTZ1CM4.css → browser-DNzQvgkY.css} +1 -1
  213. codex/static_root/assets/browser-DNzQvgkY.css.br +0 -0
  214. codex/static_root/assets/browser-DNzQvgkY.css.gz +0 -0
  215. codex/static_root/assets/{change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js → change-password-dialog-BKar2Bhb.3dbc2feb3c5a.js} +1 -1
  216. codex/static_root/assets/change-password-dialog-BKar2Bhb.3dbc2feb3c5a.js.br +0 -0
  217. codex/static_root/assets/change-password-dialog-BKar2Bhb.3dbc2feb3c5a.js.gz +0 -0
  218. codex/static_root/assets/{change-password-dialog-1ZVP_Q2w.js → change-password-dialog-BKar2Bhb.js} +1 -1
  219. codex/static_root/assets/change-password-dialog-BKar2Bhb.js.br +0 -0
  220. codex/static_root/assets/change-password-dialog-BKar2Bhb.js.gz +0 -0
  221. codex/static_root/assets/{confirm-dialog-DqCey9Iq.e315d7f8f752.js → confirm-dialog-BoBL4OoS.abef9614fea9.js} +1 -1
  222. codex/static_root/assets/confirm-dialog-BoBL4OoS.abef9614fea9.js.br +0 -0
  223. codex/static_root/assets/confirm-dialog-BoBL4OoS.abef9614fea9.js.gz +0 -0
  224. codex/static_root/assets/{confirm-dialog-DqCey9Iq.js → confirm-dialog-BoBL4OoS.js} +1 -1
  225. codex/static_root/assets/confirm-dialog-BoBL4OoS.js.br +0 -0
  226. codex/static_root/assets/confirm-dialog-BoBL4OoS.js.gz +0 -0
  227. codex/static_root/assets/{datetime-column-CBA8bYeO.17a31f189d66.js → datetime-column-BsiYRUKO.cadd764c06f4.js} +1 -1
  228. codex/static_root/assets/datetime-column-BsiYRUKO.cadd764c06f4.js.br +0 -0
  229. codex/static_root/assets/datetime-column-BsiYRUKO.cadd764c06f4.js.gz +0 -0
  230. codex/static_root/assets/{datetime-column-CBA8bYeO.js → datetime-column-BsiYRUKO.js} +1 -1
  231. codex/static_root/assets/datetime-column-BsiYRUKO.js.br +0 -0
  232. codex/static_root/assets/datetime-column-BsiYRUKO.js.gz +0 -0
  233. codex/static_root/assets/{filter-mnO3lLPo.659442401b16.js → filter-HnY0knto.07f807227dcc.js} +1 -1
  234. codex/static_root/assets/filter-HnY0knto.07f807227dcc.js.br +0 -0
  235. codex/static_root/assets/filter-HnY0knto.07f807227dcc.js.gz +0 -0
  236. codex/static_root/assets/{filter-mnO3lLPo.js → filter-HnY0knto.js} +1 -1
  237. codex/static_root/assets/filter-HnY0knto.js.br +0 -0
  238. codex/static_root/assets/filter-HnY0knto.js.gz +0 -0
  239. codex/static_root/assets/{flag-tab-Bc677pNb.1b8a2f7c0dc3.js → flag-tab-CcS5pve2.a1df5d92e222.js} +1 -1
  240. codex/static_root/assets/flag-tab-CcS5pve2.a1df5d92e222.js.br +0 -0
  241. codex/static_root/assets/flag-tab-CcS5pve2.a1df5d92e222.js.gz +0 -0
  242. codex/static_root/assets/{flag-tab-Bc677pNb.js → flag-tab-CcS5pve2.js} +1 -1
  243. codex/static_root/assets/flag-tab-CcS5pve2.js.br +0 -0
  244. codex/static_root/assets/flag-tab-CcS5pve2.js.gz +0 -0
  245. codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css +1 -0
  246. codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css.br +0 -0
  247. codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css.gz +0 -0
  248. codex/static_root/assets/flag-tab-D2MYXCHk.css +1 -0
  249. codex/static_root/assets/flag-tab-D2MYXCHk.css.br +0 -0
  250. codex/static_root/assets/flag-tab-D2MYXCHk.css.gz +0 -0
  251. codex/static_root/assets/{forwardRefs-DPRKg5wq.6f09e9d22fa6.js → forwardRefs-i80xmxX3.db393aeef392.js} +1 -1
  252. codex/static_root/assets/forwardRefs-i80xmxX3.db393aeef392.js.br +0 -0
  253. codex/static_root/assets/forwardRefs-i80xmxX3.db393aeef392.js.gz +0 -0
  254. codex/static_root/assets/{forwardRefs-DPRKg5wq.js → forwardRefs-i80xmxX3.js} +1 -1
  255. codex/static_root/assets/forwardRefs-i80xmxX3.js.br +0 -0
  256. codex/static_root/assets/forwardRefs-i80xmxX3.js.gz +0 -0
  257. codex/static_root/assets/group-tab-SY0gxmHW.f72e944c0ff1.js +1 -0
  258. codex/static_root/assets/group-tab-SY0gxmHW.f72e944c0ff1.js.br +0 -0
  259. codex/static_root/assets/group-tab-SY0gxmHW.f72e944c0ff1.js.gz +0 -0
  260. codex/static_root/assets/group-tab-SY0gxmHW.js +1 -0
  261. codex/static_root/assets/group-tab-SY0gxmHW.js.br +0 -0
  262. codex/static_root/assets/group-tab-SY0gxmHW.js.gz +0 -0
  263. codex/static_root/assets/http-error-EE8gtq5A.07e291d9d955.js +1 -0
  264. codex/static_root/assets/http-error-EE8gtq5A.07e291d9d955.js.br +0 -0
  265. codex/static_root/assets/http-error-EE8gtq5A.07e291d9d955.js.gz +0 -0
  266. codex/static_root/assets/http-error-EE8gtq5A.js +1 -0
  267. codex/static_root/assets/http-error-EE8gtq5A.js.br +0 -0
  268. codex/static_root/assets/http-error-EE8gtq5A.js.gz +0 -0
  269. codex/static_root/assets/{index-9nBCksDQ.4561d2608773.js → index-gVdawuuE.a940fadbac00.js} +1 -1
  270. codex/static_root/assets/index-gVdawuuE.a940fadbac00.js.br +0 -0
  271. codex/static_root/assets/index-gVdawuuE.a940fadbac00.js.gz +0 -0
  272. codex/static_root/assets/{index-9nBCksDQ.js → index-gVdawuuE.js} +1 -1
  273. codex/static_root/assets/index-gVdawuuE.js.br +0 -0
  274. codex/static_root/assets/index-gVdawuuE.js.gz +0 -0
  275. codex/static_root/assets/library-tab-BkDIAvyS.4b4c606b77d6.js +1 -0
  276. codex/static_root/assets/library-tab-BkDIAvyS.4b4c606b77d6.js.br +0 -0
  277. codex/static_root/assets/library-tab-BkDIAvyS.4b4c606b77d6.js.gz +0 -0
  278. codex/static_root/assets/library-tab-BkDIAvyS.js +1 -0
  279. codex/static_root/assets/library-tab-BkDIAvyS.js.br +0 -0
  280. codex/static_root/assets/library-tab-BkDIAvyS.js.gz +0 -0
  281. codex/static_root/assets/main-B5uflU6E.77f3fcd6873f.js +35 -0
  282. codex/static_root/assets/main-B5uflU6E.77f3fcd6873f.js.br +0 -0
  283. codex/static_root/assets/main-B5uflU6E.77f3fcd6873f.js.gz +0 -0
  284. codex/static_root/assets/main-B5uflU6E.js +35 -0
  285. codex/static_root/assets/main-B5uflU6E.js.br +0 -0
  286. codex/static_root/assets/main-B5uflU6E.js.gz +0 -0
  287. codex/static_root/assets/pager-full-pdf-wuQbceLs.111d9b9d4133.js +1 -0
  288. codex/static_root/assets/pager-full-pdf-wuQbceLs.111d9b9d4133.js.br +0 -0
  289. codex/static_root/assets/pager-full-pdf-wuQbceLs.111d9b9d4133.js.gz +0 -0
  290. codex/static_root/assets/pager-full-pdf-wuQbceLs.js +1 -0
  291. codex/static_root/assets/pager-full-pdf-wuQbceLs.js.br +0 -0
  292. codex/static_root/assets/pager-full-pdf-wuQbceLs.js.gz +0 -0
  293. codex/static_root/assets/{pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js → pagination-toolbar-D3JQmDiD.8513a4db85f0.js} +1 -1
  294. codex/static_root/assets/pagination-toolbar-D3JQmDiD.8513a4db85f0.js.br +0 -0
  295. codex/static_root/assets/pagination-toolbar-D3JQmDiD.8513a4db85f0.js.gz +0 -0
  296. codex/static_root/assets/{pagination-toolbar-Dx5JwWG-.js → pagination-toolbar-D3JQmDiD.js} +1 -1
  297. codex/static_root/assets/pagination-toolbar-D3JQmDiD.js.br +0 -0
  298. codex/static_root/assets/pagination-toolbar-D3JQmDiD.js.gz +0 -0
  299. codex/static_root/assets/{pdf-doc-6ZE_HmLT.10d1e16abc78.js → pdf-doc-DRBtl9_2.225a6d89e842.js} +1 -1
  300. codex/static_root/assets/pdf-doc-DRBtl9_2.225a6d89e842.js.br +0 -0
  301. codex/static_root/assets/pdf-doc-DRBtl9_2.225a6d89e842.js.gz +0 -0
  302. codex/static_root/assets/{pdf-doc-6ZE_HmLT.js → pdf-doc-DRBtl9_2.js} +1 -1
  303. codex/static_root/assets/pdf-doc-DRBtl9_2.js.br +0 -0
  304. codex/static_root/assets/pdf-doc-DRBtl9_2.js.gz +0 -0
  305. codex/static_root/assets/reader-DycvjN42.3e2b4f43235e.js +2 -0
  306. codex/static_root/assets/reader-DycvjN42.3e2b4f43235e.js.br +0 -0
  307. codex/static_root/assets/reader-DycvjN42.3e2b4f43235e.js.gz +0 -0
  308. codex/static_root/assets/reader-DycvjN42.js +2 -0
  309. codex/static_root/assets/reader-DycvjN42.js.br +0 -0
  310. codex/static_root/assets/reader-DycvjN42.js.gz +0 -0
  311. codex/static_root/assets/{relation-chips-C96hi5jm.js → relation-chips-CeHIEjvZ.04d302f4fa89.js} +1 -1
  312. codex/static_root/assets/relation-chips-CeHIEjvZ.04d302f4fa89.js.br +0 -0
  313. codex/static_root/assets/relation-chips-CeHIEjvZ.04d302f4fa89.js.gz +0 -0
  314. codex/static_root/assets/{relation-chips-C96hi5jm.9f07c9eb533a.js → relation-chips-CeHIEjvZ.js} +1 -1
  315. codex/static_root/assets/relation-chips-CeHIEjvZ.js.br +0 -0
  316. codex/static_root/assets/relation-chips-CeHIEjvZ.js.gz +0 -0
  317. codex/static_root/assets/settings-drawer-DY8o-jF3.48dfaafaa00e.js +2 -0
  318. codex/static_root/assets/settings-drawer-DY8o-jF3.48dfaafaa00e.js.br +0 -0
  319. codex/static_root/assets/settings-drawer-DY8o-jF3.48dfaafaa00e.js.gz +0 -0
  320. codex/static_root/assets/settings-drawer-DY8o-jF3.js +2 -0
  321. codex/static_root/assets/settings-drawer-DY8o-jF3.js.br +0 -0
  322. codex/static_root/assets/settings-drawer-DY8o-jF3.js.gz +0 -0
  323. codex/static_root/assets/ssrBoot-DRKt_yWP.3b5868abdd61.js +1 -0
  324. codex/static_root/assets/ssrBoot-DRKt_yWP.3b5868abdd61.js.br +0 -0
  325. codex/static_root/assets/ssrBoot-DRKt_yWP.3b5868abdd61.js.gz +0 -0
  326. codex/static_root/assets/ssrBoot-DRKt_yWP.js +1 -0
  327. codex/static_root/assets/ssrBoot-DRKt_yWP.js.br +0 -0
  328. codex/static_root/assets/ssrBoot-DRKt_yWP.js.gz +0 -0
  329. codex/static_root/assets/{stats-tab-CkT2ZGUQ.b704fa46dc88.js → stats-tab-C1XYGBKj.812896e1d09c.js} +1 -1
  330. codex/static_root/assets/stats-tab-C1XYGBKj.812896e1d09c.js.br +0 -0
  331. codex/static_root/assets/stats-tab-C1XYGBKj.812896e1d09c.js.gz +0 -0
  332. codex/static_root/assets/{stats-tab-CkT2ZGUQ.js → stats-tab-C1XYGBKj.js} +1 -1
  333. codex/static_root/assets/stats-tab-C1XYGBKj.js.br +0 -0
  334. codex/static_root/assets/stats-tab-C1XYGBKj.js.gz +0 -0
  335. codex/static_root/assets/task-tab-CW4DQ3KN.81812f9208e5.css +1 -0
  336. codex/static_root/assets/{task-tab-DfdUY9Rc.478e74041f80.css.br → task-tab-CW4DQ3KN.81812f9208e5.css.br} +1 -1
  337. codex/static_root/assets/task-tab-CW4DQ3KN.81812f9208e5.css.gz +0 -0
  338. codex/static_root/assets/task-tab-CW4DQ3KN.css +1 -0
  339. codex/static_root/assets/{task-tab-DfdUY9Rc.css.br → task-tab-CW4DQ3KN.css.br} +1 -1
  340. codex/static_root/assets/task-tab-CW4DQ3KN.css.gz +0 -0
  341. codex/static_root/assets/task-tab-lFRZ0NMd.6b90f40a1a9f.js +1 -0
  342. codex/static_root/assets/task-tab-lFRZ0NMd.6b90f40a1a9f.js.br +0 -0
  343. codex/static_root/assets/task-tab-lFRZ0NMd.6b90f40a1a9f.js.gz +0 -0
  344. codex/static_root/assets/task-tab-lFRZ0NMd.js +1 -0
  345. codex/static_root/assets/task-tab-lFRZ0NMd.js.br +0 -0
  346. codex/static_root/assets/task-tab-lFRZ0NMd.js.gz +0 -0
  347. codex/static_root/assets/unauthorized-Dld9cV8d.4e675fd9991c.js +1 -0
  348. codex/static_root/assets/unauthorized-Dld9cV8d.4e675fd9991c.js.br +0 -0
  349. codex/static_root/assets/unauthorized-Dld9cV8d.4e675fd9991c.js.gz +0 -0
  350. codex/static_root/assets/unauthorized-Dld9cV8d.js +1 -0
  351. codex/static_root/assets/unauthorized-Dld9cV8d.js.br +0 -0
  352. codex/static_root/assets/unauthorized-Dld9cV8d.js.gz +0 -0
  353. codex/static_root/assets/user-tab-CN9nBOMS.f88d8b1c8afe.js +1 -0
  354. codex/static_root/assets/user-tab-CN9nBOMS.f88d8b1c8afe.js.br +0 -0
  355. codex/static_root/assets/user-tab-CN9nBOMS.f88d8b1c8afe.js.gz +0 -0
  356. codex/static_root/assets/user-tab-CN9nBOMS.js +1 -0
  357. codex/static_root/assets/user-tab-CN9nBOMS.js.br +0 -0
  358. codex/static_root/assets/user-tab-CN9nBOMS.js.gz +0 -0
  359. codex/static_root/{manifest.225989a17f49.json → manifest.66d7229aa07a.json} +297 -277
  360. codex/static_root/manifest.66d7229aa07a.json.br +0 -0
  361. codex/static_root/manifest.66d7229aa07a.json.gz +0 -0
  362. codex/static_root/manifest.json +297 -277
  363. codex/static_root/manifest.json.br +0 -0
  364. codex/static_root/manifest.json.gz +0 -0
  365. codex/static_root/staticfiles.json +1 -1
  366. codex/status_controller.py +10 -13
  367. codex/urls/root.py +4 -3
  368. codex/views/admin/api_key.py +1 -4
  369. codex/views/admin/flag.py +2 -2
  370. codex/views/admin/group.py +2 -2
  371. codex/views/admin/library.py +4 -4
  372. codex/views/admin/stats.py +10 -10
  373. codex/views/admin/tasks.py +32 -13
  374. codex/views/admin/user.py +36 -10
  375. codex/views/auth.py +3 -8
  376. codex/views/bookmark.py +3 -3
  377. codex/views/browser/annotate/card.py +2 -2
  378. codex/views/browser/annotate/order.py +6 -7
  379. codex/views/browser/bookmark.py +3 -1
  380. codex/views/browser/breadcrumbs.py +6 -5
  381. codex/views/browser/browser.py +2 -2
  382. codex/views/browser/choices.py +7 -8
  383. codex/views/browser/cover.py +6 -3
  384. codex/views/browser/download.py +2 -1
  385. codex/views/browser/filters/bookmark.py +1 -1
  386. codex/views/browser/filters/field.py +1 -1
  387. codex/views/browser/filters/filter.py +6 -8
  388. codex/views/browser/filters/group.py +8 -8
  389. codex/views/browser/filters/search/field/expression.py +1 -2
  390. codex/views/browser/filters/search/field/optimize.py +1 -1
  391. codex/views/browser/filters/search/field/parse.py +2 -2
  392. codex/views/browser/filters/search/parse.py +4 -19
  393. codex/views/browser/group_mtime.py +1 -1
  394. codex/views/browser/metadata/copy_intersections.py +1 -1
  395. codex/views/browser/metadata/metadata.py +1 -1
  396. codex/views/browser/mtime.py +1 -1
  397. codex/views/browser/order_by.py +3 -5
  398. codex/views/browser/params.py +1 -1
  399. codex/views/browser/settings.py +1 -1
  400. codex/views/browser/validate.py +9 -6
  401. codex/views/const.py +0 -3
  402. codex/views/frontend.py +1 -1
  403. codex/views/mixins.py +3 -2
  404. codex/views/opds/authentication_v1.py +1 -1
  405. codex/views/opds/const.py +4 -4
  406. codex/views/opds/urls.py +1 -1
  407. codex/views/opds/util.py +4 -4
  408. codex/views/opds/v1/entry/entry.py +4 -2
  409. codex/views/opds/v1/entry/links.py +4 -5
  410. codex/views/opds/v1/facets.py +8 -4
  411. codex/views/opds/v1/feed.py +5 -5
  412. codex/views/opds/v1/links.py +11 -6
  413. codex/views/opds/v2/const.py +5 -2
  414. codex/views/opds/v2/feed.py +3 -3
  415. codex/views/opds/v2/links.py +18 -11
  416. codex/views/opds/v2/progression.py +8 -9
  417. codex/views/opds/v2/publications.py +10 -20
  418. codex/views/public.py +2 -2
  419. codex/views/reader/books.py +3 -2
  420. codex/views/reader/page.py +3 -3
  421. codex/views/reader/params.py +2 -2
  422. codex/views/reader/reader.py +6 -6
  423. codex/views/reader/settings.py +1 -1
  424. codex/views/session.py +10 -3
  425. codex/views/settings.py +2 -2
  426. codex/views/timezone.py +2 -2
  427. codex/views/util.py +10 -9
  428. codex/views/version.py +1 -1
  429. {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/METADATA +3 -1
  430. {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/RECORD +433 -415
  431. {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/WHEEL +1 -1
  432. codex/serializers/fields.py +0 -183
  433. codex/static_root/assets/VCheckbox-FaT6MGfu.f6732060f734.js.br +0 -0
  434. codex/static_root/assets/VCheckbox-FaT6MGfu.f6732060f734.js.gz +0 -0
  435. codex/static_root/assets/VCheckbox-FaT6MGfu.js.br +0 -0
  436. codex/static_root/assets/VCheckbox-FaT6MGfu.js.gz +0 -0
  437. codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.847452d574cf.js.br +0 -0
  438. codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.847452d574cf.js.gz +0 -0
  439. codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.js.br +0 -0
  440. codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.js.gz +0 -0
  441. codex/static_root/assets/VCombobox-C8QLNlSl.6f431e19a453.js.br +0 -0
  442. codex/static_root/assets/VCombobox-C8QLNlSl.6f431e19a453.js.gz +0 -0
  443. codex/static_root/assets/VCombobox-C8QLNlSl.js.br +0 -0
  444. codex/static_root/assets/VCombobox-C8QLNlSl.js.gz +0 -0
  445. codex/static_root/assets/VDialog-RVLeW7je.c0c7eef71eec.js.br +0 -0
  446. codex/static_root/assets/VDialog-RVLeW7je.c0c7eef71eec.js.gz +0 -0
  447. codex/static_root/assets/VDialog-RVLeW7je.js.br +0 -0
  448. codex/static_root/assets/VDialog-RVLeW7je.js.gz +0 -0
  449. codex/static_root/assets/VDivider-D1Ot4vR2.9dbee744fb3c.js.br +0 -0
  450. codex/static_root/assets/VDivider-D1Ot4vR2.9dbee744fb3c.js.gz +0 -0
  451. codex/static_root/assets/VDivider-D1Ot4vR2.js.br +0 -0
  452. codex/static_root/assets/VDivider-D1Ot4vR2.js.gz +0 -0
  453. codex/static_root/assets/VExpansionPanels-DQilGHZk.43f06d0a45e0.js.br +0 -0
  454. codex/static_root/assets/VExpansionPanels-DQilGHZk.43f06d0a45e0.js.gz +0 -0
  455. codex/static_root/assets/VExpansionPanels-DQilGHZk.js.br +0 -0
  456. codex/static_root/assets/VExpansionPanels-DQilGHZk.js.gz +0 -0
  457. codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js +0 -1
  458. codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js.br +0 -0
  459. codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js.gz +0 -0
  460. codex/static_root/assets/VForm-DNmY-qFJ.js +0 -1
  461. codex/static_root/assets/VForm-DNmY-qFJ.js.br +0 -0
  462. codex/static_root/assets/VForm-DNmY-qFJ.js.gz +0 -0
  463. codex/static_root/assets/VRadioGroup-DoayJpcS.fe5e0e74ffee.js.br +0 -0
  464. codex/static_root/assets/VRadioGroup-DoayJpcS.fe5e0e74ffee.js.gz +0 -0
  465. codex/static_root/assets/VRadioGroup-DoayJpcS.js.br +0 -0
  466. codex/static_root/assets/VRadioGroup-DoayJpcS.js.gz +0 -0
  467. codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js +0 -1
  468. codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js.br +0 -0
  469. codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js.gz +0 -0
  470. codex/static_root/assets/VSelect-BSLmHX_P.js +0 -1
  471. codex/static_root/assets/VSelect-BSLmHX_P.js.br +0 -0
  472. codex/static_root/assets/VSelect-BSLmHX_P.js.gz +0 -0
  473. codex/static_root/assets/VSelectionControl-TaKCeZgb.ad6c96efe57c.js.br +0 -0
  474. codex/static_root/assets/VSelectionControl-TaKCeZgb.ad6c96efe57c.js.gz +0 -0
  475. codex/static_root/assets/VSelectionControl-TaKCeZgb.js.br +0 -0
  476. codex/static_root/assets/VSelectionControl-TaKCeZgb.js.gz +0 -0
  477. codex/static_root/assets/VTable-BfcOiEpa.77ef3973bb54.js.br +0 -0
  478. codex/static_root/assets/VTable-BfcOiEpa.77ef3973bb54.js.gz +0 -0
  479. codex/static_root/assets/VTable-BfcOiEpa.js.br +0 -0
  480. codex/static_root/assets/VTable-BfcOiEpa.js.gz +0 -0
  481. codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js +0 -1
  482. codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js.br +0 -0
  483. codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js.gz +0 -0
  484. codex/static_root/assets/VWindowItem-D8lWcbQY.js +0 -1
  485. codex/static_root/assets/VWindowItem-D8lWcbQY.js.br +0 -0
  486. codex/static_root/assets/VWindowItem-D8lWcbQY.js.gz +0 -0
  487. codex/static_root/assets/admin-80eqCqtz.d6deb9edb8cb.js.br +0 -0
  488. codex/static_root/assets/admin-80eqCqtz.d6deb9edb8cb.js.gz +0 -0
  489. codex/static_root/assets/admin-80eqCqtz.js.br +0 -0
  490. codex/static_root/assets/admin-80eqCqtz.js.gz +0 -0
  491. codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js +0 -1
  492. codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js.br +0 -0
  493. codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js.gz +0 -0
  494. codex/static_root/assets/admin-CBLHOrM9.js +0 -1
  495. codex/static_root/assets/admin-CBLHOrM9.js.br +0 -0
  496. codex/static_root/assets/admin-CBLHOrM9.js.gz +0 -0
  497. codex/static_root/assets/admin-menu-_aAGknKO.0c08c9bd2e7d.js.br +0 -0
  498. codex/static_root/assets/admin-menu-_aAGknKO.0c08c9bd2e7d.js.gz +0 -0
  499. codex/static_root/assets/admin-menu-_aAGknKO.js.br +0 -0
  500. codex/static_root/assets/admin-menu-_aAGknKO.js.gz +0 -0
  501. codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js.br +0 -0
  502. codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js.gz +0 -0
  503. codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.js.br +0 -0
  504. codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.js.gz +0 -0
  505. codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js +0 -1
  506. codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js.br +0 -0
  507. codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js.gz +0 -0
  508. codex/static_root/assets/browser-DAprXYuP.js +0 -1
  509. codex/static_root/assets/browser-DAprXYuP.js.br +0 -0
  510. codex/static_root/assets/browser-DAprXYuP.js.gz +0 -0
  511. codex/static_root/assets/browser-chTZ1CM4.521193ab0710.css.br +0 -0
  512. codex/static_root/assets/browser-chTZ1CM4.521193ab0710.css.gz +0 -0
  513. codex/static_root/assets/browser-chTZ1CM4.css.br +0 -0
  514. codex/static_root/assets/browser-chTZ1CM4.css.gz +0 -0
  515. codex/static_root/assets/change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js.br +0 -0
  516. codex/static_root/assets/change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js.gz +0 -0
  517. codex/static_root/assets/change-password-dialog-1ZVP_Q2w.js.br +0 -0
  518. codex/static_root/assets/change-password-dialog-1ZVP_Q2w.js.gz +0 -0
  519. codex/static_root/assets/confirm-dialog-DqCey9Iq.e315d7f8f752.js.br +0 -0
  520. codex/static_root/assets/confirm-dialog-DqCey9Iq.e315d7f8f752.js.gz +0 -0
  521. codex/static_root/assets/confirm-dialog-DqCey9Iq.js.br +0 -0
  522. codex/static_root/assets/confirm-dialog-DqCey9Iq.js.gz +0 -0
  523. codex/static_root/assets/datetime-column-CBA8bYeO.17a31f189d66.js.br +0 -0
  524. codex/static_root/assets/datetime-column-CBA8bYeO.17a31f189d66.js.gz +0 -0
  525. codex/static_root/assets/datetime-column-CBA8bYeO.js.br +0 -0
  526. codex/static_root/assets/datetime-column-CBA8bYeO.js.gz +0 -0
  527. codex/static_root/assets/filter-mnO3lLPo.659442401b16.js.br +0 -0
  528. codex/static_root/assets/filter-mnO3lLPo.659442401b16.js.gz +0 -0
  529. codex/static_root/assets/filter-mnO3lLPo.js.br +0 -0
  530. codex/static_root/assets/filter-mnO3lLPo.js.gz +0 -0
  531. codex/static_root/assets/flag-tab-Bc677pNb.1b8a2f7c0dc3.js.br +0 -0
  532. codex/static_root/assets/flag-tab-Bc677pNb.1b8a2f7c0dc3.js.gz +0 -0
  533. codex/static_root/assets/flag-tab-Bc677pNb.js.br +0 -0
  534. codex/static_root/assets/flag-tab-Bc677pNb.js.gz +0 -0
  535. codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css +0 -1
  536. codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css.br +0 -0
  537. codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css.gz +0 -0
  538. codex/static_root/assets/flag-tab-glFvEgEl.css +0 -1
  539. codex/static_root/assets/flag-tab-glFvEgEl.css.br +0 -0
  540. codex/static_root/assets/flag-tab-glFvEgEl.css.gz +0 -0
  541. codex/static_root/assets/forwardRefs-DPRKg5wq.6f09e9d22fa6.js.br +0 -0
  542. codex/static_root/assets/forwardRefs-DPRKg5wq.6f09e9d22fa6.js.gz +0 -0
  543. codex/static_root/assets/forwardRefs-DPRKg5wq.js.br +0 -0
  544. codex/static_root/assets/forwardRefs-DPRKg5wq.js.gz +0 -0
  545. codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js +0 -1
  546. codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js.br +0 -0
  547. codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js.gz +0 -0
  548. codex/static_root/assets/group-tab-BVlBrMwo.js +0 -1
  549. codex/static_root/assets/group-tab-BVlBrMwo.js.br +0 -0
  550. codex/static_root/assets/group-tab-BVlBrMwo.js.gz +0 -0
  551. codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js +0 -1
  552. codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js.br +0 -0
  553. codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js.gz +0 -0
  554. codex/static_root/assets/http-error-C9-_aEBF.js +0 -1
  555. codex/static_root/assets/http-error-C9-_aEBF.js.br +0 -0
  556. codex/static_root/assets/http-error-C9-_aEBF.js.gz +0 -0
  557. codex/static_root/assets/index-9nBCksDQ.4561d2608773.js.br +0 -0
  558. codex/static_root/assets/index-9nBCksDQ.4561d2608773.js.gz +0 -0
  559. codex/static_root/assets/index-9nBCksDQ.js.br +0 -0
  560. codex/static_root/assets/index-9nBCksDQ.js.gz +0 -0
  561. codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js +0 -1
  562. codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js.br +0 -0
  563. codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js.gz +0 -0
  564. codex/static_root/assets/library-tab-BMe3dKzy.js +0 -1
  565. codex/static_root/assets/library-tab-BMe3dKzy.js.br +0 -0
  566. codex/static_root/assets/library-tab-BMe3dKzy.js.gz +0 -0
  567. codex/static_root/assets/main-BbNUiWEb.565b616eb122.js +0 -35
  568. codex/static_root/assets/main-BbNUiWEb.565b616eb122.js.br +0 -0
  569. codex/static_root/assets/main-BbNUiWEb.565b616eb122.js.gz +0 -0
  570. codex/static_root/assets/main-BbNUiWEb.js +0 -35
  571. codex/static_root/assets/main-BbNUiWEb.js.br +0 -0
  572. codex/static_root/assets/main-BbNUiWEb.js.gz +0 -0
  573. codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js +0 -1
  574. codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js.br +0 -0
  575. codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js.gz +0 -0
  576. codex/static_root/assets/pager-full-pdf-JwAUlsNI.js +0 -1
  577. codex/static_root/assets/pager-full-pdf-JwAUlsNI.js.br +0 -0
  578. codex/static_root/assets/pager-full-pdf-JwAUlsNI.js.gz +0 -0
  579. codex/static_root/assets/pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js.br +0 -0
  580. codex/static_root/assets/pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js.gz +0 -0
  581. codex/static_root/assets/pagination-toolbar-Dx5JwWG-.js.br +0 -0
  582. codex/static_root/assets/pagination-toolbar-Dx5JwWG-.js.gz +0 -0
  583. codex/static_root/assets/pdf-doc-6ZE_HmLT.10d1e16abc78.js.br +0 -0
  584. codex/static_root/assets/pdf-doc-6ZE_HmLT.10d1e16abc78.js.gz +0 -0
  585. codex/static_root/assets/pdf-doc-6ZE_HmLT.js.br +0 -0
  586. codex/static_root/assets/pdf-doc-6ZE_HmLT.js.gz +0 -0
  587. codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js +0 -2
  588. codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js.br +0 -0
  589. codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js.gz +0 -0
  590. codex/static_root/assets/reader-DdPKDuh5.js +0 -2
  591. codex/static_root/assets/reader-DdPKDuh5.js.br +0 -0
  592. codex/static_root/assets/reader-DdPKDuh5.js.gz +0 -0
  593. codex/static_root/assets/relation-chips-C96hi5jm.9f07c9eb533a.js.br +0 -0
  594. codex/static_root/assets/relation-chips-C96hi5jm.9f07c9eb533a.js.gz +0 -0
  595. codex/static_root/assets/relation-chips-C96hi5jm.js.br +0 -0
  596. codex/static_root/assets/relation-chips-C96hi5jm.js.gz +0 -0
  597. codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js +0 -2
  598. codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js.br +0 -0
  599. codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js.gz +0 -0
  600. codex/static_root/assets/settings-drawer-CqbT1rid.js +0 -2
  601. codex/static_root/assets/settings-drawer-CqbT1rid.js.br +0 -0
  602. codex/static_root/assets/settings-drawer-CqbT1rid.js.gz +0 -0
  603. codex/static_root/assets/stats-tab-CkT2ZGUQ.b704fa46dc88.js.br +0 -0
  604. codex/static_root/assets/stats-tab-CkT2ZGUQ.b704fa46dc88.js.gz +0 -0
  605. codex/static_root/assets/stats-tab-CkT2ZGUQ.js.br +0 -0
  606. codex/static_root/assets/stats-tab-CkT2ZGUQ.js.gz +0 -0
  607. codex/static_root/assets/task-tab-DfdUY9Rc.478e74041f80.css +0 -1
  608. codex/static_root/assets/task-tab-DfdUY9Rc.478e74041f80.css.gz +0 -0
  609. codex/static_root/assets/task-tab-DfdUY9Rc.css +0 -1
  610. codex/static_root/assets/task-tab-DfdUY9Rc.css.gz +0 -0
  611. codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js +0 -1
  612. codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js.br +0 -0
  613. codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js.gz +0 -0
  614. codex/static_root/assets/task-tab-a-DcU3Tj.js +0 -1
  615. codex/static_root/assets/task-tab-a-DcU3Tj.js.br +0 -0
  616. codex/static_root/assets/task-tab-a-DcU3Tj.js.gz +0 -0
  617. codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js +0 -1
  618. codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js.br +0 -0
  619. codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js.gz +0 -0
  620. codex/static_root/assets/unauthorized-BOl5YpL3.js +0 -1
  621. codex/static_root/assets/unauthorized-BOl5YpL3.js.br +0 -0
  622. codex/static_root/assets/unauthorized-BOl5YpL3.js.gz +0 -0
  623. codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js +0 -1
  624. codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js.br +0 -0
  625. codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js.gz +0 -0
  626. codex/static_root/assets/user-tab-BBDZvD8G.js +0 -1
  627. codex/static_root/assets/user-tab-BBDZvD8G.js.br +0 -0
  628. codex/static_root/assets/user-tab-BBDZvD8G.js.gz +0 -0
  629. codex/static_root/manifest.225989a17f49.json.br +0 -0
  630. codex/static_root/manifest.225989a17f49.json.gz +0 -0
  631. {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/LICENSE +0 -0
  632. {codex-1.7.3a2.dist-info → codex-1.7.5.dist-info}/entry_points.txt +0 -0
@@ -5,6 +5,7 @@ from django.db.models.expressions import F
5
5
  from django.db.models.functions import Now
6
6
  from django.db.models.query import Q
7
7
 
8
+ from codex.choices.notifications import Notifications
8
9
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
9
10
  from codex.librarian.notifier.tasks import NotifierTask
10
11
  from codex.models import Bookmark, Comic
@@ -28,6 +29,35 @@ class BookmarkUpdate(GroupACLMixin):
28
29
 
29
30
  # Used by Bookmarkd and view.bookmark.
30
31
 
32
+ @classmethod
33
+ def _get_existing_bookmarks_for_update(cls, auth_filter, comic_pks, updates):
34
+ # Get existing bookmarks
35
+ query_filter = Q(**auth_filter) & Q(comic__in=comic_pks)
36
+ existing_bookmarks = Bookmark.objects.filter(query_filter)
37
+ if updates.get("page") is not None:
38
+ existing_bookmarks = existing_bookmarks.annotate(
39
+ page_count=F("comic__page_count")
40
+ )
41
+
42
+ update_fields = (set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS) | {"updated_at"}
43
+ only_fields = (*update_fields, "pk")
44
+
45
+ existing_bookmarks = existing_bookmarks.only(*only_fields)
46
+ return existing_bookmarks, update_fields
47
+
48
+ @classmethod
49
+ def _prepare_bookmark_updates(cls, existing_bookmarks, updates):
50
+ # Prepare updates
51
+ update_bookmarks = []
52
+ for bm in existing_bookmarks:
53
+ cls._update_bookmarks_validate_page(bm, updates)
54
+ cls._update_bookmarks_validate_two_pages(bm, updates)
55
+ for key, value in updates.items():
56
+ setattr(bm, key, value)
57
+ bm.updated_at = Now()
58
+ update_bookmarks.append(bm)
59
+ return update_bookmarks
60
+
31
61
  @staticmethod
32
62
  def _update_bookmarks_validate_page(bm, updates):
33
63
  """Force bookmark page into valid range."""
@@ -44,9 +74,10 @@ class BookmarkUpdate(GroupACLMixin):
44
74
  @staticmethod
45
75
  def _update_bookmarks_validate_two_pages(bm, updates):
46
76
  """Force vertical view to not use two pages."""
47
- rd = updates.get("reading_direction")
48
77
  if bm.two_pages and bool(
49
- {rd, bm.reading_direction}.intersection(_VERTICAL_READING_DIRECTIONS)
78
+ {bm.reading_direction, updates.get("reading_direction")}.intersection(
79
+ _VERTICAL_READING_DIRECTIONS
80
+ )
50
81
  ):
51
82
  updates["two_pages"] = None
52
83
 
@@ -54,41 +85,45 @@ class BookmarkUpdate(GroupACLMixin):
54
85
  def _notify_library_changed(uid):
55
86
  """Notify one user that their library changed."""
56
87
  group = f"user_{uid}"
57
- task = NotifierTask("LIBRARY_CHANGED", group)
88
+ task = NotifierTask(Notifications.BOOKMARK.value, group)
58
89
  LIBRARIAN_QUEUE.put(task)
59
90
 
60
91
  @classmethod
61
- def _update_bookmarks(cls, auth_filter, comic_pks, updates, user) -> int:
92
+ def _update_bookmarks(cls, auth_filter, comic_pks, updates) -> int:
62
93
  """Update existing bookmarks."""
63
94
  count = 0
64
95
  if not updates:
65
96
  return count
66
- group_acl_filter = cls.get_group_acl_filter(Bookmark, user)
67
- query_filter = group_acl_filter & Q(**auth_filter) & Q(comic__in=comic_pks)
68
- existing_bookmarks = Bookmark.objects.filter(query_filter)
69
- if updates.get("page") is not None:
70
- existing_bookmarks = existing_bookmarks.annotate(
71
- page_count=F("comic__page_count")
72
- )
73
97
 
74
- update_fields = set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS
75
- update_fields.add("updated_at")
76
- only_fields = (*update_fields, "pk")
77
-
78
- existing_bookmarks = existing_bookmarks.only(*only_fields)
79
- update_bookmarks = []
80
- for bm in existing_bookmarks:
81
- cls._update_bookmarks_validate_page(bm, updates)
82
- cls._update_bookmarks_validate_two_pages(bm, updates)
83
- for key, value in updates.items():
84
- setattr(bm, key, value)
85
- bm.updated_at = Now()
86
- update_bookmarks.append(bm)
98
+ existing_bookmarks, update_fields = cls._get_existing_bookmarks_for_update(
99
+ auth_filter, comic_pks, updates
100
+ )
101
+ update_bookmarks = cls._prepare_bookmark_updates(existing_bookmarks, updates)
87
102
  count = len(update_bookmarks)
103
+
104
+ # Bulk update
88
105
  if count:
89
106
  Bookmark.objects.bulk_update(update_bookmarks, tuple(update_fields))
90
107
  return count
91
108
 
109
+ @classmethod
110
+ def _get_comics_without_bookmarks(cls, auth_filter, comic_pks):
111
+ """Get comics without bookmarks."""
112
+ exclude = {}
113
+ for key, value in auth_filter.items():
114
+ exclude["bookmark__" + key] = value
115
+ query_filter = Q(pk__in=comic_pks) & ~Q(**exclude)
116
+ return Comic.objects.filter(query_filter).only("pk")
117
+
118
+ @classmethod
119
+ def _prepare_bookmark_creates(cls, create_bookmark_comics, auth_filter, updates):
120
+ # Prepare creates
121
+ create_bookmarks = []
122
+ for comic in create_bookmark_comics:
123
+ bm = Bookmark(comic=comic, **auth_filter, **updates)
124
+ create_bookmarks.append(bm)
125
+ return create_bookmarks
126
+
92
127
  @staticmethod
93
128
  def _create_bookmarks_validate_two_pages(updates):
94
129
  """Force vertical view to not use two pages."""
@@ -99,54 +134,54 @@ class BookmarkUpdate(GroupACLMixin):
99
134
  updates.pop("two_pages", None)
100
135
 
101
136
  @classmethod
102
- def _create_bookmarks(cls, auth_filter, comic_pks, updates, user) -> int:
137
+ def _create_bookmarks(cls, auth_filter, comic_pks, updates) -> int:
103
138
  """Create new bookmarks for comics that don't exist yet."""
104
139
  count = 0
105
140
  cls._create_bookmarks_validate_two_pages(updates)
106
141
  if not updates:
107
142
  return count
108
- create_bookmarks = []
109
- group_acl_filter = cls.get_group_acl_filter(Comic, user)
110
- exclude = {}
111
- for key, value in auth_filter.items():
112
- exclude["bookmark__" + key] = value
113
- query_filter = group_acl_filter & Q(pk__in=comic_pks) & ~Q(**exclude)
114
- create_bookmark_comics = Comic.objects.filter(query_filter).only("pk")
115
- for comic in create_bookmark_comics:
116
- bm = Bookmark(comic=comic, **auth_filter, **updates)
117
- create_bookmarks.append(bm)
143
+
144
+ create_bookmark_comics = cls._get_comics_without_bookmarks(
145
+ auth_filter, comic_pks
146
+ )
147
+ create_bookmarks = cls._prepare_bookmark_creates(
148
+ create_bookmark_comics, auth_filter, updates
149
+ )
118
150
  count = len(create_bookmarks)
151
+
152
+ # Bulk create
119
153
  if count:
120
154
  Bookmark.objects.bulk_create(
121
155
  create_bookmarks,
122
156
  update_fields=tuple(_BOOKMARK_UPDATE_FIELDS),
123
- unique_fields=Bookmark._meta.unique_together[0], # type: ignore
157
+ unique_fields=Bookmark._meta.unique_together[0],
124
158
  )
125
159
  return count
126
160
 
127
161
  @classmethod
128
- def _update_user_active(cls, user, log):
162
+ def _update_user_active(cls, auth_filter, log):
129
163
  """Update user active."""
130
164
  # Offline because profile gets hit rapidly in succession.
131
165
  try:
132
- UserActive.objects.update_or_create(user=user)
166
+ user = None
167
+ for key, value in auth_filter.items():
168
+ if key == "user":
169
+ user = User.objects.get(pk=value)
170
+ if user:
171
+ UserActive.objects.update_or_create(user=user)
172
+ break
173
+ except User.DoesNotExist:
174
+ pass
133
175
  except Exception as exc:
134
- reason = f"update user activity {exc}"
176
+ reason = f"Update user activity {exc}"
135
177
  log.warning(reason)
136
178
 
137
179
  @classmethod
138
180
  def update_bookmarks(cls, auth_filter, comic_pks, updates, log):
139
181
  """Update a user bookmark."""
140
- user = None
141
- for key, value in auth_filter.items():
142
- if key == "user":
143
- user = User.objects.get(pk=value)
144
- break
145
-
146
- count = cls._update_bookmarks(auth_filter, comic_pks, updates, user)
147
- count += cls._create_bookmarks(auth_filter, comic_pks, updates, user)
148
- if user:
149
- cls._update_user_active(user, log)
182
+ count = cls._update_bookmarks(auth_filter, comic_pks, updates)
183
+ count += cls._create_bookmarks(auth_filter, comic_pks, updates)
184
+ cls._update_user_active(auth_filter, log)
150
185
  if count:
151
186
  uid = next(iter(auth_filter.values()))
152
187
  cls._notify_library_changed(uid)
@@ -32,7 +32,7 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
32
32
  with Image.open(image_io) as cover_image:
33
33
  cover_image.thumbnail(
34
34
  _THUMBNAIL_SIZE,
35
- Image.Resampling.LANCZOS, # type: ignore
35
+ Image.Resampling.LANCZOS,
36
36
  reducing_gap=3.0,
37
37
  )
38
38
  cover_image.save(cover_thumb_buffer, "WEBP", method=6)
@@ -41,7 +41,8 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
41
41
 
42
42
  @classmethod
43
43
  def _get_comic_cover_image(cls, comic_path):
44
- """Create comic cover if none exists.
44
+ """
45
+ Create comic cover if none exists.
45
46
 
46
47
  Return image thumb data or path to missing file thumb.
47
48
  """
@@ -59,8 +60,9 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
59
60
  return f.read()
60
61
 
61
62
  @classmethod
62
- def create_cover_from_path(cls, pk, cover_path, log, librarian_queue, custom=False):
63
- """Create cover for path.
63
+ def create_cover_from_path(cls, pk, cover_path, log, librarian_queue, custom):
64
+ """
65
+ Create cover for path.
64
66
 
65
67
  Called from views/cover.
66
68
  """
@@ -95,7 +97,7 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
95
97
  # zero length file is code for missing.
96
98
  cover_path.touch()
97
99
 
98
- def _bulk_create_comic_covers(self, pks, custom=False):
100
+ def _bulk_create_comic_covers(self, pks, custom):
99
101
  """Create bulk comic covers."""
100
102
  num_comics = len(pks)
101
103
  if not num_comics:
@@ -115,7 +117,7 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
115
117
  else:
116
118
  # bulk contributor creates covers inline
117
119
  data = self.create_cover_from_path(
118
- pk, cover_path, self.log, self.librarian_queue
120
+ pk, cover_path, self.log, self.librarian_queue, custom=False
119
121
  )
120
122
  if data:
121
123
  data.close()
@@ -134,6 +136,6 @@ class CoverCreateThread(QueuedThread, CoverPathMixin):
134
136
  def create_all_covers(self):
135
137
  """Create all covers for all libraries."""
136
138
  pks = CustomCover.objects.values_list("pk", flat=True)
137
- self._bulk_create_comic_covers(pks, True)
139
+ self._bulk_create_comic_covers(pks, custom=True)
138
140
  pks = Comic.objects.values_list("pk", flat=True)
139
- self._bulk_create_comic_covers(pks, False)
141
+ self._bulk_create_comic_covers(pks, custom=False)
@@ -21,20 +21,21 @@ class CoverPathMixin:
21
21
  """Translate an integer into an efficient filesystem path."""
22
22
  fnv = fnv1a_32(bytes(str(pk).zfill(cls._ZFILL), "utf-8"))
23
23
  hex_str = format(fnv, f"0{cls._ZFILL}x")
24
- parts = []
25
- for i in range(0, len(hex_str), cls._PATH_STEP):
26
- parts.append(hex_str[i : i + cls._PATH_STEP])
24
+ parts = [
25
+ hex_str[i : i + cls._PATH_STEP]
26
+ for i in range(0, len(hex_str), cls._PATH_STEP)
27
+ ]
27
28
  return Path("/".join(parts))
28
29
 
29
30
  @classmethod
30
- def get_cover_path(cls, pk, custom=False):
31
+ def get_cover_path(cls, pk, custom):
31
32
  """Get cover path for comic pk."""
32
33
  cover_path = cls._hex_path(pk)
33
34
  root = cls.CUSTOM_COVERS_ROOT if custom else cls.COVERS_ROOT
34
35
  return root / cover_path.with_suffix(".webp")
35
36
 
36
37
  @classmethod
37
- def get_cover_paths(cls, pks, custom=False):
38
+ def get_cover_paths(cls, pks, custom):
38
39
  """Get cover paths for many comic pks."""
39
40
  cover_paths = set()
40
41
  for pk in pks:
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from codex.librarian.covers.create import CoverCreateThread
8
8
  from codex.librarian.covers.status import CoverStatusTypes
9
- from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
9
+ from codex.librarian.notifier.tasks import COVERS_CHANGED_TASK
10
10
  from codex.models import Comic
11
11
  from codex.models.paths import CustomCover
12
12
  from codex.status import Status
@@ -67,9 +67,9 @@ class CoverPurgeThread(CoverCreateThread):
67
67
  self.log.info("Removed entire comic cover cache.")
68
68
  shutil.rmtree(self.CUSTOM_COVERS_ROOT)
69
69
  self.log.info("Removed entire custom cover cache.")
70
- except Exception as exc:
70
+ except OSError as exc:
71
71
  self.log.warning(exc)
72
- librarian_queue.put(LIBRARY_CHANGED_TASK)
72
+ librarian_queue.put(COVERS_CHANGED_TASK)
73
73
 
74
74
  def _cleanup_orphan_covers(self, cover_class, cover_root, name):
75
75
  """Remove all orphan cover thumbs."""
@@ -77,7 +77,7 @@ class CoverPurgeThread(CoverCreateThread):
77
77
  self.log.debug(f"Removing covers from missing {name}.")
78
78
  self.status_controller.start_many(self._CLEANUP_STATUS_MAP)
79
79
  pks = cover_class.objects.all().values_list("pk", flat=True)
80
- db_cover_paths = self.get_cover_paths(pks)
80
+ db_cover_paths = self.get_cover_paths(pks, custom=False)
81
81
 
82
82
  orphan_cover_paths = set()
83
83
  for root, _, filenames in os.walk(cover_root):
@@ -222,10 +222,10 @@ class AggregateMetadataImporter(ExtractMetadataImporter):
222
222
  if self.task.force_import_metadata:
223
223
  import_metadata = True
224
224
  else:
225
- key = AdminFlag.FlagChoices.IMPORT_METADATA.value # type: ignore
225
+ key = AdminFlag.FlagChoices.IMPORT_METADATA.value
226
226
  import_metadata = AdminFlag.objects.get(key=key).on
227
227
  if not import_metadata:
228
- self.log.warn("Admin flag set to NOT import metadata.")
228
+ self.log.warning("Admin flag set to NOT import metadata.")
229
229
 
230
230
  # Init metadata, extract and aggregate
231
231
  self.metadata[MDS] = {}
@@ -73,7 +73,6 @@ class CacheUpdateImporter(InitImporter):
73
73
  try:
74
74
  log_list = []
75
75
  for model in GROUP_MODELS:
76
- # self.log.debug(f"Updating timestamps for {model.__name__}s...")
77
76
  count = self._update_group_model(
78
77
  force_update_group_map, model, start_time, log_list
79
78
  )
@@ -85,9 +84,7 @@ class CacheUpdateImporter(InitImporter):
85
84
 
86
85
  if total_count:
87
86
  groups_log = ", ".join(log_list)
88
- self.log.info( # type: ignore
89
- f"Updated timestamps for {groups_log}."
90
- )
87
+ self.log.info(f"Updated timestamps for {groups_log}.")
91
88
  self.changed += total_count
92
89
  finally:
93
90
  self.status_controller.finish(status)
@@ -78,7 +78,7 @@ class CreateComicsImporter(LinkComicsImporter):
78
78
  Comic.objects.bulk_update(update_comics, BULK_UPDATE_COMIC_FIELDS)
79
79
  count = len(update_comics)
80
80
 
81
- self._remove_covers(comic_pks, False) # type: ignore
81
+ self._remove_covers(comic_pks, custom=False)
82
82
  self.log.debug(f"Purging covers for {len(comic_pks)} updated comics.")
83
83
  if count:
84
84
  self.log.info(f"Updated {count} comics.")
@@ -126,7 +126,7 @@ class CreateComicsImporter(LinkComicsImporter):
126
126
  create_comics,
127
127
  update_conflicts=True,
128
128
  update_fields=BULK_CREATE_COMIC_FIELDS,
129
- unique_fields=Comic._meta.unique_together[0], # type: ignore
129
+ unique_fields=Comic._meta.unique_together[0],
130
130
  )
131
131
  count = len(create_comics)
132
132
  if count:
@@ -43,7 +43,7 @@ class CreateCoversImporter(CreateComicsImporter):
43
43
  if LINK_COVER_PKS not in self.metadata:
44
44
  self.metadata[LINK_COVER_PKS] = set()
45
45
  self.metadata[LINK_COVER_PKS].update(update_cover_pks)
46
- self._remove_covers(update_cover_pks, custom=True) # type: ignore
46
+ self._remove_covers(update_cover_pks, custom=True)
47
47
  count = len(update_covers)
48
48
  if status:
49
49
  status.add_complete(count)
@@ -1,4 +1,5 @@
1
- """Create all missing comic foreign keys for an import.
1
+ """
2
+ Create all missing comic foreign keys for an import.
2
3
 
3
4
  So we may safely create the comics next.
4
5
  """
@@ -189,7 +190,7 @@ class CreateForeignKeysImporter(CreateCoversImporter):
189
190
  create_folders,
190
191
  update_conflicts=True,
191
192
  update_fields=BULK_UPDATE_FOLDER_FIELDS,
192
- unique_fields=Folder._meta.unique_together[0], # type: ignore
193
+ unique_fields=Folder._meta.unique_together[0],
193
194
  )
194
195
  count = len(create_folders)
195
196
  status.add_complete(count)
@@ -296,7 +297,7 @@ class CreateForeignKeysImporter(CreateCoversImporter):
296
297
  create_objs,
297
298
  update_conflicts=True,
298
299
  update_fields=CREATE_DICT_UPDATE_FIELDS[model],
299
- unique_fields=model._meta.unique_together[0], # type: ignore
300
+ unique_fields=model._meta.unique_together[0],
300
301
  )
301
302
  count = len(create_objs)
302
303
  if count:
@@ -13,7 +13,7 @@ from codex.status import Status
13
13
  class DeletedImporter(CacheUpdateImporter):
14
14
  """Clean up database methods."""
15
15
 
16
- def _remove_covers(self, delete_pks, custom=False):
16
+ def _remove_covers(self, delete_pks, custom: bool):
17
17
  task = CoverRemoveTask(delete_pks, custom)
18
18
  self.librarian_queue.put(task)
19
19
 
@@ -34,7 +34,7 @@ class DeletedImporter(CacheUpdateImporter):
34
34
  )
35
35
  folders.delete()
36
36
 
37
- self._remove_covers(delete_comic_pks)
37
+ self._remove_covers(delete_comic_pks, custom=False)
38
38
 
39
39
  count = len(delete_comic_pks)
40
40
  if count:
@@ -96,7 +96,7 @@ class DeletedImporter(CacheUpdateImporter):
96
96
  delete_comic_pks = frozenset(delete_qs.values_list("pk", flat=True))
97
97
  delete_qs.delete()
98
98
 
99
- self._remove_covers(delete_comic_pks)
99
+ self._remove_covers(delete_comic_pks, custom=False)
100
100
 
101
101
  count = len(delete_comic_pks)
102
102
  if count:
@@ -9,8 +9,13 @@ from comicbox.box import Comicbox
9
9
  from comicbox.box.computed import IDENTIFIERS_KEY
10
10
  from comicbox.exceptions import UnsupportedArchiveTypeError
11
11
  from comicbox.schemas.comicbox_mixin import CONTRIBUTORS_KEY
12
- from django.db.models import CharField
13
- from django.db.models.fields import DecimalField, PositiveSmallIntegerField
12
+ from django.db.models.fields import (
13
+ CharField,
14
+ DecimalField,
15
+ PositiveSmallIntegerField,
16
+ TextField,
17
+ )
18
+ from nh3 import clean
14
19
  from rarfile import BadRarFile
15
20
 
16
21
  from codex.librarian.importer.const import FIS, STORY_ARCS_METADATA_KEY
@@ -36,29 +41,39 @@ _MD_INVALID_KEYS = frozenset(
36
41
  "updated_at",
37
42
  )
38
43
  )
39
- _MD_VALID_KEYS = frozenset(
40
- (
41
- *(
42
- field.name
43
- for field in Comic._meta.get_fields()
44
- if field.name not in _MD_INVALID_KEYS
45
- ),
46
- "story_arcs",
44
+ _MD_VALID_KEYS = tuple(
45
+ sorted(
46
+ frozenset(
47
+ (
48
+ *(
49
+ field.name
50
+ for field in Comic._meta.get_fields()
51
+ if field.name not in _MD_INVALID_KEYS
52
+ ),
53
+ "story_arcs",
54
+ )
55
+ )
47
56
  )
48
57
  )
49
- _MD_CHAR_KEYS = frozenset(
50
- field.name for field in Comic._meta.get_fields() if isinstance(field, CharField)
51
- )
52
- _MD_DECIMAL_KEYS = frozenset(
53
- field.name for field in Comic._meta.get_fields() if isinstance(field, DecimalField)
54
- )
55
- _MD_PSI_KEYS = frozenset(
56
- {
57
- field.name
58
- for field in Comic._meta.get_fields()
59
- if isinstance(field, PositiveSmallIntegerField)
60
- }
61
- )
58
+
59
+
60
+ def _get_keys_by_field_type(field_type):
61
+ return tuple(
62
+ sorted(
63
+ frozenset(
64
+ field.name
65
+ for field in Comic._meta.get_fields()
66
+ if isinstance(field, field_type)
67
+ )
68
+ )
69
+ )
70
+
71
+
72
+ _MD_CHAR_KEYS = _get_keys_by_field_type(CharField)
73
+ _MD_TEXT_KEYS = _get_keys_by_field_type(TextField)
74
+ _MD_STR_KEYS = tuple(sorted(_MD_CHAR_KEYS + _MD_TEXT_KEYS))
75
+ _MD_DECIMAL_KEYS = _get_keys_by_field_type(DecimalField)
76
+ _MD_PSI_KEYS = _get_keys_by_field_type(PositiveSmallIntegerField)
62
77
  _PSI_MAX = 2**31 - 1
63
78
  _SI_MIN = 2**15 * -1
64
79
  _SI_MAX = 2**15 - 1
@@ -94,7 +109,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
94
109
 
95
110
  @classmethod
96
111
  def _clean_decimal(cls, value, field_name: str):
97
- field: DecimalField = Comic._meta.get_field(field_name) # type: ignore
112
+ field: DecimalField = Comic._meta.get_field(field_name) # type: ignore[reportAssignmentType]
98
113
  try:
99
114
  quantize_str = Decimal(f"1e-{field.decimal_places}")
100
115
  value = value.quantize(quantize_str, rounding=ROUND_DOWN)
@@ -145,11 +160,15 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
145
160
  cls._assign_or_pop(obj, key, value)
146
161
 
147
162
  @staticmethod
148
- def _clean_charfield(model, field_name, value):
163
+ def _clean_strfield(model, field_name, value):
149
164
  try:
150
- field: CharField = model._meta.get_field(field_name) # type: ignore
151
165
  if value:
152
- value = value[: field.max_length].strip()
166
+ value = value.strip()
167
+ field = model._meta.get_field(field_name) # type: ignore[reportAssignmentType]
168
+ max_length = getattr(field, "max_length", 0)
169
+ if max_length:
170
+ value = value[:max_length]
171
+ value = clean(value)
153
172
  if not value:
154
173
  value = None
155
174
  except Exception:
@@ -157,10 +176,10 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
157
176
  return value
158
177
 
159
178
  @classmethod
160
- def _clean_charfields(cls, md: dict[str, Any]) -> None:
161
- for key in _MD_CHAR_KEYS:
179
+ def _clean_strfields(cls, md: dict[str, Any]) -> None:
180
+ for key in _MD_STR_KEYS:
162
181
  value = md.get(key)
163
- value = cls._clean_charfield(Comic, key, value)
182
+ value = cls._clean_strfield(Comic, key, value)
164
183
  cls._assign_or_pop(md, key, value)
165
184
 
166
185
  @classmethod
@@ -182,10 +201,10 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
182
201
  return
183
202
  clean_contributors = {}
184
203
  for role, persons in contributors.items():
185
- clean_role = cls._clean_charfield(ContributorRole, "name", role)
204
+ clean_role = cls._clean_strfield(ContributorRole, "name", role)
186
205
  clean_persons = set()
187
206
  for person in persons:
188
- clean_person = cls._clean_charfield(ContributorPerson, "name", person)
207
+ clean_person = cls._clean_strfield(ContributorPerson, "name", person)
189
208
  if clean_person:
190
209
  clean_persons.add(clean_person)
191
210
  clean_contributors[clean_role] = clean_persons
@@ -198,7 +217,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
198
217
  return
199
218
  clean_story_arcs = {}
200
219
  for story_arc, number in story_arcs.items():
201
- clean_story_arc = cls._clean_charfield(StoryArc, "name", story_arc)
220
+ clean_story_arc = cls._clean_strfield(StoryArc, "name", story_arc)
202
221
  if clean_story_arc:
203
222
  clean_story_arcs[clean_story_arc] = number
204
223
  cls._assign_or_pop(md, STORY_ARCS_METADATA_KEY, clean_story_arcs)
@@ -210,11 +229,11 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
210
229
  return
211
230
  clean_identifiers = {}
212
231
  for nid, identifier in identifiers.items():
213
- clean_id_type = cls._clean_charfield(IdentifierType, "name", nid)
232
+ clean_id_type = cls._clean_strfield(IdentifierType, "name", nid)
214
233
  nss = identifier.get("nss")
215
- clean_nss = cls._clean_charfield(Identifier, "nss", nss)
234
+ clean_nss = cls._clean_strfield(Identifier, "nss", nss)
216
235
  url = identifier.get("url")
217
- clean_url = cls._clean_charfield(Identifier, "url", url)
236
+ clean_url = cls._clean_strfield(Identifier, "url", url)
218
237
  if clean_nss:
219
238
  clean_identifier = {"nss": nss}
220
239
  if clean_url:
@@ -230,7 +249,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
230
249
  cls._clean_comic_positive_small_ints(md)
231
250
  cls._clean_comic_small_ints(md)
232
251
  cls._clean_comic_decimals(md)
233
- cls._clean_charfields(md)
252
+ cls._clean_strfields(md)
234
253
  cls._clean_contributors(md)
235
254
  cls._clean_story_arcs(md)
236
255
  cls._clean_identifiers(md)
@@ -120,7 +120,7 @@ class FailedImportsImporter(DeletedImporter):
120
120
  create_objs,
121
121
  update_conflicts=True,
122
122
  update_fields=_BULK_UPDATE_FAILED_IMPORT_FIELDS,
123
- unique_fields=FailedImport._meta.unique_together[0], # type: ignore
123
+ unique_fields=FailedImport._meta.unique_together[0],
124
124
  )
125
125
  count = len(create_objs)
126
126
  if count:
@@ -7,7 +7,10 @@ from humanize import naturaldelta
7
7
 
8
8
  from codex.librarian.importer.moved import MovedImporter
9
9
  from codex.librarian.importer.status import ImportStatusTypes
10
- from codex.librarian.notifier.tasks import FAILED_IMPORTS_TASK, LIBRARY_CHANGED_TASK
10
+ from codex.librarian.notifier.tasks import (
11
+ FAILED_IMPORTS_CHANGED_TASK,
12
+ LIBRARY_CHANGED_TASK,
13
+ )
11
14
  from codex.librarian.search.tasks import SearchIndexUpdateTask
12
15
  from codex.librarian.tasks import DelayedTasks
13
16
 
@@ -43,12 +46,14 @@ class ComicImporter(MovedImporter):
43
46
 
44
47
  # Wait to start the search index update in case more updates are incoming.
45
48
  until = time() + 1
46
- delayed_search_task = DelayedTasks(until, (SearchIndexUpdateTask(False),))
49
+ delayed_search_task = DelayedTasks(
50
+ until, (SearchIndexUpdateTask(rebuild=False),)
51
+ )
47
52
  self.librarian_queue.put(delayed_search_task)
48
53
  else:
49
54
  self.log.info("No updates neccissary.")
50
55
  if new_failed_imports:
51
- self.librarian_queue.put(FAILED_IMPORTS_TASK)
56
+ self.librarian_queue.put(FAILED_IMPORTS_CHANGED_TASK)
52
57
 
53
58
  def apply(self):
54
59
  """Bulk import comics."""
@@ -35,7 +35,7 @@ class ComicImporterThread(QueuedThread):
35
35
  import_comics = Comic.objects.filter(pk__in=task.pks).only("path", "library_id")
36
36
  library_path_map = {}
37
37
  for import_comic in import_comics:
38
- library_id = import_comic.library_id # type: ignore
38
+ library_id = import_comic.library_id # type: ignore[reportAttributeAccessIssue]
39
39
  if library_id not in library_path_map:
40
40
  library_path_map[library_id] = set()
41
41
  library_path_map[library_id].add(import_comic.path)
@@ -89,7 +89,7 @@ class ComicImporterThread(QueuedThread):
89
89
  importer.bulk_folders_moved()
90
90
  return True
91
91
 
92
- def _adopt_orphan_folders(self, janitor=False):
92
+ def _adopt_orphan_folders(self, janitor: bool):
93
93
  """Find orphan folders and move them into their correct place."""
94
94
  status = Status(ImportStatusTypes.ADOPT_FOLDERS)
95
95
  moved_status = Status(ImportStatusTypes.DIRS_MOVED)
@@ -118,6 +118,6 @@ class ComicImporterThread(QueuedThread):
118
118
  elif isinstance(task, UpdateGroupsTask):
119
119
  self._update_groups(task)
120
120
  elif isinstance(task, AdoptOrphanFoldersTask):
121
- self._adopt_orphan_folders(task.janitor)
121
+ self._adopt_orphan_folders(janitor=task.janitor)
122
122
  else:
123
123
  self.log.warning(f"Bad task sent to library updater {task}")