Django 公式チュートリアルをやってみて

はじめに

現在Djangoの学習に取り組んでおり、最初にUdemyの講座でアプリ開発の流れを学びました。2つ目の教材としてDjango公式チュートリアルに取り組んだので、今回はその感想をまとめました。

👇 Django 公式チュートリアル

docs.djangoproject.com

良かったところ

  • 投票アプリ(Pollアプリ)の作成を通して、テストの書き方やアプリのパッケージ化、管理画面のカスタマイズまで、一連の流れと機能について体系的に学べたのがとても良かったです。
  • チュートリアルの主要な部分が日本語に翻訳されているため、英語が苦手でも取り組みやすいと感じました。
  • アプリを作成するために必要なコードがすべて記載されているので、その通りに手を動かせば実際に動くアプリが作成できるのは、初学者にとってありがたいと思いました。

悪かったところ

全てテキストによる説明のため、処理の流れや構造をイメージしにくいと思いました。初学者がいきなりこのチュートリアルから始めるのは難易度が高そうなので、まずは動画教材などで全体像を掴んでから取り組むのが良いと思います。

学んだこと

チュートリアルに沿ってハンズオンで取り組み、分からないところは都度調べながら進めたことで、Udemyでの学習が終了した時点ではぼんやりとしていたモデル、URL、ビュー、テンプレートの仕組みについて、だいぶ理解を深めることができました。
特に理解を深められたところ、今後気をつけたいことについて以下にまとめました。(コードはチュートリアルより抜粋しています)

リクエスト処理の流れ

ユーザーがサイト上のリンクをクリックし、http://127.0.0.1:8000/polls/1/にリクエストを送ると、まずsettings.pyROOT_URLCONFに設定されているモジュールを参照します。

ROOT_URLCONF = 'mysite.urls'

mysiteディレクトリ内のurls.pyurlpatterns変数を参照します。/polls/1/は一つ目のパスpollsに一致します。第二引数のinclude('polls.urls')は、これまでに一致したURLの部分(polls/)を切り捨て、残りの文字列(1/)をpolls.urlsに渡します。

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]

pollsディレクトリ内のurls.pyを参照し、1/と一致するのは2つ目の"<int:pk>/"なので、対応する DetailViewが呼び出され、ロジック処理などを行ったあとにレスポンスを返します。

from django.urls import path
from . import views

app_name = "polls"
urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path("<int:pk>/", views.DetailView.as_view(), name="detail"),
    path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]


外部キーによる関連オブジェクトの取得

以下のモデルでは、各Choice(選択肢)が外部キー(ForeignKey)によって一つのQuestion(質問)に関連付けられています。Choiceモデルの一つ目のフィールドであるquestionには、関連したQuestionのオブジェクトが格納されます。

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

このとき、choice_set<relatedmodel>_set)という名前を使って、Questionオブジェクトから関連するChoiceオブジェクトを扱うことができます。

from polls.models import Choice, Question

# 主キーが1のQuestionオブジェクトを取得
q = Question.objects.get(pk=1)

# qに関連するChoiceオブジェクトを全て取得
q.choice_set.all()

# qに関連するChoiceオブジェクトの数を取得
q.choice_set.count()

# qに関連するChoiceオブジェクトを新規作成する
q.choice_set.create(choice_text="Not much", votes=0)


レンダーとリダイレクトの違い

レンダー(render())はテンプレートを使って HTML を生成しレスポンスを返すことで、ウェブページを表示します。

from django.shortcuts import render

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/results.html", {"question": question})

リダイレクト(redirect())は指定した別のURLに遷移するため、GETリクエストが発生します。
voteメソッド(投票されたら投票数を増やす)はCRUD操作のUpdate(更新)にあたるためPOSTリクエストで呼ばれますが、レンダーを使用すると、ユーザーがページを再読み込みした際に同じPOSTリクエストが再送信される可能性があり、その度に投票数が重複してカウントされてしまいます。これを防ぐために、投票処理が成功した場合はリダイレクト(GETリクエスト)で終了します。

from django.shortcuts import redirect

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": "You didn't select a choice.",
            },
        )
     # 処理が成功した場合はリダイレクト
    else:
        selected_choice.votes = F('votes') + 1
        selected_choice.save()
        return redirect("polls:results", question.id)


汎用ビューのテンプレートとコンテキスト

汎用ビューでは、テンプレート名とコンテキスト変数名がデフォルトで決まっています。

  • テンプレート名:汎用ビューが呼び出されたときに探すテンプレートファイルの名前
  • コンテキスト変数名:テンプレート内でオブジェクトを取得する際に使用する変数名

ListViewでは複数オブジェクトを扱い、DetailViewUpdateViewなどのListView 以外の汎用ビューでは、通常は単一のオブジェクトを扱います。

クラス テンプレート名(デフォルト) コンテキスト変数名(デフォルト) オブジェクト
ListView polls/question_list.html object_list 複数オブジェクトを扱う
ListView 以外(例:DetailView polls/question_detail.html question(モデル名小文字) 単一オブジェクトを扱う(URLに<int:pk>の指定が必要)

テンプレート名とコンテキスト変数名は、以下のように指定することで任意の名前に変更することができます。

役割
使用するテンプレートファイルを明示指定 template_name = "polls/index.html"
テンプレートに渡す変数名を明示指定 context_object_name = "latest_question_list"

難しかったこと

リクエストが送られてから、どのように処理が渡って最終的にレスポンスが返されるのか、またデータの受け渡しがどのように行われているのかを理解するのが難しかったです。一つひとつのコードの流れを追っていくことで少しずつ理解することができましたが、まだ完全には理解しきれていない部分もあるので、今後のアプリ開発を通して実践的に身につけていきたいと思いました。

おわりに

公式チュートリアルに取り組んだことで、Djangoの基本について理解を深めることができました。チュートリアルはなかなかのボリュームだと感じましたが、全体のドキュメントの5%にも満たないそうです。今後、少しずつでもDjangoの豊富な機能を活用できるように、公式ドキュメントを参考にしながら開発を進めていきたいと思います。