🤯 2020 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")
bash
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:
models.py
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:
bash
user = User.objects.get(pk=1)
print user.contact_card.phone_number