Django / 복습(실습)
Web/Django

Django / 복습(실습)

뉴비뉴 2019. 7. 3.

화면에 Hello World 출력하기

code/elections/views.py

from django.shortcuts import render
from django.http import HttpResponse

def index(request):
	return HttpResponse("Hello World")

1. 사이트 접속했을 때 elecitons App 이 실행되는 조건을 지정해주어야 한다.

2. elections App이 실행되면 어느 경로에 index 함수가 실행되는지 지정해주어야 한다.

 

1.

mysite/urls.py

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^$', include('elections.urls')),
    url(r'^admin/', admin.site.urls),
]

elections/urls.py 생성

mysite/urls.py 내용 복사해서 붙여넣기 한 뒤

admin 관련 된 내용만 지워준다.

from django.conf.urls import url,include
from . import views # views.index를 사용하기 위해서는 생성해주어야한다. . 은 현재폴더를 의미

urlpatterns = [
    url(r'^', views.index), # views 안에 있는 index라는 함수를 실행하라, $ 표시는 아무것도 없을 때
]

정리해보자면 elections/views.py 에서 Hello World를 출력할 수 있는 함수 index를 생성한다.

elections/urls.py 에서 from . import views의 내용을 가져오고, url(r'^$', views.index), 를 생성해준다.

mysite/urls.py url(r'^$', include(elections.urls)) elections 폴더에 urls를 가져온다.

이렇게 설정하게 되면 화면에 Hello world 가 출력하게 된다.

 

elections/models.py

from django.db import models

class Candidate(models.Model): # 장고의 모델로 동작하려면 models.Model 을 상속받아야 한다.
    name = models.CharField(max_length=10)
    introduction = models.TextField()
    area = models.CharField(max_length=15)
    party_number = models.IntegerField(default=1)

마이그레이션과 DB

models.py 의 class Candidate를 DB에 저장하기 위해서는 몇 가지 준비사항이 필요하다.

1. setting.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'elections',
]
python manage.py makemigrations 
# /migration/0001_initial.py 생성
#DB에 해야 될 작업을 정리한 상태라고 생각하면된다.
#Model에 정의한 것들을 DB에 적용할 때 어떻게 적용할지에 대한 내용이 들어가 있다.


python manage.py migrate # DB에 실제로 공간이 만들어진다.

Django Admin

사용자 생성

python manage.py createsuperuser

Admin 추가

elections/admin.py

from django.contrib import admin

from .models import Candidate


admin.site.register(Candidate)

models 에서 정의했던 Name, Introduction, Area, Party number 가 보이는 걸 확인할 수 있다.

안에 내용을 넣고 SAVE를 눌렀더니 둘 다 제목이 Candidate object로 나타나고 있다.

클래스는 표현하는 문자열을 정의할 때는 스트링 메소드를 오버라이딩 해주면 된다.

elections/models.py

from django.db import models

class Candidate(models.Model): # 장고의 모델로 동작하려면 models.Model 을 상속받아야 한다.
    name = models.CharField(max_length=10)
    introduction = models.TextField()
    area = models.CharField(max_length=15)
    party_number = models.IntegerField(default=1)

    def __str__(self):
        return self.name # 해당 클래스를 호출 할 땐 Candidate의 name으로 출력하라

후보자의 이름으로 변경되었다.

 

데이터 보여주기

from django.shortcuts import render
from django.http import HttpResponse

from .models import Candidate

def index(request):
    candidates = Candidate.objects.all() # candidate라는 변수는 모든 candidate의 로우를 가져와서 갖게 된다.
    str = ''
    for candidate in candidates:
        str += "<p>{} 기호{}번({})<br>".format(candidate.name,
            candidate.party_number,
            candidate.area)
        str += candidate.introduction+"</p>"
    return HttpResponse(str)

위 사진과 같이 출력되었다.

Candidate의 objects.all() 모든 데이터를 candidates 변수에 저장하고, str 변수를 정의해준다.

for candidate in candidates: 반복한다. candidate 가 candidates: 와 같을 때 까지 반복한다.

"{} 기호{}번({})" %d 와 같은거라고 생각하면 된다. 뒤에 .format에서 정의한 인자들이 앞에서부터 순서대로 들어간다.

마지막 str은 후보의 소개 부분이 들어가게되고, 해당 데이터들은 리턴되어 페이지에 표시되게 된다.

 

Django Shell

Shell 접속하기

python manage.py shell
C:\im\mysite> python manage.py shell

>>> from elections.models import Candidate # elections.models 에 Candidate를 가져온다.

>>> Candidate.objects.all() # Candidate의 모든 내용을 출력한다.
<QuerySet [<Candidate: 힐러리>, <Candidate: 트럼프>]>

>>> new_candidate = Candidate(name="루비오") # Candidate(name="루비오") new_candidate에 저장한다.
>>> new_candidate
<Candidate: 루비오>

>>> Candidate.objects.all() # 하지만 아직 저장이 안된 것을 확인할 수 있다.
<QuerySet [<Candidate: 힐러리>, <Candidate: 트럼프>]>

>>> new_candidate.save() # .save()를 이용하여 DB에 적용시키고
>>> Candidate.objects.all() # 다시 모든 데이터를 출력하면 루비오도 나오는 걸 확인할 수 있다.
<QuerySet [<Candidate: 힐러리>, <Candidate: 트럼프>, <Candidate: 루비오>]>

>>> no1 = Candidate.objects.filter(party_number = 1) # party_number 후보번호가 1번인 사람을 no1에 저장
>>> no1
<QuerySet [<Candidate: 힐러리>, <Candidate: 루비오>]> # party_number 의 default 값을 1로 주었기 때문에 루비오도 같이 출력

>>> no1[0]
<Candidate: 힐러리>
>>> no1[1]
<Candidate: 루비오> # no1 = {힐러리, 루비오} 로 구성되어 있음 index 0 = 힐러리, 1 = 루비오

>>> no1[0].party_number
1
>>> no1[0].name
'힐러리'
>>> no1[1].name
'루비오'

>>> no1[1].name
'루비오'
>>> no1[0].introduction
'미국최초의 여자 대통령이 되겠습니다.'

템플릿으로 html 불러오기

템플릿을 사용하려면 elections 아래 templates 폴더를 생성

템플릿들은 반드시 templates 아래 있어야 Django 에서 사용할 수 있다.

app이름과 동일한 elections 폴더를 생성해준다. 최종 경로는 아래와 같다.

elections/templates/elections/index.html

Django는 templates 폴더에 내용을 가져와서 한 곳에 모아두고 필요한 걸 찾아서 사용한다.

Django 프로젝트에는 여러 개의 앱이 있을 수 있으니 만약에 다른 앱에서도 index.html 을 사용하면 두 개가 겹친다.

그래서 각각의 앱 안에는 앱과 똑같은 이름을 갖는 폴더를 하나 더 만들어주는 것이다.

<!-- C\Code\mysite\elections\templates\elections\index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>선거 후보</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <table class="table table-striped">
        <thead>
        <tr>
            <td><B>이름</B></td>
            <td><B>소개</B></td>
            <td><B>출마지역</B></td>
            <td><B>기호</B></td>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td>가후보</td>
            <td>후보입니다.</td>
            <td>우리나라</td>
            <td>기호1번</td>
        </tr>
        <tr>
            <td>나후보</td>
            <td>후보입니다.</td>
            <td>우리나라</td>
            <td>기호2번</td>
        </tr>
        <tbody>
    </table>
</body>

elections/views.py

from django.shortcuts import render
from django.http import HttpResponse

from .models import Candidate

def index(request):
    candidates = Candidate.objects.all() # candidate라는 변수는 모든 candidate의 로우를 가져와서 갖게 된다.
    return render(request,'elections/index.html') 

[!] render() 함수는 request 객체를 첫 번째 인수를 받고, 템플릿 이름을 두 번째 인수로 받으며, context 사전형 객체를 세 번째 선택적 인수로 받습니다. 인수로 지정된 context로 표현된 템플릿의 Httpresponse 객체가 반환됩니다.

 

템플릿에 정보 채우기

