Django ORMのモデル紐づけ入門
ForeignKey・OneToOne・ManyToMany
Djangoのモデル間リレーション(ForeignKey, OneToOneField, ManyToManyField)の設定と使い方を解説します。
こんな人向けの記事です
- モデル間のリレーションを設定したい人
- ForeignKey, OneToOneField, ManyToManyFieldの違いを知りたい人
- 関連データのアクセス方法を理解したい人
Step 1リレーションの種類
Djangoでモデル間のリレーションを設定するフィールドは3種類あります。
| フィールド | 関係 | 例 |
|---|---|---|
| ForeignKey | 多対一(N:1) | 社員 → 会社 |
| OneToOneField | 一対一(1:1) | ユーザー → プロフィール |
| ManyToManyField | 多対多(N:N) | 学生 ↔ 講座 |
Step 2一対多(ForeignKey)
最も一般的なリレーションです。「多」側のモデルにForeignKeyを定義します。
from django.db import models
class Company(models.Model):
name = models.CharField(max_length=200)
founding_date = models.DateField(null=True, blank=True)
def __str__(self):
return self.name
class Employee(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField(null=True, blank=True)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE,
related_name="employees"
)
def __str__(self):
return self.name
ForeignKeyの第一引数には関連先のモデルを指定します。on_deleteは親レコードが削除された時の挙動を指定する必須パラメータです。
ポイント: related_nameを設定すると、親モデルから子モデルにアクセスする際の名前を指定できます。設定しない場合はモデル名_set(例: employee_set)がデフォルトで使用されます。
Step 3一対一(OneToOneField)
1つのレコードに1つだけ対応するレコードがある場合に使用します。
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name="profile"
)
bio = models.TextField(blank=True)
website = models.URLField(blank=True)
def __str__(self):
return f"{self.user.username}のプロフィール"
OneToOneFieldはForeignKeyにunique=Trueを付けたものと同等です。1つのUserに対して1つのProfileしか存在できません。
Step 4多対多(ManyToManyField)
両方のモデルが複数の相手と関連を持てる場合に使用します。
class Student(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Course(models.Model):
title = models.CharField(max_length=200)
students = models.ManyToManyField(
Student,
related_name="courses",
blank=True
)
def __str__(self):
return self.title
ManyToManyFieldを定義すると、Djangoが自動的に中間テーブルを作成します。どちらのモデルに定義しても機能は同じですが、より自然な方に置くのが一般的です。
# 多対多のデータ操作
student = Student.objects.create(name="田中太郎")
course = Course.objects.create(title="Python入門")
# 関連を追加
course.students.add(student)
# 関連を削除
course.students.remove(student)
# 全関連をクリア
course.students.clear()
Step 5関連データへのアクセス
リレーションを設定すると、関連データに簡単にアクセスできます。
# === ForeignKey(一対多) ===
# 子から親を取得
employee = Employee.objects.get(id=1)
company = employee.company # Companyオブジェクト
print(company.name)
# 親から子を取得(related_nameを使用)
company = Company.objects.get(id=1)
employees = company.employees.all() # QuerySet
for emp in employees:
print(emp.name)
# === OneToOneField(一対一) ===
user = User.objects.get(id=1)
profile = user.profile # Profileオブジェクト
# === ManyToManyField(多対多) ===
course = Course.objects.get(id=1)
students = course.students.all() # 受講生一覧
student = Student.objects.get(id=1)
courses = student.courses.all() # 受講講座一覧
注意: ForeignKeyの逆参照でrelated_nameを設定していない場合は、company.employee_set.all()のように_setを使ってアクセスします。
Step 6on_deleteオプション
ForeignKeyとOneToOneFieldでは、親レコードが削除された時の挙動をon_deleteで指定します。
| オプション | 挙動 |
|---|---|
| CASCADE | 親と一緒に子も削除する(最も一般的) |
| PROTECT | 子が存在する場合、親の削除を拒否する |
| SET_NULL | 外部キーをNULLにする(null=True必須) |
| SET_DEFAULT | 外部キーをデフォルト値にする(default必須) |
| DO_NOTHING | 何もしない(整合性は自己責任) |
class Employee(models.Model):
name = models.CharField(max_length=100)
# 会社が削除されたら社員も削除
company = models.ForeignKey(Company, on_delete=models.CASCADE)
class Order(models.Model):
product_name = models.CharField(max_length=200)
# 顧客が削除されてもNULLにして注文は残す
customer = models.ForeignKey(
Customer, on_delete=models.SET_NULL, null=True
)
class Account(models.Model):
balance = models.DecimalField(max_digits=10, decimal_places=2)
# ユーザーが削除されようとしたらエラーにする
user = models.OneToOneField(User, on_delete=models.PROTECT)
ポイント: 一般的なWebアプリケーションではCASCADEが最も多く使われますが、注文データなど削除したくないデータがある場合はSET_NULLやPROTECTを検討してください。
まとめ
ForeignKeyで一対多(N:1)のリレーションを設定するOneToOneFieldで一対一(1:1)のリレーションを設定するManyToManyFieldで多対多(N:N)のリレーションを設定するrelated_nameで逆参照の名前を指定できるon_deleteで親レコード削除時の挙動を制御する- 関連データへは
employee.companyやcompany.employees.all()でアクセスする