나중에 보려 만든 Django 실시간 투표 웹서비스

실시간 투표 웹서비스

A. 모든 URL들을 App 단위로 나누어 각 App폴더 내의 urls.py에 권한을 위임
ex. /lotto/ 가 반복적으로 들어가는 url들은 모두 lottos app > urls.py에 맡기자.
1. 각 앱 폴더에 urls.py 생성
2. 권한 위임 작업
(@ website > urls.py)

from django.urls import path, include
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('polls/',include('polls.urls')) # 권한 위임 작업
]

(@ app > urls.py)

from django.urls import path
from . import views
urlpatterns = [
    path('',views.index, name='index'),
]

(@ views.py)

def index(request):
   return HttpResponse('Hello world!')
  • 만드려는 DB 구조

B. 설문조사 주제 (Question) & 보기(Choice)를 연결할 필요가 있음.
1. DB tables 생성
(polls > models.py)

from django.db import models
from django.utils import timezone
import datetime
# Create your models here.
class Question(models.Model):
   question_text = models.CharField(max_length=200) # 주제
   pub_date = models.DateTimeField('Date published') # ‘date published’라는 이름으로 관리자 페이지에서 보여질 항목명
   def __str__(self): # 하나의 행. 인스턴스생성시 자동 실행되는 함수.
       return self.question_text
   def was_published_recently(self): # 현재보다 하루 이내 올라왔는가 확인
       now = timezone.now()
       return now >= self.pub_date > now - datetime.timedelta(days=1) # 하루가 몇 milliseconds인지
class Choice(models.Model): # DB Table for 설문조사 주제별 선택지 (+ 선택지마다의 득표 수)
   # Question table의 pk를 fk로 세팅
   # on_delete=models.CASCADE : Question(질문) 항목 삭제 시 관계된 선택지들도 모두 자동 삭제
   question = models.ForeignKey(Question, on_delete=models.CASCADE) # 설문조사 주제의 id 값
   choice_text = models.CharField(max_length=200) # 설문조사 주제에 대한 선택지 텍스트
   votes = models.IntegerField(default=0) # 해당 선택지의 득표 수
   def __str__(self): # 하나의 행
        return self.choice_text


class 내부 메소드 작동원리

2. DB table 업데이트 및 저장(makemigrations, migrate)

$python manage.py makemigrations
$python manage.py migrate

3. 저장 후 확인(shell)

$python manage.py shell 
$from polls.models import Question, Choice
$Question.objects.all() 
# 수정 전: 관리자페이지에서 <QuerySet [<Question: Question object (1)>]> 가 출력됨. -> "Question: __" 형태로 바꿔주기 위해, models.py에서 __str__함수를 1번 코드와 같이 수정해줄 것. 수정 후: <QuerySet [<Question: What is the worst treatement?>]>
$from django.utils import timezone
$q = Question(question_text="What's the best treatment?", pub_date=timezone.now())
$q.save() # DB에 저장

+shell command 추가:

# all: DB의 모든 행 꺼내기
list = Question.objects.all()
# filter: 특정 조건을 맞추는 행들 리스트 형태로 꺼내기
rows = Question.objects.filter(id=1)
# get: 단일 행을 꺼낼 경우
row = Question.objects.get(question_text__startswith = 'What')
# Question에 연결된 Choice들 중 Treat문자열로 시작하는 보기들만 보여줘라
c = q.choice_set.filter(choice_text__startswith='Treat')
# delete: 변수 삭제
c.delete()
# count : 카운트
q.choice_set.count()
# (상단 DB구조 이미지 참조) Choice 테이블의 question(fk)와 연결되어있는 테이블의 pub_date의 year이 2021인것만 보여줘라
Choice.objects.filter(question__pub_date__year=2021)

4. 특정 주제에 대한 여러 보기 만들기
a. (테스팅용)cmd창에서 Question 인스턴스1 생성.

from django.utils import timezone
current_year = timezone.now().year
Question.objects.get(pub_date__year=current_year)
Question.objects.get(pk=1)
q = Question.objects.get(pk=1)
q.was_published_recently()

b. (테스팅용)cmd창에서 인스턴스1(q)에 연결된 해당하는 보기(Choice table) 만들기.

q.choice_set.create(choice_text='Treatment A', votes=0)
q.choice_set.create(choice_text='Treatment B', votes=0)
c = q.choice_set.create(choice_text='Treatment C', votes=0) 

c. 생성한 질문 및 보기 tables admin.py에서 register 해주기
(@admins.py)

from django.contrib import admin
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)

C. Error page handling

  • 존재하지 않는 /id/로 접속할 경우 404error을 발생시키자.
    방법1.
from django.http import HttpResponse, Http404
def detail(request, question_id):
    try:
        q = Question.objects.get(pk=question_id)
    except Question.DoesNotExist: # Question 내 question_id가 없어 DoesNotExist에러가 뜰 경우
        raise Http404('Question {} does not exist'.format(question_id))
    return render(request, 'polls/detail.html',{'question':q})

