codex 1.6.0__py3-none-any.whl → 1.6.0a1__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 (768) hide show
  1. codex/exceptions.py +18 -22
  2. codex/integrity.py +3 -66
  3. codex/librarian/covers/coverd.py +2 -2
  4. codex/librarian/covers/create.py +29 -50
  5. codex/librarian/covers/path.py +6 -8
  6. codex/librarian/covers/purge.py +18 -28
  7. codex/librarian/covers/tasks.py +2 -3
  8. codex/librarian/importer/{aggregate.py → aggregate_metadata.py} +88 -58
  9. codex/librarian/importer/{extract.py → clean_metadata.py} +4 -33
  10. codex/librarian/importer/const.py +13 -46
  11. codex/librarian/importer/create_comics.py +17 -101
  12. codex/librarian/importer/create_fks.py +134 -99
  13. codex/librarian/importer/deleted.py +28 -103
  14. codex/librarian/importer/failed_imports.py +64 -42
  15. codex/librarian/importer/importerd.py +290 -71
  16. codex/librarian/importer/link_comics.py +26 -26
  17. codex/librarian/importer/moved.py +81 -128
  18. codex/librarian/importer/query_fks.py +122 -142
  19. codex/librarian/importer/status.py +39 -8
  20. codex/librarian/importer/tasks.py +9 -26
  21. codex/librarian/importer/update_comics.py +89 -0
  22. codex/librarian/janitor/cleanup.py +2 -26
  23. codex/librarian/janitor/failed_imports.py +1 -1
  24. codex/librarian/janitor/janitor.py +1 -13
  25. codex/librarian/janitor/janitord.py +1 -1
  26. codex/librarian/janitor/status.py +0 -1
  27. codex/librarian/janitor/tasks.py +0 -5
  28. codex/librarian/librariand.py +5 -9
  29. codex/librarian/notifier/notifierd.py +1 -1
  30. codex/librarian/notifier/tasks.py +4 -4
  31. codex/librarian/search/update.py +1 -1
  32. codex/librarian/watchdog/db_snapshot.py +6 -9
  33. codex/librarian/watchdog/emitter.py +0 -3
  34. codex/librarian/watchdog/event_batcherd.py +3 -19
  35. codex/librarian/watchdog/events.py +36 -176
  36. codex/librarian/watchdog/observers.py +3 -13
  37. codex/middleware.py +2 -1
  38. codex/migrations/0027_sort_name.py +149 -0
  39. codex/models/admin.py +2 -0
  40. codex/models/base.py +0 -3
  41. codex/models/comic.py +9 -13
  42. codex/models/{util.py → const.py} +2 -15
  43. codex/models/groups.py +14 -28
  44. codex/models/library.py +3 -11
  45. codex/models/paths.py +14 -61
  46. codex/permissions.py +1 -1
  47. codex/search/query.py +2 -25
  48. codex/search/writing.py +1 -1
  49. codex/serializers/admin.py +44 -53
  50. codex/serializers/browser/choices.py +42 -37
  51. codex/serializers/browser/filters.py +131 -32
  52. codex/serializers/browser/metadata.py +1 -45
  53. codex/serializers/browser/mixins.py +4 -37
  54. codex/serializers/browser/page.py +6 -3
  55. codex/serializers/browser/settings.py +11 -42
  56. codex/serializers/choices.py +1 -14
  57. codex/serializers/models/pycountry.py +51 -11
  58. codex/serializers/opds/v2.py +16 -10
  59. codex/serializers/opds/v2_unused.py +3 -5
  60. codex/serializers/reader.py +7 -21
  61. codex/serializers/redirect.py +2 -3
  62. codex/serializers/route.py +7 -16
  63. codex/settings/settings.py +8 -23
  64. codex/startup.py +3 -77
  65. codex/static_root/assets/VCheckbox-u_aWQJA7.a0db8a7bd681.js +1 -0
  66. codex/static_root/assets/VCheckbox-u_aWQJA7.a0db8a7bd681.js.br +0 -0
  67. codex/static_root/assets/VCheckbox-u_aWQJA7.a0db8a7bd681.js.gz +0 -0
  68. codex/static_root/assets/VCheckbox-u_aWQJA7.js +1 -0
  69. codex/static_root/assets/VCheckbox-u_aWQJA7.js.br +0 -0
  70. codex/static_root/assets/VCheckbox-u_aWQJA7.js.gz +0 -0
  71. codex/static_root/assets/{VCheckboxBtn-DxNOKONG.5ab34c4c47aa.js → VCheckboxBtn-yCN3TB6A.29d48d14ab3b.js} +1 -1
  72. codex/static_root/assets/VCheckboxBtn-yCN3TB6A.29d48d14ab3b.js.br +0 -0
  73. codex/static_root/assets/VCheckboxBtn-yCN3TB6A.29d48d14ab3b.js.gz +0 -0
  74. codex/static_root/assets/{VCheckboxBtn-DxNOKONG.js → VCheckboxBtn-yCN3TB6A.js} +1 -1
  75. codex/static_root/assets/VCheckboxBtn-yCN3TB6A.js.br +0 -0
  76. codex/static_root/assets/VCheckboxBtn-yCN3TB6A.js.gz +0 -0
  77. codex/static_root/assets/VCombobox-dsIKbjps.d3c8da1d75b3.js +1 -0
  78. codex/static_root/assets/VCombobox-dsIKbjps.d3c8da1d75b3.js.br +0 -0
  79. codex/static_root/assets/VCombobox-dsIKbjps.d3c8da1d75b3.js.gz +0 -0
  80. codex/static_root/assets/VCombobox-dsIKbjps.js +1 -0
  81. codex/static_root/assets/VCombobox-dsIKbjps.js.br +0 -0
  82. codex/static_root/assets/VCombobox-dsIKbjps.js.gz +0 -0
  83. codex/static_root/assets/VDialog-BNHg8txZ.21bed7754d3a.js +1 -0
  84. codex/static_root/assets/VDialog-BNHg8txZ.21bed7754d3a.js.br +0 -0
  85. codex/static_root/assets/VDialog-BNHg8txZ.21bed7754d3a.js.gz +0 -0
  86. codex/static_root/assets/VDialog-BNHg8txZ.js +1 -0
  87. codex/static_root/assets/VDialog-BNHg8txZ.js.br +0 -0
  88. codex/static_root/assets/VDialog-BNHg8txZ.js.gz +0 -0
  89. codex/static_root/assets/{VDialog-lQ13FnRR.1c801ad756b2.css → VDialog-C5Nxd_zP.css} +1 -1
  90. codex/static_root/assets/VDialog-C5Nxd_zP.css.br +0 -0
  91. codex/static_root/assets/VDialog-C5Nxd_zP.css.gz +0 -0
  92. codex/static_root/assets/{VDialog-lQ13FnRR.css → VDialog-C5Nxd_zP.d327f505ef0e.css} +1 -1
  93. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.br +0 -0
  94. codex/static_root/assets/VDialog-C5Nxd_zP.d327f505ef0e.css.gz +0 -0
  95. codex/static_root/assets/VExpansionPanels-CX4W4vZK.8e90fe0b34f4.js +1 -0
  96. codex/static_root/assets/VExpansionPanels-CX4W4vZK.8e90fe0b34f4.js.br +0 -0
  97. codex/static_root/assets/VExpansionPanels-CX4W4vZK.8e90fe0b34f4.js.gz +0 -0
  98. codex/static_root/assets/VExpansionPanels-CX4W4vZK.js +1 -0
  99. codex/static_root/assets/VExpansionPanels-CX4W4vZK.js.br +0 -0
  100. codex/static_root/assets/VExpansionPanels-CX4W4vZK.js.gz +0 -0
  101. codex/static_root/assets/VRadioGroup-jCr3CAsk.685daccd04d8.js +1 -0
  102. codex/static_root/assets/VRadioGroup-jCr3CAsk.685daccd04d8.js.br +0 -0
  103. codex/static_root/assets/VRadioGroup-jCr3CAsk.685daccd04d8.js.gz +0 -0
  104. codex/static_root/assets/VRadioGroup-jCr3CAsk.js +1 -0
  105. codex/static_root/assets/VRadioGroup-jCr3CAsk.js.br +0 -0
  106. codex/static_root/assets/VRadioGroup-jCr3CAsk.js.gz +0 -0
  107. codex/static_root/assets/VSelect--XHNpxvB.ff050ef0d6de.js +1 -0
  108. codex/static_root/assets/VSelect--XHNpxvB.ff050ef0d6de.js.br +0 -0
  109. codex/static_root/assets/VSelect--XHNpxvB.ff050ef0d6de.js.gz +0 -0
  110. codex/static_root/assets/VSelect--XHNpxvB.js +1 -0
  111. codex/static_root/assets/VSelect--XHNpxvB.js.br +0 -0
  112. codex/static_root/assets/VSelect--XHNpxvB.js.gz +0 -0
  113. codex/static_root/assets/VSelectionControl-CVIKc_ra.0b2cc426e8b8.js +1 -0
  114. codex/static_root/assets/VSelectionControl-CVIKc_ra.0b2cc426e8b8.js.br +0 -0
  115. codex/static_root/assets/VSelectionControl-CVIKc_ra.0b2cc426e8b8.js.gz +0 -0
  116. codex/static_root/assets/VSelectionControl-CVIKc_ra.js +1 -0
  117. codex/static_root/assets/VSelectionControl-CVIKc_ra.js.br +0 -0
  118. codex/static_root/assets/VSelectionControl-CVIKc_ra.js.gz +0 -0
  119. codex/static_root/assets/VSlideGroup-CZw5_eNv.7ae5182e5988.js +1 -0
  120. codex/static_root/assets/VSlideGroup-CZw5_eNv.7ae5182e5988.js.br +0 -0
  121. codex/static_root/assets/VSlideGroup-CZw5_eNv.7ae5182e5988.js.gz +0 -0
  122. codex/static_root/assets/VSlideGroup-CZw5_eNv.js +1 -0
  123. codex/static_root/assets/VSlideGroup-CZw5_eNv.js.br +0 -0
  124. codex/static_root/assets/VSlideGroup-CZw5_eNv.js.gz +0 -0
  125. codex/static_root/assets/{VTable-2RHFqfg6.f0f2106b158b.js → VTable-hGJDFK5t.d2f84d68747b.js} +1 -1
  126. codex/static_root/assets/VTable-hGJDFK5t.d2f84d68747b.js.br +0 -0
  127. codex/static_root/assets/VTable-hGJDFK5t.d2f84d68747b.js.gz +0 -0
  128. codex/static_root/assets/{VTable-2RHFqfg6.js → VTable-hGJDFK5t.js} +1 -1
  129. codex/static_root/assets/VTable-hGJDFK5t.js.br +0 -0
  130. codex/static_root/assets/VTable-hGJDFK5t.js.gz +0 -0
  131. codex/static_root/assets/VTextField-C8y3lyz9.eb58405614ad.js +1 -0
  132. codex/static_root/assets/VTextField-C8y3lyz9.eb58405614ad.js.br +0 -0
  133. codex/static_root/assets/VTextField-C8y3lyz9.eb58405614ad.js.gz +0 -0
  134. codex/static_root/assets/VTextField-C8y3lyz9.js +1 -0
  135. codex/static_root/assets/VTextField-C8y3lyz9.js.br +0 -0
  136. codex/static_root/assets/VTextField-C8y3lyz9.js.gz +0 -0
  137. codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css +1 -0
  138. codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css.br +0 -0
  139. codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css.gz +0 -0
  140. codex/static_root/assets/VTextField-DuIIp-VY.css +1 -0
  141. codex/static_root/assets/VTextField-DuIIp-VY.css.br +0 -0
  142. codex/static_root/assets/VTextField-DuIIp-VY.css.gz +0 -0
  143. codex/static_root/assets/VWindowItem-BHpZIa_r.2b3c5f9594e6.js +1 -0
  144. codex/static_root/assets/VWindowItem-BHpZIa_r.2b3c5f9594e6.js.br +0 -0
  145. codex/static_root/assets/VWindowItem-BHpZIa_r.2b3c5f9594e6.js.gz +0 -0
  146. codex/static_root/assets/VWindowItem-BHpZIa_r.js +1 -0
  147. codex/static_root/assets/VWindowItem-BHpZIa_r.js.br +0 -0
  148. codex/static_root/assets/VWindowItem-BHpZIa_r.js.gz +0 -0
  149. codex/static_root/assets/{admin-B8Bp2Uih.82c98714bcf1.css → admin-BbrOU9Y9.29936748af9e.css} +1 -1
  150. codex/static_root/assets/admin-BbrOU9Y9.29936748af9e.css.br +0 -0
  151. codex/static_root/assets/admin-BbrOU9Y9.29936748af9e.css.gz +0 -0
  152. codex/static_root/assets/{admin-B8Bp2Uih.css → admin-BbrOU9Y9.css} +1 -1
  153. codex/static_root/assets/admin-BbrOU9Y9.css.br +0 -0
  154. codex/static_root/assets/admin-BbrOU9Y9.css.gz +0 -0
  155. codex/static_root/assets/admin-Bvd-SvGS.412e5b1b7ed1.js +1 -0
  156. codex/static_root/assets/admin-Bvd-SvGS.412e5b1b7ed1.js.br +0 -0
  157. codex/static_root/assets/admin-Bvd-SvGS.412e5b1b7ed1.js.gz +0 -0
  158. codex/static_root/assets/admin-Bvd-SvGS.js +1 -0
  159. codex/static_root/assets/admin-Bvd-SvGS.js.br +0 -0
  160. codex/static_root/assets/admin-Bvd-SvGS.js.gz +0 -0
  161. codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css +1 -0
  162. codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css.br +0 -0
  163. codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css.gz +0 -0
  164. codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css +1 -0
  165. codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css.br +0 -0
  166. codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css.gz +0 -0
  167. codex/static_root/assets/admin-drawer-panel-D2OV5rDa.059e3bde9568.js +30 -0
  168. codex/static_root/assets/admin-drawer-panel-D2OV5rDa.059e3bde9568.js.br +0 -0
  169. codex/static_root/assets/admin-drawer-panel-D2OV5rDa.059e3bde9568.js.gz +0 -0
  170. codex/static_root/assets/admin-drawer-panel-D2OV5rDa.js +30 -0
  171. codex/static_root/assets/admin-drawer-panel-D2OV5rDa.js.br +0 -0
  172. codex/static_root/assets/admin-drawer-panel-D2OV5rDa.js.gz +0 -0
  173. codex/static_root/assets/browser-CZeVqRNy.ccfca9a05bfe.css +1 -0
  174. codex/static_root/assets/browser-CZeVqRNy.ccfca9a05bfe.css.br +0 -0
  175. codex/static_root/assets/browser-CZeVqRNy.ccfca9a05bfe.css.gz +0 -0
  176. codex/static_root/assets/browser-CZeVqRNy.css +1 -0
  177. codex/static_root/assets/browser-CZeVqRNy.css.br +0 -0
  178. codex/static_root/assets/browser-CZeVqRNy.css.gz +0 -0
  179. codex/static_root/assets/browser-rc5ObkeN.0c1f38d229ac.js +1 -0
  180. codex/static_root/assets/browser-rc5ObkeN.0c1f38d229ac.js.br +0 -0
  181. codex/static_root/assets/browser-rc5ObkeN.0c1f38d229ac.js.gz +0 -0
  182. codex/static_root/assets/browser-rc5ObkeN.js +1 -0
  183. codex/static_root/assets/browser-rc5ObkeN.js.br +0 -0
  184. codex/static_root/assets/browser-rc5ObkeN.js.gz +0 -0
  185. codex/static_root/assets/change-password-dialog-CRHk2tjq.d4d4b5945cd4.js +1 -0
  186. codex/static_root/assets/change-password-dialog-CRHk2tjq.d4d4b5945cd4.js.br +0 -0
  187. codex/static_root/assets/change-password-dialog-CRHk2tjq.d4d4b5945cd4.js.gz +0 -0
  188. codex/static_root/assets/change-password-dialog-CRHk2tjq.js +1 -0
  189. codex/static_root/assets/change-password-dialog-CRHk2tjq.js.br +0 -0
  190. codex/static_root/assets/change-password-dialog-CRHk2tjq.js.gz +0 -0
  191. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css +1 -0
  192. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.br +0 -0
  193. codex/static_root/assets/change-password-dialog-HvIvmmIE.73be0c8a8278.css.gz +0 -0
  194. codex/static_root/assets/change-password-dialog-HvIvmmIE.css +1 -0
  195. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.br +0 -0
  196. codex/static_root/assets/change-password-dialog-HvIvmmIE.css.gz +0 -0
  197. codex/static_root/assets/confirm-dialog-DnHKM44R.d57a0196ac5a.js +1 -0
  198. codex/static_root/assets/confirm-dialog-DnHKM44R.d57a0196ac5a.js.br +0 -0
  199. codex/static_root/assets/confirm-dialog-DnHKM44R.d57a0196ac5a.js.gz +0 -0
  200. codex/static_root/assets/confirm-dialog-DnHKM44R.js +1 -0
  201. codex/static_root/assets/confirm-dialog-DnHKM44R.js.br +0 -0
  202. codex/static_root/assets/confirm-dialog-DnHKM44R.js.gz +0 -0
  203. codex/static_root/assets/datetime-column-BEqDvdeI.44b65455a2c3.js +1 -0
  204. codex/static_root/assets/datetime-column-BEqDvdeI.44b65455a2c3.js.br +0 -0
  205. codex/static_root/assets/datetime-column-BEqDvdeI.44b65455a2c3.js.gz +0 -0
  206. codex/static_root/assets/datetime-column-BEqDvdeI.js +1 -0
  207. codex/static_root/assets/datetime-column-BEqDvdeI.js.br +0 -0
  208. codex/static_root/assets/datetime-column-BEqDvdeI.js.gz +0 -0
  209. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css +1 -0
  210. codex/static_root/assets/datetime-column-Dg_RxTVM.1fa7c5cbead7.css.br +0 -0
  211. codex/static_root/assets/datetime-column-Dg_RxTVM.css +1 -0
  212. codex/static_root/assets/datetime-column-Dg_RxTVM.css.br +0 -0
  213. codex/static_root/assets/filter-_wYL6jQC.dbc7d7e7b855.js +1 -0
  214. codex/static_root/assets/filter-_wYL6jQC.dbc7d7e7b855.js.br +0 -0
  215. codex/static_root/assets/filter-_wYL6jQC.dbc7d7e7b855.js.gz +0 -0
  216. codex/static_root/assets/filter-_wYL6jQC.js +1 -0
  217. codex/static_root/assets/filter-_wYL6jQC.js.br +0 -0
  218. codex/static_root/assets/filter-_wYL6jQC.js.gz +0 -0
  219. codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css +1 -0
  220. codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css.br +0 -0
  221. codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css.gz +0 -0
  222. codex/static_root/assets/flag-tab-BacZLlVi.css +1 -0
  223. codex/static_root/assets/flag-tab-BacZLlVi.css.br +0 -0
  224. codex/static_root/assets/flag-tab-BacZLlVi.css.gz +0 -0
  225. codex/static_root/assets/flag-tab-Dpyvhwb_.2812bd46db2d.js +1 -0
  226. codex/static_root/assets/flag-tab-Dpyvhwb_.2812bd46db2d.js.br +0 -0
  227. codex/static_root/assets/flag-tab-Dpyvhwb_.2812bd46db2d.js.gz +0 -0
  228. codex/static_root/assets/flag-tab-Dpyvhwb_.js +1 -0
  229. codex/static_root/assets/flag-tab-Dpyvhwb_.js.br +0 -0
  230. codex/static_root/assets/flag-tab-Dpyvhwb_.js.gz +0 -0
  231. codex/static_root/assets/{group-tab-D5vWrihT.1afb2ce453ff.js → group-tab-DAjvuSUO.4494d4846fa6.js} +1 -1
  232. codex/static_root/assets/group-tab-DAjvuSUO.4494d4846fa6.js.br +0 -0
  233. codex/static_root/assets/group-tab-DAjvuSUO.4494d4846fa6.js.gz +0 -0
  234. codex/static_root/assets/{group-tab-D5vWrihT.js → group-tab-DAjvuSUO.js} +1 -1
  235. codex/static_root/assets/group-tab-DAjvuSUO.js.br +0 -0
  236. codex/static_root/assets/group-tab-DAjvuSUO.js.gz +0 -0
  237. codex/static_root/assets/http-error-CDQ-296a.6ea61686beb8.js +1 -0
  238. codex/static_root/assets/http-error-CDQ-296a.6ea61686beb8.js.br +0 -0
  239. codex/static_root/assets/http-error-CDQ-296a.6ea61686beb8.js.gz +0 -0
  240. codex/static_root/assets/http-error-CDQ-296a.js +1 -0
  241. codex/static_root/assets/http-error-CDQ-296a.js.br +0 -0
  242. codex/static_root/assets/http-error-CDQ-296a.js.gz +0 -0
  243. codex/static_root/assets/http-error-_wlDqEcp.5b78d3e332c9.css +1 -0
  244. codex/static_root/assets/{http-error-DKulXuTl.95e42988d5d5.css.br → http-error-_wlDqEcp.5b78d3e332c9.css.br} +0 -0
  245. codex/static_root/assets/http-error-_wlDqEcp.5b78d3e332c9.css.gz +0 -0
  246. codex/static_root/assets/http-error-_wlDqEcp.css +1 -0
  247. codex/static_root/assets/{http-error-DKulXuTl.css.br → http-error-_wlDqEcp.css.br} +0 -0
  248. codex/static_root/assets/http-error-_wlDqEcp.css.gz +0 -0
  249. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +1 -0
  250. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  251. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  252. codex/static_root/assets/index-DUjeNg4x.js +1 -0
  253. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  254. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  255. codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css +1 -0
  256. codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css.br +2 -0
  257. codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css.gz +0 -0
  258. codex/static_root/assets/library-tab-CXbkxNZt.css +1 -0
  259. codex/static_root/assets/library-tab-CXbkxNZt.css.br +2 -0
  260. codex/static_root/assets/library-tab-CXbkxNZt.css.gz +0 -0
  261. codex/static_root/assets/library-tab-LsexSRi2.34b88bbbfcdc.js +1 -0
  262. codex/static_root/assets/library-tab-LsexSRi2.34b88bbbfcdc.js.br +0 -0
  263. codex/static_root/assets/library-tab-LsexSRi2.34b88bbbfcdc.js.gz +0 -0
  264. codex/static_root/assets/library-tab-LsexSRi2.js +1 -0
  265. codex/static_root/assets/library-tab-LsexSRi2.js.br +0 -0
  266. codex/static_root/assets/library-tab-LsexSRi2.js.gz +0 -0
  267. codex/static_root/assets/main-CjPnwTfF.0f333ec81a42.js +32 -0
  268. codex/static_root/assets/main-CjPnwTfF.0f333ec81a42.js.br +0 -0
  269. codex/static_root/assets/main-CjPnwTfF.0f333ec81a42.js.gz +0 -0
  270. codex/static_root/assets/main-CjPnwTfF.js +32 -0
  271. codex/static_root/assets/main-CjPnwTfF.js.br +0 -0
  272. codex/static_root/assets/main-CjPnwTfF.js.gz +0 -0
  273. codex/static_root/assets/{main-Ztp855PW.d46d718fac45.css → main-sK7HC5h4.6f04ddfc5f1a.css} +1 -1
  274. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  275. codex/static_root/assets/{main-Ztp855PW.d46d718fac45.css.gz → main-sK7HC5h4.6f04ddfc5f1a.css.gz} +0 -0
  276. codex/static_root/assets/{main-Ztp855PW.css → main-sK7HC5h4.css} +1 -1
  277. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  278. codex/static_root/assets/{main-Ztp855PW.css.gz → main-sK7HC5h4.css.gz} +0 -0
  279. codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css +1 -0
  280. codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css.br +0 -0
  281. codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css.gz +0 -0
  282. codex/static_root/assets/pagination-toolbar-B3wS4_7p.css +1 -0
  283. codex/static_root/assets/pagination-toolbar-B3wS4_7p.css.br +0 -0
  284. codex/static_root/assets/pagination-toolbar-B3wS4_7p.css.gz +0 -0
  285. codex/static_root/assets/pagination-toolbar-B955aa-d.3fd9249f7342.js +1 -0
  286. codex/static_root/assets/pagination-toolbar-B955aa-d.3fd9249f7342.js.br +0 -0
  287. codex/static_root/assets/pagination-toolbar-B955aa-d.3fd9249f7342.js.gz +0 -0
  288. codex/static_root/assets/pagination-toolbar-B955aa-d.js +1 -0
  289. codex/static_root/assets/pagination-toolbar-B955aa-d.js.br +0 -0
  290. codex/static_root/assets/pagination-toolbar-B955aa-d.js.gz +0 -0
  291. codex/static_root/assets/pdf-doc-DPDmmEEp.74cab27a76e1.js +35 -0
  292. codex/static_root/assets/pdf-doc-DPDmmEEp.74cab27a76e1.js.br +0 -0
  293. codex/static_root/assets/pdf-doc-DPDmmEEp.74cab27a76e1.js.gz +0 -0
  294. codex/static_root/assets/pdf-doc-DPDmmEEp.js +35 -0
  295. codex/static_root/assets/pdf-doc-DPDmmEEp.js.br +0 -0
  296. codex/static_root/assets/pdf-doc-DPDmmEEp.js.gz +0 -0
  297. codex/static_root/assets/reader-BuMh4x3f.f4d7f1c97387.js +2 -0
  298. codex/static_root/assets/reader-BuMh4x3f.f4d7f1c97387.js.br +0 -0
  299. codex/static_root/assets/reader-BuMh4x3f.f4d7f1c97387.js.gz +0 -0
  300. codex/static_root/assets/reader-BuMh4x3f.js +2 -0
  301. codex/static_root/assets/reader-BuMh4x3f.js.br +0 -0
  302. codex/static_root/assets/reader-BuMh4x3f.js.gz +0 -0
  303. codex/static_root/assets/reader-Dspy46e8.925a6b05bfcf.css +1 -0
  304. codex/static_root/assets/reader-Dspy46e8.925a6b05bfcf.css.br +0 -0
  305. codex/static_root/assets/reader-Dspy46e8.925a6b05bfcf.css.gz +0 -0
  306. codex/static_root/assets/reader-Dspy46e8.css +1 -0
  307. codex/static_root/assets/reader-Dspy46e8.css.br +0 -0
  308. codex/static_root/assets/reader-Dspy46e8.css.gz +0 -0
  309. codex/static_root/assets/{relation-chips-Bsc-gUxE.98e65384c3c9.css → relation-chips-D6T6FX7G.css} +1 -1
  310. codex/static_root/assets/relation-chips-D6T6FX7G.css.br +0 -0
  311. codex/static_root/assets/relation-chips-D6T6FX7G.css.gz +0 -0
  312. codex/static_root/assets/{relation-chips-Bsc-gUxE.css → relation-chips-D6T6FX7G.db095b2497e4.css} +1 -1
  313. codex/static_root/assets/relation-chips-D6T6FX7G.db095b2497e4.css.br +0 -0
  314. codex/static_root/assets/relation-chips-D6T6FX7G.db095b2497e4.css.gz +0 -0
  315. codex/static_root/assets/relation-chips-DN_pPmnS.684c4eae4bdb.js +1 -0
  316. codex/static_root/assets/relation-chips-DN_pPmnS.684c4eae4bdb.js.br +0 -0
  317. codex/static_root/assets/relation-chips-DN_pPmnS.684c4eae4bdb.js.gz +0 -0
  318. codex/static_root/assets/relation-chips-DN_pPmnS.js +1 -0
  319. codex/static_root/assets/relation-chips-DN_pPmnS.js.br +0 -0
  320. codex/static_root/assets/relation-chips-DN_pPmnS.js.gz +0 -0
  321. codex/static_root/assets/settings-drawer-BgUazs7O.7bbeb1fd98f1.css +1 -0
  322. codex/static_root/assets/settings-drawer-BgUazs7O.7bbeb1fd98f1.css.br +0 -0
  323. codex/static_root/assets/settings-drawer-BgUazs7O.7bbeb1fd98f1.css.gz +0 -0
  324. codex/static_root/assets/settings-drawer-BgUazs7O.css +1 -0
  325. codex/static_root/assets/settings-drawer-BgUazs7O.css.br +0 -0
  326. codex/static_root/assets/settings-drawer-BgUazs7O.css.gz +0 -0
  327. codex/static_root/assets/settings-drawer-D4FgDG4z.de28fd4ce82a.js +2 -0
  328. codex/static_root/assets/settings-drawer-D4FgDG4z.de28fd4ce82a.js.br +0 -0
  329. codex/static_root/assets/settings-drawer-D4FgDG4z.de28fd4ce82a.js.gz +0 -0
  330. codex/static_root/assets/settings-drawer-D4FgDG4z.js +2 -0
  331. codex/static_root/assets/settings-drawer-D4FgDG4z.js.br +0 -0
  332. codex/static_root/assets/settings-drawer-D4FgDG4z.js.gz +0 -0
  333. codex/static_root/assets/stats-tab-AwbCeW63.a95f7d9b2059.js +1 -0
  334. codex/static_root/assets/stats-tab-AwbCeW63.a95f7d9b2059.js.br +0 -0
  335. codex/static_root/assets/stats-tab-AwbCeW63.a95f7d9b2059.js.gz +0 -0
  336. codex/static_root/assets/stats-tab-AwbCeW63.js +1 -0
  337. codex/static_root/assets/stats-tab-AwbCeW63.js.br +0 -0
  338. codex/static_root/assets/stats-tab-AwbCeW63.js.gz +0 -0
  339. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css +1 -0
  340. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.br +2 -0
  341. codex/static_root/assets/stats-tab-TsyG3wH_.b93c080356fc.css.gz +0 -0
  342. codex/static_root/assets/stats-tab-TsyG3wH_.css +1 -0
  343. codex/static_root/assets/stats-tab-TsyG3wH_.css.br +2 -0
  344. codex/static_root/assets/stats-tab-TsyG3wH_.css.gz +0 -0
  345. codex/static_root/assets/task-tab-DeHQTHuz.5f967defca9f.js +1 -0
  346. codex/static_root/assets/task-tab-DeHQTHuz.5f967defca9f.js.br +0 -0
  347. codex/static_root/assets/task-tab-DeHQTHuz.5f967defca9f.js.gz +0 -0
  348. codex/static_root/assets/task-tab-DeHQTHuz.js +1 -0
  349. codex/static_root/assets/task-tab-DeHQTHuz.js.br +0 -0
  350. codex/static_root/assets/task-tab-DeHQTHuz.js.gz +0 -0
  351. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +1 -0
  352. codex/static_root/assets/to-case-kUm8NPpW.js +1 -0
  353. codex/static_root/assets/unauthorized-BXCbvRsL.bc44d7ba68b3.js +1 -0
  354. codex/static_root/assets/unauthorized-BXCbvRsL.bc44d7ba68b3.js.br +0 -0
  355. codex/static_root/assets/unauthorized-BXCbvRsL.bc44d7ba68b3.js.gz +0 -0
  356. codex/static_root/assets/unauthorized-BXCbvRsL.js +1 -0
  357. codex/static_root/assets/unauthorized-BXCbvRsL.js.br +0 -0
  358. codex/static_root/assets/unauthorized-BXCbvRsL.js.gz +0 -0
  359. codex/static_root/assets/{unauthorized-CibJYD8o.4c2de98c2b53.css → unauthorized-DWgytUow.ca2f13dbdbf5.css} +1 -1
  360. codex/static_root/assets/unauthorized-DWgytUow.ca2f13dbdbf5.css.br +0 -0
  361. codex/static_root/assets/unauthorized-DWgytUow.ca2f13dbdbf5.css.gz +0 -0
  362. codex/static_root/assets/{unauthorized-CibJYD8o.css → unauthorized-DWgytUow.css} +1 -1
  363. codex/static_root/assets/unauthorized-DWgytUow.css.br +0 -0
  364. codex/static_root/assets/unauthorized-DWgytUow.css.gz +0 -0
  365. codex/static_root/assets/user-tab-Dq4qYzgp.5d1b6c07edd0.js +1 -0
  366. codex/static_root/assets/user-tab-Dq4qYzgp.5d1b6c07edd0.js.br +0 -0
  367. codex/static_root/assets/user-tab-Dq4qYzgp.5d1b6c07edd0.js.gz +0 -0
  368. codex/static_root/assets/user-tab-Dq4qYzgp.js +1 -0
  369. codex/static_root/assets/user-tab-Dq4qYzgp.js.br +0 -0
  370. codex/static_root/assets/user-tab-Dq4qYzgp.js.gz +0 -0
  371. codex/static_root/js/choices-admin.248384ece3f6.json +1 -0
  372. codex/static_root/js/choices-admin.248384ece3f6.json.br +0 -0
  373. codex/static_root/js/choices-admin.248384ece3f6.json.gz +0 -0
  374. codex/static_root/js/choices-admin.json +1 -1
  375. codex/static_root/js/choices-admin.json.br +0 -0
  376. codex/static_root/js/choices-admin.json.gz +0 -0
  377. codex/static_root/js/choices.b60817ba0a93.json +1 -0
  378. codex/static_root/js/choices.b60817ba0a93.json.br +0 -0
  379. codex/static_root/js/choices.b60817ba0a93.json.gz +0 -0
  380. codex/static_root/js/choices.json +1 -1
  381. codex/static_root/js/choices.json.br +0 -0
  382. codex/static_root/js/choices.json.gz +0 -0
  383. codex/static_root/manifest.b5fe5d7ef44e.json +641 -0
  384. codex/static_root/manifest.b5fe5d7ef44e.json.br +0 -0
  385. codex/static_root/manifest.b5fe5d7ef44e.json.gz +0 -0
  386. codex/static_root/manifest.json +298 -279
  387. codex/static_root/manifest.json.br +0 -0
  388. codex/static_root/manifest.json.gz +0 -0
  389. codex/static_root/staticfiles.json +1 -1
  390. codex/status_controller.py +2 -11
  391. codex/urls/api/auth.py +1 -2
  392. codex/urls/api/browser.py +4 -13
  393. codex/urls/api/reader.py +9 -3
  394. codex/urls/api/v3.py +0 -2
  395. codex/urls/opds/binary.py +1 -1
  396. codex/views/admin/api_key.py +8 -7
  397. codex/views/admin/flag.py +7 -2
  398. codex/views/admin/group.py +6 -2
  399. codex/views/admin/library.py +37 -44
  400. codex/views/admin/stats.py +83 -20
  401. codex/views/admin/tasks.py +12 -26
  402. codex/views/admin/user.py +9 -3
  403. codex/views/auth.py +54 -31
  404. codex/views/bookmark.py +72 -92
  405. codex/views/browser/base.py +90 -42
  406. codex/views/browser/browser.py +371 -92
  407. codex/views/browser/{annotations.py → browser_annotations.py} +121 -67
  408. codex/views/browser/browser_breadcrumbs.py +175 -0
  409. codex/views/browser/browser_order_by.py +63 -0
  410. codex/views/browser/choices.py +85 -38
  411. codex/views/browser/const.py +3 -0
  412. codex/views/browser/filters/annotations.py +19 -87
  413. codex/views/browser/filters/bookmark.py +12 -24
  414. codex/views/browser/filters/field.py +6 -8
  415. codex/views/browser/filters/group.py +14 -35
  416. codex/views/browser/filters/search.py +20 -31
  417. codex/views/browser/metadata.py +73 -112
  418. codex/views/browser/session.py +12 -0
  419. codex/views/const.py +0 -140
  420. codex/views/cover.py +77 -0
  421. codex/views/download.py +8 -5
  422. codex/views/frontend.py +2 -4
  423. codex/views/opds/authentication_v1.py +45 -46
  424. codex/views/opds/binary.py +10 -5
  425. codex/views/opds/const.py +0 -13
  426. codex/views/opds/urls.py +4 -6
  427. codex/views/opds/util.py +12 -4
  428. codex/views/opds/v1/entry/links.py +33 -18
  429. codex/views/opds/v1/facets.py +7 -7
  430. codex/views/opds/v1/feed.py +45 -55
  431. codex/views/opds/v1/links.py +5 -7
  432. codex/views/opds/v1/opensearch_v1.py +6 -2
  433. codex/views/opds/v2/feed.py +26 -25
  434. codex/views/opds/v2/links.py +2 -4
  435. codex/views/opds/v2/publications.py +54 -34
  436. codex/views/opds/v2/top_links.py +2 -2
  437. codex/views/reader/page.py +8 -15
  438. codex/views/reader/reader.py +322 -19
  439. codex/views/reader/{settings.py → session.py} +2 -4
  440. codex/views/session.py +102 -83
  441. codex/views/template.py +3 -3
  442. codex/views/version.py +2 -2
  443. codex/websockets/consumers.py +10 -14
  444. {codex-1.6.0.dist-info → codex-1.6.0a1.dist-info}/METADATA +15 -22
  445. {codex-1.6.0.dist-info → codex-1.6.0a1.dist-info}/RECORD +448 -466
  446. codex/librarian/importer/cache.py +0 -93
  447. codex/librarian/importer/create_covers.py +0 -96
  448. codex/librarian/importer/importer.py +0 -96
  449. codex/librarian/importer/init.py +0 -265
  450. codex/librarian/importer/link_covers.py +0 -73
  451. codex/librarian/importer/query_covers.py +0 -52
  452. codex/migrations/0027_import_order_and_covers.py +0 -280
  453. codex/serializers/browser/mtime.py +0 -21
  454. codex/serializers/fields.py +0 -149
  455. codex/serializers/settings.py +0 -12
  456. codex/static_root/assets/VCheckbox-DanrzdHo.8ec5e2094cbf.js +0 -1
  457. codex/static_root/assets/VCheckbox-DanrzdHo.8ec5e2094cbf.js.br +0 -0
  458. codex/static_root/assets/VCheckbox-DanrzdHo.8ec5e2094cbf.js.gz +0 -0
  459. codex/static_root/assets/VCheckbox-DanrzdHo.js +0 -1
  460. codex/static_root/assets/VCheckbox-DanrzdHo.js.br +0 -0
  461. codex/static_root/assets/VCheckbox-DanrzdHo.js.gz +0 -0
  462. codex/static_root/assets/VCheckboxBtn-DxNOKONG.5ab34c4c47aa.js.br +0 -0
  463. codex/static_root/assets/VCheckboxBtn-DxNOKONG.5ab34c4c47aa.js.gz +0 -0
  464. codex/static_root/assets/VCheckboxBtn-DxNOKONG.js.br +0 -0
  465. codex/static_root/assets/VCheckboxBtn-DxNOKONG.js.gz +0 -0
  466. codex/static_root/assets/VCombobox-DDvdoPTG.11d080e8edd7.js +0 -1
  467. codex/static_root/assets/VCombobox-DDvdoPTG.11d080e8edd7.js.br +0 -0
  468. codex/static_root/assets/VCombobox-DDvdoPTG.11d080e8edd7.js.gz +0 -0
  469. codex/static_root/assets/VCombobox-DDvdoPTG.js +0 -1
  470. codex/static_root/assets/VCombobox-DDvdoPTG.js.br +0 -0
  471. codex/static_root/assets/VCombobox-DDvdoPTG.js.gz +0 -0
  472. codex/static_root/assets/VDialog-Bd5dN_E1.457666d7fce1.js +0 -1
  473. codex/static_root/assets/VDialog-Bd5dN_E1.457666d7fce1.js.br +0 -0
  474. codex/static_root/assets/VDialog-Bd5dN_E1.457666d7fce1.js.gz +0 -0
  475. codex/static_root/assets/VDialog-Bd5dN_E1.js +0 -1
  476. codex/static_root/assets/VDialog-Bd5dN_E1.js.br +0 -0
  477. codex/static_root/assets/VDialog-Bd5dN_E1.js.gz +0 -0
  478. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  479. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  480. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  481. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  482. codex/static_root/assets/VExpansionPanels-DqTuYpTR.12673cf9f391.js +0 -1
  483. codex/static_root/assets/VExpansionPanels-DqTuYpTR.12673cf9f391.js.br +0 -0
  484. codex/static_root/assets/VExpansionPanels-DqTuYpTR.12673cf9f391.js.gz +0 -0
  485. codex/static_root/assets/VExpansionPanels-DqTuYpTR.js +0 -1
  486. codex/static_root/assets/VExpansionPanels-DqTuYpTR.js.br +0 -0
  487. codex/static_root/assets/VExpansionPanels-DqTuYpTR.js.gz +0 -0
  488. codex/static_root/assets/VRadioGroup-DKb8XPr-.3cf6bf5bf3bc.js +0 -1
  489. codex/static_root/assets/VRadioGroup-DKb8XPr-.3cf6bf5bf3bc.js.br +0 -0
  490. codex/static_root/assets/VRadioGroup-DKb8XPr-.3cf6bf5bf3bc.js.gz +0 -0
  491. codex/static_root/assets/VRadioGroup-DKb8XPr-.js +0 -1
  492. codex/static_root/assets/VRadioGroup-DKb8XPr-.js.br +0 -0
  493. codex/static_root/assets/VRadioGroup-DKb8XPr-.js.gz +0 -0
  494. codex/static_root/assets/VSelect-C_yPbyCG.08bd6b53a7ce.js +0 -1
  495. codex/static_root/assets/VSelect-C_yPbyCG.08bd6b53a7ce.js.br +0 -0
  496. codex/static_root/assets/VSelect-C_yPbyCG.08bd6b53a7ce.js.gz +0 -0
  497. codex/static_root/assets/VSelect-C_yPbyCG.js +0 -1
  498. codex/static_root/assets/VSelect-C_yPbyCG.js.br +0 -0
  499. codex/static_root/assets/VSelect-C_yPbyCG.js.gz +0 -0
  500. codex/static_root/assets/VSelectionControl-CmFPvGMS.802c9b3e6e28.js +0 -1
  501. codex/static_root/assets/VSelectionControl-CmFPvGMS.802c9b3e6e28.js.br +0 -0
  502. codex/static_root/assets/VSelectionControl-CmFPvGMS.802c9b3e6e28.js.gz +0 -0
  503. codex/static_root/assets/VSelectionControl-CmFPvGMS.js +0 -1
  504. codex/static_root/assets/VSelectionControl-CmFPvGMS.js.br +0 -0
  505. codex/static_root/assets/VSelectionControl-CmFPvGMS.js.gz +0 -0
  506. codex/static_root/assets/VSlideGroup-D5FZWsM1.a47491fe3e6d.js +0 -1
  507. codex/static_root/assets/VSlideGroup-D5FZWsM1.a47491fe3e6d.js.br +0 -0
  508. codex/static_root/assets/VSlideGroup-D5FZWsM1.a47491fe3e6d.js.gz +0 -0
  509. codex/static_root/assets/VSlideGroup-D5FZWsM1.js +0 -1
  510. codex/static_root/assets/VSlideGroup-D5FZWsM1.js.br +0 -0
  511. codex/static_root/assets/VSlideGroup-D5FZWsM1.js.gz +0 -0
  512. codex/static_root/assets/VTable-2RHFqfg6.f0f2106b158b.js.br +0 -0
  513. codex/static_root/assets/VTable-2RHFqfg6.f0f2106b158b.js.gz +0 -0
  514. codex/static_root/assets/VTable-2RHFqfg6.js.br +0 -0
  515. codex/static_root/assets/VTable-2RHFqfg6.js.gz +0 -0
  516. codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css +0 -1
  517. codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css.br +0 -0
  518. codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css.gz +0 -0
  519. codex/static_root/assets/VTextField-BssVoDhB.css +0 -1
  520. codex/static_root/assets/VTextField-BssVoDhB.css.br +0 -0
  521. codex/static_root/assets/VTextField-BssVoDhB.css.gz +0 -0
  522. codex/static_root/assets/VTextField-o1N9ylIb.7fffaeff8d40.js +0 -1
  523. codex/static_root/assets/VTextField-o1N9ylIb.7fffaeff8d40.js.br +0 -0
  524. codex/static_root/assets/VTextField-o1N9ylIb.7fffaeff8d40.js.gz +0 -0
  525. codex/static_root/assets/VTextField-o1N9ylIb.js +0 -1
  526. codex/static_root/assets/VTextField-o1N9ylIb.js.br +0 -0
  527. codex/static_root/assets/VTextField-o1N9ylIb.js.gz +0 -0
  528. codex/static_root/assets/VWindowItem-C_zvuz9C.663fa48efea4.js +0 -1
  529. codex/static_root/assets/VWindowItem-C_zvuz9C.663fa48efea4.js.br +0 -0
  530. codex/static_root/assets/VWindowItem-C_zvuz9C.663fa48efea4.js.gz +0 -0
  531. codex/static_root/assets/VWindowItem-C_zvuz9C.js +0 -1
  532. codex/static_root/assets/VWindowItem-C_zvuz9C.js.br +0 -0
  533. codex/static_root/assets/VWindowItem-C_zvuz9C.js.gz +0 -0
  534. codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.br +0 -0
  535. codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.gz +0 -0
  536. codex/static_root/assets/admin-B8Bp2Uih.css.br +0 -0
  537. codex/static_root/assets/admin-B8Bp2Uih.css.gz +0 -0
  538. codex/static_root/assets/admin-DHyHEWZ3.7a8015456c0f.js +0 -1
  539. codex/static_root/assets/admin-DHyHEWZ3.7a8015456c0f.js.br +0 -0
  540. codex/static_root/assets/admin-DHyHEWZ3.7a8015456c0f.js.gz +0 -0
  541. codex/static_root/assets/admin-DHyHEWZ3.js +0 -1
  542. codex/static_root/assets/admin-DHyHEWZ3.js.br +0 -0
  543. codex/static_root/assets/admin-DHyHEWZ3.js.gz +0 -0
  544. codex/static_root/assets/admin-drawer-panel-BXpIDQXM.19507a95375b.js +0 -30
  545. codex/static_root/assets/admin-drawer-panel-BXpIDQXM.19507a95375b.js.br +0 -0
  546. codex/static_root/assets/admin-drawer-panel-BXpIDQXM.19507a95375b.js.gz +0 -0
  547. codex/static_root/assets/admin-drawer-panel-BXpIDQXM.js +0 -30
  548. codex/static_root/assets/admin-drawer-panel-BXpIDQXM.js.br +0 -0
  549. codex/static_root/assets/admin-drawer-panel-BXpIDQXM.js.gz +0 -0
  550. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css +0 -1
  551. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.br +0 -0
  552. codex/static_root/assets/admin-drawer-panel-mne3FIj6.561b3f75fe65.css.gz +0 -0
  553. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css +0 -1
  554. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.br +0 -0
  555. codex/static_root/assets/admin-drawer-panel-mne3FIj6.css.gz +0 -0
  556. codex/static_root/assets/browser-D0Vjmmp_.a1bfaf48d65f.js +0 -1
  557. codex/static_root/assets/browser-D0Vjmmp_.a1bfaf48d65f.js.br +0 -0
  558. codex/static_root/assets/browser-D0Vjmmp_.a1bfaf48d65f.js.gz +0 -0
  559. codex/static_root/assets/browser-D0Vjmmp_.js +0 -1
  560. codex/static_root/assets/browser-D0Vjmmp_.js.br +0 -0
  561. codex/static_root/assets/browser-D0Vjmmp_.js.gz +0 -0
  562. codex/static_root/assets/browser-D7QQDneO.155cb612ed0c.css +0 -1
  563. codex/static_root/assets/browser-D7QQDneO.155cb612ed0c.css.br +0 -0
  564. codex/static_root/assets/browser-D7QQDneO.155cb612ed0c.css.gz +0 -0
  565. codex/static_root/assets/browser-D7QQDneO.css +0 -1
  566. codex/static_root/assets/browser-D7QQDneO.css.br +0 -0
  567. codex/static_root/assets/browser-D7QQDneO.css.gz +0 -0
  568. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +0 -1
  569. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  570. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  571. codex/static_root/assets/change-password-dialog-0u6IJA87.css +0 -1
  572. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  573. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  574. codex/static_root/assets/change-password-dialog-BBsJLPXZ.e642f7e2b96f.js +0 -1
  575. codex/static_root/assets/change-password-dialog-BBsJLPXZ.e642f7e2b96f.js.br +0 -0
  576. codex/static_root/assets/change-password-dialog-BBsJLPXZ.e642f7e2b96f.js.gz +0 -0
  577. codex/static_root/assets/change-password-dialog-BBsJLPXZ.js +0 -1
  578. codex/static_root/assets/change-password-dialog-BBsJLPXZ.js.br +0 -0
  579. codex/static_root/assets/change-password-dialog-BBsJLPXZ.js.gz +0 -0
  580. codex/static_root/assets/confirm-dialog-DCKvg3ai.2a3277b3c147.js +0 -1
  581. codex/static_root/assets/confirm-dialog-DCKvg3ai.2a3277b3c147.js.br +0 -0
  582. codex/static_root/assets/confirm-dialog-DCKvg3ai.2a3277b3c147.js.gz +0 -0
  583. codex/static_root/assets/confirm-dialog-DCKvg3ai.js +0 -1
  584. codex/static_root/assets/confirm-dialog-DCKvg3ai.js.br +0 -0
  585. codex/static_root/assets/confirm-dialog-DCKvg3ai.js.gz +0 -0
  586. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +0 -1
  587. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  588. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +0 -1
  589. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  590. codex/static_root/assets/datetime-column-DeCthByU.1f3bf499e063.css +0 -1
  591. codex/static_root/assets/datetime-column-DeCthByU.css +0 -1
  592. codex/static_root/assets/datetime-column-jHKA_uNm.039c7da2036f.js +0 -1
  593. codex/static_root/assets/datetime-column-jHKA_uNm.039c7da2036f.js.br +0 -0
  594. codex/static_root/assets/datetime-column-jHKA_uNm.039c7da2036f.js.gz +0 -0
  595. codex/static_root/assets/datetime-column-jHKA_uNm.js +0 -1
  596. codex/static_root/assets/datetime-column-jHKA_uNm.js.br +0 -0
  597. codex/static_root/assets/datetime-column-jHKA_uNm.js.gz +0 -0
  598. codex/static_root/assets/filter-DgPg2ts9.af0fcf0888c6.js +0 -1
  599. codex/static_root/assets/filter-DgPg2ts9.af0fcf0888c6.js.br +0 -0
  600. codex/static_root/assets/filter-DgPg2ts9.af0fcf0888c6.js.gz +0 -0
  601. codex/static_root/assets/filter-DgPg2ts9.js +0 -1
  602. codex/static_root/assets/filter-DgPg2ts9.js.br +0 -0
  603. codex/static_root/assets/filter-DgPg2ts9.js.gz +0 -0
  604. codex/static_root/assets/flag-tab-9qxOALxP.f89bc9891671.js +0 -1
  605. codex/static_root/assets/flag-tab-9qxOALxP.f89bc9891671.js.br +0 -0
  606. codex/static_root/assets/flag-tab-9qxOALxP.f89bc9891671.js.gz +0 -0
  607. codex/static_root/assets/flag-tab-9qxOALxP.js +0 -1
  608. codex/static_root/assets/flag-tab-9qxOALxP.js.br +0 -0
  609. codex/static_root/assets/flag-tab-9qxOALxP.js.gz +0 -0
  610. codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css +0 -1
  611. codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.br +0 -0
  612. codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.gz +0 -0
  613. codex/static_root/assets/flag-tab-BNwbLfrN.css +0 -1
  614. codex/static_root/assets/flag-tab-BNwbLfrN.css.br +0 -0
  615. codex/static_root/assets/flag-tab-BNwbLfrN.css.gz +0 -0
  616. codex/static_root/assets/group-tab-D5vWrihT.1afb2ce453ff.js.br +0 -0
  617. codex/static_root/assets/group-tab-D5vWrihT.1afb2ce453ff.js.gz +0 -0
  618. codex/static_root/assets/group-tab-D5vWrihT.js.br +0 -0
  619. codex/static_root/assets/group-tab-D5vWrihT.js.gz +0 -0
  620. codex/static_root/assets/http-error-B4FHoKfs.141fdae409bc.js +0 -1
  621. codex/static_root/assets/http-error-B4FHoKfs.141fdae409bc.js.br +0 -0
  622. codex/static_root/assets/http-error-B4FHoKfs.141fdae409bc.js.gz +0 -0
  623. codex/static_root/assets/http-error-B4FHoKfs.js +0 -1
  624. codex/static_root/assets/http-error-B4FHoKfs.js.br +0 -0
  625. codex/static_root/assets/http-error-B4FHoKfs.js.gz +0 -0
  626. codex/static_root/assets/http-error-DKulXuTl.95e42988d5d5.css +0 -1
  627. codex/static_root/assets/http-error-DKulXuTl.95e42988d5d5.css.gz +0 -0
  628. codex/static_root/assets/http-error-DKulXuTl.css +0 -1
  629. codex/static_root/assets/http-error-DKulXuTl.css.gz +0 -0
  630. codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css +0 -1
  631. codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.br +0 -0
  632. codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.gz +0 -0
  633. codex/static_root/assets/library-tab-BN0SfPcH.css +0 -1
  634. codex/static_root/assets/library-tab-BN0SfPcH.css.br +0 -0
  635. codex/static_root/assets/library-tab-BN0SfPcH.css.gz +0 -0
  636. codex/static_root/assets/library-tab-BWo0cmzk.0e3d67d0c41a.js +0 -1
  637. codex/static_root/assets/library-tab-BWo0cmzk.0e3d67d0c41a.js.br +0 -0
  638. codex/static_root/assets/library-tab-BWo0cmzk.0e3d67d0c41a.js.gz +0 -0
  639. codex/static_root/assets/library-tab-BWo0cmzk.js +0 -1
  640. codex/static_root/assets/library-tab-BWo0cmzk.js.br +0 -0
  641. codex/static_root/assets/library-tab-BWo0cmzk.js.gz +0 -0
  642. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  643. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  644. codex/static_root/assets/main-ipmlipkF.5e60e6e8e447.js +0 -32
  645. codex/static_root/assets/main-ipmlipkF.5e60e6e8e447.js.br +0 -0
  646. codex/static_root/assets/main-ipmlipkF.5e60e6e8e447.js.gz +0 -0
  647. codex/static_root/assets/main-ipmlipkF.js +0 -32
  648. codex/static_root/assets/main-ipmlipkF.js.br +0 -0
  649. codex/static_root/assets/main-ipmlipkF.js.gz +0 -0
  650. codex/static_root/assets/pagination-toolbar-BA9ifTed.b4865334ee08.css +0 -1
  651. codex/static_root/assets/pagination-toolbar-BA9ifTed.b4865334ee08.css.br +0 -0
  652. codex/static_root/assets/pagination-toolbar-BA9ifTed.b4865334ee08.css.gz +0 -0
  653. codex/static_root/assets/pagination-toolbar-BA9ifTed.css +0 -1
  654. codex/static_root/assets/pagination-toolbar-BA9ifTed.css.br +0 -0
  655. codex/static_root/assets/pagination-toolbar-BA9ifTed.css.gz +0 -0
  656. codex/static_root/assets/pagination-toolbar-kZ-Xdv8c.b3341b8028df.js +0 -1
  657. codex/static_root/assets/pagination-toolbar-kZ-Xdv8c.b3341b8028df.js.br +0 -0
  658. codex/static_root/assets/pagination-toolbar-kZ-Xdv8c.b3341b8028df.js.gz +0 -0
  659. codex/static_root/assets/pagination-toolbar-kZ-Xdv8c.js +0 -1
  660. codex/static_root/assets/pagination-toolbar-kZ-Xdv8c.js.br +0 -0
  661. codex/static_root/assets/pagination-toolbar-kZ-Xdv8c.js.gz +0 -0
  662. codex/static_root/assets/pdf-doc-C2StaYUz.cb848834dbe1.js +0 -305
  663. codex/static_root/assets/pdf-doc-C2StaYUz.cb848834dbe1.js.br +0 -0
  664. codex/static_root/assets/pdf-doc-C2StaYUz.cb848834dbe1.js.gz +0 -0
  665. codex/static_root/assets/pdf-doc-C2StaYUz.js +0 -305
  666. codex/static_root/assets/pdf-doc-C2StaYUz.js.br +0 -0
  667. codex/static_root/assets/pdf-doc-C2StaYUz.js.gz +0 -0
  668. codex/static_root/assets/reader-CCnq3j1-.b6b38ed5521e.js +0 -2
  669. codex/static_root/assets/reader-CCnq3j1-.b6b38ed5521e.js.br +0 -0
  670. codex/static_root/assets/reader-CCnq3j1-.b6b38ed5521e.js.gz +0 -0
  671. codex/static_root/assets/reader-CCnq3j1-.js +0 -2
  672. codex/static_root/assets/reader-CCnq3j1-.js.br +0 -0
  673. codex/static_root/assets/reader-CCnq3j1-.js.gz +0 -0
  674. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css +0 -1
  675. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.br +0 -0
  676. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.gz +0 -0
  677. codex/static_root/assets/reader-CqXFa_gE.css +0 -1
  678. codex/static_root/assets/reader-CqXFa_gE.css.br +0 -0
  679. codex/static_root/assets/reader-CqXFa_gE.css.gz +0 -0
  680. codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.br +0 -0
  681. codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.gz +0 -0
  682. codex/static_root/assets/relation-chips-Bsc-gUxE.css.br +0 -0
  683. codex/static_root/assets/relation-chips-Bsc-gUxE.css.gz +0 -0
  684. codex/static_root/assets/relation-chips-C9ysW0tU.670f1d1f273f.js +0 -1
  685. codex/static_root/assets/relation-chips-C9ysW0tU.670f1d1f273f.js.br +0 -0
  686. codex/static_root/assets/relation-chips-C9ysW0tU.670f1d1f273f.js.gz +0 -0
  687. codex/static_root/assets/relation-chips-C9ysW0tU.js +0 -1
  688. codex/static_root/assets/relation-chips-C9ysW0tU.js.br +0 -0
  689. codex/static_root/assets/relation-chips-C9ysW0tU.js.gz +0 -0
  690. codex/static_root/assets/settings-drawer-C0ofzAhW.c0def8f383cb.js +0 -2
  691. codex/static_root/assets/settings-drawer-C0ofzAhW.c0def8f383cb.js.br +0 -0
  692. codex/static_root/assets/settings-drawer-C0ofzAhW.c0def8f383cb.js.gz +0 -0
  693. codex/static_root/assets/settings-drawer-C0ofzAhW.js +0 -2
  694. codex/static_root/assets/settings-drawer-C0ofzAhW.js.br +0 -0
  695. codex/static_root/assets/settings-drawer-C0ofzAhW.js.gz +0 -0
  696. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css +0 -1
  697. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.br +0 -0
  698. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.gz +0 -0
  699. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css +0 -1
  700. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.br +0 -0
  701. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.gz +0 -0
  702. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css +0 -1
  703. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.br +0 -0
  704. codex/static_root/assets/stats-tab-B4yKuY0c.a5728396494b.css.gz +0 -0
  705. codex/static_root/assets/stats-tab-B4yKuY0c.css +0 -1
  706. codex/static_root/assets/stats-tab-B4yKuY0c.css.br +0 -0
  707. codex/static_root/assets/stats-tab-B4yKuY0c.css.gz +0 -0
  708. codex/static_root/assets/stats-tab-BqJj7j1w.38a3459d61ea.js +0 -1
  709. codex/static_root/assets/stats-tab-BqJj7j1w.38a3459d61ea.js.br +0 -0
  710. codex/static_root/assets/stats-tab-BqJj7j1w.38a3459d61ea.js.gz +0 -0
  711. codex/static_root/assets/stats-tab-BqJj7j1w.js +0 -1
  712. codex/static_root/assets/stats-tab-BqJj7j1w.js.br +0 -0
  713. codex/static_root/assets/stats-tab-BqJj7j1w.js.gz +0 -0
  714. codex/static_root/assets/task-tab-BsPzNk8S.e00630a60b43.js +0 -1
  715. codex/static_root/assets/task-tab-BsPzNk8S.e00630a60b43.js.br +0 -0
  716. codex/static_root/assets/task-tab-BsPzNk8S.e00630a60b43.js.gz +0 -0
  717. codex/static_root/assets/task-tab-BsPzNk8S.js +0 -1
  718. codex/static_root/assets/task-tab-BsPzNk8S.js.br +0 -0
  719. codex/static_root/assets/task-tab-BsPzNk8S.js.gz +0 -0
  720. codex/static_root/assets/to-case-KvqtSug2.9c02da2e47c7.js +0 -1
  721. codex/static_root/assets/to-case-KvqtSug2.9c02da2e47c7.js.br +0 -0
  722. codex/static_root/assets/to-case-KvqtSug2.js +0 -1
  723. codex/static_root/assets/to-case-KvqtSug2.js.br +0 -0
  724. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.br +0 -0
  725. codex/static_root/assets/unauthorized-CibJYD8o.4c2de98c2b53.css.gz +0 -0
  726. codex/static_root/assets/unauthorized-CibJYD8o.css.br +0 -0
  727. codex/static_root/assets/unauthorized-CibJYD8o.css.gz +0 -0
  728. codex/static_root/assets/unauthorized-DwSdNzX2.f418e69f3afe.js +0 -1
  729. codex/static_root/assets/unauthorized-DwSdNzX2.f418e69f3afe.js.br +0 -0
  730. codex/static_root/assets/unauthorized-DwSdNzX2.f418e69f3afe.js.gz +0 -0
  731. codex/static_root/assets/unauthorized-DwSdNzX2.js +0 -1
  732. codex/static_root/assets/unauthorized-DwSdNzX2.js.br +0 -0
  733. codex/static_root/assets/unauthorized-DwSdNzX2.js.gz +0 -0
  734. codex/static_root/assets/user-tab-DrJOb9Ys.083d993726d8.js +0 -1
  735. codex/static_root/assets/user-tab-DrJOb9Ys.083d993726d8.js.br +0 -0
  736. codex/static_root/assets/user-tab-DrJOb9Ys.083d993726d8.js.gz +0 -0
  737. codex/static_root/assets/user-tab-DrJOb9Ys.js +0 -1
  738. codex/static_root/assets/user-tab-DrJOb9Ys.js.br +0 -0
  739. codex/static_root/assets/user-tab-DrJOb9Ys.js.gz +0 -0
  740. codex/static_root/js/choices-admin.3521e2a863df.json +0 -1
  741. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  742. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  743. codex/static_root/js/choices.2f6b0359e1ef.json +0 -1
  744. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  745. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  746. codex/static_root/manifest.1c84116d9a0a.json +0 -622
  747. codex/static_root/manifest.1c84116d9a0a.json.br +0 -0
  748. codex/static_root/manifest.1c84116d9a0a.json.gz +0 -0
  749. codex/util.py +0 -12
  750. codex/views/admin/auth.py +0 -28
  751. codex/views/browser/breadcrumbs.py +0 -202
  752. codex/views/browser/cover.py +0 -165
  753. codex/views/browser/mtime.py +0 -61
  754. codex/views/browser/order_by.py +0 -65
  755. codex/views/browser/paginate.py +0 -106
  756. codex/views/browser/settings.py +0 -66
  757. codex/views/browser/title.py +0 -48
  758. codex/views/browser/validate.py +0 -161
  759. codex/views/opds/auth.py +0 -17
  760. codex/views/public.py +0 -36
  761. codex/views/reader/arcs.py +0 -92
  762. codex/views/reader/books.py +0 -181
  763. codex/views/reader/init.py +0 -125
  764. codex/views/settings.py +0 -47
  765. codex/views/util.py +0 -77
  766. {codex-1.6.0.dist-info → codex-1.6.0a1.dist-info}/LICENSE +0 -0
  767. {codex-1.6.0.dist-info → codex-1.6.0a1.dist-info}/WHEEL +0 -0
  768. {codex-1.6.0.dist-info → codex-1.6.0a1.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  """View for marking comics read and unread."""
2
2
 
3
3
  from types import MappingProxyType
4
+ from typing import ClassVar
4
5
 
6
+ import pycountry
5
7
  from caseconverter import snakecase
6
8
  from django.db.models import QuerySet
7
9
  from drf_spectacular.utils import extend_schema
@@ -11,17 +13,21 @@ from codex.logger.logging import get_logger
11
13
  from codex.models import (
12
14
  Comic,
13
15
  ContributorPerson,
16
+ Folder,
17
+ Imprint,
18
+ Publisher,
19
+ Series,
14
20
  StoryArc,
21
+ Volume,
15
22
  )
16
23
  from codex.models.named import IdentifierType
17
- from codex.serializers.browser.choices import (
18
- BrowserChoicesFilterSerializer,
19
- )
24
+ from codex.serializers.browser.choices import CHOICES_SERIALIZER_CLASS_MAP
20
25
  from codex.serializers.browser.filters import (
21
26
  BrowserFilterChoicesSerializer,
27
+ CharListField,
22
28
  )
23
- from codex.serializers.browser.settings import BrowserFilterChoicesInputSerilalizer
24
- from codex.serializers.choices import DUMMY_NULL_NAME, VUETIFY_NULL_CODE
29
+ from codex.serializers.models.pycountry import PyCountrySerializer
30
+ from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
25
31
  from codex.views.browser.filters.annotations import (
26
32
  BrowserAnnotationsFilterView,
27
33
  )
@@ -44,27 +50,37 @@ _FIELD_TO_REL_MODEL_MAP = MappingProxyType(
44
50
  ),
45
51
  }
