3.

Django Model 完全ガイド — フィールド型 / migrations / リレーション

編集
この記事の要点
  • Django の Model は DB テーブルの Python 表現。models.pyclass User(models.Model) として定義
  • 主なフィールド型: CharField / TextField / IntegerField / DateTimeField / BooleanField / DecimalField / EmailField / FileField
  • リレーション: ForeignKey (1 対多) / OneToOneField (1 対 1) / ManyToManyField (多対多)
  • 変更を DB に反映: python manage.py makemigrationspython manage.py migrate
  • メタ情報は class Metaテーブル名 / 並び順 / Index / Constraint / verbose_name を指定

Model とは

Django Model は DB テーブルに対応する Python クラスです。1 クラス = 1 テーブル、1 フィールド = 1 カラム、1 インスタンス = 1 レコード。

SQL を書かずに、Python のクラス定義でテーブル設計からクエリまでできるのが Django ORM の最大の特徴です。

最小の Model 定義

# myapp/models.py
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField(unique=True)
    age = models.IntegerField(null=True, blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "users"          # 任意。デフォルト app_user
        ordering = ["-created_at"]
        verbose_name = "ユーザー"
        verbose_name_plural = "ユーザー一覧"

    def __str__(self):
        return self.name

フィールド型一覧

フィールドSQL 型用途
CharField(max_length=N)VARCHAR(N)短い文字列
TextField()TEXT長文
IntegerField()INTEGER整数
BigIntegerField()BIGINT大きな整数
SmallIntegerField()SMALLINT小さな整数
FloatField()DOUBLE浮動小数
DecimalField(max_digits, decimal_places)DECIMAL金額など正確な小数
BooleanField()BOOLEAN真偽値
DateField()DATE日付
DateTimeField()DATETIME日時
TimeField()TIME時刻
EmailField()VARCHARメール (バリデーション付)
URLField()VARCHARURL
SlugField()VARCHARURL に使える短い識別子
FileField(upload_to=...)VARCHAR (パス)ファイルアップロード
ImageField()VARCHAR (パス)画像 (Pillow 必須)
JSONField()JSONJSON データ
UUIDField()UUIDUUID 主キー

共通オプション

オプション意味
null=TrueDB レベルで NULL を許可
blank=Trueフォームで空を許可
default=値デフォルト値
unique=TrueUNIQUE 制約
db_index=TrueINDEX を作成
choices=[(...)]選択肢を制限
verbose_name="..."管理画面の表示名
help_text="..."説明文
editable=False管理画面で編集不可

リレーション

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

class Book(models.Model):
    title = models.CharField(max_length=200)
    # 1 対多: 1 著者が多数の本
    author = models.ForeignKey(
        Author,
        on_delete=models.CASCADE,        # 著者削除で本も削除
        related_name="books",            # author.books.all() でアクセス可
    )

class Profile(models.Model):
    # 1 対 1
    user = models.OneToOneField(
        "auth.User",
        on_delete=models.CASCADE,
        primary_key=True,
    )
    bio = models.TextField()

class Tag(models.Model):
    name = models.CharField(max_length=30)

class Article(models.Model):
    title = models.CharField(max_length=200)
    # 多対多: 中間テーブル自動生成
    tags = models.ManyToManyField(Tag, related_name="articles", blank=True)

on_delete の値

動作
CASCADE親削除で子も削除
PROTECT子があれば親削除を拒否 (例外)
SET_NULL親削除で子の外部キーを NULL に (null=True 必須)
SET_DEFAULT親削除で default 値に
SET(値)特定値にセット
DO_NOTHING何もしない (DB レベル制約に任せる)

Meta クラス

class Article(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    published_at = models.DateTimeField()

    class Meta:
        db_table = "articles"
        ordering = ["-published_at"]
        verbose_name = "記事"
        verbose_name_plural = "記事一覧"
        unique_together = [["author", "slug"]]    # 著者 × slug で一意
        indexes = [
            models.Index(fields=["published_at"]),
            models.Index(fields=["author", "-published_at"]),
        ]
        constraints = [
            models.CheckConstraint(
                check=models.Q(title__gt=""),
                name="title_not_empty",
            ),
        ]

migrations の流れ

# 1. models.py を編集

# 2. マイグレーションファイル生成
python manage.py makemigrations
# → myapp/migrations/0001_initial.py が作られる

# 3. SQL を確認 (任意)
python manage.py sqlmigrate myapp 0001

# 4. DB に適用
python manage.py migrate

# 状態確認
python manage.py showmigrations

# マイグレーションを戻す
python manage.py migrate myapp 0001
python manage.py migrate myapp zero    # 全部戻す

クエリ (ORM)

# 取得
User.objects.all()
User.objects.filter(is_active=True)
User.objects.filter(name__icontains="taro")     # LIKE %taro% (case-insensitive)
User.objects.filter(age__gte=20, age__lt=30)
User.objects.exclude(is_active=False)
User.objects.get(pk=1)                          # 1 件、無ければ DoesNotExist
User.objects.first()
User.objects.count()
User.objects.exists()

# 並び替え・ページネーション
User.objects.order_by("-created_at")[:10]

# 作成
User.objects.create(name="taro", email="t@example.com")
u = User(name="hanako")
u.save()

# 更新
User.objects.filter(is_active=False).update(is_active=True)
u = User.objects.get(pk=1)
u.name = "改名"
u.save()

# 削除
User.objects.filter(is_active=False).delete()

# リレーション
book = Book.objects.select_related("author").get(pk=1)
author.books.all()       # related_name 経由
article.tags.add(tag1, tag2)
article.tags.remove(tag1)
article.tags.set([tag1, tag3])

シグナル (signals)

モデルの保存・削除に処理をフックできます:

from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver

@receiver(post_save, sender=User)
def on_user_save(sender, instance, created, **kwargs):
    if created:
        print(f"新規ユーザー: {instance.name}")

@receiver(post_delete, sender=User)
def on_user_delete(sender, instance, **kwargs):
    print(f"削除: {instance.name}")

FAQ

Q: マイグレーションがコンフリクト
A: python manage.py makemigrations --merge で統合マイグレーションを作成。

Q: null=Trueblank=True の違い
A: null は DB レベル、blank はフォーム / バリデーションレベル。文字列系は null=True を避け blank=True のみ推奨 (空文字で扱う)。

Q: テーブル名を変えたい
A: class Meta: db_table = "custom_name" で指定。既存テーブルの rename は RenameModel マイグレーションが必要。

編集
Post Share
子ページ
  1. Model の定義方法
  2. マイグレーションファイルの作成
  3. テーブル定義の確認
  4. テーブルの作成
  5. テーブル名 = アプリケーション名 + モデル名の設定変更
  6. モデルの中身を確認
同階層のページ
  1. ビュー(View)
  2. テンプレート(Template)
  3. モデル(Model)
  4. ルーティングの作成
  5. viewからtemplateへの遷移方法