Exploring Mixins with Django Model Inheritance

Django now supports Model Inheritance, and one of the coolest new opportunities that model inheritance brings is the possibility of the creation of mixins, so in this post I'll walk through the steps I went through to create some simple examples. This is just an excercise (although it could be modified to be more robust)--and right now there are better ways to achieve all of the effects of the following mixins. (See django-mptt, for example).

Model and Field Setup

First let's just set up two basic models. The first will be our mixin, NaiveHierarchy, which has a single field, parent, which is a reference to itself. Using this, we can traverse the tree and find all sorts of fun hierarchical information. Also, we'll create the canonical example model: the blog post. Our models start out looking something like this:

from django.db import models

class NaiveHierarchy(models.Model):
    parent = models.ForeignKey('self', null=True)

    class Meta:
        abstract = True

class BlogPost(NaiveHierarchy):
    title = models.CharField(max_length = 128)
    body = models.TextField()

    def __unicode__(self):
        return self.title

Now let's test to make sure that worked. We'll create some data and test that parent exists on the instances.

>>> from mixins.models import BlogPost
>>> bp = BlogPost.objects.create(title="post1", body="First post!")
>>> bp2 = BlogPost.objects.create(title="post2", body="Second post!", parent=bp)
>>> bp3 = BlogPost.objects.create(title="post3", body="Third post!", parent=bp2)
>>> bp.parent
>>> bp2.parent
<BlogPost: post1>

Inherited Class-Level Methods

So as you can see, everything is working correctly! But that really doesn't save us much time yet, as it's fairly easy to copy and paste fields onto new models, and we still have to write methods which take advantage of those new fields. In this case, I already know that I'm going to want to get the related children and descendants of my blogposts. So why not write those methods on the abstract model? Thanks to inheritance, those methods will apply to the new model as well.

class NaiveHierarchy(models.Model):
    parent = models.ForeignKey('self', null=True)

    def get_children(self):
        return self._default_manager.filter(parent=self)

    def get_descendants(self):
        descs = set(self.get_children())
        for node in list(descs):
            descs.update(node.get_descendants())
        return descs

    class Meta:
        abstract = True

Now, getting all the children or descendents of a particular node is easy:

>>> bp.get_children()
[<BlogPost: post2>]
>>> bp.get_descendants()
set([<BlogPost: post2>, <BlogPost: post3>])

Now this NaiveHierarchy mixin is starting to become quite useful! But what happens if I want to get all of the BlogPosts that have no parents? It's really manager-level functionality. So let's write a manager which defines a get_roots function. Unfortunately, using abstract managers doesn't quite work yet (it works for non-abstract inheritance), but it probably will in future versions of Django. In fact, by applying the latest patch on either Django ticket 7252 or 7154, it will work today. Let's see how this would look:

class NaiveHierarchyManager(models.Manager):
    def get_roots(self):
        return self.get_query_set().filter(parent__isnull=True)

class NaiveHierarchy(models.Model):
    parent = models.ForeignKey('self', null=True)

    tree = NaiveHierarchyManager()

    def get_children(self):
        return self._default_manager.filter(parent=self)

    def get_descendants(self):
        descs = set(self.get_children())
        for node in list(descs):
            descs.update(node.get_descendants())
        return descs

    class Meta:
        abstract = True

class BlogPost(NaiveHierarchy):
    title = models.CharField(max_length = 128)
    body = models.TextField()

    objects = models.Manager()

    def __unicode__(self):
        return self.title

Note that we needed to explicitly define objects as the basic manager, because once a parent class specifies a manager, it gets set as the default manager on all inherited subclasses. This would play out exactly how you would expect:

>>> BlogPost.tree.get_roots()
[<BlogPost: post1>]
>>> BlogPost.tree.all()
[<BlogPost: post1>, <BlogPost: post2>, <BlogPost: post3>]

Advanced Stuff

So now I really wanted to push the limit, and write a mixin which would enhance one of the basic methods of all Model classes: save(). This would be a DateMixin which would contain date_added and date_modified, where date_modified was updated on each save. To my surprise, this Just Worked. Let's see the final result:

import datetime
from django.db import models

class DateMixin(models.Model):
    date_added = models.DateTimeField(default=datetime.datetime.now)
    date_modified = models.DateTimeField()

    def save(self):
        self.date_modified = datetime.datetime.now()
        super(DateMixin, self).save()

class NaiveHierarchyManager(models.Manager):
    def get_roots(self):
        return self.get_query_set().filter(parent__isnull=True)

class NaiveHierarchy(models.Model):
    parent = models.ForeignKey('self', null=True)

    tree = NaiveHierarchyManager()

    def get_children(self):
        return self._default_manager.filter(parent=self)

    def get_descendants(self):
        descs = set(self.get_children())
        for node in list(descs):
            descs.update(node.get_descendants())
        return descs

    class Meta:
        abstract = True

class BlogPost(NaiveHierarchy, DateMixin):
    title = models.CharField(max_length = 128)
    body = models.TextField()

    objects = models.Manager()

    def __unicode__(self):
        return self.title

Conclusions

Mixins can be powerful tools, but there are some hazards in using mixins, which all boil down to the same basic problem: unexpected consequences. In the case of the DateMixin, if any other class has defined a save() method, our custom save() method simply won't be called unless called explicitly. Perhaps this is a documentation problem, but perhaps it's a fault in the idea of a date mixin altogether.

So all that being said, I'm not suggesting to go off and start using any of the mixins that I have provided here, but rather to illustrate how a mixin can be constructed with Django's new Model Inheritance. I do hope that a reusable app emerges with some great mixins that are useful for a large variety of tasks. Because mixins are powerful, and new shiny things that Django can do, and new shiny things are worth being explored!

