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
codex/logger/loggerd.py CHANGED
@@ -41,7 +41,7 @@ class CodexLogQueueListener(QueueListener):
41
41
  _LOG_FMT = "{asctime} {levelname:7} {message}"
42
42
  _DEBUG_LOG_FMT = "{asctime} {levelname:7} {name:25} {message}"
43
43
  _DATEFMT = "%Y-%m-%d %H:%M:%S %Z"
44
- _FORMATTER_KWARGS = {"style": "{", "datefmt": _DATEFMT}
44
+ _FORMATTER_STYLE = "{"
45
45
  _LOG_PATH = LOG_DIR / "codex.log"
46
46
  _LOG_MAX_BYTES = 10 * 1024 * 1024
47
47
 
@@ -54,7 +54,9 @@ class CodexLogQueueListener(QueueListener):
54
54
  handler = RotatingFileHandler(
55
55
  cls._LOG_PATH, maxBytes=cls._LOG_MAX_BYTES, backupCount=30, delay=True
56
56
  )
57
- formatter = logging.Formatter(fmt, **cls._FORMATTER_KWARGS)
57
+ formatter = logging.Formatter(
58
+ fmt, style=cls._FORMATTER_STYLE, datefmt=cls._DATEFMT
59
+ )
58
60
  handler.setFormatter(formatter)
59
61
  except Exception as exc:
60
62
  print("ERROR creating file logging handler", exc)
@@ -66,7 +68,9 @@ class CodexLogQueueListener(QueueListener):
66
68
  handler = None
67
69
  try:
68
70
  handler = logging.StreamHandler()
69
- formatter = ColorFormatter(fmt, **cls._FORMATTER_KWARGS)
71
+ formatter = ColorFormatter(
72
+ fmt, style=cls._FORMATTER_STYLE, datefmt=cls._DATEFMT
73
+ )
70
74
  handler.setFormatter(formatter)
71
75
  except Exception as exc:
72
76
  print("ERROR creating console logging handler", exc)
codex/logger/logging.py CHANGED
@@ -19,7 +19,7 @@ class CodexQueueHandler(QueueHandler):
19
19
  super().enqueue(record)
20
20
 
21
21
 
22
- LOG_HANDLER = CodexQueueHandler(LOG_QUEUE)
22
+ LOG_HANDLER = CodexQueueHandler(LOG_QUEUE) # type: ignore
23
23
 
24
24
 
25
25
  def _ensure_handler(logger, queue):
@@ -0,0 +1,24 @@
1
+ """Generated by Django 4.2.1 on 2023-05-10 22:44."""
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ """Add fields."""
8
+
9
+ dependencies = [
10
+ ("codex", "0023_rename_credit_creator_and_more"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name="comic",
16
+ name="gtin",
17
+ field=models.CharField(db_index=True, default="", max_length=32),
18
+ ),
19
+ migrations.AddField(
20
+ model_name="comic",
21
+ name="story_arc_number",
22
+ field=models.PositiveSmallIntegerField(db_index=True, null=True),
23
+ ),
24
+ ]
@@ -0,0 +1,83 @@
1
+ """Generated by Django 4.2.1 on 2023-05-17 19:22."""
2
+ import django.db.models.deletion
3
+ from django.db import migrations, models
4
+
5
+
6
+ def _create_story_arc_numbers(apps, _schema_editor):
7
+ comic_model = apps.get_model("codex", "comic")
8
+ san_model = apps.get_model("codex", "StoryArcNumber")
9
+ num_sans = 0
10
+
11
+ comics = comic_model.objects.exclude(story_arcs=None)
12
+ print()
13
+ print(f"Comics with story arcs: {comics.count()}")
14
+ # Create a StoryArcNumber for each comic
15
+ for comic in comics:
16
+ sans = set()
17
+ first_done = False
18
+ for sa in comic.story_arcs.all():
19
+ number = None if first_done else comic.story_arc_number
20
+ kwargs = {"story_arc": sa, "number": number}
21
+ san, created = san_model.objects.get_or_create(defaults=kwargs, **kwargs)
22
+ num_sans += int(created)
23
+ sans.add(san)
24
+ first_done = True
25
+ comic.story_arc_numbers.add(*sans)
26
+ comic.save()
27
+
28
+ num_sas = apps.get_model("codex", "StoryArc").objects.count()
29
+ print(f"Created {num_sans} StoryArcNumbers for {num_sas} StoryArcs")
30
+
31
+
32
+ class Migration(migrations.Migration):
33
+ """Run Migrations."""
34
+
35
+ dependencies = [
36
+ ("codex", "0024_comic_gtin_comic_story_arc_number"),
37
+ ]
38
+
39
+ operations = [
40
+ migrations.CreateModel(
41
+ name="StoryArcNumber",
42
+ fields=[
43
+ (
44
+ "id",
45
+ models.AutoField(
46
+ auto_created=True,
47
+ primary_key=True,
48
+ serialize=False,
49
+ verbose_name="ID",
50
+ ),
51
+ ),
52
+ ("created_at", models.DateTimeField(auto_now_add=True)),
53
+ ("updated_at", models.DateTimeField(auto_now=True)),
54
+ ("number", models.PositiveIntegerField(default=None, null=True)),
55
+ (
56
+ "story_arc",
57
+ models.ForeignKey(
58
+ db_index=True,
59
+ on_delete=django.db.models.deletion.CASCADE,
60
+ to="codex.storyarc",
61
+ ),
62
+ ),
63
+ ],
64
+ ),
65
+ migrations.AlterUniqueTogether(
66
+ name="storyarcnumber",
67
+ unique_together={("story_arc", "number")},
68
+ ),
69
+ migrations.AddField(
70
+ model_name="comic",
71
+ name="story_arc_numbers",
72
+ field=models.ManyToManyField(to="codex.storyarcnumber"),
73
+ ),
74
+ migrations.RunPython(_create_story_arc_numbers),
75
+ migrations.RemoveField(
76
+ model_name="comic",
77
+ name="story_arc_number",
78
+ ),
79
+ migrations.RemoveField(
80
+ model_name="comic",
81
+ name="story_arcs",
82
+ ),
83
+ ]
codex/models.py CHANGED
@@ -199,10 +199,22 @@ class SeriesGroup(NamedModel):
199
199
  """A series group the series is part of."""