46
52
  )
47
- _BACK_REL_MAP = MappingProxyType(
48
- {
49
- ContributorPerson: "contributor__",
50
- StoryArc: "storyarcnumber__",
51
- IdentifierType: "identifier__",
52
- }
53
- )
54
- _NULL_NAMED_ROW = MappingProxyType({"pk": VUETIFY_NULL_CODE, "name": DUMMY_NULL_NAME})
55
53
 
56
54
 
57
55
  class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
58
56
  """Get choices for filter dialog."""
59
57
 
60
- input_serializer_class = BrowserFilterChoicesInputSerilalizer
61
- TARGET = "choices"
62
-
63
- def init_request(self):
64
- """Initialieze request."""
65
- self.parse_params()
66
- self.set_model()
67
- self.set_rel_prefix()
58
+ permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
59
+
60
+ _CONTRIBUTORS_PERSON_REL = "contributors__person"
61
+ _STORY_ARC_REL = "story_arc_numbers__story_arc"
62
+ _IDENTIFIERS_REL = "identifiers__identifier_type"
63
+ _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
64
+ _BACK_REL_MAP = MappingProxyType(
65
+ {
66
+ ContributorPerson: "contributor__",
67
+ StoryArc: "storyarcnumber__",
68
+ IdentifierType: "identifier__",
69
+ }
70
+ )
71
+ _REL_MAP = MappingProxyType(
72
+ {
73
+ Publisher: "publisher",
74
+ Imprint: "imprint",
75
+ Series: "series",
76
+ Volume: "volume",
77
+ Comic: "pk",
78
+ Folder: "parent_folder",
79
+ StoryArc: "story_arc_numbers__story_arc",
80
+ IdentifierType: "identifiers__identifier_type",
81
+ }
82
+ )
83
+ _SERIALIZER_MANY = False
68
84
 
