codex 1.6.0a7__py3-none-any.whl → 1.6.0a8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of codex might be problematic. Click here for more details.

Files changed (510) hide show
  1. codex/integrity.py +4 -4
  2. codex/librarian/importer/{aggregate_metadata.py → aggregate.py} +54 -76
  3. codex/librarian/importer/cache.py +162 -29
  4. codex/librarian/importer/const.py +15 -6
  5. codex/librarian/importer/create_comics.py +102 -17
  6. codex/librarian/importer/create_covers.py +98 -0
  7. codex/librarian/importer/create_fks.py +73 -170
  8. codex/librarian/importer/deleted.py +59 -46
  9. codex/librarian/importer/{clean_metadata.py → extract.py} +33 -4
  10. codex/librarian/importer/failed_imports.py +41 -62
  11. codex/librarian/importer/importer.py +96 -0
  12. codex/librarian/importer/importerd.py +71 -397
  13. codex/librarian/importer/init.py +265 -0
  14. codex/librarian/importer/link_comics.py +26 -81
  15. codex/librarian/importer/link_covers.py +73 -0
  16. codex/librarian/importer/moved.py +72 -96
  17. codex/librarian/importer/query_covers.py +52 -0
  18. codex/librarian/importer/query_fks.py +128 -172
  19. codex/librarian/importer/status.py +2 -39
  20. codex/librarian/importer/tasks.py +22 -13
  21. codex/librarian/janitor/cleanup.py +2 -2
  22. codex/librarian/janitor/janitor.py +10 -0
  23. codex/librarian/librariand.py +7 -1
  24. codex/migrations/0027_import_order_and_covers.py +345 -0
  25. codex/models/comic.py +1 -2
  26. codex/models/groups.py +3 -2
  27. codex/models/paths.py +0 -1
  28. codex/search/query.py +25 -2
  29. codex/search/writing.py +1 -1
  30. codex/serializers/browser/mixins.py +0 -18
  31. codex/serializers/browser/settings.py +2 -3
  32. codex/serializers/choices.py +10 -1
  33. codex/settings/settings.py +2 -2
  34. codex/static_root/assets/{VCheckbox-BOUtyxuo.c690f0cdbe48.js → VCheckbox-DI-DgMb4.8eeb1bd3e18b.js} +1 -1
  35. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.br +0 -0
  36. codex/static_root/assets/VCheckbox-DI-DgMb4.8eeb1bd3e18b.js.gz +0 -0
  37. codex/static_root/assets/{VCheckbox-BOUtyxuo.js → VCheckbox-DI-DgMb4.js} +1 -1
  38. codex/static_root/assets/VCheckbox-DI-DgMb4.js.br +0 -0
  39. codex/static_root/assets/VCheckbox-DI-DgMb4.js.gz +0 -0
  40. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js → VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js} +1 -1
  41. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.br +0 -0
  42. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.3d8921f98ad9.js.gz +0 -0
  43. codex/static_root/assets/{VCheckboxBtn-B1-m5pEh.js → VCheckboxBtn-B8Z4NA-t.js} +1 -1
  44. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.br +0 -0
  45. codex/static_root/assets/VCheckboxBtn-B8Z4NA-t.js.gz +0 -0
  46. codex/static_root/assets/{VCombobox-DjkDXe33.22617ea193b1.js → VCombobox-D1_0lCri.6ef85a29c2dc.js} +1 -1
  47. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.br +0 -0
  48. codex/static_root/assets/VCombobox-D1_0lCri.6ef85a29c2dc.js.gz +0 -0
  49. codex/static_root/assets/{VCombobox-DjkDXe33.js → VCombobox-D1_0lCri.js} +1 -1
  50. codex/static_root/assets/VCombobox-D1_0lCri.js.br +0 -0
  51. codex/static_root/assets/VCombobox-D1_0lCri.js.gz +0 -0
  52. codex/static_root/assets/{VDialog-X0zn9AGX.0d89c749b9a6.js → VDialog-opmV0YZz.d77216e97599.js} +1 -1
  53. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.br +0 -0
  54. codex/static_root/assets/VDialog-opmV0YZz.d77216e97599.js.gz +0 -0
  55. codex/static_root/assets/{VDialog-X0zn9AGX.js → VDialog-opmV0YZz.js} +1 -1
  56. codex/static_root/assets/VDialog-opmV0YZz.js.br +0 -0
  57. codex/static_root/assets/VDialog-opmV0YZz.js.gz +0 -0
  58. codex/static_root/assets/{VExpansionPanels-CRevojaF.43d65c777c58.js → VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js} +1 -1
  59. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.br +0 -0
  60. codex/static_root/assets/VExpansionPanels-DX2dhUf5.ecc2ebfa62ec.js.gz +0 -0
  61. codex/static_root/assets/{VExpansionPanels-CRevojaF.js → VExpansionPanels-DX2dhUf5.js} +1 -1
  62. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.br +0 -0
  63. codex/static_root/assets/VExpansionPanels-DX2dhUf5.js.gz +0 -0
  64. codex/static_root/assets/{VRadioGroup-8nrrxjDv.2b4dfb984a8c.js → VRadioGroup-BGa6_q5a.d1a3ad7266bd.js} +1 -1
  65. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.br +0 -0
  66. codex/static_root/assets/VRadioGroup-BGa6_q5a.d1a3ad7266bd.js.gz +0 -0
  67. codex/static_root/assets/{VRadioGroup-8nrrxjDv.js → VRadioGroup-BGa6_q5a.js} +1 -1
  68. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.br +0 -0
  69. codex/static_root/assets/VRadioGroup-BGa6_q5a.js.gz +0 -0
  70. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +1 -0
  71. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  72. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  73. codex/static_root/assets/VSelect-ARDhJiQK.css +1 -0
  74. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  75. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  76. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js +1 -0
  77. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.br +0 -0
  78. codex/static_root/assets/VSelect-sIYW_-ud.7d40da0ec3ff.js.gz +0 -0
  79. codex/static_root/assets/VSelect-sIYW_-ud.js +1 -0
  80. codex/static_root/assets/VSelect-sIYW_-ud.js.br +0 -0
  81. codex/static_root/assets/VSelect-sIYW_-ud.js.gz +0 -0
  82. codex/static_root/assets/{VSelectionControl-CGu0oz0K.3030b7a270d6.js → VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js} +1 -1
  83. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.br +0 -0
  84. codex/static_root/assets/VSelectionControl-C8DP9rgp.ef80e8e3dfc1.js.gz +0 -0
  85. codex/static_root/assets/{VSelectionControl-CGu0oz0K.js → VSelectionControl-C8DP9rgp.js} +1 -1
  86. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.br +0 -0
  87. codex/static_root/assets/VSelectionControl-C8DP9rgp.js.gz +0 -0
  88. codex/static_root/assets/{VSlideGroup-D_oNvCOd.js → VSlideGroup-BRYhkLH8.3534ec94b2f1.js} +1 -1
  89. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.br +0 -0
  90. codex/static_root/assets/VSlideGroup-BRYhkLH8.3534ec94b2f1.js.gz +0 -0
  91. codex/static_root/assets/{VSlideGroup-D_oNvCOd.71a7101cdddd.js → VSlideGroup-BRYhkLH8.js} +1 -1
  92. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.br +0 -0
  93. codex/static_root/assets/VSlideGroup-BRYhkLH8.js.gz +0 -0
  94. codex/static_root/assets/{VTable-C7pKI7gU.087912f706f5.js → VTable-D1esKtlw.52165930e3b9.js} +1 -1
  95. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.br +0 -0
  96. codex/static_root/assets/VTable-D1esKtlw.52165930e3b9.js.gz +0 -0
  97. codex/static_root/assets/{VTable-C7pKI7gU.js → VTable-D1esKtlw.js} +1 -1
  98. codex/static_root/assets/VTable-D1esKtlw.js.br +0 -0
  99. codex/static_root/assets/VTable-D1esKtlw.js.gz +0 -0
  100. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js +1 -0
  101. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.br +0 -0
  102. codex/static_root/assets/VTextField-BnGscKcY.54032adf3a4b.js.gz +0 -0
  103. codex/static_root/assets/VTextField-BnGscKcY.js +1 -0
  104. codex/static_root/assets/VTextField-BnGscKcY.js.br +0 -0
  105. codex/static_root/assets/VTextField-BnGscKcY.js.gz +0 -0
  106. codex/static_root/assets/{VWindowItem-iyZ1XOkP.409f7d292253.js → VWindowItem-B0KokcAk.ea94224aecb9.js} +1 -1
  107. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.br +0 -0
  108. codex/static_root/assets/VWindowItem-B0KokcAk.ea94224aecb9.js.gz +0 -0
  109. codex/static_root/assets/{VWindowItem-iyZ1XOkP.js → VWindowItem-B0KokcAk.js} +1 -1
  110. codex/static_root/assets/VWindowItem-B0KokcAk.js.br +0 -0
  111. codex/static_root/assets/VWindowItem-B0KokcAk.js.gz +0 -0
  112. codex/static_root/assets/{admin-B0pCBjma.2407da79bd20.js → admin-Y0bL21AB.42d4f0d68163.js} +1 -1
  113. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.br +0 -0
  114. codex/static_root/assets/admin-Y0bL21AB.42d4f0d68163.js.gz +0 -0
  115. codex/static_root/assets/{admin-B0pCBjma.js → admin-Y0bL21AB.js} +1 -1
  116. codex/static_root/assets/admin-Y0bL21AB.js.br +0 -0
  117. codex/static_root/assets/admin-Y0bL21AB.js.gz +0 -0
  118. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js → admin-drawer-panel-DNngWUEM.4e992e3fe86e.js} +11 -11
  119. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.br +0 -0
  120. codex/static_root/assets/admin-drawer-panel-DNngWUEM.4e992e3fe86e.js.gz +0 -0
  121. codex/static_root/assets/{admin-drawer-panel-BxoNUHQr.js → admin-drawer-panel-DNngWUEM.js} +11 -11
  122. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.br +0 -0
  123. codex/static_root/assets/admin-drawer-panel-DNngWUEM.js.gz +0 -0
  124. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css +1 -0
  125. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.br +0 -0
  126. codex/static_root/assets/browser-CMAtBCLx.17a277ce43a9.css.gz +0 -0
  127. codex/static_root/assets/browser-CMAtBCLx.css +1 -0
  128. codex/static_root/assets/browser-CMAtBCLx.css.br +0 -0
  129. codex/static_root/assets/browser-CMAtBCLx.css.gz +0 -0
  130. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js +1 -0
  131. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.br +0 -0
  132. codex/static_root/assets/browser-DoJk8uvb.2c97a3858932.js.gz +0 -0
  133. codex/static_root/assets/browser-DoJk8uvb.js +1 -0
  134. codex/static_root/assets/browser-DoJk8uvb.js.br +0 -0
  135. codex/static_root/assets/browser-DoJk8uvb.js.gz +0 -0
  136. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.js → change-password-dialog-CH4xbjJa.b749aa6ea07e.js} +1 -1
  137. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.br +0 -0
  138. codex/static_root/assets/change-password-dialog-CH4xbjJa.b749aa6ea07e.js.gz +0 -0
  139. codex/static_root/assets/{change-password-dialog-DZ9dP8_F.930f4cae3cb0.js → change-password-dialog-CH4xbjJa.js} +1 -1
  140. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.br +0 -0
  141. codex/static_root/assets/change-password-dialog-CH4xbjJa.js.gz +0 -0
  142. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js → confirm-dialog-DkU0twE6.a20006aa5ba6.js} +1 -1
  143. codex/static_root/assets/{confirm-dialog-RP7JNStF.a752a7c1e697.js.br → confirm-dialog-DkU0twE6.a20006aa5ba6.js.br} +0 -0
  144. codex/static_root/assets/confirm-dialog-DkU0twE6.a20006aa5ba6.js.gz +0 -0
  145. codex/static_root/assets/{confirm-dialog-RP7JNStF.js → confirm-dialog-DkU0twE6.js} +1 -1
  146. codex/static_root/assets/{confirm-dialog-RP7JNStF.js.br → confirm-dialog-DkU0twE6.js.br} +0 -0
  147. codex/static_root/assets/confirm-dialog-DkU0twE6.js.gz +0 -0
  148. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js +1 -0
  149. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.br +0 -0
  150. codex/static_root/assets/datetime-column-BfW_j_3Q.6a614321a435.js.gz +0 -0
  151. codex/static_root/assets/datetime-column-BfW_j_3Q.js +1 -0
  152. codex/static_root/assets/datetime-column-BfW_j_3Q.js.br +0 -0
  153. codex/static_root/assets/datetime-column-BfW_j_3Q.js.gz +0 -0
  154. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +1 -0
  155. codex/static_root/assets/datetime-column-DeCthByU.css +1 -0
  156. codex/static_root/assets/{filter-DAdGUt-1.3261cbcd50b5.js → filter-DjxN78cK.35673f6e49b1.js} +1 -1
  157. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.br +0 -0
  158. codex/static_root/assets/filter-DjxN78cK.35673f6e49b1.js.gz +0 -0
  159. codex/static_root/assets/{filter-DAdGUt-1.js → filter-DjxN78cK.js} +1 -1
  160. codex/static_root/assets/filter-DjxN78cK.js.br +0 -0
  161. codex/static_root/assets/filter-DjxN78cK.js.gz +0 -0
  162. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js +1 -0
  163. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.br +0 -0
  164. codex/static_root/assets/flag-tab-D4ZNSbMR.e763a9937701.js.gz +0 -0
  165. codex/static_root/assets/flag-tab-D4ZNSbMR.js +1 -0
  166. codex/static_root/assets/flag-tab-D4ZNSbMR.js.br +0 -0
  167. codex/static_root/assets/flag-tab-D4ZNSbMR.js.gz +0 -0
  168. codex/static_root/assets/{group-tab-DEwh4jfB.a95097b5e320.js → group-tab-DucpNoBs.a6f37ae940c0.js} +1 -1
  169. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.br +0 -0
  170. codex/static_root/assets/group-tab-DucpNoBs.a6f37ae940c0.js.gz +0 -0
  171. codex/static_root/assets/{group-tab-DEwh4jfB.js → group-tab-DucpNoBs.js} +1 -1
  172. codex/static_root/assets/group-tab-DucpNoBs.js.br +0 -0
  173. codex/static_root/assets/group-tab-DucpNoBs.js.gz +0 -0
  174. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js +1 -0
  175. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.br +0 -0
  176. codex/static_root/assets/http-error-Cj0hTq-c.062a88ddd4dc.js.gz +0 -0
  177. codex/static_root/assets/http-error-Cj0hTq-c.js +1 -0
  178. codex/static_root/assets/http-error-Cj0hTq-c.js.br +0 -0
  179. codex/static_root/assets/http-error-Cj0hTq-c.js.gz +0 -0
  180. codex/static_root/assets/{library-tab-DrXvD9B2.js → library-tab-CzksdS3Q.566acdb45dec.js} +1 -1
  181. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.br +0 -0
  182. codex/static_root/assets/library-tab-CzksdS3Q.566acdb45dec.js.gz +0 -0
  183. codex/static_root/assets/{library-tab-DrXvD9B2.f765f9d3bae4.js → library-tab-CzksdS3Q.js} +1 -1
  184. codex/static_root/assets/library-tab-CzksdS3Q.js.br +0 -0
  185. codex/static_root/assets/library-tab-CzksdS3Q.js.gz +0 -0
  186. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js +32 -0
  187. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.br +0 -0
  188. codex/static_root/assets/main-DX-k2724.f73d08e9ad64.js.gz +0 -0
  189. codex/static_root/assets/main-DX-k2724.js +32 -0
  190. codex/static_root/assets/main-DX-k2724.js.br +0 -0
  191. codex/static_root/assets/main-DX-k2724.js.gz +0 -0
  192. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js +1 -0
  193. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.br +0 -0
  194. codex/static_root/assets/pagination-toolbar-CQcR0a3T.7a2d33ed8c39.js.gz +0 -0
  195. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js +1 -0
  196. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.br +0 -0
  197. codex/static_root/assets/pagination-toolbar-CQcR0a3T.js.gz +0 -0
  198. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css +1 -0
  199. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.br +0 -0
  200. codex/static_root/assets/pagination-toolbar-DWdp_kmz.css.gz +0 -0
  201. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css +1 -0
  202. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.br +0 -0
  203. codex/static_root/assets/pagination-toolbar-DWdp_kmz.f98f47e87612.css.gz +0 -0
  204. codex/static_root/assets/{pdf-doc-obrBwxa6.ec9bd13a5786.js → pdf-doc-GkMk4o5L.ef24c859a7a4.js} +1 -1
  205. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.br +0 -0
  206. codex/static_root/assets/pdf-doc-GkMk4o5L.ef24c859a7a4.js.gz +0 -0
  207. codex/static_root/assets/{pdf-doc-obrBwxa6.js → pdf-doc-GkMk4o5L.js} +1 -1
  208. codex/static_root/assets/pdf-doc-GkMk4o5L.js.br +0 -0
  209. codex/static_root/assets/pdf-doc-GkMk4o5L.js.gz +0 -0
  210. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css +1 -0
  211. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.br +0 -0
  212. codex/static_root/assets/reader-CCCfpM3x.1553e45bd775.css.gz +0 -0
  213. codex/static_root/assets/reader-CCCfpM3x.css +1 -0
  214. codex/static_root/assets/reader-CCCfpM3x.css.br +0 -0
  215. codex/static_root/assets/reader-CCCfpM3x.css.gz +0 -0
  216. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js +2 -0
  217. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.br +0 -0
  218. codex/static_root/assets/reader-Cl6mmQB_.7b9e7591954f.js.gz +0 -0
  219. codex/static_root/assets/reader-Cl6mmQB_.js +2 -0
  220. codex/static_root/assets/reader-Cl6mmQB_.js.br +0 -0
  221. codex/static_root/assets/reader-Cl6mmQB_.js.gz +0 -0
  222. codex/static_root/assets/{relation-chips-CZJcLjc8.1d7426dab654.js → relation-chips-CZ8VvR4y.d4232f3c2518.js} +1 -1
  223. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.br +0 -0
  224. codex/static_root/assets/relation-chips-CZ8VvR4y.d4232f3c2518.js.gz +0 -0
  225. codex/static_root/assets/{relation-chips-CZJcLjc8.js → relation-chips-CZ8VvR4y.js} +1 -1
  226. codex/static_root/assets/relation-chips-CZ8VvR4y.js.br +0 -0
  227. codex/static_root/assets/relation-chips-CZ8VvR4y.js.gz +0 -0
  228. codex/static_root/assets/{settings-drawer-7iTnAdjf.337eecf2fdbb.js → settings-drawer-DyS0A2pN.17a98928589f.js} +2 -2
  229. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.br +0 -0
  230. codex/static_root/assets/settings-drawer-DyS0A2pN.17a98928589f.js.gz +0 -0
  231. codex/static_root/assets/{settings-drawer-7iTnAdjf.js → settings-drawer-DyS0A2pN.js} +2 -2
  232. codex/static_root/assets/settings-drawer-DyS0A2pN.js.br +0 -0
  233. codex/static_root/assets/settings-drawer-DyS0A2pN.js.gz +0 -0
  234. codex/static_root/assets/{stats-tab-CQsK1n8O.544bf50ffb22.js → stats-tab-R3Ekobfm.70fa09a67518.js} +1 -1
  235. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.br +0 -0
  236. codex/static_root/assets/stats-tab-R3Ekobfm.70fa09a67518.js.gz +0 -0
  237. codex/static_root/assets/{stats-tab-CQsK1n8O.js → stats-tab-R3Ekobfm.js} +1 -1
  238. codex/static_root/assets/stats-tab-R3Ekobfm.js.br +0 -0
  239. codex/static_root/assets/stats-tab-R3Ekobfm.js.gz +0 -0
  240. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js +1 -0
  241. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.br +0 -0
  242. codex/static_root/assets/task-tab-BhLDK_7Q.28ff51b3ed01.js.gz +0 -0
  243. codex/static_root/assets/task-tab-BhLDK_7Q.js +1 -0
  244. codex/static_root/assets/task-tab-BhLDK_7Q.js.br +0 -0
  245. codex/static_root/assets/task-tab-BhLDK_7Q.js.gz +0 -0
  246. codex/static_root/assets/to-case-BR2frjFx.87de5d7a0503.js +1 -0
  247. codex/static_root/assets/to-case-BR2frjFx.js +1 -0
  248. codex/static_root/assets/{unauthorized-Bs7NSqea.c55650fac055.js → unauthorized-BbXjAe5l.d48912d34a68.js} +1 -1
  249. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.br +0 -0
  250. codex/static_root/assets/unauthorized-BbXjAe5l.d48912d34a68.js.gz +0 -0
  251. codex/static_root/assets/{unauthorized-Bs7NSqea.js → unauthorized-BbXjAe5l.js} +1 -1
  252. codex/static_root/assets/unauthorized-BbXjAe5l.js.br +0 -0
  253. codex/static_root/assets/unauthorized-BbXjAe5l.js.gz +0 -0
  254. codex/static_root/assets/{user-tab-ChuH7Atm.7e80cf1fb78e.js → user-tab-DxITIl2d.d79d7f062930.js} +1 -1
  255. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.br +0 -0
  256. codex/static_root/assets/user-tab-DxITIl2d.d79d7f062930.js.gz +0 -0
  257. codex/static_root/assets/{user-tab-ChuH7Atm.js → user-tab-DxITIl2d.js} +1 -1
  258. codex/static_root/assets/user-tab-DxITIl2d.js.br +0 -0
  259. codex/static_root/assets/user-tab-DxITIl2d.js.gz +0 -0
  260. codex/static_root/js/choices-admin.d790df01c20a.json +1 -0
  261. codex/static_root/js/choices-admin.d790df01c20a.json.br +0 -0
  262. codex/static_root/js/choices-admin.d790df01c20a.json.gz +0 -0
  263. codex/static_root/js/choices-admin.json +1 -1
  264. codex/static_root/js/choices-admin.json.br +0 -0
  265. codex/static_root/js/choices-admin.json.gz +0 -0
  266. codex/static_root/js/{choices.079a01e0be1a.json → choices.a0aaaa6c7ef7.json} +1 -1
  267. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  268. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  269. codex/static_root/js/choices.json +1 -1
  270. codex/static_root/js/choices.json.br +0 -0
  271. codex/static_root/js/choices.json.gz +0 -0
  272. codex/static_root/{manifest.dad79c8475ae.json → manifest.16d52597ef50.json} +239 -239
  273. codex/static_root/manifest.16d52597ef50.json.br +0 -0
  274. codex/static_root/manifest.16d52597ef50.json.gz +0 -0
  275. codex/static_root/manifest.json +239 -239
  276. codex/static_root/manifest.json.br +0 -0
  277. codex/static_root/manifest.json.gz +0 -0
  278. codex/static_root/staticfiles.json +1 -1
  279. codex/urls/api/browser.py +11 -2
  280. codex/urls/api/reader.py +1 -7
  281. codex/urls/opds/binary.py +1 -1
  282. codex/views/admin/auth.py +0 -2
  283. codex/views/admin/tasks.py +13 -1
  284. codex/views/auth.py +2 -4
  285. codex/views/browser/annotations.py +27 -148
  286. codex/views/browser/base.py +1 -18
  287. codex/views/browser/breadcrumbs.py +16 -14
  288. codex/views/browser/browser.py +5 -10
  289. codex/views/browser/choices.py +15 -39
  290. codex/views/browser/cover.py +177 -0
  291. codex/views/browser/filters/annotations.py +23 -22
  292. codex/views/browser/filters/field.py +1 -2
  293. codex/views/browser/filters/group.py +13 -8
  294. codex/views/browser/filters/search.py +19 -10
  295. codex/views/browser/metadata.py +20 -25
  296. codex/views/const.py +12 -0
  297. codex/views/mtime.py +14 -12
  298. codex/views/opds/binary.py +1 -1
  299. codex/views/opds/urls.py +2 -1
  300. codex/views/opds/util.py +2 -4
  301. codex/views/opds/v1/entry/links.py +18 -33
  302. codex/views/opds/v1/facets.py +2 -1
  303. codex/views/opds/v1/feed.py +42 -23
  304. codex/views/opds/v1/links.py +2 -1
  305. codex/views/opds/v2/feed.py +11 -9
  306. codex/views/opds/v2/links.py +2 -1
  307. codex/views/opds/v2/publications.py +13 -34
  308. codex/views/public.py +0 -2
  309. codex/views/reader/books.py +1 -1
  310. codex/views/reader/reader.py +2 -2
  311. codex/views/session.py +5 -3
  312. codex/views/util.py +32 -0
  313. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/METADATA +8 -7
  314. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/RECORD +317 -318
  315. codex/librarian/importer/update_comics.py +0 -85
  316. codex/migrations/0027_sort_name.py +0 -139
  317. codex/migrations/0028_custom_covers.py +0 -166
  318. codex/migrations/0029_choices_adminflag_and_timestamp.py +0 -44
  319. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  320. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  321. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  322. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  323. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  324. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  325. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  326. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  327. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  328. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  329. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  330. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  331. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  332. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  333. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  334. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  335. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  336. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  337. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  338. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  339. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  340. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  341. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  342. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  343. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +0 -1
  344. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  345. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  346. codex/static_root/assets/VSelect-DgfIKRnC.js +0 -1
  347. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  348. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  349. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +0 -1
  350. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
  351. codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
  352. codex/static_root/assets/VSelect-MGVSeLgr.css +0 -1
  353. codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
  354. codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
  355. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  356. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  357. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  358. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  359. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  360. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  361. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  362. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  363. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  364. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  365. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  366. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  367. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js +0 -1
  368. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  369. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  370. codex/static_root/assets/VTextField-Bs8oq9mk.js +0 -1
  371. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  372. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  373. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  374. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  375. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  376. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  377. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  378. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  379. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  380. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  381. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  382. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  383. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  384. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  385. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +0 -1
  386. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  387. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  388. codex/static_root/assets/browser-DbyAjCNh.js +0 -1
  389. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  390. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  391. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +0 -1
  392. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  393. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  394. codex/static_root/assets/browser-VnzNj1t3.css +0 -1
  395. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  396. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  397. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  398. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  399. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  400. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  401. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  402. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  403. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js +0 -1
  404. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  405. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  406. codex/static_root/assets/datetime-column-Cu3WYyPj.js +0 -1
  407. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  408. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  409. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +0 -1
  410. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  411. codex/static_root/assets/datetime-column-Dg_RxTVM.css +0 -1
  412. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  413. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  414. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  415. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  416. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  417. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +0 -1
  418. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  419. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  420. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +0 -1
  421. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  422. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  423. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  424. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  425. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  426. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  427. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +0 -1
  428. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  429. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  430. codex/static_root/assets/http-error-xs3mIL8U.js +0 -1
  431. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  432. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  433. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  434. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  435. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  436. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  437. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +0 -32
  438. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  439. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  440. codex/static_root/assets/main-Dy9Uqpmh.js +0 -32
  441. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  442. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  443. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +0 -1
  444. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  445. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  446. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +0 -1
  447. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  448. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  449. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css +0 -1
  450. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  451. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  452. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css +0 -1
  453. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  454. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  455. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  456. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  457. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  458. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  459. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +0 -2
  460. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  461. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  462. codex/static_root/assets/reader-BXKUaUeC.js +0 -2
  463. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  464. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  465. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css +0 -1
  466. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  467. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  468. codex/static_root/assets/reader-OXJ1KVaW.css +0 -1
  469. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  470. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  471. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  472. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  473. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  474. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  475. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  476. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  477. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  478. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  479. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  480. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  481. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  482. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  483. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +0 -1
  484. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  485. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  486. codex/static_root/assets/task-tab-C60TLP6e.js +0 -1
  487. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  488. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  489. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +0 -1
  490. codex/static_root/assets/to-case-CR9beRR0.js +0 -1
  491. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  492. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  493. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  494. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  495. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  496. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  497. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  498. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  499. codex/static_root/js/choices-admin.ef1ff3a8b9da.json +0 -1
  500. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
  501. codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
  502. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  503. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  504. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  505. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  506. codex/views/cover.py +0 -76
  507. codex/views/utils.py +0 -26
  508. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/LICENSE +0 -0
  509. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/WHEEL +0 -0
  510. {codex-1.6.0a7.dist-info → codex-1.6.0a8.dist-info}/entry_points.txt +0 -0
