Tutorials


4. Forms and generic view

  • 4.1 Write a simple form
  • 4.2 Use generic views: Less code is better
    • Amend URL conf
    • Amend views


원본문서


튜토리얼(4)는 튜토리얼(3)view & template 이 마무리 된 상태에서 시작합니다. 튜토리얼(4)는 웹사이트에 투표기능(앱)을 추가하는 작업을 이어하면서, 심플한 Form 프로세싱과 코드를 다듬는데 집중하겠습니다.

4.1 Write a simple form

(=2.6.1 write a simple form = intro/tutorial04)

튜토리얼(3)에서 만든 poll detail template(polls/detil.html)에 HTML의 form요소를 더해 보겠습니다.
_참고) HTML의 form 요소는 사용자가 입력한 값을 모으고 형태를 정의하는데 사용됩니다. text fields, checkboxes, radio buttons 등이 있습니다.


*__위치 : 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>

개요를 빠르게 훑어보겠습니다.

  • 위의 템플릿예제는 각각의 question choice에 라디오 버튼 하나씩을 배치했습니다. 각 라디오 버튼의 값은 question choice의 ID에 연결되어 있습니다. 이 개별 라디오 버튼의 이름은 "choice" 입니다. 이것은 누군가 라디오 버튼 중 하나를 선택했을 때, 그리고 양식(form)을 제출했을 때, 이것이 POST 데이터인 choice=#을 선택된 choice의 ID중 #이 있는 곳으로 보낸다는 뜻입니다. 이것은 HTML 양식(forms)의 기본 개념입니다.
  • 우리는 양식의 작동방식(action)을 이부분 계속 오류가 나서 잠시 생략합니다- 장고 템플릿 {중괄호와 %안에 url 'polls:vote' question.id 가 들어있는 부분입니다.} 과 method="post" 에 설정합니다. method="post"를 사용하는 것은 (method="get"과 반대로) 매우 중요합니다. 왜냐하면 이 양식을 제출하는 행위가 데이터 서버측을 변경하기 때문입니다. 우리가 데이터 서버 측을 변경하는 양식(form)을 만들든 만들지 않든간에 method="post"를 사용하십시오. 이 참고사항은 장고에 한정되지 않습니다. 이것은 웹 개발활동 자체에 좋습니다.
  • forloop.counter는 for 태그가 스스로의 루프를 몇 번이나 돌았는지 알려줍니다.
  • 우리가 POST form을 만들었기 떄문에(이 POST form은 데이터 수정에 영향을 미칩니다.) 우리는 Cross Site Request Forgeries를 주의해야 합니다. 고맙게도, 우리는 너무 심한 걱정을 할 필요는 없는데요, 장고가 뭑 쉬운 방어 시스템을 제공하기 때문입니다. 간단하게, 내부 URL들의 타겟이 되는 모든 POST form들에 {여기도 오류가 나네요 이걸 어떻게 해결한담 csrf_token %} 템플릿 태그를 추가하면 됩니다.

이제 장고 뷰를 만들어 보겠습니다. 이 장고 뷰는 제출된 데이터를 다루고, 그것을 가지고 뭔가를 할 수 있습니다. 튜토리얼(3)을 기억하세요. 우리는 polls앱을 위해 아래 내용을 포함한 URLconf를 만들었습니다.

# 작성위치 : polls/urls.py
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

그리고 우리는 vote( )함수를 임시적으로 자리만 잡아두었었습니다. 이제 vote( )를 진짜로 작성해봅시다. 아래 내용을 polls/views.py에 작성해주세요.

# 작성위치 : polls/views.py

