첫 번째 Django 앱 만들기, part 3

이 튜토리얼은 튜토리얼 2에 이어서 시작합니다. 우리는 계속해서 “뷰”라고 하는 퍼블릭 인터페이스를 작성하는데 중점을 두고 웹-투표 애플리케이션을 계속합니다.

철학

뷰는 Django 애플리케이션에서 특정한 기능을 수행하거나, 특정한 템플릿을 가지는 웹 페이지의 “유형”입니다. 예를 들어 웹로그 애플리케이션은 아래와 같은 뷰들을 가질 것 입니다.

  • 블로그 홈페이지 – 최신 엔트리 몇 개를 표시합니다.
  • 엔트리 “세부” 페이지 – 한 개의 엔트리를 위한 퍼머링크 페이지입니다.
  • 연도별 아카이브 페이지 – 주어진 연도에 있는 모든 달들을 엔트리와 함께 나타냅니다.
  • 월별 아카이브 페이지 – 주어진 달에 있는 모든 날들을 엔트리와 함께 나타냅니다.
  • 일별 아카이브 페이지 – 주어진 날에 있는 모든 엔트리를 나타냅니다.
  • 댓글 달기 – 주어진 엔트리에 코멘트를 포스팅합니다.

투표 애플리케이션에는 아래와 같이 네 개의 뷰가 있습니다.

  • 투표 “인덱스” 페이지 – 몇 개의 최신 투표를 나타냅니다.
  • 투표 “세부” 페이지 – 투표의 질문을 투표할 수 있는 폼과 함께 나타냅니다. 투표 결과는 보여주지 않습니다.
  • 투표 “결과” 페이지 – 특정 투표의 결과를 나타냅니다.
  • 투표하기 – 특정 투표에서 특정 선택지에 투표합니다.

Django에서 개별 뷰는 간단한 파이썬 함수로 나타냅니다.

URL 디자인 하기

URL 구조를 디자인하는 것은 뷰를 작성하는 첫 번째 단계입니다. URLconf라는 파이썬 모듈을 생성함으로써, URL 구조를 디자인할 수 있습니다. URLconfs는 Django가 주어진 URL을 주어진 파이썬 코드와 연결하는 방법입니다.

Django로 생성된 페이지를 사용자가 요청할 때, 시스템은 Python dotted syntax에 있는 문자열을 포함하는 ROOT_URLCONF 설정을 참고합니다. Django 는 URLconf 모듈을 로드합니다. 그리고, 아래와 같은 포맷의 튜플인 모듈-레벨 변수인 urlpatterns를 검색합니다.

(regular expression, Python callback function [, optional dictionary])

Django는 첫 번째 정규표현식을 시작으로, 일치하는 결과를 찾을 때까지 요청받은 URL을 각각의 정규표현식과 비교하면서 리스트 아래로 내려갑니다.

일치하는 결과를 찾으면, Django는 파이썬 콜백 함수를 호출합니다. 이 때, HttpRequest 객체를 첫 번째 인자로 하고, 정규표현식에서 찾은 임의의 값을 키워드 인자로 가집니다. 옵션으로 그 사전에서 임의의 키워드(튜플의 세 번째 아이템)를 가져다가 사용합니다.

HttpRequest 객체에 대해서 자세히 알고 싶다면, Request and response objects를 참고하세요. URLconfs 에 대해서 자세히 알고 싶다면, URL dispatcher를 참고하세요.

튜토리얼 1에서 처음 django-admin.py startproject mysite 를 실행 했을때, 기본 URLconf를 mysite/urls.py에 생성했습니다. 게다가 자동으로 ROOT_URLCONF을 (settings.py 안에 있는) mysite/urls.py를 가리키도록 설정했습니다.

ROOT_URLCONF = 'mysite.urls'

예제를 보겠습니다. mysite/urls.py를 아래와 같이 수정합니다.

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^polls/$', 'polls.views.index'),
    url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
    url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
    url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
    url(r'^admin/', include(admin.site.urls)),
)

