이것저것

[SK shieldus Rookies 29기] 7일차 본문

SK Shieldus Rookies 29

[SK shieldus Rookies 29기] 7일차

atfield1988 2025. 12. 4. 10:58

7일차 강의
Flask로 배우는 파이썬 웹 개발 기초부터 실전까지
Naver mail autmaion(구글링 하면 나오니까 다루지 않겠음)


1️⃣ Flask란?

📌 정의

Flask는 경량화된 파이썬 웹 애플리케이션 프레임워크입니다.

복잡하지 않으면서도 강력한 웹 애플리케이션을 개발할 수 있는 도구를 제공합니다.


🌍 웹 개발 언어 비교

언어 특징
HTML/JavaScript/PHP 전통적 웹 개발
ASP, .NET 마이크로소프트 생태계
JSP/JAVA (Spring, Struts) 엔터프라이즈 환경
Django 풀스택 파이썬 프레임워크
Flask 경량 파이썬 프레임워크 ⭐
FastAPI 고성능 파이썬 프레임워크

🔧 Flask의 기술 스택

  • WSGI 툴킷: Werkzeug
  • 템플릿 엔진: Jinja2
  • 장점: 간단함, 유연함, 배우기 쉬움

2️⃣ Flask 설치 및 시작

설치

pip install Flask

최소 Flask 앱

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "테스트입니다."

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

파일명: hello.py


Flask 서버 실행

flask --app hello run

출력:

WARNING: This is a development server. 
Do not use it in a production deployment. 
Use a production WSGI server instead.

⚠️ 주의: 개발용 서버이므로 프로덕션에서는 WSGI 서버 사용 권장


로컬 서버 접속

브라우저에서 다음 주소로 접속:

http://127.0.0.1:5000/

서버 중지

Ctrl + C

3️⃣ 라우팅(Routing) 기본

📌 라우팅이란?

사용자가 특정 URL에 접근할 때 해당 함수를 실행하여 응답을 반환하는 매커니즘입니다.

🎯 의미 있는 URL의 중요성

현대 웹 애플리케이션은 사용자에게 의미 있는 URL을 제공하여 사용자 경험을 향상시킵니다.

  • ✅ 사용자가 기억하기 쉬움
  • ✅ 직접 방문 가능
  • ✅ 재방문율 증가

route( ) 데코레이터

from flask import Flask

app = Flask(__name__)

# "/" 경로에 접근할 때 index() 함수 호출
@app.route("/")
def index():
    return '<h1>Index Page</h1>'

# "/hello" 경로에 접근할 때 hello() 함수 호출
@app.route("/hello")
def hello():
    return '<h1>Hello Page</h1>'

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

접속 주소:

  • http://127.0.0.1:5000/ → "Index Page"
  • http://127.0.0.1:5000/hello → "Hello Page"

4️⃣ HTML 템플릿 사용

📌 템플릿이란?

동적으로 내용을 변경할 수 있는 HTML 파일입니다.

Flask는 Jinja2 템플릿 엔진을 사용합니다.


디렉토리 구조

project/
├── app.py
└── templates/
    ├── index.html
    └── 2.html

Flask 앱 (app.py)

from flask import Flask, render_template

app = Flask(__name__)

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

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

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

