첫 번째 장고 앱 작성하기, part 3Link to this heading

이 튜토리얼은 Tutorial 2 에서 이어집니다. 이제 poll 어플리케이션에 공개 인터페이스인 “view” 를 추가해 보겠습니다.

개요Link to this heading

view 는 Django 어플리케이션이 일반적으로 특정 기능과 템플릿을 제공하는 웹페이지의 한 종류 입니다. 예를 들어, 블로그 어플리케이션의 경우 다음과 같은 view 를 가질 수 있습니다.

  • Blog 홈페이지 – 가장 최근의 항목들을 보여줍니다

  • 항목 “세부”(detail) 페이지 – 하나의 항목에 연결하는 영구적인 링크(permalink)를 제공합니다.

  • 년도별 축적 페이지 – 주어진 연도의 모든 월별 항목들을 표시합니다.

  • 월별 축적 페이지 – 주어진 월의 날짜별 항목들을 표시합니다.

  • 날짜별 축적 페이지 – 주어진 날짜의 모든 항목들을 표시합니다.

  • 댓글 기능 – 특정 항목의 댓글을 다룰 수 있는 기능

우리가 만드는 poll 어플리케이션에서 다음과 같은 네개의 view 를 만들어 보겠습니다.

  • 질문 “색인” 페이지 – 최근의 질문들을 표시합니다.

  • 질문 “세부” 페이지 – 질문 내용과, 투표할 수 있는 서식을 표시합니다.

  • 질문 “결과” 페이지 – 특정 질문에 대한 결과를 표시합니다

  • 투표 기능 – 특정 질문에 대해 특정 선택을 할 수 있는 투표 기능을 제공합니다.

Django 에서는, 웹 페이지와 기타 내용들이 view 에 의해 제공됩니다. 각 view 는 간단한 Python 함수(혹은 클래스를 사용할 경우엔 메소드) 를 사용하여 작성됩니다. Django 는 요청된 URL 을 조사하여 view 를 선택합니다. (조금 더 정확히 말하자면, 도메인 네임에 따라오는 URL 부분을 이용합니다.)

웹에서 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” 같은 못생긴 주소를 본 적이 있을겁니다. 다행스럽게도 Django 에서는 적어도 이것보단 훨씬 예쁜 URL 패턴 을 만들 수 있습니다.

URL 패턴은 URL 을 간단하게 일반화 시킨것을 말합니다. 예를 들어, /newsarchive/<year>/<month>/ 같이 표현할 수 있습니다.

view 를 URL 로 부터 얻기 위해, Django 는 ‘URLconfs’ 라고 부르는 것을 사용합니다. URLconf 는 URL 패턴을 views 에 연결합니다.

이 튜토리얼은 URLconfs 를 사용하는 기초 지식을 제공하며, 보다 자세한 정보는 URL dispatcher 을 참조하세요.

조금 더 view 작성하기Link to this heading

이제, polls/views.py 에 view 를 추가해 봅시다. 이 view 들은 인수를 받기 때문에 조금 모양이 다릅니다.

polls/views.py
Code
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)

다음의 path() 호출을 추가하여 이러한 새로운 view 를 polls.urls 모듈로 연결하세요.

polls/urls.py
Code
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/” 를 요청했다고 하면, Django 는 mysite.urls 파이썬 모듈을 불러오게 됩니다. ROOT_URLCONF 설정에 의해 해당 모듈을 바라보도록 지정되어 있기 때문입니다. mysite.urls 에서 urlpatterns 라는 변수를 찾고, 순서대로 패턴을 따라갑니다. 'polls/' 를 찾은 후엔, 일치하는 텍스트("polls/") 를 버리고, 남은 텍스트인 "34/" 를 ‘polls.urls’ URLconf 로 전달하여 남은 처리를 진행합니다. 거기에 '<int:question_id>/' 와 일치하여, 결과적으로 detail() view 함수가 호출됩니다.

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

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

당신이 원하지 않는 한, URL 에 .html 과 같은 것을 붙일 필요가 없습니다. 만약 원할 경우, 다음과 같이 할 수 있습니다.

Code
path('polls/latest.html', views.index),

그런데 바보같아 보이니까 하지 마세요.

view 가 실제로 뭔가 하도록 만들기Link to this heading

각 view 는 두가지중 하나를 하도록 되어 있습니다. 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하거나, 혹은 Http404 같은 예외를 발생하게 해야합니다. 나머지는 당신에게 달렸습니다.

