はじめに
このドキュメントは,もともとはラボの新メンバー向けの入門テキストとして作成したもので,はじめて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_key
にid
を使用している場合は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関数のcontext
にchoice_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回に続きます.