DJANGO
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

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-Many trong Django với Python

Một trong những mối quan hệ phổ biến nhất là mối quan hệ một-nhiều, nơi một bản ghi trong một bảng có thể liên kết với nhiều bản ghi trong bảng khác. Ví dụ điển hình của mối quan hệ này là giữa phòng ban và nhân viên, nơi một phòng ban có thể có nhiều nhân viên nhưng mỗi nhân viên chỉ thuộc về một phòng ban. Trong bài viết này, mình sẽ tìm hiểu cách sử dụng ForeignKey trong Django để thiết lập và làm việc với mối quan hệ một-nhiều một cách hiệu quả.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Mối quan hệ One-To-Many là gì?

Trong một mối quan hệ một-nhiều, một hàng trong một bảng được liên kết với một hoặc nhiều hàng trong một bảng khác. Ví dụ, một phòng ban có thể có một hoặc nhiều nhân viên và mỗi nhân viên thuộc về một phòng ban.

Mối quan hệ giữa các phòng ban và nhân viên là một mối quan hệ một-nhiều. Ngược lại, mối quan hệ giữa nhân viên và các phòng ban là một mối quan hệ nhiều-một.

Để tạo một mối quan hệ một-nhiều trong Django, bạn sử dụng ForeignKey. Ví dụ, đoạn mã sau đây sử dụng ForeignKey để tạo một mối quan hệ một-nhiều giữa các mô hình Department và Employee:

Bài viết này được đăng tại [free tuts .net]

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 Department(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
#Bài viết này được đăng tại freetuts.net
    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
    )
#Bài viết này được đăng tại freetuts.net
    department = models.ForeignKey(
        Department,
        on_delete=models.CASCADE
    )

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

Cách hoạt động

Đầu tiên, định nghĩa lớp mô hình Department.

Thứ hai, sửa đổi lớp Employee bằng cách thêm mối quan hệ một-nhiều sử dụng ForeignKey:

department = models.ForeignKey(
   Department,
   on_delete=models.CASCADE
)

Trong ForeignKey, mình truyền Department là đối số đầu tiên và đối số từ khóa on_delete models.CASCADE.

on_delete=models.CASCADE chỉ ra rằng nếu một phòng ban bị xóa, tất cả các nhân viên liên kết với phòng ban đó cũng sẽ bị xóa.

Lưu ý rằng bạn định nghĩa trường ForeignKey ở phía “nhiều” của mối quan hệ.

Tạo lệnh Migrations trong Django

Sử dụng lệnh makemigrations:

python manage.py makemigrations

Django sẽ đưa ra thông báo sau:

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:

Trong mô hình Employee, mình định nghĩa trường department là một trường không thể null. Do đó, Django cần một giá trị mặc định để cập nhật trường department (hoặc cột department_id) cho các hàng hiện có trong bảng cơ sở dữ liệu.

Ngay cả khi bảng hr_employees không có hàng nào, Django cũng yêu cầu bạn cung cấp một giá trị mặc định.

Như đã hiển thị rõ ràng trong thông báo, Django cung cấp cho bạn hai tùy chọn. Bạn cần nhập 1 hoặc 2 để chọn tùy chọn tương ứng.

Trong tùy chọn đầu tiên, Django yêu cầu một giá trị mặc định tạm thời. Nếu bạn nhập 1, thì Django sẽ hiển thị dòng lệnh Python:

>>> None

Sau khi bạn cung cấp một giá trị mặc định, Django sẽ tạo các migrations như sau:

Migrations for 'hr':
  hr\migrations\0002_department_employee_department.py
    - Create model Department
    - Add field department to employee

django orm employee department png

Trong cơ sở dữ liệu, Django tạo bảng hr_department và thêm cột department_id vào bảng hr_employee. Cột department_id của bảng hr_employee liên kết với cột id của bảng hr_department.

Nếu bạn chọn tùy chọn thứ hai bằng cách nhập số 2, Django sẽ cho phép bạn tự định nghĩa giá trị mặc định cho trường department. Trong trường hợp này, bạn có thể thêm giá trị mặc định vào trường department trong models.py như sau:

department = models.ForeignKey(
   Department,
#Bài viết này được đăng tại freetuts.net
   on_delete=models.CASCADE,
   default=None
)

Sau đó, bạn có thể tạo các migrations sử dụng lệnh makemigrations:

python manage.py makemigrations

Nó sẽ hiển thị đầu ra sau:

Migrations for 'hr':
  hr\migrations\0002_department_employee_department.py
    - Create model Department
    - Add field department to employee

Nếu bảng hr_employee có bất kỳ hàng nào, bạn cần xóa tất cả trước khi di chuyển các migrations mới:

python manage.py shell_plus
>>> Employee.objects.all().delete()

Sau đó, bạn có thể thực hiện các thay đổi 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_department_employee_department... OK

Một cách khác để xử lý vấn đề này là đặt lại migrations, mình sẽ đề cập trong hướng dẫn đặt lại migrations.

Tương tác với models trong Django

Đầu tiên, chạy lệnh shell_plus:

python manage.py shell_plus

Thứ hai, tạo một phòng ban mới với tên IT:

>>> d = Department(name='IT', description='Information Technology')
>>> d.save()

Tạo hai nhân viên và gán họ vào phòng ban IT:

>>> e = Employee(first_name='freetuts', last_name='.net', department=d)
>>> e.save()
>>> e = Employee(first_name='.net', last_name='freetut', department=d)
>>> e.save()