index.html 에 candidate의 정보를 전달하고 그걸 활용해서 정보를 뿌려보는 걸 해보자.

 

elections/views.py

from django.shortcuts import render
from django.http import HttpResponse

from .models import Candidate

def index(request):
    candidates = Candidate.objects.all() # candidate라는 변수는 모든 candidate의 로우를 가져와서 갖게 된다.
    context = {'candidates':cnadidates} # DB에서 후보들을 가져와서 context에 넣어서
    return render(request,'elections/index.html', context) 
    # html 파일로 전달하는 것이다. html file에서는 context에 들어있는 condidate를 사용할 수 있다.

index.html

<!-- C\Code\mysite\elections\templates\elections\index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>선거 후보</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <table class="table table-striped">
        <thead>
        <tr>
            <td><B>이름</B></td>
            <td><B>소개</B></td>
            <td><B>출마지역</B></td>
            <td><B>기호</B></td>
        </tr>
        </thead>
        <tbody>
        {% for candidate in candidates %}    
        <tr>
            <td>{{candidate.name}}</td>
            <td>{{candidate.introduction}}</td>
            <td>{{candidate.area}}</td>
            <td>{{candidate.party_number}}번 </td>
        </tr>
        {% endfor %}
        <tbody>
    </table>
</body>

 

MVC 패턴

site에 접속을 하면 django는 가장먼저 mysite라는 폴더 안에 urls.py 라는 파일에 정의 된 urlpatterns를 보게 된다.

url(r'^', include('elections.urls')) elections 라는 폴더 안에 urls를 보면 url(r'^$', views.index), views.index로 연결이 된 걸 확인할 수 있다. 

 

views.py 를 보면 def index(request): 라는 함수가 있고, 그 안에 candidate라는 모델에서 데이터를 불러와서 데이터를 index.html 에게 전달해준다. 그리고 index.html 이 사용자에게 보여지게 되는 것이다.

 

1. views.py (조율)

candidate model 에서 데이터를 읽어와서 index.html 이라는 템플릿에 데이터를 전달하는 전체적인 동작을 조율하는 역할을 한다. 조율한다는 부분이 views.py 파일에 가장 중요한 파일이다.

 

2. models.py(데이터)

candidate 라는 class에 정의 된 형식대로 값이 DB에 저장되어있고, 또 candidate class를 이용해서 DB에서 값을 불러온다. candidate class 는 models.py 안에 정의가 되어있다. models.py 파일은 이렇게 데이터를 관리한다고 볼 수 있다.

 

3. templates(화면)

템플릿에 들어있는 index.html 파일이 화면에 그려지고 있다. templates 안에 있는 파일들은 화면에 어떻게 보일지를 결정하는 역할을 한다.

MVC 패턴

이런 MVC 패턴은 자주 접하게 되는 패턴이기 때문에 프로그램을 MVC 로 나눠서 생각하는게 익숙해지면 좋다.

 

하지만 장고의 MVC는 좀 특이하다. Controller에 해당하는 파일의 이름이 views.py이다. 그리고 View의 해당하는 부분은 templates라고 부른다. model만 models.py 라는 파일에서 관리를 한다. 많은 사람들이 이 부분을 이상하게 생각한다.

 

여론 조사 모델

models.py 에는 모델 클래스를 여러 개 정의할 수 있고, 모델 간의 관계를 나타낼 수 있습니다.

 

DB 설계

elections/models.py

from django.db import models

class Candidate(models.Model): # 장고의 모델로 동작하려면 models.Model 을 상속받아야 한다.
    name = models.CharField(max_length=10)
    introduction = models.TextField()
    area = models.CharField(max_length=15)
    party_number = models.IntegerField(default=1)

    def __str__(self):
        return self.name # 해당 클래스를 호출 할 땐 Candidate의 name으로 출력하라

class Poll(models.Model):
    start_date = models.DeteTimeField()
    end_date = modesls.DateTimeField()
    area = models.CharField(max_length=15)
    # 여론조사 id 는 모델을 만들면 자동으로 생기기 때문에 따로 지정하진 않는다.

