From b902f558010d72478ed02141eefe4ae8b96a9063 Mon Sep 17 00:00:00 2001 From: Daniel Vainio Date: Wed, 5 Mar 2025 12:51:06 +0200 Subject: [PATCH] Add search_keywords to Community model Add a ArrayField to the Community model to enable slitghtly more optimized search. The field stores an array of strings and matches the search query to these keywords, alongside filtering by community name. This allows users to find communities with short abbreviations or alternative stylings of community names. Add the search_keywords field to CommunityListAPIView search_fields. Implement tests with various test cases for CommunityListAPIView endpoint. Refs. TS-2350 --- .../cyberstorm/tests/test_community_list.py | 36 +++++++++++++++++++ .../api/cyberstorm/views/community_list.py | 2 +- .../migrations/0034_add_search_keywords.py | 25 +++++++++++++ .../community/models/community.py | 8 +++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 django/thunderstore/community/migrations/0034_add_search_keywords.py diff --git a/django/thunderstore/api/cyberstorm/tests/test_community_list.py b/django/thunderstore/api/cyberstorm/tests/test_community_list.py index 0b9d9055b..cfa3cf3eb 100644 --- a/django/thunderstore/api/cyberstorm/tests/test_community_list.py +++ b/django/thunderstore/api/cyberstorm/tests/test_community_list.py @@ -164,3 +164,39 @@ def __query_api(client: APIClient, query: str = "", response_status_code=200) -> response = client.get(f"{url}?{query}") assert response.status_code == response_status_code return response.json() + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "query, search_keywords, name, should_match", + [ + ("repo", ["repo"], "R.E.P.O", True), + ("repo", None, "R.E.P.O", False), + ("ror2", ["ror2"], "Risk of Rain 2", True), + ("ror2", None, "Risk of Rain 2", False), + ("ror", ["ror"], "Risk of Rain 2", True), + ("ror", None, "Risk of Rain 2", False), + ("lethal", ["lethal", "lc", "lethalcompany"], "Lethal Company", True), + ("lc", ["lethal", "lc", "lethalcompany"], "Lethal Company", True), + ("lethalcompany", ["lethal", "lc", "lethalcompany"], "Lethal Company", True), + ("hello", ["lethal", "lc", "lethalcompany"], "Lethal Company", False), + ("LETHAL", ["lethal", "lc"], "Lethal Company", True), + ("Lc", ["lethal", "LC"], "Lethal Company", True), + ], +) +def test_api_cyberstorm_community_search_with_keywords( + api_client: APIClient, + query: str, + search_keywords: List[str], + name: str, + should_match: bool, +) -> None: + CommunityFactory(name=name, search_keywords=search_keywords) + data = __query_api(api_client, query=f"search={query}") + + if should_match: + assert data["count"] == 1 + assert data["results"][0]["name"] == name + else: + assert data["count"] == 0 + assert data["results"] == [] diff --git a/django/thunderstore/api/cyberstorm/views/community_list.py b/django/thunderstore/api/cyberstorm/views/community_list.py index 8fcaee457..5af66fe7a 100644 --- a/django/thunderstore/api/cyberstorm/views/community_list.py +++ b/django/thunderstore/api/cyberstorm/views/community_list.py @@ -18,7 +18,7 @@ class CommunityListAPIView(CyberstormAutoSchemaMixin, ListAPIView): pagination_class = CommunityPaginator queryset = Community.objects.listed() filter_backends = [SearchFilter, StrictOrderingFilter] - search_fields = ["name"] + search_fields = ["name", "search_keywords"] ordering_fields = [ "aggregated_fields__download_count", "aggregated_fields__package_count", diff --git a/django/thunderstore/community/migrations/0034_add_search_keywords.py b/django/thunderstore/community/migrations/0034_add_search_keywords.py new file mode 100644 index 000000000..c5ae9839d --- /dev/null +++ b/django/thunderstore/community/migrations/0034_add_search_keywords.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.7 on 2025-03-05 08:35 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("community", "0033_add_mod_manager_support_field"), + ] + + operations = [ + migrations.AddField( + model_name="community", + name="search_keywords", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=512), + blank=True, + default=list, + null=True, + size=None, + ), + ), + ] diff --git a/django/thunderstore/community/models/community.py b/django/thunderstore/community/models/community.py index 558004b7c..df613c30a 100644 --- a/django/thunderstore/community/models/community.py +++ b/django/thunderstore/community/models/community.py @@ -2,6 +2,7 @@ from functools import lru_cache from typing import TYPE_CHECKING, Optional +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.db import models, transaction from django.db.models import Manager, QuerySet @@ -117,6 +118,13 @@ class Community(TimestampMixin, models.Model): # Will hide/show "Install with Mod Manager" button on package pages has_mod_manager_support = models.BooleanField(default=True) + search_keywords = ArrayField( + models.CharField(max_length=512), + blank=True, + null=True, + default=list, + ) + @property def aggregated(self) -> "AggregatedFields": return (