투두리스트, 캘린더, 다이어리 웹사이트 개발 - Flask 개발 환경 준비

2024. 12. 30. 22:24Calender Website

2025년을 맞이하여 캘린더, 투두리스트, 다이어리 등의 기능을 통합한 웹사이트를 개발하려고 한다.

 

나는 이전에 파이썬 플라스크를 이용한 웹사이트를 개발한 후, AWS로 배포한 경험이 있기 때문에 똑같은 기술 스택을 사용하여 빠르게 개발해보려고 한다.

 


1. 프로젝트 폴더 깃허브 업로드

 

우선 프로젝트 폴더를 준비한다. 이전에는 C:\Program Files\PostgreSQL\15\data\CrimeManagementSystem 폴더 안에 테이블 스페이스와 프로젝트 폴더를 동시에 준비하였으나 이번에는 둘을 분리하였다.

 

- 프로젝트 폴더 경로 : C:\Project\calender_app

 

이제 깃허브에 폴더 업로드를 진행한다. 미리 깃허브에서 레포지토리를 만들어 주소를 준비한다.

깃허브에 폴더를 업로드 하는 방법은 이전에 올려둔 포스팅을 참고하였다. 

https://funcodingblog.tistory.com/19

 

1. 프로젝트 폴더에서 우클릭하여 git bash here 클릭

2. git init . 로 폴더 초기화

3. git remote add origin [저장소 이름] 로 원격 저장소와 연결

4. git remote -v 로 연결이 되었는지 확인

kyu00@BOOK-CFQ1B4M00U MINGW64 /c/Project/calender_app
$ git init .
Initialized empty Git repository in C:/Project/calender_app/.git/

kyu00@BOOK-CFQ1B4M00U MINGW64 /c/Project/calender_app (main)
$ git remote add origin https://github.com/knh0503/calender_app

kyu00@BOOK-CFQ1B4M00U MINGW64 /c/Project/calender_app (main)
$ git remote -v
origin  https://github.com/knh0503/calender_app (fetch)
origin  https://github.com/knh0503/calender_app (push)

 

원격 저장소와 연결이 정상적으로 되었다. git pull origin main으로 원격 저장소의 README 파일을 가져올 수 있다.

 


2. 프로젝트 폴더 구조 및 가상 환경 설치

1) 프로젝트 폴더의 전체적인 구조는 다음과 같다.

calendar_app/              # 프로젝트 루트 디렉토리
├── app/                      # 애플리케이션 패키지
│   ├── __init__.py          # 앱 초기화 (Flask 앱 생성, 설정 로드, DB 초기화)
│   ├── models/              # 데이터베이스 모델 폴더
│   │   └── __init__.py      # 모델 패키지 초기화
│   ├── routes/              # URL 라우트 처리 폴더
│   │   └── __init__.py      # 라우트 패키지 초기화
│   ├── static/              # 정적 파일 폴더
│   │   ├── css/            # CSS 파일들 (스타일링)
│   │   ├── js/             # JavaScript 파일들 (클라이언트 측 로직)
│   │   └── images/         # 이미지 파일들
│   └── templates/           # HTML 템플릿 폴더 (Jinja2 템플릿)
├── config.py                # 설정 파일 (DB URL, 시크릿 키 등)
├── requirements.txt         # 프로젝트 의존성 목록
└── run.py                   # 애플리케이션 실행 스크립트

 

 

2) 다음 명령어를 통해 하위 폴더들과 파일을 만들어 준다.

 

참고로 git bash에서 진행한다.

mkdir -p app/models app/routes app/static/css app/static/js app/static/images app/templates
touch app/__init__.py app/models/__init__.py app/routes/__init__.py config.py requirements.txt run.py

 

 

3) 가상환경을 생성하고 활성화한다.

 

가상환경을 생성하는 이유는 프로젝트마다 독립된 파이썬 환경을 만들고, 프로젝트에 필요한 패키지만 생성할 수 있기 때문이다.

python -m venv venv

 

가상머신을 활성화하려면 다음 명령어를 사용하면 된다. (venv) 가 표시되면 활성화된 것이다.

source venv/Scripts/activate

 

만약 가상머신을 비활성화하려면 다음 명령어를 사용한다.

deactivate

 

 

4)  가상환경을 활성화한 상태에서 필요한 패키지들을 설치한다.

 

requirements.txt 파일을 열어 다음과 같이 설치할 패키지를 저장한다.

Flask==3.0.0
Flask-SQLAlchemy==3.1.1
psycopg2-binary==2.9.9
python-dotenv==1.0.0

 

