Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Mối quan hệ One-to-One trong Django ORM với Python
Việc quản lý các mối quan hệ giữa các bảng trong cơ sở dữ liệu là rất quan trọng. Django, một trong những framework web phổ biến nhất, cung cấp một cách tiếp cận mạnh mẽ và linh hoạt để xử lý các mối quan hệ này thông qua Django ORM. Sau khi đã làm quen với các khái niệm cơ bản của Django ORM, mình sẽ tiếp tục tìm hiểu mối quan hệ một-một (one-to-one relationship). Hướng dẫn này sẽ giúp bạn hiểu rõ cách thiết lập và quản lý mối quan hệ một-một trong Django, từ đó giúp ứng dụng của bạn trở nên tối ưu và dễ bảo trì hơn.
Mối quan hệ One-to-One trong Django là gì?
Mối quan hệ One-to-One trong Django ORM là một loại mối quan hệ giữa hai mô hình (models) trong Django, trong đó một đối tượng của mô hình A chỉ có thể có một đối tượng liên kết trong mô hình B, và ngược lại, một đối tượng của mô hình B cũng chỉ có thể được liên kết với một đối tượng của mô hình A.
Trong mối quan hệ One-to-One, mỗi đối tượng trong một mô hình có một trường liên kết trực tiếp với một đối tượng duy nhất trong mô hình khác.
Ví dụ, mỗi nhân viên có một thông tin liên hệ và mỗi thông tin liên hệ thuộc về một nhân viên. Vì vậy, mối quan hệ giữa nhân viên và thông tin liên hệ là một mối quan hệ một-một.
Bài viết này được đăng tại [free tuts .net]
Để tạo mối quan hệ một-một, bạn sử dụng lớp OneToOneField:
OneToOneField(to, on_delete, parent_link=False, **options)
Trong cú pháp này:
- Tham số
to
xác định tên mô hình. on_delete
xác định hành động trên liên hệ khi nhân viên bị xóa.
Ví dụ sau sử dụng lớp OneToOneField để định nghĩa mối quan hệ một-một giữa các mô hình Contact và Employee trong file 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 #Bài viết này được đăng tại freetuts.net 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}'
Lớp Employee có thuộc tính contact
tham chiếu đến một instance của lớp OneToOneField.
Trong OneToOneField, mình xác định mô hình Contact và tùy chọn on_delete
để xác định hành vi khi một đối tượng nhân viên bị xóa.
Tùy chọn on_delete=models.CASCADE
có nghĩa là nếu một đối tượng Employee bị xóa, đối tượng Contact liên kết với Employee đó cũng sẽ bị xóa tự động.
Lưu ý rằng Django tạo ra một ràng buộc khóa ngoại trong cơ sở dữ liệu mà không có tùy chọn ON DELETE CASCADE
. Thay vào đó, Django xử lý việc xóa một cách thủ công trong ứng dụng. Lưu ý rằng việc triển khai nội bộ này có thể thay đổi trong tương lai.
Di chuyển các mô hình vào cơ sở dữ liệu trong Django
Đầu tiên, tạo các di chuyển bằng lệnh makemigrations
:
python manage.py makemigrations
Đầu ra:
Migrations for 'hr': hr\migrations\0002_contact_employee_contact.py - Create model Contact - Add field contact to employee
Thứ hai, áp dụng các di chuyển vào cơ sở dữ liệu bằng lệnh migrate
:
python manage.py migrate
Đầu ra:
Operations to perform: Apply all migrations: admin, auth, contenttypes, hr, sessions Running migrations: Applying hr.0002_contact_employee_contact... OK
Django tạo hai bảng hr_contact
và hr_employee
trong cơ sở dữ liệu:
Bảng hr_employee
có cột contact_id
là khóa ngoại liên kết với cột id
(khóa chính) của bảng hr_contact
.
Liên kết một liên hệ với employee trong Django với Python
Để tương tác với các mô hình Employee và Contact, bạn chạy lệnh shell_plus
với tùy chọn --print-sql
:
python manage.py shell_plus --print-sql
Tùy chọn --print-sql
xuất ra các lệnh SQL mà Django thực thi.
Đầu tiên, tạo một đối tượng Employee mới và lưu vào cơ sở dữ liệu:
>>> e = Employee(first_name='freetuts', last_name='.net') >>> e.save()
Django thực thi lệnh SQL sau:
INSERT INTO "hr_employee" ("first_name", "last_name", "contact_id") VALUES ('John', 'Doe', NULL) RETURNING "hr_employee"."id"
Thứ hai, tạo và lưu một liên hệ mới vào cơ sở dữ liệu:
>>> c = Contact(phone='1234567788', address='101 ha noi, ha noi1 , ha noi 2') >>> c.save()
Django cũng thực thi lệnh INSERT sau:
INSERT INTO "hr_contact" ("phone", "address") VALUES ('1234567788', '101 ha noi, ha noi1 , ha noi 2') RETURNING "hr_contact"."id"
Thứ ba, liên kết một liên hệ với một nhân viên:
>>> e.contact = c >>> e.save()
Django cập nhật giá trị của cột contact_id
trong bảng hr_employee
thành giá trị của cột id
trong bảng hr_contact
.
UPDATE "hr_employee" SET "first_name" = 'freetuts', "last_name" = '.net', "contact_id" = 1 WHERE "hr_employee"."id" = 3
Lấy dữ liệu từ mối quan hệ một-một trong Django
Đầu tiên, lấy nhân viên với tên freetuts.net:
>>> e = Employee.objects.filter(first_name='freetut', last_name='.net').first() <Employee: freetuts.net>
Django thực thi lệnh SELECT để lấy hàng với first_name
là 'John' và last_name
là 'Doe'.
Bảng hr_employee
có thể có nhiều nhân viên có cùng tên và họ. Do đó, phương thức filter()
trả về một QuerySet.
Để lấy hàng đầu tiên trong QuerySet, mình sử dụng phương thức first()
. Phương thức first()
trả về một instance của lớp Employee.
SELECT "hr_employee"."id", "hr_employee"."first_name", "hr_employee"."last_name", "hr_employee"."contact_id" FROM "hr_employee" #Bài viết này được đăng tại freetuts.net WHERE ("hr_employee"."first_name" = 'freetut' AND "hr_employee"."last_name" = '.net') ORDER BY "hr_employee"."id" ASC LIMIT 1
Lưu ý rằng truy vấn không lấy dữ liệu liên hệ từ bảng hr_contact
. Nó chỉ lấy dữ liệu từ bảng hr_employee
.
Khi bạn truy cập thuộc tính contact
của nhân viên:
>>> e.contact
... Django thực thi lệnh SELECT thứ hai để lấy dữ liệu từ bảng hr_contact
:
SELECT "hr_contact"."id", #Bài viết này được đăng tại freetuts.net "hr_contact"."phone", "hr_contact"."address" FROM "hr_contact" WHERE "hr_contact"."id" = 1 LIMIT 21 <Contact: 40812345678>
Dưới đây là cách lấy liên hệ với id là 1:
>>> c = Contact.objects.get(id=1)
Khi liên kết một liên hệ với một nhân viên, bạn có thể truy cập nhân viên từ đối tượng liên hệ:
>>> c.employee <Employee: freetuts.net>
Django thực thi lệnh SELECT để lấy dữ liệu từ bảng hr_employee
:
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
Lưu ý rằng lớp Contact không có thuộc tính employee
. Tuy nhiên, bạn có thể truy cập nhân viên nếu liên hệ đó được liên kết với một nhân viên.
Hãy tạo một liên hệ khác không liên kết với bất kỳ nhân viên nào:
>>> c = Contact(phone='4081111111', address='202 N 1st Street, San Jose, CA') >>> c.save()
Django sẽ thực thi lệnh INSERT sau:
INSERT INTO "hr_contact" ("phone", "address") VALUES ('4081111111', '202 N 1st Street, San Jose, CA') RETURNING "hr_contact"."id"
Nếu bạn tìm một liên hệ không liên kết với bất kỳ nhân viên nào và truy cập nhân viên, bạn sẽ gặp ngoại lệ RelatedObjectDoesNotExist
.
Chọn các đối tượng liên quan trong bảng với Django
Đầu tiên, tạo một nhân viên mới:
>>> e = Employee(first_name='Jane', last_name='Doe') >>> e.save() #Bài viết này được đăng tại freetuts.net INSERT INTO "hr_employee" ("first_name", "last_name", "contact_id") VALUES ('Jane', 'Doe', NULL) RETURNING "hr_employee"."id" Execution time: 0.003079s [Database: default]
Thứ hai, lấy tất cả các nhân viên:
>>> Employee.objects.all()
Django trả về hai nhân viên:
<QuerySet [<Employee: freetuts.net>, <Employee: freetuts.net>]>
Nếu bạn cần hiển thị tất cả nhân viên cũng như liên hệ của họ trên cùng một trang, bạn sẽ gặp vấn đề N+1 truy vấn:
- Đầu tiên, bạn cần một truy vấn để lấy tất cả nhân viên (N nhân viên).
- Thứ hai, bạn cần N truy vấn để chọn liên hệ liên quan của mỗi nhân viên.
Để tránh điều này, bạn có thể truy vấn tất cả nhân viên và liên hệ bằng một truy vấn duy nhất bằng cách sử dụng phương thức select_related()
:
>>> Employee.objects.select_related('contact').all()
Trong trường hợp này, Django thực thi lệnh LEFT JOIN sau:
SELECT "hr_employee"."id", "hr_employee"."first_name", "hr_employee"."last_name", "hr_employee"."contact_id", "hr_contact"."id", "hr_contact"."phone", #Bài viết này được đăng tại freetuts.net "hr_contact"."address" FROM "hr_employee" LEFT OUTER JOIN "hr_contact" ON ("hr_employee"."contact_id" = "hr_contact"."id") LIMIT 21
Django sử dụng LEFT JOIN để trả về tất cả nhân viên từ bảng hr_employee
và các liên hệ liên kết với nhân viên đã chọn:
<QuerySet [<Employee: freetuts.net>, <Employee: freetuts.net>]>
Kết bài
Qua hướng dẫn này, bạn đã nắm được cách sử dụng lớp OneToOneField để thiết lập và quản lý mối quan hệ một-một trong Django. Việc hiểu rõ và áp dụng mối quan hệ một-một giúp bạn xây dựng cơ sở dữ liệu hiệu quả hơn, đảm bảo dữ liệu liên kết chặt chẽ và dễ dàng truy xuất. Bằng cách nắm vững các khái niệm và kỹ thuật này, bạn có thể tạo ra các ứng dụng Django mạnh mẽ và linh hoạt, đáp ứng tốt hơn các yêu cầu phức tạp của dự án.
Hãy tiếp tục thực hành và tìm hiểu thêm các mối quan hệ khác như một-nhiều (one-to-many) và nhiều-nhiều (many-to-many) để hoàn thiện kỹ năng làm việc với Django ORM của bạn.