Truy cập đối tượng phòng ban từ đối tượng nhân viên:

>>> e.department
<Department: IT>
>>> e.department.description
'Information Technology'

Truy cập đối tượng phòng ban từ đối tượng nhân viên:

>>> e.department
<Department: IT>
>>> e.department.description
'Information Technology'

Lấy tất cả các nhân viên của một phòng ban sử dụng thuộc tính employee_set như sau:

>>> d.employee_set.all()
<QuerySet [<Employee: freetuts.net>, <Employee: Jane Doe>]>

Lấy tất cả các nhân viên của một phòng ban sử dụng thuộc tính employee_set như sau:

>>> d.employee_set.all()
<QuerySet [<Employee: John Doe>, <Employee: .net freetuts>]>

Lưu ý rằng mình không định nghĩa thuộc tính employee_set trong mô hình Department. Nội bộ, Django tự động thêm thuộc tính employee_set vào mô hình Department khi mình định nghĩa mối quan hệ một-nhiều sử dụng ForeignKey.

Phương thức all() của employee_set trả về một QuerySet chứa tất cả các nhân viên thuộc về phòng ban.

Sử dụng select_related() để join các bảng trong Django

Thoát khỏi shell_plus và chạy lại. Lần này, mình thêm tùy chọn --print-sql để hiển thị SQL được tạo mà Django sẽ thực thi đối với cơ sở dữ liệu:

python manage.py shell_plus --print-sql

Đoạn mã sau trả về nhân viên đầu tiên:

>>> 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"
#Bài viết này được đăng tại freetuts.net
 ORDER BY "hr_employee"."id" ASC
 LIMIT 1
Execution time: 0.003000s [Database: default]

Để truy cập phòng ban của nhân viên đầu tiên, bạn sử dụng thuộc tính department:

>>> e.department 
SELECT "hr_department"."id",
       "hr_department"."name",
       "hr_department"."description"
  FROM "hr_department"
 WHERE "hr_department"."id" = 1
 LIMIT 21
#Bài viết này được đăng tại freetuts.net
Execution time: 0.013211s [Database: default]
<Department: IT>

Trong trường hợp này, Django thực thi hai truy vấn. Truy vấn đầu tiên chọn nhân viên đầu tiên và truy vấn thứ hai chọn phòng ban của nhân viên được chọn.

Nếu bạn chọn N nhân viên để hiển thị trên một trang web, thì bạn cần thực thi N + 1 truy vấn để lấy cả nhân viên và phòng ban của họ. Truy vấn đầu tiên (1) chọn N nhân viên và N truy vấn chọn N phòng ban cho mỗi nhân viên. Vấn đề này được gọi là vấn đề truy vấn N + 1.

Để khắc phục vấn đề truy vấn N + 1, bạn có thể sử dụng phương thức select_related() để chọn cả nhân viên và phòng ban sử dụng một truy vấn duy nhất. Ví dụ:

>>> 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"
#Bài viết này được đăng tại freetuts.net
 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: .net freetuts>]>

Trong ví dụ này, Django chỉ thực thi một truy vấn để join bảng hr_employee hr_department.

Kết bài

Trong một mối quan hệ một-nhiều, một hàng trong một bảng được liên kết với một hoặc nhiều hàng trong một bảng khác. Để thiết lập mối quan hệ này trong Django, bạn sử dụng trường ForeignKey. Điều này cho phép bạn dễ dàng mô hình hóa các mối quan hệ phức tạp giữa các bảng dữ liệu và duy trì tính toàn vẹn của cơ sở dữ liệu.

Khi định nghĩa ForeignKey, hãy nhớ đặt nó trong mô hình ở phía "nhiều" của mối quan hệ. Điều này đảm bảo rằng mỗi bản ghi ở phía "nhiều" có thể liên kết với một bản ghi ở phía "một".

Cuối cùng, để tối ưu hóa các truy vấn và tránh vấn đề N + 1, bạn nên sử dụng phương thức select_related(). Phương thức này giúp join hai hoặc nhiều bảng trong mối quan hệ một-nhiều, giảm thiểu số lượng truy vấn cần thiết và tăng hiệu suất cho ứng dụng của bạn.

Bằng cách hiểu và áp dụng những kỹ thuật này, bạn sẽ có thể quản lý và khai thác dữ liệu một cách hiệu quả trong các dự án Django của mình.

Cùng chuyên mục:

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn toàn diện về module datetime trong Python

Hướng dẫn toàn diện về module datetime trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Từ khóa yield trong Python

Từ khóa yield trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Định dạng chuỗi Strings trong Python

Định dạng chuỗi Strings trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Làm việc với các biến môi trường trong Python

Làm việc với các biến môi trường trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa iterator và iterable trong Python

Sự khác biệt giữa iterator và iterable trong Python

Cách làm việc với file tarball/tar trong Python

Cách làm việc với file tarball/tar trong Python

Chuyển đổi kiểu dữ liệu trong Python

Chuyển đổi kiểu dữ liệu trong Python

Sự khác biệt giữa toán tử == và is trong Python

Sự khác biệt giữa toán tử == và is trong Python

Làm việc với file ZIP trong Python

Làm việc với file ZIP trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Sự khác biệt giữa byte objects và string trong Python

Sự khác biệt giữa byte objects và string trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format  trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Top