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

# はじめに

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

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

全体のコードはまとめて[GitHub](https://github.com/j54854/introDjango)に置きました．

## 変更履歴

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

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

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

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

```python
questions = Question.objects.all()
```

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

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

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

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

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

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

```python
question = Question.objects.get(条件)
```

とすることもできます（この場合は，クエリセットではなく1つのインスタンスが得られます）．

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

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

クエリセットを取得する方法の詳しい説明は[ここ](https://docs.djangoproject.com/en/3.2/topics/db/queries/)を参照してください．

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

```python
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）も忘れずに作成しておきましょう．

```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エラーページを表示させています．

```python
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`というショートカットを用いると，これを同じ処理が次のように簡単に書けるようです．

```python
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）もこれにあわせて次のように更新しておきましょう．

```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の中で実行することもできます．その場合は，次のように書きます．

```html
<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()`も同じように更新しておきましょう．

```python
...

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）も以下のように作成しておきます．

```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回](https://qiita.com/j54854/items/d4fff4f71e7d9a0d136a)に続きます．