Filter by generic field

One of my favorite features in Django is the contenttypes framework.  Through this, you can easily create a generic model that can reference to any model within your project’s apps.  Let’s say my project has these models:

class Transaction(models.Model):
transaction_type = models.CharField(max_length=1, choices=CATEGORY_TRANSACTION_TYPES)
paid_by = models.CharField(max_length=2, choices=PAYMENT_METHOD, default='CH')
amount = models.DecimalField(max_digits=11, decimal_places=2, verbose_name='Amount')
notes = models.CharField(max_length=255, blank=True, verbose_name='Optional additional information about payment')
content_type = models.ForeignKey(ContentType, related_name='related_user_record')
object_id = models.PositiveIntegerField(blank=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
amount = "{0:,f}".format(self.amount)
return u"%s: %s (%s)" % (self.get_transaction_type_display(), amount, self.get_paid_by_display())

class Student(models.Model):
admission_number = models.CharField(max_length=15)
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255, null=True, blank=True)
last_name = models.CharField(max_length=255)
profile_picture = ImageField(upload_to=settings.USER_AVATAR_PATH + '/students', null=True, blank=True)

One of the possible queries you might need based on these two classes is filtering the Transaction class by model instance to get all payments related to a student record.  Unfortunately, we can’t query directly by the content_object field like this:

transactions = Transaction.objects.filter(content_object=Student.objects.get(pk = 1))

Luckily, there’s a simple way to do this. There may be another way to do this, but this is the solution I came up with:

from django.contrib.contenttypes.models import ContentType
c_type= ContentType.objects.get(model=Student._meta.module_name)
Transaction.objects.filter(content_type=c_type, object_id=pk)

And finally to make all this DRY, I plugged it into the Student class as a property like so:

@property
def payments(self):
from ..finances.models import Transaction
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get_for_model(self)
return Transaction.objects.filter(content_type=content_type, object_id=self.pk)

And that’s all we need to filter our records

Advertisements