codex 1.4.0a1__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 (259) 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/create.py +6 -8
  5. codex/librarian/importer/aggregate_metadata.py +75 -41
  6. codex/librarian/importer/clean_metadata.py +30 -7
  7. codex/librarian/importer/create_fks.py +154 -55
  8. codex/librarian/importer/deleted.py +11 -2
  9. codex/librarian/importer/failed_imports.py +41 -5
  10. codex/librarian/importer/importerd.py +34 -11
  11. codex/librarian/importer/link_comics.py +54 -31
  12. codex/librarian/importer/moved.py +55 -11
  13. codex/librarian/importer/query_fks.py +210 -48
  14. codex/librarian/importer/tasks.py +7 -7
  15. codex/librarian/janitor/cleanup.py +17 -5
  16. codex/librarian/librariand.py +10 -0
  17. codex/librarian/watchdog/events.py +11 -14
  18. codex/librarian/watchdog/observers.py +5 -1
  19. codex/logger/loggerd.py +7 -3
  20. codex/logger/logging.py +1 -1
  21. codex/migrations/0024_comic_gtin_comic_story_arc_number.py +24 -0
  22. codex/migrations/0025_add_story_arc_number.py +83 -0
  23. codex/models.py +21 -11
  24. codex/search/backend.py +1 -1
  25. codex/search/indexes.py +1 -1
  26. codex/serializers/browser.py +1 -0
  27. codex/serializers/metadata.py +5 -1
  28. codex/serializers/models.py +16 -1
  29. codex/serializers/opds/v1.py +1 -0
  30. codex/serializers/opds/v2.py +5 -2
  31. codex/serializers/reader.py +55 -16
  32. codex/settings/settings.py +1 -1
  33. codex/static_root/assets/admin-12749881.ef0f50bac290.js +41 -0
  34. codex/static_root/assets/admin-12749881.ef0f50bac290.js.br +0 -0
  35. codex/static_root/assets/admin-12749881.ef0f50bac290.js.gz +0 -0
  36. codex/static_root/assets/admin-12749881.js +41 -0
  37. codex/static_root/assets/admin-12749881.js.br +0 -0
  38. codex/static_root/assets/admin-12749881.js.gz +0 -0
  39. codex/static_root/assets/admin-beda768d.a614eee46307.css +1 -0
  40. codex/static_root/assets/admin-beda768d.a614eee46307.css.br +0 -0
  41. codex/static_root/assets/admin-beda768d.a614eee46307.css.gz +0 -0
  42. codex/static_root/assets/admin-beda768d.css +1 -0
  43. codex/static_root/assets/admin-beda768d.css.br +0 -0
  44. codex/static_root/assets/admin-beda768d.css.gz +0 -0
  45. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css +1 -0
  46. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.br +0 -0
  47. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.gz +0 -0
  48. codex/static_root/assets/admin-drawer-panel-41c225cc.css +1 -0
  49. codex/static_root/assets/admin-drawer-panel-41c225cc.css.br +0 -0
  50. codex/static_root/assets/admin-drawer-panel-41c225cc.css.gz +0 -0
  51. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js +1 -0
  52. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.br +0 -0
  53. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.gz +0 -0
  54. codex/static_root/assets/admin-drawer-panel-522f1e6c.js +1 -0
  55. codex/static_root/assets/admin-drawer-panel-522f1e6c.js.br +0 -0
  56. codex/static_root/assets/admin-drawer-panel-522f1e6c.js.gz +0 -0
  57. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css +1 -0
  58. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.br +0 -0
  59. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.gz +0 -0
  60. codex/static_root/assets/browser-7f7d7134.css +1 -0
  61. codex/static_root/assets/browser-7f7d7134.css.br +0 -0
  62. codex/static_root/assets/browser-7f7d7134.css.gz +0 -0
  63. codex/static_root/assets/browser-af622672.d51aca96d64d.js +1 -0
  64. codex/static_root/assets/browser-af622672.d51aca96d64d.js.br +0 -0
  65. codex/static_root/assets/browser-af622672.d51aca96d64d.js.gz +0 -0
  66. codex/static_root/assets/browser-af622672.js +1 -0
  67. codex/static_root/assets/browser-af622672.js.br +0 -0
  68. codex/static_root/assets/browser-af622672.js.gz +0 -0
  69. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js +1 -0
  70. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.br +0 -0
  71. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.gz +0 -0
  72. codex/static_root/assets/http-error-5e17b794.js +1 -0
  73. codex/static_root/assets/http-error-5e17b794.js.br +0 -0
  74. codex/static_root/assets/http-error-5e17b794.js.gz +0 -0
  75. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js +1 -0
  76. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.br +0 -0
  77. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.gz +0 -0
  78. codex/static_root/assets/main-9e76a4c3.js +1 -0
  79. codex/static_root/assets/main-9e76a4c3.js.br +0 -0
  80. codex/static_root/assets/main-9e76a4c3.js.gz +0 -0
  81. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js +1 -0
  82. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.br +0 -0
  83. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.gz +0 -0
  84. codex/static_root/assets/metadata-dialog-62c29ce0.js +1 -0
  85. codex/static_root/assets/metadata-dialog-62c29ce0.js.br +0 -0
  86. codex/static_root/assets/metadata-dialog-62c29ce0.js.gz +0 -0
  87. codex/static_root/assets/{metadata-dialog-785c4cfc.694a251cda37.css → metadata-dialog-cb306ffd.cc304996d7bb.css} +1 -1
  88. codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.br +0 -0
  89. codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.gz +0 -0
  90. codex/static_root/assets/{metadata-dialog-785c4cfc.css → metadata-dialog-cb306ffd.css} +1 -1
  91. codex/static_root/assets/metadata-dialog-cb306ffd.css.br +0 -0
  92. codex/static_root/assets/metadata-dialog-cb306ffd.css.gz +0 -0
  93. codex/static_root/assets/{page-pdf-c603e996.ab2d147c9ae1.js → page-pdf-157ba97e.613d7c2beb77.js} +61 -51
  94. codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.br +0 -0
  95. codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.gz +0 -0
  96. codex/static_root/assets/{page-pdf-c603e996.js → page-pdf-157ba97e.js} +61 -51
  97. codex/static_root/assets/page-pdf-157ba97e.js.br +0 -0
  98. codex/static_root/assets/page-pdf-157ba97e.js.gz +0 -0
  99. codex/static_root/assets/reader-36266549.0b2cf1291f27.js +1 -0
  100. codex/static_root/assets/reader-36266549.0b2cf1291f27.js.br +0 -0
  101. codex/static_root/assets/reader-36266549.0b2cf1291f27.js.gz +0 -0
  102. codex/static_root/assets/reader-36266549.js +1 -0
  103. codex/static_root/assets/reader-36266549.js.br +0 -0
  104. codex/static_root/assets/reader-36266549.js.gz +0 -0
  105. codex/static_root/assets/reader-7f004141.506eecc6954b.css +1 -0
  106. codex/static_root/assets/reader-7f004141.506eecc6954b.css.br +0 -0
  107. codex/static_root/assets/reader-7f004141.506eecc6954b.css.gz +0 -0
  108. codex/static_root/assets/reader-7f004141.css +1 -0
  109. codex/static_root/assets/reader-7f004141.css.br +0 -0
  110. codex/static_root/assets/reader-7f004141.css.gz +0 -0
  111. codex/static_root/js/choices.8c58714cf5b2.json +1 -0
  112. codex/static_root/js/choices.8c58714cf5b2.json.br +5 -0
  113. codex/static_root/js/choices.8c58714cf5b2.json.gz +0 -0
  114. codex/static_root/js/choices.json +1 -1
  115. codex/static_root/js/choices.json.br +0 -0
  116. codex/static_root/js/choices.json.gz +0 -0
  117. codex/static_root/{manifest.c0e270b2e6b6.json → manifest.d2f93a519ada.json} +32 -32
  118. codex/static_root/manifest.d2f93a519ada.json.br +0 -0
  119. codex/static_root/manifest.d2f93a519ada.json.gz +0 -0
  120. codex/static_root/manifest.json +32 -32
  121. codex/static_root/manifest.json.br +0 -0
  122. codex/static_root/manifest.json.gz +0 -0
  123. codex/static_root/staticfiles.json +1 -1
  124. codex/templates/headers-script-globals.html +1 -1
  125. codex/templates/{opds → opds_v1}/index.xml +3 -1
  126. codex/templates/{opds/opensearch.xml → opds_v1/opensearch_v1.xml} +1 -1
  127. codex/templates/search/indexes/codex/comic_text.txt +2 -2
  128. codex/urls/converters.py +1 -1
  129. codex/urls/opds/authentication.py +1 -1
  130. codex/urls/opds/root.py +8 -12
  131. codex/urls/opds/v1.py +12 -5
  132. codex/urls/opds/v2.py +2 -2
  133. codex/views/bookmark.py +2 -2
  134. codex/views/browser/base.py +23 -7
  135. codex/views/browser/browser.py +51 -41
  136. codex/views/browser/browser_annotations.py +159 -50
  137. codex/views/browser/browser_order_by.py +50 -106
  138. codex/views/browser/choices.py +75 -38
  139. codex/views/browser/filters/bookmark.py +6 -9
  140. codex/views/browser/filters/field.py +9 -6
  141. codex/views/browser/filters/group.py +12 -27
  142. codex/views/browser/filters/search.py +5 -10
  143. codex/views/browser/metadata.py +44 -19
  144. codex/views/download.py +1 -1
  145. codex/views/frontend.py +2 -3
  146. codex/views/mixins.py +15 -2
  147. codex/views/opds/const.py +8 -1
  148. codex/views/opds/util.py +37 -1
  149. codex/views/opds/v1/__init__.py +1 -1
  150. codex/views/opds/v1/data.py +21 -0
  151. codex/views/opds/v1/entry/__init__.py +1 -0
  152. codex/views/opds/v1/entry/data.py +23 -0
  153. codex/views/opds/v1/entry/entry.py +151 -0
  154. codex/views/opds/v1/entry/links.py +135 -0
  155. codex/views/opds/v1/facets.py +190 -0
  156. codex/views/opds/v1/feed.py +199 -0
  157. codex/views/opds/v1/links.py +198 -0
  158. codex/views/opds/{opensearch.py → v1/opensearch_v1.py} +3 -3
  159. codex/views/opds/v2/__init__.py +1 -1
  160. codex/views/opds/v2/const.py +10 -2
  161. codex/views/opds/v2/feed.py +82 -21
  162. codex/views/opds/v2/links.py +1 -1
  163. codex/views/opds/v2/publications.py +1 -1
  164. codex/views/opds/v2/top_links.py +1 -1
  165. codex/views/reader/page.py +6 -7
  166. codex/views/reader/reader.py +191 -61
  167. codex/views/session.py +2 -1
  168. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/METADATA +10 -41
  169. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/RECORD +172 -170
  170. codex/librarian/importer/db_ops.py +0 -251
  171. codex/pdf.py +0 -115
  172. codex/static_root/assets/admin-75c007ce.199fccf24c8d.js +0 -48
  173. codex/static_root/assets/admin-75c007ce.199fccf24c8d.js.br +0 -0
  174. codex/static_root/assets/admin-75c007ce.199fccf24c8d.js.gz +0 -0
  175. codex/static_root/assets/admin-75c007ce.js +0 -48
  176. codex/static_root/assets/admin-75c007ce.js.br +0 -0
  177. codex/static_root/assets/admin-75c007ce.js.gz +0 -0
  178. codex/static_root/assets/admin-848d48b1.5de8a0c45636.css +0 -1
  179. codex/static_root/assets/admin-848d48b1.5de8a0c45636.css.br +0 -0
  180. codex/static_root/assets/admin-848d48b1.5de8a0c45636.css.gz +0 -0
  181. codex/static_root/assets/admin-848d48b1.css +0 -1
  182. codex/static_root/assets/admin-848d48b1.css.br +0 -0
  183. codex/static_root/assets/admin-848d48b1.css.gz +0 -0
  184. codex/static_root/assets/admin-drawer-panel-a110c068.edf187333272.js +0 -1
  185. codex/static_root/assets/admin-drawer-panel-a110c068.edf187333272.js.br +0 -0
  186. codex/static_root/assets/admin-drawer-panel-a110c068.edf187333272.js.gz +0 -0
  187. codex/static_root/assets/admin-drawer-panel-a110c068.js +0 -1
  188. codex/static_root/assets/admin-drawer-panel-a110c068.js.br +0 -0
  189. codex/static_root/assets/admin-drawer-panel-a110c068.js.gz +0 -0
  190. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css +0 -1
  191. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.br +0 -2
  192. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.gz +0 -0
  193. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css +0 -1
  194. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.br +0 -2
  195. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.gz +0 -0
  196. codex/static_root/assets/browser-2c2380fd.8b515af7a743.js +0 -1
  197. codex/static_root/assets/browser-2c2380fd.8b515af7a743.js.br +0 -0
  198. codex/static_root/assets/browser-2c2380fd.8b515af7a743.js.gz +0 -0
  199. codex/static_root/assets/browser-2c2380fd.js +0 -1
  200. codex/static_root/assets/browser-2c2380fd.js.br +0 -0
  201. codex/static_root/assets/browser-2c2380fd.js.gz +0 -0
  202. codex/static_root/assets/browser-7325db61.css +0 -1
  203. codex/static_root/assets/browser-7325db61.css.br +0 -0
  204. codex/static_root/assets/browser-7325db61.css.gz +0 -0
  205. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css +0 -1
  206. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.br +0 -0
  207. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.gz +0 -0
  208. codex/static_root/assets/http-error-402decbe.9ea8de1df13f.js +0 -1
  209. codex/static_root/assets/http-error-402decbe.9ea8de1df13f.js.br +0 -0
  210. codex/static_root/assets/http-error-402decbe.9ea8de1df13f.js.gz +0 -0
  211. codex/static_root/assets/http-error-402decbe.js +0 -1
  212. codex/static_root/assets/http-error-402decbe.js.br +0 -0
  213. codex/static_root/assets/http-error-402decbe.js.gz +0 -0
  214. codex/static_root/assets/main-a7f327e9.6641fe833335.js +0 -1
  215. codex/static_root/assets/main-a7f327e9.6641fe833335.js.br +0 -0
  216. codex/static_root/assets/main-a7f327e9.6641fe833335.js.gz +0 -0
  217. codex/static_root/assets/main-a7f327e9.js +0 -1
  218. codex/static_root/assets/main-a7f327e9.js.br +0 -0
  219. codex/static_root/assets/main-a7f327e9.js.gz +0 -0
  220. codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.br +0 -0
  221. codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.gz +0 -0
  222. codex/static_root/assets/metadata-dialog-785c4cfc.css.br +0 -0
  223. codex/static_root/assets/metadata-dialog-785c4cfc.css.gz +0 -0
  224. codex/static_root/assets/metadata-dialog-8a0bd8e1.c213b08d582f.js +0 -1
  225. codex/static_root/assets/metadata-dialog-8a0bd8e1.c213b08d582f.js.br +0 -0
  226. codex/static_root/assets/metadata-dialog-8a0bd8e1.c213b08d582f.js.gz +0 -0
  227. codex/static_root/assets/metadata-dialog-8a0bd8e1.js +0 -1
  228. codex/static_root/assets/metadata-dialog-8a0bd8e1.js.br +0 -0
  229. codex/static_root/assets/metadata-dialog-8a0bd8e1.js.gz +0 -0
  230. codex/static_root/assets/page-pdf-c603e996.ab2d147c9ae1.js.br +0 -0
  231. codex/static_root/assets/page-pdf-c603e996.ab2d147c9ae1.js.gz +0 -0
  232. codex/static_root/assets/page-pdf-c603e996.js.br +0 -0
  233. codex/static_root/assets/page-pdf-c603e996.js.gz +0 -0
  234. codex/static_root/assets/reader-c2965a5f.b011260169f7.js +0 -1
  235. codex/static_root/assets/reader-c2965a5f.b011260169f7.js.br +0 -0
  236. codex/static_root/assets/reader-c2965a5f.b011260169f7.js.gz +0 -0
  237. codex/static_root/assets/reader-c2965a5f.js +0 -1
  238. codex/static_root/assets/reader-c2965a5f.js.br +0 -0
  239. codex/static_root/assets/reader-c2965a5f.js.gz +0 -0
  240. codex/static_root/assets/reader-d8534888.2821de925986.css +0 -1
  241. codex/static_root/assets/reader-d8534888.2821de925986.css.br +0 -0
  242. codex/static_root/assets/reader-d8534888.2821de925986.css.gz +0 -0
  243. codex/static_root/assets/reader-d8534888.css +0 -1
  244. codex/static_root/assets/reader-d8534888.css.br +0 -0
  245. codex/static_root/assets/reader-d8534888.css.gz +0 -0
  246. codex/static_root/js/choices.6bfc2a3d293f.json +0 -1
  247. codex/static_root/js/choices.6bfc2a3d293f.json.br +0 -0
  248. codex/static_root/js/choices.6bfc2a3d293f.json.gz +0 -0
  249. codex/static_root/manifest.c0e270b2e6b6.json.br +0 -0
  250. codex/static_root/manifest.c0e270b2e6b6.json.gz +0 -0
  251. codex/urls/opds/opensearch.py +0 -18
  252. codex/views/opds/v1/browser.py +0 -346
  253. codex/views/opds/v1/entry.py +0 -278
  254. codex/views/opds/v1/start.py +0 -28
  255. codex/views/opds/v1/util.py +0 -162
  256. codex/views/opds/v2/start.py +0 -28
  257. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/LICENSE +0 -0
  258. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/WHEEL +0 -0
  259. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/entry_points.txt +0 -0