from django.shortcuts ipmort 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):
            # 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['choice']는 선택된 choice의 ID를 문자열(string)로 돌려줍니다. request.POST 값은 언제나 문자열(string)입니다.
    장고가 request.GET역시 제공한다는 것을 주의합니다. 같은 방식으로 GET 데이터에 접근하기 위해- 하지만 우리는 반드시 request.POST를 우리의 코드에 사용해야 합니다. 데이터가 POST 호출을 통해서만 변경된다는 것을 확실히 하십시오.
  • request.POST['choice']는 keyerror를 일으킵니다. 만약 choice가 POST데이터를 제공하지 않는다면 말입니다. 위의 코드는 KeyError를 체크하고 question form(질문양식)을 오류 메시지와 함께 되돌려보냅니다. choice가 주어지지 않았다면 말입니다.
  • choice의 카운트가 증가하면, 이 코드는 일반적인 HttpResponse보다 HttpResponseRedirect 하나를 돌려보냅니다. HttpResponseRedirect는 하나의 인수를 가집니다. 이 인수는 사용자가 redirected 될 수 있는 URL입니다.(바로 다음 부분이 우리가 이 경우 어떻게 URL을 구조화하는지에 대한 내용입니다.)
    이와 같은 파이썬 주석과 같이, 우리는 POST데이터를 성공적으로 처리한 후 항상 하나의 HttpResponseRedirect를 돌려보냅니다. 이 참고사항은 장고에서 특별한 내용은 아닙니다. 웹 개발 과정 전체에 기본적으로 적용되어야 하는 내용입니다.
  • 우리는 이 예시에서 reverse( )함수를 HttpResponseRedirect 건설자 안에 사용합니다. 이 기능은 뷰 함수에서 하드코딩된 URL을 가지지 않도록 도와줍니다. 이것은 뷰의 이름을 지정받습니다. 우리가 제어를 전달하려는 뷰의 이름과 그 뷰를 가리키는 URL패턴의 변수 부분이 그것입니다. 이 경우, 튜토리얼(3)에서 설정했던 URL관리자(URLconf)를 사용합니다. 이 reverse()는 아래와 같은 문자열을 호출합니다.
    '/polls/3/results/'
    3이 있는 위치는 question.id의 값입니다. 이 redireced URL은 이제 파이널 페이지를 보여주기 위한 'results'뷰라고 부를 수 있습니다.

튜토리얼(3)에서 언급한 것과 같이 request는 하나의 HttpRequest 객체입니다. 더 많은 HttpRequest 객체를 위해, request and response ducumentation을 참고하십시오.

누군가의 투표가 진행된 다음, vote( )뷰는 결과 페이지로 redirect 됩니다. 질문을 위해서. 이 뷰를 작성해보겠습니다.

# 작성위치 : 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})

위 예는 튜토리얼(3)에서 만든 detail( )뷰와 거의 똑같아 보입니다. 단 하나 다른 점은 템플릿의 이름입니다. 우리는 이 여분을 나중에 고정하겠습니다.

이제 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/로 가서 질문에 투표를 해봅시다. 우리가 투표를 할 때마다 결과 페이지를 볼 수 있습니다. 만약 우리가 선택한 choice가 없을 경우에 대한 form도 등록한다면, 우리는 에러 메시지를 볼 수 있습니다.

우리의 vote()코드에는 작은 문제가 있습니다. 이 코드가 처음으로 selected_choice 개체를 데이터베이스에서 받은 다음, votes의 새로운 값을 계산하고 그것을 다시 데이터베이스로 돌려보낼 때 일어나는 문제입니다. 만약 두명의 사용자가 우리의 웹사이트에서 정확히 같은 시간에 투표를 시도한다면, 문제가 발생합니다. : The same value, let’s say 42, will be retrieved for votes. Then, for both users the new value of 43 is computed and saved, but 44 would be the expected value.

이러한 문제를 'race condition'이라고 부릅니다. 만약 더 많은 내용을 알고 싶다면 Avoiding race conditions using F()참고하세요.



4.2 Use generic views: Less code is better

detail( )results( )함수는 매우 간단했습니다. 그리고 위에서 언급한 바와 같이 중복됩니다. index()함수는 polls의 목록을 보여주며, 역시 간단하고 중복됩니다(??)

이 뷰들은 기본적인 웹 개발의 일반적인 케이스를 보여줍니다. URL을 통해 전달된 파라미터(매개변수)를 통해 데이터베이스에서 데이터를 가져오고, 템플릿을 적용하여 돌려보냅니다. 이 과정이 매우 일반적이기 때문에 장고는 'generic views'라고 불리는 시스템을 제공합니다.

Generic veiws는 우리가 직접 파이썬 코드를 앱에 작성할 필요가 없다는 점에서 추상화된 일반 패턴(abstract common patterns)입니다.

우리의 poll 앱을 generic views 시스템을 이용해 변환시켜 봅시다. 이를 통해 코드의 상당부분을 도려낼 수 있습니다. 우리는 코드를 전환하기 위해 다음과 같은 과정을 거쳐야 합니다.

  1. URlconf를 수정한다.
  2. 오래되고 필요없는 뷰들을 지운다.
  3. 장고의 generic 뷰에 기반한 새로운 뷰를 만든다.

더 자세한 내용을 알고 싶으면 계속 읽어나가십시오.

Why the code-shuffle?
일반적으로, 장고 앱을 작성할 때, 우리는 generic view가 우리의 문제를 해결하는 데 유용하리라고 생각할 가능성이 높습니다. 그리고 시작부터 generic view를 사용하겠지요. 중간즈음 코드를 리팩토링 하기보다 말입니다. 하지만 이 튜토리얼에서는 지금까지 의도적으로 '어려운 방법'을 사용하여 뷰를 만들어왔습니다. 핵심 개념에 초점을 맞추기 위해서였습니다.
계산기를 사용하기 전 기본적인 계산 개념을 알아야 하는 것과 마찬가지입니다.


Amend URLconf
첫번째로, polls/urls.py 를 열어 다음과 같이 수정합니다.

# 작성위치 : 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'),
]

두번째와 세번째 패턴의 연결된 패턴 이름이 <question_id>에서 <pk> 로 바뀐 점에 주목하십시오.


Amend views
그 다음으로, 예전에 작성했던 index, detail, results 뷰들을 지우겠습니다. 그리고 그 대신 장고의 generic view를 사용해보겠습니다. polls/views.py를 열고 아래와 같이 수정해줍니다.

# 작성위치 : polls/views.py

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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 DatailView(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.

우리는 위 예시에서 두개의 generic view를 사용했습니다. ListViewDetailView 입니다. 이 generic view들은 각각 '개체의 리스트를 표현하다'와 '개체의 특정 부분의 상세한 페이지를 표현한다'는 추상적 개념을 가지고 있습니다.

  • 각 generic view는 이전에 어떤 모델이 동작하고 있었는지를 알아야 합니다. 이 부분은 모델의 속성 부분에서 제공받게 됩니다.
  • DetailView 라는 generic view는 URL에서 "pk"라고 언급된 부분을 캡쳐해서 primary key 값으로 받아야 합니다. 이를 위해서 question_id 라고 써두었던 것을 Pk로 수정하였습니다.

일반적으로, DetailView 라는 generic view는 <app name>/<model name>_detail.html이라고 불리는 템플릿을 사용합니다. 우리의 경우, DetailView는 "polls/question_detail.html"라는 템플릿이 되어야 했을 것입니다. template_name이라는 속성은 장고에게 특정 템플릿 이름을 사용하라고 알려줍니다. 자동으로 생성된 기본 템플릿이 아닌 다른 것을 말입니다. 우리는 또한 template_name을 results list view를 위해 특정화해야 합니다. 이 뷰는 results 뷰와 detail view가 렌더되었을 때 서로 다른 모습을 가진다는 것을 확실하게 해줍니다. 심지어 이 두 뷰 뒤에 DetailView가 숨어있더라도 말입니다.

튜토리얼의 앞선 부분에서, 템플릿들은 qustion과 latest_question_list context변수를 포함한 context와 함께 제공되었습니다. DetailView를 위해서는 question 변수가 자동적으로 제공되어야 합니다. 왜냐하면 우리가 장고의 Question모델을 사용하기 때문입니다. 장고는 context 변수를 위한 적절한 이름을 결정할 수 있습니다. 하지만 ListView를 위해서는, question_list 가 자동생성된 context 변수입니다. 이것을 덮어쓰기 위해, 우리는 context_object_name 속성을 사용합니다. 이 속성은 latest_question_list 대신 특정화하는 것입니다. 다른 방법으로, 우리는 우리의 템플릿을 새로운 기본 context변수에 맞게 수정할 수도 있습니다. 하지만 장고에게 우리가 원하는 변수를 사용하라고 말하는 편이 훨씬 쉽습니다.

서버를 실행하고, generic view가 바탕이 된 새로운 투표 앱을 사용해 보십시오.

generic view의 상세한 내용은 generic views documentation 에서 볼 수 있습니다.

form과 generic view에 익숙해 졌다면, 튜토리얼 5번으로 넘어가서 polls 앱을 검사하는 법에 대해 배우세요.

results matching ""

    No results matching ""