고객의 첫 번째 기능 구현
1. 정중앙에 로그인 창이 뜨도록 수정
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
.login-container {
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 300px;
}
h2 {
text-align: center;
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="login-container">
<h2>Login</h2>
<form method="POST" action="/">
<label for="user_id">ID: </label>
<input type="text" name="user_id" required><br>
<label for="password">Password: </label>
<input type="password" name="password" required><br>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>
2. 로그인에 실패하면 실패를 알리는 창이 뜨도록 수정 -> 확인을 누르면 다시 로그인을 시도할 수 있다.
2-1.
잘못된 로그인 정보가 입력되면 flash를 사용하여 로그인 실패 메시지를 표시한다.
로그인 실패 시 flash를 호출하고, 이는 login.html 템플릿에서 get_flashed_messages로 가져와서 JavaScript에서 팝업을 띄운다.
login.html에 추가한 코드
<script>
window.onload = function() {
// Flask에서 전달받은 flash 메시지를 JavaScript 변수로 전달
var messages = {{ get_flashed_messages() | tojson }};
if (messages.length > 0) {
console.log("Flash messages: ", messages);
alert(messages[0]); // 첫 번째 메시지를 팝업으로 표시
} else {
console.log("No flash messages.");
}
}
</script>
3. 영화 관리 시스템은 사용자(고객, 스태프, 분석가) 별로 다른 기능을 제공한다.
따라서 고객이 로그인 하면 고객의 기능들을 보여주는 html 파일로 넘어간다.
이렇게 사용자별로 html 페이지를 다르게 만들었다. (전에 사용했던 dashboard.html은 삭제)
여러 기능 중 원하는 기능을 클릭하면 해당 기능을 수행하는 페이지로 이동할 수 있도록 링크를 걸어놨다.
처음 만들었던 영화 관리 시스템은 터미널 기반이라 영화 포스터를 볼 수 없었다.
지금은 영화를 조회할 때 포스터까지 볼 수 있게 하겠다.
SQLite 데이터베이스에서 URL을 저장하려면 TEXT 자료형을 사용하면 된다.
이제 고객의 첫 번째 기능인 모든 상영 예정인 영화 조회하기를 구현하는 코드를 적고 이 기능을 구현하면서 생긴 오류와 해결과정을 적겠다. 첫 기능이니 모든 코드를 첨부하고 다음 기능부터는 추가되는 코드 부분만 첨부하겠다.
고객 - 모든 상영 예정인 영화 조회
app.py
추가된 코드 없다.
GNU nano 7.2 app.py
from flask import Flask
from routes import login_route
app = Flask(__name__)
app.secret_key = 'c7IVzYZekc'
login_route(app)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)
routes.py
view_movies 함수는 고객의 첫번째 기능을 선택하면 실행된다.
view_movies 함수에서 models.py에 작성한 print_all_movie 함수를 호출해 모든 영화의 정보를 가져온다.
가져온 정보를 movies.html로 전달해서 출력한다.
GNU nano 7.2 routes.py
from flask import render_template, request, redirect, url_for, flash
from models import fetch_members_data, distinguish_user, print_all_movie
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:
return redirect(url_for('dashboard', user_type=user_type, name=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>/<name>")
def dashboard(user_type, name):
if user_type == "user":
return render_template("user_function.html", user_type=user_type, name=name)
elif user_type == "staff":
return render_template("staff_function.html", user_type=user_type, name=name)
elif user_type == "analyst":
return render_template("analyst_function.html", user_type=user_type, name=name)
@app.route('/movies')
def view_movies():
movies = print_all_movie()
return render_template('movies.html', movies=movies)
models.py
print_all_movie 함수를 추가했고 이 함수는 상영 예정인 모든 영화의 특정 정보를 반환한다.
import sqlite3
def get_db_connection():
"""SQLite 데이터베이스 연결 함수"""
conn = sqlite3.connect('movies.db') #movies.db는 프로젝트 디렉터리에 저장된 파일
#conn.row_factory = sqlite3.Row #딕셔너리 형태로 결과를 반환하도록 설정
return conn
def print_all_movie():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("""SELECT DISTINCT m.movie_id, m.movie_title, s.theater_id, s.screening_date, m.poster_url
FROM movie m JOIN screeningschedule s ON m.movie_id = s.movie_id
WHERE s.screening_date >= CURRENT_DATE""")
result = cursor.fetchall()
conn.close()
return result
def fetch_members_data():
"""회원 데이터 가져오기"""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT id, password, name FROM Member")
members_data_from_db = cursor.fetchall()
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"
user_function.html
사용자 기능 중 첫 번째인 모든 상영 예정인 영화 조회를 클릭하면 /movies 링크로 넘어간다.
이 링크는 routes.py에 구현했듯이 view_movies 함수를 살행한다.
GNU nano 7.2 user_function.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Function</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f9;
color: #333;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: white;
padding: 20px 30px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
width: 100%;
}
h2 {
margin-bottom: 20px;
color: #4CAF50;
}
p {
margin: 10px 0;
font-size: 16px;
}
.link-list {
list-style: none;
padding: 0;
}
.link-list li {
margin: 10px 0;
}
.link-list a {
text-decoration: none;
color: #4CAF50;
font-weight: bold;
transition: color 0.2s ease;
}
.link-list a:hover {
color: #388E3C;
}
</style>
</head>
<body>
<div class="container">
<h2>환영합니다, {{ name }}님!</h2>
<p>고객님 원하시는 기능을 선택해주세요.</p>
<ul class="link-list">
<li><a href="/movies">모든 상영 예정인 영화 조회</a></li>
<li><a href="/movies/search">영화 이름으로 상영 예정인 영화 조회</a></li>
<li><a href="/movies/title-length">영화 제목 글자 수로 상영 예정인 영화 조회</a></li>
<li><a href="/movies/sort/title">영화 제목 기준으로 상영 예정인 영화를 오름차순 정렬</a></li>
<li><a href="/movies/sort/date">영화 상영 날짜 기준으로 상영 예정인 영화를 내림차순 정렬</a></li>
<li><a href="/movies/book">영화 예매</a></li>
<li><a href="/movies/cancel">영화 취소</a></li>
<li><a href="/logout">프로그램 종료</a></li>
</ul>
</div>
</body>
</html>
movies.html -> print_all_movies.html
영화 정보를 받아서 출력했다. (링크를 통해 포스터 출력도 했다.)
사용자 별로 기능이 많아 사용자별로 폴더를 따로 만들고 html 파일들을 보관했다.
따라서 movies.html 은 /user/print_all_movies.html 으로 위치와 이름을 변경하였다. (다음 블로그 글에 소개)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>All Movies</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f9f9f9;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
color: #333;
}
.movie-list {
max-width: 800px;
margin: 0 auto;
}
.movie-item {
display: flex;
align-items: center;
background: #fff;
margin: 15px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.movie-item img {
width: 100px;
height: 150px;
margin-right: 20px;
object-fit: cover;
border-radius: 4px;
border: 1px solid #ccc;
}
.movie-details {
margin: 0;
padding: 0;
list-style: none;
}
.movie-details li {
margin: 5px 0;
color: #666;
}
h2 {
margin: 0 0 10px;
color: #444;
}
</style>
</head>
<body>
<h1>All Movies</h1>
<div class="movie-list">
{% for movie in movies %}
<div class="movie-item">
<!-- Movie poster -->
<img src="{{ movie[4] }}" alt="Poster for {{ movie[1] }}">
<div>
<h2>{{ movie[1] }}</h2> <!-- Movie title -->
<ul class="movie-details">
<li><strong>Movie ID:</strong> {{ movie[0] }}</li>
<li><strong>Theater ID:</strong> {{ movie[2] }}</li>
<li><strong>Screening Date:</strong> {{ movie[3] }}</li>
</ul>
</div>
</div>
{% endfor %}
</div>
</body>
</html>
3-1 오류 : 고객 기능 중에 1~5번은 상영 예정인 영화를 조회하고 정렬하는 것이다.
상영 예정인 영화가 대상이기 때문에 screening_date >= current_date 조건을 만족해야한다.
그런데 SQL문을 2023년도에 만들어 놓아서 screening_date 는 2023년도였고 current_date는 2024년도 였다.
따라서 조건을 만족하지 못해 결과가 안나왔다.
해결방법 : SQL문에서 screeing_date를 2025년으로 바꾸고 다시 업로드했다.
3-2. 오류 : 모든 상영 예정인 영화가 출력 되어야 하는데 sqlite3.Row 객체가 출력됐다.
해결방법 : conn.row_factory를 설정하지 않으면 fetchall()은 튜플로 이루어진 리스트를 반환한다. 그러면 템플릿에서 각 튜플의 값을 인덱스로 접근하면된다.
따라서 3-2-1 사진에 있는 빨간색 네모 안에 있는 코드를 지우니 해결되었다.