Python Djangoチュートリアル:簡単なアプリを作りながら基本を学ぼう


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公式チュートリアルでもおなじみの「投票アプリケーション」を作成します。このアプリは、以下のようなシンプルな機能を持っています。

  1. 質問の一覧を表示するページ
  2. 各質問の詳細と、投票するための選択肢(フォーム)を表示するページ
  3. 投票結果を表示するページ
  4. データを簡単に追加・編集できる管理サイト

この小さなアプリケーションを通じて、あなたは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つです。

  1. Question(質問): 質問のテキストと、公開された日時を持ちます。
  2. 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のモデル機能を取り込んでいます。
  • QuestionChoice という2つのクラスを定義し、それぞれ models.Model を継承しています。これにより、これらのクラスはDjangoのモデルとして扱われます。
  • question_textpub_date などは、モデルのフィールドと呼ばれ、データベースの列に相当します。
  • models.CharField は文字列を格納するフィールドです。max_length は必須の引数です。
  • models.DateTimeField は日時を格納するフィールドです。'date published' という引数は、管理画面などで表示される人間可読なフィールド名です。
  • models.IntegerField は整数を格納するフィールドです。default=0 は、レコード作成時に値が指定されなかった場合の初期値を0に設定しています。
  • models.ForeignKey(Question, on_delete=models.CASCADE) は、このモデルが他のモデルと関連していること(リレーションシップ)を示します。
    • Question を第一引数に取ることで、ChoiceQuestion に属することを示します(多対一の関係)。
    • on_delete=models.CASCADE は、関連先の Question が削除されたときに、この Choice も一緒に削除される(連鎖削除)という設定です。
  • __str__(self) メソッドは、このクラスのオブジェクトが文字列として表現されるときに、どのような値を返すかを定義します。これを定義しておくと、後で使う管理サイトなどでオブジェクトが question_textchoice_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 というファイルが作成されたことを示しています。このファイルには、QuestionChoice のテーブルを作成するためのPythonコードが記述されています。

(任意)どんなSQLが実行されるか気になる場合は、sqlmigrate コマンドで確認できます。

bash
python manage.py sqlmigrate polls 0001

ステップ2: データベースへの適用

migrate コマンドは、まだ適用されていないマイグレーションファイルをすべて読み込み、実際にデータベースに対して変更(テーブル作成など)を実行します。

bash
python manage.py migrate

このコマンドを実行すると、polls アプリのテーブルだけでなく、INSTALLED_APPS にデフォルトで入っていた adminauth といったアプリが必要とするテーブルも一緒に作成されます。

これで、QuestionChoice のデータを格納する準備が整いました。プロジェクトのルートディレクトリに db.sqlite3 というファイルが作成されているはずです。これが、Djangoがデフォルトで使用するSQLiteデータベースの本体ファイルです。


第3章:Django管理サイトの活用

Djangoの最も強力で便利な機能の一つが、自動生成される「管理サイト」です。この管理サイトを使えば、プログラマーでなくてもWebブラウザ経由でアプリケーションのデータを簡単に追加・表示・更新・削除(CRUD操作)できます。

1. 管理者ユーザーの作成

まず、管理サイトにログインするための管理者(スーパーユーザー)アカウントを作成します。

bash
python manage.py createsuperuser

すると、ユーザー名、メールアドレス、パスワードを順番に聞かれますので、指示に従って入力してください。パスワードは入力しても画面に表示されませんが、ちゃんと入力されていますのでご安心ください。

2. モデルを管理サイトに登録

デフォルトの状態では、先ほど作成した QuestionChoice モデルは管理サイトに表示されません。どのモデルを管理サイトで扱えるようにするかを明示的に登録する必要があります。

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 から QuestionChoice クラスをインポートし、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 は、上部に検索ボックスを追加します。
  • ChoiceInlineinlines = [ChoiceInline] の設定により、Question の編集ページ内で、関連する Choice を一覧で表示し、同時に編集できるようになります。extra = 3 は、最初から3つ分の空の選択肢入力欄を用意しておく設定です。
  • fieldsets は編集ページの入力フィールドをグループ化して表示を整理します。

再度 /admin/ にアクセスして、Question の一覧ページや編集ページがどのように変わったか確認してみてください。より使いやすく、高機能になっているはずです。


第4章:ビューとテンプレートの作成 (MVTのVとT)

バックエンドのデータ管理はできましたが、まだ一般のユーザーが見るためのページがありません。この章では、ユーザーからのリクエストを処理する「ビュー」と、画面の見た目を定義する「テンプレート」を作成し、Webページとして表示できるようにします。

1. ビューとURLの基本

ユーザーがブラウザで特定のURL(例: /polls/)にアクセスすると、Djangoは以下の流れで処理を行います。

  1. プロジェクトのURL設定(mysite/urls.py)を見て、どのアプリケーションのURL設定に処理を任せるかを判断します。
  2. アプリケーションのURL設定(polls/urls.py)を見て、アクセスされたURLのパターンに一致するものを探します。
  3. 一致したURLパターンに紐付けられた「ビュー」(polls/views.py にあるPython関数)を呼び出します。
  4. ビュー関数は、必要な処理(データベースからのデータ取得など)を行い、最終的に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 %}

投票アプリ

{% 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 テンプレートに渡します。
  • 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(‘/’, views.detail, name=’detail’),
# ex: /polls/5/results/
path(‘/results/’, views.results, name=’results’),
# ex: /polls/5/vote/
path(‘/vote/’, views.vote, name=’vote’),
]
“`

  • <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 %}

{% csrf_token %}
{% for choice in question.choice_set.all %}


{% endfor %}

“`

  • <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 のビューには共通のパターンがあることに気づきます。

  1. URLからパラメータを受け取る。
  2. パラメータを使ってデータベースからデータを取得する。
  3. テンプレートをレンダリングして返す。

このようなよくある処理は、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(‘/’, views.DetailView.as_view(), name=’detail’), # question_id を pk に変更
path(‘/results/’, views.ResultsView.as_view(), name=’results’), # question_id を pk に変更
path(‘/vote/’, views.vote, name=’vote’),
]
“`

  • クラスベースのビューをURLに登録するには、.as_view() メソッドを呼び出します。
  • DetailView は、URLからキャプチャする主キーの変数名として pk を期待します。そのため、question_idpk に変更しました。

これでリファクタリングは完了です。アプリケーションの動作は以前と全く同じですが、コードはより簡潔で、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.pyTIME_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 or False) を返すかを 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

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール