IT/WEB

[Django] 튜토리얼 ⑤ : View추가 및 에러 일으키기

개발자 두더지 2020. 9. 9. 00:19
728x90

 만들고 있는 poll 어플리케이션에 네 개의 View를 추가하고 간단히 템플릿 시스템을 이용해 볼 것이고, 마지막으로 404에러를 일으켜 볼 것이다.

 

뷰 추가하기


이번에 추가 할 뷰는 총 네가지 이다.

① 질문 "색인" 페이지 : 최근의 질문들을 표시한다.

② 질문 "세부" 페이지 : 질문 내용과 투표할 수 있는 서식을 표시한다.

③ 질문 "결과" 페이지 : 특정 질문에 대한 결과를 표시한다.

④ 투표 기능 : 질문에 대해 선택을 할 수 있는 투표 기능을 제공한다.

# 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 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)

 이전 포스팅에서 설명했듯이 URLconf를 통해 URL패턴을 뷰에 연결하는 작업이 필요하다. 따라서 다음에는 path()함수를 이용해 호출을 추가하여 작성한 새로운 뷰를 polls.urls모듈에 연결한다. 

# 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/"를 해보면 아래와 같이 detail()함수를 호출하여 URL에 입력한 ID를 출력할 것이다. 

 "/polls/34/results/"와 "/polls/34/vote/"도 실행해보면 투표 결과와 투표 페이지의 기초가 되는 페이지가 출력 될 것이다. 

 동작방식은 다음과 같다. 위의 예를 이용하자면, 사용자가 "polls/34/"를 요청했다고 한다면, 장고는 mysite.urls 파이썬 모듈을 불러온다. 앞서 했던 ROOT_URLCONF 설정에 의해 해당 모듈을 바라보도록 지정되어 있기 때문이다. mysite.urls에서 urlpatterns라는 변수를 착고 순서대로 패턴을 살펴본다. 'polls/' 패턴을 찾은 후에, 일치하는 텍스트인 "polls/"부분을 잘라버리고, 남은 텍스트인 "34/"를 'polls.urls' URLconf로 전달하여 남은 처리를 진행한다. 여기서는 '<int:question_id>/'와 일치하며 결과적으로 detail() 뷰 함수가 호출된다.

detail(request=<HttpRequest object>, question_id=34)

question_id = 34 부분은 <int:question_id>에서 왔다. 괄호를 사용하여 URL의 일부를 "캡쳐"하고, 해당 내용을 keyword인수로서 뷰 함수로 전달한다. 문자열의: question_id 부분은 일치되는 패턴을 구별하기 위해 정의한 이름이며, <int: 부분은 어느 패턴이 해당 URL 경로에 일치되어야 하는 지를 결정하는 컨버터이다.

 

뷰의 동작 구현하기


각 뷰는 두 가지 동작 중 하나를 하도록 되어있다. 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하거나, 혹은 Http404같은 예외를 발생하게 해야한다. 즉 장고에게 필요한 것은 HttpResponse 객체 혹은 예외뿐이다.

 작성한 뷰는 데이터베이스의 레코드를 일을 수도 있다. 또한 뷰는 장고나 python에서 서드파티로 제공되는 템플릿 시스템을 사용할 수도 있다. 뷰를 통해 pdf파일을 생성하거나, xml을 출력하거나, 실시간으로 zip파일을 만들 수도 있다. 즉 원하는 python의 어떠한 라이브러리라도 사용할 수 있다.

 이전 예제에서 다룬 장고 자체 데이터베이스 API를 사용해보자. 새로운 index()뷰 하나를 호출했을 때, 시스템에 저장된 최소한 5개의 투표 질문이 콤마로 분리되어, 발행일에 따라 출력되도록 아래와 같이 polls/views.py에서 index()함수 부분을 변경해보자.

# polls/views.py

from django.http import HttpResponse

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)

# Leave the rest of the views (detail, results, vote) unchanged

 

템플릿(templates) 작성하기


 그러나 여기서 끝이 아니다. 이 뷰에서 페이지의 디자인이 하드코딩 되어 있다고 가정한다면 디자인을 위해 이 python 코드를 수시로 편집해야한다는 문제점이 있다. 따라서 python코드 파일과 디자인 파일을 분리하도록 장고의 템플릿 시스템을 사용한다.

 우선 polls 디렉토리에 templates라는 디렉토리를 만든다. 프로젝트의 setting.py의 TEMPLATES부분은 장고가 어떻게 템플릿을 불러오고 렌더링하는지 설정하는 부분이 된다. 기본 설정 파일은 APP_DIRS 옵션이 True로 설정된 DjangoTEmplates백엔드를 구성한다. 관례에 따라, DjangoTemplates는 각 INSTALLED_APPS 디렉토리의 "templates" 하위 디렉토리를 탐색하는 방식으로 이루어진다.

 우선 작성한 templates디렉토리에 다시 polls 디렉토리를 만들고 index.html 파일을 작성하여에 다음과 같은 코드를 입력한다. 

# polls/templates/polls/index.html

{% 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 %}

 이제 템플릿을 적용하고 polls/views.py에 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))

 위의 코드는 polls/index.html 템플릿을 불러 온 후, context를 전달한다. context는 템플릿에서 쓰이는 변수명과 Python 객체를 연결하는 사전형 값이다. 

 웹 브라우저에 "/polls/"페이지를 불러오면 이전에 작성한 "What's up" 질문이 포함된 질문 리스트가 표시된다. 각 질문 링크는 해당 질문에 대한 세부 페이지를 가리키고 있다.

 템플릿에 context를 채워넣어 표현한 결과를 HttpResponse객체와 함께 돌려주는 구문은 자주 쓰이는 용법이므로 장고에서는 단축 기능(shortcuts)을 제공하고 있다. 그것은 바로 render()를 사용하는 것이다. index()뷰를 단축 기능으로 작성하면 다음과 같다. 

# polls/views.py

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)

 모든 뷰에 적용한다면 더 이상 loader와 HttpResponse를 임포트하지 않아도 된다 (그러나 만약 detail, results, vote에서 stub메소드를 가지고 있다면, HttpResponse를 유지해야 할 것이다).

 render()함수는 request 객체를 첫 번째 인수로 받고, 템플릿 이름을 두 번째 인수로 받으며, context 사전형 객체를 세번째 옵션 인수로 받는다. 인수로 지정된 context로 표현된 템플릿의 HttpResponse 객체가 반환된다.

 

404 에러 일으키기


 원래의 상세 페이지는 지정된 설문조사의 질문 내용을 보여준다. 이러한 상세 페이지에 억지로 에러를 일으켜보도록 하겠다. 다음과 같이 detail() 함수 코드를 수정하면 된다.

#polls/views.py

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)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

 위의 코드를 살펴보면 뷰는 요청된 질문의 ID가 없을 경우 Http404예외를 발생시키도록 하고 있다. 그리고 위 코드를 제대로 작동 시키기위해 polls/templates/polls 아래에 detail.html파일을 새로 작성하고 아래의 코드를 입력한다.

# polls/templates/polls/detail.html

{{ question }}

 웹 브라우저에 "/polls/detail/"를 입력하면 Page not found(404)가 뜨는 것을 확인할 수 있다.

 이와 같이 객체가 존재하지 않을 때 get()을 사용하여 Http404예외를 발생시키는 것은 자주 사용하는 용법이므로 장고에서 역시 단축 기능인 get_object_or_404()을 제공하고 있다. get_object_or_404()를 이용하면 위의 코드와 동일한 동작을 하는 코드를 다음과 같이 작성할 수 있다. 

# 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})

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

 또한 get_list_or_404()함수도 존재한다. get()대신 filter()를 사용한다는 점에서 차이가 있으며, 이 함수는 리스트가 비어있을 경우에 Http404예외를 발생시킨다.


참고자료

docs.djangoproject.com/ko/3.1/intro/tutorial03/

728x90