앞장의 투표 상세 템플릿을 수정하여, 템플릿에 HTML form 요소를 포함시켜 봅시다.
# polls/templates/polls/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">
</form>
간략하게 설명하면
위의 템플릿은 각 질문 선택 항목에 라디오 버튼을 표시합니다. 각 라디오 버튼의 value는 연관된 질문 선택 항목의 ID입니다. 각 라디오 버튼의 name은 "choice"입니다.
폼 작업을 {% url 'polls:vote' question.id %}로 설정하고 metho="post"로 설정했습니다. method="post"를 사용하는 것은 매우 중요합니다. 이 양식을 제출하면 데이터 서버 쪽이 변경되기 때문입니다.데이터 서버 측을 변경하는 양식을 작성할 때마다 method="post"를 사용하십시오. 이 팁은 장고에만 국한된 것이 아닙니다.
forloop.counter는 for 태그가 반복을 한 횟수를 나타냅니다.
POST 양식을 만들고 있기 때문에, 사이트 간 위조 요청에 대해 걱정해야 합니다. 다행히도, 여러분은 너무 걱정하지 안하도 됩니다. 왜냐하면 장고는 그것으로부터 보호하는 유용한 시스템을 가지고 있기 때문입니다. 즉, 내부 URL을 대상으로 하는 모든 POST 양식은 {% csrf_token %} 템플릿 태그를 사용해야 합니다.
이제 젳출된 데이터를 처리하고 그 데이터로 무언가를 수행하는 Django 뷰를 작성하겠습니다. 튜토리얼3에서 설문조사 어플리케이션을 위해 아래에 나와있는 코드를 포함하는 URLconf를 만들었습니다.
# polls/urls.py
path('<int:question_id>/vote/', views.vote, name='vote'),
또, 우리는 vote() 함수를 가상으로 만들었습니다. 실제로 구현을 해봅시다. polls/views.py에 다음을 추가합시다.
# polls/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,)))
위 코드는 이 튜토리얼에서 아직 다루지 않은 몇 가지를 포함하고 있습니다.
request.POST는 키로 전송된 자료에 접근할 수 있도록 해주는 사전과 같은 객체입니다. 이 경우, request.POST['choide']는 선택된 설문의 ID를 문자열로 반환합니다. request.POST의 값은 항상 문자열입니다.
Django는 같은 방법으로 GET 자룡에 접근하기 위해 request.GET을 제공합니다. 그러나 POST 요청을 통해서만 자료가 수정되게 하기 위해서, 명시적으로 코드에 request.,POST를 사용하고 있습니다.
만약 POST 자료에 choice가 없으면 KeyError가 일어납니다. 위의 코드는 KeyError를 체크하고, choice가 주어지지 않은 경우에는 에러 메시지와 함께 설문조사 폼을 다시 보여줍니다.
설문지의 수가 증가한 이후에, 코드는 일반 HttpResponse가 아닌 HttpResponseRedirect를 반환하고, HttpResponseRedirect는 하나의 인수를 받습니다. 그 인수는 사용자가 재전송될 URL입니다.
POST 데이터를 성공적으로 처리한 후에는 항상 HttpResponseRedirect를 반환해야 합니다. 이 팁은 장고에만 국한된 것이 아닙니다.
우리는 이 예제에서 HttpResponseRedirect 생성자 안에서 reverse() 함수를 사용하고 있습니다. 이 함수는 뷰 함수에서 URL을 하드코딩 하지 않도록 도와줍니다. 제어를 전달하기 원하는 뷰의 이름을, URL 패턴의 변수 부분을 조합해서 해당 뷰를 가리킵니다. 여기서 우리는 튜토리얼 3장에서 설정했던 URLconf를 상용하였으며, 이 reverse() 호출은 아래와 같은 문자열을 반호나할 것입니다.
'/polls/3/results/'
여기서 3은 question.id 값입니다. 이렇게 리디렉션된 URL은 최종 페이지를 표시하기 위해 results 뷰를 호출합니다.
튜토리얼 3장에서 언급했듯이, request는 HttpRequest 개체입니다. HttpRequest 개체에 대해 더 알고 싶다면 request와 response 문서를 참고하세요.
어떤 이가 설문조사에 설문을 하고 난 뒤, vote() 뷰는 설문조사 결과 페이지로 리다이렉트합니다.
# polls/views.py
from django.shortcuts import get_object_or_404, render
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 템플릿을 만듭니다.
# polls/templates/polls/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>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
이제, 웹 브라우저에서 /polls/1/ 페이지로 가서, 투표를 해보세요. 당신이 투표를 하 때마다 값이 반영된 결과 페이지를 볼 수 있을 것입니다. 만약 당신이 설문지를 선택하지 않고 폼을 전송했다면, 오류 메시지를 보게 될 것입니다.
튜토리얼3에서 detail()과 result() 뷰는 매우 짧으며, 위에서 언급한 것처럼 중복됩니다. index()뷰도 유사합니다.
이러한 뷰는 URL에 전달된 매개변수에 따라 데이터베이스에서 데이터를 가져오고, 템플릿ㅇ을 로드하고, 렌더링된 템플릿을 반환하는 기본 웹 개발의 일반적인 경우를 나타냅니다. 이런 경우는 흔하기 때문에, 장고는 generic views 라고 불리는 지름길을 제공합니다.
코드를 바꾸도록 합시다. 바꾸러면 몇 가지 단계를 밟아야 합니다. 다음 작업을 수행합니다.
먼저, polls/urls.py을 열어 다음과 같이 변경하십시오.
# 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로 변경되었습니다.
다음으로 이전의 index, detail, results 뷰를 제거하고 장고의 일반적인 뷰를 대신 사용하겠습니다. 그렇게하려면 polls/views.py 파일을 열고 다음과 같이 변경하십시오.
# polls/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.
ListView와 DetailView의 두 가지 제너릭 뷰를 사용하고 있습니다. 이 두 뷰는 각각 개체 목록 표시 및 특정 개체 유형에 대한 세부 정보 페이지 표시 개념을 추상화합니다.
기본적으로 DetailView 제너릭 뷰는 appname/modelname_detail.html 템플릿을 사용합니다. 우리의 경우에는 polls/question_detail.html 템플릿을 사용할 것입니다. template_name 속성은 Django에게 자동 생성 된 기본 템플릿 이름 대신에 특정 템플릿 이름을 사용하도록 알려주기 위해 사용됩니다.
제너릭 뷰에 대한 자세한 내용은 제너릭 뷰 문서를 참조하십시오.