codex 1.6.0a7__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 (510) hide show
  1. codex/integrity.py +4 -4
  2. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +54 -76
  3. codex/librarian/importer/cache.py +162 -29
  4. codex/librarian/importer/const.py +15 -6
  5. codex/librarian/importer/create_comics.py +102 -17
  6. codex/librarian/importer/create_covers.py +98 -0
  7. codex/librarian/importer/create_fks.py +73 -170
  8. codex/librarian/importer/deleted.py +59 -46
  9. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  10. codex/librarian/importer/failed_imports.py +41 -62
  11. codex/librarian/importer/importer.py +96 -0
  12. codex/librarian/importer/importerd.py +71 -397
  13. codex/librarian/importer/init.py +265 -0
  14. codex/librarian/importer/link_comics.py +26 -81
  15. codex/librarian/importer/link_covers.py +73 -0
  16. codex/librarian/importer/moved.py +72 -96
  17. codex/librarian/importer/query_covers.py +52 -0
  18. codex/librarian/importer/query_fks.py +128 -172
  19. codex/librarian/importer/status.py +2 -39
  20. codex/librarian/importer/tasks.py +22 -13
  21. codex/librarian/janitor/cleanup.py +2 -2
  22. codex/librarian/janitor/janitor.py +10 -0
  23. codex/librarian/librariand.py +7 -1
  24. codex/migrations/0027_import_order_and_covers.py +345 -0
  25. codex/models/comic.py +1 -2
  26. codex/models/groups.py +3 -2
  27. codex/models/paths.py +0 -1
  28. codex/search/query.py +25 -2
  29. codex/search/writing.py +1 -1
  30. codex/serializers/browser/mixins.py +0 -18
  31. codex/serializers/browser/settings.py +2 -3
  32. codex/serializers/choices.py +10 -1
  33. codex/settings/settings.py +2 -2
  34. codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-DI-DgMb4.8eeb1bd3e18b.js} +1 -1
  35. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  36. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  37. codex/static_root/assets/{VCheckbox-BOUtyxuo.js → VCheckbox-DI-DgMb4.js} +1 -1
  38. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  39. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  40. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js → VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js} +1 -1
  41. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  42. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  43. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.js → VCheckboxBtn-B8Z4NA-t.js} +1 -1
  44. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  45. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  46. codex/static_root/assets/{VCombobox-DjkDXe33.22617ea193b1.js → VCombobox-D1_0lCri.6ef85a29c2dc.js} +1 -1
  47. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  48. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  49. codex/static_root/assets/{VCombobox-DjkDXe33.js → VCombobox-D1_0lCri.js} +1 -1
  50. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  51. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  52. codex/static_root/assets/{VDialog-X0zn9AGX.0d89c749b9a6.js → VDialog-opmV0YZz.d77216e97599.js} +1 -1
  53. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  54. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  55. codex/static_root/assets/{VDialog-X0zn9AGX.js → VDialog-opmV0YZz.js} +1 -1
  56. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  57. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  58. codex/static_root/assets/{VExpansionPanels-CRevojaF.43d65c777c58.js → VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js} +1 -1
  59. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  60. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  61. codex/static_root/assets/{VExpansionPanels-CRevojaF.js → VExpansionPanels-DX2dhUf5.js} +1 -1
  62. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  63. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  64. codex/static_root/assets/{VRadioGroup-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-BGa6_q5a.d1a3ad7266bd.js} +1 -1
  65. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  66. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  67. codex/static_root/assets/{VRadioGroup-8nrrxjDv.js → VRadioGroup-BGa6_q5a.js} +1 -1
  68. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  69. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  70. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  71. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  72. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  73. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  74. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  75. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  76. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +1 -0
  77. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  78. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  79. codex/static_root/assets/VSelect-sIYW_-ud.js +1 -0
  80. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  81. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  82. codex/static_root/assets/{VSelectionControl-CGu0oz0K.3030b7a270d6.js → VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js} +1 -1
  83. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  84. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  85. codex/static_root/assets/{VSelectionControl-CGu0oz0K.js → VSelectionControl-C8DP9rgp.js} +1 -1
  86. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  87. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  88. codex/static_root/assets/{VSlideGroup-D_oNvCOd.js → VSlideGroup-BRYhkLH8.3534ec94b2f1.js} +1 -1
  89. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  90. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  91. codex/static_root/assets/{VSlideGroup-D_oNvCOd.71a7101cdddd.js → VSlideGroup-BRYhkLH8.js} +1 -1
  92. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  93. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  94. codex/static_root/assets/{VTable-C7pKI7gU.087912f706f5.js → VTable-D1esKtlw.52165930e3b9.js} +1 -1
  95. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  96. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  97. codex/static_root/assets/{VTable-C7pKI7gU.js → VTable-D1esKtlw.js} +1 -1
  98. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  99. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  100. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +1 -0
  101. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  102. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  103. codex/static_root/assets/VTextField-BnGscKcY.js +1 -0
  104. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  105. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  106. codex/static_root/assets/{VWindowItem-iyZ1XOkP.409f7d292253.js → VWindowItem-B0KokcAk.ea94224aecb9.js} +1 -1
  107. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  108. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  109. codex/static_root/assets/{VWindowItem-iyZ1XOkP.js → VWindowItem-B0KokcAk.js} +1 -1
  110. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  111. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  112. codex/static_root/assets/{admin-B0pCBjma.2407da79bd20.js → admin-Y0bL21AB.42d4f0d68163.js} +1 -1
  113. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  114. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  115. codex/static_root/assets/{admin-B0pCBjma.js → admin-Y0bL21AB.js} +1 -1
  116. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  117. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  118. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-DNngWUEM.4e992e3fe86e.js} +11 -11
  119. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  120. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  121. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.js → admin-drawer-panel-DNngWUEM.js} +11 -11
  122. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  123. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  124. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +1 -0
  125. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  126. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  127. codex/static_root/assets/browser-CMAtBCLx.css +1 -0
  128. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  129. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  130. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +1 -0
  131. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  132. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  133. codex/static_root/assets/browser-DoJk8uvb.js +1 -0
  134. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  135. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  136. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.js → change-password-dialog-CH4xbjJa.b749aa6ea07e.js} +1 -1
  137. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  138. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  139. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.930f4cae3cb0.js → change-password-dialog-CH4xbjJa.js} +1 -1
  140. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  141. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  142. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
  143. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js.br → confirm-dialog-DkU0twE6.a20006aa5ba6.js.br} +0 -0
  144. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  145. codex/static_root/assets/{confirm-dialog-RP7JNStF.js → confirm-dialog-DkU0twE6.js} +1 -1
  146. codex/static_root/assets/{confirm-dialog-RP7JNStF.js.br → confirm-dialog-DkU0twE6.js.br} +0 -0
  147. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  148. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +1 -0
  149. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  150. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  151. codex/static_root/assets/datetime-column-BfW_j_3Q.js +1 -0
  152. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  153. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  154. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  155. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  156. codex/static_root/assets/{filter-DAdGUt-1.3261cbcd50b5.js → filter-DjxN78cK.35673f6e49b1.js} +1 -1
  157. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  158. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  159. codex/static_root/assets/{filter-DAdGUt-1.js → filter-DjxN78cK.js} +1 -1
  160. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  161. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  162. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js +1 -0
  163. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  164. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  165. codex/static_root/assets/flag-tab-D4ZNSbMR.js +1 -0
  166. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  167. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  168. codex/static_root/assets/{group-tab-DEwh4jfB.a95097b5e320.js → group-tab-DucpNoBs.a6f37ae940c0.js} +1 -1
  169. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  170. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  171. codex/static_root/assets/{group-tab-DEwh4jfB.js → group-tab-DucpNoBs.js} +1 -1
  172. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  173. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  174. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js +1 -0
  175. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  176. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  177. codex/static_root/assets/http-error-Cj0hTq-c.js +1 -0
  178. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  179. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  180. codex/static_root/assets/{library-tab-DrXvD9B2.js → library-tab-CzksdS3Q.566acdb45dec.js} +1 -1
  181. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  182. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  183. codex/static_root/assets/{library-tab-DrXvD9B2.f765f9d3bae4.js → library-tab-CzksdS3Q.js} +1 -1
  184. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  185. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  186. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +32 -0
  187. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  188. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  189. codex/static_root/assets/main-DX-k2724.js +32 -0
  190. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  191. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  192. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +1 -0
  193. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  194. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  195. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +1 -0
  196. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  197. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  198. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  199. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  200. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  201. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  202. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  203. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  204. codex/static_root/assets/{pdf-doc-obrBwxa6.ec9bd13a5786.js → pdf-doc-GkMk4o5L.ef24c859a7a4.js} +1 -1
  205. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  206. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  207. codex/static_root/assets/{pdf-doc-obrBwxa6.js → pdf-doc-GkMk4o5L.js} +1 -1
  208. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  209. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  210. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  211. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  212. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  213. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  214. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  215. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  216. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +2 -0
  217. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  218. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  219. codex/static_root/assets/reader-Cl6mmQB_.js +2 -0
  220. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  221. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  222. codex/static_root/assets/{relation-chips-CZJcLjc8.1d7426dab654.js → relation-chips-CZ8VvR4y.d4232f3c2518.js} +1 -1
  223. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  224. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  225. codex/static_root/assets/{relation-chips-CZJcLjc8.js → relation-chips-CZ8VvR4y.js} +1 -1
  226. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  227. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  228. codex/static_root/assets/{settings-drawer-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DyS0A2pN.17a98928589f.js} +2 -2
  229. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  230. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  231. codex/static_root/assets/{settings-drawer-7iTnAdjf.js → settings-drawer-DyS0A2pN.js} +2 -2
  232. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  233. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  234. codex/static_root/assets/{stats-tab-CQsK1n8O.544bf50ffb22.js → stats-tab-R3Ekobfm.70fa09a67518.js} +1 -1
  235. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  236. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  237. codex/static_root/assets/{stats-tab-CQsK1n8O.js → stats-tab-R3Ekobfm.js} +1 -1
  238. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  239. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  240. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +1 -0
  241. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  242. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  243. codex/static_root/assets/task-tab-BhLDK_7Q.js +1 -0
  244. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  245. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  246. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +1 -0
  247. codex/static_root/assets/to-case-BR2frjFx.js +1 -0
  248. codex/static_root/assets/{unauthorized-Bs7NSqea.c55650fac055.js → unauthorized-BbXjAe5l.d48912d34a68.js} +1 -1
  249. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  250. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  251. codex/static_root/assets/{unauthorized-Bs7NSqea.js → unauthorized-BbXjAe5l.js} +1 -1
  252. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  253. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  254. codex/static_root/assets/{user-tab-ChuH7Atm.7e80cf1fb78e.js → user-tab-DxITIl2d.d79d7f062930.js} +1 -1
  255. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  256. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  257. codex/static_root/assets/{user-tab-ChuH7Atm.js → user-tab-DxITIl2d.js} +1 -1
  258. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  259. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  260. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  261. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  262. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  263. codex/static_root/js/choices-admin.json +1 -1
  264. codex/static_root/js/choices-admin.json.br +0 -0
  265. codex/static_root/js/choices-admin.json.gz +0 -0
  266. codex/static_root/js/{choices.079a01e0be1a.json → choices.a0aaaa6c7ef7.json} +1 -1
  267. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  268. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  269. codex/static_root/js/choices.json +1 -1
  270. codex/static_root/js/choices.json.br +0 -0
  271. codex/static_root/js/choices.json.gz +0 -0
  272. codex/static_root/{manifest.dad79c8475ae.json → manifest.16d52597ef50.json} +239 -239
  273. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  274. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  275. codex/static_root/manifest.json +239 -239
  276. codex/static_root/manifest.json.br +0 -0
  277. codex/static_root/manifest.json.gz +0 -0
  278. codex/static_root/staticfiles.json +1 -1
  279. codex/urls/api/browser.py +11 -2
  280. codex/urls/api/reader.py +1 -7
  281. codex/urls/opds/binary.py +1 -1
  282. codex/views/admin/auth.py +0 -2
  283. codex/views/admin/tasks.py +13 -1
  284. codex/views/auth.py +2 -4
  285. codex/views/browser/annotations.py +27 -148
  286. codex/views/browser/base.py +1 -18
  287. codex/views/browser/breadcrumbs.py +16 -14
  288. codex/views/browser/browser.py +5 -10
  289. codex/views/browser/choices.py +15 -39
  290. codex/views/browser/cover.py +177 -0
  291. codex/views/browser/filters/annotations.py +23 -22
  292. codex/views/browser/filters/field.py +1 -2
  293. codex/views/browser/filters/group.py +13 -8
  294. codex/views/browser/filters/search.py +19 -10
  295. codex/views/browser/metadata.py +20 -25
  296. codex/views/const.py +12 -0
  297. codex/views/mtime.py +14 -12
  298. codex/views/opds/binary.py +1 -1
  299. codex/views/opds/urls.py +2 -1
  300. codex/views/opds/util.py +2 -4
  301. codex/views/opds/v1/entry/links.py +18 -33
  302. codex/views/opds/v1/facets.py +2 -1
  303. codex/views/opds/v1/feed.py +42 -23
  304. codex/views/opds/v1/links.py +2 -1
  305. codex/views/opds/v2/feed.py +11 -9
  306. codex/views/opds/v2/links.py +2 -1
  307. codex/views/opds/v2/publications.py +13 -34
  308. codex/views/public.py +0 -2
  309. codex/views/reader/books.py +1 -1
  310. codex/views/reader/reader.py +2 -2
  311. codex/views/session.py +5 -3
  312. codex/views/util.py +32 -0
  313. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/METADATA +8 -7
  314. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/RECORD +317 -318
  315. codex/librarian/importer/update_comics.py +0 -85
  316. codex/migrations/0027_sort_name.py +0 -139
  317. codex/migrations/0028_custom_covers.py +0 -166
  318. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  319. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  320. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  321. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  322. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  323. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  324. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  325. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  326. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  327. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  328. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  329. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  330. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  331. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  332. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  333. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  334. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  335. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  336. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  337. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  338. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  339. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  340. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  341. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  342. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  343. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +0 -1
  344. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  345. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  346. codex/static_root/assets/VSelect-DgfIKRnC.js +0 -1
  347. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  348. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  349. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  350. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  351. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  352. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  353. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  354. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  355. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  356. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  357. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  358. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  359. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  360. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  361. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  362. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  363. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  364. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  365. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  366. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  367. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js +0 -1
  368. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  369. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  370. codex/static_root/assets/VTextField-Bs8oq9mk.js +0 -1
  371. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  372. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  373. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  374. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  375. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  376. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  377. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  378. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  379. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  380. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  381. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  382. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  383. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  384. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  385. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +0 -1
  386. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  387. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  388. codex/static_root/assets/browser-DbyAjCNh.js +0 -1
  389. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  390. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  391. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +0 -1
  392. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  393. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  394. codex/static_root/assets/browser-VnzNj1t3.css +0 -1
  395. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  396. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  397. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  398. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  399. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  400. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  401. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  402. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  403. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js +0 -1
  404. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  405. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  406. codex/static_root/assets/datetime-column-Cu3WYyPj.js +0 -1
  407. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  408. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  409. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  410. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  411. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  412. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  413. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  414. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  415. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  416. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  417. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +0 -1
  418. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  419. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  420. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +0 -1
  421. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  422. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  423. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  424. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  425. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  426. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  427. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +0 -1
  428. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  429. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  430. codex/static_root/assets/http-error-xs3mIL8U.js +0 -1
  431. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  432. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  433. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  434. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  435. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  436. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  437. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +0 -32
  438. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  439. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  440. codex/static_root/assets/main-Dy9Uqpmh.js +0 -32
  441. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  442. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  443. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +0 -1
  444. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  445. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  446. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +0 -1
  447. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  448. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  449. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css +0 -1
  450. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  451. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  452. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css +0 -1
  453. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  454. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  455. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  456. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  457. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  458. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  459. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +0 -2
  460. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  461. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  462. codex/static_root/assets/reader-BXKUaUeC.js +0 -2
  463. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  464. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  465. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css +0 -1
  466. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  467. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  468. codex/static_root/assets/reader-OXJ1KVaW.css +0 -1
  469. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  470. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  471. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  472. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  473. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  474. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  475. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  476. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  477. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  478. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  479. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  480. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  481. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  482. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  483. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +0 -1
  484. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  485. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  486. codex/static_root/assets/task-tab-C60TLP6e.js +0 -1
  487. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  488. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  489. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +0 -1
  490. codex/static_root/assets/to-case-CR9beRR0.js +0 -1
  491. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  492. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  493. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  494. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  495. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  496. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  497. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  498. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  499. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  500. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  501. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  502. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  503. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  504. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  505. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  506. codex/views/cover.py +0 -76
  507. codex/views/utils.py +0 -26
  508. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
  509. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
  510. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
