[Django]-Icontains and getlist django python

1๐Ÿ‘

โœ…

I was able to solve this by creating 2 separate functions within the get_querset() function, which is called when a GET request is made.

 def get_queryset(self):
        queryset = News.objects.all()
        source_list = self.request.query_params.getlist('source')
        keyword_list = self.request.query_params.getlist('title')
        if source_list or keyword_list:
            def create_q_source(*args):
                list = [*args]
                source = Q()
                for value in list:
                    source.add(Q(news_source=value), Q.OR)
                return source
            def create_q_keyword(*args):
                list = [*args]
                keyword = Q()
                for value in list:
                    keyword.add(Q(news_title__icontains=value), Q.OR)
                return keyword
            queryset = queryset.filter(create_q_source(*source_list),create_q_keyword(*keyword_list))
        return queryset

Edit:
When you go to the api link and pass in the parameters, filtering will occur based on what is passed in:

http://127.0.0.1:8000/api/notes/?keyword=trump&keyword=beyond&keyword=money&source=1

SQL Equivalent:

select * from news where news_source = 1 AND news_title like '%beyond%' OR news_title like '%money%'
๐Ÿ‘คdataviews

4๐Ÿ‘

You can not perform an __icontains with a list, but you can for example design a function that, for a list constructs the logical or of these values. For example:

from django.db.models import Q
from functools import reduce
from operator import or_

def or_fold(list_of_qs):
    if list_of_qs:
        return reduce(or_, list_of_qs)
    else:
        return Q()

def unroll_lists_or(qs, **kwargs):
    return qs.filter([
        or_fold(Q(**{k: vi}) for vi in v)
        for k, v in kwargs.items()
    ])

You can then call the unroll_lists_or with a queryset, and each item should be an iterable (for example a list). It will then perform or-logic between the items of the list, and and-logic between different keys. In case an iterable is empty, it is ignored.

So we can then write the check as:

unroll_lists_or(queryset, news_title__icontains=title, news_source=source)

In case title contains two items (so title == [title1, title2]), and source contains three items (so source = [source1, source2, source3]), then this will result in:

qs.filter(
    Q(news_title__icontains=title1) | Q(news_title__icontains=title2),
    Q(news_source=source1) | Q(news_source=source2) | Q(news_source=source3)
)

You can however combine it with an .filter(..) for the __in check. For example:

queryset = News.objects.all()
if source:
    queryset = queryset.filter(news_source__in=source)
queryset = unroll_lists_or(queryset, news_title__icontains=title)

Leave a comment