학습일지

[스나이퍼팩토리] 한컴AI아카데미(영단어장 리뷰)

cd-record 2026. 4. 30. 11:23
리뷰(영단어장 웹사이트)

 

- app.py

from flask import Flask, request, render_template, redirect, url_for
import sqlite3
import os

 

1. from flask import Flask, request, render_template, redirect, url_for

웹 서버 기능을 구현하기 위한 필수 도구들을 불러오는 것입니다.

  • Flask: 웹 서버를 띄우기 위한 핵심 클래스입니다.
  • request: 사용자가 웹페이지에서 보낸 데이터(입력값, 폼 데이터 등)를 가져올 때 사용합니다.
  • render_template: 파이썬 코드 안에서 HTML 파일을 불러와 화면에 보여줄 때 사용합니다.
  • redirect: 특정 페이지로 이동(URL 재요청)시킬 때 사용합니다.
  • url_for: 페이지 이름(함수명)을 기준으로 주소를 동적으로 생성해 줍니다.

2. import sqlite3

데이터를 저장하고 관리하기 위한 SQLite 데이터베이스를 다루기 위해 필요한 라이브러리입니다. 별도의 설치 없이 파이썬에 내장되어 있습니다.

 

3. import os

컴퓨터의 운영체제(OS)와 상호작용하기 위한 라이브러리입니다.

  • 주로 파일 경로를 다루거나(os.path), 환경 변수를 확인하는 등 파일 시스템과 관련된 작업을 할 때 사용합니다.
app = Flask(__name__)
DB_NAME = os.path.join(os.path.dirname(__file__), "..", "Vocabularys.db")
  1. __file__: 현재 실행 중인 파이썬 파일의 전체 경로를 담고 있는 특별한 변수입니다.
  2. os.path.dirname(__file__): __file__ 경로에서 파일명을 뺀 폴더 경로(디렉토리)만 추출합니다.
    • 예: 파일이 C:/project/sqlite/app.py에 있다면, 이 결과는 C:/project/sqlite가 됩니다.
  3. "..": 운영체제에서 사용하는 상위 폴더(부모 디렉토리)를 의미합니다.
  4. os.path.join(...): 경로 요소들을 운영체제에 맞게 결합해 주는 함수입니다.
    • 윈도우는 \를 사용하고, 리눅스/맥은 /를 사용하는데, 이 함수를 쓰면 환경에 상관없이 자동으로 알맞은 구분자를 넣어줍니다.

전체적인 의미

이 코드는 "현재 실행 중인 파일의 부모 폴더(상위 폴더) 안에 있는 Vocabularys.db 파일의 경로를 찾아라"는 뜻입니다.

  • 만약 지금 실행 중인 코드가 c:/hancomAI_5/PYTHON/0429/sqlite/app.py라면,
  • ..을 통해 한 단계 위로 올라가 c:/hancomAI_5/PYTHON/0429/ 폴더를 가리키게 되고,
  • 최종적으로 c:/hancomAI_5/PYTHON/0429/Vocabularys.db라는 경로를 DB_NAME에 저장하게 됩니다.

def init_db():
    conn = sqlite3.connect(DB_NAME)
    conn.execute("""
            CREATE TABLE IF NOT EXISTS Vocabularys (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                word TEXT NOT NULL,
                meaning TEXT NOT NULL,
                wordclass TEXT NOT NULL,
                status TEXT NOT NULL CHECK(status IN ('완료','미완료')) DEFAULT '미완료'
                
            )
        """)
        # CHECK(status IN ('완료', '미완료')): 이 컬럼에는 '완료' 또는 '미완료'라는 값만 들어올 수 있습니다. 만약 다른 값(예: '진행중')을 넣으려고 하면 SQLite는 에러를 발생시키며 저장을 거부합니다.
        #DEFAULT '미완료': 데이터를 추가할 때 상태 값을 따로 지정하지 않으면 기본적으로 '미완료' 상태로 자동 저장되게 하는 설정입니다.
    conn.commit()
    conn.close()

 