@@ -14,6 +14,16 @@ from codex.librarian.importer.const import (
14
14
  DICT_MODEL_CLASS_FIELDS_MAP,
15
15
  DICT_MODEL_FIELD_MODEL_MAP,
16
16
  DICT_MODEL_REL_MAP,
17
+ FK_CREATE,
18
+ FKC_CONTRIBUTORS,
19
+ FKC_CREATE_FKS,
20
+ FKC_CREATE_GROUPS,
21
+ FKC_FOLDER_PATHS,
22
+ FKC_IDENTIFIERS,
23
+ FKC_STORY_ARC_NUMBERS,
24
+ FKC_TOTAL_FKS,
25
+ FKC_UPDATE_GROUPS,
26
+ FKS,
17
27
  GROUP_COMPARE_FIELDS,
18
28
  GROUP_TREES,
19
29
  IDENTIFIERS_FIELD_NAME,
@@ -23,7 +33,8 @@ from codex.librarian.importer.const import (
23
33
  SERIES,
24
34
  STORY_ARCS_METADATA_KEY,
25
35
  )
26
- from codex.librarian.importer.status import ImportStatusTypes, status_notify
36
+ from codex.librarian.importer.query_covers import QueryCustomCoversImporter
37
+ from codex.librarian.importer.status import ImportStatusTypes
27
38
  from codex.logger.logging import get_logger
28
39
  from codex.models import (
29
40
  Comic,
@@ -36,13 +47,9 @@ from codex.models import (
36
47
  Volume,
37
48
  )
38
49
  from codex.models.named import Identifier
39
- from codex.models.paths import CustomCover
40
50
  from codex.settings.settings import FILTER_BATCH_SIZE
41
51
  from codex.status import Status
42
- from codex.threads import QueuedThread
43
52
 
44
- _CREATE_GROUPS = "create_groups"
45
- _UPDATE_GROUPS = "update_groups"
46
53
  _CLASS_QUERY_FIELDS_MAP = MappingProxyType(
47
54
  {
48
55
  Contributor: ("role__name", "person__name"),
@@ -58,18 +65,23 @@ _DEFAULT_QUERY_FIELDS = ("name",)
58
65
  _EXTRA_UPDATE_FIELDS_MAP = MappingProxyType(
59
66
  {Series: ("volume_count",), Volume: ("issue_count",)}
60
67
  )
61
-
68
+ _DICT_MODEL_KEY_MAP = MappingProxyType(
69
+ {
70
+ CONTRIBUTORS_FIELD_NAME: FKC_CONTRIBUTORS,
71
+ STORY_ARCS_METADATA_KEY: FKC_STORY_ARC_NUMBERS,
72
+ IDENTIFIERS_FIELD_NAME: FKC_IDENTIFIERS,
73
+ }
74
+ )
62
75
  LOG = get_logger(__name__)
63
76
 
64
77
 
65
- class QueryForeignKeysMixin(QueuedThread):
78
+ class QueryForeignKeysImporter(QueryCustomCoversImporter):
66
79
  """Methods for querying what fks are missing."""
67
80
 
68
- @staticmethod
69
- def _get_query_fks_totals(fks):
81
+ def _get_query_fks_totals(self):
70
82
  """Get the query foreign keys totals."""
71
83
  fks_total = 0
72
- for key, objs in fks.items():
84
+ for key, objs in self.metadata[FKS].items():
73
85
  if key == GROUP_TREES:
74
86
  for groups in objs.values():
75
87
  fks_total += len(groups)
@@ -94,11 +106,11 @@ class QueryForeignKeysMixin(QueuedThread):
94
106
  update_mds,
95
107
  q_obj: Q,
96
108
  status,
109
+ and_q_obj: Q | None = None,
97
110
  ):
98
111
  """Get create metadata by comparing proposed meatada to existing rows."""
99
- if status:
100
- vnp = fk_cls._meta.verbose_name_plural.title()
101
- status.subtitle = vnp
112
+ vnp = fk_cls._meta.verbose_name_plural.title()
113
+ status.subtitle = vnp
102
114
  offset = 0
103
115
  num_qs = len(q_obj.children)
104
116
  while offset < num_qs:
@@ -106,6 +118,8 @@ class QueryForeignKeysMixin(QueuedThread):
106
118
  # django.db.utils.OperationalError: Expression tree is too large (maximum depth 1000)
107
119
  children_chunk = q_obj.children[offset : offset + FILTER_BATCH_SIZE]
108
120
  filter_chunk = Q(*children_chunk, _connector=Q.OR)
121
+ if and_q_obj:
122
+ filter_chunk = and_q_obj & filter_chunk
109
123
 
110
124
  existing_mds = self._query_existing_mds(fk_cls, filter_chunk)
111
125
  create_mds.difference_update(existing_mds)
@@ -117,13 +131,12 @@ class QueryForeignKeysMixin(QueuedThread):
117
131
  )
118
132
  update_mds.difference_update(match_with_extra_fields_mds)
119
133
 
120
- status.complete += len(filter_chunk)
134
+ status.add_complete(len(filter_chunk))
121
135
  self.status_controller.update(status)
122
136
 
123
137
  offset += FILTER_BATCH_SIZE
124
138
 
125
- if status:
126
- status.subtitle = ""
139
+ status.subtitle = ""
127
140
 
128
141
  return num_qs
129
142
 
@@ -172,11 +185,11 @@ class QueryForeignKeysMixin(QueuedThread):
172
185
  update_group_set.update(update_groups)
173
186
  return count
174
187
 
175
- @classmethod
176
188
  def _update_action_group(
177
- cls, group_cls, action_groups, group_tree, count_value: int | None
189
+ self, group_cls, action_groups_key, group_tree, count_value: int | None
178
190
  ):
179
191
  """Update the create or update group dict with the count dict."""
192
+ action_groups = self.metadata[FK_CREATE][action_groups_key]
180
193
  if group_cls not in action_groups:
181
194
  action_groups[group_cls] = {}
182
195
  if group_tree not in action_groups[group_cls]:
@@ -189,13 +202,12 @@ class QueryForeignKeysMixin(QueuedThread):
189
202
  create_group_set,
190
203
  update_group_set,
191
204
  group_cls,
192
- create_and_update_groups,
193
205
  status,
194
206
  ) = data
195
207
  if group_tree in create_group_set:
196
208
  self._update_action_group(
197
209
  group_cls,
198
- create_and_update_groups[_CREATE_GROUPS],
210
+ FKC_CREATE_GROUPS,
199
211
  group_tree,
200
212
  count_value,
201
213
  )
@@ -203,14 +215,11 @@ class QueryForeignKeysMixin(QueuedThread):
203
215
  # This always updates Volume or Series
204
216
  self._update_action_group(
205
217
  group_cls,
206
- create_and_update_groups[_UPDATE_GROUPS],
218
+ FKC_UPDATE_GROUPS,
207
219
  group_tree,
208
220
  count_value,
209
221
  )
210
- if status:
211
- status.complete = status.complete or 0
212
- status.complete += 1
213
- self.status_controller.update(status)
222
+ status.add_complete(1)
214
223
 
215
224
  @staticmethod
216
225
  def _prune_group_updates_get_filter(group_cls, update_group_trees):
@@ -281,22 +290,19 @@ class QueryForeignKeysMixin(QueuedThread):
281
290
  prune_count = len(update_group_trees) - len(pruned_update_group_trees)
282
291
  self.log.debug(f"Pruned {prune_count} {vnp} from updates.")
283
292
 
284
- @status_notify()
285
293
  def _query_missing_group(
286
294
  self,
287
295
  groups,
288
296
  group_cls,
289
- create_and_update_groups,
290
- status=None,
297
+ status,
291
298
  ):
292
299
  """Get missing groups from proposed groups to create."""
293
300
  count = 0
294
301
  if not groups:
295
302
  return count
296
303
 
297
- if status:
298
- vnp = group_cls._meta.verbose_name_plural.title()
299
- status.subtitle = vnp
304
+ vnp = group_cls._meta.verbose_name_plural.title()
305
+ status.subtitle = vnp
300
306
 
301
307
  create_group_set = set()
302
308
  update_group_set = set()
@@ -308,24 +314,23 @@ class QueryForeignKeysMixin(QueuedThread):
308
314
  create_group_set,
309
315
  update_group_set,
310
316
  group_cls,
311
- create_and_update_groups,
312
317
  status,
313
318
  )
