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.
- codex/config_default.yaml +12 -4
- codex/db_functions.py +4 -2
- codex/integrity.py +17 -6
- codex/librarian/covers/coverd.py +3 -11
- codex/librarian/covers/create.py +15 -21
- codex/librarian/covers/tasks.py +2 -16
- codex/librarian/importer/aggregate_metadata.py +75 -41
- codex/librarian/importer/clean_metadata.py +30 -7
- codex/librarian/importer/create_fks.py +154 -55
- codex/librarian/importer/deleted.py +11 -2
- codex/librarian/importer/failed_imports.py +44 -5
- codex/librarian/importer/importerd.py +37 -12
- codex/librarian/importer/link_comics.py +54 -31
- codex/librarian/importer/moved.py +55 -11
- codex/librarian/importer/query_fks.py +210 -48
- codex/librarian/importer/tasks.py +7 -7
- codex/librarian/janitor/cleanup.py +17 -5
- codex/librarian/librariand.py +10 -0
- codex/librarian/watchdog/events.py +11 -14
- codex/librarian/watchdog/observers.py +5 -1
- codex/logger/loggerd.py +7 -3
- codex/logger/logging.py +1 -1
- codex/migrations/0024_comic_gtin_comic_story_arc_number.py +24 -0
- codex/migrations/0025_add_story_arc_number.py +83 -0
- codex/models.py +21 -11
- codex/search/backend.py +1 -1
- codex/search/indexes.py +1 -1
- codex/serializers/browser.py +1 -0
- codex/serializers/metadata.py +5 -1
- codex/serializers/models.py +16 -1
- codex/serializers/opds/v1.py +1 -0
- codex/serializers/opds/v2.py +5 -2
- codex/serializers/reader.py +55 -16
- codex/settings/settings.py +1 -1
- codex/static_root/assets/admin-12749881.ef0f50bac290.js +41 -0
- codex/static_root/assets/admin-12749881.ef0f50bac290.js.br +0 -0
- codex/static_root/assets/admin-12749881.ef0f50bac290.js.gz +0 -0
- codex/static_root/assets/admin-12749881.js +41 -0
- codex/static_root/assets/admin-12749881.js.br +0 -0
- codex/static_root/assets/admin-12749881.js.gz +0 -0
- codex/static_root/assets/admin-beda768d.a614eee46307.css +1 -0
- codex/static_root/assets/admin-beda768d.a614eee46307.css.br +0 -0
- codex/static_root/assets/admin-beda768d.a614eee46307.css.gz +0 -0
- codex/static_root/assets/admin-beda768d.css +1 -0
- codex/static_root/assets/admin-beda768d.css.br +0 -0
- codex/static_root/assets/admin-beda768d.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css +1 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.css +1 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-41c225cc.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js +1 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js +1 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js.gz +0 -0
- codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css +1 -0
- codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.br +0 -0
- codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.gz +0 -0
- codex/static_root/assets/browser-7f7d7134.css +1 -0
- codex/static_root/assets/browser-7f7d7134.css.br +0 -0
- codex/static_root/assets/browser-7f7d7134.css.gz +0 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js +1 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js.br +0 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js.gz +0 -0
- codex/static_root/assets/browser-af622672.js +1 -0
- codex/static_root/assets/browser-af622672.js.br +0 -0
- codex/static_root/assets/browser-af622672.js.gz +0 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js +1 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.br +0 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.gz +0 -0
- codex/static_root/assets/http-error-5e17b794.js +1 -0
- codex/static_root/assets/http-error-5e17b794.js.br +0 -0
- codex/static_root/assets/http-error-5e17b794.js.gz +0 -0
- codex/static_root/assets/{main-a6ac9581.2fd9e52cbcc3.css → main-0898f4bb.181e0145c642.css} +1 -1
- codex/static_root/assets/main-0898f4bb.181e0145c642.css.br +0 -0
- codex/static_root/assets/{main-a6ac9581.2fd9e52cbcc3.css.gz → main-0898f4bb.181e0145c642.css.gz} +0 -0
- codex/static_root/assets/{main-a6ac9581.css → main-0898f4bb.css} +1 -1
- codex/static_root/assets/main-0898f4bb.css.br +0 -0
- codex/static_root/assets/{main-a6ac9581.css.gz → main-0898f4bb.css.gz} +0 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js +1 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.br +0 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.gz +0 -0
- codex/static_root/assets/main-9e76a4c3.js +1 -0
- codex/static_root/assets/main-9e76a4c3.js.br +0 -0
- codex/static_root/assets/main-9e76a4c3.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js +1 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.br +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.js +1 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.js.br +0 -0
- codex/static_root/assets/metadata-dialog-62c29ce0.js.gz +0 -0
- codex/static_root/assets/{metadata-dialog-785c4cfc.694a251cda37.css → metadata-dialog-cb306ffd.cc304996d7bb.css} +1 -1
- codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.br +0 -0
- codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.gz +0 -0
- codex/static_root/assets/{metadata-dialog-785c4cfc.css → metadata-dialog-cb306ffd.css} +1 -1
- codex/static_root/assets/metadata-dialog-cb306ffd.css.br +0 -0
- codex/static_root/assets/metadata-dialog-cb306ffd.css.gz +0 -0
- codex/static_root/assets/{page-pdf-abfd509d.3870dab8eaf4.js → page-pdf-157ba97e.613d7c2beb77.js} +61 -51
- codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.br +0 -0
- codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.gz +0 -0
- codex/static_root/assets/{page-pdf-abfd509d.js → page-pdf-157ba97e.js} +61 -51
- codex/static_root/assets/page-pdf-157ba97e.js.br +0 -0
- codex/static_root/assets/page-pdf-157ba97e.js.gz +0 -0
- codex/static_root/assets/reader-36266549.0b2cf1291f27.js +1 -0
- codex/static_root/assets/reader-36266549.0b2cf1291f27.js.br +0 -0
- codex/static_root/assets/reader-36266549.0b2cf1291f27.js.gz +0 -0
- codex/static_root/assets/reader-36266549.js +1 -0
- codex/static_root/assets/reader-36266549.js.br +0 -0
- codex/static_root/assets/reader-36266549.js.gz +0 -0
- codex/static_root/assets/reader-7f004141.506eecc6954b.css +1 -0
- codex/static_root/assets/reader-7f004141.506eecc6954b.css.br +0 -0
- codex/static_root/assets/reader-7f004141.506eecc6954b.css.gz +0 -0
- codex/static_root/assets/reader-7f004141.css +1 -0
- codex/static_root/assets/reader-7f004141.css.br +0 -0
- codex/static_root/assets/reader-7f004141.css.gz +0 -0
- codex/static_root/js/choices-admin.24cecf0a0568.json +1 -0
- codex/static_root/js/choices-admin.24cecf0a0568.json.br +0 -0
- codex/static_root/js/choices-admin.24cecf0a0568.json.gz +0 -0
- codex/static_root/js/choices-admin.json +1 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/choices.8c58714cf5b2.json +1 -0
- codex/static_root/js/choices.8c58714cf5b2.json.br +5 -0
- codex/static_root/js/choices.8c58714cf5b2.json.gz +0 -0
- codex/static_root/js/choices.json +1 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/{manifest.64a989215af8.json → manifest.d2f93a519ada.json} +34 -34
- codex/static_root/manifest.d2f93a519ada.json.br +0 -0
- codex/static_root/manifest.d2f93a519ada.json.gz +0 -0
- codex/static_root/manifest.json +34 -34
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/templates/headers-script-globals.html +1 -1
- codex/templates/{opds → opds_v1}/index.xml +3 -1
- codex/templates/{opds/opensearch.xml → opds_v1/opensearch_v1.xml} +1 -1
- codex/templates/search/indexes/codex/comic_text.txt +2 -2
- codex/urls/converters.py +1 -1
- codex/urls/opds/authentication.py +1 -1
- codex/urls/opds/root.py +8 -12
- codex/urls/opds/v1.py +12 -5
- codex/urls/opds/v2.py +2 -2
- codex/views/admin/tasks.py +6 -1
- codex/views/bookmark.py +2 -2
- codex/views/browser/base.py +23 -7
- codex/views/browser/browser.py +66 -56
- codex/views/browser/browser_annotations.py +159 -50
- codex/views/browser/browser_order_by.py +51 -105
- codex/views/browser/choices.py +75 -38
- codex/views/browser/filters/bookmark.py +6 -9
- codex/views/browser/filters/field.py +9 -6
- codex/views/browser/filters/group.py +12 -27
- codex/views/browser/filters/search.py +5 -10
- codex/views/browser/metadata.py +44 -19
- codex/views/download.py +1 -1
- codex/views/frontend.py +2 -3
- codex/views/mixins.py +15 -2
- codex/views/opds/const.py +8 -1
- codex/views/opds/util.py +37 -1
- codex/views/opds/v1/__init__.py +1 -1
- codex/views/opds/v1/data.py +21 -0
- codex/views/opds/v1/entry/__init__.py +1 -0
- codex/views/opds/v1/entry/data.py +23 -0
- codex/views/opds/v1/entry/entry.py +151 -0
- codex/views/opds/v1/entry/links.py +135 -0
- codex/views/opds/v1/facets.py +190 -0
- codex/views/opds/v1/feed.py +199 -0
- codex/views/opds/v1/links.py +198 -0
- codex/views/opds/{opensearch.py → v1/opensearch_v1.py} +3 -3
- codex/views/opds/v2/__init__.py +1 -1
- codex/views/opds/v2/const.py +10 -2
- codex/views/opds/v2/feed.py +82 -21
- codex/views/opds/v2/links.py +1 -1
- codex/views/opds/v2/publications.py +1 -1
- codex/views/opds/v2/top_links.py +1 -1
- codex/views/reader/page.py +6 -7
- codex/views/reader/reader.py +191 -61
- codex/views/session.py +2 -1
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/METADATA +10 -41
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/RECORD +187 -185
- codex/librarian/importer/db_ops.py +0 -248
- codex/pdf.py +0 -115
- codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js +0 -48
- codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js.br +0 -0
- codex/static_root/assets/admin-73d93dc7.2c3eb62e50a0.js.gz +0 -0
- codex/static_root/assets/admin-73d93dc7.js +0 -48
- codex/static_root/assets/admin-73d93dc7.js.br +0 -0
- codex/static_root/assets/admin-73d93dc7.js.gz +0 -0
- codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css +0 -1
- codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css.br +0 -0
- codex/static_root/assets/admin-79555229.5f2c4cb3a73c.css.gz +0 -0
- codex/static_root/assets/admin-79555229.css +0 -1
- codex/static_root/assets/admin-79555229.css.br +0 -0
- codex/static_root/assets/admin-79555229.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js +0 -1
- codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.a85324c9ccd8.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.js +0 -1
- codex/static_root/assets/admin-drawer-panel-64bcc083.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-64bcc083.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css +0 -1
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.br +0 -2
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.css +0 -1
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.br +0 -2
- codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.gz +0 -0
- codex/static_root/assets/browser-7325db61.css +0 -1
- codex/static_root/assets/browser-7325db61.css.br +0 -0
- codex/static_root/assets/browser-7325db61.css.gz +0 -0
- codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css +0 -1
- codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.br +0 -0
- codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.gz +0 -0
- codex/static_root/assets/browser-d2caeed7.2262000a6d55.js +0 -1
- codex/static_root/assets/browser-d2caeed7.2262000a6d55.js.br +0 -0
- codex/static_root/assets/browser-d2caeed7.2262000a6d55.js.gz +0 -0
- codex/static_root/assets/browser-d2caeed7.js +0 -1
- codex/static_root/assets/browser-d2caeed7.js.br +0 -0
- codex/static_root/assets/browser-d2caeed7.js.gz +0 -0
- codex/static_root/assets/http-error-0221c37d.480d5066da92.js +0 -1
- codex/static_root/assets/http-error-0221c37d.480d5066da92.js.br +0 -0
- codex/static_root/assets/http-error-0221c37d.480d5066da92.js.gz +0 -0
- codex/static_root/assets/http-error-0221c37d.js +0 -1
- codex/static_root/assets/http-error-0221c37d.js.br +0 -0
- codex/static_root/assets/http-error-0221c37d.js.gz +0 -0
- codex/static_root/assets/main-a6ac9581.2fd9e52cbcc3.css.br +0 -0
- codex/static_root/assets/main-a6ac9581.css.br +0 -0
- codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js +0 -1
- codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js.br +0 -0
- codex/static_root/assets/main-e33dcfb0.a65044fc1a08.js.gz +0 -0
- codex/static_root/assets/main-e33dcfb0.js +0 -1
- codex/static_root/assets/main-e33dcfb0.js.br +0 -0
- codex/static_root/assets/main-e33dcfb0.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.br +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.gz +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.css.br +0 -0
- codex/static_root/assets/metadata-dialog-785c4cfc.css.gz +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js +0 -1
- codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js.br +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.d12b42b1c9da.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.js +0 -1
- codex/static_root/assets/metadata-dialog-8b0e8aaa.js.br +0 -0
- codex/static_root/assets/metadata-dialog-8b0e8aaa.js.gz +0 -0
- codex/static_root/assets/page-pdf-abfd509d.3870dab8eaf4.js.br +0 -0
- codex/static_root/assets/page-pdf-abfd509d.3870dab8eaf4.js.gz +0 -0
- codex/static_root/assets/page-pdf-abfd509d.js.br +0 -0
- codex/static_root/assets/page-pdf-abfd509d.js.gz +0 -0
- codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css +0 -1
- codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css.br +0 -0
- codex/static_root/assets/reader-a8b8f766.875abdd0d22e.css.gz +0 -0
- codex/static_root/assets/reader-a8b8f766.css +0 -1
- codex/static_root/assets/reader-a8b8f766.css.br +0 -0
- codex/static_root/assets/reader-a8b8f766.css.gz +0 -0
- codex/static_root/assets/reader-fe9345d2.759c31f82998.js +0 -1
- codex/static_root/assets/reader-fe9345d2.759c31f82998.js.br +0 -0
- codex/static_root/assets/reader-fe9345d2.759c31f82998.js.gz +0 -0
- codex/static_root/assets/reader-fe9345d2.js +0 -1
- codex/static_root/assets/reader-fe9345d2.js.br +0 -0
- codex/static_root/assets/reader-fe9345d2.js.gz +0 -0
- codex/static_root/js/choices-admin.3d958ea7f83b.json +0 -1
- codex/static_root/js/choices-admin.3d958ea7f83b.json.br +0 -0
- codex/static_root/js/choices-admin.3d958ea7f83b.json.gz +0 -0
- codex/static_root/js/choices.6bfc2a3d293f.json +0 -1
- codex/static_root/js/choices.6bfc2a3d293f.json.br +0 -0
- codex/static_root/js/choices.6bfc2a3d293f.json.gz +0 -0
- codex/static_root/manifest.64a989215af8.json.br +0 -0
- codex/static_root/manifest.64a989215af8.json.gz +0 -0
- codex/urls/opds/opensearch.py +0 -18
- codex/views/opds/v1/browser.py +0 -346
- codex/views/opds/v1/entry.py +0 -278
- codex/views/opds/v1/start.py +0 -28
- codex/views/opds/v1/util.py +0 -162
- codex/views/opds/v2/start.py +0 -28
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/LICENSE +0 -0
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/WHEEL +0 -0
- {codex-1.4.0a0.dist-info → codex-1.4.1.dist-info}/entry_points.txt +0 -0
codex/views/opds/v2/feed.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""OPDS
|
|
1
|
+
"""OPDS v2.0 Feed."""
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
|
|
4
4
|
from drf_spectacular.utils import extend_schema
|
|
@@ -6,6 +6,7 @@ from rest_framework.authentication import BasicAuthentication, SessionAuthentica
|
|
|
6
6
|
from rest_framework.response import Response
|
|
7
7
|
|
|
8
8
|
from codex.logger.logging import get_logger
|
|
9
|
+
from codex.models import AdminFlag
|
|
9
10
|
from codex.serializers.opds.v2 import OPDS2FeedSerializer
|
|
10
11
|
from codex.views.browser.browser import BrowserView
|
|
11
12
|
from codex.views.opds.const import BLANK_TITLE, FALSY
|
|
@@ -27,6 +28,8 @@ LOG = get_logger(__name__)
|
|
|
27
28
|
class OPDS2FeedView(PublicationMixin, TopLinksMixin):
|
|
28
29
|
"""OPDS 2.0 Feed."""
|
|
29
30
|
|
|
31
|
+
DEFAULT_ROUTE_NAME = "opds:v2:feed"
|
|
32
|
+
|
|
30
33
|
authentication_classes = (BasicAuthentication, SessionAuthentication)
|
|
31
34
|
serializer_class = OPDS2FeedSerializer
|
|
32
35
|
|
|
@@ -45,41 +48,92 @@ class OPDS2FeedView(PublicationMixin, TopLinksMixin):
|
|
|
45
48
|
return result
|
|
46
49
|
|
|
47
50
|
def _detect_user_agent(self):
|
|
48
|
-
|
|
51
|
+
"""Hacks for individual clients."""
|
|
49
52
|
user_agent = self.request.headers.get("User-Agent")
|
|
50
53
|
if not user_agent:
|
|
51
54
|
return
|
|
52
55
|
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _is_allowed(link_spec):
|
|
58
|
+
"""Return if the link allowed."""
|
|
59
|
+
if (
|
|
60
|
+
getattr(link_spec, "group", None) == "f"
|
|
61
|
+
or getattr(link_spec, "query_param_value", None) == "f"
|
|
62
|
+
):
|
|
63
|
+
# Folder perms
|
|
64
|
+
efv_flag = (
|
|
65
|
+
AdminFlag.objects.only("on")
|
|
66
|
+
.get(key=AdminFlag.FlagChoices.FOLDER_VIEW.value)
|
|
67
|
+
.on
|
|
68
|
+
)
|
|
69
|
+
if not efv_flag:
|
|
70
|
+
return False
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _create_link_kwargs(data, link_spec):
|
|
75
|
+
"""Create link kwargs."""
|
|
76
|
+
if data.group_kwarg:
|
|
77
|
+
# Nav Groups
|
|
78
|
+
pk = getattr(link_spec, "pk", 0)
|
|
79
|
+
kwargs = {"group": link_spec.group, "pk": pk, "page": 1}
|
|
80
|
+
elif link_spec.query_param_value in ("f", "a"):
|
|
81
|
+
# Special Facets
|
|
82
|
+
kwargs = {
|
|
83
|
+
"group": link_spec.query_param_value,
|
|
84
|
+
"pk": 0,
|
|
85
|
+
"page": 1,
|
|
86
|
+
}
|
|
87
|
+
else:
|
|
88
|
+
# Regular Facets
|
|
89
|
+
kwargs = None
|
|
90
|
+
return kwargs
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _create_link_query_params(group_spec, link_spec, kwargs):
|
|
94
|
+
"""Create link query params."""
|
|
95
|
+
if qp_key := getattr(group_spec, "query_param_key", None):
|
|
96
|
+
# Facet shorthand with group
|
|
97
|
+
qps = {qp_key: link_spec.query_param_value}
|
|
98
|
+
elif ls_qps := getattr(link_spec, "query_params", None):
|
|
99
|
+
# Nav Group
|
|
100
|
+
qps = ls_qps
|
|
101
|
+
else:
|
|
102
|
+
# Regular Group
|
|
103
|
+
qps = None
|
|
104
|
+
|
|
105
|
+
# Special order by for story_arcs
|
|
106
|
+
if (
|
|
107
|
+
kwargs
|
|
108
|
+
and kwargs.get("group") == "a"
|
|
109
|
+
and kwargs.get("pk")
|
|
110
|
+
and (not qps or not qps.get("orderBy"))
|
|
111
|
+
):
|
|
112
|
+
if not qps:
|
|
113
|
+
qps = {}
|
|
114
|
+
qps["orderBy"] = "story_arc_number"
|
|
115
|
+
return qps
|
|
116
|
+
|
|
53
117
|
def _create_links_section(self, group_specs, data):
|
|
54
118
|
"""Create links sections for groups and facets."""
|
|
55
119
|
groups = []
|
|
56
120
|
for group_spec in group_specs:
|
|
57
121
|
link_dict = {}
|
|
58
122
|
for link_spec in group_spec.links:
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
pk = getattr(link_spec, "pk", 0)
|
|
62
|
-
kwargs = {"group": link_spec.group, "pk": pk, "page": 1}
|
|
63
|
-
else:
|
|
64
|
-
# Facets & Regular Groups
|
|
65
|
-
kwargs = None
|
|
66
|
-
rel = data.rel if data.rel else link_spec.rel
|
|
123
|
+
if not self._is_allowed(link_spec):
|
|
124
|
+
continue
|
|
67
125
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
elif ls_qps := getattr(link_spec, "query_params", None):
|
|
72
|
-
# Nav Group
|
|
73
|
-
qps = ls_qps
|
|
74
|
-
else:
|
|
75
|
-
# Regular Group
|
|
76
|
-
qps = None
|
|
126
|
+
kwargs = self._create_link_kwargs(data, link_spec)
|
|
127
|
+
|
|
128
|
+
qps = self._create_link_query_params(group_spec, link_spec, kwargs)
|
|
77
129
|
|
|
78
130
|
title = getattr(link_spec, "title", "")
|
|
79
131
|
if not title:
|
|
80
132
|
title = getattr(link_spec, "name", "")
|
|
81
133
|
|
|
82
134
|
href_data = HrefData(kwargs, qps)
|
|
135
|
+
|
|
136
|
+
rel = data.rel if data.rel else link_spec.rel
|
|
83
137
|
link_data = LinkData(rel, href_data, title=title)
|
|
84
138
|
link = self.link(link_data)
|
|
85
139
|
self.link_aggregate(link_dict, link)
|
|
@@ -117,8 +171,15 @@ class OPDS2FeedView(PublicationMixin, TopLinksMixin):
|
|
|
117
171
|
def get_object(self):
|
|
118
172
|
"""Get the browser page and serialize it for this subclass."""
|
|
119
173
|
group = self.kwargs.get("group")
|
|
120
|
-
|
|
121
|
-
|
|
174
|
+
if group in ("f", "a"):
|
|
175
|
+
self.acquisition_groups = frozenset()
|
|
176
|
+
else:
|
|
177
|
+
self.acquisition_groups = frozenset(self.valid_nav_groups[-2:])
|
|
178
|
+
if group == "a":
|
|
179
|
+
pk = self.kwargs["pk"]
|
|
180
|
+
self.is_opds_2_acquisition = bool(pk)
|
|
181
|
+
else:
|
|
182
|
+
self.is_opds_2_acquisition = group in self.acquisition_groups
|
|
122
183
|
self.is_opds_metadata = (
|
|
123
184
|
self.request.query_params.get("opdsMetadata", "").lower() not in FALSY
|
|
124
185
|
)
|
codex/views/opds/v2/links.py
CHANGED
codex/views/opds/v2/top_links.py
CHANGED
codex/views/reader/page.py
CHANGED
|
@@ -8,11 +8,11 @@ from rest_framework.negotiation import BaseContentNegotiation
|
|
|
8
8
|
|
|
9
9
|
from codex.logger.logging import get_logger
|
|
10
10
|
from codex.models import Comic
|
|
11
|
-
from codex.pdf import PDF
|
|
12
11
|
from codex.version import COMICBOX_CONFIG
|
|
13
12
|
from codex.views.bookmark import BookmarkBaseView
|
|
14
13
|
|
|
15
14
|
LOG = get_logger(__name__)
|
|
15
|
+
PDF_MIME_TYPE = "application/pdf"
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class IgnoreClientContentNegotiation(BaseContentNegotiation):
|
|
@@ -50,23 +50,22 @@ class ReaderPageView(BookmarkBaseView):
|
|
|
50
50
|
|
|
51
51
|
def _get_page_image(self):
|
|
52
52
|
"""Get the image data and content type."""
|
|
53
|
-
group_acl_filter = self.get_group_acl_filter(
|
|
53
|
+
group_acl_filter = self.get_group_acl_filter(Comic)
|
|
54
54
|
pk = self.kwargs.get("pk")
|
|
55
55
|
comic = Comic.objects.filter(group_acl_filter).only("path").get(pk=pk)
|
|
56
56
|
page = self.kwargs.get("page")
|
|
57
57
|
if comic.file_type == Comic.FileType.PDF.value:
|
|
58
|
-
|
|
59
|
-
content_type = PDF.MIME_TYPE
|
|
58
|
+
content_type = PDF_MIME_TYPE
|
|
60
59
|
else:
|
|
61
|
-
car = ComicArchive(comic.path, config=COMICBOX_CONFIG)
|
|
62
60
|
content_type = self.content_type
|
|
63
|
-
|
|
61
|
+
with ComicArchive(comic.path, config=COMICBOX_CONFIG) as car:
|
|
62
|
+
page_image = car.get_page_by_index(page)
|
|
64
63
|
return page_image, content_type
|
|
65
64
|
|
|
66
65
|
@extend_schema(
|
|
67
66
|
responses={
|
|
68
67
|
(200, content_type): OpenApiTypes.BINARY,
|
|
69
|
-
(200,
|
|
68
|
+
(200, PDF_MIME_TYPE): OpenApiTypes.BINARY,
|
|
70
69
|
}
|
|
71
70
|
)
|
|
72
71
|
def get(self, *args, **kwargs):
|
codex/views/reader/reader.py
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
47
|
-
top_group = session.get("top_group")
|
|
36
|
+
arc_group = self.params.get("arc_group")
|
|
48
37
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
rel = "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(**
|
|
73
|
-
.
|
|
74
|
-
.only(*
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
|
94
|
-
"""Get
|
|
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.
|
|
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(
|
|
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(
|
|
118
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 (>=
|
|
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 (
|
|
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 (>=
|
|
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_
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
|