@@ -1,24 +1,23 @@
1
1
  """Views for reading comic books."""
2
- from django.db.models import F
2
+ from django.db.models import F, IntegerField, Value
3
3
  from django.urls import reverse
4
4
  from rest_framework.exceptions import NotFound
5
5
  from rest_framework.response import Response
6
6
 
7
7
  from codex.logger.logging import get_logger
8
- from codex.models import Bookmark, Comic
9
- from codex.serializers.reader import ReaderInfoSerializer
8
+ from codex.models import AdminFlag, Bookmark, Comic
9
+ from codex.serializers.reader import ReaderComicsSerializer
10
10
  from codex.serializers.redirect import ReaderRedirectSerializer
11
11
  from codex.views.bookmark import BookmarkBaseView
12
12
  from codex.views.session import BrowserSessionViewBase
13
13
 
14
14
  LOG = get_logger(__name__)
15
- PAGE_TTL = 60 * 60 * 24
16
15
 
17
16
 
18
17
  class ReaderView(BookmarkBaseView):
19
18
  """Get info for displaying comic pages."""
20
19
 
21
- serializer_class = ReaderInfoSerializer
20
+ serializer_class = ReaderComicsSerializer
22
21
 
23
22
  SETTINGS_ATTRS = ("fit_to", "two_pages", "read_in_reverse", "vertical")
