codex 1.6.0a10__py3-none-any.whl → 1.6.0a12__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 (426) hide show
  1. codex/librarian/covers/create.py +21 -16
  2. codex/serializers/browser/choices.py +23 -44
  3. codex/serializers/browser/filters.py +30 -36
  4. codex/serializers/browser/mixins.py +37 -4
  5. codex/serializers/browser/settings.py +10 -3
  6. codex/serializers/choices.py +4 -0
  7. codex/serializers/fields.py +75 -66
  8. codex/serializers/models/pycountry.py +11 -51
  9. codex/serializers/opds/v2.py +4 -6
  10. codex/serializers/reader.py +1 -0
  11. codex/static_root/assets/{VCheckbox-CY3rcqlS.de5154294920.js → VCheckbox-B3vCf1Vu.e1991f98ab6c.js} +1 -1
  12. codex/static_root/assets/VCheckbox-B3vCf1Vu.e1991f98ab6c.js.br +0 -0
  13. codex/static_root/assets/VCheckbox-B3vCf1Vu.e1991f98ab6c.js.gz +0 -0
  14. codex/static_root/assets/{VCheckbox-CY3rcqlS.js → VCheckbox-B3vCf1Vu.js} +1 -1
  15. codex/static_root/assets/VCheckbox-B3vCf1Vu.js.br +0 -0
  16. codex/static_root/assets/VCheckbox-B3vCf1Vu.js.gz +0 -0
  17. codex/static_root/assets/{VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js → VCheckboxBtn-Cj6bUYmw.196cc43ca1c8.js} +1 -1
  18. codex/static_root/assets/VCheckboxBtn-Cj6bUYmw.196cc43ca1c8.js.br +0 -0
  19. codex/static_root/assets/VCheckboxBtn-Cj6bUYmw.196cc43ca1c8.js.gz +0 -0
  20. codex/static_root/assets/{VCheckboxBtn-D2tPTt6P.js → VCheckboxBtn-Cj6bUYmw.js} +1 -1
  21. codex/static_root/assets/VCheckboxBtn-Cj6bUYmw.js.br +0 -0
  22. codex/static_root/assets/VCheckboxBtn-Cj6bUYmw.js.gz +0 -0
  23. codex/static_root/assets/{VCombobox-aJr--Ou7.f290ccf2ce25.js → VCombobox-wIqJY8no.3547ff73e91e.js} +1 -1
  24. codex/static_root/assets/VCombobox-wIqJY8no.3547ff73e91e.js.br +0 -0
  25. codex/static_root/assets/VCombobox-wIqJY8no.3547ff73e91e.js.gz +0 -0
  26. codex/static_root/assets/{VCombobox-aJr--Ou7.js → VCombobox-wIqJY8no.js} +1 -1
  27. codex/static_root/assets/VCombobox-wIqJY8no.js.br +0 -0
  28. codex/static_root/assets/VCombobox-wIqJY8no.js.gz +0 -0
  29. codex/static_root/assets/{VDialog-61pzfUB2.33f73beb5224.js → VDialog-D6HUFV6a.ac71da510b3f.js} +1 -1
  30. codex/static_root/assets/VDialog-D6HUFV6a.ac71da510b3f.js.br +0 -0
  31. codex/static_root/assets/VDialog-D6HUFV6a.ac71da510b3f.js.gz +0 -0
  32. codex/static_root/assets/{VDialog-61pzfUB2.js → VDialog-D6HUFV6a.js} +1 -1
  33. codex/static_root/assets/VDialog-D6HUFV6a.js.br +0 -0
  34. codex/static_root/assets/VDialog-D6HUFV6a.js.gz +0 -0
  35. codex/static_root/assets/{VExpansionPanels-rewm0P4z.5c7680f815b6.js → VExpansionPanels-BUJaYmzY.44e8a00a2ebe.js} +1 -1
  36. codex/static_root/assets/VExpansionPanels-BUJaYmzY.44e8a00a2ebe.js.br +0 -0
  37. codex/static_root/assets/VExpansionPanels-BUJaYmzY.44e8a00a2ebe.js.gz +0 -0
  38. codex/static_root/assets/{VExpansionPanels-rewm0P4z.js → VExpansionPanels-BUJaYmzY.js} +1 -1
  39. codex/static_root/assets/VExpansionPanels-BUJaYmzY.js.br +0 -0
  40. codex/static_root/assets/VExpansionPanels-BUJaYmzY.js.gz +0 -0
  41. codex/static_root/assets/{VRadioGroup-QFQbK1qo.750a42b1b814.js → VRadioGroup-CayclHra.0a5b1c9a7b71.js} +1 -1
  42. codex/static_root/assets/VRadioGroup-CayclHra.0a5b1c9a7b71.js.br +0 -0
  43. codex/static_root/assets/VRadioGroup-CayclHra.0a5b1c9a7b71.js.gz +0 -0
  44. codex/static_root/assets/{VRadioGroup-QFQbK1qo.js → VRadioGroup-CayclHra.js} +1 -1
  45. codex/static_root/assets/VRadioGroup-CayclHra.js.br +0 -0
  46. codex/static_root/assets/VRadioGroup-CayclHra.js.gz +0 -0
  47. codex/static_root/assets/{VSelect-Dixy6okt.5d53830daa8a.js → VSelect-ou2Q9UaN.8ddd219784e1.js} +1 -1
  48. codex/static_root/assets/VSelect-ou2Q9UaN.8ddd219784e1.js.br +0 -0
  49. codex/static_root/assets/VSelect-ou2Q9UaN.8ddd219784e1.js.gz +0 -0
  50. codex/static_root/assets/{VSelect-Dixy6okt.js → VSelect-ou2Q9UaN.js} +1 -1
  51. codex/static_root/assets/VSelect-ou2Q9UaN.js.br +0 -0
  52. codex/static_root/assets/VSelect-ou2Q9UaN.js.gz +0 -0
  53. codex/static_root/assets/{VSelectionControl-MNmxKc3J.js → VSelectionControl-BOKIwNZb.16e0cdde978c.js} +1 -1
  54. codex/static_root/assets/VSelectionControl-BOKIwNZb.16e0cdde978c.js.br +0 -0
  55. codex/static_root/assets/VSelectionControl-BOKIwNZb.16e0cdde978c.js.gz +0 -0
  56. codex/static_root/assets/{VSelectionControl-MNmxKc3J.08055d00917e.js → VSelectionControl-BOKIwNZb.js} +1 -1
  57. codex/static_root/assets/VSelectionControl-BOKIwNZb.js.br +0 -0
  58. codex/static_root/assets/VSelectionControl-BOKIwNZb.js.gz +0 -0
  59. codex/static_root/assets/{VSlideGroup-DZEV0FoV.5f996daf0e98.js → VSlideGroup-C3XgVg2T.d2b0ee4f596c.js} +1 -1
  60. codex/static_root/assets/VSlideGroup-C3XgVg2T.d2b0ee4f596c.js.br +0 -0
  61. codex/static_root/assets/VSlideGroup-C3XgVg2T.d2b0ee4f596c.js.gz +0 -0
  62. codex/static_root/assets/{VSlideGroup-DZEV0FoV.js → VSlideGroup-C3XgVg2T.js} +1 -1
  63. codex/static_root/assets/VSlideGroup-C3XgVg2T.js.br +0 -0
  64. codex/static_root/assets/VSlideGroup-C3XgVg2T.js.gz +0 -0
  65. codex/static_root/assets/{VTable-lcnYg9Ee.d72927b13c2e.js → VTable-CrIiBr9q.b4669247af45.js} +1 -1
  66. codex/static_root/assets/VTable-CrIiBr9q.b4669247af45.js.br +0 -0
  67. codex/static_root/assets/VTable-CrIiBr9q.b4669247af45.js.gz +0 -0
  68. codex/static_root/assets/{VTable-lcnYg9Ee.js → VTable-CrIiBr9q.js} +1 -1
  69. codex/static_root/assets/VTable-CrIiBr9q.js.br +0 -0
  70. codex/static_root/assets/VTable-CrIiBr9q.js.gz +0 -0
  71. codex/static_root/assets/{VTextField-BkFrjhdV.js → VTextField-HAAxrpNi.6f6dcb50079a.js} +1 -1
  72. codex/static_root/assets/VTextField-HAAxrpNi.6f6dcb50079a.js.br +0 -0
  73. codex/static_root/assets/VTextField-HAAxrpNi.6f6dcb50079a.js.gz +0 -0
  74. codex/static_root/assets/{VTextField-BkFrjhdV.b02653a9b3b7.js → VTextField-HAAxrpNi.js} +1 -1
  75. codex/static_root/assets/VTextField-HAAxrpNi.js.br +0 -0
  76. codex/static_root/assets/VTextField-HAAxrpNi.js.gz +0 -0
  77. codex/static_root/assets/{VWindowItem-C__tAqdc.9791c128c3b7.js → VWindowItem-aal6-Uv4.d178e2af20a7.js} +1 -1
  78. codex/static_root/assets/VWindowItem-aal6-Uv4.d178e2af20a7.js.br +0 -0
  79. codex/static_root/assets/VWindowItem-aal6-Uv4.d178e2af20a7.js.gz +0 -0
  80. codex/static_root/assets/{VWindowItem-C__tAqdc.js → VWindowItem-aal6-Uv4.js} +1 -1
  81. codex/static_root/assets/VWindowItem-aal6-Uv4.js.br +0 -0
  82. codex/static_root/assets/VWindowItem-aal6-Uv4.js.gz +0 -0
  83. codex/static_root/assets/admin-D5JDmLDy.e4df3dd3a3fe.js +1 -0
  84. codex/static_root/assets/admin-D5JDmLDy.e4df3dd3a3fe.js.br +0 -0
  85. codex/static_root/assets/admin-D5JDmLDy.e4df3dd3a3fe.js.gz +0 -0
  86. codex/static_root/assets/admin-D5JDmLDy.js +1 -0
  87. codex/static_root/assets/admin-D5JDmLDy.js.br +0 -0
  88. codex/static_root/assets/admin-D5JDmLDy.js.gz +0 -0
  89. codex/static_root/assets/{admin-drawer-panel-rrNKi_GE.e2b034db7934.js → admin-drawer-panel-BF-h3Wm0.1f372db8d08f.js} +2 -2
  90. codex/static_root/assets/admin-drawer-panel-BF-h3Wm0.1f372db8d08f.js.br +0 -0
  91. codex/static_root/assets/admin-drawer-panel-BF-h3Wm0.1f372db8d08f.js.gz +0 -0
  92. codex/static_root/assets/{admin-drawer-panel-rrNKi_GE.js → admin-drawer-panel-BF-h3Wm0.js} +2 -2
  93. codex/static_root/assets/admin-drawer-panel-BF-h3Wm0.js.br +0 -0
  94. codex/static_root/assets/admin-drawer-panel-BF-h3Wm0.js.gz +0 -0
  95. codex/static_root/assets/browser-B1-dPld9.0b3fa4fdbe22.css +1 -0
  96. codex/static_root/assets/browser-B1-dPld9.0b3fa4fdbe22.css.br +0 -0
  97. codex/static_root/assets/browser-B1-dPld9.0b3fa4fdbe22.css.gz +0 -0
  98. codex/static_root/assets/browser-B1-dPld9.css +1 -0
  99. codex/static_root/assets/browser-B1-dPld9.css.br +0 -0
  100. codex/static_root/assets/browser-B1-dPld9.css.gz +0 -0
  101. codex/static_root/assets/browser-VhFkImB_.9c3b2c746614.js +1 -0
  102. codex/static_root/assets/browser-VhFkImB_.9c3b2c746614.js.br +0 -0
  103. codex/static_root/assets/browser-VhFkImB_.9c3b2c746614.js.gz +0 -0
  104. codex/static_root/assets/browser-VhFkImB_.js +1 -0
  105. codex/static_root/assets/browser-VhFkImB_.js.br +0 -0
  106. codex/static_root/assets/browser-VhFkImB_.js.gz +0 -0
  107. codex/static_root/assets/{change-password-dialog-Bn20jYzD.593c5af1732b.js → change-password-dialog-BCYw5K7E.a8bae0450f20.js} +1 -1
  108. codex/static_root/assets/change-password-dialog-BCYw5K7E.a8bae0450f20.js.br +0 -0
  109. codex/static_root/assets/change-password-dialog-BCYw5K7E.a8bae0450f20.js.gz +0 -0
  110. codex/static_root/assets/{change-password-dialog-Bn20jYzD.js → change-password-dialog-BCYw5K7E.js} +1 -1
  111. codex/static_root/assets/change-password-dialog-BCYw5K7E.js.br +0 -0
  112. codex/static_root/assets/change-password-dialog-BCYw5K7E.js.gz +0 -0
  113. codex/static_root/assets/{confirm-dialog-CWszioQE.859051c35dd9.js → confirm-dialog-c9JOSGSy.3c5788f95f47.js} +1 -1
  114. codex/static_root/assets/confirm-dialog-c9JOSGSy.3c5788f95f47.js.br +3 -0
  115. codex/static_root/assets/confirm-dialog-c9JOSGSy.3c5788f95f47.js.gz +0 -0
  116. codex/static_root/assets/{confirm-dialog-CWszioQE.js → confirm-dialog-c9JOSGSy.js} +1 -1
  117. codex/static_root/assets/confirm-dialog-c9JOSGSy.js.br +3 -0
  118. codex/static_root/assets/confirm-dialog-c9JOSGSy.js.gz +0 -0
  119. codex/static_root/assets/{datetime-column-BSdlLt-4.27ec2df67a0b.js → datetime-column-D-6LPKdI.d46bbd8134df.js} +1 -1
  120. codex/static_root/assets/datetime-column-D-6LPKdI.d46bbd8134df.js.br +0 -0
  121. codex/static_root/assets/datetime-column-D-6LPKdI.d46bbd8134df.js.gz +0 -0
  122. codex/static_root/assets/{datetime-column-BSdlLt-4.js → datetime-column-D-6LPKdI.js} +1 -1
  123. codex/static_root/assets/datetime-column-D-6LPKdI.js.br +0 -0
  124. codex/static_root/assets/datetime-column-D-6LPKdI.js.gz +0 -0
  125. codex/static_root/assets/{filter-D3gaGf87.1f14dbfd9929.js → filter-BOnVoHTU.93c3d0d0d3f6.js} +1 -1
  126. codex/static_root/assets/filter-BOnVoHTU.93c3d0d0d3f6.js.br +0 -0
  127. codex/static_root/assets/filter-BOnVoHTU.93c3d0d0d3f6.js.gz +0 -0
  128. codex/static_root/assets/{filter-D3gaGf87.js → filter-BOnVoHTU.js} +1 -1
  129. codex/static_root/assets/filter-BOnVoHTU.js.br +0 -0
  130. codex/static_root/assets/filter-BOnVoHTU.js.gz +0 -0
  131. codex/static_root/assets/{flag-tab-8vzpdySX.a982c7f0776a.js → flag-tab-IniE-dLy.a02733e359aa.js} +1 -1
  132. codex/static_root/assets/flag-tab-IniE-dLy.a02733e359aa.js.br +0 -0
  133. codex/static_root/assets/flag-tab-IniE-dLy.a02733e359aa.js.gz +0 -0
  134. codex/static_root/assets/{flag-tab-8vzpdySX.js → flag-tab-IniE-dLy.js} +1 -1
  135. codex/static_root/assets/flag-tab-IniE-dLy.js.br +0 -0
  136. codex/static_root/assets/flag-tab-IniE-dLy.js.gz +0 -0
  137. codex/static_root/assets/{group-tab-Cz9t_ECi.a9e8aea79254.js → group-tab-RPa8XC0J.3d483919ea92.js} +1 -1
  138. codex/static_root/assets/group-tab-RPa8XC0J.3d483919ea92.js.br +0 -0
  139. codex/static_root/assets/group-tab-RPa8XC0J.3d483919ea92.js.gz +0 -0
  140. codex/static_root/assets/{group-tab-Cz9t_ECi.js → group-tab-RPa8XC0J.js} +1 -1
  141. codex/static_root/assets/group-tab-RPa8XC0J.js.br +0 -0
  142. codex/static_root/assets/group-tab-RPa8XC0J.js.gz +0 -0
  143. codex/static_root/assets/{http-error-Cqtszzf1.b31df3d9c29f.js → http-error-DBQycf8i.561cde2b4598.js} +1 -1
  144. codex/static_root/assets/http-error-DBQycf8i.561cde2b4598.js.br +0 -0
  145. codex/static_root/assets/http-error-DBQycf8i.561cde2b4598.js.gz +0 -0
  146. codex/static_root/assets/{http-error-Cqtszzf1.js → http-error-DBQycf8i.js} +1 -1
  147. codex/static_root/assets/http-error-DBQycf8i.js.br +0 -0
  148. codex/static_root/assets/http-error-DBQycf8i.js.gz +0 -0
  149. codex/static_root/assets/{library-tab-DMlnswUU.js → library-tab-C5trLvkq.35c7a1b79c71.js} +1 -1
  150. codex/static_root/assets/library-tab-C5trLvkq.35c7a1b79c71.js.br +0 -0
  151. codex/static_root/assets/library-tab-C5trLvkq.35c7a1b79c71.js.gz +0 -0
  152. codex/static_root/assets/{library-tab-DMlnswUU.30f454e84184.js → library-tab-C5trLvkq.js} +1 -1
  153. codex/static_root/assets/library-tab-C5trLvkq.js.br +0 -0
  154. codex/static_root/assets/library-tab-C5trLvkq.js.gz +0 -0
  155. codex/static_root/assets/main-x0Pcl23A.364bfe447c9c.js +32 -0
  156. codex/static_root/assets/main-x0Pcl23A.364bfe447c9c.js.br +0 -0
  157. codex/static_root/assets/main-x0Pcl23A.364bfe447c9c.js.gz +0 -0
  158. codex/static_root/assets/main-x0Pcl23A.js +32 -0
  159. codex/static_root/assets/main-x0Pcl23A.js.br +0 -0
  160. codex/static_root/assets/main-x0Pcl23A.js.gz +0 -0
  161. codex/static_root/assets/pagination-toolbar-BLUoVz2y.50051431acf1.css +1 -0
  162. codex/static_root/assets/pagination-toolbar-BLUoVz2y.50051431acf1.css.br +0 -0
  163. codex/static_root/assets/pagination-toolbar-BLUoVz2y.50051431acf1.css.gz +0 -0
  164. codex/static_root/assets/pagination-toolbar-BLUoVz2y.css +1 -0
  165. codex/static_root/assets/pagination-toolbar-BLUoVz2y.css.br +0 -0
  166. codex/static_root/assets/pagination-toolbar-BLUoVz2y.css.gz +0 -0
  167. codex/static_root/assets/pagination-toolbar-DEgDOzzp.71aa03f525e4.js +1 -0
  168. codex/static_root/assets/pagination-toolbar-DEgDOzzp.71aa03f525e4.js.br +0 -0
  169. codex/static_root/assets/pagination-toolbar-DEgDOzzp.71aa03f525e4.js.gz +0 -0
  170. codex/static_root/assets/pagination-toolbar-DEgDOzzp.js +1 -0
  171. codex/static_root/assets/pagination-toolbar-DEgDOzzp.js.br +0 -0
  172. codex/static_root/assets/pagination-toolbar-DEgDOzzp.js.gz +0 -0
  173. codex/static_root/assets/{pdf-doc-BiamrjRZ.4c9be1ade43c.js → pdf-doc-CkV-xfvG.35c1a6a19a41.js} +1 -1
  174. codex/static_root/assets/pdf-doc-CkV-xfvG.35c1a6a19a41.js.br +0 -0
  175. codex/static_root/assets/pdf-doc-CkV-xfvG.35c1a6a19a41.js.gz +0 -0
  176. codex/static_root/assets/{pdf-doc-BiamrjRZ.js → pdf-doc-CkV-xfvG.js} +1 -1
  177. codex/static_root/assets/pdf-doc-CkV-xfvG.js.br +0 -0
  178. codex/static_root/assets/pdf-doc-CkV-xfvG.js.gz +0 -0
  179. codex/static_root/assets/reader-ClcgSG93.6afe950e70ac.js +2 -0
  180. codex/static_root/assets/reader-ClcgSG93.6afe950e70ac.js.br +0 -0
  181. codex/static_root/assets/reader-ClcgSG93.6afe950e70ac.js.gz +0 -0
  182. codex/static_root/assets/reader-ClcgSG93.js +2 -0
  183. codex/static_root/assets/reader-ClcgSG93.js.br +0 -0
  184. codex/static_root/assets/reader-ClcgSG93.js.gz +0 -0
  185. codex/static_root/assets/{reader-BpsctRsY.5d72e393911a.css → reader-CqXFa_gE.78267e9de0c2.css} +1 -1
  186. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.br +0 -0
  187. codex/static_root/assets/reader-CqXFa_gE.78267e9de0c2.css.gz +0 -0
  188. codex/static_root/assets/{reader-BpsctRsY.css → reader-CqXFa_gE.css} +1 -1
  189. codex/static_root/assets/reader-CqXFa_gE.css.br +0 -0
  190. codex/static_root/assets/reader-CqXFa_gE.css.gz +0 -0
  191. codex/static_root/assets/{relation-chips-C5acBjpy.dc67d4528868.js → relation-chips-2vga5ApB.d94c344f909b.js} +1 -1
  192. codex/static_root/assets/relation-chips-2vga5ApB.d94c344f909b.js.br +0 -0
  193. codex/static_root/assets/relation-chips-2vga5ApB.d94c344f909b.js.gz +0 -0
  194. codex/static_root/assets/{relation-chips-C5acBjpy.js → relation-chips-2vga5ApB.js} +1 -1
  195. codex/static_root/assets/relation-chips-2vga5ApB.js.br +0 -0
  196. codex/static_root/assets/relation-chips-2vga5ApB.js.gz +0 -0
  197. codex/static_root/assets/{settings-drawer-D_JuRi7F.f02f3f981f8f.js → settings-drawer-BOTI_tHr.03748f801e08.js} +2 -2
  198. codex/static_root/assets/settings-drawer-BOTI_tHr.03748f801e08.js.br +0 -0
  199. codex/static_root/assets/settings-drawer-BOTI_tHr.03748f801e08.js.gz +0 -0
  200. codex/static_root/assets/{settings-drawer-D_JuRi7F.js → settings-drawer-BOTI_tHr.js} +2 -2
  201. codex/static_root/assets/settings-drawer-BOTI_tHr.js.br +0 -0
  202. codex/static_root/assets/settings-drawer-BOTI_tHr.js.gz +0 -0
  203. codex/static_root/assets/{stats-tab-ClqRTk1z.c759ace65a87.js → stats-tab-CBz_jhmH.9bdc41225f06.js} +1 -1
  204. codex/static_root/assets/stats-tab-CBz_jhmH.9bdc41225f06.js.br +0 -0
  205. codex/static_root/assets/stats-tab-CBz_jhmH.9bdc41225f06.js.gz +0 -0
  206. codex/static_root/assets/{stats-tab-ClqRTk1z.js → stats-tab-CBz_jhmH.js} +1 -1
  207. codex/static_root/assets/stats-tab-CBz_jhmH.js.br +0 -0
  208. codex/static_root/assets/stats-tab-CBz_jhmH.js.gz +0 -0
  209. codex/static_root/assets/{task-tab-DF9qa6LO.0b32cafde4ad.js → task-tab-B8sYV6Ia.90a8deec0214.js} +1 -1
  210. codex/static_root/assets/task-tab-B8sYV6Ia.90a8deec0214.js.br +0 -0
  211. codex/static_root/assets/task-tab-B8sYV6Ia.90a8deec0214.js.gz +0 -0
  212. codex/static_root/assets/{task-tab-DF9qa6LO.js → task-tab-B8sYV6Ia.js} +1 -1
  213. codex/static_root/assets/task-tab-B8sYV6Ia.js.br +0 -0
  214. codex/static_root/assets/task-tab-B8sYV6Ia.js.gz +0 -0
  215. codex/static_root/assets/to-case-CnnzURtN.f0e4b42ddb89.js +1 -0
  216. codex/static_root/assets/to-case-CnnzURtN.js +1 -0
  217. codex/static_root/assets/{unauthorized-BjKuLH6k.2ae00ddd79fb.js → unauthorized-D5-FvA2_.61b1cd239b47.js} +1 -1
  218. codex/static_root/assets/unauthorized-D5-FvA2_.61b1cd239b47.js.br +0 -0
  219. codex/static_root/assets/unauthorized-D5-FvA2_.61b1cd239b47.js.gz +0 -0
  220. codex/static_root/assets/{unauthorized-BjKuLH6k.js → unauthorized-D5-FvA2_.js} +1 -1
  221. codex/static_root/assets/unauthorized-D5-FvA2_.js.br +0 -0
  222. codex/static_root/assets/unauthorized-D5-FvA2_.js.gz +0 -0
  223. codex/static_root/assets/{user-tab-Dpxe3ygA.7d90174631f0.js → user-tab-pzFjrggM.97c02ebee729.js} +1 -1
  224. codex/static_root/assets/user-tab-pzFjrggM.97c02ebee729.js.br +0 -0
  225. codex/static_root/assets/user-tab-pzFjrggM.97c02ebee729.js.gz +0 -0
  226. codex/static_root/assets/{user-tab-Dpxe3ygA.js → user-tab-pzFjrggM.js} +1 -1
  227. codex/static_root/assets/user-tab-pzFjrggM.js.br +0 -0
  228. codex/static_root/assets/user-tab-pzFjrggM.js.gz +0 -0
  229. codex/static_root/js/{choices.a0aaaa6c7ef7.json → choices.2f6b0359e1ef.json} +1 -1
  230. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  231. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  232. codex/static_root/js/choices.json +1 -1
  233. codex/static_root/js/choices.json.br +0 -0
  234. codex/static_root/js/choices.json.gz +0 -0
  235. codex/static_root/{manifest.35e46a3ec8fa.json → manifest.a18726680d5f.json} +223 -223
  236. codex/static_root/manifest.a18726680d5f.json.br +0 -0
  237. codex/static_root/manifest.a18726680d5f.json.gz +0 -0
  238. codex/static_root/manifest.json +223 -223
  239. codex/static_root/manifest.json.br +0 -0
  240. codex/static_root/manifest.json.gz +0 -0
  241. codex/static_root/staticfiles.json +1 -1
  242. codex/util.py +8 -6
  243. codex/views/bookmark.py +29 -23
  244. codex/views/browser/annotations.py +9 -45
  245. codex/views/browser/browser.py +26 -6
  246. codex/views/browser/choices.py +4 -22
  247. codex/views/browser/cover.py +35 -11
  248. codex/views/browser/filters/annotations.py +23 -8
  249. codex/views/browser/metadata.py +22 -12
  250. codex/views/const.py +1 -1
  251. codex/views/reader/init.py +16 -12
  252. codex/views/reader/page.py +13 -2
  253. codex/views/session.py +3 -1
  254. codex/views/util.py +13 -0
  255. codex/websockets/consumers.py +1 -1
  256. {codex-1.6.0a10.dist-info → codex-1.6.0a12.dist-info}/METADATA +1 -1
  257. {codex-1.6.0a10.dist-info → codex-1.6.0a12.dist-info}/RECORD +260 -262
  258. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.br +0 -0
  259. codex/static_root/assets/VCheckbox-CY3rcqlS.de5154294920.js.gz +0 -0
  260. codex/static_root/assets/VCheckbox-CY3rcqlS.js.br +0 -0
  261. codex/static_root/assets/VCheckbox-CY3rcqlS.js.gz +0 -0
  262. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.br +0 -0
  263. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.9a90d6818bf6.js.gz +0 -0
  264. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.br +0 -0
  265. codex/static_root/assets/VCheckboxBtn-D2tPTt6P.js.gz +0 -0
  266. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.br +0 -0
  267. codex/static_root/assets/VCombobox-aJr--Ou7.f290ccf2ce25.js.gz +0 -0
  268. codex/static_root/assets/VCombobox-aJr--Ou7.js.br +0 -0
  269. codex/static_root/assets/VCombobox-aJr--Ou7.js.gz +0 -0
  270. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.br +0 -0
  271. codex/static_root/assets/VDialog-61pzfUB2.33f73beb5224.js.gz +0 -0
  272. codex/static_root/assets/VDialog-61pzfUB2.js.br +0 -0
  273. codex/static_root/assets/VDialog-61pzfUB2.js.gz +0 -0
  274. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.br +0 -0
  275. codex/static_root/assets/VExpansionPanels-rewm0P4z.5c7680f815b6.js.gz +0 -0
  276. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.br +0 -0
  277. codex/static_root/assets/VExpansionPanels-rewm0P4z.js.gz +0 -0
  278. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.br +0 -0
  279. codex/static_root/assets/VRadioGroup-QFQbK1qo.750a42b1b814.js.gz +0 -0
  280. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.br +0 -0
  281. codex/static_root/assets/VRadioGroup-QFQbK1qo.js.gz +0 -0
  282. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.br +0 -0
  283. codex/static_root/assets/VSelect-Dixy6okt.5d53830daa8a.js.gz +0 -0
  284. codex/static_root/assets/VSelect-Dixy6okt.js.br +0 -0
  285. codex/static_root/assets/VSelect-Dixy6okt.js.gz +0 -0
  286. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.br +0 -0
  287. codex/static_root/assets/VSelectionControl-MNmxKc3J.08055d00917e.js.gz +0 -0
  288. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.br +0 -0
  289. codex/static_root/assets/VSelectionControl-MNmxKc3J.js.gz +0 -0
  290. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.br +0 -0
  291. codex/static_root/assets/VSlideGroup-DZEV0FoV.5f996daf0e98.js.gz +0 -0
  292. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.br +0 -0
  293. codex/static_root/assets/VSlideGroup-DZEV0FoV.js.gz +0 -0
  294. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.br +0 -0
  295. codex/static_root/assets/VTable-lcnYg9Ee.d72927b13c2e.js.gz +0 -0
  296. codex/static_root/assets/VTable-lcnYg9Ee.js.br +0 -0
  297. codex/static_root/assets/VTable-lcnYg9Ee.js.gz +0 -0
  298. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.br +0 -0
  299. codex/static_root/assets/VTextField-BkFrjhdV.b02653a9b3b7.js.gz +0 -0
  300. codex/static_root/assets/VTextField-BkFrjhdV.js.br +0 -0
  301. codex/static_root/assets/VTextField-BkFrjhdV.js.gz +0 -0
  302. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.br +0 -0
  303. codex/static_root/assets/VWindowItem-C__tAqdc.9791c128c3b7.js.gz +0 -0
  304. codex/static_root/assets/VWindowItem-C__tAqdc.js.br +0 -0
  305. codex/static_root/assets/VWindowItem-C__tAqdc.js.gz +0 -0
  306. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js +0 -1
  307. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.br +0 -0
  308. codex/static_root/assets/admin-CXES2HXe.9e48a9bf84e0.js.gz +0 -0
  309. codex/static_root/assets/admin-CXES2HXe.js +0 -1
  310. codex/static_root/assets/admin-CXES2HXe.js.br +0 -0
  311. codex/static_root/assets/admin-CXES2HXe.js.gz +0 -0
  312. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.br +0 -0
  313. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.e2b034db7934.js.gz +0 -0
  314. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.br +0 -0
  315. codex/static_root/assets/admin-drawer-panel-rrNKi_GE.js.gz +0 -0
  316. codex/static_root/assets/browser-BuDeXGEe.css +0 -1
  317. codex/static_root/assets/browser-BuDeXGEe.css.br +0 -0
  318. codex/static_root/assets/browser-BuDeXGEe.css.gz +0 -0
  319. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css +0 -1
  320. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.br +0 -0
  321. codex/static_root/assets/browser-BuDeXGEe.f60854183b45.css.gz +0 -0
  322. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js +0 -1
  323. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.br +0 -0
  324. codex/static_root/assets/browser-s6mFzrK9.0d2200b451af.js.gz +0 -0
  325. codex/static_root/assets/browser-s6mFzrK9.js +0 -1
  326. codex/static_root/assets/browser-s6mFzrK9.js.br +0 -0
  327. codex/static_root/assets/browser-s6mFzrK9.js.gz +0 -0
  328. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.br +0 -0
  329. codex/static_root/assets/change-password-dialog-Bn20jYzD.593c5af1732b.js.gz +0 -0
  330. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.br +0 -0
  331. codex/static_root/assets/change-password-dialog-Bn20jYzD.js.gz +0 -0
  332. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.br +0 -0
  333. codex/static_root/assets/confirm-dialog-CWszioQE.859051c35dd9.js.gz +0 -0
  334. codex/static_root/assets/confirm-dialog-CWszioQE.js.br +0 -0
  335. codex/static_root/assets/confirm-dialog-CWszioQE.js.gz +0 -0
  336. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.br +0 -0
  337. codex/static_root/assets/datetime-column-BSdlLt-4.27ec2df67a0b.js.gz +0 -0
  338. codex/static_root/assets/datetime-column-BSdlLt-4.js.br +0 -0
  339. codex/static_root/assets/datetime-column-BSdlLt-4.js.gz +0 -0
  340. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.br +0 -0
  341. codex/static_root/assets/filter-D3gaGf87.1f14dbfd9929.js.gz +0 -0
  342. codex/static_root/assets/filter-D3gaGf87.js.br +0 -0
  343. codex/static_root/assets/filter-D3gaGf87.js.gz +0 -0
  344. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.br +0 -0
  345. codex/static_root/assets/flag-tab-8vzpdySX.a982c7f0776a.js.gz +0 -0
  346. codex/static_root/assets/flag-tab-8vzpdySX.js.br +0 -0
  347. codex/static_root/assets/flag-tab-8vzpdySX.js.gz +0 -0
  348. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.br +0 -0
  349. codex/static_root/assets/group-tab-Cz9t_ECi.a9e8aea79254.js.gz +0 -0
  350. codex/static_root/assets/group-tab-Cz9t_ECi.js.br +0 -0
  351. codex/static_root/assets/group-tab-Cz9t_ECi.js.gz +0 -0
  352. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.br +0 -0
  353. codex/static_root/assets/http-error-Cqtszzf1.b31df3d9c29f.js.gz +0 -0
  354. codex/static_root/assets/http-error-Cqtszzf1.js.br +0 -0
  355. codex/static_root/assets/http-error-Cqtszzf1.js.gz +0 -0
  356. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.br +0 -0
  357. codex/static_root/assets/library-tab-DMlnswUU.30f454e84184.js.gz +0 -0
  358. codex/static_root/assets/library-tab-DMlnswUU.js.br +0 -0
  359. codex/static_root/assets/library-tab-DMlnswUU.js.gz +0 -0
  360. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js +0 -32
  361. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.br +0 -0
  362. codex/static_root/assets/main-vjXu_QYL.548bd4c38799.js.gz +0 -0
  363. codex/static_root/assets/main-vjXu_QYL.js +0 -32
  364. codex/static_root/assets/main-vjXu_QYL.js.br +0 -0
  365. codex/static_root/assets/main-vjXu_QYL.js.gz +0 -0
  366. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css +0 -1
  367. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.br +0 -0
  368. codex/static_root/assets/pagination-toolbar-DGdMiBRs.5829b01b8a2f.css.gz +0 -0
  369. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css +0 -1
  370. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.br +0 -0
  371. codex/static_root/assets/pagination-toolbar-DGdMiBRs.css.gz +0 -0
  372. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js +0 -1
  373. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.br +0 -0
  374. codex/static_root/assets/pagination-toolbar-JW78pa0q.91024effdab9.js.gz +0 -0
  375. codex/static_root/assets/pagination-toolbar-JW78pa0q.js +0 -1
  376. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.br +0 -0
  377. codex/static_root/assets/pagination-toolbar-JW78pa0q.js.gz +0 -0
  378. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.br +0 -0
  379. codex/static_root/assets/pdf-doc-BiamrjRZ.4c9be1ade43c.js.gz +0 -0
  380. codex/static_root/assets/pdf-doc-BiamrjRZ.js.br +0 -0
  381. codex/static_root/assets/pdf-doc-BiamrjRZ.js.gz +0 -0
  382. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.br +0 -0
  383. codex/static_root/assets/reader-BpsctRsY.5d72e393911a.css.gz +0 -0
  384. codex/static_root/assets/reader-BpsctRsY.css.br +0 -0
  385. codex/static_root/assets/reader-BpsctRsY.css.gz +0 -0
  386. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js +0 -2
  387. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.br +0 -0
  388. codex/static_root/assets/reader-CDk8oZzB.ad62eebda920.js.gz +0 -0
  389. codex/static_root/assets/reader-CDk8oZzB.js +0 -2
  390. codex/static_root/assets/reader-CDk8oZzB.js.br +0 -0
  391. codex/static_root/assets/reader-CDk8oZzB.js.gz +0 -0
  392. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.br +0 -0
  393. codex/static_root/assets/relation-chips-C5acBjpy.dc67d4528868.js.gz +0 -0
  394. codex/static_root/assets/relation-chips-C5acBjpy.js.br +0 -0
  395. codex/static_root/assets/relation-chips-C5acBjpy.js.gz +0 -0
  396. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.br +0 -0
  397. codex/static_root/assets/settings-drawer-D_JuRi7F.f02f3f981f8f.js.gz +0 -0
  398. codex/static_root/assets/settings-drawer-D_JuRi7F.js.br +0 -0
  399. codex/static_root/assets/settings-drawer-D_JuRi7F.js.gz +0 -0
  400. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.br +0 -0
  401. codex/static_root/assets/stats-tab-ClqRTk1z.c759ace65a87.js.gz +0 -0
  402. codex/static_root/assets/stats-tab-ClqRTk1z.js.br +0 -0
  403. codex/static_root/assets/stats-tab-ClqRTk1z.js.gz +0 -0
  404. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.br +0 -0
  405. codex/static_root/assets/task-tab-DF9qa6LO.0b32cafde4ad.js.gz +0 -0
  406. codex/static_root/assets/task-tab-DF9qa6LO.js.br +0 -0
  407. codex/static_root/assets/task-tab-DF9qa6LO.js.gz +0 -0
  408. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js +0 -1
  409. codex/static_root/assets/to-case-CGbULRaE.4c80644f460d.js.br +0 -0
  410. codex/static_root/assets/to-case-CGbULRaE.js +0 -1
  411. codex/static_root/assets/to-case-CGbULRaE.js.br +0 -0
  412. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.br +0 -0
  413. codex/static_root/assets/unauthorized-BjKuLH6k.2ae00ddd79fb.js.gz +0 -0
  414. codex/static_root/assets/unauthorized-BjKuLH6k.js.br +0 -0
  415. codex/static_root/assets/unauthorized-BjKuLH6k.js.gz +0 -0
  416. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.br +0 -0
  417. codex/static_root/assets/user-tab-Dpxe3ygA.7d90174631f0.js.gz +0 -0
  418. codex/static_root/assets/user-tab-Dpxe3ygA.js.br +0 -0
  419. codex/static_root/assets/user-tab-Dpxe3ygA.js.gz +0 -0
  420. codex/static_root/js/choices.a0aaaa6c7ef7.json.br +0 -0
  421. codex/static_root/js/choices.a0aaaa6c7ef7.json.gz +0 -0
  422. codex/static_root/manifest.35e46a3ec8fa.json.br +0 -0
  423. codex/static_root/manifest.35e46a3ec8fa.json.gz +0 -0
  424. {codex-1.6.0a10.dist-info → codex-1.6.0a12.dist-info}/LICENSE +0 -0
  425. {codex-1.6.0a10.dist-info → codex-1.6.0a12.dist-info}/WHEEL +0 -0
  426. {codex-1.6.0a10.dist-info → codex-1.6.0a12.dist-info}/entry_points.txt +0 -0