당신이 작성한 view 는 데이터베이스의 레코드를 읽을 수도 있습니다. 또한 view 는 Django 나, Python 에서 서드파티로 제공되는 템플릿 시스템을 사용 할 수도 있습니다. view 는 PDF 를 생성하거나, XML 출력을 하거나, 실시간으로 ZIP 파일을 만들 수 있습니다. view 는 당신이 원하는 무엇이든, Python 의 어떤 라이브러리라도 사용할 수 있습니다.

모든 Django 는 HttpResponse 객체나, 혹은 예외(exception) 를 원합니다.

왜냐면, 그렇게 다루는게 편리하기 때문입니다. Tutorial 2 의 예제에서 Django 가 자신만의 데이터베이스 API 를 사용해봅시다. 새로운 index() view 하나를 호출했을 때, 시스템에 저장된 최소한 5 개의 투표 질문이 콤마로 분리되어, 발행일에 따라 출력됩니다.

polls/views.py
Code
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

여기 몇가지 문제가 있습니다. view 에서 페이지의 디자인이 하드코딩 되어 있습니다. 만약 페이지가 보여지는 방식을 바꾸고 싶다면, 이 Python 코드를 편집해야만 할겁니다. 그럼, view 가 사용할 수 있는 템플릿을 작성하여, Python 코드로부터 디자인을 분리하도록 Django 의 템플릿 시스템을 사용해 봅시다.

우선, polls 디렉토리에 templates 라는 디렉토리를 만듭니다. Django 는 여기서 템플릿을 찾게될 것입니다.

project 의 TEMPLATES 설정에는 Django 가 어떻게 template 을 불러오고 랜더링 할 것인지 를 서술합니다. 기본 설정 파일은 APP_DIRS 옵션이 True 로 설정된 DjangoTemplates 백엔드를 구성합니다 관례에 따라, DjangoTemplates 은 각 INSTALLED_APPS 디렉토리의 “templates” 하위 디렉토리를 탐색합니다.

방금 만든 templates 디렉토리 내에 polls 라는 디렉토리를 생성하고, 그 안에 index.html 을 만듭니다. 즉, template 은 polls/templates/polls/index.html 과 같은 형태가 됩니다. app_directories template 로더가 그렇게 동작하기 때문에, Django 에서 이 template 을 단순히 polls/index.html 로 참조할 수 있습니다

템플릿에 다음과 같은 코드를 입력합니다.

polls/templates/polls/index.html
Django template
{% 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 %}

이제, template 을 이용하여 polls/views.pyindex view 를 업데이트 해보도록 하겠습니다.

polls/views.py
Code
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 template 을 불러온 후, context 를 전달합니다. context 는 template 에서 쓰이는 변수명과, Python 의 객체를 연결하는 사전형 값입니다.

브라우저에서 “/polls/” 페이지를 불러오면, Tutorial 2 에서 작성한 “What’s up” 질문이 포함된 리스트가 표시됩니다. 표시된 질문의 링크는 해당 질문에 대한 세부 페이지를 가르킵니다.

지름길: render()Link to this heading

template 에 context 를 채워넣어 표현한 결과를 HttpResponse 객체와 함께 돌려주는 구문은 자주 쓰는 용법입니다. 따라서 Django 는 이런 표현을 쉽게 표현할 수 있도록 단축 기능(shortcuts)을 제공합니다. index() view 를 단축 기능으로 작성하면 다음과 같습니다.

polls/views.py
Code
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)

모든 view 에 적용한다면, 더 이상 loaderHttpResponse 를 import 하지 않아도 됩니다. (만약 detail, results, vote 에서 stub 메소드를 가지고 있다면, HttpResponse 를 유지해야 할 것입니다.)

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

404 에러 일으키기Link to this heading

이제, 질문의 detail view 에 태클을 걸어보겠습니다. detail view 는 지정된 설문조사의 질문 내용을 보여줍니다. 다음과 같습니다:

polls/views.py
Code
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})

여기 새로운 내용이 추가되었습니다. view 는 요청된 질문의 ID 가 없을 경우 Http404 예외를 발생시킵니다.

조금 후에 polls/detail.html template 에 무엇을 넣을 수 있는지 논의하겠지만, 일단 위의 예제를 동작시키기 위해 아래의 내용이 들어있는 파일을 작성하세요.

polls/templates/polls/detail.html
Django template
{{ question }}

