Django Tutorial #4

yjshin·2022년 12월 6일
0

Django_Tutorial

목록 보기
4/4

우리는 계속해서 Web-poll 애플리케이션을 진행하고 있고,
간단한 폼 처리와 소스코드를 줄이는 데 중점을 둘 것이다.

간단한 폼 쓰기

앞장의 투표 상세 템플릿("polls/detail.html")을 수정하여,
템플릿에 HTML

요소를 포함시켜 보자.

  • polls/templates/polls/detail.html
  <form action="{% url 'polls:vote' question.id %}" method="post">
  {% csrf_token %}
  <filedset>
    <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 %}    
  </filedset>
  <input type="submit" value="Vote">
  </form>       

간략하게 설명하면:

  • 위의 템플릿은 각 질문 선택 항목에 대한 라디오 버튼을 표시한다.
    각 라디오 버튼의 value는 연관된 질문 선택 항목의 ID이다.
    각 라디오 버튼의 name은 "choice"이다.
    즉, 누군가가 라디오 버튼 중 하나를 선택하여 폼을 제출하면,
    POST 데이터인 choice=#을 보낼 것이다.
    여기서 #은 선택한 항목의 ID이다. 이것은 HTML 폼의 기본 개념이다.
  • 양식의 action을 {% url 'polls:vote' question.id %}로 설정하고,
    method=post로 설정한다.
    이 양식을 제출하는 행위는 데이터 서버측을 변화시키기 때문에 method="post"
    (method="get"와 반대로)를 사용하는 것은 매우 중요하다.
    데이터 서버측을 변경하는 양식을 만들때마다 method="post"를 사용해라.
    이 팁은 Django에만 국한된 것이 아니라 일반적인 좋은 웹 개발 관행이다.
  • forloop.counter는 for 태그가 반복을 한 횟수를 나타낸다.
  • POST 양식을 만들고 있기 때문에(데이터를 수정하는 효과가 있을 수 있음),
    교차 사이트 요청 위조(Cross Site Request Forgeries)에 대해 걱정해야 한다.
    다행히도, 우리는 너무 걱정하지 않아도 된다.
    왜냐하면 Django는 그것으로부터 보호하는 유용한 시스템을 가지고 있기 때문이다.
    간단히 말하자면, 내부 URL을 대상으로 하는 모든 POST 양식은 {% csrf_token %}
    템플릿 태그를 사용해야 한다.

이제 제출된 데이터를 처리하고 그 데이터로 무언가를 수행하는 Django 뷰를 작성하자.
Tutorial 3에서 설문 조사 어플리케이션을 위해 아래에 나와있는 code를 포함하는 URLconf를 만들었다.

  • polls/urls/py
path('<int:question_id>/vote/', views.vote, name='vote'),

또, 우리는 vote() 함수를 가상으로 만들었다.
실제로 구현을 해보자. polls/views.py에 다음 code와 같이 추가하자.

  • 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,)))
  

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

  • request.POST는 키로 전송된 자료에 접근할 수 있도록 해주는 사전과 같은 객체이다.
    이 경우, request.POST['choice']는 선택된 설문의 ID를 문자열로 반환한다.
    request.POST의 값은 항상 문자열들이다.

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

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

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

    위의 Python 주석에서 지적한 바와 같이 POST 데이터를 성공적으로 처리한 후에는 항상
    HttpResponseRedirect를 반환해야 한다. 이 팁은 Django에만 국한된 것이 아니라
    일반적으로 좋은 웹 개발 관행이다.

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

    'polls/3/results/'

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

    Tutorial 3장에서 언급했듯이, request는 HttpRequest 개체이다.

    어떤 이가 설문조사에 설문을 하고 난 뒤에는, vote() 뷰는 설문조사 결과 페이지로 리다이렉트한다.
    그 뷰를 작성해보자.

    • polls/views.py
    from django.shortcuts import get_object_or_404, render
    
    def result(request, question_id):
    	question = get_object_or_404(Question, pk=question_id)
    	return render(request, 'polls/results.html', {'question': question})

    Tutorial 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/ 페이지로 가서, 투표를 해보자.
    당신이 투표를 할 때마다 값이 반영된 결과 페이지를 있을 것이다.
    만약 당신이 설문지를 선택하지 않고 폼을 전송했다면, 오류 메시지를 보게 될 것이다.

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

    이를 경쟁 상태라 한다.

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

detail() (from Tutorial3)과 results()의 view들은 매우
간단하며, 위에서 언급한 바와 같이 중복된다.
여론조사 목록을 보여주는 index() view도 비슷하다.

