データベース設計ではテーブル同士を関連付けることが不可欠です。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 は親レコードが削除されたときの振る舞いを指定する必須パラメータです。
親が削除されたら子も一緒に削除される。最もよく使われる設定。
親に紐づく子が存在する限り、親の削除を禁止する。誤削除を防ぎたいときに有効。
親が削除されたら子の外部キーを NULL にする。null=True と組み合わせて使う。
親が削除されたら子の外部キーをデフォルト値にリセットする。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 つのプロフィールが対応する」のような関係が一対一です。内部的には ForeignKey に unique=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)
多対一。逆方向アクセスは QuerySet を返す(例: category.articles.all())
一対一。逆方向アクセスはインスタンスを直接返す(例: 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)
Comment は Article と User の両方にリレーションを持ちます。「どの記事に対する、誰のコメントか」を 1 つのモデルで表現できるわけです。
リレーションの設計はアプリケーション全体の構造を決定づけます。「1 対多なのか 1 対 1 なのか」を正確に見極めることが、無理のないデータベース設計への第一歩になります。