class Choice(models.Model):
    poll = models.ForeignKey(Poll) # Poll이라는 모델을 활용해서 가져온다. 외래키
    candidate = models.ForeignKey(Candidate) # 이미있는 Candidate 모델을 사용
    votes = models.IntegerField(default = 0) # 득표 수 기본 값 0

이제 어드민에서 Poll에 데이터를 추가해보자. 그 전에 admin.py 파일에 Poll을 등록하자.

그래야 어드민에서 Poll이라는 모델을 사용할 수 있다. import 또한 추가해주자.

 

elections/admin.py

from django.contrib import admin

from .models import Candidate, Poll

# Register your models here.

admin.site.register(Candidate)
admin.site.register(Poll)

Polls 를 누르면 위와 같은 오류가 발생한다. candidate 모델을 준비할 때를 생각해보면 Model만 만든다고 끝이 아니라 DB에서 받아들일 수 있도록 준비가 필요했다. makimigration 와 migrate를 해줘야 한다.

이 과정은 powerShell에서 실행해보자.

 

python manage.py makemigrations # elections/migrations 아래 0002_ 파일 생성

python manage.py migrate # migration 파일이 DB에 반영되도록 타이핑

위와 같이 DB를 설정해주고, admin 안에 Polls를 눌러보면 제대로 실행되는 걸 확인할 수 있다.

SAVE를 눌러주면 Poll object가 생성된다.

Poll object라는 이름을 변경해주고 싶으면  def __str__(self): 을 사용해 return 값으로 원하는 값을 return 해주면 된다.

 

URL 다루기

지난 시간에 만든 Poll과 Choice 모델의 페이지를 준비해보겠다.

미국을 누르면 해당 지역구에 여론조사 페이지에 이동되도록 링크를 만들어보겠다.

링크를 만들려면 templates/elections/index.html 파일을 열어서

<td><a href="areas/{{candidate.area}}/">
	{{candidate.area}}</td></a>
# 와 작성해주면 미국에 링크가 걸린 걸 확인할 수 있다.

localhost:8000/areas/미국 로 링크는 제대로 잡히지만 에러가 발생한다. 왜냐하면 저런 url을 처리할 수 있는 URL이 정의되어 있지 않기 때문에 urls.py 를 설정해줘야 한다.

 

elections/urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index),
    url(r'^areas/(?P<area>.+)/$', views.areas)
]

elections/views.py

def areas(request, area) : #어떤 지역인지를 매개변수 area로 받습니다.
    return HttpResponse(area)
    

areas/한국 하면 화면에 한국이 출력되고, 다른 값들을 넣으면 다른 값들이 출력된다 왜 그런지 아래 코드를 통해 확인

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index),
    url(r'^areas/(?P<area>.+)/$', views.areas) 
    # views.areas 라는 함수에 area라는 매개변수를 전달하고 싶다면 그 변수 명을 < > 안에 적어준다.
    # 그렇게 되면 /(?P<area>.+) 부분이 area라는 변수로 views.areas에 전달이 된다.
    # 어떤 URL 형식을 허용할 지를 area> 다음부분에 적어주는데 .+는 어떤 URL도 허용한다는 의미
	# 그래서 영어로 적어도되고 한글로 적어도 된다. 하지만 숫자만 허용하고 싶다면 \d+ 을 적어준다.
    # (?<P<area>\d+) 숫자만 입력가능하다, 다른 걸 입력하면 오류페이지를 띄운다.
    
    # 이렇게 전달하고 싶은 변수명과 허용한 형식을 적고 전체적으로 (?P ) 하면 이 URL에 대해서
    # views.areas로 원하는 형식으로 전달이 되는 것이다. URL에 어떤 형식을 허용할지 .+나 \d+ 같은 것을
    # 설정하는 방법을 더 자세히 알아보고 싶다면 정규표현식이라는 걸 공부하면 좋다.
    # https://opentutorials.org/module/622/5143
]
def areas(request, area): # 미국이라거나 한국이라는 그런 부분을 area라는 매개변수로 받을 수 있었던 것이다.
	return HttpResponse(area)