@@ -26,17 +26,17 @@ class CoverCreateMixin(CoverPathMixin):
26
26
  @classmethod
27
27
  def _create_cover_thumbnail(cls, cover_image_data):
28
28
  """Isolate the save thumbnail function for leak detection."""
29
- with BytesIO() as cover_thumb_buffer:
30
- with BytesIO(cover_image_data) as image_io:
31
- with Image.open(image_io) as cover_image:
32
- cover_image.thumbnail(
33
- cls._THUMBNAIL_SIZE,
34
- Image.Resampling.LANCZOS, # type: ignore
35
- reducing_gap=3.0,
36
- )
37
- cover_image.save(cover_thumb_buffer, "WEBP", method=6)
38
- cover_image.close() # extra close for animated sequences
39
- return cover_thumb_buffer.getvalue()
29
+ cover_thumb_buffer = BytesIO()
30
+ with BytesIO(cover_image_data) as image_io:
31
+ with Image.open(image_io) as cover_image:
32
+ cover_image.thumbnail(
33
+ cls._THUMBNAIL_SIZE,
34
+ Image.Resampling.LANCZOS, # type: ignore
35
+ reducing_gap=3.0,
36
+ )
37
+ cover_image.save(cover_thumb_buffer, "WEBP", method=6)
38
+ cover_image.close() # extra close for animated sequences
39
+ return cover_thumb_buffer
40
40
 
