codex 1.4.0a0__py3-none-any.whl → 1.4.1__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 (279) hide show
  1. codex/config_default.yaml +12 -4
  2. codex/db_functions.py +4 -2
  3. codex/integrity.py +17 -6
  4. codex/librarian/covers/coverd.py +3 -11
  5. codex/librarian/covers/create.py +15 -21
  6. codex/librarian/covers/tasks.py +2 -16
  7. codex/librarian/importer/aggregate_metadata.py +75 -41
  8. codex/librarian/importer/clean_metadata.py +30 -7
  9. codex/librarian/importer/create_fks.py +154 -55
  10. codex/librarian/importer/deleted.py +11 -2
  11. codex/librarian/importer/failed_imports.py +44 -5
  12. codex/librarian/importer/importerd.py +37 -12
  13. codex/librarian/importer/link_comics.py +54 -31
  14. codex/librarian/importer/moved.py +55 -11
  15. codex/librarian/importer/query_fks.py +210 -48
  16. codex/librarian/importer/tasks.py +7 -7
  17. codex/librarian/janitor/cleanup.py +17 -5
  18. codex/librarian/librariand.py +10 -0
  19. codex/librarian/watchdog/events.py +11 -14
  20. codex/librarian/watchdog/observers.py +5 -1
  21. codex/logger/loggerd.py +7 -3
  22. codex/logger/logging.py +1 -1
  23. codex/migrations/0024_comic_gtin_comic_story_arc_number.py +24 -0
  24. codex/migrations/0025_add_story_arc_number.py +83 -0
  25. codex/models.py +21 -11
  26. codex/search/backend.py +1 -1
  27. codex/search/indexes.py +1 -1
  28. codex/serializers/browser.py +1 -0
  29. codex/serializers/metadata.py +5 -1
  30. codex/serializers/models.py +16 -1
  31. codex/serializers/opds/v1.py +1 -0
  32. codex/serializers/opds/v2.py +5 -2
  33. codex/serializers/reader.py +55 -16
  34. codex/settings/settings.py +1 -1
  35. codex/static_root/assets/admin-12749881.ef0f50bac290.js +41 -0
  36. codex/static_root/assets/admin-12749881.ef0f50bac290.js.br +0 -0
  37. codex/static_root/assets/admin-12749881.ef0f50bac290.js.gz +0 -0
  38. codex/static_root/assets/admin-12749881.js +41 -0
  39. codex/static_root/assets/admin-12749881.js.br +0 -0
  40. codex/static_root/assets/admin-12749881.js.gz +0 -0
  41. codex/static_root/assets/admin-beda768d.a614eee46307.css +1 -0
  42. codex/static_root/assets/admin-beda768d.a614eee46307.css.br +0 -0
  43. codex/static_root/assets/admin-beda768d.a614eee46307.css.gz +0 -0
  44. codex/static_root/assets/admin-beda768d.css +1 -0
  45. codex/static_root/assets/admin-beda768d.css.br +0 -0
  46. codex/static_root/assets/admin-beda768d.css.gz +0 -0
  47. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css +1 -0
  48. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.br +0 -0
  49. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.gz +0 -0
  50. codex/static_root/assets/admin-drawer-panel-41c225cc.css +1 -0
  51. codex/static_root/assets/admin-drawer-panel-41c225cc.css.br +0 -0
  52. codex/static_root/assets/admin-drawer-panel-41c225cc.css.gz +0 -0
  53. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js +1 -0
  54. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.br +0 -0
  55. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.gz +0 -0
  56. codex/static_root/assets/admin-drawer-panel-522f1e6c.js +1 -0
  57. codex/static_root/assets/admin-drawer-panel-522f1e6c.js.br +0 -0
  58. codex/static_root/assets/admin-drawer-panel-522f1e6c.js.gz +0 -0
  59. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css +1 -0
  60. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.br +0 -0
  61. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.gz +0 -0
  62. codex/static_root/assets/browser-7f7d7134.css +1 -0
  63. codex/static_root/assets/browser-7f7d7134.css.br +0 -0
  64. codex/static_root/assets/browser-7f7d7134.css.gz +0 -0
  65. codex/static_root/assets/browser-af622672.d51aca96d64d.js +1 -0
  66. codex/static_root/assets/browser-af622672.d51aca96d64d.js.br +0 -0
  67. codex/static_root/assets/browser-af622672.d51aca96d64d.js.gz +0 -0
  68. codex/static_root/assets/browser-af622672.js +1 -0
  69. codex/static_root/assets/browser-af622672.js.br +0 -0
  70. codex/static_root/assets/browser-af622672.js.gz +0 -0
  71. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js +1 -0
  72. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.br +0 -0
  73. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.gz +0 -0
  74. codex/static_root/assets/http-error-5e17b794.js +1 -0
  75. codex/static_root/assets/http-error-5e17b794.js.br +0 -0
  76. codex/static_root/assets/http-error-5e17b794.js.gz +0 -0
  77. codex/static_root/assets/{main-a6ac9581.2fd9e52cbcc3.css → main-0898f4bb.181e0145c642.css} +1 -1
  78. codex/static_root/assets/main-0898f4bb.181e0145c642.css.br +0 -0
  79. codex/static_root/assets/{main-a6ac9581.2fd9e52cbcc3.css.gz → main-0898f4bb.181e0145c642.css.gz} +0 -0
  80. codex/static_root/assets/{main-a6ac9581.css → main-0898f4bb.css} +1 -1
  81. codex/static_root/assets/main-0898f4bb.css.br +0 -0
  82. codex/static_root/assets/{main-a6ac9581.css.gz → main-0898f4bb.css.gz} +0 -0
  83. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js +1 -0
  84. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.br +0 -0
  85. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.gz +0 -0
  86. codex/static_root/assets/main-9e76a4c3.js +1 -0
  87. codex/static_root/assets/main-9e76a4c3.js.br +0 -0
  88. codex/static_root/assets/main-9e76a4c3.js.gz +0 -0
  89. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js +1 -0
  90. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.br +0 -0
  91. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.gz +0 -0
  92. codex/static_root/assets/metadata-dialog-62c29ce0.js +1 -0
  93. codex/static_root/assets/metadata-dialog-62c29ce0.js.br +0 -0
  94. codex/static_root/assets/metadata-dialog-62c29ce0.js.gz +0 -0
  95. codex/static_root/assets/{metadata-dialog-785c4cfc.694a251cda37.css → metadata-dialog-cb306ffd.cc304996d7bb.css} +1 -1
  96. codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.br +0 -0
  97. codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.gz +0 -0
  98. codex/static_root/assets/{metadata-dialog-785c4cfc.css → metadata-dialog-cb306ffd.css} +1 -1
  99. codex/static_root/assets/metadata-dialog-cb306ffd.css.br +0 -0
  100. codex/static_root/assets/metadata-dialog-cb306ffd.css.gz +0 -0
  101. codex/static_root/assets/{page-pdf-abfd509d.3870dab8eaf4.js → page-pdf-157ba97e.613d7c2beb77.js} +61 -51
  102. codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.br +0 -0
  103. codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.gz +0 -0
  104. codex/static_root/assets/{page-pdf-abfd509d.js → page-pdf-157ba97e.js} +61 -51
  105. codex/static_root/assets/page-pdf-157ba97e.js.br +0 -0
  106. codex/static_root/assets/page-pdf-157ba97e.js.gz +0 -0
  107. codex/static_root/assets/reader-36266549.0b2cf1291f27.js +1 -0
  108. codex/static_root/assets/reader-36266549.0b2cf1291f27.js.br +0 -0
  109. codex/static_root/assets/reader-36266549.0b2cf1291f27.js.gz +0 -0
  110. codex/static_root/assets/reader-36266549.js +1 -0
  111. codex/static_root/assets/reader-36266549.js.br +0 -0
  112. codex/static_root/assets/reader-36266549.js.gz +0 -0
  113. codex/static_root/assets/reader-7f004141.506eecc6954b.css +1 -0
  114. codex/static_root/assets/reader-7f004141.506eecc6954b.css.br +0 -0
  115. codex/static_root/assets/reader-7f004141.506eecc6954b.css.gz +0 -0
  116. codex/static_root/assets/reader-7f004141.css +1 -0
  117. codex/static_root/assets/reader-7f004141.css.br +0 -0
  118. codex/static_root/assets/reader-7f004141.css.gz +0 -0
  119. codex/static_root/js/choices-admin.24cecf0a0568.json +1 -0
  120. codex/static_root/js/choices-admin.24cecf0a0568.json.br +0 -0
  121. codex/static_root/js/choices-admin.24cecf0a0568.json.gz +0 -0
  122. codex/static_root/js/choices-admin.json +1 -1
  123. codex/static_root/js/choices-admin.json.br +0 -0
  124. codex/static_root/js/choices-admin.json.gz +0 -0
  125. codex/static_root/js/choices.8c58714cf5b2.json +1 -0
  126. codex/static_root/js/choices.8c58714cf5b2.json.br +5 -0
  127. codex/static_root/js/choices.8c58714cf5b2.json.gz +0 -0
  128. codex/static_root/js/choices.json +1 -1
  129. codex/static_root/js/choices.json.br +0 -0
  130. codex/static_root/js/choices.json.gz +0 -0
  131. codex/static_root/{manifest.64a989215af8.json → manifest.d2f93a519ada.json} +34 -34
  132. codex/static_root/manifest.d2f93a519ada.json.br +0 -0
  133. codex/static_root/manifest.d2f93a519ada.json.gz +0 -0
  134. codex/static_root/manifest.json +34 -34
  135. codex/static_root/manifest.json.br +0 -0
  136. codex/static_root/manifest.json.gz +0 -0
  137. codex/static_root/staticfiles.json +1 -1
  138. codex/templates/headers-script-globals.html +1 -1
  139. codex/templates/{opds → opds_v1}/index.xml +3 -1
  140. codex/templates/{opds/opensearch.xml → opds_v1/opensearch_v1.xml} +1 -1
  141. codex/templates/search/indexes/codex/comic_text.txt +2 -2
  142. codex/urls/converters.py +1 -1
  143. codex/urls/opds/authentication.py +1 -1
  144. codex/urls/opds/root.py +8 -12
  145. codex/urls/opds/v1.py +12 -5
  146. codex/urls/opds/v2.py +2 -2
  147. codex/views/admin/tasks.py +6 -1
  148. codex/views/bookmark.py +2 -2
  149. codex/views/browser/base.py +23 -7
  150. codex/views/browser/browser.py +66 -56
  151. codex/views/browser/browser_annotations.py +159 -50
  152. codex/views/browser/browser_order_by.py +51 -105
  153. codex/views/browser/choices.py +75 -38
  154. codex/views/browser/filters/bookmark.py +6 -9
  155. codex/views/browser/filters/field.py +9 -6
  156. codex/views/browser/filters/group.py +12 -27
  157. codex/views/browser/filters/search.py +5 -10
  158. codex/views/browser/metadata.py +44 -19
  159. codex/views/download.py +1 -1
  160. codex/views/frontend.py +2 -3
  161. codex/views/mixins.py +15 -2
  162. codex/views/opds/const.py +8 -1
  163. codex/views/opds/util.py +37 -1
  164. codex/views/opds/v1/__init__.py +1 -1
  165. codex/views/opds/v1/data.py +21 -0
  166. codex/views/opds/v1/entry/__init__.py +1 -0
  167. codex/views/opds/v1/entry/data.py +23 -0
  168. codex/views/opds/v1/entry/entry.py +151 -0
  169. codex/views/opds/v1/entry/links.py +135 -0
  170. codex/views/opds/v1/facets.py +190 -0
  171. codex/views/opds/v1/feed.py +199 -0
  172. codex/views/opds/v1/links.py +198 -0
  173. codex/views/opds/{opensearch.py → v1/opensearch_v1.py} +3 -3
  174. codex/views/opds/v2/__init__.py +1 -1
  175. codex/views/opds/v2/const.py +10 -2
  176. codex/views/opds/v2/feed.py +82 -21
  177. codex/views/opds/v2/links.py +1 -1
  178. codex/views/opds/v2/publications.py +1 -1
  179. codex/views/opds/v2/top_links.py +1 -1
  180. codex/views/reader/page.py +6 -7
  181. codex/views/reader/reader.py +191 -61
  182. codex/views/session.py +2 -1
  183. {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/METADATA +10 -41
  184. {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/RECORD +187 -185
  185. codex/librarian/importer/db_ops.py +0 -248
  186. codex/pdf.py +0 -115
  187. codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js +0 -48
  188. codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js.br +0 -0
  189. codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js.gz +0 -0
  190. codex/static_root/assets/admin-73d93dc7.js +0 -48
  191. codex/static_root/assets/admin-73d93dc7.js.br +0 -0
  192. codex/static_root/assets/admin-73d93dc7.js.gz +0 -0
  193. codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css +0 -1
  194. codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css.br +0 -0
  195. codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css.gz +0 -0
  196. codex/static_root/assets/admin-79555229.css +0 -1
  197. codex/static_root/assets/admin-79555229.css.br +0 -0
  198. codex/static_root/assets/admin-79555229.css.gz +0 -0
  199. codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js +0 -1
  200. codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js.br +0 -0
  201. codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js.gz +0 -0
  202. codex/static_root/assets/admin-drawer-panel-64bcc083.js +0 -1
  203. codex/static_root/assets/admin-drawer-panel-64bcc083.js.br +0 -0
  204. codex/static_root/assets/admin-drawer-panel-64bcc083.js.gz +0 -0
  205. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css +0 -1
  206. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.br +0 -2
  207. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.gz +0 -0
  208. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css +0 -1
  209. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.br +0 -2
  210. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.gz +0 -0
  211. codex/static_root/assets/browser-7325db61.css +0 -1
  212. codex/static_root/assets/browser-7325db61.css.br +0 -0
  213. codex/static_root/assets/browser-7325db61.css.gz +0 -0
  214. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css +0 -1
  215. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.br +0 -0
  216. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.gz +0 -0
  217. codex/static_root/assets/browser-d2caeed7.2262000a6d55.js +0 -1
  218. codex/static_root/assets/browser-d2caeed7.2262000a6d55.js.br +0 -0
  219. codex/static_root/assets/browser-d2caeed7.2262000a6d55.js.gz +0 -0
  220. codex/static_root/assets/browser-d2caeed7.js +0 -1
  221. codex/static_root/assets/browser-d2caeed7.js.br +0 -0
  222. codex/static_root/assets/browser-d2caeed7.js.gz +0 -0
  223. codex/static_root/assets/http-error-0221c37d.480d5066da92.js +0 -1
  224. codex/static_root/assets/http-error-0221c37d.480d5066da92.js.br +0 -0
  225. codex/static_root/assets/http-error-0221c37d.480d5066da92.js.gz +0 -0
  226. codex/static_root/assets/http-error-0221c37d.js +0 -1
  227. codex/static_root/assets/http-error-0221c37d.js.br +0 -0
  228. codex/static_root/assets/http-error-0221c37d.js.gz +0 -0
  229. codex/static_root/assets/main-a6ac9581.2fd9e52cbcc3.css.br +0 -0
  230. codex/static_root/assets/main-a6ac9581.css.br +0 -0
  231. codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js +0 -1
  232. codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js.br +0 -0
  233. codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js.gz +0 -0
  234. codex/static_root/assets/main-e33dcfb0.js +0 -1
  235. codex/static_root/assets/main-e33dcfb0.js.br +0 -0
  236. codex/static_root/assets/main-e33dcfb0.js.gz +0 -0
  237. codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.br +0 -0
  238. codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.gz +0 -0
  239. codex/static_root/assets/metadata-dialog-785c4cfc.css.br +0 -0
  240. codex/static_root/assets/metadata-dialog-785c4cfc.css.gz +0 -0
  241. codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js +0 -1
  242. codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js.br +0 -0
  243. codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js.gz +0 -0
  244. codex/static_root/assets/metadata-dialog-8b0e8aaa.js +0 -1
  245. codex/static_root/assets/metadata-dialog-8b0e8aaa.js.br +0 -0
  246. codex/static_root/assets/metadata-dialog-8b0e8aaa.js.gz +0 -0
  247. codex/static_root/assets/page-pdf-abfd509d.3870dab8eaf4.js.br +0 -0
  248. codex/static_root/assets/page-pdf-abfd509d.3870dab8eaf4.js.gz +0 -0
  249. codex/static_root/assets/page-pdf-abfd509d.js.br +0 -0
  250. codex/static_root/assets/page-pdf-abfd509d.js.gz +0 -0
  251. codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css +0 -1
  252. codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css.br +0 -0
  253. codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css.gz +0 -0
  254. codex/static_root/assets/reader-a8b8f766.css +0 -1
  255. codex/static_root/assets/reader-a8b8f766.css.br +0 -0
  256. codex/static_root/assets/reader-a8b8f766.css.gz +0 -0
  257. codex/static_root/assets/reader-fe9345d2.759c31f82998.js +0 -1
  258. codex/static_root/assets/reader-fe9345d2.759c31f82998.js.br +0 -0
  259. codex/static_root/assets/reader-fe9345d2.759c31f82998.js.gz +0 -0
  260. codex/static_root/assets/reader-fe9345d2.js +0 -1
  261. codex/static_root/assets/reader-fe9345d2.js.br +0 -0
  262. codex/static_root/assets/reader-fe9345d2.js.gz +0 -0
  263. codex/static_root/js/choices-admin.3d958ea7f83b.json +0 -1
  264. codex/static_root/js/choices-admin.3d958ea7f83b.json.br +0 -0
  265. codex/static_root/js/choices-admin.3d958ea7f83b.json.gz +0 -0
  266. codex/static_root/js/choices.6bfc2a3d293f.json +0 -1
  267. codex/static_root/js/choices.6bfc2a3d293f.json.br +0 -0
  268. codex/static_root/js/choices.6bfc2a3d293f.json.gz +0 -0
  269. codex/static_root/manifest.64a989215af8.json.br +0 -0
  270. codex/static_root/manifest.64a989215af8.json.gz +0 -0
  271. codex/urls/opds/opensearch.py +0 -18
  272. codex/views/opds/v1/browser.py +0 -346
  273. codex/views/opds/v1/entry.py +0 -278
  274. codex/views/opds/v1/start.py +0 -28
  275. codex/views/opds/v1/util.py +0 -162
  276. codex/views/opds/v2/start.py +0 -28
  277. {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/LICENSE +0 -0
  278. {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/WHEEL +0 -0
  279. {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/entry_points.txt +0 -0
@@ -1,278 +0,0 @@
1
- """OPDS Entry."""
2
- import json
3
- from dataclasses import dataclass
4
- from datetime import datetime, timezone
5
- from urllib.parse import quote_plus, urlencode
6
-
7
- from django.contrib.staticfiles.storage import staticfiles_storage
8
- from django.urls import reverse
9
-
10
- from codex.logger.logging import get_logger
11
- from codex.models import Comic
12
- from codex.views.opds.const import (
13
- AUTHOR_ROLES,
14
- BLANK_TITLE,
15
- MimeType,
16
- Rel,
17
- )
18
- from codex.views.opds.util import (
19
- get_creator_people,
20
- get_m2m_objects,
21
- update_href_query_params,
22
- )
23
- from codex.views.opds.v1.util import (
24
- OPDSLink,
25
- )
26
-
27
- LOG = get_logger(__name__)
28
-
29
-
30
- @dataclass
31
- class OPDS1EntryObject:
32
- """Fake entry db object for top link & facet entries."""
33
-
34
- query_params: dict
35
- group: str = ""
36
- pk: int = 0
37
- name: str = ""
38
- summary: str = ""
39
- fake: bool = True
40
-
41
-
42
- class OPDS1Entry:
43
- """An OPDS entry object."""
44
-
45
- _DATE_FORMAT_BASE = "%Y-%m-%dT%H:%M:%S"
46
- DATE_FORMAT_MS = _DATE_FORMAT_BASE + ".%f%z"
47
- DATE_FORMAT = _DATE_FORMAT_BASE + "%z"
48
- DATE_FORMATS = (DATE_FORMAT_MS, DATE_FORMAT)
49
-
50
- def __init__(self, obj, query_params, extra_data):
51
- """Initialize params."""
52
- self.obj = obj
53
- self.fake = isinstance(self.obj, OPDS1EntryObject)
54
- self.query_params = query_params
55
- self.acquision_groups, self.issue_max, self.metadata = extra_data
56
-
57
- @property
58
- def id_tag(self):
59
- """GUID is a nav url."""
60
- # Id top links by query params but not regular entries.
61
- return self._nav_href(metadata=self.metadata)
62
-
63
- @property
64
- def title(self):
65
- """Compute the item title."""
66
- result = ""
67
- try:
68
- parts = []
69
- if not self.fake:
70
- group = self.obj.group
71
- if group == "i":
72
- parts.append(self.obj.publisher_name)
73
- elif group == "v":
74
- parts.append(self.obj.series_name)
75
- elif group == "c":
76
- title = Comic.get_title(self.obj, issue_max=self.issue_max)
77
- parts.append(title)
78
-
79
- if name := self.obj.name:
80
- parts.append(name)
81
-
82
- result = " ".join(filter(None, parts))
83
- except Exception as exc:
84
- LOG.exception(exc)
85
-
86
- if not result:
87
- result = BLANK_TITLE
88
- return result
89
-
90
- @property
91
- def issued(self):
92
- """Return the published date."""
93
- if self.obj.group == "c":
94
- return self.obj.date
95
- return None
96
-
97
- @property
98
- def publisher(self):
99
- """Return the publisher."""
100
- return self.obj.publisher_name
101
-
102
- def _get_datefield(self, key):
103
- result = None
104
- if not self.fake and (value := getattr(self.obj, key, None)):
105
- for date_format in self.DATE_FORMATS:
106
- try:
107
- if isinstance(value, str):
108
- result = datetime.strptime(value, date_format).astimezone(
109
- timezone.utc
110
- )
111
- if isinstance(value, datetime):
112
- result = value.astimezone(timezone.utc).strftime(date_format)
113
- break
114
- except ValueError:
115
- pass
116
- return result
117
-
118
- @property
119
- def updated(self):
120
- """When the entry was last updated."""
121
- return self._get_datefield("updated_at")
122
-
123
- @property
124
- def published(self):
125
- """When the entry was created."""
126
- return self._get_datefield("created_at")
127
-
128
- @property
129
- def language(self):
130
- """Return the entry language."""
131
- return self.obj.language
132
-
133
- @property
134
- def summary(self):
135
- """Return a child count or comic summary."""
136
- if self.obj.group == "c":
137
- desc = self.obj.summary
138
- else:
139
- children = self.obj.child_count
140
- desc = f"{children} issues"
141
- return desc
142
-
143
- def _thumb_link(self):
144
- if self.fake:
145
- return None
146
- cover_pk = self.obj.cover_pk
147
- if cover_pk:
148
- kwargs = {"pk": cover_pk}
149
- href = reverse("opds:bin:cover", kwargs=kwargs)
150
- elif cover_pk == 0:
151
- href = staticfiles_storage.url("img/missing_cover.webp")
152
- else:
153
- return None
154
- return OPDSLink(Rel.THUMBNAIL, href, "image/webp")
155
-
156
- def _image_link(self):
157
- if self.fake:
158
- return None
159
- cover_pk = self.obj.cover_pk
160
- if cover_pk:
161
- kwargs = {"pk": cover_pk, "page": 0}
162
- href = reverse("opds:bin:page", kwargs=kwargs)
163
- mime_type = "image/jpeg"
164
- elif cover_pk == 0:
165
- href = staticfiles_storage.url("img/missing_cover.webp")
166
- mime_type = "image/webp"
167
- else:
168
- return None
169
- return OPDSLink(Rel.IMAGE, href, mime_type)
170
-
171
- def _nav_href(self, metadata=False):
172
- kwargs = {"group": self.obj.group, "pk": self.obj.pk, "page": 1}
173
- href = reverse("opds:v1:browser", kwargs=kwargs)
174
- qps = {} if self.fake or self.obj.group == "c" else self.query_params
175
-
176
- if metadata:
177
- qps.update({"opdsMetadata": 1})
178
- return update_href_query_params(href, self.query_params, qps)
179
-
180
- def _nav_link(self, metadata=False):
181
- group = self.obj.group
182
-
183
- if group in self.acquision_groups:
184
- mime_type = MimeType.ENTRY_CATALOG if metadata else MimeType.ACQUISITION
185
- else:
186
- mime_type = MimeType.NAV
187
-
188
- href = self._nav_href(metadata)
189
- thr_count = 0 if self.fake else self.obj.child_count
190
- rel = Rel.ALTERNATE if metadata else "subsection"
191
-
192
- return OPDSLink(rel, href, mime_type, thr_count=thr_count)
193
-
194
- def _download_link(self):
195
- pk = self.obj.pk
196
- if not pk:
197
- return None
198
- fn = Comic.get_filename(self.obj)
199
- fn = quote_plus(fn)
200
- kwargs = {"pk": pk, "filename": fn}
201
- href = reverse("opds:bin:download", kwargs=kwargs)
202
- return OPDSLink(Rel.ACQUISITION, href, MimeType.DOWNLOAD, length=self.obj.size)
203
-
204
- def _stream_link(self):
205
- pk = self.obj.pk
206
- if not pk:
207
- return None
208
- base_url = reverse("opds:v1:start")
209
- href = f"{base_url}c/{pk}/"
210
- href += "{pageNumber}/page.jpg?bookmark=1"
211
- count = self.obj.page_count
212
- page = self.obj.page
213
- bookmark_updated_at = self.obj.bookmark_updated_at
214
- return OPDSLink(
215
- Rel.STREAM,
216
- href,
217
- MimeType.STREAM,
218
- pse_count=count,
219
- pse_last_read=page,
220
- pse_last_read_date=bookmark_updated_at,
221
- )
222
-
223
- @property
224
- def links(self):
225
- """Create all entry links."""
226
- result = []
227
- if thumb := self._thumb_link():
228
- result += [thumb]
229
- if image := self._image_link():
230
- result += [image]
231
-
232
- if self.obj.group == "c" and not self.fake:
233
- if download := self._download_link():
234
- result += [download]
235
- if stream := self._stream_link():
236
- result += [stream]
237
- if not self.metadata and (metadata := self._nav_link(metadata=True)):
238
- result += [metadata]
239
- elif nav := self._nav_link():
240
- result += [nav]
241
-
242
- return result
243
-
244
- @staticmethod
245
- def _add_url_to_obj(objs, filter_key):
246
- """Add filter urls to objects."""
247
- kwargs = {"group": "s", "pk": 0, "page": 1}
248
- url_base = reverse("opds:v1:browser", kwargs=kwargs)
249
- result = []
250
- for obj in objs:
251
- qp = {"filters": json.dumps({filter_key: [obj.pk]})}
252
- qp = urlencode(qp)
253
- obj.url = url_base + "?" + qp
254
- result.append(obj)
255
- return result
256
-
257
- @property
258
- def authors(self):
259
- """Get Author names."""
260
- if not self.metadata:
261
- return []
262
- people = get_creator_people(self.obj.pk, AUTHOR_ROLES)
263
- return self._add_url_to_obj(people, "creators")
264
-
265
- @property
266
- def contributors(self):
267
- """Get Contributor names."""
268
- if not self.metadata:
269
- return []
270
- people = get_creator_people(self.obj.pk, AUTHOR_ROLES, exclude=True)
271
- return self._add_url_to_obj(people, "creators")
272
-
273
- @property
274
- def category_groups(self):
275
- """Get Category labels."""
276
- if not self.metadata:
277
- return {}
278
- return get_m2m_objects(self.obj.pk)
@@ -1,28 +0,0 @@
1
- """OPDS start view."""
2
- from django.http.response import HttpResponseRedirect
3
- from django.urls import reverse
4
-
5
- from codex.serializers.choices import DEFAULTS
6
-
7
-
8
- def opds_1_start_view(request):
9
- """Redirect to start view, forwarding query strings and auth."""
10
- kwargs = DEFAULTS["route"]
11
- url = reverse("opds:v1:browser", kwargs=kwargs)
12
-
13
- # Forward the query string.
14
- path = request.get_full_path()
15
- if path:
16
- parts = path.split("?")
17
- if len(parts) >= 2: # noqa PLR2004
18
- parts[0] = url
19
- url = "?".join(parts)
20
-
21
- response = HttpResponseRedirect(url)
22
-
23
- # Forward authorization.
24
- auth_header = request.META.get("HTTP_AUTHORIZATION")
25
- if auth_header:
26
- response["HTTP_AUTHORIZATION"] = auth_header
27
-
28
- return response
@@ -1,162 +0,0 @@
1
- """OPDS 1 Utility classes."""
2
- from collections import defaultdict
3
- from dataclasses import dataclass
4
- from datetime import datetime
5
- from typing import Optional, Union
6
-
7
- from comicbox.metadata.comic_json import json
8
-
9
- from codex.views.opds.const import MimeType, Rel
10
-
11
-
12
- class OpdsNs:
13
- """xml namespaces."""
14
-
15
- CATALOG = "http://opds-spec.org/2010/catalog"
16
- ACQUISITION = "http://opds-spec.org/2010/acquisition"
17
-
18
-
19
- class UserAgents:
20
- """Control whether to hack in facets with nav links."""
21
-
22
- CLIENT_REORDERS = ("Chunky",)
23
- FACET_SUPPORT = ("yar",) # kybooks
24
- # NO_FACET_SUPPORT "Panels", "Chunky", "PocketBook"
25
-
26
-
27
- class TopRoutes:
28
- """Routes for top groups."""
29
-
30
- PUBLISHER = {"group": "p", "pk": 0}
31
- SERIES = {"group": "s", "pk": 0}
32
- FOLDER = {"group": "f", "pk": 0}
33
- ROOT = {"group": "r", "pk": 0}
34
-
35
-
36
- @dataclass
37
- class FacetGroup:
38
- """An opds:facetGroup."""
39
-
40
- title_prefix: str
41
- query_param: str
42
- glyph: str
43
- facets: tuple
44
-
45
-
46
- @dataclass
47
- class Facet:
48
- """An OPDS facet."""
49
-
50
- value: str
51
- title: str
52
-
53
-
54
- @dataclass
55
- class TopLink:
56
- """A non standard root link when facets are unsupported."""
57
-
58
- kwargs: dict
59
- rel: str
60
- mime_type: str
61
- query_params: defaultdict[str, Union[str, bool]]
62
- glyph: str
63
- title: str
64
- desc: str
65
-
66
-
67
- class TopLinks:
68
- """Top link definitions."""
69
-
70
- START = TopLink(
71
- TopRoutes.ROOT,
72
- Rel.START,
73
- MimeType.NAV,
74
- defaultdict(),
75
- "⌂",
76
- "Start of the catalog",
77
- "",
78
- )
79
- ALL = (START,)
80
-
81
-
82
- class RootTopLinks:
83
- """Top Links that only appear at the root."""
84
-
85
- NEW = TopLink(
86
- TopRoutes.SERIES,
87
- Rel.SORT_NEW,
88
- MimeType.ACQUISITION,
89
- defaultdict(None, {"orderBy": "created_at", "orderReverse": True}),
90
- "📥",
91
- "Recently Added",
92
- "",
93
- )
94
- FEATURED = TopLink(
95
- TopRoutes.SERIES,
96
- Rel.FEATURED,
97
- MimeType.NAV,
98
- defaultdict(
99
- None, {"orderBy": "date", "filters": json.dumps({"bookmark": "UNREAD"})}
100
- ),
101
- "📚",
102
- "Oldest Unread",
103
- "Unread issues, oldest published first",
104
- )
105
- ALL = (NEW, FEATURED)
106
-
107
-
108
- class FacetGroups:
109
- """Facet Group definitions."""
110
-
111
- ORDER_BY = FacetGroup(
112
- "Order By",
113
- "orderBy",
114
- "➠",
115
- (
116
- Facet("date", "Date"),
117
- Facet("sort_name", "Name"),
118
- ),
119
- )
120
- ORDER_REVERSE = FacetGroup(
121
- "Order",
122
- "orderReverse",
123
- "⇕",
124
- (Facet("false", "Ascending"), Facet("true", "Descending")),
125
- )
126
- ALL = (ORDER_BY, ORDER_REVERSE)
127
-
128
-
129
- class RootFacetGroups:
130
- """Facet Groups that only appear at the root."""
131
-
132
- TOP_GROUP = FacetGroup(
133
- "",
134
- "topGroup",
135
- "⊙",
136
- (Facet("p", "Publishers View"), Facet("s", "Series View")),
137
- )
138
- ALL = (TOP_GROUP,)
139
-
140
-
141
- DEFAULT_FACETS = {
142
- "topGroup": "p",
143
- "orderBy": "sort_name",
144
- "orderReverse": "false",
145
- }
146
-
147
-
148
- @dataclass
149
- class OPDSLink:
150
- """An OPDS Link."""
151
-
152
- rel: str
153
- href: str
154
- mime_type: str
155
- title: str = ""
156
- length: int = 0
157
- facet_group: str = ""
158
- facet_active: bool = False
159
- thr_count: int = 0
160
- pse_count: int = 0
161
- pse_last_read: int = 0
162
- pse_last_read_date: Optional[datetime] = None
@@ -1,28 +0,0 @@
1
- """OPDS 2 start view."""
2
- from django.http.response import HttpResponseRedirect
3
- from django.urls import reverse
4
-
5
- from codex.serializers.choices import DEFAULTS
6
-
7
-
8
- def opds_2_start_view(request):
9
- """Redirect to start view, forwarding query strings and auth."""
10
- kwargs = DEFAULTS["route"]
11
- url = reverse("opds:v2:feed", kwargs=kwargs)
12
-
13
- # Forward the query string.
14
- path = request.get_full_path()
15
- if path:
16
- parts = path.split("?")
17
- if len(parts) >= 2: # noqa PLR2004
18
- parts[0] = url
19
- url = "?".join(parts)
20
-
21
- response = HttpResponseRedirect(url)
22
-
23
- # Forward authorization.
24
- auth_header = request.META.get("HTTP_AUTHORIZATION")
25
- if auth_header:
26
- response["HTTP_AUTHORIZATION"] = auth_header
27
-
28
- return response
File without changes