314
319
  for group_tree, count_value in groups.items():
315
320
  self._query_group_tree(data, group_tree, count_value)
321
+ self.status_controller.update(status)
316
322
 
317
323
  # after counts have been tallied, prune the ones that don't need an update.
318
- update_groups = create_and_update_groups.get(_UPDATE_GROUPS)
324
+ update_groups = self.metadata[FK_CREATE][FKC_UPDATE_GROUPS]
319
325
  self._prune_group_updates(group_cls, update_groups)
320
326
 
321
- if status:
322
- status.subtitle = ""
327
+ status.subtitle = ""
323
328
 
324
329
  create_count = len(
325
- create_and_update_groups.get(_CREATE_GROUPS, {}).get(group_cls, {})
330
+ self.metadata[FK_CREATE][FKC_CREATE_GROUPS].get(group_cls, {})
326
331
  )
327
332
  update_count = len(
328
- create_and_update_groups.get(_UPDATE_GROUPS, {}).get(group_cls, {})
333
+ self.metadata[FK_CREATE][FKC_UPDATE_GROUPS].get(group_cls, {})
329
334
  )
330
335
  count = create_count + update_count
331
336
  if count:
@@ -421,9 +426,9 @@ class QueryForeignKeysMixin(QueuedThread):
421
426
  restored_create_objs.append((*create_obj, url))
