Python Djangoチュートリアル:簡単な投票アプリを作りながら基本を学ぼう
はじめに
Webアプリケーション開発の世界へようこそ!このチュートリアルでは、Pythonで最も人気のあるWebフレームワークの一つである「Django(ジャンゴ)」を使い、簡単な投票アプリケーションをゼロから構築していきます。プログラミングの経験、特にPythonの基本的な文法を理解している方を対象に、Djangoの核心的な概念と開発フローを実践的に学べるように構成しました。
Djangoとは?
Djangoは、「完璧主義者のための締め切り厳守のWebフレームワーク」というキャッチフレーズで知られる、高レベルなPython Webフレームワークです。その最大の特徴は「Batteries Included(バッテリー同梱)」という哲学にあります。これは、Web開発で必要となるであろう多くの機能(ユーザー認証、管理画面、サイトマップ、RSSフィードなど)が、フレームワーク本体に標準で組み込まれていることを意味します。これにより、開発者は車輪の再発明をすることなく、アプリケーションの本質的なロジック開発に集中できます。
また、DjangoはMVT (Model-View-Template) と呼ばれるアーキテクチャを採用しています。
- Model(モデル): アプリケーションのデータ構造を定義します。データベースのテーブル設計に相当し、Pythonのクラスとして記述します。
- View(ビュー): ビジネスロジックを担当します。ユーザーからのリクエストを受け取り、必要なデータをモデルから取得し、どのテンプレートを使ってレスポンスを返すかを決定します。
- Template(テンプレート): ユーザーに見せる最終的な出力を生成する部分です。HTMLファイルに、Django独自のテンプレート言語を埋め込む形で記述します。
このMVTアーキテクチャにより、データの扱い、ビジネスロジック、見た目の部分が明確に分離され、メンテナンス性が高く、スケーラブルなアプリケーション開発が可能になります。
これから作るもの:投票アプリケーション
このチュートリアルでは、Django公式チュートリアルでもおなじみの「投票アプリケーション」を作成します。このアプリは、以下のようなシンプルな機能を持っています。
- 質問の一覧を表示するページ
- 各質問の詳細と、投票するための選択肢(フォーム)を表示するページ
- 投票結果を表示するページ
- データを簡単に追加・編集できる管理サイト
この小さなアプリケーションを通じて、あなたはDjangoプロジェクトの立ち上げから、データベースの設計、URLのルーティング、画面表示、フォームの処理、そしてDjangoの強力な管理機能まで、一連の開発サイクルを体験することになります。
準備するもの
- Python: コンピュータにPython 3.6以上がインストールされている必要があります。
- ターミナル(コマンドプロンプト): コマンドを実行するためのツールです。
さあ、準備はいいですか?それでは、Djangoの世界へ一歩踏み出しましょう!
第1章:プロジェクトの準備
まずはじめに、開発環境を整え、Djangoプロジェクトの骨格を作成します。
1. 仮想環境の構築
本格的な開発では、プロジェクトごとにライブラリのバージョンを管理するために「仮想環境」を作成するのが一般的です。これにより、他のプロジェクトやシステム全体のPython環境に影響を与えることなく、クリーンな状態で開発を始められます。
ターミナルを開き、プロジェクトを置きたいディレクトリに移動してください。そして、以下のコマンドを実行します。
“`bash
“venv” という名前の仮想環境を作成
python -m venv venv
仮想環境を有効化 (Windowsの場合)
.\venv\Scripts\activate
仮想環境を有効化 (Mac/Linuxの場合)
source venv/bin/activate
“`
成功すると、コマンドプロンプトの先頭に (venv)
と表示され、仮想環境に入ったことがわかります。これ以降のコマンドは、この仮想環境内で実行されます。
2. Djangoのインストール
仮想環境が有効な状態で、Pythonのパッケージ管理ツール pip
を使ってDjangoをインストールします。
bash
pip install django
インストールが完了したら、バージョンを確認してみましょう。
bash
django-admin --version
4.2.7
のようなバージョン番号が表示されれば、インストールは成功です。
3. Djangoプロジェクトの作成
いよいよDjangoプロジェクトを作成します。django-admin
コマンドを使い、プロジェクトの骨組みを自動生成します。ここでは mysite
という名前のプロジェクトを作成します。
bash
django-admin startproject mysite .
startproject mysite
の後にスペースとドット (.
) を付けていることに注意してください。これにより、現在のディレクトリ直下にプロジェクトファイルが作成されます。もしドットを付けないと、mysite
というディレクトリがもう一つ作られて階層が深くなってしまいます。
コマンドを実行すると、以下のようなファイルとディレクトリが生成されます。
.
├── venv/
├── mysite/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
それぞれのファイル・ディレクトリの役割を簡単に見ておきましょう。
manage.py
: Djangoプロジェクトを管理するためのコマンドラインユーティリティです。サーバーの起動、データベースの操作、アプリケーションの作成など、様々なタスクをこのファイル経由で実行します。mysite/
: プロジェクトの本体となるPythonパッケージです。__init__.py
: このディレクトリがPythonパッケージであることを示すための空のファイルです。settings.py
: プロジェクト全体の設定ファイルです。データベース接続情報、インストール済みアプリ、タイムゾーンなど、重要な設定がここに記述されます。urls.py
: プロジェクトのURL定義ファイルです。どのURLにアクセスされたら、どの処理(ビュー)を呼び出すかを定義します。いわばサイトの「目次」です。asgi.py
/wsgi.py
: WebサーバーとDjangoアプリケーションを連携させるためのエントリーポイントです。今は詳しく知る必要はありません。
4. 開発サーバーの起動
Djangoには、開発用の軽量なWebサーバーが組み込まれています。manage.py
を使って、このサーバーを起動してみましょう。
bash
python manage.py runserver
ターミナルに以下のようなメッセージが表示されたら、サーバーは正常に起動しています。
“`
Watching for file changes with StatReloader
Performing system checks…
System check identified no issues (0 silenced).
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run ‘python manage.py migrate’ to apply them.
September 25, 2023 – 15:00:00
Django version 4.2.5, using settings ‘mysite.settings’
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
“`
途中に unapplied migration(s)
という警告が出ていますが、これはデータベースの設定がまだ完了していないためで、後ほど解決しますので今は無視して問題ありません。
Webブラウザを開き、アドレスバーに http://127.0.0.1:8000/
または http://localhost:8000/
と入力してください。ロケットが打ち上がるイラストと共に “The install worked successfully! Congratulations!” というページが表示されれば、第一段階は完了です!
第2章:アプリケーションの作成とモデルの定義
Djangoでは、一つの「プロジェクト」の中に、複数の「アプリケーション」を作成して機能を分割します。この章では、投票機能を持つ polls
アプリケーションを作成し、そのデータ構造を「モデル」として定義します。
1. プロジェクトとアプリケーションの違い
この概念は初学者にとって少し紛らわしいかもしれません。
- プロジェクト (Project): Webサイト全体の設定と、複数のアプリケーションをまとめるコンテナです。今回作成した
mysite
がこれにあたります。 - アプリケーション (Application): 特定の機能を持つ、再利用可能なWebアプリケーションの単位です。例えば、ブログ機能、投票機能、お問い合わせフォーム機能などをそれぞれ別のアプリケーションとして作ることができます。
この仕組みにより、開発したアプリケーションを別のプロジェクトで再利用することも容易になります。
2. 投票アプリケーションの作成
manage.py
を使って、polls
という名前のアプリケーションを作成しましょう。開発サーバーが起動している場合は、Ctrl + C
で一旦停止してください。
bash
python manage.py startapp polls
このコマンドを実行すると、polls
という名前のディレクトリが新しく作成され、その中にアプリケーション用のファイル群が生成されます。
.
├── venv/
├── mysite/
│ ...
├── polls/
│ ├── migrations/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── manage.py
models.py
: データベースのテーブル設計(モデル)を定義するファイルです。views.py
: リクエストを処理するロジック(ビュー)を記述するファイルです。admin.py
: Django管理サイトの設定を記述するファイルです。tests.py
: アプリケーションのテストコードを記述するファイルです。apps.py
: アプリケーション自体の設定ファイルです。migrations/
: データベースの設計変更の履歴(マイグレーションファイル)を格納するディレクトリです。
3. モデルの設計と作成 (MVTのM: Model)
それでは、投票アプリで扱うデータを定義しましょう。必要なデータは以下の2つです。
- Question(質問): 質問のテキストと、公開された日時を持ちます。
- Choice(選択肢): 選択肢のテキストと、その選択肢が獲得した投票数を持ちます。また、各選択肢は必ず一つの質問に紐付きます。
このデータ構造を、polls/models.py
ファイルにPythonのクラスとして記述していきます。ファイルを開いて、以下のように編集してください。
“`python
polls/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField(‘date published’)
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
“`
コードを詳しく見ていきましょう。
from django.db import models
でDjangoのモデル機能を取り込んでいます。Question
とChoice
という2つのクラスを定義し、それぞれmodels.Model
を継承しています。これにより、これらのクラスはDjangoのモデルとして扱われます。question_text
やpub_date
などは、モデルのフィールドと呼ばれ、データベースの列に相当します。models.CharField
は文字列を格納するフィールドです。max_length
は必須の引数です。models.DateTimeField
は日時を格納するフィールドです。'date published'
という引数は、管理画面などで表示される人間可読なフィールド名です。models.IntegerField
は整数を格納するフィールドです。default=0
は、レコード作成時に値が指定されなかった場合の初期値を0に設定しています。models.ForeignKey(Question, on_delete=models.CASCADE)
は、このモデルが他のモデルと関連していること(リレーションシップ)を示します。Question
を第一引数に取ることで、Choice
がQuestion
に属することを示します(多対一の関係)。on_delete=models.CASCADE
は、関連先のQuestion
が削除されたときに、このChoice
も一緒に削除される(連鎖削除)という設定です。
__str__(self)
メソッドは、このクラスのオブジェクトが文字列として表現されるときに、どのような値を返すかを定義します。これを定義しておくと、後で使う管理サイトなどでオブジェクトがquestion_text
やchoice_text
で表示されるようになり、非常に便利です。
4. モデルをDjangoに認識させる
モデルを定義しただけでは、Djangoプロジェクトはまだ polls
アプリケーションの存在を知りません。プロジェクトにアプリケーションを登録する必要があります。
mysite/settings.py
を開き、INSTALLED_APPS
というリストを探してください。そして、リストの末尾に polls
アプリケーションを追加します。
“`python
mysite/settings.py
INSTALLED_APPS = [
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘polls.apps.PollsConfig’, # この行を追加
]
“`
'polls.apps.PollsConfig'
と書くのが正式な方法です。これにより、Djangoは polls
ディレクトリ内の apps.py
に定義されている設定を読み込み、このアプリケーションをプロジェクトの一部として認識します。
5. データベースのマイグレーション
モデルの定義が完了し、アプリも登録されました。次はこのモデルの定義(設計図)に基づいて、実際のデータベースにテーブルを作成する作業、「マイグレーション」を行います。
マイグレーションは2つのステップで行います。
ステップ1: マイグレーションファイルの作成
makemigrations
コマンドは、models.py
の変更点を検出し、その変更をデータベースに適用するための指示書(マイグレーションファイル)を生成します。
bash
python manage.py makemigrations polls
実行すると、以下のような出力が表示されます。
Migrations for 'polls':
polls/migrations/0001_initial.py
- Create model Question
- Create model Choice
これは、polls
アプリの migrations
ディレクトリ内に 0001_initial.py
というファイルが作成されたことを示しています。このファイルには、Question
と Choice
のテーブルを作成するためのPythonコードが記述されています。
(任意)どんなSQLが実行されるか気になる場合は、sqlmigrate
コマンドで確認できます。
bash
python manage.py sqlmigrate polls 0001
ステップ2: データベースへの適用
migrate
コマンドは、まだ適用されていないマイグレーションファイルをすべて読み込み、実際にデータベースに対して変更(テーブル作成など)を実行します。
bash
python manage.py migrate
このコマンドを実行すると、polls
アプリのテーブルだけでなく、INSTALLED_APPS
にデフォルトで入っていた admin
や auth
といったアプリが必要とするテーブルも一緒に作成されます。
これで、Question
と Choice
のデータを格納する準備が整いました。プロジェクトのルートディレクトリに db.sqlite3
というファイルが作成されているはずです。これが、Djangoがデフォルトで使用するSQLiteデータベースの本体ファイルです。
第3章:Django管理サイトの活用
Djangoの最も強力で便利な機能の一つが、自動生成される「管理サイト」です。この管理サイトを使えば、プログラマーでなくてもWebブラウザ経由でアプリケーションのデータを簡単に追加・表示・更新・削除(CRUD操作)できます。
1. 管理者ユーザーの作成
まず、管理サイトにログインするための管理者(スーパーユーザー)アカウントを作成します。
bash
python manage.py createsuperuser
すると、ユーザー名、メールアドレス、パスワードを順番に聞かれますので、指示に従って入力してください。パスワードは入力しても画面に表示されませんが、ちゃんと入力されていますのでご安心ください。
2. モデルを管理サイトに登録
デフォルトの状態では、先ほど作成した Question
と Choice
モデルは管理サイトに表示されません。どのモデルを管理サイトで扱えるようにするかを明示的に登録する必要があります。
polls/admin.py
ファイルを開き、以下のように編集してください。
“`python
polls/admin.py
from django.contrib import admin
from .models import Question, Choice
Register your models here.
admin.site.register(Question)
admin.site.register(Choice)
“`
from .models import Question, Choice
で、同じディレクトリにある models.py
から Question
と Choice
クラスをインポートし、admin.site.register()
を使ってそれぞれのモデルを管理サイトに登録しています。たったこれだけです。
3. 管理サイトでデータを操作してみる
それでは、実際に管理サイトを使ってみましょう。開発サーバーを起動してください。
bash
python manage.py runserver
ブラウザで http://127.0.0.1:8000/admin/
にアクセスします。ログイン画面が表示されるので、先ほど作成した管理者ユーザーの情報を入力してログインしてください。
ログインすると、Django管理サイトのトップページが表示されます。”POLLS” というセクションに “Choices” と “Questions” が表示されているはずです。
“Questions” の横にある “+ Add” をクリックしてみましょう。質問を追加するフォームが表示されます。
- Question text: 「好きなプログラミング言語は?」と入力します。
- Pub date: “Today” と “Now” をクリックすると、現在の日時が自動で入力されます。
入力したら、右下の “SAVE” ボタンをクリックします。
これで、最初の Question
データが作成されました。次に、この質問に対する選択肢 (Choice
) を追加しましょう。管理サイトのトップに戻り、”Choices” の横にある “+ Add” をクリックします。
- Question: プルダウンメニューから、先ほど作成した「好きなプログラミング言語は?」を選択します。
- Choice text: 「Python」と入力します。
- Votes: 初期値が0なのでそのままでOKです。
“SAVE AND ADD ANOTHER” をクリックして、同様に「JavaScript」「Rust」などの選択肢も追加してみてください。最後に “SAVE” をクリックします。
このように、コードを一行も書かずに、データのCRUD操作が可能な高機能な管理画面が手に入りました。これがDjangoの「Batteries Included」たる所以です。
4. (発展) 管理サイトの表示をカスタマイズする
デフォルトのままでも十分便利ですが、管理サイトは柔軟にカスタマイズできます。例えば、Question
の一覧ページに公開日も表示したり、Question
の編集ページで関連する Choice
も一緒に編集できるようにしてみましょう。
polls/admin.py
を以下のように変更します。
“`python
polls/admin.py
from django.contrib import admin
from .models import Question, Choice
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 3
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {‘fields’: [‘question_text’]}),
(‘Date information’, {‘fields’: [‘pub_date’], ‘classes’: [‘collapse’]}),
]
inlines = [ChoiceInline]
list_display = (‘question_text’, ‘pub_date’) # 一覧ページに表示するフィールドを追加
list_filter = [‘pub_date’] # 日付で絞り込むフィルターを追加
search_fields = [‘question_text’] # 質問テキストで検索する機能を追加
admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice) # Choiceは単独の管理は不要なら消しても良い
“`
この変更で何が変わったか見てみましょう。
QuestionAdmin
というクラスを作成し、Question
モデルの管理サイトでの表示方法を細かく設定しています。list_display
は、一覧ページに表示するフィールドを指定します。list_filter
は、右側に日付での絞り込みサイドバーを追加します。search_fields
は、上部に検索ボックスを追加します。ChoiceInline
とinlines = [ChoiceInline]
の設定により、Question
の編集ページ内で、関連するChoice
を一覧で表示し、同時に編集できるようになります。extra = 3
は、最初から3つ分の空の選択肢入力欄を用意しておく設定です。fieldsets
は編集ページの入力フィールドをグループ化して表示を整理します。
再度 /admin/
にアクセスして、Question
の一覧ページや編集ページがどのように変わったか確認してみてください。より使いやすく、高機能になっているはずです。
第4章:ビューとテンプレートの作成 (MVTのVとT)
バックエンドのデータ管理はできましたが、まだ一般のユーザーが見るためのページがありません。この章では、ユーザーからのリクエストを処理する「ビュー」と、画面の見た目を定義する「テンプレート」を作成し、Webページとして表示できるようにします。
1. ビューとURLの基本
ユーザーがブラウザで特定のURL(例: /polls/
)にアクセスすると、Djangoは以下の流れで処理を行います。
- プロジェクトのURL設定(
mysite/urls.py
)を見て、どのアプリケーションのURL設定に処理を任せるかを判断します。 - アプリケーションのURL設定(
polls/urls.py
)を見て、アクセスされたURLのパターンに一致するものを探します。 - 一致したURLパターンに紐付けられた「ビュー」(
polls/views.py
にあるPython関数)を呼び出します。 - ビュー関数は、必要な処理(データベースからのデータ取得など)を行い、最終的にHTTPレスポンス(HTMLなど)を生成してブラウザに返します。
2. 最初のビューとURL設定
まずは、質問の一覧を表示するためのページを作成しましょう。
ステップ1: ビューの作成
polls/views.py
を開き、最初のビューとなる index
関数を作成します。
“`python
polls/views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by(‘-pub_date’)[:5]
context = {‘latest_question_list’: latest_question_list}
return render(request, ‘polls/index.html’, context)
“`
from .models import Question
で、Question
モデルをインポートします。Question.objects.order_by('-pub_date')[:5]
は、データベースに問い合わせを行う部分です。Question.objects
は、Question
モデルの全データにアクセスするためのマネージャーです。.order_by('-pub_date')
は、公開日(pub_date
)の降順(マイナス記号が降順を示す)で並び替えることを意味します。[:5]
は、並び替えた結果の先頭から5件を取得することを意味します(Pythonのスライスと同じ)。
context
は、テンプレートに渡すためのデータを格納する辞書です。キー('latest_question_list'
)がテンプレート内で使う変数名になり、値がビューで用意したデータ(latest_question_list
)になります。render(request, 'polls/index.html', context)
は、Djangoの便利なショートカット関数です。request
オブジェクト、テンプレートファイルのパス、context
辞書を引数に取ります。- 指定されたテンプレートを
context
のデータでレンダリングし、その結果をHttpResponse
オブジェクトとして返します。
ステップ2: アプリケーション用URLconfの作成
次に、この index
ビューを呼び出すためのURLを定義します。polls
ディレクトリ内に urls.py
というファイルを新規作成し、以下のように記述します。
“`python
polls/urls.py
from django.urls import path
from . import views
app_name = ‘polls’ # アプリケーションの名前空間を設定
urlpatterns = [
path(”, views.index, name=’index’),
]
“`
urlpatterns
は、URLパターンとビューのマッピングをリスト形式で定義します。path('', views.index, name='index')
- 第一引数
''
はURLのパターンです。空文字列なので、/polls/
のルートを示します。 - 第二引数
views.index
は、このURLにアクセスされたときに呼び出されるビュー関数です。 - 第三引数
name='index'
は、このURLパターンに名前を付けています。後でテンプレートからURLを逆引きする際に非常に便利です。
- 第一引数
app_name = 'polls'
は、このURLconfの「名前空間」を定義します。他のアプリで同じname
(例:index
)が使われても区別できるようになります。
ステップ3: プロジェクト用URLconfの設定
最後に、プロジェクト全体のURLconf(mysite/urls.py
)に、「/polls/
で始まるURLへのアクセスは、polls.urls
に処理を任せる」という設定を追加します。
mysite/urls.py
を開き、以下のように編集してください。
“`python
mysite/urls.py
from django.contrib import admin
from django.urls import include, path # include をインポート
urlpatterns = [
path(‘polls/’, include(‘polls.urls’)), # この行を追加
path(‘admin/’, admin.site.urls),
]
“`
from django.urls import include
を追加します。path('polls/', include('polls.urls'))
という行を追加します。これは、URLがpolls/
で始まるリクエストを受け取ると、URLのpolls/
の部分を切り落とし、残りの文字列をpolls.urls
モジュール(先ほど作成したpolls/urls.py
)に渡して、さらなる処理を委譲するという意味です。
これでURLの設定は完了です。
3. テンプレートの作成 (MVTのT: Template)
ビューは「polls/index.html
」というテンプレートを使うように指定しましたが、まだそのファイルは存在しません。作成しましょう。
Djangoは、デフォルトでは各アプリケーションのディレクトリ内にある templates
という名前のディレクトリを探します。さらに、その中にアプリケーションと同じ名前のディレクトリを作成するのが慣習です(名前空間の衝突を避けるため)。
polls
ディレクトリ内に templates
ディレクトリを作成し、さらにその中に polls
ディレクトリを作成してください。最終的な構造は polls/templates/polls/
となります。
この polls/templates/polls/
ディレクトリの中に、index.html
というファイルを新規作成し、以下の内容を記述します。
“`html
{% if latest_question_list %}
投票アプリ
-
{% for question in latest_question_list %}
- {{ question.question_text }}
{% endfor %}
{% else %}
利用可能な投票はありません。
{% endif %}
“`
これがDjangoのテンプレート言語 (DTL)です。
{% ... %}
はテンプレートタグで、if
文やfor
ループなどのロジックを記述します。{{ ... }}
は変数で、ビューから渡されたコンテキストのデータを表示します。{% if latest_question_list %}
: ビューから渡されたlatest_question_list
が存在するかどうかをチェックします。{% for question in latest_question_list %}
:latest_question_list
をループ処理します。{{ question.question_text }}
: ループ中の各question
オブジェクトのquestion_text
属性を表示します。{% url 'polls:detail' question.id %}
: これがURLの逆引きです。url
タグは、指定された名前のURLパターンに対応するURLを生成します。'polls:detail'
は、「polls
アプリケーション(名前空間)のdetail
という名前のURL」を指します。question.id
は、URLパターンに含まれる引数(後で作成します)に渡す値です。- これにより、URLを
/polls/1/
のようにハードコーディングするのではなく、動的に生成できるため、後でURL構成を変更した際にもテンプレートを修正する必要がなくなります。
さて、開発サーバーを起動(または再起動)して、ブラウザで http://127.0.0.1:8000/polls/
にアクセスしてください。管理サイトで追加した質問がリスト形式で表示されれば成功です!
4. 詳細ページと投票フォームの作成
次に、リストの各質問をクリックしたときに表示される詳細ページを作成します。このページには質問のテキストと、投票するための選択肢がラジオボタン付きのフォームとして表示されます。
ステップ1: 詳細・結果・投票ビューの追加
polls/views.py
に、detail
, results
, vote
の3つのビューを追加します。
“`python
polls/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, Question
… indexビューはそのまま …
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, ‘polls/detail.html’, {‘question’: question})
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, ‘polls/results.html’, {‘question’: question})
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST[‘choice’])
except (KeyError, Choice.DoesNotExist):
# 質問フォームを再表示します。
return render(request, ‘polls/detail.html’, {
‘question’: question,
‘error_message’: “選択肢を選んでください。”,
})
else:
selected_choice.votes += 1
selected_choice.save()
# POSTデータが成功した後は、常にHttpResponseRedirectを返す。
# これにより、ユーザーがブラウザの「戻る」ボタンを押したときに
# データが2回送信されるのを防ぎます。
return HttpResponseRedirect(reverse(‘polls:results’, args=(question.id,)))
“`
detail
ビュー:- URLから
question_id
を受け取ります。 get_object_or_404(Question, pk=question_id)
は、指定された主キー(pk
)を持つQuestion
オブジェクトを取得しようとします。もしオブジェクトが存在しない場合は、HTTP 404 (Not Found) エラーを発生させます。これはtry-except
を書くよりも簡潔です。- 取得した
question
オブジェクトをpolls/detail.html
テンプレートに渡します。
- URLから
results
ビュー:detail
と同様にquestion
を取得し、polls/results.html
テンプレートに渡します。
vote
ビュー:- このビューはフォームからのPOSTリクエストを処理します。
request.POST['choice']
で、フォームから送信されたデータにアクセスします。'choice'
は、HTMLのフォーム要素のname
属性に対応します。question.choice_set.get(...)
で、その質問に紐づく選択肢の中から、POSTされたIDを持つものを取得します。try-except
ブロックで、選択肢が選ばれずにフォームが送信された場合(KeyError
)や、不正なIDが送られてきた場合(Choice.DoesNotExist
)のエラーを処理します。エラー時は、エラーメッセージ付きで詳細ページを再表示します。- 正常に選択肢が取得できた場合 (
else
節)、その選択肢のvotes
を1増やして (+= 1
)、データベースに保存 (save()
) します。 - 最後に
HttpResponseRedirect
を返します。これはブラウザに「このURLにリダイレクトしてください」と指示するレスポンスです。 reverse('polls:results', args=(question.id,))
は、url
テンプレートタグのPython版です。polls
アプリのresults
という名前のURLを逆引きし、question.id
を引数として/polls/1/results/
のようなURL文字列を生成します。- このようにPOST処理の後にリダイレクトするのは POST-Redirect-GET パターンと呼ばれ、ユーザーがリロードボタンを押してもフォームが再送信されるのを防ぐためのWeb開発のベストプラクティスです。
ステップ2: URLパターンの追加
polls/urls.py
を更新して、新しいビューに対応するURLパターンを追加します。
“`python
polls/urls.py
from django.urls import path
from . import views
app_name = ‘polls’
urlpatterns = [
# ex: /polls/
path(”, views.index, name=’index’),
# ex: /polls/5/
path(‘
# ex: /polls/5/results/
path(‘
# ex: /polls/5/vote/
path(‘
]
“`
<int:question_id>
という部分は「パスコンバータ」と呼ばれます。URLの一部をキャプチャし、ビュー関数に引数として渡します。int
は、ここに来る文字列が整数であることを示します。question_id
は、キャプチャした値を入れる変数名です。これがビュー関数の引数名question_id
に対応します。
ステップ3: 詳細ページのテンプレート作成
polls/templates/polls/
ディレクトリに detail.html
を作成します。
“`html
{{ question.question_text }}
{% if error_message %}
{{ error_message }}
{% endif %}
“`
<form>
タグのaction
属性には、フォームの送信先URLを指定します。ここでもurl
タグを使って動的に生成しています。method="post"
は、フォームデータをPOSTメソッドで送信することを指定します。データの状態を変更する操作にはPOSTを使うのが原則です。{% csrf_token %}
は、クロスサイトリクエストフォージェリ (CSRF) というセキュリティ攻撃を防ぐための必須のテンプレートタグです。DjangoはPOSTフォームにこのトークンがないとリクエストを拒否します。question.choice_set.all
で、question
オブジェクトに関連する全てのChoice
オブジェクトを取得してループします。<input type="radio" name="choice" ...>
: ラジオボタンを作成します。重要なのはname="choice"
の部分です。これにより、複数のラジオボタンがグループ化され、どれか一つしか選択できなくなります。このname
が、ビューのrequest.POST['choice']
で使われます。value="{{ choice.id }}"
で、選択されたラジオボタンの値として、Choice
オブジェクトのIDを送信します。
ステップ4: 結果ページのテンプレート作成
最後に、polls/templates/polls/
ディレクトリに results.html
を作成します。
“`html
{{ question.question_text }}
-
{% for choice in question.choice_set.all %}
- {{ choice.choice_text }} — {{ choice.votes }} vote{{ choice.votes|pluralize }}
{% endfor %}
Vote again?
Back to poll list
“`
choice.votes|pluralize
はテンプレートフィルタの一例です。choice.votes
の値が1以外の場合に、pluralize
は自動的に末尾に “s” を追加します(”1 vote”, “2 votes” のように表示されます)。
これで全てのパーツが揃いました。/polls/
にアクセスし、質問をクリックして詳細ページが表示されるか、ラジオボタンを選択して “Vote” ボタンを押し、結果ページにリダイレクトされるかを確認してください。選択せずに投票しようとするとエラーメッセージが表示されるはずです。
第5章:汎用ビューの活用
ここまででアプリケーションは動作するようになりましたが、polls/views.py
を見てみると、index
, detail
, results
のビューには共通のパターンがあることに気づきます。
- URLからパラメータを受け取る。
- パラメータを使ってデータベースからデータを取得する。
- テンプレートをレンダリングして返す。
このようなよくある処理は、Djangoでは「汎用ビュー (Generic Views)」としてクラスベースで提供されています。汎用ビューを使うと、定型的なコードを大幅に削減し、よりDRY (Don’t Repeat Yourself) なコードを書くことができます。
polls/views.py
を汎用ビューを使って書き換えてみましょう。
“`python
polls/views.py
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic # genericをインポート
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = ‘polls/index.html’
context_object_name = ‘latest_question_list’
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = ‘polls/detail.html’
class ResultsView(generic.DetailView):
model = Question
template_name = ‘polls/results.html’
voteビューはフォーム処理ロジックが特殊なので、そのまま残す
def vote(request, question_id):
# (中身は変更なし)
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST[‘choice’])
except (KeyError, Choice.DoesNotExist):
return render(request, ‘polls/detail.html’, {
‘question’: question,
‘error_message’: “選択肢を選んでください。”,
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse(‘polls:results’, args=(question.id,)))
“`
見てください。index
, detail
, results
のための関数が、3つのクラスに置き換わり、非常にスッキリしました。
generic.ListView
: オブジェクトの「一覧」を表示するためのビューです。template_name
: 使用するテンプレートを明示的に指定します。context_object_name
: テンプレート内で使う変数名を指定します。これを指定しない場合、デフォルトでquestion_list
という名前になります。get_queryset()
: 表示するオブジェクトのリストを返すメソッドをオーバーライドします。
generic.DetailView
: 特定のオブジェクトの「詳細」を表示するためのビューです。model = Question
: どのモデルを扱うかを指定します。DetailView
は、URLから渡された主キー(pk
)を使って、自動的にQuestion.objects.get(pk=pk)
を実行してくれます。template_name
: 使用するテンプレートを指定します。
汎用ビューを使うようにしたので、polls/urls.py
も少し修正が必要です。関数ベースのビューではなく、クラスベースのビューを呼び出すように変更します。
“`python
polls/urls.py
from django.urls import path
from . import views
app_name = ‘polls’
urlpatterns = [
path(”, views.IndexView.as_view(), name=’index’),
path(‘
path(‘
path(‘
]
“`
- クラスベースのビューをURLに登録するには、
.as_view()
メソッドを呼び出します。 DetailView
は、URLからキャプチャする主キーの変数名としてpk
を期待します。そのため、question_id
をpk
に変更しました。
これでリファクタリングは完了です。アプリケーションの動作は以前と全く同じですが、コードはより簡潔で、Djangoの標準的な書き方に沿ったものになりました。
第6章:テストの作成
アプリケーションが複雑になるにつれ、一つの変更が意図しない別の部分に影響(デグレード)を与えてしまうことがあります。これを防ぎ、コードの品質を担保するために「自動テスト」は不可欠です。
Djangoは強力なテストフレームワークを内蔵しており、簡単にテストコードを記述できます。
Question
モデルに「最近公開されたかどうか」を判定するメソッドを追加し、そのメソッドのテストを書いてみましょう。
ステップ1: モデルにメソッドを追加
polls/models.py
を開き、Question
クラスに was_published_recently
メソッドを追加します。
“`python
polls/models.py
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# … 既存のフィールド …
def str(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
“`
- このメソッドは、
pub_date
が「過去1日以内」であればTrue
を、そうでなければFalse
を返します。timezone.now()
を使うことで、settings.py
のTIME_ZONE
設定を考慮した現在時刻を取得できます。
ステップ2: テストコードの作成
polls/tests.py
を開き、was_published_recently
メソッドの動作を検証するテストコードを記述します。
“`python
polls/tests.py
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date
is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
“`
django.test.TestCase
を継承したクラスを作成します。test_
で始まる名前のメソッドが、テストとして実行されます。- 各テストメソッドでは、特定の状況(未来の質問、古い質問、最近の質問)を作り出し、
was_published_recently()
メソッドが期待通りの値 (True
orFalse
) を返すかをself.assertIs()
を使って検証しています。
ステップ3: テストの実行
ターミナルで以下のコマンドを実行します。
bash
python manage.py test polls
このコマンドは、polls
アプリケーション内のテストを探して実行します。
“`
Creating test database for alias ‘default’…
System check identified no issues (0 silenced).
…
Ran 3 tests in 0.002s
OK
Destroying test database for alias ‘default’…
“`
OK
と表示されれば、3つのテストがすべて成功したことを意味します。これで、将来 was_published_recently
メソッドのロジックを変更したとしても、このテストを実行するだけで、意図した通りに動作しているか(あるいは壊してしまったか)を瞬時に確認できます。
まとめと次のステップ
お疲れ様でした!このチュートリアルを通して、あなたはDjangoを使ってWebアプリケーションを構築する一連の流れを体験しました。
- プロジェクトのセットアップ: 仮想環境を構築し、Djangoをインストールし、プロジェクトとアプリを作成しました。
- モデル (M):
models.py
にPythonクラスとしてデータ構造を定義し、マイグレーションによってデータベースに反映させました。 - 管理サイト: ほとんどコードを書かずに、高機能なデータ管理画面を作成し、カスタマイズしました。
- ビュー (V) と URL: URLとビュー関数(またはクラス)を紐付け、ユーザーからのリクエストを処理するロジックを記述しました。
- テンプレート (T): Djangoテンプレート言語を使い、ロジックと見た目を分離した動的なHTMLページを作成しました。
- 汎用ビュー: 定型的なビューをクラスベースの汎用ビューで置き換え、コードをDRYに保ちました。
- テスト: アプリケーションの品質を保証するための自動テストを作成しました。
あなたは今、Django開発者としての確かな一歩を踏み出しました。ここからさらに学びを深めていくための道筋はたくさんあります。
- 静的ファイル(CSS, JavaScript, 画像)の扱い: アプリケーションの見た目をリッチにする方法を学びましょう。
- ユーザー認証: Djangoに組み込まれた認証システムを使って、ログイン・ログアウト機能を実装してみましょう。
- デプロイ: 作成したアプリケーションを、HerokuやAWSなどのクラウドサービスに公開(デプロイ)してみましょう。
- Django REST Framework: Djangoを使ってWeb APIを構築するための強力なライブラリです。
学習の最良のリソースは、常にDjangoの公式ドキュメントです。非常に丁寧で網羅的なので、困ったときや新しい機能を学びたいときには、ぜひ参照してください。
このチュートリアルが、あなたのWeb開発の旅の素晴らしいスタート地点となったことを願っています。Happy Djangonauting