1. 코드의 역할: 테이블 생성

init_db()는 프로그램이 실행될 때 데이터베이스 파일에 Vocabularys라는 이름의 표(테이블)가 있는지 확인하고, 없으면 새로 만드는 역할을 합니다.

 

2. 데이터베이스 연결

conn = sqlite3.connect(DB_NAME) 이 코드는 "데이터베이스 파일을 찾아 문을 열고, 연결 통로(데이터를 주고받을 선)를 만드는 작업"입니다.

 

3. 데이터베이스 연결 통로

 

  • conn: 앞서 sqlite3.connect(DB_NAME)으로 만들어둔 데이터베이스 연결 통로입니다.
  • .execute(): "지금부터 내가 적어주는 SQL 명령을 데이터베이스에 실행해라!"라고 시키는 함수입니다.

4. 컬럼(열) 상세 설명

  • CREATE TABLE  IF NOT EXISTS Vocabularys
    • CREATE TABLE은 '표(테이블)를 만들어라'는 뜻입니다. IF NOT EXISTS는 "이미 있으면 새로 만들지 말고 건너뛰어라"는 뜻, 테이블을 만들 때 지정한 각 항목들의 의미입니다.
  • id INTEGER PRIMARY KEY AUTOINCREMENT:
    • 각 단어장에 고유한 번호를 매깁니다. 새로운 단어를 추가할 때마다 1, 2, 3... 순서대로 숫자가 자동으로 올라갑니다.
  • word TEXT NOT NULL:
    • 영어 단어를 저장하는 칸입니다. NOT NULL은 "여기에 값이 없으면 저장하지 않겠다(필수 입력)"는 뜻입니다.
  • meaning TEXT NOT NULL:
    • 단어의 뜻을 저장합니다. 마찬가지로 필수 입력입니다.
  • wordclass TEXT NOT NULL:
    • 품사(명사, 동사 등)를 저장합니다. 필수 입력입니다.
  • status TEXT NOT NULL ... DEFAULT '미완료':
    • 암기 상태를 저장합니다.
    • CHECK(status IN ('완료', '미완료')): '완료'나 '미완료'라는 글자 외에는 절대 저장할 수 없게 규칙을 정한 것입니다.
    • DEFAULT '미완료': 단어를 처음 입력할 때 암기 상태를 설정하지 않으면, 자동으로 '미완료' 상태로 저장되게 만듭니다.

5. conn.commit(), conn.close()

  • conn.commit(): 지금까지 내린 명령(테이블 생성)을 데이터베이스 파일에 실제로 저장하고 적용
  • conn.close():  데이터베이스 파일과의 연결을 안전하게 끊습니다.

def get_db_connection():
    conn = sqlite3.connect(DB_NAME)
    conn.row_factory = sqlite3.Row
    return conn

1. conn.row_factory = sqlite3.Row

 
  • 의미: 원래 sqlite3는 데이터를 가져오면 데이터가 단순히 (1, 'apple', '사과')처럼 튜플(순서 있는 묶음) 형태로 나옵니다. 이렇게 되면 나중에 데이터가 많아질 때 몇 번째가 무엇인지 기억하기 어렵습니다.
    • sqlite3.Row를 설정하면, 데이터를 가져올 때 row['word']나 row['meaning']처럼 컬럼 이름(열 이름)으로 값을 바로 꺼낼 수 있는 딕셔너리 형태로 변환해 줍니다.

전체적인 의미

이 코드는 "DB에 있는 데이터를 딕셔너리 형태로 바꿔서 conn으로 다시 리턴"을 의미합니다.


- 기본 메인 화면