69
85
  @staticmethod
70
86
  def get_field_choices_query(comic_qs, field_name):
@@ -77,13 +93,21 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
77
93
 
78
94
  def get_m2m_field_query(self, model, comic_qs: QuerySet):
79
95
  """Get distinct m2m value objects for the relation."""
80
- back_rel = _BACK_REL_MAP.get(model, "")
81
- m2m_filter = {f"{back_rel}comic__in": comic_qs}
96
+ if self.model is None:
97
+ LOG.error("No model to make filter choices!")
98
+ m2m_filter = {}
99
+ else:
100
+ model_rel: str = self._REL_MAP[self.model] # type: ignore
101
+ back_rel = self._BACK_REL_MAP.get(model, "")
102
+ back_rel += "comic__"
103
+ back_rel += model_rel
104
+ back_rel += "__in"
105
+ m2m_filter = {back_rel: comic_qs}
82
106
  return model.objects.filter(**m2m_filter).values("pk", "name").distinct()
83
107
 
84
108
  @staticmethod
85
109
  def does_m2m_null_exist(comic_qs, rel):
86
- """Get if comics exist in the filter without values exists for an m2m field."""
110
+ """Get if null values exists for an m2m field."""
87
111
  return comic_qs.filter(**{f"{rel}__isnull": True}).exists()
