codex 1.6.0a7__py3-none-any.whl → 1.6.0a9__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 (517) hide show
  1. codex/integrity.py +5 -6
  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 +101 -17
  6. codex/librarian/importer/create_covers.py +96 -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 +42 -64
  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 +76 -104
  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/mtime.py +21 -0
  32. codex/serializers/browser/settings.py +2 -3
  33. codex/serializers/choices.py +10 -1
  34. codex/serializers/fields.py +8 -9
  35. codex/serializers/reader.py +5 -5
  36. codex/serializers/route.py +9 -4
  37. codex/settings/settings.py +2 -2
  38. codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-eNspVUje.da8282a08876.js} +1 -1
  39. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.br +0 -0
  40. codex/static_root/assets/VCheckbox-eNspVUje.da8282a08876.js.gz +0 -0
  41. codex/static_root/assets/{VCheckbox-BOUtyxuo.js → VCheckbox-eNspVUje.js} +1 -1
  42. codex/static_root/assets/VCheckbox-eNspVUje.js.br +0 -0
  43. codex/static_root/assets/VCheckbox-eNspVUje.js.gz +0 -0
  44. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js → VCheckboxBtn-DLzF_WHB.9a47873b8548.js} +1 -1
  45. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.br +0 -0
  46. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.9a47873b8548.js.gz +0 -0
  47. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.js → VCheckboxBtn-DLzF_WHB.js} +1 -1
  48. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.br +0 -0
  49. codex/static_root/assets/VCheckboxBtn-DLzF_WHB.js.gz +0 -0
  50. codex/static_root/assets/{VCombobox-DjkDXe33.22617ea193b1.js → VCombobox-BIO7lU_B.d607ada71b06.js} +1 -1
  51. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.br +0 -0
  52. codex/static_root/assets/VCombobox-BIO7lU_B.d607ada71b06.js.gz +0 -0
  53. codex/static_root/assets/{VCombobox-DjkDXe33.js → VCombobox-BIO7lU_B.js} +1 -1
  54. codex/static_root/assets/VCombobox-BIO7lU_B.js.br +0 -0
  55. codex/static_root/assets/VCombobox-BIO7lU_B.js.gz +0 -0
  56. codex/static_root/assets/{VDialog-X0zn9AGX.0d89c749b9a6.js → VDialog-87AymJC5.4a46716358c0.js} +1 -1
  57. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.br +0 -0
  58. codex/static_root/assets/VDialog-87AymJC5.4a46716358c0.js.gz +0 -0
  59. codex/static_root/assets/{VDialog-X0zn9AGX.js → VDialog-87AymJC5.js} +1 -1
  60. codex/static_root/assets/VDialog-87AymJC5.js.br +0 -0
  61. codex/static_root/assets/VDialog-87AymJC5.js.gz +0 -0
  62. codex/static_root/assets/{VExpansionPanels-CRevojaF.43d65c777c58.js → VExpansionPanels-CgA3shsd.9e524c73726a.js} +1 -1
  63. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.br +0 -0
  64. codex/static_root/assets/VExpansionPanels-CgA3shsd.9e524c73726a.js.gz +0 -0
  65. codex/static_root/assets/{VExpansionPanels-CRevojaF.js → VExpansionPanels-CgA3shsd.js} +1 -1
  66. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.br +0 -0
  67. codex/static_root/assets/VExpansionPanels-CgA3shsd.js.gz +0 -0
  68. codex/static_root/assets/{VRadioGroup-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-B--7K_v5.4a4e9c2d6c42.js} +1 -1
  69. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.br +0 -0
  70. codex/static_root/assets/VRadioGroup-B--7K_v5.4a4e9c2d6c42.js.gz +0 -0
  71. codex/static_root/assets/{VRadioGroup-8nrrxjDv.js → VRadioGroup-B--7K_v5.js} +1 -1
  72. codex/static_root/assets/VRadioGroup-B--7K_v5.js.br +0 -0
  73. codex/static_root/assets/VRadioGroup-B--7K_v5.js.gz +0 -0
  74. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  75. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  76. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  77. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  78. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  79. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  80. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js +1 -0
  81. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.br +0 -0
  82. codex/static_root/assets/VSelect-BoJq222l.fc714657e214.js.gz +0 -0
  83. codex/static_root/assets/VSelect-BoJq222l.js +1 -0
  84. codex/static_root/assets/VSelect-BoJq222l.js.br +0 -0
  85. codex/static_root/assets/VSelect-BoJq222l.js.gz +0 -0
  86. codex/static_root/assets/{VSelectionControl-CGu0oz0K.3030b7a270d6.js → VSelectionControl-CVQ9wkE2.c02186cceba6.js} +1 -1
  87. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.br +0 -0
  88. codex/static_root/assets/VSelectionControl-CVQ9wkE2.c02186cceba6.js.gz +0 -0
  89. codex/static_root/assets/{VSelectionControl-CGu0oz0K.js → VSelectionControl-CVQ9wkE2.js} +1 -1
  90. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.br +0 -0
  91. codex/static_root/assets/VSelectionControl-CVQ9wkE2.js.gz +0 -0
  92. codex/static_root/assets/{VSlideGroup-D_oNvCOd.71a7101cdddd.js → VSlideGroup-Ce0j2BKK.85e64443a928.js} +1 -1
  93. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.br +0 -0
  94. codex/static_root/assets/VSlideGroup-Ce0j2BKK.85e64443a928.js.gz +0 -0
  95. codex/static_root/assets/{VSlideGroup-D_oNvCOd.js → VSlideGroup-Ce0j2BKK.js} +1 -1
  96. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.br +0 -0
  97. codex/static_root/assets/VSlideGroup-Ce0j2BKK.js.gz +0 -0
  98. codex/static_root/assets/{VTable-C7pKI7gU.087912f706f5.js → VTable-CrWkhkiP.6d530eea0c09.js} +1 -1
  99. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.br +0 -0
  100. codex/static_root/assets/VTable-CrWkhkiP.6d530eea0c09.js.gz +0 -0
  101. codex/static_root/assets/{VTable-C7pKI7gU.js → VTable-CrWkhkiP.js} +1 -1
  102. codex/static_root/assets/VTable-CrWkhkiP.js.br +0 -0
  103. codex/static_root/assets/VTable-CrWkhkiP.js.gz +0 -0
  104. codex/static_root/assets/{VTextField-Bs8oq9mk.js → VTextField-CkWbil3K.2cac13161dfe.js} +1 -1
  105. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.br +0 -0
  106. codex/static_root/assets/VTextField-CkWbil3K.2cac13161dfe.js.gz +0 -0
  107. codex/static_root/assets/{VTextField-Bs8oq9mk.7999fef12a03.js → VTextField-CkWbil3K.js} +1 -1
  108. codex/static_root/assets/VTextField-CkWbil3K.js.br +0 -0
  109. codex/static_root/assets/VTextField-CkWbil3K.js.gz +0 -0
  110. codex/static_root/assets/{VWindowItem-iyZ1XOkP.409f7d292253.js → VWindowItem-7z55Y4lK.98eb7487e039.js} +1 -1
  111. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.br +0 -0
  112. codex/static_root/assets/VWindowItem-7z55Y4lK.98eb7487e039.js.gz +0 -0
  113. codex/static_root/assets/{VWindowItem-iyZ1XOkP.js → VWindowItem-7z55Y4lK.js} +1 -1
  114. codex/static_root/assets/VWindowItem-7z55Y4lK.js.br +0 -0
  115. codex/static_root/assets/VWindowItem-7z55Y4lK.js.gz +0 -0
  116. codex/static_root/assets/{admin-B0pCBjma.2407da79bd20.js → admin-D3OK784R.f69891726ec5.js} +1 -1
  117. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.br +0 -0
  118. codex/static_root/assets/admin-D3OK784R.f69891726ec5.js.gz +0 -0
  119. codex/static_root/assets/{admin-B0pCBjma.js → admin-D3OK784R.js} +1 -1
  120. codex/static_root/assets/admin-D3OK784R.js.br +0 -0
  121. codex/static_root/assets/admin-D3OK784R.js.gz +0 -0
  122. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-1pnfvY8O.5438f252970e.js} +11 -11
  123. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.br +0 -0
  124. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.5438f252970e.js.gz +0 -0
  125. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.js → admin-drawer-panel-1pnfvY8O.js} +11 -11
  126. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.br +0 -0
  127. codex/static_root/assets/admin-drawer-panel-1pnfvY8O.js.gz +0 -0
  128. codex/static_root/assets/browser-BuU2x9y7.css +1 -0
  129. codex/static_root/assets/browser-BuU2x9y7.css.br +0 -0
  130. codex/static_root/assets/browser-BuU2x9y7.css.gz +0 -0
  131. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css +1 -0
  132. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.br +0 -0
  133. codex/static_root/assets/browser-BuU2x9y7.f67595d24664.css.gz +0 -0
  134. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js +1 -0
  135. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.br +0 -0
  136. codex/static_root/assets/browser-CO9EYHIr.be49367c6a75.js.gz +0 -0
  137. codex/static_root/assets/browser-CO9EYHIr.js +1 -0
  138. codex/static_root/assets/browser-CO9EYHIr.js.br +0 -0
  139. codex/static_root/assets/browser-CO9EYHIr.js.gz +0 -0
  140. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.930f4cae3cb0.js → change-password-dialog-CjZdzaIk.188c765431ab.js} +1 -1
  141. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.br +0 -0
  142. codex/static_root/assets/change-password-dialog-CjZdzaIk.188c765431ab.js.gz +0 -0
  143. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.js → change-password-dialog-CjZdzaIk.js} +1 -1
  144. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.br +0 -0
  145. codex/static_root/assets/change-password-dialog-CjZdzaIk.js.gz +0 -0
  146. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js → confirm-dialog-jwL7B98r.1cfd579cbcfe.js} +1 -1
  147. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.br +0 -0
  148. codex/static_root/assets/confirm-dialog-jwL7B98r.1cfd579cbcfe.js.gz +0 -0
  149. codex/static_root/assets/{confirm-dialog-RP7JNStF.js → confirm-dialog-jwL7B98r.js} +1 -1
  150. codex/static_root/assets/confirm-dialog-jwL7B98r.js.br +0 -0
  151. codex/static_root/assets/confirm-dialog-jwL7B98r.js.gz +0 -0
  152. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js +1 -0
  153. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.br +0 -0
  154. codex/static_root/assets/datetime-column-9w3GcKvo.f9172256f091.js.gz +0 -0
  155. codex/static_root/assets/datetime-column-9w3GcKvo.js +1 -0
  156. codex/static_root/assets/datetime-column-9w3GcKvo.js.br +0 -0
  157. codex/static_root/assets/datetime-column-9w3GcKvo.js.gz +0 -0
  158. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  159. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  160. codex/static_root/assets/{filter-DAdGUt-1.3261cbcd50b5.js → filter-C-pghOri.eeb7c2ecf398.js} +1 -1
  161. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.br +0 -0
  162. codex/static_root/assets/filter-C-pghOri.eeb7c2ecf398.js.gz +0 -0
  163. codex/static_root/assets/{filter-DAdGUt-1.js → filter-C-pghOri.js} +1 -1
  164. codex/static_root/assets/filter-C-pghOri.js.br +0 -0
  165. codex/static_root/assets/filter-C-pghOri.js.gz +0 -0
  166. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js +1 -0
  167. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.br +0 -0
  168. codex/static_root/assets/flag-tab-Da2DBNyz.ffbe5ca37e1c.js.gz +0 -0
  169. codex/static_root/assets/flag-tab-Da2DBNyz.js +1 -0
  170. codex/static_root/assets/flag-tab-Da2DBNyz.js.br +0 -0
  171. codex/static_root/assets/flag-tab-Da2DBNyz.js.gz +0 -0
  172. codex/static_root/assets/{group-tab-DEwh4jfB.a95097b5e320.js → group-tab-BONYWOc1.6ddf321a4a56.js} +1 -1
  173. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.br +0 -0
  174. codex/static_root/assets/group-tab-BONYWOc1.6ddf321a4a56.js.gz +0 -0
  175. codex/static_root/assets/{group-tab-DEwh4jfB.js → group-tab-BONYWOc1.js} +1 -1
  176. codex/static_root/assets/group-tab-BONYWOc1.js.br +0 -0
  177. codex/static_root/assets/group-tab-BONYWOc1.js.gz +0 -0
  178. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js +1 -0
  179. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.br +0 -0
  180. codex/static_root/assets/http-error-D2Jnc20C.32846bc3a43d.js.gz +0 -0
  181. codex/static_root/assets/http-error-D2Jnc20C.js +1 -0
  182. codex/static_root/assets/http-error-D2Jnc20C.js.br +0 -0
  183. codex/static_root/assets/http-error-D2Jnc20C.js.gz +0 -0
  184. codex/static_root/assets/{library-tab-DrXvD9B2.js → library-tab-B419agXA.d4b778172aaa.js} +1 -1
  185. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.br +0 -0
  186. codex/static_root/assets/library-tab-B419agXA.d4b778172aaa.js.gz +0 -0
  187. codex/static_root/assets/{library-tab-DrXvD9B2.f765f9d3bae4.js → library-tab-B419agXA.js} +1 -1
  188. codex/static_root/assets/library-tab-B419agXA.js.br +0 -0
  189. codex/static_root/assets/library-tab-B419agXA.js.gz +0 -0
  190. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js +32 -0
  191. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.br +0 -0
  192. codex/static_root/assets/main-CMQwJ9aG.9836fe81c80a.js.gz +0 -0
  193. codex/static_root/assets/main-CMQwJ9aG.js +32 -0
  194. codex/static_root/assets/main-CMQwJ9aG.js.br +0 -0
  195. codex/static_root/assets/main-CMQwJ9aG.js.gz +0 -0
  196. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  197. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  198. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  199. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  200. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  201. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  202. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js +1 -0
  203. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.br +0 -0
  204. codex/static_root/assets/pagination-toolbar-LTuj5U-G.a295defbcd2f.js.gz +0 -0
  205. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js +1 -0
  206. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.br +0 -0
  207. codex/static_root/assets/pagination-toolbar-LTuj5U-G.js.gz +0 -0
  208. codex/static_root/assets/{pdf-doc-obrBwxa6.ec9bd13a5786.js → pdf-doc-8RVZqILx.2e7166dd26fb.js} +1 -1
  209. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.br +0 -0
  210. codex/static_root/assets/pdf-doc-8RVZqILx.2e7166dd26fb.js.gz +0 -0
  211. codex/static_root/assets/{pdf-doc-obrBwxa6.js → pdf-doc-8RVZqILx.js} +1 -1
  212. codex/static_root/assets/pdf-doc-8RVZqILx.js.br +0 -0
  213. codex/static_root/assets/pdf-doc-8RVZqILx.js.gz +0 -0
  214. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  215. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  216. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  217. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  218. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  219. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  220. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js +2 -0
  221. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.br +0 -0
  222. codex/static_root/assets/reader-DNnK_Chr.114d0e321547.js.gz +0 -0
  223. codex/static_root/assets/reader-DNnK_Chr.js +2 -0
  224. codex/static_root/assets/reader-DNnK_Chr.js.br +0 -0
  225. codex/static_root/assets/reader-DNnK_Chr.js.gz +0 -0
  226. codex/static_root/assets/{relation-chips-CZJcLjc8.1d7426dab654.js → relation-chips-PYua7aXv.428ba0b5861e.js} +1 -1
  227. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.br +0 -0
  228. codex/static_root/assets/relation-chips-PYua7aXv.428ba0b5861e.js.gz +0 -0
  229. codex/static_root/assets/{relation-chips-CZJcLjc8.js → relation-chips-PYua7aXv.js} +1 -1
  230. codex/static_root/assets/relation-chips-PYua7aXv.js.br +0 -0
  231. codex/static_root/assets/relation-chips-PYua7aXv.js.gz +0 -0
  232. codex/static_root/assets/{settings-drawer-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DbN6ISRH.d14f525d8eee.js} +2 -2
  233. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.br +0 -0
  234. codex/static_root/assets/settings-drawer-DbN6ISRH.d14f525d8eee.js.gz +0 -0
  235. codex/static_root/assets/{settings-drawer-7iTnAdjf.js → settings-drawer-DbN6ISRH.js} +2 -2
  236. codex/static_root/assets/settings-drawer-DbN6ISRH.js.br +0 -0
  237. codex/static_root/assets/settings-drawer-DbN6ISRH.js.gz +0 -0
  238. codex/static_root/assets/{stats-tab-CQsK1n8O.544bf50ffb22.js → stats-tab-DImNhH8O.d5026283841a.js} +1 -1
  239. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.br +0 -0
  240. codex/static_root/assets/stats-tab-DImNhH8O.d5026283841a.js.gz +0 -0
  241. codex/static_root/assets/{stats-tab-CQsK1n8O.js → stats-tab-DImNhH8O.js} +1 -1
  242. codex/static_root/assets/stats-tab-DImNhH8O.js.br +0 -0
  243. codex/static_root/assets/stats-tab-DImNhH8O.js.gz +0 -0
  244. codex/static_root/assets/{task-tab-C60TLP6e.63aa929a4ed6.js → task-tab-DPD53kjv.e56a35ff1691.js} +1 -1
  245. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.br +0 -0
  246. codex/static_root/assets/task-tab-DPD53kjv.e56a35ff1691.js.gz +0 -0
  247. codex/static_root/assets/{task-tab-C60TLP6e.js → task-tab-DPD53kjv.js} +1 -1
  248. codex/static_root/assets/task-tab-DPD53kjv.js.br +0 -0
  249. codex/static_root/assets/task-tab-DPD53kjv.js.gz +0 -0
  250. codex/static_root/assets/to-case-ehC9Ccyj.a7b8e25b391f.js +1 -0
  251. codex/static_root/assets/to-case-ehC9Ccyj.js +1 -0
  252. codex/static_root/assets/{unauthorized-Bs7NSqea.c55650fac055.js → unauthorized-BOIx7Y18.ee3961abfccb.js} +1 -1
  253. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.br +0 -0
  254. codex/static_root/assets/unauthorized-BOIx7Y18.ee3961abfccb.js.gz +0 -0
  255. codex/static_root/assets/{unauthorized-Bs7NSqea.js → unauthorized-BOIx7Y18.js} +1 -1
  256. codex/static_root/assets/unauthorized-BOIx7Y18.js.br +0 -0
  257. codex/static_root/assets/unauthorized-BOIx7Y18.js.gz +0 -0
  258. codex/static_root/assets/{user-tab-ChuH7Atm.7e80cf1fb78e.js → user-tab-BABwfxLO.37dd0440fbdd.js} +1 -1
  259. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.br +0 -0
  260. codex/static_root/assets/user-tab-BABwfxLO.37dd0440fbdd.js.gz +0 -0
  261. codex/static_root/assets/{user-tab-ChuH7Atm.js → user-tab-BABwfxLO.js} +1 -1
  262. codex/static_root/assets/user-tab-BABwfxLO.js.br +0 -0
  263. codex/static_root/assets/user-tab-BABwfxLO.js.gz +0 -0
  264. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  265. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  266. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  267. codex/static_root/js/choices-admin.json +1 -1
  268. codex/static_root/js/choices-admin.json.br +0 -0
  269. codex/static_root/js/choices-admin.json.gz +0 -0
  270. codex/static_root/js/{choices.079a01e0be1a.json → choices.a0aaaa6c7ef7.json} +1 -1
  271. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  272. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  273. codex/static_root/js/choices.json +1 -1
  274. codex/static_root/js/choices.json.br +0 -0
  275. codex/static_root/js/choices.json.gz +0 -0
  276. codex/static_root/{manifest.dad79c8475ae.json → manifest.cbea5663003a.json} +239 -239
  277. codex/static_root/manifest.cbea5663003a.json.br +0 -0
  278. codex/static_root/manifest.cbea5663003a.json.gz +0 -0
  279. codex/static_root/manifest.json +239 -239
  280. codex/static_root/manifest.json.br +0 -0
  281. codex/static_root/manifest.json.gz +0 -0
  282. codex/static_root/staticfiles.json +1 -1
  283. codex/urls/api/browser.py +11 -2
  284. codex/urls/api/reader.py +1 -7
  285. codex/urls/api/v3.py +1 -1
  286. codex/urls/opds/binary.py +1 -1
  287. codex/views/admin/auth.py +0 -2
  288. codex/views/admin/tasks.py +10 -1
  289. codex/views/auth.py +2 -4
  290. codex/views/bookmark.py +44 -40
  291. codex/views/browser/annotations.py +38 -174
  292. codex/views/browser/base.py +6 -22
  293. codex/views/browser/breadcrumbs.py +16 -14
  294. codex/views/browser/browser.py +11 -20
  295. codex/views/browser/choices.py +15 -39
  296. codex/views/browser/cover.py +177 -0
  297. codex/views/browser/filters/annotations.py +63 -18
  298. codex/views/browser/filters/bookmark.py +21 -5
  299. codex/views/browser/filters/field.py +1 -2
  300. codex/views/browser/filters/group.py +19 -11
  301. codex/views/browser/filters/search.py +19 -10
  302. codex/views/browser/metadata.py +20 -25
  303. codex/views/browser/mtime.py +72 -0
  304. codex/views/const.py +20 -0
  305. codex/views/opds/binary.py +1 -1
  306. codex/views/opds/urls.py +2 -1
  307. codex/views/opds/util.py +2 -4
  308. codex/views/opds/v1/entry/links.py +18 -33
  309. codex/views/opds/v1/facets.py +2 -1
  310. codex/views/opds/v1/feed.py +42 -23
  311. codex/views/opds/v1/links.py +2 -1
  312. codex/views/opds/v2/feed.py +11 -9
  313. codex/views/opds/v2/links.py +2 -1
  314. codex/views/opds/v2/publications.py +13 -34
  315. codex/views/public.py +0 -2
  316. codex/views/reader/books.py +7 -9
  317. codex/views/reader/reader.py +25 -22
  318. codex/views/session.py +5 -3
  319. codex/views/util.py +32 -0
  320. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/METADATA +8 -7
  321. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/RECORD +324 -325
  322. codex/librarian/importer/update_comics.py +0 -85
  323. codex/migrations/0027_sort_name.py +0 -139
  324. codex/migrations/0028_custom_covers.py +0 -166
  325. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  326. codex/serializers/mtime.py +0 -25
  327. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  328. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  329. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  330. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  331. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  332. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  333. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  334. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  335. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  336. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  337. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  338. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  339. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  340. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  341. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  342. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  343. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  344. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  345. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  346. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  347. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  348. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  349. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  350. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  351. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +0 -1
  352. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  353. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  354. codex/static_root/assets/VSelect-DgfIKRnC.js +0 -1
  355. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  356. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  357. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  358. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  359. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  360. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  361. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  362. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  363. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  364. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  365. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  366. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  367. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  368. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  369. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  370. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  371. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  372. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  373. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  374. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  375. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  376. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  377. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  378. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  379. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  380. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  381. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  382. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  383. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  384. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  385. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  386. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  387. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  388. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  389. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  390. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  391. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +0 -1
  392. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  393. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  394. codex/static_root/assets/browser-DbyAjCNh.js +0 -1
  395. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  396. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  397. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +0 -1
  398. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  399. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  400. codex/static_root/assets/browser-VnzNj1t3.css +0 -1
  401. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  402. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  403. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  404. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  405. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  406. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  407. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
  408. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  409. codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
  410. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  411. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js +0 -1
  412. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  413. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  414. codex/static_root/assets/datetime-column-Cu3WYyPj.js +0 -1
  415. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  416. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  417. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  418. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  419. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  420. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  421. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  422. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  423. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  424. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  425. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +0 -1
  426. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  427. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  428. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +0 -1
  429. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  430. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  431. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  432. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  433. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  434. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  435. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +0 -1
  436. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  437. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  438. codex/static_root/assets/http-error-xs3mIL8U.js +0 -1
  439. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  440. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  441. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  442. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  443. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  444. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  445. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +0 -32
  446. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  447. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  448. codex/static_root/assets/main-Dy9Uqpmh.js +0 -32
  449. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  450. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  451. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +0 -1
  452. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  453. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  454. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +0 -1
  455. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  456. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  457. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css +0 -1
  458. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  459. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  460. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css +0 -1
  461. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  462. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  463. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  464. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  465. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  466. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  467. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +0 -2
  468. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  469. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  470. codex/static_root/assets/reader-BXKUaUeC.js +0 -2
  471. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  472. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  473. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css +0 -1
  474. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  475. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  476. codex/static_root/assets/reader-OXJ1KVaW.css +0 -1
  477. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  478. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  479. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  480. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  481. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  482. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  483. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  484. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  485. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  486. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  487. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  488. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  489. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  490. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  491. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  492. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  493. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  494. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  495. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +0 -1
  496. codex/static_root/assets/to-case-CR9beRR0.js +0 -1
  497. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  498. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  499. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  500. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  501. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  502. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  503. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  504. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  505. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  506. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  507. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  508. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  509. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  510. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  511. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  512. codex/views/cover.py +0 -76
  513. codex/views/mtime.py +0 -70
  514. codex/views/utils.py +0 -26
  515. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/LICENSE +0 -0
  516. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/WHEEL +0 -0
  517. {codex-1.6.0a7.dist-info → codex-1.6.0a9.dist-info}/entry_points.txt +0 -0