@app.route("/")
def index():
    search = request.args.get("search", "").strip()
    conn = get_db_connection()
    
    if search:
        # 단어, 의미, 품사, 암기상태 에서 검색
        query = """
            SELECT id, word, meaning, wordclass, status FROM Vocabularys
            WHERE word LIKE ? OR meaning LIKE ? OR wordclass LIKE ? OR status LIKE?
            ORDER BY id DESC
        """
        search_param = f"%{search}%"
        Vocabularys = conn.execute(query, (search_param, search_param, search_param, search_param)).fetchall()
    else:
        Vocabularys = conn.execute("SELECT id, word, meaning, wordclass, status FROM Vocabularys ORDER BY id DESC").fetchall()
    
    conn.close()
    return render_template("index.html", Vocabularys=Vocabularys, search=search)

 

1. 사용자 요청 확인 (search = request.args.get("search", "").strip())

  • 사용자가 검색창에 무언가를 입력했는지 확인합니다.
  • request.args.get("search", ""): 주소창(URL) 뒤에 붙은 ?search=사과 같은 값을 찾습니다. 값이 없으면 빈 문자열("")을 줍니다.
  • .strip(): 입력값 앞뒤에 실수로 들어간 공백을 제거합니다. (예: " 사과 " → "사과")

2. 데이터베이스 연결 (conn = get_db_connection())

  • 앞서 만든 도우미 함수를 불러와 데이터베이스와 연결 통로를 엽니다.

3. 검색 로직 (if - else 구조)

이 부분이 이 함수의 핵심입니다. 검색을 했느냐, 안 했느냐에 따라 다른 SQL 명령을 내립니다.

  • 검색어가 있을 때 (if search:):
    • LIKE ?: 단어장(Vocabularys)에서 입력한 글자가 포함된(%글자%) 데이터를 찾습니다.
    • WHERE ... OR ...: 단어, 의미, 품사, 상태 중 하나라도 검색어와 일치하면 모두 가져옵니다.
    • ORDER BY id DESC: 최신 데이터(번호가 높은 순)부터 보여주기 위해 내림차순으로 정렬합니다.
    • 데이터베이스에서 LIKE 연산자와 함께 사용하는 % 기호는 '아무 글자나 와도 된다(와일드카드)'는 뜻입니다.
      • 만약 search에 "사과"가 들어왔다면, search_param은 "%사과%"가 됩니다.
      • 해석: * "사과"로 시작해도 OK,
        • "사과"로 끝나도 OK,
        • 중간에 "사과"가 끼어 있어도 OK!
        • 즉, 사용자가 "사과"라고만 검색해도 "빨간 사과", "사과나무", "풋사과" 등을 모두 찾아낼 수 있게 만드는 장치입니다.
    • 찾은 변수를 .fetchall()을 써서 리스트 형태로 담는다. 
  • 검색어가 없을 때 (else:):
    • 그냥 전체 목록을 가져옵니다.

4. 데이터베이스 연결 종료 (conn.close())

  • 작업이 끝나면 항상 데이터베이스 창고의 문을 닫아줍니다.

5. 화면 표시 (return render_template(...))

  • render_template("index.html", Vocabularys=Vocabularys, search=search):
    • 파이썬에서 가져온 단어 목록(Vocabularys)과 검색어(search)를 index.html이라는 HTML 파일로 쏙 집어넣어서 전달합니다.

- 영단어 추가

@app.route("/add", methods=["GET", "POST"])
def add_Vocabulary():
    if request.method == "POST":
        word = request.form.get("word", "").strip()
        meaning = request.form.get("meaning", "").strip()
        wordclass = request.form.get("wordclass", "").strip()
        status = request.form.get("status", "").strip()


        if not word or not meaning or not wordclass or not status:
            return redirect(url_for("index"))

        try:
            conn = get_db_connection()
            conn.execute(
                "INSERT INTO Vocabularys (word, meaning, wordclass, status) VALUES (?, ?, ?, ?)",
                (word, meaning, wordclass, status)
            )
            conn.commit()
            conn.close()
        except sqlite3.IntegrityError:
            pass
        
        return redirect(url_for("index"))
    
    return render_template("add.html")

 

1. methods=["GET", "POST"]