이제 시작해도 됩니다.

지름길: get_object_or_404()Link to this heading

만약 객체가 존재하지 않을 때 get() 을 사용하여 Http404 예외를 발생시키는것은 자주 쓰이는 용법입니다. Django 에서 이 기능에 대한 단축 기능을 제공합니다. detail() view 를 단축 기능으로 작성하면 다음과 같습니다.

polls/views.py
Code
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() 함수는 Django 모델을 첫번째 인자로 받고, 몇개의 키워드 인수를 모델 관리자의 get() 함수에 넘깁니다. 만약 객체가 존재하지 않을 경우, Http404 예외가 발생합니다.

또한, get_object_or_404() 함수 처럼 동작하는 get_list_or_404() 함수가 있습니다. get() 대신 filter() 를 쓴다는 것이 다릅니다. 리스트가 비어있을 경우, Http404 예외를 발생시킵니다.

Template 시스템 사용하기Link to this heading

poll 어플리케이션의 detail() view 로 되돌아 가봅시다. context 변수 question 이 주어졌을때, polls/detail.html 라는 template 이 어떻게 보이는지 봅시다.

polls/templates/polls/detail.html
Django template
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

template 시스템은 변수의 속성에 접근하기 위해 점-탐색(dot-lookup) 문법을 사용합니다. 예제의 {{ question.question_text }} 구문을 보면, Django 는 먼저 question 객체에 대해 사전형으로 탐색합니다. 탐색에 실패하게 되면 속성값으로 탐색합니다. (이 예에서는 속성값에서 탐색이 완료됩니다만) 만약 속성 탐색에도 실패한다면 리스트의 인덱스 탐색을 시도하게 됩니다.

{% for %} 반복 구문에서 메소드 호출이 일어납니다. question.choice_set.all 은 Python 에서 question.choice_set.all() 코드로 해석되는데, 이때 반환된 Choice 객체의 반복자는 {% for %} 에서 사용하기 적당합니다.

template 에 대한 더 많은 정보는 template 지침서 를 참고하세요

Template 에서 하드코딩된 URL 을 제거하기Link to this heading

기억하셔야 할 것은, polls/index.html template 에 링크를 적으면, 이 링크는 다음과 같이 부분적으로 하드코딩 됩니다.

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

이러한 강력하게 결합되고 하드코딩된 접근방식의 문제는 수 많은 템플릿을 가진 프로젝트들의 URL 을 바꾸는 게 어려운 일이 된다는 점입니다. 그러나, polls.urls 모듈의 path() 함수에서 인수의 이름을 정의했으므로, {% url %} template 태그를 사용하여 url 설정에 정의된 특정한 URL 경로들의 의존성을 제거할 수 있습니다.

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

이것이 polls.urls 모듈에 서술된 URL 의 정의를 탐색하는 식으로 동작합니다. 다음과 같이 ‘detail’ 이라는 이름의 URL 이 어떻게 정의되어 있는지 확인할 수 있습니다.

Code
...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

만약 detail view 의 URL 을 polls/specifics/12/ 로 바꾸고 싶다면, template 에서 바꾸는 것이 아니라 polls/urls.py 에서 바꿔야 합니다.:

Code
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...

URL 의 이름공간(namespace) 나누기Link to this heading

튜토리얼의 project 는 하나의 polls 라는 app 하나만 가지고 진행했습니다. 실제 Django 의 project 는 app 이 몇개라도 올 수 있습니다. Django 는 이 app 들의 URL 을 어떻게 구별해 낼까요? 예를 들어, polls app 은 detail 이라는 view 를 가지고 있고, 동일한 project 에 블로그를 위한 app 이 있을수도 있습니다. Django 가 {% url %} template 태그를 사용할 때, 어떤 app 의 view 에서 URL 을 생성할지 알 수 있을까요?

정답은 URLconf 에 이름공간(namespace)을 추가하는 것입니다. polls/urls.py 파일에 app_name 을 추가하여 어플리케이션의 이름공간을 설정할 수 있습니다.

polls/urls.py
Code
from django.urls import path

from . import views

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

이제, polls/index.html template 의 기존 내용을

polls/templates/polls/index.html
Django template
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

아래와 같이 이름공간으로 나눠진 detail 의 view 를 가르키도록 변경하세요.

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

view 를 작성하는 것이 익숙해 지셨다면, 4장 튜토리얼 에서 간단한 서식 처리와 generic view 를 배워보세요.