첫 번째 Django 앱 만들기! part 4

본 튜토리얼은 튜토리얼 3에 이어서 계속됩니다. 우리는 웹 투표 애플리케이션을 만들고 있는 중이죠. 이 장에서는 코드의 양을 줄이고, 간단한 폼 처리를 하는데 집중할 것입니다.

간단한 폼 사용하기

HTML <form> 요소를 포함하고 있는 앞 장에서 사용한 투표 세부 템플릿을 수정합시다.

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

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

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

간략하게 설명하면,

  • 위의 템플릿은 각 설문 선택지의 라디오버튼을 표시합니다. 각 라디오 버튼의 value는 연결된 투표 선택지의 ID입니다. 각 라디오 버튼의 name"choice"입니다. 이것은, 누군가 라디오버튼중 하나를 선택하고, 폼을 제출하면, 폼은 POST 자료 choice=3를 전송한다는 것을 의미합니다. 이것이 HTML 폼의 기본이죠.
  • 우리는 폼의 action/polls/{{ poll.id }}/vote/로 지정하고, method="post"로 지정했습다. 이 폼을 제출하는 행위가 서버측 자료를 변경할 수 있기 때문에 method="post"를 사용하는 것은 (혹은 반대로 method="get") 매우 중요합니다. 서버측 자료를 변경할 폼을 만들때는, 항상 method="post"을 사용하세요. 이 정보는 Django에 국한된 정보가 아니라, 단지 좋은 웹 개발 관례일 뿐입니다.
  • forloop.counterfor 태그가 반복을 한 횟수를 나타냅니다.
  • 우리는 POST 폼(자료를 수정하는 효과를 가진)을 만들고 있으므로, 사이트 간 요청 위조 (Cross Site Request Forgeries)에 대해 고민해야합니다. 고맙게도, Django는 사이트 간 요청 위조(CSRF)에 대항하기 위한 사용하기 쉬운 시스템을 가지고 있기 때문에, 너무 심각하게 고민할 필요가 없습니다. 간단히 말하면, 내부 URL들을 향하는 모든 POST 폼에 템플릿 태그 {% csrf_token %}을 사용하면 됩니다.

{% csrf_token %} 태그는 템플릿 문법에서 접근이 불가능한 요청(request) 개체로부터의 정보를 필요로 합니다. 이부분을 적용하기 위해, 다음과 같은 작은 조정이 detail 뷰에 필요합니다.

from django.template import RequestContext
# ...
def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/detail.html', {'poll': p},
                               context_instance=RequestContext(request))

이것이 작동하는 것에 대한 자세한 내용은 RequestContext 부분의 문서를 참고하시면 됩니다.

이제 전송된 자료를 다룰 Django 뷰를 만들고, 그것을 가지고 무엇인가를 해봅시다. 튜토리얼 3에서 투표 애플리케이션을 위해 아래에 나와있는 코드를 포함하는 URLconf를 만들었습니다.

(r'^(?P<poll_id>\d+)/vote/$', 'vote'),

또, 우리는 vote()함수를 가상으로 만들었습니다. 실제로 구현을 해봅시다. polls/views.py에 다음을 추가합시다.

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from polls.models import Choice, Poll
# ...
def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # 투표 폼을 다시 보여주기.
        return render_to_response('polls/detail.html', {
            'poll': p,
            'error_message': "You didn't select a choice.",
        }, context_instance=RequestContext(request))
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 전송된 POST 자료를 성공적으로 처리한 뒤에는 항상 HttpResponseRedirect를 반환하라.
        # with POST data. This prevents data from being posted twice if a
        # 자료가 두 번 전송되는 경우를 막아준다.
        return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))

위 코드는 이 튜토리얼에서 아직 다루지 않은 몇 가지를 포함하고 있습니다.

  • 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을 어떻게 구성하는지 다음 항목을 보라).

    위 소스안의 주석내용대로, POST 자료를 성공적으로 처리한 후에는 항상 HttpResponseRedirect를 반환해야 합니다. 이것은 Django에만 국한된 팁이 아닙니다. 좋은 웹 개발관례일 뿐이죠.

  • 우리는 이 예제에서 HttpResponseRedirect 생성자 안의 reverse() 함수를 사용하고 있습니다. 이 함수는 함수에서 URL을 하드코딩하는 것을 피하는 데에 도움이 됩니다. 제어를 전달하기 원하는 뷰의 이름을 제공하고, URL 패턴의 변수부분이 그 뷰를 가리킵니다. 이 경우에, URLconf를 사용하여 우리는 튜토리얼 3에서 설정하였습니다. 아래와 같은 문자열을 반환할 것입니다.

    '/polls/3/results/'
    

    ... 3p.id의 값이다. 이 리다이렉트된 URL은 마지막 페이지를 보여주기 위해 'results' 뷰를 호출합니다. 뷰 이름(풀네임)을 사용해야한다는 것을 주의하세요(접두사를 포함하여).

