diff --git a/apps/news/filters.py b/apps/news/filters.py index 44583a35..a56463c5 100644 --- a/apps/news/filters.py +++ b/apps/news/filters.py @@ -1,6 +1,8 @@ """Filters from application News""" -from django_filters import rest_framework as filters +from django.core.validators import EMPTY_VALUES from django.utils.translation import gettext_lazy as _ +from django_filters import rest_framework as filters +from rest_framework.serializers import ValidationError from news import models @@ -29,6 +31,7 @@ class NewsListFilterSet(filters.FilterSet): (SORT_BY_START_CHOICE, "start"), ) sort_by = filters.ChoiceFilter(method='sort_by_field', choices=SORT_BY_CHOICES) + search = filters.CharFilter(method='search_news') class Meta: """Meta class""" @@ -41,8 +44,16 @@ class NewsListFilterSet(filters.FilterSet): 'tag_value__in', 'state', 'sort_by', + 'search', ) + def search_news(self, queryset, name, value): + if value not in EMPTY_VALUES: + if len(value) < 3: + raise ValidationError({'detail': _('Type at least 3 characters to search please.')}) + return queryset.trigram_search(value) + return queryset + def in_tags(self, queryset, name, value): tags = value.split('__') return queryset.filter(tags__value__in=tags) diff --git a/apps/news/models.py b/apps/news/models.py index fffed321..b22c84da 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -6,7 +6,9 @@ from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import HStoreField from django.db import models -from django.db.models import Case, When +from django.db.models import Case, When, Q, F +from django.db.models.functions import Cast +from django.contrib.postgres.search import TrigramSimilarity from django.urls.exceptions import NoReverseMatch from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -144,6 +146,46 @@ class NewsQuerySet(TranslationQuerysetMixin): def by_locale(self, locale): return self.filter(title__icontains=locale) + def trigram_search(self, search_value: str): + """Search with mistakes by name or last name.""" + return self.annotate( + description_str=Cast('description', models.TextField()), + title_str=Cast('title', models.TextField()), + subtitle_str=Cast('subtitle', models.TextField()), + search_contains_match=Case( + models.When(Q(description_str__icontains=search_value) | Q(title_str__icontains=search_value) | Q( + subtitle_str__icontains=search_value), then=100), + default=0, + output_field=models.FloatField(), + ), + description_similarity=models.Case( + models.When( + Q(description__isnull=False), + then=TrigramSimilarity('description_str', search_value.lower()), + ), + default=0, + output_field=models.FloatField() + ), + title_similarity=models.Case( + models.When( + Q(title__isnull=False), + then=TrigramSimilarity('title_str', search_value.lower()), + ), + default=0, + output_field=models.FloatField() + ), + subtitle_similarity=models.Case( + models.When( + Q(subtitle__isnull=False), + then=TrigramSimilarity('subtitle_str', search_value.lower()), + ), + default=0, + output_field=models.FloatField() + ), + relevance=(F('search_contains_match') + F('description_similarity') + F('title_similarity') + F( + 'subtitle_similarity')) + ).filter(relevance__gte=0.3).order_by('-relevance') + class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin):