pyhaya’s diary

プログラミング、特にPythonについての記事を書きます。Djangoや機械学習などホットな話題をわかりやすく説明していきたいと思います。

Djangoのチュートリアル 6 フォームを書く

今回は、Djangoで作ったページにフォームを追加していきます。前回までの記事はこちらです。

Djangoのチュートリアル 1 インストールから最初のビュー作成まで - pyhaya’s diary
Djangoのチュートリアル 2 モデルを作成する - pyhaya’s diary
Djangoのチュートリアル 3 ビューとモデルを組み合わせる - pyhaya’s diary
Djangoのチュートリアル 4 HTMLに慣れる - pyhaya’s diary
pyhaya.hatenablog.com

目次

フォームとは

Webページにおけるフォームというのは、Webページにおいてユーザーが入力を行う部分になります。通常はformタグを使って、その中にinputタグなどを使って入力部を作っていきます。例えばお問い合わせフォームなどがあります。詳しくは下のページを見てください。
html-coding.co.jp

フォームの例

フォームに慣れるためにいくつかフォームを作ってみます。htmlタグなどは省略して、body内のみを書いていきます。

<form method="POST" action="">
    <p>名前<input type="text"></p>    <!-- テキスト入力部 -->
    <p>身分
        <input type="radio">会社員        <!-- ラジオボタン -->
        <input type="radio">学生
        <input type="radio">主婦
        <input type="radio">無職
    </p>
    <p>年齢:
        <select>        <!-- 選択 -->
            <option>10歳以下</option>
            <option>10~20歳</option>
            <option>20~30歳</option>
            <option> それ以上</option>
        </select>
    </p>
    ご要望:
    <p>
        <textarea rows=5 cols=30>    <!-- テキストエリア -->
        </textarea>
    </p>
    <p><input type="submit" value="送信"></p>    <!-- 送信ボタン -->
</form>

いろいろ書きましたが、これをブラウザで見ると下のようになります。
f:id:pyhaya:20181101075854p:plain

フォームを作る

ではDjangoを使ってフォームを実装してみます。

どこを変更するか

今回はdetail.htmlを変更していきます。前回までで作ったdetail.htmlは下のようでした。

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
    </head>
    <body>
        <h1>{{ question.question_text }} </h1>
        {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}</li>
        {% endfor %}
    </body>
</html>

これを下のように変更します。

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
    </head>
    <body>
        <h1>{{ question.question_text }} </h1>
        <form action="{% url 'polls:vote' question.id %}" method="post">
            {% csrf_token %}
            {% for choice in question.choice_set.all %}
            <p>
                <input type="radio" name="choice" id="choice{{ forloop.counter }}"
                                                  value="{{ choice.id }}">
                <label for="choice{{ forloop.counter }}">
                    {{ choice.choice_text }}
                </label>
            {% endfor %}
            </p>
            <p>
            <input type="submit" value="Vote">
            </p>
        </form>
    </body>
</html>

だいぶいろいろ付け加わりました。

formタグの書き方

8行目を見てみると、ここでフォームの定義とアクションが設定されています。アクションには前回の記事で説明したように、{% url %}タグを用いています。しかしその中身は前回説明したものとは少し異なっています。'polls:vote'とは何でしょうか?これはpollsがアプリケーションの名前で、voteがname=によって識別された名前を表しています。pollsアプリケーションを認識させるためにはもうひと手間必要ですが、これはurls.pyを編集するところで解説します。

もうひとつ重要なのが、methodの欄です。データベースに変更があるときには必ず"post"にする必要があります

forループ

HTML内でのforループが再び出てきました。もう見慣れてきたかもしれませんが、こちらも今までとは異なる要素が入ってきています。それはforloop.countです。これはforループが何回回ったかをあらわすものです。

ビューを編集する

detail.htmlを書き換えましたので、ビューもこれを正しく表示できるように変更しましょう。detail.htmlにはVoteボタンがあるので、これが押されたときの遷移画面も考えなくてはいけません。

views.py

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)

def detail(request, question_id):
    question = Question.objects.get(pk=question_id)
    return render(request, 'polls/detail.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' : 'You did not select a choice',
            })

    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', \
            args=(question.id,)))

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

vote関数について説明をします。

get_object_or_404(Question, pk=question_id)

これは、Questionオブジェクトの中からpk要素がquestion_idに等しいものがある場合にはそれを取得し、ない場合には「404 ページが見つかりませんでした」の表示を行います。

HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

reverse関数というものが登場しています。これは{% url %}タグと似ていて、URLのハードコードを避けることができます。'polls:result'を探し出して引数としてquestion_idを渡します。

Voteボタンが押されたらHttpResponseRedirectが呼ばれてresultsに結び付けられているページへ遷移する仕組みになっています。

resultsビューはすぐ下に定義されていて、results.htmlに結び付けられています。ではこのページについても見ておきましょう。

results.htmlの中身

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>{{ question.question_text }}</h1>
        <ul>
        {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}--{{ choice.votes }} vote</li>
        {% endfor %}
        </ul>
        <a href="{% url 'polls:detail' question.id %}">Vote Again?</a>
    </body>
</html>

ここに関しては新しい要素というのは出てきてはいません。Vote Againにリンクが結び付けられていて、質問ページに戻れるようになっています。

urls.pyを編集する

ではこれまでの編集で新たに必要になった要素をurls.pyに付け足していきます。

urls.py

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
        path('', views.index, name='index'),
        path('<int:question_id>/', views.detail, name='detail'),
        path('<int:question_id>/results/', views.results, name='results'),
        path('<int:question_id>/vote', views.vote, name='vote'),
        ]

urlpatternだけではなく、app_nameという要素が付け加わったことに注目してください。HTMLファイルを編集していた時に'polls:results'のような表現が現れていたことを覚えているでしょうか?この'polls'がapp_nameに対応しています。

お疲れ様です。これで完成です。今回の記事では、実際にpollsアプリケーションで投票操作を実装しました。