【问题标题】:Django override behavior of double underscore relationship lookup in queriesDjango覆盖查询中双下划线关系查找的行为
【发布时间】:2016-11-23 01:25:10
【问题描述】:

我的主要问题是:有什么方法可以改变相关查找的行为,例如MyModel.objects.filter(relationship__field="value")

考虑这种设置。我与自定义管理器建立了一对多关系,该管理器过滤掉带有active=False的书籍

from django.db import models


class ActiveOnly(models.Manager):

    use_for_related_fields = True

    def get_queryset(self):
        return super(ActiveOnly, self).get_queryset().filter(active=True)

class Author(models.Model):
    name = models.TextField()

class Book(models.Model):
    active = models.BooleanField()
    author = models.ForeignKey(Author, related_name="books")
    title = models.TextField()
    objects = ActiveOnly()

让我们创建一些数据:

jim = Author.objects.create(name="Jim")
ulysses = Book.objects.create(title="Ulysses", author=jim, active=True)
finnegans = Book.objects.create(title="Finnegan's Wake", author=jim, active=False)

bill = Author.objects.create(name="Bill")
hamlet = Book.objects.create(title="Hamlet", author=bill, active=False)

基本上,我永远不想处理不活跃的书籍。以下是一些用于测试各种场景的查询。

>>> Book.objects.all().count()  # expecting the 1 active book: good
1  
>>> jim.books.all()  # also expecting only 1: good
1
>>> Author.objects.filter(books__title="Hamlet").first().name
u'Bill'  
# ^ this is what I don't want to see, because bill's only book has active=False.
# I want the queryset to return no results.

是否有任何方法来更改books__* 查找的行为以包括active 上的附加过滤器?

【问题讨论】:

  • 如果您不提供完整的详细信息,我不知道您如何期望得到好的答案
  • 我本来很仓促的写了一天的问题,我现在更新了例子。
  • 你说的 date_deleted 在哪里?
  • @e4c5,在我正在处理的实际代码中,我使用的是date_deleted 字段。您在对答案的评论中提出的观点是,对可索引字段进行过滤比在不可索引的 BooleanField 上过滤更有效,但我希望这个示例尽可能简单,所以我选择了 BooleanField。为了看看它是否真的有效,它没有任何区别。

标签: python sql django django-models django-orm


【解决方案1】:

Django 1.10 中,Manager.use_for_related_fields 已被弃用,而赞成在模型上设置 Meta.base_manager_name。有关详细信息,请参阅更新的文档:

Model._base_manager

使用管理器进行相关对象访问

默认情况下,Django 使用Model._base_manager 管理器的实例 访问相关对象时的类(即choice.poll),而不是 _default_manager 在相关对象上。这是因为 Django 需要能够检索相关对象,即使它本来可以 被默认管理器过滤掉(因此无法访问)。

如果普通基管理器类 (django.db.models.Manager) 不是 适合你的情况,你可以告诉 Django 哪个类 通过设置Meta.base_manager_name来使用。

警告,不排除 BaseManager 中的对象仍然存在!

【讨论】:

    【解决方案2】:

    编辑:我不确定您是否可以在某个地方覆盖 QuerySet 类来获得此功能,我唯一的建议是将books__active=True 添加到您的查询中。

    如果有人想试一试,我已将代码添加到 github 存储库并通过测试说明问题。 https://github.com/alecklandgraf/NonDeletableDjangoModel

    原帖:

    我认为您可能需要在您的Book 模型中设置default_manager,我会添加第二个模型管理器来查询非活动书籍。

    class Book(models.Model):
        active = models.BooleanField()
        author = models.ForeignKey(Author, related_name="books")
        title = models.TextField()
        objects = ActiveOnly()
        # try adding the following...
        default_manager = ActiveOnly()
        inactive_objects = models.Manager()
    

    我重新构建了您的示例,发现它与 Django 1.8 和 1.9 相同。感兴趣的是您所说明的创建的查询。您可以在Book 查询的WHERE 子句中看到添加的"active" = True,但在Author 查询中看不到。

    我会在这方面稍作讨论。

    In [1]: Book.objects.filter(title="Hamlet")
    Out[1]: []
    
    In [2]: Author.objects.filter(books__title="Hamlet")
    Out[2]: [<Author: Bill>]
    
    In [3]: print Book.objects.filter(title="Hamlet").query
    SELECT "library_book"."id", "library_book"."active", "library_book"."author_id", "library_book"."title" FROM "library_book" WHERE ("library_book"."active" = True AND "library_book"."title" = Hamlet)
    
    In [4]: print Author.objects.filter(books__title="Hamlet").query
    SELECT "library_author"."id", "library_author"."name" FROM "library_author" INNER JOIN "library_book" ON ("library_author"."id" = "library_book"."author_id") WHERE "library_book"."title" = Hamlet
    

    【讨论】:

    • 不幸的是,这不会改变books__* 查询的行为。我也试过设置base_manager = ActiveOnly(),没有骰子。
    • 这不起作用,因为根据文档,“Django 在访问相关对象时使用 plain 管理器类的实例,而不是相关对象上的默认管理器。”
    【解决方案3】:

    引用documentation

    默认情况下,Django 在访问相关对象(即choice.poll)时使用“普通”管理器类的实例,而不是相关对象上的默认管理器。这是因为 Django 需要能够检索相关对象,即使它会被默认管理器过滤掉(因此无法访问)。

    如果普通的普通管理器类 (django.db.models.Manager) 不适合您的情况,您可以通过设置管理器类的 use_for_related_fields 属性来强制 Django 使用与模型的默认管理器相同的类.

    所以您需要将您的经理更改为:

    class ActiveOnly(models.Manager):
    
        use_for_related_fields = True
    
        def get_queryset(self):
            return super(ActiveOnly, self).get_queryset().filter(active=True)
    

    【讨论】:

    猜你喜欢
    • 2013-02-03
    • 2023-04-06
    • 1970-01-01
    • 2020-05-03
    • 2012-02-13
    • 2012-09-06
    • 1970-01-01
    • 2018-06-30
    相关资源
    最近更新 更多