diff --git a/django_select2/forms.py b/django_select2/forms.py index 0fedc39d..32d4ba11 100644 --- a/django_select2/forms.py +++ b/django_select2/forms.py @@ -46,8 +46,8 @@ :parts: 1 """ +import re import uuid -from functools import reduce from itertools import chain from pickle import PicklingError # nosec @@ -315,6 +315,8 @@ class HeavySelect2TagWidget(HeavySelect2Mixin, Select2TagWidget): class ModelSelect2Mixin: """Widget mixin that provides attributes and methods for :class:`.AutoResponseView`.""" + _word_split_pattern = re.compile(r"\t|\n| ") + model = None queryset = None search_fields = [] @@ -397,14 +399,15 @@ def filter_queryset(self, request, term, queryset=None, **dependent_fields): queryset = self.get_queryset() search_fields = self.get_search_fields() select = Q() - term = term.replace("\t", " ") - term = term.replace("\n", " ") - for t in [t for t in term.split(" ") if not t == ""]: - select &= reduce( - lambda x, y: x | Q(**{y: t}), - search_fields[1:], - Q(**{search_fields[0]: t}), - ) + + for field in search_fields: + field_select = Q(**{field: term}) + if "contains" in field: + for word in filter(None, self._word_split_pattern.split(term)): + field_select |= Q(**{field: word}) + + select |= field_select + if dependent_fields: select &= Q(**dependent_fields) diff --git a/tests/test_forms.py b/tests/test_forms.py index 1f02c828..130f650c 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -446,6 +446,52 @@ def test_filter_queryset(self, genres): ) assert qs.exists() + def test_filter_queryset__startswith(self, genres): + genre = Genre.objects.create(title="Space Genre") + widget = TitleModelSelect2Widget(queryset=Genre.objects.all()) + assert widget.filter_queryset(None, genre.title).exists() + + widget = TitleModelSelect2Widget( + search_fields=["title__istartswith"], queryset=Genre.objects.all() + ) + qs = widget.filter_queryset(None, "Space Gen") + assert qs.exists() + + qs = widget.filter_queryset(None, "Gen") + assert not qs.exists() + + def test_filter_queryset__contains(self, genres): + genre = Genre.objects.create(title="Space Genre") + widget = TitleModelSelect2Widget(queryset=Genre.objects.all()) + assert widget.filter_queryset(None, genre.title).exists() + + widget = TitleModelSelect2Widget( + search_fields=["title__contains"], queryset=Genre.objects.all() + ) + qs = widget.filter_queryset(None, "Space Gen") + assert qs.exists() + + qs = widget.filter_queryset(None, "NOT Gen") + assert qs.exists(), "contains works even if only one part matches" + + def test_filter_queryset__multiple_fields(self, genres): + genre = Genre.objects.create(title="Space Genre") + widget = TitleModelSelect2Widget(queryset=Genre.objects.all()) + assert widget.filter_queryset(None, genre.title).exists() + + widget = TitleModelSelect2Widget( + search_fields=[ + "title__startswith", + "title__endswith", + ], + queryset=Genre.objects.all(), + ) + qs = widget.filter_queryset(None, "Space") + assert qs.exists() + + qs = widget.filter_queryset(None, "Genre") + assert qs.exists() + def test_model_kwarg(self): widget = ModelSelect2Widget(model=Genre, search_fields=["title__icontains"]) genre = Genre.objects.last()