41
41
  @classmethod
42
42
  def _get_comic_cover_image(cls, comic_path):
@@ -71,15 +71,18 @@ class CoverCreateMixin(CoverPathMixin):
71
71
  cover_image = cls._get_custom_cover_image(db_path)
72
72
  else:
73
73
  cover_image = cls._get_comic_cover_image(db_path)
74
- data = cls._create_cover_thumbnail(cover_image)
74
+ thumb_buffer = cls._create_cover_thumbnail(cover_image)
75
+ thumb_bytes = thumb_buffer.getvalue()
76
+ thumb_buffer.seek(0)
75
77
  except Exception as exc:
76
- data = b""
78
+ thumb_bytes = b""
79
+ thumb_buffer = None
77
80
  cover_str = db_path if db_path else f"{pk=}"
78
81
  log.warning(f"Could not create cover thumbnail for {cover_str}: {exc}")
79
82
 
80
- task = CoverSaveToCache(cover_path, data)
83
+ task = CoverSaveToCache(cover_path, thumb_bytes)
81
84
  librarian_queue.put(task)
82
- return data
85
+ return thumb_buffer
83
86
 
84
87
  def save_cover_to_cache(self, cover_path, data):
85
88
  """Save cover thumb image to the disk cache."""
@@ -110,9 +113,11 @@ class CoverCreateMixin(CoverPathMixin):
110
113
  status.decrement_total()
