モデルとデータベース

この章では、Django ORM を使ってデータモデルを定義し、データベーステーブルを作成し、テストデータを挿入する方法を学びます。


なぜモデルが必要なのか?

前の章ではページのテキストをハードコードしました。本当のブログは複数の記事を保存し、内容は随時変更される必要があります。

モデル(Model)Django のデータベース層です。Python クラスでデータ構造を定義すると、Django が対応するデータベーステーブルを自動生成します。

SQL を一行も書く必要はありません。これがORM(オブジェクト関係マッピング)の中核的な価値です。


Post モデルを定義する

開いてblog/models.py、記事モデルを定義します。

実例

# ファイルパス:blog/models.py
from django.db import models
from django.utils import timezone

class Category(models.Model):
    """記事カテゴリー"""
    name = models.CharField('カテゴリー名', max_length=50, unique=True)
    # slug は URL 内の英語識別子に使われます(例:/category/django/)
    slug = models.SlugField('URL 識別子', max_length=50, unique=True)

    class Meta:
        verbose_name = 'カテゴリー'
        verbose_name_plural = 'カテゴリー'

    def __str__(self):
        return self.name


class Post(models.Model):
    """ブログ記事"""
    # タイトル:CharField は短いテキスト用、max_length は必須
    title = models.CharField('タイトル', max_length=200)

    # slug:記事の URL 内英語識別子
    slug = models.SlugField('URL 識別子', max_length=200, unique=True)

    # 要約:TextField は長いテキスト用、blank=True で任意指定
    summary = models.TextField('要約', blank=True)

    # 本文:TextField は長さ制限なし
    content = models.TextField('本文')

    # カテゴリー:ForeignKey 一対多の関係、1つの記事は1つのカテゴリーに属する
    # on_delete=models.CASCADE はカテゴリー削除時に、その下の全ての記事も一緒に削除することを示す
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        verbose_name='カテゴリー',
        related_name='posts'    # 逆引き:category.posts.all()
    )

    # auto_now_add:作成時に現在時刻を自動入力
    created_at = models.DateTimeField('作成日時', auto_now_add=True)

    # auto_now:保存のたびに現在時刻に自動更新
    updated_at = models.DateTimeField('更新日時', auto_now=True)

    class Meta:
        verbose_name = '記事'
        verbose_name_plural = '記事'
        # デフォルトは作成日時の降順
        ordering = ['-created_at']

    def __str__(self):
        # Admin バックエンドや Shell でオブジェクトを表示するときにタイトルを表示する
        return self.title

よく使うフィールド型早見表

フィールド型対応するデータベース型使用シーン
CharField(max_length=N)VARCHAR(N)タイトル、タグ、名前などの短いテキスト
TextField()TEXT記事本文、紹介文、コメントなどの内容
IntegerField()INTEGERカウント、年齢などの整数
DateTimeField()DATETIME公開日時、更新日時
ForeignKey()外部キー制約一対多の関係(記事→カテゴリー)
ManyToManyField()中間テーブル多対多の関係(記事←→タグ)
SlugField()VARCHARURL に適した識別子
ImageField()VARCHAR(パス)カバー画像、アバター(Pillow ライブラリが必要)

__str__メソッドは、オブジェクトが文字列としてどのように表示されるかを決定します。Admin バックエンドのドロップダウン選択ボックスや Shell 出力では、このメソッドが呼び出されてオブジェクト名が表示されます。このメソッドを書かないと、目にするのはPost object (1)このような無意味な文字です。


マイグレーション:モデルをデータベースに同期する

モデルクラスを書いたら、マイグレーション(Migration)でデータベーステーブルを作成します。

(venv) $ python manage.py makemigrations   # 生成迁移文件(检测模型变化)
Migrations for 'blog':
  blog/migrations/0001_initial.py
    - Create model Category
    - Create model Post

(venv) $ python manage.py migrate           # 执行迁移(创建数据库表)
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0001_initial... OK

