본문으로 건너뛰기
김신건의 로그

[Flask] Templates (Jinja2)

· 수정 · 📖 약 2분 · 673자/단어 #python #flask #jinja2 #templates #ssr
Flask Templates, Flask Jinja2, Jinja2 Flask, render_template, Flask 템플릿, template inheritance, Jinja macro

정의

Flask Templates 는 Jinja2 템플릿 엔진의 Flask 통합입니다. render_template() 로 컨텍스트 (변수) 를 넘겨 HTML/텍스트를 렌더링하고, template inheritance, macro, filter 등으로 재사용 가능한 UI 를 구축합니다.

서버 사이드 렌더링 (SSR) 이 필요한 앱 (관리자 페이지, SEO 중요 페이지, HTMX 앱) 에서 여전히 강력. React/Vue SPA 로 완전히 대체할 필요 없는 경우 많음.

Hello World

# app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.get("/user/<name>")
def user(name):
    return render_template("user.html", name=name, items=[1, 2, 3])
<!-- templates/user.html -->
<!DOCTYPE html>
<html>
<body>
  <h1>Hello, {{ name }}</h1>
  <ul>
    {% for item in items %}
      <li>{{ item }}</li>
    {% endfor %}
  </ul>
</body>
</html>

Jinja2 문법

변수 출력

{{ variable }}
{{ user.name }}
{{ user["email"] }}
{{ items[0] }}
{{ func(arg) }}
{{ user.get("name", "unknown") }}

Filter (변수 변환)

{{ name | upper }}                     <!-- 대문자 -->
{{ text | truncate(100) }}             <!-- 100자 자르기 -->
{{ date | dateformat("%Y-%m-%d") }}    <!-- 날짜 포맷 -->
{{ list | join(", ") }}                <!-- 배열을 문자열로 -->
{{ dict | tojson }}                    <!-- JSON 직렬화 -->
{{ text | safe }}                      <!-- HTML escape 안 함 -->
{{ number | round(2) }}                <!-- 소수점 -->
{{ url | urlencode }}

커스텀 filter:

@app.template_filter("reverse")
def reverse_filter(s: str) -> str:
    return s[::-1]

# 템플릿: {{ "hello" | reverse }}  ->  olleh

Control Flow

{% if user %}
  Hi, {{ user.name }}
{% elif guest %}
  Hi, guest
{% else %}
  Please log in.
{% endif %}

{% for item in items %}
  <li>{{ loop.index }}: {{ item }}</li>
{% else %}
  <p>No items.</p>
{% endfor %}

loop.index (1-based), loop.index0 (0-based), loop.first, loop.last, loop.length.

Comment

{# 이것은 렌더링되지 않는 주석 #}

Whitespace 제어

{%- for x in xs %}         <!-- 앞 공백 제거 -->
  {{ x }}
{% endfor -%}              <!-- 뒷 공백 제거 -->

Template Inheritance

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
  <nav>{% include "_nav.html" %}</nav>
  <main>
    {% block content %}{% endblock %}
  </main>
  <footer>{% block footer %}Default footer{% endblock %}</footer>
</body>
</html>
<!-- templates/user.html -->
{% extends "base.html" %}

{% block title %}{{ name }}의 페이지{% endblock %}

{% block content %}
  <h1>{{ name }}</h1>
  <ul>
    {% for item in items %}
      <li>{{ item }}</li>
    {% endfor %}
  </ul>
{% endblock %}

super() 로 부모 블록 확장

{% block content %}
  {{ super() }}
  <p>추가 컨텐츠</p>
{% endblock %}

include vs extends

  • {% extends %}: 부모 템플릿 상속 (block override)
  • {% include %}: 파일 삽입 (context 공유)

Macro

함수처럼 재사용 가능한 템플릿 조각.

<!-- templates/_macros.html -->
{% macro render_field(field, label=None) %}
<div class="form-group">
  <label>{{ label or field.label }}</label>
  {{ field(class="form-control") }}
  {% if field.errors %}
    <ul class="errors">
      {% for error in field.errors %}<li>{{ error }}</li>{% endfor %}
    </ul>
  {% endif %}
</div>
{% endmacro %}
<!-- templates/register.html -->
{% from "_macros.html" import render_field %}

<form method="post">
  {{ render_field(form.email, "이메일") }}
  {{ render_field(form.password) }}
  <button>가입</button>
</form>

Context Processor

모든 템플릿에서 접근 가능한 전역 변수 주입.

@app.context_processor
def inject_globals():
    return {
        "current_user": get_current_user(),
        "site_name": "MySite",
        "now": datetime.utcnow(),
    }

이제 어느 템플릿에서든 {{ current_user }}, {{ site_name }} 사용 가능.

Blueprint 도 자기만의 context processor:

@bp.app_context_processor
def bp_context():
    return {"key": "value"}

Autoescape (XSS 방어)

기본 활성화. {{ user_input }} 은 HTML escape.

<!-- user.name = "<script>alert(1)</script>" -->
<p>{{ user.name }}</p>
<!-- 렌더링: <p>&lt;script&gt;alert(1)&lt;/script&gt;</p> -->

| safe 는 escape 를 해제. 신뢰할 만한 HTML 만 (예: 관리자가 CMS 에 넣은 것).

{{ trusted_html | safe }}

Markup 객체를 파이썬에서 직접:

from markupsafe import Markup
return render_template("...", html=Markup("<b>bold</b>"))

URL 생성

<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('user', name=user.name) }}">Profile</a>
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">

