본문 바로가기

프로젝트/파이썬 장고를 이용한 웹페이지 만들기

7. 튜토리얼 따라하기 - 설문조사(2)

저번 시간에는 설문조사 앱의 메인 페이지만 만들었다.

이번 시간에는 다음의 여러가지 뷰를 더 생성할 것이다.

  • 투표 목록 : 등록된 투표의 목록을 표시하고 상세 페이지로 이동하는 링크 제공
  • 투표 상세 : 투표의 상세 항목을 보여줌
  • 투표 기능 : 선택한 답변을 반영
  • 투표 결과 : 선택한 답변을 반영한 후 결과를 보여줌

이제 위의 네가지 뷰를 만들어보자.

 

여러가지 뷰 생성하기

polls/views.py 를 다음과 같이 수정한다.

def detail(request, question_id):
    return HttpResponse("You're looking at question %s" % question_id)


def results(request, question_id):
    response = "You're 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)

이제 이 뷰가 동작하도록 URL을 연결해주자. polls/urls.py를 다음과 같이 수정한다.

urlpatterns = [
    path('', views.index, name='index'),  # ex : /polls/
    path('<int:question_id>', views.detail, name='detail'),  # /polls/5
    path('<int:question_id>/results/', views.results, name='results'),  # /polls/5/results/
    path('<int:question_id>/vote/', views.vote, name='vote')   # /polls/5/vote/
]

사용자가 웹사이트의 페이지를 요청할 때, django는 myproject.urls의 파이썬 모듈을 불러오게 된다. ROOT_URLCONF 설정에서 해당 모듈을 바라보도록 지정되어 있기 때문이다. myproject.urls에서 urlpatterns라는 변수를 찾고, 순서대로 패턴을 따라간다.

1. '/polls/34/'를 요청한다

2. 'polls/'를 찾은 후에, 일치하는 텍스트 "polls/"를 버리고, 남은 텍스트인 "34/"를 'polls.urls'URLconf로 전달한다.

3. <int:question_id>와 일치하여, 결과적으로 detail() 뷰 함수가 호출된다.

 

추가한 3개의 뷰(detail, results, vote)를 위한 URL을 연결했다. index 뷰와는 달리 특이한 점을 발견할 수 있다.

각 URL에 있는 <>(화살괄호)는 변수를 의미한다. 이 부분에 해당하는 값을 뷰 함수에 인자로 전달한다.

예를 들면, 우리는 위에서 <int:question_id>로 지정을 했는데, :question_id> 는 일치되는 패턴을 구별하기 위해 정의한 이름이며, <int: 는 어느 패턴이 해당 URL 경로에 일치되어야 하는지 결정하는 컨버터이다.

 

이제 실제 동작되는 뷰를 만들어보자. polls/views.py를 다음과 같이 수정하자.

from .models import Question

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)

수정을 끝냈으면 화면을 확인해보자. 메인 화면에 투표 목록이 나타난다. 아직은 투표가 한개뿐이라 아래와 같이 하나만 보인다.

 

/polls/ 확인

기능이 있는 뷰를 만들었지만 MTV패턴에는 따르지 않은 모습니다. 템플릿을 만들어 파있헌 코드와 HTML 코드를 분리해야 한다.

템플릿 파일을 만들기 위해서 새로운 폴더를 만들자. polls 폴더 아래에 directory를 하나 만들자.

폴더 이름은 templates라고 입력하고, templates 폴더에 다시 polls 라는 폴더를 추가한다.

templates 폴더 추가

이제 polls/templates/polls/ 하위에 HTML 파일을 추가해보자. 파일이름은 index로 지정한다.

index.html 추가

이제 body 태그 안에 템플릿 코드를 입력하자. polls/templates/polls/index.html을 다음과 같이 수정하자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
{% if latest_question_list %}
    <ul>
        {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a> </li>
        {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
</body>
</html>

만든 템플릿을 이용하도록 뷰를 변경하자. 템플릿을 불러오기 위해 [loader]를 임포트를 하고 index 뷰의 내용을 다음 코드를 참조해 변경하자.

polls/views.py를 다음과 같이 변경하자.

from django.http import HttpResponse
from django.template import loader
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))

loader를 이용해 index.html를 불러오고 여기에 미리 만들어 둔 투표 목록을 context라는 변수를 이용해 전달한다. 하지만 이런 절차가 약간은 불편할 것이다. 그래서 장고에는 render라는 단축함수가 존재한다.(views.py 위쪽에 이미 임포트 되어있음) 이 함수를 이용해 코드를 변경하자.

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)

render 메서드는 request와 템플릿 이름 그리고 사전형 객체를 인자로 받는다. 여기서 사전형 객체(context 변수)는 템플릿에서 사용할 변수들이다. 이제 투표 메인 페이지에 다시 접근해보자.

template이 반영된 메인 화면

처음 메인 페이지와는 달라진 것을 볼 수 있을 것이다. 이제 다른 뷰들도 변경보자.

 

404 오류 일으키키