88
112
 
89
113
  def get_rel_and_model(self, field_name):
@@ -98,18 +122,22 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
98
122
  rel = field_name
99
123
  model = remote_field.model if remote_field else None
100
124
 
125
+ rel = self.rel_prefix + rel
126
+
101
127
  return rel, model
102
128
 
103
129
  def get_object(self):
104
130
  """Get the comic subquery use for the choices."""
105
- return self.get_filtered_queryset(Comic)
131
+ object_filter = self.get_query_filters(self.model, choices=True)
132
+ qs = self.model.objects.filter(object_filter) # type: ignore
133
+ return self.filter_by_annotations(qs, self.model, binary=True)
106
134
 
107
- @extend_schema(parameters=[input_serializer_class])
135
+ @extend_schema(request=BrowserAnnotationsFilterView.input_serializer_class)
108
136
  def get(self, *_args, **_kwargs):
109
137
  """Return choices."""
110
138
  self.init_request()
111
139
  obj = self.get_object()
112
- serializer = self.get_serializer(obj)
140
+ serializer = self.get_serializer(obj, many=self._SERIALIZER_MANY)
113
141
  return Response(serializer.data)
114
142
 
115
143
 
@@ -142,7 +170,6 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
142
170
  def get_object(self):
143
171
  """Get choice counts."""