url의 첫 번째 인자 - r'^areas/(?P<area>.+)/$'

  • Django 에서 url의 첫 번째 인자는 보통 r'^.../...$' 와 같은 형태를 띄우고 있다.
  • ^ : 문자열의 시작을 알린다.
  • areas : 문자 그대로 스트링 areas이다
  • $ : 문자열의 끝을 알린다.
  • (?<name>...) : symbolic 그룹 이름 name 으로 그룹과 매칭 되는 부분 문자열에 접근이 가능합니다. (?P<name>.+)의 경우
    • group 이름은 area( 부등호<> 로 감싸진 부분)이며, 이를 통해 views.areas 문자열에 전달한다.
    • .+는 개행 문자를 제외한 모든 문자를 의미한다.
    • \d+는 숫자만 입력이 가능하게 끔 한다.

여론조사 화면 구현

area.html

<!-- \mysite\templates\elections\area.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>지역구</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>지역구</h1>
<br>
    <table class="table table-striped">
        <thead>
        <tr>
            <td><B>이름</B></td>
            <td><B>소개</B></td>
            <td><B>기호</B></td>
            <td><B>지지하기</B></td>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td> 후보1</td>
            <td> 후보소개 </td>
            <td> 기호1번 </td>
            <td>
                <form action = "#" method="post">
                    <button name="choice" value="#">선택</button>
                </form>
            </td>
        </tr>
        <tr>
            <td> 후보2</td>
            <td> 후보소개 </td>
            <td> 기호2번 </td>
            <td>
                <form action = "#" method="post">
                    <button name="choice" value="#">선택</button>
                </form>
            </td>
        </tr>       
        </tbody>
    </table>
</div>
</body>

elections/views.py

def areas(request, area): 
	return render(request, 'elections/area.html')

빨간색 원인 area는 Candidate model 안에 정의 된 area를 의미하고, 파란 원은 매개변수로 받아온 area를 의미한다. Candidate에서 area가 매개변수로 받아온 값과 같은 값만 필터해서 가져와라

elections/views.py

def areas(request, area) : #어떤 지역인지를 매개변수 area로 받습니다.
    candidates = Candidate.objects.filter(area = area)
    context = {'candidates' : candidates,
    'area' : area}
    return render(request, 'elections/area.html', context)
    # 후보에 대한 정보와 지역구에 대한 정보가 area.html 에 전달된다.

templates/elections/area.html

<!-- \mysite\templates\elections\area.html -->
<!DOCTYPE html>
<html lang="en">
<head> 
  <title>{{area}}</title> #
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>{{area}}</h1> #
<br>
    <table class="table table-striped">
        <thead>
        <tr>
            <td><B>이름</B></td>
            <td><B>소개</B></td>
            <td><B>기호</B></td>
            <td><B>지지하기</B></td>
        </tr>
        </thead>
        {% for candidate in candidates %} #
        <tbody>
        <tr>
            <td> {{candidate.name}}</td> #
            <td> {{candidate.introduction}} </td> #
            <td> 기호{{candidate.party_number}}번 </td> # 
            <td>
                <form action = "#" method="post">
                    <button name="choice" value="#">선택</button>
                </form>
            </td>
        </tr>
        {% endfor %} #
        </tbody>
    </table>
</div>
</body>

해당 지역구의 현재 진행중인 for 이 있는지 확인하고, 해당 for을 이용해서 데이터를 저장해야 한다.

지금부터 그 부분을 만들어보자.

 

views.py

from django.shortcuts import render
from django.http import HttpResponse

from .models import Candidate, Poll, Choice
import datetime # 진행 중인 Poll이 있는지는 날짜를 통해 확인해야 함으로 datetime

def index(request):
    candidates = Candidate.objects.all() # candidate라는 변수는 모든 candidate의 로우를 가져와서 갖게 된다.
    context = {'candidates':candidates} # DB에서 후보들을 가져와서 context에 넣어서
    return render(request,'elections/index.html', context) 
    # html 파일로 전달하는 것이다. html file에서는 context에 들어있는 condidate를 사용할 수 있다.

