codex 1.6.0a5__py3-none-any.whl → 1.6.0a7__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 (564) hide show
  1. codex/exceptions.py +1 -0
  2. codex/librarian/covers/purge.py +1 -2
  3. codex/librarian/importer/aggregate_metadata.py +2 -10
  4. codex/librarian/importer/cache.py +47 -0
  5. codex/librarian/importer/const.py +8 -0
  6. codex/librarian/importer/deleted.py +45 -7
  7. codex/librarian/importer/importerd.py +11 -1
  8. codex/librarian/importer/update_comics.py +0 -2
  9. codex/librarian/janitor/janitor.py +1 -1
  10. codex/librarian/librariand.py +1 -1
  11. codex/migrations/0029_choices_adminflag_and_timestamp.py +44 -0
  12. codex/models/admin.py +0 -1
  13. codex/permissions.py +1 -1
  14. codex/serializers/admin.py +1 -2
  15. codex/serializers/browser/filters.py +8 -101
  16. codex/serializers/browser/mixins.py +20 -3
  17. codex/serializers/browser/page.py +2 -3
  18. codex/serializers/browser/settings.py +2 -0
  19. codex/serializers/fields.py +123 -0
  20. codex/serializers/mtime.py +25 -0
  21. codex/serializers/opds/v2.py +7 -11
  22. codex/serializers/opds/v2_unused.py +5 -3
  23. codex/serializers/reader.py +5 -2
  24. codex/serializers/route.py +7 -3
  25. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js +1 -0
  26. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  27. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  28. codex/static_root/assets/VCheckbox-BOUtyxuo.js +1 -0
  29. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  30. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  31. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js +1 -0
  32. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  33. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  34. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js +1 -0
  35. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  36. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  37. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js +1 -0
  38. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  39. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  40. codex/static_root/assets/VCombobox-DjkDXe33.js +1 -0
  41. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  42. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  43. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js +1 -0
  44. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  45. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  46. codex/static_root/assets/VDialog-X0zn9AGX.js +1 -0
  47. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  48. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  49. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js +1 -0
  50. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  51. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  52. codex/static_root/assets/VExpansionPanels-CRevojaF.js +1 -0
  53. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  54. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  55. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js +1 -0
  56. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  57. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  58. codex/static_root/assets/VRadioGroup-8nrrxjDv.js +1 -0
  59. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  60. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  61. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +1 -0
  62. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  63. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  64. codex/static_root/assets/VSelect-DgfIKRnC.js +1 -0
  65. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  66. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  67. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js +1 -0
  68. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  69. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  70. codex/static_root/assets/VSelectionControl-CGu0oz0K.js +1 -0
  71. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  72. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  73. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js +1 -0
  74. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  75. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  76. codex/static_root/assets/VSlideGroup-D_oNvCOd.js +1 -0
  77. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  78. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  79. codex/static_root/assets/{VTable-FKMTcdkF.6c1eed84d25e.js → VTable-C7pKI7gU.087912f706f5.js} +1 -1
  80. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  81. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  82. codex/static_root/assets/{VTable-FKMTcdkF.js → VTable-C7pKI7gU.js} +1 -1
  83. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  84. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  85. codex/static_root/assets/{VTextField-Dftcik85.js → VTextField-Bs8oq9mk.7999fef12a03.js} +1 -1
  86. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  87. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  88. codex/static_root/assets/{VTextField-Dftcik85.cb2f1dda87e1.js → VTextField-Bs8oq9mk.js} +1 -1
  89. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  90. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  91. codex/static_root/assets/{VWindowItem-CYCwnt3s.28025dc36c6a.js → VWindowItem-iyZ1XOkP.409f7d292253.js} +1 -1
  92. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  93. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  94. codex/static_root/assets/{VWindowItem-CYCwnt3s.js → VWindowItem-iyZ1XOkP.js} +1 -1
  95. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  96. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  97. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js +1 -0
  98. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  99. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  100. codex/static_root/assets/admin-B0pCBjma.js +1 -0
  101. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  102. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  103. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js +30 -0
  104. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  105. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  106. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js +30 -0
  107. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  108. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  109. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
  110. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  111. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
  112. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
  113. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  114. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
  115. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +1 -0
  116. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  117. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  118. codex/static_root/assets/browser-DbyAjCNh.js +1 -0
  119. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  120. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  121. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +1 -0
  122. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  123. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  124. codex/static_root/assets/browser-VnzNj1t3.css +1 -0
  125. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  126. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  127. codex/static_root/assets/{change-password-dialog-BIBG6Bk8.3583021c124a.js → change-password-dialog-DZ9dP8_F.930f4cae3cb0.js} +1 -1
  128. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  129. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  130. codex/static_root/assets/{change-password-dialog-BIBG6Bk8.js → change-password-dialog-DZ9dP8_F.js} +1 -1
  131. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  132. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  133. codex/static_root/assets/{confirm-dialog-DARLmXRf.53fc6938fb8c.js → confirm-dialog-RP7JNStF.a752a7c1e697.js} +1 -1
  134. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
  135. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  136. codex/static_root/assets/{confirm-dialog-DARLmXRf.js → confirm-dialog-RP7JNStF.js} +1 -1
  137. codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
  138. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  139. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
  140. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  141. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
  142. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  143. codex/static_root/assets/{datetime-column-LpLgWpkf.16ce3324970f.js → datetime-column-Cu3WYyPj.54b4c3817b74.js} +1 -1
  144. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  145. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  146. codex/static_root/assets/{datetime-column-LpLgWpkf.js → datetime-column-Cu3WYyPj.js} +1 -1
  147. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  148. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  149. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js +1 -0
  150. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  151. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  152. codex/static_root/assets/filter-DAdGUt-1.js +1 -0
  153. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  154. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  155. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +1 -0
  156. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  157. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  158. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +1 -0
  159. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  160. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  161. codex/static_root/assets/{group-tab-MK1lcjUU.98406d5babdc.js → group-tab-DEwh4jfB.a95097b5e320.js} +1 -1
  162. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  163. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  164. codex/static_root/assets/{group-tab-MK1lcjUU.js → group-tab-DEwh4jfB.js} +1 -1
  165. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  166. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  167. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +1 -0
  168. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  169. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  170. codex/static_root/assets/http-error-xs3mIL8U.js +1 -0
  171. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  172. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  173. codex/static_root/assets/{library-tab-DYeEBYlT.a2a2ef088d1c.js → library-tab-DrXvD9B2.f765f9d3bae4.js} +1 -1
  174. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  175. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  176. codex/static_root/assets/{library-tab-DYeEBYlT.js → library-tab-DrXvD9B2.js} +1 -1
  177. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  178. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  179. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +32 -0
  180. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  181. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  182. codex/static_root/assets/main-Dy9Uqpmh.js +32 -0
  183. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  184. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  185. codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
  186. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  187. codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
  188. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
  189. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  190. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
  191. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +1 -0
  192. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  193. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  194. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +1 -0
  195. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  196. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  197. codex/static_root/assets/{pagination-toolbar-ClZui9Zf.697d7ae11c21.css → pagination-toolbar-r6nbtWxg.4088ab042c0c.css} +1 -1
  198. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  199. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  200. codex/static_root/assets/{pagination-toolbar-ClZui9Zf.css → pagination-toolbar-r6nbtWxg.css} +1 -1
  201. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  202. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  203. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js +305 -0
  204. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  205. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  206. codex/static_root/assets/pdf-doc-obrBwxa6.js +305 -0
  207. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  208. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  209. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +2 -0
  210. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  211. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  212. codex/static_root/assets/reader-BXKUaUeC.js +2 -0
  213. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  214. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  215. codex/static_root/assets/{reader-CF3xt6JG.19fad7f62450.css → reader-OXJ1KVaW.a463b19d0508.css} +1 -1
  216. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  217. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  218. codex/static_root/assets/{reader-CF3xt6JG.css → reader-OXJ1KVaW.css} +1 -1
  219. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  220. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  221. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js +1 -0
  222. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  223. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  224. codex/static_root/assets/relation-chips-CZJcLjc8.js +1 -0
  225. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  226. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  227. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js +2 -0
  228. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  229. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  230. codex/static_root/assets/settings-drawer-7iTnAdjf.js +2 -0
  231. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  232. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  233. codex/static_root/assets/{settings-drawer-BJOfMtqA.6471a50fc05b.css → settings-drawer-nZ9HvKSZ.01744e131806.css} +1 -1
  234. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.br +0 -0
  235. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.gz +0 -0
  236. codex/static_root/assets/{settings-drawer-BJOfMtqA.css → settings-drawer-nZ9HvKSZ.css} +1 -1
  237. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.br +0 -0
  238. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.gz +0 -0
  239. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js +1 -0
  240. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  241. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  242. codex/static_root/assets/stats-tab-CQsK1n8O.js +1 -0
  243. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  244. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  245. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +1 -0
  246. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  247. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  248. codex/static_root/assets/task-tab-C60TLP6e.js +1 -0
  249. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  250. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  251. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +1 -0
  252. codex/static_root/assets/to-case-CR9beRR0.js +1 -0
  253. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js +1 -0
  254. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  255. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  256. codex/static_root/assets/unauthorized-Bs7NSqea.js +1 -0
  257. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  258. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  259. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js +1 -0
  260. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  261. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  262. codex/static_root/assets/user-tab-ChuH7Atm.js +1 -0
  263. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  264. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  265. codex/static_root/js/choices.079a01e0be1a.json +1 -0
  266. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  267. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  268. codex/static_root/js/choices.json +1 -1
  269. codex/static_root/js/choices.json.br +0 -0
  270. codex/static_root/js/choices.json.gz +0 -0
  271. codex/static_root/manifest.dad79c8475ae.json +629 -0
  272. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  273. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  274. codex/static_root/manifest.json +265 -278
  275. codex/static_root/manifest.json.br +0 -0
  276. codex/static_root/manifest.json.gz +0 -0
  277. codex/static_root/staticfiles.json +1 -1
  278. codex/urls/api/auth.py +2 -1
  279. codex/urls/api/browser.py +2 -2
  280. codex/urls/api/reader.py +2 -2
  281. codex/urls/api/v3.py +2 -0
  282. codex/util.py +10 -0
  283. codex/views/admin/api_key.py +2 -6
  284. codex/views/admin/auth.py +30 -0
  285. codex/views/admin/flag.py +2 -7
  286. codex/views/admin/group.py +2 -6
  287. codex/views/admin/library.py +7 -20
  288. codex/views/admin/stats.py +7 -66
  289. codex/views/admin/tasks.py +12 -11
  290. codex/views/admin/user.py +3 -9
  291. codex/views/auth.py +31 -52
  292. codex/views/bookmark.py +7 -11
  293. codex/views/browser/{browser_annotations.py → annotations.py} +119 -78
  294. codex/views/browser/base.py +24 -72
  295. codex/views/browser/breadcrumbs.py +200 -0
  296. codex/views/browser/browser.py +64 -344
  297. codex/views/browser/choices.py +25 -33
  298. codex/views/browser/filters/annotations.py +2 -2
  299. codex/views/browser/filters/bookmark.py +9 -13
  300. codex/views/browser/filters/field.py +9 -6
  301. codex/views/browser/filters/group.py +8 -5
  302. codex/views/browser/filters/search.py +11 -8
  303. codex/views/browser/metadata.py +3 -7
  304. codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
  305. codex/views/browser/paginate.py +106 -0
  306. codex/views/browser/{session.py → settings.py} +4 -3
  307. codex/views/browser/title.py +48 -0
  308. codex/views/browser/validate.py +158 -0
  309. codex/views/const.py +97 -0
  310. codex/views/cover.py +4 -6
  311. codex/views/download.py +5 -8
  312. codex/views/frontend.py +4 -2
  313. codex/views/mtime.py +70 -0
  314. codex/views/opds/auth.py +17 -0
  315. codex/views/opds/authentication_v1.py +46 -45
  316. codex/views/opds/binary.py +4 -9
  317. codex/views/opds/const.py +13 -0
  318. codex/views/opds/urls.py +5 -4
  319. codex/views/opds/util.py +2 -8
  320. codex/views/opds/v1/facets.py +6 -7
  321. codex/views/opds/v1/feed.py +10 -17
  322. codex/views/opds/v1/links.py +6 -5
  323. codex/views/opds/v1/opensearch_v1.py +2 -6
  324. codex/views/opds/v2/feed.py +12 -15
  325. codex/views/opds/v2/links.py +3 -2
  326. codex/views/opds/v2/publications.py +21 -20
  327. codex/views/opds/v2/top_links.py +2 -2
  328. codex/views/public.py +38 -0
  329. codex/views/reader/arcs.py +101 -0
  330. codex/views/reader/books.py +199 -0
  331. codex/views/reader/page.py +3 -7
  332. codex/views/reader/reader.py +53 -298
  333. codex/views/reader/{session.py → settings.py} +4 -2
  334. codex/views/session.py +74 -102
  335. codex/views/settings.py +34 -0
  336. codex/views/template.py +3 -3
  337. codex/views/util.py +31 -0
  338. codex/views/utils.py +26 -0
  339. codex/views/version.py +2 -2
  340. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/METADATA +12 -8
  341. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/RECORD +344 -330
  342. codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js +0 -1
  343. codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.br +0 -0
  344. codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.gz +0 -0
  345. codex/static_root/assets/VCheckbox-BF_99tgB.js +0 -1
  346. codex/static_root/assets/VCheckbox-BF_99tgB.js.br +0 -0
  347. codex/static_root/assets/VCheckbox-BF_99tgB.js.gz +0 -0
  348. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js +0 -1
  349. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.br +0 -0
  350. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.gz +0 -0
  351. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js +0 -1
  352. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.br +0 -0
  353. codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.gz +0 -0
  354. codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js +0 -1
  355. codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.br +0 -0
  356. codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.gz +0 -0
  357. codex/static_root/assets/VCombobox-CTRWnOuM.js +0 -1
  358. codex/static_root/assets/VCombobox-CTRWnOuM.js.br +0 -0
  359. codex/static_root/assets/VCombobox-CTRWnOuM.js.gz +0 -0
  360. codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js +0 -1
  361. codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.br +0 -0
  362. codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.gz +0 -0
  363. codex/static_root/assets/VDialog-pzwzInEh.js +0 -1
  364. codex/static_root/assets/VDialog-pzwzInEh.js.br +0 -0
  365. codex/static_root/assets/VDialog-pzwzInEh.js.gz +0 -0
  366. codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js +0 -1
  367. codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.br +0 -0
  368. codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.gz +0 -0
  369. codex/static_root/assets/VExpansionPanels-CKSq1GpG.js +0 -1
  370. codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.br +0 -0
  371. codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.gz +0 -0
  372. codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js +0 -1
  373. codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.br +0 -0
  374. codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.gz +0 -0
  375. codex/static_root/assets/VRadioGroup-CW5GobGU.js +0 -1
  376. codex/static_root/assets/VRadioGroup-CW5GobGU.js.br +0 -0
  377. codex/static_root/assets/VRadioGroup-CW5GobGU.js.gz +0 -0
  378. codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js +0 -1
  379. codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.br +0 -0
  380. codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.gz +0 -0
  381. codex/static_root/assets/VSelect-ChOlLRyH.js +0 -1
  382. codex/static_root/assets/VSelect-ChOlLRyH.js.br +0 -0
  383. codex/static_root/assets/VSelect-ChOlLRyH.js.gz +0 -0
  384. codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js +0 -1
  385. codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.br +0 -0
  386. codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.gz +0 -0
  387. codex/static_root/assets/VSelectionControl-DKOhc2zB.js +0 -1
  388. codex/static_root/assets/VSelectionControl-DKOhc2zB.js.br +0 -0
  389. codex/static_root/assets/VSelectionControl-DKOhc2zB.js.gz +0 -0
  390. codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js +0 -1
  391. codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.br +0 -0
  392. codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.gz +0 -0
  393. codex/static_root/assets/VSlideGroup-zFz5_byd.js +0 -1
  394. codex/static_root/assets/VSlideGroup-zFz5_byd.js.br +0 -0
  395. codex/static_root/assets/VSlideGroup-zFz5_byd.js.gz +0 -0
  396. codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.br +0 -0
  397. codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.gz +0 -0
  398. codex/static_root/assets/VTable-FKMTcdkF.js.br +0 -0
  399. codex/static_root/assets/VTable-FKMTcdkF.js.gz +0 -0
  400. codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.br +0 -0
  401. codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.gz +0 -0
  402. codex/static_root/assets/VTextField-Dftcik85.js.br +0 -0
  403. codex/static_root/assets/VTextField-Dftcik85.js.gz +0 -0
  404. codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.br +0 -0
  405. codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.gz +0 -0
  406. codex/static_root/assets/VWindowItem-CYCwnt3s.js.br +0 -0
  407. codex/static_root/assets/VWindowItem-CYCwnt3s.js.gz +0 -0
  408. codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js +0 -1
  409. codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.br +0 -0
  410. codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.gz +0 -0
  411. codex/static_root/assets/admin-DH_DrB0F.js +0 -1
  412. codex/static_root/assets/admin-DH_DrB0F.js.br +0 -0
  413. codex/static_root/assets/admin-DH_DrB0F.js.gz +0 -0
  414. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js +0 -30
  415. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.br +0 -0
  416. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.gz +0 -0
  417. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js +0 -30
  418. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.br +0 -0
  419. codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.gz +0 -0
  420. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
  421. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
  422. codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css +0 -1
  423. codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.br +0 -0
  424. codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.gz +0 -0
  425. codex/static_root/assets/browser-CpppBvim.css +0 -1
  426. codex/static_root/assets/browser-CpppBvim.css.br +0 -0
  427. codex/static_root/assets/browser-CpppBvim.css.gz +0 -0
  428. codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js +0 -1
  429. codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.br +0 -0
  430. codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.gz +0 -0
  431. codex/static_root/assets/browser-lKU7rqLd.js +0 -1
  432. codex/static_root/assets/browser-lKU7rqLd.js.br +0 -0
  433. codex/static_root/assets/browser-lKU7rqLd.js.gz +0 -0
  434. codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.br +0 -0
  435. codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.gz +0 -0
  436. codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.br +0 -0
  437. codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.gz +0 -0
  438. codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.br +0 -0
  439. codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.gz +0 -0
  440. codex/static_root/assets/confirm-dialog-DARLmXRf.js.br +0 -0
  441. codex/static_root/assets/confirm-dialog-DARLmXRf.js.gz +0 -0
  442. codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.br +0 -0
  443. codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.gz +0 -0
  444. codex/static_root/assets/datetime-column-LpLgWpkf.js.br +0 -0
  445. codex/static_root/assets/datetime-column-LpLgWpkf.js.gz +0 -0
  446. codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js +0 -1
  447. codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.br +0 -0
  448. codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.gz +0 -0
  449. codex/static_root/assets/filter-jRB1Ob6r.js +0 -1
  450. codex/static_root/assets/filter-jRB1Ob6r.js.br +0 -0
  451. codex/static_root/assets/filter-jRB1Ob6r.js.gz +0 -0
  452. codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js +0 -1
  453. codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.br +0 -0
  454. codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.gz +0 -0
  455. codex/static_root/assets/flag-tab-mYS2wg5e.js +0 -1
  456. codex/static_root/assets/flag-tab-mYS2wg5e.js.br +0 -0
  457. codex/static_root/assets/flag-tab-mYS2wg5e.js.gz +0 -0
  458. codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.br +0 -0
  459. codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.gz +0 -0
  460. codex/static_root/assets/group-tab-MK1lcjUU.js.br +0 -0
  461. codex/static_root/assets/group-tab-MK1lcjUU.js.gz +0 -0
  462. codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js +0 -1
  463. codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.br +0 -0
  464. codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.gz +0 -0
  465. codex/static_root/assets/http-error-HFeYnF0n.js +0 -1
  466. codex/static_root/assets/http-error-HFeYnF0n.js.br +0 -0
  467. codex/static_root/assets/http-error-HFeYnF0n.js.gz +0 -0
  468. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
  469. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  470. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  471. codex/static_root/assets/index-DUjeNg4x.js +0 -1
  472. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  473. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  474. codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.br +0 -0
  475. codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.gz +0 -0
  476. codex/static_root/assets/library-tab-DYeEBYlT.js.br +0 -0
  477. codex/static_root/assets/library-tab-DYeEBYlT.js.gz +0 -0
  478. codex/static_root/assets/main-gSW7TFDY.329223b148d4.js +0 -32
  479. codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.br +0 -0
  480. codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.gz +0 -0
  481. codex/static_root/assets/main-gSW7TFDY.js +0 -32
  482. codex/static_root/assets/main-gSW7TFDY.js.br +0 -0
  483. codex/static_root/assets/main-gSW7TFDY.js.gz +0 -0
  484. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  485. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  486. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
  487. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
  488. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
  489. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
  490. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js +0 -1
  491. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.br +0 -0
  492. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.gz +0 -0
  493. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js +0 -1
  494. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.br +0 -0
  495. codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.gz +0 -0
  496. codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js +0 -35
  497. codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.br +0 -0
  498. codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.gz +0 -0
  499. codex/static_root/assets/pdf-doc-BTdS9hUz.js +0 -35
  500. codex/static_root/assets/pdf-doc-BTdS9hUz.js.br +0 -0
  501. codex/static_root/assets/pdf-doc-BTdS9hUz.js.gz +0 -0
  502. codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js +0 -2
  503. codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.br +0 -0
  504. codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.gz +0 -0
  505. codex/static_root/assets/reader-BtSTIETq.js +0 -2
  506. codex/static_root/assets/reader-BtSTIETq.js.br +0 -0
  507. codex/static_root/assets/reader-BtSTIETq.js.gz +0 -0
  508. codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.br +0 -0
  509. codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.gz +0 -0
  510. codex/static_root/assets/reader-CF3xt6JG.css.br +0 -0
  511. codex/static_root/assets/reader-CF3xt6JG.css.gz +0 -0
  512. codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js +0 -1
  513. codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.br +0 -0
  514. codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.gz +0 -0
  515. codex/static_root/assets/relation-chips-C4Ob7ySi.js +0 -1
  516. codex/static_root/assets/relation-chips-C4Ob7ySi.js.br +0 -0
  517. codex/static_root/assets/relation-chips-C4Ob7ySi.js.gz +0 -0
  518. codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.br +0 -0
  519. codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.gz +0 -0
  520. codex/static_root/assets/settings-drawer-BJOfMtqA.css.br +0 -0
  521. codex/static_root/assets/settings-drawer-BJOfMtqA.css.gz +0 -0
  522. codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js +0 -2
  523. codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.br +0 -0
  524. codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.gz +0 -0
  525. codex/static_root/assets/settings-drawer-xSctoxZn.js +0 -2
  526. codex/static_root/assets/settings-drawer-xSctoxZn.js.br +0 -0
  527. codex/static_root/assets/settings-drawer-xSctoxZn.js.gz +0 -0
  528. codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js +0 -1
  529. codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.br +0 -0
  530. codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.gz +0 -0
  531. codex/static_root/assets/stats-tab-C_uC_U7o.js +0 -1
  532. codex/static_root/assets/stats-tab-C_uC_U7o.js.br +0 -0
  533. codex/static_root/assets/stats-tab-C_uC_U7o.js.gz +0 -0
  534. codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js +0 -1
  535. codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.br +0 -0
  536. codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.gz +0 -0
  537. codex/static_root/assets/task-tab-VIpy1Ula.js +0 -1
  538. codex/static_root/assets/task-tab-VIpy1Ula.js.br +0 -0
  539. codex/static_root/assets/task-tab-VIpy1Ula.js.gz +0 -0
  540. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
  541. codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
  542. codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js +0 -1
  543. codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.br +0 -0
  544. codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.gz +0 -0
  545. codex/static_root/assets/unauthorized-kEuFL5br.js +0 -1
  546. codex/static_root/assets/unauthorized-kEuFL5br.js.br +0 -0
  547. codex/static_root/assets/unauthorized-kEuFL5br.js.gz +0 -0
  548. codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js +0 -1
  549. codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.br +0 -0
  550. codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.gz +0 -0
  551. codex/static_root/assets/user-tab-CZNFTTEI.js +0 -1
  552. codex/static_root/assets/user-tab-CZNFTTEI.js.br +0 -0
  553. codex/static_root/assets/user-tab-CZNFTTEI.js.gz +0 -0
  554. codex/static_root/js/choices.23fedac2b9ba.json +0 -1
  555. codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
  556. codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
  557. codex/static_root/manifest.6ef112683dae.json +0 -642
  558. codex/static_root/manifest.6ef112683dae.json.br +0 -0
  559. codex/static_root/manifest.6ef112683dae.json.gz +0 -0
  560. codex/views/browser/browser_breadcrumbs.py +0 -175
  561. codex/views/browser/const.py +0 -3
  562. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/LICENSE +0 -0
  563. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/WHEEL +0 -0
  564. {codex-1.6.0a5.dist-info → codex-1.6.0a7.dist-info}/entry_points.txt +0 -0
