Djangoのテーブル間リレーションシップを理解する

プログラミング

Djangoでは外部キーの紐付けはModelから簡単に設定することが出来ます。テーブルのリレーションシップは主に3種類あります。

Djangoでは ForeignKeyOneToOneFieldManyToManyFieldという3つのフィールドをモデルに設定するだけで、自動的にDB上でリレーションシップが作成されます。多対多の場合は中間テーブルも自動で作成してくれます。

ForeignKey(1対多)

ブログで複数の記事がひとつのカテゴリに属するという設定にしています。

ForeignKeyを使用してPost(多)に対してCategory(1)を紐付けします。

class Post(models.Model):
    title = models.CharField(max_length=128)
    body = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.PROTECT)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)


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

インスタンス名.フィールド名(多→1)

post.category でその記事の所属するカテゴリを取得できます。

def post_view(self, post_id):
    post = Post.objects.get(pk=post_id)
    category = post.category

インスタンス名.フィールド名_id (多→1)

最初なぜこれでアクセスできるのかよくわからなかったのですが、自動で設定されるようです。post.category_id にアクセスすると外部テーブルである、Categoryのid(プライマリーキー)が取得出来ます。

category_id = post.category_id

インスタンス名.モデル名_set.all()(1→多)

このように記述すると、Category側から逆参照できます。.all()の部分は好きなQuerysetを使用すればいいと思います。(Django逆引きチートシート(QuerySet編)

def category_list_view(self):
    first_category = Category.objects.first()
    posts = first_category.post_set.all()

first_category.post_set.all() でそのカテゴリに所属するすべてのPostを取得します。

related_nameを使う場合

class Post(models.Model):
    ....
    category = models.ForeignKey(
        Category, on_delete=models.PROTECT, related_name='posts'
    )

上記のように設定すると、category.posts(インスタンス名.related_name名) で参照できるようになります。

>>> from .models import Category
>>> category = Category.objects.get(id=1)
>>> category.posts.all()

OneToOneField(1対1)

OneToOneFieldでは2つのフィールドを一対一の関係性で紐付けます。今回は例として、Djangoが持つデフォルトのユーザーに対してプロフィールをOneToOneで紐付けます。

from django.db import models
from django.conf import settings


class Profile(models.Model):

    GENDER_CHOICES = (
        ('1', '女性'),
        ('2', '男性'),
    )

    nickname = models.CharField('ニックネーム', max_length=30)
    bio = models.TextField('自己紹介', max_length=500)
    gender = models.CharField('性別', choices=GENDER_CHOICES, max_length=2)
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

今回の記事では関係ないことですが、紐付けているユーザーはsettingsのAUTH_USER_MODELとしています。理由はなりとさんのブログを参考にしています。(いつも勉強させて頂いております🙇🏻)

組み込みUserと関連付ける場合は、Userモデルを直接指定するのではなく、settings.AUTH_USER_MODELとするのがベターです。

Django、予約サイトのモデルを作成 : https://blog.narito.ninja/detail/158

インスタンス名.モデル名で互いにアクセス

これでuserインスタンスからはuser.profile、profileインスタンスからはprofile.userでアクセスできるようになります。シンプルで一番わかりやすいですね😄

>>> from snippets.models import Profile
>>> from django.contrib.auth.models import User

>>> user = User.objects.get(pk=1)
>>> user.profile.bio
>>> "Hi there! I'm kazuo."

>>> profile = Profile.objects.get(user=user)
>>> profile.user.username
>>> 'kazuo'

ManyToManyField(多対多)

以下の図のように、ブログ記事に対するタグ(多対多)の関係を紐付けるには、ManyToManyFieldを使用します。

かえってわかりにくいような図になってしまったかも😅
from django.db import models


class Post(models.Model):
    title = models.CharField('タイトル', max_length=32)
    body = models.TextField('本文')
    created_at = models.DateTimeField(auto_now=True)


class Tag(models.Model):
    tag_name = models.CharField('タグ', max_length=100)
    posts = models.ManyToManyField(Post)

モデルはこのようにManyToManyFieldで紐付けします。

インスタンス名.モデル名_set.all()(ManyToManyFieldがない側)

Tagから紐付けされたPost側からはインスタンス名.モデル名_set.all()でアクセスできます。

>>> from post.models import Post, Tag

>>> post = Post.objects.get(pk=1)
>>> post.tag_set.all()
>>> <QuerySet [<Tag: Tag object (1)>]>

インスタンス名.フィールド名.all()(ManyToManyFieldがある側)

外部キーで紐付けを行ったTag側からPostにアクセスする場合は、インスタンス名.フィールド名.all()でデータを取得できます。

>>> tag = Tag.objects.get(pk=1)
>>> tag.posts.all()
>>> <PostQuerySet [<Post: 初めての日記>, <Post: 今日もいい天気>]>

Django外部キーアクセス方法まとめ

タイプ外部キーフィールドがある側外部キーフィールドがない側
ForeignKey(1対多)インスタンス名.フィールド名インスタンス名.モデル名_set.all()
OneToOneField(1対1)インスタンス名.モデル名インスタンス名.モデル名
ManyToManyField(多対多)インスタンス名.フィールド名.all()インスタンス名.モデル名_set.all()

注).all() の部分は好きなfilterを使用できます。

コメント