Model Inheritance in Django

Models inheritance works the same way as normal Python class inheritance works, the only difference is, whether we want the parent models to have their own table in the database or not. When the parent model tables are not created as tables it just acts as a container for common fields and methods.

There are three styles of inheritance possible in Django.

  • Abstract base classes : Use this when the parent class contains common fields and the parent class table is not desirable.
  • Multi-table inheritance : Use this when the parent class has common fields, but the parent class table also exists in the database all by itself.
  • Proxy models : Use this when you want to modify the behavior of the parent class, like by changing orderby or a new model manager.

Abstract base classes

from django.db import models


class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class Address(TimestampedModel):
    state = models.CharField(max_length=10)
    country = models.CharField(max_length=10)
    pin_code = models.IntegerField()

    def __str__(self):
        return self.country

When we run makemigrations for the above model changes, only Address model changes will be there. Please note that in the Meta class of TimestampedModel we have put abstract=True, this tells Django, not to create a database table for the corresponding table.

Migration
class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Address',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('created_at', models.DateTimeField(auto_now_add=True)),
                ('updated_at', models.DateTimeField(auto_now=True)),
                ('state', models.CharField(max_length=10)),
                ('country', models.CharField(max_length=10)),
                ('pin_code', models.IntegerField()),
            ],
            options={
                'abstract': False,
            },
        ),
    ]

Going forward if we inherit from the TimestampedModel, that would by default add created_at and updated_at, with this, we achieve DRY (Don't Repeat Yourself) and a cleaner way to code.

childClass now has a nice method to soft-delete objects. Let’s see, how it works.

from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone


class BaseSoftDeletableModel(models.Model):
    is_deleted = models.BooleanField(default=False)
    deleted_at = models.DateTimeField(null=True)
    deleted_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True)

    class Meta:
        abstract = True

    def soft_delete(self, user_id=None):
        self.is_deleted = True
        self.deleted_by = user_id
        self.deleted_at = timezone.now()
        self.save()


class Address(TimestampedModel, BaseSoftDeletableModel):
    state = models.CharField(max=10)
    country = models.CharField(max=10)
    pin_code = models.IntegerField()

    def __str__(self):
        return self,country

Now Let’s open the django shell by running command python manage.py shell.

In [1]: from common.models import Address
In [2]: address = Address.objects.create(state='DL', country='IN', pin_code=110011)


In [3]: address.__dict__
Out[3]: 
{
    '_state': <django.db.models.base.ModelState object at 0x102d573a0>,
    'id': 1,
    'created_at': datetime.datetime(2022, 7, 29, 13, 11, 30, 557322, tzinfo=<UTC>),
    'updated_at': datetime.datetime(2022, 7, 29, 13, 11, 30, 557372, tzinfo=<UTC>),
    'is_deleted': False,
    'deleted_at': None,
    'deleted_by_id': None,
    'state': 'DL',
    'country': 'IN',
    'pin_code': 110011
}

In [4]: address.soft_delete()

In [5]: address.__dict__
Out[5]: 
{
    '_state': <django.db.models.base.ModelState object at 0x102d573a0>,
    'id': 1,
    'created_at': datetime.datetime(2022, 7, 29, 13, 11, 30, 557322, tzinfo=<UTC>),
    'updated_at': datetime.datetime(2022, 7, 29, 13, 13, 38, 232611, tzinfo=<UTC>),
    'is_deleted': True,
    'deleted_at': datetime.datetime(2022, 7, 29, 13, 13, 38, 231991, tzinfo=<UTC>),
    'deleted_by_id': None,
    'state': 'DL',
    'country': 'IN',
    'pin_code': 110011
}

Multi-table Inheritance

In the above example of Address class, although it is inheriting from multiple parent tables, it is still an example of the abstract base class inheritance style, since both the parent classes are abstract in nature.

For multi-table inheritance, parentClass's table also gets created in the database. The name multi-table comes from the fact that multiple tables actually gets created in the database and not because the childClass is inheriting multiple parentClass.

from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone


class BaseSoftDeletableModel(models.Model):
    is_deleted = models.Boolean(default=False)
    deleted_at = models.DateTimeField(null=True)
    deleted_by = models.ForeignKey(User, null=True)

    class Meta:
        abstract = True

    def soft_delete(self, user_id=None):
        self.is_deleted = True
        self.deleted_by = user_id
        self.deleted_at = timezone.now()
        self.save()


class Address(TimestampedModel, BaseSoftDeletableModel):
    state = models.CharField(max=10)
    country = models.CharField(max=10)
    pin_code = models.IntegerField()

    def __str__(self):
        return self.country


class Place(Address):
    name = models.CharField(max_length=10)

    def __str__(self):
        return self.name

Again, Open the Django Shell python manage.py shell

Now, when we will create a Place instance, it will also create an entry for address in the database.

In [1]: from common.models import Address, Place

In [2]: place = Place.objects.create(name='home', state='KR', country='IN', pin_code=13123)

In [3]: place.__dict__
Out[3]: 
{
    '_state': <django.db.models.base.ModelState object at 0x103cce9d0>,
    'id': 2,
    'created_at': datetime.datetime(2022, 7, 29, 13, 18, 6, 259917, tzinfo=<UTC>),
    'updated_at': datetime.datetime(2022, 7, 29, 13, 18, 6, 259963, tzinfo=<UTC>),
    'is_deleted': False,
    'deleted_at': None,
    'deleted_by_id': None,
    'state': 'DL',
    'country': 'IN',
    'pin_code': 110022,
    'address_ptr_id': 2,
    'name': 'home'
}

In [4]: Address.objects.count()
Out[4]: 2

Here you can see, 1 entry for address also got created.

Source Code. github

Explore More Django Posts

Efficient Django Project Settings with Split Settings Library

Learn how to efficiently manage your Django project settings with the Split Settings library. Use environment variables, keep sensitive information i…

Read More
Integrating Flake8 with Django: Best Practices

Learn how to integrate Flake8 with Django projects and enforce code quality. Follow our step-by-step guide and optimize your Django workflow with Fla…

Read More
Django Authentication and Authorization with JWT

Learn how to implement JSON Web Token (JWT) based authentication and authorization in Django web applications with step-by-step guide and code exampl…

Read More
Best Practices for Django Development: Tips and Tricks

Learn the best practices for Django development, including project structure, code organization, testing, and deployment. Build high-quality web apps.

Read More
Django Middleware: Tips, Tricks and Examples

Learn how to use Django Middleware to improve your app's performance and security. Includes examples and best practices.

Read More
Django Production Deployment: Best Practices & Checklist

Learn the best practices and checklist for deploying a Django application to production. Includes tips on web servers, databases, caching, security, …

Read More