@@ -1,18 +1,13 @@
1
1
  """Views for browsing comic library."""
2
2
 
3
- from copy import deepcopy
4
- from math import ceil, floor, log10
5
- from os import sep
6
- from pathlib import Path
3
+ from math import floor, log10
7
4
  from types import MappingProxyType
8
- from typing import TYPE_CHECKING, ClassVar
5
+ from typing import TYPE_CHECKING
9
6
 
10
- from django.core.paginator import EmptyPage, Paginator
11
7
  from django.db.models import Max
12
8
  from drf_spectacular.utils import extend_schema
13
9
  from rest_framework.response import Response
14
10
 
15
- from codex.exceptions import SeeOtherRedirectError
16
11
  from codex.logger.logging import get_logger
17
12
  from codex.models import (
18
13
  AdminFlag,
@@ -24,14 +19,16 @@ from codex.models import (
24
19
  Publisher,
25
20
  Series,
26
21
  StoryArc,
27
- Timestamp,
28
22
  Volume,
29
23
  )
30
24
  from codex.serializers.browser.page import BrowserPageSerializer
31
- from codex.serializers.choices import DEFAULTS
32
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
33
- from codex.views.browser.browser_breadcrumbs import BrowserBreadcrumbsView
34
- from codex.views.browser.const import MAX_OBJ_PER_PAGE
25
+ from codex.util import max_none
26
+ from codex.views.browser.title import BrowserTitleView
27
+ from codex.views.const import (
28
+ COMIC_GROUP,
29
+ FOLDER_GROUP,
30
+ STORY_ARC_GROUP,
31
+ )
35
32
 
36
33
  if TYPE_CHECKING:
37
34
  from django.db.models.query import QuerySet
@@ -40,10 +37,9 @@ if TYPE_CHECKING:
40
37
  LOG = get_logger(__name__)
41
38
 
42
39
 
43
- class BrowserView(BrowserBreadcrumbsView):
40
+ class BrowserView(BrowserTitleView):
44
41
  """Browse comics with a variety of filters and sorts."""
45
42
 
46
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
47
43
  serializer_class = BrowserPageSerializer
48
44
 
49
45
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
@@ -52,7 +48,7 @@ class BrowserView(BrowserBreadcrumbsView):
52
48
  AdminFlag.FlagChoices.IMPORT_METADATA.value: "import_metadata",
53
49
  }
