如何基于递归ManyToManyField生成Django查询集?解决复合动词组件需求
问题描述
我开发了一个词典应用,单词(Lemma)可由其他单词组成,models.py中的模型定义如下:
class Lemma(models.Model): cf = models.CharField(max_length=200) #citation form pos = models.ForeignKey(Pos, on_delete=models.CASCADE) #part of speech components = models.ManyToManyField("self", symmetrical=False, blank=True) #component Lemma
需要返回两个查询集:
- 所有复合动词:存在
components关联且pos.term为"verb"的Lemma; - 复合动词的所有唯一名词组件:作为其他
Lemma的component_set成员,且自身pos.term为"noun"的Lemma。
当前视图中,查询集1可以轻松实现,但查询集2的实现会得到普通列表,无法使用order_by()排序,views.py相关代码如下:
class CompVerb(generic.ListView): model = Lemma queryset = Lemma.objects.filter( Q(pos__term="verb") & ~Q(components=None) ).order_by("cf") #queryset 1, compound verbs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) nouns = self.queryset.values_list("components") nouns = set([Lemma.objects.get(pk=l[0]) for l in nouns]) nouns= [l for l in nouns if l.pos.term == "noun"] context["nouns"] = nouns #queryset 2, nouns that are components of compound verbs return context
希望找到更好的方法,让查询集2返回合法的查询集对象。
优化方案
可以通过Django ORM的关联查询直接构造查询集,避免手动遍历和转换,同时保证结果是支持排序的QuerySet对象:
步骤1:优化查询集1
原查询集1中的~Q(components=None)可以简化为components__isnull=False,语义更清晰:
queryset = Lemma.objects.filter( pos__term="verb", components__isnull=False ).order_by("cf")
步骤2:构造查询集2
利用多对多的反向关联component_set,结合in查询和distinct()去重,直接获取符合条件的名词组件查询集:
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # 获取复合动词查询集 compound_verbs = self.queryset # 构造唯一名词组件查询集 noun_components = Lemma.objects.filter( pos__term="noun", # 反向关联:当前Lemma是复合动词的components成员 component_set__in=compound_verbs ).distinct().order_by("cf") context["nouns"] = noun_components return context
优势说明
- 结果是合法的QuerySet对象,支持
order_by()、切片等QuerySet操作; - 避免了原代码中的N+1查询(多次调用
Lemma.objects.get),性能更优; - 利用
distinct()自动去重,无需手动转集合; - 代码更简洁,符合Django ORM的最佳实践。
内容的提问来源于stack exchange,提问作者McBoatface et al.
相关产品推荐
相关产品推荐

