As a developer with DBA tendencies one thing that always drove me (and all of our actual DBA's) crazy, is Django's default naming of PK fields to 'id'. This leads to sloppy looking, sometimes incomprehensible, joins and just generally upsets database admins. We long ago adopted the rule "every model gets an AutoField named <model_name>_id defined", but it always bothered me a bit having to rely on the devs to get it right. I prefer to let the computers enforce the sanity.

Django's docs didn't provide much in the way of advice on this and my googling didn't turn up anything useful either. So eventually I decided to dive a little deeper and hit the Django source directly. What I discovered was something of a mess. Django's Model class has a metaclass of ModelBase which instantiate's a new Options class and attaches it to your new model as _meta. This Options class applies the default pk field, parses the class Meta that is attached to your model, and even does a bit of validation to ensure that you are making a valid model.

With my new knowledge in hand it seemed like there would be a LOT of code that would have to be copy and pasted just to override the _prepare method of Options. Re-creating and managing 5-600 lines of code just to override a single line of a single method is not my idea of a clean solution. Fortunately we can actually use some metaclass magic in our favor and do it in just a few lines!

Extending from the default metaclass ModelBase, we can simply inspect the model that's about to be created and tack an attribute to the class named <modelname>_id which is just an instance of a Django AutoField.

from django.db import models
from django.utils import six


class AutoPKModelBase(models.base.ModelBase):
    '''Defaults auto generated pk field name to <modelname>_id '''

    def __new__(cls, name, bases, attrs):
        supr = super(AutoPKModelBase, cls)

        pk_name = "{}_id".format(name.lower())

        # Skip modifying if the class is the abstract base or if a model
        # has explicitly defined a field named <modelname>_id
        if name != 'AbstractBaseModel' and pk_name not in attrs:
            attrs[pk_name] = models.AutoField(primary_key=True)

        # Now that we effectively patched our Model with a PK field,
        # let django take the wheel.
        return supr.__new__(cls, name, bases, attrs)


# We could just define __metaclass__, but using six we can maintain py3k compat
class AbstractBaseModel(models.Model, six.with_metaclass(AutoPKModelBase)):
    '''Simple abstract base model '''

    class Meta(TimeStampedModel.Meta):
        abstract = True

Note: Only tested with Django 1.7, but I don't see anything post-1.5 that would make this not work. Also this is a bit lazy. All that it does is enforce that there is a correctly named field, not actually enforcing the type of that field. This could be easily expanded if your needs are a bit stricter.

Now any Model that you create that inherits from your base will automatically have a more explicitly named primary key field. Cheers!


References