April 23rd, 2010

Generic Field Filtering in Django by charlie

Creating a generic and reusable application needs a lot of time and effort but thankfully Django framework provides us with many convenient tools. This time I will write about how to filter a model with Django’s contenttypes framework. The contenttypes framework is useful, especially when you want to create a generic model that can reference any other model listed in your application such as historical data of an object.

For example, I have these models in my application :

class History(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')
    action_time = models.DateTimeField(auto_now=True)
    message = models.CharField(max_length=100)
    action_type = models.PositiveSmallIntegerField()

class SalesOrder(models.Model):
    subject = models.CharField(max_length=100, db_index=True)
    client = models.CharField(max_length=100)
    value = models.PositiveIntegerField()

class PurchaseOrder(models.Model):
    subject = models.CharField(max_length=100, db_index=True)
    vendor = models.ForeignKey(Contact, db_index=True)
    value = models.PositiveIntegerField()

Sometimes we want to filter the history model by instance, but unfortunately Django’s filter method does not allow us to directly filter the model by content_object i.e. (history_list = History.objects.filter(content_object=SalesOrder.objects.get(pk = 1)) <– this won’t do).

There are a few ways to get around this problem, but I’ll try to explain how to do it in the simplest yet reliable way (since I’m new in python-django programming, there might be another way to solve this case more efficiently).

From the model declarations above we can assume that a history instance can refer to both SalesOrder’s or PurchaseOrder’s instances. So how do I filter for all history instances referring to a specific SalesOrder? Here is how:

#We first get the SalesOrder object :
salesorder = SalesOrder.objects.get(pk=1)

#Then we need to get the SalesOrder's content type:
content_type = ContentType.objects.get_for_model(salesorder)
#ContentType is a model in django that store information about the models installed in your project, and new instances of ContentTypes are automatically created whenever new models are installed.
#and the final step :
history_list = History.objects.filter(content_type=content_type, object_id = salesorder.id)

We can also do it without first getting the object instance to save an extra database hit, assuming we already know the primary key of the object we want to filter for. Here is how we do it if we want to filter for a PurchaseOrder instance

content_type = ContentType.objects.get(model=PurchaseOrder._meta.module_name)
#and do filtering as in the first example given :
history_list = History.objects.filter(content_type=content_type, object_id = purchaseorder_id)

Easy isn’t it? Happy trying and don’t give up too fast ^^.

bible : http://docs.djangoproject.com/en/1.1/ref/contrib/contenttypes/#ref-contrib-contenttypes

Copyright © 2020 User Inspired Technology Services.