지금까지 우리는 Python 코드 안에 HTML을 문자열로 작성했습니다. 😰 하지만 이 방법은 매우 불편하고, 유지보수도 어렵습니다!
1
| return f"<h1>안녕하세요, {name}님!</h1><p>{content}</p>" # 😱
|
Jinja2는 이 문제를 해결해줍니다! HTML 파일을 따로 관리하고, Python 변수를 쉽게 주입할 수 있습니다.
프론트엔드 개발자와 백엔드 개발자가 협업할 때 필수적인 기술입니다! 💡
(35분 완독 ⭐⭐⭐⭐)
🎯 오늘의 학습 목표
- Jinja2 템플릿 엔진 이해하기
- 템플릿에 변수 전달하고 출력하기
- 제어문과 반복문 사용하기
- 템플릿 상속과 재사용하기
📚 사전 지식
🎯 학습 목표 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>© 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>"
자동 이스케이프: <script>alert('해킹!')</script>
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에서 학습한 내용:
- Jinja2 템플릿: HTML과 Python 코드 분리
- 변수 출력: 이중 중괄호로 변수 출력, 필터 사용
- 제어문:
if, for, else 태그 - 템플릿 상속:
extends, block 태그
📚 다음 학습
Day 84: 폼 처리 ⭐⭐⭐⭐
내일은 사용자 입력을 받는 HTML 폼을 만들고, Flask에서 처리하는 방법을 배웁니다!
“템플릿 상속은 코드 재사용의 핵심입니다!” 🚀
| Day 83/100 | Phase 9: 웹 개발 입문 | #100DaysOfPython |