===================================== 첫 번째 Django 앱 만들기, part 3 ===================================== 이 튜토리얼은 :doc:`튜토리얼 2 `\ 에 이어서 시작합니다. 우리는 계속해서 "뷰"라고 하는 퍼블릭 인터페이스를 작성하는데 중점을 두고 웹-투표 애플리케이션을 계속합니다. 철학 ========== 뷰는 Django 애플리케이션에서 특정한 기능을 수행하거나, 특정한 템플릿을 가지는 웹 페이지의 "유형"\ 입니다. 예를 들어 웹로그 애플리케이션은 아래와 같은 뷰들을 가질 것 입니다. * 블로그 홈페이지 -- 최신 엔트리 몇 개를 표시합니다. * 엔트리 "세부" 페이지 -- 한 개의 엔트리를 위한 퍼머링크 페이지입니다. * 연도별 아카이브 페이지 -- 주어진 연도에 있는 모든 달들을 엔트리와 함께 나타냅니다. * 월별 아카이브 페이지 -- 주어진 달에 있는 모든 날들을 엔트리와 함께 나타냅니다. * 일별 아카이브 페이지 -- 주어진 날에 있는 모든 엔트리를 나타냅니다. * 댓글 달기 -- 주어진 엔트리에 코멘트를 포스팅합니다. 투표 애플리케이션에는 아래와 같이 네 개의 뷰가 있습니다. * 투표 "인덱스" 페이지 -- 몇 개의 최신 투표를 나타냅니다. * 투표 "세부" 페이지 -- 투표의 질문을 투표할 수 있는 폼과 함께 나타냅니다. 투표 결과는 보여주지 않습니다. * 투표 "결과" 페이지 -- 특정 투표의 결과를 나타냅니다. * 투표하기 -- 특정 투표에서 특정 선택지에 투표합니다. Django에서 개별 뷰는 간단한 파이썬 함수로 나타냅니다. URL 디자인 하기 ================ URL 구조를 디자인하는 것은 뷰를 작성하는 첫 번째 단계입니다. URLconf라는 파이썬 모듈을 생성함으로써, URL 구조를 디자인할 수 있습니다. URLconfs는 Django가 주어진 URL을 주어진 파이썬 코드와 연결하는 방법입니다. Django로 생성된 페이지를 사용자가 요청할 때, 시스템은 Python dotted syntax에 있는 문자열을 포함하는 :setting:`ROOT_URLCONF` 설정을 참고합니다. Django 는 URLconf 모듈을 로드합니다. 그리고, 아래와 같은 포맷의 튜플인 모듈-레벨 변수인 ``urlpatterns``\ 를 검색합니다. :: (regular expression, Python callback function [, optional dictionary]) Django는 첫 번째 정규표현식을 시작으로, 일치하는 결과를 찾을 때까지 요청받은 URL을 각각의 정규표현식과 비교하면서 리스트 아래로 내려갑니다. 일치하는 결과를 찾으면, Django는 파이썬 콜백 함수를 호출합니다. 이 때, :class:`~django.http.HttpRequest` 객체를 첫 번째 인자로 하고, 정규표현식에서 찾은 임의의 값을 키워드 인자로 가집니다. 옵션으로 그 사전에서 임의의 키워드(튜플의 세 번째 아이템)를 가져다가 사용합니다. :class:`~django.http.HttpRequest` 객체에 대해서 자세히 알고 싶다면, :doc:`/ref/request-response`\ 를 참고하세요. URLconfs 에 대해서 자세히 알고 싶다면, :doc:`/topics/http/urls`\ 를 참고하세요. 튜토리얼 1에서 처음 ``django-admin.py startproject mysite`` 를 실행 했을때, 기본 URLconf를 ``mysite/urls.py``\ 에 생성했습니다. 게다가 자동으로 :setting:`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\d+)/$', 'polls.views.detail'), url(r'^polls/(?P\d+)/results/$', 'polls.views.results'), url(r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), url(r'^admin/', include(admin.site.urls)), ) 여기까지의 내용을 설명하면, 누군가 웹 사이트에 "/polls/23/"\ 이라는 요청을 하면, Django는 :setting:`ROOT_URLCONF` 설정이 가리키고 있기 때문에 위의 파이썬 모듈을 로드 할 것입니다. 위의 파이썬 모듈은 ``urlpatterns``\ 라는 이름을 가진 변수를 찾습니다. 그리고, 정규표현식들을 순서대로 순회합니다. 위의 파이썬 모듈이 일치하는 정규표현식을 찾았을때 -- ``r'^polls/(?P\d+)/$'`` -- 위의 파이썬 모듈은 ``polls/views.py``\ 에 있는 ``detail()`` 함수를 로드합니다. 끝으로 파이썬 모듈은 아래와 같이 ``detail()`` 함수를 호출합니다. :: detail(request=, poll_id='23') ``poll_id='23'``\ 는 ``(?P\d+)``\ 에서 온 것입니다. 패턴 주위에 괄호()를 사용해서 그 패턴과 일치하는 텍스트를 "캡처"하고, 뷰 함수에게 인자로 보냅니다; ``?P``\ 는 일치하는 패턴을 인식하는데 사용할 이름을 정의합니다. 그리고 ``\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/``\ 만 찾을 것입니다. 만약, 정규표현식에 대해 도움이 필요하면, `위키피디아 문서`_ 및 :mod:`re` 모듈의 문서를 참고하십시오. 또, Jeffrey Friedl 의 오라일리의 책 "Mastering Regular Expressions"\ 도 훌륭합니다. 끝으로, 성능 노트. 처음 URLconf 모듈이 로드되었을때, 이 정규표현식들은 컴파일되었습니다. 그래서, 매우 빠릅니다. .. _위키피디아 문서: http://en.wikipedia.org/wiki/Regular_expression 첫 번째 뷰 작성하기 ===================== 우리는 URLconf만 만들었습니다. 아직 아무런 뷰도 생성하지 않았습니다. 그렇지만 이전에 Django가 아래의 URLconf를 철저히 따르고 있는지 확실히 하십시오. 이제 Django 개발 웹 서버를 구동합니다. .. code-block:: bash 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와 투표 페이지를 보여줍니다. 실제로 동작하는 뷰 작성하기 ====================================== 각각의 뷰는 둘 중에 하나를 수행합니다. 요청받은 페이지에 대한 컨텐츠를 가지고 있는 :class:`~django.http.HttpResponse` 객체를 반환 하거나 :exc:`~django.http.Http404` 같은 예외를 발생합니다. 나머지는 당신에게 달렸습니다. 당신의 뷰는 데이터베이스에서 레코드들을 읽을 수도, 아닐 수도 있습니다. Django에 있는 것 같은 템플릿 시스템 --또는 서드 파티 파이썬 템플릿 시스템 -- 을 사용할 수도, 안 할 수도 있습니다. PDF 파일을 생성하거나, XML을 출력하거나, ZIP파일을 생성하거나, 당신이 무엇을 원하든 간에 원하는 파이썬 라이브러리들을 사용하십시오. 모든 Django가 필요한 것은 :class:`~django.http.HttpResponse`\ 입니다. 또는 예외입니다. 사용하기 편리하므로, 우리가 :doc:`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.py``\ 의 :setting:`TEMPLATE_DIRS`\ 를 수정해서, Django에게 어디서 템플릿들을 찾을 수 있는지 알려주십시오. -- 튜토리얼 2의 "Customize the admin look and feel" 섹션에서 한 것처럼 하면 됩니다. 위의 내용을 완료했다면, ``polls`` 디렉토리를 템플릿 디렉토리에 생성하십시오. 그 안에 ``index.html`` 파일을 생성하십시오. 우리의 ``loader.get_template('polls/index.html')`` 코드는 파일 시스템에 있는 "[template_directory]/polls/index.html" 과 매핑된다는 것을 기억하십시오. 아래의 코드를 템플릿에 입력하십시오. .. code-block:: html+django {% if latest_poll_list %} {% else %}

