【问题标题】:icontains and getlist django pythonicontains 和 getlist django python
【发布时间】:2018-12-04 11:55:33
【问题描述】:

我们正在尝试返回 Django API 的标题列表,其中标题可以包含几个关键字。

例如,如果我们使用__icontains 方法搜索“money”和“world” (api.com/?keyworld=money&keyword=world),这将返回所有包含 money、world 或两者的记录。

相关的SQL语句为:

select * from news
    where news_source = 1 or news_source = 2
        and news_title like '%money%' or news_title like '%world%'

我们正在尝试使用此代码来允许用户拥有多个__icontains 的关键字以及多个来源,因此最终目标是:

api.com/?keyworld=money&keyword=world&source=1&source=2

我们的代码:

def get_queryset(self):
    queryset = News.objects.all()
    title = self.request.query_params.getlist('title')
    source = self.request.query_params.getlist('source')
    if title:
        queryset = queryset.filter(news_title__icontains=title, news_source__in=source)
    return queryset

问题在于,如果使用第二个关键字,这只会返回第二个关键字,而不是在&keyword= 中键入的内容之前的其他关键字。

【问题讨论】:

  • 在获取参数中您使用keyworld,但在过滤器getlist('title') 中是否正确?并请显示数据示例。

标签: python django python-3.x django-rest-framework


【解决方案1】:

您不能使用列表执行__icontains,但您可以设计一个函数,例如,为列表构造这些值的逻辑或。例如:

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()
    ])

然后您可以使用查询集调用unroll_lists_or,并且每个项目都应该是可迭代的(例如列表)。然后它将在列表的项目之间执行-逻辑,并在不同键之间执行-逻辑。如果一个可迭代对象为空,则忽略它。

所以我们可以把支票写成:

unroll_lists_or(queryset, <b>news_title__icontains=title, news_source=source</b>)

如果title 包含两个项目(所以title == [title1, title2]),而source 包含三个项目(所以source = [source1, source2, source3]),那么这将导致:

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

但是,您可以将其与 .filter(..) 结合使用以进行 __in 检查。例如:

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

【讨论】:

  • 非常感谢您抽出宝贵时间回复我的信息。我已经用类似的方式解决了这个问题,我希望你可以看看我下面的逻辑,看看这是否有意义。那么我仍然会在这里给你一些功劳,因为我指出了正确的方向。我想在阅读您的帖子后尝试自己解决它,以便我可以学习这个框架。
【解决方案2】:

我能够通过在 get_querset() 函数中创建 2 个单独的函数来解决这个问题,该函数在发出 GET 请求时被调用。

 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

编辑: 当你去api链接传入参数时,会根据传入的内容进行过滤:

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

SQL 等效项:

select * from news where news_source = 1 AND news_title like '%beyond%' OR news_title like '%money%'

【讨论】:

  • 使用Q(..) 初始化它的事实不会导致接受所有 元素(因为Q() 对象匹配everything,因此添加带有 or 逻辑的条件,不会限制过滤器)。
  • @WillemVanOnsem 使用上面的代码,这将返回所有包含关键字的源 1 和源 2 内容:标题中的王牌和超越。在 queryset.filter() 方法中,我们调用创建 Q 对象的函数并将它们返回给 filter 方法,因此理论上它们看起来与您将它们放在答案中的方式完全相同:qs.filter( Q(news_title__icontains= title1) | Q(news_title__icontains=title2), Q(news_source=source1) | Q(news_source=source2) | Q(news_source=source3))
  • 我还可以在这两个函数上去掉 *args 和 list = [*args] ,然后在 source_list 或 keyword_list 中说出价值 ---- 我刚刚意识到
  • 是的,因为*args 是元组,因此也是可迭代的。
猜你喜欢
  • 2012-06-26
  • 2018-09-26
  • 1970-01-01
  • 2021-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
相关资源
最近更新 更多