이전 장에서 View에서 텍스트를 반환하는 방법에서 특이한 점이 있었을 것인데,
아래와 같이 HTML이 하드코딩 되었다는 점입니다.
def current_datetime(request):
now = datetime.datetime.now()
html = "It is now %s." % now
return HttpResponse(html)
이 방법은 작동원리를 설명하기에는 정말 편리하겠지만, HTML을 하드코딩 하는 방법은 좋은 방법이 아닙니다.
이유는 아래와 같습니다.
1. 페이지의 디자인을 변경하려면 PYTHON 코드를 변경해야합니다. 사실 HTML과 CSS 등은 파이썬 코드보다 훨씬더 자주 변경됩니다. 그렇기 때문에 파이썬코드와 디자인을 변경하는 편이 훨씬 더 편할 것입니다.
2. 일반적인 웹페이지 템플릿에는 수백에서 수천줄의 HTML과 스크립트가 있습니다.
3. 파이썬 코드를 작성하고 HTML을 디자인 하는것은 각각 다른분야입니다. 결국 나누게 되면 각각의 책임을 별도의 사람들에게 나눠서 분업할 수 있다는 장점이 있습니다.
4. 백단이 Python 코드로 작업할 수 있고 프론트 단이 동시에 템플릿을 작업할 수 있다면 가장 Best 하다고 볼 수 있고, 한 사람이 다른 사람이 파이썬과 HTML을 모두 포함하는 하나의 파일 편집을 끝내기를 기다릴 필요가 없습니다.
즉 분업이 완벽하게 될 수 있다고 볼수 있습니다.
이러한 1~4의 이유로 페이지의 디자인을 파이썬 코드 자체와 분리하는 것이 훨씬 깔끔하고 유지보수가 용이하다고 볼 수있습니다.
우리는 이것을 Django의 템플릿 시스템으로 할 수 있습니다.
우선 Django의 템플릿은 문서의 표시를 데이터와 분리하기 위한 텍스트 문자열입니다.
[Django 템플릿의 예입니다.]
<html>
<head>
<title>Ordering notice</title>
</head>
<body>
<h1>Ordering notice</h1>
<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>
<p>Here are the items you've ordered:</p>
<ul>
{% for item in item_list %}
<li>{{ item }}</li>{% endfor %}
</ul>
{% 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>
</body>
</html>
이 템플릿은 기본 HTML이며, 일부 변수와 템플릿 태그가 들어있습니다.
단계별로 살펴보겟습니다.
1. 한 쌍의 중괄호로 묶인 텍스트 ( EX : {{ pserson_name }} ) 는 변수입니다.
그렇다면 변수값을 어떻게 할당할 것인지에 관한건 조금뒤에 살펴보기로 합시다.
2. 중괄호와 %기호 (EX : {% if ordered_warranty %} )로 둘러싸인 텍스트는 템플릿 태그입니다.
태그의 정의는 매우 광범위 합니다. 이 태그는 템플릿 체제에 "무언가를 하도록" 지시합니다.
3. 위의 예제 템플릿은 for 태그와 if 태그를 포함합니다.
for 태그는 Python의 for 문과 매우 유사하게 작동하고 if 태그는 예상대로 if 문으로 작동합니다.
4. 마지막으로 위의 예제를 보시면 filter 예가 포함되어있습니다. {{ ship_date|date:"F j, Y" }} 인데요, ship_date 변수를 date 필터에 전달합니다. date 필터는 해당 인수에 지정된 날짜 형식으로 반환합니다.
필터는 Unix 파이프 방식으로 문자 (|)를 사용하려 연결됩니다.
각 Django 템플릿은 여러 내장 태그와 필터에 액세스할 수 있습니다.
[템플릿 시스템 사용하기]
Django 프로젝트는 하나 또는 여러개의 템플릿 엔진으로 구성 될 수 있습니다.
Django는 자체 템플릿 시스템을 위한 Django-Template-Language(DTL)을 제공합니다.
장고 1.8부터는 Jinja2 도 지원하기 시작하였습니다.
다른 백엔드를 선택할 이유가 없다면 DTL을 무조건 사용하십쇼
Django에서 contrib 에서 django 템플릿을 포합합니다.
django.contrib.admin 처럼 DTL을 사용하시기 바랍니다. 이번 포스트의 모든 예제는 DTL을 사용할 것입니다.
Django의 템플릿 시스템을 파이썬 코드로 사용할 수 있는 가장 기본적인 방법은 아래와 같습니다.
1. raw 템플릿 코드를 문자열로 제공해서 템플릿 객체를 만듭니다.
2. 주어진 변수세트 template.Context({'name': 'Nige'}) 로 Template 객체의 render() 메소드를 호출하세요
이렇게 하면 완전히 렌더링 된 템플릿이 문자열로 반환되며 모든 변수와 템플릿 대그는 Context 에 따라서 Return 이 다르게 됩니다.
>>> from django import template
>>> t = template.Template('My name is {{ name }}.')
>>> c = template.Context({'name': 'Nige'})
>>> print (t.render(c))
My name is Nige.
>>> c = template.Context({'name': 'Barry'})
>>> print (t.render(c))
My name is Barry.
[템플릿 객체 만들기]
Template 객체를 생성하는 가장 쉬운 방법은 직접 인스턴스화 하는 것입니다.
Template 클래스는 django.template 모듈에 존재하고 있고, 생성자는 raw 템플릿 코드인 하나의 인수를 가집니다. (Ex : Template('My name is {{ name }}.') )
파이썬 인터프리터에서 해당 코드를 어떻게 해석하는지 살펴보겠습니다.
이전 포스트에서 만든 mysite 프로젝트 디렉토리에서 python manage.py shell 을 입력해서 인터프리터를 시작합니다.
아래의 코드를 칩니다.
>>> from django.template import Template
>>> t = Template('My name is {{ name }}.')
>>> print (t)
그럼 아마 아래와 같이 결과가 나올겁니다.
<django.template.base.Template object at 0x030396B0>
0x030396B0는 매번 달라지며 관련이 없습니다. Template 객체를 만들면 템플릿 시스템에서 Raw 템플릿 코드를 내부의 최적화 된 형식으로 컴파일 하여 랜더링 할 준비를 합니다.
그러나 템플릿 코드에 구문 오류가 있는 경우 Template() 을 호출하면 TemplateSyntaxError 가 발생합니다.
>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
File "", line 1, in ?
...
django.template.base.TemplateSyntaxError: Invalid block tag: 'notatag'
TemplateSnytaxErorr는 아래과 같은 경우에 발생합니다.
1. 유효하지 않은 tag
2. 태그는 유효하지만, 잘못된 인수가 있을경우.
3. 유효하지 않은 필터
4. 필터는 유요하지만, 잘못된 인수가 있을경우.
5. 잘못된 템플릿 구문.
6. 닫히지 않은 태그.
[템플릿 랜더링하기]
Template 객체를 얻은 후에는 컨텍스트를 지정하여 데이터를 전달 할 수 있습니다.
컨텍스트는 단순히 템플릿 변수 이름 및 관련 값의 집합입니다.
템플릿은 이 컨텍스트를 변수를 사용하여 템플릿에 변수를 할당하고 태그를 평가합니다.
컨텍스트는 Django 에서 django.template 모듈에 있는 Context 클래스로 표현됩니다.
생성자는 하나의 선택적 인수를 사용하는데, 즉 변수이름을 변수값에 맵핑하는 딕셔너리를 사용합니다.
컨텍스트를 사용해서 Template 객체의 render() 메서드를 호출하여 템플릿을 채웁니다.
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
'My name is Stephane.'
* 여기서 잠깐 : 왜 쉘에서 python3 로 실행하면 되지 python manage.py shell로 실행하나요?
=> 가장 큰 차이는 Django shell은 Django 설정파일을 기본적으로 불러오기 때문입니다.
인터프리터를 시작하기 전에 Django에게 어떤 설정파일을 사용할 것인지를 알려줍니다. 템플릿 시스템을 포함한 Django의 많은 부분은 설정에 의존하는 부분이 많고, 프로그램워크가 사용할 설정을 아는 경우가 아니라면 사용할 수 없습니다.
Django shell을 실행하면 Django는 DJANGO_SETTINGS_MODULE이라는 환경변수를 찾습니다.
이는 settings.py의 경로를 말합니다.
예를들면 DJANGO_SETTINGS_MODULE은 아마 mysite.settings 일겁니다.
이제 python manage.py shell 을 실행할때 이 명령은 DJANGO_SETTINGS_MODULE 설정을 처리합니다.
[딕셔너리와 컨텍스트]
파이썬의 딕셭너리는 키와 변수값 사이를 맵핑하는 것인데, Context는 사전과 유사하지만 컨텍스트는 추가 기능을 제공합니다.
>>> 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(2015, 7, 2),
... 'ordered_warranty': False})
>>> t.render(c)
"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor Equipment. It\
's scheduled to\nship on July 2,2015.</p>\n\n\n<p>You didn't order a warranty, so you\
're on your own when\nthe products inevitably stop working.</p>\n\n\n<p>Sincerely,<br\
/>Outdoor Equipment</p>"
1. 먼저 django.template 모듈에 있는 Template과 Context 클래스를 가져옵니다.
2. 템플릿의 raw 텍스트를 raw_template 변수에 저장합니다.
3. 여러 줄에 걸쳐있기 때문에 문자열을 지정하기 위해서 """를 사용합니다.
4. raw_template을 Template 클래스 생성자에 전달해서 템플릿 객체 t를 만듭니다.
5. 파이썬 표준 라이브러리에서 datetime 모듈을 가지고 옵니다.
6. 그런 다음 Context 객체를 만듭니다. Context 생성자는 변수이름을 값에 맵핑하는 딕셔너리를 사용합니다.
7. 마지막으로 템플릿 객체에서 render() 메소드를 호출하여 컨텍스트를 전달합니다.
이것은 랜더링 되어진 템플릿을 반환합니다. 즉 템플릿 변수를 실제 변수값으로 바꾸고 템플릿 태그를 실행한 값을 반환합니다. ( 랜더링 되어진 템플릿은 필터까지 같이 처리된 결과가 리턴됩니다. )
[같은 템플릿에 여러가지의 콘텍스트]
Template 객체를 얻으면 이를 통해 여러가지 컨텍스트를 랜더링 가능합니다.
>>> 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
이와 같은 여러 콘텍스트를 랜더링 하기 위해서 동일한 템플릿 소스를 사용할 때마다 Template 객체를 한번 생성한 다음에 render ()를 여러번 호출 하는 것이 훨씬 효율적입니다.
# Bad
for name in ('John', 'Julie', 'Pat'):
t = Template('Hello, {{ name }}')
print (t.render(Context({'name': name})))
# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print (t.render(Context({'name': name})))
Django의 템플릿 파싱은 꽤 빠른편이고, 내부적으로는 정규식 기반으로 작동합니다. 이것은 XML 기반 템플릿 엔진과 완전히 대조됩니다. XML파서의 오버헤드도 없기 때문에 일반적으로 XML기반 템플릿 엔진보다 훨씬 빠릅니다.
[ 콘텍스트 .검색 ]
지금까지의 예제는 컨텍스트에서 간단한 값을 전달하였습니다. 대부분 문자열과 datetime 예제였습니다. 그러나 템플릿 시스템은 리스트, 딕셔너리 및 사용자정의 객체와 같이 보다 복잡한 데이터 구조를 효율적으로 처리합니다. Django 템플릿에서 복잡한 데이터 구조를 탐색하는 핵심은 dot(.) 입니다.
.을 사용해서 딕셔너리 키, 속성, 메소드 또는 index에 액세스 하세요.
이것은 몇가지 예를 통해 설명해드리겠습니다.
예를들어, 파이썬 딕셔너리를 템플릿에 전달한다고 가정하볼게요.
해당 딕셔너리의 값에 딕셔너리 키로 액세스 하려면 점을 사용하십시오.
>>> 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)
'Sally is 43 years old.'
비슷하게 객체 속성에 대한 엑세스도 마찬가지입니다.
예를 들어, 파이썬 datetime.date 객체는 year, month, day 속성을 가지고 있으며, .을 사용하여 장고 템플릿의 속성에 엑세스할수 있습니다.
>>> 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)
'The month is 5 and the year is 1993.'
아래의 예제에서는 사용자 정의 클래스를 사용해서 dot(.) 이 임의의 객체에 대한 속성 액세스도 허용한다는 것을 보여줍니다.
>>> from django.template import Template, Context
>>> class Person(object):
... def __init__(self, first_name, last_name):
... self.first_name, self.last_name =
first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
'Hello, John Smith.'
.은 객체의 메서드를 참조할 수도 있습니다. 예를 들어, 각각의 파이썬 문자열은 upper() 와 isdigit() 메서드를 가지고 있으며, 같은 닷 구문을 사용하여 Django 템플릿에서 호출 가능합니다.
>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
'123 -- 123 -- True'
파이썬과 다르게 메서드 호출에 괄포를 포함하지 않는것을 보세요. 또한 메서드에 매개변수를 전달할 수도 없습니다. 매개 변수가 필요없는 메서드만 호출 가능합니다. 마지막으로 점들도 리스트에 엑세스 하는데 사용됩니다 예를 들면 아래와 같습니다.
>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas',
'carrots']})
>>> t.render(c)
'Item 2 is carrots.'
- 연산자를 가진 색인은 허용되지 않습니다. 예를들어 템플릿 변수 {{items.-1}} 은 TemplateSyntaxError를 발생시킵니다.
템플릿 . 조회를 요약하자면 템플릿 시스템이 변수 이름에 점을 발견하면 다음 순서로 조회합니다.
1. Dictinary 조회
2. 속성 조회
3. 메서드 콜링.
4. 리스트 인덱스 조회
시스템은 동작하는 첫번째 조회 유형을 사용합니다. 도트 조회는 여러 단계로 중첩도 가능합니다. 예를 들어 다음예제는 {{person.name.upper}}를 사용합니다.
딕셔너리 조회 (person['name'])과 메소드 호출 (upper())로 변환됩니다.
>>> 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)
'SALLY is 43 years old.'
[메소드 호출 방법]
메소드 호출은 다른 조회 유형보다 조금 더 복잡합니다. 다음은 유의사항입니다. 메소드 조회중에 메서드가 예외를 발생시키면 silent_variable_failure 값이 Ture이면 변수는 엔진의 string_if_invalid ( 기본적으로 빈 문자열 )으로 렌더링 됩니다.
아래의 예제를 보세요.
>>>
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(Exception):
... silent_variable_failure = True
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
'My name is .'
* 메서드 호출은 메서드가 매개변수가 필요없는 경우에만 작동합니다. 그렇지 않으면 시스템은 다음 조회유형으로 (리스트) 이동합니다.
* Django에서는 템플릿에서 사용할 수 있는 처리량을 의도적으로 제한하기 때문에 템플릿 내에서 엑세스 되는 메서드 호출에 매개변수를 전달 할 수 없는 것입니다.
[ 유효하지 않은 변수를 처리하는 방법 ]
일반적으로 변수가 없으면 템플릿 시스템은 엔진의 string_if_invalid 구성 옵션값을 삽입합니다 (기본적으로 빈 문자열입니다.)
>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
'Your name is .'
>>> t.render(Context({'var': 'hello'}))
'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
'Your name is .'
이러한 방법은 사람의 Operation 에따라 오류가 해결될 수 있기 때문에 예외를 발생시키는 것보다 효율적인 방법입니다.
이러한 경우 변수의 대소문자나 이름이 잘못되어서 모든 조회가 실패하였습니다.
현실적으로는 작은 템플릿 구문 오류 ( 대소문자, 이름실수 등 )으로 인해 웹사이트에 엑세스 할수 없게되는것은 굉장히 비효율 적입니다.