29 Comments So Far...

By Orestis Markou at 4:50 a.m. on May 17, 2008

You might want to split up the double Meta: abstract = True in the last example.

Also, the DateAddedMixin is referenced in the first example, but it's not defined yet.

Good ideas overall! Model inheritance looks very good, I can see lots of "plugins" being created, in the :acts_as_* fashion that Rails has...

 

By Eric Florenzano at 5:03 a.m. on May 17, 2008

Whoops! Good catches, thanks. Fixing now.

 

By Thierry Schellenbach at 7:25 a.m. on May 17, 2008

I came from a Symfony background and am glad to see this kind of functionality in Django :)
It is quite similar in purpose to propel behaviors for Symfony :)

 

By sean at 11:21 a.m. on May 17, 2008

model inheritance would really help me a lot in designing models!

 

By mb0 at 11:29 a.m. on May 17, 2008

what about adding another method datemixin_save that is called by default? clients can override the save method and only need to call the mixin save method of the parent class... easy enough, but not tested.

 

By Eric Flo at 11:38 a.m. on May 17, 2008

You know, that's a really great idea. This could be done automatically with a small patch to Model.save(). Maybe I'll write up a patch later!

 

By kevin at 12:02 p.m. on May 17, 2008

eric, great post. looking forward to some interesting implementations in the future.

also, we're talking model inheritance this week at django-nyc meetup, and we now have another great reference/discussion point.

cheers!

 

By Anonymous at 1:02 p.m. on May 17, 2008

Could someone show how to use the DateMixin alone?

 

By Alex at 1:31 p.m. on May 17, 2008

You just subclass it alone, the behavior is self contained.

 

By Carl Meyer at 6:58 p.m. on May 17, 2008

You don't give the DateMixin enough credit. I've been using something very much like that in production ever since qs-rf merged. Anytime any model overrides save() it should use super() to call the default save() from models.Model. If that model is now a subclass of DateMixin instead of Model, the identical super() call will call DateMixin.save(), which will in turn call Model.save() - in other words, it all just works. The subclass doesn't need to do anything different than it would if subclassing Model normally.

 

By hachaboob at 7:58 p.m. on May 17, 2008

Nice example! An excellent way to stay DRYer in the model.

 

By anon at 5:41 a.m. on May 18, 2008

BlogPost.tree.get_roots() generates an attribute error for me on django revision 7538, anybody experiencing this...

AttributeError: 'Options' object has no attribute '_join_cache' happens in

'''
/usr/local/lib/python2.5/site-packages/django/db/models/sql/query.py in setup_joins(self, names, opts, alias, dupe_multis, allow_many, allow_explicit_fk, can_reuse)
1121 opts.pk.column), exclusions=joins)
1122 joins.append(alias)
-> 1123 cached_data = opts._join_cache.get(name)
1124 orig_opts = opts
1125
'''

 

By Eric Florenzano at 3:21 p.m. on May 18, 2008

Yes, this a result of the manager which doesn't yet work with Django trunk. I still suggest checking out

http://code.djangoproject.com/ticket/7252
and http://code.djangoproject.com/ticket/7154

 

By anon at 5:56 a.m. on May 18, 2008

BlogPost.tree.get_roots() does work when tree = NaiveHierarchyManager() is defined under BlogPost

 

By Dewayne Curry at 9:48 p.m. on June 2, 2008

undersleep piaffe scotomata methine anisotropism seres antihysteric unpoisonable
<a href= http://freebizadsweb.com/tablemaker/tablemaker.asp >Button Maker</a>
http://www.cleversley.com/

 

By Jeffery Witt at 7:24 a.m. on June 4, 2008

undersleep piaffe scotomata methine anisotropism seres antihysteric unpoisonable
<a href= http://www.theoxfordgroup.com/ >The Oxford Group</a>
http://www.winspec.com/

 

By Rosalie Dunlap at 3:49 a.m. on June 8, 2008

undersleep piaffe scotomata methine anisotropism seres antihysteric unpoisonable
<a href= http://www.creative.com.cy/ >Creative Tours</a>
http://www.thisisthenortheast.co.uk/the_north_east/newton_aycliffe

 

By ben 10 oyunları at 3:06 a.m. on May 25, 2009

You know, that's a really great idea. This could be done automatically with a small patch to Model.save(). Maybe I'll write up a patch later!

 

By wholesale jewelry at 4:26 a.m. on June 1, 2009

Nice blog.thanks for your sharing.

 

By jimmy poiy at 1:16 p.m. on June 8, 2009

nike tip, thanks a lot.

 

By wow power leveling at 4:53 a.m. on June 17, 2009

Great work, u can say it again.

 

By wholesale Lingerie at 8:12 p.m. on June 22, 2009

Web developers often use caches off the database due to a few aspects inherent with databases. Computing values from a normalized database takes time, and so it's a good target for memorization. Even if it's cheap to compute or pull at the database

 

By jordan shoes at 3:03 a.m. on June 25, 2009

undersleep piaffe scotomata methine anisotropism seres antihysteric unpoisonable

 

By jordan shoes at 3:03 a.m. on June 25, 2009

You know, that's a really great idea. This could be done automatically with a small patch to Model.save(). Maybe I'll write up a patch later!

 

By ugg boots at 3:04 a.m. on June 25, 2009

Great work, u can say it again.

 

By nike shoes at 3:04 a.m. on June 25, 2009

nike tip, thanks a lot.

 

By tiffany jewellery at 3:05 a.m. on June 25, 2009

Nice blog.thanks for your sharing.

 

By lingerie wholesale at 9:59 a.m. on June 28, 2009

I think you will make these projects into a success also!

 

By sare at 2 a.m. on July 2, 2009

 

Voice your opinion...