DEV Community

Cover image for Using abstract models in Django
CH S Sankalp jonna
CH S Sankalp jonna

Posted on

Using abstract models in Django

One of the principles of Python is “Do not repeat yourself”.  Abstract models in Django are meant to do exactly that.

If you are designing a database for a school, there would be database models representing all types of people who attend this school which includes the students, teachers, cleaning staff, cafeteria staff, school bus drivers, etc.

However, all of these folks have some common information such as name, date of birth, date of joining, address and contact information, etc.

It doesn't seem right to repeat all of these fields for each database model while writing the code, and this is where abstract models work great.

How to create an abstract base model?

All you have to do is create a base model with all the common fields and set abstract = True in the meta class. 

This will ensure that there is no actual database table created for this base model and it is meant only for being inherited by other models that actually become tables in the database.

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=255)
    date_of_birth = models.DateTimeField()
    date_of_joining = models.DateTimeField()
    address = models.TextField()

    class Meta:
        abstract = True

class Student(Person):
    roll_number = models.IntegerField()


class Teacher(Person):
    compensation = models.CharField(max_length=255)
Enter fullscreen mode Exit fullscreen mode

After running migrations, there will be a student table and a teacher table created in your database while the person model stays in your codebase ready to be inherited in another model if you chose to.

Abstract as much as possible

The “Do not repeat yourself” principle does not have to end here. For instance, even though the folks belonging to the cleaning staff and cafeteria staff are also persons, they both have shift timings which students and teachers do not.

from django.db import models

#======= Abstract models =========#
class Person(models.Model):
    name = models.CharField(max_length=255)
    date_of_birth = models.DateTimeField()
    date_of_joining = models.DateTimeField()
    address = models.TextField()

    class Meta:
        abstract = True

class SchoolEmployee(Person):
    compensation = models.CharField(max_length=255)

    class Meta:
        abstract = True

class HiredHelp(SchoolEmployee):
    shift_timings = models.JSONField()

    class Meta:
        abstract = True
#--------------------------------#

#======= Actual models ==========#
class Student(Person):
    roll_number = models.IntegerField()

class Teacher(SchoolEmployee):
    qualifications = models.JSONField()

class CafeteriaEmployee(HiredHelp):
    pass

class CleaningStaffMember(HiredHelp):
    pass
#--------------------------------#
Enter fullscreen mode Exit fullscreen mode

Depending on your requirement you can have further abstract models that inherit from other abstract models.

Closing notes

It is important to keep in mind that abstract models by themselves are quite useless. There is no database table created for them and hence they are of no consequence.

Abstract models are only useful if you intend to create other models which inherit from the abstract ones. 

Abstract classes are a good example of how the concept of inheritance can be used to write reusable code. 

Originally posted on my blog

Top comments (9)

Collapse
 
nemrtvej profile image
Marek Makovec

In my opinion, this article describes a really easy way how to painfully shoot yourself in the foot when you store your data in SQL databases.

In my experience, introducing abstract models in Django leads to unsolvable N+1 query issues in the long run.

Imagine following scenario:

  • You have an abstract model Person with field full_name.
  • You have a class Student inheriting from Person.
  • You have a class Teacher inheriting from Person.

Imagine you have a model called AccessCard.
This AccessCard has properties value (int) and owner (Person). When you create a table listing 100 AccessCards per page, showing both the AccessCard.value and AccessCard.owner.full_name, is there a way, how to provide these data this using just 1 SQL query instead of 1+100?

If I remember correctly, it is easy when abstract classes are not used. See QuerySet.prefetch_related / QuerySet.select_related. However when I was trying to find a solution for this with inheritance in play, I failed miserably.

In my experience with ORM mappers in Django and with Doctrine from the PHP world, using inheritance in conjunction with SQL is a bad idea.
The time you save by not typing again the definitions are definitely lost when you try to optimize the horribly inefficient queries.

I do not want to degrade the value of your article, I just want to point out the downsides and that this approach has, in my opinion, significant drawbacks which should be pointed out.

Collapse
 
guzmanojero profile image
guzmanojero • Edited

