我们说我有两个模型: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
属性,用于显示或隐藏网站上的对象。如果Book
未获批准,则会被隐藏。如果Author
未获批准,则会隐藏其所有图书。
为了以干燥的方式定义这些标准,使自定义QuerySet看起来像一个完美的解决方案:
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)
将这些QuerysSets连接到相应的模型后,可以像这样查询它们: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
模型的自定义QuerySet。
答案 0 :(得分:0)
我认为,我已经提出了基于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()
方法时,它都会自动反映在BookQuerySet.for_site()
方法中。
此处,自定义QuerySet
类通过组合不同的Q
对象在类级别执行选择,而不是在对象级别使用filter()
或exclude()
方法。拥有Q
对象允许3种不同的使用方式:
filter()
调用内,以过滤查询集; Q
或& (AND)
运算符将其与其他| (OR)
个对象合并; Q
属性来动态更改children
对象中使用的关键字名称,该属性在超类django.utils.tree.Node
中定义每个自定义_qq()
类中定义的QuerySet
方法负责将指定的related_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'])
因此,如果有人想出更优雅的解决方案,请提出建议。非常感谢。