Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료
Python 프로그래밍Unit 11: 나의 첫 웹 앱 만들기Topic 2: 사용자와 대화하는 웹 만들기 💬

Topic 2: 템플릿과 폼 - 데이터 주고받기 📝

🎯 학습 목표

Flask로 사용자와 상호작용하는 웹 애플리케이션을 만들어봐요!

  • 템플릿(Templates)이 왜 필요한지 이해하기
  • HTML 폼(Form)으로 사용자 입력 받는 방법
  • 데이터가 어떻게 흘러가는지 파악하기
  • GET과 POST 방식의 차이점 알아보기

🤔 왜 템플릿이 필요할까요?

Topic 1에서는 HTML을 파이썬 문자열로 직접 작성했어요. 간단한 페이지에는 괜찮지만, 복잡한 웹사이트를 만들려면 문제가 생겨요.

문자열로 HTML을 작성할 때의 문제점

# 이런 식으로 HTML을 작성하면... @app.route('/') def home(): return ''' <html> <head><title>제목</title></head> <body> <h1>안녕하세요!</h1> <p>오늘은 좋은 날이에요.</p> <ul> <li>항목 1</li> <li>항목 2</li> </ul> </body> </html> '''

문제점들:

  1. 가독성: 파이썬 코드와 HTML이 섞여서 읽기 어려워요
  2. 재사용: 같은 디자인을 여러 페이지에서 쓰기 힘들어요
  3. 유지보수: HTML을 수정하려면 파이썬 코드를 건드려야 해요
  4. 협업: 디자이너와 개발자가 함께 작업하기 어려워요

템플릿이 해결해주는 것들

템플릿을 사용하면 HTML을 별도 파일로 분리할 수 있어요:

문제점템플릿 해결책
가독성 문제HTML과 Python 코드 완전 분리
재사용 어려움공통 레이아웃을 여러 페이지에서 재사용
유지보수 복잡HTML 파일만 수정하면 됨
협업 어려움디자이너는 HTML, 개발자는 Python에 집중

📁 프로젝트 구조 만들기

Flask 템플릿을 사용하려면 특별한 폴더 구조가 필요해요:

# 프로젝트 폴더 만들기 mkdir flask_templates_app cd flask_templates_app # templates 폴더는 반드시 이 이름이어야 해요! mkdir templates

폴더 구조:

flask_templates_app/ ├── app.py # 메인 Flask 애플리케이션 └── templates/ # HTML 템플릿 파일들 ├── base.html # 기본 레이아웃 └── home.html # 홈페이지 템플릿

중요: templates 폴더는 반드시 이 이름이어야 해요. Flask가 자동으로 이 폴더에서 템플릿을 찾거든요!

🎨 첫 번째 템플릿 만들기

이제 실제로 템플릿을 만들어봐요!

단계 1: 기본 Flask 앱 만들기

먼저 app.py 파일을 만들어요:

# render_template 함수를 추가로 가져와요 from flask import Flask, render_template app = Flask(__name__) @app.route('/') def home(): # 문자열 대신 render_template 함수를 사용해요 return render_template('home.html') if __name__ == '__main__': app.run(debug=True)

코드 이해하기

from flask import Flask, render_template

  • render_template는 HTML 템플릿 파일을 읽어서 완성된 HTML로 만들어주는 함수예요

return render_template('home.html')

  • templates/home.html 파일을 찾아서 HTML로 변환해요
  • 문자열을 직접 반환하는 대신 템플릿 파일을 사용해요

단계 2: HTML 템플릿 파일 만들기

이제 templates/home.html 파일을 만들어요:

<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Flask 템플릿 실습</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #333; text-align: center; } p { color: #666; line-height: 1.6; } </style> </head> <body> <div class="container"> <h1>🎉 Flask 템플릿 성공!</h1> <p>이 페이지는 별도의 HTML 파일에서 만들어졌습니다.</p> <p>Python 코드와 HTML이 깔끔하게 분리되었어요!</p> </div> </body> </html>

단계 3: 실행해보기

python app.py

브라우저에서 http://localhost:5000에 접속해보세요!

🔄 데이터를 템플릿에 전달하기

템플릿의 진짜 힘은 동적 데이터를 표시할 수 있다는 거예요!

from flask import Flask, render_template, request import random app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def name_game(): message = "" color = "#4ECDC4" if request.method == 'POST': name = request.form.get('name', '').strip() if name: # 재미있는 랜덤 반응들 reactions = [ f"와! {name}님! 정말 멋진 이름이에요! ✨", f"{name}님은 분명 특별한 사람이에요! 🌟", f"{name}님의 이름에는 마법이 있어요! 🎭", f"안녕하세요 {name}님! 오늘 기분이 어떠세요? 😊", f"{name}님! 프로그래밍 천재가 되실 것 같아요! 🚀", ] message = random.choice(reactions) color = random.choice(["#FF6B6B", "#4ECDC4", "#45B7D1", "#96E6B3", "#F7DC6F"]) return render_template('name_game.html', message=message, color=color) if __name__ == '__main__': app.run(debug=True)