24
23
  _COMIC_FIELDS = (
@@ -30,103 +29,234 @@ class ReaderView(BookmarkBaseView):
30
29
  "volume",
31
30
  "read_ltr",
32
31
  )
32
+ _VALID_ARC_GROUPS = frozenset(("f", "s", "a"))
33
33
 
34
- def _append_with_settings(self, books, book, bookmark_filter):
35
- """Get bookmarks and filename and append to book list."""
36
- book.settings = (
37
- Bookmark.objects.filter(**bookmark_filter, comic=book)
38
- .only(*self.SETTINGS_ATTRS)
39
- .first()
40
- )
41
- book.filename = book.filename()
42
- books.append(book)
43
-
44
- def _get_comic_query_params(self, pk):
34
+ def _get_comics_list(self):
45
35
  """Get the reader naviation group filter."""
46
- session = self.request.session.get(BrowserSessionViewBase.SESSION_KEY, {})
47
- top_group = session.get("top_group")
36
+ arc_group = self.params.get("arc_group")
48
37
 
49
- select_related_fields = ["series", "volume"]
50
- if top_group == "f":
51
- rel = "parent_folder__comic"
38
+ if arc_group == "a":
39
+ # for story arcs
40
+ rel = "story_arc_numbers__story_arc"
41
+ fields = self._COMIC_FIELDS
42
+ arc_name_rel = "story_arc_numbers__story_arc__name"
43
+ arc_pk_rel = "story_arc_numbers__story_arc__pk"
44
+ arc_index = F("story_arc_numbers__number")
45
+ ordering = ("arc_index", "date", *Comic.ORDERING)
46
+ elif arc_group == self.FOLDER_GROUP:
47
+ # folder mode
48
+ rel = "parent_folder"
49
+ fields = (*self._COMIC_FIELDS, "parent_folder")
50
+ arc_pk_rel = "parent_folder__pk"
51
+ arc_name_rel = "parent_folder__name"
52
+ arc_index = Value(None, IntegerField())
52
53
  ordering = ("path", "pk")