422
427
  return restored_create_objs
423
428
 
424
- def _query_missing_dict_model(self, field_name, fks, create_objs, status):
429
+ def _query_missing_dict_model(self, field_name, create_objs_key, status):
425
430
  """Find missing dict type m2m models."""
426
- possible_objs = fks.pop(field_name, None)
431
+ possible_objs = self.metadata[FKS].pop(field_name, None)
427
432
  if not possible_objs:
428
433
  return 0
429
434
 
@@ -443,38 +448,51 @@ class QueryForeignKeysMixin(QueuedThread):
443
448
  )
444
449
 
445
450
  # Build combined query object from the value_filter
446
- combined_q_obj = Q()
447
- for filter_and_prefix, value_filter in query_filter_map.items():
448
- combined_q_obj |= filter_and_prefix & value_filter
451
+ model = DICT_MODEL_FIELD_MODEL_MAP[field_name]
452
+ if model == Identifier:
453
+ for filter_and_prefix, value_filter in query_filter_map.items():
454
+ self._query_create_metadata(
455
+ model,
456
+ possible_create_objs,
457
+ None,
458
+ value_filter,
459
+ status,
460
+ and_q_obj=filter_and_prefix,
461
+ )
462
+ else:
463
+ combined_q_obj = Q()
464
+ for filter_and_prefix, value_filter in query_filter_map.items():
465
+ combined_q_obj |= filter_and_prefix & value_filter
466
+ self._query_create_metadata(
467
+ model,
468
+ possible_create_objs,
469
+ None,
470
+ combined_q_obj,
471
+ status,
472
+ )
449
473
 
