Django One-To-Many Relationship

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 makemigrationsCode 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:

>>> NoneCode 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 employeeCode 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 makemigrationsCode 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 employeeCode 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 migrateCode language: plaintext (plaintext)

Output:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, hr, sessions
Running migrations:
  Applying hr.0002_department_employee_department... OKCode 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_plusCode 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-sqlCode 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.
Did you find this tutorial helpful ?