You can use QuerySet.prefetch_related or QuerySet.select_related with Abstract Base Classes in Django.

Let's say we have these models:


class Country(models.Model):
    name = models.CharField(max_length=200)

    def __str__(self):
        return self.name

class AbstractPerson(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

    class Meta:
        abstract = True

class AbstractAddress(models.Model):
    street = models.CharField(max_length=100)
    city = models.CharField(max_length=50)
    state = models.CharField(max_length=2)
    zip_code = models.CharField(max_length=10)
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

    class Meta:
        abstract = True

class AbstractPhoneNumber(models.Model):
    phone_number = models.CharField(max_length=15)

    class Meta:
        abstract = True

class Contact(AbstractPerson, AbstractAddress, AbstractPhoneNumber):
    email = models.EmailField()

    def __str__(self):
        return f"{self.first_name} {self.last_name} - {self.country}"
Enter fullscreen mode Exit fullscreen mode

If you make this query you wouldn't have a N+1 issue:

> Contact.objects.select_related("country").get(id=1).country
Enter fullscreen mode Exit fullscreen mode

You have one query only. The generated SQL is:

SELECT app_contact.id,
       app_contact.first_name,
       app_contact.last_name,
       app_contact.street,
       app_contact.city,
       app_contact.state,
       app_contact.zip_code,
       app_contact.country_id,
       app_contact.phone_number,
       app_contact.email,
       app_country.id,
       app_country.name
  FROM app_contact
INNER JOIN app_country
    ON (app_contact.country_id = app_country.id)
 WHERE app_contact.id = '1'
Enter fullscreen mode Exit fullscreen mode

Maybe you were referring to something else?

Collapse
 
ottohirr profile image
Otto Hirr

I find this to be a very bad example of using abstract base classes. Certainly it shows the repeated use of common fields from a base entity. Just because you can doesn't mean you should.

Per Django docs, there are different ways to inherent, this article only expresses one method.

The very heart of the article suggests DRY, yet the implementation fails that. A "teacher" could also be a "student" and a "student" could work part time as "cleaning staff" or "cafeteria staff", and so on.

For each case, the "teacher" would have a name, and other Person fields, and likewise the "student" would have a name and other Person fields - hence violating the very DRY reasoning for the article's depiction of inheritance.

A more accurate depiction of real world modeling would be to model a person and then add by relationship the various roles they may play.

Within Django, it would seem that given the concept of an app being a single full concept, a Person app would be a basis for modeling a real person. Then a different app for each of the other person roles would capture the additional fields and behavior associated with that particular role, all related, as an extension of a person.

Regards,

.. Otto

Collapse
 
kirancapoor94 profile image
Kiran Capoor

As per what I’ve observed, abstract models aren’t written in the DB. So there’s no Table for an abstract model.

The Django ORM, maps the fields to each model it’s subclassing only at the time of migrations.

However, when in the case of Inheritance, Django ORM fails miserably. N+1 queries, INNER JOINS are way too common which ultimately make the DB Queries VERY slow.

My recommendation is to use Abstract Tables only when needed, and avoid Inheritance completely when developing using Django. Instead use OneToOne/ForeignKey relationships to extend the tables.

Thanks 🍻

Collapse
 
guzmanojero profile image
guzmanojero

I don't follow what you're saying.

If an abstract model isn't creating tables you don't have a N+1 issue.

I've commented here:
dev.to/guzmanojero/comment/2ac2g

Can you expand?

Collapse
 
dollardhingra profile image
Dollar Dhingra

Very well explained! Simple & easy to understand. I have also written article on how abstract base classes work in Python. Read here: dev.to/dollardhingra/understanding...

Collapse
 
sankalpjonna profile image
CH S Sankalp jonna

This is a great article. Thanks for sharing!

Collapse
 
king11 profile image
Lakshya Singh

Ohh this is something real nice thanks for sharing back to DRY from the darkness :)

Collapse
 
sankalpjonna profile image
CH S Sankalp jonna

It is easy to forget DRY when one is deep into development. So hoping this post serves as a reminder