ForeignKey と OneToOneField でリレーションを作る

データベース設計ではテーブル同士を関連付けることが不可欠です。Django では ForeignKey(多対一)と OneToOneField(一対一)の 2 つのフィールドで、リレーションを Python のクラス属性として表現できます。

ForeignKey(多対一)

「1 つのカテゴリに複数の記事が属する」のような関係が多対一です。ForeignKey を使って、「多」側のモデルに定義します。

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100)

class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        related_name='articles'
    )

Article から Category へのリレーションが作られ、データベースには category_id というカラムが追加されます。on_delete は親レコードが削除されたときの振る舞いを指定する必須パラメータです。

CASCADE

親が削除されたら子も一緒に削除される。最もよく使われる設定。

PROTECT

親に紐づく子が存在する限り、親の削除を禁止する。誤削除を防ぎたいときに有効。

SET_NULL

親が削除されたら子の外部キーを NULL にする。null=True と組み合わせて使う。

SET_DEFAULT

親が削除されたら子の外部キーをデフォルト値にリセットする。default の指定が必要。

ForeignKey を使ったデータの作成と参照

リレーションが定義されていれば、親オブジェクトをそのまま代入できます。

tech = Category.objects.create(name='技術')
article = Article.objects.create(
    title='Django入門',
    body='本文...',
    category=tech
)

# 記事からカテゴリを参照
print(article.category.name)  # '技術'

逆方向のアクセスには related_name で指定した名前を使います。

# カテゴリから記事一覧を参照
tech_articles = tech.articles.all()

related_name を省略すると、Django は article_set という名前を自動生成します。明示的に指定しておく方が可読性は高くなります。

OneToOneField(一対一)

「1 人のユーザーに 1 つのプロフィールが対応する」のような関係が一対一です。内部的には ForeignKeyunique=True を付けたものと同等ですが、逆方向のアクセスが QuerySet ではなくインスタンスを直接返す点が異なります。

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)
    avatar_url = models.URLField(blank=True)
ForeignKey

多対一。逆方向アクセスは QuerySet を返す(例: category.articles.all())

OneToOneField

一対一。逆方向アクセスはインスタンスを直接返す(例: user.profile)

OneToOneField を使ったデータの作成と参照

from django.contrib.auth.models import User

user = User.objects.create_user(
    username='alice',
    password='secret123'
)
profile = Profile.objects.create(
    user=user,
    bio='Pythonが好きです'
)

# ユーザーからプロフィールを参照
print(user.profile.bio)  # 'Pythonが好きです'

# プロフィールからユーザーを参照
print(profile.user.username)  # 'alice'

OneToOneField では逆方向のアクセスが .profile のようにインスタンスを直接返します。該当するレコードが存在しない場合は RelatedObjectDoesNotExist 例外が発生するため、hasattr(user, 'profile') で確認するか、例外処理を入れるのが安全です。

ForeignKey の活用パターン

実際のアプリケーションでは、1 つのモデルに複数の ForeignKey を持たせることもあります。

class Comment(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        related_name='comments'
    )
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='comments'
    )
    text = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

CommentArticleUser の両方にリレーションを持ちます。「どの記事に対する、誰のコメントか」を 1 つのモデルで表現できるわけです。

リレーションの設計はアプリケーション全体の構造を決定づけます。「1 対多なのか 1 対 1 なのか」を正確に見極めることが、無理のないデータベース設計への第一歩になります。