Django中的use_for_related_fields是如何工作的?

19 投票
2 回答
9176 浏览
提问于 2025-04-16 18:00

我看不懂文档里的内容,感觉特别模糊,具体来说:

  • 这个设置是全局的吗?也就是说,如果我在某个模型管理器上指定了这个属性,所有模型类都会用这个设置吗?
  • 如果不是全局设置,那具体会影响哪些关系呢?
  • 可以为同一个模型的不同关系设置不同的模型管理器吗?

最重要的是,我希望能看到一些简单的例子,因为我觉得文档里缺少这些。谢谢。

2 个回答

2

简单来说:在这个bug修复之前,'use-for-related-fields' 在django里是没用的,只对一对一的关系有效。所以如果你的情况是多对多(m2m)或者多对一(m2o),就别费劲了,不然你会失望的。

52

这是一个全局设置吗?如果我在某个模型管理器上指定了这个属性,所有模型类都会使用它吗?

如果我理解你说的“全局”的意思,那么答案是否定的。这个属性只会在某个类中使用,如果该类的默认管理器(类中指定的第一个管理器)设置了这个属性。你可以在多个模型中重用这个管理器,但这个属性只会对那些将其作为默认管理器的类产生影响。

为了更好地理解这个问题,我觉得举个例子会有帮助。我们来看看以下的成员和个人资料模型,它们之间是一对一的关系:

from django.db import models  

class Member(models.Model):
    name = models.CharField(max_length=100)
    active = models.BooleanField()

    def __unicode__(self):
        return self.name


class Profile(models.Model):
    member = models.OneToOneField(Member)
    age = models.PositiveIntegerField()

    def __unicode__(self):
        return str(self.age)

我们将创建几个成员,活跃的约翰和不活跃的菲尔,并为他们各自设置一个个人资料:

>>> m1 = Member(name='John', active=True)
>>> m1.save()
>>> p1 = Profile(member=m1, age=18)
>>> p1.save()
>>> m2 = Member(name='Phil', active=False)
>>> m2.save()
>>> p2 = Profile(member=m2, age=35)
>>> p2.save()

Django如何存储关系

首先,我们来看一下Django是如何存储关系的。我们以约翰的个人资料为例,看看它的命名空间:

>>> p = Profile.objects.get(id=1)
>>> p.__dict__
{'age': 18, '_state': <django.db.models.base.ModelState object at 0x95d054c>, 'id': 1, 'member_id': 1}

在这里,我们可以看到关系是通过存储成员实例的ID值来定义的。当我们引用member属性时,Django会使用一个管理器从数据库中获取成员的详细信息并创建实例。顺便提一下,这些信息会被缓存,以便我们下次使用:

>>> p.member
<Member: John>
>>> p.__dict__
{'age': 18, '_member_cache': <Member: John>, '_state': <django.db.models.base.ModelState object at 0x95d054c>, 'id': 1, 'member_id': 1}

使用哪个管理器

除非另有说明,Django会使用标准管理器来查找这个关系,而不是任何添加到模型中的自定义管理器。例如,假设我们创建了以下管理器,只返回活跃的成员:

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

然后我们将其添加到我们的成员模型中作为默认管理器(在实际情况下,这样做是个坏主意,因为许多工具,比如dumpdata管理命令,专门使用默认管理器,因此一个过滤掉实例的默认管理器可能会导致数据丢失或其他不好的副作用):

class Member(models.Model):
    name = models.CharField(max_length=100)
    active = models.BooleanField()

    objects = ActiveManager()

    def __unicode__(self):
        return self.name

现在这个管理器过滤掉了不活跃的用户,我们只能从数据库中获取约翰的会员信息:

>>> Member.objects.all()
[<Member: John>]

然而,两个个人资料仍然是可用的:

>>> Profile.objects.all()
[<Profile: 18>, <Profile: 35>]

因此,我们可以通过个人资料访问不活跃的成员,因为关系查找仍然使用标准管理器,而不是我们的自定义管理器:

>>> p = Profile.objects.get(id=2)
>>> p.member
<Member: Phil>

但是,如果我们现在在我们的管理器上设置use_for_related_fields属性,这将告诉Django在任何关系查找中必须使用这个管理器:

class ActiveManager(models.Manager):
    use_for_related_fields = True

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

因此,我们无法再通过个人资料获取菲尔的会员记录:

>>> p = Profile.objects.get(id=2)
>>> p.member
---------------------------------------------------------------------------
DoesNotExist                              Traceback (most recent call last)

/home/blair/<ipython console> in <module>()

/usr/lib/pymodules/python2.6/django/db/models/fields/related.pyc in __get__(self, instance, instance_type)
    298             db = router.db_for_read(self.field.rel.to, instance=instance)
    299             if getattr(rel_mgr, 'use_for_related_fields', False):
--> 300                 rel_obj = rel_mgr.using(db).get(**params)
    301             else:
    302                 rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)

/usr/lib/pymodules/python2.6/django/db/models/query.pyc in get(self, *args, **kwargs)
    339         if not num:
    340             raise self.model.DoesNotExist("%s matching query does not exist."
--> 341                     % self.model._meta.object_name)
    342         raise self.model.MultipleObjectsReturned("get() returned more than one %s -- it returned %s! Lookup parameters were %s"
    343                 % (self.model._meta.object_name, num, kwargs))

DoesNotExist: Member matching query does not exist.

请注意,这只有在自定义管理器是模型的默认管理器(即第一个定义的管理器)时才会生效。所以,让我们尝试将标准管理器作为默认管理器,而将我们的自定义管理器作为辅助管理器:

class Member(models.Model):
    name = models.CharField(max_length=100)
    active = models.BooleanField()

    objects = models.Manager()
    active_members = ActiveManager()

    def __unicode__(self):
        return self.name

当直接查看成员时,这两个管理器的工作效果如预期:

>>> Member.objects.all()
[<Member: John>, <Member: Phil>]
>>> Member.active_members.all()
[<Member: John>]

而且由于默认管理器可以检索所有对象,关系查找也成功了:

>>> Profile.objects.get(id=2)
>>> p.member
<Member: Phil>

现实情况

到这里,你可能会发现我为什么选择一对一关系作为示例模型。实际上(与文档相悖),use_for_related_fields属性仅在一对一关系中使用。外键和多对多关系会忽略它。这是Django跟踪器中的票据 #14891

是否可以为同一模型的一个关系使用一个模型管理器,而为另一个关系使用另一个模型管理器?

不可以。不过,在关于上述错误的讨论中,这在未来被提到过作为一种可能性。

撰写回答