Step 3: 예쁜 HTML 템플릿 만들기

templates/name_game.html 파일을 만들어요:

<!DOCTYPE html> <html> <head> <title>마법의 이름 게임 ✨</title> <style> body { margin: 0; padding: 0; min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Arial', sans-serif; display: flex; justify-content: center; align-items: center; } .game-container { background: white; padding: 50px; border-radius: 30px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); text-align: center; max-width: 500px; animation: slideUp 0.8s ease; } @keyframes slideUp { from { transform: translateY(50px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } h1 { color: #333; margin-bottom: 30px; font-size: 2.5em; } .magic-wand { font-size: 3em; animation: wiggle 2s ease-in-out infinite; } @keyframes wiggle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(10deg); } 75% { transform: rotate(-10deg); } } .input-group { margin: 30px 0; } input[type="text"] { width: 100%; padding: 15px; font-size: 18px; border: 3px solid #ddd; border-radius: 15px; text-align: center; transition: all 0.3s; } input[type="text"]:focus { outline: none; border-color: #667eea; transform: scale(1.05); } .magic-button { background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 15px 40px; border: none; border-radius: 25px; font-size: 18px; cursor: pointer; transition: all 0.3s; margin-top: 20px; } .magic-button:hover { transform: translateY(-3px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); } .response { margin-top: 30px; padding: 20px; border-radius: 15px; font-size: 20px; font-weight: bold; animation: bounce 0.6s ease; } @keyframes bounce { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } .sparkles { position: absolute; font-size: 2em; animation: sparkle 3s ease-in-out infinite; } @keyframes sparkle { 0%, 100% { opacity: 0; transform: scale(0); } 50% { opacity: 1; transform: scale(1); } } </style> </head> <body> <div class="sparkles" style="top: 10%; left: 10%;">✨</div> <div class="sparkles" style="top: 20%; right: 15%; animation-delay: 1s;">⭐</div> <div class="sparkles" style="bottom: 15%; left: 20%; animation-delay: 2s;">💫</div> <div class="sparkles" style="bottom: 25%; right: 10%; animation-delay: 0.5s;">🌟</div> <div class="game-container"> <div class="magic-wand">🪄</div> <h1>마법의 이름 게임</h1> <p style="color: #666; font-size: 16px;">이름을 입력하면 마법이 일어나요!</p> <form method="POST"> <div class="input-group"> <input type="text" name="name" placeholder="당신의 이름을 입력하세요..." required> </div> <button type="submit" class="magic-button">✨ 마법 시전하기! ✨</button> </form> {% if message %} <div class="response" style="background-color: {{ color }}; color: white;"> {{ message }} </div> {% endif %} </div> </body> </html>

🎮 두 번째 작품: 색깔 투표 시스템

이번엔 더 복잡한 걸 만들어봐요! 좋아하는 색깔에 투표하고 결과를 예쁘게 보여주는 시스템이에요.

전체 코드 (app.py에 추가)

# 기존 코드 아래에 이것들을 추가하세요! # 투표 데이터 저장 votes = { '❤️ 빨강': 0, '💙 파랑': 0, '💚 초록': 0, '💛 노랑': 0, '💜 보라': 0, '🧡 주황': 0 } @app.route('/vote', methods=['GET', 'POST']) def color_vote(): if request.method == 'POST': color = request.form.get('color') if color in votes: votes[color] += 1 # 총 투표 수 계산 total_votes = sum(votes.values()) # 퍼센트 계산 percentages = {} for color, count in votes.items(): if total_votes > 0: percentages[color] = (count / total_votes) * 100 else: percentages[color] = 0 return render_template('vote.html', votes=votes, percentages=percentages, total_votes=total_votes)

투표 템플릿 만들기

templates/vote.html 파일을 만들어요:

<!DOCTYPE html> <html> <head> <title>색깔 투표 🌈</title> <style> body { margin: 0; padding: 20px; background: linear-gradient(45deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%); font-family: 'Arial', sans-serif; min-height: 100vh; } .container { max-width: 800px; margin: 0 auto; } .vote-card { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 15px 35px rgba(0,0,0,0.1); margin-bottom: 30px; animation: fadeIn 0.8s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } h1 { text-align: center; color: #333; margin-bottom: 30px; font-size: 2.5em; } .vote-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin: 30px 0; } .vote-option { text-align: center; } .vote-button { width: 100%; padding: 20px; font-size: 18px; border: 3px solid #ddd; border-radius: 15px; background: white; cursor: pointer; transition: all 0.3s; } .vote-button:hover { transform: scale(1.1); box-shadow: 0 10px 20px rgba(0,0,0,0.1); } .submit-btn { display: block; margin: 30px auto; padding: 15px 40px; background: linear-gradient(45deg, #667eea, #764ba2); color: white; border: none; border-radius: 25px; font-size: 18px; cursor: pointer; transition: all 0.3s; } .submit-btn:hover { transform: translateY(-3px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); } .results { margin-top: 40px; } .result-item { margin: 15px 0; padding: 15px; background: #f8f9fa; border-radius: 10px; position: relative; overflow: hidden; } .result-bar { position: absolute; left: 0; top: 0; height: 100%; background: linear-gradient(45deg, #667eea, #764ba2); opacity: 0.2; transition: width 0.8s ease; } .result-text { position: relative; z-index: 1; display: flex; justify-content: space-between; align-items: center; font-weight: bold; } .crown { font-size: 1.5em; animation: bounce 1s infinite; } @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } </style> </head> <body> <div class="container"> <div class="vote-card"> <h1>🌈 좋아하는 색깔 투표 🌈</h1> <p style="text-align: center; font-size: 18px; color: #666;"> 어떤 색깔을 가장 좋아하나요? </p> <form method="POST"> <div class="vote-options"> {% for color in votes.keys() %} <div class="vote-option"> <label> <input type="radio" name="color" value="{{ color }}" style="display: none;" required> <div class="vote-button">{{ color }}</div> </label> </div> {% endfor %} </div> <button type="submit" class="submit-btn">🗳️ 투표하기!</button> </form> </div> {% if total_votes > 0 %} <div class="vote-card results"> <h2 style="text-align: center; color: #333;">📊 실시간 투표 결과</h2> <p style="text-align: center; color: #666;">총 {{ total_votes }}명이 투표했어요!</p> {% for color, count in votes.items() %} <div class="result-item"> <div class="result-bar" style="width: {{ percentages[color] }}%;"></div> <div class="result-text"> <span>{{ color }}</span> <span> {{ count }}표 ({{ "%.1f"|format(percentages[color]) }}%) {% if count == votes.values()|max and count > 0 %} <span class="crown">👑</span> {% endif %} </span> </div> </div> {% endfor %} </div> {% endif %} </div> <script> // 라디오 버튼 시각적 피드백 document.querySelectorAll('input[type="radio"]').forEach(radio => { radio.addEventListener('change', function() { document.querySelectorAll('.vote-button').forEach(btn => { btn.style.background = 'white'; btn.style.borderColor = '#ddd'; }); this.nextElementSibling.style.background = '#667eea'; this.nextElementSibling.style.borderColor = '#667eea'; this.nextElementSibling.style.color = 'white'; }); }); </script> </body> </html>

💡 Flask 폼 처리 핵심 개념

Flask에서 폼 데이터를 처리하는 방법을 정리해보면:

기능Flask 구현 방법
POST 요청 받기@app.route('/submit', methods=['POST'])
폼 데이터 가져오기request.form.get('name')
템플릿에 데이터 전달render_template('page.html', data=data)
템플릿에서 변수 출력{{ title }} (Jinja2)

이렇게 간단하고 직관적이에요! 😊

🎯 도전 과제: 더 재미있게!

1. 이름 게임에 새 반응 추가하기

reactions = [ f"와! {name}님! 정말 멋진 이름이에요! ✨", # 여기에 더 재미있는 반응들을 추가해보세요! f"{name}님은 미래의 개발자가 될 거예요! 💻", f"{name}님의 이름을 보니 행복해져요! 😄" ]

2. 투표에 새로운 옵션 추가하기

votes = { '❤️ 빨강': 0, '💙 파랑': 0, # 여기에 새로운 색깔 추가해보세요! '🖤 검정': 0, '🤍 하양': 0 }

3. 실시간 시계 만들기

Python의 datetime을 사용해서 페이지를 새로고침할 때마다 최신 시간을 보여주는 시계를 만들어보세요!

from datetime import datetime @app.route('/time') def show_time(): current_time = datetime.now().strftime('%Y년 %m월 %d일 %H:%M:%S') return render_template('time.html', current_time=current_time)

💡 퀴즈: 폼 처리 이해도 체크

Q1. Flask에서 POST 데이터를 받으려면?

  1. request.get('name')
  2. request.form.get('name')
  3. request.data['name']

💡 정답 확인

정답: 2번

request.form.get('name')을 사용해서 폼 데이터를 받아요!

name = request.form.get('name')

Q2. 템플릿에서 조건문을 사용하려면?

  1. {{ if condition }}
  2. {% if condition %}
  3. {# if condition #}

💡 정답 확인

정답: 2번

{% if condition %}을 사용해서 조건문을 만들어요!

{% if message %} <p>{{ message }}</p> {% endif %}

✅ Topic 2 마스터 체크리스트

🚀 다음 단계

와! 이제 사용자와 대화할 수 있는 웹사이트를 만들었네요!

다음 Topic에서는:

  • Unit 10에서 배운 웹 스크래핑을 웹에 연결하기
  • 실시간 날씨 정보 보여주기
  • 진짜 살아있는 데이터를 웹에 띄우기

준비되셨나요? Let’s go! 🚀

Last updated on