This article assumes a readership who is familiar with database schema design. Django explains its architecture as following an MTV (Model-Template-View) paradigm where model is the central component that encapsulates data and behavior for a web application, view is the mechanism that defines what data is to be presented to the user and template dictates how data will be presented to the user via HTML.
Today, I'm going to focus on what model relationships are in Django, but first, let me give a little introduction on database. The Django framework is designed to work with relational database engines in the back end such as SQLite, PostgreSQL, MySQL and Oracle. However, support for non-relational database such as MongoDb is also possible through third-party connectors such as Djongo. From a relational database perspective, data is organized as tables that may be related to one another in one of these associations: one-to-one, one-to-many, many-to-many.
Examples of a one-to-one relationship are plenty in the real world. Consider these scenarios:
- A user profile in a web application is associated with a unique email address. Some web applications, such as PayPal and LinkedIn, allow a user to have multiple email addresses, but many, such as social media, banking and online shopping, restrict an email address to only one user.
- A citizen of a country is associated with a passport. Not every citizen requires a passport but if they want to travel outside the country, they must be issued a passport.
- An ISBN number is associated with a book, but a book does not have to acquire an ISBN number in order for it to be published.
In general, to implement a table that is related to another table, a FOREIGN KEY designation is assigned to a column to bind the two tables together. In SQL, to implement a one-to-one relationship between two tables, another designation, UNIQUE, is usually added to the foreign key column as well.
In Django, models are translated to database tables via SQL. A model has fields and field types whereas a table has columns and column types. A field or column type tells us how data should be stored, for example, as a string, integer, float, boolean, etc. To implement a one-to-one relationship between models, Django lets us create a special field type, OneToOneField
, that typically resides in one of the two models.
To decide which one of the two models should have this OneToOneField, consider this example with two Django models, Book
and Isbn
. If a Book
can exist without an Isbn
, but an Isbn
must always be linked to a book, then place the OneToOneField
in the Isbn
model.
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=20)
...
class Isbn(models.Model):
isbn = models.CharField(max_length=13)
book = models.OneToOneField(to=Book, on_delete=models.CASCADE)
...
In the code example above, we place the OneToOneField
in the Isbn
model assigned to the field named, book
. Let's investigate the next type of relationship.
Real world objects may be linked with one another via a one-to-many or many-to-one relationship. Consider these scenarios:
- A person may have one or more social media accounts, but a social media account may only be linked to one person.
- A country may have many ambassadors stationed in different countries, but each ambassador may only represent one country.
- An author may write many books, but each book may only be associated with one author.
We would typically represent a one-to-many relationship in a relational database schema using the FOREIGN KEY designation as well. One would expect Django to provide a OneToManyField
field type to correspond to the one-to-many relationship. However, this is not so. Instead, Django provides a ForeignKey
field which raises some confusion among developers.
A question that often comes up is 'Where does one place the ForeignKey
field type?' Suppose we have two Django models, Book
and Author
, and they are related by a one-to-many relationship. I use one of these two criteria to help me decide where to place the ForeignKey
field type.
- Since a
Book
can only exist because it has anAuthor
but anAuthor
can exist even without anyBook
to their credit, we should place theForeignKey
field type inAuthor
. - Since a
Book
has a many-to-one relationship withAuthor
, we should place theForeignKey
field type in the model that has the "many" relationship, which isBook
.
In both cases, Book
is the host of the ForeignKey
field type and it should be assigned to a field such as author
.
from django.db import models
# Author has a one-to-many relationship with Book
class Author(models.Model):
name = models.CharField(max_length=20)
...
# Book has a many-to-one relationship with Author
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.ForeignKey(to=Author, on_delete=models.CASCADE)
...
In the code example above, the author
field houses the ForeignKey
field type to show that a Book
must be associated with an Author
. Let's explore the last type of relationship.
Examples of many-to-many relationship abound in the real world as well. Consider these scenarios:
- A customer may subscribe to many publications, while a publication may have many subscribers.
- A person may join many clubs, while a club may have many members.
- A recipe may have many ingredients, while the same ingredient may belong to many recipes.
In a relational database schema, we typically represent a many-to-many relationship by creating a JOIN table to house two columns that are designated as foreign keys which are themselves primary keys in the join table. This gives rise to the concept of multiple primary keys in a table, also known as COMPOSITE PRIMARY KEY.
From the Django perspective, implementing a composite primary key is not possible due to issues. A ticket that requested this feature was opened back in 2005 and is still waiting to be resolved at the time of writing. But not to worry, as Django supports many-to-many relationship between models via a ManyToManyField
field type.
If two Django models are associated by a many-to-many relationship, we can place the ManyToManyField
in any of the two models.
from django.db import models
class Ingredient(models.Model):
name = models.CharField(max_length=20)
...
class Recipe(models.Model):
name = models.CharField(max_length=20)
ingredients = models.ManyToManyField()
...
From the code example above, both Ingredient
and Recipe
are related by having a many-to-many relationship. The ManyToManyField
field type is placed in Recipe
assigned to the field named ingredients
, but it could also be placed in Ingredient
and assigned to a field named recipes
.
The Django framework provides solutions to implement the classic relationships between objects via the OneToOneField
, ForeignKey
and ManyToManyField
. The choice to use the name ForeignKey
instead of OneToManyField
or ManyToOneField
often raises confusion as it breaks the convention of the other two related field types. This article hopefully dispels the confusion among new Django developers. Thank you for reading!