codex 1.6.0a6__py3-none-any.whl → 1.6.0a8__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 (612) hide show
  1. codex/exceptions.py +1 -0
  2. codex/integrity.py +4 -4
  3. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +56 -86
  4. codex/librarian/importer/cache.py +180 -0
  5. codex/librarian/importer/const.py +15 -6
  6. codex/librarian/importer/create_comics.py +102 -17
  7. codex/librarian/importer/create_covers.py +98 -0
  8. codex/librarian/importer/create_fks.py +73 -170
  9. codex/librarian/importer/deleted.py +59 -46
  10. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  11. codex/librarian/importer/failed_imports.py +41 -62
  12. codex/librarian/importer/importer.py +96 -0
  13. codex/librarian/importer/importerd.py +71 -395
  14. codex/librarian/importer/init.py +265 -0
  15. codex/librarian/importer/link_comics.py +26 -81
  16. codex/librarian/importer/link_covers.py +73 -0
  17. codex/librarian/importer/moved.py +72 -96
  18. codex/librarian/importer/query_covers.py +52 -0
  19. codex/librarian/importer/query_fks.py +128 -172
  20. codex/librarian/importer/status.py +2 -39
  21. codex/librarian/importer/tasks.py +22 -13
  22. codex/librarian/janitor/cleanup.py +2 -2
  23. codex/librarian/janitor/janitor.py +11 -1
  24. codex/librarian/librariand.py +8 -2
  25. codex/migrations/0027_import_order_and_covers.py +345 -0
  26. codex/models/comic.py +1 -2
  27. codex/models/groups.py +3 -2
  28. codex/models/paths.py +0 -1
  29. codex/permissions.py +1 -1
  30. codex/search/query.py +25 -2
  31. codex/search/writing.py +1 -1
  32. codex/serializers/admin.py +1 -2
  33. codex/serializers/browser/filters.py +8 -101
  34. codex/serializers/browser/mixins.py +2 -3
  35. codex/serializers/browser/page.py +2 -2
  36. codex/serializers/browser/settings.py +4 -3
  37. codex/serializers/choices.py +10 -1
  38. codex/serializers/fields.py +123 -0
  39. codex/serializers/mtime.py +25 -0
  40. codex/serializers/opds/v2.py +7 -11
  41. codex/serializers/opds/v2_unused.py +5 -3
  42. codex/serializers/reader.py +5 -2
  43. codex/serializers/route.py +7 -3
  44. codex/settings/settings.py +2 -2
  45. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js +1 -0
  46. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  47. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  48. codex/static_root/assets/VCheckbox-DI-DgMb4.js +1 -0
  49. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  50. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  51. codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js → VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js} +1 -1
  52. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  53. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  54. codex/static_root/assets/{VCheckboxBtn-m4IbdjEe.js → VCheckboxBtn-B8Z4NA-t.js} +1 -1
  55. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  56. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  57. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js +1 -0
  58. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  59. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  60. codex/static_root/assets/VCombobox-D1_0lCri.js +1 -0
  61. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  62. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  63. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js +1 -0
  64. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  65. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  66. codex/static_root/assets/VDialog-opmV0YZz.js +1 -0
  67. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  68. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  69. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js +1 -0
  70. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  71. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  72. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js +1 -0
  73. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  74. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  75. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js +1 -0
  76. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  77. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  78. codex/static_root/assets/VRadioGroup-BGa6_q5a.js +1 -0
  79. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  80. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  81. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  82. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  83. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  84. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  85. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  86. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  87. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +1 -0
  88. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  89. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  90. codex/static_root/assets/VSelect-sIYW_-ud.js +1 -0
  91. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  92. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  93. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js +1 -0
  94. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  95. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  96. codex/static_root/assets/VSelectionControl-C8DP9rgp.js +1 -0
  97. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  98. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  99. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js +1 -0
  100. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  101. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  102. codex/static_root/assets/VSlideGroup-BRYhkLH8.js +1 -0
  103. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  104. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  105. codex/static_root/assets/{VTable-C0gAriFq.fa6e4b60d4ef.js → VTable-D1esKtlw.52165930e3b9.js} +1 -1
  106. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  107. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  108. codex/static_root/assets/{VTable-C0gAriFq.js → VTable-D1esKtlw.js} +1 -1
  109. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  110. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  111. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +1 -0
  112. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  113. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  114. codex/static_root/assets/VTextField-BnGscKcY.js +1 -0
  115. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  116. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  117. codex/static_root/assets/{VWindowItem-CFhKCj49.b2458002a937.js → VWindowItem-B0KokcAk.ea94224aecb9.js} +1 -1
  118. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  119. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  120. codex/static_root/assets/{VWindowItem-CFhKCj49.js → VWindowItem-B0KokcAk.js} +1 -1
  121. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  122. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  123. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js +1 -0
  124. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  125. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  126. codex/static_root/assets/admin-Y0bL21AB.js +1 -0
  127. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  128. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  129. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js +30 -0
  130. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  131. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  132. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js +30 -0
  133. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  134. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  135. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
  136. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  137. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
  138. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
  139. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  140. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
  141. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +1 -0
  142. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  143. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  144. codex/static_root/assets/browser-CMAtBCLx.css +1 -0
  145. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  146. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  147. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +1 -0
  148. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  149. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  150. codex/static_root/assets/browser-DoJk8uvb.js +1 -0
  151. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  152. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  153. codex/static_root/assets/{change-password-dialog-JYM9aAR9.js → change-password-dialog-CH4xbjJa.b749aa6ea07e.js} +1 -1
  154. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  155. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  156. codex/static_root/assets/{change-password-dialog-JYM9aAR9.f71d378f4fa6.js → change-password-dialog-CH4xbjJa.js} +1 -1
  157. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  158. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  159. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.515858dd21e8.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
  160. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.br +0 -0
  161. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  162. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.js → confirm-dialog-DkU0twE6.js} +1 -1
  163. codex/static_root/assets/confirm-dialog-DkU0twE6.js.br +0 -0
  164. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  165. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
  166. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  167. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
  168. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  169. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +1 -0
  170. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  171. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  172. codex/static_root/assets/datetime-column-BfW_j_3Q.js +1 -0
  173. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  174. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  175. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  176. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  177. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js +1 -0
  178. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  179. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  180. codex/static_root/assets/filter-DjxN78cK.js +1 -0
  181. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  182. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  183. codex/static_root/assets/{flag-tab-Ci_XcDT0.a5ea640a08d6.js → flag-tab-D4ZNSbMR.e763a9937701.js} +1 -1
  184. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  185. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  186. codex/static_root/assets/{flag-tab-Ci_XcDT0.js → flag-tab-D4ZNSbMR.js} +1 -1
  187. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  188. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  189. codex/static_root/assets/{group-tab-DFma2R-E.879c37b15bbd.js → group-tab-DucpNoBs.a6f37ae940c0.js} +1 -1
  190. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  191. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  192. codex/static_root/assets/{group-tab-DFma2R-E.js → group-tab-DucpNoBs.js} +1 -1
  193. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  194. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  195. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js +1 -0
  196. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  197. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  198. codex/static_root/assets/http-error-Cj0hTq-c.js +1 -0
  199. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  200. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  201. codex/static_root/assets/{library-tab-V8DUGoHN.js → library-tab-CzksdS3Q.566acdb45dec.js} +1 -1
  202. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  203. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  204. codex/static_root/assets/{library-tab-V8DUGoHN.809c548ff347.js → library-tab-CzksdS3Q.js} +1 -1
  205. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  206. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  207. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +32 -0
  208. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  209. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  210. codex/static_root/assets/main-DX-k2724.js +32 -0
  211. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  212. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  213. codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
  214. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  215. codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
  216. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
  217. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  218. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
  219. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +1 -0
  220. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  221. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  222. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +1 -0
  223. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  224. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  225. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  226. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  227. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  228. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  229. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  230. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  231. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js +305 -0
  232. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  233. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  234. codex/static_root/assets/pdf-doc-GkMk4o5L.js +305 -0
  235. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  236. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  237. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  238. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  239. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  240. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  241. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  242. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  243. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +2 -0
  244. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  245. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  246. codex/static_root/assets/reader-Cl6mmQB_.js +2 -0
  247. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  248. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  249. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js +1 -0
  250. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  251. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  252. codex/static_root/assets/relation-chips-CZ8VvR4y.js +1 -0
  253. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  254. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  255. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js +2 -0
  256. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  257. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  258. codex/static_root/assets/settings-drawer-DyS0A2pN.js +2 -0
  259. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  260. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  261. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js +1 -0
  262. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  263. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  264. codex/static_root/assets/stats-tab-R3Ekobfm.js +1 -0
  265. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  266. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  267. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +1 -0
  268. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  269. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  270. codex/static_root/assets/task-tab-BhLDK_7Q.js +1 -0
  271. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  272. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  273. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +1 -0
  274. codex/static_root/assets/to-case-BR2frjFx.js +1 -0
  275. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js +1 -0
  276. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  277. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  278. codex/static_root/assets/unauthorized-BbXjAe5l.js +1 -0
  279. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  280. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  281. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js +1 -0
  282. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  283. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  284. codex/static_root/assets/user-tab-DxITIl2d.js +1 -0
  285. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  286. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  287. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  288. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  289. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  290. codex/static_root/js/choices-admin.json +1 -1
  291. codex/static_root/js/choices-admin.json.br +0 -0
  292. codex/static_root/js/choices-admin.json.gz +0 -0
  293. codex/static_root/js/choices.a0aaaa6c7ef7.json +1 -0
  294. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  295. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  296. codex/static_root/js/choices.json +1 -1
  297. codex/static_root/js/choices.json.br +0 -0
  298. codex/static_root/js/choices.json.gz +0 -0
  299. codex/static_root/manifest.16d52597ef50.json +629 -0
  300. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  301. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  302. codex/static_root/manifest.json +275 -288
  303. codex/static_root/manifest.json.br +0 -0
  304. codex/static_root/manifest.json.gz +0 -0
  305. codex/static_root/staticfiles.json +1 -1
  306. codex/urls/api/auth.py +2 -1
  307. codex/urls/api/browser.py +13 -4
  308. codex/urls/api/reader.py +3 -9
  309. codex/urls/api/v3.py +2 -0
  310. codex/urls/opds/binary.py +1 -1
  311. codex/util.py +10 -0
  312. codex/views/admin/api_key.py +2 -6
  313. codex/views/admin/auth.py +28 -0
  314. codex/views/admin/flag.py +2 -7
  315. codex/views/admin/group.py +2 -6
  316. codex/views/admin/library.py +7 -20
  317. codex/views/admin/stats.py +7 -66
  318. codex/views/admin/tasks.py +24 -11
  319. codex/views/admin/user.py +3 -9
  320. codex/views/auth.py +30 -53
  321. codex/views/bookmark.py +7 -11
  322. codex/views/browser/{browser_annotations.py → annotations.py} +90 -181
  323. codex/views/browser/base.py +23 -88
  324. codex/views/browser/breadcrumbs.py +202 -0
  325. codex/views/browser/browser.py +64 -342
  326. codex/views/browser/choices.py +22 -54
  327. codex/views/browser/cover.py +177 -0
  328. codex/views/browser/filters/annotations.py +24 -23
  329. codex/views/browser/filters/bookmark.py +9 -13
  330. codex/views/browser/filters/field.py +5 -5
  331. codex/views/browser/filters/group.py +18 -10
  332. codex/views/browser/filters/search.py +30 -18
  333. codex/views/browser/metadata.py +22 -31
  334. codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
  335. codex/views/browser/paginate.py +106 -0
  336. codex/views/browser/{session.py → settings.py} +4 -3
  337. codex/views/browser/title.py +48 -0
  338. codex/views/browser/validate.py +158 -0
  339. codex/views/const.py +109 -0
  340. codex/views/download.py +5 -8
  341. codex/views/frontend.py +4 -2
  342. codex/views/mtime.py +72 -0
  343. codex/views/opds/auth.py +17 -0
  344. codex/views/opds/authentication_v1.py +46 -45
  345. codex/views/opds/binary.py +5 -10
  346. codex/views/opds/const.py +13 -0
  347. codex/views/opds/urls.py +6 -4
  348. codex/views/opds/util.py +4 -12
  349. codex/views/opds/v1/entry/links.py +18 -33
  350. codex/views/opds/v1/facets.py +7 -7
  351. codex/views/opds/v1/feed.py +52 -40
  352. codex/views/opds/v1/links.py +7 -5
  353. codex/views/opds/v1/opensearch_v1.py +2 -6
  354. codex/views/opds/v2/feed.py +22 -23
  355. codex/views/opds/v2/links.py +4 -2
  356. codex/views/opds/v2/publications.py +34 -54
  357. codex/views/opds/v2/top_links.py +2 -2
  358. codex/views/public.py +36 -0
  359. codex/views/reader/arcs.py +101 -0
  360. codex/views/reader/books.py +199 -0
  361. codex/views/reader/page.py +3 -7
  362. codex/views/reader/reader.py +56 -301
  363. codex/views/reader/{session.py → settings.py} +4 -2
  364. codex/views/session.py +77 -103
  365. codex/views/settings.py +34 -0
  366. codex/views/template.py +3 -3
  367. codex/views/util.py +63 -0
  368. codex/views/version.py +2 -2
  369. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/METADATA +19 -14
  370. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/RECORD +373 -362
  371. codex/librarian/importer/covers.py +0 -47
  372. codex/librarian/importer/update_comics.py +0 -85
  373. codex/migrations/0027_sort_name.py +0 -139
  374. codex/migrations/0028_custom_covers.py +0 -166
  375. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  376. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js +0 -1
  377. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
  378. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
  379. codex/static_root/assets/VCheckbox-BS5fdM4M.js +0 -1
  380. codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
  381. codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
  382. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
  383. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
  384. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
  385. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
  386. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +0 -1
  387. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
  388. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
  389. codex/static_root/assets/VCombobox-jg7SI5mn.js +0 -1
  390. codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
  391. codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
  392. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js +0 -1
  393. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
  394. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
  395. codex/static_root/assets/VDialog-DiixpkQL.js +0 -1
  396. codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
  397. codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
  398. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js +0 -1
  399. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
  400. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
  401. codex/static_root/assets/VExpansionPanels-Db5mPFue.js +0 -1
  402. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
  403. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
  404. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js +0 -1
  405. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
  406. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
  407. codex/static_root/assets/VRadioGroup-DULWiMSI.js +0 -1
  408. codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
  409. codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
  410. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +0 -1
  411. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
  412. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
  413. codex/static_root/assets/VSelect-BnoCfCLY.js +0 -1
  414. codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
  415. codex/static_root/assets/VSelect-BnoCfCLY.js.gz +0 -0
  416. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  417. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  418. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  419. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  420. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  421. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  422. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js +0 -1
  423. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
  424. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
  425. codex/static_root/assets/VSelectionControl-BSL0qeL9.js +0 -1
  426. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
  427. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
  428. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js +0 -1
  429. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
  430. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
  431. codex/static_root/assets/VSlideGroup-BQtz8h9p.js +0 -1
  432. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
  433. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
  434. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
  435. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
  436. codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
  437. codex/static_root/assets/VTable-C0gAriFq.js.gz +0 -0
  438. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js +0 -1
  439. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
  440. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
  441. codex/static_root/assets/VTextField-Da_Hs7ls.js +0 -1
  442. codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
  443. codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
  444. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
  445. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
  446. codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
  447. codex/static_root/assets/VWindowItem-CFhKCj49.js.gz +0 -0
  448. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js +0 -1
  449. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
  450. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
  451. codex/static_root/assets/admin-BpGFsvOH.js +0 -1
  452. codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
  453. codex/static_root/assets/admin-BpGFsvOH.js.gz +0 -0
  454. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
  455. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
  456. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +0 -30
  457. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
  458. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
  459. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +0 -30
  460. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
  461. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
  462. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +0 -1
  463. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
  464. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
  465. codex/static_root/assets/browser-CRPvEdqv.js +0 -1
  466. codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
  467. codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
  468. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +0 -1
  469. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
  470. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
  471. codex/static_root/assets/browser-Zqc9cR6T.css +0 -1
  472. codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
  473. codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
  474. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
  475. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
  476. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
  477. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
  478. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
  479. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
  480. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
  481. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
  482. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js +0 -1
  483. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
  484. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
  485. codex/static_root/assets/datetime-column-BT1MSP82.js +0 -1
  486. codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
  487. codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
  488. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  489. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  490. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  491. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  492. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js +0 -1
  493. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
  494. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
  495. codex/static_root/assets/filter-frSNsexx.js +0 -1
  496. codex/static_root/assets/filter-frSNsexx.js.br +0 -0
  497. codex/static_root/assets/filter-frSNsexx.js.gz +0 -0
  498. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
  499. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
  500. codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
  501. codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
  502. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
  503. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
  504. codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
  505. codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
  506. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js +0 -1
  507. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
  508. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
  509. codex/static_root/assets/http-error-B3fQGxpt.js +0 -1
  510. codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
  511. codex/static_root/assets/http-error-B3fQGxpt.js.gz +0 -0
  512. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
  513. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  514. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  515. codex/static_root/assets/index-DUjeNg4x.js +0 -1
  516. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  517. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  518. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
  519. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
  520. codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
  521. codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
  522. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js +0 -32
  523. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
  524. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
  525. codex/static_root/assets/main-Cavvqkyp.js +0 -32
  526. codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
  527. codex/static_root/assets/main-Cavvqkyp.js.gz +0 -0
  528. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  529. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  530. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css +0 -1
  531. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
  532. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
  533. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css +0 -1
  534. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
  535. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
  536. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js +0 -1
  537. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
  538. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
  539. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +0 -1
  540. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
  541. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
  542. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js +0 -35
  543. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
  544. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
  545. codex/static_root/assets/pdf-doc-4PKLN-Z3.js +0 -35
  546. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
  547. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
  548. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js +0 -2
  549. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
  550. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
  551. codex/static_root/assets/reader-C39uGGxn.js +0 -2
  552. codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
  553. codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
  554. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css +0 -1
  555. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
  556. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
  557. codex/static_root/assets/reader-N2Ei_y2K.css +0 -1
  558. codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
  559. codex/static_root/assets/reader-N2Ei_y2K.css.gz +0 -0
  560. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js +0 -1
  561. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
  562. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
  563. codex/static_root/assets/relation-chips-CAC5ytqH.js +0 -1
  564. codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
  565. codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
  566. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js +0 -2
  567. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
  568. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
  569. codex/static_root/assets/settings-drawer-CT73ieiV.js +0 -2
  570. codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
  571. codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
  572. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js +0 -1
  573. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
  574. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
  575. codex/static_root/assets/stats-tab-C6qAnV6-.js +0 -1
  576. codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
  577. codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
  578. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +0 -1
  579. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
  580. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
  581. codex/static_root/assets/task-tab-JcG2vvgt.js +0 -1
  582. codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
  583. codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
  584. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
  585. codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
  586. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js +0 -1
  587. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
  588. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
  589. codex/static_root/assets/unauthorized-28bmiwAU.js +0 -1
  590. codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
  591. codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
  592. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js +0 -1
  593. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
  594. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
  595. codex/static_root/assets/user-tab-BOq4BMMc.js +0 -1
  596. codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
  597. codex/static_root/assets/user-tab-BOq4BMMc.js.gz +0 -0
  598. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  599. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  600. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  601. codex/static_root/js/choices.23fedac2b9ba.json +0 -1
  602. codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
  603. codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
  604. codex/static_root/manifest.ad9da9714e46.json +0 -642
  605. codex/static_root/manifest.ad9da9714e46.json.br +0 -0
  606. codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
  607. codex/views/browser/browser_breadcrumbs.py +0 -175
  608. codex/views/browser/const.py +0 -3
  609. codex/views/cover.py +0 -78
  610. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
  611. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
  612. {codex-1.6.0a6.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,98 @@
1
+ """Create Custom Covers."""
2
+
3
+ from django.db.models.functions.datetime import Now
4
+
5
+ from codex.librarian.importer.const import (
6
+ COVERS_CREATE,
7
+ COVERS_UPDATE,
8
+ CUSTOM_COVER_UPDATE_FIELDS,
9
+ LINK_COVER_PKS,
10
+ )
11
+ from codex.librarian.importer.create_comics import CreateComicsImporter
12
+ from codex.librarian.importer.status import ImportStatusTypes
13
+ from codex.models import (
14
+ CustomCover,
15
+ )
16
+ from codex.status import Status
17
+
18
+
19
+ class CreateCoversImporter(CreateComicsImporter):
20
+ """Create Custom Covers."""
21
+
22
+ def update_custom_covers(self, status=None):
23
+ """Update Custom Covers."""
24
+ update_covers_qs = self.metadata.pop(COVERS_UPDATE, None)
25
+ if not update_covers_qs:
26
+ return
27
+ update_covers_count = update_covers_qs.count()
28
+ if not update_covers_count:
29
+ return
30
+ status = Status(ImportStatusTypes.COVERS_MODIFIED, 0, update_covers_count)
31
+ self.status_controller.start(status)
32
+
33
+ now = Now()
34
+
35
+ update_covers = []
36
+ for cover in update_covers_qs.only(*CUSTOM_COVER_UPDATE_FIELDS):
37
+ cover.updated_at = now
38
+ cover.presave()
39
+ update_covers.append(cover)
40
+
41
+ count = 0
42
+ if update_covers:
43
+ CustomCover.objects.bulk_update(update_covers, CUSTOM_COVER_UPDATE_FIELDS)
44
+ update_cover_pks = update_covers_qs.values_list("pk", flat=True)
45
+ if LINK_COVER_PKS not in self.metadata:
46
+ self.metadata[LINK_COVER_PKS] = set()
47
+ self.metadata[LINK_COVER_PKS].update(update_cover_pks)
48
+ self._remove_covers(update_cover_pks, custom=True) # type: ignore
49
+ count = len(update_covers)
50
+ if status:
51
+ status.add_complete(count)
52
+
53
+ link_covers_status = Status(
54
+ ImportStatusTypes.COVERS_LINK, 0, len(self.metadata[LINK_COVER_PKS])
55
+ )
56
+ self.status_controller.update(link_covers_status, notify=False)
57
+
58
+ self.changed += count
59
+ self.status_controller.finish(status)
60
+
61
+ def create_custom_covers(self, status=None):
62
+ """Create Custom Covers."""
63
+ create_cover_paths = self.metadata.pop(COVERS_CREATE, ())
64
+ num_create_cover_paths = len(create_cover_paths)
65
+ if not num_create_cover_paths:
66
+ return
67
+ status = Status(ImportStatusTypes.COVERS_MODIFIED, 0, num_create_cover_paths)
68
+ self.status_controller.start(status)
69
+
70
+ create_covers = []
71
+ for path in create_cover_paths:
72
+ cover = CustomCover(library=self.library, path=path)
73
+ cover.presave()
74
+ create_covers.append(cover)
75
+
76
+ count = 0
77
+ if create_covers:
78
+ objs = CustomCover.objects.bulk_create(
79
+ create_covers,
80
+ update_conflicts=True,
81
+ update_fields=("path", "stat"),
82
+ unique_fields=CustomCover._meta.unique_together[0],
83
+ )
84
+ created_pks = frozenset(obj.pk for obj in objs)
85
+ if LINK_COVER_PKS not in self.metadata:
86
+ self.metadata[LINK_COVER_PKS] = set()
87
+ self.metadata[LINK_COVER_PKS].update(created_pks)
88
+ count = len(created_pks)
89
+ if status:
90
+ status.add_complete(count)
91
+
92
+ link_covers_status = Status(
93
+ ImportStatusTypes.COVERS_LINK, 0, len(self.metadata[LINK_COVER_PKS])
94
+ )
95
+ self.status_controller.update(link_covers_status, notify=False)
96
+
97
+ self.changed += count
98
+ self.status_controller.finish(status)
@@ -6,14 +6,21 @@ So we may safely create the comics next.
6
6
  from pathlib import Path
7
7
 
8
8
  from django.core.exceptions import ObjectDoesNotExist
9
- from django.db.models.functions.datetime import Now
10
9
 
11
10
  from codex.librarian.importer.const import (
12
11
  BULK_UPDATE_FOLDER_FIELDS,
13
12
  CLASS_CUSTOM_COVER_GROUP_MAP,
14
13
  COUNT_FIELDS,
15
14
  CREATE_DICT_UPDATE_FIELDS,
16
- CUSTOM_COVER_UPDATE_FIELDS,
15
+ FK_CREATE,
16
+ FKC_CONTRIBUTORS,
17
+ FKC_CREATE_FKS,
18
+ FKC_CREATE_GROUPS,
19
+ FKC_FOLDER_PATHS,
20
+ FKC_IDENTIFIERS,
21
+ FKC_STORY_ARC_NUMBERS,
22
+ FKC_TOTAL_FKS,
23
+ FKC_UPDATE_GROUPS,
17
24
  GROUP_BASE_FIELDS,
18
25
  GROUP_UPDATE_FIELDS,
19
26
  IMPRINT,
@@ -23,7 +30,8 @@ from codex.librarian.importer.const import (
23
30
  SERIES,
24
31
  VOLUME_COUNT,
25
32
  )
26
- from codex.librarian.importer.status import ImportStatusTypes, status_notify
33
+ from codex.librarian.importer.create_covers import CreateCoversImporter
34
+ from codex.librarian.importer.status import ImportStatusTypes
27
35
  from codex.models import (
28
36
  Contributor,
29
37
  ContributorPerson,
@@ -39,10 +47,9 @@ from codex.models import (
39
47
  )
40
48
  from codex.models.named import Identifier, IdentifierType
41
49
  from codex.status import Status
42
- from codex.threads import QueuedThread
43
50
 
44
51
 
45
- class CreateForeignKeysMixin(QueuedThread):
52
+ class CreateForeignKeysImporter(CreateCoversImporter):
46
53
  """Methods for creating foreign keys."""
47
54
 
48
55
  @staticmethod
@@ -107,12 +114,11 @@ class CreateForeignKeysMixin(QueuedThread):
107
114
  obj = None
108
115
  return obj
109
116
 
110
- @status_notify()
111
- def _bulk_group_create(self, group_tree_counts, group_class, status=None) -> int:
117
+ def _bulk_group_create(self, group_tree_counts, group_class, status):
112
118
  """Bulk creates groups."""
113
119
  count = 0
114
120
  if not group_tree_counts:
115
- return count
121
+ return
116
122
  create_groups = []
117
123
  for group_param_tuple, group_count in group_tree_counts.items():
118
124
  obj = self._create_group_obj(group_class, group_param_tuple, group_count)
@@ -130,18 +136,14 @@ class CreateForeignKeysMixin(QueuedThread):
130
136
  if count:
131
137
  vnp = group_class._meta.verbose_name_plural.title()
132
138
  self.log.info(f"Created {count} {vnp}.")
133
- if status:
134
- status.complete = status.complete or 0
135
- status.complete += count
136
- self.status_controller.update(status)
137
- return count
139
+ status.add_complete(count)
140
+ self.status_controller.update(status)
141
+ return
138
142
 
139
- @status_notify()
140
- def _bulk_group_updater(self, group_tree_counts, group_class, status=None):
143
+ def _bulk_group_updater(self, group_tree_counts, group_class, status):
141
144
  """Bulk update groups."""
142
- count = 0
143
145
  if not group_tree_counts:
144
- return count
146
+ return
145
147
  update_groups = []
146
148
  for group_param_tuple, group_count in group_tree_counts.items():
147
149
  obj = self._update_group_obj(group_class, group_param_tuple, group_count)
@@ -149,30 +151,27 @@ class CreateForeignKeysMixin(QueuedThread):
149
151
  update_groups.append(obj)
150
152
  count_field = COUNT_FIELDS[group_class]
151
153
  group_class.objects.bulk_update(update_groups, fields=[count_field])
152
- count += len(update_groups)
154
+ count = len(update_groups)
153
155
  if count:
154
156
  vnp = group_class._meta.verbose_name_plural.title()
155
157
  self.log.info(f"Updated {count} {vnp}.")
156
- if status:
157
- status.complete = status.complete or 0
158
- status.complete += count
159
- self.status_controller.update(status)
160
- return count
158
+ status.add_complete(count)
159
+ self.status_controller.update(status)
161
160
 
162
- def _bulk_folders_create_add_folder(self, library, path, create_folders):
161
+ def _bulk_folders_create_add_folder(self, path, create_folders):
163
162
  """Add one folder to the create list."""
164
163
  parent_path = str(path.parent)
165
164
  parent = None
166
165
  try:
167
166
  parent = Folder.objects.get(path=parent_path)
168
167
  except Folder.DoesNotExist:
169
- if path.parent != Path(library.path):
168
+ if path.parent != Path(self.library.path):
170
169
  self.log.warning(
171
170
  f"Can't find parent folder {parent_path}"
172
- f" for {path} in library {library.path}"
171
+ f" for {path} in library {self.library.path}"
173
172
  )
174
173
  folder = Folder(
175
- library=library,
174
+ library=self.library,
176
175
  path=str(path),
177
176
  name=path.name,
178
177
  parent_folder=parent,
@@ -181,11 +180,11 @@ class CreateForeignKeysMixin(QueuedThread):
181
180
  self._add_custom_cover_to_group(Folder, folder)
182
181
  create_folders.append(folder)
183
182
 
184
- def _bulk_folders_create_depth_level(self, library, paths, status):
183
+ def _bulk_folders_create_depth_level(self, paths, status):
185
184
  """Create a depth level of folders."""
186
185
  create_folders = []
187
186
  for path in sorted(paths):
188
- self._bulk_folders_create_add_folder(library, path, create_folders)
187
+ self._bulk_folders_create_add_folder(path, create_folders)
189
188
  Folder.objects.bulk_create(
190
189
  create_folders,
191
190
  update_conflicts=True,
@@ -193,18 +192,15 @@ class CreateForeignKeysMixin(QueuedThread):
193
192
  unique_fields=Folder._meta.unique_together[0], # type: ignore
194
193
  )
195
194
  count = len(create_folders)
196
- if status:
197
- status.complete = status.complete or 0
198
- status.complete += count
199
- self.status_controller.update(status)
195
+ status.add_complete(count)
196
+ self.status_controller.update(status)
200
197
  return count
201
198
 
202
- @status_notify()
203
- def bulk_folders_create(self, folder_paths: set, library, status=None):
199
+ def bulk_folders_create(self, folder_paths: frozenset, status):
204
200
  """Create folders breadth first."""
205
201
  count = 0
206
202
  if not folder_paths:
207
- return count
203
+ return
208
204
  # group folder paths by depth
209
205
  folder_path_dict = {}
210
206
  for path_str in folder_paths:
@@ -215,19 +211,18 @@ class CreateForeignKeysMixin(QueuedThread):
215
211
  folder_path_dict[path_length].add(path)
216
212
 
217
213
  # create each depth level first to ensure we can assign parents
218
- for _, paths in sorted(folder_path_dict.items()):
219
- count += self._bulk_folders_create_depth_level(library, paths, status)
214
+ for paths in sorted(folder_path_dict.values()):
215
+ count += self._bulk_folders_create_depth_level(paths, status)
220
216
 
221
217
  if count:
222
218
  self.log.info(f"Created {count} Folders.")
223
- return count
219
+ self.status_controller.update(status)
224
220
 
225
- @status_notify()
226
- def _bulk_create_named_models(self, names, named_class, status=None):
221
+ def _bulk_create_named_models(self, names, named_class, status):
227
222
  """Bulk create named models."""
228
223
  count = len(names)
229
224
  if not count:
230
- return count
225
+ return
231
226
  create_named_objs = []
232
227
  is_story_arc = named_class == StoryArc
233
228
  for name in names:
@@ -250,11 +245,8 @@ class CreateForeignKeysMixin(QueuedThread):
250
245
  if count:
251
246
  vnp = named_class._meta.verbose_name_plural.title()
252
247
  self.log.info(f"Created {count} {vnp}.")
253
- if status:
254
- status.complete = status.complete or 0
255
- status.complete += count
256
- self.status_controller.update(status)
257
- return count
248
+ status.add_complete(count)
249
+ self.status_controller.update(status)
258
250
 
259
251
  @staticmethod
260
252
  def _create_contributor_args(values):
@@ -275,17 +267,23 @@ class CreateForeignKeysMixin(QueuedThread):
275
267
  "url": values[2],
276
268
  }
277
269
 
278
- @status_notify()
270
+ _CREATE_DICT_FUNCTION_MAP = (
271
+ (Contributor, FKC_CONTRIBUTORS, _create_contributor_args),
272
+ (StoryArcNumber, FKC_STORY_ARC_NUMBERS, _create_story_arc_number_args),
273
+ (Identifier, FKC_IDENTIFIERS, _create_identifier_args),
274
+ )
275
+
279
276
  def _bulk_create_dict_models(
280
- self, create_tuples, create_args_method, model, status=None
277
+ self, create_tuples_key, create_args_func, model, status
281
278
  ):
282
279
  """Bulk create a dict type m2m model."""
280
+ create_tuples = self.metadata[FK_CREATE].pop(create_tuples_key, None)
283
281
  if not create_tuples:
284
- return 0
282
+ return
285
283
 
286
284
  create_objs = []
287
285
  for values_tuple in create_tuples:
288
- args = create_args_method(values_tuple)
286
+ args = create_args_func(values_tuple)
289
287
  obj = model(**args)
290
288
  create_objs.append(obj)
291
289
 
@@ -299,139 +297,44 @@ class CreateForeignKeysMixin(QueuedThread):
299
297
  if count:
300
298
  vnp = model._meta.verbose_name_plural.title()
301
299
  self.log.info(f"Created {count} {vnp}.")
302
- if status:
303
- status.complete = status.complete or 0
304
- status.complete += count
305
- self.status_controller.update(status)
306
- return count
300
+ status.add_complete(count)
301
+ self.status_controller.update(status)
307
302
 
308
- def create_all_fks(self, library, create_data) -> int:
303
+ def create_all_fks(self):
309
304
  """Bulk create all foreign keys."""
310
- (
311
- create_groups,
312
- update_groups,
313
- create_folder_paths,
314
- create_fks,
315
- create_contributors,
316
- create_story_arc_numbers,
317
- create_identifiers,
318
- total_fks,
319
- ) = create_data
320
-
321
- status = Status(ImportStatusTypes.CREATE_FKS, 0, total_fks)
305
+ fkc = self.metadata.get(FK_CREATE)
306
+ if not fkc:
307
+ return
308
+
309
+ status = Status(ImportStatusTypes.CREATE_FKS, 0, fkc.pop(FKC_TOTAL_FKS, None))
322
310
  try:
323
311
  self.status_controller.start(status)
324
312
 
325
- for group_class, group_tree_counts in create_groups.items():
326
- count = self._bulk_group_create(
327
- group_tree_counts,
328
- group_class,
329
- status=status,
330
- )
331
- status.add_complete(count)
313
+ for group_class, group_tree_counts in fkc.pop(
314
+ FKC_CREATE_GROUPS, {}
315
+ ).items():
316
+ self._bulk_group_create(group_tree_counts, group_class, status)
332
317
 
333
- for group_class, group_tree_counts in update_groups.items():
334
- count = self._bulk_group_updater(
335
- group_tree_counts,
336
- group_class,
337
- status=status,
338
- )
339
- status.add_complete(count)
318
+ for group_class, group_tree_counts in fkc.pop(
319
+ FKC_UPDATE_GROUPS, {}
320
+ ).items():
321
+ self._bulk_group_updater(group_tree_counts, group_class, status)
340
322
 
341
- count = self.bulk_folders_create(
342
- sorted(create_folder_paths),
343
- library,
344
- status=status,
345
- )
346
- status.add_complete(count)
323
+ self.bulk_folders_create(fkc.pop(FKC_FOLDER_PATHS, frozenset()), status)
347
324
 
348
- for named_class, names in create_fks.items():
349
- count = self._bulk_create_named_models(
350
- names,
351
- named_class,
352
- status=status,
353
- )
354
- status.add_complete(count)
325
+ for named_class, names in fkc.pop(FKC_CREATE_FKS, {}).items():
326
+ self._bulk_create_named_models(names, named_class, status)
355
327
 
356
328
  # These all depend on bulk_create_named_models running first
357
- create_dict_data = (
358
- (Contributor, create_contributors, self._create_contributor_args),
359
- (
360
- StoryArcNumber,
361
- create_story_arc_numbers,
362
- self._create_story_arc_number_args,
363
- ),
364
- (Identifier, create_identifiers, self._create_identifier_args),
365
- )
366
- for model, create_objs, func in create_dict_data:
367
- count = self._bulk_create_dict_models(
329
+ for model, create_objs, func in self._CREATE_DICT_FUNCTION_MAP:
330
+ self._bulk_create_dict_models(
368
331
  create_objs,
369
332
  func,
370
333
  model,
371
- status=status,
334
+ status,
372
335
  )
373
- status.add_complete(count)
374
336
 
375
337
  finally:
338
+ self.metadata.pop(FK_CREATE, None)
376
339
  self.status_controller.finish(status)
377
- return status.complete if status.complete else 0
378
-
379
- @status_notify(ImportStatusTypes.COVERS_MODIFIED, updates=False)
380
- def update_custom_covers(
381
- self, update_covers_qs, link_cover_pks, status=None
382
- ) -> int:
383
- """Update Custom Covers."""
384
- count = 0
385
- update_covers_count = update_covers_qs.count()
386
- if not update_covers_count:
387
- return count
388
- if status:
389
- status.total = update_covers_count
390
- now = Now()
391
-
392
- update_covers = []
393
- for cover in update_covers_qs.only(*CUSTOM_COVER_UPDATE_FIELDS):
394
- cover.updated_at = now
395
- cover.presave()
396
- update_covers.append(cover)
397
-
398
- if update_covers:
399
- CustomCover.objects.bulk_update(update_covers, CUSTOM_COVER_UPDATE_FIELDS)
400
- update_cover_pks = update_covers_qs.values_list("pk", flat=True)
401
- link_cover_pks.update(update_cover_pks)
402
- self._remove_covers(update_cover_pks, custom=True) # type: ignore
403
- count = len(update_covers)
404
- if status:
405
- status.add_complete(count)
406
- return count
407
-
408
- @status_notify(ImportStatusTypes.COVERS_CREATED, updates=False)
409
- def create_custom_covers(
410
- self, create_cover_paths, library, link_cover_pks, status=None
411
- ) -> int:
412
- """Create Custom Covers."""
413
- count = 0
414
- if not create_cover_paths:
415
- return count
416
- if status:
417
- status.total = len(create_cover_paths)
418
-
419
- create_covers = []
420
- for path in create_cover_paths:
421
- cover = CustomCover(library=library, path=path)
422
- cover.presave()
423
- create_covers.append(cover)
424
-
425
- if create_covers:
426
- objs = CustomCover.objects.bulk_create(
427
- create_covers,
428
- update_conflicts=True,
429
- update_fields=("path", "stat"),
430
- unique_fields=CustomCover._meta.unique_together[0],
431
- )
432
- created_pks = frozenset(obj.pk for obj in objs)
433
- link_cover_pks.update(created_pks)
434
- count = len(created_pks)
435
- if status:
436
- status.add_complete(count)
437
- return count
340
+ self.changed += status.complete if status.complete else 0
@@ -1,29 +1,34 @@
1
1
  """Clean up the database after moves or imports."""
2
2
 
3
3
  from codex.librarian.covers.tasks import CoverRemoveTask
4
+ from codex.librarian.importer.cache import CacheUpdateImporter
4
5
  from codex.librarian.importer.const import COMIC_GROUP_FIELD_NAMES
5
- from codex.librarian.importer.status import ImportStatusTypes, status_notify
6
+ from codex.librarian.importer.status import ImportStatusTypes
6
7
  from codex.models import Comic, Folder, StoryArc
7
8
  from codex.models.paths import CustomCover
8
9
  from codex.settings.settings import MAX_CHUNK_SIZE
9
- from codex.threads import QueuedThread
10
+ from codex.status import Status
10
11
 
11
12
 
12
- class DeletedMixin(QueuedThread):
13
+ class DeletedImporter(CacheUpdateImporter):
13
14
  """Clean up database methods."""
14
15
 
15
16
  def _remove_covers(self, delete_pks, custom=False):
16
17
  task = CoverRemoveTask(delete_pks, custom)
17
18
  self.librarian_queue.put(task)
18
19
 
19
- @status_notify(status_type=ImportStatusTypes.DIRS_DELETED, updates=False)
20
- def _bulk_folders_deleted(self, delete_folder_paths, library, **kwargs):
20
+ def _bulk_folders_deleted(self, **kwargs):
21
21
  """Bulk delete folders."""
22
- if not delete_folder_paths:
22
+ if not self.task.dirs_deleted:
23
23
  return 0
24
- folders = Folder.objects.filter(library=library, path__in=delete_folder_paths)
24
+ status = Status(ImportStatusTypes.DIRS_DELETED, 0, len(self.task.dirs_deleted))
25
+ self.status_controller.start(status)
26
+ folders = Folder.objects.filter(
27
+ library=self.library, path__in=self.task.dirs_deleted
28
+ )
29
+ self.task.dirs_deleted = frozenset()
25
30
  delete_comic_pks = frozenset(
26
- Comic.objects.filter(library=library, folders__in=folders)
31
+ Comic.objects.filter(library=self.library, folders__in=folders)
27
32
  .distinct()
28
33
  .values_list("pk", flat=True)
29
34
  )
@@ -31,14 +36,27 @@ class DeletedMixin(QueuedThread):
31
36
 
32
37
  self._remove_covers(delete_comic_pks)
33
38
 
34
- count = len(delete_folder_paths)
39
+ count = len(delete_comic_pks)
35
40
  if count:
36
41
  self.log.info(
37
42
  f"Deleted {count} folders and {len(delete_comic_pks)} comics"
38
- f"from {library.path}"
43
+ f"from {self.library.path}"
39
44
  )
45
+ self.status_controller.finish(status)
40
46
  return count
41
47
 
48
+ @staticmethod
49
+ def _init_deleted_comic_groups():
50
+ """Init deleted_comic_groups, used later even if no deletes."""
51
+ deleted_comic_groups = {}
52
+ for field_name in COMIC_GROUP_FIELD_NAMES:
53
+ if field_name == "story_arc_numbers":
54
+ related_model = StoryArc
55
+ else:
56
+ related_model = Comic._meta.get_field(field_name).related_model
57
+ deleted_comic_groups[related_model] = set()
58
+ return deleted_comic_groups
59
+
42
60
  @staticmethod
43
61
  def _populate_deleted_comic_groups(delete_qs, deleted_comic_groups):
44
62
  """Populate changed groups for cover timestamp updater."""
@@ -60,21 +78,18 @@ class DeletedMixin(QueuedThread):
60
78
  related_id = getattr(comic, field_name).pk
61
79
  deleted_comic_groups[related_model].add(related_id)
62
80
 
63
- @staticmethod
64
- def _init_deleted_comic_groups(deleted_comic_groups):
65
- """Init deleted_comic_groups, used later even if no deletes."""
66
- for field_name in COMIC_GROUP_FIELD_NAMES:
67
- related_model = Comic._meta.get_field(field_name).related_model
68
- deleted_comic_groups[related_model] = set()
69
-
70
- @status_notify(status_type=ImportStatusTypes.FILES_DELETED, updates=False)
71
- def _bulk_comics_deleted(
72
- self, delete_comic_paths, library, deleted_comic_groups, **kwargs
73
- ):
81
+ def _bulk_comics_deleted(self, deleted_comic_groups, **kwargs):
74
82
  """Bulk delete comics found missing from the filesystem."""
75
- if not delete_comic_paths:
83
+ if not self.task.files_deleted:
76
84
  return 0
77
- delete_qs = Comic.objects.filter(library=library, path__in=delete_comic_paths)
85
+ status = Status(
86
+ ImportStatusTypes.FILES_DELETED, 0, len(self.task.files_deleted)
87
+ )
88
+ self.status_controller.start(status)
89
+ delete_qs = Comic.objects.filter(
90
+ library=self.library, path__in=self.task.files_deleted
91
+ )
92
+ self.task.files_deleted = frozenset()
78
93
 
79
94
  self._populate_deleted_comic_groups(delete_qs, deleted_comic_groups)
80
95
 
@@ -83,43 +98,41 @@ class DeletedMixin(QueuedThread):
83
98
 
84
99
  self._remove_covers(delete_comic_pks)
85
100
 
86
- count = len(delete_comic_paths)
101
+ count = len(delete_comic_pks)
87
102
  if count:
88
- self.log.info(f"Deleted {count} comics from {library.path}")
89
-
103
+ self.log.info(f"Deleted {count} comics from {self.library.path}")
104
+ self.status_controller.finish(status)
90
105
  return count
91
106
 
92
- @status_notify(status_type=ImportStatusTypes.COVERS_DELETED, updates=False)
93
- def _bulk_covers_deleted(self, delete_cover_paths, library, **kwargs):
107
+ def _bulk_covers_deleted(self, **kwargs):
94
108
  """Bulk delete comics found missing from the filesystem."""
95
- if not delete_cover_paths:
109
+ if not self.task.covers_deleted:
96
110
  return 0
111
+ status = Status(
112
+ ImportStatusTypes.COVERS_DELETED, 0, len(self.task.covers_deleted)
113
+ )
114
+ self.status_controller.start(status)
97
115
  covers = CustomCover.objects.filter(
98
- library=library, path__in=delete_cover_paths
116
+ library=self.library, path__in=self.task.covers_deleted
99
117
  )
118
+ self.task.covers_deleted = frozenset()
100
119
  delete_cover_pks = frozenset(covers.values_list("pk", flat=True))
101
120
  covers.delete()
102
121
 
103
122
  self._remove_covers(delete_cover_pks, custom=True)
104
123
 
105
- count = len(delete_cover_paths)
124
+ count = len(delete_cover_pks)
106
125
  if count:
107
- self.log.info(f"Deleted {count} custom covers from {library.path}")
126
+ self.log.info(f"Deleted {count} custom covers from {self.library.path}")
108
127
 
128
+ self.status_controller.finish(status)
109
129
  return count
110
130
 
111
- def delete(self, library, task, deleted_comic_groups):
131
+ def delete(self):
112
132
  """Delete files and folders."""
113
- count = self._bulk_folders_deleted(task.dirs_deleted, library)
114
- task.dirs_deleted = None
115
-
116
- self._init_deleted_comic_groups(deleted_comic_groups)
117
- count += self._bulk_comics_deleted(
118
- task.files_deleted, library, deleted_comic_groups
119
- )
120
- task.files_deleted = None
121
-
122
- count += self._bulk_covers_deleted(task.covers_deleted, library)
123
- task.covers_deleted = None
124
-
125
- return count
133
+ count = self._bulk_folders_deleted()
134
+ deleted_comic_groups = self._init_deleted_comic_groups()
135
+ count += self._bulk_comics_deleted(deleted_comic_groups)
136
+ count += self._bulk_covers_deleted()
137
+ self.changed += count
138
+ return deleted_comic_groups