템플릿 파일 (templates/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
</head>
<body>
    <h1>홈페이지 테스트</h1>
</body>
</html>

템플릿 파일 (templates/2.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page 2</title>
</head>
<body>
    <h1>두번째 페이지</h1>
</body>
</html>

5️⃣ 실습 1️⃣: RSS 서비스

📌 요구사항

  1. 사용자에게 RSS URL 입력받기
  2. feedparser로 RSS 파싱
  3. 결과를 테이블로 표시

디렉토리 구조

project/
├── app.py
├── templates/
│   ├── index.html
│   └── rss.html
└── static/
    └── style.css

Flask 앱 (app.py)

from flask import Flask, render_template, request
import feedparser

app = Flask(__name__)

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

@app.route("/rss", methods=['GET', 'POST'])
def rss():
    rss_url = request.form['rss_url']
    feed = feedparser.parse(rss_url)
    return render_template('rss.html', feed=feed)

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

입력 페이지 (templates/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RSS 서비스</title>
</head>
<body>
    <h1>RSS 서비스</h1>
    <form method="post" action="{{ url_for('rss') }}">
        <b>RSS 주소를 입력하세요.</b><br>
        <input type="text" name="rss_url" size="50"><br>
        <button type="submit">확인</button>
    </form>
</body>
</html>

결과 페이지 (templates/rss.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RSS 결과</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>RSS 결과 페이지</h1>
    <table border="1">
        <tr>
            <th>제목</th>
            <th>링크</th>
            <th>요약</th>
        </tr>
        {% for entry in feed.entries %}
        <tr>
            <td>{{ entry.title }}</td>
            <td><a href="{{ entry.link }}">{{ entry.link }}</a></td>
            <td>{{ entry.description }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

스타일시트 (static/style.css)

body {
    font-family: Arial, sans-serif;
}

h1 {
    color: #333;
}

table {
    border-collapse: collapse;
    width: 100%;
}

th, td {
    border: 1px solid #ddd;
    padding: 8px;
    text-align: left;
}

th {
    background-color: #f2f2f2;
}

tr:nth-child(even) {
    background-color: #f9f9f9;
}

tr:hover {
    background-color: #f5f5f5;
}

결과



6️⃣ 실습 2️⃣: 번역 서비스

📌 요구사항

  1. Excel 파일 업로드
  2. 파일 내용을 한글 → 영문 번역
  3. 번역된 파일 다운로드

필수 라이브러리 설치

pip install python-docx openpyxl deep_translator

디렉토리 구조

project/
├── app.py
├── templates/
│   ├── index.html
│   └── result.html
└── uploads/

Flask 앱 (app.py)

from flask import Flask, render_template, request, send_file
import os
from openpyxl import load_workbook
from deep_translator import GoogleTranslator

app = Flask(__name__)

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

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    # 파일 저장
    file = request.files["file"]
    file.save(os.path.join("uploads", file.filename))

    # 엑셀 파일 로드
    workbook = load_workbook(os.path.join("uploads", file.filename))
    sheet = workbook.active

    # 셀별 번역
    for row in sheet.iter_rows():
        for cell in row:
            if cell.value:
                translated_text = GoogleTranslator(
                    source='ko', 
                    target='en'
                ).translate(cell.value)
                cell.value = translated_text

    # 결과 저장
    workbook.save('result_en.xlsx')
    return render_template('result.html')

@app.route('/download_report')
def download_report():
    return send_file('result_en.xlsx', as_attachment=True)

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

입력 페이지 (templates/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>번역 서비스</title>
</head>
<body>
    <h1>번역할 파일(xlsx)을 올리세요.</h1>
    <form method="POST" action="{{ url_for('upload') }}" enctype="multipart/form-data">
        <input type="file" name="file" required><br><br>
        <input type="submit" value="업로드">
    </form>
</body>
</html>

결과 페이지 (templates/result.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>번역 완료</title>
</head>
<body>
    <h1>번역이 완료되었습니다!</h1>
    <form action="{{ url_for('download_report') }}">
        <input type="submit" value="다운로드">
    </form>
</body>
</html>

결과



7️⃣ 실습 3️⃣: 수료증 발급 시스템

📌 요구사항

  1. 사용자에게 이름, 과정, 날짜 입력받기
  2. 템플릿 Word 파일 기반 수료증 생성
  3. PDF 변환 후 자동 다운로드

필수 라이브러리 설치

pip install python-docx docx2pdf

Flask 앱 (app.py)

from flask import Flask, render_template, request, send_file
from docx import Document
from docx2pdf import convert

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def certificate():
    if request.method == 'POST':
        # 폼에서 데이터 받기
        name = request.form['name']
        course = request.form['course']
        date = request.form['date']

        # 템플릿 문서 로드
        doc = Document("template.docx")

        # 텍스트 변환
        for paragraph in doc.paragraphs:
            if 'NAME' in paragraph.text:
                paragraph.text = paragraph.text.replace('NAME', name)
            elif 'COURSE' in paragraph.text:
                paragraph.text = paragraph.text.replace('COURSE', course)
            elif 'DATE' in paragraph.text:
                paragraph.text = paragraph.text.replace('DATE', date)

        # 파일명 설정
        doc_filename = f"{name}_{course}_certificate.docx"
        pdf_filename = f"{name}_{course}_certificate.pdf"

        # 문서 저장 및 PDF 변환
        doc.save(doc_filename)
        convert(doc_filename, pdf_filename)

        # PDF 다운로드
        return send_file(pdf_filename, as_attachment=True)

    return render_template('index.html')

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

입력 페이지 (templates/index.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>수료증 발급 서비스</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 500px;
            margin: 50px auto;
        }
        form {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
        }
        input[type="text"], input[type="date"] {
            width: 100%;
            padding: 8px;
            margin: 10px 0;
            box-sizing: border-box;
        }
        input[type="submit"] {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>수료증 발급 서비스</h1>

    <form method="POST">
        <label for="name"><b>이름:</b></label><br>
        <input type="text" id="name" name="name" required><br>

        <label for="course"><b>과정:</b></label><br>
        <input type="text" id="course" name="course" required><br>

        <label for="date"><b>수료 날짜:</b></label><br>
        <input type="date" id="date" name="date" required><br><br>

        <input type="submit" value="수료증 발급">
    </form>
</body>
</html>

결과


  • 이 입력란을 작성하세요. -> required를 사용

docx

pdf


8️⃣ 실습 4️⃣: 파일 목록 및 압축 서비스

📌 요구사항

  1. uploads 폴더의 파일 목록 표시
  2. 파일 정보(크기, 생성일) 표시
  3. 다중 선택 후 압축 다운로드

Flask 앱 (app.py)

from flask import Flask, render_template, request, send_file
import os
from datetime import datetime
import zipfile

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def list_files():
    UPLOAD_PATH = 'uploads'
    files = []

    # uploads 폴더의 모든 파일 정보 수집
    for file in os.listdir(UPLOAD_PATH):
        file_path = os.path.join(UPLOAD_PATH, file)
        file_size = os.path.getsize(file_path)
        file_ctime = datetime.fromtimestamp(
            os.path.getctime(file_path)
        ).strftime('%Y-%m-%d %H:%M:%S')

        files.append((file, file_size, file_ctime, file_path))

    return render_template('list.html', files=files)

@app.route('/compress', methods=['GET', 'POST'])
def compress():
    UPLOAD_PATH = 'uploads'
    selected_files = request.form.getlist("files")
    zip_path = os.path.join(UPLOAD_PATH, "compress.zip")

    # ZIP 파일 생성
    with zipfile.ZipFile(zip_path, "w") as zip_file:
        for file in selected_files:
            file_path = os.path.join(UPLOAD_PATH, file)
            zip_file.write(file_path, file)

    return send_file(zip_path, as_attachment=True)

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

파일 목록 페이지 (templates/list.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>파일 목록</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>파일 목록</h1>

    <form method="post" action="{{ url_for('compress') }}">
        <table border="1">
            <tr>
                <th>파일 이름</th>
                <th>파일 크기 (Byte)</th>
                <th>생성 날짜</th>
                <th>파일 경로</th>
                <th>✓ 선택</th>
            </tr>
            {% for file in files %}
            <tr>
                <td>{{ file[0] }}</td>
                <td>{{ file[1] }}</td>
                <td>{{ file[2] }}</td>
                <td>{{ file[3] }}</td>
                <td><input type="checkbox" name="files" value="{{ file[0] }}"></td>
            </tr>
            {% endfor %}
        </table>
        <br>
        <button type="submit">선택된 파일 압축하기</button>
    </form>
</body>
</html>

스타일시트 (static/style.css)

body {
    font-family: Arial, sans-serif;
    margin: 20px;
}

h1 {
    color: #333;
}

table {
    border-collapse: collapse;
    width: 100%;
    margin-top: 20px;
}

th, td {
    border: 1px solid #ddd;
    padding: 12px;
    text-align: left;
}

th {
    background-color: #f2f2f2;
    font-weight: bold;
}

tr:nth-child(even) {
    background-color: #f9f9f9;
}

tr:hover {
    background-color: #e8e8e8;
}

button {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
}

button:hover {
    background-color: #45a049;
}

결과

))

성공!


📋 핵심 개념 정리

✅ Flask 핵심 기능

기능 설명
@app.route() URL 경로를 함수와 연결
render_template() HTML 템플릿 렌더링
request 사용자 입력 받기
send_file() 파일 다운로드
Jinja2 템플릿 엔진

📁 Flask 프로젝트 구조

my_flask_app/
├── app.py              # 메인 애플리케이션
├── templates/          # HTML 템플릿
│   ├── index.html
│   ├── result.html
│   └── list.html
├── static/             # CSS, JS, 이미지
│   └── style.css
└── uploads/            # 사용자 업로드 폴더

🎯 Flask 개발 흐름

1. 라우팅 정의 (@app.route)
   ↓
2. 사용자 입력 받기 (request.form, request.files)
   ↓
3. 데이터 처리 (파싱, 번역, 변환 등)
   ↓
4. 템플릿 렌더링 (render_template)
   ↓
5. 결과 반환 (HTML, 파일 다운로드)

🚨 개발 주의사항

⚠️ debug=True는 개발용만

# 개발 환경
app.run(debug=True)

# 프로덕션 환경
app.run(debug=False)

⚠️ 파일 업로드 보안

  • 파일 확장자 검증
  • 파일 크기 제한
  • 악성 파일 필터링

💡 실무 활용 사례

📌 자동화 서비스

  • RSS 뉴스 수집 서비스
  • 파일 형식 변환 서비스
  • 대량 파일 처리

📌 문서 생성 서비스

  • 수료증 자동 발급
  • 계약서 생성
  • 보고서 자동 생성

📌 파일 관리 서비스

  • 파일 업로드/다운로드
  • 대량 파일 압축
  • 파일 메타데이터 표시

마지막에는 배웠던 거 다 때려박는 느낌이라 좀 빡셈...