codex 1.4.2__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-be9c2e22.33b594bc8ee7.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-be9c2e22.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-dc8c7804.7f60dfea2ea4.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-dc8c7804.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-e4b426e6.6fb67bd8d938.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-e4b426e6.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-804658a5.112a80d295b8.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-804658a5.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.3ee495a508d6.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 +26 -17
- codex/views/browser/browser.py +67 -53
- codex/views/browser/browser_annotations.py +5 -5
- codex/views/browser/browser_order_by.py +18 -15
- codex/views/browser/choices.py +37 -22
- codex/views/browser/filters/search.py +3 -3
- codex/views/browser/metadata.py +49 -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.2.dist-info → codex-1.4.3.dist-info}/METADATA +24 -28
- {codex-1.4.2.dist-info → codex-1.4.3.dist-info}/RECORD +167 -167
- {codex-1.4.2.dist-info → codex-1.4.3.dist-info}/WHEEL +1 -1
- codex/static_root/assets/admin-a34bbd42.9722989091b5.js +0 -41
- codex/static_root/assets/admin-a34bbd42.9722989091b5.js.br +0 -0
- codex/static_root/assets/admin-a34bbd42.9722989091b5.js.gz +0 -0
- codex/static_root/assets/admin-a34bbd42.js +0 -41
- codex/static_root/assets/admin-a34bbd42.js.br +0 -0
- codex/static_root/assets/admin-a34bbd42.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-be9c2e22.33b594bc8ee7.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-be9c2e22.33b594bc8ee7.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-be9c2e22.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-be9c2e22.js.gz +0 -0
- codex/static_root/assets/browser-349d0e1c.a4b979e545a7.js +0 -1
- codex/static_root/assets/browser-349d0e1c.a4b979e545a7.js.br +0 -0
- codex/static_root/assets/browser-349d0e1c.a4b979e545a7.js.gz +0 -0
- codex/static_root/assets/browser-349d0e1c.js +0 -1
- codex/static_root/assets/browser-349d0e1c.js.br +0 -0
- codex/static_root/assets/browser-349d0e1c.js.gz +0 -0
- codex/static_root/assets/browser-dc8c7804.7f60dfea2ea4.css.br +0 -0
- codex/static_root/assets/browser-dc8c7804.7f60dfea2ea4.css.gz +0 -0
- codex/static_root/assets/browser-dc8c7804.css.br +0 -0
- codex/static_root/assets/browser-dc8c7804.css.gz +0 -0
- codex/static_root/assets/http-error-e4b426e6.6fb67bd8d938.js.br +0 -0
- codex/static_root/assets/http-error-e4b426e6.6fb67bd8d938.js.gz +0 -0
- codex/static_root/assets/http-error-e4b426e6.js.br +0 -0
- codex/static_root/assets/http-error-e4b426e6.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-1cd792f1.4ca9a2ab454b.js +0 -1
- codex/static_root/assets/main-1cd792f1.4ca9a2ab454b.js.br +0 -0
- codex/static_root/assets/main-1cd792f1.4ca9a2ab454b.js.gz +0 -0
- codex/static_root/assets/main-1cd792f1.js +0 -1
- codex/static_root/assets/main-1cd792f1.js.br +0 -0
- codex/static_root/assets/main-1cd792f1.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-c4306743.4b1a3bd9e8be.js +0 -1
- codex/static_root/assets/metadata-dialog-c4306743.4b1a3bd9e8be.js.br +0 -0
- codex/static_root/assets/metadata-dialog-c4306743.4b1a3bd9e8be.js.gz +0 -0
- codex/static_root/assets/metadata-dialog-c4306743.js +0 -1
- codex/static_root/assets/metadata-dialog-c4306743.js.br +0 -0
- codex/static_root/assets/metadata-dialog-c4306743.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-804658a5.112a80d295b8.js.br +0 -0
- codex/static_root/assets/page-pdf-804658a5.112a80d295b8.js.gz +0 -0
- codex/static_root/assets/page-pdf-804658a5.js.br +0 -0
- codex/static_root/assets/page-pdf-804658a5.js.gz +0 -0
- codex/static_root/assets/reader-2df231f9.7d07a31e0fbf.css +0 -1
- codex/static_root/assets/reader-2df231f9.7d07a31e0fbf.css.br +0 -0
- codex/static_root/assets/reader-2df231f9.7d07a31e0fbf.css.gz +0 -0
- codex/static_root/assets/reader-2df231f9.css +0 -1
- codex/static_root/assets/reader-2df231f9.css.br +0 -0
- codex/static_root/assets/reader-2df231f9.css.gz +0 -0
- codex/static_root/assets/reader-75d90a0f.40ac454e2359.js +0 -1
- codex/static_root/assets/reader-75d90a0f.40ac454e2359.js.br +0 -0
- codex/static_root/assets/reader-75d90a0f.40ac454e2359.js.gz +0 -0
- codex/static_root/assets/reader-75d90a0f.js +0 -1
- codex/static_root/assets/reader-75d90a0f.js.br +0 -0
- codex/static_root/assets/reader-75d90a0f.js.gz +0 -0
- codex/static_root/manifest.3ee495a508d6.json.br +0 -0
- codex/static_root/manifest.3ee495a508d6.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.2.dist-info → codex-1.4.3.dist-info}/LICENSE +0 -0
- {codex-1.4.2.dist-info → codex-1.4.3.dist-info}/entry_points.txt +0 -0
codex/views/admin/user.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"""Admin User ViewSet."""
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
2
4
|
from django.contrib.auth.models import User
|
|
3
5
|
from django.contrib.auth.password_validation import validate_password
|
|
4
6
|
from django.core.cache import cache
|
|
@@ -21,7 +23,7 @@ LOG = get_logger(__name__)
|
|
|
21
23
|
class AdminUserViewSet(ModelViewSet):
|
|
22
24
|
"""User ViewSet."""
|
|
23
25
|
|
|
24
|
-
permission_classes = [IsAdminUser]
|
|
26
|
+
permission_classes: ClassVar[list] = [IsAdminUser]
|
|
25
27
|
queryset = User.objects.prefetch_related("groups").defer(
|
|
26
28
|
"first_name", "last_name", "email"
|
|
27
29
|
)
|
|
@@ -60,7 +62,7 @@ class AdminUserViewSet(ModelViewSet):
|
|
|
60
62
|
class AdminUserChangePasswordView(GenericAPIView):
|
|
61
63
|
"""Special View to hash user password."""
|
|
62
64
|
|
|
63
|
-
permission_classes = [IsAdminUser]
|
|
65
|
+
permission_classes: ClassVar[list] = [IsAdminUser]
|
|
64
66
|
serializer_class = UserChangePasswordSerializer
|
|
65
67
|
|
|
66
68
|
def put(self, request, *args, **kwargs):
|
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,7 +62,7 @@ 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, search_scores):
|
|
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
68
|
object_filter &= self.get_search_filter(model, search_scores)
|
|
@@ -59,7 +70,7 @@ class BrowserBaseView(
|
|
|
59
70
|
object_filter &= self.get_comic_field_filter()
|
|
60
71
|
return object_filter
|
|
61
72
|
|
|
62
|
-
def get_query_filters(self, model, search_scores, choices=False):
|
|
73
|
+
def get_query_filters(self, model, search_scores: dict, choices=False):
|
|
63
74
|
"""Return the main object filter and the one for aggregates."""
|
|
64
75
|
object_filter = self.get_query_filters_without_group(model, search_scores)
|
|
65
76
|
object_filter &= self.get_group_filter(choices)
|
|
@@ -78,13 +89,11 @@ class BrowserBaseView(
|
|
|
78
89
|
parsed_val = val
|
|
79
90
|
|
|
80
91
|
result[key] = parsed_val
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return result
|
|
92
|
+
return underscoreize(result, **api_settings.JSON_UNDERSCOREIZE)
|
|
84
93
|
|
|
85
94
|
def parse_params(self):
|
|
86
95
|
"""Validate sbmitted settings and apply them over the session settings."""
|
|
87
|
-
self.params = deepcopy(self.SESSION_DEFAULTS)
|
|
96
|
+
self.params = deepcopy(dict(self.SESSION_DEFAULTS))
|
|
88
97
|
if self.request.method == "GET":
|
|
89
98
|
data = self._parse_query_params(self.request.GET)
|
|
90
99
|
elif hasattr(self.request, "data"):
|
codex/views/browser/browser.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
7
|
from django.db.models import F, Max, Value
|
|
@@ -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,8 +135,11 @@ 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, search_scores):
|
|
138
|
+
def _get_group_queryset(self, search_scores: dict):
|
|
131
139
|
"""Create group queryset."""
|
|
140
|
+
if not self.model:
|
|
141
|
+
reason = "Model not set in browser."
|
|
142
|
+
raise ValueError(reason)
|
|
132
143
|
if self.model != Comic:
|
|
133
144
|
object_filter = self.get_query_filters(self.model, search_scores, False)
|
|
134
145
|
qs = self.model.objects.filter(object_filter)
|
|
@@ -138,7 +149,7 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
138
149
|
qs = self.model.objects.none()
|
|
139
150
|
return qs
|
|
140
151
|
|
|
141
|
-
def _get_book_queryset(self, search_scores):
|
|
152
|
+
def _get_book_queryset(self, search_scores: dict):
|
|
142
153
|
"""Create book queryset."""
|
|
143
154
|
if self.model in (Comic, Folder):
|
|
144
155
|
object_filter = self.get_query_filters(Comic, search_scores, False)
|
|
@@ -175,20 +186,20 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
175
186
|
def _set_group_instance(self):
|
|
176
187
|
"""Create group_class instance."""
|
|
177
188
|
pk = self.kwargs.get("pk")
|
|
178
|
-
self.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
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
|
|
192
203
|
|
|
193
204
|
def _get_browse_up_route(self):
|
|
194
205
|
"""Get the up route from the first valid ancestor."""
|
|
@@ -240,8 +251,7 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
240
251
|
# remove library path for not admins
|
|
241
252
|
parent_name = parent_name.removeprefix(prefix)
|
|
242
253
|
suffix = "/" + group_instance.name
|
|
243
|
-
|
|
244
|
-
return parent_name
|
|
254
|
+
return parent_name.removesuffix(suffix)
|
|
245
255
|
|
|
246
256
|
def _get_browser_page_title(self):
|
|
247
257
|
"""Get the proper title for this browse level."""
|
|
@@ -291,7 +301,8 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
291
301
|
except EmptyPage:
|
|
292
302
|
if page < 1 or page > paginator.num_pages:
|
|
293
303
|
self._page_out_out_bounds(page, paginator.num_pages)
|
|
294
|
-
|
|
304
|
+
model_name = self.model.__name__ if self.model else "NO_MODEL"
|
|
305
|
+
LOG.warning(f"No {model_name}s on page {page}")
|
|
295
306
|
model.objects.none()
|
|
296
307
|
|
|
297
308
|
return qs, num_pages, total_count
|
|
@@ -322,7 +333,7 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
322
333
|
self.set_rel_prefix(self.model)
|
|
323
334
|
self._set_group_instance() # Placed up here to invalidate earlier
|
|
324
335
|
# Create the main query with the filters
|
|
325
|
-
search_scores = self.get_search_scores()
|
|
336
|
+
search_scores: dict = self.get_search_scores()
|
|
326
337
|
group = self.kwargs.get("group")
|
|
327
338
|
|
|
328
339
|
group_qs = self._get_group_queryset(search_scores)
|
|
@@ -361,19 +372,21 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
361
372
|
)
|
|
362
373
|
|
|
363
374
|
# construct final data structure
|
|
364
|
-
return
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
+
)
|
|
377
390
|
|
|
378
391
|
def _get_valid_top_groups(self):
|
|
379
392
|
"""Get valid top groups for the current settings.
|
|
@@ -382,8 +395,9 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
382
395
|
"""
|
|
383
396
|
valid_top_groups = []
|
|
384
397
|
|
|
398
|
+
show: MappingProxyType = self.params["show"] # type:ignore
|
|
385
399
|
for nav_group in self._NAV_GROUPS:
|
|
386
|
-
if
|
|
400
|
+
if show.get(nav_group):
|
|
387
401
|
valid_top_groups.append(nav_group)
|
|
388
402
|
# Issues is always a valid top group
|
|
389
403
|
valid_top_groups += [self.COMIC_GROUP]
|
|
@@ -420,8 +434,8 @@ class BrowserView(BrowserAnnotationsView):
|
|
|
420
434
|
|
|
421
435
|
def _raise_redirect(self, route_mask, reason, settings_mask=None):
|
|
422
436
|
"""Redirect the client to a valid group url."""
|
|
423
|
-
route = deepcopy(self._DEFAULT_ROUTE)
|
|
424
|
-
route["params"].update(route_mask)
|
|
437
|
+
route = deepcopy(dict(self._DEFAULT_ROUTE))
|
|
438
|
+
route["params"].update(route_mask) # type: ignore
|
|
425
439
|
settings = deepcopy(self.params)
|
|
426
440
|
if settings_mask:
|
|
427
441
|
settings.update(settings_mask)
|
|
@@ -121,14 +121,13 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
121
121
|
|
|
122
122
|
if self.kwargs.get("group") == self.FOLDER_GROUP and model == Comic:
|
|
123
123
|
# File View Filename
|
|
124
|
-
|
|
124
|
+
return queryset.annotate(
|
|
125
125
|
sort_name=Right(
|
|
126
126
|
"path",
|
|
127
127
|
StrIndex(Reverse(F("path")), Value(sep)) - 1, # type: ignore
|
|
128
128
|
output_field=CharField(),
|
|
129
129
|
)
|
|
130
130
|
)
|
|
131
|
-
return queryset
|
|
132
131
|
|
|
133
132
|
##################################################
|
|
134
133
|
# Otherwise Remove articles from the browse name #
|
|
@@ -173,7 +172,9 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
173
172
|
pk = self.kwargs["pk"]
|
|
174
173
|
if group == self.STORY_ARC_GROUP and pk:
|
|
175
174
|
story_arc_pk = pk
|
|
176
|
-
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
|
+
):
|
|
177
178
|
story_arc_pk = story_arc_pks[0]
|
|
178
179
|
else:
|
|
179
180
|
story_arc_pk = None
|
|
@@ -278,5 +279,4 @@ class BrowserAnnotationsView(BrowserOrderByView):
|
|
|
278
279
|
# cover depends on the above annotations for order-by
|
|
279
280
|
qs = self._annotate_cover_pk(qs, model)
|
|
280
281
|
qs = self._annotate_bookmarks(qs, model, bm_rel, bm_filter)
|
|
281
|
-
|
|
282
|
-
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,19 +12,21 @@ 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
31
|
_ANNOTATED_ORDER_FIELDS = frozenset(
|
|
29
32
|
("sort_name", "search_score", "bookmark_updated_at")
|
|
@@ -72,7 +75,7 @@ class BrowserOrderByView(BrowserBaseView):
|
|
|
72
75
|
func = self._get_path_query_func(self.order_key)
|
|
73
76
|
elif model == Comic or self.order_key in self._ANNOTATED_ORDER_FIELDS:
|
|
74
77
|
# agg_none uses group fields not comic fields.
|
|
75
|
-
func = F(self.order_key)
|
|
78
|
+
func = F(self.order_key) # type: ignore
|
|
76
79
|
else:
|
|
77
80
|
func = self.get_aggregate_func(model, self.order_key)
|
|
78
81
|
return func
|
|
@@ -84,7 +87,7 @@ class BrowserOrderByView(BrowserBaseView):
|
|
|
84
87
|
prefix += "-"
|
|
85
88
|
|
|
86
89
|
if self.order_key == "sort_name":
|
|
87
|
-
order_fields =
|
|
90
|
+
order_fields = model.ORDERING
|
|
88
91
|
elif self.order_key == "bookmark_updated_at":
|
|
89
92
|
order_fields = ("order_value", "updated_at", "created_at", "pk")
|
|
90
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):
|
|
@@ -11,7 +11,7 @@ LOG = get_logger(__name__)
|
|
|
11
11
|
class SearchFilterMixin:
|
|
12
12
|
"""Search Filters Methods."""
|
|
13
13
|
|
|
14
|
-
def get_search_scores(self):
|
|
14
|
+
def get_search_scores(self) -> dict:
|
|
15
15
|
"""Perform the search and return the scores as a dict."""
|
|
16
16
|
search_scores = {}
|
|
17
17
|
text = self.params.get("q", "") # type: ignore
|
|
@@ -34,14 +34,14 @@ class SearchFilterMixin:
|
|
|
34
34
|
LOG.exception(exc)
|
|
35
35
|
return search_scores
|
|
36
36
|
|
|
37
|
-
def _get_search_query_filter(self, model, search_scores):
|
|
37
|
+
def _get_search_query_filter(self, model, search_scores: dict):
|
|
38
38
|
"""Get the search filter and scores."""
|
|
39
39
|
prefix = "" if model == Comic else self.rel_prefix # type: ignore
|
|
40
40
|
rel = prefix + "pk__in"
|
|
41
41
|
query_dict = {rel: search_scores.keys()}
|
|
42
42
|
return Q(**query_dict)
|
|
43
43
|
|
|
44
|
-
def get_search_filter(self, model, search_scores):
|
|
44
|
+
def get_search_filter(self, model, search_scores: dict):
|
|
45
45
|
"""Preparse search, search and return the filter and scores."""
|
|
46
46
|
search_filter = Q()
|
|
47
47
|
try:
|