From a5d73e864eba4c669dbd298329910f671b4a4fb0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 12 Jul 2023 01:11:15 -0400 Subject: [PATCH] hide search category --- catalog/api.py | 5 +- catalog/search/models.py | 8 +-- catalog/search/typesense.py | 9 +-- catalog/search/views.py | 23 ++++++- catalog/templates/search_results.html | 88 +++++++++++++++----------- common/templates/_header.html | 38 +++++++---- common/templatetags/duration.py | 12 ++++ doc/catalog.md | 2 +- users/account.py | 7 ++ users/data.py | 3 + users/models.py | 8 ++- users/templates/users/preferences.html | 13 ++++ 12 files changed, 149 insertions(+), 67 deletions(-) diff --git a/catalog/api.py b/catalog/api.py index 89a1dbc3..012060ea 100644 --- a/catalog/api.py +++ b/catalog/api.py @@ -45,7 +45,10 @@ def search_item( if not query: return 400, {"message": "Invalid query"} items, num_pages, count, _ = query_index( - query, page=page, category=category, prepare_external=False + query, + page=page, + categories=[category] if category else None, + prepare_external=False, ) return 200, {"data": items, "pages": num_pages, "count": count} diff --git a/catalog/search/models.py b/catalog/search/models.py index 210b347a..23025963 100644 --- a/catalog/search/models.py +++ b/catalog/search/models.py @@ -18,7 +18,7 @@ _logger = logging.getLogger(__name__) class DbIndexer: @classmethod - def search(cls, q, page=1, category=None, tag=None, sort=None): + def search(cls, q, page=1, categories=None, tag=None, sort=None): result = lambda: None result.items = Item.objects.filter(title__contains=q)[:10] result.num_pages = 1 @@ -47,14 +47,14 @@ else: Indexer = DbIndexer -def query_index(keywords, category=None, tag=None, page=1, prepare_external=True): +def query_index(keywords, categories=None, tag=None, page=1, prepare_external=True): if ( page < 1 or page > 99 or (not tag and isinstance(keywords, str) and len(keywords) < 2) ): return [], 0, 0, [] - result = Indexer.search(keywords, page=page, category=category, tag=tag) + result = Indexer.search(keywords, page=page, categories=categories, tag=tag) keys = set() items = [] duplicated_items = [] @@ -92,7 +92,7 @@ def query_index(keywords, category=None, tag=None, page=1, prepare_external=True if prepare_external: # store site url to avoid dups in external search - cache_key = f"search_{category}_{keywords}" + cache_key = f"search_{','.join(categories or [])}_{keywords}" urls = list(set(cache.get(cache_key, []) + urls)) cache.set(cache_key, urls, timeout=300) diff --git a/catalog/search/typesense.py b/catalog/search/typesense.py index 98459d46..b636bcd4 100644 --- a/catalog/search/typesense.py +++ b/catalog/search/typesense.py @@ -311,13 +311,10 @@ class Indexer: logger.warn(f"delete item error: \n{e}") @classmethod - def search(cls, q, page=1, category=None, tag=None, sort=None): + def search(cls, q, page=1, categories=None, tag=None, sort=None): f = [] - if category: - if category == "movietv": - f.append("category:= [movie,tv]") - else: - f.append("category:= " + category) + if categories: + f.append(f"category:= [{','.join(categories)}]") if tag: f.append(f"tags:= '{tag}'") filters = " && ".join(f) diff --git a/catalog/search/views.py b/catalog/search/views.py index b88cebc5..41224e75 100644 --- a/catalog/search/views.py +++ b/catalog/search/views.py @@ -5,7 +5,7 @@ from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.utils.translation import gettext_lazy as _ from django.http import HttpResponseRedirect -from catalog.common.models import SiteName +from catalog.common.models import ItemCategory, SiteName from catalog.common.sites import AbstractSite, SiteManager from ..models import * from django.conf import settings @@ -82,10 +82,27 @@ def fetch(request, url, is_refetch: bool = False, site: AbstractSite | None = No ) +def visible_categories(request): + vc = request.session.get("p_categories", None) + if vc is None: + vc = [ + x + for x in item_categories() + if x.value not in request.user.preference.hidden_categories + ] + request.session["p_categories"] = vc + return vc + + def search(request): category = request.GET.get("c", default="all").strip().lower() - if category == "all": + if category == "all" or not category: category = None + categories = visible_categories(request) + elif category == "movietv": + categories = [ItemCategory.Movie, ItemCategory.TV] + else: + categories = [ItemCategory(category)] keywords = request.GET.get("q", default="").strip() tag = request.GET.get("tag", default="").strip() p = request.GET.get("page", default="1") @@ -105,7 +122,7 @@ def search(request): if site: return fetch(request, keywords, False, site) - items, num_pages, _, dup_items = query_index(keywords, category, tag, p) + items, num_pages, _, dup_items = query_index(keywords, categories, tag, p) return render( request, "search_results.html", diff --git a/catalog/templates/search_results.html b/catalog/templates/search_results.html index 42621785..2648ed3f 100644 --- a/catalog/templates/search_results.html +++ b/catalog/templates/search_results.html @@ -4,6 +4,7 @@ {% load humanize %} {% load admin_url %} {% load mastodon %} +{% load duration %} {% load oauth_token %} {% load truncate %} {% load highlight %} @@ -26,46 +27,59 @@
“{{ request.GET.q }}” {% trans '的搜索结果' %}
+ {% visible_categories as cats %} {% if request.GET.c and request.GET.c != 'all' %} - 全部 + 全部 {% else %} 全部 {% endif %} - | - {% if request.GET.c != 'book' %} - 书籍 - {% else %} - 书籍 + {% if 'book' in cats %} + | + {% if request.GET.c != 'book' %} + 书籍 + {% else %} + 书籍 + {% endif %} {% endif %} - | - {% if request.GET.c != 'movietv' %} - 影视 - {% else %} - 影视 + {% if 'movie' in cats or 'tv' in cats %} + | + {% if request.GET.c != 'movietv' %} + 影视 + {% else %} + 影视 + {% endif %} {% endif %} - | - {% if request.GET.c != 'podcast' %} - 播客 - {% else %} - 播客 + {% if 'podcast' in cats %} + | + {% if request.GET.c != 'podcast' %} + 播客 + {% else %} + 播客 + {% endif %} {% endif %} - | - {% if request.GET.c != 'music' %} - 音乐 - {% else %} - 音乐 + {% if 'music' in cats %} + | + {% if request.GET.c != 'music' %} + 音乐 + {% else %} + 音乐 + {% endif %} {% endif %} - | - {% if request.GET.c != 'game' %} - 游戏 - {% else %} - 游戏 + {% if 'game' in cats %} + | + {% if request.GET.c != 'game' %} + 游戏 + {% else %} + 游戏 + {% endif %} {% endif %} - | - {% if request.GET.c != 'performance' %} - 演出 - {% else %} - 演出 + {% if 'performance' in cats %} + | + {% if request.GET.c != 'performance' %} + 演出 + {% else %} + 演出 + {% endif %} {% endif %}
@@ -116,24 +130,24 @@ diff --git a/common/templates/_header.html b/common/templates/_header.html index 0ddaccbc..35884c1e 100644 --- a/common/templates/_header.html +++ b/common/templates/_header.html @@ -1,4 +1,5 @@ {% load admin_url %} +{% load duration %} {% load static %} {% load i18n %}
@@ -18,19 +19,32 @@ class="search" value="{{ request.GET.q|default:'' }}" /> diff --git a/common/templatetags/duration.py b/common/templatetags/duration.py index f095427e..90dff12e 100644 --- a/common/templatetags/duration.py +++ b/common/templatetags/duration.py @@ -2,10 +2,22 @@ from django import template from django.template.defaultfilters import stringfilter from django.utils.text import Truncator from django.utils.safestring import mark_safe +from catalog.common.models import ItemCategory, item_categories +from catalog.search.views import visible_categories as _visible_categories register = template.Library() +@register.simple_tag(takes_context=True) +def visible_categories(context): + return _visible_categories(context["request"]) + + +@register.simple_tag +def all_categories(): + return item_categories() + + @register.filter(is_safe=True) @stringfilter def duration_format(value, unit): diff --git a/doc/catalog.md b/doc/catalog.md index ec8be421..e8996226 100644 --- a/doc/catalog.md +++ b/doc/catalog.md @@ -88,7 +88,7 @@ classDiagram Add a new site -------------- -> **Site official API** should be the prioritised way to get data when adding a new site. + - If official API is available for the site, it should be the preferred way to get data. - add a new value to `IdType` and `SiteName` in `catalog/common/models.py` - add a new file in `catalog/sites/`, a new class inherits `AbstractSite`, with: * `SITE_NAME` diff --git a/users/account.py b/users/account.py index abeef85d..4da010ee 100644 --- a/users/account.py +++ b/users/account.py @@ -457,9 +457,16 @@ def swap_login(request, token, site, refresh_token): return redirect(reverse("users:data")) +def clear_preference_cache(request): + for key in list(request.session.keys()): + if key.startswith("p_"): + del request.session[key] + + def auth_login(request, user): """Decorates django ``login()``. Attach token to session.""" auth.login(request, user, backend="mastodon.auth.OAuth2Backend") + clear_preference_cache(request) if ( user.mastodon_last_refresh < timezone.now() - timedelta(hours=1) or user.mastodon_account == {} diff --git a/users/data.py b/users/data.py index 024322f0..cd046aa7 100644 --- a/users/data.py +++ b/users/data.py @@ -29,6 +29,7 @@ def preferences(request): preference.default_no_share = bool(request.POST.get("default_no_share")) preference.no_anonymous_view = bool(request.POST.get("no_anonymous_view")) preference.classic_homepage = int(request.POST.get("classic_homepage")) + preference.hidden_categories = request.POST.getlist("hidden_categories") preference.mastodon_publish_public = bool( request.POST.get("mastodon_publish_public") ) @@ -45,8 +46,10 @@ def preferences(request): "mastodon_publish_public", "mastodon_append_tag", "show_last_edit", + "hidden_categories", ] ) + clear_preference_cache(request) return render(request, "users/preferences.html") diff --git a/users/models.py b/users/models.py index cde3c8f4..9d687f06 100644 --- a/users/models.py +++ b/users/models.py @@ -20,7 +20,7 @@ from django.templatetags.static import static import hashlib from loguru import logger -RESERVED_USERNAMES = [ +_RESERVED_USERNAMES = [ "connect", "oauth2_login", "__", @@ -40,7 +40,7 @@ class UsernameValidator(validators.RegexValidator): flags = re.ASCII def __call__(self, value): - if value and value.lower() in RESERVED_USERNAMES: + if value and value.lower() in _RESERVED_USERNAMES: raise ValidationError(self.message, code=self.code) return super().__call__(value) @@ -52,6 +52,7 @@ def report_image_path(instance, filename): class User(AbstractUser): + preference: "Preference" username_validator = UsernameValidator() username = models.CharField( _("username"), @@ -457,7 +458,7 @@ class User(AbstractUser): or target.mastodon_site in self.mastodon_domain_blocks ) if target.is_authenticated - else self.preference.no_anonymous_view # type: ignore + else self.preference.no_anonymous_view ) def is_blocked_by(self, target): @@ -556,6 +557,7 @@ class Preference(models.Model): mastodon_append_tag = models.CharField(max_length=2048, default="") show_last_edit = models.PositiveSmallIntegerField(default=0) no_anonymous_view = models.PositiveSmallIntegerField(default=0) + hidden_categories = models.JSONField(default=list) def __str__(self): return str(self.user) diff --git a/users/templates/users/preferences.html b/users/templates/users/preferences.html index 1d06ee6a..01619859 100644 --- a/users/templates/users/preferences.html +++ b/users/templates/users/preferences.html @@ -4,6 +4,7 @@ {% load mastodon %} {% load oauth_token %} {% load truncate %} +{% load duration %} {% load thumb %} @@ -108,6 +109,18 @@ placeholder="例如 #我的书影音" value="{{ request.user.preference.mastodon_append_tag }}"> +
+ {% trans '搜索时不显示以下类型:' %} + +