ORM

Django ORMのモデル紐づけ入門|ForeignKey・OneToOne・ManyToMany

Django ORM リレーション

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を定義します。

models.py
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つだけ対応するレコードがある場合に使用します。

models.py
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)

両方のモデルが複数の相手と関連を持てる場合に使用します。

models.py
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が自動的に中間テーブルを作成します。どちらのモデルに定義しても機能は同じですが、より自然な方に置くのが一般的です。

Python
# 多対多のデータ操作
student = Student.objects.create(name="田中太郎")
course = Course.objects.create(title="Python入門")

# 関連を追加
course.students.add(student)

# 関連を削除
course.students.remove(student)

# 全関連をクリア
course.students.clear()

Step 5関連データへのアクセス

リレーションを設定すると、関連データに簡単にアクセスできます。

Python
# === 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何もしない(整合性は自己責任)
models.py
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_NULLPROTECTを検討してください。

まとめ

  • ForeignKeyで一対多(N:1)のリレーションを設定する
  • OneToOneFieldで一対一(1:1)のリレーションを設定する
  • ManyToManyFieldで多対多(N:N)のリレーションを設定する
  • related_nameで逆参照の名前を指定できる
  • on_deleteで親レコード削除時の挙動を制御する
  • 関連データへはemployee.companycompany.employees.all()でアクセスする