서버 관리 및 모니터링 프로젝트 (영화관 시스템)

플라스크 프로젝트로 변환(로그인 기능)

dmswo 2024. 12. 4. 16:03

플라스크 프로젝트로 변환

터미널에서 실행되던 영화 관리 시스템을  Flask 웹 서버에서 동작하도록 수정한다.

내용이 많기 때문에 작은 기능들 부터 확인하면서 수정하겠다.

Flask 애플리케이션을 여러 파일로 분리하겠다.

 

routes.py: 애플리케이션의 URL 경로에 해당하는 **라우트(route)**를 정의하는 파일입니다. 각 라우트는 웹 요청을 처리하고, 적절한 데이터를 반환한다.

models.py: 데이터베이스 연결 및 관리와 관련된 모든 기능을 처리하는 파일이다.

app.py: Flask 애플리케이션을 실행하고, 다른 파일에서 정의한 라우트와 모델을 연결한다.

 

Flask 프로젝트 디렉토리 구조

movie_project/

├── app.py                # 애플리케이션 설정 및 실행
├── routes.py             # 라우트 처리
├── models.py             # 데이터베이스 모델 처리
├── templates/            # HTML 템플릿 폴더
│   ├── login.html        # 로그인 폼

│   ├── dashboard.html  # 대시보드 (로그인 후)
│   └──  나중에 추가 생성...
└── movies.db             # SQLite 데이터베이스 파일

 

우선 로그인 기능부터 구현하겠다.

models.py (수정된 Flask 버전)

def get_db_connection():
        """SQLite 데이터베이스 연결 함수"""
        conn = sqlite3.connect('movies.db') #movies.db는 프로젝트 디렉터리에 저장된 파일
        conn.row_factory = sqlite3.Row #딕셔너리 형태로 결과를 반환하도록 설정
        return conn

def fetch_members_data():
    """회원 데이터 가져오기"""
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT id, password, member_id FROM Member")
    members_data_from_db = cursor.fetchall()
    conn.close()
    return members_data_from_db

def distinguish_user(input_id):
    """사용자 유형 구분하기"""
    if "user" in input_id:
        return "user"
    elif "staff" in input_id:
        return "staff"
    elif "analyst" in input_id:
        return "analyst"

models.py (구버전)

import psycopg2

if __name__ == '__main__':
    con = psycopg2.connect(
        database='movie2023',
        user='db2023',
        password='db!2023',
        host='::1',
        port='5432'
    )
    
def fetch_members_data():
    cursor = con.cursor()
    cursor.execute("SELECT id, password, member_id FROM Member")
    members_data_from_db = cursor.fetchall()
    return members_data_from_db
    
def contains_substring(main_string, substring):
    return substring in main_string

def distinguish_user(input_id3):
    if contains_substring(input_id3, "user"):
        return "user"
    elif contains_substring(input_id3, "staff"):
        return "staff"
    elif contains_substring(input_id3, "analyst"):
        return "analyst"

 

수정된 부분

  • psycopg2 대신 sqlite3를 사용하고, movies.db 파일을 활용 
  • fetch_members_data 함수에 conn.close() 추가
  • 불필요한 contains_substring 함수를 distinguish_user 함수로 통합

 

routes.py (수정된 Flask 버전)

  • 로그인 폼을 처리하고, 로그인 후 대시보드를 보여주는 부분
from flask import render_template, request, redirect, url_for, flash
from models import fetch_members_data, distinguish_user

