[04] django의 template
이전장에서의 view를 통해 text를 리턴하는 것을 이상하게 생각할 수 있을 것이다. 다시말해 HTML은 아래와 같이 python code에 hard-code되어 있다.
비록 이 기술이 view의 작동을 설명하는데 목적을 뒀지만, 이와 같이 hard-code된 HTML을 view에서 사용하는 것은 좋지 않은 생각이다.
- page design의 변경이 python code의 변경을 요구한다. site의 design은 python code보다 훨씬 자주 변경되는데, 그래서 design이 python code 수정없이 design을 변경하는 방법이 필요하다.
- python code를 작성하고 HTML을 design하는데는 두개의 다른 원리가 있는데, 대부분의 web 개발 도구는 개별 사람(혹은 부서)의 책임을 분리한다. designer와 HTML/CSS 개발자는 python code를 작성할 필요가 없다.
- 만일 개발자가 python을 개발할 수 있고, 동시에 designer는 template 작업을 할 수 있다면, 최고의 효과를 발휘할 것이다. 한작업 끝나고 다른 작업이 끝날때 까지 기다리는 것보다 효과적이다.
이러한 이유로 python code 그자체로 부터 page를 직업 design하지 않도록 분리하는 것이 보다 깔끔하고 유지보수에 유용하다고 본다. 이제 이러한 django의 template system을 설명할 것이다.
template system 기초
django의 template는 document의 표현을 분리하는데 사용되는 text 문자열이다. template는 placeholder(위치)와 문서를 어떻게 표시하는지를 조절하는 기본 logic(template tags)의 다양한 bit를 정의한다. 보통 template는 HTML을 생산하는데 사용되는데, django template는 어떤 text 기반의 format을 만들어 내는 능력이 있다.
이제 간단한 template 예제를 살펴보자. 이러한 django template는 임의의 한 회사의 상품을 주문하는데 감사하다는 내용의 HTML page로 구성되어 있다.
이러한 template는 몇몇 변수와 template tag가 포함된 기본적인 HTML이다. 자세히 살펴보면,
- 괄호쌍에 의해 둘러쌓인 text(예, {{ person_name }})은 변수이다. 이는, "주어진 이름을 가진 변수의 값을 넣어라"라는 뜻이다. (어떻게 변수의 값을 구할 것인가? 그건 그 당시의 값으로 결정된다.)
- % 문자에 의해 쌓인 text(예, {% if ordered_warranty %})는 template tag이다. 이것의 정의는 조금 광대한데, tag는 template system에 "뭔가 하라"라고 알려준다.
위 예에서는 for tag({% for item in item_list %})과 if tag({% if orderred_warrenty %})가 사용되었다.
for tag는 python의 for 구문과 유사하게 동작하는데, 각각의 item이 순서대로 loop를 돌수 있도록 한다. if tag는 예상했듯이 "if" 구문과 논리적으로 유사하게 동작한다. 특별한 경우 tag는 ordered_warranty 변수의 값에 따라 True로 결정한다. 그런 경우 {% if ordered_warranty %}와 {% else %} 사이가 표시된다. 그렇지 않은 경우, {% else %}와 {% endif %} 사이가 표시된다. {% else %}는 옵션 사항이다. - 마지막으로, template의 두번째 문단은 filter의 예를 보여주는데, 변수를 formatting을 변경하는 가장 편한 방법을 제공한다. {{ ship_date|date:"F j, Y" }}는 ship_date 변수를 date filter로 전달하는 것이며, filter의 argument로 "F j, Y"가 전달된다. date filter는 해당 argument에 의해 지정되어 특별하게 처리되게 주어진 format으로 date를 format한다. filter는 pipe 문자(|)를 사용한다.
각 django template는 몇개의 built-in tag와 filter가 있다. 대부분 본 section에서 다루게 될 것이다.
그리고, 자신만의 고유한 filter와 tag를 만들 수 있는데, 이는 chapter 9에서 다룰 것이다.
template system 사용하기
django의 template sytem이 어떻게 동작하는지를 확인할 차례이다. 이전 chapter에서 만들어진 view를 아직 통합하지 않았다. 일단 해당 sytem이 django에 의존하여 동작하는지를 알아내는 것을 목표로 한다. (다르게 말하면, 보통 django view내의 template system을 사용할 것인데, 그러나 django view가 아닌 어느곳에서든 사용가능한 python library인지를 확인하기를 원할 것이다.)
python code로 구성된 django template system을 사용할 수 있는 가장 기본적인 방법은 아래와 같다.
- raw template code를 제공하는 것에 의해 Template object를 생성한다.
- 변수(context)들의 집합과 함께 Template object의 render() method를 호출한다. 그러면 context로 부터 계산된 모든 변수와 template tag를 가지고 fuuly renderred template를 문자열로 구한다.
코드상으로, 이것은 다음과 같이 나타난다.
C:\mydjango\mysite>python manage.py shell Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from django import template >>> t = template.Template('My name is {{ name }}.') >>> c = template.Context({'name': 'Adrian'}) >>> print t.render(c) My name is Adrian. >>> c = template.Context({'name': 'Fred'}) >>> print t.render(c) My name is Fred. >>> |
Template object 생성하기
Template object를 생성하는데 가장 쉬운 방법은 직접 초기화하는 것이다. Template class는 django.template module에 있고 생성자는 하나의 argument를 받는데, 이는 raw template code이다. python interactive interpreter가 어떻게 code를 동작시키는지 확인해 보자.
mysite project directory(django-admin.py startproject)에서 python manage.py shell을 통해 interactive interpreter를 시작한다. (그래야만 DJANGO_SETTING_MODULE를 받아들여 오류없이 동자하게 된다.)
>>> from django.template import Template >>> t = Template('My name is {{ name }}.') >>> print t <django.template.base.Template object at 0x011E2BD0> |
Template object를 생성하면 template system은 rendering 준비를 위해 최적화된 형태로 내부로 raw template code를 compile한다. 그러나 template code에 syntax error가 있다면 Tempate() 호출시 TemplateSyntaxError execption이 발생한다.
>>> from django.template import Template >>> t = Template('{% notatag %}') Traceback (most recent call last): File "<console>", line 1, in <module> File "C:\Python25\Lib\site-packages\django\template\base.py", line 125, in __i nit__ self.nodelist = compile_string(template_string, origin) File "C:\Python25\Lib\site-packages\django\template\base.py", line 153, in com ... raise self.error(token, "Invalid block tag: '%s'" % command) TemplateSyntaxError: Invalid block tag: 'notatag' >>> |
시스템은 다음 경우일때 TemplateSyntaxError exception을 발생한다.
- 무효한(invalid) tag
- 무효한 argument를 유효한(valid) tag에 전달할 때
- 무효한 filter
- 무효한 argument를 유효한 filter에 전달할 때
- 무효한 template syntax
- 닫지 않은 tag (for tag는 closing tag를 요구한다)
Template rendering 하기
Template object를 일단 생성하였다면, context를 제공하여 data를 전달할 수 있다. context는 변수명과 그것와 연계된 값을 가지는 단순한 집합이다.
context는 django.template module에 있는 Context class에 의해 표현된다. 그것의 생성자는 옵션으로 처리된 arugment가 하나 있는데, 그것은 변수명을 값으로 매핑하는데 사용될 사전(dictionary)이다. Template object의 render() method를 template를 채울 context와 함께 호출한다.
>>> from django.template import Context, Template >>> t = Template('My name is {{ name }}.') >>> c = Context({'name':'Stephane'}) >>> t.render(c) u'My name is Stephane.' >>> |
사전(Dictionary)과 Context python diectionary는 알려진 key와 변수값이 매핑되어 있는 것을 말한다. Context는 dictionary와 비슷한데, Context는 추가 기능을 제공한다. (chapter 9) |
아래는 그 예제이다.
>>> from django.template import Template, Context >>> raw_template = """<p>Dear {{ person_name }},</p> ... ... <p>Thanks for placing an order from {{ company }}. It's scheduled to ... ship on {{ ship_date|date:"F j, Y" }}.</p> ... ... {% if ordered_warranty %} ... <p>Your warranty information will be included in the packaging.</p> ... {% else %} ... <p>You didn't order a warranty, so you're on your own when ... the products inevitably stop working.</p> ... {% endif %} ... ... <p>Sincerely,<br />{{ company }}</p>""" >>> t = Template(raw_template) >>> import datetime >>> c = Context({'person_name': 'John Smith', ... 'company': 'Outdoor Equipment', ... 'ship_date': datetime.date(2009, 4, 2), ... 'ordered_warranty': False}) >>> t.render(c) u"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor Equipme nt. It's scheduled to\nship on April 2, 2009.</p>\n\n\n<p>You didn't order a war ranty, so you're on your own when\nthe products inevitably stop working.</p>\n\n \n<p>Sincerely,<br />Outdoor Equipment</p>" >>> |
- 처음으로, django.template에 있는 Template와 Context class를 import하였다.
- raw_template 변수로 template의 raw text를 넣는다. 참고로 3개의 따옴표(""")로 표기하였는데, 이는 여러개의 라인을 받기 위함이다. 대조적으로 단일 따옴표는 여러개의 라인을 받지 못한다.
- 다음으로 t라는 template object를 생성하는데, raw_template를 Template class의 생성자에 전달한다.
- python의 datetime module를 import하는데, 다음 구문에 사용하기 때문이다.
- c라는 Context object를 생성한다. Context 생성자에 python dictionary를 전달하고, 그것은 변수명과 값으로 매핑되어 있다.
- 마지막으로 context를 가지고 render()를 호출한다. 이는 rendered template가 리턴된다. template 변수가 실제값으로 변경되고, template tag를 실행한다.
"You didn't order a warranty" 문단은 orderred_warranty 변수가 False인 경우 표기된다. 역시 April 2, 2009와 같이 date가 표기되는데, 'F j, Y' 형태의 format으로 적용된다.
python의 초보자라면 output에 새로운 개행 대신 왜 \n이 포함되었는지 의아해 할 것이다. python interactive interpreter의 미묘함 때문인데, t.render(c)는 문자열을 리턴하는데 interactive interpreter는 기본적으로 그 문자열의 값을 출력하기 보다는, 그 문자열의 표현(representation)을 출력한다. 만일 \n 대신 실제 개행이 되는 것으로 출력하길 원한다면, print t.render(c)와 같이 print 구문을 이용한다.
이것이 django template system의 기초를 알아보았는데, template 문자열을 작성하고 Template object를 생성하며, Context를 생성하고 그리고 render()를 호출하는 것으로 구성된다.
여러개의 Context에 동일한 Template
일단 Template object를 만들었다면, 다음과 같이 여러개의 context를 render할 수 있다.
>>> from django.template import Template, Context >>> t = Template('Hello, {{ name }}') >>> print t.render(Context({'name':'John'})) Hello, John >>> print t.render(Context({'name':'Julie'})) Hello, Julie >>> print t.render(Context({'name':'Pat'})) Hello, Pat >>> |
django의 template parsing은 꽤나 속도가 빠른 편이다. 대부분의 parsing은 단일 regular expression을 호출하는것을 경유한다. XML 기반의 template engine과 비교해서 극명한 대조를 이룬다. 즉, XML parser 부하의 부하를 발생시키는 것으로 django의 template render engine 보다 훨씬 느리다.
Context 변수 찾기
예제에서 우리는 context에 단순한 값만 전달했는데, 대부분 문자열이였고 datetime 한개가 더 있었다. 그러나 template system은 보다 복잡한 data structure를 처리하는데, 예를 들어, list나 dictionary 그리고 custom object등이다.
django에서는 복잡한 data strucrure의 값을 구하기 위한 traversing은 점(.)문자를 이용하는 것이다. 이러한 '.'을 사용하여 dictionary key, attribute, method, 혹은 object의 index에 접근한다.
예를 들어, python의 dictionary를 template에 전달하는 것을 가정해보라. 저런 dictionary의 값을 접근하기 위해 '.'을 사용한다.
>>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) u'Sally is 43 years old.' >>> |
>>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year 1993 >>> d.month 5 >>> d.day 2 >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}. ') >>> c = Context({'date': d}) >>> t.render(c) u'The month is 5 and the year is 1993.' >>> |
>>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) u'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) u'123 -- 123 -- True' |
마지막으로, '.'는 list index를 접근하는데에도 사용된다.
>>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) u'Item 2 is carrots.' |
template system이 변수명에 '.'을 발견하면, 다음 항목에 대해 순서대로 찾기를 시도한다.
- Dictionary lookup (예, foo["bar"])
- Attribute lookup (예, foo.bar)
- Method call (예, foo.bar())
- List-index lookup (예, foo[2])
즉, system은 처음으로 찾은 type에 대해 작업을 시도한다.
'.'은 여러 단계의 하부를 찾는다. 예를 들어, {{ persion.name.upper }}를 사용하고, dictionay에 persion['name']을 찾아 upper() method를 호출한다.
>>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name.upper }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) u'SALLY is 43 years old.' |
method 호출
method 호출은 다른 type보다도 좀더 복잡하다. 이럴때의 유의할 점을 정리하였다.
- method를 찾는 동안, method가 exception을 발생하였다면, exception이 silent_variable_failure attribute가 True가 아니라면 exception이 전달된다. 만일 exception에 silent_variable_failure attribute가 있다면 변수는 비어있는 문자열이 될 것이다. 아래예를 참고하시오.
>>> t = Template("My name is {{ person.first_name }}.")
>>> class PersonClass3:
... def first_name(self):
... raise AssertionError, "foo"
>>> p = PersonClass3()
>>> t.render(Context({"person": p}))
Traceback (most recent call last):
...
AssertionError: foo>>> class SilentAssertionError(AssertionError):
... silent_variable_failure = True
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
u'My name is .'
- method 호출은 argument가 없는 경우에만 동작한다. 그렇지 않는 경우, 다음 type을 찾는다.
- 명확하게도 몇개의 method는 부작용이 있고, 어리석을 수 있다. 심지어 보안 취약점이 있으며 template system을 access 하도록 허락된다.
예를 들어, BankAccount object는 delete() method를 가지고 있는데, {{ account.delete }}와 같이 templete가 구성되면 template가 render될 때 object는 삭제될 것이다.
이러한 것을 방지하기 위해, 함수 attribute로 alters_data를 다음과 같이 세팅한다.
template system은 이러한 방법 mark된 method를 실행하지 않는다. 위 예를 계속해서, 만약 template가 {{ account.delete }}를 포함하고 deleete() method가 alters_data=True가 있다면 delete() method는 template가 render 될때 실행되지 않고 조용히 실패하게 된다.
무효한(invalid) 변수는 어떻게 처리되는가?
기본으로 만약 변수가 존재하지 않는다면, template system은 비어있는 문자열로 render하고 조용히 실패한다. 아래는 그 예이다.
>>> from django.template import Template, Context >>> t = Template('Your name is {{ name }}.') >>> t.render(Context()) u'Your name is .' >>> t.render(Context({'var': 'hello'})) u'Your name is .' >>> t.render(Context({'NAME': 'hello'})) u'Your name is .' >>> t.render(Context({'Name': 'hello'})) u'Your name is .' |
Context object로 장난하기
대부분의 경우 미리 사전에 모두 생성된 dictionary를 Context()에 전달하여 Context object를 초기화한다. 그러나, 초기화 이후에도 다음과 같이 item을 추가/삭제할 수 있다.
>>> from django.template import Context >>> c = Context({"foo": "bar"}) >>> c['foo'] 'bar' >>> del c['foo'] >>> c['foo'] Traceback (most recent call last): ... KeyError: 'foo' >>> c['newvariable'] = 'hello' >>> c['newvariable'] 'hello' |
Template tag와 Filter 기초
이미 언급했듯이, django의 tmeplate system은 built-in tag와 filter를 가지고 있다. 본 section은 가장 일반적으로 사용되는 tag와 filter에 대해 설명하도록 한다.
tag
if/else
{% if %} tag는 변수값을 평가하고 그 값이 "True"이면 {% if %}와 {% endif %} 사이에 있는 모든것을 표시한다.
{% if today_is_weekend %} <p>Welcome to the weekend!</p> {% endif %} |
{% if today_is_weekend %} <p>Welcome to the weekend!</p> {% else %} <p>Get back to work.</p> {% endif %} |
Python의 "Truthiness" django template system 그리고 python에서 다음 object는 Boolean의 False로 평가한다.
그 이외는 모두 True로 평가한다. |
{% if athlete_list and coach_list %} {% if not athlete_list %} {% if athlete_list or coach_list %} {% if not athlete_list or coach_list %} {% if athlete_list and not coach_list %} |
{% if athlete_list and coach_list or cheerleader_list %} |
{% if athlete_list %} {% if coach_list or cheerleader_list %} We have athletes, and either coaches or cheerleaders! {% endif %} {% endif %} |
{% if athlete_list or coach_list or parent_list or teacher_list %} |
{% if athlete_list %} <p>Here are the athletes: {{ athlete_list }}.</p> {% else %} <p>No athletes are available.</p> {% if coach_list %} <p>Here are the coaches: {{ coach_list }}.</p> {% endif %} {% endif %} |
for
{% for %} tag는 연속적인(sequence) 각각의 item을 loop로 다루도록 해준다. python의 for statement 처럼, 문법은 for X in Y 와 같은데, Y는 loop를 위한 sequence이며, X는 loop에서 사용할 변수의 이름을 의미한다. loop 내의 각각의 시간동안 template system은 {% for %}와 {% endfor %} 사이의 모든것을 render 한다.
예를 들어, athlete_list의 모든 항목을 출력한다.
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul> |
{% for athlete in athlete_list reversed %} ... {% endfor %} |
{% for athlete in athlete_list %} <h1>{{ athlete.name }}</h1> <ul> {% for sport in athlete.sports_played %} <li>{{ sport }}</li> {% endfor %} </ul> {% endfor %} |
{% if athlete_list %} {% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% endfor %} {% else %} <p>There are no athletes. Only computer programmers.</p> {% endif %} |
{% for athlete in athlete_list %} <p>{{ athlete.name }}</p> {% empty %} <p>There are no athletes. Only computer programmers.</p> {% endfor %} |
{% for %} loop 내부에서, forloop라는 임시 변수에 접근할 수 있다. 이 변수는 몇몇 attribute를 가지는데, loop의 진척도 정보를 제공한다.
- forloop.counter는 loop에 들어간 횟수를 리턴한다. 이것은 1부터 시작하는 값이다. 그 예는 다음과 같다.
{% for item in todo_list %}
<p>{{ forloop.counter }}: {{ item }}</p>
{% endfor %} - forloop.counter0는 forloop.count와 같지만, 0부터 시작한다.
- forloop.revcounter는 loop에서 남은 item의 개수를 알려준다. 만일 loop의 마지막이라면 forloop.revcount는 1이다.
- forloop.revcounter0은 forloop.revcount와 같지만, loop의 마지막이라면 forloop.revcount0은 0이다.
- forloop.first는 loop의 처음인 경우 True가 전달된다. 다음과 같은 경우 편리하게 사용된다.
{% for object in objects %}
{% if forloop.first %}<li class="first">{% else %}<li>{% endif %}
{{ object }}
</li>
{% endfor %} - forloop.last는 loop의 마지막인 경우 True가 전달된다. 이것은 다음과 같이 link list간의 pipe 문자를 표시하는데 사용된다.
{% for link in links %}{{ link }}{% if not forloop.last %} | {% endif %}{% endfor %}
Link1 | Link2 | Link3 | Link4
Favorite places:
{% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %} - forloop.parentloop는 중첩된 loop에서 부모 loop의 forloop object를 참조한다. 다음은 그 예이다.
{% for country in countries %}
<table>
{% for city in country.city_list %}
<tr>
<td>Country #{{ forloop.parentloop.counter }}</td>
<td>City #{{ forloop.counter }}</td>
<td>{{ city }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
이와 같은 forloop 변수는 loop에서만 사용가능하다. {% endif %}를 만나면 forloop는 제거된다.
ifequal / ifnotequal
django template system은 python 구문을 단독을 실행시켜 줄 만큼의 program language는 아니다. 그러나, {% ifequal %} tag등을 이용하여 두개의 값을 비교하여 표시하는 방법은 제공하고 있다.
{% ifequal %} tag는 만일 값이 동일하다면 {% ifequal %}와 {% endequal %} 사이의 모든 것을 출력한다.
다음은 그 예이다.
{% ifequal user currentuser %} <h1>Welcome!</h1> {% endifequal %} |
{% ifequal section 'sitenews' %} {% ifequal section "community" %} |
{% ifequal section 'sitenews' %} <h1>Site News</h1> {% else %} <h1>No News Here</h1> {% endifequal %} |
{% ifequal variable 1 %} {% ifequal variable 1.23 %} {% ifequal variable 'foo' %} {% ifequal variable "foo" %} |
{% ifequal variable True %} {% ifequal variable [1, 2, 3] %} {% ifequal variable {'key': 'value'} %} |
주석
HTML 혹은 python에서 django template language는 주석을 허용한다. {# #}를 사용한다.
{# This is a comment #} |
위와 같은 주석은 여러줄을 허용하지 않는다. 아래의 주석은 template 그대로 출력된다.
This is a {# this is not a comment #} test. |
{% comment %} This is a multi-line comment. {% endcomment %} |
Filter
이전에서 잠시 설명했는데, template filter는 출력되기 전 간단하게 변수값을 변환하는 방법을 제공한다. filter는 pipe문자를 사용하는데, 다음의 예와 같다.
{{ name|lower }} |
filter는 이어질(chained)수 있는데, 그 결과가 다음으로 적용된다. 다음은 list 첫 항목을 대문자로 변환한다.
{{ my_list|first|upper }} |
{{ bio|truncatewords:"30" }} |
이외의 주요한 filter는 다음과 같다.
- addslashes : \, ', " 앞에 \문자를 추가한다. 이는 javascript 문자열을 포함하는데 유용하게 사용된다.
- date : date 혹은 datetime object를 주어진 format 문자열로 format한다.
{{ pub_date|date:"F j, Y" }} - length : value의 length를 리턴한다. list인 경우 항목의 개수를 리턴하고, 문자열인 경우 문자 개수를 전달한다. (python의 __len__() method를 참조한다)
철학과 제약
이제 django template language에서 몇몇의 철학에 따른 의도적인 제약에 대해 설명하고자 한다.
web application의 다른 component 이상으로 template syntax는 무척 주관적이고, programmer의 의견 또한 분분하다. python은 수십개의 open source template-language 구현을 가지고 있는데, 각각 모두 모든 template language의 부족한점이 있다고 개발자는 간주하였기 때문이다. (즉, 이는 모든 python 개발자는 자신만의 template language를 가지고 있다는 말이다!)
이런 생각을 가지고, django는 그것의 template language를 사용하는 것을 요구하지 않도록 되어 있다. 왜냐하면 django는 의도적으로 full-stack web framework으로 되어 있다. 그것은 web 생산적이길 원하는 개발자에게 필요한 모든 조각을 제공하는데, 다른 python template library 보다 django의 template system이 훨씬 몇배 더 편리했기 때문이다. 그러나 그것은 엄격하게 요구된건 아니다. 이에 대해서는 다음장을 통해 확인할 것이다. 즉, django로 다른 template language를 사용하는것은 그리 어려운것은 아니다.
그래도 여전히 django의 template language로 작업하는것을 강력히 추천한다. template system은 World Online과 django를 만든이의 경험이 조합되어 만들어져, 그 뿌리를 두고 있다.
- Business logic(비지니스 로직)은 presentation logic(표현 로직)에서 분리되어야 한다. django의 개발자는 표현의 도구로 template system을 볼 것이다. template system은 이러한 기본적인 목표 넘어 있는 기능은 지원하지 않아야 한다.
이러한 이유로, django template에는 직접적인 python code를 넣을 수 없다. 모든 "programming"은 근본적으로 template tag가 무엇을 할 수 있는 것의 범위(scope)를 제약한다. 독단적인 custom template tag를 작성하는 것은 가능하나, 격이 다른 django template tag는 의도적으로 독단적인 python code 실행을 허락하지 않는다. - 문법은 HTML/XML으로 부터 분리되어야 한다. 비록 django의 template system이 HTML을 생산하는데 일차적으로 사용되는데, plain-text와 같은 non-HTML format에도 사용하도록 되어 있다. 몇몇 다른 template language는 XML 기반인데, 모든 template logic을 XML tag나 attribute로 놓고 진행하나 django는 d의도적으로 이 제약을 피했다. template를 작성하는데 유효한 XML을 요구한다는 것이 인간의 실수나 이해하기 어려운 오류 메시지등을 유발하기 때문이다. 그리고 XML 엔진은 처리하는데 큰 부하를 유발한다.
- Designer는 HTML code에 편안함을 느낀다고 가정한다. template system은 Dreamweaver와 같은 WYSIWYG 편집기에서 보여지는대로 나타나도록 설계된것이 아니다. 그것은 너무한 제약사항이며 친숙한 문법은 아니다. django는 HTML을 직접 편집하는데 편안하게 할 수 있도록 기대한다.
- Designer는 python 개발자가 아닌 것으로 가정한다. template system을 사용하는 개발자는 web page template는 programmer가 아닌 designer에 의해 작성되어야 한다고 인지하며, 그리하여 python 지식을 가정하지 않는다.
그러나, system은 역시 template를 생성할 python programmer로 구성된 작은 팀을 수용할 수 있도록 의도하기도 한다. raw python code를 작성하여 문법을 확장할 수 있는 방법을 제공한다. - 새로운 programming language를 개발하는것이 목적이 아니다. 충분한 프로그래밍풍의 기능을 제공하는 것인데, 분기문이라던지 loop등이 포함된다. 이는 표현하는데 관련된 결정을 구현하는데 필수적이기 때문이다.
view에서 template 사용하기
template system을 사용하는 기초를 확인하였다. 이제 view를 생성하는 방법을 알아볼 것이다. mysite.views에 있는 current_datetime view를 상기해보아라. 이는 이전 chapter에서 다루었으며, 다음과 같은 모습을 보인다.
django의 template system을 사용하기 위해 view를 변경해 보자. 우선 다음과 같은 것을 생각해 볼 수 있다.
물론, template system을 사용하나 이번 chapter의 소개때 지목하기도 했던 문제를 해결하진 못하고 있다. 다시 말해, template는 여전히 python code에 내장되어 있다. 그래서 data와 표현의 분리를 진정으로 분리하는 것이 달성되지 않았다. 이제 template를 분리된 파일에 놓고, view가 직접 load하는 것을 만들어 보자.
당신의 filesystem상의 어느곳에 template를 저장할 것인지를 우선 고려해야 하고, python의 built-in file 열기 기능을 이용하여 template의 내용을 읽도록 한다. 아래는 /home/djangouser/templates/mytemplate.html에 저장되어 있다고 가정하고 작성된 것이다.
이러한 접근은 그러나 다음과 같은 이유로 그다지 매력적이지 않다.
- file이 없는 경우 처리할 수 없다. 만약 mytemplate.html이 존재하지 않거나 읽을 상태가 아니라면, open()는 IOError exception을 발생한다.
- template의 위치를 hard-code하게 된다. 만일 모든 view에 이런 논리를 적용한다면, template 위치까지도 복사해야 한다. 수많은 typing은 굳이 언급하지 않아도 알 것이다.
- 끓고 있는 보일러 판을 포함하고 있다. open(), fp.read() 그리고 fp.close()이 template가 로드될때 마다 호출된다.
이러한 문제를 풀기 위해 template loading과 template directory를 사용한다.
Template Loading
(주의)
아래 부분은 과거 django 버전인 경우로, 현재 1.8 이상의 버전에서 TEMPLATE_DIRS는 지원되지 않는다. Tempate directory를 지정하는 방법은 https://docs.djangoproject.com/en/1.8/ref/settings/#template-dirs에 기술된 것처럼, setting.py의 TEMPLATE = [ ... 'DIRS': [BASE_DIR, MY_DIR, ...... 와 같이 DIRS에 기입해야 한다.
django는 filesystem으로 부터 template를 load할때 사용되는 편리하고 강력한 API를 제공하는데, template-load 호출과 template 그 자체에 대한 중복된 부분을 제거하는데 그 목표를 두고 있다.
template-loading API를 사용하기 위해 저장된 template이 있는 위치를 framework에 알려줘야 한다. 이는 settings.py에 지정되어 있다.
settings.py에 TEMPLATE_DIRS setting을 찾을 수 있다. 기본적으로 비어있는 tuple로 되어 있는데, 다음과 같이 자동으로 생성된 주석을 포함하고 있다.
이러한 setting은 template를 찾는 mechanism에 알려진다. TEMPLATE_DIR에 당신이 저장한 template의 위치를 추가할 수 있다.
여기서 주목해야 할 점을 몇기 기술한다.
- 원하는 directory를 작성할 수 있는데, web server가 동작하는 계정에 의해 읽을 수 있는 directory여야 한다. 만약 적당한 경로를 찾지 못하였다면 project 경로에 template를 만들것을 추천한다.
- 만약 TEMPLATE_DIRS가 하나의 directory만 포함한다면, 마지막의 ,를 빼먹으면 안된다.
- 만약 windows에서 동작한다면, \ 대신 /를 사용하도록 한다.
- 절대 경롤ㄹ 이용하면 편리하다. 만약 보다 유연하고 느슨하게 결합되기를 바란다면, TEMPLATE_DIRS에 동적인 경로를 넣도록 하는 것이다. 다음과 같다.
이런 경우, __file__이라는 python의 변수를 사용하고 있는데, python 모듈의 경로를 자동으로 세팅해 준다. 즉, settings.py(os.path.dirname)을 포함하는 경로를 구하고, templates를 join한다(os.path.join). 그다음 \ 대신 /를 사용하도록 치환한다(windows인 경우).
TEMPLATE_DIRS가 세팅되었다면, 그 다음으로 django의 template load하는 부분을 기존의 hard-code된 것으로 부터 수정한다. current_datetime view로 돌아와서, 다음과 같이 수정해 보자.
본 예에서 django.template.loader.get_template()를 사용한다. get_template() 함수는 template 이름을 argument로 받고 filesystem에 있는 template를 찾고 open한 다음 그리고 compile된 Template object를 리턴한다.
본 예의 template는 current_datetime.html로 되어 있는데, 아직 별다른 .html 확장자의 파일도 없다. 확장자는 붙이거나 빼거나 해도 무방하다.
filesystem 상의 template의 위치 찾기를 결정하기 위해, get_template()는 TEMPLATE_DIRS의 경로와 get_template()에 전달한 template 이름을 결합한다. 예를 들어, TEMPLATE_DIRS이 '/home/django/mysite/templates'로 되어 있고 위 예처럼 get_template()가 호출하면 /home/django/mysite/templates/current_datetime.html를 찾게 된다.
만약 get_template()에서 주어진 이름으로 찾지 못했다면, TemplateDoesNotExist exception이 발생한다. 현재 위 예를 가지고 실험하면 아래와 같은 오류가 발생한다.
template directory에 아래와 같은 current_datetime.html을 생성하면 오류없이 실행된다.
<html><body>It is now {{ current_date }}.</body></html> |
render_to_response()
이제까지 template를 어떻게 load하는지를 보아왔다. 그것은 Context를 채우고 HttpResponse object를 rendered template 결과를 가지고 리턴한다. hard-coding된 template와 경로 대신에 get_template()를 사용하여 최적화 하였다. 그러나 여전히 몇가지가 요구되어 졌다. django는 template를 load, render, HttpResponse 리턴하는데 지름길을 제공한다. 즉, 모든것을 하나의 line으로 해결하는 것이다.
이러한 지름길이 render_to_response()로, django.shortcuts module에 있다. 대부분의 경우 template를 load하고 Context를 생성하고 HttpResponse object를 수동으로 생성하여 리턴하는등의 작업 대신 render_to_response()를 사용한다.
여기까지의 current_datetime 예제를 render_to_response()로 적용하면 아래와 같다.
여태까지와 비교해서 변경된것은 다음과 같다.
- get_template, Template, Context, 혹은 HttpResponse를 import할 필요가 없다. 대신 django.shortcuts.render_to_response만 import한다. import datetime은 남아있다.
- current_datetime 함수에서 여전히 now를 계산하지만 template loading, context 생성, template rendering 그리고 HttpResponse 생성은 render_to_reponse() 호출에 의해 제거되었다.
render_to_response()는 HttpResponse object를 리턴하기 때문에, 간단히 view 함수의 리턴으로 사용할 수 있다.
render_to_reponse()의 첫 argument는 사용할 template의 이름이다. 두번째 argument는 만약 지정된다면, template를 위한 Context 생성에 사용될 dictionary이다. 만약 두번째 argument가 없다면, render_to_response()는 비어있는 dictionary를 사용한다.
locals() 속임수
이제 마지막 current_datetime을 고려해 보자.
많은 경우, 주어진 예에서 몇몇 값을 계산하고, 변수에 저장하며 template에 전달한다. 게으른 programmer는 template 변수명을 위한 이름을 주는 것과 임시 변수를 위해 이름을 주는 것이 약간은 쓸모 없을 것으로 보여질 것이다. 쓸모 없을 뿐 아니라 추가적인 typing이라고 간주한다.
만약 당신이 게으른 programmer이고 축약된 code를 선호한다면, local()이라는 built-in python 함수를 사용할 수 있다. 그것은 지역(local) 변수와 값을 매핑한 dictionary를 리턴하는데, local 이라는 뜻은 현재 범위(scope)내에 정의된 변수들을 의미한다. 그래서 위의 코드는 다음과 같이 다시 작성될 수 있다.
앞선 예와 같이 context dictionary를 수동으로 지정한 대신, local()을 사용하였는데, 함수의 실행 싯점의 정의된 모든 변수가 포함된다. 결과적으로 now 변수를 current_date로 이름을 변경하였는데, 이는 template가 예상하는 변수명이기 때문이다. 본 예에서, local()는 거대할 정도로 향상을 가져오진 않는데, 다른 임시 변수의 사용을 줄일 수 있거나 게으를 때등, typing을 줄여주는 효과가 있다.
get_template()의 subdirectory 사용
template를 단일 경로로 사용되지 않는 경우도 많다. template directory의 하부 directory에 template를 저장하는 것을 선호할 수 있다. django에서도 이를 추천하며, 추후 11장에서 다룰 이러한 template layout을 기본 관습(convention)으로 간주한다.
template directory의 하부 directory를 지정하는 것은 간단하게 해결된다. get_template()에 하부 directory와 template 이름을 아래와 같이 명기하면 된다.
render_to_response()도 같은 논리로 접근할 수 있다.
include template tag
여기까지 template-loading system을 살펴보았는데, {% include %}라는 built-in template tag의 장점을 살펴보기로 하자. 이 tag는 다른 template의 content를 포함하도록 도와준다. tag의 argument는 포함할 template 이름이며, 그 이름은 (따옴표로 쌓인)hard-code 혹은 변수가 가능하다. 여러개의 template중 동일한 것들을 모으고 {% include %}를 사용하여 이러한 중복을 제거할 수 있다.
아래는 nav.html이라는 template의 내용을 포함하는 예제이다. ' 혹은 " 따옴표는 허용된다.
{% include 'nav.html' %} {% include "nav.html" %} |
{% include 'includes/nav.html' %} |
{% include template_name %} |
포함된 template는 그것을 포함하는 template의 context와 함께 계산되어 진다. 예를 들면, 아래 두개의 예를 들 수 있다.
# mypage.html <html> # includes/nav.html <div id="nav"> |
만일, {% include %} tag 안의 주어진 template 이름이 발견되지 않는다면, django는 다음 두개중 하나를 실행한다.
- DEBUG가 True라면, TemplateDoesNotExist 예외가 발생하여 오류 page가 표시된다.
- DEBUG가 False라면, 아무것도 표기하지 않고 조용히 넘어간다.
Template Inheritance(상속)
여태껏 봐왔던 template 예제는 매우 작은 HTML로 구성되었는데, 실제로는 규모의 HTML로 구성된 template system을 사용할 것이다. 이는 다음과 같은 공통된 web 개발 문제를 야기한다 : web site를 통틀어 sitewide navigation(사이트맵)과 같은 동일한 page 영역에 대해 불필요한 중복등을 제거할 순 없을까?
이 문제를 해결하기 위한 전통적인 방법은, server-side include를 사용하는 것인데, 당신의 HTML page에 다른 web page를 그 안으로 "포함(include)"하여 놓도록 지시한다. django는 이러한 접근방법을 {% include %} template tag를 이용하여 지원한다. 그러나 이러한 문제를 풀기위한 다른 선호된 방법은 template inheritance을 이용하는 것이다.
본질적으로 template inheritance는 당신 site의 모든 공통된 부분을 포함하는 "뼈대(skeleton)" template를 기반(base)을 만들게 하고, "blocks"라는 override 가능한 자식 template를 정의하도록 한다.
우리의 current_datetime view를 위해 template를 좀더 완성하여 본다. 다음은 current_datetime.html 파일이다.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <hr> |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <hr> |
server-side include 해결 방식은 각 template의 공통된 부분을 뽑아내고 그것의 개별적인 template로 저장하는데, 그것은 다른 template에 의해 포함되어 진다. header.html로 호출될 화면 윗 부분의 template를 만들어 보자.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> |
<hr> <p>Thanks for visiting my site.</p> </body> </html> |
django의 template inheritance system은 이러한 문제를 해결하려고 만들어졌다. 단순하게 server-side include를 뒤집은 것으로 생각하면 된다. 공통된 부분 조각을 정의하는 대신, 다른 부분을 정의하도록 한 것이다.
처음할일은, base template를 정의하는 것이다. 그것은 이후에 정의될 자식(child) templates로 채워질 skeleton을 의미한다. 아래는 base template 예이다.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html> |
여태껏 보지 못했던 {% block %} tag가 등장하는데, template의 부분을 child template가 override 해주도록 template engine에 알려준다.
이제 base template를 가졌으니, current_datetime.html을 만들어 보자.
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} |
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} |
이제 그 동작 방법을 알아보자. current_datetime.html을 load하면, template engine은 {% extends %} tag를 보게 되고 이 template는 child template임을 인지한다. engine은 즉각 부모 template인 base.html을 load한다.
이때 template engine은 base.html에 3개의 {% block %} tag를 발견하고 child template의 내용과 교환하게 된다. 그래서 {% block title %}에서 정의한 title은 사용되고, 역시 {% block content %}도 그러하다.
child template는 footer block이 정의되지 않음을 주목하라. parent template로 부터 값을 대신한다. parent template의 {% block %} tag에 있는 내용(content)은 만일을 위한 대비책으로 들어간 것이다.
inheritance는 template context에 영향을 주진 않는다. 다시 말해 inheritance tree 상의 어떤 template은 모든 context로 부터 변수값에 대한 접근이 가능하다.
필여한 만큼 많은 level의 inheritance를 사용할 수 있다. inheritance를 사용하기 위한 다음의 3가지 level의 접근법이 일반적이다.
- 당신 site의 look-and-feel을 가지는 base.html을 생성한다. 이것은 거의 변경되지 않아야 한다.
- 당신 site의 각 "section"에 들어갈 base_SECTION.html template를 생성한다(예, base_photos.html 그리고 base_forum.html). 이러한 template는 base.html을 확장(extend)하고 section별로 style/design을 포함한다.
- page 각 type을 위한 개별 template를 생성한다. 이는 forum page나 photo gallery와 같을 것이다. 이 template는 section template를 확장(extend)한다.
이러한 접근법은 code 재활용성을 최대화하고 공유된 영역에 대해 손쉽게 추가할 수 있도록 한다.
template inheritance를 가지고 작업하는 몇가지 guideline을 공유한다.
- {% extents %}를 사용하고자 한다면, template에 있는 첫 부분에 넣도록 하라. 그렇지 않으면 동작하지 않는다.
- 일반적으로 {% block %} tag가 많으면 많을수록 좋다. child template는 모든 parent block을 재정의할 필요는 없다. 그래서 적당하게 기본적인 block만 채워넣는다.
- template에서 code가 중복되고 있다면, parent template에 {% block %}로 이동하자.
- parent template으로 부터 block의 내용을 구할 필요가 있다면, {{ block.super }}를 이용한다. 이는 parent template의 render된 text를 제공하여 준다. 이는 전체를 재정의하는 대신 일부를 추가할 때 유용하게 사용된다.
- 동일한 이름의 여러개의 {% block %} tag를 정의하지 않도록 한다. 이 제약은 "양쪽(both)"의 방향에서 동작해야 하기 때문이다. block tag는 채워질 구멍을 제공하는 것 뿐만 아니라, 부모(parent)의 구멍을 채우기도 하기 때문이다. 2개의 동일한 이름의 {% block %}이 있다면, template의 parent는 어떤 block을 사용해야 할 지 모른다.
- {% extends %}로 전달된 template는 get_template() 사용에 의해 load된다. 다시 말해, TEMPLATE_DIRS setting에 추가된 이름이어야 한다.
- 대부분의 겨우 {% extands %}에 들어가는 argument는 문자열이여야 하는데, 변수값도 가능하여 parent 이름도 runtime에 결정될 수 있어, 멋지게 동적인 것을 만들게 해준다.
'django > the django book study' 카테고리의 다른 글
[06] django admin site (3) | 2012.04.27 |
---|---|
[05] django의 model (8) | 2012.04.12 |
[03] django 1.4의 view와 urlconfs (5) | 2012.04.04 |
[02] django 1.4 설치와 project 생성하기 (1) | 2012.03.30 |
[01] django 소개 (1) | 2012.03.28 |