450
474
  # Finally run the query and get only the correct create_objs
451
- model = DICT_MODEL_FIELD_MODEL_MAP[field_name]
452
- self._query_create_metadata(
453
- model, possible_create_objs, None, combined_q_obj, status
454
- )
455
475
  possible_create_objs = self._query_missing_dict_model_identifiers_restore_urls(
456
476
  field_name, possible_create_objs, url_restore_map
457
477
  )
458
478
 
459
479
  # Final Cleanup
460
- create_objs.update(possible_create_objs)
461
- count = len(create_objs)
480
+ self.metadata[FK_CREATE][create_objs_key].update(possible_create_objs)
481
+ count = len(self.metadata[FK_CREATE][create_objs_key])
462
482
  if count:
463
483
  model = DICT_MODEL_FIELD_MODEL_MAP[field_name]
464
484
  vnp = model._meta.verbose_name_plural
465
485
  vnp = vnp.title() if vnp else "Nothings"
466
486
  self.log.info(f"Prepared {count} new {vnp}.")
467
- if status:
468
- status.complete = status.complete or 0
469
- status.complete += count
487
+ status.add_complete(count)
488
+ self.status_controller.update(status, notify=False)
470
489
  return count
471
490
 
472
491
  ##########
473
492
  # SIMPLE #