111
114
  else:
112
115
  # bulk contributor creates covers inline
113
- self.create_cover_from_path(
116
+ data = self.create_cover_from_path(
114
117
  pk, cover_path, self.log, self.librarian_queue
115
118
  )
119
+ if data:
120
+ data.close()
116
121
  status.increment_complete()
117
122
  self.status_controller.update(status)
118
123
 
@@ -4,66 +4,40 @@ from types import MappingProxyType
4
4
 
5
5
  from rest_framework.fields import SerializerMethodField
6
6
  from rest_framework.serializers import (
7
- BooleanField,
8
7
  CharField,
9
- ChoiceField,
10
- FloatField,
11
- IntegerField,
12
8
  Serializer,
13
9
  )
14
10
  from rest_framework.utils.serializer_helpers import ReturnList
15
11
 
16
12
  from codex.serializers.browser.filters import BrowserSettingsFilterSerializer
17
13
  from codex.serializers.fields import (
18
- BooleanListField,
19
- CharListField,
20
- DecadeListField,
21
- FloatListField,
22
- IntListField,
14
+ VuetifyCharField,
15
+ VuetifyIntegerField,
23
16
  )
17
+ from codex.serializers.models.pycountry import CountrySerializer, LanguageSerializer
24
18
 
25
19
 
26
20
  class BrowserChoicesCharPkSerializer(Serializer):
27
- """Named Model Serailizer with pk = char hack for languages & countries."""
21
+ """Named Model Serializer."""
28
22
 
29
- pk = CharField(read_only=True)
23
+ pk = VuetifyCharField(read_only=True)
30
24
  name = CharField(read_only=True)
31
25
 
32
26
 
33
- class BrowserChoicesBoolPkSerializer(BrowserChoicesCharPkSerializer):
34
- """Named Model Serailizer with pk = char hack for languages & countries."""
35
-
36
- pk = BooleanField(read_only=True)
37
-
38
-
39
- class BrowserChoicesIntPkSerializer(BrowserChoicesCharPkSerializer):
40
- """Named Model Serailizer with pk = char hack for languages & countries."""
41
-
42
- pk = IntegerField(read_only=True)
43
-
44
-
45
- class BrowserChoicesFloatPkSerializer(BrowserChoicesCharPkSerializer):
27
+ class BrowserChoicesIntegerPkSerializer(BrowserChoicesCharPkSerializer):
46
28
  """Named Model Serailizer with pk = char hack for languages & countries."""
47
29
 
48
- pk = FloatField(read_only=True)
30
+ pk = VuetifyIntegerField(read_only=True)
49
31
 
50
32
 
51
- _CHOICES_TYPE_SERIALIZER_MAP = MappingProxyType(
52
- {
53
- BooleanListField: BrowserChoicesBoolPkSerializer,
54
- CharListField: BrowserChoicesCharPkSerializer,
55
- ChoiceField: BrowserChoicesCharPkSerializer,
56
- DecadeListField: BrowserChoicesIntPkSerializer,
57
- FloatListField: BrowserChoicesFloatPkSerializer,
58
- IntListField: BrowserChoicesIntPkSerializer,
59
- }
60
- )
61
- _CHOICES_SERIALIZER_CLASS_MAP = MappingProxyType(
33
+ _CHOICES_NAME_SERIALIZER_MAP = MappingProxyType(
62
34
  {
63
- name: _CHOICES_TYPE_SERIALIZER_MAP[field.__class__]
64
- for name, field in BrowserSettingsFilterSerializer().fields.items() # type: ignore
35
+ "bookmark": BrowserChoicesCharPkSerializer,
36
+ "country": CountrySerializer,
37
+ "language": LanguageSerializer,
65
38
  }
66
39
  )
40
+ _LIST_FIELDS = frozenset({"decade", "monochrome", "reading_direction", "year"})
67
41
 
68
42
 
69
43
  class BrowserChoicesFilterSerializer(Serializer):
@@ -73,10 +47,15 @@ class BrowserChoicesFilterSerializer(Serializer):
73
47
 
74
48
  def get_choices(self, obj) -> ReturnList:
75
49
  """Dynamic Serializer response by field type."""
76
- key = obj.get("field_name", "")
50
+ field_name = obj.get("field_name", "")
77
51
  choices = obj.get("choices", [])
78
- serializer_class = _CHOICES_SERIALIZER_CLASS_MAP.get(
79
- key, BrowserChoicesCharPkSerializer
80
- )
81
- serializer = serializer_class(choices, many=True)
82
- return serializer.data # type: ignore
52
+ serializer_class = _CHOICES_NAME_SERIALIZER_MAP.get(field_name)
53
+ value: ReturnList
54
+ if serializer_class:
55
+ value = serializer_class(choices, many=True).data # type: ignore
56
+ elif not serializer_class and field_name in _LIST_FIELDS:
57
+ field = BrowserSettingsFilterSerializer().get_fields().get(field_name) # type: ignore
58
+ value = field.to_representation(choices)
59
+ else:
60
+ value = BrowserChoicesIntegerPkSerializer(choices, many=True).data # type: ignore
61
+ return value
@@ -1,18 +1,14 @@
1
1
  """Browser Settings Filter Serializers."""
2
2
 
3
- from rest_framework.serializers import (
4
- BooleanField,
5
- ChoiceField,
6
- Serializer,
7
- )
3
+ from rest_framework.serializers import BooleanField, ListField, Serializer
8
4
 
9
- from codex.serializers.choices import CHOICES
10
5
  from codex.serializers.fields import (
11
- BooleanListField,
12
- CharListField,
13
- DecadeListField,
14
- FloatListField,
15
- IntListField,
6
+ BookmarkFilterField,
7
+ VuetifyBooleanField,
8
+ VuetifyCharField,
9
+ VuetifyDecadeField,
10
+ VuetifyFloatField,
11
+ VuetifyIntegerField,
16
12
  )
17
13
 
18
14
 
@@ -46,29 +42,27 @@ class BrowserFilterChoicesSerializer(Serializer):
46
42
  class BrowserSettingsFilterSerializer(Serializer):
47
43
  """Filter values for settings."""
48
44
 
49
- bookmark = ChoiceField(
50
- choices=tuple(CHOICES["bookmarkFilter"].keys()), required=False
51
- )
45
+ bookmark = BookmarkFilterField(required=False)
52
46
  # Dynamic filters
53
- age_rating = CharListField(allow_blank=True, required=False)
54
- characters = IntListField(required=False)
55
- community_rating = FloatListField(required=False)
56
- country = CharListField(allow_blank=True, required=False)
57
- contributors = IntListField(required=False)
58
- critical_rating = FloatListField(required=False)
59
- decade = DecadeListField(required=False)
60
- file_type = CharListField(allow_blank=True, required=False)
61
- genres = IntListField(required=False)
62
- identifier_type = CharListField(allow_blank=True, required=False)
63
- language = CharListField(allow_blank=True, required=False)
64
- locations = IntListField()
65
- monochrome = BooleanListField(required=False)
66
- original_format = CharListField(required=False, allow_blank=True)
67
- reading_direction = CharListField(required=False)
68
- series_groups = IntListField(required=False)
69
- stories = IntListField(required=False)
70
- story_arcs = IntListField(required=False)
71
- tagger = CharListField(required=False)
72
- tags = IntListField(required=False)
73
- teams = IntListField(required=False)
74
- year = IntListField(required=False)
47
+ age_rating = ListField(child=VuetifyCharField(), required=False)
48
+ characters = ListField(child=VuetifyIntegerField(), required=False)
49
+ community_rating = ListField(child=VuetifyFloatField(), required=False)
50
+ country = ListField(child=VuetifyIntegerField(), required=False)
51
+ contributors = ListField(child=VuetifyIntegerField(), required=False)
52
+ critical_rating = ListField(child=VuetifyFloatField(), required=False)
53
+ decade = ListField(child=VuetifyDecadeField(), required=False)
54
+ file_type = ListField(child=VuetifyCharField(), required=False)
55
+ genres = ListField(child=VuetifyIntegerField(), required=False)
56
+ identifier_type = ListField(child=VuetifyCharField(), required=False)
57
+ language = ListField(child=VuetifyIntegerField(), required=False)
58
+ locations = ListField(child=VuetifyIntegerField(), required=False)
59
+ monochrome = ListField(child=VuetifyBooleanField(), required=False)
60
+ original_format = ListField(child=VuetifyCharField(), required=False)
61
+ reading_direction = ListField(child=VuetifyCharField(), required=False)
62
+ series_groups = ListField(child=VuetifyIntegerField(), required=False)
63
+ stories = ListField(child=VuetifyIntegerField(), required=False)
64
+ story_arcs = ListField(child=VuetifyIntegerField(), required=False)
65
+ tagger = ListField(child=VuetifyCharField(), required=False)
66
+ tags = ListField(child=VuetifyIntegerField(), required=False)
67
+ teams = ListField(child=VuetifyIntegerField(), required=False)
68
+ year = ListField(child=VuetifyIntegerField(), required=False)
@@ -1,27 +1,33 @@
1
1
  """Serializer mixins."""
2
2
 
3
+ from datetime import datetime, timezone
4
+
3
5
  from rest_framework.serializers import (
4
6
  BooleanField,
5
7
  CharField,
6
8
  DateTimeField,
7
9
  DecimalField,
8
10
  IntegerField,
11
+ ListField,
9
12
  Serializer,
13
+ SerializerMethodField,
10
14
  )
11
15
 
12
- from codex.serializers.browser.filters import IntListField
13
- from codex.serializers.fields import TimestampField
16
+ from codex.logger.logging import get_logger
17
+ from codex.views.const import COMIC_GROUP, EPOCH_START
18
+
19
+ LOG = get_logger(__name__)
14
20
 
15
21
 
16
22
  class BrowserAggregateSerializerMixin(Serializer):
17
23
  """Mixin for browser, opds & metadata serializers."""
18
24
 
19
25
  group = CharField(read_only=True, max_length=1)
20
- ids = IntListField(read_only=True)
26
+ ids = ListField(child=IntegerField(), read_only=True)
21
27
 
22
28
  # Aggregate Annotations
23
29
  child_count = IntegerField(read_only=True)
24
- mtime = TimestampField(read_only=True)
30
+ mtime = SerializerMethodField(read_only=True)
25
31
 
26
32
  # Bookmark annotations
27
33
  page = IntegerField(read_only=True)
@@ -30,3 +36,30 @@ class BrowserAggregateSerializerMixin(Serializer):
30
36
  progress = DecimalField(
31
37
  max_digits=5, decimal_places=2, read_only=True, coerce_to_string=False
32
38
  )
39
+
40
+ def get_mtime(self, obj) -> int:
41
+ """Compute mtime from json array aggregates."""
42
+ mtime = EPOCH_START
43
+ for dt_str in obj.updated_ats:
44
+ if not dt_str:
45
+ continue
46
+ try:
47
+ dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S.%f").replace(
48
+ tzinfo=timezone.utc
49
+ )
50
+ except ValueError:
51
+ LOG.warning(
52
+ f"computing group mtime: {dt_str} is not a valid datetime string."
53
+ )
54
+ continue
55
+
56
+ if dt > mtime:
57
+ mtime = dt
58
+
59
+ if obj.group != COMIC_GROUP and (
60
+ mbua := getattr(obj, "max_bookmark_updated_at", None)
61
+ ):
62
+ mtime = max(mtime, mbua)
63
+
64
+ # print(obj.group, obj.pk, obj.name, obj.updated_ats, obj.max_bookmark_updated_at, "max:", mtime)
65
+ return int(mtime.timestamp() * 1000)
@@ -11,6 +11,7 @@ from rest_framework.serializers import (
11
11
  from codex.serializers.browser.filters import BrowserSettingsFilterSerializer
12
12
  from codex.serializers.choices import CHOICES, VUETIFY_NULL_CODE
13
13
  from codex.serializers.fields import BreadcrumbsField, TimestampField, TopGroupField
14
+ from codex.serializers.route import SimpleRouteSerializer
14
15
 
15
16
  VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
16
17
 
@@ -31,8 +32,8 @@ class BrowserFilterChoicesInputSerilalizer(Serializer):
31
32
  q = CharField(allow_blank=True, required=False)
32
33
 
33
34
 
34
- class BrowserCoverInputSerializer(BrowserFilterChoicesInputSerilalizer):
35
- """Browser Settings for the cover response."""
35
+ class BrowserCoverInputSerializerBase(BrowserFilterChoicesInputSerilalizer):
36
+ """Base Serializer for Cover and Settings."""
36
37
 
37
38
  custom_covers = BooleanField(required=False)
38
39
  dynamic_covers = BooleanField(required=False)
@@ -41,7 +42,13 @@ class BrowserCoverInputSerializer(BrowserFilterChoicesInputSerilalizer):
41
42
  show = BrowserSettingsShowGroupFlagsSerializer(required=False)
42
43
 
43
44
 
44
- class BrowserSettingsSerializerBase(BrowserCoverInputSerializer):
45
+ class BrowserCoverInputSerializer(BrowserCoverInputSerializerBase):
46
+ """Browser Settings for the cover response."""
47
+
48
+ parent = SimpleRouteSerializer(required=False)
49
+
50
+
51
+ class BrowserSettingsSerializerBase(BrowserCoverInputSerializerBase):
45
52
  """Base Serializer for Browser & OPDS Settings."""
46
53
 
47
54
  # search_results_limit = IntegerField(required=False)
@@ -53,10 +53,13 @@ _SINGLE_VALUE_KEYS = frozenset(
53
53
  {
54
54
  "customCovers",
55
55
  "dynamicCovers",
56
+ "finishOnLastPage",
56
57
  "orderReverse",
57
58
  "q",
59
+ "readRtlInReverse",
58
60
  "searchResultsLimit",
59
61
  "twentyFourHourTime",
62
+ "twoPages",
60
63
  }
61
64
  )
62
65
 
@@ -65,6 +68,7 @@ CHOICES = {}
65
68
  DEFAULTS = {}
66
69
  WEBSOCKET_MESSAGES = {}
67
70
  VUETIFY_NULL_CODE = -1
71
+ DUMMY_NULL_NAME = "_none_"
68
72
 
69
73
 
70
74
  def _find_filename_regex(js_root, module_name):
@@ -3,9 +3,9 @@
3
3
  from abc import ABC
4
4
  from datetime import datetime, timezone
5
5
 
6
+ import pycountry
6
7
  from django.core.exceptions import ValidationError
7
8
  from django.utils.translation import gettext_lazy as _
8
- from rest_framework.fields import Field
9
9
  from rest_framework.serializers import (
10
10
  BooleanField,
11
11
  CharField,
@@ -15,10 +15,12 @@ from rest_framework.serializers import (
15
15
  ListField,
16
16
  )
17
17
 
18
- from codex.serializers.choices import CHOICES, VUETIFY_NULL_CODE
18
+ from codex.logger.logging import get_logger
19
+ from codex.serializers.choices import CHOICES, DUMMY_NULL_NAME, VUETIFY_NULL_CODE
19
20
  from codex.serializers.route import RouteSerializer
20
21
 
21
22
  VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
23
+ LOG = get_logger(__name__)
22
24
 
23
25
 
24
26
  class TimestampField(IntegerField):
@@ -35,93 +37,61 @@ class TimestampField(IntegerField):
35
37
  return datetime.fromtimestamp(float(data) / 1000, tz=timezone.utc)
36
38
 
37
39
 
38
- def validate_decades(decades):
40
+ def validate_decade(decade):
39
41
  """Validate decades."""
40
- if not decades:
41
- return
42
42
  # * We don't need a whole db call just to be perfectly accurate
43
43
  # * -1s are decoded back into None before validation
44
- exists = False
45
- for decade in decades:
46
- if decade is None or decade % 10 == 0:
47
- exists = True
48
- break
49
- if not exists:
50
- raise ValidationError(_(f"Invalid decade in: {decades}"))
44
+ if decade is not None and decade % 10 != 0:
45
+ raise ValidationError(_(f"Invalid decade: {decade}"))
46
+ return True
51
47
 
52
48
 
53
- def validate_int_null(values):
54
- """Use a special code for null.
49
+ class VuetifyNullCodeFieldMixin:
50
+ """Convert Vuetify null codes to None."""
55
51
 
56
- Because if a vuetify component has a null key it changes it to the
57
- array index.
58
- """
59
- for index, value in enumerate(values):
60
- if value == VUETIFY_NULL_CODE:
61
- values[index] = None
62
- return values
52
+ NULL_CODE = VUETIFY_NULL_CODE
63
53
 
54
+ def to_internal_value(self, data):
55
+ """Convert numeric null code to None."""
56
+ return None if data == self.NULL_CODE else data
64
57
 
65
- def validate_str_null(values):
66
- """Use a special code for null.
67
58
 
68
- Because if a vuetify component has a null key it changes it to the
69
- array index. This is the version for CharFields.
70
- """
71
- for index, value in enumerate(values):
72
- if value == VUETIFY_NULL_CODE_STR:
73
- values[index] = None
74
- return values
59
+ class VuetifyFloatField(VuetifyNullCodeFieldMixin, FloatField): # type: ignore
60
+ """Float Field with null code conversion."""
75
61
 
76
62
 
77
- class FilterListField(ListField, ABC):
78
- """Filter List field with custom arguments."""
63
+ class VuetifyIntegerField(VuetifyNullCodeFieldMixin, IntegerField): # type: ignore
64
+ """Integer Field with null code conversion."""
79
65
 
80
- CHILD_CLASS = Field
81
- VALIDATORS = ()
82
66
 
83
- def __init__(self, *args, **kwargs):
84
- """Apply the subclass's arguments."""
85
- super().__init__(
86
- *args,
87
- child=self.CHILD_CLASS(allow_null=True, **kwargs),
88
- required=False,
89
- validators=self.VALIDATORS,
90
- )
91
-
92
-
93
- class FloatListField(FilterListField):
94
- """Decimal List Field with validation."""
95
-
96
- CHILD_CLASS = FloatField # type: ignore
97
- VALIDATORS = (validate_int_null,)
67
+ class VuetifyCharField(VuetifyNullCodeFieldMixin, CharField): # type: ignore
68
+ """Char Field with null code conversion."""
98
69
 
70
+ NULL_CODE = VUETIFY_NULL_CODE_STR
99
71
 
100
- class IntListField(FilterListField):
101
- """Integer List Field with validation."""
102
72
 
103
- CHILD_CLASS = IntegerField # type: ignore
104
- VALIDATORS = (validate_int_null,)
73
+ class VuetifyBooleanField(VuetifyNullCodeFieldMixin, BooleanField): # type: ignore
74
+ """Boolean Field with null code conversion."""
105
75
 
106
76
 
107
- class DecadeListField(IntListField):
108
- """Integer List Field with validation for decades."""
77
+ class BookmarkFilterField(ChoiceField):
78
+ """Bookmark Choice Field."""
109
79
 
110
- VALIDATORS = (validate_int_null, validate_decades)
111
-
112
-
113
- class CharListField(FilterListField):
114
- """Char List Field with validation."""
80
+ def __init__(self, *args, **kwargs):
81
+ """Use bookmark filter choices."""
82
+ super().__init__(
83
+ *args, choices=tuple(CHOICES["bookmarkFilter"].keys()), **kwargs
84
+ )
115
85
 
116
- CHILD_CLASS = CharField # type: ignore
117
- VALIDATORS = (validate_str_null,)
118
86
 
87
+ class VuetifyDecadeField(VuetifyIntegerField):
88
+ """Integer field with null code conversion and decade validation."""
119
89
 
120
- class BooleanListField(FilterListField):
121
- """Bool List Field with validation."""
90
+ VALIDATORS = (validate_decade,)
122
91
 
123
- CHILD_CLASS = BooleanField # type: ignore
124
- VALIDATORS = (validate_int_null,)
92
+ def __init__(self, *args, **kwargs):
93
+ """Use decade validator."""
94
+ super().__init__(*args, validators=self.VALIDATORS, **kwargs)
125
95
 
126
96
 
127
97
  class BreadcrumbsField(ListField):
@@ -138,3 +108,42 @@ class TopGroupField(ChoiceField):
138
108
  def __init__(self, *args, **kwargs):
139
109
  """Initialize with choices."""
140
110
  super().__init__(*args, choices=self.class_choices, **kwargs)
111
+
112
+
113
+ class PyCountryField(CharField, ABC):
114
+ """Serialize to a long pycountry name."""
115
+
116
+ LOOKUP_MODULE = pycountry.countries
117
+ _ALPHA_2_LEN = 2
118
+
119
+ def to_representation(self, value):
120
+ """Lookup the name with pycountry, just copy the value on fail."""
121
+ if not value:
122
+ return ""
123
+ if value == DUMMY_NULL_NAME:
124
+ return value
125
+ try:
126
+ # fix for https://github.com/flyingcircusio/pycountry/issues/41
127
+ lookup_obj = (
128
+ self.LOOKUP_MODULE.get(alpha_2=value)
129
+ if len(value) == self._ALPHA_2_LEN
130
+ else self.LOOKUP_MODULE.lookup(value)
131
+ )
132
+ # If lookup fails, return the key as the name
133
+ except Exception:
134
+ LOG.warning(f"Could not serialize name with pycountry {value}")
135
+ return value
136
+ else:
137
+ return lookup_obj.name if lookup_obj else value
138
+
139
+
140
+ class CountryField(PyCountryField):
141
+ """Serializer to long country name."""
142
+
143
+ LOOKUP_MODULE = pycountry.countries
144
+
145
+
146
+ class LanguageField(PyCountryField):
147
+ """Serializer to long language name."""
148
+
149
+ LOOKUP_MODULE = pycountry.languages
@@ -1,73 +1,33 @@
1
1
  """Pycountry Model Serializers."""
2
2
 
3
- import pycountry
4
- from drf_spectacular.types import OpenApiTypes
5
- from drf_spectacular.utils import extend_schema_field
6
- from rest_framework.serializers import (
7
- SerializerMethodField,
8
- )
9
-
3
+ from codex.logger.logging import get_logger
10
4
  from codex.models import (
11
5
  Country,
12
6
  Language,
13
7
  )
8
+ from codex.serializers.fields import CountryField, LanguageField
14
9
  from codex.serializers.models.named import NamedModelSerializer
15
10
 
11
+ LOG = get_logger(__name__)
16
12
 
17
- class PyCountrySerializer(NamedModelSerializer):
18
- """PyCountry tag serializer to include long names.
19
13
 
20
- Takes a single string value and serializes the value to
21
- a pk attribute and a pycountry long name lookup of that value to
22
- the name.
23
- """
24
-
25
- LOOKUP_MODULE = pycountry.countries
26
- _ALPHA_2_LEN = 2
14
+ class CountrySerializer(NamedModelSerializer):
15
+ """Pycountry serializer for country field."""
27
16
 
28
- name = SerializerMethodField()
17
+ name = CountryField(read_only=True)
29
18
 
30
19
  class Meta(NamedModelSerializer.Meta):
31
- """Abstract class."""
32
-
33
- abstract = True
34
-
35
- @classmethod
36
- def lookup_name(cls, lookup_module, obj):
37
- """Lookup the name with pycountry, just copy the key on fail."""
38
- name = obj.name
39
- if not name:
40
- return ""
41
- # fix for https://github.com/flyingcircusio/pycountry/issues/41
42
- lookup_obj = (
43
- lookup_module.get(alpha_2=name)
44
- if len(name) == cls._ALPHA_2_LEN
45
- else lookup_module.lookup(name)
46
- )
47
- # If lookup fails, return the key as the name
48
- return lookup_obj.name if lookup_obj else name
20
+ """Configure model."""
49
21
 
50
- @extend_schema_field(OpenApiTypes.STR)
51
- def get_name(self, obj):
52
- """Lookup the name with pycountry, just copy the key on fail."""
53
- return self.lookup_name(self.LOOKUP_MODULE, obj)
22
+ model = Country
54
23
 
55
24
 
56
- class LanguageSerializer(PyCountrySerializer):
25
+ class LanguageSerializer(NamedModelSerializer):
57
26
  """Pycountry serializer for language field."""
58
27
 
59
- LOOKUP_MODULE = pycountry.languages
28
+ name = LanguageField(read_only=True)
60
29
 
61
- class Meta(PyCountrySerializer.Meta):
30
+ class Meta(NamedModelSerializer.Meta):
62
31
  """Configure model."""
63
32
 
64
33
  model = Language
65
-
66
-
67
- class CountrySerializer(PyCountrySerializer):
68
- """Pycountry serializer for country field."""
69
-
70
- class Meta(PyCountrySerializer.Meta):
71
- """Configure model."""
72
-
73
- model = Country