이러한 보기는 URL에 전달된 매개 변수에 따라 데이터베이스에서
데이터를 가져오고, 템플릿을 로드하고, 렌더링된 템플릿을
반환하는 기본 웹 개발의 일반적인 경우를 나타낸다.
이것이 매우 흔하기 때문에,
Django는 "generic views" 시스템이라고 불리는 단축키를 제공한다.

제너릭 뷰는 일반적인 패턴을 추상화하여 앱을 작성하기 위해
Python Code를 작성하지 않아도 된다.

우리 설문조사 애플리케이션을 제너릭 뷰 시스템으로 변환해서 우리의 코드를 많이 삭제하도록 하자.
이러한 전환을 하려면 다음과 같은 몇 가지 단계만 거치면 된다.
1. URLconf를 변환하시오.
2. 불필요한 오래된보기 중 일부를 삭제하시오.
3. Django의 제너릭 뷰를 기반으로 새로운 뷰를 도입하시오.

  • 왜 코드 셔플인가?

일반적으로 Django 앱을 작성할 때 일반 뷰가 문제에 적합한 지 여부를 평가할 것이며 코드를 중간에서 다시 리팩토링하지 않고
처음부터 사용하게 된다.
그러나 이 튜토리얼은 의도적으로 현재까지 핵심 개념에 초점을 맞추기 위해 "어려운 방법"으로 뷰를 작성하는 데 중점을 두었다.
계산기를 사용하기 전에 기본 수학을 알아햐 한다.

URLconf 수정

먼저, polls/urls.py URLconf를 열어 다음과 같이 변경하시오.

  • 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.ResultView.as_view(),
  		path('<int:qeustion_id>/vote/', views.vote, 
  		name='vote')
  ]

두 번째와 세 번째 패턴의 경로 문자열에서 일치하는 패턴들의
이름이 <question_id>에서 로 변경되었다.

views 수정

다음으로 이전의 index, detail, results 뷰를 제거하고
장고의 일반적인 뷰를 대신 사용하겠다.
그렇게하려면 polls/views.py 파일을 열고 다음 Code와 같이 변경하시오.

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 ResultView(generic.DetailView):
  	model = Question
  	template_name = 'polls/results.html'
  
def vote(request, question_id):
  	... #same as above, no changes needed.
  

ListView와 DetailView의 두가지 제너릭 뷰를 사용하고 있다.
이 두 뷰는 각각 "개체 목록 표시" 및 "특정 개체 유형에 대한
세부 정보 페이지 표시" 개념을 추상화한다.

  • 각 제너릭 뷰는 어떤 모델이 적용될 것인지를 알아야한다.
    이것은 model 속성을 사용하여 제공된다.
  • DetailView 제너릭 뷰는 URL에서 캡처된 기본 키 값이 "pk"
    라고 기대하기 때문에 question_id 제너릭 뷰를 위해 pk로 변경한다.

기본적으로 DetailView 제너릭 뷰는 /_detail.html 템플릿을 사용한다.
우리의 경우에는 "polls/question_detail.html" 템플릿을 사용할 것이다. template_name 속성은 Django에게 자동 생성 된
기본 템플릿 이름 대신에 특정 템플릿 이름을 사용하도록 알려주기 위해 사용된다. results 리스트 뷰에 대해서 template_name을 지정한다. -결과 뷰와 상세 뷰가 렌더링 될 때 서로 다른 모습을 갖도록 합니다. 이들이 둘 다 동일한 DetailView를 사용하고 있더라도 말이다.

마찬가지로, ListView 제너릭 뷰는 /_list.html 템플릿을 기본으로 사용한다. 이미 있는 "polls/index.html" 템플릿을 사용하기 위해 ListView에 template_name을 전달했다.

튜토리얼의 이전 부분에서 템플릿은 question 및 latest_question_list 컨텍스트 변수를 포함하는 컨텍스트와 함께 제공되었습니다. DetailView의 경우 question 변수가 자동으로 제공되는데, 이는 우리가 Django 모델(Question)을 사용하고 있기 때문에 Django가 컨텍스트 변수의 적절한 이름을 결정할 수 있다. 그러나 ListView의 경우 자동으로 생성되는 컨텍스트 변수는 question_list이다. 이것을 덮어 쓰려면 context_object_name 속성을 제공하고,
대신에 latest_question_list를 사용하도록 지정하시오.
새로운 기본 컨텍스트 변수와 일치하도록 템플릿을 변경할 수도 있지만, 원하는 변수를 사용하도록 Django에게 지시하는 것이 훨씬 쉽다.

서버를 실행하고 제너릭 뷰를 기반으로 한 새 설문조사 앱을 사용하시오.

profile
study

0개의 댓글