54
50
  )
55
-
51
+ TARGET = "browser"
56
52
  _GROUP_INSTANCE_SELECT_RELATED: MappingProxyType[
57
53
  type[BrowserGroupModel], tuple[str | None, ...]
58
54
  ] = MappingProxyType(
@@ -66,45 +62,43 @@ class BrowserView(BrowserBreadcrumbsView):
66
62
  StoryArc: (None,),
67
63
  }
68
64
  )
69
- DEFAULT_ROUTE = MappingProxyType(
70
- {
71
- "name": "browser",
72
- "params": deepcopy(DEFAULTS["breadcrumbs"][0]),
73
- }
74
- )
75
- _OPDS_M2M_RELS = (
76
- "characters",
77
- "genres",
78
- "locations",
79
- "series_groups",
80
- "story_arc_numbers",
81
- "story_arc_numbers__story_arc",
82
- "tags",
83
- "teams",
84
- "contributors",
85
- "contributors__role",
86
- "contributors__person",
87
- )
88
- TARGET = "browser"
89
65
 
90
66
  def __init__(self, *args, **kwargs):
91
67
  """Set params for the type checker."""
92
68
  super().__init__(*args, **kwargs)
93
69
  self.is_opds_2_acquisition = False
94
70
  self.group_query: QuerySet = Comic.objects.none()