하나의 함수에서 두 가지 일을 합니다.

  • GET: 사용자가 단어 입력 페이지(add.html)를 처음 열 때 실행됩니다. (화면을 보여줌)
  • POST: 사용자가 입력폼에 내용을 적고 '저장' 버튼을 누를 때 실행됩니다. (데이터를 서버로 전송)

2. 데이터 가져오기 및 검증

  • request.form.get("키값"): HTML 폼에서 사용자가 입력한 내용을 가져옵니다.
  • .strip(): 입력값 앞뒤에 실수로 들어간 공백을 제거합니다.
  • 유효성 검사: 만약 단어, 의미, 품사, 상태 중 하나라도 비어있다면(not word 등) 저장하지 않고 메인 페이지(index)로 되돌려 보냅니다. 데이터가 엉뚱하게 저장되는 것을 방지합니다.

3. 데이터베이스에 데이터 넣기 (INSERT)

  • INSERT INTO: 데이터베이스 표에 새로운 줄(행)을 추가하는 명령어입니다.
  • (?, ?, ?, ?): 사용자의 입력값이 바로 SQL 문에 들어가면 해킹 위험(SQL Injection)이 있기 때문에, 물음표 자리에 나중에 안전하게 데이터를 끼워 넣겠다는 뜻입니다.
  • conn.commit(): 매우 중요합니다. 이 명령을 내려야 비로소 데이터베이스 파일에 실제 데이터가 "확정"되어 저장됩니다.

4. 예외 처리 (try - except)

  • 혹시라도 데이터베이스의 규칙(예: NOT NULL 제약조건 위반 등)을 어기는 데이터가 들어와서 에러가 나더라도, 프로그램이 멈추지 않고 그냥 지나가게(pass) 만듭니다. (안정성을 위한 처리입니다.)

- 영단어 수정

@app.route("/edit/<int:Vocabulary_id>", methods=["GET", "POST"])
def edit_Vocabulary(Vocabulary_id):
    conn = get_db_connection()
    Vocabulary = conn.execute(
        "SELECT id, word, meaning, wordclass, status FROM Vocabularys WHERE id = ?",
        (Vocabulary_id,)
    ).fetchone()
    
    if request.method == "POST":
        word = request.form.get("word", "").strip()
        meaning = request.form.get("meaning", "").strip()
        wordclass = request.form.get("wordclass", "").strip()
        status = request.form.get("status", "").strip()

        if not word or not meaning or not wordclass or not status:
            conn.close()
            return redirect(url_for("index"))

        try:
            conn.execute(
                "UPDATE Vocabularys SET word = ?, meaning = ?, wordclass = ?, status = ? WHERE id = ?",
                (word, meaning, wordclass, status, Vocabulary_id)
            )
            conn.commit()
        except sqlite3.IntegrityError:
            pass
        finally:
            conn.close()
        
        return redirect(url_for("index"))
    
    conn.close()
    if Vocabulary is None:
        return redirect(url_for("index"))
    
    return render_template("edit.html", Vocabulary=Vocabulary)

 

1. 라우트 설정 및 데이터 조회

 
  • 동적 라우팅: <int:Vocabulary_id>를 통해 수정할 데이터의 고유 ID를 URL로 전달받습니다.
  • 초기 조회: GET 요청(페이지 접속)과 POST 요청(수정 제출) 모두에서 현재 데이터를 먼저 조회하여 존재 여부를 확인합니다.

2. GET 요청: 수정 페이지 표시

 
  • 사용자가 처음 수정 버튼을 눌러 들어왔을 때 실행됩니다.
  • 데이터가 없으면 메인 페이지(index)로 튕겨내고, 데이터가 있다면 edit.html 템플릿에 기존 데이터를 채워서 보여줍니다.

3. POST 요청: 데이터 업데이트

사용자가 폼을 작성하고 "저장" 버튼을 눌렀을 때의 로직입니다.