def areas(request, area):
    today = datetime.datetime.now()
    try :
        poll = Poll.objects.get(area = area, start_date__lte = today, end_date__gte=today) # get에 인자로 조건을 전달해줍니다. 
        candidates = Candidate.objects.filter(area = area) # Candidate의 area와 매개변수 area가 같은 객체만 불러오기
    except:
        poll = None
        candidates = None
    context = {'candidates': candidates,
    'area' : area,
    'poll' : poll }
    return render(request, 'elections/area.html', context)
    

templates/elections/area.html

<!-- C:\Code\mysite\templates\elections\area.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>{{area}}</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>{{area}}</h1>
<br>
{% if poll %}
    <table class="table table-striped">
        <thead>
        <tr>
            <td><B>이름</B></td>
            <td><B>소개</B></td>
            <td><B>기호</B></td>
            <td><B>지지하기</B></td>
        </tr>
        </thead>
        <tbody>
        {% for candidate in candidates %}
        <tr>
            <td> {{candidate.name}}</td>
            <td> {{candidate.introduction}}</td>
            <td> 기호{{candidate.party_number}}번 </td>
            <td>
                <form action = "#" method = "post">
                    <button name="choice" value="#">선택</button>
                </form>
            </td>
        </tr>
        {% endfor %}
        <tbody>
    </table>
{% else %}
여론조사가 없습니다
{% endif %}
</div>
</body>

여론조사 결과 저장

투표결과를 DB저장하는 부분을 생성해보자

지금은 여론조사에서 선택을 누르면 오류페이지가 나온다. 선택 버튼을 눌렀을 때 서버로 전송되도록 수정을 해보자.

<form action = "/polls/{{poll.id}}/" method = "post">
    <button name="choice" value="#">선택</button>
</form>

# action = "/polls/{{poll.id}}/"  : 선택의 결과가 전달될 url
# poll은 areas() 에서 context로 전달하고 있다. 그 poll의 id를 URL에 포함해서 전달하는 것이고,
# urls.py views.py 에서 받아서 처리할수 있도록 구현해주면 된다.

# values = "candidate.id" : 선택한 후보의 id값 : 전달할 결과
# candidate은 areas() 에서 context로 전달하고 있다.

이제 URL들을 받을 수 있게 urls.py 를 수정해주어야 한다.

urls.py

url(r'^polls/(?P<poll_id>\d+)/$', views.polls) # 추가, views.polls가 아직 정의가 안되어서 정의해줘야한다.

views.py

def polls(request, poll_id):
    poll = Poll.ojects.get(pk=poll_id) # poll 객체를 구분하는 주요키가 poll id 인 객체를 불러와서 poll 에게 전달해준다.
    selection = request.POST['choice'] # request.POST를 통해 사용자가 선택한 부분도 받아와야 한다. 

    choice = Choice.objects.get(poll_id = poll_id,
        candidate_id = selection) # 사용자에 초이스 모델을 불러와서 거기에 투표를 한거니 숫자를 1 증가시켜 준다.
    choice.votes += 1
    choice.save()

    return HttpResponse("finish")

위와 같이 입력하면 csrf 관련 오류가 발생한다.

area.html

<form action = "/polls/{{poll.id}}/" method = "post">
    {% csrf_token %} # 추가
    <button name="choice" value="{{candidate.id}}}">선택</button>
</form>

csrf 토큰은 아무나 post를 할 수 없도록 토큰을 발급하고, 그 토큰을 갖고 있는 클라이언트만 서버로 POST를 할 수 있도록 하는 방법이다. 위와 같이 수정하였더니 DoesNotExist 에러가 발생하였고, choice를 get하는 부분에서 에러가 발생한 걸 확인할 수 있다.  투표를 최초로한다면 DB에 아직 Choice 객체가 저장이 안되어있을 수 있다.

이 부분은 예외처리를 해주면 된다.

try: 
    choice = Choice.objects.get(poll_id = poll.id, candidate_id = selection)
    choice.votes += 1
    choice.save()
    
except:
# 최초로 투표하는 경우, DB에 저장된 Choice객체가 없기 때문에 Choice를 새로 생성합니다
# 예외가 발생한다면 choice를 새로 만들어주어야 한다.

