Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/donations/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
has_archive_generation_deadline_passed,
has_recent_archive_job,
)
from .common.search import NgoCauseMixedSearchMixin
from .common.search import CauseSearchMixin

logger = logging.getLogger(__name__)

Expand All @@ -49,7 +49,7 @@ def post(self, request, *args, **kwargs):
return redirect_success


class SearchCausesApi(TemplateView, NgoCauseMixedSearchMixin):
class SearchCausesApi(TemplateView, CauseSearchMixin):
queryset = Cause.public_active

def get(self, request, *args, **kwargs):
Expand Down
41 changes: 40 additions & 1 deletion backend/donations/views/common/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from donations.models.donors import Donor
from donations.models.ngos import Cause, Ngo
from utils.text.registration_number import probable_registration_number


class ConfigureSearch:
Expand Down Expand Up @@ -99,7 +100,7 @@ def get_search_results(cls, queryset: QuerySet, query: str, language_code: str)
return ngos


class CauseSearchMixin(CommonSearchMixin):
class DeprecatedCauseSearchMixin(CommonSearchMixin):
@classmethod
def get_search_results(cls, queryset: QuerySet, query: str, language_code: str) -> QuerySet[Cause]:
search_fields = ["name"]
Expand All @@ -126,6 +127,44 @@ def get_search_results(cls, queryset: QuerySet, query: str, language_code: str)
return causes


class CauseSearchMixin(CommonSearchMixin):
@classmethod
def get_search_results(cls, queryset: QuerySet, query: str, language_code: str) -> QuerySet[Cause]:
if settings.ENABLE_CAUSE_SEARCH_EXACT_MATCH:
query_filter = Q(name__icontains=query)
# If the query looks like a registration number then also try to find the main causes owned by
# organisations which have that registration number
registration_number = probable_registration_number(query)
if registration_number:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search is a bit annoying, but we should take into account one path if we get a registration number (CIF) and a different one if it isn't that.
It seems a bit more straightforward if it's a registration number and we may need to just run a filter with Q(ngo__registration_number=registration_number) & Q(is_main=True) and not much more (we probably don't need search vectors or other things.

In this if statement I'd branch out things and directly return Cause.objects.filter(Q(ngo__registration_number=registration_number) & Q(is_main=True)) or something like that
Simplifies the flow and leaves a clearer trail to follow once you only have the name.

query_filter = query_filter | (Q(ngo__registration_number=registration_number) & Q(is_main=True))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we remove the initial query_filter?
If we have a registration number, it feels redundant to search in the name for it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know if we have a real registration number or just some plain text.


exact_causes: QuerySet[Cause] = queryset.filter(query_filter).order_by("id").distinct("id")
if exact_causes.count():
return exact_causes

search_vector: SearchVector = ConfigureSearch.vector(("name",), language_code)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the vector be configured by what we're searching?
I think it should be changed to the registration number if we're going on that path.

search_query: SearchQuery = ConfigureSearch.query(query, language_code)

if settings.ENABLE_CAUSE_SEARCH_WORD_SIMILARITY:
trigram_similarity = TrigramWordSimilarity(query, "name")
similarity_threshold = 0.4
else:
trigram_similarity = TrigramSimilarity("name", query)
similarity_threshold = 0.3

fuzzy_causes: QuerySet[Cause] = (
queryset.annotate(
rank=SearchRank(search_vector, search_query),
similarity=trigram_similarity,
)
.filter(Q(rank__gte=0.3) | Q(similarity__gt=similarity_threshold))
.order_by("id")
.distinct("id")
)

return fuzzy_causes


class NgoCauseMixedSearchMixin(CommonSearchMixin):
@classmethod
def get_search_results(cls, queryset: QuerySet, query: str, language_code: str) -> QuerySet[Cause]:
Expand Down
4 changes: 2 additions & 2 deletions backend/donations/views/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ..models.donors import Donor
from ..models.ngos import FRONTPAGE_NGOS_KEY, FRONTPAGE_STATS_KEY, Cause
from .base import BaseVisibleTemplateView
from .common.search import CauseSearchMixin, NgoCauseMixedSearchMixin
from .common.search import CauseSearchMixin

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -139,7 +139,7 @@ def get_context_data(self, **kwargs):
return context


class NgoListHandler(NgoCauseMixedSearchMixin):
class NgoListHandler(CauseSearchMixin):
template_name = "public/all-ngos.html"
context_object_name = "causes"
queryset = Cause.public_active
Expand Down
1 change: 1 addition & 0 deletions backend/redirectioneaza/settings/feature_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Search tweaks
ENABLE_NGO_SEARCH_WORD_SIMILARITY = env.bool("ENABLE_NGO_SEARCH_WORD_SIMILARITY", False)
ENABLE_CAUSE_SEARCH_WORD_SIMILARITY = env.bool("ENABLE_CAUSE_SEARCH_WORD_SIMILARITY", False)
ENABLE_CAUSE_SEARCH_EXACT_MATCH = env.bool("ENABLE_CAUSE_SEARCH_EXACT_MATCH", True)

# Feature flags
ENABLE_FLAG_CONTACT = env.bool("ENABLE_FLAG_CONTACT", False)
Expand Down
15 changes: 15 additions & 0 deletions backend/utils/text/registration_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,18 @@ def ngo_id_number_validator(value):
return

raise ValidationError(_("The ID number is not valid"))


def probable_registration_number(query: str) -> str | None:
"""
Try to guess if a query string could be a registration number and return that number
"""
query = query.strip().upper()

if query.startswith("RO") and query[2:].isnumeric() and len(query) <= 12:
return query[2:]

if query.isnumeric() and len(query) <= 10:
return query

return None