假设我有两个模型:Book
和 Author
class Author(models.Model):
name = models.CharField()
country = models.CharField()
approved = models.BooleanField()
class Book(models.Model):
title = models.CharField()
approved = models.BooleanField()
author = models.ForeignKey(Author)
这两个模型都有一个 approved
属性,用于在网站中显示或隐藏对象。如果图书
未获批准,它将被隐藏。如果作者
未获得批准,他的所有书籍都会被隐藏。
为了以 DRY 方式定义这些条件,制作自定义查询集看起来是一个完美的解决方案:
class AuthorQuerySet(models.query.QuerySet):
def for_site():
return self.filter(approved=True)
class BookQuerySet(models.query.QuerySet):
def for_site():
reuturn self.filter(approved=True).filter(author__approved=True)
将这些 QuerysSet 连接到相应的模型后,可以像这样查询它们:Book.objects.for_site()
,而无需每次都硬编码所有过滤。
尽管如此,这个解决方案仍然不完美。稍后我可以决定为作者添加另一个过滤器:
class AuthorQuerySet(models.query.QuerySet):
def for_site():
return self.filter(approved=True).exclude(country='Problematic Country')
但这个新过滤器仅适用于 Author.objects.for_site()
,但不适用于 Book.objects.for_site()
,因为它是硬编码的。
所以我的问题是:在过滤不同模型时是否可以应用相关模型的自定义查询集,使其看起来与此类似:
class BookQuerySet(models.query.QuerySet):
def for_site():
reuturn self.filter(approved=True).filter(author__for_site=True)
其中 for_site
是 Author
模型的自定义查询集。
最佳答案
我想,我已经提出了一个基于 Q
对象的解决方案,official documentation 中对此进行了描述。 。这绝对不是人们能发明的最优雅的解决方案,但它确实有效。请参阅下面的代码。
from django.db import models
from django.db.models import Q
######## Custom querysets
class QuerySetRelated(models.query.QuerySet):
"""Queryset that can be applied in filters on related models"""
@classmethod
def _qq(cls, q, related_name):
"""Returns a Q object or a QuerySet filtered with the Q object, prepending fields with the related_name if specified"""
if not related_name:
# Returning Q object without changes
return q
# Recursively updating keywords in this and nested Q objects
for i_child in range(len(q.children)):
child = q.children[i_child]
if isinstance(child, Q):
q.children[i_child] = cls._qq(child, related_name)
else:
q.children[i_child] = ('__'.join([related_name, child[0]]), child[1])
return q
class AuthorQuerySet(QuerySetRelated):
@classmethod
def for_site_q(cls, q_prefix=None):
q = Q(approved=True)
q = q & ~Q(country='Problematic Country')
return cls._qq(q, q_prefix)
def for_site(self):
return self.filter(self.for_site_q())
class BookQuerySet(QuerySetRelated):
@classmethod
def for_site_q(cls, q_prefix=None):
q = Q(approved=True) & AuthorQuerySet.for_site_q('author')
return cls._qq(q, q_prefix)
def for_site(self):
return self.filter(self.for_site_q())
######## Models
class Author(models.Model):
name = models.CharField(max_length=255)
country = models.CharField(max_length=255)
approved = models.BooleanField()
objects = AuthorQuerySet.as_manager()
class Book(models.Model):
title = models.CharField(max_length=255)
approved = models.BooleanField()
author = models.ForeignKey(Author)
objects = BookQuerySet.as_manager()
这样,每当 AuthorQuerySet.for_site_q()
方法发生更改时,它都会自动反射(reflect)在 BookQuerySet.for_site()
方法中。
此处,自定义 QuerySet
类通过组合不同的 Q
对象在类级别执行选择,而不是使用 filter()
或 exclude()
方法。拥有 Q
对象可以实现 3 种不同的使用方式:
- 将其放入
filter()
调用中,以过滤查询集; - 使用
& (AND)
或| 将其与其他
Q
对象组合起来(OR) 运算符; - 通过访问其
children
属性(在父类(super class)django.utils.tree.Node
中定义)动态更改Q
对象中使用的关键字名称。
每个自定义 QuerySet
类中定义的 _qq()
方法负责将指定的 lated_name
添加到所有过滤器键的前面。
如果我们有一个 q = Q(approved=True)
对象,那么我们可以得到以下输出:
self._qq(q)
– 相当于self.filter(approved=True)
;self._qq(q, 'author')
– 相当于self.filter(author__approved=True)
这个解决方案仍然有严重的缺点:
- 必须显式导入并调用相关模型的自定义
QuerySet
类; - 对于每个过滤器方法,必须定义两个方法
filter_q
(类方法)和filter
(实例方法);
更新:通过动态创建过滤器方法可以部分减少2.
的缺点:
# in class QuerySetRelated
@classmethod
def add_filters(cls, names):
for name in names:
method_q = getattr(cls, '{0:s}_q'.format(name))
def function(self, *args, **kwargs):
return self.filter(method_q(*args, **kwargs))
setattr(cls, name, function)
AuthorQuerySet.add_filters(['for_site'])
BookQuerySet.add_filters(['for_site'])
<小时/>
因此,如果有人提出更优雅的解决方案,请提出。我们将不胜感激。
关于django - 按 Django 中相关模型的自定义查询集进行过滤,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43639802/