95
- self.group_instance: BrowserGroupModel | None = None
96
71
  self.valid_nav_groups: tuple[str, ...] = ()
97
72
 
98
- def _raise_redirect(self, reason, route_mask=None, settings_mask=None):
99
- """Redirect the client to a valid group url."""
100
- route = deepcopy(dict(self.DEFAULT_ROUTE))
101
- if route_mask:
102
- route["params"].update(route_mask) # type: ignore
103
- settings = deepcopy(dict(self.params))
104
- if settings_mask:
105
- settings.update(settings_mask)
106
- detail = {"route": route, "settings": settings, "reason": reason}
107
- raise SeeOtherRedirectError(detail=detail)
73
+ def _set_group_query_and_instance(self):
74
+ """Create group_class instance."""
75
+ pks = self.kwargs.get("pks")
76
+ if pks and self.group_class:
77
+ try:
78
+ select_related: tuple[str | None, ...] = (
79
+ self._GROUP_INSTANCE_SELECT_RELATED[self.group_class]
80
+ )
81
+ self.group_query = self.group_class.objects.select_related(
82
+ *select_related
83
+ ).filter(pk__in=pks)
84
+ except self.group_class.DoesNotExist:
85
+ group = self.kwargs.get("group")
86
+ page = self.kwargs.get("page")
87
+ if group == "r" and not pks and page == 1:
88
+ self.group_query = self.group_class.objects.none()
89
+ else:
90
+ reason = f"{group}__in={pks} does not exist!"
91
+ self.raise_redirect(reason, route_mask={"group": group})
92
+ elif self.group_class:
93
+ self.group_query = self.group_class.objects.none()
94
+ else:
95
+ self.group_query = Comic.objects.none()
96
+ self.group_instance = self.group_query.first()
97
+
98
+ def init_request(self):
99
+ """Initialize request."""
100
+ super().init_request()
101
+ self._set_group_query_and_instance()
108
102
 