codex/views/mtime.py CHANGED
@@ -9,7 +9,7 @@ from codex.util import max_none
9
9
  from codex.views.auth import AuthGenericAPIView
10
10
  from codex.views.browser.filters.bookmark import BookmarkFilterMixin
11
11
  from codex.views.const import GROUP_MODEL_MAP
12
- from codex.views.utils import reparse_json_query_params
12
+ from codex.views.util import reparse_json_query_params
13
13
 
14
14
  _REPARSE_JSON_FIELDS = frozenset({"groups"})
15
15
 
@@ -20,23 +20,27 @@ class MtimeView(AuthGenericAPIView, BookmarkFilterMixin):
20
20
  serializer_class = GroupsMtimeSerializer
21
21
  response_serializer_class = MtimeSerializer
22
22
 
23
- def _get_group_mtime(self, group, pks, use_bookmark_filter):
23
+ def _parse_params(self):
24
+ params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
25
+ self.groups = params.get("groups", "")
26
+ self.use_bookmark_filter = self.request.GET.get("use_bookmark_filter", False)
27
+
28
+ def _get_group_mtime(self, group, pks):
24
29
  """Get one group's mtimes."""
25
30
  model = GROUP_MODEL_MAP[group]
26
31
  if not model:
27
32
  model = Publisher
