Jello's development blog

Jello's development blog

Django 튜토리얼 따라하기 4

템플릿에 form 태그 추가

polls/templates/polls/detail.html파일을 수정하여 detail 템플릿에 form 태그를 추가해보자.

polls/templates/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>

여기서 주목해야 할 것은 form태그이다. form태그의 action에는 저번 포스트에서 설명한 url태그가 들어가있고, CSRF(Cross-site Request Forgery)를 방지하기 위한 태그가 들어가있다. 이 태그는 위조 방지 토큰을 가지는 Input태그를 생성하여 취약점을 보완해준다. 그 밑의 for문에 있는 question.choice_set.all은 해당 Question모델을 foreign key로 참조하는 모든 Choice 모델을 가져온다. forloop.counter는 for문이 얼마나 돌았는지 1부터 시작하여 1씩 증가하여 반환한다.

vote view 생성

위에서 생성한 form 태그가 vote 로 향하는 url을 가르키고 있으니, view를 업데이트 해줘야한다.

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):
    # 모델을 받아오고, 존재하지 않으면 404페이지를 띄운다.
    question = get_object_or_404(Question, pk=question_id)
    try:
        # post로 넘어온 choice 값으로 선택된 choice를 얻는다.
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # 예외가 발생했을 시에 question모델과 에러 메시지를 템플릿에 전달한다.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        # choice를 잘 얻어왔다면 투표 수를 1 증가시키고, 결과창으로 이동시킨다.
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

vote 함수를 보면, Question모델을 받아온 뒤에(없으면 404 페이지), 선택된 Choice모델을 얻고, 예외가 발생했을 시에 에러 메시지를 뿌려준다. 만약 Choice모델을 얻어오는데에 성공했다면 투표 수를 1 증가시키고, results라는 결과창으로 이동시킨다.

reverse('polls:results', args=(question.id)는 이전에 url세팅을 해준대로 results라는 name을 가지고 있는 url에 arg를 전달하여 나온 url을 반환한다. 따라서 /polls/3/results/를 반환할 것이고, HttpResponseRedirect는 이 주소대로 redirect시켜준다.

results view 생성

위에서 vote view가 results로 redirect되게 만들었으니, 이제는 results view를 업데이트해보도록 하자. 간단하게 템플릿만 띄워줄 것이다.

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

이 코드는 이제 이해가 될 것이라고 생각하고 설명을 생략하도록 하겠다. results 템플릿을 띄워주도록 만들었으니 또 다시 만들어야 할 파일이 생겼다.

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>

pluralize 필터는 말 그대로 복수형으로 만들어주는 기능을 한다. 만약 choice_votes가 2 이상이라면, 앞의 vote 문자열에 s를 붙여주어 복수로 되게 한다. 이에 대한 자세한 설명은 Django Docs를 참고하자.

Admin 설정, 마무리

이제 서버를 실행한 후에 admin(http://localhost:8000/admin)으로 들어가자.

어드민 Poll 패널

Add버튼을 눌러서 적절한 투표와 선택지를 추가해보자. 먼저 투표를 추가하기 위해서 Question을 추가한다.

Question 추가

예시대로 입력 후에 저장 버튼을 누른다. 다음은 선택지를 추가하기 위해서 Choice를 추가한다.

Choice 추가

아까 만들었던 Question을 선택 후에, 같은 방식으로 첫째 주를 입력하고 저장한다. 같은 방식으로 둘째 주, 셋째 주까지 저장한다. 이제 우리가 만든 polls(http://localhost:8000/polls)로 들어가보자.

index언제 놀러갈까?라는 질문이 등록되어 있으며, 이를 클릭하여 detail으로 들어가면 아까 만들었던 세 개의 선택지가 보일 것이다.

detail

10월 둘째 주를 선택하고 vote 버튼을 눌러본다.

results

vote view의 내용대로 detail로 redirect되고, 투표 수가 1 증가한 것을 볼 수 있다.

마치며

튜토리얼을 따라하면서, 먼저 튜토리얼조차도 쉽고 간단하지 못하다는 것을 느꼈고, Django는 엄청나게 방대한 웹 프레임워크라는 것을 느꼈다. 모델, 뷰, 템플릿을 이용한 백엔드 프로세스부터 프론트엔드까지 모든 기능이 세세하게 갖추어져 있었다. 수많은 shortcut들과 템플릿 태그 등으로 빈틈없고 완성도 높은 서비스를 편리하게 만들 수 있을 것 같다. 편리하고 강력한 기능이 많지만, 새로운 기능이 필요할 때마다 찾아서 그 기능을 숙지한 뒤에 활용하기까지는 시간이 꽤 걸릴 것 같다. Django를 능숙하게 다룰 줄 안다면, 믿음직한 맥가이버 칼을 얻었다고 생각해도 될 것 같다.