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
@@ -4,18 +4,20 @@ from types import MappingProxyType
4
4
 
5
5
  from django.db.models import Q
6
6
 
7
- from codex.views.session import BrowserSessionViewBase
7
+ from codex.models.comic import Comic
8
+ from codex.views.browser.filters.bookmark import BookmarkFilterMixin
9
+ from codex.views.browser.filters.group import GroupFilterView
8
10
 
9
11
  _FILTER_REL_MAP = MappingProxyType(
10
12
  {
11
- BrowserSessionViewBase.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
12
- BrowserSessionViewBase.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
13
- BrowserSessionViewBase.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
13
+ GroupFilterView.CONTRIBUTOR_PERSON_UI_FIELD: "contributors__person",
14
+ GroupFilterView.STORY_ARC_UI_FIELD: "story_arc_numbers__story_arc",
15
+ GroupFilterView.IDENTIFIER_TYPE_UI_FIELD: "identifiers__identifier_type",
14
16
  }
15
17
  )
16
18
 
17
19
 
18
- class ComicFieldFilter(BrowserSessionViewBase):
20
+ class ComicFieldFilterView(GroupFilterView, BookmarkFilterMixin):
19
21
  """Comic field filters."""
20
22
 
21
23
  def _filter_by_comic_field(self, field, rel_prefix):
@@ -36,9 +38,10 @@ class ComicFieldFilter(BrowserSessionViewBase):
36
38
  filter_query |= Q(**{f"{rel}__in": filter_list})
37
39
  return filter_query
38
40
 
39
- def get_comic_field_filter(self, rel_prefix):
41
+ def get_comic_field_filter(self, model):
40
42
  """Filter the comics based on the form filters."""
41
43
  comic_field_filter = Q()
44
+ rel_prefix = "" if model == Comic else self.rel_prefix # type: ignore
42
45
  for attribute in self.FILTER_ATTRIBUTES:
43
46
  comic_field_filter &= self._filter_by_comic_field(attribute, rel_prefix)
44
47
  return comic_field_filter
@@ -2,25 +2,28 @@
2
2
 
3
3
  from django.db.models import Q
4
4
 
5
- from codex.views.auth import GroupACLMixin
5
+ from codex.views.const import FOLDER_GROUP, GROUP_RELATION
6
+ from codex.views.session import SessionView
6
7
 
7
8
 
8
- class GroupFilterMixin(GroupACLMixin):
9
+ class GroupFilterView(SessionView):
9
10
  """Group Filters."""
10
11
 
12
+ SESSION_KEY = SessionView.BROWSER_SESSION_KEY
13
+
11
14
  def get_group_filter(self, choices):
12
15
  """Get filter for the displayed group."""
13
16
  group = self.kwargs["group"] # type: ignore
14
17
  pks = self.kwargs["pks"] # type: ignore
15
18
  if pks: # type: ignore
16
19
  group_relation = "comic__" if choices else ""
17
- if choices and group == self.FOLDER_GROUP:
20
+ if choices and group == FOLDER_GROUP:
18
21
  group_relation += "folders"
19
22
  else:
20
- group_relation += self.GROUP_RELATION[group]
23
+ group_relation += GROUP_RELATION[group]
21
24
  group_relation += "__in"
22
25
  group_filter_dict = {group_relation: pks} # type: ignore
23
- elif group == self.FOLDER_GROUP:
26
+ elif group == FOLDER_GROUP:
24
27
  group_filter_dict = {"parent_folder": None}
25
28
  else:
26
29
  group_filter_dict = {}
@@ -9,12 +9,13 @@ from codex.logger.logging import get_logger
9
9
  from codex.models import Comic
10
10
  from codex.search.query import CodexSearchQuerySet
11
11
  from codex.settings.settings import DEBUG
12
- from codex.views.browser.const import MAX_OBJ_PER_PAGE
12
+ from codex.views.browser.filters.field import ComicFieldFilterView
13
+ from codex.views.const import MAX_OBJ_PER_PAGE
13
14
 
14
15
  LOG = get_logger(__name__)