144
172
  qs = super().get_object()
145
- filters = self.params.get("filters", {})
146
173
  data = {}
147
174
  for field_name in self.serializer_class().get_fields(): # type: ignore
148
175
  if field_name == "story_arcs" and self.model == StoryArc:
@@ -155,9 +182,9 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
155
182
  else:
156
183
  count = self._get_field_choices_count(qs, rel)
157
184
 
185
+ filters = self.params.get("filters", {})
158
186
  try:
159
- is_filter_set = bool(filters.get(field_name))
160
- flag = is_filter_set or count > 1 # type: ignore
187
+ flag = count > 1 or field_name in filters # type: ignore
161
188
  except TypeError:
162
189
  flag = False
163
190
  data[field_name] = flag
@@ -167,7 +194,26 @@ class BrowserChoicesAvailableView(BrowserChoicesViewBase):
167
194
  class BrowserChoicesView(BrowserChoicesViewBase):
168
195
  """Get choices for filter dialog."""
169
196
 
170
- serializer_class = BrowserChoicesFilterSerializer
197
+ # serializer_class = Dynamic class determined in get()
198
+ _SERIALIZER_MANY = True
199
+
200
+ def _get_field_choices(self, comic_qs, field_name):
201
+ """Create a pk:name object for fields without tables."""
202
+ qs = self.get_field_choices_query(comic_qs, field_name)
203
+
204
+ if field_name == "country":
205
+ lookup = pycountry.countries
206
+ elif field_name == "language":
207
+ lookup = pycountry.languages
208
+ else:
209
+ lookup = None
210
+
211
+ choices = []
212
+ for val in qs:
213
+ name = PyCountrySerializer.lookup_name(lookup, val) if lookup else val
214
+ choices.append({"pk": val, "name": name})
215
+
216
+ return choices
171
217
 