200
200
 
201
201
 
202
- class StoryArc(NamedModel):
202
+ class StoryArc(NamedModel, BrowserGroupModel):
203
203
  """A story arc the comic is part of."""
204
204
 
205
205
 
206
+ class StoryArcNumber(BaseModel):
207
+ """A story arc number the comic represents."""
208
+
209
+ story_arc = ForeignKey(StoryArc, db_index=True, on_delete=CASCADE)
210
+ number = PositiveIntegerField(null=True, default=None)
211
+
212
+ class Meta:
213
+ """Declare constraints and indexes."""
214
+
215
+ unique_together = ("story_arc", "number")
216
+
217
+
206
218
  class Location(NamedModel):
207
219
  """A location that appears in the comic."""
208
220
 
@@ -343,13 +355,15 @@ class Comic(WatchedPath):
343
355
  read_ltr = BooleanField(db_index=True, default=True)
344
356
  scan_info = CharField(max_length=MAX_NAME_LEN, default="")
345
357
  web = URLField(default="")
358
+ gtin = CharField(db_index=True, max_length=MAX_FIELD_LEN, default="")
359
+
346
360
  # ManyToMany
347
361
  characters = ManyToManyField(Character)
348
362
  creators = ManyToManyField(Creator)
349
363
  genres = ManyToManyField(Genre)
350
364
  locations = ManyToManyField(Location)
351
365
  series_groups = ManyToManyField(SeriesGroup)
352
- story_arcs = ManyToManyField(StoryArc)
366
+ story_arc_numbers = ManyToManyField(StoryArcNumber)
353
367
  tags = ManyToManyField(Tag)
354
368
  teams = ManyToManyField(Team)
355
369
  # Ignore these, they seem useless:
@@ -465,18 +479,14 @@ class Comic(WatchedPath):
465
479
  return title
466
480
 
467
481
  @classmethod
468
- def get_filename(cls, obj, issue_max=None):
482
+ def get_filename(cls, obj):
469
483
  """Get the fileaname from dict."""
470
- fn = cls.get_title(obj, issue_max=issue_max)
471
- ft = obj.file_type or "cbz"
472
- fn += "." + ft.lower()
473
- return fn
484
+ path = Path(obj.path)
485
+ return path.stem + path.suffix
474
486
 
475
- def filename(self, issue_max=None):
487
+ def filename(self):
476
488
  """Create a filename for download."""
477
- self.series_name = self.series.name
478
- self.volume_name = self.volume.name
479
- return self.get_filename(self, issue_max)
489
+ return self.get_filename(self)
480
490
 
481
491
  def __str__(self):
482
492
  """Most common text representation for logging."""
codex/search/backend.py CHANGED
@@ -155,7 +155,7 @@ class CodexSearchBackend(WhooshSearchBackend, WorkerBaseMixin):
155
155
  "genres",
156
156
  "locations",
157
157
  "series_groups",
158
- "story_arcs",
158
+ "story_arc_numbers__story_arc",
159
159
  "tags",
160
160
  "teams",
161
161
  "creators__person",
codex/search/indexes.py CHANGED
@@ -21,7 +21,7 @@ class ComicIndex(ModelSearchIndex, Indexable):
21
21
  locations = CharField(model_attr="locations__name", null=True)
22
22
  series_groups = CharField(model_attr="series_groups__name", null=True)
23
23
  story_arcs = CharField(
24
- model_attr="story_arcs__name",
24
+ model_attr="story_arc_numbers__story_arc__name",
25
25
  null=True,
26
26
  )
27
27
  tags = CharField(model_attr="tags__name", null=True)
@@ -162,6 +162,7 @@ class BrowserSettingsSerializer(Serializer):
162
162
  twenty_four_hour_time = BooleanField(required=False)
163
163
  top_group = ChoiceField(choices=tuple(CHOICES["topGroup"].keys()), required=False)
164
164
  opds_metadata = BooleanField(required=False)
165
+ limit = IntegerField(required=False)
165
166
 
166
167
 
167
168
  class BrowserCardSerializer(BrowserAggregateSerializerMixin):
@@ -2,7 +2,7 @@
2
2
  from rest_framework.serializers import CharField, IntegerField
3
3
 
4
4
  from codex.serializers.mixins import BrowserAggregateSerializerMixin
5
- from codex.serializers.models import ComicSerializer
5
+ from codex.serializers.models import ComicSerializer, StoryArcSerializer
6
6
 
7
7
 
8
8
  class MetadataSerializer(BrowserAggregateSerializerMixin, ComicSerializer):
@@ -12,3 +12,7 @@ class MetadataSerializer(BrowserAggregateSerializerMixin, ComicSerializer):
12
12
  parent_folder_pk = IntegerField(read_only=True, required=False)
13
13
  series_volume_count = IntegerField(read_only=True)
14
14
  volume_issue_count = IntegerField(read_only=True)
15
+ story_arcs = StoryArcSerializer(
16
+ many=True,
17
+ allow_null=True,
18
+ )
@@ -24,6 +24,7 @@ from codex.models import (
24
24
  Series,
25
25
  SeriesGroup,
26
26
  StoryArc,
27
+ StoryArcNumber,
27
28
  Tag,
28
29
  Team,
29
30
  Volume,
@@ -233,6 +234,20 @@ class StoryArcSerializer(NamedModelSerializer):
233
234
  model = StoryArc
234
235
 
235
236
 
237
+ class StoryArcNumberSerializer(Serializer):
238
+ """StoryArc model."""
239
+
240
+ story_arc = StoryArcSerializer()
241
+ number = IntegerField(read_only=True, allow_null=True)
242
+
243
+ class Meta(NamedModelMeta):
244
+ """Configure model."""
245
+
246
+ model = StoryArcNumber
247
+ fields = ("pk", "story_arc", "number")
248
+ depth = 1
249
+
250
+
236
251
  class TagSerializer(NamedModelSerializer):
237
252
  """Tag model."""
238
253
 
@@ -276,7 +291,7 @@ class ComicSerializer(ModelSerializer):
276
291
  genres = GenreSerializer(many=True, allow_null=True)
277
292
  locations = LocationSerializer(many=True, allow_null=True)
278
293
  series_groups = SeriesGroupSerializer(many=True, allow_null=True)
279
- story_arcs = StoryArcSerializer(
294
+ story_arc_numbers = StoryArcNumberSerializer(
280
295
  many=True,
281
296
  allow_null=True,
282
297
  )
@@ -60,6 +60,7 @@ class OPDS1TemplateSerializer(Serializer):
60
60
  """OPDS Browser Template Serializer."""
61
61
 
62
62
  opds_ns = CharField(read_only=True)
63
+ is_acquisition = BooleanField(read_only=True)
63
64
  id_tag = CharField(read_only=True)
64
65
  title = CharField(read_only=True)
65
66
  updated = DateTimeField(read_only=True, default_timezone=UTC_TZ)
@@ -1,4 +1,6 @@
1
1
  """OPDS 2 Serializers."""
2
+ from typing import Optional
3
+
2
4
  from rest_framework.fields import (
3
5
  BooleanField,
4
6
  CharField,
@@ -59,7 +61,7 @@ class OPDS2LinkSerializer(Serializer):
59
61
  children = CharListField(read_only=True, required=False)
60
62
  properties = OPDS2LinkPropertiesSerializer(read_only=True, required=False)
61
63
 
62
- def get_rel(self, obj):
64
+ def get_rel(self, obj) -> Optional[str]:
63
65
  """Allow for CharField or CharListField types."""
64
66
  rel = obj.get("rel")
65
67
  if not isinstance(rel, (list, str)):
@@ -118,7 +120,8 @@ class OPDS2PublicationMetadataSerializer(OPDS2MetadataSerializer):
118
120
  https://readium.org/webpub-manifest/schema/metadata.schema.json
119
121
  """
120
122
 
121
- # type = CharField(read_only=True, required=False) # TODO @type
123
+ # possibly change to @ on output if this is really needed
124
+ # @type = CharField(read_only=True, required=False)
122
125
  publisher = CharField(read_only=True, required=False)
123
126
  imprint = CharField(read_only=True, required=False)
124
127
  language = CharField(read_only=True, required=False)
@@ -25,27 +25,66 @@ class ReaderSettingsSerializer(Serializer):
25
25
  vertical = BooleanField(allow_null=True, required=False)
26
26
 
27
27
 
28
+ class ReaderArcSerializer(Serializer):
29
+ """A group of comics or a story arc."""
30
+
31
+ group = CharField(read_only=True)
32
+ pk = IntegerField(read_only=True)
33
+ name = CharField(read_only=True)
34
+
35
+
28
36
  class ReaderComicSerializer(Serializer):
29
- """Components for constructing the title."""
37
+ """Prev, Next and Current Comic info."""
30
38
 
31
39
  pk = IntegerField(read_only=True)
32
- file_type = CharField(read_only=True)
33
- issue = DecimalField(
34
- max_digits=None, decimal_places=3, read_only=True, coerce_to_string=False
35
- )
36
- issue_suffix = CharField(read_only=True)
37
- issue_count = IntegerField(read_only=True)
40
+ settings = ReaderSettingsSerializer(read_only=True)
38
41
  max_page = IntegerField(read_only=True)
39
- series_name = CharField(read_only=True)
40
- volume_name = CharField(read_only=True)
41
- series_index = IntegerField(read_only=True)
42
42
  read_ltr = BooleanField(allow_null=True, read_only=True)
43
- settings = ReaderSettingsSerializer(read_only=True)
44
- filename = CharField(read_only=True)
45
43
 
46
44
 
47
- class ReaderInfoSerializer(Serializer):
48
- """Information about the series this comic belongs to."""
45
+ class ReaderCurrentArcSerializer(Serializer):
46
+ """Information about the current Arc."""
47
+
48
+ group = CharField(read_only=True)
49
+ pk = IntegerField(read_only=True)
50
+ index = IntegerField(read_only=True)
51
+ count = IntegerField(read_only=True, required=False)
52
+
53
+
54
+ class ReaderCurrentComicSerializer(ReaderComicSerializer):
55
+ """Current comic only Serializer."""
56
+
57
+ # For title
58
+ series_name = CharField(read_only=True, required=False)
59
+ volume_name = CharField(read_only=True, required=False)
60
+ issue = DecimalField(
61
+ max_digits=None,
62
+ decimal_places=3,
63
+ read_only=True,
64
+ coerce_to_string=False,
65
+ required=False,
66
+ )
67
+ issue_suffix = CharField(read_only=True, required=False)
68
+ issue_count = IntegerField(
69
+ read_only=True,
70
+ required=False,
71
+ )
72
+
73
+ file_type = CharField(read_only=True, required=False)
74
+ filename = CharField(read_only=True, required=False)
75
+
76
+
77
+ class ReaderBooksSerializer(Serializer):
78
+ """All comics relevant to the reader."""
79
+
80
+ current = ReaderCurrentComicSerializer(read_only=True)
81
+ prev_book = ReaderComicSerializer(read_only=True, required=False)
82
+ next_book = ReaderComicSerializer(read_only=True, required=False)
83
+
84
+
85
+ class ReaderComicsSerializer(Serializer):
86
+ """Books and arcs."""
49
87
 
50
- books = ReaderComicSerializer(many=True, read_only=True)
51
- series_count = IntegerField(read_only=True)
88
+ books = ReaderBooksSerializer(read_only=True)
89
+ arcs = ReaderArcSerializer(many=True, read_only=True)
90
+ arc = ReaderCurrentArcSerializer(read_only=True)
@@ -181,7 +181,7 @@ PORT = int(HYPERCORN_CONFIG.bind[0].split(":")[1])
181
181
 
182
182
  # Static files (CSS, JavaScript, Images)
183
183
  # https://docs.djangoproject.com/en/dev/howto/static-files/
184
- # WHITENOISE_KEEP_ONLY_HASHED_FILES is not usable with vite chunking
184
+ # WHITENOISE_KEEP_ONLY_HASHED_FILES is still not usable with vite chunking
185
185
  WHITENOISE_STATIC_PREFIX = "static/"
186
186
  WHITENOISE_IMMUTABLE_FILE_TEST = immutable_file_test
187
187
  STATIC_ROOT = CODEX_PATH / "static_root"