01 Aug 2023

Polymorphism in Django ORM

Challenges of polymorphism

Django ORM maps attributes to columns in the database.

  • How should Django ORM map attributes to the columns in the table?
  • Should different objects reside in the same table?
  • Should there be multiple tables?

Example

Simplest way

Ecommerce website that sells books. You need the following models: Book, Cart. This approach is non-polymorphic.

class Book(Model):
    name = models.CharField(
        max_length=100,
  )
  price = models.PositiveIntegerField(
        help_text='in cents',
  )
  weight = models.PositiveIntegerField(
        help_text='in grams',
  )

class Cart(Model):
    user = models.OneToOneField(
        get_user_model(),
        primary_key=True,
        on_delete=models.CASCADE,
  )
  books = models.ManyToManyField(Book)

To create a cart with books:

book = Book.objects.create(name="Awesome book", price=100, weight=200)
user = get_user_model().create_user("Some user")
cart = Cart.objects.create(user=user)

cart.products.add(book)

Abstract Base Model

Abstract base model creates an abstract base class that only exists in the code, not in the database.

class Product(Model):
        class Meta:
            abstract = True

    name = models.CharField(
        max_length=100,
    )
    price = models.PositiveIntegerField(
        help_text='in cents',
    )

class Book(Product):
    weight = models.PositiveIntegerField()

class EBook(Product):
    download_link = models.URLField()

Both Book and EBook inherit from Product. Now let’s create the Cart.

class Cart(Model):
    user = models.OneToOneField(
        get_user_model(),
        primary_key=True,
        on_delete=models.CASCADE,
  )

    # we have to create a different variable for each type of product
    book = models.ManyToManyField(Book)
    ebooks = models.ManyToManyField(EBook)

Concrete Base Model

Concrete base model is an approach that does create a separate table in the database.

In this example we have both Books and EBooks in our store. Note that the Product Model is not defined with abstract = True.

class Product(Model):
    name = models.CharField(
        max_length=100,
    )
    price = models.PositiveIntegerField(
        help_text='in cents',
    )

class Book(Product):
    weight = models.PositiveIntegerField()

class EBook(Product):
    download_link = models.URLField()

Create new products:

book = Book.objects.create(...)
ebook = EBook.objects.create(...)

This will add an entry in the Product table that will contain all the common fields, and 2 entries in the book and ebook tables, for the values specific to their respective products.

Notice the productptrid in Books table which points to its respective item in Products table.

Column Type
productptrid integer
weight integer

Django performs a join in the background when fetching a book.

Since all products are managed in the Product table, you can reference them using a foreign key in the Cart model.

class Cart(Model):
    ...
    items = models.ManyToManyField(Product)
cart = Cart.objects.create(user=user)
cart.items.add(book, ebook)