LoginSignup
1
5

More than 1 year has passed since last update.

はじめてのDjango (4) view関数からデータベースを参照してページに情報を送り込もう

Last updated at Posted at 2019-04-11

はじめに

このドキュメントは,もともとはラボの新メンバー向けの入門テキストとして作成したもので,はじめてDjangoにふれる人にざっとその全体像をつかんでもらうことを狙っています.Djangoの日本語での参考資料も充実してきたので今さらという気がしないでもないですが,見直しを機にQiitaに移すことにしました.万が一でもどなたかの参考になれば幸いです.

Djangoのバージョンは2.0以降を想定しています.説明のための具体例として,Djangoのオフィシャルチュートリアルにある投票アプリをとりあげています(が,説明上の都合で少しコードを追加,変更しているところもあります).素人の独学がベースで,特に前半は公式チュートリアルをやってみた感想のような記事です(今回は全7回中の4回目).

全体のコードはまとめてGitHubに置きました.

変更履歴

  • [2021/04/26] Djangoのバーションを3.2に更新し,それ基づいて内容を微修正しました.

view関数からのデータベースの参照

ここでは,データベースに格納されているデータにアクセスしてそれをview関数の中で利用する方法を紹介します.

例えば,データベースに格納されているQestionクラスのデータ(インスタンス)をすべて取得したいときには,次のようにします(データモデルのクラス名の後ろに.objects.all()をつける).

questions = Question.objects.all()

データベースに対するクエリ処理はDjangoが面倒をみてくれるので,view関数の中では,questionsを単にQuestionクラスのリストのように扱うことができます(厳密には,リストではなくクエリセットというデータ構造みたいです).したがって,このあとquestions[0]questions[0:5]といった表現が使えます.

何らかのfieldの値でクエリセットをソートしたいときは次のようにします.

questions = Question.objects.all().order_by(field名)

ある条件を満たすものだけを抜き出したいときや,逆に,ある条件を満たすものを除外して抜き出したいときには,次のように書くことができます.

questions = Question.objects.filter(条件)
questions = Question.objects.exclude(条件)

また,条件に合うものが1つだけであることがわかっているときには,

question = Question.objects.get(条件)

とすることもできます(この場合は,クエリセットではなく1つのインスタンスが得られます).

Choiceも同じように扱うことができますが,Questionとの関係を利用して,あるquestionというQuestionインスタンスに関連するものだけを抜き出す,といったこともできます.

choices = question.choice_set.all()
choices = question.choice_set.filter(条件)
choices = question.choice_set.exclude(条件)

クエリセットを取得する方法の詳しい説明はここを参照してください.

続いて,これらを用いた具体的なview関数の例をみていきましょう.まず,views.pyにQuestionとChoiceをimportしてから,index()を次のように書き換えてみます.

from .models import Question, Choice

...

def index(request):
    latest_question_list = Question.objects.all().order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Questionクラスのインスタンスをすべて抜き出したあと,pub_dataフィールドの逆順(新しいものから古いものへ)にソートし,最後にスライスで前から5個のみを取り出してlatest_question_listに代入していることがみてとれます.そして,それをcontextに含めてrender()を呼んでいます.

対応するtemplate(index.html)も忘れずに作成しておきましょう.

<h1>Welcome to Polls App.</h1>
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li>
            <a href="{% url 'polls:detail' question.id %}">
            {{ question.question_text }}
            </a>
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

この例で,templateの中でif文やforループを使用する方法が示されています.

また,idはデータモデル作成の際に自動的に追加された行番号のようなもので,これによって同じデータモデルクラスのインスタンスを個々に区別することができます(このようにデータモデルのインスタンスを区別するための目印をprimary_keyといいます.id以外のfieldを自分でprimary_keyに指定することもできますが,その場合は異なるインスタンスのそのfieldには同じ値は入れられません.また,pkという名前でprimary_keyを指し示すことができ,デフォルトのままprimary_keyidを使用している場合はpk=idとなります).

次に,detail()を書き換えます.このview関数は,指定された質問項目の詳細を表示するためのものであり,もし指定された質問項目が存在しなかった場合には404エラーページを表示させています.

from django.http import Http404

...

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    choice_list = question.choice_set.all()
    context = {'question': question, 'choice_list':choice_list}
    return render(request, 'polls/detail.html', context)

最初にimportしているHttp404は,404エラーページを表示させるためのものです.

tryで指定されたpk(=id)のQuestionインスタンスの取得を試みています.もしそれが存在しなかった場合は例外(DoesNotExist)が発生するので,それをexceptで捕捉して404エラーページを表示させていることがみてとれます.うまく行った場合は,choice_listを作成し,それをcontextに含めてrender()を呼んでいます.

なお,get_object_or_404というショートカットを用いると,これを同じ処理が次のように簡単に書けるようです.

from django.shortcuts import get_object_or_404

...

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    choice_list = question.choice_set.all()
    context = {'question': question, 'choice_list':choice_list}
    return render(request, 'polls/detail.html', context)

対応するtemplate(detail.html)もこれにあわせて次のように更新しておきましょう.

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in choice_list %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

実は,question.choice_set.all()で関連するChoiceインスタンスを抜き出してくるメソッドはtemplateの中で実行することもできます.その場合は,次のように書きます.

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

templateの中でメソッドを呼ぶ場合は()が不要になることに注意しましょう(上の例では,all()allになっています).なお,こちらの方法を用いる場合は,view関数のcontextchoice_listを含める必要はありません.

最後に,results()も同じように更新しておきましょう.

...

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

対応するtemplate(results.html)も以下のように作成しておきます.

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>
    {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
    </li>
{% endfor %}
</ul>
<p>
    <a href="{% url 'polls:detail' question.id %}">Vote again</a> or
    <a href="{% url 'polls:index' %}">go back to top page</a>?
</p>

ここに,forループの中の<li>タグの中で出てくるpluralizeはフィルタの一種であり,choice.votesの値が2以上の場合に名詞を複数形にするためのsを出力してくれます.

おわりに

以上で4回目は終了です.第5回に続きます.

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5