방법2. Table에 존재하지 않는 id가 입력되었을 경우 에러 발생: "get_object_or_404"

from django.shortcuts import render, get_object_or_404
def detail(request, question_id):
    # q = Question.objects.get(pk=question_id)
    q = get_object_or_404(Question, pk=question_id) # 꺼낼 테이블 이름, 꺼내려는 기준 # get_objects_or_404도 존재
    return render(request, 'polls/detail.html',{'question':q})

D. 결과 확인을 위한 urls.py & views.py 수정
설계 및 예상 결과물)
http://127.0.0.1:8000/polls/ : 모든 Question가 나열되고, 누르면 아래로 route.

http://127.0.0.1:8000/polls/3/

http://127.0.0.1:8000/polls/3/results/ : 위에서 vote submit 후 보여주는 결과창

http://127.0.0.1:8000/polls/3/vote/ : POST 요청을 처리하는 개별적인 주소가 될 것임.

구현 코드)
(@urls.py)

from django.urls import path
from . import views
app_name = 'polls' # 나중에 detail이라는 name이 다른 앱에 존재할 수도 있으니까!. 추후 html에서 해당 name 사용시, "polls:detail"이라 명시 가능. 
urlpatterns = [
    path('',views.index, name='index'), # /polls/ # 설문조사 페이지에서
    # polls/3/
    path('<int:question_id>/',views.detail, name='detail'), # 설문조사 주제 detail page
    # /polls/3/results/
    path('<int:question_id>/results', views.results, name='results'),
    # /polls/3/vote/ : 보여주려는 용도 X. post요청을 처리하려는 개별적인 주소.
    path('<int:question_id>/vote/',views.vote, name='vote'),
]

(@views.py)

from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse, Http404
from .models import Question, Choice
# Create your views here.
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)
def detail(request, question_id):
    # q = Question.objects.get(pk=question_id)
    q = get_object_or_404(Question, pk=question_id) # 꺼낼 테이블 이름, 꺼내려는 기준
    return render(request, 'polls/detail.html',{'question':q})
def results(request, question_id):
    response = "You are now at results {}"
    return HttpResponse(response.format(question_id))
def vote(request, question_id):
   # input tag로 들어갈 value = request.POST('choice_select') # name으로 꺼내고 돌려받는 건 value
   question = get_object_or_404(Question, pk=question_id)
   # return HttpResponse('vote')
   try: # pk=999가 입력되었을 때, 애러 뻥!
       selected_choice = question.choice_set.get(pk=request.POST['choice_select'])
       # request.POST['choice_select'] : value값(ex.8)을 리턴함.
       # print(request.POST) 로 확인해볼 것
       # 선택된 답안 selected_choice에 저장.
       # detail.html의 <input type="radio" name="choice_select" value="{{ choice.id }}">에서 날라온 값
       # form으로 제출된 POST Request 전체에서 'choice_select'가 name인 HTML 태그의 value를 꺼내는 코드
       # request.POST 는 {~~~, 'choice_select':7} 와 같은 dictionary 형태
   except:
       # request.POST['choice_select']값이 없을 경우, error_message를 가지고 details.html로 되돌아감
       context = {'question': question, 'error_message': "You didn't select a choice."}
       return render(request, 'polls/detail.html', context)
   else: # 에러가 발생하지 않았을 경우, else 실행...
       selected_choice.votes += 1 # vote수 1씩 증가
       selected_choice.save() # 실제 DB 저장
       return redirect('polls:results', question_id = question.id)

E. Template 파일 생성
(@index.html)

{% if latest_question_list %}  <!-- latest_question_list가 있다먄 -->
  <ul>
    {% for question in latest_question_list %}
      <li>
        <!--하드코딩: <a href='/polls/{{ question.id }}/'>{{ question.question_text }}</a> -->
        <a href='{% url "polls:detail" question_id=question.id %}'>{{ question.question_text }}</a>
        <!-- 자동 url 생성. name이 detail인 url의 question_id에 question.id를 넣어라 -->
      </li>
    {% endfor %}
  </ul>
{% else %}
  <p>No polls are available.</p>
{% endif %}

(@detail.html)
Question Choices를 form tag로 보여주기

<h2>{{ question.question_text }}</h2>
{% if error_message %}
 <p><strong>{{ error_message }}</strong></p>
{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
 {% csrf_token %} <!-- form에는 반드시 넣어줘야해 -->
 {% for choice in question.choice_set.all %}
   <!-- for loop마다 숫자를 세어주려고: forloop.counter -->
   <input type="radio" name="choice_select" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
   <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label> <!-- label눌러도 인식되도록...사용자 입장에서 편하게! -->
   <br>
 {% endfor %}
 <input type="submit" value="Vote">
</form>










좋은 웹페이지 즐겨찾기