@@ -8,7 +8,9 @@ from drf_spectacular.utils import extend_schema
8
8
  from rest_framework.exceptions import NotFound
9
9
  from rest_framework.response import Response
10
10
 
11
- from codex.librarian.importer.const import COMIC_M2M_FIELD_NAMES
11
+ from codex.librarian.importer.const import (
12
+ COMIC_M2M_FIELD_NAMES,
13
+ )
12
14
  from codex.logger.logging import get_logger
13
15
  from codex.models import AdminFlag, Comic
14
16
  from codex.serializers.browser.metadata import MetadataSerializer
@@ -52,6 +54,11 @@ class MetadataView(BrowserAnnotationsView):
52
54
  }
53
55
  )
54
56
 
57
+ def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
58
+ """Limited allowed nav groups for metadata."""
59
+ group = self.kwargs["group"]
60
+ self.valid_nav_groups = (group,)
61
+
55
62
  def _get_comic_value_fields(self):
56
63
  """Include the path field for staff."""
57
64
  fields = set(_COMIC_VALUE_FIELD_NAMES)
@@ -153,6 +160,8 @@ class MetadataView(BrowserAnnotationsView):
153
160
  if field_name in _GROUP_RELS:
154
161
  group_by = "name" if field_name == "volume" else "sort_name"