28
33
 
29
- qs = model.objects
34
+ qs = model.objects.all()
30
35
 
31
36
  if pks and 0 not in pks:
32
37
  qs = qs.filter(pk__in=pks)
33
38
  updated_at_max = qs.aggregate(max=Max("updated_at"))["max"]
34
39
 
35
- if use_bookmark_filter:
40
+ if self.use_bookmark_filter:
36
41
  bm_rel = self.get_bm_rel(model)
37
42
  bm_filter = self.get_my_bookmark_filter(bm_rel)
38
43
  qs = qs.filter(bm_filter)
39
- # TODO can these be combined into one max?
40
44
  bookmark_updated_at_max = qs.aggregate(max=(Max(f"{bm_rel}__updated_at")))[
41
45
  "max"
42
46
  ]
@@ -44,25 +48,23 @@ class MtimeView(AuthGenericAPIView, BookmarkFilterMixin):
44
48
 
45
49
  return updated_at_max
46
50
 
47
- def get_max_groups_mtime(self, groups, use_bookmark_filter):
51
+ def get_max_groups_mtime(self):
48
52
  """Get max mtime for all groups."""
49
53
  max_mtime = None
50
54
 
51
- for item in groups:
55
+ for item in self.groups:
52
56
  group = item["group"]
53
57
  pks = tuple(int(pk) for pk in item["pks"].split(","))