여기까지의 내용을 설명하면, 누군가 웹 사이트에 “/polls/23/”이라는 요청을 하면, Django는 ROOT_URLCONF 설정이 가리키고 있기 때문에 위의 파이썬 모듈을 로드 할 것입니다. 위의 파이썬 모듈은 urlpatterns라는 이름을 가진 변수를 찾습니다. 그리고, 정규표현식들을 순서대로 순회합니다. 위의 파이썬 모듈이 일치하는 정규표현식을 찾았을때 – r'^polls/(?P<poll_id>\d+)/$' – 위의 파이썬 모듈은 polls/views.py에 있는 detail() 함수를 로드합니다. 끝으로 파이썬 모듈은 아래와 같이 detail() 함수를 호출합니다.

detail(request=<HttpRequest object>, poll_id='23')

poll_id='23'(?P<poll_id>\d+)에서 온 것입니다. 패턴 주위에 괄호()를 사용해서 그 패턴과 일치하는 텍스트를 “캡처”하고, 뷰 함수에게 인자로 보냅니다; ?P<poll_id>는 일치하는 패턴을 인식하는데 사용할 이름을 정의합니다. 그리고 \d+는 숫자가 나와야 할 순서를 의미하는 정규표현식입니다.(역자 주: 정규착 표현식에서 d는 0~9 사이의 숫자를 의미하고 +는 앞 문자가 1번 이상 나타나면 된다는 것을 의미합니다. 따라서, 1개 이상의 숫자가 있으면 된다는 뜻입니다.

URL 패턴들은 정규표현식이기 때문에, 무엇을 하든 제한이 없습니다. 그리고, 아래와 같은 악취미가 있지 않는 한 .php같은 URL 부스러기들을 붙일 필요가 없습니다.

(r'^polls/latest\.php$', 'polls.views.index'),

위 처럼 하지 마세요. 바보같으니까요.

이런 정규표현식들은 GET, POST 파라미터들 또는 도메인 이름을 검색하지는 않는다는 것을 기억하십시오. 예를 들어 http://www.example.com/myapp/을 향한 요청에서, URLconf는 myapp/만 찾을 것 입니다. http://www.example.com/myapp/?page=3를 향한 요청에서, URLconf는 myapp/만 찾을 것입니다.

만약, 정규표현식에 대해 도움이 필요하면, 위키피디아 문서re 모듈의 문서를 참고하십시오. 또, Jeffrey Friedl 의 오라일리의 책 “Mastering Regular Expressions”도 훌륭합니다.

끝으로, 성능 노트. 처음 URLconf 모듈이 로드되었을때, 이 정규표현식들은 컴파일되었습니다. 그래서, 매우 빠릅니다.

첫 번째 뷰 작성하기

우리는 URLconf만 만들었습니다. 아직 아무런 뷰도 생성하지 않았습니다. 그렇지만 이전에 Django가 아래의 URLconf를 철저히 따르고 있는지 확실히 하십시오.

이제 Django 개발 웹 서버를 구동합니다.

python manage.py runserver

웹브라우저에서 “http://localhost:8000/polls/“로 이동하십시오. 당신은 유쾌하게 컬러링된 아래 메세지의 에러 페이지를 보게 될 것입니다.

ViewDoesNotExist at /polls/

Could not import polls.views.index. View does not exist in module polls.views.

이 오류는 polls/views.py 모듈에 index() 함수를 작성한 적이 없기 때문에 발생합니다.

“/polls/23/”, “/polls/23/results/” 그리고 “/polls/23/vote/”도 시도해 보십시오. 오류 메세지들은 Django가 어떤 뷰에 접근 하려고 했는지 알려줍니다(그리고, 당신이 뷰들을 작성하지 않았기 때문에 찾기에 실패합니다).

첫 번째 뷰를 작성합니다. polls/views.py를 열어서 아래의 파이썬 코드를 입력합니다.

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the poll index.")

실제로 가능한 가장 간단한 뷰입니다. 브라우저에서 “/polls/” 로 이동하면, 당신이 작성한 텍스트를 볼 수 있어야*.

뷰를 몇 개 더 추가해 봅니다. 이 뷰들은 인자를 가지기 때문에 조금씩 다릅니다. (기억하세요, URLconf에서 정규표현식으로 무엇이 ‘캡처’되었든지 인자로 들어옵니다.)

def detail(request, poll_id):
    return HttpResponse("You're looking at poll %s." % poll_id)

def results(request, poll_id):
    return HttpResponse("You're looking at the results of poll %s." % poll_id)

def vote(request, poll_id):
    return HttpResponse("You're voting on poll %s." % poll_id)

브라우저에서 “/polls/34/”를 둘러보세요. detail() 메소드를 실행하고, URL에 입력한 ID를 나타냅니다. “/polls/34/results/”과 “/polls/34/vote/”도 시도해 보세요. – 이 URL들은 placeholder results와 투표 페이지를 보여줍니다.

실제로 동작하는 뷰 작성하기

각각의 뷰는 둘 중에 하나를 수행합니다. 요청받은 페이지에 대한 컨텐츠를 가지고 있는 HttpResponse 객체를 반환 하거나 Http404 같은 예외를 발생합니다. 나머지는 당신에게 달렸습니다.

당신의 뷰는 데이터베이스에서 레코드들을 읽을 수도, 아닐 수도 있습니다. Django에 있는 것 같은 템플릿 시스템 –또는 서드 파티 파이썬 템플릿 시스템 – 을 사용할 수도, 안 할 수도 있습니다. PDF 파일을 생성하거나, XML을 출력하거나, ZIP파일을 생성하거나, 당신이 무엇을 원하든 간에 원하는 파이썬 라이브러리들을 사용하십시오.

모든 Django가 필요한 것은 HttpResponse입니다. 또는 예외입니다.

사용하기 편리하므로, 우리가 Tutorial 1에서 다루었던 Django가 가지고 있는 데이터베이스 API를 사용해 봅시다. 날짜 순서로 정렬하고, 콤마로 나눈, 시스템에 있는 최근 5개의 투표 질문들을 보여주는``index()`` 뷰를 만들어봅시다.

from polls.models import Poll
from django.http import HttpResponse

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    output = ', '.join([p.question for p in latest_poll_list])
    return HttpResponse(output)

위의 코드에는 문제가 있습니다. 페이지 디자인을 뷰에 하드 코딩했습니다. 만약 페이지가 보이는 형태를 변경하려고 한다면, 파이썬 코드를 수정 해야합니다. 그래서 디자인을 파이썬으로부터 분리하기 위해서 Django 템플릿 시스템을 이용합니다.

from django.template import Context, loader
from polls.models import Poll
from django.http import HttpResponse

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    t = loader.get_template('polls/index.html')
    c = Context({
        'latest_poll_list': latest_poll_list,
    })
    return HttpResponse(t.render(c))

위의 코드는 “polls/index.html” 템플릿을 로드하고 컨텍스트를 전달합니다. 컨텍스트는 파이썬 객체들 이름을 가진 사전 매핑 템플릿 변수 입니다.

페이지를 새로고침 하십시오. 에러를 보게 될것 입니다.

TemplateDoesNotExist at /polls/
polls/index.html

아. 아직 템플릿이 없군요. 첫 번째, 당신의 파일시스템 아무 곳에나 Django가 접근할 수 있는 곳에 디렉토리를 생성하십시오. (Django runs as whatever user your server runs.) 대신, 그 파일들은 문서 root에 넣지 마세요. 보안 문제가 있으므로, 디렉토리를 퍼블릭으로 만들지 않아야합니다. 그리고, settings.pyTEMPLATE_DIRS를 수정해서, Django에게 어디서 템플릿들을 찾을 수 있는지 알려주십시오. – 튜토리얼 2의 “Customize the admin look and feel” 섹션에서 한 것처럼 하면 됩니다.

위의 내용을 완료했다면, polls 디렉토리를 템플릿 디렉토리에 생성하십시오. 그 안에 index.html 파일을 생성하십시오. 우리의 loader.get_template('polls/index.html') 코드는 파일 시스템에 있는 “[template_directory]/polls/index.html” 과 매핑된다는 것을 기억하십시오.

아래의 코드를 템플릿에 입력하십시오.

{% if latest_poll_list %}
    <ul>
    {% for poll in latest_poll_list %}
        <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

웹 브라우저에서 페이지를 로드하면, 튜토리얼 1의 “What’s up” 투표를 포함하는 bulleted-list를 볼 수 있어야합니다. 이 링크는 투표의 세부 페이지를 가리킵니다.

지름길: render_to_response()

render_to_response()는 템플릿을 로드하고, 컨텍스트를 채우고, HttpResponse 객체를 렌더링된 템플릿의 결과와 함께 반환하기 위해 자주 사용하는 구문입니다. Django는 지름길을 제공합니다. 전체 index() 뷰를 재작성했습니다.

from django.shortcuts import render_to_response
from polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})

