Extending Django's Model Class

Mar 2, 2007 — Whitney Young

Django's Model class is intended to be subclassed when creating a model for your application. That's about the only intended use. But what if you want to add some extra functionality to Django's model class that all of your models can use?

You would think that something like this:

from django.db import models
class M(models.Model):
  def something_special(self): pass
class MyModel(M):
  field = models.CharField(maxlength=255)

would work just fine, but it doesn't work as desired. There's a good reason why it doesn't. Here's the MySQL code that Django produces for this setup:

BEGIN;
CREATE TABLE `myapp_mymodel` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `field` varchar(255) NOT NULL
);
CREATE TABLE `myapp_m` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY
);
COMMIT;

which helps explain why it doesn't work as desired. Django considers the M class a valid model, one that can be saved to the database, so it creates an entry in the database for it. We don't need or want it in the database, though. We just want to extend the basic model class.

Here's how to go about this. You have to trick Django into thinking that you're registering a model that only inherits from Django's model class.

from django.db import models as standard_models
from django.db.models.base import ModelBase
class Model(standard_models.Model): pass
class IntermediateModelBase(ModelBase):
  def __new__(cls, name, bases, attrs):
    if Model in bases:
      if bases == (Model,):
        # create the class via the super method
        newclass = ModelBase.__new__(ModelBase, name, (standard_models.Model,), attrs)
        # but then make it inherit from our model class
        newclass.__bases__ = (Model,)
        return newclass
      else: raise Exception, "IntermediateModelBase does not support more than one base"
    else:
      return type.__new__(cls, name, bases, attrs)
class Model(standard_models.Model):
  __metaclass__ = IntermediateModelBase
  def something_special(self): pass

If you put this code into its own module and import it after importing Django's Model class, then you have a plug and play replacement for Django's Model with you own special additions.

I hope this helps someone. It works great for me. If you find a better way of doing this, let me know.

Tags: Code, Django

Comments
Chris Apr 27, 2007

I just came across this, and it's already very helpful. However, I was wondering if something similar could be used to add certain fields to all models. For example, if you wanted to a TextField named description.

Whitney Young Apr 27, 2007

Sure Chris. Do something like this:

# create the class via the super method
# and add a description field to all models
attrs['description'] = models.CharField(maxlength=255)
newclass = ModelBase.__new__(ModelBase, name, (standard_models.Model,), attrs)

Just put that one line of code right there in the innermost if statement.

HelloWorld Apr 28, 2007

Peace people

We love you

Chris May 1, 2007

Thanks so much. That was just what I needed.