Tutorials
3. View and Templates
"writing your first Django app, part3"
원본문서
(intro/tutorial03/)
3.1 Overview
3.2 Writing more views
3.3 Write views that actually do something
- A shortcut: render()
3.4 Raising a 404 error
- A shortcut: get_object_or_404()
3.5 Use the template system
3.6 Removing hardcoded URLs in templates
3.7 Namespacing URL names
3번 튜토리얼은 2번 튜토리얼을 마친 후 진행됩니다. 우리는 계속해서 web-poll 앱을 만질거고, 그 중에서도 퍼블릭 인터페이스를 만드는데 집중하겠습니다. 이 퍼블릭 인터페이스는 "views"에 의해 동작합니다.
View +
url & detail()
Application
View
프로젝트를 만들고, 그 프로젝트를 테스트 할 테스트서버(개발용 서버)를 만듭니다. 프로젝트 안에 앱(application)을 만들고, 그 앱을 위한 뷰(view)를 만듭니다.
먼저 view 에 대한 내용을 둘러보겠습니다.
3.1 overview
뷰는 우리의 장고 앱의 웹 페이지 타입을 말합니다. 특정 기능과 특정 템플릿을 제공하지요. 예를 들어, 블로그 앱에서 우리는 보통 아래와 같은 뷰를 사용하게 됩니다.
- 블로그 홈페이지 : 최근에 작성된 글들을 보여줍니다..
- 디테일 페이지 목록 : 단일 엔트리에 대한 permalink 페이지
- 연 단위 모음 페이지 : 해당 연도의 목록과 모든 달을 보여주는 페이지
- 월 단위 모음 페이지 : 해당 월(month)과 모든 일일 엔트리를 보여주는 페이지
- 일 단위 모음 페이지 : 해당 날짜의 모든 엔트리를 보여주는 페이지
- 이하 생략
그리고 우리의 폴 앱에는, 아래와 같은 네개의 뷰가 생길 것입니다.
- 질문 '인덱스' 페이지 : 최근의 몇가지 질문을 보여줍니다.
- 질문 '상세' 페이지 : 질문 내용을 보여줍니다. 답변은 나타나지 않지만 투표를 하는 폼이 제공됩니다.
- 질문 '결과' 페이지 : 부분적인 질문에 대한 결과를 보여줍니다.
- 투표 액션 : 개별 질문에 대한 투표를 조정합니다.
장고에서 웹페이지나 다른 컨텐츠들은 뷰를 통해 전달됩니다. 각각의 뷰는 심플한 파이썬 함수(또는 메서드 = 클래스 기반 뷰 CBV인 경우)에 의해 나타나게 됩니다. 장고는 url을 통해 적절한 뷰를 골라서 불러옵니다. (좀 더 정확히 말하자면,도메인 이름 뒤에 더해지는 url 부분을 이용합니다.)
우리는 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”. 따위의 url을 사용할 필요가 없습니다. 장고는 훨씬 우아한 url패턴을 사용합니다.
장고의 url패턴은 이런 식입니다.
/newsarchive/
url로부터 view로 가기 위해서, 장고는 urlconf를 이용합니다. urlconf는 url 패턴들과 그물처럼 연결되어 있습니다. (그 연결망에는 정규표현식이 이용됩니다. )
이 튜토리얼은 urlconf 사용법에 대한 기본적인 내용을 알려드립니다. django.urls에서 더 많은 내용을 확인하실 수 있습니다.
3.2 Writing more views
polls 폴더의 views.py에 또다른 뷰를 추가해 보겠습니다. 이 뷰들은 앞에서 다룬 것과 약간 다릅니다. argument를 사용하기 때문입니다.
새로운 뷰를 polls 폴더의 urls에 연결해줍니다.
# 작성경로 : 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)
이 새로운 뷰들을 polls.urls 모듈에 연결해 줍니다. 아래의 예제와 같이 작성하면 됩니다.
# 작성위치 : polls/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
# 예를들어: /polls/
url(r'^$', views.index, name='index'),
# 예를들어: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# 예를들어 : /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
브라우저의 주소창에 (로컬도메인)+/polls/34/를 입력해봅니다.
http://127.0.0.1:8000/polls/34/
그러면 detail()메서드가 실행되면서 그 url에 우리가 제공하는 ID 를 보여줍니다. http://127.0.0.1:8000/polls/34/results/
와 http://127.0.0.1:8000/polls/34/vote/
를 모두 실행해 보십시오. placeholder의 결과와 투표 페이지가 나타날 것입니다.
누군가 우리의 웹 사이트에 페이지를 요청했을 때, 예를 들어 "/polls/34/"라고 말한다면, 장고는 mysite.url를 불러옵니다. 이 파이썬 모듈이 setting에서 ROOT_URLCONF로 지정되어 있기 때문입니다. 이것은 다양한 urlpatterns 를 찾아내고, 순서에 따라 정규 표현식을 확인합니다. '^polls'과 매칭되는 것을 찾으면 매칭된 텍스트인 "polls/" 는 젖혀둡니다. 그리고 남은 텍스트인 "34/"를 polls.urls로 보냅니다. 이 polls.urls는 상위의 url관리자입니다. 이 파일 안에서 r'^(?P<question_id>[0-9]+)/$'
이"34/"와 매칭됩니다. 그러면 detail()뷰가 이 url에 연결됩니다. 이 경우 detail()뷰의 괄호 안에는 다음과 같은 인수가 주어지게 됩니다.
detail(request=<HttpRequest object>, question_id='34')
question_id='34'라는 부분은 (?P<question_id>[0-9]+)
에서 왔습니다. 괄호를 패턴주변에 사용하면 패턴에 매치되는 텍스트를 캡쳐합니다. 그리고 그 텍스트를 뷰의 함수에 인수로 보내줍니다.
(이미지 추가필요함)
P
url 패턴들이 정규 표현식으로 만들어져 있기 때문에, 우리가 무엇을 하든지 아무런 제약이 없습니다. 그리고 .html같은 url cruft를 추가할 필요도 없습니다. 우리는 이런식으로도 url을 작성할 수 있습니다.
url(r'^polls/latest\.html$', views.index),
하지만 이렇게 하는 건 바보짓입니다. 하지 마세요.
3.3 Write views that actually do something
각각의 뷰는 기본적으로 HttpResponse 객체에 적절한 응답을 하거나, Http404 오류 메시지를 보내는 역할을 해야 합니다. 그 외의 기능은 우리가 정하기 나름입니다.
뷰는 데이터베이스로부터의 기록을 읽거나, 읽지 못할 수 있습니다. 뷰가 데이터베이스의 기록을 읽어오는 데는 장고나 써드파티의 파이썬 템플릿시스템을 이용합니다. 이를 통해 PDF파일을 만들거나, XML, ZIP파일 등을 만들 수 있지요. 당신이 원하는 건 무엇이든지요. 원한다면 파이썬 라이브러리를 사용하세요.
모든 장고는 HttpResponse 또는 예외상황(exception)을 요구합니다.
왜냐하면 그것이 편리하기 때문입니다. 장고가 데이터베이스에 가지고 있는 API를 사용하세요. 우리가 튜토리얼 2에서 덮어놓고 넘어갔던 것들입니다. 여기에는 Index()에 대한 좀 더 새로운 내용이 있습니다. 최근의 5개 투표 질문을 보여주는 것입니다. , 콤마로 구분되고, 발행일을 포함합니다.
# 작성위치 : 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_lisst])
return HttpResponse(output)
# 뷰의 나머지 부분- detail, results, vote는 그대로 남겨둡니다.
여기에는 이 페이지의 디자인이 하드 코딩 되어있다는 문제가 있습니다. 하드코딩을 사용하면 우리가 페이지가 보여지는 방식을 바꾸고 싶을 때 관련된 파이썬 코드 전체를 수정해야 합니다. 따라서 장고 템플릿 시스템을 사용해 파이썬에서 디자인을 분리해야 합니다. 뷰가 이용할 수 있는 템플릿을 따로 만들어 보겠습니다.
첫째, templates라고 불리는 폴더를 만듭니다. 위치는 polls폴더의 안쪽입니다. 장고는 앞으로 템플릿을 이 폴더 안에서 찾게 됩니다.
settings.py의 TEMPLATES에는 장고가 템플릿을 어떤 방식으로 렌더(구현)하고 보여줄 것인지에 대해 설정되어 있습니다. 기본 세팅파일은 DjangoTemplates 백엔드를 구성하며, 이 백엔드의 APP_DIRS 옵션은 기본값이 True로 설정되어 있습니다. 관습적으로 DjangoTemplates는 "templates"라는 하위 폴더를 각각의 INSTALLED_APPS 에서 찾게 되어 있습니다.
우리가 만든 templates 폴더 안에 polls라는 또다른 폴더를 만듭니다. 그리고 그 안에 index.html이라는 파일을 만듭니다. 다르게 표현하자면, 우리의 템플릿은 polls/templates/polls/index.html 이라는 경로를 가져야 합니다. 왜냐하면 app_directories 라는 템플릿 로더(loader)가 이러한 경로로 움직이기 때문입니다. 장고에서 우리는 이 템플릿을 간단히 polls/index.html이라고 나타낼 수 있습니다.
template namespacing
여기서 우리는 templates의 하위 폴더로 또다른 polls폴더를 만드는 것보다, 직접적으로 polls/templates를 이용하는 것이 좋을 것 같다고 생각하게 됩니다. 하지만 그건 별로 좋지 않은 생각입니다. 장고는 이름이 일치하는 첫번재 템플릿을 선택합니다. 그리고 만약 우리가 각각 다른 앱에 대해 같은 이름의 템플릿을 사용한다면 장고는 둘 중 하나를 고를 수 없습니다. 우리는 장고에게 정확한 하나를 알려줘야 합니다. 그리고 가장 쉬운 방법은 namespacing을 이용하는 것입니다. 그것은 응용프로그램 이름을 딴 또다른 디렉토리 안에 템플릿을 집어넣는 것입니다.
아래 코드를 템플릿에 집어넣어 봅시다.
# 작성위치 : 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 avialable.</p>
{% endif %}
이제 우리의 index 뷰를 업데이트 해봅시다. polls/views.py가 이 템플릿을 사용할 수 있게 만들어주어야 합니다. 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는 파이썬 객체에 대한 딕셔너리 맵핑 변수 이름입니다.
우리의 브라우저가 "/polls/"에서 가리키는 페이지를 불러옵니다. 그리고 우리는 앞에 점이 찍힌 리스트를 보게 되는데, 그 리스트는 튜토리얼 2에서 만든 "What's up" 이라는 질문을 담고 있습니다. 이 링크는 질문의 디테일 페이지를 가리킵니다.
A shortcut : render()
render( )는 아주 일반적으로 사용되는 관용구입니다. context를 채우고 HttpResponse객체를 템플릿 렌더의 결과와 함께 돌려줍니다. 장고는 shortcut을 제공하며, 여기에 새로 작성한 index( ) view가 있습니다.
# 작성위치 : 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)
우리는 앞에서 이 내용을 원래는 이렇게 작성했었습니다.
# 작성위치 : polls/views.py
from django.http import HttpResponse
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("Hello, world. You're at the polls index.")
render( ) 를 사용하면 우리는 더 이상 loader나 HttpResponse 를 import 하지 않아도 됩니다. (만약 우리가 여전히 stub 메서드를 사용한다면, 예를 들어 detail, results, vote같은, HttpResponse를 계속 사용해야 할 수도 있습니다.)
render()함수는 request 객체를 첫번째 인수로 받고, 템플릿 이름을 두번째, 딕셔너리를 세번째 인수로 받습니다. render()는 주어진 템플릿의 HttpResponse 객체를 주어진 컨텍스트와 함께 렌더하여 돌려줍니다.
3.4 Rasing a 404 error
이제 question의 세세한 뷰를 끼워넣어봅시다. 이 페이지는 주어진 투표에 대한 질문입니다. 아래를 보십시오.
# 작성위치 : 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/detail.html 템플릿을 집어넣을 수 있는지 잠시 후에 살펴볼 것입니다. 하지만 만약 위의 예제를 지금 당장 동작시켜 보고 싶다면, 아래와 같은 파일 하나를 만들면 됩니다.
# 작성위치 : polls/templates/polls/detail.html
{{ question }}
A shortcut: 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( ) 함수에 전달됩니다. get( )함수는 모델의 매니저입니다. 적절한 객체를 찾지 못하면 Http404 에러가 나타나게 됩니다.
철학
왜 자동으로 나타나는 ObjectDoesNotExist 대신 get_object_or_404( )를 사용하는 걸까요? 왜 모델 API가 Http404 에러를 띄우게 하는 걸까요?ObjectDoesNotExist를 이용하면 모델레이어와 뷰 레이어가 겹칠 수 있기 때문입니다. 장고의 큰 목표중 하나는 느슨한 커플링을 유지하는 것입니다. (연결관계를 유여하게 유지하는 것입니다?)몇몇 조정되는 커플링은 django.shortcuts 모듈에서 소개될 것입니다.
3.5 Use the template system
다시 detail( ) 뷰로 돌아오겠습니다. 주어진 문맥적 변수 question 이 템플릿 파일 html 에서 어떻게 작성되는지 봅시다.
# 작성위치 : 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>
템플릿 시스템은 dot-lookup 문법을 사용합니다. 즉, 슬래시를 사용하지 않고 점(dot)을 이용해 변수의 속성에 접근합니다. 예를 들어 의 경우 가장먼저 장고가 사전을 조회하는 것은 question 객체에 대한 것입니다. 거기에 실패하면 장고는 속성을 조회합니다. 위 예의 에서는 question_text가 조회되었습니다. 만약 속성조회도 실패하면 장고는 list-index 를 조회합니다.
메서드 호출은 {% for %}
루프 안에서 일어납니다. question.choice_set.all 은 파이썬 코드로 question.choice_set.all()로 변환됩니다. 이 코드는 Choice객체의 iterable을 돌려주고 {% for %}
태그에 사용되기 적합합니다.
템플릿에 대해 더 많은 것을 알고 싶다면 template guide를 참고하십시오.
3.6 Removing hardcoded URLs in templates
우리가 앞에서 polls/index.html 에 quesiton을 연결했을 때, 그 링크는 부분적으로 하드코드였습니다. 아래 예시처럼요.
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
이 하드코드의 문제는 꽉 묶인 커플로의 접근은 프로젝트 상의 URL들이 변할 때 문제가 될 수 있다는 점입니다. 특히 템플릿의 개수가 많을때 말입니다. 하지만 , 우리가 polls.urls 모듈에 url( )
함수의 인수 이름을 지정해 놓았기 때문에, 우리는 특정 URL 경로의 신뢰를 제거할 수 있습니다. (우리가 {% url %}
템플릿 태그를 써서 url 관리자 안에 지정해 놓은 것 말입니다.
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
이 방법은 URL 정의를 찾습니다. polls.urls 모듈에 정의해 둔. 우리는 'detail'이라는 URL 이름이 어디 정의되어 있는지 아래에서 정확히 볼 수 있습니다.
...
# the 'name' value as called by the {% url %} template tag url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
만약 polls detail view 의 URL을 바꾸기 원한다면, 아마도 polls/specifics/12/ 같은 경로가 템플릿 또는 템플릿 폴더 안에서 그 자리를 대신하게 될 것입니다. 우리는 그것을 polls/urls.py로 대신할 수 있습니다.
...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
3.7 Namespacing URL names
이 튜토리얼 프로젝트에서는 polls라는 앱 하나만을 다루고 있습니다. 하지만 실질적인 장고 프로젝트에서는 5, 10, 20개의 앱 또는 더 많은 앱들을 사용합니다. 장고는 그 모든 URL 이름을 어떻게 구별할까요? 예를 들어, polls 앱에 detail 이라는 이름의 뷰가 있습니다. 그리고 같은 프로젝트의 X라는 앱 역시 detail이라는 이름의 뷰를 가지고 있습니다. 이 경우, 장고는 어떤 detail 뷰를 {% url %}
템플릿 태그와 연결해야 하는지 어떻게 알 수 있을까요?
답은 namespace를 URL관리자에 추가하는 것입니다. 앱의 namespace를 설정하기 위해 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>
namespace를 가진 detail 뷰를 지정합니다.
# 작성위치 : polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.qustion_text }}</a></li>
뷰와 템플릿이 연결된 것을 함께 보면 이렇습니다.
뷰를 작성하는 것이 익숙해졌다면, 간단한 폼 프로세싱과 제너릭 뷰에 대해 알려주는 튜토리얼 (4)로 넘어갑니다.