【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その3～

# はじめに

みなさん、初めまして。
Djangoを用いた投票 (poll) アプリケーションの作成過程を備忘録として公開していこうと思います。
Qiita初心者ですので読みにくい部分もあると思いますがご了承ください。

シリーズ

- [【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その0～](https://qiita.com/oboerarenai_user/items/4ff87b2afbad8c16ad40)
- [【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その1～](https://qiita.com/oboerarenai_user/items/4357841493832cd6ca2a)
- [【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その2～](https://qiita.com/oboerarenai_user/items/4a36c2b11e7dee74b66b)

# 作業開始

## オーバービュー

MVCモデルのViewとはユーザーに表示する画面を返すプログラムのことです。

>投票アプリケーションでは、以下4つのビューを作成します:
>
>質問 "インデックス" ページ -- 最新の質問をいくつか表示
>質問 "詳細" ページ -- 結果を表示せず、質問テキストと投票フォームを表示
>質問 "結果" ページ -- 特定の質問の結果を表示
>投票ページ -- 特定の質問の選択を投票として受付

ちなみに、URLからビューを得るDjango機能をURLconfといいます

## もっとビューを書いてみる

以下の動作をURLconfを用いて実装します。

ユーザが「/polls/100/」にアクセス
　→config/urls.pyのpath.'polls/'に一致するためpolls/urls.pyを読み込む
　　→polls/urls.pyのpath.'100/'に一致するためviews.pyのdetail(request=<HttpRequest object>, question_id=100)を返す


```python:polls/views.py
# Create your views here.
from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello,world.You're at the polls index.")


def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)


def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)


def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)
```

question_id=100は<>で挟んでURLの一部からキャプチャします。

```python:polls/urls.py
from django.urls import path
from . import views

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'),
]
```

## 実際に動作するビューを書く

いままで動かないもの作っていたのか・・・
違うみたいです。「実際に動作する」とは「HttpRequest処理と例外処理を実装して、実運用に沿った挙動を実現する」ということみたいです。

viewに以下を追加します。

```python:polls/views.py
from .models import Question
from django.template import loader


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

「template.render(context, request)」でlatest_question_listをpolls/index.htmlに渡し、レンダリングします。
Djangoのloader.get_templateの仕様で、レンダリングテンプレートを「.templates/アプリ名(polls)」ディレクトリ配下から検索するようです。

```html:polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
```

ショートカットすることもできます。
django.shortcutからrenderをインポートします。

```python:polls/views.py
from .models import Question
from django.shortcuts import render


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)
```

このような画面が表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/1184b616-eeaa-afda-2902-011fcadf7d85.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/ca02e05a-a7a5-8825-84a3-acff12caaf89.png)

## 404 エラーの送出

まずはビューを作成します。

```python:polls/views.py
from django.http import HttpResponse, Http404
from .models import Question
from django.shortcuts import render

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

続いてHTML

```html:polls/templates/polls/detail.html
{{question}}
```

動作確認します。

question_id=5は存在するため、「[http://127.0.0.1:8000/polls/5/](http://127.0.0.1:8000/polls/5/)」は以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/1d437b9f-607e-5478-628f-b2a10f224d3b.png)

question_id=999は存在しないため、「[http://127.0.0.1:8000/polls/999/](http://127.0.0.1:8000/polls/999/)」は以下のように表示されます。
Exceptionをキャッチして「Question dose not exist」が表示されてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/4153001d-afa3-d5cd-aa45-e5d6f6a403e6.png)

## テンプレートシステムを使う

detailページのhtmlをマークアップしていきます。

```html:polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
```

再度、「[http://127.0.0.1:8000/polls/5/](http://127.0.0.1:8000/polls/5/)」を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/6bbd74c6-ac54-59ff-a39f-aa6f17c392af.png)

## テンプレート内のハードコードされたURLを削除

URL部分を変数化します。URLの変更時にテンプレートの修正が必要になるためです。

仕組みとしては、polls/urls.py > urlpatterns > pathで name='detail' を指定しましたね。
これをpolls/templates/polls/index.htmlで読み込みます。するとname='detail'に紐づくURLが読み込まれます。

```html:polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
```

```python:polls/urls.py
urlpatterns = [
    ***
    path('<int:question_id>/', views.detail, name='detail'),
    ***
]
```

表示も問題ないです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/55f9569b-7545-7596-ce6a-3fce51910878.png)

## URL 名の名前空間

今回作成したアプリはpollsアプリ1つだけです。
複数アプリを作成した場合、複数アプリがdetailビューを持つ可能性があります。
pollsアプリのdetailビューを取り出すためには、名前空間を作成・指定します。

名前空間の作成は、<アプリ名>/templates/<アプリ名>/index.htmlにapp_name=<アプリ名>を追記します。

```python:polls/urls.py
app_name = 'polls'
urlpatterns = [
    ***
    path('<int:question_id>/', views.detail, name='detail'),
    ***
]
```

名前空間を指定したURL指定は、<アプリ名>:detailとします。

```html:polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
```

本日はここまでにします。ありがとうございました。