109
103
  def get_model_group(self):
110
104
  """Get the group of the models to browse."""
@@ -112,16 +106,19 @@ class BrowserView(BrowserBreadcrumbsView):
112
106
  # A valid nav group or 'c'
113
107
  # the child of the current nav group or 'c'
114
108
  group = self.kwargs["group"]
115
- if group == self.FOLDER_GROUP:
109
+ if group == FOLDER_GROUP:
116
110
  return group
117
- if group == self.STORY_ARC_GROUP:
111
+ if group == STORY_ARC_GROUP:
118
112
  pks = self.kwargs.get("pks")
119
- return self.COMIC_GROUP if pks else group
113
+ return COMIC_GROUP if pks else group
120
114
  if group == self.valid_nav_groups[-1]:
121
115
  # special case for lowest valid group
122
- return self.COMIC_GROUP
116
+ return COMIC_GROUP
123
117
  return self.valid_nav_groups[self.valid_nav_groups.index(group) + 1]
124
118
 
119
+ ################
120
+ # MAIN QUERIES #
121
+ ################
125
122
  def _get_common_queryset(self, model):
126
123
  """Create queryset common to group & books."""
127
124
  object_filter = self.get_query_filters(model, False)
@@ -162,167 +159,16 @@ class BrowserView(BrowserBreadcrumbsView):
162
159
  count = 0
163
160
  return qs, count
164
161
 
165
- def _set_group_query_and_instance(self):
166
- """Create group_class instance."""
167
- pks = self.kwargs.get("pks")
168
- if pks and self.group_class:
169
- try:
170
- select_related: tuple[str | None, ...] = (
171
- self._GROUP_INSTANCE_SELECT_RELATED[self.group_class]
172
- )
173
- self.group_query = self.group_class.objects.select_related(
174
- *select_related
175
- ).filter(pk__in=pks)
176
- except self.group_class.DoesNotExist:
177
- group = self.kwargs.get("group")
178
- page = self.kwargs.get("page")
179
- if group == "r" and not pks and page == 1:
180
- self.group_query = self.group_class.objects.none()
181
- else:
182
- reason = f"{group}__in={pks} does not exist!"
183
- self._raise_redirect(reason, route_mask={"group": group})
184
- elif self.group_class:
185
- self.group_query = self.group_class.objects.none()
162
+ def _get_page_mtime(self, group_qs):
163
+ if self.group_instance:
164
+ page_updated_at_max = self.group_instance.updated_at
186
165
  else:
