Wednesday, May 21, 2014

Determining if a Manager is being used by a related object.

Recently, I wanted to figure out if a custom manager was being called by a related class or the original class. As an example, consider this abbreviated code:

class MembershipManager(models.Manager):
    def get_by_type(self, type, user=None):
        # figure out if we need user or not...
        # then call self.get_queryset().get()
        pass


class Membership(models.Model):
    user = models.ForeignKey('auth.User', related_name='memberships')
    m_type = models.CharField(max_length=200)


    objects = MembershipManager()

So... how does get_by_type know if it's being called as "user.memberships.get_by_type(foo)" or "Membership.objects.get_by_type(foo, user=bar)?" Well, I knew that related managers pre-filter on the object they're related to, so I just had to find the code that does that.

It's right here in the ForeignRelatedObjectsDescriptor class. The interesting bit is:

    def related_manager_cls(self):
        # Dynamically create a class that subclasses the related model's default
        # manager.
        superclass = self.related.model._default_manager.__class__
        rel_field = self.related.field
        rel_model = self.related.model

        class RelatedManager(superclass):
            def __init__(self, instance):
                super(RelatedManager, self).__init__()
                self.instance = instance
                self.core_filters= {'%s__exact' % rel_field.name: instance}
                self.model = rel_model

This is a class factory that, in this case, subclasses MembershipManager when creating the attribute user.memberships (actually, that's not quite true. user.memberships is actually the ForeignRelatedObjectsDescriptor which hands out this dynamically created, and unnamed, subclass of MembershipManager. The descriptor is used as a proxy so that User.memberships doesn't return anything that actually is a related manager because it would be misleading at best.)

So, long story short, if self.instance exists when `get_by_type` is called, we're in a related object subclass and you can check if self.instance is a User object, in which case you can then allow the caller to skip the user keyword argument.

This is probably documented somewhere on the Django site, but I couldn't find it easily.

1 comment:

  1. “Benjamin Briel Lee was very professional at all times, keeping me aware of everything that was happening, If I had any questions he was always available to answer. This was my first home purchase, I didn’t know much about the loan process, he made it very easy to understand the things I had questions about. I really enjoyed working with him.”  
    He's a loan officer working with a group of investor's who are willing to fund any project or loan you any amount with a very low interest.Contact Benjamin Briel Lee E-Mail: 247officedept@gmail.com  Whats-App Number: +1-989-394-3740.

    ReplyDelete