[장고 튜토리얼 따라하기] 첫 번째 장고 앱 작성하기, part4

작성 중...

장고 공식 문서

간단한 폼 쓰기

앞장의 투표 상세 템플릿을 수정하여, 템플릿에 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>

폼의 action속성의 값을 {% url 'polls:vote' question.id %}로 하였는데, action 속성값으로 지정한 url로 요청을 method 속성값의 형식으로 보내겠다는 의미이다.
{% url %} 템플릿 태그는 어제 공부하였다.
method 속성값은, 대부분 POST와 GET이 있다.

[django] method = "get"

GET방식은 클라이언트가 서버에 요청을 할 때, url에 해당 데이터를 담아 보낸다. 도메인 뒤에?id=1&name=ohmygodkimchi 처럼 따라붙는 형태이다(이것을 쿼리 스트링이라고 한다).
이러한 GET방식은 데이터가 url에 노출되기 때문에, 보안에 적절하지 않다고 한다, 따라서 GET방식은 데이터를 조회하는 데 쓰인다고 함.

[django] method = "post"

POST방식은 클라이언트가 서버에 요청을 할 때, 데이터가 노출되지 않는 방식으로, 주로 데이터의 변경을 요청하는 경우에 쓰인다고 함.

[django] {% csrf_token %}

(cross site request forgery) 교차 사이트 요청 위조,사용자가 하지않은 요청을 위조하여 악용하는 것을 방지하는 역할.
폼처리를 할때는 Django가 제공하는 csrf_token 템플릿 태그를 사용한다!

[html] fieldset, legend

mdn 웹 문서

views.py에 다음과 같이 vote함수를 추가하자.

#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['choice'] 는 선택된 설문의 ID를 문자열로 반환한다. request.POST 의 값은 항상 문자열들이다.

Django는 같은 방법으로 GET 자료에 접근하기 위해 request.GET 를 제공한다 – 그러나 POST 요청을 통해서만 자료가 수정되게하기 위해서, 명시적으로 코드에 request.POST 를 사용하고 있다.

만약 POST 자료에 choice 가 없으면, request.POST['choice']KeyError 가 일어난다. 위의 코드는 KeyError 를 체크하고, choice가 주어지지 않은 경우에는 에러 메시지와 함께 설문조사 폼을 다시보여준다.

설문지의 수가 증가한 이후에, 코드는 일반 HttpResponse 가 아닌 HttpResponseRedirect 를 반환하고, HttpResponseRedirect 는 하나의 인수를 받는다. 그 인수는 사용자가 재전송될 URL 이다. (이 경우에 우리가 URL을 어떻게 구성하는지 다음 항목을 보셈).

우리는 이 예제에서 HttpResponseRedirect 생성자 안에서 reverse() 함수를 사용하고 있다. 이 함수는 뷰 함수에서 URL을 하드코딩하지 않도록 도와준다. 제어를 전달하기 원하는 뷰의 이름을, URL패턴의 변수부분을 조합해서 해당 뷰를 가리킨다. 여기서 우리는 튜토리얼 3장에서 설정했던 URLconf를 사용하였으며, 이 reverse() 호출은 '/polls/3/results/' 문자열을 반환할 것이다.

여기서 3 은 question.id 값이다. 이렇게 리디렉션된 URL은 최종 페이지를 표시하기 위해 'results' 뷰를 호출한다.

어떤 이가 설문조사에 설문을 하고난 뒤에는, 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/ 페이지로 가서, 투표를 해보자. 만약 설문지를 선택하지 않고 폼을 전송했다면, 오류 메시지를 보게 될 것이다.

[django] 경쟁 상태

경쟁 상태 피하기
우리의 vote() 뷰에는 문제가 있다. 먼저 데이터베이스에서 selected_choice 객체를 가져온 다음, votes 의 새 값을 계산하고 나서, 데이터베이스에 다시 저장한다. 만약 여러분의 웹사이트에 두 명의 사용자가 정확하게 같은 시간 에 투표를 할려고 시도할 경우, 잘못될 수 있다. votes 의 조회값이 42라고 할 경우, 두 명의 사용자에게 새로운 값인 43이 계산 되고, 저장된다. 그러나 44가 되야 할 것이다.


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

CRUD기능은 자주 사용되기 때문에, Django는 제네릭 뷰(generic views)라는 것을 지원한다.

자주 사용되는 패턴을 추상화하여, 약간의 설정만 해주면 CRUD 기능을 구현 할 수 있게 된다.

# 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'),
]

[django] 왜 템플릿 변수 이름을 pk로 바꾸었을까?

제네릭 뷰에서 사용하는 템플릿 변수의 이름은 기본값으로 pk로 설정되어 있기 때문이다.
urls.py에서 정해준 템플릿 변수명을 그대로 사용하고 싶다면, 제네릭 뷰 안에 다음과 같이 설정해준다.
pk_url_kwarg = {뷰에 넘겨주는 변수명}

좋은 웹페이지 즐겨찾기