튜토리얼 3에서 언급했듯이, requestHttpRequest개체입니다. HttpRequest 개체에 대해 더 알고 싶다면 요청 및 응답 문서을 참고하세요.

어떤 이가 투표에 설문을 하고난 뒤에는, vote() 뷰는 투표 결과 페이지로 리다이렉트합니다. 그 뷰를 작성해봅시다.

def results(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/results.html', {'poll': p})

이것은 Tutorial 3detail() 뷰와 거의 동일합니다. 다른 점은 템플릿 이름 밖에 없습니다. 우리는 후에 이 쓸데없는 것들을 수정할 것입니다.(역자주:쓸데없는 것이라고 한 이유는 위 파일이 필요없어지기 때문입니다.)

이제, results.html 템플릿을 만듭시다.

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

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

<a href="/polls/{{ poll.id }}/">Vote again?</a>

이제, 웹 브라우저에서 /polls/1/ 페이지로 가서, 투표를 해보세요. 당신이 투표를 할 때마다 값이 반영된 결과 페이지를 볼 수 있을 것입니다. 만약 당신이 설문지를 선택하지 않고 폼을 전송했다면, 오류 메시지를 보게 될 것입니다.

제너릭 뷰 사용하기: 적은 코드가 더 좋아요

detail() ( 튜토리얼 3에서의) 과 results() 뷰는 매우 비슷했습니다. – 그리고, 위에서 언급한 것 처럼 장황합니다. 설문 목록들을 보여주는 index() 뷰(이것도 튜토리얼 3에서)도 마찬가지입니다.

이 뷰들은 기본적인 웹 개발의 일반적인 케이스들(URL로 전달된 매개변수를 이용해서 데이터베이스에서 자료를 얻고, 템플릿을 불러오며, 처리된 템플릿을 반환하는)을 제공합니다. 이것들이 매우 일반적인 것들이기 때문에, Django는 “제너릭 뷰(generia view)” 시스템이라 불리는 지름길을 제공합니다.

앱을 만들기위해 파이썬 코드를 쓸 필요가 없을 정도로 제너릭 뷰는 일반적인(역자주-중복되고, 장황한) 패턴들을 제거합니다.

제너릭 뷰 시스템을 사용해서 우리의 투표 앱을 바꿔봅시다. 그리고 이미 만든 우리의 코드를 삭제할 수도 있습니다. 제너릭 뷰로 변환하기에는 다음과 같은 몇 단계를 거쳐야 합니다.

  1. URLconf를 변환한다.
  2. 오래되고 불필요한 뷰들 일부를 삭제한다.
  3. 새로운 뷰들을 위해 URL 다루기를 수정한다.

자세한 내용을 읽어보세요.

왜 코드 섞기(code-shuffle)(역자주:코드를 이렇게 저렇게 수정하는 것)인가?

일반적으로, Django 앱을 작성할 때, 당신은 제너릭 뷰가 당신의 문제에 잘 맞는지 평가할 것이고, 절반정도 작성된 당신의 코드를 다시 작성하기보다는 처음부터 제너릭 뷰를 사용할 것이다. 그러나 이 튜토리얼은 지금까지 핵심개념들을 배우기 위해 의도적으로 “힘든 방법으로”, 뷰 작성하기에 초점을 맞춰왔다.

계산기를 사용하기 전에 기본적인 산수 정도는 알아야하지 않겠는가.

먼저, polls/urls.py의 URLconf를 봅시다. 지금까지의 튜토리얼 내용대로라면 아래와 같은 내용이 보일 것입니다.

from django.conf.urls import patterns, include, url

urlpatterns = patterns('polls.views',
    url(r'^$', 'index'),
    url(r'^(?P<poll_id>\d+)/$', 'detail'),
    url(r'^(?P<poll_id>\d+)/results/$', 'results'),
    url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

아래와 같이 변경하세요.

from django.conf.urls import patterns, include, url
from django.views.generic import DetailView, ListView
from polls.models import Poll

urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(
            queryset=Poll.objects.order_by('-pub_date')[:5],
            context_object_name='latest_poll_list',
            template_name='polls/index.html')),
    url(r'^(?P<pk>\d+)/$',
        DetailView.as_view(
            model=Poll,
            template_name='polls/detail.html')),
    url(r'^(?P<pk>\d+)/results/$',
        DetailView.as_view(
            model=Poll,
            template_name='polls/results.html'),
        name='poll_results'),
    url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
)