모든 뷰에서 한 번만 위의 작업을 하면, 더이상 loader, Context 그리고 HttpResponse를 불러올 필요가 없다는 것을 기억하십시오.

render_to_response() 함수는 템플릿 이름을 첫 번째 인자로, 선택 가능한 두 번째 인자로 사전을 갖습니다. render_to_response() 함수는 주어진 컨텍스트와 함께 렌더링된 템플릿 객체의 HttpResponse 를 반환합니다.

404 띄우기

세부 투표 뷰를 봅시다. – 세부 투표 뷰는 주어진 투표에 대한 제목을 표시합니다. 아래에 뷰가 있습니다.

from django.http import Http404
# ...
def detail(request, poll_id):
    try:
        p = Poll.objects.get(pk=poll_id)
    except Poll.DoesNotExist:
        raise Http404
    return render_to_response('polls/detail.html', {'poll': p})

위의 코드에는 이전에 보지 못한 개념이 있습니다. 투표와 함께 요청받은 ID가 존재 하지 않으면 뷰가 Http404 예외를 띄웁니다.

polls/detail.html`에 무엇을 넣을지는 조금 나중에 고민할 것입니다. 그렇지만, 빠르게 예제를 동작하게 만들고 싶다면, 단지

{{ poll }}

코드가 지금 당장 코드를 시작 할 수 있게 할 것입니다.

지름길: get_object_or_404()

get_object_or404()는 get()를 사용하기 위해서 자주 사용하는 구문입니다. 객체가 없을 때는 Http404를 띄웁니다. Django는 지름길을 제공합니다. detail() 뷰를 재작성했습니다.

from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/detail.html', {'poll': p})

get_object_or_404() 함수는 Django 모델을 첫 번째 인자로 받고, get() 함수에서 전달된 임의의 숫자를 키워드 인자로 받습니다. 만약, 객체가 존재하지 않으면, Http404를 띄웁니다.

철학

왜 우리가 더 높은 레벨에서 ObjectDoesNotExist를 사용해서 자동으로 예외를 처리하는 대신 헬퍼 함수인 get_object_or_404()를 사용했을까요?

또는 ObjectDoesNotExist를 사용하는 대신 Http404를 띄우는 모델 API를 가지도록 했을까요?

그 이유는 모델 계층을 뷰 계층에 결합하기 때문입니다. Django의 으뜸가는 디자인 목표중 하나는 느슨한 결합을 유지하는 것입니다.

물론 get_object_or_404()와 거의 똑같이 동작하는 get_list_or_404() 함수도 있습니다. – get() 대신 filter()를 사용하는 경우는 제외합니다. get_list_or_404()는 리스트가 비어 있을 경우에 Http404를 띄웁니다.

404 (page not found) 뷰 작성하기

뷰에서 Http404를 일으키면, Django는 404 오류를 다루기로 한 특별한 뷰를 로드할 것 입니다. Django는 루트 URLconf(그리고 오직 루트 URLconf에서 handler404를 설정해야합니다. 이외의 위치에서는 모두 무시합니다.)에서 handler404 python dotted syntax 내의 문자열 변수를 찾음으로써 404오류를 다루기로한 특별한 뷰를 찾을 것입니다. – 같은 형태로 일반적인 URLconf 콜백들을 사용합니다. 404 뷰 자체로는 특별한게 아무것도 없습니다. 일반적인 뷰일 뿐입니다.

일반적으로는 404 뷰들을 만드는데 신경쓸 필요가 없습니다. 만약 handler404를 설정하지 않으면, 내장된 django.views.defaults.page_not_found()를 기본적으로 사용합니다. 이 경우에도 여전히 한 가지 할 일이 남아 있기는합니다. 404.html 템플릿을 root템플릿 디렉토리에 생성 해야합니다. 기본적인 404 뷰는 그 템플릿을 모든 404 오류에 사용할 것입니다. 만약 DEBUGFalse (settings 모듈에서)로 설정하거나, 404.html 파일을 만들지 않으면, Http500을 대신 띄웁니다. 그런 이유로 404.html을 생성하는 것을 기억하십시오.

404 뷰들에 대해서 몇 가지 더 알아두어야 할 것이 있습니다.

  • DEBUGTrue로 설정 했다면(설정 모듈 안에서) 404 뷰는 절대로 사용하지 않을 것입니다. (그리고 404.html 템플릿은 절대 렌더되지 않습니다). 왜냐하면 트레이스백을 대신 표시하기 때문입니다.
  • 404 뷰는 Django가 URLconf 내의 모든 정규표현식을 검색하고도, 일치하는 표현을 찾지 못했을 때도 호출합니다.

500 (server error) 뷰 작성하기

비슷하게, 루트 URLconf는 서버 오류인 경우에 호출하기 위한 뷰를 가리키는 handler500을 정의하고 있을 것입니다. 서버 오류는 뷰 코드에서 런타임 에러가 있을때 발생합니다.

템플릿 시스템 사용하기

투표 어플리케이션의 detail() 뷰로 돌아가 보도록합니다. poll이 컨텍스트 변수로 주어졌을때, “polls/detail.html” 템플릿이 어떻게 되는지 보도록합니다.

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

템플릿 시스템은 변수 어트리뷰트들에 접근 하기 위해서 dot-lookup 구문을 사용합니다. {{ poll.question }}의 예제에서 보면, Django는 처음에 poll 객체에서 사전을 찾습니다. 찾기에 실패하면, 어트리뷰트를 조회합니다. – 이 경우에는 잘 동작합니다. 만약, 어트리뷰트 조회에 실패하면, list-index 조회를 시도합니다.

{% for %} 루프에서 메소드 호출이 발생합니다: poll.choice_set.all은 파이썬 코드 poll.choice_set.all()로 변환합니다. 파이썬 코드 poll.choice_set.all()는 선택한 객체들의 an iterable을 반환하고, {% for %} 태그에서 사용하기 적당합니다.

템플릿에 대한 자세한 내용은 템플릿 안내를 보세요.

URLconfs를 단순화 하기

뷰와 템플릿 시스템사이를 종횡무진 하기에는 시간이 좀 걸립니다. URLconf를 수정 하는 동안, 내부에 상당한 양의 불필요한 중복이 있다는 것을 알아차렸을 것입니다.

urlpatterns = patterns('',
    url(r'^polls/$', 'polls.views.index'),
    url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
    url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
    url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
)

다시말해 polls.views가 모든 콜백에 있습니다.

왜냐하면 공통적인 경우에, URLconf 프레임워크는 공통적인 prefixs들에 대한 지름길을 제공합니다. 당신은 공통적인 prefix들을 뽑아 낼수 있습니다. 그리고, 뽑아낸 prefix들을 patterns()에 아래와 같이 첫 번째 인자로 추가 할 수 있습니다.

urlpatterns = patterns('polls.views',
    url(r'^polls/$', 'index'),
    url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
    url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
    url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)

이 방법은 이전의 포매팅과 기능적으로 일치합니다. 더 깔끔합니다.

일반적으로 URLconf의 모든 콜백에 하나의 애플리케이션을 위한 prefix를 적용하고 싶지 않다면, 여러 개의 patterns()를 연결할 수 있습니다. mysite/urls.py 전체는 아래와 같습니다.

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('polls.views',
    url(r'^polls/$', 'index'),
    url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
    url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
    url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)

urlpatterns += patterns('',
    url(r'^admin/', include(admin.site.urls)),
)

URLconf의 결합도 낮추기

하는 김에 우리는 Django 프로젝트 설정으로 부터 우리의 투표-앱 URL 들을 분리하는 시간을 가져야합니다. Django 앱들은 플러그인처럼 사용할 수 있도록 만들었습니다. – 각각의 특정한 앱이 최소한의 수정으로 다른 Django 설치본으로 이전 가능해야합니다.

이 시점에서 투표 앱은 상당히 결합도가 낮습니다. python manage.py startapp가 생성한 엄격한 디렉토리 구조에 감사하기 바랍니다. 그렇나, 단 하나 URLconf가 Django 설정들과 결합되어 있습니다.

mysite/urls.py에 있는 URL들은 수정했습니다. 그러나, 앱의 URL 디자인은 Django installation이 아니라, 특정 앱 지향적입니다. – 그래서 URL들을 앱 디렉토리 안으로 이동합니다.

mysite/urls.py 파일을 polls/urls.py로 복사하십시오. 그리고, mysite/urls.py에서 투표-한정 URL들을 제거 하고, include()를 그 자리에 추가하도록 변경합니다.

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', include(admin.site.urls)),
)

include()은 간단하게 다른 URLconf를 참조합니다. 정규표현식은 $(문자열의 끝과 일치하는 문자)를 가지지 않고, trailling slash를 가지는 점을 기억하십시오. 어디든지 Django가 include() 함수를 만나면, 해당 시점에서 URL의 일치하는 부분을 잘라냅니다. 그리고, 남은 문자열을 추가 처리를 위해서 불러들인 URLconf로 전송합니다.

사용자가 이 시스템에서 “/polls/34/”로 이동하는 경우 무슨 일이 생기는지 보십시오.

  • Django는 '^polls/'에서 일치하는 것을 찾을 것입니다.
  • 그리고나서, Django 는 일치하는 문자("polls/")를 벗겨낼 것입니다. 남은 문자 – "34/" –를 ‘polls.urls’ URLconf로 추가 처리를 위해서 보냅니다.

이제, URLconf와 Django 설정의 결합도를 낮추었습니다. 우리는 개별 라인 맨 앞에 있는 “polls/”를 삭제하고, 관리자 사이트에 등록한 라인들을 삭제 함으로써 polls.urls와 URLconf의 결합도를 낮추었습니다. polls/urls.py 파일은 지금 아래와 같아야합니다.

from django.conf.urls import patterns, include, url

urlpatterns = patterns('polls.views',
    url(r'^$', 'index'),
    url(r'^(?P<poll_id>\d+)/$', 'detail'),
    url(r'^(?P<poll_id>\d+)/results/$', 'results'),
    url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

include()와 URLconf의 결합도를 낮추는 아이디어는 URL들의 플러그인플레이를 쉽게 만들어 줍니다. 이제 투표들은 각자 URLconf를 가지고 있습니다. 투표들은 “/polls/” 밑에도 있을 수 있고, “/fun_polls/” 밑에도, “/content/polls/” 밑에도, 그리고 다른 어떠한 루트 경로에도 있을 수 있습니다. 그리고 앱은 여전히 잘 동작할 것 입니다.

모든 투표 앱들은 절대 경로가 아니라, 상대 경로를 사용합니다.

뷰를 작성하는 것에 익숙해지면 단순한 폼 처리와 제너릭 뷰를 배우기 위해서 튜토리얼 4부를 읽어보십시오.