タイトル: テーブルの作成
SEOタイトル: Django モデルでのテーブル作成完全ガイド(CharField / ForeignKey / migrate)
| この記事の要点 |
|
Django モデルとテーブルの関係
Django ORM では models.py に書いた Python クラスが、そのまま 1 つの DB テーブルに対応します。フィールド型は SQL の DDL に自動変換され、マイグレーション機能で世代管理されます。スキーマ変更も全部 Python コードで管理できるのが強み。
| Python クラス | ↔ | DB テーブル |
|---|---|---|
| class Post(models.Model) | ↔ | blog_post テーブル |
| title = CharField(max_length=200) | ↔ | title VARCHAR(200) |
| created_at = DateTimeField() | ↔ | created_at DATETIME |
| author = ForeignKey(User) | ↔ | author_id BIGINT + FK 制約 |
基本のモデル定義
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField('カテゴリ名', max_length=50, unique=True)
slug = models.SlugField(max_length=50, unique=True)
class Meta:
db_table = 'blog_category'
verbose_name = 'カテゴリ'
verbose_name_plural = 'カテゴリ一覧'
ordering = ['name']
def __str__(self):
return self.name
class Post(models.Model):
STATUS_CHOICES = [
('draft', '下書き'),
('published', '公開'),
('archived', 'アーカイブ'),
]
title = models.CharField('タイトル', max_length=200)
body = models.TextField('本文')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
view_count = models.PositiveIntegerField(default=0)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='posts',
verbose_name='著者',
)
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True, blank=True,
related_name='posts',
)
tags = models.ManyToManyField('Tag', blank=True, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = 'blog_post'
ordering = ['-created_at']
indexes = [
models.Index(fields=['status', '-created_at']),
models.Index(fields=['author', 'status']),
]
constraints = [
models.UniqueConstraint(
fields=['author', 'title'],
name='unique_author_title',
),
]
def __str__(self):
return self.title
class Tag(models.Model):
name = models.CharField(max_length=30, unique=True)
def __str__(self):
return self.name
主なフィールド型
| Django フィールド | 用途 | SQL 型 (PostgreSQL 例) |
|---|---|---|
CharField(max_length=N) | 短い文字列(必須: max_length) | VARCHAR(N) |
TextField() | 長文 | TEXT |
SlugField(max_length=50) | URL 用識別子 | VARCHAR(50) |
EmailField() | メールアドレス(バリデーション付き) | VARCHAR(254) |
URLField() | URL | VARCHAR(200) |
IntegerField() | 整数 | INTEGER |
BigIntegerField() | 大きな整数 | BIGINT |
PositiveIntegerField() | 0 以上の整数 | INTEGER CHECK (>= 0) |
FloatField() | 浮動小数点 | DOUBLE PRECISION |
DecimalField(max_digits, decimal_places) | 金額・正確な小数 | NUMERIC |
BooleanField() | 真偽値 | BOOLEAN |
DateField() | 日付 | DATE |
DateTimeField() | 日時 | TIMESTAMP |
TimeField() | 時刻 | TIME |
JSONField() | JSON(Django 3.1+) | JSONB (PostgreSQL) |
FileField(upload_to=...) | ファイルアップロード | VARCHAR (パス) |
ImageField(upload_to=...) | 画像(Pillow 必須) | VARCHAR (パス) |
UUIDField(default=uuid.uuid4) | UUID 主キー | UUID |
リレーション 3 種
| 種類 | フィールド | 例 |
|---|---|---|
| 多対 1 | ForeignKey | 記事 N - 著者 1 |
| 1 対 1 | OneToOneField | User - Profile |
| 多対多 | ManyToManyField | 記事 N - タグ N |
on_delete の選択肢
# author が削除されたときの挙動
author = models.ForeignKey(User, on_delete=models.CASCADE)
# CASCADE - 関連も一緒に削除
# PROTECT - 関連があれば削除をエラーに
# SET_NULL - NULL にする (null=True 必須)
# SET_DEFAULT - デフォルト値にする (default 必須)
# SET(関数) - 任意の関数で値設定
# DO_NOTHING - 何もしない (SQL レベルの FK 制約による)
多対多の through テーブル
# 追加情報(タグ付与日など)を中間テーブルに持たせたい場合
class Post(models.Model):
tags = models.ManyToManyField('Tag', through='PostTag')
class PostTag(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
tag = models.ForeignKey('Tag', on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)
weight = models.IntegerField(default=1)
class Meta:
unique_together = ('post', 'tag')
マイグレーションの実行
# 1. マイグレーションファイル生成
python manage.py makemigrations blog
# blog/migrations/0001_initial.py が生成される
# 2. 生成された SQL を確認(実行はしない)
python manage.py sqlmigrate blog 0001
# 3. DB に反映
python manage.py migrate
# 4. 適用状態の確認
python manage.py showmigrations
# 5. ロールバック(前の世代に戻す)
python manage.py migrate blog 0001
# 6. ゼロまで戻す
python manage.py migrate blog zero
admin.py に登録
# blog/admin.py
from django.contrib import admin
from .models import Post, Category, Tag, PostTag
class PostTagInline(admin.TabularInline):
model = PostTag
extra = 1
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'author', 'category', 'status', 'created_at')
list_filter = ('status', 'category', 'created_at')
search_fields = ('title', 'body')
raw_id_fields = ('author',)
inlines = [PostTagInline]
date_hierarchy = 'created_at'
admin.site.register(Category)
admin.site.register(Tag)
主キー(id)の挙動
明示しなければ id BIGAUTO が自動で追加されます(Django 3.2+)。UUID 主キーにしたいときは:
import uuid
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
FAQ
Q: 既存 DB に Django を載せたい(マイグレーション不要)
A: python manage.py inspectdb > models.py で既存テーブルから models を逆生成。各モデルで class Meta: managed = False を付けると Django が DDL に介入しなくなります。
Q: マイグレーションファイルをやり直したい
A: 開発初期なら migrations/ 内の 0001_initial.py 以降を削除 + DB をリセット + 再 makemigrations。本番では絶対やらない。
Q: フィールド追加で「default を聞かれる」
A: 既存レコードに対する初期値が必要。default= を付ける、null=True を付ける、または対話プロンプトで一時値を指定します。