여기에서 제너릭 뷰 두 개, 즉 ListViewDetailView를 사용하고 있습니다. 이 두 개의 뷰들은 각각, “개체들의 목록을 보여주기” 와 “개체의 특정한 형식에 맞춘 세부 페이지 보여주기” 란 개념을 가지고 있습니다.

  • 각 제너릭 뷰는 제너릭 뷰가 어떤 모델에 작용할지 알아야합니다. 그것은 model 파라미터를 사용하여 제공합니다.
  • DetailView 제너릭 뷰는 URL에서 가져온 기본 키 값("pk"라 불리는)을 받을 준비를 하고 있습니다. 그래서 제너릭 뷰를 위해 우리는 poll_idpk로 변경했습니다.
  • 우리는 results 뷰에 poll_results란 이름을 추가했습니다, 그렇게 하면 URL 이후에 오는 변수를 참조할 수 있게 되죠.( 이것에 관한 정보는 URL 패턴 명명 문서를 참고하세요). 또 django.conf.urlsurl() 함수를 사용했습니다. 이와같은 패턴 이름을 제공하는 경우에 url()사용은 좋은 습관입니다.

기본적으로 DetailView 제너릭 뷰는 <app name>/<model name>_detail.html이라 불리는 템플릿을 사용합니다. 우리는 "polls/poll_detail.html" 템플릿을 사용할 것입니다. 자동생성되는 기본 템플릿 이름 대신에 특정한 템플릿 이름을 사용하여 Django에게 전달하기 위해서는 template_name 를 사용해야 합니다. 그래서 results 리스트 뷰를 위해 template_name를 지정했습니다 – 비록 둘 다 같은 DetailView이긴 하지만. 이렇게 하면 results 뷰와 detail 뷰를 다르게 표현할 수 있죠.

비슷하게, 기존 "polls/index.html" 템플릿을 사용하기 위해 template_name을 사용해서 ListView에 전달합니다.

이전 튜토리얼에서, 템플릿에는 polllatest_poll_list의 컨텍스트 변수를 포함한 컨텍스트가 제공되었습니다. DetailView를 위한 poll 변수는 자동으로 제공됩니다 – Django 모델(Poll)을 사용하고 있기 때문에, Django는 컨텍스트 변수에 대한 적절한 이름을 확인할 수 있습니다. 그러나, ListView를 위해, 자동적으로 생성되는 컨텍스트 변수는 poll_list입니다. poll_list 대신 사용하고 싶은 latest_poll_listcontext_object_name옵션에 지정하시면 됩니다. 다른 방법으로, 새로운 기본 컨텍스트 변수들과 매치되는 당신만으로 템플릿으로 바꿀 수 있습니다. – 그러나 Django에게 당신이 원하는 변수를 전달하는 것이 훨씬 쉽습니다.(역자주:context_object_name를 지정하는 것이 훨씬 쉽다는 말입니다.)

이제 polls/views.py파일에서 index(), detail() 그리고 results() 뷰들을 지울 수 있습니다. 그 뷰들은 더이상 필요하지 않습니다.– 그 뷰들이 제너릭 뷰로 대체된 것입니다.

마지막으로 해야 할 것은 제너릭 뷰들의 사용을 처리하는 URL 다루기를 수정하는 것입니다. 위의 vote 뷰에서, 우리의 URL들을 하드코딩하는 것을 피하기 위해 reverse() 함수를 사용했습니다. 이제 우린 제너릭 뷰로 전환을 했으니. reverse() 함수가 새로운 제너릭 뷰를 호출하도록 바꿔야합니다. 더이상 우리는 단순히 view 함수를 사용할 수 없습니다 – 제너릭 뷰들은 여러번 사용하거나, 사용되어질 수 있다 –그러나 우리는 우리에게 주어진 이름을 사용할 수 있다(역자주: 뷰를 삭제했으므로 사용할 수 없다는 말이고, 대신에 name을 지정하여 제너릭 뷰를 사용할 수 있다는 뜻).

return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

서버를 실행하고, 제너릭 뷰를 기반으로 하는 새로운 투표 앱을 사용해봅시다.

제너릭 뷰에 대한 자세한 내용은, 제너릭 뷰 문서를 참조하세요.

곧 올것들

당분간 이 튜토리얼은 여기에서 끝납니다. 이 튜토리얼이 앞으로 다룰 내용들입니다.

  • 진보된 폼 처리
  • RSS 프레임워크 사용하기
  • 캐시 프레임워크 사용하기
  • 댓글 프레임워크 사용하기
  • 진보된 관리자 기능: 권한
  • 진보된 관리자 기능: 사용자 정의 자바스크립트

그 동안, where to go from here에서 몇몇 부분들을 살펴볼 수 있습니다.