How does use_for_related_fields work in Django?

前端 未结 2 458
一整个雨季
一整个雨季 2021-02-02 12:47

I\'m unable to grasp that from the docs. It\'s totally unclear to me, more specifically:

  • Is it a global setting? So if I specify this attribute it on one of the mo
2条回答
  •  北海茫月
    2021-02-02 13:33

    Is it a global setting? So if I specify this attribute it on one of the model managers, will it be used globally by all the model classes?

    If I have understood what you mean by global, the answer is no. It will only be used for a class if the default manager (the first manager specified in the class) has it set. You could re-use the manager in several models, and the attribute would only have an effect on those classes it was the default manager for.

    This is something I thought an example would help with understanding. Lets use the following member and profile models, linked in a one-to-one relationship:

    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)
    

    We'll create a couple of members, the active John and the inactive Phil, and set up a profile for each of them:

    >>> 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()
    

    How Django stores relationships

    First, lets look at how Django stores a relationship. We'll take John's profile and look at its namespace:

    >>> p = Profile.objects.get(id=1)
    >>> p.__dict__
    {'age': 18, '_state': , 'id': 1, 'member_id': 1}
    

    Here we can see the relationship is defined by storing the ID value of the member instance. When we reference the member attribute, Django uses a manager to retrieve the member details from the database and create the instance. Incidentally, this information is then cached in case we want to use it again:

    >>> p.member
    
    >>> p.__dict__
    {'age': 18, '_member_cache': , '_state': , 'id': 1, 'member_id': 1}
    

    Which manager to use

    Unless told otherwise, Django uses a standard manager for this relationship lookup rather than any custom manager added to the model. For example, say we created the following manager to only return active members:

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

    And we then added it to our Member model as the default manager (in real life, this is a Bad Idea™ as a number of utilities, such as the dumpdata management command, exclusively use the default manager, and hence a default manager which filters out instances could lead to lost data or similar nasty side effects):

    class Member(models.Model):
        name = models.CharField(max_length=100)
        active = models.BooleanField()
    
        objects = ActiveManager()
    
        def __unicode__(self):
            return self.name
    

    Now that the manager filters out inactive users, we can only retrieve John's membership from the database:

    >>> Member.objects.all()
    []
    

    However, both profiles are available:

    >>> Profile.objects.all()
    [, ]
    

    And hence we can get to the inactive member through the profile, as the relationship lookup still uses a standard manager rather than our custom one:

    >>> p = Profile.objects.get(id=2)
    >>> p.member
    
    

    However, if We now set the use_for_related_fields attribute on our manager, this will tell Django it must use this manager for any relationship lookups:

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

    Hence we can no longer get Phil's membership record from his profile:

    >>> p = Profile.objects.get(id=2)
    >>> p.member
    ---------------------------------------------------------------------------
    DoesNotExist                              Traceback (most recent call last)
    
    /home/blair/ in ()
    
    /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.
    

    Note that this only has an effect if the custom manager is the default manager for the model (i.e., it is the first manager defined). So, lets try using the standard manager as the default manager, and our custom one as a secondary manager:

    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
    

    The two managers work as expected when looking directly at the members:

    >>> Member.objects.all()
    [, ]
    >>> Member.active_members.all()
    []
    

    And since the default manager can retrieve all objects, the relationship lookup succeeds too:

    >>> Profile.objects.get(id=2)
    >>> p.member
    
    

    The reality

    Having made it this far, you now find out why I chose a one-to-one relationship for the example models. It turns out that in reality (and in contradiction of the documentation) the use_for_related_fields attribute is only used for one-to-one relationships. Foreign key and many-to-many relationships ignore it. This is ticket #14891 in the Django tracker.

    Is it possible to have one model manager for one relationship and another one for another relationship with the same model?

    No. Although, in this discussion of the bug mentioned above this came up as a possibility in the future.

提交回复
热议问题