15
- SEARCH_SCORE_MAX = 100.0
16
- SEARCH_SCORE_MIN = 0.001
17
- SEARCH_SCORE_EMPTY = 0.0
16
+ _SEARCH_SCORE_MAX = 100.0
17
+ _SEARCH_SCORE_MIN = 0.001
18
+ _SEARCH_SCORE_EMPTY = 0.0
18
19
 
19
20
 
20
21
  @dataclass
@@ -26,7 +27,7 @@ class SearchScorePks:
26
27
  next_pks: tuple[int, ...] = ()
27
28
 
28
29
 
29
- class SearchFilterMixin:
30
+ class SearchFilterView(ComicFieldFilterView):
30
31
  """Search Filters Methods."""
31
32
 
32
33
  def _is_search_results_limited(self) -> bool:
@@ -154,10 +155,12 @@ class SearchFilterMixin:
154
155
  when = {prefix + "pk": pk, "then": score}
155
156
  whens.append(When(**when))
156
157
  if prev_pks:
157
- whens.append(When(pk__in=prev_pks, then=SEARCH_SCORE_MAX))
158
+ whens.append(When(pk__in=prev_pks, then=_SEARCH_SCORE_MAX))
158
159
  if next_pks:
159
- whens.append(When(pk__in=next_pks, then=SEARCH_SCORE_MIN))
160
- search_score = self.order_agg_func(Case(*whens, default=SEARCH_SCORE_EMPTY)) # type: ignore
160
+ whens.append(When(pk__in=next_pks, then=_SEARCH_SCORE_MIN))
161
+ search_score = self.order_agg_func( # type: ignore
162
+ Case(*whens, default=_SEARCH_SCORE_EMPTY)
163
+ )
161
164
  else:
162
165
  search_score = Value(0.0)
163
166
  return qs.annotate(search_score=search_score)
@@ -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
  from django.core.exceptions import FieldDoesNotExist
7
6
  from django.db.models import Count, IntegerField, Sum, Value
@@ -13,8 +12,7 @@ from codex.librarian.importer.const import COMIC_M2M_FIELD_NAMES
13
12
  from codex.logger.logging import get_logger
14
13
  from codex.models import AdminFlag, Comic
15
14
  from codex.serializers.browser.metadata import MetadataSerializer
16
- from codex.views.auth import IsAuthenticatedOrEnabledNonUsers
17
- from codex.views.browser.browser_annotations import BrowserAnnotationsView
15
+ from codex.views.browser.annotations import BrowserAnnotationsView
18
16
 
19
17
  LOG = get_logger(__name__)
20
18
  _ADMIN_OR_FILE_VIEW_ENABLED_COMIC_VALUE_FIELDS = frozenset({"path"})
@@ -37,7 +35,7 @@ _COMIC_VALUE_FIELD_NAMES = frozenset(
37
35
  )
38
36
  _COMIC_VALUE_FIELDS_CONFLICTING_PREFIX = "conflict_"
39
37
  _COMIC_RELATED_VALUE_FIELDS = frozenset({"series__volume_count", "volume__issue_count"})
40
- _PATH_GROUPS = ("c", "f")
38
+ _PATH_GROUPS = frozenset({"c", "f"})
41
39
  _CONTRIBUTOR_RELATIONS = ("role", "person")
42
40
  _SUM_FIELDS = frozenset({"page_count", "size"})
43
41
  _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
@@ -46,7 +44,6 @@ _GROUP_RELS = frozenset({"publisher", "imprint", "series", "volume"})
46
44
  class MetadataView(BrowserAnnotationsView):
47
45
  """Comic metadata."""
48
46
 
49
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
50
47
  serializer_class = MetadataSerializer
51
48
  TARGET = "metadata"
