404 예외 처리 (Raising a 404 error)

이번 강좌에서는 404 에러 메세지를 출력하는 방법에 대해서 알아보겠습니다. 뷰 파일안의 디테일 뷰(각 투표의 모든 질문을 보여주는 페이지)를 다음과 같이 수정하여 주십시오.polls/views.py

from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    question = Question.objects.get(pk=question_id)

    return render(request, 'polls/detail.html', {'question': question})

polls/detail.html 디테일 템플릿을 제대로 만드는 방법은 조금 뒤에 배우도록 하고, 일단은 강좌의 진행을 위해 다음과 같은 임시 디테일 템플릿 파일을 만들어 주십시오.polls/templates/polls/detail.html

{{ question }}

필자의 말

다음의 404 예제를 시작하기 전에 먼저 브라우저의 주소창에 데이터베이스에 존재하지 않는 Question 오브젝트를 요청하는 url 주소를 입력하여 보십시오.

http://localhost:8000/polls/4

디버깅 에러 메세지가 보이시나요? 밑의 예제를 따라해보신 후에 브라우저를 리프레쉬 해 보세요. 404 메세지가 출력되는 것을 볼 수 있을겁니다.

그럼 이번에는 예외처리를 위해 디테일 뷰에 코드를 추가하겠습니다.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})

여기서 새로운 컨셉트는 만약 요청으로 들어온 question 오브젝트의 ID가 데이터베이스상에 존재하지 않을 경우 Http404 예외를 발생시키는 것입니다.

숏컷: get_object_or_404()

get() 함수를 사용하여 Http404 예외를 발생시키는 것은 아주 일반적인 방법입니다. 장고는 이 절차를 더 간단히 해주는 숏컷을 제공합니다. 이 숏컷을 이용하여 detail() 뷰를 수정해 보겠습니다. 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_object_or_404() 이외에도 get_object_or_404() 똑같이 쓰여지는 get_list_or_404() 함수가 있습니다.  다만 get_list_or_404() 함수는 get()이 아닌 filter() 함수를 사용하여 모델 오브젝트 리스트를 리턴합니다. 하지만 리스트이 비어있을 경우에는 Http404 예외를 발생시킵니다.

템플릿 시스템 사용하기

다시 투표 앱의 detail() 뷰로 돌아가 보도록 하죠. polls/detail.html 템플릿은 detail 뷰로 부터 question 오브젝트를 전달 받습니다. 전달 받은 오브젝트를 표시하기 위해 파일을 다음과 같이 수정할 수 있습니다.

필자의 말

아래의 주소를 웹브라우저의 주소창에 입력하여 현재 디테일 템플릿이 출력하는 내용을 확인하여 주십시오.

http://localhost:8000/polls/1

“What’s up?”이라는 Question 오브젝트의 question_text만을 출력하고 있습니다.polls/templates/polls/detail.html

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

브라우저를 리프레쉬하시면 새로운 디테일 템플릿이 아래와 같은 내용을 출력하는 것을 볼 수 있습니다.

django detail view

템플릿 시스템은 dot-lookup 구문을 사용하여 변수 속성(variable attributes)에 접근합니다. 위의 {{ question.question_text }}를 예로 들면 question_text를 찾기 위해 question 오브젝트가 가지고 있는 파이썬 사전을 가장 먼저 검색 하고 사전안에서 찾지 못하면 속성(attribute)에서 다시 검색을 시작합니다. 그리고 속성 검색에서도 실패를 하면, 리스트 인덱스를 검색 합니다.

{% for %} 루프 안에서는 메소드 호출(method-calling)이 발생합니다. question.choice_set.all은 Python 코드인 question.choice_set.all()로 변환되며 {% for %} 태그에서 사용 가능한 iterable인 Choice 오브젝트를 리턴합니다.

템플릿 사용법에 대해서 자세히 알고 싶으시면 template guide를 참고하여 주십시오.

템플릿에서 하드코딩 URL 지우기

현재 polls/index.html 템플릿의 qustion 디테일 페이지의 링크 일부는 다음과 같이 하드코딩 되어 있습니다.

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

위의 코드의 링크 주소인 href="/polls/{{ question.id }}/"는 하드코딩이 되어 있습니다. 이러한 하드 코딩은 프로젝트의 URLs 셋팅에서 url을 바꾸면 모든 템플릿의 링크를 수정해 주어야 한다는 문제점을 가지고 있습니다. 그런데 polls.urls 모듈의  url() 함수 안에 name 파라메터를 정의하면 하드코딩 url의 문제점을 없애주는 아주 편리한 {% url %} 템플릿 태그를 사용할 수 있습니다.

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

위의 코드는 가장 먼저 polls.urls에서 “name=’detail’의 정의를 찾습니다. 밑의 코드를 보시면 정확히 ‘detail’ 이름을 가지고 있는 URL이 있는 것을 알 수 있습니다.

...
# {% url %} 템플릿 태그가 호출하는 'name' 파라메터
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

만약에 polls 디테일 뷰의 URL을 polls/specifics/12/와 같이 바꾸고 싶다면, 템플릿을 바꾸는 대신에 polls/urls.py을 다음과 같이 바꾸면 됩니다.

...
# 'specifics'를 추가
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

네임스페이싱 URL 이름

이 강좌의 프로젝트는 polls앱 하나만을 가지고 있습니다. 하지만 실제 장고 프로젝트는 앱을 5개, 10개 또는 20개 이상을 가질 수도 있습니다. 그럼 이 많은 앱이 사용하는 URL 이름을 장고는 어떻게 구별할까요? 예를 들어, polls 앱에 name=detail 의 url을 가지고 있고, 다른 블로그 앱도 같은 이름의 url을 가지고 있을 수도 있을 겁니다. {% url %} 템플릿태그를 사용했을때 장고는 어떤 뷰를 사용해야 할지 알 수 있을까요?

이것에 대한 해답은 URLconf에 네임스페이스를 정의하는 겁니다. polls/urls.py 파일에 app_name 이라는 변수를 이용하여 어플리케이션의 네임스페이스를 정의하여 주십시오.polls/urls.py

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

현재 polls/index.html 템플릿은 다음과 같을 겁니다.polls/templates/polls/index.html

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

‘detail’ 뷰의 네임스페이스를 포인트하기 위해 다음과 같이 수정하여 주십시오.polls/templates/polls/index.html

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

뷰 만들기에 익숙해 지셨으면, 나의 첫 Django 앱 만들기 – part 4 – 1 에서 간단한 유저폼 프로세싱과 제네릭 뷰에 대해서 배워 보도록 하죠.