polls/templates/polls/detail.html<form action="'#" method="'post">
<h1>{{question.question_text}}</h1>
{% 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> 
{% csrf_token %}<form action="'#" method="'post">
{% csrf_token %}
<h1>{{question.question_text}}</h1>
{% 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>


선택하지 않고 submit할 경우 error message 출력
{% csrf_token %}
<form action= {% url 'polls:vote' question.id %} method="post">
{% csrf_token %}
<h1>{{question.question_text}}</h1>
{% 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 %}
<input type="submit" value="Vote">
</form>
polls/views.py
try-except 문을 통해 선택하지 않았을 경우 템플릿으로 error_message 전달
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):
return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다.'})
else:
selected_choice.votes += 1
selected_choice.save() # 테이블에 값 저장
# index로 돌아가기
return HttpResponseRedirect(reverse('polls:index'))
에러 메시지 화면

DoesNotExist 가 발생polls/views.py'error_message': f"선택이 없습니다. id={request.POST['choice']}" 에서 id까지 함께 에러 메시지에 출력하게 한다
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):
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
A, B가 서로 다른 장고 서버 A_server, B_server에 접속하고, 이 서버는 모두 하나의 데이터베이스와 연결되어 있다
이때 A, B가 동시에 같은 선택지를 선택 후 submit한다면?
똑같은 명령, Votes = 1 이라는 명령을 실행하게 되는데 어쩔 수 없이 우열이 생기므로 실제로 Votes = 2가 되어야 하지만 결과적으로 Votes = 1이 된다
기존의 코드에서는 로컬 메모리에 저장한 뒤 서버 DB에 save하기 때문에 이러한 문제가 생긴다
F() 활용
F()는 DB에서 바로 값을 가져와 업데이트 하라는 의미
selected_choice.votes = F('votes') + 1
polls/views.py
HttpResponseRedirect(reverse('polls:index')) : submit하면 다른 페이지로 이동하도록 한다.
- 이때, reverse()는 함수의 url 패턴의 이름을 사용해 해당 url을 가져온다
from django.urls import reverse
from django.db.models import F
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):
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
else:
# A서버에서도 Votes = 1
# B서버에서도 Votes = 1
selected_choice.votes = F('votes') + 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
submit 후 현재까지의 vote 결과를 보여주는 페이지를 구현한다
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>/vote/', views.vote, name='vote'),
path('<int:question_id>/result/', views.result, name='result'),
]
polls/templates/polls/result.html
choice를 순회하며 choice - votes 형태로 출력될 수 있도록 구현한다
<h1>{{question.question_text}}</h1>
{% for choice in question.choice_set.all %}
<label for="choice{{forloop.counter}}">
{{choice.choice_text}} -- {{choice.votes}}
</label>
<br>
{% endfor %}
polls/views.py
submit 후 result 페이지로 이동하도록 구현
from django.shortcuts import get_object_or_404, render
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):
return render(request, 'polls/detail.html', {'question': question, 'error_message': f"선택이 없습니다. id={request.POST['choice']}"})
else:
selected_choice.votes = F('votes') + 1
selected_choice.save() # 테이블에 값 저장
# result 보여주기
return HttpResponseRedirect(reverse('polls:result', args = (question_id,)))
# vote 결과
def result(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/result.html', {'question':question})
결과 화면



pub_date = models.DateTimeField(*auto_now_add=True*) : 자동으로 현재 시간을 추가해준다readonly field로 설정 
polls/admin.pyextra는 몇 개의 choice를 “추가로” 등록할 것인지 나타낸다
이미 2개의 choice 객체가 있을 경우 3개를 추가로 띄운다
from django.contrib import admin
from .models import *
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3 # 몇 개의 choice를 "추가로" 등록할 것인지
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields' : ['question_text']}),
('생성일', {'fields' : ['pub_date']}),
]
readonly_fields = ['pub_date']
inlines = [ChoiceInline]
# Register your models here.
admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice)

admin.StackedInline : 요소를 세로로 나열한다admin.TabularInline : 요소를 가로로 나열한다 class ChoiceInline(admin.TabularInline):
model = Choice
extra = 3 
숨기고 싶을 경우 inlines 에 포함시킨다
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields' : ['question_text']}),
('생성일', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
]
readonly_fields = ['pub_date']
inlines = [ChoiceInline]

polls/admin.pylist_display: 목록에서 표시할 요소
from django.contrib import admin
from .models import Choice, Question
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields' : ['question_text']}),
('생성일', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
]
list_display = ('question_text', 'pub_date', 'was_published_recently')
readonly_fields = ['pub_date']
inlines = [ChoiceInline]

models.pyverbose_name : label 표기 설정
@admin.display(boolean=True, description='최근생성(하루기준)')
- boolean : True로 할 경우, 아이콘으로 보인다. False로 할 경우 True, False로 보인다.
- escription : label 표기 설정
import datetime
from django.db import models
from django.utils import timezone
from django.contrib import admin
class Question(models.Model):
# 질문 (최대 길이 200)
question_text = models.CharField(max_length=200, verbose_name = '질문')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name = '생성일') #
@admin.display(boolean=True, description='최근생성(하루기준)')
def was_published_recently(self):
# pub date가 어제보다 크면, 어제보다 더 최근에 만들어졌는지
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

models.pylist_filter = ['pub_date'] : 필터로 사용할 요소
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields' : ['question_text']}),
('생성일', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
]
list_display = ('question_text', 'pub_date', 'was_published_recently')
readonly_fields = ['pub_date']
inlines = [ChoiceInline]
list_filter = ['pub_date']

models.pysearch_fields = ['question_text'] : 검색할 때 탐색할 요소
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields' : ['question_text']}),
('생성일', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
]
list_display = ('question_text', 'pub_date', 'was_published_recently')
readonly_fields = ['pub_date']
inlines = [ChoiceInline]
list_filter = ['pub_date']
search_fields = ['question_text']

search_fields 에 추가하면 choice에 대해서도 검색할 수 있다.class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
('질문 섹션', {'fields' : ['question_text']}),
('생성일', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
]
list_display = ('question_text', 'pub_date', 'was_published_recently')
readonly_fields = ['pub_date']
inlines = [ChoiceInline]
list_filter = ['pub_date']
search_fields = ['question_text', 'choice__choice_text'] 