Django覆盖查询中双下划线关系查找的行为

时间:2016-07-20 00:30:11

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

我的主要问题:有没有办法改变相关查找的行为,例如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上添加其他过滤器?

3 个答案:

答案 0 :(得分:2)

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中的对象仍然存在!

答案 1 :(得分:1)

引用documentation

  

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

     

如果普通的普通管理器类(django.db.models.Manager)不适合您的情况,可以通过在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)

答案 2 :(得分:0)

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

我已经将代码添加到github repo中,并且测试说明了如果有人想要将其用于旋转的问题。 https://github.com/alecklandgraf/NonDeletableDjangoModel

原帖:

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

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相同。有趣的是您已经说明的创建的查询。您可以在"active" = True查询的WHERE子句中看到添加的Book,但不会在Author查询中看到。{/ p>

我会在这方面吃一段时间。

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