포스트

[Python 100일 챌린지] Day 83 - 템플릿 엔진 (Jinja2)

[Python 100일 챌린지] Day 83 - 템플릿 엔진 (Jinja2)

지금까지 우리는 Python 코드 안에 HTML을 문자열로 작성했습니다. 😰 하지만 이 방법은 매우 불편하고, 유지보수도 어렵습니다!

1
return f"<h1>안녕하세요, {name}님!</h1><p>{content}</p>"  # 😱

Jinja2는 이 문제를 해결해줍니다! HTML 파일을 따로 관리하고, Python 변수를 쉽게 주입할 수 있습니다.

프론트엔드 개발자와 백엔드 개발자가 협업할 때 필수적인 기술입니다! 💡

(35분 완독 ⭐⭐⭐⭐)

🎯 오늘의 학습 목표

  1. Jinja2 템플릿 엔진 이해하기
  2. 템플릿에 변수 전달하고 출력하기
  3. 제어문과 반복문 사용하기
  4. 템플릿 상속과 재사용하기

📚 사전 지식


🎯 학습 목표 1: Jinja2 템플릿 엔진 이해하기

한 줄 설명

템플릿 엔진 = HTML 틀에 Python 데이터를 끼워 넣어서 완성된 웹페이지를 만드는 도구

실생활 비유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
📝 편지 양식과 우편 병합:

템플릿 엔진 = 워드프로세서의 우편 병합 기능
- 편지 양식(HTML 템플릿): "안녕하세요, {{이름}}님!"
- 데이터(Python): {"이름": "홍길동", "나이": 25}
- 최종 결과: "안녕하세요, 홍길동님!"

편지를 1000명에게 보낸다면?
❌ 나쁜 방법: 1000개 파일을 각각 작성 (app.py에 HTML 하드코딩)
✅ 좋은 방법: 양식 1개 + 데이터 1000개 (템플릿 + Jinja2)

코드 비교:
❌ return f"<h1>안녕, {name}님!</h1>"  ← Python에 HTML 섞임 (지저분!)
✅ return render_template('hello.html', name=name)  ← 깔끔하게 분리!

템플릿 엔진이란?

템플릿 엔진은 HTML 템플릿에 동적 데이터를 삽입하여 최종 HTML을 생성하는 도구입니다.

1
2
3
4
5
6
7
8
9
10
11
Python 코드 (app.py)          HTML 템플릿 (template.html)
      |                              |
      |  데이터 전달                  |
      |----------------------------->|
      |                              |
      |                         Jinja2 처리
      |                              |
      |    최종 HTML                 |
      |<-----------------------------|
      |                              |
    브라우저로 전송

