Bar*_*Naz 5 django django-queryset
假设我有两个模型: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)
Run Code Online (Sandbox Code Playgroud)
这两个模型中的每一个都有一个approved属性,用于在网站上显示或隐藏对象。如果Book未获批准,则将其隐藏。如果Author不批准,他所有的书都会被隐藏起来。
为了以 DRY 方式定义这些标准,制作自定义 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)
Run Code Online (Sandbox Code Playgroud)
将这些 QuerySets 连接到相应的模型后,可以像这样查询它们:Book.objects.for_site(),无需每次都对所有过滤进行硬编码。
尽管如此,这个解决方案仍然不完美。稍后我可以决定向作者添加另一个过滤器:
class AuthorQuerySet(models.query.QuerySet):
def for_site():
return self.filter(approved=True).exclude(country='Problematic Country')
Run Code Online (Sandbox Code Playgroud)
但是这个新过滤器只能在 中使用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)
Run Code Online (Sandbox Code Playgroud)
哪里for_site是Author模型的自定义 QuerySet 。
我想,我已经提出了一个基于Q对象的解决方案,官方文档中对此进行了描述。这绝对不是人们能发明的最优雅的解决方案,但它确实有效。请参阅下面的代码。
from django.db import models\nfrom django.db.models import Q\n\n\n######## Custom querysets\nclass QuerySetRelated(models.query.QuerySet):\n """Queryset that can be applied in filters on related models"""\n\n @classmethod\n def _qq(cls, q, related_name):\n """Returns a Q object or a QuerySet filtered with the Q object, prepending fields with the related_name if specified"""\n if not related_name:\n # Returning Q object without changes\n return q\n # Recursively updating keywords in this and nested Q objects\n for i_child in range(len(q.children)):\n child = q.children[i_child]\n if isinstance(child, Q):\n q.children[i_child] = cls._qq(child, related_name)\n else:\n q.children[i_child] = (\'__\'.join([related_name, child[0]]), child[1])\n return q\n\n\nclass AuthorQuerySet(QuerySetRelated):\n\n @classmethod\n def for_site_q(cls, q_prefix=None):\n q = Q(approved=True)\n q = q & ~Q(country=\'Problematic Country\')\n return cls._qq(q, q_prefix)\n\n\n def for_site(self):\n return self.filter(self.for_site_q())\n\n\nclass BookQuerySet(QuerySetRelated):\n\n @classmethod\n def for_site_q(cls, q_prefix=None):\n q = Q(approved=True) & AuthorQuerySet.for_site_q(\'author\')\n return cls._qq(q, q_prefix)\n\n\n def for_site(self):\n return self.filter(self.for_site_q())\n\n\n\n######## Models\nclass Author(models.Model):\n name = models.CharField(max_length=255)\n country = models.CharField(max_length=255)\n approved = models.BooleanField()\n\n objects = AuthorQuerySet.as_manager()\n\n\nclass Book(models.Model):\n title = models.CharField(max_length=255)\n approved = models.BooleanField()\n author = models.ForeignKey(Author)\n\n objects = BookQuerySet.as_manager()\nRun Code Online (Sandbox Code Playgroud)\n\n这样,每当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)\xe2\x80\x93 相当于self.filter(approved=True);self._qq(q, \'author\')\xe2\x80\x93 相当于self.filter(author__approved=True)该解决方案仍然存在严重的缺陷:
\n\nQuerySet相关模型的自定义类;filter_q(类方法)和filter(实例方法);更新:2.通过动态创建过滤器方法可以部分减少该缺点:
# in class QuerySetRelated\n @classmethod\n def add_filters(cls, names):\n for name in names:\n method_q = getattr(cls, \'{0:s}_q\'.format(name))\n def function(self, *args, **kwargs):\n return self.filter(method_q(*args, **kwargs))\n setattr(cls, name, function)\n\nAuthorQuerySet.add_filters([\'for_site\'])\nBookQuerySet.add_filters([\'for_site\'])\nRun Code Online (Sandbox Code Playgroud)\n\n因此,如果有人提出更优雅的解决方案,请提出。我们将非常感激。
\n