2つのコマンドの役割分担:

  • makemigrations:models.py をスキャンして、マイグレーションスクリプト(Python ファイル)を生成する
  • migrate:マイグレーションスクリプトを実行して、データベースに実際のテーブルを作成する

models.py を変更するたびに、実行する必要があるのはmakemigrations + migrateこの2つのコマンドです。どちらも欠かせません。makemigrations だけで migrate しない場合、テーブルは作成されません。migrate を直接実行しても makemigrations を実行していなければ、Django はモデルが変更されたことを認識しません。


Django Shell を使ってテストデータを挿入する

ページを書き始める前に、Django Shell を使ってデータベースに数件のデータを入力し、モデルが正しく動作するか検証します。

(venv) $ python manage.py shell

これにより対話式の Python Shell が開き、その中でデータベースを操作できます。

実例

# Django Shell で1行ずつ入力
from blog.models import Category, Post

# 1. カテゴリーを作成
cat_django = Category.objects.create(name='Django', slug='django')
cat_python = Category.objects.create(name='Python', slug='python')
cat_css = Category.objects.create(name='CSS', slug='css')

# 2. 記事を作成
Post.objects.create(
    title='Django 入門完全ガイド',
    slug='django-beginner-guide',
    summary='ゼロから Django を学び、モデル、ビュー、テンプレートなどの核心概念をカバーします。',
    content='<h2>なぜ Django を学ぶのか?</h2><p>Django は Python で最も人気のある Web フレームワークの1つです...</p>',
    category=cat_django
)

Post.objects.create(
    title='Python 非同期プログラミング詳説',
    slug='python-async',
    summary='asyncio、コルーチン、イベントループを1つの記事で完全に理解する。',
    content='<h2>コルーチンとは?</h2><p>コルーチンはスレッドよりも軽量な並行処理の仕組みです...</p>',
    category=cat_python
)

Post.objects.create(
    title='CSS Grid レイアウト実践',
    slug='css-grid-layout',
    summary='CSS Grid を使って複雑なレスポンシブレイアウトを簡単に実現します。',
    content='<h2>Grid 入門</h2><p>Grid は2次元レイアウトシステムです...</p>',
    category=cat_css
)

# 3. クエリを検証
print(Post.objects.all())           # すべての記事を出力
print(Post.objects.count())         # 記事の総数
print(Post.objects.filter(category=cat_django))  # Django カテゴリーの記事

Django ORM 基本クエリ

ORM を使うと、Python のメソッドチェーンでデータベースを操作でき、SQL を書く必要はありません。

メソッド同等の SQL戻り値
Post.objects.all()SELECT * FROM blog_postQuerySet(すべての記事)
Post.objects.get(id=1)SELECT ... WHERE id=1単一のオブジェクト(見つからない場合は例外を投げる)
Post.objects.filter(category__slug='django')WHERE category.slug='django'QuerySet
Post.objects.order_by('-created_at')ORDER BY created_at DESCQuerySet
Post.objects.exclude(status='draft')WHERE status != 'draft'QuerySet

QuerySet は遅延評価です。クエリを定義してもすぐには実行されず、実際にデータが必要になったとき(ループ、表示、list への変換など)にのみ SQL が発行されます。そのため、複数の filter をチェーンしてもクエリが複数回実行されることはありません。


やってみよう:後続の章のためにデータを準備する

Shell で記事とカテゴリーをさらに数件入力し、Django、Python、CSS の3つのカテゴリーをカバーし、各カテゴリーに少なくとも2件の記事を用意します。

管理コマンドやスクリプトを書いて一括挿入することもできますが、現段階では Shell で1件ずつ挿入するので十分です。


本章のまとめ

この章では、Django ORM の中核的な流れを学びました:models.py で Python クラス(モデル)を定義し、makemigrations でマイグレーションファイルを生成し、migrate でデータベースに同期し、Shell と ORM でデータを照会・挿入します。

これで、記事とカテゴリーの構造がデータベースに構築されました。

その他の拡張