172
218
  def _get_m2m_field_choices(self, model, comic_qs, rel):
173
219
  """Get choices with nulls where there are nulls."""
@@ -179,7 +225,7 @@ class BrowserChoicesView(BrowserChoicesViewBase):
179
225
  # rows. :(
180
226
  if self.does_m2m_null_exist(comic_qs, rel):
181
227
  choices = list(qs)
182
- choices.append(_NULL_NAMED_ROW)
228
+ choices.append(self._NULL_NAMED_ROW)
183
229
  else:
184
230
  choices = qs
185
231
  return choices
@@ -188,6 +234,11 @@ class BrowserChoicesView(BrowserChoicesViewBase):
188
234
  field_name = self.kwargs.get("field_name", "")
189
235
  return snakecase(field_name)
190
236
 
237
+ def get_serializer_class(self): # type: ignore
238
+ """Dynamic serializer class."""
239
+ field_name = self._get_field_name()
240
+ return CHOICES_SERIALIZER_CLASS_MAP.get(field_name, CharListField)
241
+
191
242
  def get_object(self):
192
243
  """Return choices with more than one choice."""
193
244
  qs = super().get_object()
@@ -197,9 +248,5 @@ class BrowserChoicesView(BrowserChoicesViewBase):
197
248
  if m2m_model:
