Django チュートリアルを進めていく4 (docker-composeを使って)

どうもこんにちは、ごっちです。

前回の続きです。

今回も公式のチュートリアルに則って進めます。

投票ページ

前回、投票ページを作っていなかったので、今回はそこからスタートです。

polls/detail.html を編集します。

<h1>{{ question.question_text }}</h1>

{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post" >
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>

次にポストされたものがきちんと処理できるように、 polls/views.py を編集します。

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse

from .models import Choice, 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 didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

vote で何をやってんのじゃほい

  1. 該当の question を引っ張り出します。
  2. question に紐づく choice のなかで、ポストされた choice を引っ張り出して、 selected_choice に代入します。
  3. selected_choice の votes を1増やして、保存します。
  4. リダイレクトします。

うんうん。シンプルな挙動ですね。

結果の表示

これだけあっても、結果の表示ページがまだ作り切れてないので、作ります。

polls/views.py を編集します。

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

polls/results.html も編集していきます。

<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.cotes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote Again</a>

これでいい具合に回答できて、保存もできるようになったかと思います。

結構さまになってきました、このアプリ。いいと思います。

汎用化

全体的にコードを見てみると、冗長になっているところがちらほらあります。

リファクタリングしていきましょう。

これからやることとして、

  • URLの設定をする。
  • 不要なビューを削除する。
  • 新しいビューにDjangoの汎用できるビューを作る

URLの再設定

polls/urls.py を編集します。

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'), # ここを編集
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), # ここを編集
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), # ここを編集
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

viewsの修正

index, detail, results を一度削除して、代わりにDjangoの汎用ビューを使用します。

ということで、 polls/views.py を編集します。

from django.shortcuts import get_object_or_404, render
from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.template import loader
from django.views import generic
from django.urls import reverse

from .models import Question, Choice

class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'

def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]

class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'

class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'

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 didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id, )))

急にclassになったんだけど、なにやってんじゃほい。

ListView, DetailView

これらは、「オブジェクトをリストで表示します!」および「とあるタイプのオブジェクトの詳細ページを表示します!」っていう便利なものらしいです。

DetailView汎用ビューは pk という名前でURLから primary_key を渡すルールになっているので、URLの設定を書き換えてます。

デフォルトでは、 DetailView は <app_name>/<model_name>_detail.html という名前のテンプレート使います。 ListView は <app_name>/<model_name>_list.html という名前のテンプレートを使います。

今回はクラスが宣言された時点で template_name を指定しているので、そのテンプレートをつかうことになっています。

初期データ

こう勉強してて、 Docker のコンテナをいちいち削除しているんですけど、コンテナを削除するたびにDB情報もリセットされるので、初期データを作っておきたいなと。

コマンド一発でエクスポートできて、インポートできるのでDjangoも便利ですね。

# Migrate
$ docker-compose run web python manage.py migrate

# エクスポート
$ docker-compose run web python manage.py dumpdata --all -o dump.json
[......]

# インポート
$ docker-compose run web python manage.py loaddata dump.json

これで、いったんはいちいち shell を立ち上げなくてすみますね。

Dockerfile に組み込んで自動でやりたいところ。。。。

今日はここまで

今日はここまで

References


Originally published at gist.github.com.