[miniProject] 심리테스트 웹페이지 프로젝트를 분석해보자!
프로젝트 개요
심리테스트를 진행 할수 있는 파이썬 기반의 웹 어플리케이션을 구동하는 코드로 질문을 추가,수정 및 업데이트 할 수 있으며 테스트 참여자의 결과와 일일 참여자 수를 볼수 있다.
Dependency
- poetry : package 관리를 위한 라이브러리
- flask : 웹 페이지 제작을 위한 웹 프레임 워크
- flask-sqlalchemy : ORM방식으로 데이터 베이스 조작을 위한 라이브러리
- flask-mysqldb : 데이터 베이스와 flask 연결을 위한 라이브러리
- flask-migrate : 데이터 베이스 관리를 돕는 라이브러리
- pandas : 참여자의 결과를 분석하기 위해 사용되는 라이브러리
- plotly : 참여자의 결과를 시각화 하기위한 라이브러리
프로젝트 구조
database.py
from flask_sqlalchemy import SQLAlchemy
# SQLAlchemy 연결
db = SQLAlchemy()
SQLAlchemy 인스턴스를 생성
models.py
from .database import db
from datetime import datetime
#DB 컬럼 정의
## 참가자(유저)에 대한 정보 테이블
class Participant(db.Model):
__tablename__ = "participant"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
age = db.Column(db.Integer)
gender = db.Column(db.String(10))
created_at = db.Column(db.DateTime, default=datetime.now())
## 관리자에 대한 정보 테이블
class Admin(db.Model):
__tablename__ = "admin"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50))
password = db.Column(db.String(50))
## 질문에 대한 정보 테이블
class Question(db.Model):
__tablename__ = "question"
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(255))
order_num = db.Column(db.Integer, default=0)
is_active = db.Column(db.Boolean, default=True)
## 답변에 대한 정보 테이블
class Quiz(db.Model):
__tablename__ = "quiz"
id = db.Column(db.Integer, primary_key=True)
participant_id = db.Column(db.Integer, db.ForeignKey("participant.id"))
question_id = db.Column(db.Integer, db.ForeignKey("question.id"))
chosen_answer = db.Column(db.String(255))
participant = db.relationship("Participant", backref="quizzes")
question = db.relationship("Question", backref="quizzes")
Database에 SQLAlchemy를 통해 데이터를 저장하기 위해 ORM 모델 정보를 선언
routes.py
웹페이지의 route를 설정하고 관리하는 코드이며 main(User Page), admin(Administrator)페이지로 나뉜다.
전체 코드
from flask import (
jsonify,
render_template,
request,
Blueprint,
redirect,
url_for,
flash,
session,
)
from werkzeug.security import check_password_hash
from .models import Question, Participant, Quiz, Admin
from .database import db
import plotly.express as px
import pandas as pd
import plotly
import json
from sqlalchemy import func, extract
import plotly.graph_objs as go
from plotly.offline import plot
from datetime import datetime
# 'main'이라는 이름의 Blueprint 객체 생성
main = Blueprint("main", __name__)
admin = Blueprint("admin", __name__, url_prefix="/admin/")
@main.route("/", methods=["GET"])
def home():
# 참여자 정보 입력 페이지를 렌더링합니다.
return render_template("index.html")
#url/participants 가 post methods로 들어오면 참여자 추가
@main.route("/participants", methods=["POST"])
def add_participant():
data = request.get_json()
new_participant = Participant(
name=data["name"], age=data["age"], gender=data["gender"] , created_at=datetime.now()
)
db.session.add(new_participant)
db.session.commit()
# 리다이렉션 URL과 참여자 ID를 JSON 응답으로 전송
return jsonify(
{"redirect": url_for("main.quiz"), "participant_id": new_participant.id}
)
@main.route("/quiz")
def quiz():
# 퀴즈 페이지를 렌더링합니다. 참여자 ID 쿠키가 필요합니다.
participant_id = request.cookies.get("participant_id")
if not participant_id:
# 참여자 ID가 없으면, 홈페이지로 리다이렉션합니다.
return redirect(url_for("main.home"))
questions = Question.query.all()
questions_list = [question.content for question in questions]
return render_template("quiz.html", questions=questions_list)
#url/submit 가 post methods로 들어오면 선택한 결과값을 데이터 베이스에 추가
@main.route("/submit", methods=["POST"])
def submit():
# 참여자 ID가 필요합니다.
participant_id = request.cookies.get("participant_id")
if not participant_id:
return jsonify({"error": "Participant ID not found"}), 400
data = request.json
quizzes = data.get("quizzes", [])
for quiz in quizzes:
question_id = quiz.get("question_id")
chosen_answer = quiz.get("chosen_answer")
# 새 Quiz 인스턴스 생성
new_quiz_entry = Quiz(
participant_id=participant_id,
question_id=question_id,
chosen_answer=chosen_answer,
)
# 데이터베이스에 추가
db.session.add(new_quiz_entry)
# 변경 사항 커밋
db.session.commit()
return jsonify(
{
"message": "Quiz answers submitted successfully.",
"redirect": url_for("main.show_results"),
}
)
# 질문 종류중 activate인것들만 불러오기
@main.route("/questions")
def get_questions():
# is_active가 True인 질문만 선택하고, order_num에 따라 정렬
questions = (
Question.query.filter(Question.is_active == True)
.order_by(Question.order_num)
.all()
)
questions_list = [
{
"id": question.id,
"content": question.content,
"order_num": question.order_num,
}
for question in questions
]
return jsonify(questions=questions_list)
#DB 내용 시각화
@main.route("/results")
def show_results():
# 데이터베이스에서 데이터 조회
participants_query = Participant.query.all()
quizzes_query = Quiz.query.join(Question).all()
# pandas DataFrame으로 변환
participants_data = [
{"age": participant.age, "gender": participant.gender}
for participant in participants_query
]
quizzes_data = [
{
"question_id": quiz.question_id,
"chosen_answer": quiz.chosen_answer,
"participant_age": quiz.participant.age,
}
for quiz in quizzes_query
]
participants_df = pd.DataFrame(participants_data)
quizzes_df = pd.DataFrame(quizzes_data)
# Plotly 시각화 생성
# 예시 1: 나이별 분포 (도넛 차트)
fig_age = px.pie(
participants_df,
names="age",
hole=0.3,
title="Age Distribution",
color_discrete_sequence=px.colors.sequential.RdBu,
labels={"age": "Age Group"},
)
fig_age.update_traces(textposition="inside", textinfo="percent+label")
fig_gender = px.pie(
participants_df,
names="gender",
hole=0.3,
title="Gender Distribution",
color_discrete_sequence=px.colors.sequential.Purp,
labels={"gender": "Gender"},
)
fig_gender.update_traces(textposition="inside", textinfo="percent+label")
quiz_response_figs = {}
# 각 질문 ID별로 반복하여 그래프 생성
for question_id in quizzes_df["question_id"].unique():
filtered_df = quizzes_df[quizzes_df["question_id"] == question_id]
fig = px.histogram(
filtered_df,
x="chosen_answer",
title=f"Question {question_id} Responses",
color="chosen_answer",
barmode="group",
category_orders={"chosen_answer": ["yes", "no"]}, # 카테고리 순서 지정
color_discrete_map={"yes": "RebeccaPurple", "no": "LightSeaGreen"},
) # 컬러 매핑
fig.update_layout(
xaxis_title="Chosen Answer",
yaxis_title="Count",
plot_bgcolor="rgba(0,0,0,0)", # 배경색 투명
paper_bgcolor="rgba(0,0,0,0)", # 전체 배경색 투명
font=dict(family="Courier New, monospace", size=18, color="#7f7f7f"),
title_font=dict(
family="Helvetica, Arial, sans-serif", size=22, color="RebeccaPurple"
),
)
fig.update_traces(marker_line_width=1.5, opacity=0.6) # 투명도와 테두리 두께 조정
# 생성된 그래프를 딕셔너리에 저장
quiz_response_figs[f"question_{question_id}"] = fig
age_quiz_response_figs = {}
# 나이대를 구분하는 함수
def age_group(age):
if age == 'teenage':
return "10s"
elif age == 'twenty':
return "20s"
elif age == 'thirty':
return "30s"
elif age == 'forty':
return "40s"
elif age == 'fifties':
return "50s"
else:
return "60s+"
# 나이대 그룹 열 추가
quizzes_df["age_group"] = quizzes_df["participant_age"].apply(age_group)
# 각 질문 ID와 나이대별로 대답 분포를 시각화
for question_id in quizzes_df["question_id"].unique():
filtered_df = quizzes_df[quizzes_df["question_id"] == question_id]
fig = px.histogram(
filtered_df,
x="age_group",
color="chosen_answer",
barmode="group",
title=f"Question {question_id} Responses by Age Group",
labels={"age_group": "Age Group", "chosen_answer": "Chosen Answer"},
category_orders={"age_group": ["10s", "20s", "30s", "40s", "50s+"]},
)
# 스타일 조정
fig.update_layout(
xaxis_title="Age Group",
yaxis_title="Count",
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
font=dict(family="Courier New, monospace", size=18, color="#7f7f7f"),
title_font=dict(
family="Helvetica, Arial, sans-serif", size=22, color="RebeccaPurple"
),
)
fig.update_traces(marker_line_width=1.5, opacity=0.6)
age_quiz_response_figs[f"question_{question_id}"] = fig
# 딕셔너리에 저장된 그래프들을 JSON으로 변환
graphs_json = json.dumps(
{
"age_distribution": fig_age,
"gender_distribution": fig_gender,
"quiz_responses": quiz_response_figs,
"age_quiz_response_figs": age_quiz_response_figs,
},
cls=plotly.utils.PlotlyJSONEncoder,
)
# 데이터를 results.html에 전달
return render_template("results.html", graphs_json=graphs_json)
#관리자 페이지를 보여주고, 로그인 하는 부분
@admin.route("", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
admin = Admin.query.filter_by(username=username).first()
if admin and check_password_hash(admin.password, password):
session["admin_logged_in"] = True
return redirect(url_for("admin.dashboard"))
else:
flash("Invalid username or password")
return render_template("admin.html")
#로그아웃시 연결된는 부분
@admin.route("/logout")
def logout():
session.pop("admin_logged_in", None)
return redirect(url_for("admin.login"))
from functools import wraps
from flask import redirect, url_for, session
#로그인이 되었는지 확인하는 부분, 안되어 있을시 로그인 페이지로 이동하게 함
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if "admin_logged_in" not in session:
return redirect(url_for("admin.login", next=request.url))
return f(*args, **kwargs)
return decorated_function
# 대시보드에 일자별 참가자 수를 통계하는 부분
@admin.route("dashboard")
@login_required
def dashboard():
# 날짜별 참가자 수를 계산
participant_counts = (
db.session.query(
func.date(Participant.created_at).label("date"),
func.count(Participant.id).label("count"),
)
.group_by("date")
.all()
)
# 날짜와 참가자 수를 분리하여 리스트에 저장
dates = [result.date for result in participant_counts]
counts = [result.count for result in participant_counts]
# Plotly 그래프 생성
graph = go.Figure(go.Scatter(x=dates, y=counts, mode="lines+markers"))
graph.update_layout(title="일자별 참가자 수", xaxis_title="날짜", yaxis_title="참가자 수")
# Plotly 그래프를 HTML로 변환
graph_div = plot(graph, output_type="div", include_plotlyjs=False,config = {'displayModeBar': False})
# 생성된 HTML을 템플릿으로 전달
return render_template("dashboard.html", graph_div=graph_div)
#질문관리 페이지, 질문을 추가, 수정할수 있도록 하는 부분
@admin.route("/dashboard/question", methods=["GET", "POST"])
@login_required
def manage_questions():
if request.method == "POST":
if "new_question" in request.form:
# 새 질문 추가
is_active = (
"is_active" in request.form and request.form["is_active"] == "on"
)
new_question = Question(
content=request.form["content"],
order_num=request.form["order_num"],
is_active=is_active,
)
db.session.add(new_question)
db.session.commit()
else:
# 기존 질문 수정
question_id = request.form["question_id"]
question = Question.query.get(question_id)
if question:
is_active = (
"is_active" in request.form and request.form["is_active"] == "on"
)
question.content = request.form["content"]
question.order_num = request.form["order_num"]
question.is_active = is_active
db.session.commit()
questions = Question.query.order_by(Question.order_num).all()
return render_template("manage_questions.html", questions=questions)
#참여자들의 결과 확인할수 있게 하는 부분
@admin.route("/dashboard/list")
@login_required
def quiz_list():
quizzes = Quiz.query.all()
return render_template("quiz_list.html", quizzes=quizzes)
route 별 상세 내용
접속시 보여지는 페이지로 참여자의 정보를 입력하는 페이지를 렌더링
@main.route("/", methods=["GET"])
def home():
# 참여자 정보 입력 페이지를 렌더링합니다.
return render_template("index.html")
/participants로 post요청을 보내면 진행되는 함수로 참여자의 정보를 데이터 베이스에 저장
#url/participants 가 post methods로 들어오면 참여자 추가
@main.route("/participants", methods=["POST"])
def add_participant():
data = request.get_json()
new_participant = Participant(
name=data["name"], age=data["age"], gender=data["gender"] , created_at=datetime.now()
)
db.session.add(new_participant)
db.session.commit()
# 리다이렉션 URL과 참여자 ID를 JSON 응답으로 전송
return jsonify(
{"redirect": url_for("main.quiz"), "participant_id": new_participant.id}
)
/quiz로 접속하면 보이지는 페이지로 각 퀴즈에 대한 질문이 보여지는 페이지
@main.route("/quiz")
def quiz():
# 퀴즈 페이지를 렌더링합니다. 참여자 ID 쿠키가 필요합니다.
participant_id = request.cookies.get("participant_id")
if not participant_id:
# 참여자 ID가 없으면, 홈페이지로 리다이렉션합니다.
return redirect(url_for("main.home"))
questions = Question.query.all()
questions_list = [question.content for question in questions]
return render_template("quiz.html", questions=questions_list)
/submit를 post 메소드로 실행하게 되면 사용자가 준 답변을 데이터 베이스에 저장
#url/submit 가 post methods로 들어오면 선택한 결과값을 데이터 베이스에 추가
@main.route("/submit", methods=["POST"])
def submit():
# 참여자 ID가 필요합니다.
participant_id = request.cookies.get("participant_id")
if not participant_id:
return jsonify({"error": "Participant ID not found"}), 400
data = request.json
quizzes = data.get("quizzes", [])
for quiz in quizzes:
question_id = quiz.get("question_id")
chosen_answer = quiz.get("chosen_answer")
# 새 Quiz 인스턴스 생성
new_quiz_entry = Quiz(
participant_id=participant_id,
question_id=question_id,
chosen_answer=chosen_answer,
)
# 데이터베이스에 추가
db.session.add(new_quiz_entry)
# 변경 사항 커밋
db.session.commit()
return jsonify(
{
"message": "Quiz answers submitted successfully.",
"redirect": url_for("main.show_results"),
}
)
/questions로 접속했을때 질문 리스트를 가저 올수 있게 해주는 부분
# 질문 종류중 activate인것들만 불러오기
@main.route("/questions")
def get_questions():
# is_active가 True인 질문만 선택하고, order_num에 따라 정렬
questions = (
Question.query.filter(Question.is_active == True)
.order_by(Question.order_num)
.all()
)
questions_list = [
{
"id": question.id,
"content": question.content,
"order_num": question.order_num,
}
for question in questions
]
return jsonify(questions=questions_list)
/results로 접속시 데이터베이스의 자료를 통해서 결과를 시각화 해줌
#DB 내용 시각화
@main.route("/results")
def show_results():
# 데이터베이스에서 데이터 조회
participants_query = Participant.query.all()
quizzes_query = Quiz.query.join(Question).all()
# pandas DataFrame으로 변환
participants_data = [
{"age": participant.age, "gender": participant.gender}
for participant in participants_query
]
quizzes_data = [
{
"question_id": quiz.question_id,
"chosen_answer": quiz.chosen_answer,
"participant_age": quiz.participant.age,
}
for quiz in quizzes_query
]
participants_df = pd.DataFrame(participants_data)
quizzes_df = pd.DataFrame(quizzes_data)
# Plotly 시각화 생성
# 예시 1: 나이별 분포 (도넛 차트)
fig_age = px.pie(
participants_df,
names="age",
hole=0.3,
title="Age Distribution",
color_discrete_sequence=px.colors.sequential.RdBu,
labels={"age": "Age Group"},
)
fig_age.update_traces(textposition="inside", textinfo="percent+label")
fig_gender = px.pie(
participants_df,
names="gender",
hole=0.3,
title="Gender Distribution",
color_discrete_sequence=px.colors.sequential.Purp,
labels={"gender": "Gender"},
)
fig_gender.update_traces(textposition="inside", textinfo="percent+label")
quiz_response_figs = {}
# 각 질문 ID별로 반복하여 그래프 생성
for question_id in quizzes_df["question_id"].unique():
filtered_df = quizzes_df[quizzes_df["question_id"] == question_id]
fig = px.histogram(
filtered_df,
x="chosen_answer",
title=f"Question {question_id} Responses",
color="chosen_answer",
barmode="group",
category_orders={"chosen_answer": ["yes", "no"]}, # 카테고리 순서 지정
color_discrete_map={"yes": "RebeccaPurple", "no": "LightSeaGreen"},
) # 컬러 매핑
fig.update_layout(
xaxis_title="Chosen Answer",
yaxis_title="Count",
plot_bgcolor="rgba(0,0,0,0)", # 배경색 투명
paper_bgcolor="rgba(0,0,0,0)", # 전체 배경색 투명
font=dict(family="Courier New, monospace", size=18, color="#7f7f7f"),
title_font=dict(
family="Helvetica, Arial, sans-serif", size=22, color="RebeccaPurple"
),
)
fig.update_traces(marker_line_width=1.5, opacity=0.6) # 투명도와 테두리 두께 조정
# 생성된 그래프를 딕셔너리에 저장
quiz_response_figs[f"question_{question_id}"] = fig
age_quiz_response_figs = {}
# 나이대를 구분하는 함수
def age_group(age):
if age == 'teenage':
return "10s"
elif age == 'twenty':
return "20s"
elif age == 'thirty':
return "30s"
elif age == 'forty':
return "40s"
elif age == 'fifties':
return "50s"
else:
return "60s+"
# 나이대 그룹 열 추가
quizzes_df["age_group"] = quizzes_df["participant_age"].apply(age_group)
# 각 질문 ID와 나이대별로 대답 분포를 시각화
for question_id in quizzes_df["question_id"].unique():
filtered_df = quizzes_df[quizzes_df["question_id"] == question_id]
fig = px.histogram(
filtered_df,
x="age_group",
color="chosen_answer",
barmode="group",
title=f"Question {question_id} Responses by Age Group",
labels={"age_group": "Age Group", "chosen_answer": "Chosen Answer"},
category_orders={"age_group": ["10s", "20s", "30s", "40s", "50s+"]},
)
# 스타일 조정
fig.update_layout(
xaxis_title="Age Group",
yaxis_title="Count",
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
font=dict(family="Courier New, monospace", size=18, color="#7f7f7f"),
title_font=dict(
family="Helvetica, Arial, sans-serif", size=22, color="RebeccaPurple"
),
)
fig.update_traces(marker_line_width=1.5, opacity=0.6)
age_quiz_response_figs[f"question_{question_id}"] = fig
# 딕셔너리에 저장된 그래프들을 JSON으로 변환
graphs_json = json.dumps(
{
"age_distribution": fig_age,
"gender_distribution": fig_gender,
"quiz_responses": quiz_response_figs,
"age_quiz_response_figs": age_quiz_response_figs,
},
cls=plotly.utils.PlotlyJSONEncoder,
)
# 데이터를 results.html에 전달
return render_template("results.html", graphs_json=graphs_json)
관리자 페이지 접속시 로그인 화면을 보여주고 입력후 로그인 하면 대시보드 화면을 보여줌
#관리자 페이지를 보여주고, 로그인 하는 부분
@admin.route("", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
admin = Admin.query.filter_by(username=username).first()
if admin and check_password_hash(admin.password, password):
session["admin_logged_in"] = True
return redirect(url_for("admin.dashboard"))
else:
flash("Invalid username or password")
return render_template("admin.html")
/logout이 들어오면 관리자 로그인 페이지로 이동
#로그아웃시 연결된는 부분
@admin.route("/logout")
def logout():
session.pop("admin_logged_in", None)
return redirect(url_for("admin.login"))
로기인이 되어있는지 확인하는 함수
from functools import wraps
from flask import redirect, url_for, session
#로그인이 되었는지 확인하는 부분, 안되어 있을시 로그인 페이지로 이동하게 함
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if "admin_logged_in" not in session:
return redirect(url_for("admin.login", next=request.url))
return f(*args, **kwargs)
return decorated_function
대시보드 진입시 일자별 참가자 수를 통계해서 보여주는 부분
# 대시보드에 일자별 참가자 수를 통계하는 부분
@admin.route("dashboard")
@login_required
def dashboard():
# 날짜별 참가자 수를 계산
participant_counts = (
db.session.query(
func.date(Participant.created_at).label("date"),
func.count(Participant.id).label("count"),
)
.group_by("date")
.all()
)
# 날짜와 참가자 수를 분리하여 리스트에 저장
dates = [result.date for result in participant_counts]
counts = [result.count for result in participant_counts]
# Plotly 그래프 생성
graph = go.Figure(go.Scatter(x=dates, y=counts, mode="lines+markers"))
graph.update_layout(title="일자별 참가자 수", xaxis_title="날짜", yaxis_title="참가자 수")
# Plotly 그래프를 HTML로 변환
graph_div = plot(graph, output_type="div", include_plotlyjs=False,config = {'displayModeBar': False})
# 생성된 HTML을 템플릿으로 전달
return render_template("dashboard.html", graph_div=graph_div)
질문에 대해서 추가, 변경, 삭제가 가능하도록 하는 함수
#질문관리 페이지, 질문을 추가, 수정할수 있도록 하는 부분
@admin.route("/dashboard/question", methods=["GET", "POST"])
@login_required
def manage_questions():
if request.method == "POST":
if "new_question" in request.form:
# 새 질문 추가
is_active = (
"is_active" in request.form and request.form["is_active"] == "on"
)
new_question = Question(
content=request.form["content"],
order_num=request.form["order_num"],
is_active=is_active,
)
db.session.add(new_question)
db.session.commit()
else:
# 기존 질문 수정
question_id = request.form["question_id"]
question = Question.query.get(question_id)
if question:
is_active = (
"is_active" in request.form and request.form["is_active"] == "on"
)
question.content = request.form["content"]
question.order_num = request.form["order_num"]
question.is_active = is_active
db.session.commit()
questions = Question.query.order_by(Question.order_num).all()
return render_template("manage_questions.html", questions=questions)
참여자들의 결과 리스트를 가져오고 보여준다
#참여자들의 결과 확인할수 있게 하는 부분
@admin.route("/dashboard/list")
@login_required
def quiz_list():
quizzes = Quiz.query.all()
return render_template("quiz_list.html", quizzes=quizzes)
templetes
웹페이지에 보여줄 구조와 꾸밈(HTML + CSS + JS)를 저장한 폴더
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>
<!--참여자 정보 입력 폼-->
<h2>참여자 정보 입력</h2>
<form id="participantForm">
<div>
<label for="name">이름:</label>
<input type="text" id="name" name="name" required autocomplete="off"/>
</div>
<div>
<label for="age">나이:</label>
<!-- <input type="number" id="age" name="age" required /> -->
<select id="age" name="age" required>
<option value="teenage">10대</option>
<option value="twenty">20대</option>
<option value="thirty">30대</option>
<option value="forty">40대</option>
<option value="fifties">50대</option>
<option value="sixty">60대 이상</option>
</select>
</div>
<div>
<label for="gender">성별:</label>
<select id="gender" name="gender" required>
<option value="male">남성</option>
<option value="female">여성</option>
<option value="other">기타</option>
</select>
</div>
<button type="submit">퀴즈 시작하기</button>
</form>
<!--submit 클릭시 작동되는 부분-->
<script>
document.getElementById("participantForm").addEventListener("submit", function (event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = Object.fromEntries(formData.entries());
fetch("/participants", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((response) => {
if (!response.ok) throw new Error("Network response was not ok.");
return response.json(); // 서버 응답을 JSON으로 파싱
})
.then((data) => {
// 참여자 ID를 쿠키에 저장
document.cookie = "participant_id=" + data.participant_id + ";max-age=" + (60 * 60 * 24).toString() + ";path=/";
// 서버에서 받은 리다이렉션 URL로 페이지 이동
if (data.redirect) {
window.location.href = data.redirect;
}
})
.catch((error) => console.error("Error:", error));
});
</script>
</body>
</html>
manage_questions.html
질문 관리 페이지
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Manage Questions</title>
</head>
<body>
<!--뒤로가기 버튼 작동부-->
<a href="{{ url_for('admin.dashboard') }}">
<button class="dashboard-button">Back to Dashboard</button>
</a>
<h1>Manage Questions</h1>
<!--질문지 추가 부분-->
<h2>Add New Question</h2>
<form action="{{ url_for('admin.manage_questions') }}" method="post">
<input type="text" name="content" placeholder="Question content" required>
<input type="number" name="order_num" placeholder="Order number" required>
<label>
<input type="checkbox" name="is_active"> Active
</label>
<button type="submit" name="new_question">Add Question</button>
</form>
<!-- 존재하는 질문지를 표시하는 부분-->
<h2>Existing Questions</h2>
{% for question in questions %}
<form action="{{ url_for('admin.manage_questions') }}" method="post">
<input type="hidden" name="question_id" value="{{ question.id }}">
<input type="text" name="content" value="{{ question.content }}" required>
<input type="number" name="order_num" value="{{ question.order_num }}" required>
<label>
<input type="checkbox" name="is_active" {% if question.is_active %}checked{% endif %}> Active
</label>
<button type="submit">Update Question</button>
</form>
{% endfor %}
</body>
</html>
quiz.html
질문 페이지
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>심리테스트</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
}
#question {
margin-top: 50px;
font-size: 24px;
}
#buttons {
margin-top: 20px;
}
button {
font-size: 18px;
margin: 15px;
padding: 10px 20px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="question"></div>
<div id="buttons">
<button id="yes">예</button>
<button id="no">아니오</button>
</div>
<div id="resultPage" style="display: none;">
<button onclick="sendResult()">결과 제출</button>
</div>
<script>
async function fetchQuestions() {
const response = await fetch('/questions');
const data = await response.json();
return data.questions; // 수정됨: 질문 데이터 배열 반환
}
const questionElement = document.getElementById("question");
const buttonsElement = document.getElementById("buttons");
const resultPageElement = document.getElementById("resultPage");
let currentQuestionIndex = 0;
let userAnswers = [];
let questions_list;
async function initializeQuiz() {
questions_list = await fetchQuestions();
userAnswers = Array(questions_list.length).fill(null);
showQuestion();
}
//질문을 보여주는 함수
function showQuestion() {
if (currentQuestionIndex < questions_list.length) {
questionElement.innerText = questions_list[currentQuestionIndex].content;
buttonsElement.style.display = "block";
resultPageElement.style.display = "none";
} else {
questionElement.innerText = "심리테스트가 종료되었습니다.";
buttonsElement.style.display = "none";
resultPageElement.style.display = "block";
}
}
//yes 버튼
document.getElementById("yes").addEventListener("click", () => {
userAnswers[currentQuestionIndex] = "yes";
currentQuestionIndex++;
showQuestion();
});
//no 버튼
document.getElementById("no").addEventListener("click", () => {
userAnswers[currentQuestionIndex] = "no";
currentQuestionIndex++;
showQuestion();
});
// 결과를 전송하는 부분
async function sendResult() {
const quizzes = questions_list.map((question, index) => ({
question_id: question.id,
chosen_answer: userAnswers[index]
}));
try {
const response = await fetch("/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ quizzes })
});
if (!response.ok) throw new Error("서버 응답 실패");
console.log("결과 전송 완료");
// 성공적으로 제출 후, 결과 페이지로 이동하거나 다른 액션을 수행할 수 있습니다.
window.location.href = "/results"
} catch (error) {
console.error("오류 발생:", error);
}
}
initializeQuiz();
</script>
</body>
</html>
quiz_list.html
유저별 질문 결과 확인 페이지
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Quiz List</title>
</head>
<body>
<h1>Quiz List</h1>
<!--뒤로가기 버튼 작동부-->
<a href="{{ url_for('admin.dashboard') }}">
<button class="dashboard-button">Back to Dashboard</button>
</a>
<!-- 결과물을 출력해주는 부분-->
<table border="1">
<thead>
<tr>
<th>Participant Name</th>
<th>Question Content</th>
<th>Chosen Answer</th>
</tr>
</thead>
<tbody>
{% for quiz in quizzes %}
<tr>
<td>{{ quiz.participant.name }}</td>
<td>{{ quiz.question.content }}</td>
<td>{{ quiz.chosen_answer }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
results.html
결과 페이지(결과를 시각화 해서 보여주는 페이지)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Results Dashboard</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
}
h1 {
text-align: center;
color: #333;
}
.graph-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin: 20px auto;
padding: 20px;
max-width: 600px;
}
#quiz-responses-container, #age-quiz-responses-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
#quiz-responses-container div, #age-quiz-responses-container div {
flex-basis: calc(50% - 40px);
margin: 20px;
}
</style>
</head>
<body>
<h1>Results Dashboard</h1>
<div class="graph-container" id="age-distribution"></div>
<div class="graph-container" id="gender-distribution"></div>
<!-- 각 질문의 응답 분포 그래프를 위한 컨테이너 -->
<div id="quiz-responses-container"></div>
<!-- 각 질문에 대한 나이대별 응답 분포 그래프를 위한 컨테이너 -->
<div id="age-quiz-responses-container"></div>
<script>
var graphs_json = {{ graphs_json | safe }};
Plotly.newPlot('age-distribution', graphs_json.age_distribution.data, graphs_json.age_distribution.layout);
Plotly.newPlot('gender-distribution', graphs_json.gender_distribution.data, graphs_json.gender_distribution.layout);
// 각 질문의 응답 데이터에 대한 그래프 동적 생성 및 렌더링
Object.keys(graphs_json.quiz_responses).forEach(function(key) {
var quizResponse = graphs_json.quiz_responses[key];
var div = document.createElement('div');
div.id = key;
div.className = 'graph-container';
document.getElementById('quiz-responses-container').appendChild(div);
Plotly.newPlot(key, quizResponse.data, quizResponse.layout);
});
// 나이대별 응답 데이터에 대한 그래프 동적 생성 및 렌더링
Object.keys(graphs_json.age_quiz_response_figs).forEach(function(key) {
var ageQuizResponse = graphs_json.age_quiz_response_figs[key];
var div = document.createElement('div');
div.id = key + "-age";
div.className = 'graph-container';
document.getElementById('age-quiz-responses-container').appendChild(div);
Plotly.newPlot(div.id, ageQuizResponse.data, ageQuizResponse.layout);
});
</script>
</body>
</html>
dashboard.html
admin로그인 후 일자별 사용자 대시보드를 보여주는 페이지
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin Dashboard</title>
<!--대시보드 표시-->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
.dashboard-button, .logout-button {
padding: 10px 20px;
margin: 5px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
.dashboard-button:hover, .logout-button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<!--각 버튼을 통해 원하는 기능 페이지로 이동할 수 있도록-->
<h1>Admin Dashboard</h1>
<a href="{{ url_for('admin.manage_questions') }}">
<button class="dashboard-button">Manage Questions</button>
</a>
<a href="{{ url_for('admin.quiz_list') }}">
<button class="dashboard-button">Quiz List</button>
</a>
<a href="{{ url_for('admin.logout') }}">
<button class="logout-button">Logout</button>
</a>
<div>{{ graph_div|safe }}</div>
</body>
</html>
migrations folder
flask-migrate를 위한 폴더
run.py
플라스크를 실행시 실행되는 파일, 실제 구동은 위의 py폴더들을 통해서 구동
db.sqlite
sqlite로 저장된 데이터 베이스 파일