在ModelMultipleChoiceField中定义选择时,如何合并两个查询集?

sou*_*eux 1 python django django-forms django-queryset

我在两个不同的地方使用一段代码,以便动态生成一些表单字段。在这两种情况下,dynamic_fields都是一个字典,其中的键是对象,值是对象列表(在空列表的情况下,值是False):

class ExampleForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        dynamic_fields = kwargs.pop('dynamic_fields')
        super(ExampleForm, self).__init__(*args, **kwargs)

        for key in dynamic_fields:
            if dynamic_fields[key]:
                self.fields[key.description] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=dynamic_fields[key], required=False)


    class Meta:
        model = Foo
        fields = ()
Run Code Online (Sandbox Code Playgroud)

在一个视图中,对于任何键,该值都是通过单个数据库查询(单个正常查询集)返回的对象列表。这种观点很好。

在另一个视图中,它需要多个查询才能获得构造给定值所需的一切。我首先实例化具有等于空白列表的值的字典,然后使用基本列表理解(dict[key] += queryset)一次将从这些多个查询中获得的查询集添加到适当的列表中。这使每个值成为一个二维列表,然后通过执行以下操作将其展平(并删除重复项):

for key in dict:
    dict[key] = list(set(dict[key]))
Run Code Online (Sandbox Code Playgroud)

我尝试了几种不同的方法-将每个查询集中的查询直接附加到值/列表,使用append而不是将其保留为列表列表,+=但每次都会遇到相同的错误:'list' object has no attribute 'none'

通过回溯查看,错误出现在表单的clean方法中。这是django.forms.models中代码的相关部分:

def clean(self, value):
    if self.required and not value:
        raise ValidationError(self.error_messages['required'], code='required')
    elif not self.required and not value:
        return self.queryset.none() # the offending line
Run Code Online (Sandbox Code Playgroud)

到目前为止,我的思考过程是:在第一个视图中,我正在通过单个查询生成用作每个键的值的列表,但是在第二个视图中,我将多个查询组合到一个列表中。该列表没有none像通常使用单个查询集那样的方法。

如何合并多个查询集,而又不会失去对该方法的访问权限?

我找到了这篇文章,但是我仍然遇到itertools.chain与建议相同的问题。我唯一能够完成的事情就是将错误改为'chain''set' object has no attribute 'none'


编辑:这是有关如何生成查询集的一些其他信息。我有以下模型(仅显示相关字段):

class Profile(models.Model):
    user = models.OneToOneField(User)
    preferred_genres = models.ManyToManyField(Genre, blank=True)

class Genre(models.Model):
    description = models.CharField(max_length=200, unique=True)
    parent = models.ForeignKey("Genre", null=True, blank=True)

class Trope(models.Model):
    description = models.CharField(max_length=200, unique=True)
    genre_relation = models.ManyToManyField(Genre)
Run Code Online (Sandbox Code Playgroud)

在(工作状态)视图1中,我用来生成字段的字典的键等于某个流派,其值等于该键为父的流派列表。换句话说,对于每个键,queryset为Genre.objects.filter(parent=key, **kwargs)

在非功能性视图#2中,我们需要从配置文件的preferred_genres字段开始。对于每一个preferred_genre我需要拉相关联Tropes,并将它们组合成一个查询集。现在,我正在遍历preferred_genres并执行以下操作:

for g in preferred_genres:
    tropeset = g.trope_set.all()
Run Code Online (Sandbox Code Playgroud)

这使我得到了一堆包含我所需信息的单独查询集,但是我找不到一种将多个查询集组合tropesets成一个大查询集的方法(与没有该none属性的列表相对)。(顺便说一句,这也使我的数据库受到一堆查询的困扰。我也想尽我所能解决如何使用prefetch_related减少查询数量的问题,但一次只能解决一件事。)

如果我不能将这些查询集合并为一个,但是可以通过一个查询以某种方式完成这些查询,那我真是无所不能!我现在正在阅读有关使用Q对象进行复杂查询的文档。令人着迷的是-我可以概念化这将如何完成我要寻找的东西,但前提是我可以一次调用所有查询。因为我给他们打电话反复一次一个,我不知道如何使用默认的Q对象|&在一起。

Sim*_*tte 5

您可以使用|&运算符组合查询集。

from functools import reduce
from operator import and_, or_

querysets = [q1, q2, q3, ...]  # List of querysets you want to combine.

# Objects that are present in *at least one* of the queries
combined_or_querysets = reduce(or_, querysets[1:], querysets[0])

# Objects that are present in *all* of the queries
combined_and_querysets = reduce(and_, querysets[1:], querysets[0])
Run Code Online (Sandbox Code Playgroud)

从Django 1.11+开始,您还可以使用unionintersection方法。

  • 绝对出色,并且完美!谢谢!除将来的读者外,要使用此方法组合的查询集必须具有相同的基本模型。否则,您将收到“ AssertionError:无法在两个不同的基本模型上组合查询”。再次感谢您!! (2认同)