MVT에서 UI를 담당하는 것이 템플릿 시스템이다. 장고의 템플릿 시스템은 템플릿 문법으로 작성된 템플릿 코드를 해석하여 템플릿 파일로 결과물을 만든다. 이 과정을 렌더링이라고 한다. 결과물인 템플릿 파일은 HTML, XML, JSON 등의 단순한 텍스트 파일이다.
템플릿 코드 vs 템플릿 파일
렌더링 전의 템플릿 문법에 따라 작성된 파일은 템플릿 코드, 렌더링 후의 결과물인 HTML과 같은 텍스트 파일은 템플릿 파일
템플릿 코드에서 사용하는 변수는 다음과 같은 형식이다.
{{ variable }}
템플릿 시스템은 변수를 평가해서 변수값으로 출력해준다. 변수의 속성에 접근할 수 있는 도트(.) 표현식도 가능한데 이 도트는 파이썬 언어와는 다르다. 템플릿 문법에서 도트(.)를 만나면 장고는 다음 순서로 찾기(lookup)를 시도한다.
foo.bar라는 템플릿 변수가 있다면 다음과 같이 해석한다.
템플릿 시스템은 정의가 되어 있지 않은 변수를 사용하는 경우, 빈 문자열('')로 채워주고, 이 값을 변경하려면 settings.py 파일에 다음과 같은 속성을 지정해주면 된다. 디폴트는 빈 문자열이다.
TEMPLATE_STRING_IF_INVALID
필터란 일반적인 용어로 어떤 객체나 처리 결과에 추가적으로 명령을 적용하여 해당 명령에 맞게 최종 결과를 변경하는 것을 말한다. 템플릿 변수에 필터를 적용하여 변수의 출력 결과를 변경할 수 있다.
필터는 파이프(|) 문자를 사용한다.
name 변수 값의 모든 문자를 소문자로 바꿔주는 필터
{{ name|lower }}
필터를 체인으로 연결할 수 있다. text 변수값 중에서 특수 문자를 escape하고, 그 결과 스트링에 HTML <p> 태그를 붙여준다.(linebreaks)
{{ text|escape|linebreaks }}
몇 가지의 필터는 인자를 가질 수 없다. bio 변수 값 중 앞에 30개의 단어만 보여주고, 줄 바꿈 문자는 모두 없애준다.
{{ bio|truncatewords:30 }}
필터의 인자에 빈칸이 있는 경우 따옴표로 묶어준다. 만일 list가 ['a', 'b', 'c']라면 결과는 "a // b // c"가 된다.
{{ list|join:" // " }}
value 변수값이 False이거나 없는 경우, "nothing"으로 보여준다.
{{ value|default:"nothing" }}
value 변수값의 길이를 반환한다. value가 스트링이나 리스트인 경우도 가능하다.
{{ value|length }}
value 변수값에서 HTML 태그를 모두 없애준다. 100% 보장은 안해준다.
{{ value|striptags }}
results.html에서 사용했던 복수 접미사 필터이다.
{{ vlaue|pluralize }}
value 변수값이 1이 아니면 복수 접미사 s를 붙여준다. 다른 복수 접미사 es 또는 ies를 붙일 때는 필터에 인자를 사용한다.
{{ value|pluralize:"es" }}
또는
{{ value|pluralize:"ies" }}
더하기 필터. 데이터 타입에 따라 결과가 달라지므로 주의.
{{ value|add:"2" }}
value 변수값과 add 필터의 인자가 모두 int 타입이라고 간주하고 덧셈을 시도한다. 이 시도가 실패하면 타입이 허용하는 문법에 따라 더하기를 시도한다. 실패할 경우 빈 문자열을 반환한다.
리스트에 담겨 있는 항목들을 순회하면서 출력 가능
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
변수를 평가하여 True이면 바로 아래 문장이 표시.
{% if %}태그에 필터와 연산자를 사용할 수 잇다. 주의할 점은 대부분의 필터가 스트링을 반환하므로 산술 연산이 안되는데 length 필터는 예외적으로 가능하다.
{% if athlete_list|length > 1 %}
POST 방식의 form을 사용하는 템플릿 코드에서는 CSRF 공격을 방지하기 위해 {% csrf_token %}태그를 사용해야 한다. 폼 데이터에는 악의적인 스크립트 문장이 들어있을 수 있기 때문이다.
<form action="." method="post">{% csrf_token %}
위치는 form 엘리먼트의 첫 줄 다음에 넣어주면 된다. 이 태그를 통해 장고는 내부적으로 CSRF 토큰값의 유효성을 검증한다. 만일 CSRF 토큰값 검증에 실패하면 사용자에게 403 에러를 보여준다. 한 가지 주의할 점은 CSRF 토큰값이 유출될 수 있으므로 외부 URL로 보내는 form에는 사용하지 않도록 한다.
CSRF 공격이란?
CSRF(Cross-Site Request Forgery)는 사이트 간 요청 위조 공격이다. 웹 사이트의 취약점을 공격하는 방식 중의 하나로, 특정 웹 사이트에서 이미 인증을 받은 사용자를 이용하여 공격을 시도한다. 인증을 받은 사용자가 공격 코드가 삽입된 페이즈를 열면 공격 대상이 되는 웹 사이트에는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것으로 판단하게 되어 공격을 받게 되는 방식이다.
detail.html 템플릿 파일에서 다음과 같은 코드를 사용했었다.
<form action="{% url 'voting:vote' question.id %}" method="post">
이 태그의 목적은 소스에 URL을 하드 코딩하는 것을 방지하기 위한 것으로 이 태그를 사용하지 않는다면 다음과 같이 URL을 하드코딩해야 한다.
<form action="/voting/3/vote/" method="post">
위와 같은 하드코딩은 URL을 조금만 변경할 경우에도 URLconf뿐만 아니라 모든 html을 찾아서 변경해줘야 하는 문제가 발생한다. 또한 /3/이라는 숫자는 런타임에 따라 결정되어 항상 변하는 값으로, 변수 처리를 해줘야 하기 때문에 이 또한 불편하다.
{% url %}태그를 사용하면 URL이 변경되더라도 URLconf만을 참조하여 원하는 URL을 추출할 수 있다.
{% url 'namespace:view-name' arg1 arg2 %}
특정 값을 변수에 저장해두는 기능
{% with total=business.employees.count %}
{{ total }} people works at business
{% endwith %}
total 변수의 유효 범위는 with 구문 내이다. 이 태그는 DB는 조회하는 것처럼 부하가 큰 동작의 결과를 저장해둠으로써, 다시 동일한 동작이 필요한 경우 저장해 둔 결과를 활용하여 부하를 줄이기 위한 태그이다.
사용자 정의 태그 및 필터를 로딩해준다.
{% load somelibrary package.otherlibrary %}
태그 및 필터는 장고에서 기본적으로 제공하는 것 외에도 개발자가 정의하여 사용할 수 있는데 그 전에 로딩을 먼저 해줘야 한다.
한 문장의 전부 또는 일부를 주석 처리하는 방법
{# greeting #}hello
여러 줄의 주석문 처리
{% comment "Optional note" %}
<p>Commented out text here</p>
{% endcomment %}
템플릿 코드를 렌더링해서 HTML 텍스트를 만들 때, 템플릿 변수에 HTMl의 태그가 들어있는 경우, 있는 그대로 렌더링하면 원하지 않는 결과가 나올 수 있다.
name 변수에 HTML 태그가 들어있는 상황에서
name = "<b>username"
템플릿 코드에 다음 문장을 사용하면
Hello, {{ name }}
결과는 다음과 같다.
Hello, <b>username
웹 브라우저에 표시될 때 <b> 태그 이후의 문장을 모두 볼드체로 바꿔버리기 때문에 원했던 것과 다른 결과가 나타난다. 이런 약점을 이용하여 XSS 공격이 이루어진다.
XSS 공격이란?
XSS(Cross-Site Scrpting)은 사이트 간 스크립팅 공격이다. 웹 사이트의 취약점을 공격하는 방식 중 하나로 웹 사이트 관리자가 아닌 일반 사용자라도 시도할 수 있는 공격 방법이다. 주로 여러 사용자가 보게 되는 전자 게시판에 악성 스크립트가 담긴 글을 올리는 형태로 이뤄진다.
이처럼 사용자가 입력한 데이터를 그대로 렌더링하는 것은 위험하다. 장고는 이를 방지하기 위해 자동 이스케이프 기능을 제공한다. 즉 장고는 디폴트로 HTML에 사용되는 예약 문자들을 예약 의미를 제거한 문자로 변경해주는 기능을 제공한다.
< (less than) 문자는 <로
> (greater than) 문자는 >로
' (single quote) 문자는 '로
" (double quote) 문자는 &quout;로
& (ampersand) 문자는 &로 변경
그러나 자동 HTML 이스케이프 기능을 비활성화 시켜야 할 때도 있다. HTML 태그를 그대로 출력하고 싶은 경우나 이스케이프 문자가 들어 있는 이메일 메시지를 템플릿 파일에 출력하는 경우에 해당한다.
장고의 자동 이스케이프 기능 비활성화 방법
This will not be escaped: {{ date|safe }}
{% auotescape %}
Hello {{ name }}
{% endauotescape %}
필터의 인자에 사용되는 스트링 리터럴에는 자동 이스케이프 기능이 적용되지 않는다. 그래서 다음의 첫 문장보다는 두 번째 문장을 사용하는 것이 좋다.
{{ data|default:"3 < 5" }} # 스트링 리터럴에서는 자동 이스케이프 안됨
{{ data|default:"3 < 5" }}
템플릿 상속을 통해 템플릿 코드를 재사용할 수 있고, 사이트의 룩앤필을 일관성 있게 보여줄 수 있다. 부모 템플릿은 템플릿의 뼈대를 만들어주고 {% block %} 태크를 통해 하위로 상속해줄 부분을 지정해주면, 자식 템플릿은 부모 템플릿의 뼈대는 그대로 재사용하고 {% block %}의 부분만 채워주면 된다.
아래는 title 블록, sidebar 블록, content 블록 3개를 {% block %}처리한 경우이다.
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>{% block title %}My amazing site{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
{% endblock %}
</div>
<div>
{% block content %}
{% endblock %}
</div>
</body>
</html>
자식 템플릿에서는 3개의 blcok 태그 중 2개를 채웠다. 모두를 채울 필요는 없다. 채우지 않으면 부모 템프릿 내용을 그대로 사용한다. 상속받는다는 것을 표시하기 위해 {% extends %}태그를 사용한다.
{% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
이처럼 템플릿 상속을 사용하면 템플릿 전체의 모습을 구조화할 수 있어 코드의 재사용이나 변경이 용이하고 사이트 UI의 룩앤필을 일관되게 가져갈 수 있다. 사이트 전체적으로 조화로운 룩앤필을 위하여 일반적으로 템플릿 상속을 3단계로 사용하는 것을 권장하고 있다.
사이트 전체적으로 조화로운 룩앤필을 위해 템플릿 상속을 3단계로 사용한다.
1단계 : 사이트 전체의 룩앤필을 담고 있는 base.html을 만든다.
2단계 : 사이트 하위의 섹션별 스타일을 담고 있는 base_news.html, base_sports.html 등의 템플릿을 만든다. -> 상속이용
3단계 : 개별 페이지에 대한 템플릿을 만든다.
템플릿 상속을 정의할 때는 다음 사항을 유의해야 한다.
출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님