53
- select_related_fields += ["parent_folder"]
54
54
  else:
55
- rel = "series__comic"
55
+ # browser mode.
56
+ rel = "series"
57
+ fields = self._COMIC_FIELDS
58
+ arc_pk_rel = "series__pk"
59
+ arc_name_rel = "series__name"
60
+ arc_index = Value(None, IntegerField())
56
61
  ordering = Comic.ORDERING
57
62
 
58
- return {rel: pk}, ordering, select_related_fields
63
+ group_acl_filter = self.get_group_acl_filter(Comic)
64
+ arc_pk = self.params.get("arc_pk")
65
+ if not arc_pk:
66
+ rel += "__comic"
67
+ arc_pk = self.kwargs.get("pk")
68
+ nav_filter = {rel: arc_pk}
59
69
 
60
- def _get_group_comics(self):
61
- """Get comics for the series or folder."""
62
- pk = self.kwargs.get("pk")
63
- group_acl_filter = self.get_group_acl_filter(True)
64
- (
65
- group_nav_filter,
66
- ordering,
67
- select_related_fields,
68
- ) = self._get_comic_query_params(pk)
69
-
70
- return (
70
+ qs = (
71
71
  Comic.objects.filter(group_acl_filter)
72
- .filter(**group_nav_filter)
73
- .select_related(*select_related_fields)
74
- .only(*self._COMIC_FIELDS)
72
+ .filter(**nav_filter)
73
+ .prefetch_related("story_arc_numbers__story_arc")
74
+ .only(*fields)
75
75
  .annotate(
76
76
  series_name=F("series__name"),
77
77
  volume_name=F("volume__name"),
78
78
  issue_count=F("volume__issue_count"),
79
79
  )
80
- .order_by(*ordering)
80
+ .annotate(
81
+ arc_pk=F(arc_pk_rel),
82
+ arc_name=F(arc_name_rel),
83
+ arc_index=arc_index,
84
+ )
81
85
  )
82
86
 
83
- def _raise_not_found(self):
84
- """Raise not found exception."""
85
- pk = self.kwargs.get("pk")
86
- detail = {
87
- "route": reverse("app:start"),
88
- "reason": f"comic {pk} not found",
89
- "serializer": ReaderRedirectSerializer,
90
- }
91
- raise NotFound(detail=detail)
87
+ return qs.order_by(*ordering)
92
88
 
93
- def get_object(self):
94
- """Get the previous and next comics in a series.
89
+ def _append_with_settings(self, book, bookmark_filter):
90
+ """Get bookmarks and filename and append to book list."""
91
+ book.settings = (
92
+ Bookmark.objects.filter(**bookmark_filter, comic=book)
93
+ .only(*self.SETTINGS_ATTRS)
94
+ .first()
95
+ )
96
+ return book
97
+
98
+ def _get_book_collection(self):
99
+ """Get the -1, +1 window around the current issue.
95
100
 
96
101
  Uses iteration in python. There are some complicated ways of
97
102
  doing this with __gt[0] & __lt[0] in the db, but I think they
98
103
  might be even more expensive.
104
+
105
+ Yields 1 to 3 books
99
106
  """
100
- comics = self._get_group_comics()
107
+ comics = self._get_comics_list()
101
108
  bookmark_filter = self.get_bookmark_filter()
102
-
103
- # Select the -1, +1 window around the current issue
104
- # Yields 1 to 3 books
105
- books = []
109
+ books = {}
106
110
  prev_book = None
107
111
  pk = self.kwargs.get("pk")
108
112
  for index, book in enumerate(comics):
109
- book.series_index = index + 1 # type: ignore
110
113
  if books:
111
114
  # after match set next comic and break
112
- self._append_with_settings(books, book, bookmark_filter)
115
+ books["next"] = self._append_with_settings(book, bookmark_filter)
113
116
  break
114
117
  if book.pk == pk:
115
118
  # first match. set previous and current comic
116
119
  if prev_book:
117
- self._append_with_settings(books, prev_book, bookmark_filter)
118
- self._append_with_settings(books, book, bookmark_filter)
120
+ books["prev"] = self._append_with_settings(
121
+ prev_book, bookmark_filter
122
+ )
123
+ # create extra current book attrs:
124
+ if book.arc_index is None: # type: ignore
125
+ book.arc_index = index + 1 # type: ignore
126
+ book.filename = book.filename() # type: ignore
127
+
128
+ books["current"] = self._append_with_settings(book, bookmark_filter)
119
129
  else:
120
130
  # Haven't matched yet, so set the previous comic
121
131
  prev_book = book
132
+ return books, comics.count()
133
+
134
+ def _get_folder_arc(self, book):
135
+ """Create the folder arc."""
136
+ efv_flag = (
137
+ AdminFlag.objects.only("on")
138
+ .get(key=AdminFlag.FlagChoices.FOLDER_VIEW.value)
139
+ .on
140
+ )
122
141
 
123
- if not books:
142
+ if efv_flag:
143
+ folder_arc = {
144
+ "group": self.FOLDER_GROUP,
145
+ "pk": book.parent_folder.pk,
146
+ "name": book.parent_folder.name,
147
+ }
148
+ else:
149
+ folder_arc = None
150
+ return folder_arc
151
+
152
+ def _get_arcs(self, book):
153
+ """Get all series/folder/story arcs."""
154
+ # create top arcs
155
+ folder_arc = self._get_folder_arc(book)
156
+ series_arc = {"group": "s", "pk": book.series.pk, "name": book.series.name}
157
+
158
+ # order top arcs
159
+ top_group = self.params.get("top_group")
160
+ if top_group == self.FOLDER_GROUP and folder_arc:
161
+ arc = folder_arc
162
+ other_arc = series_arc
163
+ else:
164
+ arc = series_arc
165
+ other_arc = folder_arc
166
+
167
+ arcs = []
168
+ arcs.append(arc)
169
+ if other_arc:
170
+ arcs.append(other_arc)
171
+
172
+ # story arcs
173
+ sas = []
174
+ for san in book.story_arc_numbers.all():
175
+ sa = san.story_arc
176
+ arc = {
177
+ "group": "a",
178
+ "pk": sa.pk,
179
+ "name": sa.name,
180
+ }
181
+ sas.append(arc)
182
+ sas = sorted(sas, key=lambda x: x["name"])
183
+ arcs += sas
184
+ return arcs
185
+
186
+ def _raise_not_found(self):
187
+ """Raise not found exception."""
188
+ pk = self.kwargs.get("pk")
189
+ detail = {
190
+ "route": reverse("app:start"),
191
+ "reason": f"comic {pk} not found",
192
+ "serializer": ReaderRedirectSerializer,
193
+ }
194
+ raise NotFound(detail=detail)
195
+
196
+ def get_object(self):
197
+ """Get the previous and next comics in a group or story arc."""
198
+ # Books
199
+ books, arc_count = self._get_book_collection()
200
+
201
+ current = books.get("current")
202
+ if not current:
124
203
  self._raise_not_found()
125
204
 
126
- return {"books": books, "series_count": comics.count()}
205
+ prev_book = books.get("prev")
206
+ next_book = books.get("next")
207
+ books = {
208
+ "current": current,
209
+ "prev_book": prev_book,
210
+ "next_book": next_book,
211
+ }
212
+
213
+ # Arcs
214
+ arcs = self._get_arcs(current)
215
+
216
+ arc_group = self.params.get("arc_group")
217
+ arc = {
218
+ "group": arc_group,
219
+ "pk": current.arc_pk, # type: ignore
220
+ "index": current.arc_index, # type: ignore
221
+ "count": arc_count,
222
+ }
223
+
224
+ return {
225
+ "books": books,
226
+ "arcs": arcs,
227
+ "arc": arc,
228
+ }
229
+
230
+ def _parse_params(self):
231
+ data = self.request.GET
232
+
233
+ # PARAMS
234
+ session = self.request.session.get(BrowserSessionViewBase.SESSION_KEY, {})
235
+ top_group = session.get("top_group", "s")
236
+ arc_group = data.get("arcGroup")
237
+ if not arc_group:
238
+ arc_group = top_group
239
+
240
+ if arc_group not in self._VALID_ARC_GROUPS:
241
+ arc_group = "s"
242
+
243
+ arc_pk = data.get("arcPk")
244
+ if arc_pk is not None:
245
+ arc_pk = int(arc_pk)
246
+ elif top_group == "a":
247
+ last_route = session.get("route", {})
248
+ arc_pk = last_route.get("pk")
249
+
250
+ params = {
251
+ "arc_group": arc_group,
252
+ "arc_pk": arc_pk,
253
+ "top_group": top_group,
254
+ }
255
+ self.params = params
127
256
 
128
257
  def get(self, *args, **kwargs):
129
258
  """Get the book info."""
259
+ self._parse_params()
130
260
  obj = self.get_object()
131
261
  serializer = self.get_serializer(obj)
132
262
  return Response(serializer.data)
codex/views/session.py CHANGED
@@ -63,6 +63,7 @@ class BrowserSessionViewBase(SessionViewBaseBase):
63
63
 
64
64
  SESSION_KEY = "browser" # type: ignore
65
65
  CREATOR_PERSON_UI_FIELD = "creators"
66
+ STORY_ARC_UI_FIELD = "story_arcs"
66
67
  _DYNAMIC_FILTER_DEFAULTS = {
67
68
  "age_rating": [],
68
69
  "characters": [],
@@ -79,7 +80,7 @@ class BrowserSessionViewBase(SessionViewBaseBase):
79
80
  "q": "",
80
81
  "read_ltr": [],
81
82
  "series_groups": [],
82
- "story_arcs": [],
83
+ STORY_ARC_UI_FIELD: [],
83
84
  "tags": [],
84
85
  "teams": [],
85
86
  "year": [],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codex
3
- Version: 1.4.0a1
3
+ Version: 1.4.1
4
4
  Summary: A comic archive web server.
5
5
  Home-page: https://github.com/ajslater/codex
6
6
  License: GPL-3.0-only
@@ -28,27 +28,24 @@ Requires-Dist: aioprocessing (>=2.0.1,<3.0.0)
28
28
  Requires-Dist: ansicolors (>=1.1,<2.0)
29
29
  Requires-Dist: case-converter (>=1.1.0,<2.0.0)
30
30
  Requires-Dist: channels (>=4.0.0,<5.0.0)
31
- Requires-Dist: comicbox (>=0.6.7,<0.7.0)
31
+ Requires-Dist: comicbox[pdf] (>=0.10.1,<0.11.0)
32
32
  Requires-Dist: django (>=4.2,<5.0)
33
- Requires-Dist: django-cors-headers (>=3.2,<4.0)
33
+ Requires-Dist: django-cors-headers (>=4.0,<5.0)
34
34
  Requires-Dist: django-haystack (>=3.2.1,<4.0.0)
35
35
  Requires-Dist: django-rest-registration (>=0.8.0,<0.9.0)
36
- Requires-Dist: django-vite (>=2.0.2,<3.0.0)
36
+ Requires-Dist: django-vite (==2.1.1)
37
37
  Requires-Dist: djangorestframework (>=3.11,<4.0)
38
38
  Requires-Dist: djangorestframework-camel-case (>=1.3.0,<2.0.0)
39
39
  Requires-Dist: drf-spectacular (>=0.26.0,<0.27.0)
40
- Requires-Dist: filelock (>=3.4.2,<4.0.0)
41
- Requires-Dist: filetype (>=1.0.12,<2.0.0)
42
40
  Requires-Dist: fnvhash (>=0.1,<0.2)
43
41
  Requires-Dist: humanfriendly (>=10.0,<11.0)
44
42
  Requires-Dist: humanize (>=4.0.0,<5.0.0)
45
43
  Requires-Dist: hypercorn[h3] (>=0.14.1,<0.15.0)
46
44
  Requires-Dist: psutil (>=5.9.4,<6.0.0)
47
45
  Requires-Dist: pycountry (>=22.1,<23.0)
48
- Requires-Dist: pymupdf (>=1.21.1,<2.0.0)
49
46
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
50
47
  Requires-Dist: requests (>=2.24,<3.0)
51
- Requires-Dist: tzlocal (>=4.1,<5.0)
48
+ Requires-Dist: tzlocal (>=5.0,<6.0)
52
49
  Requires-Dist: watchdog (>=3.0,<4.0)
53
50
  Requires-Dist: websocket_client (>=1.2,<2.0)
54
51
  Requires-Dist: whitenoise[brotli] (>=6.0,<7.0)
@@ -163,45 +160,17 @@ packaged for Linux, but here are some instructions:
163
160
 
164
161
  Unrar as packaged for Alpine Linux v3.14 seems to work on Alpine v3.15
165
162
 
166
- Codex will also prefer to use the `unrar-cffi` package it finds it installed,
167
- this is not required.
168
-
169
163
  #### Windows
170
164
 
171
165
  Windows users should use Docker to run Codex until this documentation section is
172
166
  complete.
173
167
 
174
- Codex can _probably_ run using Cygwin or the Windows Linux Subsystem but I
175
- haven't done it yet. Contributions to this documentation accepted on
176
- [the outstanding issue](https://github.com/ajslater/codex/issues/76) or discord.
177
-
178
- ##### Windows Linux Subsystem
179
-
180
- Untested. Try following the instructions for [Debian](#debian) above.
181
-
182
- ##### Cygwin
168
+ Codex can _probably_ Windows Linux Subsystem but I haven't done personally
169
+ tested it yet. Try following the instructions for [Debian](#debian) above. There
170
+ may be outstanding platform related bugs.
183
171
 
184
- Untested partial instructions for the brave.
185
-
186
- 1. Install [Cygwin](https://www.cygwin.com/).
187
- 2. Install wget with cygwin.
188
- 3. Install:
189
- - python3.9+
190
- - gcc
191
- - gcc-g++
192
- - libffi-devel
193
- - libjpeg-devel
194
- - libssl-devel
195
- - mpfr
196
- - mpc
197
- - python3-devel
198
- - python39-cffi
199
- - python3.9-openssl with cygwin.
200
- 4. Using a terminal:
201
-
202
- ```sh
203
- pip install wheel
204
- ```
172
+ Contributions to this documentation accepted on
173
+ [the outstanding issue](https://github.com/ajslater/codex/issues/76) or discord.
205
174
 
206
175
  #### Install Codex with pip
207
176