diff --git a/taiga/base/filters.py b/taiga/base/filters.py index cd802eee..a0b994a1 100644 --- a/taiga/base/filters.py +++ b/taiga/base/filters.py @@ -13,7 +13,8 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - +import operator +from functools import reduce from django.db.models import Q from django.db.models.sql.where import ExtraWhere, OR @@ -214,3 +215,28 @@ class TagsFilter(FilterBackend): queryset = tags.filter(queryset, contains=query_tags) return super().filter_queryset(request, queryset, view) + + +class SearchFieldFilter(filters.SearchFilter): + """Search filter that looks up the search param in the parameter named after the search field, + that is: ?=... instead of looking for the search param: ?search=... + This way you can search in a field-specific way. + """ + def get_search_terms(self, request, field): + params = request.QUERY_PARAMS.get(field, '') + return params.replace(',', ' ').split() + + def filter_queryset(self, request, queryset, view): + search_fields = getattr(view, "search_fields", None) + if not search_fields: + return queryset + + lookups = dict((self.construct_search(field), self.get_search_terms(request, field)) + for field in search_fields) + + for lookup, values in lookups.items(): + or_queries = [Q(**{lookup: value}) for value in values] + if or_queries: + queryset = queryset.filter(reduce(operator.or_, or_queries)) + + return queryset diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py index 667178df..78ef81ac 100644 --- a/taiga/projects/userstories/api.py +++ b/taiga/projects/userstories/api.py @@ -48,9 +48,11 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi list_serializer_class = serializers.UserStorySerializer permission_classes = (permissions.UserStoryPermission,) - filter_backends = (filters.CanViewUsFilterBackend, filters.TagsFilter) + filter_backends = (filters.CanViewUsFilterBackend, filters.TagsFilter, + filters.SearchFieldFilter) retrieve_exclude_filters = (filters.TagsFilter,) filter_fields = ['project', 'milestone', 'milestone__isnull', 'status', 'is_archived'] + search_fields = ('subject',) # Specific filter used for filtering neighbor user stories _neighbor_tags_filter = filters.TagsFilter('neighbor_tags') diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index 9a508261..a127b972 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -51,3 +51,16 @@ def test_api_delete_userstory(client): response = client.delete(url) assert response.status_code == 204 + + +def test_api_filter_by_subject(client): + f.create_userstory() + us = f.create_userstory(subject="some random subject") + url = reverse("userstories-list") + "?subject=some subject" + + client.login(us.owner) + response = client.get(url) + number_of_stories = len(response.data) + + assert response.status_code == 200 + assert number_of_stories == 1, number_of_stories