Django One-To-One Relationship

Summary: in this tutorial, you’ll learn about Django’s one-to-one relationship and how it works under the hood.

This tutorial begins where the Django ORM tutorial left off.

Introduction to the Django One-To-One relationship

The one-to-one relationship defines a link between two tables, where each row in a table appears once in another table.

For example, each employee has a contact and each contact belongs to one employee. So the relationship between employees and contacts is a one-to-one relationship.

To create a one-to-one relationship, you use the OneToOneField class:

OneToOneField(to, on_delete, parent_link=False, **options)Code language: Python (python)

In this syntax:

  • to parameter defines the model name.
  • on_delete specifies an action on the contact when the employee is deleted.

The following example uses a OneToOneField class to define a one-to-one relationship between Contact and Employee models in the models.py:

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 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)

    def __str__(self):
        return f'{self.first_name} {self.last_name}'Code language: Python (python)

The Employee class has the contact attribute that references an instance of the OneToOneField class.

In the OneToOneField, we specify the Contact model and the on_delete option that defines the behavior when an employee object is deleted.

The on_delete=models.CASCADE option means if an Employee object is deleted, the Contact object associated with the Employee will also be deleted automatically.

Note that Django creates a foreign key constraint in the database without an ON DELETE CASCADE option. Instead, Django handles the deletion manually in the application. Note that this internal implementation may change in the future.

Migrating models to the database

First, make migrations using the makemigrations command:

python manage.py makemigrationsCode language: Python (python)
Migrations for 'hr':
  hr\migrations\0002_contact_employee_contact.py
    - Create model Contact
    - Add field contact to employeeCode language: Python (python)

Second, apply the migrations to the database using the migrate command:

python manage.py migrateCode language: Python (python)
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, hr, sessions
Running migrations:
  Applying hr.0002_contact_employee_contact... OKCode language: Python (python)

Behind the scenes, Django creates two tables hr_contact and hr_employee in the database:

The hr_employee table has the contact_id column which is a foreign key that links to the id (primary key) of the hr_contact table.

Associating a contact with an employee

To interact with the Employee and Contact models, you run the shell_plus command with the --print-sql option:

python manage.py shell_plus --print-sqlCode language: Python (python)

The --print-sql option outputs the SQL command that Django executes.

First, create a new Employee object and save it to the database:

>>> e = Employee(first_name='John',last_name='Doe')
>>> e.save()Code language: Python (python)

Django executes the following SQL command:

INSERT INTO "hr_employee" ("first_name", "last_name", "contact_id")
VALUES ('John', 'Doe', NULL) 
RETURNING "hr_employee"."id"Code language: Python (python)

Second, create and save a new contact into the database:

>>> c = Contact(phone='40812345678', address='101 N 1st Street, San Jose, CA')
>>> c.save()Code language: Python (python)

Django also executes the following INSERT command:

INSERT INTO "hr_contact" ("phone", "address")
VALUES ('40812345678', '101 N 1st Street, San Jose, CA') 
RETURNING "hr_contact"."id"Code language: Python (python)

Third, associate a contact with an employee:

>>> e.contact = c
>>> e.save()Code language: Python (python)

Django updates the value of the contact_id column in the hr_employee table to the value of the id column in the hr_contact table.

UPDATE "hr_employee"
   SET "first_name" = 'John',
       "last_name" = 'Doe',
       "contact_id" = 1
 WHERE "hr_employee"."id" = 3Code language: Python (python)

Getting data from a one-to-one relationship

First, get the employee with the name John Doe:

>>> e = Employee.objects.filter(first_name='John',last_name='Doe').first()
<Employee: John Doe>Code language: Python (python)

Django executes the SELECT statement that gets the row with the first_name 'John' and last_name 'Doe'.

The hr_employee may have multiple employees with the same first name and last name. Therefore, the filter() returns a QuerySet.

To get the first row in the QuerySet, we use the first() method. The first() method returns a single instance of the Employee class.

SELECT "hr_employee"."id",
       "hr_employee"."first_name",
       "hr_employee"."last_name",
       "hr_employee"."contact_id"
  FROM "hr_employee"
 WHERE ("hr_employee"."first_name" = 'John' AND "hr_employee"."last_name" = 'Doe')
 ORDER BY "hr_employee"."id" ASC
 LIMIT 1Code language: Python (python)

Note that the query doesn’t get the contact data from the hr_contact table. It only gets data from the hr_employee table.

When you access the contact attribute of the employee:

>>> e.contactCode language: Python (python)

… Django executes the second SELECT statement to get data from the hr_contact table:

SELECT "hr_contact"."id",
       "hr_contact"."phone",
       "hr_contact"."address"
  FROM "hr_contact"
 WHERE "hr_contact"."id" = 1
 LIMIT 21
<Contact: 40812345678>Code language: Python (python)

The following gets the contact with id 1:

>>> c = Contact.objects.get(id=1)Code language: Python (python)

When associating a contact with an employee, you can access the employee from the contact object:

>>> c.employee
<Employee: John Doe>Code language: Python (python)

Django executes a SELECT statement to get data from the hr_employee table:

SELECT "hr_employee"."id",
       "hr_employee"."first_name",
       "hr_employee"."last_name",
       "hr_employee"."contact_id"
  FROM "hr_employee"
 WHERE "hr_employee"."contact_id" = 1
 LIMIT 21Code language: Python (python)

Note that the Contact class doesn’t have the employee attribute. However, you can access the employee if the contact is associated with one.

Let’s create another contact that doesn’t associate with any employee:

>>> c = Contact(phone='4081111111',address='202 N 1st Street, San Jose, CA')
>>> c.save()Code language: Python (python)

Django will execute the following INSERT statement:

INSERT INTO "hr_contact" ("phone", "address")
VALUES ('4081111111', '202 N 1st Street, San Jose, CA') 
RETURNING "hr_contact"."id"Code language: Python (python)

If you find a contact that doesn’t associate with any employee and access the employee, you’ll get a RelatedObjectDoesNotExist exception.

Selecting related objects

First, create a new employee:

>>> e = Employee(first_name='Jane',last_name='Doe')
>>> e.save()
INSERT INTO "hr_employee" ("first_name", "last_name", "contact_id")
VALUES ('Jane', 'Doe', NULL) RETURNING "hr_employee"."id"
Execution time: 0.003079s [Database: default]Code language: Python (python)

Second, get all employees:

>>> Employee.objects.all()Code language: Python (python)

Django returns two employees:

<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>Code language: Python (python)

If you have to display all employees as well as their contacts on the same page, then you have the N+1 query problem:

  • First, you need one query to get all employees (N employees).
  • Second, you need N queries to select the related contact of each employee.

To avoid this, you can query all employees and contacts using a single query by using the select_related() method:

>>> Employee.objects.select_related('contact').all()Code language: Python (python)

In this case, Django executes the following LEFT JOIN statement:

SELECT "hr_employee"."id",
       "hr_employee"."first_name",
       "hr_employee"."last_name",
       "hr_employee"."contact_id",
       "hr_contact"."id",
       "hr_contact"."phone",
       "hr_contact"."address"
  FROM "hr_employee"
  LEFT OUTER JOIN "hr_contact"
    ON ("hr_employee"."contact_id" = "hr_contact"."id")
 LIMIT 21Code language: Python (python)

Django uses LEFT JOIN that returns all employees from the hr_employee table and contacts associated with the selected employees:

<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>Code language: Python (python)

Download the project source code here

Summary

  • Use OneToOneField class to establish a one-to-one relationship.
Did you find this tutorial helpful ?