52
49
  ADMIN_FLAG_VALUE_KEY_MAP = MappingProxyType(
@@ -259,7 +256,6 @@ class MetadataView(BrowserAnnotationsView):
259
256
  filtered_qs = qs
260
257
  qs = self.annotate_order_aggregates(qs, self.model)
261
258
  qs = self.annotate_card_aggregates(qs, self.model)
262
- qs = self.annotate_for_metadata(qs, self.model)
263
259
  group_by = self.get_group_by()
264
260
  qs = qs.group_by(group_by)
265
261
  qs = self._annotate_values_and_fks(qs, filtered_qs)
@@ -277,7 +273,7 @@ class MetadataView(BrowserAnnotationsView):
277
273
  m2m_intersections = self._query_m2m_intersections(filtered_qs)
278
274
  return self._copy_annotations_into_comic_fields(obj, m2m_intersections) # type: ignore
279
275
 
280
- def set_valid_browse_nav_groups(self, _valid_top_groups):
276
+ def set_valid_browse_nav_groups(self, valid_top_groups): # noqa: ARG002
281
277
  """Limited allowed nav groups for metadata."""
282
278
  group = self.kwargs["group"]
283
279
  self.valid_nav_groups = (group,)
@@ -6,22 +6,18 @@ from codex.models import Comic
6
6
  from codex.views.browser.filters.annotations import (
7
7
  BrowserAnnotationsFilterView,
8
8
  )
9
+ from codex.views.const import FOLDER_GROUP
9
10
 
10
11
 
11
12
  class BrowserOrderByView(BrowserAnnotationsFilterView):
12
13
  """Base class for views that need ordering."""
13
14
 
14
- def __init__(self, *args, **kwargs):
15
- """Set params for the type checker."""
16
- super().__init__(*args, **kwargs)
17
- self.order_key: str = ""
18
-
19
15
  def set_order_key(self):
20
16
  """Get the default order key for the view."""
21
17
  order_key: str = self.params.get("order_by", "") # type: ignore
22
18
  if not order_key:
23
19
  group = self.kwargs.get("group")
24
- order_key = "filename" if group == self.FOLDER_GROUP else "sort_name"
20
+ order_key = "filename" if group == FOLDER_GROUP else "sort_name"
25
21
  self.order_key: str = order_key
26
22
  order_reverse = self.params.get("order_reverse")
27
23
  self.order_agg_func = Max if order_reverse else Min
@@ -0,0 +1,106 @@
1
+ """Browser pagination."""
2
+
3
+ from math import ceil
4
+
5
+ from django.core.paginator import EmptyPage, Paginator
6
+
7
+ from codex.logger.logging import get_logger
8
+ from codex.models import (
9
+ Comic,
10
+ Folder,
11
+ )
12
+ from codex.views.browser.annotations import BrowserAnnotationsView
13
+ from codex.views.const import (
14
+ MAX_OBJ_PER_PAGE,
15
+ )
16
+
17
+ LOG = get_logger(__name__)
18
+
19
+
20
+ class BrowserPaginateView(BrowserAnnotationsView):
21
+ """Browser pagination."""
22
+
23
+ def _check_page_in_bounds(self, total_count):
24
+ """Redirect page out of bounds."""
25
+ pks = self.kwargs.get("pks")
26
+ page = self.kwargs.get("page", 1)
27
+ num_pages = ceil(total_count / MAX_OBJ_PER_PAGE)
28
+ if (not pks and page == 1) or (page >= 1 and page <= num_pages):
29
+ # Don't redirect if on the root page for the group.
30
+ # Or page within valid range.
31
+ return num_pages
32
+
33
+ # Adjust route mask for redirect
34
+ group = self.kwargs.get("group")
35
+ if num_pages and page > 1:
36
+ # Try page 1 first.
37
+ new_page = num_pages if num_pages and page > num_pages else 1
38
+ pks = pks if pks else "0"
39
+ else:
40
+ # Redirect to root for the group.
41
+ pks = "0"
42
+ new_page = 1
43
+
44
+ reason = f"{page=} does not exist!"
45
+ route_changes = {"group": group, "pks": pks, "page": new_page}
46
+ return self.raise_redirect(reason, route_mask=route_changes)
47
+
48
+ def _paginate_section(self, model, qs, page):
49
+ """Paginate a group or Comic section."""
50
+ orphans = 0 if self.model_group == "f" or self.params.get("q") else 5
51
+ paginator = Paginator(qs, MAX_OBJ_PER_PAGE, orphans=orphans)
52
+ try:
53
+ qs = paginator.page(page).object_list
54
+ except EmptyPage:
55
+ if model != Folder:
56
+ model_name = self.model.__name__ if self.model else "NO_MODEL"
57
+ LOG.warning(f"No {model_name}s on page {page}")
58
+ qs = model.objects.none()
59
+
60
+ return qs
61
+
62
+ def _paginate_groups(self, group_qs, total_group_count):
63
+ """Paginate the group object list before books."""
64
+ if total_group_count:
65
+ page = self.kwargs.get("page", 1)
66
+ page_group_qs = self._paginate_section(self.model, group_qs, page)
67
+ else:
68
+ page_group_qs = self.model.objects.none() # type: ignore
69
+ return page_group_qs
70
+
71
+ def _paginate_books(self, book_qs, total_group_count, page_obj_count):
72
+ """Paginate the book object list based on how many group/folders are showing."""
73
+ if page_obj_count >= MAX_OBJ_PER_PAGE:
74
+ # No books for this page
75
+ page_book_qs = Comic.objects.none()
76
+ else:
77
+ group_remainder = total_group_count % MAX_OBJ_PER_PAGE
78
+ if page_obj_count:
79
+ # There are books after the groups on the same page
80
+ # Manually add books without the paginator
81
+ book_limit = MAX_OBJ_PER_PAGE - group_remainder
82
+ page_book_qs = book_qs[:book_limit]
83
+ else:
84
+ # There are books after the groups on a new page
85
+ if group_remainder:
86
+ book_offset = MAX_OBJ_PER_PAGE - group_remainder
87
+ page_book_qs = book_qs[book_offset:]
88
+ else:
89
+ page_book_qs = book_qs
90
+
91
+ num_group_and_mixed_pages = ceil(total_group_count / MAX_OBJ_PER_PAGE)
92
+ book_only_page = self.kwargs.get("page", 1) - num_group_and_mixed_pages
93
+ page_book_qs = self._paginate_section(
94
+ Comic, page_book_qs, book_only_page
95
+ )
96
+ return page_book_qs
97
+
98
+ def paginate(self, group_qs, book_qs, group_count, book_count):
99
+ """Paginate the queryset into a group and book object lists."""
100
+ num_pages = self._check_page_in_bounds(group_count + book_count)
101
+ page_group_qs = self._paginate_groups(group_qs, group_count)
102
+ page_group_count = page_group_qs.count()
103
+ page_book_qs = self._paginate_books(book_qs, group_count, page_group_count)
104
+ page_book_count = page_book_qs.count()
105
+
106
+ return page_group_qs, page_book_qs, num_pages, page_group_count, page_book_count
@@ -1,12 +1,13 @@
1
1
  """Browser session view."""
2
2
 
3
3
  from codex.serializers.browser.settings import BrowserSettingsSerializer
4
- from codex.views.session import BrowserSessionViewBase, SessionViewBase
4
+ from codex.views.settings import SettingsView
5
5
 
6
6
 
7
- class BrowserSessionView(BrowserSessionViewBase, SessionViewBase):
7
+ class BrowserSettingsView(SettingsView):
8
8
  """Get Browser Settings."""
9
9
 
10
10
  # Put Browser Settings is normally done through BrowserView.get()
11
-
12
11
  serializer_class = BrowserSettingsSerializer # type: ignore
12
+
13
+ SESSION_KEY = SettingsView.BROWSER_SESSION_KEY
@@ -0,0 +1,48 @@
1
+ """Browser title."""
2
+
3
+ from codex.models import BrowserGroupModel, Comic, Volume
4
+ from codex.views.browser.breadcrumbs import BrowserBreadcrumbsView
5
+
6
+
7
+ class BrowserTitleView(BrowserBreadcrumbsView):
8
+ """Browser title methods."""
9
+
10
+ def __init__(self, *args, **kwargs):
11
+ """Set params for the type checker."""
12
+ super().__init__(*args, **kwargs)
13
+ self.group_instance: BrowserGroupModel | None = None
14
+
15
+ def _get_root_group_name(self):
16
+ if not self.model:
17
+ reason = "No model set in browser"
18
+ raise ValueError(reason)
19
+ plural = self.model._meta.verbose_name_plural
20
+ if not plural:
21
+ reason = f"No plural name for {self.model}"
22
+ raise ValueError(reason)
23
+ return plural.capitalize(), 0
24
+
25
+ def _get_group_name(self):
26
+ group_count = 0
27
+ if gi := self.group_instance:
28
+ if isinstance(gi, Volume):
29
+ group_count = gi.series.volume_count
30
+ elif isinstance(gi, Comic):
31
+ group_count = gi.volume.issue_count
32
+ group_name = gi.name
33
+ else:
34
+ group_name = ""
35
+ return group_name, group_count
36
+
37
+ def get_browser_page_title(self):
38
+ """Get the proper title for this browse level."""
39
+ pks = self.kwargs.get("pks")
40
+ if not pks:
41
+ group_name, group_count = self._get_root_group_name()
42
+ else:
43
+ group_name, group_count = self._get_group_name()
44
+
45
+ return {
46
+ "group_name": group_name,
47
+ "group_count": group_count,
48
+ }
@@ -0,0 +1,158 @@
1
+ """Browser Settings and URL Validation."""
2
+
3
+ from copy import deepcopy
4
+ from types import MappingProxyType
5
+
6
+ from codex.exceptions import SeeOtherRedirectError
7
+ from codex.logger.logging import get_logger
8
+ from codex.serializers.choices import DEFAULTS
9
+ from codex.views.browser.base import BrowserBaseView
10
+ from codex.views.const import (
11
+ COMIC_GROUP,
12
+ FOLDER_GROUP,
13
+ ROOT_GROUP,
14
+ STORY_ARC_GROUP,
15
+ )
16
+
17
+ LOG = get_logger(__name__)
18
+
19
+
20
+ class BrowserValidateView(BrowserBaseView):
21
+ """Browser Settings and URL Validation."""
22
+
23
+ DEFAULT_ROUTE = MappingProxyType(
24
+ {
25
+ "name": "browser",
26
+ "params": deepcopy(DEFAULTS["breadcrumbs"][0]),
27
+ }
28
+ )
29
+
30
+ def raise_redirect(self, reason, route_mask=None, settings_mask=None):
31
+ """Redirect the client to a valid group url."""
32
+ route = deepcopy(dict(self.DEFAULT_ROUTE))
33
+ if route_mask:
34
+ route["params"].update(route_mask) # type: ignore
35
+ settings = deepcopy(dict(self.params))
36
+ if settings_mask:
37
+ settings.update(settings_mask)
38
+ detail = {"route": route, "settings": settings, "reason": reason}
39
+ raise SeeOtherRedirectError(detail=detail)
40
+
41
+ def _get_valid_browse_top_groups(self):
42
+ """Get valid top groups for the current settings.
43
+
44
+ Valid top groups are determined by the Browser Settings.
45
+ """
46
+ valid_top_groups = []
47
+
48
+ show = self.params["show"]
49
+ for nav_group, allowed in show.items():
50
+ if allowed:
51
+ valid_top_groups.append(nav_group)
52
+ # Issues is always a valid top group
53
+ valid_top_groups += [COMIC_GROUP]
54
+
55
+ return valid_top_groups
56
+
57
+ def _validate_top_group(self, valid_top_groups):
58
+ nav_group = self.kwargs.get("group")
59
+ top_group = self.params.get("top_group")
60
+ if top_group not in valid_top_groups:
61
+ reason = f"top_group {top_group} not in valid nav groups, changed to "
62
+ if nav_group in valid_top_groups:
63
+ valid_top_group = nav_group
64
+ reason += "nav group: "
65
+ else:
66
+ valid_top_group = valid_top_groups[0]
67
+ reason += "first valid top group "
68
+ reason += valid_top_group
69
+ pks = self.kwargs.get("pks", ())
70
+ page = self.kwargs["page"]
71
+ route = {"group": nav_group, "pks": pks, "page": page}
72
+ settings_mask = {"top_group": valid_top_group}
73
+ self.raise_redirect(reason, route, settings_mask)
74
+
75
+ def set_valid_browse_nav_groups(self, valid_top_groups):
76
+ """Get valid nav groups for the current settings.
77
+
78
+ Valid nav groups are the top group and below that are also
79
+ enabled in browser settings.
80
+
81
+ May always navigate to root 'r' nav group.
82
+ """
83
+ top_group = self.params["top_group"]
84
+ nav_group = self.kwargs["group"]
85
+ valid_nav_groups = [ROOT_GROUP]
86
+
87
+ for possible_index, possible_nav_group in enumerate(valid_top_groups):
88
+ if top_group == possible_nav_group:
89
+ # all the nav groups past this point,
90
+ # 'c' is obscured by the web reader url, but valid for opds
91
+ tail_top_groups = valid_top_groups[possible_index:]
92
+ valid_nav_groups += tail_top_groups
93
+
94
+ if nav_group not in valid_nav_groups:
95
+ reason = (
96
+ f"Nav group {nav_group} unavailable, "
97
+ f"redirect to {ROOT_GROUP}"
98
+ )
99
+ self.raise_redirect(reason)
100
+ break
101
+ self.valid_nav_groups = tuple(valid_nav_groups)
102
+
103
+ def _validate_folder_settings(self):
104
+ """Check that all the view variables for folder mode are set right."""
105
+ # Check folder view admin flag
106
+ if not self.admin_flags["folder_view"]:
107
+ reason = "folder view disabled"
108
+ valid_top_groups = self._get_valid_browse_top_groups()
109
+ settings_mask = {"top_group": valid_top_groups[0]}
110
+ self.raise_redirect(reason, settings_mask=settings_mask)
111
+
112
+ valid_top_groups = (FOLDER_GROUP,)
113
+ self._validate_top_group(valid_top_groups)
114
+ self.valid_nav_groups = valid_top_groups
115
+
116
+ def _validate_browser_group_settings(self):
117
+ """Check that all the view variables for browser mode are set right."""
118
+ # Validate Browser top_group
119
+ # Change top_group if its not in the valid top groups
120
+ valid_top_groups = self._get_valid_browse_top_groups()
121
+ self._validate_top_group(valid_top_groups)
122
+
123
+ # Validate pks
124
+ nav_group = self.kwargs["group"]
125
+ pks = self.kwargs["pks"]
126
+ if nav_group == ROOT_GROUP and (pks and 0 not in pks):
127
+ # r never has pks
128
+ reason = f"Redirect r with {pks=} to pks 0"
129
+ self.raise_redirect(reason)
130
+
131
+ # Validate Browser nav_group
132
+ # Redirect if nav group is wrong
133
+ self.set_valid_browse_nav_groups(valid_top_groups)
134
+
135
+ def _validate_story_arc_settings(self):
136
+ """Validate story arc settings."""
137
+ valid_top_groups = (STORY_ARC_GROUP,)
138
+ self._validate_top_group(valid_top_groups)
139
+ self.valid_nav_groups = valid_top_groups
140
+
141
+ def validate_settings(self):
142
+ """Validate group and top group settings."""
143
+ group = self.kwargs["group"]
144
+ if group == FOLDER_GROUP:
145
+ self._validate_folder_settings()
146
+ elif group == STORY_ARC_GROUP:
147
+ self._validate_story_arc_settings()
148
+ else:
149
+ self._validate_browser_group_settings()
150
+
151
+ # Validate order
152
+ if self.order_key == "filename" and not self.admin_flags["folder_view"]:
153
+ pks = self.kwargs["pks"]
154
+ page = self.kwargs["page"]
155
+ route_changes = {"group": group, "pks": pks, "page": page}
156
+ reason = "order by filename not allowed by admin flag."
157
+ settings_mask = {"order_by": "sort_name"}
158
+ self.raise_redirect(reason, route_changes, settings_mask)
codex/views/const.py CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  from types import MappingProxyType
4
4
 
5
+ from django.contrib.auth.models import Group, User
6
+ from django.contrib.sessions.models import Session
7
+
8
+ from codex.models import (
9
+ AgeRating,
10
+ BrowserGroupModel,
11
+ Character,
12
+ Comic,
13
+ Contributor,
14
+ ContributorPerson,
15
+ ContributorRole,
16
+ Country,
17
+ Folder,
18
+ Genre,
19
+ Identifier,
20
+ IdentifierType,
21
+ Imprint,
22
+ Language,
23
+ Library,
24
+ Location,
25
+ OriginalFormat,
26
+ Publisher,
27
+ ScanInfo,
28
+ Series,
29
+ SeriesGroup,
30
+ StoryArc,
31
+ StoryArcNumber,
32
+ Tag,
33
+ Tagger,
34
+ Team,
35
+ Volume,
36
+ )
5
37
  from codex.settings.settings import CODEX_PATH
6
38
 
7
39
  FALSY = {None, "", "false", "0", False}
@@ -18,3 +50,68 @@ MISSING_COVER_NAME_MAP = MappingProxyType(
18
50
  )
19
51
  MISSING_COVER_FN = "missing-cover-165.webp"
20
52
  MISSING_COVER_PATH = STATIC_IMG_PATH / MISSING_COVER_FN
53
+
54
+
55
+ ROOT_GROUP = "r"
56
+ FOLDER_GROUP = "f"
57
+ STORY_ARC_GROUP = "a"
58
+ COMIC_GROUP = "c"
59
+ GROUP_RELATION = MappingProxyType(
60
+ {
61
+ **GROUP_NAME_MAP,
62
+ COMIC_GROUP: "pk",
63
+ FOLDER_GROUP: "parent_folder",
64
+ STORY_ARC_GROUP: "story_arc_numbers__story_arc",
65
+ }
66
+ )
67
+ GROUP_MODEL_MAP: MappingProxyType[str, type[BrowserGroupModel] | None] = (
68
+ MappingProxyType(
69
+ {
70
+ ROOT_GROUP: None,
71
+ "p": Publisher,
72
+ "i": Imprint,
73
+ "s": Series,
74
+ "v": Volume,
75
+ COMIC_GROUP: Comic,
76
+ FOLDER_GROUP: Folder,
77
+ STORY_ARC_GROUP: StoryArc,
78
+ }
79
+ )
80
+ )
81
+ GROUP_MODELS = (
82
+ Publisher,
83
+ Imprint,
84
+ Series,
85
+ Volume,
86
+ Comic,
87
+ Folder,
88
+ )
89
+ METADATA_MODELS = (
90
+ AgeRating,
91
+ Character,
92
+ Country,
93
+ Genre,
94
+ Identifier,
95
+ IdentifierType,
96
+ Language,
97
+ Location,
98
+ OriginalFormat,
99
+ SeriesGroup,
100
+ ScanInfo,
101
+ StoryArc,
102
+ StoryArcNumber,
103
+ Team,
104
+ Tag,
105
+ Tagger,
106
+ Contributor,
107
+ ContributorPerson,
108
+ ContributorRole,
109
+ )
110
+ CONFIG_MODELS = (
111
+ Library,
112
+ User,
113
+ Group,
114
+ Session,
115
+ )
116
+ MAX_OBJ_PER_PAGE = 100
117
+ GROUP_MTIME_MODEL_MAP = MappingProxyType({"r": Publisher, "a": StoryArc, "f": Folder})
codex/views/cover.py CHANGED
@@ -1,18 +1,17 @@
1
1
  """Comic cover thumbnail view."""
2
2
 
3
- from typing import ClassVar
4
-
5
3
  from django.http import HttpResponse
6
4
  from drf_spectacular.types import OpenApiTypes
7
5
  from drf_spectacular.utils import extend_schema
8
6
  from rest_framework.renderers import BaseRenderer
9
- from rest_framework.views import APIView
10
7
 
11
8
  from codex.librarian.covers.create import CoverCreateMixin
12
9
  from codex.librarian.covers.path import CoverPathMixin
13
10
  from codex.librarian.mp_queue import LIBRARIAN_QUEUE
14
11
  from codex.logger.logging import get_logger
15
- from codex.views.auth import GroupACLMixin, IsAuthenticatedOrEnabledNonUsers
12
+ from codex.views.auth import (
13
+ AuthAPIView,
14
+ )
16
15
  from codex.views.const import MISSING_COVER_FN, MISSING_COVER_NAME_MAP, STATIC_IMG_PATH
17
16
 
18
17
  LOG = get_logger(__name__)
@@ -31,10 +30,9 @@ class WEBPRenderer(BaseRenderer):
31
30
  return data
32
31
 
33
32
 
34
- class CoverView(APIView, GroupACLMixin):
33
+ class CoverView(AuthAPIView):
35
34
  """ComicCover View."""
36
35
 
37
- permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers] # type: ignore
38
36
  renderer_classes = (WEBPRenderer,)
39
37
  content_type = "image/webp"
40
38