클래스 기반 view(제너릭 뷰)로 구현하면 함수 기반 view와 기능은 같으나 소스코드가 줄어드는 효과가 있다. detail.html을 수정하여 form요소를 포함시켜 보자.
polls/templates/pools/detail.html
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %} <!--보안 관련 토큰-->
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% 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 %}
</fieldset>
<input type="submit" value="Vote"> <!--submit을 누르게 되면 url(polls:vote)을 호출하며, value값이 넘어가게 된다-->
</form>
제출된 data를 처리하고 무언가를 수행하는 view를 만들 것이다. 그 전에 URLconf를 만들었다.
polls/urls.py
path("<int:question_id>/vote/", views.vote, name="vote"),
views.py에 가상으로 만들었던 vote함수를 구현해 보도록 하자.
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
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):
# Redisplay the question voting form.
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
코드의 설명은 Django의 공식문서를 참고하여 천천히 이해해 보도록 하자.
다음과 같이 detail.html이 바뀐 것을 확인할 수 있다.
- request.POST 는 키로 전송된 자료에 접근할 수 있도록 해주는 사전과 같은 객체입니다. 이 경우, request.POST['choice'] 는 선택된 설문의 ID를 문자열로 반환합니다. request.POST 의 값은 항상 문자열들입니다.
- Django는 같은 방법으로 GET 자료에 접근하기 위해 request.GET 를 제공합니다 – 그러나 POST 요청을 통해서만 자료가 수정되게하기 위해서, 명시적으로 코드에 request.POST 를 사용하고 있습니다.
- 만약 POST 자료에 choice 가 없으면, request.POST['choice'] 는 KeyError 가 일어납니다. 위의 코드는 KeyError 를 체크하고, choice가 주어지지 않은 경우에는 에러 메시지와 함께 설문조사 폼을 다시보여줍니다.
- 설문지의 수가 증가한 이후에, 코드는 일반 HttpResponse 가 아닌 HttpResponseRedirect 를 반환하고, HttpResponseRedirect 는 하나의 인수를 받습니다: 그 인수는 사용자가 재전송될 URL 입니다. (이 경우에 우리가 URL을 어떻게 구성하는지 다음 항목을 보세요).
- 위의 Python 주석에서 지적한 바와 같이 POST 데이터를 성공적으로 처리 한 후에는 항상 HttpResponseRedirect 를 반환해야 합니다. 이 팁은 Django에만 국한된 것이 아니라 일반적으로 좋은 웹 개발 관행입니다.
- 우리는 이 예제에서 HttpResponseRedirect 생성자 안에서 reverse() 함수를 사용하고 있습니다. 이 함수는 뷰 함수에서 URL을 하드코딩하지 않도록 도와줍니다. 제어를 전달하기 원하는 뷰의 이름을, URL패턴의 변수부분을 조합해서 해당 뷰를 가리킵니다. 여기서 우리는 튜토리얼 3장에서 설정했던 URLconf를 사용하였으며, 이 reverse() 호출은 아래와 같은 문자열을 반환할 것입니다.
/polls/3/results/
detail.html에서 선택된 choice를 url(polls:votes)로 보내고, 해당 url은 polls/view.py의 vote함수로 보낸다. 다음 해당 함수는 result url로 결과를 보내고, result url은 views.py의 result함수로 들어간다.
그러면 result함수를 만들어보자.
polls/view.py
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
polls/templates/polls/results.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}.
<!--단수이면 vote를 복수이면 votes를 띄우도록 하는 pluralize기능-->
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
투표의 결과를 보여주는 html코드이다.
위 페이지에서 vote를 누르게 되면 이렇게 투표의 결과를 보여주는 result.html을 띄워주는 것을 확인할 수 있다.URLconf를 다음과 같이 수정한다.
polls/urls.py
from django.urls import path
from . import views
app_name = "polls"
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("<int:pk>/", views.DetailView.as_view(), name="detail"),
path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
]
두 번째와 세 번째 패턴의 경로 문자열에서 일치하는 패턴들의 이름이 <question_id>
에서 <pk>
로 변경되었다.
views.py또한 수정한다.
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Return the last five published questions."""
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):
... # same as above, no changes needed.
다음과 같이 전체적인 코드가 줄어든 것을 확인할 수 있다.