소개
이 튜토리얼은 Creating the Public Interface Views에서 중단된 부분부터 시작합니다. 웹 설문 애플리케이션을 계속 진행하며, 폼 처리와 코드 축소에 중점을 둘 것입니다.
이 튜토리얼은 Creating the Public Interface Views에서 중단된 부분부터 시작합니다. 웹 설문 애플리케이션을 계속 진행하며, 폼 처리와 코드 축소에 중점을 둘 것입니다.
마지막 튜토리얼에서 다룬 설문 상세 템플릿 (polls/detail.html) 을 업데이트하여 템플릿에 HTML <form> 요소를 포함하도록 하겠습니다.
<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>
간단한 설명:
value는 관련 질문 선택 항목의 ID 입니다. 각 라디오 버튼의 name은 "choice"입니다. 즉, 누군가 라디오 버튼 중 하나를 선택하고 폼을 제출하면 choice=# 형식의 POST 데이터를 전송합니다. 여기서 #은 선택한 선택 항목의 ID 입니다. 이것이 HTML 폼의 기본 개념입니다.action을 {% url 'polls:vote' question.id %}로 설정하고 method="post"를 설정했습니다. method="post"를 사용하는 것 ( method="get"와 반대로) 은 매우 중요합니다. 이 폼을 제출하는 행위가 서버 측 데이터를 변경하기 때문입니다. 서버 측 데이터를 변경하는 폼을 만들 때는 항상 method="post"를 사용하십시오. 이 팁은 Django 에만 국한된 것이 아니라 일반적인 웹 개발 관행입니다.forloop.counter는 for 태그가 루프를 몇 번 반복했는지 나타냅니다.{% csrf_token %} 템플릿 태그를 사용해야 합니다.이제 제출된 데이터를 처리하고 이를 사용하여 무언가를 수행하는 Django 뷰를 만들어 보겠습니다. 기억하세요, **Creating the Public Interface Views**에서 다음 줄을 포함하는 설문 애플리케이션에 대한 URLconf 를 만들었습니다.
path("<int:question_id>/vote/", views.vote, name="vote"),
또한 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 <django.http.HttpRequest.POST>는 키 이름으로 제출된 데이터에 액세스할 수 있는 딕셔너리 유사 객체입니다. 이 경우 request.POST['choice']는 선택한 선택 항목의 ID 를 문자열로 반환합니다. request.POST <django.http.HttpRequest.POST> 값은 항상 문자열입니다.
Django 는 request.GET <django.http.HttpRequest.GET>도 제공하여 GET 데이터에 동일한 방식으로 액세스할 수 있습니다. 하지만 데이터가 POST 호출을 통해서만 변경되도록 하기 위해 코드에서 명시적으로 request.POST <django.http.HttpRequest.POST>를 사용하고 있습니다.
request.POST['choice']는 POST 데이터에 choice가 제공되지 않은 경우 KeyError를 발생시킵니다. 위의 코드는 KeyError를 확인하고 choice가 제공되지 않은 경우 오류 메시지와 함께 질문 폼을 다시 표시합니다.
선택 항목 수를 증가시킨 후 코드는 일반 ~django.http.HttpResponse 대신 ~django.http.HttpResponseRedirect를 반환합니다. ~django.http.HttpResponseRedirect는 단일 인수를 사용합니다. 즉, 사용자가 리디렉션될 URL 입니다 (이 경우 URL 을 구성하는 방법은 다음 사항을 참조하십시오).
위의 Python 주석에서 언급했듯이 POST 데이터를 성공적으로 처리한 후에는 항상 ~django.http.HttpResponseRedirect를 반환해야 합니다. 이 팁은 Django 에만 국한된 것이 아니라 일반적인 웹 개발 관행입니다.
이 예제에서는 ~django.http.HttpResponseRedirect 생성자에서 ~django.urls.reverse 함수를 사용하고 있습니다. 이 함수는 뷰 함수에서 URL 을 하드코딩하는 것을 피하는 데 도움이 됩니다. 제어 권한을 넘겨주고 싶은 뷰의 이름과 해당 뷰를 가리키는 URL 패턴의 변수 부분을 제공합니다. 이 경우, **Creating the Public Interface Views**에서 설정한 URLconf 를 사용하면 이 ~django.urls.reverse 호출은 다음과 같은 문자열을 반환합니다.
"/polls/3/results/"
여기서 3은 question.id의 값입니다. 이 리디렉션된 URL 은 results 뷰를 호출하여 최종 페이지를 표시합니다.
**Creating the Public Interface Views**에서 언급했듯이 request는 ~django.http.HttpRequest 객체입니다. ~django.http.HttpRequest 객체에 대한 자세한 내용은 request and response documentation </ref/request-response>를 참조하십시오.
누군가 질문에 투표한 후 vote() 뷰는 질문에 대한 결과 페이지로 리디렉션됩니다. 해당 뷰를 작성해 보겠습니다.
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})
이것은 **Creating the Public Interface Views**의 detail() 뷰와 거의 동일합니다. 유일한 차이점은 템플릿 이름입니다. 이 중복은 나중에 수정하겠습니다.
이제 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/로 이동하여 질문에 투표하십시오. 투표할 때마다 업데이트되는 결과 페이지가 표시됩니다. 선택 항목을 선택하지 않고 폼을 제출하면 오류 메시지가 표시됩니다.
cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080