choice = Choice(poll_id = poll.id, candidate_id = selection, votes = 1) # 새로 생성, 최초의 투표라면 votes를 1로
choice.save() # 저장 

finish라고 출력이 되면 저장이 되었는지 확인을 하면 된다. 이 부분은 admin에서 진행을 하도록 하겠다.

admin.py

from django.contrib import admin

from .models import Candidate, Poll, Choice

# Register your models here.

admin.site.register(Candidate)
admin.site.register(Poll)
admin.site.register(Choice)

지지하기에 선택 버튼을 누르면 Votes 가 ++ 된다.

좀 더 편리하게 보게끔 Choice object가 아닌 해당 object의 후보이름을 넣고 싶으면 아래와 같이 models.py에 추가해주면 된다.

class Choice(models.Model):
    poll = models.ForeignKey(Poll) # Poll이라는 모델을 활용해서 가져온다. 외래키
    candidate = models.ForeignKey(Candidate) # 이미있는 Candidate 모델을 사용
    votes = models.IntegerField(default=0) # 득표 수 기본 값 0
    
    def __str__(self):
        return self.candidate

여기서 주의해야 할 점은 Choice 안에 정의 된 것들이여야 된다는거다. ex) poll, candidate, votes

 

여론조사 결과보기1 - http redirect하기

결과를 보기 화면에 표시하기 위해 result.html 을 하나 작성해준다.

<!-- C:\Code\mysite\elections\templates\elections\result.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>지역구 여론조사 결과</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>지역구</h1>
<br>
<table class="table table-striped">
    <thead>
    <tr>
        <td><B>기간</B></td>
        <td><B>후보1</B></td>
        <td><B>후보2</B></td>        
    </tr>
    </thead>
    <tbody>
    <tr>
        <td> 기간1 </td>
        <td> 후보1 지지율</td>
        <td> 후보2 지지율</td>
    </tr>    
    <tbody>
</table>
</div>
</body>

그리고 urls.py 파일을 수정해서 :8000/areas/한글/result 한글이 아니라 다른 문자를 입력하면 에러발생하게 설정하자

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index),
    url(r'^areas/(?P<area>[가-힣]+)/$', views.areas), # :8000/areas/한글/result 한글이 아니라 다른 문자를 입력하면 에러발생
    url(r'^areas/(?P<area>[가-힣]+)/result$', views.results), # 결과 페이지 추가
    url(r'^polls/(?P<poll_id>\d+)/$', views.polls),
]

views 로 이동해서 Redirect에 대해 설정해주자

from django.http import HttpResponse, HttpResponseRedirect # HttpResponseRedirect 가져와 사용할 수 있게 정의

return HttpResponseRedirect("/areas/{}/results".format(poll.area))
# 어디로 redirect 할지 정해준다. "/areas/{}/results"
# 중간 {} 에는 지역구를 지정해준다.
# 중간 {} 에는 format으로 지정 된 (poll.area) 가 들어가게 된다.

이제 views.results 를 구현해보자.

#url(r'^areas/(?P<area>[가-힣]+)/result$', views.results),
#area를 중간에 URL로 받아들여서 매개변수로 전달해주는 것이다.

def results(request, area):
    return render(request, 'elections/result.html')

여론조사 결과보기2 - 후보 표시하기

동그라미 친 부분들을 DB에서 읽어와서 안에 값이 들어가게 끔 설정해보자.

 

views.py

def results(request, area):
    candidates = Candidate.objects.filter(area=area) # 이 지역구에 해당하는 후보들만 불러온다.
    context = {'candidates' : candidates, 'area' : area}
    return render(request, 'elections/result.html', context) # context가 없다면 데이터 값을 html 에서 나타낼 수 없다.

result.html

<!-- C:\Code\mysite\elections\templates\elections\result.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>{{area}} 여론조사 결과</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>{{지역구}} 여론조사 결과</h1>
<br>
<table class="table table-striped">
    <thead>
    <tr>
        <td><B>기간</B></td>
        {% for candidate in candidates %}
        <td><B>{{candidate.name}}</B></td>
        {% endfor %}
    </tr>
    </thead>
    <tbody>
    <tr>
        <td> 기간1 </td>
        <td> 후보1 지지율</td>
        <td> 후보2 지지율</td>
    </tr>    
    <tbody>