155
162
  intersection_qs = intersection_qs.group_by(group_by)
163
+ if field_name == "identifiers":
164
+ intersection_qs = intersection_qs.select_related("identifier_type")
156
165
  intersection_qs = intersection_qs.alias(count=Count("comic")).filter(
157
166
  count=comic_pks_count
158
167
  )
@@ -185,18 +194,18 @@ class MetadataView(BrowserAnnotationsView):
185
194
  def _get_optimized_m2m_query(key, qs):
186
195
  # XXX The prefetch gets removed by field.set() :(
187
196
  if key == "contributors":
188
- optimized_qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
197
+ qs = qs.prefetch_related(*_CONTRIBUTOR_RELATIONS).only(
189
198
  *_CONTRIBUTOR_RELATIONS
190
199
  )
191
200
  elif key == "story_arc_numbers":
192
- optimized_qs = qs.prefetch_related("story_arc").only("story_arc", "number")
201
+ qs = qs.prefetch_related("story_arc")
202
+ qs = qs.only("story_arc", "number")
193
203
  elif key == "identifiers":
194
- optimized_qs = qs.prefetch_related("identifier_type").only(
195
- "identifier_type", "nss", "url"
196
- )
204
+ qs = qs.prefetch_related("identifier_type")
205
+ qs = qs.only("identifier_type", "nss", "url")
197
206
  else:
198
- optimized_qs = qs.only("name")
199
- return optimized_qs
207
+ qs = qs.only("name")
208
+ return qs
200
209
 
201
210
  @classmethod
202
211
  def _copy_m2m_intersections(cls, obj, m2m_intersections):
@@ -244,26 +253,17 @@ class MetadataView(BrowserAnnotationsView):
244
253
  # values dicts don't copy relations to the serializer. The values
245
254
  # dict is necessary because of the folders view union in browser.py.
246
255
 
247
- if self.model is None:
248
- # TODO this looks redundant after set_browse_model
249
- group = self.kwargs["group"]
250
- raise NotFound(detail=f"Cannot get metadata for {group=}")
256
+ qs = self.get_filtered_queryset(self.model)
251
257
 
252
- object_filter = self.get_query_filters_without_group(self.model) # type: ignore
253
- pks = self.kwargs["pks"]
254
- qs = self.model.objects.filter(object_filter, pk__in=pks)
255
- qs = self.filter_by_annotations(qs, self.model, binary=True)
256
258
  filtered_qs = qs
257
259
  qs = self.annotate_order_aggregates(qs, self.model)
258
260
  qs = self.annotate_card_aggregates(qs, self.model)
261
+ qs = self._annotate_values_and_fks(qs, filtered_qs)
259
262
  group_by = self.get_group_by()
