15강 / 20강

Jinja2 — 표의 한 줄이 화면의 한 줄이 되는 곳

풀스택 뼈대 ③ 화면을 만드는 층

Jinja2 — 표의 한 줄이 화면의 한 줄이 되는 곳
템플릿은 빈칸이 있는 상장 양식입니다. 이름만 바꿔 100장을 찍어 내죠.

학교에서 상장을 100장 만든다고 생각해 보세요. 100장을 처음부터 손으로 쓸까요? 아니죠. 양식 한 장을 만들고 이름과 상 이름만 바꿔 가며 인쇄합니다.

📄
템플릿
빈칸이 있는 양식 products.html
🗄️
데이터
DB에서 읽은 행들
🖨️
Jinja2
빈칸을 채워 인쇄

Jinja2(진자투)는 Flask에 기본으로 들어 있는 템플릿 엔진입니다. 설치할 것도, 배울 문법도 거의 없어요. 딱 세 가지만 알면 됩니다.

문법하는 일예시
{{ 값 }}값을 그 자리에 출력{{ p.name }} → 텀블러
{% for %}여러 줄을 반복상품 6개 → 카드 6개
{% if %}조건에 따라 보이거나 감추기재고 0이면 '품절'

기억하세요. 중괄호 두 개는 출력, 중괄호-퍼센트는 명령입니다.

DB → 화면, 전체 그림 한 장

왼쪽이 2층(Flask), 오른쪽이 3층(Jinja2)입니다. 두 파일이 짝을 이룹니다.

# ── app.py (2층) ─────────────────────────
@app.route("/")
def product_list():
    conn = db()
    products = conn.execute(
        "SELECT id, name, price, stock FROM product ORDER BY id").fetchall()
    conn.close()
    return render_template("products.html", products=products)
    #                       ↑ 파일 이름        ↑ 템플릿에 넘길 데이터
<!-- ── templates/products.html (3층) ─────── -->
<h1>상품 목록 ({{ products|length }}개)</h1>

<table>
  <tr><th>이름</th><th>가격</th><th>재고</th></tr>

  {% for p in products %}          <!-- DB의 행 하나 = 화면의 줄 하나 -->
  <tr>
    <td>{{ p.name }}</td>
    <td>{{ "{:,}".format(p.price) }}원</td>
    <td>
      {% if p.stock > 0 %}
        {{ p.stock }}개 남음
      {% else %}
        <b>품절</b>
      {% endif %}
    </td>
  </tr>
  {% endfor %}

</table>

템플릿 파일은 반드시 templates/ 폴더 안에 두어야 합니다. Flask가 그곳을 자동으로 찾아보거든요.

🧪 템플릿 엔진을 직접 만들어 보기

Jinja2가 마법처럼 보이나요? 사실 원리는 아주 단순합니다. 빈칸을 찾아 값으로 바꿔치기하는 것뿐이에요. 직접 만들어 봅시다.

템플릿 = 양식 + 데이터. 상품을 하나 더 추가해 보세요 — 화면이 저절로 늘어납니다.
Python 브라우저에서 실행

🎮 Jinja2 실습실 — 직접 써 보고 바로 확인하세요

아래 편집기에 템플릿을 쓰면 이 서버의 진짜 Jinja2가 렌더링해 돌려줍니다. 왼쪽 아래에 보이는 products 데이터는 여러분의 연습 DB에서 실제로 읽어 온 행이에요. 타이핑을 멈추면 자동으로 다시 그려집니다.

  • ② 반복 예제를 열고 {% for %} 줄을 지워 보세요. 화면이 어떻게 바뀌나요?
  • 생성된 HTML 탭을 눌러 보세요. 템플릿 6줄이 몇 줄의 HTML이 되었나요?
  • ⑤ Bootstrap 카드 — 3층과 4층이 만나는 그 코드가 실제로 어떻게 보이는지 확인하세요.
  • ⑥ 자동 이스케이프 — {{ evil }} 에 |safe 를 붙였다 떼며 비교해 보세요.
  • ⑦ 오류 — {% endfor %} 를 빠뜨리면 Jinja2가 몇 번째 줄이라고 알려 주는지 보세요.
Jinja2 실습실 — 템플릿을 고치면 화면이 바로 바뀝니다
불러오는 중…

상속 — 같은 껍데기를 100번 쓰지 않기

사이트의 모든 페이지에는 같은 것이 들어갑니다. 상단 메뉴, 로고, 하단 정보… 페이지마다 복사하면 메뉴 하나 바꿀 때 모든 파일을 고쳐야 하죠. 10강의 중복의 저주가 화면에서도 똑같이 일어나는 겁니다.

<!-- templates/base.html — 모든 페이지의 공통 껍데기 -->
<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>{% block title %}우리 쇼핑몰{% endblock %}</title>
</head>
<body>
  <nav><a href="{{ url_for('product_list') }}">🛍️ 우리 쇼핑몰</a></nav>

  {% block content %}{% endblock %}      <!-- 여기에 각 페이지 내용이 들어온다 -->

  <footer>(주)디앤에이보이 · 2026</footer>
</body>
</html>
<!-- templates/products.html — 껍데기를 물려받는다 -->
{% extends "base.html" %}

{% block title %}상품 목록{% endblock %}

{% block content %}
  <h1>오늘의 상품</h1>
  {% for p in products %}
    <p>{{ p.name }} — {{ p.price }}원</p>
  {% endfor %}
{% endblock %}

이제 메뉴를 바꾸려면 base.html 한 파일만 고치면 됩니다. 표를 나눠 중복을 없앤 정규화와 똑같은 발상이에요.

url_for('product_list')는 주소를 직접 쓰는 대신 함수 이름으로 주소를 만들어 달라는 뜻입니다. 나중에 주소가 바뀌어도 링크가 깨지지 않아요.

🤔 확인 문제
상품이 6개에서 60개로 늘었습니다. 무엇을 고쳐야 할까요?