54
- mtime = self._get_group_mtime(group, pks, use_bookmark_filter)
58
+ mtime = self._get_group_mtime(group, pks)
55
59
  max_mtime = max_none(max_mtime, mtime)
56
60
  return max_mtime
57
61
 
58
62
  def get(self, *args, **kwargs):
59
63
  """Get the mtimes for the submitted groups."""
60
64
  # Parse Request
61
- params = reparse_json_query_params(self.request.GET, _REPARSE_JSON_FIELDS)
62
- groups = params.get("groups", "")
63
- use_bookmark_filter = self.request.GET.get("use_bookmark_filter", False)
65
+ self._parse_params()
64
66
 
65
- max_mtime = self.get_max_groups_mtime(groups, use_bookmark_filter)
67
+ max_mtime = self.get_max_groups_mtime()
66
68
 
67
69
  # Serialize Response
68
70
  result = {"max_mtime": max_mtime}
@@ -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
 
@@ -75,7 +75,7 @@ class ReaderBooksView(BookmarkBaseView, SessionView, SharedAnnotationsMixin):
75
75
 
76
76
  def _get_comics_list(self):
77
77
  """Get the reader naviation group filter."""
78
- select_related = ("series", "volume")
78
+ select_related = ("series",)
79
79
  prefetch_related = ()