다음 명령어를 이용하여 requirements.txt에 적힌 패키지를 일괄 설치할 수 있다.

pip install -r requirements.txt

 

설치한 패키지를 확인하려면 pip list 로 확인할 수 있다.

$ pip list
Package           Version
----------------- -------
blinker           1.9.0
click             8.1.8
colorama          0.4.6
Flask             3.0.0
Flask-SQLAlchemy  3.1.1
greenlet          3.1.1
itsdangerous      2.2.0
Jinja2            3.1.5
MarkupSafe        3.0.2
pip               24.0
psycopg2-binary   2.9.9
python-dotenv     1.0.0
SQLAlchemy        2.0.36
typing_extensions 4.12.2
Werkzeug          3.1.3

3. 데이터베이스 테이블스페이스 및 테이블 준비

 

1) 테이블 스페이스 디렉토리 생성

 

- 테이블 스페이스 폴더 경로 : C:\Program Files\PostgreSQL\15\data\tablespaces\calendar_app

 

2) 테이블 스페이스 생성

postgres=# CREATE TABLESPACE calendar_space LOCATION 'C:\Program Files\PostgreSQL\15\data\tablespaces\calendar_app';
경고:  테이블스페이스 경로는 데이터 디렉터리 안에 있으면 안됩니다
CREATE TABLESPACE

 

3) 데이터베이스 생성

CREATE DATABASE calendar_db TABLESPACE calendar_space;

 

    테이블을 확인하려면 \l+, 테이블 스페이스를 확인하려면 \db+ 을 입력한다.

 

4)  데이터베이스에 진입 후 테이블 생성

# 데이터베이스 진입
\c calendar_db

# 사용자 테이블
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(80) UNIQUE NOT NULL,
    email VARCHAR(120) UNIQUE NOT NULL,
    password VARCHAR(128) NOT NULL
);

# 이벤트 테이블
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100) NOT NULL,
    description TEXT,
    start_date TIMESTAMP NOT NULL,
    end_date TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    user_id INTEGER REFERENCES users(id) NOT NULL
);
-- 테이블 목록 확인
\dt

-- 각 테이블 구조 확인
\d users
\d events

 

5) 테이블에 샘플 데이트 삽입

-- 사용자 추가
INSERT INTO users (username, email, password) 
VALUES ('test_user', 'test@example.com', 'password123');

-- 이벤트 추가
INSERT INTO events (title, description, start_date, end_date, user_id)
VALUES (
    '테스트 일정',
    '테스트 설명',
    '2024-01-01 10:00:00',
    '2024-01-01 11:00:00',
    1  -- 위에서 생성된 test_user의 id
);

 

6) 데이터가 정상적으로 추가되었는지 확인

-- 사용자 데이터 확인
SELECT * FROM users;

-- 이벤트 데이터 확인
SELECT * FROM events;

 

이제 postgresql에 정상적으로 테이블 스페이스, 데이터베이스, 테이블, 샘플데이터가 추가되었다.


4. Flask 파일 설정

1) config.py 설정

import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

 

- load_dotenv() : .env 파일에서 환경 변수를 로드한다.

- SECRET_KEY = os.environ.get('SECRET_KEY) or 'dev' : .env 파일에 정의된 SECRET_KEY 환경변수를 불러온다.

- SQLALCHEMY_TRACK_MODIFICATIONS = False : SQLAlchemy가 객체 변경 사항을 추적하는 기능을 꺼서 불필요한    리소스 소모를 막을 수 있다.

 

2) .env 파일 설정

    .env에 환경 변수를 설정하고 .gitignore에 추가하여 Git에 추적되지 않도록 한다.

DATABASE_URL=postgresql://postgres:your_password@localhost/calendar_db
SECRET_KEY=your_secret_key
.env

 

3) models 폴더 수정

    models 폴더는 테이블과 매핑할 모델을 정의하고 임포트하여 데이터베이스를 사용할 수 있도록 한다. 

   

    calender.py은 테이블과 매핑할 모델을 정의한다.

from app import db
from datetime import datetime

class User(db.Model):
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(128), nullable=False)
    events = db.relationship('Event', backref='user', lazy=True)

class Event(db.Model):
    __tablename__ = 'events'
    
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text)
    start_date = db.Column(db.DateTime, nullable=False)
    end_date = db.Column(db.DateTime, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)

 

    __init__.py 에서 모델을 임포트한다.

from app.models.calender import User, Event

 