198
249
  choices = self._get_m2m_field_choices(m2m_model, qs, rel)
199
250
  else:
200
- choices = self.get_field_choices_query(qs, field_name)
201
-
202
- return {
203
- "field_name": field_name,
204
- "choices": choices,
205
- }
251
+ choices = self._get_field_choices(qs, rel)
252
+ return choices
@@ -0,0 +1,3 @@
1
+ """Browser constants."""
2
+
3
+ MAX_OBJ_PER_PAGE = 100
@@ -1,102 +1,34 @@
1
1
  """Annotations used by a filter."""
2
2
 
3
- from django.db.models.aggregates import Count, Max
4
- from django.db.models.functions import Coalesce, Greatest
3
+ from django.db.models.aggregates import Count
4
+ from django.db.models.expressions import Value
5
+ from django.db.models.fields import PositiveSmallIntegerField
5
6
 
6
7
  from codex.models.comic import Comic
7
- from codex.views.browser.filters.bookmark import BookmarkFilterMixin
8
- from codex.views.browser.validate import BrowserValidateView
9
- from codex.views.const import (
10
- EPOCH_START_DATETIMEFIELD,
11
- ONE_INTEGERFIELD,
12
- )
8
+ from codex.views.browser.base import BrowserBaseView
13
9
 
14
10
 
15
- class BrowserAnnotationsFilterView(BrowserValidateView, BookmarkFilterMixin):
11
+ class BrowserAnnotationsFilterView(BrowserBaseView):
16
12
  """Annotations that also filter."""
17
13
 
18
- def __init__(self, *args, **kwargs) -> None:
19
- """Initialize."""
20
- super().__init__(*args, **kwargs)
21
- self.init_bookmark_data()
14
+ ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
22
15
 
23
- def _get_query_filters( # noqa: PLR0913
24
- self, model, group, pks, bookmark_filter, page_mtime=False, group_filter=True
25
- ):
26
- """Return all the filters except the group filter."""
27
- # TODO all these ifs and flags are awful
28
- object_filter = self.get_group_acl_filter(model)
29
- if group_filter:
30
- object_filter &= self.get_group_filter(group, pks, page_mtime=page_mtime)
31
- object_filter &= self.get_comic_field_filter(model)
32
- if bookmark_filter:
33
- # not needed when used by outerrefl OuterRef is applied next
34
- object_filter &= self.get_bookmark_filter(model)
35
- return object_filter
36
-
37
- def _filter_by_child_count(self, qs, model):
38
- """Filter group by child count."""
39
- rel = self.rel_prefix + "pk"
40
- ann_name = "child_count"
41
- count_func = ONE_INTEGERFIELD if model == Comic else Count(rel, distinct=True)
42
- ann = {ann_name: count_func}
16
+ def _annotate_child_count(self, qs, model):
17
+ """Annotate Child Count."""
18
+ if model == Comic:
19
+ child_count_sum = self.ONE_INTEGERFIELD
20
+ else:
21
+ child_count_sum = Count(self.rel_prefix + "pk", distinct=True)
43
22
  if self.TARGET == "opds2":
44
23
  if model != Comic:
45
- qs = qs.alias(**ann)
24
+ qs = qs.alias(child_count=child_count_sum)
46
25
  else:
47
- qs = qs.annotate(**ann)
26
+ qs = qs.annotate(child_count=child_count_sum)
48
27
  if model != Comic:
49
- qs = qs.filter(**{f"{ann_name}__gt": 0})
28
+ qs = qs.filter(child_count__gt=0)
50
29
  return qs
51
30
 
52
- def get_filtered_queryset( # noqa: PLR0913
53
- self,
54
- model,
55
- group=None,
56
- pks=None,
57
- bookmark_filter=True,
58
- page_mtime=False,
59
- group_filter=True,
60
- ):
61
- """Get a filtered queryset for the model."""
62
- object_filter = self._get_query_filters(
63
- model,
64
- group,
65
- pks,
66
- bookmark_filter,
67
- page_mtime=page_mtime,
68
- group_filter=group_filter,
69
- )
70
- qs = model.objects.filter(object_filter)
71
- qs = self.apply_search_filter(qs, model)
72
- return self._filter_by_child_count(qs, model)
73
-
74
- def get_group_mtime(self, model, group=None, pks=None, page_mtime=False):
75
- """Get a filtered mtime for browser pages and mtime checker."""
76
- qs = self.get_filtered_queryset(
77
- model,
78
- group=group,
79
- pks=pks,
80
- bookmark_filter=self.is_bookmark_filtered,
81
- page_mtime=page_mtime,
82
- )
83
- qs = qs.annotate(max_updated_at=Max("updated_at"))
84
-
85
- if self.is_bookmark_filtered:
86
- bm_rel, bm_filter = self.get_bookmark_rel_and_filter(model)
87
- qs = qs.filter(bm_filter)
88
- ua_rel = bm_rel + "__updated_at"
89
- mbua = Max(ua_rel)
90
- else:
91
- mbua = EPOCH_START_DATETIMEFIELD
92
- qs = qs.annotate(max_bookmark_updated_at=mbua)
93
-
94
- mtime = qs.aggregate(
95
- max=Greatest(
96
- Coalesce("max_bookmark_updated_at", EPOCH_START_DATETIMEFIELD),
97
- "max_updated_at",
98
- )
99
- )["max"]
100
- if mtime == NotImplemented:
101
- mtime = None
102
- return mtime
31
+ def filter_by_annotations(self, qs, model, binary=False):
32
+ """Filter queryset by annotations."""
33
+ qs = self._annotate_child_count(qs, model)
34
+ return self.apply_search_filter(qs, model, binary=binary)
@@ -1,21 +1,16 @@
1
1
  """Bookmark filter view methods."""
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
3
  from django.db.models import Q
6
4
 
7
- if TYPE_CHECKING:
8
- from codex.models import BrowserGroupModel
5
+ from codex.serializers.choices import CHOICES
9
6
 
10
7
 
11
8
  class BookmarkFilterMixin:
12
9
  """BookmarkFilter view methods."""
13
10
 
14
- def init_bookmark_data(self):
15
- """Initialize the bm_annotation_data."""
16
- self._bm_annotation_data: dict[BrowserGroupModel, tuple[str, Q]] = {}
11
+ _BOOKMARK_FILTERS = frozenset(set(CHOICES["bookmarkFilter"].keys()) - {"ALL"})
17
12
 
18
- def _get_bm_rel(self, model):
13
+ def get_bm_rel(self, model):
19
14
  """Create bookmark relation."""