187
- self.group_query = Comic.objects.none()
188
- self.group_instance = self.group_query.first()
189
-
190
- def _get_root_group_name(self):
191
- if not self.model:
192
- reason = "No model set in browser"
193
- raise ValueError(reason)
194
- plural = self.model._meta.verbose_name_plural
195
- if not plural:
196
- reason = f"No plural name for {self.model}"
197
- raise ValueError(reason)
198
- return plural.capitalize()
199
-
200
- def _get_folder_parent_name(self):
201
- """Get title for a folder browser page."""
202
- group_instance: Folder = self.group_instance # type: ignore
203
- if not group_instance.path:
204
- return ""
205
-
206
- parent_path = Path(group_instance.path).parent
207
- if not self.is_admin():
208
- try:
209
- # remove library path for not admins
210
- library_path_parent = Path(group_instance.library.path).parent
211
- parent_path = parent_path.relative_to(library_path_parent)
212
- except ValueError:
213
- parent_path = ""
214
-
215
- parent_path_str = str(parent_path)
216
- parent_path_str = parent_path_str.removeprefix(".")
217
- if parent_path_str and parent_path_str[0] != sep:
218
- parent_path_str = sep + parent_path_str
219
- return parent_path_str
220
-
221
- def _get_browser_page_title(self):
222
- """Get the proper title for this browse level."""
223
- group_count = 0
224
- group_name = ""
225
- pks = self.kwargs.get("pks")
226
- if not pks:
227
- group_name = self._get_root_group_name()
228
- else:
229
- group_instance = self.group_instance
230
- if group_instance:
231
- if isinstance(group_instance, Volume):
232
- group_count = group_instance.series.volume_count
233
- elif isinstance(group_instance, Comic):
234
- group_count = group_instance.volume.issue_count
235
- group_name = group_instance.name
236
-
237
- return {
238
- "group_name": group_name,
239
- "group_count": group_count,
240
- }
241
-
242
- def _check_page_in_bounds(self, total_count):
243
- """Redirect page out of bounds."""
244
- pks = self.kwargs.get("pks")
245
- page = self.kwargs.get("page", 1)
246
- num_pages = ceil(total_count / MAX_OBJ_PER_PAGE)
247
- if (not pks and page == 1) or (page >= 1 and page <= num_pages):
248
- # Don't redirect if on the root page for the group.
249
- # Or page within valid range.
250
- return num_pages
251
-
252
- # Adjust route mask for redirect
253
- group = self.kwargs.get("group")
254
- if num_pages and page > 1:
255
- # Try page 1 first.
256
- new_page = num_pages if num_pages and page > num_pages else 1
257
- pks = pks if pks else "0"
258
- else:
259
- # Redirect to root for the group.
260
- pks = "0"
261
- new_page = 1
262
-
263
- reason = f"{page=} does not exist!"
264
- route_changes = {"group": group, "pks": pks, "page": new_page}
265
- return self._raise_redirect(reason, route_mask=route_changes)
266
-
267
- def _paginate_section(self, model, qs, page):
268
- """Paginate a group or Comic section."""
269
- orphans = 0 if self.model_group == "f" or self.params.get("q") else 5
270
- paginator = Paginator(qs, MAX_OBJ_PER_PAGE, orphans=orphans)
271
- try:
272
- qs = paginator.page(page).object_list
273
- except EmptyPage:
274
- if model != Folder:
275
- model_name = self.model.__name__ if self.model else "NO_MODEL"
276
- LOG.warning(f"No {model_name}s on page {page}")
277
- qs = model.objects.none()
166
+ page_updated_at_max = group_qs.aggregate(max=Max("updated_at"))["max"]
278
167
 
279
- return qs
280
-
281
- def _paginate_groups(self, group_qs, total_group_count):
282
- """Paginate the group object list before books."""
283
- if total_group_count:
284
- page = self.kwargs.get("page", 1)
285
- page_group_qs = self._paginate_section(self.model, group_qs, page)
286
- else:
287
- page_group_qs = self.model.objects.none() # type: ignore
288
- return page_group_qs
289
-
290
- def _paginate_books(self, book_qs, total_group_count, page_obj_count):
291
- """Paginate the book object list based on how many group/folders are showing."""
292
- if page_obj_count >= MAX_OBJ_PER_PAGE:
293
- # No books for this page
294
- page_book_qs = Comic.objects.none()
295
- else:
296
- group_remainder = total_group_count % MAX_OBJ_PER_PAGE
297
- if page_obj_count:
298
- # There are books after the groups on the same page
299
- # Manually add books without the paginator
300
- book_limit = MAX_OBJ_PER_PAGE - group_remainder
301
- page_book_qs = book_qs[:book_limit]
302
- else:
303
- # There are books after the groups on a new page
304
- if group_remainder:
305
- book_offset = MAX_OBJ_PER_PAGE - group_remainder
306
- page_book_qs = book_qs[book_offset:]
307
- else:
308
- page_book_qs = book_qs
309
-
310
- num_group_and_mixed_pages = ceil(total_group_count / MAX_OBJ_PER_PAGE)
311
- book_only_page = self.kwargs.get("page", 1) - num_group_and_mixed_pages
312
- page_book_qs = self._paginate_section(
313
- Comic, page_book_qs, book_only_page
314
- )
315
- return page_book_qs
168
+ agg_func = self.get_bookmark_updated_at_aggregate(self.model, True)
169
+ page_bookmark_updated_at = group_qs.aggregate(max=agg_func)["max"]
316
170
 
317
- def _paginate(self, group_qs, book_qs, group_count, book_count):
318
- """Paginate the queryset into a group and book object lists."""
319
- num_pages = self._check_page_in_bounds(group_count + book_count)
320
- page_group_qs = self._paginate_groups(group_qs, group_count)
321
- page_group_count = page_group_qs.count()
322
- page_book_qs = self._paginate_books(book_qs, group_count, page_group_count)
323
- page_book_count = page_book_qs.count()
324
-
325
- return page_group_qs, page_book_qs, num_pages, page_group_count, page_book_count
171
+ return max_none(page_updated_at_max, page_bookmark_updated_at)
326
172
 
327
173
  @staticmethod
328
174
  def _get_zero_pad(book_qs):
@@ -340,9 +186,12 @@ class BrowserView(BrowserBreadcrumbsView):
340
186
  group_qs, group_count = self._get_group_queryset()
341
187
  book_qs, book_count = self._get_book_queryset()
342
188
  # Paginate