5) routes 폴더 수정

    routes 폴더의 main.py는 데이터베이스에서 데이터를 가져와 HTML 템플릿으로 전달할 수 있다.

    이전 프로젝트에서는 routes/main.py의 내용을 run.py와 통합 사용하여 코드 관리가 힘들었다.

 

    main.py는 테이블과 매핑된 모델을 import한 다음 각 테이블의 모든 쿼리들을 HTML로 전송한다.

    또한 main = Blueprint('main', __name__)은 블루프린트를 정의하는 코드로, URL 라우팅을 모듈화하고 구분하여 관리할

    수 있도록 도와준다. 

from flask import Blueprint, jsonify
from app.models.calender import User, Event
from app import db

main = Blueprint('main', __name__)

@main.route('/users')
def get_users():
    users = User.query.all()
    return jsonify([{
        'id': user.id,
        'username': user.username,
        'email': user.email
    } for user in users])

@main.route('/events')
def get_events():
    events = Event.query.all()
    return jsonify([{
        'id': event.id,
        'title': event.title,
        'start_date': event.start_date.isoformat(),
        'end_date': event.end_date.isoformat()
    } for event in events])

 

6) app/__init__.py 수정

    app/init.py 파일은 Flask 애플리케이션을 초기화하고 설정을 로드한다.

    또한 블루프린트를 등록하여 Flask 애플리케이션에서 동작할 수 있도록 한다.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    
    db.init_app(app)
    
    from app.routes.main import main
    app.register_blueprint(main)
    
    return app

 

7)  run.py 수정

    애플리케이션을 실행하는 스크립트로, 이 파일을 실행하면 Flask 서버가 시작된다.

from app import create_app

app = create_app()

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

5. 간단한 홈페이지 만들기(HTML, CSS, Javascript)

    앞서 기본적인 Flask 설정을 완료하였다. 데이터베이스에서 불러온 데이터를 출력할 화면을 만들어야 한다.

   

    1) HTML : app/templates/users_and_events.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Users and Events</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <header>
        <h1>Calendar App</h1>
        <nav>
            <ul>
                <li><a href="#users-section">Users</a></li>
                <li><a href="#events-section">Events</a></li>
            </ul>
        </nav>
    </header>

    <main>
        <!-- Users Section -->
        <section id="users-section">
            <h2>Users</h2>
            <table id="users-table">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Username</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- User data will be added here by JavaScript -->
                </tbody>
            </table>
        </section>

        <!-- Events Section -->
        <section id="events-section">
            <h2>Events</h2>
            <table id="events-table">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Title</th>
                        <th>Start Date</th>
                        <th>End Date</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- Event data will be added here by JavaScript -->
                </tbody>
            </table>
        </section>
    </main>

    <footer>
        <p>&copy; 2024 Calendar App</p>
    </footer>
    
    <script src="/static/js/script.js"></script>
</body>
</html>

 

    2) CSS : /app/static/css/style.css

body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    background-color: #f4f4f9;
}

header {
    background: #35495e;
    color: #fff;
    padding: 1rem 0;
    text-align: center;
}

header h1 {
    margin: 0;
}

nav ul {
    list-style: none;
    padding: 0;
    margin: 0;
}

nav ul li {
    display: inline;
    margin: 0 10px;
}

nav ul li a {
    color: #fff;
    text-decoration: none;
}

main {
    padding: 20px;
}

section {
    margin: 20px 0;
}

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

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

table th {
    background-color: #35495e;
    color: white;
}

footer {
    text-align: center;
    padding: 10px;
    background: #35495e;
    color: white;
    margin-top: 20px;
}

 

    3) Javascript : /app/static/js/script.js

// Fetch and display users
fetch('/users')
    .then(response => response.json())
    .then(data => {
        const usersTable = document.getElementById('users-table').querySelector('tbody');
        data.forEach(user => {
            const row = document.createElement('tr');
            row.innerHTML = `
                <td>${user.id}</td>
                <td>${user.username}</td>
                <td>${user.email}</td>
            `;
            usersTable.appendChild(row);
        });
    })
    .catch(error => console.error('Error fetching users:', error));

// Fetch and display events
fetch('/events')
    .then(response => response.json())
    .then(data => {
        const eventsTable = document.getElementById('events-table').querySelector('tbody');
        data.forEach(event => {
            const row = document.createElement('tr');
            row.innerHTML = `
                <td>${event.id}</td>
                <td>${event.title}</td>
                <td>${event.start_date}</td>
                <td>${event.end_date}</td>
            `;
            eventsTable.appendChild(row);
        });
    })
    .catch(error => console.error('Error fetching events:', error));

 


 

run.py를 실행하여 홈페이지를 확인하면 다음과 같이 잘 출력된다.