20
15
  rel_prefix = self.get_rel_prefix(model) # type: ignore
21
16
  return rel_prefix + "bookmark"
@@ -31,24 +26,13 @@ class BookmarkFilterMixin:
31
26
  }
32
27
  return Q(**my_bookmarks_kwargs)
33
28
 
34
- def get_bookmark_rel_and_filter(self, model):
35
- """Get the bookmark rel and filter once."""
36
- if model not in self._bm_annotation_data:
37
- bm_rel = self._get_bm_rel(model)
38
- bm_filter = self._get_my_bookmark_filter(bm_rel)
39
- self._bm_annotation_data[model] = (bm_rel, bm_filter)
40
- return self._bm_annotation_data[model]
41
-
42
29
  def get_bookmark_filter(self, model):
43
30
  """Build bookmark query."""
44
- choice: str = self.params.get("filters", {}).get("bookmark", "") # type: ignore
45
- if choice:
46
- bm_rel, my_bookmark_filter = self.get_bookmark_rel_and_filter(model)
47
- if choice == "READ":
48
- bookmark_filter = my_bookmark_filter & Q(
49
- **{f"{bm_rel}__finished": True}
50
- )
51
- else:
31
+ choice = self.params["filters"].get("bookmark", "ALL") # type: ignore
32
+ if choice in self._BOOKMARK_FILTERS:
33
+ bm_rel = self.get_bm_rel(model)
34
+ my_bookmark_filter = self._get_my_bookmark_filter(bm_rel)
35
+ if choice in ("UNREAD", "IN_PROGRESS"):
52
36
  my_not_finished_filter = my_bookmark_filter & Q(
53
37
  **{f"{bm_rel}__finished__in": (False, None)}
54
38
  )
@@ -58,6 +42,10 @@ class BookmarkFilterMixin:
58
42
  bookmark_filter = my_not_finished_filter & Q(
59
43
  **{f"{bm_rel}__page__gt": 0}
60
44
  )
45
+ else: # READ
46
+ bookmark_filter = my_bookmark_filter & Q(
47
+ **{f"{bm_rel}__finished": True}
48
+ )
61
49
  else:
62
50
  bookmark_filter = Q()
63
51
  return bookmark_filter
@@ -4,19 +4,18 @@ from types import MappingProxyType
4
4
 
5
5
  from django.db.models import Q
6
6
 
7
- from codex.models.comic import Comic
8
- from codex.views.browser.filters.group import GroupFilterView
7
+ from codex.views.session import BrowserSessionViewBase
9
8
 
10
9
  _FILTER_REL_MAP = MappingProxyType(
11
10
  {
12
- GroupFilterView.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
13
- GroupFilterView.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
14
- GroupFilterView.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
11
+ BrowserSessionViewBase.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
12
+ BrowserSessionViewBase.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
13
+ BrowserSessionViewBase.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
15
14
  }
16
15
  )
17
16
 
18
17
 
19
- class ComicFieldFilterView(GroupFilterView):
18
+ class ComicFieldFilter(BrowserSessionViewBase):
20
19
  """Comic field filters."""
21
20
 
22
21
  def _filter_by_comic_field(self, field, rel_prefix):
@@ -37,10 +36,9 @@ class ComicFieldFilterView(GroupFilterView):
37
36
  filter_query |= Q(**{f"{rel}__in": filter_list})
38
37
  return filter_query
39
38
 
40
- def get_comic_field_filter(self, model):
39
+ def get_comic_field_filter(self, rel_prefix):
41
40
  """Filter the comics based on the form filters."""
42
41
  comic_field_filter = Q()
43
- rel_prefix = "" if model == Comic else self.rel_prefix # type: ignore
44
42
  for attribute in self.FILTER_ATTRIBUTES:
45
43
  comic_field_filter &= self._filter_by_comic_field(attribute, rel_prefix)
46
44
  return comic_field_filter
@@ -2,46 +2,25 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.views.const import (
6
- FILTER_ONLY_GROUP_RELATION,
7
- FOLDER_GROUP,
8
- GROUP_RELATION,
9
- )
10
- from codex.views.session import SessionView
5
+ from codex.views.auth import GroupACLMixin
11
6
 
12
7
 
13
- class GroupFilterView(SessionView):
8
+ class GroupFilterMixin(GroupACLMixin):
14
9
  """Group Filters."""
15
10
 
16
- SESSION_KEY = SessionView.BROWSER_SESSION_KEY
17
-
18
- def _get_rel_for_pks(self, group, page_mtime=False):
19
- """Get the relation from the model to the pks."""
20
- # XXX these TARGET refs might be better as subclass get rel methods.
21
- target: str = self.TARGET # type: ignore
22
- if target in ("cover", "choices"):
23
- rel = FILTER_ONLY_GROUP_RELATION[group]
24
- elif target in ("metadata", "mtime") or page_mtime:
25
- # metadata, mtime, browser.page_mtime
26
- rel = "pk"
27
- else:
28
- # browser.group, opds
29
- rel = GROUP_RELATION[group]
30
-
31
- rel += "__in"
32
- return rel
33
-
34
- def get_group_filter(self, group=None, pks=None, page_mtime=False):
11
+ def get_group_filter(self, choices):
35
12
  """Get filter for the displayed group."""
36
- if group is None:
37
- group = self.kwargs["group"]
38
- if pks is None:
39
- pks = self.kwargs["pks"]
40
-
41
- if pks and 0 not in pks:
42
- rel = self._get_rel_for_pks(group, page_mtime)
43
- group_filter_dict = {rel: pks}
44
- elif group == FOLDER_GROUP:
13
+ group = self.kwargs["group"] # type: ignore
14
+ pks = self.kwargs["pks"] # type: ignore
15
+ if pks: # type: ignore
16
+ group_relation = "comic__" if choices else ""
17
+ if choices and group == self.FOLDER_GROUP:
18
+ group_relation += "folders"
19
+ else:
20
+ group_relation += self.GROUP_RELATION[group]
21
+ group_relation += "__in"
22
+ group_filter_dict = {group_relation: pks} # type: ignore
23
+ elif group == self.FOLDER_GROUP:
45
24
  group_filter_dict = {"parent_folder": None}
46
25
  else:
47
26
  group_filter_dict = {}
@@ -9,13 +9,12 @@ from codex.logger.logging import get_logger
9
9
  from codex.models import Comic
10
10
  from codex.search.query import CodexSearchQuerySet
11
11
  from codex.settings.settings import DEBUG
12
- from codex.views.browser.filters.field import ComicFieldFilterView
13
- from codex.views.const import MAX_OBJ_PER_PAGE
12
+ from codex.views.browser.const import MAX_OBJ_PER_PAGE
14
13
 
15
14
  LOG = get_logger(__name__)
16
- _SEARCH_SCORE_MAX = 100.0
17
- _SEARCH_SCORE_MIN = 0.001
18
- _SEARCH_SCORE_EMPTY = 0.0
15
+ SEARCH_SCORE_MAX = 100.0
16
+ SEARCH_SCORE_MIN = 0.001
17
+ SEARCH_SCORE_EMPTY = 0.0
19
18
 
20
19
 
21
20
  @dataclass
@@ -27,11 +26,9 @@ class SearchScorePks:
27
26
  next_pks: tuple[int, ...] = ()
28
27
 
29
28
 
30
- class SearchFilterView(ComicFieldFilterView):
29
+ class SearchFilterMixin:
31
30
  """Search Filters Methods."""
32
31
 
33
- TARGET = ""
34
-
35
32
  def _is_search_results_limited(self) -> bool:
36
33
  """Get search result limit from params."""
37
34
  # user = self.request.user # type: ignore
@@ -117,7 +114,7 @@ class SearchFilterView(ComicFieldFilterView):
117
114
 
118
115
  return tuple(scores_pairs), tuple(prev_pks), tuple(next_pks), tuple(scored_pks)
119
116
 
120
- def _get_search_scores(self, model, qs):
117
+ def _get_search_scores(self, binary=False):
121
118
  """Perform the search and return the scores as a dict."""
122
119
  text = self.params.get("q", "") # type: ignore
123
120
  if not text:
@@ -130,20 +127,13 @@ class SearchFilterView(ComicFieldFilterView):
130
127
  try:
131
128
  sqs = CodexSearchQuerySet()
132
129
  sqs = sqs.auto_query(text) # .filter(score__gt=0.0)
133
- prefix = "" if model == Comic else self.rel_prefix # type: ignore
134
- comic_ids = qs.values_list(prefix + "pk", flat=True)
135
- if comic_ids:
136
- # Prefilter comic ids. If nothing is allowed, don't search.
137
- sqs = sqs.filter_comic_ids(comic_ids)
138
- if (
139
- self.TARGET in frozenset({"cover", "choices"})
140
- or self.params.get("order_by", "") != "search_score" # type: ignore
141
- ):
142
- scored_pks = self._get_binary_search_scores(sqs)
143
- else:
144
- score_pairs, prev_pks, next_pks, scored_pks = (
145
- self._get_browser_search_scores(sqs)
146
- )
130
+ binary = binary or self.params.get("order_by") != "search_score" # type: ignore
131
+ if binary:
132
+ scored_pks = self._get_binary_search_scores(sqs)
133
+ else:
134
+ score_pairs, prev_pks, next_pks, scored_pks = (
135
+ self._get_browser_search_scores(sqs)
136
+ )
147
137
  except MemoryError:
148
138
  LOG.warning("Search engine needs more memory, results truncated.")
149
139
  except Exception:
@@ -164,12 +154,10 @@ class SearchFilterView(ComicFieldFilterView):
164
154
  when = {prefix + "pk": pk, "then": score}
165
155
  whens.append(When(**when))
166
156
  if prev_pks:
167
- whens.append(When(pk__in=prev_pks, then=_SEARCH_SCORE_MAX))
157
+ whens.append(When(pk__in=prev_pks, then=SEARCH_SCORE_MAX))
168
158
  if next_pks:
169
- whens.append(When(pk__in=next_pks, then=_SEARCH_SCORE_MIN))
170
- search_score = self.order_agg_func( # type: ignore
171
- Case(*whens, default=_SEARCH_SCORE_EMPTY)
172
- )
159
+ whens.append(When(pk__in=next_pks, then=SEARCH_SCORE_MIN))
160
+ search_score = self.order_agg_func(Case(*whens, default=SEARCH_SCORE_EMPTY)) # type: ignore
173
161
  else:
174
162
  search_score = Value(0.0)
175
163
  return qs.annotate(search_score=search_score)
@@ -180,16 +168,17 @@ class SearchFilterView(ComicFieldFilterView):
180
168
  rel = prefix + "pk__in"
181
169
  return {rel: scored_pks}
182
170
 
183
- def apply_search_filter(self, qs, model):
171
+ def apply_search_filter(self, qs, model, binary=False):
184
172
  """Preparse search, search and return the filter and scores."""
185
173
  try:
186
174
  score_pairs, prev_pks, next_pks, scored_pks = self._get_search_scores(
187
- model, qs
175
+ binary
188
176
  )
189
177
  qs = self.annotate_search_score(qs, model, score_pairs, prev_pks, next_pks)
190
- if score_pairs or scored_pks:
178
+ if score_pairs:
191
179
  search_filter = self._get_search_query_filter(model, scored_pks)
192
180
  qs = qs.filter(**search_filter)
181
+ self.cover_search_score_pairs = score_pairs
193
182
  except Exception:
194
183
  LOG.exception("Creating the search filter")
195
184