Login

Get actual child model in multi-table inheritance

Author:
bronger
Posted:
July 3, 2010
Language:
Python
Version:
1.2
Score:
1 (after 1 ratings)

A common problem (it hit the Django mailinglist a couple of times) is that if you get models.Topping.objects.all(), you get a list of toppings, although they stand for other classes such as SalamiTopping or CheeseTopping. If you need the actual object, just derive Topping from PolymorphicModel, and say topping.actual_instance. This will give you e.g. a SalamiTopping.

Sometimes you just want to check for the actual class. You can get it by saying topping.content_type.model_class().

There is a slight performance impact when creating objects because they have to be saved twice.

NEWS: A good alternative to this approach is the InheritanceManager.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class PolymorphicModel(models.Model):
    u"""Abstract model class, which provides the attribute ``actual_instance``.
    This solves the problem that Django's ORM does not implement automatic
    resolution of polymorphy.  For example, if you get a list of Toppings,
    they're just Toppings.  However sometimes, you must have the actual object,
    i.e. CheeseTopping, SalamiTopping etc.  Then, ``topping.actual_instance``
    will give just that.

    Simply derive the top-level model class from this one, and then you can
    easily resolve polymorphy in it and its derived classes.
    """
    content_type = models.ForeignKey(ContentType, null=True, blank=True)
    actual_object_id = models.PositiveIntegerField(null=True, blank=True)
    actual_instance = generic.GenericForeignKey("content_type", "actual_object_id")

    def save(self, *args, **kwargs):
        u"""Saves the instance and assures that `actual_instance` is set.
        """
        super(PolymorphicModel, self).save(*args, **kwargs)
        if not self.actual_object_id:
            self.actual_instance = self
            super(PolymorphicModel, self).save()

    class Meta:
        abstract = True

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

gmandx (on July 3, 2010):

Why not set self.actual_instance before super(PolymorphicModel, self).save()?

#

bronger (on July 3, 2010):

Hmm ... I haven't tried that, to be honest. However, the object must have a primary key already, which it gets after it has been saved to the DB for the first time.

#

felix_the_third (on July 3, 2010):
content_type = models.ForeignKey(ContentType,editable=False,null=True)


def save(self, force_insert=False, force_update=False,using=None):
    if(not self.content_type):
        self.content_type = ContentType.objects.get_for_model(self.__class__)
    self.save_base(force_insert=force_insert, force_update=force_update,using=using)

def as_leaf_class(self):
    if not type(self) == AbsMail:
        return self
    content_type = self.content_type
    model = content_type.model_class()
    if(model == AbsMail):
        # AbsMail is abstract although it has a db table
        raise Exception("Found instance of AbsMail.")
    return model.objects.get(id=self.id)

just call thing.as_leaf_class()

#

bronger (on July 3, 2010):

I used a similar method (called find_actual_instance()) in my first attempt, but I think it makes sense to optimise for reading instead of writing. Therefore, my snippet is especially cheap when you try to retrieve the instance.

#

Please login first before commenting.