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 makemigrations
Code language: Python (python)
Migrations for 'hr':
hr\migrations\0002_contact_employee_contact.py
- Create model Contact
- Add field contact to employee
Code language: Python (python)
Second, apply the migrations to the database using the migrate
command:
python manage.py migrate
Code language: Python (python)
Operations to perform:
Apply all migrations: admin, auth, contenttypes, hr, sessions
Running migrations:
Applying hr.0002_contact_employee_contact... OK
Code 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-sql
Code 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" = 3
Code 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 1
Code 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.contact
Code 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 21
Code 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 21
Code 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.