Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does use_for_related_fields work in Django?

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 model managers, will it be used globally by all the model classes?
  • If it's not a global setting then which relationships exactly will be affected?
  • Is it possible to have one model manager for one relationship and another one for another relationship with the same model?

Most of all I would appreciate any good minimal example usages as the documentation lacks those afaik. Thanks.

like image 263
julx Avatar asked May 20 '11 03:05

julx


2 Answers

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': <django.db.models.base.ModelState object at 0x95d054c>, '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
<Member: John>
>>> p.__dict__
{'age': 18, '_member_cache': <Member: John>, '_state': <django.db.models.base.ModelState object at 0x95d054c>, '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()
[<Member: John>]

However, both profiles are available:

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

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
<Member: Phil>

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/<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.

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: John>, <Member: Phil>]
>>> Member.active_members.all()
[<Member: John>]

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

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

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.

like image 57
Blair Avatar answered Oct 11 '22 15:10

Blair


The short answer is: until this bug is fixed, 'use-for-related-fields' doesn't work in django, except for one-to-one relationships, so don't bother if your use case is m2m or m2one, or you'll be disappointed.

like image 32
B Robster Avatar answered Oct 11 '22 14:10

B Robster