474
493
  ##########
475
494
 
476
- @status_notify()
477
- def _query_missing_simple_models(self, names, fk_data, status=None):
495
+ def _query_missing_simple_models(self, names, fk_data, status):
478
496
  """Find missing named models and folders."""
479
497
  # count = 0
480
498
  if not names:
@@ -488,9 +506,8 @@ class QueryForeignKeysMixin(QueuedThread):
488
506
  create_names = set(names)
489
507
  num_proposed_names = len(proposed_names)
490
508
 
491
- if status:
492
- vnp = fk_cls._meta.verbose_name_plural.title()
493
- status.subtitle = vnp
509
+ vnp = fk_cls._meta.verbose_name_plural.title()
510
+ status.subtitle = vnp
494
511
  while start < num_proposed_names:
495
512
  # Do this in batches so as not to exceed the 1k line sqlite limit
496
513
  end = start + FILTER_BATCH_SIZE
@@ -499,11 +516,8 @@ class QueryForeignKeysMixin(QueuedThread):
499
516
  fk_filter = Q(**filter_args)
500
517
  create_names -= self._query_existing_mds(fk_cls, fk_filter)
501
518
  num_in_batch = len(batch_proposed_names)
502
- # count += num_in_batch
503
- if status:
504
- status.complete = status.complete or 0
505
- status.complete += num_in_batch
506
- self.status_controller.update(status)
519
+ status.add_complete(num_in_batch)
520
+ self.status_controller.update(status)
507
521
  start += FILTER_BATCH_SIZE
508
522
 
509
523
  if status:
@@ -520,7 +534,7 @@ class QueryForeignKeysMixin(QueuedThread):
520
534
  self.log.log(level, f"Prepared {len(create_names)} new {vnp}.")
521
535
  return len(names)
522
536
 
523
- def _query_one_simple_model(self, fk_field, fks, create_fks, status):
537
+ def _query_one_simple_model(self, fk_field, status):
524
538
  """Batch query one simple model name."""
525
539
  for cls, names in DICT_MODEL_CLASS_FIELDS_MAP.items():
526
540
  if fk_field in names:
@@ -528,25 +542,24 @@ class QueryForeignKeysMixin(QueuedThread):
528
542
  break
529
543
  else:
530
544
  base_cls = Comic
