Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Hello World Flask 애플리케이션 예제

from flask import Flask # Flask 모듈 임포트
from flask import Flask, render_template # Flask 모듈 임포트
# Flask는 파이썬으로 작성된 웹 프레임워크로, 웹 애플리케이션을 쉽게 만들 수 있도록 도와줍니다.


app = Flask(__name__) # Flask 애플리케이션 객체 생성

@app.route("/") # 루트 URL에 대한 라우팅 설정
def hello():
return "Hello, World!" # 브라우저에 출력될 텍스트
def home():
return render_template("index_html") # 브라우저에 출력될 텍스트

if __name__ == "__main__":
app.run(debug=True) # 개발 서버 실행 (디버그 모드)
Binary file added clubsite/__pycache__/config.cpython-313.pyc
Binary file not shown.
76 changes: 76 additions & 0 deletions clubsite/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# app/__init__.py
from flask import Flask, render_template
from flask_login import LoginManager, current_user, login_required
from config import Config

login_manager = LoginManager()

@login_manager.user_loader
def load_user(user_id):
# 함수 내부에서 import하여 순환 import 방지
from app.models import User
return User.query.get(int(user_id))

def create_app():
app = Flask(__name__)
app.config.from_object(Config)

# DB 초기화 (늦은 import)
from app.models import db
db.init_app(app)

# 로그인 매니저 초기화
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
login_manager.login_message = '로그인이 필요합니다.'
login_manager.login_message_category = 'info'

@app.context_processor
def inject_user():
return {
'current_user': current_user,
'is_admin': current_user.is_authenticated and current_user.is_admin_user()
}

# 블루프린트 등록 (늦은 import)
from app.routes.auth import auth_bp
from app.routes.admin import admin_bp

app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(admin_bp, url_prefix='/admin')

# 메인 라우트
from flask import Blueprint
main_bp = Blueprint('main', __name__)

@main_bp.route('/')
def index():
return render_template('index0.html', title='동아리 홈페이지')

@main_bp.route('/dashboard')
@login_required
def dashboard():
user_teams = current_user.get_teams()
return render_template('index.html',
title='대시보드',
teams=user_teams)


app.register_blueprint(main_bp)

# 에러 핸들러
@app.errorhandler(403)
def forbidden(error):
return render_template('errors/403.html'), 403

@app.errorhandler(404)
def not_found(error):
return render_template('errors/404.html'), 404

@app.errorhandler(500)
def internal_error(error):
from app.models import db # 함수 내부에서 import
db.session.rollback()
return render_template('errors/500.html'), 500

return app
Binary file added clubsite/app/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file added clubsite/app/__pycache__/models.cpython-313.pyc
Binary file not shown.
84 changes: 84 additions & 0 deletions clubsite/app/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# app/decorators.py
from functools import wraps
from flask import abort, redirect, url_for, request, flash
from flask_login import current_user, login_required

def admin_required(f):
"""관리자 권한이 필요한 페이지에 사용하는 데코레이터"""
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
if not current_user.is_admin_user():
flash('관리자 권한이 필요합니다.', 'error')
abort(403)
return f(*args, **kwargs)
return decorated_function

def group_access_required(f):
"""그룹 상세 정보 접근 시 권한 확인 데코레이터"""
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
# 함수 호출 시점에 import하여 순환 import 방지
from app.models import Group

group_id = kwargs.get('group_id') or request.view_args.get('group_id')
if not group_id:
abort(404)

group = Group.query.get_or_404(group_id)

# 관리자는 모든 그룹에 접근 가능
if current_user.is_admin_user():
return f(*args, **kwargs)

# 해당 그룹의 멤버인지 확인
if not group.has_member(current_user.id):
flash('해당 그룹의 정보에 접근할 권한이 없습니다.', 'error')
abort(403)

return f(*args, **kwargs)
return decorated_function

def member_access_required(f):
"""멤버 개인정보 접근 시 권한 확인 데코레이터"""
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
from app.models import Member

member_id = kwargs.get('member_id') or request.view_args.get('member_id')
if not member_id:
abort(404)

member = Member.query.get_or_404(member_id)

# 관리자는 모든 멤버 정보에 접근 가능
if current_user.is_admin_user():
return f(*args, **kwargs)

# 같은 그룹 멤버인지 확인
user_member = current_user.get_member_info()
if not user_member or user_member.group_id != member.group_id:
flash('해당 멤버의 정보에 접근할 권한이 없습니다.', 'error')
abort(403)

return f(*args, **kwargs)
return decorated_function

def own_profile_or_admin_required(f):
"""자신의 프로필이거나 관리자만 접근 가능한 데코레이터"""
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
user_id = kwargs.get('user_id') or request.view_args.get('user_id')
if not user_id:
abort(404)

# 관리자이거나 자신의 프로필인 경우
if current_user.is_admin_user() or current_user.id == int(user_id):
return f(*args, **kwargs)

flash('접근 권한이 없습니다.', 'error')
abort(403)
return decorated_function
81 changes: 81 additions & 0 deletions clubsite/app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# app/models.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime

db = SQLAlchemy()

class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150), unique=True, nullable=False)
password_hash = db.Column(db.String(150), nullable=False)
is_admin = db.Column(db.Boolean, default=False)
email = db.Column(db.String(150), unique=True, nullable=False)
birthdate = db.Column(db.Date, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

# User와 Member 관계 설정
member = db.relationship('Member', backref='user', uselist=False)

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
return check_password_hash(self.password_hash, password)

def is_admin_user(self):
"""관리자인지 확인"""
return self.is_admin

def get_member_info(self):
"""해당 사용자의 Member 정보 반환"""
return Member.query.filter_by(user_id=self.id).first()

def get_accessible_groups(self):
"""접근 가능한 그룹들 반환"""
if self.is_admin:
return Group.query.all()
else:
member = self.get_member_info()
if member:
return [member.group]
return []

def get_teams(self):
"""사용자가 속한 팀들 반환 (auth.py에서 사용)"""
return self.get_accessible_groups()

class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
groups = db.relationship('Group', backref='category', lazy=True)

def __repr__(self):
return f'<Category {self.name}>'

class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
members = db.relationship('Member', backref='group', lazy=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

def get_member_count(self):
"""그룹 멤버 수 반환"""
return len(self.members)

def has_member(self, user_id):
"""특정 사용자가 이 그룹의 멤버인지 확인"""
return any(member.user_id == user_id for member in self.members if member.user_id)

def __repr__(self):
return f'<Group {self.name}>'

class Member(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
department = db.Column(db.String(150))
blog_url = db.Column(db.String(200))
group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading