codex 1.4.1__py3-none-any.whl → 1.4.3__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/applications/websocket.py +1 -1
- codex/librarian/importer/aggregate_metadata.py +38 -25
- codex/librarian/importer/clean_metadata.py +26 -13
- codex/librarian/importer/create_fks.py +1 -1
- codex/librarian/librariand.py +7 -4
- codex/librarian/watchdog/db_snapshot.py +1 -1
- codex/librarian/watchdog/event_batcherd.py +14 -11
- codex/librarian/watchdog/events.py +1 -1
- codex/logger/loggerd.py +14 -11
- codex/migrations/0001_init.py +3 -2
- codex/migrations/0002_auto_20200826_0622.py +4 -2
- codex/migrations/0003_auto_20200831_2033.py +4 -2
- codex/migrations/0004_failedimport.py +4 -2
- codex/migrations/0005_auto_20200918_0146.py +4 -2
- codex/migrations/0006_update_default_names_and_remove_duplicate_comics.py +12 -7
- codex/migrations/0007_auto_20211210_1710.py +3 -2
- codex/migrations/0008_alter_comic_created_at_alter_comic_format_and_more.py +4 -2
- codex/migrations/0009_alter_comic_parent_folder.py +4 -2
- codex/migrations/0010_haystack.py +4 -2
- codex/migrations/0011_library_groups_and_metadata_changes.py +3 -2
- codex/migrations/0012_rename_description_comic_comments.py +4 -2
- codex/migrations/0013_int_issue_count_longer_charfields.py +3 -3
- codex/migrations/0014_pdf_issue_suffix_remove_cover_image_sort_name.py +3 -2
- codex/migrations/0015_link_comics_to_top_level_folders.py +5 -2
- codex/migrations/0016_remove_comic_cover_path_librarianstatus.py +3 -3
- codex/migrations/0017_alter_timestamp_options_alter_adminflag_name_and_more.py +3 -3
- codex/migrations/0018_rename_userbookmark_bookmark.py +3 -2
- codex/migrations/0019_delete_queuejob.py +3 -2
- codex/migrations/0020_remove_search_tables.py +3 -2
- codex/migrations/0021_bookmark_fit_to_choices_read_in_reverse.py +3 -2
- codex/migrations/0022_bookmark_vertical_useractive_null_statuses.py +3 -2
- codex/migrations/0023_rename_credit_creator_and_more.py +3 -2
- codex/migrations/0024_comic_gtin_comic_story_arc_number.py +4 -2
- codex/migrations/0025_add_story_arc_number.py +4 -2
- codex/models.py +3 -4
- codex/search/backend.py +34 -31
- codex/serializers/auth.py +2 -1
- codex/serializers/choices.py +1 -0
- codex/static_root/assets/admin-b2b56cd6.f68d07d2bf93.js +41 -0
- codex/static_root/assets/admin-b2b56cd6.f68d07d2bf93.js.br +0 -0
- codex/static_root/assets/admin-b2b56cd6.f68d07d2bf93.js.gz +0 -0
- codex/static_root/assets/admin-b2b56cd6.js +41 -0
- codex/static_root/assets/admin-b2b56cd6.js.br +0 -0
- codex/static_root/assets/admin-b2b56cd6.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-522f1e6c.089d70878270.js → admin-drawer-panel-efc525ec.ddab36a24e08.js} +1 -1
- codex/static_root/assets/admin-drawer-panel-efc525ec.ddab36a24e08.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-efc525ec.ddab36a24e08.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-522f1e6c.js → admin-drawer-panel-efc525ec.js} +1 -1
- codex/static_root/assets/admin-drawer-panel-efc525ec.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-efc525ec.js.gz +0 -0
- codex/static_root/assets/admin-f2bb1dc8.css +1 -0
- codex/static_root/assets/admin-f2bb1dc8.css.br +0 -0
- codex/static_root/assets/admin-f2bb1dc8.css.gz +0 -0
- codex/static_root/assets/admin-f2bb1dc8.ecec18791c01.css +1 -0
- codex/static_root/assets/admin-f2bb1dc8.ecec18791c01.css.br +0 -0
- codex/static_root/assets/admin-f2bb1dc8.ecec18791c01.css.gz +0 -0
- codex/static_root/assets/{browser-7f7d7134.0fe3749b0f2f.css → browser-198df919.css} +1 -1
- codex/static_root/assets/browser-198df919.css.br +0 -0
- codex/static_root/assets/browser-198df919.css.gz +0 -0
- codex/static_root/assets/{browser-7f7d7134.css → browser-198df919.f06301531790.css} +1 -1
- codex/static_root/assets/browser-198df919.f06301531790.css.br +0 -0
- codex/static_root/assets/browser-198df919.f06301531790.css.gz +0 -0
- codex/static_root/assets/browser-ca158ba5.980d652eb174.js +1 -0
- codex/static_root/assets/browser-ca158ba5.980d652eb174.js.br +0 -0
- codex/static_root/assets/browser-ca158ba5.980d652eb174.js.gz +0 -0
- codex/static_root/assets/browser-ca158ba5.js +1 -0
- codex/static_root/assets/browser-ca158ba5.js.br +0 -0
- codex/static_root/assets/browser-ca158ba5.js.gz +0 -0
- codex/static_root/assets/{http-error-5e17b794.77ceeb2d4641.js → http-error-d31fd3bd.6ab9acf65973.js} +1 -1
- codex/static_root/assets/http-error-d31fd3bd.6ab9acf65973.js.br +0 -0
- codex/static_root/assets/http-error-d31fd3bd.6ab9acf65973.js.gz +0 -0
- codex/static_root/assets/{http-error-5e17b794.js → http-error-d31fd3bd.js} +1 -1
- codex/static_root/assets/http-error-d31fd3bd.js.br +0 -0
- codex/static_root/assets/http-error-d31fd3bd.js.gz +0 -0
- codex/static_root/assets/{main-0898f4bb.181e0145c642.css → main-c11eb0f1.776522baac3b.css} +1 -1
- codex/static_root/assets/main-c11eb0f1.776522baac3b.css.br +0 -0
- codex/static_root/assets/{main-0898f4bb.181e0145c642.css.gz → main-c11eb0f1.776522baac3b.css.gz} +0 -0
- codex/static_root/assets/{main-0898f4bb.css → main-c11eb0f1.css} +1 -1
- codex/static_root/assets/main-c11eb0f1.css.br +0 -0
- codex/static_root/assets/{main-0898f4bb.css.gz → main-c11eb0f1.css.gz} +0 -0
- codex/static_root/assets/main-c5736dea.a4790dbdb569.js +1 -0
- codex/static_root/assets/main-c5736dea.a4790dbdb569.js.br +0 -0
- codex/static_root/assets/main-c5736dea.a4790dbdb569.js.gz +0 -0
- codex/static_root/assets/main-c5736dea.js +1 -0
- codex/static_root/assets/main-c5736dea.js.br +0 -0
- codex/static_root/assets/main-c5736dea.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-83c74d48.b5cccc13c737.css +1 -0
- codex/static_root/assets/metadata-dialog-83c74d48.b5cccc13c737.css.br +0 -0
- codex/static_root/assets/metadata-dialog-83c74d48.b5cccc13c737.css.gz +0 -0
- codex/static_root/assets/metadata-dialog-83c74d48.css +1 -0
- codex/static_root/assets/metadata-dialog-83c74d48.css.br +0 -0
- codex/static_root/assets/metadata-dialog-83c74d48.css.gz +0 -0
- codex/static_root/assets/metadata-dialog-8c0a11ff.b281b7635db5.js +1 -0
- codex/static_root/assets/metadata-dialog-8c0a11ff.b281b7635db5.js.br +0 -0
- codex/static_root/assets/metadata-dialog-8c0a11ff.b281b7635db5.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-8c0a11ff.js +1 -0
- codex/static_root/assets/metadata-dialog-8c0a11ff.js.br +0 -0
- codex/static_root/assets/metadata-dialog-8c0a11ff.js.gz +0 -0
- codex/static_root/assets/{page-pdf-157ba97e.613d7c2beb77.js → page-pdf-ed976750.730244f14d16.js} +1 -1
- codex/static_root/assets/page-pdf-ed976750.730244f14d16.js.br +0 -0
- codex/static_root/assets/page-pdf-ed976750.730244f14d16.js.gz +0 -0
- codex/static_root/assets/{page-pdf-157ba97e.js → page-pdf-ed976750.js} +1 -1
- codex/static_root/assets/page-pdf-ed976750.js.br +0 -0
- codex/static_root/assets/page-pdf-ed976750.js.gz +0 -0
- codex/static_root/assets/reader-5540ffcb.8ea3c63a3154.css +1 -0
- codex/static_root/assets/reader-5540ffcb.8ea3c63a3154.css.br +0 -0
- codex/static_root/assets/reader-5540ffcb.8ea3c63a3154.css.gz +0 -0
- codex/static_root/assets/reader-5540ffcb.css +1 -0
- codex/static_root/assets/reader-5540ffcb.css.br +0 -0
- codex/static_root/assets/reader-5540ffcb.css.gz +0 -0
- codex/static_root/assets/reader-c562377d.7f78718f4c63.js +1 -0
- codex/static_root/assets/reader-c562377d.7f78718f4c63.js.br +0 -0
- codex/static_root/assets/reader-c562377d.7f78718f4c63.js.gz +0 -0
- codex/static_root/assets/reader-c562377d.js +1 -0
- codex/static_root/assets/reader-c562377d.js.br +0 -0
- codex/static_root/assets/reader-c562377d.js.gz +0 -0
- codex/static_root/{manifest.d2f93a519ada.json → manifest.55457ccaa01c.json} +32 -32
- codex/static_root/manifest.55457ccaa01c.json.br +0 -0
- codex/static_root/manifest.55457ccaa01c.json.gz +0 -0
- codex/static_root/manifest.json +32 -32
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/pwa/{offline.37a4206d79f0.html → offline.7bfaf9f94bf9.html} +1 -1
- codex/static_root/pwa/offline.7bfaf9f94bf9.html.br +0 -0
- codex/static_root/pwa/offline.7bfaf9f94bf9.html.gz +0 -0
- codex/static_root/pwa/offline.html +1 -1
- codex/static_root/pwa/offline.html.br +0 -0
- codex/static_root/pwa/offline.html.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/threads.py +1 -1
- codex/views/admin/api_key.py +3 -1
- codex/views/admin/flag.py +3 -1
- codex/views/admin/group.py +3 -1
- codex/views/admin/library.py +5 -4
- codex/views/admin/stats.py +10 -6
- codex/views/admin/tasks.py +35 -30
- codex/views/admin/user.py +4 -2
- codex/views/bookmark.py +6 -4
- codex/views/browser/base.py +30 -28
- codex/views/browser/browser.py +78 -80
- codex/views/browser/browser_annotations.py +15 -10
- codex/views/browser/browser_order_by.py +21 -16
- codex/views/browser/choices.py +37 -22
- codex/views/browser/filters/search.py +19 -16
- codex/views/browser/metadata.py +50 -41
- codex/views/cover.py +3 -1
- codex/views/download.py +4 -2
- codex/views/frontend.py +3 -2
- codex/views/mixins.py +13 -9
- codex/views/opds/authentication_v1.py +45 -41
- codex/views/opds/const.py +20 -13
- codex/views/opds/v1/entry/data.py +2 -1
- codex/views/opds/v1/facets.py +2 -1
- codex/views/opds/v1/feed.py +11 -4
- codex/views/opds/v1/links.py +8 -6
- codex/views/opds/v1/opensearch_v1.py +1 -1
- codex/views/opds/v2/feed.py +2 -1
- codex/views/opds/v2/publications.py +15 -12
- codex/views/reader/page.py +1 -1
- codex/views/session.py +50 -43
- codex/views/template.py +2 -2
- codex/websockets/listener.py +10 -7
- {codex-1.4.1.dist-info → codex-1.4.3.dist-info}/METADATA +24 -28
- {codex-1.4.1.dist-info → codex-1.4.3.dist-info}/RECORD +167 -167
- {codex-1.4.1.dist-info → codex-1.4.3.dist-info}/WHEEL +1 -1
- codex/static_root/assets/admin-12749881.ef0f50bac290.js +0 -41
- 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 +0 -41
- 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 +0 -1
- 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 +0 -1
- 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-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.br +0 -0
- codex/static_root/assets/admin-drawer-panel-522f1e6c.js.gz +0 -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.br +0 -0
- codex/static_root/assets/browser-7f7d7134.css.gz +0 -0
- codex/static_root/assets/browser-af622672.d51aca96d64d.js +0 -1
- 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 +0 -1
- 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.br +0 -0
- codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.gz +0 -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-0898f4bb.181e0145c642.css.br +0 -0
- codex/static_root/assets/main-0898f4bb.css.br +0 -0
- codex/static_root/assets/main-9e76a4c3.6844a407d14c.js +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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-cb306ffd.cc304996d7bb.css +0 -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-cb306ffd.css +0 -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-157ba97e.613d7c2beb77.js.br +0 -0
- codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.gz +0 -0
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- codex/static_root/assets/reader-7f004141.css.br +0 -0
- codex/static_root/assets/reader-7f004141.css.gz +0 -0
- codex/static_root/manifest.d2f93a519ada.json.br +0 -0
- codex/static_root/manifest.d2f93a519ada.json.gz +0 -0
- codex/static_root/pwa/offline.37a4206d79f0.html.br +0 -0
- codex/static_root/pwa/offline.37a4206d79f0.html.gz +0 -0
- {codex-1.4.1.dist-info → codex-1.4.3.dist-info}/LICENSE +0 -0
- {codex-1.4.1.dist-info → codex-1.4.3.dist-info}/entry_points.txt +0 -0
codex/views/bookmark.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"""Bookmark views."""
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
2
4
|
from drf_spectacular.utils import extend_schema
|
|
3
5
|
from rest_framework.generics import GenericAPIView
|
|
4
6
|
from rest_framework.response import Response
|
|
@@ -15,16 +17,16 @@ LOG = get_logger(__name__)
|
|
|
15
17
|
class BookmarkBaseView(GenericAPIView, GroupACLMixin):
|
|
16
18
|
"""Bookmark Updater."""
|
|
17
19
|
|
|
18
|
-
permission_classes = [IsAuthenticatedOrEnabledNonUsers]
|
|
19
|
-
_BOOKMARK_UPDATE_FIELDS =
|
|
20
|
+
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers]
|
|
21
|
+
_BOOKMARK_UPDATE_FIELDS = (
|
|
20
22
|
"page",
|
|
21
23
|
"finished",
|
|
22
24
|
"fit_to",
|
|
23
25
|
"two_pages",
|
|
24
26
|
"vertical",
|
|
25
27
|
"read_in_reverse",
|
|
26
|
-
|
|
27
|
-
_BOOKMARK_ONLY_FIELDS =
|
|
28
|
+
)
|
|
29
|
+
_BOOKMARK_ONLY_FIELDS = (*_BOOKMARK_UPDATE_FIELDS, "pk", "comic")
|
|
28
30
|
_COMIC_ONLY_FIELDS = ("pk", "max_page")
|
|
29
31
|
|
|
30
32
|
def get_bookmark_filter(self):
|
codex/views/browser/base.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Views for browsing comic library."""
|
|
2
2
|
import json
|
|
3
3
|
from copy import deepcopy
|
|
4
|
+
from types import MappingProxyType
|
|
4
5
|
from urllib.parse import unquote_plus
|
|
5
6
|
|
|
6
7
|
from django.contrib.auth.models import User
|
|
@@ -8,7 +9,15 @@ from djangorestframework_camel_case.settings import api_settings
|
|
|
8
9
|
from djangorestframework_camel_case.util import underscoreize
|
|
9
10
|
|
|
10
11
|
from codex.logger.logging import get_logger
|
|
11
|
-
from codex.models import
|
|
12
|
+
from codex.models import (
|
|
13
|
+
Comic,
|
|
14
|
+
Folder,
|
|
15
|
+
Imprint,
|
|
16
|
+
Publisher,
|
|
17
|
+
Series,
|
|
18
|
+
StoryArc,
|
|
19
|
+
Volume,
|
|
20
|
+
)
|
|
12
21
|
from codex.serializers.browser import BrowserSettingsSerializer
|
|
13
22
|
from codex.views.browser.filters.bookmark import BookmarkFilterMixin
|
|
14
23
|
from codex.views.browser.filters.field import ComicFieldFilter
|
|
@@ -25,16 +34,18 @@ class BrowserBaseView(
|
|
|
25
34
|
|
|
26
35
|
input_serializer_class = BrowserSettingsSerializer
|
|
27
36
|
|
|
28
|
-
GROUP_MODEL_MAP =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
GROUP_MODEL_MAP = MappingProxyType(
|
|
38
|
+
{
|
|
39
|
+
GroupFilterMixin.ROOT_GROUP: None,
|
|
40
|
+
"p": Publisher,
|
|
41
|
+
"i": Imprint,
|
|
42
|
+
"s": Series,
|
|
43
|
+
"v": Volume,
|
|
44
|
+
GroupFilterMixin.COMIC_GROUP: Comic,
|
|
45
|
+
GroupFilterMixin.FOLDER_GROUP: Folder,
|
|
46
|
+
GroupFilterMixin.STORY_ARC_GROUP: StoryArc,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
38
49
|
|
|
39
50
|
_GET_JSON_KEYS = frozenset(("filters", "show"))
|
|
40
51
|
|
|
@@ -51,26 +62,19 @@ class BrowserBaseView(
|
|
|
51
62
|
self._is_admin = user and isinstance(user, User) and user.is_staff
|
|
52
63
|
return self._is_admin
|
|
53
64
|
|
|
54
|
-
def get_query_filters_without_group(self, model):
|
|
65
|
+
def get_query_filters_without_group(self, model, search_scores: dict):
|
|
55
66
|
"""Return all the filters except the group filter."""
|
|
56
67
|
object_filter = self.get_group_acl_filter(model)
|
|
57
|
-
|
|
58
|
-
search_filter, search_scores = self.get_search_filter()
|
|
59
|
-
object_filter &= search_filter
|
|
68
|
+
object_filter &= self.get_search_filter(model, search_scores)
|
|
60
69
|
object_filter &= self.get_bookmark_filter(model)
|
|
61
70
|
object_filter &= self.get_comic_field_filter()
|
|
62
|
-
return object_filter
|
|
71
|
+
return object_filter
|
|
63
72
|
|
|
64
|
-
def get_query_filters(self, model, choices=False):
|
|
73
|
+
def get_query_filters(self, model, search_scores: dict, choices=False):
|
|
65
74
|
"""Return the main object filter and the one for aggregates."""
|
|
66
|
-
(
|
|
67
|
-
object_filter,
|
|
68
|
-
search_scores,
|
|
69
|
-
) = self.get_query_filters_without_group(model)
|
|
70
|
-
|
|
75
|
+
object_filter = self.get_query_filters_without_group(model, search_scores)
|
|
71
76
|
object_filter &= self.get_group_filter(choices)
|
|
72
|
-
|
|
73
|
-
return object_filter, search_scores
|
|
77
|
+
return object_filter
|
|
74
78
|
|
|
75
79
|
def _parse_query_params(self, query_params):
|
|
76
80
|
"""Parse GET query parameters: filter object & snake case."""
|
|
@@ -85,13 +89,11 @@ class BrowserBaseView(
|
|
|
85
89
|
parsed_val = val
|
|
86
90
|
|
|
87
91
|
result[key] = parsed_val
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return result
|
|
92
|
+
return underscoreize(result, **api_settings.JSON_UNDERSCOREIZE)
|
|
91
93
|
|
|
92
94
|
def parse_params(self):
|
|
93
95
|
"""Validate sbmitted settings and apply them over the session settings."""
|
|
94
|
-
self.params = deepcopy(self.SESSION_DEFAULTS)
|
|
96
|
+
self.params = deepcopy(dict(self.SESSION_DEFAULTS))
|
|
95
97
|
if self.request.method == "GET":
|
|
96
98
|
data = self._parse_query_params(self.request.GET)
|
|
97
99
|
elif hasattr(self.request, "data"):
|
codex/views/browser/browser.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Views for browsing comic library."""
|
|
2
2
|
from copy import deepcopy
|
|
3
|
-
from
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
from typing import ClassVar, Optional
|
|
4
5
|
|
|
5
6
|
from django.core.paginator import EmptyPage, Paginator
|
|
6
|
-
from django.db.models import F, Max,
|
|
7
|
+
from django.db.models import F, Max, Value
|
|
7
8
|
from django.db.models.fields import (
|
|
8
9
|
CharField,
|
|
9
10
|
)
|
|
@@ -14,6 +15,7 @@ from codex.exceptions import SeeOtherRedirectError
|
|
|
14
15
|
from codex.logger.logging import get_logger
|
|
15
16
|
from codex.models import (
|
|
16
17
|
AdminFlag,
|
|
18
|
+
BrowserGroupModel,
|
|
17
19
|
Comic,
|
|
18
20
|
Folder,
|
|
19
21
|
Imprint,
|
|
@@ -35,28 +37,34 @@ LOG = get_logger(__name__)
|
|
|
35
37
|
class BrowserView(BrowserAnnotationsView):
|
|
36
38
|
"""Browse comics with a variety of filters and sorts."""
|
|
37
39
|
|
|
38
|
-
permission_classes = [IsAuthenticatedOrEnabledNonUsers]
|
|
40
|
+
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers]
|
|
39
41
|
serializer_class = BrowserPageSerializer
|
|
40
42
|
|
|
41
43
|
MAX_OBJ_PER_PAGE = 100
|
|
42
|
-
_MODEL_GROUP_MAP =
|
|
44
|
+
_MODEL_GROUP_MAP = MappingProxyType(
|
|
45
|
+
{v: k for k, v in BrowserAnnotationsView.GROUP_MODEL_MAP.items()}
|
|
46
|
+
)
|
|
43
47
|
_NAV_GROUPS = "rpisv"
|
|
44
48
|
_ORPHANS = int(MAX_OBJ_PER_PAGE / 20)
|
|
45
49
|
|
|
46
|
-
_GROUP_INSTANCE_SELECT_RELATED =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
_GROUP_INSTANCE_SELECT_RELATED = MappingProxyType(
|
|
51
|
+
{
|
|
52
|
+
Comic: ("series", "volume"),
|
|
53
|
+
Volume: ("series",),
|
|
54
|
+
Series: (None,),
|
|
55
|
+
Imprint: ("publisher",),
|
|
56
|
+
Publisher: (None,),
|
|
57
|
+
Folder: ("parent_folder",),
|
|
58
|
+
StoryArc: (None,),
|
|
59
|
+
}
|
|
60
|
+
)
|
|
55
61
|
DEFAULT_ROUTE_NAME = "browser"
|
|
56
|
-
_DEFAULT_ROUTE =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
_DEFAULT_ROUTE = MappingProxyType(
|
|
63
|
+
{
|
|
64
|
+
"name": DEFAULT_ROUTE_NAME,
|
|
65
|
+
"params": deepcopy(DEFAULTS["route"]),
|
|
66
|
+
}
|
|
67
|
+
)
|
|
60
68
|
_OPDS_M2M_RELS = (
|
|
61
69
|
"characters",
|
|
62
70
|
"genres",
|
|
@@ -91,7 +99,7 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
91
99
|
group_names["publisher_name"] = F("publisher__name")
|
|
92
100
|
return queryset.annotate(**group_names)
|
|
93
101
|
|
|
94
|
-
def _add_annotations(self, queryset, model, search_scores):
|
|
102
|
+
def _add_annotations(self, queryset, model, search_scores: dict):
|
|
95
103
|
"""Annotations for display and sorting."""
|
|
96
104
|
queryset = self.annotate_common_aggregates(queryset, model, search_scores)
|
|
97
105
|
|
|
@@ -127,30 +135,26 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
127
135
|
model_group = self._get_model_group()
|
|
128
136
|
self.model = self.GROUP_MODEL_MAP[model_group]
|
|
129
137
|
|
|
130
|
-
def _get_group_queryset(self,
|
|
138
|
+
def _get_group_queryset(self, search_scores: dict):
|
|
131
139
|
"""Create group queryset."""
|
|
132
|
-
if self.model
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
if not self.model:
|
|
141
|
+
reason = "Model not set in browser."
|
|
142
|
+
raise ValueError(reason)
|
|
143
|
+
if self.model != Comic:
|
|
144
|
+
object_filter = self.get_query_filters(self.model, search_scores, False)
|
|
135
145
|
qs = self.model.objects.filter(object_filter)
|
|
136
146
|
qs = self._add_annotations(qs, self.model, search_scores)
|
|
137
147
|
qs = self.add_order_by(qs, self.model)
|
|
148
|
+
else:
|
|
149
|
+
qs = self.model.objects.none()
|
|
138
150
|
return qs
|
|
139
151
|
|
|
140
|
-
def _get_book_queryset(self,
|
|
152
|
+
def _get_book_queryset(self, search_scores: dict):
|
|
141
153
|
"""Create book queryset."""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
self.model, False
|
|
147
|
-
)
|
|
148
|
-
else:
|
|
149
|
-
comic_object_filter = object_filter
|
|
150
|
-
comic_search_scores = search_scores
|
|
151
|
-
|
|
152
|
-
qs = Comic.objects.filter(comic_object_filter)
|
|
153
|
-
qs = self._add_annotations(qs, Comic, comic_search_scores)
|
|
154
|
+
if self.model in (Comic, Folder):
|
|
155
|
+
object_filter = self.get_query_filters(Comic, search_scores, False)
|
|
156
|
+
qs = Comic.objects.filter(object_filter)
|
|
157
|
+
qs = self._add_annotations(qs, Comic, search_scores)
|
|
154
158
|
qs = self.add_order_by(qs, Comic)
|
|
155
159
|
if limit := self.params.get("limit"):
|
|
156
160
|
qs = qs[:limit]
|
|
@@ -182,20 +186,20 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
182
186
|
def _set_group_instance(self):
|
|
183
187
|
"""Create group_class instance."""
|
|
184
188
|
pk = self.kwargs.get("pk")
|
|
185
|
-
self.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
self.
|
|
189
|
+
if pk and self.group_class:
|
|
190
|
+
try:
|
|
191
|
+
select_related: tuple[str, ...] = self._GROUP_INSTANCE_SELECT_RELATED[
|
|
192
|
+
self.group_class
|
|
193
|
+
] # type: ignore
|
|
194
|
+
self.group_instance: Optional[
|
|
195
|
+
BrowserGroupModel
|
|
196
|
+
] = self.group_class.objects.select_related(*select_related).get(pk=pk)
|
|
197
|
+
except self.group_class.DoesNotExist:
|
|
198
|
+
group = self.kwargs.get("group")
|
|
199
|
+
reason = f"{group}={pk} does not exist!"
|
|
200
|
+
self._raise_redirect({"group": group, "pk": 0, "page": 1}, reason)
|
|
201
|
+
else:
|
|
202
|
+
self.group_instance: Optional[BrowserGroupModel] = None
|
|
199
203
|
|
|
200
204
|
def _get_browse_up_route(self):
|
|
201
205
|
"""Get the up route from the first valid ancestor."""
|
|
@@ -247,8 +251,7 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
247
251
|
# remove library path for not admins
|
|
248
252
|
parent_name = parent_name.removeprefix(prefix)
|
|
249
253
|
suffix = "/" + group_instance.name
|
|
250
|
-
|
|
251
|
-
return parent_name
|
|
254
|
+
return parent_name.removesuffix(suffix)
|
|
252
255
|
|
|
253
256
|
def _get_browser_page_title(self):
|
|
254
257
|
"""Get the proper title for this browse level."""
|
|
@@ -298,7 +301,8 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
298
301
|
except EmptyPage:
|
|
299
302
|
if page < 1 or page > paginator.num_pages:
|
|
300
303
|
self._page_out_out_bounds(page, paginator.num_pages)
|
|
301
|
-
|
|
304
|
+
model_name = self.model.__name__ if self.model else "NO_MODEL"
|
|
305
|
+
LOG.warning(f"No {model_name}s on page {page}")
|
|
302
306
|
model.objects.none()
|
|
303
307
|
|
|
304
308
|
return qs, num_pages, total_count
|
|
@@ -329,20 +333,11 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
329
333
|
self.set_rel_prefix(self.model)
|
|
330
334
|
self._set_group_instance() # Placed up here to invalidate earlier
|
|
331
335
|
# Create the main query with the filters
|
|
332
|
-
|
|
333
|
-
object_filter, search_scores = self.get_query_filters(self.model, False)
|
|
334
|
-
except Folder.DoesNotExist:
|
|
335
|
-
pk = self.kwargs.get("pk")
|
|
336
|
-
self._raise_redirect(
|
|
337
|
-
{"group": self.FOLDER_GROUP},
|
|
338
|
-
f"folder {pk} Does not exist! Redirect to root folder.",
|
|
339
|
-
)
|
|
340
|
-
object_filter = Q()
|
|
341
|
-
search_scores = {}
|
|
336
|
+
search_scores: dict = self.get_search_scores()
|
|
342
337
|
group = self.kwargs.get("group")
|
|
343
338
|
|
|
344
|
-
group_qs = self._get_group_queryset(
|
|
345
|
-
book_qs = self._get_book_queryset(
|
|
339
|
+
group_qs = self._get_group_queryset(search_scores)
|
|
340
|
+
book_qs = self._get_book_queryset(search_scores)
|
|
346
341
|
|
|
347
342
|
# Paginate
|
|
348
343
|
group_qs, book_qs, num_pages, total_count = self._paginate(group_qs, book_qs)
|
|
@@ -377,19 +372,21 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
377
372
|
)
|
|
378
373
|
|
|
379
374
|
# construct final data structure
|
|
380
|
-
return
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
375
|
+
return MappingProxyType(
|
|
376
|
+
{
|
|
377
|
+
"up_route": up_route,
|
|
378
|
+
"browser_title": browser_page_title,
|
|
379
|
+
"model_group": self.model_group,
|
|
380
|
+
"groups": group_qs,
|
|
381
|
+
"books": book_qs,
|
|
382
|
+
"issue_max": issue_max,
|
|
383
|
+
"num_pages": num_pages,
|
|
384
|
+
"total_count": total_count,
|
|
385
|
+
"admin_flags": {"folder_view": efv_flag.on},
|
|
386
|
+
"libraries_exist": libraries_exist,
|
|
387
|
+
"covers_timestamp": covers_timestamp,
|
|
388
|
+
}
|
|
389
|
+
)
|
|
393
390
|
|
|
394
391
|
def _get_valid_top_groups(self):
|
|
395
392
|
"""Get valid top groups for the current settings.
|
|
@@ -398,8 +395,9 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
398
395
|
"""
|
|
399
396
|
valid_top_groups = []
|
|
400
397
|
|
|
398
|
+
show: MappingProxyType = self.params["show"] # type:ignore
|
|
401
399
|
for nav_group in self._NAV_GROUPS:
|
|
402
|
-
if
|
|
400
|
+
if show.get(nav_group):
|
|
403
401
|
valid_top_groups.append(nav_group)
|
|
404
402
|
# Issues is always a valid top group
|
|
405
403
|
valid_top_groups += [self.COMIC_GROUP]
|
|
@@ -436,8 +434,8 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
436
434
|
|
|
437
435
|
def _raise_redirect(self, route_mask, reason, settings_mask=None):
|
|
438
436
|
"""Redirect the client to a valid group url."""
|
|
439
|
-
route = deepcopy(self._DEFAULT_ROUTE)
|
|
440
|
-
route["params"].update(route_mask)
|
|
437
|
+
route = deepcopy(dict(self._DEFAULT_ROUTE))
|
|
438
|
+
route["params"].update(route_mask) # type: ignore
|
|
441
439
|
settings = deepcopy(self.params)
|
|
442
440
|
if settings_mask:
|
|
443
441
|
settings.update(settings_mask)
|
|
@@ -9,6 +9,7 @@ from django.db.models import (
|
|
|
9
9
|
DateTimeField,
|
|
10
10
|
F,
|
|
11
11
|
FilteredRelation,
|
|
12
|
+
Max,
|
|
12
13
|
Min,
|
|
13
14
|
OuterRef,
|
|
14
15
|
Q,
|
|
@@ -49,15 +50,19 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
49
50
|
|
|
50
51
|
is_opds_1_acquisition = False
|
|
51
52
|
|
|
52
|
-
def _annotate_search_score(self, queryset, search_scores):
|
|
53
|
-
"""Annotate the search score for ordering by search score.
|
|
53
|
+
def _annotate_search_score(self, queryset, search_scores, model):
|
|
54
|
+
"""Annotate the search score for ordering by search score.
|
|
55
|
+
|
|
56
|
+
Choose the maximum matching score for the group.
|
|
57
|
+
"""
|
|
54
58
|
if self.order_key != "search_score":
|
|
55
59
|
return queryset
|
|
56
60
|
whens = []
|
|
61
|
+
prefix = "" if model == Comic else self.rel_prefix
|
|
57
62
|
for pk, score in search_scores.items():
|
|
58
|
-
when = {
|
|
63
|
+
when = {prefix + "pk": pk, "then": score}
|
|
59
64
|
whens.append(When(**when))
|
|
60
|
-
annotate = {
|
|
65
|
+
annotate = {"search_score": Max(Case(*whens, default=0.0))}
|
|
61
66
|
return queryset.annotate(**annotate)
|
|
62
67
|
|
|
63
68
|
def _annotate_cover_pk(self, queryset, model):
|
|
@@ -116,14 +121,13 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
116
121
|
|
|
117
122
|
if self.kwargs.get("group") == self.FOLDER_GROUP and model == Comic:
|
|
118
123
|
# File View Filename
|
|
119
|
-
|
|
124
|
+
return queryset.annotate(
|
|
120
125
|
sort_name=Right(
|
|
121
126
|
"path",
|
|
122
127
|
StrIndex(Reverse(F("path")), Value(sep)) - 1, # type: ignore
|
|
123
128
|
output_field=CharField(),
|
|
124
129
|
)
|
|
125
130
|
)
|
|
126
|
-
return queryset
|
|
127
131
|
|
|
128
132
|
##################################################
|
|
129
133
|
# Otherwise Remove articles from the browse name #
|
|
@@ -168,7 +172,9 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
168
172
|
pk = self.kwargs["pk"]
|
|
169
173
|
if group == self.STORY_ARC_GROUP and pk:
|
|
170
174
|
story_arc_pk = pk
|
|
171
|
-
elif story_arc_pks := self.params.get("filters", {}).get(
|
|
175
|
+
elif story_arc_pks := self.params.get("filters", {}).get( # type: ignore
|
|
176
|
+
"story_arcs", []
|
|
177
|
+
):
|
|
172
178
|
story_arc_pk = story_arc_pks[0]
|
|
173
179
|
else:
|
|
174
180
|
story_arc_pk = None
|
|
@@ -261,7 +267,7 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
261
267
|
|
|
262
268
|
def annotate_common_aggregates(self, qs, model, search_scores):
|
|
263
269
|
"""Annotate common aggregates between browser and metadata."""
|
|
264
|
-
qs = self._annotate_search_score(qs, search_scores)
|
|
270
|
+
qs = self._annotate_search_score(qs, search_scores, model)
|
|
265
271
|
qs = self._annotate_child_count(qs, model)
|
|
266
272
|
qs = self._annotate_page_count(qs, model)
|
|
267
273
|
bm_rel = self.get_bm_rel(model)
|
|
@@ -273,5 +279,4 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
273
279
|
# cover depends on the above annotations for order-by
|
|
274
280
|
qs = self._annotate_cover_pk(qs, model)
|
|
275
281
|
qs = self._annotate_bookmarks(qs, model, bm_rel, bm_filter)
|
|
276
|
-
|
|
277
|
-
return qs
|
|
282
|
+
return self._annotate_progress(qs)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Base view for ordering the query."""
|
|
2
2
|
from os import sep
|
|
3
|
+
from types import MappingProxyType
|
|
3
4
|
|
|
4
5
|
from django.db.models import Avg, F, Max, Min, Sum, Value
|
|
5
6
|
from django.db.models.functions import Reverse, Right, StrIndex
|
|
@@ -11,21 +12,25 @@ from codex.views.browser.base import BrowserBaseView
|
|
|
11
12
|
class BrowserOrderByView(BrowserBaseView):
|
|
12
13
|
"""Base class for views that need ordering."""
|
|
13
14
|
|
|
14
|
-
_ORDER_AGGREGATE_FUNCS =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
_ORDER_AGGREGATE_FUNCS = MappingProxyType(
|
|
16
|
+
{
|
|
17
|
+
"age_rating": Avg,
|
|
18
|
+
"community_rating": Avg,
|
|
19
|
+
"created_at": Min,
|
|
20
|
+
"critical_rating": Avg,
|
|
21
|
+
"date": Min,
|
|
22
|
+
"page_count": Sum,
|
|
23
|
+
"path": Min,
|
|
24
|
+
"size": Sum,
|
|
25
|
+
"updated_at": Min,
|
|
26
|
+
"search_score": Min,
|
|
27
|
+
"story_arc_number": Min,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
27
30
|
_SEP_VALUE = Value(sep)
|
|
28
|
-
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
31
|
+
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
32
|
+
("sort_name", "search_score", "bookmark_updated_at")
|
|
33
|
+
)
|
|
29
34
|
|
|
30
35
|
def set_order_key(self):
|
|
31
36
|
"""Get the default order key for the view."""
|
|
@@ -70,7 +75,7 @@ class BrowserOrderByView(BrowserBaseView):
|
|
|
70
75
|
func = self._get_path_query_func(self.order_key)
|
|
71
76
|
elif model == Comic or self.order_key in self._ANNOTATED_ORDER_FIELDS:
|
|
72
77
|
# agg_none uses group fields not comic fields.
|
|
73
|
-
func = F(self.order_key)
|
|
78
|
+
func = F(self.order_key) # type: ignore
|
|
74
79
|
else:
|
|
75
80
|
func = self.get_aggregate_func(model, self.order_key)
|
|
76
81
|
return func
|
|
@@ -82,7 +87,7 @@ class BrowserOrderByView(BrowserBaseView):
|
|
|
82
87
|
prefix += "-"
|
|
83
88
|
|
|
84
89
|
if self.order_key == "sort_name":
|
|
85
|
-
order_fields =
|
|
90
|
+
order_fields = model.ORDERING
|
|
86
91
|
elif self.order_key == "bookmark_updated_at":
|
|
87
92
|
order_fields = ("order_value", "updated_at", "created_at", "pk")
|
|
88
93
|
elif self.order_key == "story_arc_number" and model == Comic:
|
codex/views/browser/choices.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"""View for marking comics read and unread."""
|
|
2
|
+
from types import MappingProxyType
|
|
3
|
+
from typing import ClassVar, Union
|
|
4
|
+
|
|
2
5
|
import pycountry
|
|
3
6
|
from caseconverter import snakecase
|
|
4
7
|
from django.db.models import QuerySet
|
|
@@ -7,6 +10,7 @@ from rest_framework.response import Response
|
|
|
7
10
|
|
|
8
11
|
from codex.logger.logging import get_logger
|
|
9
12
|
from codex.models import (
|
|
13
|
+
BrowserGroupModel,
|
|
10
14
|
Comic,
|
|
11
15
|
CreatorPerson,
|
|
12
16
|
Folder,
|
|
@@ -30,21 +34,25 @@ LOG = get_logger(__name__)
|
|
|
30
34
|
class BrowserChoicesViewBase(BrowserBaseView):
|
|
31
35
|
"""Get choices for filter dialog."""
|
|
32
36
|
|
|
33
|
-
permission_classes = [IsAuthenticatedOrEnabledNonUsers]
|
|
37
|
+
permission_classes: ClassVar[list] = [IsAuthenticatedOrEnabledNonUsers]
|
|
34
38
|
|
|
35
39
|
_CREATORS_PERSON_REL = "creators__person"
|
|
36
40
|
_STORY_ARC_REL = "story_arc_numbers__story_arc"
|
|
37
|
-
_NULL_NAMED_ROW = {"pk": -1, "name": "_none_"}
|
|
38
|
-
_BACK_REL_MAP =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
_NULL_NAMED_ROW = MappingProxyType({"pk": -1, "name": "_none_"})
|
|
42
|
+
_BACK_REL_MAP = MappingProxyType(
|
|
43
|
+
{CreatorPerson: "creator__", StoryArc: "storyarcnumber__"}
|
|
44
|
+
)
|
|
45
|
+
_REL_MAP = MappingProxyType(
|
|
46
|
+
{
|
|
47
|
+
Publisher: "publisher",
|
|
48
|
+
Imprint: "imprint",
|
|
49
|
+
Series: "series",
|
|
50
|
+
Volume: "volume",
|
|
51
|
+
Comic: "pk",
|
|
52
|
+
Folder: "parent_folder",
|
|
53
|
+
StoryArc: "story_arc_numbers__story_arc",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
48
56
|
|
|
49
57
|
@staticmethod
|
|
50
58
|
def get_field_choices_query(comic_qs, field_name):
|
|
@@ -57,13 +65,17 @@ class BrowserChoicesViewBase(BrowserBaseView):
|
|
|
57
65
|
|
|
58
66
|
def get_m2m_field_query(self, model, comic_qs: QuerySet):
|
|
59
67
|
"""Get distinct m2m value objects for the relation."""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
if self.model is None:
|
|
69
|
+
LOG.error("No model to make filter choices!")
|
|
70
|
+
m2m_filter = {}
|
|
71
|
+
else:
|
|
72
|
+
model_rel: str = self._REL_MAP[self.model] # type: ignore
|
|
73
|
+
back_rel = self._BACK_REL_MAP.get(model, "")
|
|
74
|
+
back_rel += "comic__"
|
|
75
|
+
back_rel += model_rel
|
|
76
|
+
back_rel += "__in"
|
|
77
|
+
m2m_filter = {back_rel: comic_qs}
|
|
78
|
+
return model.objects.filter(**m2m_filter).values("pk", "name").distinct()
|
|
67
79
|
|
|
68
80
|
@staticmethod
|
|
69
81
|
def does_m2m_null_exist(comic_qs, rel):
|
|
@@ -91,15 +103,18 @@ class BrowserChoicesViewBase(BrowserBaseView):
|
|
|
91
103
|
|
|
92
104
|
def get_object(self):
|
|
93
105
|
"""Get the comic subquery use for the choices."""
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
search_scores = self.get_search_scores()
|
|
107
|
+
object_filter = self.get_query_filters(self.model, search_scores, True)
|
|
108
|
+
return self.model.objects.filter(object_filter) # type: ignore
|
|
96
109
|
|
|
97
110
|
def _set_model(self):
|
|
98
111
|
"""Set the model to query."""
|
|
99
112
|
group = self.kwargs["group"]
|
|
100
113
|
if group == self.ROOT_GROUP:
|
|
101
114
|
group = self.params.get("top_group", "p")
|
|
102
|
-
self.model = self.GROUP_MODEL_MAP[
|
|
115
|
+
self.model: Union[BrowserGroupModel, Comic, None] = self.GROUP_MODEL_MAP[
|
|
116
|
+
group
|
|
117
|
+
] # type: ignore
|
|
103
118
|
|
|
104
119
|
|
|
105
120
|
class BrowserChoicesAvailableView(BrowserChoicesViewBase):
|