django - 按 Django 中相关模型的自定义查询集进行过滤

标签 django django-queryset

假设我有两个模型:BookAuthor

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_siteAuthor 模型的自定义查询集。

最佳答案

我想,我已经提出了一个基于 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 种不同的使用方式:

  1. 将其放入 filter() 调用中,以过滤查询集;
  2. 使用 & (AND)| 将其与其他 Q 对象组合起来(OR) 运算符;
  3. 通过访问其 children 属性(在父类(super class) django.utils.tree.Node 中定义)动态更改 Q 对象中使用的关键字名称。

每个自定义 QuerySet 类中定义的 _qq() 方法负责将指定的 lated_name 添加到所有过滤器键的前面。

如果我们有一个 q = Q(approved=True) 对象,那么我们可以得到以下输出:

  1. self._qq(q) – 相当于self.filter(approved=True);
  2. self._qq(q, 'author') – 相当于 self.filter(author__approved=True)
<小时/>

这个解决方案仍然有严重的缺点:

  1. 必须显式导入并调用相关模型的自定义 QuerySet 类;
  2. 对于每个过滤器方法,必须定义两个方法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/

相关文章:

Django 过滤同一个外键对象的两个字段

python - Django 打印用户列表

python - django 在表单提交后添加外键值

python - 用有序相关模型的第一个值注释 QuerySet

Django:获取相关对象的相关对象并传递给模板

django - 如何在Django ORM中实现SUMaggregate()+distinct(fields)

python - 我可以在 python 中删除一个查询集的项目但不删除数据库中的那个项目吗?

python - 如何使用 django imageField 保存图像?

django 向导,在同一步骤中使用表单和表单集

django - Rietveld 是否与 App Engine 密不可分?