Django通过MultiSelectField值过滤QueryString?

3 投票
3 回答
4709 浏览
提问于 2025-04-18 15:03

我正在使用MultiSelectField来存储对话的主题。

我的模型大概是这样的:

class Conversation(models.Model):
    (...)
    note= models.CharField(max_lenght=250)
    TOPIC_CHOICES =(
        (1,"about cats"),
        (2, "about dogs"),
        (3, "about fluffy little things"),
    )
    topic =MultiSelectField(choices=TOPIC_CHOICES)

我使用的是ListView,并在get_queryset方法中通过GET参数进行过滤:

表单提取:

class SearchForm(Form):
    (...)
    topic = MultipleChoiceField(choices=Conversation.TOPIC_CHOICES, required=False)

获取查询集的提取:

(...)
if form.cleaned_data['topic']:
                search_params.update({'topic__in': form.cleaned_data['topic']})
(...)
return qs.filter(**search_params)

这个方法在处理单个值的选择字段时效果很好。

但在这种情况下,比如我在表单中选择“关于猫”,我只得到了主题设置为猫的对象仅仅是(“关于猫”,没有其他 - 单一值)。

我想要的是所有主题值中包含1-“关于猫”的对象。这意味着如果某个对象的主题是1,3(猫和毛茸茸的东西),它也应该出现。

第二种情况:我在表单中选择“关于猫”和“关于狗” - 我想要所有包含猫作为主题之一的对象和所有包含狗作为主题之一的对象。

现在,当我选择多个选项,比如猫和狗时,我只得到了仅仅是猫的对象和仅仅是狗的对象。

有没有其他的字段查找字符串可以替代__in来实现这个?如果没有,最简单的方法是什么?

3 个回答

2

MultiSelectField 实际上是一个字符字段,用来存储多个选择的值,这些值是用逗号分隔开的字符串。

因此,当你想要对 MultiSelectField 进行查询过滤时,需要扫描整个表的数据。这时候你可以使用 __regex 字段查找 来过滤你的查询集,以匹配特定的选择值:

(...)
searched_topics = form.cleaned_data['topic']
if searched_topics:
    search_topic_regexp = "(^|,)%s(,|$)" % "|".join(searched_topics)
    search_params.update({'topic__regex': search_topic_regexp})
(...)

为了提高性能(避免在有很多对话记录时对主题字段进行全表扫描),你应该不使用 MultiSelectField,而是使用 多对多关系(这会使用一个单独的连接表)。

3

MultiSelectField 会把值存储为用逗号分隔的字符串在一个 CharField 里,所以你实际上应该使用 .topic__contains=topic 而不是 .topic__in=topic

不过,根据你的搜索表单,你可能需要用不同的布尔运算符来连接条件,以便缩小或扩大搜索结果。根据你的需求,我建议你可以选择以下其中一种方法:

  • 使用 Q 来构建一个复杂的查询,使用 OR 连接(虽然复杂,但更灵活)
  • 从字典中解包命名参数到 .filter() 中(简单,但限制较多)

使用 Q 动态构建查询。

这是更好的方法。它允许你在查询中使用 ORANDNOTXOR 来细化搜索选项。

# ...
search_params = Q()

# Let's say we have other search parameters also
search_params = search_params | Q(note__contains="example note")

if form.cleaned_data['topic']:
    search_params = search_params | Q(topic__in=form.cleaned_data['topic'])

# ...
return qs.filter(search_params)

生成的 MYSQL 查询大概会是这样的:

SELECT * FROM `search_form`
    WHERE (
        `note` LIKE '%example note%' OR
        `topic` LIKE '%about cats%'
    )

从字典中解包命名参数

这种方法只能用来通过在查询中使用 AND 来缩小搜索结果。

# ...

search_params = {}

# let's say there are other items in the query
search_params['note__contains'] = 'example note'

if form.cleaned_data['topic']:
    search_params['topic__contains'] = form.cleaned_data['topic']

# ...
# unpack the search_params into the named parameters
return qs.filter(**search_params)

这将有效地构建 Django 查询,大概是这样的:

SearchForm.objects.filter(
    note__contains="example note",
    topic__contains="about cats"
)

生成的数据库查询大概会是这样的:

SELECT * FROM `search_form`
    WHERE (
        `note` LIKE '%example note%' AND 
        `topic` LIKE '%about cats%'
    )
5

你可以试试用Django的Q集合来处理这个问题,这样你的过滤器看起来会像这样:

...objects.filter( Q(topic__exact = cat) | Q(topic__startswith = '%s,' % cat) | Q(topic__endswith = ',%s' % cat) | Q(topic__contains = ',%s,' % cat),
other_attributes = 'xxx',

撰写回答