343
- group_qs, book_qs, num_pages, page_group_count, page_book_count = (
344
- self._paginate(group_qs, book_qs, group_count, book_count)
189
+ group_qs, book_qs, num_pages, page_group_count, page_book_count = self.paginate(
190
+ group_qs, book_qs, group_count, book_count
345
191
  )
192
+ mtime = self._get_page_mtime(
193
+ group_qs
194
+ ) # TODO should this be after paginate or not?
346
195
  if page_group_count:
347
196
  group_qs = self.annotate_card_aggregates(group_qs, self.model)
348
197
  if page_book_count:
@@ -356,25 +205,20 @@ class BrowserView(BrowserBreadcrumbsView):
356
205
 
357
206
  recovered_group_list = self.re_cover_multi_groups(group_qs)
358
207
  total_count = page_group_count + page_book_count
359
- return recovered_group_list, book_qs, num_pages, total_count, zero_pad
208
+ return recovered_group_list, book_qs, num_pages, total_count, zero_pad, mtime
360
209
 
361
210
  def get_object(self):
362
211
  """Validate settings and get the querysets."""
363
- group_list, book_qs, num_pages, total_count, zero_pad = (
212
+ group_list, book_qs, num_pages, total_count, zero_pad, mtime = (
364
213
  self._get_group_and_books()
365
214
  )
366
215
 
367
216
  # get additional context
368
217
  parent_breadcrumbs = self.get_parent_breadcrumbs()
369
- title = self._get_browser_page_title()
218
+ title = self.get_browser_page_title()
370
219
  # needs to happen after pagination
371
220
  # runs obj list query twice :/
372
221
  libraries_exist = Library.objects.filter(covers_only=False).exists()
373
- covers_timestamp = int(
374
- Timestamp.objects.get(
375
- key=Timestamp.TimestampChoices.COVERS.value
376
- ).updated_at.timestamp()
377
- )
378
222
 
379
223
  # construct final data structure
380
224
  return MappingProxyType(
@@ -389,135 +233,11 @@ class BrowserView(BrowserBreadcrumbsView):
389
233
  "total_count": total_count,
390
234
  "admin_flags": self.admin_flags,
391
235
  "libraries_exist": libraries_exist,
392
- "covers_timestamp": covers_timestamp,
236
+ "mtime": mtime,
393
237
  }
394
238
  )
395
239
 
396
- def _get_valid_browse_top_groups(self):
397
- """Get valid top groups for the current settings.
398
-
399
- Valid top groups are determined by the Browser Settings.
400
- """
401
- valid_top_groups = []
402
-
403
- show = self.params["show"]
404
- for nav_group, allowed in show.items():
405
- if allowed:
406
- valid_top_groups.append(nav_group)
407
- # Issues is always a valid top group
408
- valid_top_groups += [self.COMIC_GROUP]
409
-
410
- return valid_top_groups
411
-
412
- def _validate_top_group(self, valid_top_groups):
413
- nav_group = self.kwargs.get("group")
414
- top_group = self.params.get("top_group")
415
- if top_group not in valid_top_groups:
416
- reason = f"top_group {top_group} not in valid nav groups, changed to "
417
- if nav_group in valid_top_groups:
418
- valid_top_group = nav_group
419
- reason += "nav group: "
420
- else:
421
- valid_top_group = valid_top_groups[0]
422
- reason += "first valid top group "
423
- reason += valid_top_group
424
- pks = self.kwargs.get("pks", ())
425
- page = self.kwargs["page"]
426
- route = {"group": nav_group, "pks": pks, "page": page}
427
- settings_mask = {"top_group": valid_top_group}
428
- self._raise_redirect(reason, route, settings_mask)
429
-
430
- def set_valid_browse_nav_groups(self, valid_top_groups):
431
- """Get valid nav groups for the current settings.
432
-
433
- Valid nav groups are the top group and below that are also
434
- enabled in browser settings.
435
-
436
- May always navigate to root 'r' nav group.
437
- """
438
- top_group = self.params["top_group"]
439
- nav_group = self.kwargs["group"]
440
- valid_nav_groups = [self.ROOT_GROUP]
441
-
442
- for possible_index, possible_nav_group in enumerate(valid_top_groups):
443
- if top_group == possible_nav_group:
444
- # all the nav groups past this point,
445
- # 'c' is obscured by the web reader url, but valid for opds
446
- tail_top_groups = valid_top_groups[possible_index:]
447
- valid_nav_groups += tail_top_groups
448
-
449
- if nav_group not in valid_nav_groups:
450
- reason = (
451
- f"Nav group {nav_group} unavailable, "
452
- f"redirect to {self.ROOT_GROUP}"
453
- )
454
- self._raise_redirect(reason)
455
- break
456
- self.valid_nav_groups = tuple(valid_nav_groups)
457
-
458
- def _validate_folder_settings(self):
459
- """Check that all the view variables for folder mode are set right."""
460
- # Check folder view admin flag
461
- if not self.admin_flags["folder_view"]:
462
- reason = "folder view disabled"
463
- valid_top_groups = self._get_valid_browse_top_groups()
464
- settings_mask = {"top_group": valid_top_groups[0]}
465
- self._raise_redirect(reason, settings_mask=settings_mask)
466
-
467
- valid_top_groups = (self.FOLDER_GROUP,)
468
- self._validate_top_group(valid_top_groups)
469
- self.valid_nav_groups = valid_top_groups
470
-
471
- def _validate_browser_group_settings(self):
472
- """Check that all the view variables for browser mode are set right."""
473
- # Validate Browser top_group
474
- # Change top_group if its not in the valid top groups
475
- valid_top_groups = self._get_valid_browse_top_groups()
476
- self._validate_top_group(valid_top_groups)
477
-
478
- # Validate pks
479
- nav_group = self.kwargs["group"]
480
- pks = self.kwargs["pks"]
481
- if nav_group == self.ROOT_GROUP and (pks and 0 not in pks):
482
- # r never has pks
483
- reason = f"Redirect r with {pks=} to pks 0"
484
- self._raise_redirect(reason)
485
-
486
- # Validate Browser nav_group
487
- # Redirect if nav group is wrong
488
- self.set_valid_browse_nav_groups(valid_top_groups)
489
-
490
- def _validate_story_arc_settings(self):
491
- """Validate story arc settings."""
492
- valid_top_groups = (self.STORY_ARC_GROUP,)
493
- self._validate_top_group(valid_top_groups)
494
- self.valid_nav_groups = valid_top_groups
495
-
496
- def validate_settings(self):
497
- """Validate group and top group settings."""
498
- group = self.kwargs["group"]
499
- if group == self.FOLDER_GROUP:
500
- self._validate_folder_settings()
501
- elif group == self.STORY_ARC_GROUP:
502
- self._validate_story_arc_settings()
503
- else:
504
- self._validate_browser_group_settings()
505
-
506
- # Validate order
507
- if self.order_key == "filename" and not self.admin_flags["folder_view"]:
508
- pks = self.kwargs["pks"]
509
- page = self.kwargs["page"]
510
- route_changes = {"group": group, "pks": pks, "page": page}
511
- reason = "order by filename not allowed by admin flag."
512
- settings_mask = {"order_by": "sort_name"}
513
- self._raise_redirect(reason, route_changes, settings_mask)
514
-
515
- def init_request(self):
516
- """Initialize request."""
517
- super().init_request()
518
- self._set_group_query_and_instance()
519
-
520
- @extend_schema(request=BrowserBreadcrumbsView.input_serializer_class)
240
+ @extend_schema(request=BrowserTitleView.input_serializer_class)
521
241
  def get(self, *_args, **_kwargs):
522
242
  """Get browser settings."""
523
243
  self.init_request()
@@ -1,7 +1,6 @@
1
1
  """View for marking comics read and unread."""
2
2
 
3
3
  from types import MappingProxyType
4
- from typing import ClassVar
5
4
 
6
5
  import pycountry
7
6
  from caseconverter import snakecase
@@ -27,7 +26,6 @@ from codex.serializers.browser.filters import (
27
26
  CharListField,
28
27
  )
29
28
  from codex.serializers.models.pycountry import PyCountrySerializer
30
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
31
29
  from codex.views.browser.filters.annotations import (
32
30
  BrowserAnnotationsFilterView,
33
31
  )
@@ -50,38 +48,32 @@ _FIELD_TO_REL_MODEL_MAP = MappingProxyType(
50
48
  ),
51
49
  }