왜 템플릿을 사용해야 할까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ❌ 템플릿 없이 (유지보수 지옥)
@app.route('/user/<username>')
def user(username):
    return f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>{username}의 프로필</title>
        <style>
            body {{ font-family: Arial; }}
            h1 {{ color: blue; }}
        </style>
    </head>
    <body>
        <h1>안녕하세요, {username}님!</h1>
        <p>환영합니다.</p>
    </body>
    </html>
    """  # 😱 읽기 힘들고, 수정하기 어려움!


# ✅ 템플릿 사용 (깔끔하고 관리하기 쉬움)
@app.route('/user/<username>')
def user(username):
    return render_template('user.html', username=username)

Flask에서 템플릿 사용하기

1. 폴더 구조 만들기

1
2
3
4
5
6
my_flask_app/
├── app.py
└── templates/          # Flask가 자동으로 찾는 폴더
    ├── index.html
    ├── user.html
    └── about.html

2. 첫 번째 템플릿 만들기

templates/index.html:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
    <title>환영합니다</title>
</head>
<body>
    <h1>Hello, Flask Templates!</h1>
    <p>이것이 첫 번째 템플릿입니다.</p>
</body>
</html>

3. Flask에서 템플릿 렌더링

app.py:

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    # templates/index.html을 렌더링
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

🎯 학습 목표 2: 템플릿에 변수 전달하고 출력하기

한 줄 설명

변수 전달 = Python에서 만든 데이터를 HTML로 보내서 안에 표시하기

실생활 비유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
🎭 연극 대본과 배우:

템플릿(HTML) = 연극 대본
- "{{ 주인공 }}이/가 무대에 등장합니다"

Python 코드 = 연출자
- render_template('script.html', 주인공='홍길동')

최종 공연 = 브라우저에 보이는 결과
- "홍길동이 무대에 등장합니다"

필터 = 대사를 다듬는 것
- {{ 이름|upper }} → "홍길동" → "홍길동"을 대문자로
- {{ 가격|format_number }} → 1000000 → "1,000,000"원

같은 대본(템플릿)으로 배우(데이터)만 바꿔서 여러 공연(페이지) 가능!

변수 전달하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
    # 템플릿에 변수 전달
    return render_template('hello.html', username=name)

@app.route('/profile/<username>/<int:age>')
def profile(username, age):
    # 여러 변수 전달
    return render_template(
        'profile.html',
        username=username,
        age=age,
        is_adult=(age >= 19)
    )

if __name__ == '__main__':
    app.run(debug=True)

템플릿에서 변수 출력하기

templates/hello.html:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
    <title>인사</title>
</head>
<body>
    <!-- {{ }} 안에 변수 이름 작성 -->
    <h1>안녕하세요, {{ username }}님!</h1>
    <p>환영합니다.</p>
</body>
</html>

templates/profile.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
    <title>{{ username }}의 프로필</title>
</head>
<body>
    <h1>프로필</h1>
    <ul>
        <li>이름: {{ username }}</li>
        <li>나이: {{ age }}살</li>
        <li>성인: {{ is_adult }}</li>
    </ul>
</body>
</html>

딕셔너리와 리스트 전달

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# app.py
@app.route('/dashboard')
def dashboard():
    user_data = {
        'name': '홍길동',
        'email': '[email protected]',
        'role': 'admin'
    }

    recent_posts = [
        {'title': 'Python 공부', 'views': 150},
        {'title': 'Flask 튜토리얼', 'views': 230},
        {'title': 'Jinja2 템플릿', 'views': 180}
    ]

    return render_template(
        'dashboard.html',
        user=user_data,
        posts=recent_posts,
        total_posts=len(recent_posts)
    )

templates/dashboard.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<body>
    <h1>{{ user.name }}님의 대시보드</h1>
    <p>이메일: {{ user.email }}</p>
    <p>역할: {{ user.role }}</p>

    <h2>최근 게시글 (총 {{ total_posts }}개)</h2>
    <ul>
        {% for post in posts %}
        <li>{{ post.title }} - 조회수: {{ post.views }}</li>
        {% endfor %}
    </ul>
</body>
</html>

필터 사용하기

Jinja2는 변수를 가공할 수 있는 필터 기능을 제공합니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 대문자 변환 -->
<p>{{ username|upper }}</p>

<!-- 소문자 변환 -->
<p>{{ username|lower }}</p>

<!-- 첫 글자만 대문자 -->
<p>{{ username|capitalize }}</p>

<!-- 제목 형식 (각 단어의 첫 글자 대문자) -->
<p>{{ title|title }}</p>

<!-- 리스트 길이 -->
<p>총 {{ posts|length }}개의 게시글</p>

<!-- 기본값 설정 -->
<p>{{ description|default('설명이 없습니다') }}</p>

<!-- HTML 이스케이프 (보안) -->
<p>{{ content|escape }}</p>

<!-- HTML 렌더링 (주의: 신뢰할 수 있는 내용만!) -->
<p>{{ html_content|safe }}</p>

실전 예제:

1
2
3
4
5
6
7
8
9
10
11
12
13
# app.py
from datetime import datetime

@app.route('/post/<int:post_id>')
def post(post_id):
    post_data = {
        'title': 'jinja2 템플릿 엔진',
        'content': '<p>이것은 <strong>HTML</strong> 내용입니다.</p>',
        'author': 'alice',
        'created_at': datetime(2025, 6, 21, 10, 30, 0)
    }

    return render_template('post.html', post=post_data)

templates/post.html:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<body>
    <h1>{{ post.title|title }}</h1>
    <p>작성자: {{ post.author|upper }}</p>
    <p>작성일: {{ post.created_at.strftime('%Y년 %m월 %d일') }}</p>

    <div>
        {{ post.content|safe }}
    </div>
</body>
</html>

🎯 학습 목표 3: 제어문과 반복문 사용하기

한 줄 설명

제어문 = HTML 안에서 조건에 따라 다른 내용을 보여주거나, 목록을 자동으로 반복 출력하기

실생활 비유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
🍽️ 레스토랑 메뉴판:

{% if %} = 조건에 따라 메뉴 표시
- {% if is_vegan %} → 채식 메뉴만 보여주기
- {% if age >= 19 %} → 성인 메뉴 표시
- 조건이 거짓이면 그 부분은 아예 안 보임!

{% for %} = 메뉴 목록 자동 생성
- {% for dish in menu %} → 메뉴 100개를 자동으로 표시
- 코드 3줄로 1000개 상품도 출력 가능!

실제 예시:
❌ 나쁜 방법:
    <li>김치찌개</li>
    <li>된장찌개</li>
    <li>순두부찌개</li>  ← 메뉴 추가할 때마다 HTML 수정!

✅ 좋은 방법:
    {% for menu in menus %}
        <li>{{ menu }}</li>
    {% endfor %}  ← Python에서 리스트만 업데이트하면 됨!

if 문 (조건부 렌더링)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- templates/user.html -->
<!DOCTYPE html>
<html>
<body>
    <h1>{{ username }}님의 프로필</h1>

    {% if age >= 19 %}
        <p>🔞 성인입니다.</p>
        <a href="/adult-content">성인 컨텐츠 보기</a>
    {% else %}
        <p>👶 미성년자입니다.</p>
        <p>성인 컨텐츠는 볼 수 없습니다.</p>
    {% endif %}

    {% if is_premium %}
        <span class="badge">⭐ 프리미엄 회원</span>
    {% endif %}
</body>
</html>

if-elif-else

1
2
3
4
5
6
7
8
9
{% if score >= 90 %}
    <p class="grade-a">A등급 🏆</p>
{% elif score >= 80 %}
    <p class="grade-b">B등급 👍</p>
{% elif score >= 70 %}
    <p class="grade-c">C등급 ✅</p>
{% else %}
    <p class="grade-f">재시험 대상 😢</p>
{% endif %}

for 문 (반복)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 리스트 반복 -->
<h2>할 일 목록</h2>
<ul>
    {% for todo in todos %}
        <li>{{ todo }}</li>
    {% endfor %}
</ul>

<!-- 딕셔너리 리스트 반복 -->
<h2>상품 목록</h2>
<table>
    <tr>
        <th>이름</th>
        <th>가격</th>
    </tr>
    {% for product in products %}
    <tr>
        <td>{{ product.name }}</td>
        <td>{{ product.price }}원</td>
    </tr>
    {% endfor %}
</table>

빈 리스트 처리 (else)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<h2>게시글</h2>
{% if posts %}
    <ul>
        {% for post in posts %}
            <li>{{ post.title }}</li>
        {% endfor %}
    </ul>
{% else %}
    <p>아직 게시글이 없습니다.</p>
{% endif %}

<!-- 또는 for-else 사용 -->
<ul>
    {% for post in posts %}
        <li>{{ post.title }}</li>
    {% else %}
        <li>게시글이 없습니다.</li>
    {% endfor %}
</ul>

loop 변수 (반복문 정보)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<table>
    {% for item in items %}
    <tr class="{% if loop.index % 2 == 0 %}even{% else %}odd{% endif %}">
        <td>{{ loop.index }}</td>     <!-- 1부터 시작하는 인덱스 -->
        <td>{{ loop.index0 }}</td>    <!-- 0부터 시작하는 인덱스 -->
        <td>{{ item }}</td>
        <td>
            {% if loop.first %}첫 번째{% endif %}
            {% if loop.last %}마지막{% endif %}
        </td>
    </tr>
    {% endfor %}
</table>

<!-- loop 변수들:
    loop.index    : 1부터 시작하는 반복 횟수
    loop.index0   : 0부터 시작하는 반복 횟수
    loop.first    : 첫 번째 반복인지 (True/False)
    loop.last     : 마지막 반복인지 (True/False)
    loop.length   : 전체 아이템 개수
    loop.revindex : 역순 인덱스 (마지막부터 1)
-->

🎯 학습 목표 4: 템플릿 상속과 재사용하기

한 줄 설명

템플릿 상속 = 공통 레이아웃(헤더, 푸터)을 한 번만 만들고, 페이지마다 내용만 바꾸기

실생활 비유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🏢 건물 설계도:

베이스 템플릿 = 건물의 기본 골조
- 1층: 로비 (헤더)
- 2-10층: {% block content %} (비어있는 공간)
- 11층: 옥상 (푸터)

자식 템플릿 = 각 층의 인테리어
- home.html → 2층을 카페로 꾸밈
- about.html → 2층을 사무실로 꾸밈
- contact.html → 2층을 상담실로 꾸밈

모든 페이지가 같은 헤더/푸터를 공유!
헤더를 수정하면 모든 페이지에 자동 적용!

코드 비교:
❌ 나쁜 방법: 100개 페이지에 똑같은 헤더/푸터 복사 붙여넣기
✅ 좋은 방법: base.html 하나 + {% extends "base.html" %}

템플릿 상속 (Template Inheritance)

웹사이트의 모든 페이지는 보통 같은 헤더, 네비게이션, 푸터를 가집니다. 템플릿 상속을 사용하면 공통 부분을 한 번만 작성할 수 있습니다!

1. 베이스 템플릿 만들기

templates/base.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}내 웹사이트{% endblock %}</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
        }
        header {
            background-color: #333;
            color: white;
            padding: 1rem;
        }
        nav {
            background-color: #555;
            padding: 0.5rem;
        }
        nav a {
            color: white;
            margin-right: 1rem;
            text-decoration: none;
        }
        main {
            padding: 2rem;
        }
        footer {
            background-color: #333;
            color: white;
            text-align: center;
            padding: 1rem;
            margin-top: 2rem;
        }
    </style>
</head>
<body>
    <header>
        <h1>🌐 내 웹사이트</h1>
    </header>

    <nav>
        <a href="/"></a>
        <a href="/about">소개</a>
        <a href="/posts">게시판</a>
        <a href="/contact">연락처</a>
    </nav>

    <main>
        {% block content %}
        <!-- 자식 템플릿이 여기에 내용을 채움 -->
        {% endblock %}
    </main>

    <footer>
        <p>&copy; 2025 내 웹사이트. All rights reserved.</p>
    </footer>
</body>
</html>

2. 자식 템플릿 만들기

templates/home.html:

1
2
3
4
5
6
7
8
{% extends "base.html" %}

{% block title %}홈 - 내 웹사이트{% endblock %}

{% block content %}
    <h2>환영합니다!</h2>
    <p>이것은 홈 페이지입니다.</p>
{% endblock %}

templates/about.html:

1
2
3
4
5
6
7
8
{% extends "base.html" %}

{% block title %}소개 - 내 웹사이트{% endblock %}

{% block content %}
    <h2>소개</h2>
    <p>우리는 Python과 Flask를 사랑합니다.</p>
{% endblock %}

3. Flask 라우트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('home.html')

@app.route('/about')
def about():
    return render_template('about.html')

if __name__ == '__main__':
    app.run(debug=True)

include (템플릿 조각 삽입)

반복되는 작은 HTML 조각을 별도 파일로 분리할 수 있습니다:

templates/_navigation.html:

1
2
3
4
5
<nav>
    <a href="/"></a>
    <a href="/about">소개</a>
    <a href="/posts">게시판</a>
</nav>

templates/base.html:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
    {% include '_navigation.html' %}

    <main>
        {% block content %}{% endblock %}
    </main>
</body>
</html>

매크로 (Macro) - 함수처럼 사용

1
2
3
4
5
6
7
8
9
10
11
12
<!-- templates/macros.html -->
{% macro render_post(post) %}
    <article class="post">
        <h2>{{ post.title }}</h2>
        <p class="meta">작성자: {{ post.author }} | 날짜: {{ post.date }}</p>
        <p>{{ post.content }}</p>
    </article>
{% endmacro %}

{% macro render_button(text, url, style='primary') %}
    <a href="{{ url }}" class="btn btn-{{ style }}">{{ text }}</a>
{% endmacro %}

사용하기:

1
2
3
4
5
6
7
8
<!-- templates/posts.html -->
{% from 'macros.html' import render_post, render_button %}

{% for post in posts %}
    {{ render_post(post) }}
{% endfor %}

{{ render_button('더 보기', '/posts', 'secondary') }}

💻 실전 예제

예제 1: 블로그 목록 페이지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# app.py
from flask import Flask, render_template
from datetime import datetime

app = Flask(__name__)

posts = [
    {
        'id': 1,
        'title': 'Flask 시작하기',
        'author': '홍길동',
        'content': 'Flask는 Python 웹 프레임워크입니다.',
        'created_at': datetime(2025, 6, 19)
    },
    {
        'id': 2,
        'title': 'Jinja2 템플릿',
        'author': '김철수',
        'content': 'Jinja2로 HTML을 동적으로 생성할 수 있습니다.',
        'created_at': datetime(2025, 6, 21)
    },
]

@app.route('/')
def home():
    return render_template('blog.html', posts=posts, site_name='내 블로그')

if __name__ == '__main__':
    app.run(debug=True)

templates/blog.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
    <title>{{ site_name }}</title>
    <style>
        body { font-family: Arial; max-width: 800px; margin: 0 auto; padding: 20px; }
        .post { border: 1px solid #ddd; padding: 15px; margin: 15px 0; }
        .meta { color: #666; font-size: 0.9em; }
    </style>
</head>
<body>
    <h1>{{ site_name }}</h1>
    <p>총 {{ posts|length }}개의 게시글</p>

    {% for post in posts %}
        <div class="post">
            <h2>{{ post.title }}</h2>
            <p class="meta">
                {{ post.author }} |
                {{ post.created_at.strftime('%Y-%m-%d') }}
            </p>
            <p>{{ post.content }}</p>
        </div>
    {% else %}
        <p>게시글이 없습니다.</p>
    {% endfor %}
</body>
</html>

예제 2: 상품 목록 with 템플릿 상속

templates/base.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}쇼핑몰{% endblock %}</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial; }
        header { background: #333; color: white; padding: 1rem; }
        nav { background: #555; padding: 0.5rem; }
        nav a { color: white; margin: 0 1rem; text-decoration: none; }
        main { padding: 2rem; }
    </style>
</head>
<body>
    <header>
        <h1>🛒 온라인 쇼핑몰</h1>
    </header>

    <nav>
        <a href="/"></a>
        <a href="/products">상품</a>
        <a href="/cart">장바구니</a>
    </nav>

    <main>
        {% block content %}{% endblock %}
    </main>
</body>
</html>

templates/products.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{% extends "base.html" %}

{% block title %}상품 목록 - 쇼핑몰{% endblock %}

{% block content %}
    <h2>상품 목록</h2>

    <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
        {% for product in products %}
            <div style="border: 1px solid #ddd; padding: 1rem;">
                <h3>{{ product.name }}</h3>
                <p>{{ product.price|format_number }}원</p>
                <p>{{ product.description }}</p>

                {% if product.stock > 0 %}
                    <button>구매하기</button>
                {% else %}
                    <p style="color: red;">품절</p>
                {% endif %}
            </div>
        {% endfor %}
    </div>
{% endblock %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app.py에 추가
@app.route('/products')
def products():
    product_list = [
        {'name': '노트북', 'price': 1500000, 'stock': 5, 'description': '고성능 노트북'},
        {'name': '마우스', 'price': 30000, 'stock': 0, 'description': '무선 마우스'},
        {'name': '키보드', 'price': 120000, 'stock': 10, 'description': '기계식 키보드'},
    ]
    return render_template('products.html', products=product_list)

# 커스텀 필터 추가
@app.template_filter('format_number')
def format_number(value):
    return f"{value:,}"

⚠️ 주의사항

1. XSS (Cross-Site Scripting) 공격 방지

1
2
3
4
5
6
7
8
9
10
11
<!-- ❌ 위험: 사용자 입력을 그대로 출력 -->
<p>{{ user_input|safe }}</p>

<!-- ✅ 안전: 자동 이스케이프 (기본값) -->
<p>{{ user_input }}</p>

<!-- 예:
    user_input = "<script>alert('해킹!')</script>"
    자동 이스케이프: &lt;script&gt;alert('해킹!')&lt;/script&gt;
    safe 필터: <script>alert('해킹!')</script> (실행됨! 위험!)
-->

2. 템플릿 경로는 상대 경로

1
2
3
4
5
# ✅ 올바름
return render_template('posts/detail.html')  # templates/posts/detail.html

# ❌ 잘못됨
return render_template('/templates/posts/detail.html')

3. 블록 이름은 명확하게

1
2
3
4
5
6
7
8
<!-- ❌ 나쁜 예 -->
{% block a %}{% endblock %}
{% block b %}{% endblock %}

<!-- ✅ 좋은 예 -->
{% block title %}{% endblock %}
{% block content %}{% endblock %}
{% block sidebar %}{% endblock %}

🧪 연습 문제

문제 1: 성적표 출력

학생 목록을 받아 성적표를 출력하는 페이지를 만드세요.

  • 90점 이상: A (파란색)
  • 80-89점: B (초록색)
  • 70-79점: C (노란색)
  • 70점 미만: F (빨간색)
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/grades')
def grades():
    students = [
        {'name': '홍길동', 'score': 95},
        {'name': '김철수', 'score': 82},
        {'name': '이영희', 'score': 68},
    ]
    return render_template('grades.html', students=students)

if __name__ == '__main__':
    app.run(debug=True)

templates/grades.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
    <title>성적표</title>
    <style>
        .grade-a { color: blue; }
        .grade-b { color: green; }
        .grade-c { color: orange; }
        .grade-f { color: red; }
    </style>
</head>
<body>
    <h1>성적표</h1>
    <table border="1">
        <tr>
            <th>이름</th>
            <th>점수</th>
            <th>등급</th>
        </tr>
        {% for student in students %}
        <tr>
            <td>{{ student.name }}</td>
            <td>{{ student.score }}</td>
            <td>
                {% if student.score >= 90 %}
                    <span class="grade-a">A</span>
                {% elif student.score >= 80 %}
                    <span class="grade-b">B</span>
                {% elif student.score >= 70 %}
                    <span class="grade-c">C</span>
                {% else %}
                    <span class="grade-f">F</span>
                {% endif %}
            </td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

📝 요약

이번 Day 83에서 학습한 내용:

  1. Jinja2 템플릿: HTML과 Python 코드 분리
  2. 변수 출력: 이중 중괄호로 변수 출력, 필터 사용
  3. 제어문: if, for, else 태그
  4. 템플릿 상속: extends, block 태그

📚 다음 학습

Day 84: 폼 처리 ⭐⭐⭐⭐

내일은 사용자 입력을 받는 HTML 폼을 만들고, Flask에서 처리하는 방법을 배웁니다!


“템플릿 상속은 코드 재사용의 핵심입니다!” 🚀

Day 83/100 Phase 9: 웹 개발 입문 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.