No polls are available.

{% endif %} 웹 브라우저에서 페이지를 로드하면, 튜토리얼 1의 "What's up" 투표를 포함하는 bulleted-list를 볼 수 있어야합니다. 이 링크는 투표의 세부 페이지를 가리킵니다. 지름길: render_to_response() -------------------------------- render_to_response()는 템플릿을 로드하고, 컨텍스트를 채우고, :class:`~django.http.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}) 모든 뷰에서 한 번만 위의 작업을 하면, 더이상 :mod:`~django.template.loader`, :class:`~django.template.Context` 그리고 :class:`~django.http.HttpResponse`\ 를 불러올 필요가 없다는 것을 기억하십시오. :func:`~django.shortcuts.render_to_response` 함수는 템플릿 이름을 첫 번째 인자로, 선택 가능한 두 번째 인자로 사전을 갖습니다. :func:`~django.shortcuts.render_to_response` 함수는 주어진 컨텍스트와 함께 렌더링된 템플릿 객체의 :class:`~django.http.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가 존재 하지 않으면 뷰가 :exc:`~django.http.Http404` 예외를 띄웁니다. `polls/detail.html``\ 에 무엇을 넣을지는 조금 나중에 고민할 것입니다. 그렇지만, 빠르게 예제를 동작하게 만들고 싶다면, 단지 :: {{ poll }} 코드가 지금 당장 코드를 시작 할 수 있게 할 것입니다. 지름길: get_object_or_404() ------------------------------- get_object_or404()는 :meth:`~django.db.models.query.QuerySet.get`\ 를 사용하기 위해서 자주 사용하는 구문입니다. 객체가 없을 때는 :exc:`~django.http.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}) :func:`~django.shortcuts.get_object_or_404` 함수는 Django 모델을 첫 번째 인자로 받고, :meth:`~django.db.models.query.QuerySet.get` 함수에서 전달된 임의의 숫자를 키워드 인자로 받습니다. 만약, 객체가 존재하지 않으면, :exc:`~django.http.Http404`\ 를 띄웁니다. .. admonition:: 철학 왜 우리가 더 높은 레벨에서 :exc:`~django.core.exceptions.ObjectDoesNotExist`\ 를 사용해서 자동으로 예외를 처리하는 대신 헬퍼 함수인 :func:`~django.shortcuts.get_object_or_404`\ 를 사용했을까요? 또는 :exc:`~django.core.exceptions.ObjectDoesNotExist`\ 를 사용하는 대신 :exc:`~django.http.Http404`\ 를 띄우는 모델 API를 가지도록 했을까요? 그 이유는 모델 계층을 뷰 계층에 결합하기 때문입니다. Django의 으뜸가는 디자인 목표중 하나는 느슨한 결합을 유지하는 것입니다. 물론 :func:`~django.shortcuts.get_object_or_404`\ 와 거의 똑같이 동작하는 :func:`~django.shortcuts.get_list_or_404` 함수도 있습니다. -- :meth:`~django.db.models.query.QuerySet.get` 대신 :meth:`~django.db.models.query.QuerySet.filter`\ 를 사용하는 경우는 제외합니다. :func:`~django.shortcuts.get_list_or_404`\ 는 리스트가 비어 있을 경우에 :exc:`~django.http.Http404`\ 를 띄웁니다. 404 (page not found) 뷰 작성하기 ================================= 뷰에서 :exc:`~django.http.Http404`\ 를 일으키면, Django는 404 오류를 다루기로 한 특별한 뷰를 로드할 것 입니다. Django는 루트 URLconf(그리고 오직 루트 URLconf에서 ``handler404``\ 를 설정해야합니다. 이외의 위치에서는 모두 무시합니다.)에서 ``handler404`` python dotted syntax 내의 문자열 변수를 찾음으로써 404오류를 다루기로한 특별한 뷰를 찾을 것입니다. -- 같은 형태로 일반적인 URLconf 콜백들을 사용합니다. 404 뷰 자체로는 특별한게 아무것도 없습니다. 일반적인 뷰일 뿐입니다. 일반적으로는 404 뷰들을 만드는데 신경쓸 필요가 없습니다. 만약 ``handler404``\ 를 설정하지 않으면, 내장된 :func:`django.views.defaults.page_not_found`\ 를 기본적으로 사용합니다. 이 경우에도 여전히 한 가지 할 일이 남아 있기는합니다. ``404.html`` 템플릿을 root템플릿 디렉토리에 생성 해야합니다. 기본적인 404 뷰는 그 템플릿을 모든 404 오류에 사용할 것입니다. 만약 :setting:`DEBUG`\ 를 ``False`` (settings 모듈에서)로 설정하거나, ``404.html`` 파일을 만들지 않으면, ``Http500``\ 을 대신 띄웁니다. 그런 이유로 ``404.html``\ 을 생성하는 것을 기억하십시오. 404 뷰들에 대해서 몇 가지 더 알아두어야 할 것이 있습니다. * :setting:`DEBUG`\ 를 ``True``\ 로 설정 했다면(설정 모듈 안에서) 404 뷰는 절대로 사용하지 않을 것입니다. (그리고 ``404.html`` 템플릿은 절대 렌더되지 않습니다). 왜냐하면 트레이스백을 대신 표시하기 때문입니다. * 404 뷰는 Django가 URLconf 내의 모든 정규표현식을 검색하고도, 일치하는 표현을 찾지 못했을 때도 호출합니다. 500 (server error) 뷰 작성하기 =============================== 비슷하게, 루트 URLconf는 서버 오류인 경우에 호출하기 위한 뷰를 가리키는 ``handler500``\ 을 정의하고 있을 것입니다. 서버 오류는 뷰 코드에서 런타임 에러가 있을때 발생합니다. 템플릿 시스템 사용하기 ======================= 투표 어플리케이션의 ``detail()`` 뷰로 돌아가 보도록합니다. ``poll``\ 이 컨텍스트 변수로 주어졌을때, "polls/detail.html" 템플릿이 어떻게 되는지 보도록합니다. .. code-block:: html+django