404  오류는 웹 서비스에서 자주 볼 수 있는 오류이다. 파일이 존재하지 않을 때 발생하는 오류로, 게시판 등 정보를 불러오는 페이지의 경우 해당 데이터가 존재하지 않는다는 의미로 사용한다.

다음 코드와 같이 polls/views.py파일을 다음과 같이 수정하여 detail 뷰를 변경해보자.

from django.http import Http404

(중략)

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

* Question.objects.get(pk=question_id)에서 pk는 무엇일까?

pk는 데이터베이스의 각 레코드를 식별하는 기본키, 즉 primary key 의 줄임말이다.

같은 클래스로부터 객체들이 생성되는데, 그것들을 구분하기 위한 인식표라고 생각하면 된다.

Question 모델을 살펴보면, 컬럼 생성 시 기본키를 지정하지 않았기 때문에 장고는 pk라는 필드를 추가해 줘야 한다.

Http404를 이용하면 상세 정보를 불러올 수 있는 투표 항목이 없을 경우 404 오류를 발생시킬 수 있다. 이전의 index 뷰와 마찬가지로 detail 뷰에서 detail.html이라는 템플릿을 사용하는 것을 볼 수 있다. detail.html 파일을 만들고 body 태그 안에 다음 코드를 입력한다.

 

polls/templates/polls/detail.html 파일을 생성하고 body 태그 안에 다음 코드를 입력한다.

{{ question }}

Http404를 처리할 때는 loader-render의 관계처럼 단축 함수가 존재한다. 바로 get_object_or_404이다. 이 함수를 사용하여 detail 뷰를 수정한다.

get_object_or_404() 함수는 django 모델을 첫번째 인자로 받고 나머지 키워드 인수를 모델 관리자의 get() 함수에 넘긴다. 만약, 객체가 존재하지 않는 경우 Http404 예외가 발생한다.

 

polls/views.py를 다음과 같이 수정한다.

from django.shortcuts import render, get_object_or_404

(중략)

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

render 옆에 get_object_or_404를 추가해 임포트 한다. 그리고 detail 뷰에서 try-except 문을 없애고 get_object_or_404를 이용해 코드를 간소화 한다.

 

마지막으로, polls/templates/polls/detail.html 템플릿에 내용을 추가한다. body 태그 안의 내용을 다음과 같이 수정한다.

    <h1>{{ question.question_text }}</h1>
    <ul>
        {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}</li>
        {% endfor %}
    </ul>

하드코딩된 URL 없애기

index.html 파일을 살펴보면 상세 페이지로 이동하기 위한 링크의 주소가 하드 코딩되어 있는 것을 알 수 있다.

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a> </li>

위의 코드 처럼 href 속성의 값을 직접 써주는 방식으로 해두면 나중에 주소를 polls가 아닌 다른 형태로 바꿀 때, 관련된모든 html를 직접 다 변경해야하는 불편함이 있다. 때문에 url 템플릿 태그를 사용해 하드 코딩된 URL을 없애 보자.

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a> </li>

url 템플릿 태그를 사용해 주소를 만들어 출력하는 방식이다. url 템플릿 태그는 URL의 이름을 필수 인자로 전달받는다. detail이라는 이름을 가진 URL 형식을 찾아서 URL을 만들어 출력하는 것이다. 해당 이름을 가진 URL은 urls.py 전체를 검색해 찾는다.

 

URL 네임스페이스 설정하기

네임스페이스(Namespace)는 프로그래밍 용어 중 하나다. 분리된 경로를 만드는 개념인데, 예를 들어 detail라는 주소 이름을 가진 뷰가 polls에도 있고 다른 앱에도 있는 경우 장고는 어느 뷰의 URL을 만들지 알 수가 없다. 이런 경우 네임스페이스를 설정해 각각의 뷰가 어느 앱에 속한 것인지 구분하도록 할 수 있다. 필수로 설정할 필요는 없지만 프로젝트가 복잡해질수록 네임스페이스가 있는 것이 편리하다.

 

네임스페이스는 urls.py에 설정한다. polls/urls.py를 다음과 같이 수정한다. app_name만 추가해주면 된다.

app_name = 'polls'

urlpatterns = [
    path('', views.index, name='index'),  # ex : /polls/
    path('<int:question_id>', views.detail, name='detail'),  # /polls/5
    path('<int:question_id>/results/', views.results, name='results'),  # /polls/5/results/
    path('<int:question_id>/vote/', views.vote, name='vote')   # /polls/5/vote/
]

이렇게 네임스페이스 설정은 끝이다. 이 네임스페이스를 사용하기 위해서 템플릿에도 수정을 해야 한다.

polls/templates/polls/index.html을 다음과 같이 수정한다.

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a> </li>

URL 이름은 detail 앞에 [polls:]라고 네임스페이스를 추가하면 사용할 수 있다.

 

이렇게 오늘은 여러가지 뷰를 추가하고, 404 오류도 설정했으며, 네임스페이스까지 설정해봤다.

 

다음 포스터에는 투표 기능이 동작하도록 뷰를 변경해보겠다.