참고:
vote() 뷰에 대한 코드에는 작은 문제가 있습니다. 먼저 데이터베이스에서 selected_choice 객체를 가져온 다음 votes의 새 값을 계산한 다음 다시 데이터베이스에 저장합니다. 웹사이트의 두 사용자가 정확히 동시에 투표하려고 하면 문제가 발생할 수 있습니다. 동일한 값 (예: 42) 이 votes에 대해 검색됩니다. 그런 다음 두 사용자 모두에 대해 새 값 43 이 계산되어 저장되지만 예상 값은 44 입니다.
이것을 *경쟁 조건 (race condition)*이라고 합니다. 관심이 있다면 avoiding-race-conditions-using-f를 읽고 이 문제를 해결하는 방법을 배울 수 있습니다.
detail() ( **Creating the Public Interface Views**에서) 및 results() 뷰는 매우 짧으며 위에서 언급했듯이 중복됩니다. 설문 목록을 표시하는 index() 뷰도 유사합니다.
이러한 뷰는 기본 웹 개발의 일반적인 경우를 나타냅니다. URL 에서 전달된 매개변수에 따라 데이터베이스에서 데이터를 가져오고, 템플릿을 로드하고, 렌더링된 템플릿을 반환합니다. 이것이 매우 일반적이기 때문에 Django 는 "제네릭 뷰" 시스템이라는 바로 가기를 제공합니다.
제네릭 뷰는 일반적인 패턴을 추상화하여 앱을 작성하기 위해 Python 코드를 작성할 필요조차 없게 만듭니다.
설문 앱을 제네릭 뷰 시스템을 사용하도록 변환하여 자체 코드를 많이 삭제할 수 있습니다. 변환을 위해 몇 가지 단계를 거쳐야 합니다. 다음을 수행합니다.
자세한 내용은 계속 읽어보세요.
왜 코드 셔플인가요?
일반적으로 Django 앱을 작성할 때 제네릭 뷰가 문제에 적합한지 평가하고 처음부터 사용하며 중간에 코드를 리팩터링하는 대신 사용합니다. 그러나 이 튜토리얼은 핵심 개념에 집중하기 위해 지금까지 의도적으로 "어려운 방법"으로 뷰를 작성하는 데 중점을 두었습니다.
계산기를 사용하기 전에 기본적인 수학을 알아야 합니다.
먼저 polls/urls.py URLconf 를 열고 다음과 같이 변경합니다.
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"),
]
두 번째 및 세 번째 패턴의 경로 문자열에서 일치하는 패턴의 이름이 <question_id>에서 <pk>로 변경되었음에 유의하십시오.
다음으로, 이전 index, detail 및 results 뷰를 제거하고 대신 Django 의 제네릭 뷰를 사용합니다. 이렇게 하려면 polls/views.py 파일을 열고 다음과 같이 변경합니다.
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 ResultsView(generic.DetailView):
model = Question
template_name = "polls/results.html"
def vote(request, question_id):
... ## same as above, no changes needed.
여기서는 두 개의 제네릭 뷰를 사용하고 있습니다: ~django.views.generic.list.ListView 및 ~django.views.generic.detail.DetailView. 각각, 이 두 뷰는 "객체 목록 표시" 및 "특정 유형의 객체에 대한 상세 페이지 표시"의 개념을 추상화합니다.
model 속성을 사용하여 제공됩니다.~django.views.generic.detail.DetailView 제네릭 뷰는 URL 에서 캡처된 기본 키 값을 "pk"로 호출할 것으로 예상하므로 제네릭 뷰에 대해 question_id를 pk로 변경했습니다.기본적으로 ~django.views.generic.detail.DetailView 제네릭 뷰는 <app name>/<model name>_detail.html이라는 템플릿을 사용합니다. 이 경우 "polls/question_detail.html" 템플릿을 사용합니다. template_name 속성은 Django 에 자동 생성된 기본 템플릿 이름 대신 특정 템플릿 이름을 사용하도록 지시하는 데 사용됩니다. 또한 results 목록 뷰에 대해 template_name을 지정합니다. 이렇게 하면 결과 뷰와 상세 뷰가 렌더링될 때 서로 다른 모양을 갖도록 보장합니다. 이는 둘 다 백그라운드에서 ~django.views.generic.detail.DetailView이기 때문입니다.
마찬가지로, ~django.views.generic.list.ListView 제네릭 뷰는 <app name>/<model name>_list.html이라는 기본 템플릿을 사용합니다. template_name을 사용하여 ~django.views.generic.list.ListView에 기존의 "polls/index.html" 템플릿을 사용하도록 지시합니다.
튜토리얼의 이전 부분에서 템플릿은 question 및 latest_question_list 컨텍스트 변수를 포함하는 컨텍스트와 함께 제공되었습니다. DetailView의 경우 question 변수가 자동으로 제공됩니다. Django 모델 (Question) 을 사용하고 있으므로 Django 는 컨텍스트 변수에 적절한 이름을 결정할 수 있습니다. 그러나 ListView 의 경우 자동 생성된 컨텍스트 변수는 question_list입니다. 이를 재정의하기 위해 context_object_name 속성을 제공하여 latest_question_list를 사용하도록 지정합니다. 다른 접근 방식으로, 새 기본 컨텍스트 변수에 맞게 템플릿을 변경할 수 있습니다. 하지만 원하는 변수를 사용하도록 Django 에 지시하는 것이 훨씬 쉽습니다.
서버를 실행하고 제네릭 뷰를 기반으로 하는 새 설문 앱을 사용하십시오.
축하합니다! 폼 처리 및 코드 줄이기 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.