{{ poll.question }}

    {% for choice in poll.choice_set.all %}
  • {{ choice.choice }}
  • {% endfor %}
템플릿 시스템은 변수 어트리뷰트들에 접근 하기 위해서 dot-lookup 구문을 사용합니다. ``{{ poll.question }}``\ 의 예제에서 보면, Django는 처음에 ``poll`` 객체에서 사전을 찾습니다. 찾기에 실패하면, 어트리뷰트를 조회합니다. -- 이 경우에는 잘 동작합니다. 만약, 어트리뷰트 조회에 실패하면, list-index 조회를 시도합니다. :ttag:`{% for %}` 루프에서 메소드 호출이 발생합니다: ``poll.choice_set.all``\ 은 파이썬 코드 ``poll.choice_set.all()``\ 로 변환합니다. 파이썬 코드 ``poll.choice_set.all()``\ 는 선택한 객체들의 an iterable을 반환하고, :ttag:`{% for %}` 태그에서 사용하기 적당합니다. 템플릿에 대한 자세한 내용은 :doc:`템플릿 안내 `\ 를 보세요. URLconfs를 단순화 하기 ======================== 뷰와 템플릿 시스템사이를 종횡무진 하기에는 시간이 좀 걸립니다. URLconf를 수정 하는 동안, 내부에 상당한 양의 불필요한 중복이 있다는 것을 알아차렸을 것입니다. :: urlpatterns = patterns('', url(r'^polls/$', 'polls.views.index'), url(r'^polls/(?P\d+)/$', 'polls.views.detail'), url(r'^polls/(?P\d+)/results/$', 'polls.views.results'), url(r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), ) 다시말해 ``polls.views``\ 가 모든 콜백에 있습니다. 왜냐하면 공통적인 경우에, URLconf 프레임워크는 공통적인 prefixs들에 대한 지름길을 제공합니다. 당신은 공통적인 prefix들을 뽑아 낼수 있습니다. 그리고, 뽑아낸 prefix들을 :func:`~django.conf.urls.patterns`\ 에 아래와 같이 첫 번째 인자로 추가 할 수 있습니다. :: urlpatterns = patterns('polls.views', url(r'^polls/$', 'index'), url(r'^polls/(?P\d+)/$', 'detail'), url(r'^polls/(?P\d+)/results/$', 'results'), url(r'^polls/(?P\d+)/vote/$', 'vote'), ) 이 방법은 이전의 포매팅과 기능적으로 일치합니다. 더 깔끔합니다. 일반적으로 URLconf의 모든 콜백에 하나의 애플리케이션을 위한 prefix를 적용하고 싶지 않다면, 여러 개의 :func:`~django.conf.urls.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\d+)/$', 'detail'), url(r'^polls/(?P\d+)/results/$', 'results'), url(r'^polls/(?P\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들을 제거 하고, :func:`~django.conf.urls.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)), ) :func:`~django.conf.urls.include`\ 은 간단하게 다른 URLconf를 참조합니다. 정규표현식은 ``$``\ (문자열의 끝과 일치하는 문자)를 가지지 않고, trailling slash를 가지는 점을 기억하십시오. 어디든지 Django가 :func:`~django.conf.urls.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\d+)/$', 'detail'), url(r'^(?P\d+)/results/$', 'results'), url(r'^(?P\d+)/vote/$', 'vote'), ) :func:`~django.conf.urls.include`\ 와 URLconf의 결합도를 낮추는 아이디어는 URL들의 플러그인플레이를 쉽게 만들어 줍니다. 이제 투표들은 각자 URLconf를 가지고 있습니다. 투표들은 "/polls/" 밑에도 있을 수 있고, "/fun_polls/" 밑에도, "/content/polls/" 밑에도, 그리고 다른 어떠한 루트 경로에도 있을 수 있습니다. 그리고 앱은 여전히 잘 동작할 것 입니다. 모든 투표 앱들은 절대 경로가 아니라, 상대 경로를 사용합니다. 뷰를 작성하는 것에 익숙해지면 단순한 폼 처리와 제너릭 뷰를 배우기 위해서 :doc:`튜토리얼 4부 `\ 를 읽어보십시오.