80
80
 
81
81
  arc: Mapping = self.params.get("arc", {}) # type: ignore
@@ -15,6 +15,7 @@ from codex.logger.logging import get_logger
15
15
  from codex.serializers.reader import ReaderArcSerializer, ReaderComicsSerializer
16
16
  from codex.serializers.redirect import ReaderRedirectSerializer
17
17
  from codex.views.reader.arcs import ReaderArcsView
18
+ from codex.views.util import pop_name
18
19
 
19
20
  LOG = get_logger(__name__)
20
21
  _VALID_ARC_GROUPS = frozenset({"f", "s", "a"})
@@ -140,8 +141,7 @@ class ReaderView(
140
141
  arc["pks"] = (current.arc_pk,) # type: ignore
141
142
  arc["index"] = current.arc_index # type:ignore
142
143
  arc["count"] = current.arc_count # type: ignore
143
- close_route = self.last_route
144
- close_route.pop("name", None)
144
+ close_route = pop_name(self.last_route)
145
145
 
146
146
  return {
147
147
  "books": books,
codex/views/session.py CHANGED
@@ -7,6 +7,7 @@ from types import MappingProxyType
7
7
  from codex.logger.logging import get_logger
8
8
  from codex.serializers.choices import DEFAULTS
9
9
  from codex.views.auth import AuthFilterGenericAPIView
10
+ from codex.views.util import pop_name
10
11
 
11
12
  LOG = get_logger(__name__)
12
13
 
@@ -62,8 +63,9 @@ class SessionView(AuthFilterGenericAPIView, ABC):
62
63
  "q": DEFAULTS["q"],
63
64
  "search_results_limit": DEFAULTS["searchResultsLimit"],
64
65
  "show": DEFAULTS["show"],
65
- "cover_style": DEFAULTS["coverStyle"],
66
- "twenty_four_hour_time": False,
66
+ "dynamic_covers": DEFAULTS["dynamicCovers"],
67
+ "custom_covers": DEFAULTS["customCovers"],
68
+ "twenty_four_hour_time": DEFAULTS["twentyFourHourTime"],
67
69
  "top_group": DEFAULTS["topGroup"],
68
70
  },
69
71
  READER_SESSION_KEY: {
@@ -96,7 +98,7 @@ class SessionView(AuthFilterGenericAPIView, ABC):
96
98
  breadcrumbs = DEFAULTS["breadcrumbs"]
97
99
  last_route = breadcrumbs[-1]
98
100
  if not name:
99
- last_route.pop("name", None)
101
+ last_route = pop_name(last_route)
100
102
 
101
103
  return last_route
102
104
 
codex/views/util.py CHANGED
@@ -1,6 +1,14 @@
1
1
  """Utility classes by many views."""
2
2
 
3
+ import json
4
+ from collections.abc import Mapping
5
+ from contextlib import suppress
3
6
  from dataclasses import asdict, dataclass
7
+ from json.decoder import JSONDecodeError
8
+ from urllib.parse import unquote_plus
9
+
10
+ from djangorestframework_camel_case.settings import api_settings
11
+ from djangorestframework_camel_case.util import underscoreize
4
12
 
5
13
 
6
14
  @dataclass()
@@ -29,3 +37,27 @@ class Route(dict):
29
37
  )
30
38
 
31
39
  dict = asdict
40
+
41
+
42
+ def reparse_json_query_params(query_params, keys) -> dict:
43
+ """Reparse JSON encoded query_params."""
44
+ # It is an unbelievable amount of trouble to try to parse axios native bracket
45
+ # encoded complex objects in python
46
+ parsed_dict = {}
47
+ for key, value in query_params.items():
48
+ if key in keys:
49
+ parsed_value = unquote_plus(value)
50
+ with suppress(JSONDecodeError):
51
+ parsed_value = json.loads(parsed_value)
52
+ else:
53
+ parsed_value = value
54
+
55
+ parsed_dict[key] = parsed_value
56
+ return dict(underscoreize(parsed_dict, **api_settings.JSON_UNDERSCOREIZE)) # type:ignore
57
+
58
+
59
+ def pop_name(kwargs: Mapping):
60
+ """Pop name from a mapping route."""
61
+ kwargs = dict(kwargs)
62
+ kwargs.pop("name", None)
63
+ return kwargs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codex
3
- Version: 1.6.0a7
3
+ Version: 1.6.0a8
4
4
  Summary: A comic archive web server.
5
5
  Home-page: https://github.com/ajslater/codex
6
6
  License: GPL-3.0-only
@@ -29,7 +29,7 @@ Requires-Dist: ansicolors (>=1.1,<2.0)
29
29
  Requires-Dist: bidict (>=0.23.1,<0.24.0)
30
30
  Requires-Dist: case-converter (>=1.1.0,<2.0.0)
31
31
  Requires-Dist: channels (>=4.0.0,<5.0.0)
32
- Requires-Dist: comicbox[pdf] (>=1.1.7,<2.0.0)
32
+ Requires-Dist: comicbox[pdf] (>=1.1.8,<2.0.0)
33
33
  Requires-Dist: django (>=5.0.6,<6.0.0)
34
34
  Requires-Dist: django-cachalot (>=2.6.2,<3.0.0)
35
35
  Requires-Dist: django-cors-headers (>=4.0,<5.0)
@@ -348,12 +348,13 @@ index, a Django cache and comic book cover thumbnails.
348
348
 
349
349
  Codex contains some experimental throttling controls. The value supplied to
350
350
  these variables will be interpreted as the maximum number of allowed requests
351
- per minute.
351
+ per minute. For example, the following settings would limit each described group
352
+ to 2 queries per second.
352
353
 
353
- - `CODEX_THROTTLE_ANON=60` Anonymous users
354
- - `CODEX_THROTTLE_USER=60` Authenticated users
355
- - `CODEX_THROTTLE_OPDS=60` The OPDS v1 & v2 APIs
356
- - `CODEX_THROTTLE_OPENSEARCH=60` The OPDS v1 Opensearch API
354
+ - `CODEX_THROTTLE_ANON=30` Anonymous users
355
+ - `CODEX_THROTTLE_USER=30` Authenticated users
356
+ - `CODEX_THROTTLE_OPDS=30` The OPDS v1 & v2 APIs (Panels uses this for search)
357
+ - `CODEX_THROTTLE_OPENSEARCH=30` The OPDS v1 Opensearch API
357
358
 
358
359
  ### Reverse Proxy
359
360