codex 1.6.0a6__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 (548) hide show
  1. codex/exceptions.py +1 -0
  2. codex/librarian/importer/aggregate_metadata.py +2 -10
  3. codex/librarian/importer/{covers.py → cache.py} +2 -2
  4. codex/librarian/importer/importerd.py +5 -3
  5. codex/librarian/janitor/janitor.py +1 -1
  6. codex/librarian/librariand.py +1 -1
  7. codex/permissions.py +1 -1
  8. codex/serializers/admin.py +1 -2
  9. codex/serializers/browser/filters.py +8 -101
  10. codex/serializers/browser/mixins.py +20 -3
  11. codex/serializers/browser/page.py +2 -2
  12. codex/serializers/browser/settings.py +2 -0
  13. codex/serializers/fields.py +123 -0
  14. codex/serializers/mtime.py +25 -0
  15. codex/serializers/opds/v2.py +7 -11
  16. codex/serializers/opds/v2_unused.py +5 -3
  17. codex/serializers/reader.py +5 -2
  18. codex/serializers/route.py +7 -3
  19. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js +1 -0
  20. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.br +0 -0
  21. codex/static_root/assets/VCheckbox-BOUtyxuo.c690f0cdbe48.js.gz +0 -0
  22. codex/static_root/assets/VCheckbox-BOUtyxuo.js +1 -0
  23. codex/static_root/assets/VCheckbox-BOUtyxuo.js.br +0 -0
  24. codex/static_root/assets/VCheckbox-BOUtyxuo.js.gz +0 -0
  25. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js +1 -0
  26. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.br +0 -0
  27. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.e2b8cbdb92c9.js.gz +0 -0
  28. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js +1 -0
  29. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.br +0 -0
  30. codex/static_root/assets/VCheckboxBtn-B1-m5pEh.js.gz +0 -0
  31. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js +1 -0
  32. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.br +0 -0
  33. codex/static_root/assets/VCombobox-DjkDXe33.22617ea193b1.js.gz +0 -0
  34. codex/static_root/assets/VCombobox-DjkDXe33.js +1 -0
  35. codex/static_root/assets/VCombobox-DjkDXe33.js.br +0 -0
  36. codex/static_root/assets/VCombobox-DjkDXe33.js.gz +0 -0
  37. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js +1 -0
  38. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.br +0 -0
  39. codex/static_root/assets/VDialog-X0zn9AGX.0d89c749b9a6.js.gz +0 -0
  40. codex/static_root/assets/VDialog-X0zn9AGX.js +1 -0
  41. codex/static_root/assets/VDialog-X0zn9AGX.js.br +0 -0
  42. codex/static_root/assets/VDialog-X0zn9AGX.js.gz +0 -0
  43. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js +1 -0
  44. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.br +0 -0
  45. codex/static_root/assets/VExpansionPanels-CRevojaF.43d65c777c58.js.gz +0 -0
  46. codex/static_root/assets/VExpansionPanels-CRevojaF.js +1 -0
  47. codex/static_root/assets/VExpansionPanels-CRevojaF.js.br +0 -0
  48. codex/static_root/assets/VExpansionPanels-CRevojaF.js.gz +0 -0
  49. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js +1 -0
  50. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.br +0 -0
  51. codex/static_root/assets/VRadioGroup-8nrrxjDv.2b4dfb984a8c.js.gz +0 -0
  52. codex/static_root/assets/VRadioGroup-8nrrxjDv.js +1 -0
  53. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.br +0 -0
  54. codex/static_root/assets/VRadioGroup-8nrrxjDv.js.gz +0 -0
  55. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js +1 -0
  56. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.br +0 -0
  57. codex/static_root/assets/VSelect-DgfIKRnC.fa7a73a9c415.js.gz +0 -0
  58. codex/static_root/assets/VSelect-DgfIKRnC.js +1 -0
  59. codex/static_root/assets/VSelect-DgfIKRnC.js.br +0 -0
  60. codex/static_root/assets/VSelect-DgfIKRnC.js.gz +0 -0
  61. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js +1 -0
  62. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.br +0 -0
  63. codex/static_root/assets/VSelectionControl-CGu0oz0K.3030b7a270d6.js.gz +0 -0
  64. codex/static_root/assets/VSelectionControl-CGu0oz0K.js +1 -0
  65. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.br +0 -0
  66. codex/static_root/assets/VSelectionControl-CGu0oz0K.js.gz +0 -0
  67. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js +1 -0
  68. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.br +0 -0
  69. codex/static_root/assets/VSlideGroup-D_oNvCOd.71a7101cdddd.js.gz +0 -0
  70. codex/static_root/assets/VSlideGroup-D_oNvCOd.js +1 -0
  71. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.br +0 -0
  72. codex/static_root/assets/VSlideGroup-D_oNvCOd.js.gz +0 -0
  73. codex/static_root/assets/{VTable-C0gAriFq.fa6e4b60d4ef.js → VTable-C7pKI7gU.087912f706f5.js} +1 -1
  74. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.br +0 -0
  75. codex/static_root/assets/VTable-C7pKI7gU.087912f706f5.js.gz +0 -0
  76. codex/static_root/assets/{VTable-C0gAriFq.js → VTable-C7pKI7gU.js} +1 -1
  77. codex/static_root/assets/VTable-C7pKI7gU.js.br +0 -0
  78. codex/static_root/assets/VTable-C7pKI7gU.js.gz +0 -0
  79. codex/static_root/assets/{VTextField-Da_Hs7ls.c295f24c251e.js → VTextField-Bs8oq9mk.7999fef12a03.js} +1 -1
  80. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.br +0 -0
  81. codex/static_root/assets/VTextField-Bs8oq9mk.7999fef12a03.js.gz +0 -0
  82. codex/static_root/assets/{VTextField-Da_Hs7ls.js → VTextField-Bs8oq9mk.js} +1 -1
  83. codex/static_root/assets/VTextField-Bs8oq9mk.js.br +0 -0
  84. codex/static_root/assets/VTextField-Bs8oq9mk.js.gz +0 -0
  85. codex/static_root/assets/{VWindowItem-CFhKCj49.b2458002a937.js → VWindowItem-iyZ1XOkP.409f7d292253.js} +1 -1
  86. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.br +0 -0
  87. codex/static_root/assets/VWindowItem-iyZ1XOkP.409f7d292253.js.gz +0 -0
  88. codex/static_root/assets/{VWindowItem-CFhKCj49.js → VWindowItem-iyZ1XOkP.js} +1 -1
  89. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.br +0 -0
  90. codex/static_root/assets/VWindowItem-iyZ1XOkP.js.gz +0 -0
  91. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js +1 -0
  92. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.br +0 -0
  93. codex/static_root/assets/admin-B0pCBjma.2407da79bd20.js.gz +0 -0
  94. codex/static_root/assets/admin-B0pCBjma.js +1 -0
  95. codex/static_root/assets/admin-B0pCBjma.js.br +0 -0
  96. codex/static_root/assets/admin-B0pCBjma.js.gz +0 -0
  97. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js +30 -0
  98. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.br +0 -0
  99. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.5e2e1a4a255d.js.gz +0 -0
  100. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js +30 -0
  101. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.br +0 -0
  102. codex/static_root/assets/admin-drawer-panel-BxoNUHQr.js.gz +0 -0
  103. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css → admin-drawer-panel-o87BwD38.ce39427c8201.css} +1 -1
  104. codex/static_root/assets/admin-drawer-panel-o87BwD38.ce39427c8201.css.br +0 -0
  105. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz → admin-drawer-panel-o87BwD38.ce39427c8201.css.gz} +0 -0
  106. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css → admin-drawer-panel-o87BwD38.css} +1 -1
  107. codex/static_root/assets/admin-drawer-panel-o87BwD38.css.br +0 -0
  108. codex/static_root/assets/{admin-drawer-panel-BHdNPwzF.css.gz → admin-drawer-panel-o87BwD38.css.gz} +0 -0
  109. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js +1 -0
  110. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.br +0 -0
  111. codex/static_root/assets/browser-DbyAjCNh.577a228a966b.js.gz +0 -0
  112. codex/static_root/assets/browser-DbyAjCNh.js +1 -0
  113. codex/static_root/assets/browser-DbyAjCNh.js.br +0 -0
  114. codex/static_root/assets/browser-DbyAjCNh.js.gz +0 -0
  115. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css +1 -0
  116. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.br +0 -0
  117. codex/static_root/assets/browser-VnzNj1t3.cff861ad2ca3.css.gz +0 -0
  118. codex/static_root/assets/browser-VnzNj1t3.css +1 -0
  119. codex/static_root/assets/browser-VnzNj1t3.css.br +0 -0
  120. codex/static_root/assets/browser-VnzNj1t3.css.gz +0 -0
  121. codex/static_root/assets/{change-password-dialog-JYM9aAR9.f71d378f4fa6.js → change-password-dialog-DZ9dP8_F.930f4cae3cb0.js} +1 -1
  122. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.br +0 -0
  123. codex/static_root/assets/change-password-dialog-DZ9dP8_F.930f4cae3cb0.js.gz +0 -0
  124. codex/static_root/assets/{change-password-dialog-JYM9aAR9.js → change-password-dialog-DZ9dP8_F.js} +1 -1
  125. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.br +0 -0
  126. codex/static_root/assets/change-password-dialog-DZ9dP8_F.js.gz +0 -0
  127. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.515858dd21e8.js → confirm-dialog-RP7JNStF.a752a7c1e697.js} +1 -1
  128. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.br +0 -0
  129. codex/static_root/assets/confirm-dialog-RP7JNStF.a752a7c1e697.js.gz +0 -0
  130. codex/static_root/assets/{confirm-dialog-ZAZ6P1Rc.js → confirm-dialog-RP7JNStF.js} +1 -1
  131. codex/static_root/assets/confirm-dialog-RP7JNStF.js.br +0 -0
  132. codex/static_root/assets/confirm-dialog-RP7JNStF.js.gz +0 -0
  133. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js +1 -0
  134. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.bec27f144aca.js.br +0 -0
  135. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js +1 -0
  136. codex/static_root/assets/copy-to-clipboard-DoUvDJVx.js.br +0 -0
  137. codex/static_root/assets/{datetime-column-BT1MSP82.9617f6b94ebf.js → datetime-column-Cu3WYyPj.54b4c3817b74.js} +1 -1
  138. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.br +0 -0
  139. codex/static_root/assets/datetime-column-Cu3WYyPj.54b4c3817b74.js.gz +0 -0
  140. codex/static_root/assets/{datetime-column-BT1MSP82.js → datetime-column-Cu3WYyPj.js} +1 -1
  141. codex/static_root/assets/datetime-column-Cu3WYyPj.js.br +0 -0
  142. codex/static_root/assets/datetime-column-Cu3WYyPj.js.gz +0 -0
  143. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js +1 -0
  144. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.br +0 -0
  145. codex/static_root/assets/filter-DAdGUt-1.3261cbcd50b5.js.gz +0 -0
  146. codex/static_root/assets/filter-DAdGUt-1.js +1 -0
  147. codex/static_root/assets/filter-DAdGUt-1.js.br +0 -0
  148. codex/static_root/assets/filter-DAdGUt-1.js.gz +0 -0
  149. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js +1 -0
  150. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.br +0 -0
  151. codex/static_root/assets/flag-tab-BP7Zs7iQ.cb16ef6363d4.js.gz +0 -0
  152. codex/static_root/assets/flag-tab-BP7Zs7iQ.js +1 -0
  153. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.br +0 -0
  154. codex/static_root/assets/flag-tab-BP7Zs7iQ.js.gz +0 -0
  155. codex/static_root/assets/{group-tab-DFma2R-E.879c37b15bbd.js → group-tab-DEwh4jfB.a95097b5e320.js} +1 -1
  156. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.br +0 -0
  157. codex/static_root/assets/group-tab-DEwh4jfB.a95097b5e320.js.gz +0 -0
  158. codex/static_root/assets/{group-tab-DFma2R-E.js → group-tab-DEwh4jfB.js} +1 -1
  159. codex/static_root/assets/group-tab-DEwh4jfB.js.br +0 -0
  160. codex/static_root/assets/group-tab-DEwh4jfB.js.gz +0 -0
  161. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js +1 -0
  162. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.br +0 -0
  163. codex/static_root/assets/http-error-xs3mIL8U.e143c472a96c.js.gz +0 -0
  164. codex/static_root/assets/http-error-xs3mIL8U.js +1 -0
  165. codex/static_root/assets/http-error-xs3mIL8U.js.br +0 -0
  166. codex/static_root/assets/http-error-xs3mIL8U.js.gz +0 -0
  167. codex/static_root/assets/{library-tab-V8DUGoHN.809c548ff347.js → library-tab-DrXvD9B2.f765f9d3bae4.js} +1 -1
  168. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.br +0 -0
  169. codex/static_root/assets/library-tab-DrXvD9B2.f765f9d3bae4.js.gz +0 -0
  170. codex/static_root/assets/{library-tab-V8DUGoHN.js → library-tab-DrXvD9B2.js} +1 -1
  171. codex/static_root/assets/library-tab-DrXvD9B2.js.br +0 -0
  172. codex/static_root/assets/library-tab-DrXvD9B2.js.gz +0 -0
  173. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js +32 -0
  174. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.br +0 -0
  175. codex/static_root/assets/main-Dy9Uqpmh.2f99b1914b3e.js.gz +0 -0
  176. codex/static_root/assets/main-Dy9Uqpmh.js +32 -0
  177. codex/static_root/assets/main-Dy9Uqpmh.js.br +0 -0
  178. codex/static_root/assets/main-Dy9Uqpmh.js.gz +0 -0
  179. codex/static_root/assets/{main-sK7HC5h4.css → main-Ztp855PW.css} +1 -1
  180. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  181. codex/static_root/assets/{main-sK7HC5h4.css.gz → main-Ztp855PW.css.gz} +0 -0
  182. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css → main-Ztp855PW.d46d718fac45.css} +1 -1
  183. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  184. codex/static_root/assets/{main-sK7HC5h4.6f04ddfc5f1a.css.gz → main-Ztp855PW.d46d718fac45.css.gz} +0 -0
  185. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js +1 -0
  186. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.br +0 -0
  187. codex/static_root/assets/pagination-toolbar-DbS_HOT1.81222ab51ef4.js.gz +0 -0
  188. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js +1 -0
  189. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.br +0 -0
  190. codex/static_root/assets/pagination-toolbar-DbS_HOT1.js.gz +0 -0
  191. codex/static_root/assets/{pagination-toolbar-ClZui9Zf.697d7ae11c21.css → pagination-toolbar-r6nbtWxg.4088ab042c0c.css} +1 -1
  192. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.br +0 -0
  193. codex/static_root/assets/pagination-toolbar-r6nbtWxg.4088ab042c0c.css.gz +0 -0
  194. codex/static_root/assets/{pagination-toolbar-ClZui9Zf.css → pagination-toolbar-r6nbtWxg.css} +1 -1
  195. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.br +0 -0
  196. codex/static_root/assets/pagination-toolbar-r6nbtWxg.css.gz +0 -0
  197. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js +305 -0
  198. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.br +0 -0
  199. codex/static_root/assets/pdf-doc-obrBwxa6.ec9bd13a5786.js.gz +0 -0
  200. codex/static_root/assets/pdf-doc-obrBwxa6.js +305 -0
  201. codex/static_root/assets/pdf-doc-obrBwxa6.js.br +0 -0
  202. codex/static_root/assets/pdf-doc-obrBwxa6.js.gz +0 -0
  203. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js +2 -0
  204. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.br +0 -0
  205. codex/static_root/assets/reader-BXKUaUeC.953d30b969ef.js.gz +0 -0
  206. codex/static_root/assets/reader-BXKUaUeC.js +2 -0
  207. codex/static_root/assets/reader-BXKUaUeC.js.br +0 -0
  208. codex/static_root/assets/reader-BXKUaUeC.js.gz +0 -0
  209. codex/static_root/assets/{reader-N2Ei_y2K.8c0cdc0d513f.css → reader-OXJ1KVaW.a463b19d0508.css} +1 -1
  210. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.br +0 -0
  211. codex/static_root/assets/reader-OXJ1KVaW.a463b19d0508.css.gz +0 -0
  212. codex/static_root/assets/{reader-N2Ei_y2K.css → reader-OXJ1KVaW.css} +1 -1
  213. codex/static_root/assets/reader-OXJ1KVaW.css.br +0 -0
  214. codex/static_root/assets/reader-OXJ1KVaW.css.gz +0 -0
  215. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js +1 -0
  216. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.br +0 -0
  217. codex/static_root/assets/relation-chips-CZJcLjc8.1d7426dab654.js.gz +0 -0
  218. codex/static_root/assets/relation-chips-CZJcLjc8.js +1 -0
  219. codex/static_root/assets/relation-chips-CZJcLjc8.js.br +0 -0
  220. codex/static_root/assets/relation-chips-CZJcLjc8.js.gz +0 -0
  221. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js +2 -0
  222. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.br +0 -0
  223. codex/static_root/assets/settings-drawer-7iTnAdjf.337eecf2fdbb.js.gz +0 -0
  224. codex/static_root/assets/settings-drawer-7iTnAdjf.js +2 -0
  225. codex/static_root/assets/settings-drawer-7iTnAdjf.js.br +0 -0
  226. codex/static_root/assets/settings-drawer-7iTnAdjf.js.gz +0 -0
  227. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js +1 -0
  228. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.br +0 -0
  229. codex/static_root/assets/stats-tab-CQsK1n8O.544bf50ffb22.js.gz +0 -0
  230. codex/static_root/assets/stats-tab-CQsK1n8O.js +1 -0
  231. codex/static_root/assets/stats-tab-CQsK1n8O.js.br +0 -0
  232. codex/static_root/assets/stats-tab-CQsK1n8O.js.gz +0 -0
  233. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js +1 -0
  234. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.br +0 -0
  235. codex/static_root/assets/task-tab-C60TLP6e.63aa929a4ed6.js.gz +0 -0
  236. codex/static_root/assets/task-tab-C60TLP6e.js +1 -0
  237. codex/static_root/assets/task-tab-C60TLP6e.js.br +0 -0
  238. codex/static_root/assets/task-tab-C60TLP6e.js.gz +0 -0
  239. codex/static_root/assets/to-case-CR9beRR0.21bb805fdab4.js +1 -0
  240. codex/static_root/assets/to-case-CR9beRR0.js +1 -0
  241. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js +1 -0
  242. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.br +0 -0
  243. codex/static_root/assets/unauthorized-Bs7NSqea.c55650fac055.js.gz +0 -0
  244. codex/static_root/assets/unauthorized-Bs7NSqea.js +1 -0
  245. codex/static_root/assets/unauthorized-Bs7NSqea.js.br +0 -0
  246. codex/static_root/assets/unauthorized-Bs7NSqea.js.gz +0 -0
  247. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js +1 -0
  248. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.br +0 -0
  249. codex/static_root/assets/user-tab-ChuH7Atm.7e80cf1fb78e.js.gz +0 -0
  250. codex/static_root/assets/user-tab-ChuH7Atm.js +1 -0
  251. codex/static_root/assets/user-tab-ChuH7Atm.js.br +0 -0
  252. codex/static_root/assets/user-tab-ChuH7Atm.js.gz +0 -0
  253. codex/static_root/js/choices.079a01e0be1a.json +1 -0
  254. codex/static_root/js/choices.079a01e0be1a.json.br +0 -0
  255. codex/static_root/js/choices.079a01e0be1a.json.gz +0 -0
  256. codex/static_root/js/choices.json +1 -1
  257. codex/static_root/js/choices.json.br +0 -0
  258. codex/static_root/js/choices.json.gz +0 -0
  259. codex/static_root/manifest.dad79c8475ae.json +629 -0
  260. codex/static_root/manifest.dad79c8475ae.json.br +0 -0
  261. codex/static_root/manifest.dad79c8475ae.json.gz +0 -0
  262. codex/static_root/manifest.json +263 -276
  263. codex/static_root/manifest.json.br +0 -0
  264. codex/static_root/manifest.json.gz +0 -0
  265. codex/static_root/staticfiles.json +1 -1
  266. codex/urls/api/auth.py +2 -1
  267. codex/urls/api/browser.py +2 -2
  268. codex/urls/api/reader.py +2 -2
  269. codex/urls/api/v3.py +2 -0
  270. codex/util.py +10 -0
  271. codex/views/admin/api_key.py +2 -6
  272. codex/views/admin/auth.py +30 -0
  273. codex/views/admin/flag.py +2 -7
  274. codex/views/admin/group.py +2 -6
  275. codex/views/admin/library.py +7 -20
  276. codex/views/admin/stats.py +7 -66
  277. codex/views/admin/tasks.py +12 -11
  278. codex/views/admin/user.py +3 -9
  279. codex/views/auth.py +31 -52
  280. codex/views/bookmark.py +7 -11
  281. codex/views/browser/{browser_annotations.py → annotations.py} +91 -61
  282. codex/views/browser/base.py +23 -71
  283. codex/views/browser/breadcrumbs.py +200 -0
  284. codex/views/browser/browser.py +64 -337
  285. codex/views/browser/choices.py +25 -33
  286. codex/views/browser/filters/annotations.py +2 -2
  287. codex/views/browser/filters/bookmark.py +9 -13
  288. codex/views/browser/filters/field.py +6 -5
  289. codex/views/browser/filters/group.py +8 -5
  290. codex/views/browser/filters/search.py +11 -8
  291. codex/views/browser/metadata.py +3 -7
  292. codex/views/browser/{browser_order_by.py → order_by.py} +2 -6
  293. codex/views/browser/paginate.py +106 -0
  294. codex/views/browser/{session.py → settings.py} +4 -3
  295. codex/views/browser/title.py +48 -0
  296. codex/views/browser/validate.py +158 -0
  297. codex/views/const.py +97 -0
  298. codex/views/cover.py +4 -6
  299. codex/views/download.py +5 -8
  300. codex/views/frontend.py +4 -2
  301. codex/views/mtime.py +70 -0
  302. codex/views/opds/auth.py +17 -0
  303. codex/views/opds/authentication_v1.py +46 -45
  304. codex/views/opds/binary.py +4 -9
  305. codex/views/opds/const.py +13 -0
  306. codex/views/opds/urls.py +5 -4
  307. codex/views/opds/util.py +2 -8
  308. codex/views/opds/v1/facets.py +6 -7
  309. codex/views/opds/v1/feed.py +10 -17
  310. codex/views/opds/v1/links.py +6 -5
  311. codex/views/opds/v1/opensearch_v1.py +2 -6
  312. codex/views/opds/v2/feed.py +12 -15
  313. codex/views/opds/v2/links.py +3 -2
  314. codex/views/opds/v2/publications.py +21 -20
  315. codex/views/opds/v2/top_links.py +2 -2
  316. codex/views/public.py +38 -0
  317. codex/views/reader/arcs.py +101 -0
  318. codex/views/reader/books.py +199 -0
  319. codex/views/reader/page.py +3 -7
  320. codex/views/reader/reader.py +53 -298
  321. codex/views/reader/{session.py → settings.py} +4 -2
  322. codex/views/session.py +74 -102
  323. codex/views/settings.py +34 -0
  324. codex/views/template.py +3 -3
  325. codex/views/util.py +31 -0
  326. codex/views/utils.py +26 -0
  327. codex/views/version.py +2 -2
  328. {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/METADATA +12 -8
  329. {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/RECORD +332 -320
  330. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js +0 -1
  331. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.br +0 -0
  332. codex/static_root/assets/VCheckbox-BS5fdM4M.a5d446e4aeb3.js.gz +0 -0
  333. codex/static_root/assets/VCheckbox-BS5fdM4M.js +0 -1
  334. codex/static_root/assets/VCheckbox-BS5fdM4M.js.br +0 -0
  335. codex/static_root/assets/VCheckbox-BS5fdM4M.js.gz +0 -0
  336. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js +0 -1
  337. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.br +0 -0
  338. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.f0c4a814ded8.js.gz +0 -0
  339. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js +0 -1
  340. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.br +0 -0
  341. codex/static_root/assets/VCheckboxBtn-m4IbdjEe.js.gz +0 -0
  342. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js +0 -1
  343. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.br +0 -0
  344. codex/static_root/assets/VCombobox-jg7SI5mn.af66f18b4c54.js.gz +0 -0
  345. codex/static_root/assets/VCombobox-jg7SI5mn.js +0 -1
  346. codex/static_root/assets/VCombobox-jg7SI5mn.js.br +0 -0
  347. codex/static_root/assets/VCombobox-jg7SI5mn.js.gz +0 -0
  348. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js +0 -1
  349. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.br +0 -0
  350. codex/static_root/assets/VDialog-DiixpkQL.f84fd1fd1f84.js.gz +0 -0
  351. codex/static_root/assets/VDialog-DiixpkQL.js +0 -1
  352. codex/static_root/assets/VDialog-DiixpkQL.js.br +0 -0
  353. codex/static_root/assets/VDialog-DiixpkQL.js.gz +0 -0
  354. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js +0 -1
  355. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.br +0 -0
  356. codex/static_root/assets/VExpansionPanels-Db5mPFue.56d0ad094a62.js.gz +0 -0
  357. codex/static_root/assets/VExpansionPanels-Db5mPFue.js +0 -1
  358. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.br +0 -0
  359. codex/static_root/assets/VExpansionPanels-Db5mPFue.js.gz +0 -0
  360. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js +0 -1
  361. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.br +0 -0
  362. codex/static_root/assets/VRadioGroup-DULWiMSI.4ae535e01d2b.js.gz +0 -0
  363. codex/static_root/assets/VRadioGroup-DULWiMSI.js +0 -1
  364. codex/static_root/assets/VRadioGroup-DULWiMSI.js.br +0 -0
  365. codex/static_root/assets/VRadioGroup-DULWiMSI.js.gz +0 -0
  366. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js +0 -1
  367. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.br +0 -0
  368. codex/static_root/assets/VSelect-BnoCfCLY.4d50fd11c77c.js.gz +0 -0
  369. codex/static_root/assets/VSelect-BnoCfCLY.js +0 -1
  370. codex/static_root/assets/VSelect-BnoCfCLY.js.br +0 -0
  371. codex/static_root/assets/VSelect-BnoCfCLY.js.gz +0 -0
  372. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js +0 -1
  373. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.br +0 -0
  374. codex/static_root/assets/VSelectionControl-BSL0qeL9.5b5835d286df.js.gz +0 -0
  375. codex/static_root/assets/VSelectionControl-BSL0qeL9.js +0 -1
  376. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.br +0 -0
  377. codex/static_root/assets/VSelectionControl-BSL0qeL9.js.gz +0 -0
  378. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js +0 -1
  379. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.br +0 -0
  380. codex/static_root/assets/VSlideGroup-BQtz8h9p.ff97dbf209e1.js.gz +0 -0
  381. codex/static_root/assets/VSlideGroup-BQtz8h9p.js +0 -1
  382. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.br +0 -0
  383. codex/static_root/assets/VSlideGroup-BQtz8h9p.js.gz +0 -0
  384. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.br +0 -0
  385. codex/static_root/assets/VTable-C0gAriFq.fa6e4b60d4ef.js.gz +0 -0
  386. codex/static_root/assets/VTable-C0gAriFq.js.br +0 -0
  387. codex/static_root/assets/VTable-C0gAriFq.js.gz +0 -0
  388. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.br +0 -0
  389. codex/static_root/assets/VTextField-Da_Hs7ls.c295f24c251e.js.gz +0 -0
  390. codex/static_root/assets/VTextField-Da_Hs7ls.js.br +0 -0
  391. codex/static_root/assets/VTextField-Da_Hs7ls.js.gz +0 -0
  392. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.br +0 -0
  393. codex/static_root/assets/VWindowItem-CFhKCj49.b2458002a937.js.gz +0 -0
  394. codex/static_root/assets/VWindowItem-CFhKCj49.js.br +0 -0
  395. codex/static_root/assets/VWindowItem-CFhKCj49.js.gz +0 -0
  396. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js +0 -1
  397. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.br +0 -0
  398. codex/static_root/assets/admin-BpGFsvOH.d7193c78d0dd.js.gz +0 -0
  399. codex/static_root/assets/admin-BpGFsvOH.js +0 -1
  400. codex/static_root/assets/admin-BpGFsvOH.js.br +0 -0
  401. codex/static_root/assets/admin-BpGFsvOH.js.gz +0 -0
  402. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
  403. codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
  404. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js +0 -30
  405. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.br +0 -0
  406. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.113c50cdf2cc.js.gz +0 -0
  407. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js +0 -30
  408. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.br +0 -0
  409. codex/static_root/assets/admin-drawer-panel-C34Ze3-5.js.gz +0 -0
  410. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js +0 -1
  411. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.br +0 -0
  412. codex/static_root/assets/browser-CRPvEdqv.f355a71f4380.js.gz +0 -0
  413. codex/static_root/assets/browser-CRPvEdqv.js +0 -1
  414. codex/static_root/assets/browser-CRPvEdqv.js.br +0 -0
  415. codex/static_root/assets/browser-CRPvEdqv.js.gz +0 -0
  416. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css +0 -1
  417. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.br +0 -0
  418. codex/static_root/assets/browser-Zqc9cR6T.b7d6ddeff130.css.gz +0 -0
  419. codex/static_root/assets/browser-Zqc9cR6T.css +0 -1
  420. codex/static_root/assets/browser-Zqc9cR6T.css.br +0 -0
  421. codex/static_root/assets/browser-Zqc9cR6T.css.gz +0 -0
  422. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.br +0 -0
  423. codex/static_root/assets/change-password-dialog-JYM9aAR9.f71d378f4fa6.js.gz +0 -0
  424. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.br +0 -0
  425. codex/static_root/assets/change-password-dialog-JYM9aAR9.js.gz +0 -0
  426. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.br +0 -0
  427. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.515858dd21e8.js.gz +0 -0
  428. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.br +0 -0
  429. codex/static_root/assets/confirm-dialog-ZAZ6P1Rc.js.gz +0 -0
  430. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.br +0 -0
  431. codex/static_root/assets/datetime-column-BT1MSP82.9617f6b94ebf.js.gz +0 -0
  432. codex/static_root/assets/datetime-column-BT1MSP82.js.br +0 -0
  433. codex/static_root/assets/datetime-column-BT1MSP82.js.gz +0 -0
  434. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js +0 -1
  435. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.br +0 -0
  436. codex/static_root/assets/filter-frSNsexx.eab1570bd32d.js.gz +0 -0
  437. codex/static_root/assets/filter-frSNsexx.js +0 -1
  438. codex/static_root/assets/filter-frSNsexx.js.br +0 -0
  439. codex/static_root/assets/filter-frSNsexx.js.gz +0 -0
  440. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js +0 -1
  441. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.br +0 -0
  442. codex/static_root/assets/flag-tab-Ci_XcDT0.a5ea640a08d6.js.gz +0 -0
  443. codex/static_root/assets/flag-tab-Ci_XcDT0.js +0 -1
  444. codex/static_root/assets/flag-tab-Ci_XcDT0.js.br +0 -0
  445. codex/static_root/assets/flag-tab-Ci_XcDT0.js.gz +0 -0
  446. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.br +0 -0
  447. codex/static_root/assets/group-tab-DFma2R-E.879c37b15bbd.js.gz +0 -0
  448. codex/static_root/assets/group-tab-DFma2R-E.js.br +0 -0
  449. codex/static_root/assets/group-tab-DFma2R-E.js.gz +0 -0
  450. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js +0 -1
  451. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.br +0 -0
  452. codex/static_root/assets/http-error-B3fQGxpt.9efc29496e30.js.gz +0 -0
  453. codex/static_root/assets/http-error-B3fQGxpt.js +0 -1
  454. codex/static_root/assets/http-error-B3fQGxpt.js.br +0 -0
  455. codex/static_root/assets/http-error-B3fQGxpt.js.gz +0 -0
  456. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js +0 -1
  457. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.br +0 -0
  458. codex/static_root/assets/index-DUjeNg4x.36d50c91b2c1.js.gz +0 -0
  459. codex/static_root/assets/index-DUjeNg4x.js +0 -1
  460. codex/static_root/assets/index-DUjeNg4x.js.br +0 -0
  461. codex/static_root/assets/index-DUjeNg4x.js.gz +0 -0
  462. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.br +0 -0
  463. codex/static_root/assets/library-tab-V8DUGoHN.809c548ff347.js.gz +0 -0
  464. codex/static_root/assets/library-tab-V8DUGoHN.js.br +0 -0
  465. codex/static_root/assets/library-tab-V8DUGoHN.js.gz +0 -0
  466. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js +0 -32
  467. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.br +0 -0
  468. codex/static_root/assets/main-Cavvqkyp.a9c4df906406.js.gz +0 -0
  469. codex/static_root/assets/main-Cavvqkyp.js +0 -32
  470. codex/static_root/assets/main-Cavvqkyp.js.br +0 -0
  471. codex/static_root/assets/main-Cavvqkyp.js.gz +0 -0
  472. codex/static_root/assets/main-sK7HC5h4.6f04ddfc5f1a.css.br +0 -0
  473. codex/static_root/assets/main-sK7HC5h4.css.br +0 -0
  474. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
  475. codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
  476. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
  477. codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
  478. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js +0 -1
  479. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.br +0 -0
  480. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.b2eb1e0c6985.js.gz +0 -0
  481. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js +0 -1
  482. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.br +0 -0
  483. codex/static_root/assets/pagination-toolbar-Dg9tH1Ju.js.gz +0 -0
  484. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js +0 -35
  485. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.br +0 -0
  486. codex/static_root/assets/pdf-doc-4PKLN-Z3.62d8de4d1fd8.js.gz +0 -0
  487. codex/static_root/assets/pdf-doc-4PKLN-Z3.js +0 -35
  488. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.br +0 -0
  489. codex/static_root/assets/pdf-doc-4PKLN-Z3.js.gz +0 -0
  490. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js +0 -2
  491. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.br +0 -0
  492. codex/static_root/assets/reader-C39uGGxn.67b0f324cd5e.js.gz +0 -0
  493. codex/static_root/assets/reader-C39uGGxn.js +0 -2
  494. codex/static_root/assets/reader-C39uGGxn.js.br +0 -0
  495. codex/static_root/assets/reader-C39uGGxn.js.gz +0 -0
  496. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.br +0 -0
  497. codex/static_root/assets/reader-N2Ei_y2K.8c0cdc0d513f.css.gz +0 -0
  498. codex/static_root/assets/reader-N2Ei_y2K.css.br +0 -0
  499. codex/static_root/assets/reader-N2Ei_y2K.css.gz +0 -0
  500. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js +0 -1
  501. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.br +0 -0
  502. codex/static_root/assets/relation-chips-CAC5ytqH.f205d920ffea.js.gz +0 -0
  503. codex/static_root/assets/relation-chips-CAC5ytqH.js +0 -1
  504. codex/static_root/assets/relation-chips-CAC5ytqH.js.br +0 -0
  505. codex/static_root/assets/relation-chips-CAC5ytqH.js.gz +0 -0
  506. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js +0 -2
  507. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.br +0 -0
  508. codex/static_root/assets/settings-drawer-CT73ieiV.fac6f69e86ff.js.gz +0 -0
  509. codex/static_root/assets/settings-drawer-CT73ieiV.js +0 -2
  510. codex/static_root/assets/settings-drawer-CT73ieiV.js.br +0 -0
  511. codex/static_root/assets/settings-drawer-CT73ieiV.js.gz +0 -0
  512. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js +0 -1
  513. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.br +0 -0
  514. codex/static_root/assets/stats-tab-C6qAnV6-.f08c46b5f141.js.gz +0 -0
  515. codex/static_root/assets/stats-tab-C6qAnV6-.js +0 -1
  516. codex/static_root/assets/stats-tab-C6qAnV6-.js.br +0 -0
  517. codex/static_root/assets/stats-tab-C6qAnV6-.js.gz +0 -0
  518. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js +0 -1
  519. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.br +0 -0
  520. codex/static_root/assets/task-tab-JcG2vvgt.eed6e03db487.js.gz +0 -0
  521. codex/static_root/assets/task-tab-JcG2vvgt.js +0 -1
  522. codex/static_root/assets/task-tab-JcG2vvgt.js.br +0 -0
  523. codex/static_root/assets/task-tab-JcG2vvgt.js.gz +0 -0
  524. codex/static_root/assets/to-case-kUm8NPpW.5b3369f511b8.js +0 -1
  525. codex/static_root/assets/to-case-kUm8NPpW.js +0 -1
  526. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js +0 -1
  527. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.br +0 -0
  528. codex/static_root/assets/unauthorized-28bmiwAU.c74825b5c16f.js.gz +0 -0
  529. codex/static_root/assets/unauthorized-28bmiwAU.js +0 -1
  530. codex/static_root/assets/unauthorized-28bmiwAU.js.br +0 -0
  531. codex/static_root/assets/unauthorized-28bmiwAU.js.gz +0 -0
  532. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js +0 -1
  533. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.br +0 -0
  534. codex/static_root/assets/user-tab-BOq4BMMc.b64ac15dd772.js.gz +0 -0
  535. codex/static_root/assets/user-tab-BOq4BMMc.js +0 -1
  536. codex/static_root/assets/user-tab-BOq4BMMc.js.br +0 -0
  537. codex/static_root/assets/user-tab-BOq4BMMc.js.gz +0 -0
  538. codex/static_root/js/choices.23fedac2b9ba.json +0 -1
  539. codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
  540. codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
  541. codex/static_root/manifest.ad9da9714e46.json +0 -642
  542. codex/static_root/manifest.ad9da9714e46.json.br +0 -0
  543. codex/static_root/manifest.ad9da9714e46.json.gz +0 -0
  544. codex/views/browser/browser_breadcrumbs.py +0 -175
  545. codex/views/browser/const.py +0 -3
  546. {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/LICENSE +0 -0
  547. {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/WHEEL +0 -0
  548. {codex-1.6.0a6.dist-info → codex-1.6.0a7.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,8 @@
1
1
  """Views for reading comic books."""
2
2
 
3
- from copy import deepcopy
4
3
  from types import MappingProxyType
5
4
 
6
5
  from comicbox.box import Comicbox
7
- from django.db.models import F, IntegerField, Value
8
6
  from django.urls import reverse
9
7
  from drf_spectacular.types import OpenApiTypes
10
8
  from drf_spectacular.utils import OpenApiParameter, extend_schema
@@ -14,41 +12,23 @@ from rest_framework.response import Response
14
12
  from codex.librarian.importer.tasks import LazyImportComicsTask
15
13
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
16
14
  from codex.logger.logging import get_logger
17
- from codex.models import AdminFlag, Bookmark, Comic
18
15
  from codex.serializers.reader import ReaderArcSerializer, ReaderComicsSerializer
19
16
  from codex.serializers.redirect import ReaderRedirectSerializer
20
- from codex.views.bookmark import BookmarkBaseView
21
- from codex.views.mixins import SharedAnnotationsMixin
22
- from codex.views.session import BrowserSessionViewBase
17
+ from codex.views.reader.arcs import ReaderArcsView
23
18
 
24
19
  LOG = get_logger(__name__)
25
-
26
-
27
- _MIN_CRUMB_WALKBACK_LEN = 3
20
+ _VALID_ARC_GROUPS = frozenset({"f", "s", "a"})
28
21
 
29
22
 
30
23
  class ReaderView(
31
- BookmarkBaseView,
32
- BrowserSessionViewBase,
33
- SharedAnnotationsMixin,
24
+ ReaderArcsView,
34
25
  ):
35
26
  """Get info for displaying comic pages."""
36
27
 
37
28
  serializer_class = ReaderComicsSerializer
38
29
  input_serializer_class = ReaderArcSerializer
39
30
 
40
- SETTINGS_ATTRS = ("fit_to", "two_pages", "reading_direction")
41
- _COMIC_FIELDS = (
42
- "file_type",
43
- "issue_number",
44
- "issue_suffix",
45
- "page_count",
46
- "series",
47
- "volume",
48
- "reading_direction",
49
- "updated_at",
50
- )
51
- _VALID_ARC_GROUPS = frozenset({"f", "s", "a"})
31
+ SESSION_KEY = "reader"
52
32
  TARGET = "reader"
53
33
 
54
34
  def __init__(self, *args, **kwargs):
@@ -56,238 +36,6 @@ class ReaderView(
56
36
  super().__init__(*args, **kwargs)
57
37
  self.series_pks: tuple[int, ...] = ()
58
38
 
59
- def _get_series_pks_from_breadcrumbs(self):
60
- """Get Multi-Group pks from the breadcrumbs."""
61
- if self.series_pks:
62
- return self.series_pks
63
- breadcrumbs = self.get_from_session("breadcrumbs")
64
- if breadcrumbs:
65
- crumb = breadcrumbs[-1]
66
- crumb_group = crumb.get("group")
67
- if crumb_group == "v" and len(breadcrumbs) >= _MIN_CRUMB_WALKBACK_LEN:
68
- crumb = breadcrumbs[-2]
69
- crumb_group = crumb.get("group")
70
- if crumb_group == "s":
71
- self.series_pks = crumb.get("pks", ())
72
-
73
- return self.series_pks
74
-
75
- def _get_reader_arc_pks(
76
- self, arc, arc_pk_select_related, prefetch_related, arc_pk_rel
77
- ):
78
- """Get the nav filter."""
79
- arc_pks = arc.get("pks", ())
80
- if not arc_pks:
81
- comic_pk = self.kwargs["pk"]
82
- try:
83
- arc_pk_qs = Comic.objects.filter(pk=comic_pk)
84
- arc_pk_qs = arc_pk_qs.select_related(*arc_pk_select_related)
85
- arc_pk_qs = arc_pk_qs.prefetch_related(*prefetch_related)
86
- arc_pk = arc_pk_qs.values_list(arc_pk_rel, flat=True)[0]
87
- except IndexError:
88
- arc_pk = 0
89
-
90
- if arc_pk_rel == "series__pk":
91
- multi_arc_pks = self._get_series_pks_from_breadcrumbs()
92
- if not arc_pk or arc_pk in multi_arc_pks:
93
- arc_pks = multi_arc_pks
94
- if not arc_pks:
95
- arc_pks = (arc_pk,)
96
-
97
- return arc_pks
98
-
99
- def _get_comics_list(self):
100
- """Get the reader naviation group filter."""
101
- select_related = ("series", "volume")
102
- prefetch_related = ()
103
-
104
- arc = self.params.get("arc", {})
105
-
106
- arc_group = arc.get("group", "s")
107
- if arc_group == "a":
108
- # for story arcs
109
- rel = "story_arc_numbers__story_arc"
110
- fields = self._COMIC_FIELDS
111
- arc_pk_rel = "story_arc_numbers__story_arc__pk"
112
- prefetch_related = (*prefetch_related, "story_arc_numbers__story_arc")
113
- arc_index = F("story_arc_numbers__number")
114
- ordering = ("arc_index", "date")
115
- arc_pk_select_related = ()
116
- elif arc_group == self.FOLDER_GROUP:
117
- # folder mode
118
- rel = "parent_folder"
119
- fields = (*self._COMIC_FIELDS, "parent_folder")
120
- arc_pk_rel = "parent_folder__pk"
121
- select_related = (*select_related, "parent_folder")
122
- arc_index = Value(None, IntegerField())
123
- ordering = ("path", "pk")
124
- arc_pk_select_related = ("parent_folder",)
125
- else:
126
- # browser mode.
127
- rel = "series"
128
- fields = self._COMIC_FIELDS
129
- arc_pk_rel = "series__pk"
130
- arc_index = Value(None, IntegerField())
131
- ordering = ()
132
- arc_pk_select_related = ("series",)
133
-
134
- arc_pks = self._get_reader_arc_pks(
135
- arc,
136
- arc_pk_select_related,
137
- prefetch_related,
138
- arc_pk_rel,
139
- )
140
- nav_filter = {f"{rel}__in": arc_pks}
141
- group_acl_filter = self.get_group_acl_filter(Comic)
142
-
143
- qs = (
144
- Comic.objects.filter(group_acl_filter)
145
- .filter(**nav_filter)
146
- .select_related(*select_related)
147
- .prefetch_related(*prefetch_related)
148
- .only(*fields)
149
- .annotate(
150
- issue_count=F("volume__issue_count"),
151
- )
152
- .annotate(
153
- arc_pk=F(arc_pk_rel),
154
- arc_index=arc_index,
155
- )
156
- .annotate(mtime=F("updated_at"))
157
- )
158
- qs = self.annotate_group_names(qs, Comic)
159
- if arc_group == "s":
160
- show = deepcopy(self.get_from_session("show"))
161
- show.pop("p", None)
162
- show.pop("i", None)
163
- qs, comic_sort_names = self.alias_sort_names(
164
- qs, Comic, pks=arc_pks, model_group="i", show=show
165
- )
166
- ordering = (
167
- *comic_sort_names,
168
- "issue_number",
169
- "issue_suffix",
170
- "sort_name",
171
- )
172
- return qs.order_by(*ordering), arc_group
173
-
174
- def _append_with_settings(self, book, bookmark_filter):
175
- """Append bookmark to book list."""
176
- book.settings = (
177
- Bookmark.objects.filter(**bookmark_filter, comic=book)
178
- .only(*self.SETTINGS_ATTRS)
179
- .first()
180
- )
181
- return book
182
-
183
- def _get_book_collection(self):
184
- """Get the -1, +1 window around the current issue.
185
-
186
- Uses iteration in python. There are some complicated ways of
187
- doing this with __gt[0] & __lt[0] in the db, but I think they
188
- might be even more expensive.
189
-
190
- Yields 1 to 3 books
191
- """
192
- comics, arc_group = self._get_comics_list()
193
- bookmark_filter = self.get_bookmark_filter()
194
- books = {}
195
- prev_book = None
196
- pk = self.kwargs.get("pk")
197
- for index, book in enumerate(comics):
198
- if books:
199
- # after match set next comic and break
200
- books["next"] = self._append_with_settings(book, bookmark_filter)
201
- break
202
- if book.pk == pk:
203
- # first match. set previous and current comic
204
- if prev_book:
205
- books["prev"] = self._append_with_settings(
206
- prev_book, bookmark_filter
207
- )
208
- # create extra current book attrs:
209
- if book.arc_index is None: # type: ignore
210
- book.arc_index = index + 1 # type: ignore
211
- book.filename = book.get_filename() # type: ignore
212
- book.arc_group = arc_group
213
- book.arc_count = comics.count()
214
- books["current"] = self._append_with_settings(book, bookmark_filter)
215
- else:
216
- # Haven't matched yet, so set the previous comic
217
- prev_book = book
218
- return books
219
-
220
- def _get_folder_arc(self, book):
221
- """Create the folder arc."""
222
- efv_flag = (
223
- AdminFlag.objects.only("on")
224
- .get(key=AdminFlag.FlagChoices.FOLDER_VIEW.value)
225
- .on
226
- )
227
-
228
- if efv_flag:
229
- folder = book.parent_folder
230
- folder_arc = {
231
- "group": self.FOLDER_GROUP,
232
- "pks": (folder.pk,),
233
- "name": folder.name,
234
- }
235
- else:
236
- folder_arc = None
237
- return folder_arc
238
-
239
- def _get_series_arc(self, book):
240
- """Create the series arc."""
241
- series = book.series
242
- if series:
243
- arc_pks = self._get_series_pks_from_breadcrumbs()
244
- if book.series.pk not in arc_pks:
245
- arc_pks = (book.series.pk,)
246
- arc = (
247
- {"group": "s", "pks": arc_pks, "name": series.name} if series else None
248
- )
249
- else:
250
- arc = None
251
- return arc
252
-
253
- def _get_story_arcs(self, book):
254
- """Create story arcs."""
255
- sas = []
256
- for san in book.story_arc_numbers.all():
257
- sa = san.story_arc
258
- arc = {
259
- "group": "a",
260
- "pks": (sa.pk,),
261
- "name": sa.name,
262
- }
263
- sas.append(arc)
264
- return sorted(sas, key=lambda x: x["name"])
265
-
266
- def _get_arcs(self, book):
267
- """Get all series/folder/story arcs."""
268
- # create top arcs
269
- folder_arc = self._get_folder_arc(book)
270
- series_arc = self._get_series_arc(book)
271
-
272
- # order top arcs
273
- top_group = self.params.get("top_group")
274
- if top_group == self.FOLDER_GROUP and folder_arc:
275
- arc = folder_arc
276
- other_arc = series_arc
277
- else:
278
- arc = series_arc
279
- other_arc = folder_arc
280
-
281
- arcs = []
282
- arcs.append(arc)
283
- if other_arc:
284
- arcs.append(other_arc)
285
-
286
- # story arcs
287
- sas = self._get_story_arcs(book)
288
- arcs += sas
289
- return arcs
290
-
291
39
  def _raise_not_found(self):
292
40
  """Raise not found exception."""
293
41
  pk = self.kwargs.get("pk")
@@ -298,6 +46,46 @@ class ReaderView(
298
46
  }
299
47
  raise NotFound(detail=detail)
300
48
 
49
+ def _parse_params(self):
50
+ data = self.request.GET
51
+ # Hack for query_parm parser
52
+ arc_data = MappingProxyType(
53
+ {"group": data.get("arc[group]"), "pks": data.get("arc[pks]")}
54
+ )
55
+ serializer = self.input_serializer_class(data=arc_data)
56
+ arc = {}
57
+ try:
58
+ serializer.is_valid()
59
+ arc: dict = serializer.validated_data # type: ignore
60
+ except ValidationError:
61
+ pass
62
+
63
+ # PARAMS
64
+ top_group = self.get_from_session(
65
+ "top_group", "s", session_key=self.BROWSER_SESSION_KEY
66
+ )
67
+
68
+ # arc.group validation
69
+ # Can't be in the serializer
70
+ arc_group = arc.get("group", "")
71
+ if not arc_group:
72
+ series_pks = self.get_series_pks_from_breadcrumbs()
73
+ if series_pks:
74
+ arc_group = "s"
75
+ arc["pks"] = series_pks
76
+ if not arc_group:
77
+ arc_group = top_group
78
+ if arc_group not in _VALID_ARC_GROUPS:
79
+ arc_group = None
80
+
81
+ params = MappingProxyType(
82
+ {
83
+ "arc": arc,
84
+ "top_group": top_group,
85
+ }
86
+ )
87
+ self.params = params
88
+
301
89
  @staticmethod
302
90
  def _lazy_metadata(current, prev_book, next_book):
303
91
  """Get reader metadata from comicbox if it's not in the db."""
@@ -326,7 +114,7 @@ class ReaderView(
326
114
  """Get the previous and next comics in a group or story arc."""
327
115
  # Books
328
116
  self.last_route = self.get_last_route()
329
- books = self._get_book_collection()
117
+ books = self.get_book_collection()
330
118
 
331
119
  current = books.get("current")
332
120
  if not current:
@@ -343,7 +131,7 @@ class ReaderView(
343
131
  }
344
132
 
345
133
  # Arcs
346
- arcs = self._get_arcs(current)
134
+ arcs, mtime = self.get_arcs(current)
347
135
 
348
136
  arc = self.params.get("arc", {})
349
137
  if not arc.get("group"):
@@ -355,46 +143,13 @@ class ReaderView(
355
143
  close_route = self.last_route
356
144
  close_route.pop("name", None)
357
145
 
358
- return {"books": books, "arcs": arcs, "arc": arc, "close_route": close_route}
359
-
360
- def _parse_params(self):
361
- data = self.request.GET
362
- # Hack for query_parm parser
363
- arc_data = MappingProxyType(
364
- {"group": data.get("arc[group]"), "pks": data.get("arc[pks]")}
365
- )
366
- serializer = self.input_serializer_class(data=arc_data)
367
- arc = {}
368
- try:
369
- serializer.is_valid()
370
- arc = serializer.validated_data
371
- except ValidationError:
372
- pass
373
-
374
- # PARAMS
375
- session = self.request.session.get(BrowserSessionViewBase.SESSION_KEY, {})
376
- top_group = session.get("top_group", "s")
377
-
378
- # arc.group validation
379
- # Can't be in the serializer
380
- arc_group = arc.get("group", "")
381
- if not arc_group:
382
- series_pks = self._get_series_pks_from_breadcrumbs()
383
- if series_pks:
384
- arc_group = "s"
385
- arc["pks"] = series_pks
386
- if not arc_group:
387
- arc_group = top_group
388
- if arc_group not in self._VALID_ARC_GROUPS:
389
- arc_group = None
390
-
391
- params = MappingProxyType(
392
- {
393
- "arc": arc,
394
- "top_group": top_group,
395
- }
396
- )
397
- self.params = params
146
+ return {
147
+ "books": books,
148
+ "arcs": arcs,
149
+ "arc": arc,
150
+ "close_route": close_route,
151
+ "mtime": mtime,
152
+ }
398
153
 
399
154
  @extend_schema(
400
155
  parameters=[
@@ -1,10 +1,12 @@
1
1
  """Reader session view."""
2
2
 
3
3
  from codex.serializers.reader import ReaderSettingsSerializer
4
- from codex.views.session import ReaderSessionViewBase, SessionViewBase
4
+ from codex.views.settings import SettingsView
5
5
 
6
6
 
7
- class ReaderSessionView(ReaderSessionViewBase, SessionViewBase):
7
+ class ReaderSettingsView(SettingsView):
8
8
  """Get Reader Settings."""
9
9
 
10
10
  serializer_class = ReaderSettingsSerializer # type: ignore
11
+
12
+ SESSION_KEY = "reader"
codex/views/session.py CHANGED
@@ -3,64 +3,20 @@
3
3
  from abc import ABC
4
4
  from copy import deepcopy
5
5
  from types import MappingProxyType
6
- from typing import ClassVar
7
-
8
- from drf_spectacular.utils import extend_schema
9
- from rest_framework.generics import GenericAPIView
10
- from rest_framework.response import Response
11
6
 
12
7
  from codex.logger.logging import get_logger
13
8
  from codex.serializers.choices import DEFAULTS
14
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
9
+ from codex.views.auth import AuthFilterGenericAPIView
15
10
 
16
11
  LOG = get_logger(__name__)
17
12
 
18
13
 
19
- class SessionViewBaseBase(GenericAPIView, ABC):
14
+ class SessionView(AuthFilterGenericAPIView, ABC):
20
15
  """Generic Session View."""
21
16
 
22
17
  # Must override both of these
23
18
  SESSION_KEY = ""
24
- SESSION_DEFAULTS = MappingProxyType({})
25
-
26
- @classmethod
27
- def _get_source_values_or_set_defaults(cls, defaults_dict, source_dict, data):
28
- """Recursively copy source_dict values into data or use defaults."""
29
- result = deepcopy(data)
30
- for key, default_value in defaults_dict.items():
31
- result[key] = source_dict.get(key, default_value)
32
- if result[key] is None:
33
- # extra check for migrated or corrupt data
34
- result[key] = default_value
35
- if isinstance(default_value, dict):
36
- result[key] = cls._get_source_values_or_set_defaults(
37
- default_value, source_dict.get(key, {}), result[key]
38
- )
39
- return result
40
-
41
- def get_from_session(self, key): # only used by frontend
42
- """Get one key from the session or its default."""
43
- session = self.request.session.get(self.SESSION_KEY, self.SESSION_DEFAULTS)
44
- return session.get(key, self.SESSION_DEFAULTS[key])
45
19
 
46
- def save_params_to_session(self, params): # reader session & browser final
47
- """Save the session from params with defaults for missing values."""
48
- try:
49
- # Deepcopy this so serializing the values later later for http response doesn't alter them
50
- params = deepcopy(dict(params))
51
- data = self._get_source_values_or_set_defaults(
52
- self.SESSION_DEFAULTS, params, {}
53
- )
54
- self.request.session[self.SESSION_KEY] = data
55
- self.request.session.save()
56
- except Exception as exc:
57
- LOG.warning(f"Saving params to session: {exc}")
58
-
59
-
60
- class BrowserSessionViewBase(SessionViewBaseBase):
61
- """Browser session base."""
62
-
63
- SESSION_KEY = "browser" # type: ignore
64
20
  CONTRIBUTOR_PERSON_UI_FIELD = "contributors"
65
21
  STORY_ARC_UI_FIELD = "story_arcs"
66
22
  IDENTIFIER_TYPE_UI_FIELD = "identifier_type"
@@ -80,7 +36,6 @@ class BrowserSessionViewBase(SessionViewBaseBase):
80
36
  "locations": [],
81
37
  "monochrome": [],
82
38
  "original_format": [],
83
- "q": "",
84
39
  "reading_direction": [],
85
40
  "series_groups": [],
86
41
  "stories": [],
@@ -92,27 +47,51 @@ class BrowserSessionViewBase(SessionViewBaseBase):
92
47
  }
93
48
  )
94
49
  FILTER_ATTRIBUTES = frozenset(_DYNAMIC_FILTER_DEFAULTS.keys())
50
+ BROWSER_SESSION_KEY = "browser"
51
+ READER_SESSION_KEY = "reader"
95
52
  SESSION_DEFAULTS = MappingProxyType(
96
53
  {
97
- "breadcrumbs": DEFAULTS["breadcrumbs"],
98
- "filters": {
99
- "bookmark": DEFAULTS["bookmarkFilter"],
100
- **_DYNAMIC_FILTER_DEFAULTS,
54
+ BROWSER_SESSION_KEY: {
55
+ "breadcrumbs": DEFAULTS["breadcrumbs"],
56
+ "filters": {
57
+ "bookmark": DEFAULTS["bookmarkFilter"],
58
+ **_DYNAMIC_FILTER_DEFAULTS,
59
+ },
60
+ "order_by": DEFAULTS["orderBy"],
61
+ "order_reverse": DEFAULTS["orderReverse"],
62
+ "q": DEFAULTS["q"],
63
+ "search_results_limit": DEFAULTS["searchResultsLimit"],
64
+ "show": DEFAULTS["show"],
65
+ "cover_style": DEFAULTS["coverStyle"],
66
+ "twenty_four_hour_time": False,
67
+ "top_group": DEFAULTS["topGroup"],
68
+ },
69
+ READER_SESSION_KEY: {
70
+ "fit_to": DEFAULTS["fitTo"],
71
+ "two_pages": False,
72
+ "reading_direction": DEFAULTS["readingDirection"],
101
73
  },
102
- "order_by": DEFAULTS["orderBy"],
103
- "order_reverse": DEFAULTS["orderReverse"],
104
- "q": DEFAULTS["q"],
105
- "search_results_limit": DEFAULTS["searchResultsLimit"],
106
- "show": DEFAULTS["show"],
107
- "cover_style": DEFAULTS["coverStyle"],
108
- "twenty_four_hour_time": False,
109
- "top_group": DEFAULTS["topGroup"],
110
74
  }
111
75
  )
112
76
 
77
+ def get_from_session(
78
+ self, key, default=None, session_key=None
79
+ ): # only used by frontend
80
+ """Get one key from the session or its default."""
81
+ if session_key is None:
82
+ session_key = self.SESSION_KEY
83
+ session = self.request.session.get(
84
+ session_key, self.SESSION_DEFAULTS[session_key]
85
+ )
86
+ if default is None:
87
+ default = self.SESSION_DEFAULTS[session_key][key]
88
+ return session.get(key, default)
89
+
113
90
  def get_last_route(self, name=True):
114
91
  """Get the last route from the breadcrumbs."""
115
- breadcrumbs = self.get_from_session("breadcrumbs")
92
+ breadcrumbs = self.get_from_session(
93
+ "breadcrumbs", session_key=self.BROWSER_SESSION_KEY
94
+ )
116
95
  if not breadcrumbs:
117
96
  breadcrumbs = DEFAULTS["breadcrumbs"]
118
97
  last_route = breadcrumbs[-1]
@@ -121,47 +100,40 @@ class BrowserSessionViewBase(SessionViewBaseBase):
121
100
 
122
101
  return last_route
123
102
 
103
+ def load_params_from_session(self, validated_data=None, session_key=None):
104
+ """Load params from the session with defaults."""
105
+ session_key = session_key if session_key else self.SESSION_KEY
106
+ params = deepcopy(dict(self.SESSION_DEFAULTS[session_key]))
107
+ session_params = self.request.session.get(session_key)
108
+ if session_params:
109
+ params.update(session_params)
110
+ if validated_data:
111
+ params.update(validated_data)
112
+ self.params = MappingProxyType(params)
124
113
 
125
- class ReaderSessionViewBase(SessionViewBaseBase):
126
- """Reader session base."""
127
-
128
- SESSION_KEY = "reader" # type: ignore
129
- SESSION_DEFAULTS = MappingProxyType(
130
- {
131
- "fit_to": DEFAULTS["fitTo"],
132
- "two_pages": False,
133
- "reading_direction": DEFAULTS["readingDirection"],
134
- }
135
- )
136
-
137
-
138
- class SessionViewBase(SessionViewBaseBase, GenericAPIView, ABC):
139
- """Session view for retrieving stored settings."""
140
-
141
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
142
-
143
- def load_params_from_session(self):
144
- """Set the params from view session, creating missing values from defaults."""
145
- session = self.request.session.get(self.SESSION_KEY, self.SESSION_DEFAULTS)
146
- return self._get_source_values_or_set_defaults(
147
- self.SESSION_DEFAULTS, session, {}
148
- )
149
-
150
- def get(self, *args, **kwargs):
151
- """Get session settings."""
152
- params = self.load_params_from_session()
153
- data = {}
154
- for key, filter_name in params.get("filters", {}).items():
155
- if filter_name:
156
- data[key] = filter_name
157
- params["filters"] = data
158
- serializer = self.get_serializer(params)
159
- return Response(serializer.data)
114
+ @classmethod
115
+ def _get_source_values_or_set_defaults(cls, defaults_dict, source_dict, data):
116
+ """Recursively copy source_dict values into data or use defaults."""
117
+ result = deepcopy(data)
118
+ for key, default_value in defaults_dict.items():
119
+ result[key] = source_dict.get(key, default_value)
120
+ if result[key] is None:
121
+ # extra check for migrated or corrupt data
122
+ result[key] = default_value
123
+ if isinstance(default_value, dict):
124
+ result[key] = cls._get_source_values_or_set_defaults(
125
+ default_value, source_dict.get(key, {}), result[key]
126
+ )
127
+ return result
160
128
 
161
- @extend_schema(responses=None)
162
- def put(self, *args, **kwargs):
163
- """Update session settings."""
164
- serializer = self.get_serializer(data=self.request.data)
165
- serializer.is_valid(raise_exception=True)
166
- self.save_params_to_session(serializer.validated_data)
167
- return Response()
129
+ def save_params_to_session(self, params): # reader session & browser final
130
+ """Save the session from params with defaults for missing values."""
131
+ try:
132
+ # Deepcopy this so serializing the values later later for http response doesn't alter them
133
+ params = deepcopy(dict(params))
134
+ defaults = self.SESSION_DEFAULTS[self.SESSION_KEY]
135
+ data = self._get_source_values_or_set_defaults(defaults, params, {})
136
+ self.request.session[self.SESSION_KEY] = data
137
+ self.request.session.save()
138
+ except Exception as exc:
139
+ LOG.warning(f"Saving params to session: {exc}")
@@ -0,0 +1,34 @@
1
+ """Settings View."""
2
+
3
+ from abc import ABC
4
+
5
+ from drf_spectacular.utils import extend_schema
6
+ from rest_framework.response import Response
7
+
8
+ from codex.views.session import SessionView
9
+
10
+
11
+ class SettingsView(SessionView, ABC):
12
+ """Settings View."""
13
+
14
+ def get(self, *args, **kwargs):
15
+ """Get session settings."""
16
+ defaults = self.SESSION_DEFAULTS[self.SESSION_KEY]
17
+ session = self.request.session.get(self.SESSION_KEY, defaults)
18
+ params = self._get_source_values_or_set_defaults(defaults, session, {})
19
+ data = {}
20
+ for key, filter_name in params.get("filters", {}).items():
21
+ if filter_name:
22
+ data[key] = filter_name
23
+ params["filters"] = data
24
+ serializer = self.get_serializer(params)
25
+ return Response(serializer.data)
26
+
27
+ @extend_schema(responses=None)
28
+ def put(self, *args, **kwargs):
29
+ """Update session settings."""
30
+ data = self.request.data # type: ignore
31
+ serializer = self.get_serializer(data=data)
32
+ serializer.is_valid(raise_exception=True)
33
+ self.save_params_to_session(serializer.validated_data)
34
+ return Response()