</table>
</div>
</body>

후보이름이 잘 출력되는 걸 확인할 수 있다.

다음은 기간에 데이터가 들어가게 끔 해보자.

views.py

def results(request, area):
    candidates = Candidate.objects.filter(area=area) # 이 지역구에 해당하는 후보들만 불러온다.
    
    polls = Poll.objects.filter(area = area)
    poll_results = []
    for poll in polls:
        result = {} # 딕셔너리 구성
        result['start_date'] = poll.start_date # 시작일
        result['end_date'] = poll.end_date # 종료일

        poll_results.append(result) # poll_results에 append 추가한다 result를
    
    context = {'candidates' : candidates, 'area' : area
    ,'poll_results' : poll_results}
    return render(request, 'elections/result.html', context)

result.html

<!-- C:\Code\mysite\elections\templates\elections\result.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>{{area}} 여론조사 결과</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>{{지역구}} 여론조사 결과</h1>
<br>
<table class="table table-striped">
    <thead>
    <tr>
        <td><B>기간</B></td>
        {% for candidate in candidates %}
        <td><B>{{candidate.name}}</B></td>
        {% endfor %}
    </tr>
    </thead>
    <tbody>
    {% for result in poll_results %}
    <tr>
        <td> {{result.start_date.year}}/{{result.start_date.month}}/
            {{result.start_date.day}} ~ {{result.end_date.year}}/{{result.end_date.month}}/{{result.end_date.day}}</td>
        <td> 후보1 지지율</td>
        <td> 후보2 지지율</td>
    </tr>
    {% endfor %}
    <tbody>
</table>
</div>
</body>

여론조사 결과보기3 - Dictionary로 데이터 정리

이제 지지율 정보를 채워보도록 하겠습니다.

def results(request, area):
    candidates = Candidate.objects.filter(area = area)
    polls = Poll.objects.filter(area = area)
    poll_results = []
    for poll in polls:
        result = {}
        result['start_date'] = poll.start_date
        result['end_date'] = poll.end_date

        # poll.id에 해당하는 전체 투표수
        total_votes = Choice.objects.filter(poll_id = poll.id).aggregate(Sum('votes'))
        result['total_votes'] = total_votes['votes__sum']

        rates = [] #지지율
        for candidate in candidates:
            # choice가 하나도 없는 경우 - 예외처리로 0을 append
            try:
                choice = Choice.objects.get(poll = poll, candidate = candidate)
                rates.append(
                    round(choice.votes * 100 / result['total_votes'], 1)
                    )
            except :
                rates.append(0)
        result['rates'] = rates
        poll_results.append(result)

    context = {'candidates':candidates, 'area':area,
    'poll_results' : poll_results}
    return render(request, 'elections/result.html', context)

result.html

<!-- C:\Code\mysite\elections\templates\elections\result.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>{{area}} 여론조사 결과</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h1>{{area}}</h1>
<br>
<table class="table table-striped">
    <thead>
    <tr>
        <td><B>기간</B></td>
        {% for candidate in candidates %}
        <td><B>{{candidate.name}}</B></td>
        {% endfor %}
    </tr>
    </thead>
    <tbody>
    {% for result in poll_results %}
    <tr>
        <td> {{result.start_date.year}}/{{result.start_date.month}}/{{result.start_date.day}}~{{result.end_date.year}}/{{result.end_date.month}}/{{result.end_date.day}} </td>
        {% for rate in result.rates %}
        <td> {{rate}}%</td>
        {% endfor %}
    </tr>    
    <tbody>
    {% endfor %}
</table>
</div>
</body>

'Web > Django' 카테고리의 다른 글

Django / 복습(book)  (0) 2019.07.05
Django / 복습(AWS)  (0) 2019.07.05
Django / 복습  (0) 2019.07.01
Django / form  (0) 2019.07.01
Django / 프로그램 애플리케이션 확장  (0) 2019.06.30

댓글

💲 추천 글