① 데이터 수집 및 유효성 검사

 
  • strip()을 사용하여 앞뒤 공백을 제거합니다.
  • 필수 필드가 하나라도 비어있으면 수정을 중단하고 인덱스 페이지로 리다이렉트합니다.

② DB 반영 (Try-Except-Finally)

 
  • SQL UPDATE: 전달받은 값들로 해당 ID의 레코드를 갱신합니다.
  • 예외 처리: 중복된 단어 입력 등 DB 제약 조건 위반(IntegrityError) 시 프로그램이 멈추지 않도록 처리했습니다.
  • 자원 해제: finally를 통해 성공하든 실패하든 반드시 conn.close()를 호출하여 DB 연결을 닫습니다.

- 영단어 삭제

@app.route("/delete/<int:Vocabulary_id>")
def delete_Vocabulary(Vocabulary_id):
    conn = get_db_connection()
    conn.execute("DELETE FROM Vocabularys WHERE id = ?", (Vocabulary_id,))
    conn.commit()
    conn.close()
    return redirect(url_for("index"))

 

1. 라우트 및 매개변수 설정

 
  • URL 구조: /delete/5와 같은 형식으로 요청을 받습니다. 여기서 5는 삭제할 데이터의 고유 식별자(Primary Key)인 Vocabulary_id로 전달됩니다.
  • 메서드: 별도의 methods 지정이 없으므로 기본값인 GET 방식으로 동작합니다. (참고: 보안상 실제 서비스에서는 삭제 시 POST 방식을 권장하기도 합니다.)

2. 데이터베이스 삭제 실행

 
  • 연결: get_db_connection()을 통해 DB에 접속합니다.
  • SQL문: DELETE FROM Vocabularys WHERE id = ?
    • Vocabularys 테이블에서 id 값이 입력받은 값과 일치하는 행(row)을 삭제합니다.
  • 확정(Commit): conn.commit()을 호출해야만 DB에 변경 사항이 실제로 반영됩니다. 이 줄을 빠뜨리면 삭제 명령을 내려도 데이터가 지워지지 않으니 주의해야 합니다.
  • 닫기: 작업이 끝나면 conn.close()로 연결을 해제하여 자원을 반환합니다.

3. 처리 후 이동

 
  • 삭제가 완료되면 별도의 페이지를 보여주는 대신, 다시 메인 목록 페이지(index)로 사용자를 보냅니다. 사용자는 목록에서 해당 단어가 사라진 것을 바로 확인할 수 있게 됩니다.

if __name__ == "__main__":
    init_db()
    app.run(debug=True, port=5002)

 

1. if __name__ == "__main__":

  • 직접 실행할 때: 터미널에서 python app.py라고 입력하여 실행하면 __name__ 변수에 "__main__"이라는 값이 담겨 아래 코드들이 실행됩니다.
  • 가져오기(Import)할 때: 다른 파일에서 import app을 통해 이 파일을 불러올 때는 아래 코드들이 실행되지 않습니다. 즉, 원치 않게 서버가 갑자기 켜지는 것을 방지하는 안전장치 역할을 합니다.

2. init_db()

  • 데이터베이스 초기화: 서버를 켜기 전에 테이블을 생성하거나 초기 데이터를 넣는 함수를 호출합니다.

3. app.run(debug=True, port=5002)

  • 더보기
    debug=True (디버그 모드):
    • 코드를 수정하고 저장하면 서버가 자동으로 재시작됩니다.
    • 에러가 발생하면 브라우저에 상세한 에러 로그를 보여주어 디버깅을 도와줍니다.
    • 주의: 실제 서비스(운영 환경)에서는 보안상 반드시 False로 바꿔야 합니다.
  • port=5002:
    • 서버가 사용할 통로(포트 번호)를 지정합니다. 기본값은 5000이지만, 이미 다른 프로그램이 사용 중일 경우를 대비해 5002처럼 다른 번호로 지정한 것입니다.

 

 

 

——————————————————————————

본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.