def login_route(app):
    @app.route("/", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            input_id = request.form['user_id']
            input_password = request.form['password']

            members_data = fetch_members_data()

            for member in members_data:
                if input_id == member[0] and input_password == member[1]:
                    user_type = distinguish_user(input_id)
                    if user_type:
                        print(f"user_type: {user_type}, member_id: {member[2]}")
                        return redirect(url_for('dashboard', user_type=user_type, member_id=member[2]))
                    else:
                        flash("User type could not be determined.")
                        return redirect(url_for('login'))

            flash("Login failed. ID and password do not match.")
            return redirect(url_for('login'))
            
        return render_template("login.html")

    @app.route("/dashboard/<user_type>/<member_id>")
    def dashboard(user_type, member_id):
        return render_template("dashboard.html", user_type=user_type, member_id=member_id)

routes.py (구버전)

def login():
    user_type, member_id, input_id1, input_password1 = login_attempt()

    if not user_type:
        while not user_type:
            user_type, member_id, input_id1, input_password1 = login_attempt()
    return user_type, member_id, input_id1, input_password1

 

  • GET 요청은 사용자가 웹 페이지에 접속할 때 자동으로 발생한다.
  • 로그인 페이지를 처음 요청하는 경우 GET 요청이 발생하고, return render_template("login.html")가 실행되어 로그인 페이지가 사용자에게 렌더링된다.
  • @app.route("/", methods=["GET", "POST"]): / 경로에서 GET과 POST 요청을 처리하는 라우트
  • GET: 로그인 페이지를 렌더링할 때 사용된다.
  • POST: 사용자가 로그인 폼을 제출할 때 데이터를 처리한다.

app.py (수정된 Flask 버전)

Flask 애플리케이션을 설정하고, 실행하는 코드

from flask import Flask
from routes import login_route

app = Flask(__name__)
app.secret_key = 'c7IVzYZekc'


login_route(app)

if __name__ == "__main__":

    app.run(debug=True, host='0.0.0.0')

 

app.py (구버전)

userType, memberId, input_id, input_password = login()

 

 dashboard.html

  GNU nano 7.2                                         dashboard.html                                                   <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dashboard</title>
</head>
<body>
    <h2>Welcome, {{ user_type }} {{ member_id }}!</h2>
    <p>Choose your function:</p>
    {% if user_type == 'user' %}
        <p>User Function</p>
    {% elif user_type == 'staff' %}
        <p>Staff Function</p>
    {% elif user_type == 'analyst' %}
        <p>Analyst Function</p>
    {% endif %}
</body>
</html>

 

login.html

 

  GNU nano 7.2                                           login.html                                                     <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    <form method="POST" action="/">
        <label for="user_id">ID: </label>
        <input type="text" name="user_id" required><br><br>
        <label for="password">Password: </label>
        <input type="password" name="password" required><br><br>
        <button type="submit">Login</button>
    </form>
</body>
</html>

 

이제 웹 애플리케이션을 http://localhost:5000/에서 실행시킨다.

1. 그런데 원하는 화면이 나오지 않았다.

2. 오류의 원인

Flask 앱이 실행되어야 한다.
app.py 파일에서 Flask 애플리케이션을 실행하고 있어야 하며, EC2 서버에서 Flask 서버가 실행 중이어야 한다. 예를 들어, EC2 서버에서 python3 app.py 명령어로 Flask 애플리케이션을 실행한 상태여야 한다. 그래서 가상환경을 이용했다.

source venv/bin/activate

2. Flask 실행

3. SyntaxError 해결 

  • 코드 뒤에 있는 주석은 """ """에서 #으로 바꿨다.
  •  login_route(app) 으로 수정

3. SyntaxError 해결
3. SyntaxError 해결

4. app.py 실행

4. Flask 실행

 

5. http://<EC2-public-IP>:5000 주소로 외부에서 접속 시도

접속 실패 -> 보안 그룹에 5000번 포트 추가하기 -> 접속 성공

EC2 인스턴스에 연결된 보안 그룹을 확인한 후, 해당 보안 그룹에 5000번 포트를 추가해야한다. (처음에 다른 보안 그룹에 추가해서 실패함)

 

5. 5000 포트 추가
5. 로그인 창

 

6. 로그인 창에 잘못된 정보를 넣으면 다시 처음의 로그인 창이 뜬다.

정확한 정보를 넣고 로그인 버튼을 누르면 다음 화면으로 넘어가야하는데 에러창이 뜬다.

6. 에러

7. 에러분석

url_for('dashboard', user_type=user_type, member_id=member[2]) 함수 호출에서 발생한 문제이고 member_id 값이 제대로 전달되지 않았다. (print 문을 추가해서 확인)

7. 에러 분석
7. 에러 분석

 

8. 해결

member_id를 자동 증가 값인 SERIAL로 설정해서 PostgreSQL에서는 자동으로 값을 생성하지만, 다른 데이터베이스인SQLite에서는 추가 설정이 필요하다.

SERIAL 필드는 PostgreSQL에서만 지원되며, SQLite에서는 AUTOINCREMENT를 사용해야 한다.

 

기존 영화 관리 시스템을 만들 때 여러 테이블에 SERIAL이 포함 되어 있으므로 SQL 파일에서 이를 모두 AUTOINCREMENT 로 바꾼 후 다시 movies.db 를 만들겠다. 

 

예시 ) member_id SERIAL PRIMARY KEY  에서 member_id INTEGER PRIMARY KEY AUTOINCREMENT 로 변경

          seat_id SERIAL 에서 seat_id INTEGER AUTOINCREMENT로 변경

 

수정된 내용은 SQL_code(SQLite3).sql 파일에 저장했다.

(지금부터의 내용 설명은 프로그램 파일과 데이터베이스 업로드, 설정 글을 참고하면된다.)

 

8-1. SQL 파일 업로드

8-1
8-1

9.  SQLite3 명령어로 데이터베이스 생성 중 오류

SQLite에서는 기본적으로 INTEGER PRIMARY KEY AUTOINCREMENT를 사용하여 자동 증가하는 기본 키를 설정한다. 그리고  AUTOINCREMENT는 **PRIMARY KEY**와 함께 사용해야 한다.

그런데 Seat 테이블의 seat_id는 primary key가 아니였어서 INTEGER AUTOINCREMENT로 변경했다. 이 부분을  

seat_id INTEGER PRIMARY KEY AUTOINCREMENT 이렇게 바꾸겠다.

그리고 Seat 테이블에 PRIMARY KEY (seat_id, theater_id) 부분을 삭제했다. ( seat_id, theater_id 모두 primary key이므로 로)

9 오류

10. movies.db 다시 생성, member_id가 잘 생성된 것 확인

10
10

11. 이제 로그인하면 Dashboard 화면으로 넘어간다!