Blueprint:

<a href="{{ url_for('auth.login') }}">Login</a>

Flash Messages

한 번만 노출되는 사용자 알림.

from flask import flash

@app.post("/login")
def login():
    if success:
        flash("로그인되었습니다", category="success")
    else:
        flash("실패했습니다", category="error")
    return redirect(url_for("index"))
{% with messages = get_flashed_messages(with_categories=true) %}
  {% for category, message in messages %}
    <div class="alert alert-{{ category }}">{{ message }}</div>
  {% endfor %}
{% endwith %}

CSRF Token (Flask-WTF)

<form method="post">
  {{ form.hidden_tag() }}     <!-- CSRF 토큰 자동 삽입 -->
  ...
</form>

Extension 없이 수동으로 하려면:

<form>
  <input type="hidden" name="csrf_token" value="{{ session.csrf_token }}">
  ...
</form>

세션에 CSRF 토큰을 발급하고 form 처리 시 검증.

Set / Assign

{% set price = item.price * 1.1 %}
<p>세금 포함: {{ price }}</p>

{% set nav %}
  <a href="/">홈</a>
  <a href="/about">소개</a>
{% endset %}
<nav>{{ nav | safe }}</nav>

Template Rendering 성능

Jinja2 는 템플릿을 파이썬 코드로 컴파일 후 실행. 첫 렌더링 시 컴파일. Flask 는 개발 모드에서 파일 변경 감지, 프로덕션은 캐시.

app.jinja_env.cache_size = 400   # 기본 100
app.jinja_env.auto_reload = False  # 프로덕션 필수

Extension (자주 쓰이는)

  • Jinja2 built-in: if, for, set, include, extends, block, macro, import, from, with, filter, endfilter
  • jinja2.ext.do: {% do list.append(x) %} (side effect)
  • jinja2.ext.loopcontrols: {% break %}, {% continue %}
  • jinja2.ext.i18n: gettext 통합
app.jinja_env.add_extension("jinja2.ext.do")

HTMX / Alpine 조합

Jinja2 + HTMX (혹은 Alpine.js) 는 SPA 대비 훨씬 단순한 인터랙션 앱을 만들 수 있음:

<button hx-get="/api/count" hx-target="#count">Refresh</button>
<span id="count">{{ count }}</span>

서버가 partial HTML 을 반환하면 HTMX 가 target 만 교체.

@app.get("/api/count")
def count():
    return render_template("_count.html", count=get_count())

_count.html:

<span id="count">{{ count }}</span>

함정

WARNING

| safe 남용은 XSS 취약점. 사용자 입력에 절대 붙이지 않기.

CAUTION

autoescape 해제 파일. .html, .htm, .xml, .xhtml 은 기본 autoescape 활성. 그 외는 활성 안 됨. select_autoescape 로 명시 관리.

WARNING

get_flashed_messages 는 한 번만 소비. 다시 렌더링하면 메시지 사라짐. 저장 필요하면 별도.

IMPORTANT

템플릿에서 heavy computation 금지. DB 쿼리, 파일 IO 등은 뷰에서 미리. 템플릿은 렌더링만.

CAUTION

Template caching. Jinja2 cache_size 를 넘으면 LRU 로 축출. 큰 앱은 늘리기.

관련 위키

이 글의 용어 (6개)
[Flask] Application Factory Patternflask
정의 Application Factory 는 Flask 앱 인스턴스를 함수 안에서 생성 하는 패턴입니다. 모듈 스코프에 를 두는 대신 함수를 정의해 반환합니다. 이 패턴은 사실상…
[Flask] Blueprintsflask
정의 Blueprint 는 Flask 앱을 여러 모듈로 나누는 표준 도구입니다. 라우트, 정적파일, 템플릿, before/after hook, error handler 를 그룹화…
[Flask] Extensions (SQLAlchemy, Login, Migrate, WTF, ...)flask
정의 Flask Extension 은 코어에 없는 기능 (DB, 인증, 마이그레이션, 캐시 등) 을 표준 인터페이스로 통합하는 서드파티 패키지입니다. 패턴이 관용이며, appli…
[Flask] Request & Responseflask
정의 Flask Request/Response 는 Werkzeug 의 / 래퍼를 확장한 것입니다. Thread-local 프록시 로 뷰 함수 어디서든 현재 요청에 접근하고, re…
[Flask] Sessions & Cookiesflask
정의 Flask Session 은 요청 간 상태를 유지하는 방법입니다. 기본 구현은 서명된 쿠키 (secure cookie) 로 클라이언트에 저장 (server-less). ex…
[Python] Flaskflask
정의 Flask 는 Armin Ronacher 가 2010년 발표한 WSGI 기반 Python 마이크로프레임워크 입니다. Pallets Projects 가 유지관리하고, 2026…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기