Summary: in this tutorial, you’ll learn about one-to-many relationships in Django and how to use the ForeignKey to model them.
Introduction to the Django one-to-many relationships
In a one-to-many relationship, a row in a table is associated with one or more rows in another table. For example, a department may have one or more employees and each employee belongs to one department.
The relationship between departments and employees is a one-to-many relationship. Conversely, the relationship between employees and departments is a many-to-one relationship.
To create a one-to-many relationship in Django, you use ForeignKey
. For example, the following uses the ForeignKey
to create a one-to-many relationship between Department
and Employee
models:
from django.db import models
class Contact(models.Model):
phone = models.CharField(max_length=50, unique=True)
address = models.CharField(max_length=50)
def __str__(self):
return self.phone
class Department(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
def __str__(self):
return self.name
class Employee(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
contact = models.OneToOneField(
Contact,
on_delete=models.CASCADE,
null=True
)
department = models.ForeignKey(
Department,
on_delete=models.CASCADE
)
def __str__(self):
return f'{self.first_name} {self.last_name}'
Code language: Python (python)
How it works.
First, define the Department
model class.
Second, modify the Employee
class by adding the one-to-many relationship using the ForeignKey
:
department = models.ForeignKey(
Department,
on_delete=models.CASCADE
)
Code language: Python (python)
In the ForeignKey
, we pass the Department
as the first argument and the on_delete
keyword argument as models.CASCADE
.
The on_delete=models.CASCADE
indicates that if a department is deleted, all employees associated with the department are also deleted.
Note that you define the ForeignKey
field in the many-side of the relationship.
Third, make migrations using the makemigrations
command:
python manage.py makemigrations
Code language: plaintext (plaintext)
Django will issue the following message:
It is impossible to add a non-nullable field 'department' to employee without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option:
Code language: plaintext (plaintext)
In the Employee
model, we define the department
field as a non-nullable field. Therefore, Django needs a default value to update the department
field (or department_id
column) for the existing rows in the database table.
Even if the hr_employees
table doesn’t have any rows, Django also requests you to provide a default value.
As clearly shown in the message, Django provides you with two options. You need to enter 1 or 2 to select the corresponding option.
In the first option, Django requests a one-off default value. If you enter 1, then Django will show the Python command line:
>>>
Code language: Python (python)
In this case, you can use any valid value in Python e.g., None
:
>>> None
Code language: Python (python)
Once you provide a default value, Django will make the migrations like this:
Migrations for 'hr':
hr\migrations\0002_department_employee_department.py
- Create model Department
- Add field department to employee
Code language: plaintext (plaintext)
In the database, Django creates the hr_department
and adds the department_id
column to the hr_employee
table. The department_id
column of the hr_employee
table links to the id
column of the hr_department
table.
If you select the second option by entering the number 2, Django will allow you to manually define the default value for the department field. In this case, you can add a default value to the department
field in the models.py
like this:
department = models.ForeignKey(
Department,
on_delete=models.CASCADE,
default=None
)
Code language: Python (python)
After that, you can make the migrations using the makemigrations
command:
python manage.py makemigrations
Code language: plaintext (plaintext)
It’ll show the following output:
Migrations for 'hr':
hr\migrations\0002_department_employee_department.py
- Create model Department
- Add field department to employee
Code language: plaintext (plaintext)
If the hr_employee
table has any rows, you need to remove all of them before migrating the new migrations:
python manage.py shell_plus
>>> Employee.objects.all().delete()
Code language: CSS (css)
Then you can make the changes to the database using the migrate
command:
python manage.py migrate
Code language: plaintext (plaintext)
Output:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, hr, sessions
Running migrations:
Applying hr.0002_department_employee_department... OK
Code language: plaintext (plaintext)
Another way to deal with this is to reset migrations that we will cover in the reset migrations tutorial.
Interacting with models
First, run the shell_plus
command:
python manage.py shell_plus
Code language: plaintext (plaintext)
Second, create a new department with the name IT
:
>>> d = Department(name='IT',description='Information Technology')
>>> d.save()
Code language: Python (python)
Third, create two employees and assign them to the IT
department:
>>> e = Employee(first_name='John',last_name='Doe',department=d)
>>> e.save()
>>> e = Employee(first_name='Jane',last_name='Doe',department=d)
>>> e.save()
Code language: Python (python)
Fourth, access the department
object from the employee
object:
>>> e.department
<Department: IT>
>>> e.department.description
'Information Technology'
Code language: Python (python)
Fif, get all employees of a department using use the employee_set
attribute like this:
>>> d.employee_set.all()
<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
Code language: Python (python)
Note that we did not define the employee_set
property in the Department
model. Internally, Django automatically added the employee_set
property to the Department
model when we defined the one-to-many relationship using the ForeignKey
.
The all()
method of the employee_set
returns a QuerySet
that contains all employees who belong to the department.
Using select_related() to join employee with department
Exit the shell_plus
and execute it again. This time we add the --print-sql
option to display the generated SQL that Django will execute against the database:
python manage.py shell_plus --print-sql
Code language: plaintext (plaintext)
The following returns the first employee:
>>> e = Employee.objects.first()
SELECT "hr_employee"."id",
"hr_employee"."first_name",
"hr_employee"."last_name",
"hr_employee"."contact_id",
"hr_employee"."department_id"
FROM "hr_employee"
ORDER BY "hr_employee"."id" ASC
LIMIT 1
Execution time: 0.003000s [Database: default]
Code language: SQL (Structured Query Language) (sql)
To access the department of the first employee, you use the department
attribute:
>>> e.department
SELECT "hr_department"."id",
"hr_department"."name",
"hr_department"."description"
FROM "hr_department"
WHERE "hr_department"."id" = 1
LIMIT 21
Execution time: 0.013211s [Database: default]
<Department: IT>
Code language: SQL (Structured Query Language) (sql)
In this case, Django executes two queries. The first query selects the first employee and the second query selects the department of the selected employee.
If you select N employees to display them on a web page, then you need to execute N + 1 query to get both employees and their departments. The first query (1) selects the N employees and the N queries select N departments for each employee. This issue is known as the N + 1 query problem.
To fix the N + 1 query problem, you can use the select_related()
method to select both employees and departments using a single query. For example:
>>> Employee.objects.select_related('department').all()
SELECT "hr_employee"."id",
"hr_employee"."first_name",
"hr_employee"."last_name",
"hr_employee"."contact_id",
"hr_employee"."department_id",
"hr_department"."id",
"hr_department"."name",
"hr_department"."description"
FROM "hr_employee"
INNER JOIN "hr_department"
ON ("hr_employee"."department_id" = "hr_department"."id")
LIMIT 21
Execution time: 0.012124s [Database: default]
<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>
Code language: SQL (Structured Query Language) (sql)
In this example, Django executes only one query that joins the hr_employee
and hr_department
tables.
Download the Django one-to-many relationship source code
Summary
- In a one-to-many relationship, a row in a table is associated with one or more rows in another table.
- Use
ForeignKey
to establish a one-to-many relationship between models in Django. - Define the
ForeignKey
in the model of the “many” side of the relationship. - Use the
select_related()
method to join two or more tables in the one-to-many relationships.