Reversing a unique Generic Foreign Key with Django

When using GenericRelations with Django to create unique generic foreign keys between objects, it can become tiresome having to navigate the RelatedManager every time you need to retrieve the single instance of the one-to-one relationship. This post shows how creating a quick Mixin can help overcome this inconvenience.

Update

This is an old post. It might not still be the best way to do things. Buyer beware.

Django’s generic foreign keys makes it very easy to create a unique generic object that can be attached to any model instance. The following is a contact card that we can attach to people or users:

models.py
class ContactDetails(models.Model):
    phone_number = models.CharField(...)
    address = models.CharField(...)
 
    #...
 
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')
 
    class Meta:
        unique_together = ('content_type', 'object_id',)
 
class User(models.Model):
    contacts = generic.GenericRelation(ContactDetails, object_id_field="object_pk")
user = User.objects.get(pk=1)
contact_card = user.contacts.all()[0]
print(contact_card.phone_number)

This works, but it’s a little bulky as we have to navigate the RelatedManager every time we want to return the object on the other end of our one-to-one relationship. A simple solution is to create a mixin that will make this transparent:

class ContactMixin(object):
 
    @property
    def contact_card(self):
        ctype = ContentType.objects.get_for_model(self.__class__)
        try:
            contact = Contact.objects.get(content_type__pk = ctype.id, object_id=self.id)
        except Contact.DoesNotExist:
            return None
        return contact
 
    class User(models.Model, ContactMixin):
        pass

Now we don’t have to meddle with the related manager; we can instead access our instance object directly:

user = User.objects.get(pk=1)
print user.contact_card.phone_number