52
50
  )
51
+ _NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
52
+ _BACK_REL_MAP = MappingProxyType(
53
+ {
54
+ ContributorPerson: "contributor__",
55
+ StoryArc: "storyarcnumber__",
56
+ IdentifierType: "identifier__",
57
+ }
58
+ )
59
+ _CLASS_REL_MAP = MappingProxyType(
60
+ {
61
+ Publisher: "publisher",
62
+ Imprint: "imprint",
63
+ Series: "series",
64
+ Volume: "volume",
65
+ Comic: "pk",
66
+ Folder: "parent_folder",
67
+ StoryArc: "story_arc_numbers__story_arc",
68
+ IdentifierType: "identifiers__identifier_type",
69
+ }
70
+ )
71
+ _SERIALIZER_MANY = False
53
72
 
54
73
 
55
74
  class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
56
75
  """Get choices for filter dialog."""
57
76
 
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
84
-
85
77
  @staticmethod
86
78
  def get_field_choices_query(comic_qs, field_name):
87
79
  """Get distinct values for the field."""
@@ -97,8 +89,8 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
97
89
  LOG.error("No model to make filter choices!")
98
90
  m2m_filter = {}
99
91
  else:
100
- model_rel: str = self._REL_MAP[self.model] # type: ignore
101
- back_rel = self._BACK_REL_MAP.get(model, "")
92
+ model_rel: str = _CLASS_REL_MAP[self.model] # type: ignore
93
+ back_rel = _BACK_REL_MAP.get(model, "")
102
94
  back_rel += "comic__"
103
95
  back_rel += model_rel
104
96
  back_rel += "__in"
@@ -137,7 +129,7 @@ class BrowserChoicesViewBase(BrowserAnnotationsFilterView):
137
129
  """Return choices."""
138
130
  self.init_request()
139
131
  obj = self.get_object()
140
- serializer = self.get_serializer(obj, many=self._SERIALIZER_MANY)
132
+ serializer = self.get_serializer(obj, many=_SERIALIZER_MANY)
141
133
  return Response(serializer.data)
142
134
 
143
135
 
@@ -225,7 +217,7 @@ class BrowserChoicesView(BrowserChoicesViewBase):
225
217
  # rows. :(
226
218
  if self.does_m2m_null_exist(comic_qs, rel):
227
219
  choices = list(qs)
228
- choices.append(self._NULL_NAMED_ROW)
220
+ choices.append(_NULL_NAMED_ROW)
229
221
  else:
230
222
  choices = qs
231
223
  return choices
@@ -5,10 +5,10 @@ from django.db.models.expressions import Value
5
5
  from django.db.models.fields import PositiveSmallIntegerField
6
6
 
7
7
  from codex.models.comic import Comic
8
- from codex.views.browser.base import BrowserBaseView
8
+ from codex.views.browser.validate import BrowserValidateView
9
9
 
10
10
 
11
- class BrowserAnnotationsFilterView(BrowserBaseView):
11
+ class BrowserAnnotationsFilterView(BrowserValidateView):
12
12
  """Annotations that also filter."""
13
13
 
14
14
  ONE_INTEGERFIELD = Value(1, PositiveSmallIntegerField())
@@ -2,20 +2,16 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.serializers.choices import CHOICES
6
-
7
5
 
8
6
  class BookmarkFilterMixin:
9
7
  """BookmarkFilter view methods."""
10
8
 
11
- _BOOKMARK_FILTERS = frozenset(set(CHOICES["bookmarkFilter"].keys()) - {"ALL"})
12
-
13
9
  def get_bm_rel(self, model):
14
10
  """Create bookmark relation."""
15
11
  rel_prefix = self.get_rel_prefix(model) # type: ignore
16
12
  return rel_prefix + "bookmark"
17
13
 
18
- def _get_my_bookmark_filter(self, bm_rel):
14
+ def get_my_bookmark_filter(self, bm_rel):
19
15
  """Get a filter for my session or user defined bookmarks."""
20
16
  if self.request.user.is_authenticated: # type: ignore
21
17
  my_bookmarks_kwargs = {f"{bm_rel}__user": self.request.user} # type: ignore
@@ -28,11 +24,15 @@ class BookmarkFilterMixin:
28
24
 
29
25
  def get_bookmark_filter(self, model):
30
26
  """Build bookmark query."""
31
- choice = self.params["filters"].get("bookmark", "ALL") # type: ignore
32
- if choice in self._BOOKMARK_FILTERS:
27
+ choice = self.params["filters"].get("bookmark", "") # type: ignore
28
+ if choice:
33
29
  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"):
30
+ my_bookmark_filter = self.get_my_bookmark_filter(bm_rel)
31
+ if choice == "READ":
32
+ bookmark_filter = my_bookmark_filter & Q(
33
+ **{f"{bm_rel}__finished": True}
34
+ )
35
+ else:
36
36
  my_not_finished_filter = my_bookmark_filter & Q(
37
37
  **{f"{bm_rel}__finished__in": (False, None)}
38
38
  )
@@ -42,10 +42,6 @@ class BookmarkFilterMixin:
42
42
  bookmark_filter = my_not_finished_filter & Q(
43
43
  **{f"{bm_rel}__page__gt": 0}
44
44
  )
45
- else: # READ
46
- bookmark_filter = my_bookmark_filter & Q(
47
- **{f"{bm_rel}__finished": True}
48
- )
49
45
  else:
50
46
  bookmark_filter = Q()
51
47
  return bookmark_filter