【Django Model入門】モデルでデータベースを操作する基本を完全解説
はじめに:本格的なWebアプリケーションへの第一歩
前回までは、ビュー関数の中に直接データを書いていました。でも、これって実際のWebアプリケーションではありえないですよね。
# これまでのやり方(データを直接書いていた)
POSTS = [
{
"id": 1,
"title": "Djangoを始めました",
"content": "Djangoの学習を始めました。楽しいです!",
# ...
},
]
今回は、ついにデータベースを使ってデータを管理する方法を学びます!これができるようになると、本当の意味でのWebアプリケーションが作れるようになります。
「データベースって難しそう...」と思うかもしれません。でも大丈夫!DjangoのORM(Object-Relational Mapping)を使えば、SQLを書かなくてもPythonのコードでデータベースを操作できるんです。
初めてこの記事を読む方へ:
この記事は連載の第6回目です。テンプレートの基本がまだの方は、第5回の記事をご覧ください。もしすぐにモデルの作成を始めたい方は、前回GitHubコードを参考にしながら進めることもできます。
この記事で学べること
- DjangoのModel(モデル)とは何か
- モデルの作成方法とフィールドタイプ
- マイグレーションの仕組みと実行方法
- Django管理画面の使い方
- QuerySetを使ったデータの取得・作成・更新・削除
- ビューとモデルの連携方法
それでは、データベースの世界へ飛び込んでいきましょう!
Model(モデル)とは?
▶MVTパターンのMの部分
覚えていますか?DjangoのMVTパターン:
- Model:データの管理(今回の主役!)
- View:リクエストを受けて、レスポンスを返す
- Template:HTMLの生成
今回は、この「Model」の部分を実装していきます。
▶モデルの役割
モデルは「データベースとPythonの橋渡し役」です。
例えるなら:
- データベース:巨大な倉庫(データを保管)
- モデル:倉庫管理人(データの出し入れを担当)
- ビュー:注文係(何が必要か伝える)
▶ORMって何?
ORM(Object-Relational Mapping) は、オブジェクト指向プログラミング言語(Python)とリレーショナルデータベース(SQLite、PostgreSQLなど)の間を取り持つ技術です。
簡単に言うと:
- SQLを書かなくても、Pythonのコードでデータベースを操作できる
- データベースのテーブルをPythonのクラスとして扱える
- データベースの行(レコード)をPythonのオブジェクトとして扱える
# SQLを使った場合
# SELECT * FROM blog_post WHERE title LIKE '%Django%';
# DjangoのORMを使った場合
Post.objects.filter(title__contains='Django')
どちらが読みやすいですか?ORMの方がPythonらしくて分かりやすいですよね!
▶DjangoのORMがどのように動くか
ここで、DjangoのORMがどのように動作するのか、もう少し詳しく見てみましょう。
あなたがPythonで書いたコードは、Django ORMによって自動的にSQLに変換され、データベースで実行されます。その結果は再びPythonオブジェクトとして返ってきます。
この図を詳しく説明すると:
-
Pythonコード(一番上)
- あなたが実際に書くコード
Post.objects.filter()
のような、Pythonらしい直感的な書き方- SQLの知識がなくても理解できる
-
Django ORM(真ん中)
- Pythonコードを解析して、適切なSQLクエリに変換
- データベースの種類(SQLite、PostgreSQL、MySQLなど)に応じて、適切なSQL方言を生成
- 「翻訳者」のような役割
-
SQLクエリ(下から2番目)
- ORMが自動生成したSQL文
- 手書きのSQLと同等の効率的なクエリ
- デバッグ時には、どんなSQLが生成されたか確認することも可能
-
データベース(一番下)
- 実際のデータが保存されている場所
- テーブル形式でデータを管理
- ORMのおかげで、直接触る必要がない
つまり、あなたはPythonコードを書くだけで、残りはすべてDjangoが自動的に処理してくれるんです!これがORMの素晴らしいところです。
▶モデルクラスとデータベーステーブルの関係
もう一つ重要な概念があります。DjangoのORMは、Pythonのクラスをデータベースのテーブルに変換してくれます。
具体的にどういうことか、図で見てみましょう:
この変換の仕組みを理解すると、モデルの作成がより簡単になります:
-
Pythonクラス(左側)
class Post(Model):
で定義- 各フィールドはクラス変数として定義
CharField
、TextField
などのフィールドタイプを使用- Pythonの通常のクラスと同じように書ける
-
データベーステーブル(右側)
- クラス名が自動的にテーブル名に変換(
Post
→blog_post
) - 各フィールドがテーブルのカラムに変換
- フィールドタイプが適切なデータ型に変換(
CharField
→VARCHAR
) - 自動的に
id
フィールド(主キー)が追加される
- クラス名が自動的にテーブル名に変換(
-
オブジェクトとレコードの対応
- Pythonで作成したオブジェクト(
post = Post()
)は、データベースの1行(レコード)に対応 post.save()
を実行すると、データベースに保存される- オブジェクトの属性を変更すると、データベースのデータも更新できる
- Pythonで作成したオブジェクト(
この仕組みのおかげで、データベースのテーブル設計をSQLで書く必要がないんです。Pythonクラスを書くだけで、Djangoが適切なテーブルを作成してくれます。
僕も最初は「本当にこれだけでテーブルができるの?」と半信半疑でしたが、実際にやってみると本当に簡単でした!
最初のモデルを作成しよう
▶Postモデルの設計
ブログの記事(Post)を管理するモデルを作ります。まず、どんな情報を保存したいか考えてみましょう:
- タイトル(title)
- 本文(content)
- 注目記事(is_featured)
- 公開状態(is_published)
- 作成日時(created_at)
- 更新日時(updated_at)
▶モデルの実装
blog/models.py
を開いて、以下のように編集します:
from django.db import models
from django.utils import timezone
class Post(models.Model):
"""ブログ記事のモデル"""
title = models.CharField(max_length=200, verbose_name="タイトル")
content = models.TextField(verbose_name="本文")
is_featured = models.BooleanField(default=False, verbose_name="注目記事")
is_published = models.BooleanField(default=False, verbose_name="公開状態")
created_at = models.DateTimeField(default=timezone.now, verbose_name="作成日時")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")
class Meta:
verbose_name = "記事"
verbose_name_plural = "記事"
ordering = ["-created_at"] # 新しい記事から順に並べる
def __str__(self):
return self.title
▶コードの詳しい解説
1. クラスの定義
class Post(models.Model):
models.Model
を継承することで、このクラスがDjangoのモデルになります- クラス名は単数形で、最初の文字は大文字にします(Post、User、Categoryなど)
2. フィールドの定義
CharField:短い文字列用
title = models.CharField(max_length=200, verbose_name='タイトル')
max_length
:最大文字数(必須)verbose_name
:管理画面での表示名
TextField:長い文字列用
content = models.TextField(verbose_name='本文')
- 文字数制限なし
- ブログ本文のような長文に適している
DateTimeField:日時用
created_at = models.DateTimeField(default=timezone.now, verbose_name='作成日時')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新日時')
default=timezone.now
:作成時に現在時刻を自動設定auto_now=True
:保存するたびに現在時刻で更新
timezone.nowの注意点
default=timezone.now
と書くときは、timezone.now()
ではなくtimezone.now
と書きます!
- 正しい:
default=timezone.now
(関数そのものを渡す) - 間違い:
default=timezone.now()
(関数の実行結果を渡してしまう)
括弧を付けると、モデル定義時の時刻が固定されてしまいます。
BooleanField:真偽値用
is_published = models.BooleanField(default=False, verbose_name='公開状態')
True
またはFalse
を保存default=False
:デフォルトは非公開
3. Metaクラス
class Meta:
verbose_name = '記事'
verbose_name_plural = '記事'
ordering = ['-created_at']
Metaクラスは、モデルに関するメタデータ(モデル自体の設定)を定義する場所です:
verbose_name
:モデルの表示名(単数形)verbose_name_plural
:モデルの表示名(複数形)ordering
:デフォルトの並び順(-
を付けると降順)
verbose_nameの単数形・複数形について
日本語では「記事」という言葉は単数でも複数でも同じなので、両方に「記事」と設定していますが、これは英語の場合に特に重要になります:
# 英語のモデルの例
class Category(models.Model):
name = models.CharField(max_length=50)
class Meta:
verbose_name = 'category' # 単数形
verbose_name_plural = 'categories' # 複数形
Djangoの管理画面は、これらの設定を使って表示を切り替えます:
- 「Add category」(1つ追加)
- 「3 categories」(3つのカテゴリー)
日本語でも、例えば「ユーザー」と「ユーザー一覧」のように使い分けたい場合は:
verbose_name = 'ユーザー'
verbose_name_plural = 'ユーザー一覧'
Metaクラスの設定は管理画面以外でも使われます
verbose_name
やverbose_name_plural
は主に管理画面で使われますが、それ以外にも:
- フォームの自動生成時:ModelFormでラベルとして使用
- APIのドキュメント生成時:Django REST frameworkなどで活用
- エラーメッセージ:モデル名を含むエラーメッセージで使用
また、ordering
は:
- QuerySetのデフォルト順序:
Post.objects.all()
を実行したときの並び順 - 管理画面の初期表示順:リスト表示の初期状態
つまり、Metaクラスの設定はアプリケーション全体で一貫性のある表示を実現するために重要です。
4. __str__メソッド
def __str__(self):
return self.title
- オブジェクトを文字列として表現するときの形式
- 管理画面やシェルで見やすくなる
よく使うフィールドタイプ一覧
Djangoには様々なフィールドタイプが用意されています:
▶文字列系フィールド
フィールドタイプ | 用途 | 例 |
---|---|---|
CharField | 短い文字列(最大長指定必須) | タイトル、名前 |
TextField | 長い文字列 | 本文、説明文 |
EmailField | メールアドレス | user@example.com |
URLField | URL | https://example.com |
SlugField | URLに使える文字列 | my-first-post |
▶数値系フィールド
フィールドタイプ | 用途 | 例 |
---|---|---|
IntegerField | 整数 | 年齢、個数 |
FloatField | 小数 | 評価点(4.5) |
DecimalField | 正確な小数 | 価格(1,234.56円) |
BooleanField | 真偽値 | 公開/非公開 |
▶日時系フィールド
フィールドタイプ | 用途 | 例 |
---|---|---|
DateField | 日付 | 2025-07-12 |
TimeField | 時刻 | 15:30:00 |
DateTimeField | 日時 | 2025-07-12 15:30:00 |
▶その他
フィールドタイプ | 用途 | 例 |
---|---|---|
FileField | ファイルアップロード | PDFファイル |
ImageField | 画像アップロード | プロフィール画像 |
ForeignKey | 他のモデルとの関連(多対一) | 記事→著者 |
ManyToManyField | 他のモデルとの関連(多対多) | 記事→タグ |
ForeignKeyとManyToManyFieldについて
「ForeignKey」や「ManyToManyField」は、他のモデルとの関係を表現する特殊なフィールドです。
今は「そういうものがあるんだ」という理解で大丈夫です。
この記事の後半で:
- ForeignKey:「カテゴリー機能を追加」のセクションで詳しく説明
- ManyToManyField:「タグ機能を追加してみよう」のセクションで詳しく説明
実際にコードを書きながら学んだ方が理解しやすいので、今は気にせず先に進んでください!
マイグレーション:モデルをデータベースに反映する
▶マイグレーションとは?
マイグレーションは、モデルの変更をデータベースに反映させる仕組みです。
例えるなら:
- モデル:設計図
- マイグレーション:工事計画書
- migrate:実際の工事
▶マイグレーションファイルの作成
VSCodeのターミナルで以下のコマンドを実行:
$ python manage.py makemigrations
実行結果:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
これでblog/migrations/0001_initial.py
というファイルが作成されました。中身を見てみましょう:
# Generated by Django 5.2.4 on 2025-07-12 12:21
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200, verbose_name='タイトル')),
('content', models.TextField(verbose_name='本文')),
('is_featured', models.BooleanField(default=False, verbose_name='注目記事')),
('is_published', models.BooleanField(default=False, verbose_name='公開状態')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新日時')),
],
options={
'verbose_name': '記事',
'verbose_name_plural': '記事',
'ordering': ['-created_at'],
},
),
]
idフィールドについて
id
フィールドを定義していないのに、マイグレーションファイルには含まれていますね。
Djangoは自動的にid
という主キー(Primary Key)フィールドを追加します:
- 型:BigAutoField(自動連番の整数)
- 1から順番に番号が振られる
- 各レコードを一意に識別する
明示的に主キーを定義することもできますが、通常は自動生成されるid
で十分です。
▶マイグレーションの実行
作成したマイグレーションをデータベースに適用します:
$ python manage.py migrate
実行結果:
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
...
Applying blog.0001_initial... OK
Applying sessions.0001_initial... OK
これでdb.sqlite3
というデータベースファイルが作成(または更新)されました!
SQLiteについて
DjangoのデフォルトデータベースはSQLiteです:
- ファイルベースのデータベース(
db.sqlite3
という1つのファイル) - 設定不要ですぐ使える
- 開発環境に最適
- 本番環境ではPostgreSQLやMySQLを使うことが多い
Django管理画面を使ってみよう
▶管理画面を日本語化する
管理画面にアクセスする前に、まず表示言語を日本語に設定してみましょう。
mysite/settings.py
を開いて、以下の設定を変更します:
# 言語とタイムゾーンの設定 (日本語と日本時間に変更)
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
言語とタイムゾーン設定の効果
LANGUAGE_CODE = 'ja' の効果:
- Django管理画面が日本語表示になる
- エラーメッセージが日本語で表示される
- フォームの検証メッセージも日本語化
- 日付フォーマットが日本式(年/月/日)になる
TIME_ZONE = 'Asia/Tokyo' の効果:
- 日時データの保存・表示が日本時間基準になる
created_at
やupdated_at
などの時刻が正しく表示される- サーバーログのタイムスタンプも日本時間に
▶管理者ユーザーの作成
まず、管理画面にログインするための管理者(スーパーユーザー)を作成します:
$ python manage.py createsuperuser
以下の情報を入力します:
ユーザー名 (leave blank to use 'your_username'): admin
メールアドレス: admin@example.com
Password: ********
Password (again): ********
Superuser created successfully.
パスワードについて
- パスワードは8文字以上
- 入力時は画面に表示されません(セキュリティのため)
- 簡単すぎるパスワードは警告が出ますが、開発環境なら無視してOK
このパスワードは一般的すぎます。 Bypass password validation and create user anyway? [y/N]: y
▶モデルを管理画面に登録
blog/admin.py
を編集して、Postモデルを管理画面に登録します:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
"""記事の管理画面設定"""
list_display = ["title", "is_featured", "is_published"]
list_filter = ["is_published", "is_featured", "created_at"]
search_fields = ["title", "content"]
date_hierarchy = "created_at"
ordering = ["-created_at"]
管理画面のカスタマイズオプション
list_display
:一覧表示する項目list_filter
:サイドバーに表示するフィルターsearch_fields
:検索対象のフィールドdate_hierarchy
:日付による階層表示ordering
:デフォルトの並び順
日付フォーマットの日本語化
LANGUAGE_CODE = 'ja'
の設定により、管理画面での日付表示が日本式になります:
- 英語:
July 12, 2025, 3:30 p.m.
- 日本語:
2025年7月12日 15:30
テンプレートでも{{ post.created_at }}
のように表示すると、自動的に日本式フォーマットになります。
▶管理画面にアクセス
開発サーバーを起動して、管理画面にアクセスしてみましょう:
$ python manage.py runserver
ブラウザで http://localhost:8000/admin/ にアクセスし、作成したユーザーでログインします。
管理画面が日本語表示にならない場合
- サーバーを再起動したか確認(
Ctrl+C
で停止後、python manage.py runserver
) settings.py
のLANGUAGE_CODE = 'ja'
が正しく設定されているか確認- ブラウザのキャッシュをクリア(Ctrl+F5 または Cmd+Shift+R)
ログイン後の画面:
日本語化の効果で、「サイト管理」「認証と認可」など、メニューが日本語で表示されていますね!
▶記事を作成してみよう
- 「記事」の横の「追加」をクリック
- タイトルと本文を入力
- 「保存」をクリック
管理画面の素晴らしい点:
- コードを書かずにデータの作成・編集・削除ができる
- 入力値の検証が自動で行われる
- 日本語化により、ボタンやメッセージが分かりやすい
- カスタマイズも簡単
QuerySet:データベースからデータを取得する
▶Django Shellを使ってみよう
Django Shellは、Djangoの環境でPythonを対話的に実行できるツールです。モデルの操作を練習するのに最適です!
$ python manage.py shell
Django 5の新機能:モデルの自動インポート
Django 5から、python manage.py shell
を実行するとすべてのモデルが自動的にインポートされるようになりました!
Django 4以前の方法:
>>> from blog.models import Post # 手動でインポートが必要だった
>>> Post.objects.all()
Django 5以降の方法:
>>> Post.objects.all() # インポート不要でいきなり使える!
<QuerySet [<Post: Djangoを始めました>]>
これにより、開発がさらにスムーズになりました。ただし、他のモジュール(datetime
など)は従来通りインポートが必要です。
もし古いバージョンのDjangoや他の記事を参考にしている場合、from ... import ...
が書かれているかもしれませんが、Django 5では不要です!
(InteractiveConsole)
>>> # すべての記事を取得
>>> Post.objects.all()
<QuerySet [<Post: Djangoでブログを作ろう!>]>
>>>
>>> # 記事の数を確認
>>> Post.objects.count()
1
▶基本的なCRUD操作
Create(作成)
>>> # 新しい記事を作成
>>> post = Post(
... title="Pythonの基礎",
... content="Pythonの基本的な文法について学びました。"
... )
>>> post.save()
>>>
>>> # または、createメソッドを使って一度に作成
>>> post2 = Post.objects.create(
... title="Djangoのモデル",
... content="モデルの使い方を学習中です。",
... is_published=True
... )
>>>
Read(読み取り)
>>> # すべての記事を取得
>>> all_posts = Post.objects.all()
>>> for post in all_posts:
... print(f"{post.title} - {post.is_published}")
...
Djangoのモデル - True
Pythonの基礎 - False
Djangoでブログを作ろう! - True
>>>
>>> # 特定の条件で絞り込み
>>> published_posts = Post.objects.filter(is_published=True)
>>> published_posts
<QuerySet [<Post: Djangoのモデル>, <Post: Djangoでブログを作ろう!>]>
>>>
>>> # 1件だけ取得
>>> first_post = Post.objects.first()
>>> first_post
<Post: Djangoのモデル>
>>>
>>> latest_post = Post.objects.latest('created_at')
>>> latest_post
<Post: Djangoのモデル>
>>>
>>> # IDで取得
>>> post = Post.objects.get(id=1)
>>> post
<Post: Djangoでブログを作ろう!>
Update(更新)
>>> # 記事を取得して更新
>>> post = Post.objects.get(id=1)
>>> post.title = "Djangoを始めました(更新)"
>>> post.is_published = True
>>> post.save()
>>> print(f"{post.title}, {post.is_published}, {post.updated_at}")
Djangoでブログを作ろう(更新)!, False, 2025-07-12 06:37:13.693273+00:00
>>>
>>> # または、updateメソッドで一括更新
>>> Post.objects.filter(is_published=False).update(is_published=True)
2
Delete(削除)
>>> # 記事を削除
>>> post = Post.objects.get(id=1)
>>> post.delete()
(1, {'blog.Post': 1})
>>>
>>> # 条件に合う記事をすべて削除
>>> Post.objects.filter(is_published=True).delete()
(2, {'blog.Post': 2})
>>>
>>> # データが全部削除されたことを確認
>>> Post.objects.all()
<QuerySet []>
exitでシェルを終了
Django Shellを終了するには:
>>> exit()
now exiting InteractiveConsole...
または Ctrl+D
(Mac)/ Ctrl+Z
→ Enter
(Windows)
▶よく使うQuerySetメソッド
メソッド | 説明 | 例 |
---|---|---|
all() | すべてのオブジェクトを取得 | Post.objects.all() |
filter() | 条件に合うものを取得 | Post.objects.filter(is_published=True) |
exclude() | 条件に合わないものを取得 | Post.objects.exclude(is_published=False) |
get() | 1件だけ取得(なければエラー) | Post.objects.get(id=1) |
first() | 最初の1件 | Post.objects.first() |
last() | 最後の1件 | Post.objects.last() |
count() | 件数を取得 | Post.objects.count() |
exists() | 存在するかチェック | Post.objects.filter(title="test").exists() |
order_by() | 並び替え | Post.objects.order_by('-created_at') |
▶フィルタの条件指定
# 完全一致
Post.objects.filter(title="Djangoの基礎")
# 部分一致(大文字小文字区別しない)
Post.objects.filter(title__icontains="django")
# 前方一致
Post.objects.filter(title__startswith="Django")
# より大きい/小さい
Post.objects.filter(id__gt=5) # greater than
Post.objects.filter(id__lt=10) # less than
# 範囲指定
Post.objects.filter(id__range=[1, 10])
# リストに含まれる
Post.objects.filter(id__in=[1, 3, 5])
# NULL/NOT NULL
Post.objects.filter(content__isnull=False)
ビューとモデルを連携させる
▶実際のデータを表示する
今まで使っていた仮のデータを、実際のデータベースのデータに置き換えましょう!
blog/views.py
を修正:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
"""記事一覧を表示"""
# 公開されている記事のみ取得
posts = Post.objects.filter(is_published=True).order_by("-created_at")
# 注目記事を取得(最新2件取得)
featured_posts = Post.objects.filter(is_published=True, is_featured=True).order_by(
"-created_at"
)[:2]
context = {
"posts": posts,
"featured_posts": featured_posts,
"total_posts": posts.count(),
}
return render(request, "blog/post_list.html", context)
def post_detail(request, post_id):
"""記事詳細を表示"""
# 指定されたIDの記事を取得(なければ404エラー)
post = get_object_or_404(Post, id=post_id, is_published=True)
# サイドバー用に最新記事を取得
recent_posts = (
Post.objects.filter(is_published=True)
.exclude(id=post_id)
.order_by("-created_at")[:5]
)
context = {
"post": post,
"posts": recent_posts, # サイドバー用
}
return render(request, "blog/post_detail.html", context)
▶重要な変更点
-
仮のデータ(POSTS)を削除
- もうハードコードされたデータは不要!
-
モデルからデータを取得
posts = Post.objects.filter(is_published=True)
-
get_object_or_404の使用
post = get_object_or_404(Post, id=post_id)
- 記事が見つからない場合、自動的に404エラーページを表示
try-except
を書かなくて済む!# try-exceptを使った面倒な書き方 try: post = Post.objects.get(id=post_id) except Post.DoesNotExist: from django.http import Http404 raise Http404("記事が見つかりません")
django.shortcutsの便利な関数たち
get_object_or_404
以外にも、django.shortcuts
には便利な関数がたくさんあります:
- render:テンプレートをレンダリング(すでに使っていますね!)
- redirect:別のページにリダイレクト
- get_list_or_404:リストを取得、空なら404エラー
例えばリダイレクトはこんな感じで使います:
from django.shortcuts import redirect
def post_create(request):
# 記事を作成した後...
return redirect('post_detail', post_id=post.id)
# または
# return redirect('/blog/') # URLを直接指定
これらの関数は「ショートカット」という名前の通り、よく使う処理を簡単に書けるようにしてくれます!
▶テンプレートの調整
モデルのフィールド名に合わせて、テンプレートも少し修正が必要です。
blog/templates/blog/post_detail.html
の一部を修正:
<article class="post-detail">
<h2>{{ post.title }}</h2>
<p class="post-meta">投稿日: {{ post.created_at }}</p>
<div class="post-body">
{{ post.content|linebreaks }}
</div>
<p><a href="{% url 'post_list' %}">← 記事一覧に戻る</a></p>
</article>
主な変更点:
post.body
→post.content
(モデルのフィールド名に合わせる)
データを追加して動作確認
▶管理画面から記事を追加
- http://localhost:8000/admin/ にアクセス
- 「記事」→「記事を追加」
- 以下のような記事を3〜5個作成:
記事1
- タイトル:Djangoでブログを作ろう!
- 本文:
DjangoのMVTパターンを使って、本格的なブログアプリケーションを作成していきます。 Pythonの基本的な知識があれば、誰でも素敵なWebサイトが作れるようになります。 このチュートリアルでは、以下のことを学びます: ・モデルを使ったデータベース操作 ・ビューでのデータ処理 ・テンプレートでの表示 一緒に楽しくDjangoを学んでいきましょう!
- 注目記事:✓(チェックを入れる)
- 公開状態:✓(チェックを入れる)
記事2
- タイトル:モデルとデータベースの基礎
- 本文:
DjangoのORMを使えば、SQLを書かなくてもデータベースを操作できます。 これは初心者にとって大きなメリットです。 今日学んだこと: 1. モデルクラスの定義方法 2. マイグレーションの実行 3. 管理画面でのデータ操作 特にマイグレーションの仕組みが面白いです。 Pythonのクラスを書くだけで、自動的にデータベースのテーブルが作成されるなんて魔法みたいですね!
- 注目記事: (チェックなし)
- 公開状態:✓(チェックを入れる)
記事3
- タイトル:QuerySetの使い方をマスターしよう
- 本文:
今日はDjango Shellを使って、QuerySetの練習をしました。 よく使うメソッド: - all():すべてのデータを取得 - filter():条件に合うデータを絞り込み - get():1件だけ取得 - order_by():並び順を指定 最初は難しく感じましたが、SQLよりも直感的で分かりやすいです。 Pythonらしい書き方ができるのが嬉しいですね。
- 注目記事:✓(チェックを入れる)
- 公開状態:✓(チェックを入れる)
記事4(非公開テスト用)
- タイトル:下書き:次回の内容について
- 本文:
次回はクラスベースビューについて学ぶ予定です。 準備することリスト: - ListViewの基本 - DetailViewの使い方 - ページネーションの実装 この記事はまだ下書きなので公開しません。
- 注目記事:(チェックなし)
- 公開状態:(チェックなし)
記事5(オプション)
- タイトル:Djangoテンプレートの継承が便利すぎる
- 本文:
テンプレートの継承を使い始めてから、コードの重複がなくなりました! base.htmlに共通部分を書いて、各ページでは必要な部分だけを書けばいい。 これはDRY原則(Don't Repeat Yourself)の良い例ですね。 特に便利なのは: - ヘッダーやフッターの一元管理 - ページごとのタイトル設定 - CSSやJavaScriptの追加も簡単 もうテンプレート継承なしでは開発できません!
- 注目記事: (チェックなし)
- 公開状態:✓(チェックを入れる)
記事作成のポイント
- 改行を活用:本文で改行を入れると、
linebreaks
フィルタによって<br>
タグに変換されます - 空行で段落分け:空行を入れると
<p>
タグで段落が分かれます - リスト表示:「・」や「1.」などを使ってリスト風に書くと読みやすくなります
- 実際のブログっぽく:学習した内容や感想を書くと、よりリアルなブログになります
▶ブログサイトで確認
http://localhost:8000/ にアクセスして、作成した記事が表示されることを確認してみましょう!
素晴らしい!データベースから取得したデータが表示されています。そして、非公開の記事は表示されていないことも確認できますね。
モデルの拡張:より実践的なブログへ
▶カテゴリー機能を追加
実際のブログでは、記事をカテゴリーで分類したいですよね。新しいモデルを追加してみましょう。
blog/models.py
に追加:
class Category(models.Model):
"""カテゴリーモデル"""
name = models.CharField(max_length=50, unique=True, verbose_name="カテゴリー名")
slug = models.SlugField(unique=True, verbose_name="スラッグ")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="作成日時")
class Meta:
verbose_name = "カテゴリー"
verbose_name_plural = "カテゴリー"
ordering = ["name"]
def __str__(self):
return self.name
class Post(models.Model):
"""ブログ記事のモデル"""
title = models.CharField(max_length=200, verbose_name="タイトル")
content = models.TextField(verbose_name="本文")
is_featured = models.BooleanField(default=False, verbose_name="注目記事")
is_published = models.BooleanField(default=False, verbose_name="公開状態")
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
related_name="posts",
verbose_name="カテゴリー",
null=True,
blank=True,
)
created_at = models.DateTimeField(default=timezone.now, verbose_name="作成日時")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")
class Meta:
verbose_name = "記事"
verbose_name_plural = "記事"
ordering = ["-created_at"] # 新しい記事から順に並べる
def __str__(self):
return self.title
▶新しい概念:ForeignKey(外部キー)
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
related_name='posts'
)
ForeignKeyは、他のモデルとの「多対一」の関係を表します:
- 1つの記事は1つのカテゴリーに属する
- 1つのカテゴリーには複数の記事が属する
on_deleteオプション:参照先が削除されたときの動作
CASCADE
:一緒に削除(例:カテゴリーを削除したら、そのカテゴリーの記事も全て削除される)PROTECT
:削除を防ぐ(例:記事が1つでも存在するカテゴリーは削除できなくなる)SET_NULL
:NULLに設定(例:カテゴリーを削除しても記事は残り、カテゴリーがNULLになる)
▶マイグレーションの実行
モデルを変更したら、必ずマイグレーションを実行します:
$ python manage.py makemigrations
既存のPostにcategoryフィールドを追加する場合、デフォルト値を聞かれます:
You are trying to add a non-nullable field 'category' to post without a default...
Select an option:
1) Provide a one-off default now
2) Quit, and let me add a default in models.py
今回はnull=True, blank=True
を追加したので、この質問は出ません。
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0002_category_post_category... OK
▶管理画面にカテゴリーを追加
blog/admin.py
を更新:
from django.contrib import admin
from .models import Post, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ["name", "slug", "created_at"]
prepopulated_fields = {"slug": ("name",)}
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "is_featured", "is_published", "category", "updated_at"]
list_filter = ["is_published", "is_featured", "created_at"]
search_fields = ["title", "content"]
date_hierarchy = "created_at"
ordering = ["-created_at"]
prepopulated_fieldsの便利機能
prepopulated_fields = {'slug': ('name',)}
これを設定すると、管理画面で「カテゴリー名」を入力すると、自動的に「スラッグ」が生成されます!
例:
- カテゴリー名:「Django Model」
- スラッグ:「django-model」(自動生成)
ただし、日本語の場合は正しく変換されないので注意が必要です:
- カテゴリー名:「Django入門」
- スラッグ:「django」(「入門」が消えてしまう)
日本語を含む場合は、手動で適切な英語のスラッグに修正してみましょう:
- 「Django入門」→
django-basics
- 「Python基礎」→
python-fundamentals
- 「Web開発」→
web-development
▶カテゴリーを追加して記事に設定
マイグレーション実行後、管理画面でカテゴリーを追加してみましょう。
- http://localhost:8000/admin/ にアクセス
- 「カテゴリー」→「カテゴリーを追加」
- 以下のカテゴリーを作成:
カテゴリー1
- カテゴリー名:Django入門
- スラッグ:django-basics
カテゴリー2
- カテゴリー名:Python基礎
- スラッグ:python-fundamentals
カテゴリー3
- カテゴリー名:Web開発
- スラッグ:web-development
- 次に、各記事にカテゴリーを設定:
- 「Djangoでブログを作ろう!」→「Django入門」
- 「モデルとデータベースの基礎」→「Django入門」
- 「QuerySetの使い方をマスターしよう」→「Django入門」
- 「Djangoテンプレートの継承が便利すぎる」→「Web開発」
▶カテゴリー別記事一覧ページの実装
カテゴリーをクリックしたときに、そのカテゴリーの記事だけを表示するページを作りましょう。
1. ビュー関数の追加
blog/views.py
に新しいビュー関数を追加:
from django.shortcuts import render, get_object_or_404
from .models import Post, Category
def category_posts(request, slug):
"""カテゴリー別の記事一覧を表示"""
# スラッグからカテゴリーを取得
category = get_object_or_404(Category, slug=slug)
# そのカテゴリーの公開記事を取得
posts = Post.objects.filter(category=category, is_published=True).order_by(
"-created_at"
)
# すべてのカテゴリー(サイドバー用)
categories = Category.objects.all().order_by("name")
context = {
"category": category,
"posts": posts,
"categories": categories,
"total_posts": posts.count(),
}
return render(request, "blog/category_posts.html", context)
2. URLパターンの追加
blog/urls.py
にURLパターンを追加:
from django.urls import path
from . import views
urlpatterns = [
path("", views.post_list, name="post_list"),
path("post/<int:post_id>/", views.post_detail, name="post_detail"),
path("category/<slug:slug>/", views.category_posts, name="category_posts"), # 追加
]
3. カテゴリー別記事一覧テンプレートの作成
blog/templates/blog/category_posts.html
を作成:
{% extends 'blog/base.html' %}
{% block title %}{{ category.name }}の記事一覧 - My Blog{% endblock %}
{% block content %}
<div class="page-header">
<h2>{{ category.name }}の記事</h2>
<p class="post-count">
{% if posts %}
全{{ posts|length }}件の記事
{% else %}
このカテゴリーにはまだ記事がありません
{% endif %}
</p>
</div>
<div class="posts-grid">
{% for post in posts %}
{% include 'blog/includes/post_card.html' %}
{% empty %}
<div class="no-posts">
<p>{{ category.name }}カテゴリーの記事はまだありません。</p>
<p><a href="{% url 'post_list' %}">すべての記事を見る →</a></p>
</div>
{% endfor %}
</div>
{% endblock %}
4. サイドバーのカテゴリーリンクを更新
blog/templates/blog/includes/sidebar.html
を修正:
<div class="sidebar-section">
<h3>カテゴリー</h3>
<ul class="category-list">
{% for category in categories %}
<li>
<a href="#">{{ category.name }}</a>
<span class="post-count">({{ category.posts.count }})</span>
</li>
{% empty %}
<li>カテゴリーがありません</li>
{% endfor %}
</ul>
</div>
<!-- 以降の内容は変更なし -->
▶既存のビューとテンプレートを更新
5. 記事一覧と詳細ビューの更新
サイドバーでカテゴリーを表示するため、blog/views.py
の既存のビュー関数を更新:
def post_list(request):
"""記事一覧を表示"""
# 公開されている記事のみ取得
posts = Post.objects.filter(is_published=True).order_by("-created_at")
# 注目記事を取得
featured_posts = Post.objects.filter(is_published=True, is_featured=True).order_by(
"-created_at"
)[:2]
# カテゴリー一覧を取得
categories = Category.objects.all().order_by("name")
context = {
"posts": posts,
"featured_posts": featured_posts,
"total_posts": posts.count(),
"categories": categories, # カテゴリーを追加
}
return render(request, "blog/post_list.html", context)
def post_detail(request, post_id):
"""記事詳細を表示"""
# 指定されたIDの記事を取得(なければ404エラー)
post = get_object_or_404(Post, id=post_id, is_published=True)
# サイドバー用に最新記事を取得
recent_posts = (
Post.objects.filter(is_published=True)
.exclude(id=post_id)
.order_by("-created_at")[:5]
)
# カテゴリー一覧を取得
categories = Category.objects.all().order_by("name")
context = {
"post": post,
"posts": recent_posts, # サイドバー用
"categories": categories, # カテゴリーを追加
}
return render(request, "blog/post_detail.html", context)
6. 記事カードテンプレートの更新
第5回で作成したpost_card.html
を修正して、カテゴリーを表示できるようにします。
blog/templates/blog/includes/post_card.html
を修正:
<article class="post-card">
<div class="post-card-header">
{% if post.category %}
<span class="category">{{ post.category.name }}</span>
{% else %}
<span class="category">未分類</span>
{% endif %}
<span class="date">{{ post.created_at }}</span>
</div>
<!-- 以降の内容は変更なし -->
</article>
7. 記事詳細テンプレートの更新
blog/templates/blog/post_detail.html
にもカテゴリーを追加:
<article class="post-detail">
<h2>{{ post.title }}</h2>
{% if post.category %}
<p class="post-category">カテゴリー: {{ post.category.name }}</p>
{% endif %}
<p class="post-meta">投稿日: {{ post.created_at }}</p>
<div class="post-body">
{{ post.content|linebreaks }}
</div>
<p><a href="{% url 'post_list' %}" class="back-link">← 記事一覧に戻る</a></p>
</article>
▶CSSでカテゴリーの見た目を整える
最後に、static/blog/css/style.css
に追加:
/* カテゴリー表示のスタイル */
.post-category {
color: #007bff;
font-size: 0.9em;
margin: 5px 0;
}
.post-detail .post-category {
font-size: 1em;
margin-bottom: 10px;
}
/* サイドバーのカテゴリー数表示 */
.category-list .post-count {
color: #7f8c8d;
font-size: 0.9em;
margin-left: 5px;
}
/* ページヘッダー */
.page-header {
margin-bottom: 2rem;
}
.page-header h2 {
color: #2c3e50;
margin-bottom: 0.5rem;
}
.page-header .post-count {
color: #7f8c8d;
font-size: 0.9rem;
}
▶結果を確認
記事一覧にカテゴリーが表示され、サイドバーのカテゴリーをクリックすると、そのカテゴリーの記事だけが表示されるようになりました!各記事がどのカテゴリーに属しているか一目で分かり、カテゴリー別に記事を絞り込むこともできます。
実践:タグ機能を追加してみよう
▶ManyToManyFieldの使用
記事に複数のタグを付けられるようにしてみましょう。
blog/models.py
に追加:
class Tag(models.Model):
"""タグモデル"""
name = models.CharField(max_length=30, unique=True, verbose_name='タグ名')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='作成日時')
class Meta:
verbose_name = 'タグ'
verbose_name_plural = 'タグ'
ordering = ['name']
def __str__(self):
return self.name
Postモデルにtagsフィールドを追加:
class Post(models.Model):
# 既存のフィールド...
tags = models.ManyToManyField(
Tag, related_name="posts", verbose_name="タグ", blank=True
)
created_at = models.DateTimeField(default=timezone.now, verbose_name="作成日時")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")
# 以下既存のコード...
▶ManyToManyFieldの特徴
- 多対多の関係を表現
- 中間テーブルが自動的に作成される
- 双方向からアクセス可能
# 記事からタグを取得
post.tags.all()
# タグから記事を取得
tag.posts.all()
▶管理画面でタグを使いやすくする
blog/admin.py
を更新:
from django.contrib import admin
from .models import Post, Category, Tag
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ["name", "created_at"]
search_fields = ["name"]
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "is_featured", "is_published", "category", "updated_at"]
list_filter = ["is_published", "is_featured", "created_at"]
search_fields = ["title", "content"]
date_hierarchy = "created_at"
ordering = ["-created_at"]
filter_horizontal = ["tags"] # タグを選びやすくする
filter_horizontal
を使うと、タグの選択が格段に使いやすくなります!
▶タグを追加して記事に設定
マイグレーション実行後、管理画面でタグを追加してみましょう。
-
管理画面で「タグ」→「タグを追加」
-
以下のタグを作成:
- Django
- Python
- ORM
- データベース
- モデル
- テンプレート
- 初心者向け
- チュートリアル
-
各記事に適切なタグを設定:
- 「Djangoでブログを作ろう!」→「Django」「初心者向け」「チュートリアル」
- 「モデルとデータベースの基礎」→「Django」「モデル」「データベース」「ORM」
- 「QuerySetの使い方をマスターしよう」→「Django」「ORM」「データベース」
- 「Djangoテンプレートの継承が便利すぎる」→「Django」「テンプレート」
▶タグを表示するためのテンプレート更新
blog/templates/blog/includes/post_card.html
を修正して、タグも表示できるようにします:
<article class="post-card">
<!-- 前の既存の内容は変更なし -->
<div class="post-card-footer">
{% if post.tags.exists %}
<div class="tags">
{% for tag in post.tags.all %}
<span class="tag">#{{ tag.name }}</span>
{% endfor %}
</div>
{% endif %}
<a href="{% url 'post_detail' post_id=post.id %}" class="read-more">
続きを読む →
</a>
</div>
</article>
blog/templates/blog/post_detail.html
にもタグを追加:
<article class="post-detail">
<h2>{{ post.title }}</h2>
{% if post.category %}
<p class="post-category">カテゴリー: {{ post.category.name }}</p>
{% endif %}
<p class="post-meta">投稿日: {{ post.created_at }}</p>
<div class="post-body">
{{ post.content|linebreaks }}
</div>
{% if post.tags.exists %}
<div class="post-tags">
タグ:
{% for tag in post.tags.all %}
<span class="tag">{{ tag.name }}</span>
{% endfor %}
</div>
{% endif %}
<p><a href="{% url 'post_list' %}" class="back-link">← 記事一覧に戻る</a></p>
</article>
▶タグのスタイルを追加
static/blog/style.css
に追加:
/* タグのスタイル */
.post-detail .post-tags {
margin: 20px 0;
}
.post-detail .tag {
font-size: 0.9em;
}
▶最終的な表示結果
記事一覧にカテゴリーとタグが表示され、より実践的なブログらしくなりました!
記事詳細ページでも、カテゴリーとタグが適切に表示されています。これで、読者は記事の分類や関連トピックを簡単に把握できるようになりました。
データベースの中身を確認する
▶VSCodeのSQLite Viewer
第2回で紹介したSQLite Viewer拡張機能を使って、データベースの中身を見てみましょう。
- VSCodeで
db.sqlite3
ファイルをクリック - テーブル一覧が表示される
blog_post
テーブルをクリック
実際のデータがテーブル形式で保存されているのが分かりますね!
まとめ
今回は、DjangoのModel(モデル)について学びました:
- モデルはデータベースとPythonの橋渡し役
- フィールドでデータの型を定義
- マイグレーションでデータベースに反映
- 管理画面でデータを簡単に操作
- QuerySetでデータの取得・作成・更新・削除
- ForeignKeyとManyToManyFieldで関連を表現
▶できるようになったこと
最初は:
# ハードコードされたデータ
POSTS = [{"id": 1, "title": "Hello", ...}]
今は:
# データベースから動的に取得
posts = Post.objects.filter(is_published=True)
これは大きな進歩です!本物のWebアプリケーションに一歩近づきました。
▶モデル設計のポイント
-
シンプルに始める
- 最初から完璧を目指さない
- 必要最小限のフィールドから始める
-
命名規則を守る
- モデル名:単数形、キャメルケース(Post、Category)
- フィールド名:スネークケース(created_at、is_published)
-
適切なフィールドタイプを選ぶ
- 文字数が決まっている → CharField
- 長文 → TextField
- 真偽値 → BooleanField
-
関連を理解する
- 一対多:ForeignKey
- 多対多:ManyToManyField
▶次のステップへ
データベースを使えるようになったことで、可能性が大きく広がりました:
- ユーザーが投稿できるブログ
- コメント機能
- いいね機能
- 検索機能
これらすべてが、今回学んだモデルの知識で実現できます!
プログラミングの楽しさは、「自分で作ったものが動く」ことです。今回、あなたは実際にデータベースを操作して、動的なWebサイトを作ることができました。この達成感を忘れずに、次のステップへ進みましょう!
完成したコード
もし実行でエラーが出たり、不明な点がある場合は、以下の完成後のコードと比較してみてください:
https://github.com/techarm/django-blog-tutorial/tree/06-models-database
特にmodels.py
のフィールド定義、views.py
のQuerySetの使い方、マイグレーションファイルの内容を確認することで、問題を解決できるでしょう。
次回予告
次回は、Djangoフォーム(Forms) について学びます!
Webアプリケーションの醍醐味は、ユーザーとの「対話」です。次回学ぶフォームを使えば:
- 記事の検索機能
- コメント投稿機能
- お問い合わせフォーム
- ユーザー登録・ログイン
など、様々な機能が実現できるようになります。
具体的には:
- フォームクラスの基本
- バリデーション(入力チェック)
- セキュリティ対策(CSRF、XSS)
- 検索フォームの実装
- コメント投稿機能の実装
ユーザーからの入力を安全に処理する方法を学んで、インタラクティブなWebアプリケーションを作りましょう!🚀
この記事はいかがでしたか?
もしこの記事が参考になりましたら、
高評価をいただけると大変嬉しいです!
皆様からの応援が励みになります。ありがとうございます! ✨