260
263
  qs = qs.group_by(group_by)
261
- qs = self._annotate_values_and_fks(qs, filtered_qs)
262
-
263
- qs_list = self.re_cover_multi_groups(qs)
264
264
 
265
265
  try:
266
- obj = qs_list[0]
266
+ obj = qs[0]
267
267
  if not obj:
268
268
  reason = "Empty obj"
269
269
  raise ValueError(reason) # noqa TRY301
@@ -273,11 +273,6 @@ class MetadataView(BrowserAnnotationsView):
273
273
  m2m_intersections = self._query_m2m_intersections(filtered_qs)
274
274
  return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
275
275
 
276
- def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
277
- """Limited allowed nav groups for metadata."""
278
- group = self.kwargs["group"]
279
- self.valid_nav_groups = (group,)
280
-
281
276
  @extend_schema(request=BrowserAnnotationsView.input_serializer_class)
282
277
  def get(self, *_args, **_kwargs):
283
278
  """Get metadata for a filtered browse group."""
@@ -0,0 +1,72 @@
1
+ """Get the mtimes for the submitted groups."""
2
+
3
+ from types import MappingProxyType
4
+
5
+ from rest_framework.response import Response
6
+
7
+ from codex.logger.logging import get_logger
8
+ from codex.models.groups import Publisher
9
+ from codex.serializers.browser.mtime import GroupsMtimeSerializer, MtimeSerializer
10
+ from codex.util import max_none
11
+ from codex.views.browser.filters.annotations import BrowserAnnotationsFilterView
12
+ from codex.views.const import GROUP_MODEL_MAP
13
+
14
+ LOG = get_logger(__name__)
15
+
16
+
17
+ class MtimeView(BrowserAnnotationsFilterView):
18
+ """Get the mtimes for the submitted groups."""
19
+
20
+ input_serializer_class = GroupsMtimeSerializer
21
+ serializer_class = MtimeSerializer
22
+
23
+ REPARSE_JSON_FIELDS = frozenset({"groups", "filters"})
24
+
25
+ def __init__(self, *args, **kwargs) -> None:
26
+ """Initialize."""
27
+ super().__init__(*args, **kwargs)
28
+ self.init_bookmark_data()
29
+
30
+ def parse_params(self):
31
+ """Parse GET params."""
32
+ try:
33
+ validated_data = super().parse_params()
34
+ params = dict(self.params)
35
+ params["groups"] = validated_data["groups"] # type: ignore
36
+ self.params = MappingProxyType(params)
37
+
38
+ except Exception:
39
+ LOG.exception("parse")
40
+ raise
41
+
42
+ def _get_group_mtime(self, item):
43
+ """Get one group's mtimes."""
44
+ group = item["group"]
45
+ pks = item["pks"]
46
+
47
+ model = GROUP_MODEL_MAP[group]
48
+ if not model:
49
+ model = Publisher
50
+
51
+ return self.get_group_mtime(model, group, pks)
52
+
53
+ def get_max_groups_mtime(self):
54
+ """Get max mtime for all groups."""
55
+ max_mtime = None
56
+
57
+ for item in self.params["groups"]: # type: ignore
58
+ mtime = self._get_group_mtime(item)
59
+ max_mtime = max_none(max_mtime, mtime)
60
+ return max_mtime
61
+
62
+ def get(self, *args, **kwargs):
63
+ """Get the mtimes for the submitted groups."""
64
+ # Parse Request
65
+ self.parse_params()
66
+
67
+ max_mtime = self.get_max_groups_mtime()
68
+
69
+ # Serialize Response
70
+ result = {"max_mtime": max_mtime}
71
+ serializer = self.get_serializer(result)
72
+ return Response(serializer.data)
codex/views/const.py CHANGED
@@ -1,9 +1,12 @@
1
1
  """Common view constants."""