531
- fk_data = create_fks, base_cls, fk_field, "name"
532
- names = fks.pop(fk_field)
533
- status.complete += self._query_missing_simple_models(
534
- names,
535
- fk_data,
536
- status=status,
545
+ fk_data = self.metadata[FK_CREATE][FKC_CREATE_FKS], base_cls, fk_field, "name"
546
+ names = self.metadata[FKS].pop(fk_field)
547
+ status.add_complete(
548
+ self._query_missing_simple_models(
549
+ names,
550
+ fk_data,
551
+ status,
552
+ )
537
553
  )
554
+ self.status_controller.update(status, notify=False)
538
555
 
539
556
  ###########
540
557
  # FOLDERS #
541
558
  ###########
542
-
543
- @status_notify()
544
- def query_missing_folder_paths(
545
- self, comic_paths, library_path, create_folder_paths, status=None
546
- ):
559
+ def query_missing_folder_paths(self, comic_paths, status):
547
560
  """Find missing folder paths."""
548
561
  # Get the proposed folder_paths
549
- library_path = Path(library_path)
562
+ library_path = Path(self.library.path)
550
563
  proposed_folder_paths = set()
551
564
  for comic_path in comic_paths:
552
565
  for path in Path(comic_path).parents:
@@ -559,136 +572,79 @@ class QueryForeignKeysMixin(QueuedThread):
559
572
  self._query_missing_simple_models(
560
573
  proposed_folder_paths,
561
574
  fk_data,
562
- status=status,
575
+ status,
563
576
  )
564
- folder_paths_set = create_folder_paths_dict.get(Folder, set())
565
- create_folder_paths |= folder_paths_set
577
+ create_folder_paths = create_folder_paths_dict.get(Folder, set())
578
+ if FK_CREATE not in self.metadata:
579
+ self.metadata[FK_CREATE] = {}
580
+ if FKC_FOLDER_PATHS not in self.metadata[FK_CREATE]:
581
+ self.metadata[FK_CREATE][FKC_FOLDER_PATHS] = set()
582
+ self.metadata[FK_CREATE][FKC_FOLDER_PATHS] |= create_folder_paths
566
583
  count = len(create_folder_paths)
567
584
  if count:
568
585
  self.log.info(f"Prepared {count} new Folders.")
569
586
  return count
570
587
 
571
- @staticmethod
572
- def _get_create_fks_totals(create_data):
573
- (
574
- create_groups,
575
- update_groups,
576
- create_folder_paths,
577
- create_fks,
578
- create_contributors,
579
- create_story_arc_numbers,
580
- create_identifiers,
581
- ) = create_data
588
+ def _get_create_fks_totals(self):
589
+ fkc = self.metadata[FK_CREATE]
582
590
  total_fks = 0
583
591
  for data_group in chain(
584
- create_groups.values(), update_groups.values(), create_fks.values()
592
+ fkc[FKC_CREATE_GROUPS].values(),
593
+ fkc[FKC_UPDATE_GROUPS].values(),
594
+ fkc[FKC_CREATE_FKS].values(),
585
595
  ):
586
596
  total_fks += len(data_group)
587
597
  total_fks += (
588
- len(create_folder_paths)
589
- + len(create_contributors)
590
- + len(create_story_arc_numbers)
591
- + len(create_identifiers)
598
+ len(fkc[FKC_FOLDER_PATHS])
599
+ + len(fkc[FKC_CONTRIBUTORS])
600
+ + len(fkc[FKC_STORY_ARC_NUMBERS])
601
+ + len(fkc[FKC_IDENTIFIERS])
592
602
  )
593
603
  return total_fks
594
604
 
595
- def query_all_missing_fks(self, library_path, fks):
605
+ def query_all_missing_fks(self):
596
606
  """Get objects to create by querying existing objects for the proposed fks."""
597
- create_contributors = set()
598
- create_story_arc_numbers = set()
599
- create_identifiers = set()
600
- create_groups = {}
601
- update_groups = {}
602
- create_folder_paths = set()
603
- create_fks = {}
604
- self.log.debug(f"Querying existing foreign keys for comics in {library_path}")
605
- fks_total = self._get_query_fks_totals(fks)
606
- dict_model_map = MappingProxyType(
607
- {
608
- CONTRIBUTORS_FIELD_NAME: create_contributors,
609
- STORY_ARCS_METADATA_KEY: create_story_arc_numbers,
610
- IDENTIFIERS_FIELD_NAME: create_identifiers,
611
- }
607
+ if FKS not in self.metadata:
608
+ return
609
+ self.metadata[FK_CREATE] = {
610
+ FKC_CONTRIBUTORS: set(),
611
+ FKC_STORY_ARC_NUMBERS: set(),
612
+ FKC_IDENTIFIERS: set(),
613
+ FKC_CREATE_GROUPS: {},
614
+ FKC_UPDATE_GROUPS: {},
615
+ FKC_FOLDER_PATHS: set(),
616
+ FKC_CREATE_FKS: {},
617
+ }
618
+ self.log.debug(
619
+ f"Querying existing foreign keys for comics in {self.library.path}"
612
620
  )
613
- status = Status(ImportStatusTypes.QUERY_MISSING_FKS, 0, fks_total)
621
+ status = Status(ImportStatusTypes.QUERY_MISSING_FKS)
614
622
  try:
615
623
  self.status_controller.start(status)
616
- for field_name, create_objs in dict_model_map.items():
624
+ for field_name, create_objs_key in _DICT_MODEL_KEY_MAP.items():
617
625
  self._query_missing_dict_model(
618
626
  field_name,
619
- fks,
620
- create_objs,
627
+ create_objs_key,
621
628
  status,
622
629
  )
623
-
624
- create_and_update_groups = {
625
- _CREATE_GROUPS: create_groups,
626
- _UPDATE_GROUPS: update_groups,
627
- }
628
- for group_class, groups in fks.pop(GROUP_TREES, {}).items():
630
+ for group_class, groups in self.metadata[FKS].pop(GROUP_TREES, {}).items():
629
631
  self._query_missing_group(
630
632
  groups,
631
633
  group_class,
632
- create_and_update_groups,
633
- status=status,
634
+ status,
634
635
  )
635
636
 
636
637
  self.query_missing_folder_paths(
637
- fks.pop(COMIC_PATHS, ()),
638
- library_path,
639
- create_folder_paths,
640
- status=status,
638
+ self.metadata[FKS].pop(COMIC_PATHS, ()), status
641
639
  )
642
640
 
643
- for fk_field in sorted(fks.keys()):
644
- self._query_one_simple_model(fk_field, fks, create_fks, status)
641
+ for fk_field in sorted(self.metadata[FKS].keys()):
642
+ self._query_one_simple_model(fk_field, status)
643
+ self.metadata.pop(FKS)
645
644
  finally:
646
645
  self.status_controller.finish(status)
647
646
 
648
- create_data = (
649
- create_groups,
650
- update_groups,
651
- create_folder_paths,
652
- create_fks,
653
- create_contributors,
654
- create_story_arc_numbers,
655
- create_identifiers,
656
- )
657
-
658
- total_fks = self._get_create_fks_totals(create_data)
647
+ total_fks = self._get_create_fks_totals()
659
648
  status = Status(ImportStatusTypes.CREATE_FKS, 0, total_fks)
660
649
  self.status_controller.update(status, notify=False)
661
- return (*create_data, total_fks)
662
-
663
- @status_notify(ImportStatusTypes.QUERY_MISSING_COVERS, updates=False)
664
- def query_missing_custom_covers(
665
- self, cover_paths: frozenset, library, query_data: list, status=None
666
- ):
667
- """Identify update & create covers."""
668
- self.log.debug(f"Querying {len(cover_paths)} custom cover_paths")
669
- if status:
670
- status.total = len(cover_paths)
671
-
672
- update_covers_qs = CustomCover.objects.filter(
673
- library=library, path__in=cover_paths
674
- )
675
- query_data.append(update_covers_qs)
676
- update_cover_paths = frozenset(update_covers_qs.values_list("path", flat=True))
677
- update_count = len(update_cover_paths)
678
- update_status = Status(ImportStatusTypes.COVERS_MODIFIED, 0, update_count)
679
- self.status_controller.update(update_status, notify=False)
680
-
681
- create_cover_paths = cover_paths - update_cover_paths
682
- query_data.append(create_cover_paths)
683
- create_count = len(create_cover_paths)
684
- create_status = Status(ImportStatusTypes.COVERS_CREATED, 0, create_count)
685
- self.status_controller.update(create_status, notify=False)
686
-
687
- count = create_count + update_count
688
- if count:
689
- self.log.debug(
690
- f"Discovered {update_count} custom covers to update and {create_count} to create."
691
- )
692
- if status:
693
- status.add_complete(count)
694
- return count
650
+ self.metadata[FK_CREATE][FKC_TOTAL_FKS] = total_fks
@@ -1,13 +1,7 @@
1
1
  """Librarian Status for imports."""
2
2
 
3
- from collections.abc import Callable
4
- from enum import Enum
5
- from time import time
6
-
7
3
  from django.db.models import Choices
8
4
 
9
- from codex.status import Status
10
-
11
5
 
12
6
  class ImportStatusTypes(Choices):
13
7
  """Keys for Import tasks."""
@@ -31,36 +25,5 @@ class ImportStatusTypes(Choices):
31
25
  COVERS_CREATED = "ICC"
32
26
  COVERS_DELETED = "ICD"
33
27
  COVERS_LINK = "ICL"
34
-
35
-
36
- def status_notify(status_type: str | Enum = "", updates=True):
37
- """Wrap a function with status changes."""
38
- # https://stackoverflow.com/questions/5929107/decorators-with-parameters
39
-
40
- def decorator(func) -> Callable[..., int]:
41
- def wrapper(self, data, *args, status=None, **kwargs) -> int:
42
- """Run a function bracketed by status changes."""
43
- num_elements = len(data)
44
- if not num_elements:
45
- return 0
46
-
47
- if status:
48
- finish = False
49
- else:
50
- complete = 0 if updates else None
51
- status = Status(status_type, complete, num_elements, time())
52
- self.status_controller.start(status)
53
- finish = True
54
-
55
- kwargs["status"] = status
56
- try:
57
- count = func(self, data, *args, **kwargs)
58
- finally:
59
- if finish:
60
- self.status_controller.finish(status)
61
-
62
- return count
63
-
64
- return wrapper
65
-
66
- return decorator
28
+ GROUP_UPDATE = "IGU"
29
+ ADOPT_FOLDERS = "IAF"
@@ -1,6 +1,8 @@
1
1
  """DB Import Tasks."""
2
2
 
3
- from dataclasses import dataclass
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
4
6
 
5
7
 
6
8
  @dataclass
@@ -14,20 +16,20 @@ class ImportDBDiffTask(ImportTask):
14
16
 
15
17
  library_id: int
16
18
 
17
- dirs_moved: dict[str, str]
18
- dirs_modified: frozenset[str]
19
- # dirs_created: frozenset[str]
20
- dirs_deleted: frozenset[str]
19
+ dirs_moved: Mapping[str, str] = field(default_factory=dict)
20
+ dirs_modified: frozenset[str] = frozenset()
21
+ # dirs_created: frozenset[str] | None = frozenset()
22
+ dirs_deleted: frozenset[str] = frozenset()
21
23
 
22
- files_moved: dict[str, str]
23
- files_modified: frozenset[str]
24
- files_created: frozenset[str]
25
- files_deleted: frozenset[str]
24
+ files_moved: Mapping[str, str] = field(default_factory=dict)
25
+ files_modified: frozenset[str] = frozenset()
26
+ files_created: frozenset[str] = frozenset()
27
+ files_deleted: frozenset[str] = frozenset()
26
28
 
27
- covers_moved: dict[str, str]
28
- covers_modified: frozenset[str]
29
- covers_created: frozenset[str]
30
- covers_deleted: frozenset[str]
29
+ covers_moved: Mapping[str, str] = field(default_factory=dict)
30
+ covers_modified: frozenset[str] = frozenset()
31
+ covers_created: frozenset[str] = frozenset()
32
+ covers_deleted: frozenset[str] = frozenset()
31
33
 
32
34
  force_import_metadata: bool = False
33
35
 
@@ -42,3 +44,10 @@ class LazyImportComicsTask(ImportTask):
42
44
  @dataclass
43
45
  class AdoptOrphanFoldersTask(ImportTask):
44
46
  """Move orphaned folders into a correct tree position."""
47
+
48
+
49
+ @dataclass
50
+ class UpdateGroupsFirstComic(ImportTask):
51
+ """Force the update of the first comics."""
52
+
53
+ start_time: datetime | None = None
@@ -6,7 +6,7 @@ from time import time
6
6
  from types import MappingProxyType
7
7
 
8
8
  from django.contrib.sessions.models import Session
9
- from django.utils.timezone import now
9
+ from django.db.models.functions.datetime import Now
10
10
 
11
11
  from codex.librarian.janitor.status import JanitorStatusTypes
12
12
  from codex.models import (
@@ -137,7 +137,7 @@ class CleanupMixin(WorkerBaseMixin):
137
137
  status = Status(JanitorStatusTypes.CLEANUP_SESSIONS)
138
138
  try:
139
139
  self.status_controller.start(status)
140
- count, _ = Session.objects.filter(expire_date__lt=now()).delete()
140
+ count, _ = Session.objects.filter(expire_date__lt=Now()).delete()
141
141
  if count:
142
142
  self.log.info(f"Deleted {count} expired sessions.")
143
143