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
codex/exceptions.py CHANGED
@@ -30,6 +30,7 @@ class SeeOtherRedirectError(APIException):
30
30
  route: dict = detail.get("route", {}) # type: ignore
31
31
  params: dict = route.get("params", DEFAULTS["breadcrumbs"][0])
32
32
  # Save route in server format for redirect reverse
33
+ params.pop("name", None)
33
34
  self.route_kwargs = params
34
35
  # Serialize route for detail
35
36
  serializer = RouteSerializer(params)
codex/integrity.py CHANGED
@@ -36,7 +36,7 @@ MIGRATION_0011 = "0010_library_groups_and_metadata_changes"
36
36
  MIGRATION_0018 = "0018_rename_userbookmark_bookmark"
37
37
  MIGRATION_0025 = "0025_add_story_arc_number"
38
38
  MIGRATION_0026 = "0026_comicbox_1"
39
- MIGRATION_0028 = "0028_custom_covers" # TODO merge with 27
39
+ MIGRATION_0027 = "0027_import_order_and_covers"
40
40
  M2M_NAMES = MappingProxyType(
41
41
  {
42
42
  "Character": "characters",
@@ -308,7 +308,7 @@ def _repair_library_groups(apps):
308
308
 
309
309
 
310
310
  def _delete_extra_custom_cover_libraries(apps):
311
- if not has_applied_migration(MIGRATION_0028):
311
+ if not has_applied_migration(MIGRATION_0027):
312
312
  return
313
313
  library_model = apps.get_model("codex", "library")
314
314
  custom_cover_libraries = library_model.objects.filter(covers_only=True)
@@ -339,7 +339,7 @@ def _delete_extra_custom_cover_libraries(apps):
339
339
 
340
340
 
341
341
  def _repair_groups_with_custom_covers(apps):
342
- if not has_applied_migration(MIGRATION_0028):
342
+ if not has_applied_migration(MIGRATION_0027):
343
343
  return
344
344
  custom_cover_model = apps.get_model("codex", "customcover")
345
345
  now = Now()
@@ -366,7 +366,7 @@ def _delete_errors():
366
366
 
367
367
  for host_model_name in HAVE_LIBRARY_FKS:
368
368
  if host_model_name == "CustomCover" and not has_applied_migration(
369
- MIGRATION_0028
369
+ MIGRATION_0027
370
370
  ):
371
371
  continue
372
372
  _delete_fk_integrity_errors(apps, host_model_name, "Library", "library")
@@ -2,13 +2,7 @@
2
2
 
3
3
  from collections.abc import Mapping
4
4
  from pathlib import Path
5
- from zipfile import BadZipFile
6
5
 
7
- from comicbox.box import Comicbox
8
- from comicbox.exceptions import UnsupportedArchiveTypeError
9
- from rarfile import BadRarFile
10
-
11
- from codex.librarian.importer.clean_metadata import CleanMetadataMixin
12
6
  from codex.librarian.importer.const import (
13
7
  COMIC_FK_FIELD_NAMES,
14
8
  COMIC_M2M_FIELD_NAMES,
@@ -24,13 +18,15 @@ from codex.librarian.importer.const import (
24
18
  MDS,
25
19
  VOLUME_COUNT,
26
20
  )
27
- from codex.librarian.importer.status import ImportStatusTypes, status_notify
21
+ from codex.librarian.importer.extract import ExtractMetadataImporter
22
+ from codex.librarian.importer.status import ImportStatusTypes
28
23
  from codex.models import Imprint, Publisher, Series, Volume
29
24
  from codex.models.admin import AdminFlag
30
25
  from codex.status import Status
26
+ from codex.util import max_none
31
27
 
32
28
 
33
- class AggregateMetadataMixin(CleanMetadataMixin):
29
+ class AggregateMetadataImporter(ExtractMetadataImporter):
34
30
  """Aggregate metadata from comics to prepare for importing."""
35
31
 
36
32
  _BROWSER_GROUPS = (Publisher, Imprint, Series, Volume)
@@ -86,37 +82,13 @@ class AggregateMetadataMixin(CleanMetadataMixin):
86
82
  m2m_md[FOLDERS_FIELD] = Path(path).parents
87
83
  return m2m_md
88
84
 
89
- def _get_path_metadata(self, path, import_metadata):
85
+ def _get_path_metadata(self, md, path):
90
86
  """Get the metadata from comicbox and munge it a little."""
91
- md = {}
92
- fk_md = {}
93
- group_tree_md = {}
94
- m2m_md = {}
95
- failed_import = {}
96
- try:
97
- if import_metadata:
98
- with Comicbox(path) as cb:
99
- md = cb.to_dict()
100
- md = md.get("comicbox", {})
101
- if "file_type" not in md:
102
- md["file_type"] = cb.get_file_type()
103
- if "page_count" not in md:
104
- md["page_count"] = cb.get_page_count()
105
-
106
- md["path"] = path
107
- md = self.clean_md(md)
108
-
109
- group_tree_md = self._get_group_tree(md)
110
- fk_md = self._get_fk_metadata(md)
111
- m2m_md = self._get_m2m_metadata(md, path)
112
-
113
- except (UnsupportedArchiveTypeError, BadRarFile, BadZipFile, OSError) as exc:
114
- self.log.warning(f"Failed to import {path}: {exc}")
115
- failed_import = {path: exc}
116
- except Exception as exc:
117
- self.log.exception(f"Failed to import: {path}")
118
- failed_import = {path: exc}
119
- return md, m2m_md, fk_md, group_tree_md, failed_import
87
+ group_tree_md = self._get_group_tree(md)
88
+ fk_md = self._get_fk_metadata(md)
89
+ m2m_md = self._get_m2m_metadata(md, path)
90
+
91
+ return md, m2m_md, fk_md, group_tree_md
120
92
 
121
93
  @staticmethod
122
94
  def _aggregate_m2m_metadata_dict_value(names, key, values, all_fks):
@@ -182,22 +154,13 @@ class AggregateMetadataMixin(CleanMetadataMixin):
182
154
  all_fks[field] = set()
183
155
  all_fks[field].add(name)
184
156
 
185
- @staticmethod
186
- def _none_max(a, b):
187
- """None aware math.max."""
188
- if a is not None and b is not None:
189
- return max(a, b)
190
- if a is None:
191
- return b
192
- return a
193
-
194
157
  @classmethod
195
158
  def _set_max_group_count(cls, common_args, group_class, index, count_key):
196
159
  """Assign the maximum group count number."""
197
160
  all_fks, group_tree, group_md = common_args
198
161
  group_name = group_tree[0:index]
199
162
  try:
200
- count = cls._none_max(
163
+ count = max_none(
201
164
  all_fks[GROUP_TREES][Series].get(group_name),
202
165
  group_md.get(count_key),
203
166
  )
@@ -215,72 +178,79 @@ class AggregateMetadataMixin(CleanMetadataMixin):
215
178
  cls._set_max_group_count(common_args, Series, 3, VOLUME_COUNT)
216
179
  cls._set_max_group_count(common_args, Volume, 4, ISSUE_COUNT)
217
180
 
218
- def _aggregate_path(self, data, path, import_metadata):
181
+ def _aggregate_path(self, md, path, status):
219
182
  """Aggregate metadata for one path."""
220
- path_str = str(path)
221
- md, m2m_md, fk_md, group_tree_md, failed_import = self._get_path_metadata(
222
- path_str, import_metadata
223
- )
183
+ md, m2m_md, fk_md, group_tree_md = self._get_path_metadata(md, path)
224
184
 
225
- all_failed_imports, all_mds, all_m2m_mds, all_fks, status = data
226
- if failed_import:
227
- all_failed_imports.update(failed_import)
228
- else:
229
- if md:
230
- all_mds[path_str] = md
185
+ path_str = str(path)
186
+ if md:
187
+ all_mds = self.metadata[MDS]
188
+ all_mds[path_str] = md
231
189
 
232
- if m2m_md:
233
- self._aggregate_m2m_metadata(all_m2m_mds, m2m_md, all_fks, path_str)
190
+ all_fks = self.metadata[FKS]
191
+ if m2m_md:
192
+ all_m2m_mds = self.metadata[M2M_MDS]
193
+ self._aggregate_m2m_metadata(all_m2m_mds, m2m_md, all_fks, path_str)
234
194
 
235
- if fk_md:
236
- self._aggregate_fk_metadata(all_fks, fk_md)
195
+ if fk_md:
196
+ self._aggregate_fk_metadata(all_fks, fk_md)
237
197
 
238
- if group_tree_md:
239
- self._aggregate_group_tree_metadata(all_fks, group_tree_md)
198
+ if group_tree_md:
199
+ self._aggregate_group_tree_metadata(all_fks, group_tree_md)
240
200
 
241
201
  if status:
242
202
  status.complete += 1
243
203
  self.status_controller.update(status)
244
204
 
245
- @status_notify(status_type=ImportStatusTypes.AGGREGATE_TAGS)
246
- def get_aggregate_metadata( # noqa: PLR0913
205
+ def get_aggregate_metadata(
247
206
  self,
248
- all_paths,
249
- library_path,
250
- metadata,
251
- force_import_metadata,
252
207
  status=None,
253
208
  ):
254
209
  """Get aggregated metadata for the paths given."""
210
+ all_paths = self.task.files_modified | self.task.files_created
255
211
  total_paths = len(all_paths)
212
+
256
213
  if not total_paths:
257
214
  return 0
258
- self.log.info(f"Reading tags from {total_paths} comics in {library_path}...")
259
- all_mds = metadata[MDS]
260
- all_m2m_mds = metadata[M2M_MDS]
261
- all_fks = metadata[FKS]
262
- all_fks[GROUP_TREES] = {cls: {} for cls in self._BROWSER_GROUPS}
263
- all_failed_imports = metadata[FIS]
264
-
265
- if status and status.complete is None:
266
- status.complete = 0
267
- key = AdminFlag.FlagChoices.IMPORT_METADATA.value # type: ignore
268
- if force_import_metadata:
215
+ self.log.info(
216
+ f"Reading tags from {total_paths} comics in {self.library.path}..."
217
+ )
218
+ status = Status(ImportStatusTypes.AGGREGATE_TAGS, 0, total_paths)
219
+ self.status_controller.start(status, notify=False)
220
+
221
+ # Set import_metadata flag
222
+ if self.task.force_import_metadata:
269
223
  import_metadata = True
270
224
  else:
225
+ key = AdminFlag.FlagChoices.IMPORT_METADATA.value # type: ignore
271
226
  import_metadata = AdminFlag.objects.get(key=key).on
272
227
  if not import_metadata:
273
228
  self.log.warn("Admin flag set to NOT import metadata.")
274
- data = (all_failed_imports, all_mds, all_m2m_mds, all_fks, status)
229
+
230
+ # Init metadata, extract and aggregate
231
+ self.metadata[MDS] = {}
232
+ self.metadata[M2M_MDS] = {}
233
+ self.metadata[FKS] = {GROUP_TREES: {cls: {} for cls in self._BROWSER_GROUPS}}
234
+ self.metadata[FIS] = {}
275
235
  for path in all_paths:
276
- self._aggregate_path(data, path, import_metadata)
236
+ md = self.extract_and_clean(path, import_metadata)
237
+ if md:
238
+ self._aggregate_path(md, path, status)
277
239
 
278
- all_fks[COMIC_PATHS] = frozenset(all_mds.keys())
279
- fi_status = Status(ImportStatusTypes.FAILED_IMPORTS, 0, len(all_failed_imports))
240
+ # Aggregate further
241
+ self.metadata[FKS][COMIC_PATHS] = frozenset(self.metadata[MDS].keys())
242
+ fis = self.metadata[FIS]
243
+ self.task.files_modified -= fis.keys()
244
+ self.task.files_created -= fis.keys()
245
+
246
+ # Set statii
247
+ fi_status = Status(ImportStatusTypes.FAILED_IMPORTS, 0, len(fis))
280
248
  self.status_controller.update(
281
249
  fi_status,
282
250
  notify=False,
283
251
  )
284
252
  count = status.complete if status else 0
285
253
  self.log.info(f"Aggregated tags from {count} comics.")
254
+
255
+ self.status_controller.finish(status)
286
256
  return count
@@ -0,0 +1,180 @@
1
+ """Update Groups timestamp for cover cache busting."""
2
+
3
+ import os
4
+
5
+ from django.db.models.aggregates import Count
6
+ from django.db.models.functions.datetime import Now
7
+ from django.db.models.query import Q
8
+
9
+ from codex.librarian.importer.init import InitImporter
10
+ from codex.librarian.importer.status import ImportStatusTypes
11
+ from codex.models import Comic, Folder, Imprint, Publisher, Series, StoryArc, Volume
12
+ from codex.status import Status
13
+
14
+ _FIRST_COVER_MODEL_UPDATE_ORDER = (Volume, Series, Imprint, Publisher, Folder, StoryArc)
15
+ _UPDATE_FIELDS = ("first_comic", "updated_at")
16
+
17
+
18
+ class CacheUpdateImporter(InitImporter):
19
+ """Update Groups timestamp for cover cache busting."""
20
+
21
+ @staticmethod
22
+ def _get_folder_batches(model_qs):
23
+ """Create batches of folders by folder depth level."""
24
+ # Collect all related folders
25
+ folder_pks = model_qs.values_list("pk", flat=True)
26
+ all_folders = (
27
+ Folder.objects.filter(Q(folder__pk__in=folder_pks) | Q(pk__in=folder_pks))
28
+ .select_related("first_comic")
29
+ .only("path", *_UPDATE_FIELDS)
30
+ )
31
+
32
+ # Create a map of each folder by path depth level
33
+ batch_map = {}
34
+ for folder in all_folders:
35
+ depth = folder.path.count(os.sep)
36
+ if depth not in batch_map:
37
+ batch_map[depth] = set()
38
+ batch_map[depth].add(folder)
39
+
40
+ # Order batches by reverse depth level to update bottom up.
41
+ batches = []
42
+ for _, folders in sorted(batch_map.items(), reverse=True):
43
+ batches.append(folders)
44
+
45
+ return tuple(batches)
46
+
47
+ @staticmethod
48
+ def _update_first_cover_volume(obj):
49
+ qs = Comic.objects.filter(volume=obj)
50
+ qs = qs.order_by("issue_number", "issue_suffix", "sort_name").only("pk")
51
+ obj.first_comic = qs.first()
52
+
53
+ @staticmethod
54
+ def _update_first_cover_group(obj, model, field):
55
+ qs = model.objects.filter(**{field: obj})
56
+ order_by = "name" if model == Volume else "sort_name"
57
+ qs = qs.select_related("first_comic").order_by(order_by).only("first_comic")
58
+ obj.first_comic = qs.first().first_comic
59
+
60
+ @staticmethod
61
+ def _update_first_cover_story_arc(obj):
62
+ qs = Comic.objects.filter(story_arc_numbers__story_arc=obj)
63
+ qs = qs.order_by("story_arc_numbers__number", "date").only("pk")
64
+ obj.first_comic = qs.first()
65
+
66
+ @staticmethod
67
+ def _update_first_cover_folder(obj):
68
+ first_comic = (
69
+ Comic.objects.filter(parent_folder=obj)
70
+ .order_by("path")
71
+ .only("path")
72
+ .first()
73
+ )
74
+ first_folder = (
75
+ Folder.objects.filter(parent_folder=obj)
76
+ .select_related("first_comic")
77
+ .order_by("path")
78
+ .only("first_comic", "path")
79
+ .first()
80
+ )
81
+ if first_comic and not first_folder:
82
+ obj.first_comic = first_comic
83
+ elif first_folder and not first_comic:
84
+ obj.first_comic = first_folder.first_comic
85
+ elif first_folder and first_comic:
86
+ if first_comic and first_comic.path < first_folder.path:
87
+ obj.first_comic = first_comic
88
+ else:
89
+ obj.first_comic = first_folder.first_comic
90
+
91
+ @classmethod
92
+ def _update_first_cover(cls, model, obj):
93
+ if model == Volume:
94
+ cls._update_first_cover_volume(obj)
95
+ elif model == Series:
96
+ cls._update_first_cover_group(obj, Volume, "series")
97
+ elif model == Imprint:
98
+ cls._update_first_cover_group(obj, Series, "imprint")
99
+ elif model == Publisher:
100
+ cls._update_first_cover_group(obj, Imprint, "publisher")
101
+ elif model == StoryArc:
102
+ cls._update_first_cover_story_arc(obj)
103
+ elif model == Folder:
104
+ cls._update_first_cover_folder(obj)
105
+
106
+ @classmethod
107
+ def _update_group_first_comic(
108
+ cls, force_update_group_map, model, start_time, log_list
109
+ ):
110
+ rel = "storyarcnumber__" if model == StoryArc else ""
111
+ updated_at_rel = rel + "comic__updated_at__gt"
112
+ updated_filter = {updated_at_rel: start_time}
113
+ filter_query = (
114
+ Q(**updated_filter)
115
+ | Q(first_comic__isnull=True)
116
+ | Q(updated_at__gt=start_time)
117
+ )
118
+ if model != Volume:
119
+ filter_query |= Q(custom_cover__updated_at__gt=start_time)
120
+ pks = force_update_group_map.get(model)
121
+ if pks:
122
+ filter_query |= Q(pk__in=pks)
123
+ model_qs = model.objects.filter(filter_query)
124
+
125
+ # only update those with comics
126
+ rel_prefix = "storyarcnumber__" if model == StoryArc else ""
127
+ rel_prefix += "comic"
128
+ model_qs = model_qs.alias(child_count=Count(f"{rel_prefix}__pk", distinct=True))
129
+ model_qs = model_qs.filter(child_count__gt=0)
130
+ model_qs = model_qs.distinct()
131
+ if model == Folder:
132
+ batches = cls._get_folder_batches(model_qs)
133
+ else:
134
+ model_qs.select_related("first_comic").only(*_UPDATE_FIELDS)
135
+ batches = (model_qs,)
136
+
137
+ total_count = 0
138
+ for model_qs in batches:
139
+ updated = []
140
+ for obj in model_qs:
141
+ cls._update_first_cover(model, obj)
142
+ obj.updated_at = Now()
143
+ updated.append(obj)
144
+
145
+ count = len(updated)
146
+ if count:
147
+ model.objects.bulk_update(updated, _UPDATE_FIELDS)
148
+ total_count += count
149
+ if total_count:
150
+ log_list.append(f"{total_count} {model.__name__}s")
151
+ return total_count
152
+
153
+ def update_all_groups_first_comics(self, force_update_group_map, start_time):
154
+ """Update timestamps for each group for cover cache busting."""
155
+ total_count = 0
156
+ status = Status(ImportStatusTypes.GROUP_UPDATE)
157
+ self.status_controller.start(status)
158
+ try:
159
+ log_list = []
160
+ for model in _FIRST_COVER_MODEL_UPDATE_ORDER:
161
+ # self.log.debug(f"Updating first covers for {model.__name__}s...")
162
+ count = self._update_group_first_comic(
163
+ force_update_group_map, model, start_time, log_list
164
+ )
165
+ if count:
166
+ self.log.debug(
167
+ f"Updated {count} first covers for {model.__name__}s"
168
+ )
169
+ status.add_complete(count)
170
+ self.status_controller.update(status, notify=False)
171
+ total_count += count
172
+
173
+ if total_count:
174
+ groups_log = ", ".join(log_list)
175
+ self.log.info( # type: ignore
176
+ f"Updated first covers for {groups_log}."
177
+ )
178
+ self.changed += total_count
179
+ finally:
180
+ self.status_controller.finish(status)
@@ -115,12 +115,7 @@ DICT_MODEL_REL_LINK_MAP = MappingProxyType(
115
115
  # BULK UPDATE #
116
116
  ###############
117
117
 
118
- _EXCLUDEBULK_UPDATE_COMIC_FIELDS = {
119
- "created_at",
120
- "searchresult",
121
- "id",
122
- "bookmark",
123
- }
118
+ _EXCLUDEBULK_UPDATE_COMIC_FIELDS = {"bookmark", "created_at", "id", "library"}
124
119
  GROUP_BASE_FIELDS = ("name", "sort_name")
125
120
  BULK_UPDATE_COMIC_FIELDS = tuple(
126
121
  sorted(
@@ -130,6 +125,7 @@ BULK_UPDATE_COMIC_FIELDS = tuple(
130
125
  and (field.name not in _EXCLUDEBULK_UPDATE_COMIC_FIELDS)
131
126
  )
132
127
  )
128
+ BULK_CREATE_COMIC_FIELDS = (*BULK_UPDATE_COMIC_FIELDS, "library")
133
129
  BULK_UPDATE_FOLDER_FIELDS = (
134
130
  *GROUP_BASE_FIELDS,
135
131
  "stat",
@@ -189,6 +185,10 @@ MDS = "mds"
189
185
  M2M_MDS = "m2m_mds"
190
186
  FKS = "fks"
191
187
  FIS = "fis"
188
+ FK_CREATE = "fk_create"
189
+ COVERS_UPDATE = "covers_update"
190
+ COVERS_CREATE = "covers_create"
191
+ LINK_COVER_PKS = "link_cover_pks"
192
192
  GROUP_COMPARE_FIELDS = MappingProxyType(
193
193
  {
194
194
  Series: ("publisher__name", "imprint__name", "name"),
@@ -227,3 +227,12 @@ COMIC_GROUP_FIELD_NAMES = (
227
227
  "story_arc_numbers",
228
228
  "folders",
229
229
  )
230
+
231
+ FKC_CONTRIBUTORS = "create_contributors"
232
+ FKC_STORY_ARC_NUMBERS = "create_story_arc_numbers"
233
+ FKC_IDENTIFIERS = "create_identifiers"
234
+ FKC_CREATE_GROUPS = "create_groups"
235
+ FKC_UPDATE_GROUPS = "update_groups"
236
+ FKC_CREATE_FKS = "create_fks"
237
+ FKC_FOLDER_PATHS = "create_folder_paths"
238
+ FKC_TOTAL_FKS = "total_fks"
@@ -1,39 +1,122 @@
1
1
  """Bulk update and create comic objects and bulk update m2m fields."""
2
2
 
3
- from codex.librarian.importer.const import BULK_UPDATE_COMIC_FIELDS
4
- from codex.librarian.importer.link_comics import LinkComicsMixin
5
- from codex.librarian.importer.status import ImportStatusTypes, status_notify
3
+ from django.db.models import NOT_PROVIDED
4
+ from django.db.models.functions import Now
5
+
6
+ from codex.librarian.importer.const import (
7
+ BULK_CREATE_COMIC_FIELDS,
8
+ BULK_UPDATE_COMIC_FIELDS,
9
+ BULK_UPDATE_COMIC_FIELDS_WITH_VALUES,
10
+ MDS,
11
+ )
12
+ from codex.librarian.importer.link_comics import LinkComicsImporter
13
+ from codex.librarian.importer.status import ImportStatusTypes
6
14
  from codex.models import (
7
15
  Comic,
8
16
  )
17
+ from codex.status import Status
9
18
 
10
19
 
11
- class CreateComicsMixin(LinkComicsMixin):
20
+ class CreateComicsImporter(LinkComicsImporter):
12
21
  """Create comics methods."""
13
22
 
14
- @status_notify(status_type=ImportStatusTypes.FILES_CREATED, updates=False)
15
- def bulk_create_comics(self, comic_paths, library, mds, **kwargs):
16
- """Bulk create comics."""
17
- if not comic_paths:
18
- return 0
19
- num_comics = len(comic_paths)
23
+ def _bulk_update_comics_add_comic(self, now, comic, results):
24
+ """Add one comic and stats to the bulk update list."""
25
+ try:
26
+ md = self.metadata[MDS].pop(comic.path)
27
+ self.get_comic_fk_links(md, comic.path)
28
+ for field_name in BULK_UPDATE_COMIC_FIELDS_WITH_VALUES:
29
+ value = md.get(field_name)
30
+ if value is None:
31
+ default_value = Comic._meta.get_field(field_name).default
32
+ if default_value != NOT_PROVIDED:
33
+ value = default_value
34
+ setattr(comic, field_name, value)
35
+ comic.presave()
36
+ comic.updated_at = now
37
+ update_comics, comic_pks, comic_update_paths = results
38
+ update_comics.append(comic)
39
+ comic_pks.append(comic.pk)
40
+ comic_update_paths.add(comic.path)
41
+ except Exception:
42
+ self.log.exception(f"Error preparing {comic} for update.")
20
43
 
44
+ def bulk_update_comics(self):
45
+ """Bulk update comics, and move nonextant comics into create job.."""
46
+ num_comics = len(self.task.files_modified)
47
+ if not num_comics:
48
+ return num_comics
49
+
50
+ self.log.debug(
51
+ f"Preparing {num_comics} comics for update in library {self.library.path}."
52
+ )
53
+ status = Status(ImportStatusTypes.FILES_MODIFIED, 0, num_comics)
54
+ self.status_controller.start(status, notify=False)
55
+ # Get existing comics to update
56
+ comics = Comic.objects.filter(
57
+ library=self.library, path__in=self.task.files_modified
58
+ ).only(*BULK_UPDATE_COMIC_FIELDS)
59
+
60
+ # set attributes for each comic
61
+ update_comics = []
62
+ now = Now()
63
+ comic_pks = []
64
+ comic_update_paths = set()
65
+ results = update_comics, comic_pks, comic_update_paths
66
+ for comic in comics.iterator():
67
+ self._bulk_update_comics_add_comic(now, comic, results)
68
+
69
+ converted_create_paths = frozenset(
70
+ set(self.task.files_modified) - comic_update_paths
71
+ )
72
+ self.task.files_created |= converted_create_paths
73
+ count = len(converted_create_paths)
74
+ if count:
75
+ self.log.info(f"Converted {count} update paths to create paths.")
76
+
77
+ self.log.debug(f"Bulk updating {len(update_comics)} comics.")
78
+ try:
79
+ Comic.objects.bulk_update(update_comics, BULK_UPDATE_COMIC_FIELDS)
80
+ count = len(update_comics)
81
+
82
+ self._remove_covers(comic_pks, False) # type: ignore
83
+ self.log.debug(f"Purging covers for {len(comic_pks)} updated comics.")
84
+ if count:
85
+ self.log.info(f"Updated {count} comics.")
86
+ except Exception:
87
+ self.log.exception(f"While updating {comic_update_paths}")
88
+
89
+ self.task.files_modified = frozenset()
90
+
91
+ self.status_controller.finish(status)
92
+ return count
93
+
94
+ def bulk_create_comics(self):
95
+ """Bulk create comics."""
96
+ num_comics = len(self.task.files_created)
97
+ if not num_comics:
98
+ return num_comics
21
99
  # prepare create comics
22
100
  self.log.debug(
23
- f"Preparing {num_comics} comics for creation in library {library.path}."
101
+ f"Preparing {num_comics} comics for creation in library {self.library.path}."
24
102
  )
103
+ status = Status(ImportStatusTypes.FILES_CREATED, 0, num_comics)
104
+ self.status_controller.start(status, notify=False)
105
+
25
106
  create_comics = []
26
- for path in comic_paths:
107
+ for path in sorted(self.task.files_created):
27
108
  try:
28
- md = mds.pop(path, {})
29
- self.get_comic_fk_links(md, library, path)
30
- comic = Comic(**md)
109
+ md = self.metadata[MDS].pop(path, {})
110
+ self.get_comic_fk_links(md, path)
111
+ comic = Comic(**md, library=self.library)
31
112
  comic.presave()
32
113
  create_comics.append(comic)
33
114
  except KeyError:
34
115
  self.log.warning(f"No comic metadata for {path}")
35
116
  except Exception:
36
117
  self.log.exception(f"Error preparing {path} for create.")
118
+ self.task.files_created = frozenset()
119
+ self.metadata.pop(MDS)
37
120
 
38
121
  num_comics = len(create_comics)
39
122
  count = 0
@@ -43,13 +126,15 @@ class CreateComicsMixin(LinkComicsMixin):
43
126
  Comic.objects.bulk_create(
44
127
  create_comics,
45
128
  update_conflicts=True,
46
- update_fields=BULK_UPDATE_COMIC_FIELDS,
129
+ update_fields=BULK_CREATE_COMIC_FIELDS,
47
130
  unique_fields=Comic._meta.unique_together[0], # type: ignore
48
131
  )
49
132
  count = len(create_comics)
50
133
  if count:
51
134
  self.log.info(f"Created {count} comics.")
52
135
  except Exception:
53
- self.log.exception(f"While creating {comic_paths}")
136
+ self.log.exception(f"While creating {num_comics} comics")
54
137
 
138
+ self.changed += count
139
+ self.status_controller.finish(status)
55
140
  return count