2
2
 
3
+ from datetime import datetime, timezone
3
4
  from types import MappingProxyType
4
5
 
5
6
  from django.contrib.auth.models import Group, User
6
7
  from django.contrib.sessions.models import Session
8
+ from django.db.models.expressions import Value
9
+ from django.db.models.fields import DateTimeField, PositiveSmallIntegerField
7
10
 
8
11
  from codex.models import (
9
12
  AgeRating,
@@ -64,6 +67,18 @@ GROUP_RELATION = MappingProxyType(
64
67
  STORY_ARC_GROUP: "story_arc_numbers__story_arc",
65
68
  }
66
69
  )
70
+ GROUP_ORDER = "rpisv"
71
+ MODEL_REL_MAP = MappingProxyType(
72
+ {
73
+ Publisher: "publisher",
74
+ Imprint: "imprint",
75
+ Series: "series",
76
+ Volume: "volume",
77
+ Folder: "parent_folder",
78
+ StoryArc: "story_arc_numbers__story_arc",
79
+ Comic: "pk",
80
+ }
81
+ )
67
82
  GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
68
83
  MappingProxyType(
69
84
  {
@@ -115,3 +130,8 @@ CONFIG_MODELS = (
115
130
  )
116
131
  MAX_OBJ_PER_PAGE = 100
117
132
  GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
133
+ EPOCH_START = datetime.fromtimestamp(0, tz=timezone.utc)
134
+ ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
135
+ NONE_INTEGERFIELD = Value(None, PositiveSmallIntegerField())
136
+ NONE_DATETIMEFIELD = Value(None, DateTimeField())
137
+ EPOCH_START_DATETIMEFILED = Value(EPOCH_START)
@@ -1,6 +1,6 @@
1
1
  """Binary views with Basic Authentication added."""
2
2
 
3
- from codex.views.cover import CoverView
3
+ from codex.views.browser.cover import CoverView
4
4
  from codex.views.download import DownloadView
5
5
  from codex.views.opds.auth import OPDSAuthMixin
6
6
  from codex.views.reader.page import ReaderPageView
codex/views/opds/urls.py CHANGED
@@ -6,6 +6,7 @@ from rest_framework.response import Response
6
6
  from codex.serializers.choices import DEFAULTS
7
7
  from codex.serializers.opds.urls import OPDSURLsSerializer
8
8
  from codex.views.auth import AuthGenericAPIView
9
+ from codex.views.util import pop_name
9
10
 
10
11
  _OPDS_VERSIONS = (1, 2)
11
12
 
@@ -19,7 +20,7 @@ class OPDSURLsView(AuthGenericAPIView):
19
20
  """Resolve the urls."""
20
21
  obj = {}
21
22
  route = DEFAULTS["breadcrumbs"][0]
22
- route.pop("name", None)
23
+ route = pop_name(route)
23
24
  for version in _OPDS_VERSIONS:
24
25
  key = f"v{version}"
25
26
  name = f"opds:v{version}:feed"
codex/views/opds/util.py CHANGED
@@ -8,9 +8,9 @@ from django.utils.http import urlencode
8
8
  from codex.models import (
9
9
  Contributor,
10
10
  ContributorPerson,
11
- StoryArc,
12
11
  )
13
12
  from codex.serializers.choices import DEFAULTS
13
+ from codex.views.auth import GroupACLMixin
14
14
  from codex.views.opds.const import OPDS_M2M_MODELS
15
15
 
16
16
 
@@ -58,9 +58,7 @@ def get_m2m_objects(pks) -> dict:
58
58
  cats = {}
59
59
  for model in OPDS_M2M_MODELS:
60
60
  table = model.__name__.lower()
61
- rel = "comic"
62
- if model == StoryArc:
63
- rel = "storyarcnumber__" + rel
61
+ rel = GroupACLMixin.get_rel_prefix(model)
64
62
  comic_filter = {rel + "__in": pks}
65
63
  qs = model.objects.filter(**comic_filter).order_by("name").only("name")
66
64
  cats[table] = qs
@@ -1,12 +1,12 @@
1
1
  """OPDS v1 Entry Links Methods."""
2
2
 
3
+ from datetime import datetime
4
+ from math import floor
3
5
  from urllib.parse import quote_plus
4
6
 
5
- from django.contrib.staticfiles.storage import staticfiles_storage
6
7
  from django.urls import reverse
7
8
 
8
9
  from codex.logger.logging import get_logger
9
- from codex.views.const import MISSING_COVER_FN, MISSING_COVER_NAME_MAP
10
10
  from codex.views.opds.const import MimeType, Rel
11
11
  from codex.views.opds.util import update_href_query_params
12
12
  from codex.views.opds.v1.data import OPDS1Link
@@ -31,39 +31,24 @@ class OPDS1EntryLinksMixin:
31
31
  self.mime_type_map = data.mime_type_map
32
32
  self.title_filename_fallback = title_filename_fallback
33
33
 
34
- def _thumb_link(self):
34
+ def _cover_link(self, rel):
35
35
  if self.fake:
36
36
  return None
37
- cover_pk = getattr(self.obj, "cover_pk", None)
38
- if cover_pk:
39
- kwargs = {"pk": cover_pk}
37
+ # cover_pk = getattr(self.obj, "cover_pk", None)
38
+ try:
39
+ kwargs = {"group": self.obj.group, "pks": self.obj.ids}
40
+ ts = floor(datetime.timestamp(self.obj.updated_at))
41
+ query_params = {
42
+ "customCovers": True,
43
+ "dynamicCovers": False,
44
+ "ts": ts,
45
+ }
40
46
  href = reverse("opds:bin:cover", kwargs=kwargs)
47
+ href = update_href_query_params(href, query_params)
41
48
  mime_type = "image/webp"
42
- else:
43
- cover_name = MISSING_COVER_NAME_MAP.get(self.obj.group)
44
- cover_path = "img/"
45
- if cover_name:
46
- cover_path += cover_name + ".svg"
47
- mime_type = "image/svg+xml"
48
- else:
49
- cover_path += MISSING_COVER_FN
50
- mime_type = "image/webp"
51
- href = staticfiles_storage.url(cover_path)
52
- return OPDS1Link(Rel.THUMBNAIL, href, mime_type)
53
-
54
- def _image_link(self):
55
- if self.fake:
56
- return None
57
- cover_pk = getattr(self.obj, "cover_pk", None)
58
- if cover_pk:
59
- kwargs = {"pk": cover_pk, "page": 0}
60
- href = reverse("opds:bin:page", kwargs=kwargs)
61
- mime_type = "image/jpeg"
62
- else:
63
- fn = f"img/{MISSING_COVER_FN}"
64
- href = staticfiles_storage.url(fn)
65
- mime_type = "image/webp"
66
- return OPDS1Link(Rel.IMAGE, href, mime_type)
49
+ return OPDS1Link(rel, href, mime_type)
50
+ except Exception:
51
+ LOG.exception("create thumb")
67
52
 
68
53
  def _nav_href(self, metadata=False):
69
54
  try:
@@ -149,9 +134,9 @@ class OPDS1EntryLinksMixin:
149
134
  """Create all entry links."""
150
135
  result = []
151
136
  try:
152
- if thumb := self._thumb_link():
137
+ if thumb := self._cover_link(Rel.THUMBNAIL):
153
138
  result += [thumb]
154
- if image := self._image_link():
139
+ if image := self._cover_link(Rel.IMAGE):
155
140
  result += [image]
156
141
 
157
142
  if self.obj.group == "c" and not self.fake:
@@ -12,6 +12,7 @@ from codex.views.opds.util import update_href_query_params
12
12
  from codex.views.opds.v1.data import OPDS1Link
13
13
  from codex.views.opds.v1.entry.data import OPDS1EntryData, OPDS1EntryObject
14
14
  from codex.views.opds.v1.entry.entry import OPDS1Entry
15
+ from codex.views.util import pop_name
15
16
 
16
17
 
17
18
  @dataclass
@@ -89,7 +90,7 @@ class OPDS1FacetsView(BrowserView):
89
90
  obj = MappingProxyType({})
90
91
 
91
92
  def _facet(self, kwargs, facet_group, facet_title, new_query_params):
92
- kwargs.pop("name", None)
93
+ kwargs = pop_name(kwargs)
93
94
  href = reverse("opds:v1:feed", kwargs=kwargs)
94
95
  facet_active = False
95
96
  for key, val in new_query_params.items():
@@ -41,13 +41,13 @@ class OpdsNs:
41
41
  ACQUISITION = "http://opds-spec.org/2010/acquisition"
42
42
 
43
43
 
44
- class UserAgentPrefixes:
44
+ class UserAgentNames:
45
45
  """Control whether to hack in facets with nav links."""
46
46
 
47
- CLIENT_REORDERS = ("Chunky",)
48
- FACET_SUPPORT = ("yar",) # kybooks
49
- SIMPLE_DOWNLOAD_MIME_TYPES = ("PocketBook",)
50
- # Other known valid prefixes:
47
+ CLIENT_REORDERS = frozenset({"Chunky"})
48
+ FACET_SUPPORT = frozenset({"yar"}) # kybooks
49
+ SIMPLE_DOWNLOAD_MIME_TYPES = frozenset({"PocketBook Reader"})
50
+ # Other known valid names:
51
51
  # "Panels", "Chunky"
52
52
 
53
53
 
@@ -168,14 +168,17 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
168
168
 
169
169
  def _ensure_page_counts(self):
170
170
  """Ensure page counts on books with just in time comicbox."""
171
+ if self.admin_flags.get("import_metadata"):
172
+ return
171
173
  books_qs: QuerySet = self.obj["books"] # type: ignore
172
174
  import_pks = set()
173
175
  new_books = []
174
176
  for book in books_qs:
175
- if not book.page_count:
176
- with Comicbox(book.path) as cb:
177
- book.file_type = cb.get_file_type()
178
- book.page_count = cb.get_page_count()
177
+ if book.page_count is not None:
178
+ continue
179
+ with Comicbox(book.path) as cb:
180
+ book.file_type = cb.get_file_type()
181
+ book.page_count = cb.get_page_count()
179
182
  new_books.append(book)
180
183
  import_pks.add(book.pk)
181
184
  if new_books:
@@ -187,7 +190,24 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
187
190
 
188
191
  def get_object(self): # type: ignore
189
192
  """Get the browser page and serialize it for this subclass."""
190
- self.obj = super().get_object()
193
+ group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
194
+ super()._get_group_and_books()
195
+ )
196
+ book_qs = book_qs.select_related("series", "volume", "language")
197
+
198
+ title = self.get_browser_page_title()
199
+ self.obj = MappingProxyType(
200
+ {
201
+ "title": title,
202
+ "groups": group_qs,
203
+ "books": book_qs,
204
+ "zero_pad": zero_pad,
205
+ "num_pages": num_pages,
206
+ "total_count": total_count,
207
+ "mtime": mtime,
208
+ }
209
+ )
210
+
191
211
  self.is_aq_feed = self.model_group in ("c", "f")
192
212
 
193
213
  self._ensure_page_counts()
@@ -197,22 +217,21 @@ class OPDS1FeedView(OPDS1LinksView, OPDSTemplateView):
197
217
 
198
218
  def _set_user_agent_variables(self):
199
219
  """Set User Agent variables."""
200
- # defaults in FacetsMixin
201
220
  user_agent = self.request.headers.get("User-Agent")
202
221
  if not user_agent:
203
222
  return
204
- for prefix in UserAgentPrefixes.FACET_SUPPORT:
205
- if user_agent.startswith(prefix):
206
- self.use_facets = True
207
- break
208
- for prefix in UserAgentPrefixes.CLIENT_REORDERS:
209
- if user_agent.startswith(prefix):
210
- self.skip_order_facets = True
211
- break
212
- for prefix in UserAgentPrefixes.SIMPLE_DOWNLOAD_MIME_TYPES:
213
- if user_agent.startswith(prefix):
214
- self.mime_type_map = MimeType.SIMPLE_FILE_TYPE_MAP
215
- break
223
+ user_agent_parts = user_agent.split("/", 1)
224
+ if user_agent_parts:
225
+ user_agent_name = user_agent_parts[0]
226
+ else:
227
+ return
228
+
229
+ if user_agent_name in UserAgentNames.FACET_SUPPORT:
230
+ self.use_facets = True
231
+ if user_agent_name in UserAgentNames.CLIENT_REORDERS:
232
+ self.skip_order_facets = True
233
+ if user_agent_name in UserAgentNames.SIMPLE_DOWNLOAD_MIME_TYPES:
234
+ self.mime_type_map = MimeType.SIMPLE_FILE_TYPE_MAP
216
235
 
217
236
  def set_opds_request_type(self):
218
237
  """Set the opds request type variables."""
@@ -15,6 +15,7 @@ from codex.views.opds.v1.data import OPDS1Link
15
15
  from codex.views.opds.v1.entry.data import OPDS1EntryData, OPDS1EntryObject
16
16
  from codex.views.opds.v1.entry.entry import OPDS1Entry
17
17
  from codex.views.opds.v1.facets import OPDS1FacetsView
18
+ from codex.views.util import pop_name
18
19
 
19
20
  LOG = get_logger(__name__)
20
21
 
@@ -129,7 +130,7 @@ class OPDS1LinksView(OPDS1FacetsView):
129
130
  """Create a link."""
130
131
  if query_params is None:
131
132
  query_params = self.request.GET
132
- kwargs.pop("name", None)
133
+ kwargs = pop_name(kwargs)
133
134
  href = reverse("opds:v1:feed", kwargs=kwargs)
134
135
  href = update_href_query_params(href, query_params)
135
136
  return OPDS1Link(rel, href, mime_type)
@@ -175,32 +175,34 @@ class OPDS2FeedView(OPDSAuthMixin, OPDS2PublicationView):
175
175
 
176
176
  def get_object(self):
177
177
  """Get the browser page and serialize it for this subclass."""
178
- browser_page = super().get_object()
179
-
178
+ group_qs, book_qs, num_pages, total_count, zero_pad, mtime = (
179
+ self._get_group_and_books()
180
+ )
181
+ title = self.get_browser_page_title()
180
182
  # convert browser_page into opds page
181
183
 
182
184
  # instance vars
183
- self.is_aq_feed = browser_page.get("model_group") in ("c", "f")
184
- self.num_pages = browser_page["num_pages"]
185
+ self.is_aq_feed = self.model_group in ("c", "f")
186
+ self.num_pages = num_pages
185
187
 
186
188
  # opds page
187
- title = self._title(browser_page.get("title"))
188
- number_of_items = browser_page["total_count"]
189
+ title = self._title(title)
190
+ number_of_items = total_count
189
191
  current_page = self.kwargs.get("page")
190
192
  up_route = self.get_last_route()
191
193
  links = self.get_links(up_route)
192
194
  facets = self._get_facets()
193
195
 
194
196
  # opds groups
195
- page_groups = browser_page.get("groups")
196
- page_books = browser_page.get("books")
197
- zero_pad = browser_page["zero_pad"]
197
+ page_groups = group_qs
198
+ page_books = book_qs
198
199
  groups = self._get_groups(page_groups, page_books, title, zero_pad)
199
200
 
200
201
  return MappingProxyType(
201
202
  {
202
203
  "metadata": {
203
204
  "title": title,
205
+ "modified": mtime,
204
206
  "number_of_items": number_of_items,
205
207
  "items_per_page": MAX_OBJ_PER_PAGE,
206
208
  "current_page": current_page,
@@ -10,6 +10,7 @@ from codex.views.browser.browser import BrowserView
10
10
  from codex.views.const import FALSY
11
11
  from codex.views.opds.const import MimeType, Rel
12
12
  from codex.views.opds.util import update_href_query_params
13
+ from codex.views.util import pop_name
13
14
 
14
15
 
15
16
  @dataclass
@@ -69,7 +70,7 @@ class OPDS2LinksView(BrowserView):
69
70
  if "page" in kwargs and not self._href_page_validate(kwargs, data):
70
71
  return None
71
72
 
72
- kwargs.pop("name", None)
73
+ kwargs = pop_name(kwargs)
73
74
  href = reverse(url_name, kwargs=kwargs)
74
75
  return self._href_update_query_params(href, data)
75
76
 
@@ -1,10 +1,10 @@
1
1
  """Publication Methods for OPDS v2.0 feed."""
2
2
 
3
+ from datetime import datetime
4
+ from math import floor
3
5
  from types import MappingProxyType
4
6
  from urllib.parse import quote_plus
5
7
 
6
- from django.contrib.staticfiles.storage import staticfiles_storage
7
-
8
8
  from codex.librarian.covers.create import CoverCreateMixin
9
9
  from codex.models import Comic
10
10
  from codex.views.opds.const import AUTHOR_ROLES, MimeType, Rel
@@ -32,39 +32,16 @@ class OPDS2PublicationView(OPDS2TopLinksView):
32
32
 
33
33
  is_opds_metadata = False
34
34
 
35
- def _get_big_image_link(self, obj, cover_pk):
36
- if cover_pk:
37
- mime_type = MimeType.JPEG
38
- url_name = "opds:bin:page"
39
- href = None
40
- min_page = 0
41
- max_page = obj.max_page or 1
42
- else:
43
- mime_type = MimeType.WEBP
44
- href = staticfiles_storage.url("img/missing_cover.webp")
45
- url_name = None
46
- min_page = None
47
- max_page = None
48
-
49
- kwargs = {"pk": obj.pk, "page": 0}
50
- href_data = HrefData(
51
- kwargs,
52
- absolute_query_params=True,
53
- url_name=url_name,
54
- min_page=min_page,
55
- max_page=max_page,
56
- )
57
- link_data = LinkData(Rel.IMAGE, href_data, href=href, mime_type=mime_type)
58
- return self.link(link_data)
59
-
60
35
  def _images(self, obj):
61
36
  # Images
62
- cover_pk = obj.cover_pk
63
-
64
- kwargs = {"pk": cover_pk}
65
- href_data = HrefData(
66
- kwargs, url_name="opds:bin:cover", absolute_query_params=True
67
- )
37
+ kwargs = {"group": obj.group, "pks": obj.ids}
38
+ ts = floor(datetime.timestamp(obj.updated_at))
39
+ query_params = {
40
+ "customCovers": True,
41
+ "dynamicCovers": False,
42
+ "ts": ts,
43
+ }
44
+ href_data = HrefData(kwargs, query_params, True, "opds:bin:cover")
68
45
  link_data = LinkData(
69
46
  Rel.THUMBNAIL,
70
47
  href_data,
@@ -75,7 +52,9 @@ class OPDS2PublicationView(OPDS2TopLinksView):
75
52
 
76
53
  cover_link = self.link(link_data)
77
54
 
78
- image_link = self._get_big_image_link(obj, cover_pk)
55
+ link_data.rel = Rel.IMAGE
56
+
57
+ image_link = self.link(link_data)
79
58
 
80
59
  return [
81
60
  cover_link,
codex/views/public.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from rest_framework.generics import GenericAPIView
4
4
  from rest_framework.mixins import RetrieveModelMixin
5
- from rest_framework.parsers import JSONParser
6
5
 
7
6
  from codex.logger.logging import get_logger
8
7
  from codex.models import AdminFlag
@@ -21,7 +20,6 @@ _ADMIN_FLAG_KEYS = frozenset(
21
20
  class AdminFlagsView(GenericAPIView, RetrieveModelMixin):
22
21
  """Get admin flags relevant to auth."""
23
22
 
24
- parser_classes = (JSONParser,)
25
23
  serializer_class = AuthAdminFlagsSerializer
26
24
  queryset = AdminFlag.objects.filter(key__in=_ADMIN_FLAG_KEYS).only("key", "on")
27
25