# polls/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
# 뷰 추가
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
# polls/urls.py
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
/polls/34/
를 요청했다면 django는 mysite.urls
파이썬 모듈을 불러옵니다.urlpatterns
라는 변수를 찾고 순서대로 패턴을 따라갑니다.polls/
를 찾은 후엔 polls/
를 버리고 남은 34/
를 <polls.urls> URLconf로 전달하여 남은 처리를 진행합니다.<int:question_id>/
와 일치하여 결과적으로 detail() 뷰 함수가 호출됩니다.detail(request=<HttpRequest object>, question_id=34)
# polls/views.py
from django.http import HttpResponse
from .models import Question
# 새로운 index() 뷰 하나를 호출했을 때, 시스템에 저장된
# 최소한 5개의 투표 질문이 콤마로 분리되어, 발행일에 따라 출력됩니다.
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged
polls/templates/polls/index.html
<!-- polls/templates/polls/index.html -->
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<!-- question객체가 있다면 각각의 id링크 생성 -->
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<!-- question객체가 없을 때 -->
<p>No polls are available.</p>
{% endif %}
# polls/views.py
from django.http import HttpResponse
from django.template import loader # template import
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))
render()
: request와 template_name을 필수로 받고 넘겨준 것들을 조합해서 HTTPRespense를 리턴해 주는 함수입니다.render()
를 적용한다면 더이상 loader
와 HttpResponse
를 import하지 않아도 됩니다.from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
/polls/
로 접속해 봅시다./polls/{{ question.id }}/
로 이동from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
# 요청된 question의 id가 없을 경우 Http404 예외 발생
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
<!-- polls/templates/polls/detail.html -->
{{ question }}
DEBUG = False
설정 시get_object_or_404()
: 만약 객체가 존재하지 않을 때 get()을 사용하여 Http404 예외를 발생시키는 것get_object_or_404()
함수는 django 모델을 첫번째 인자로 받고, 몇 개의 키워드 인수를 모델 관리자의 get()함수에 넘깁니다. 만약 객체가 존재하지 않을 경우, Http404 예외가 발생합니다.get_list_or_404()
: get() 대신 filter()를 씁니다. 리스트가 비어있을 경우, Http404 예외를 발생시킵니다.# polls/views.py
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
<!-- polls/templates/polls/detail.html -->
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
{{ question.question_text }}question
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
polls/specifics/12/
로 바꾸고 싶다면 템플릿에서 바꾸는 것이 아니라 polls/urls.py에서 바꿔야 합니다.# polls/urls.py
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
이름공간(namespace)
을 추가하면 됩니다.# polls/urls.py
from django.urls import path
from . import views
app_name = 'polls' # 앱의 이름공간 추가
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
<!-- polls/templates/polls/index.html -->
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
<form>
요소를 포함시켜 봅시다.<!-- 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>
choice=#(선택한 항목의 ID)
을 보낼 것입니다. -> HTML폼의 기본 개념입니다.# 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_id라는 primary key를 기준으로 일치하는 Question을 가져옵니다.
question = get_object_or_404(Question, pk=question_id)
try:
# post request에서 준 choice라는 value와 일치하는 primary key를 가진 Question을 가져옵니다.
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# KeyError 발생시 question 투표 양식을 다시 표시합니다.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# POST 데이터를 성공적으로 처리한 후에는 항상 HttpResponseRedirect를 반환합니다.
# 이렇게 하면 사용자가 뒤로 버튼을 눌러도 데이터가 두 번 게시되지 않습니다.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
# a라는 같은 키값으로, a=1과 a=2, c=3을 같이 보낸다
>>> QueryDict('a=1&a=2&c=3')
# <QueryDict: {'a':['1','2'], 'c':['3']}>
QueryDict objects
- HttpRequest object인 GET, POST 속성은 django.http.QueryDict의 인스턴스입니다.
- QueryDict는 딕셔너리 같은 것입니다.
request.POST
는 키로 전송된 자료에 접근할 수 있도록 해주는 딕셔너리 같은 객체입니다.choice=id
로 들어온 POST 요청을 key는 choice, value는 id인 딕셔너리로 변환시켜서request.POST['choice']
로 id 값을 구합니다.
-> 선택된 choice의 id를 문자열로 반환합니다.request.POST
의 값은 항상 문자열들입니다.
HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
- Exception이 발생하지 않으면 choice의 투표 수가 증가한 이후에, 코드는 일반
HttpResponse
가 아닌HttpResponseRedirect
를 반환하고 하나의 인수를 받습니다. 그 인수는 재전송될 URL입니다.
-> 선택을 끝낸 사용자에게 보여줄 결과화면의 URLreverse()
: 뷰 함수에서 URL을 하드코딩하지 않도록 하기 위해서 사용합니다. 제어를 전달하기 원하는 뷰의 이름과 URL패턴의 변수부분을 조합해서 해당 뷰를 가리킵니다.reverse('polls:results', args=(question.id,))
->/polls/3/results/
반환 (3은 question.id 값입니다.)
# 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/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>
- 우리의 vote() 뷰에서는
경쟁 상태
를 해결할 수 없습니다.
-> 두 명의 사용자가 정확하게 같은 시간에 투표하려고 시도할 경우, 잘못될 수 있습니다.
제너릭 뷰(Generic view)
: 일반적인 패턴을 추상화하여 앱을 작성하기 위해 Python코드를 작성하지 않아도 됩니다.# polls/urls.py
# <question_id> -> <pk>로 변경
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'),
]
# 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 # 제너릭 뷰 import
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
# 최소 5개의 투표 question이 발행일에 따라 리턴
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.
DetailView
<app name>/<model name>_detail.html
템플릿을 사용합니다.polls/question_detail.html
DetailView
를 사용하고 있지만 template_name
속성을 통해 렌더링 될 때 서로 다른 모습을 갖도록 합니다.ListView
<app name>/<model name>_list.html
템플릿을 사용합니다.polls/index.html
템플릿을 사용하기 위해 template_name
으로 전달했습니다.