diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/__pycache__/config.cpython-314.pyc b/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000..f299eb7 Binary files /dev/null and b/__pycache__/config.cpython-314.pyc differ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..345ee6c --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,36 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_login import LoginManager +from config import Config + +db = SQLAlchemy() +login_manager = LoginManager() + +def create_app(config_class=Config): + app = Flask(__name__) + app.config.from_object(config_class) + + db.init_app(app) + login_manager.init_app(app) + login_manager.login_view = 'auth.login' + login_manager.login_message = '请先登录后再访问此页面' + + from app.routes.main import main as main_blueprint + app.register_blueprint(main_blueprint) + + from app.routes.auth import auth as auth_blueprint + app.register_blueprint(auth_blueprint, url_prefix='/auth') + + from app.routes.goals import goals as goals_blueprint + app.register_blueprint(goals_blueprint, url_prefix='/goals') + + from app.routes.tasks import tasks as tasks_blueprint + app.register_blueprint(tasks_blueprint, url_prefix='/tasks') + + from app.routes.reports import reports as reports_blueprint + app.register_blueprint(reports_blueprint, url_prefix='/reports') + + with app.app_context(): + db.create_all() + + return app diff --git a/app/__pycache__/__init__.cpython-314.pyc b/app/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..da3ef0c Binary files /dev/null and b/app/__pycache__/__init__.cpython-314.pyc differ diff --git a/app/__pycache__/models.cpython-314.pyc b/app/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..e510720 Binary files /dev/null and b/app/__pycache__/models.cpython-314.pyc differ diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..f696cf8 --- /dev/null +++ b/app/models.py @@ -0,0 +1,102 @@ +from datetime import datetime +from app import db, login_manager +from flask_login import UserMixin +from werkzeug.security import generate_password_hash, check_password_hash + +class User(UserMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(64), index=True, unique=True, nullable=False) + email = db.Column(db.String(120), index=True, unique=True, nullable=False) + password_hash = db.Column(db.String(256), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + goals = db.relationship('LearningGoal', backref='user', lazy='dynamic') + + 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 __repr__(self): + return f'' + +class LearningGoal(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + description = db.Column(db.Text) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + start_date = db.Column(db.DateTime, default=datetime.utcnow) + end_date = db.Column(db.DateTime) + status = db.Column(db.String(20), default='active') + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + tasks = db.relationship('LearningTask', backref='goal', lazy='dynamic', cascade='all, delete-orphan') + + def __repr__(self): + return f'' + +class LearningTask(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(200), nullable=False) + description = db.Column(db.Text) + goal_id = db.Column(db.Integer, db.ForeignKey('learning_goal.id'), nullable=False) + parent_task_id = db.Column(db.Integer, db.ForeignKey('learning_task.id')) + status = db.Column(db.String(20), default='pending') + progress = db.Column(db.Integer, default=0) + priority = db.Column(db.String(20), default='medium') + estimated_hours = db.Column(db.Float, default=0) + actual_hours = db.Column(db.Float, default=0) + deadline = db.Column(db.DateTime) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + completed_at = db.Column(db.DateTime) + + parent_task = db.relationship('LearningTask', remote_side=[id], backref='subtasks') + study_records = db.relationship('StudyRecord', backref='task', lazy='dynamic', cascade='all, delete-orphan') + reminders = db.relationship('TaskReminder', backref='task', lazy='dynamic', cascade='all, delete-orphan') + + def __repr__(self): + return f'' + +class StudyRecord(db.Model): + id = db.Column(db.Integer, primary_key=True) + task_id = db.Column(db.Integer, db.ForeignKey('learning_task.id'), nullable=False) + start_time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + end_time = db.Column(db.DateTime) + duration_minutes = db.Column(db.Integer, default=0) + notes = db.Column(db.Text) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f'' + +class TaskReminder(db.Model): + id = db.Column(db.Integer, primary_key=True) + task_id = db.Column(db.Integer, db.ForeignKey('learning_task.id'), nullable=False) + reminder_time = db.Column(db.DateTime, nullable=False) + message = db.Column(db.String(200)) + is_sent = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f'' + +class StudyReport(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + report_type = db.Column(db.String(20), nullable=False) + start_date = db.Column(db.DateTime, nullable=False) + end_date = db.Column(db.DateTime, nullable=False) + total_study_hours = db.Column(db.Float, default=0) + completed_tasks = db.Column(db.Integer, default=0) + total_tasks = db.Column(db.Integer, default=0) + goals_progress = db.Column(db.Text) + recommendations = db.Column(db.Text) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f'' + +@login_manager.user_loader +def load_user(id): + return User.query.get(int(id)) diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 0000000..04d95f3 --- /dev/null +++ b/app/routes/__init__.py @@ -0,0 +1,7 @@ +from flask import Blueprint + +main = Blueprint('main', __name__) +auth = Blueprint('auth', __name__) +goals = Blueprint('goals', __name__) +tasks = Blueprint('tasks', __name__) +reports = Blueprint('reports', __name__) diff --git a/app/routes/__pycache__/__init__.cpython-314.pyc b/app/routes/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..a4a2486 Binary files /dev/null and b/app/routes/__pycache__/__init__.cpython-314.pyc differ diff --git a/app/routes/__pycache__/auth.cpython-314.pyc b/app/routes/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000..6977766 Binary files /dev/null and b/app/routes/__pycache__/auth.cpython-314.pyc differ diff --git a/app/routes/__pycache__/goals.cpython-314.pyc b/app/routes/__pycache__/goals.cpython-314.pyc new file mode 100644 index 0000000..7ae45e9 Binary files /dev/null and b/app/routes/__pycache__/goals.cpython-314.pyc differ diff --git a/app/routes/__pycache__/main.cpython-314.pyc b/app/routes/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000..57fa35b Binary files /dev/null and b/app/routes/__pycache__/main.cpython-314.pyc differ diff --git a/app/routes/__pycache__/reports.cpython-314.pyc b/app/routes/__pycache__/reports.cpython-314.pyc new file mode 100644 index 0000000..f6981dd Binary files /dev/null and b/app/routes/__pycache__/reports.cpython-314.pyc differ diff --git a/app/routes/__pycache__/tasks.cpython-314.pyc b/app/routes/__pycache__/tasks.cpython-314.pyc new file mode 100644 index 0000000..1478d11 Binary files /dev/null and b/app/routes/__pycache__/tasks.cpython-314.pyc differ diff --git a/app/routes/auth.py b/app/routes/auth.py new file mode 100644 index 0000000..e5a6aaf --- /dev/null +++ b/app/routes/auth.py @@ -0,0 +1,70 @@ +from flask import render_template, redirect, url_for, flash, request +from flask_login import login_user, logout_user, current_user, login_required +from app import db +from app.routes import auth +from app.models import User + +@auth.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('main.index')) + + if request.method == 'POST': + username = request.form.get('username') + email = request.form.get('email') + password = request.form.get('password') + password2 = request.form.get('password2') + + if not username or not email or not password: + flash('请填写所有必填字段') + return redirect(url_for('auth.register')) + + if password != password2: + flash('两次密码输入不一致') + return redirect(url_for('auth.register')) + + if User.query.filter_by(username=username).first(): + flash('用户名已存在') + return redirect(url_for('auth.register')) + + if User.query.filter_by(email=email).first(): + flash('邮箱已被注册') + return redirect(url_for('auth.register')) + + user = User(username=username, email=email) + user.set_password(password) + db.session.add(user) + db.session.commit() + + flash('注册成功!请登录') + return redirect(url_for('auth.login')) + + return render_template('register.html') + +@auth.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('main.index')) + + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + remember = request.form.get('remember') + + user = User.query.filter_by(username=username).first() + + if user is None or not user.check_password(password): + flash('用户名或密码错误') + return redirect(url_for('auth.login')) + + login_user(user, remember=remember) + next_page = request.args.get('next') + return redirect(next_page or url_for('main.index')) + + return render_template('login.html') + +@auth.route('/logout') +@login_required +def logout(): + logout_user() + return redirect(url_for('auth.login')) diff --git a/app/routes/goals.py b/app/routes/goals.py new file mode 100644 index 0000000..cc2bb24 --- /dev/null +++ b/app/routes/goals.py @@ -0,0 +1,115 @@ +from flask import render_template, redirect, url_for, flash, request, jsonify +from flask_login import login_required, current_user +from app import db +from app.routes import goals +from app.models import LearningGoal, LearningTask +from datetime import datetime + +@goals.route('/') +@login_required +def list_goals(): + status = request.args.get('status', 'active') + if status == 'all': + goals_list = LearningGoal.query.filter_by(user_id=current_user.id).order_by(LearningGoal.created_at.desc()).all() + else: + goals_list = LearningGoal.query.filter_by(user_id=current_user.id, status=status).order_by(LearningGoal.created_at.desc()).all() + + return render_template('goals/list.html', goals=goals_list, current_status=status) + +@goals.route('/create', methods=['GET', 'POST']) +@login_required +def create_goal(): + if request.method == 'POST': + title = request.form.get('title') + description = request.form.get('description') + end_date_str = request.form.get('end_date') + + if not title: + flash('请输入目标标题') + return redirect(url_for('goals.create_goal')) + + end_date = None + if end_date_str: + try: + end_date = datetime.strptime(end_date_str, '%Y-%m-%d') + except ValueError: + flash('日期格式错误,请使用YYYY-MM-DD格式') + return redirect(url_for('goals.create_goal')) + + goal = LearningGoal( + title=title, + description=description, + user_id=current_user.id, + end_date=end_date + ) + db.session.add(goal) + db.session.commit() + + flash('学习目标创建成功!') + return redirect(url_for('goals.view_goal', goal_id=goal.id)) + + return render_template('goals/create.html') + +@goals.route('/') +@login_required +def view_goal(goal_id): + goal = LearningGoal.query.get_or_404(goal_id) + if goal.user_id != current_user.id: + flash('无权访问此目标') + return redirect(url_for('goals.list_goals')) + + tasks = LearningTask.query.filter_by(goal_id=goal_id, parent_task_id=None).order_by(LearningTask.created_at.desc()).all() + + total_tasks = LearningTask.query.filter_by(goal_id=goal_id).count() + completed_tasks = LearningTask.query.filter_by(goal_id=goal_id, status='completed').count() + in_progress_tasks = LearningTask.query.filter_by(goal_id=goal_id, status='in_progress').count() + + stats = { + 'total': total_tasks, + 'completed': completed_tasks, + 'in_progress': in_progress_tasks, + 'pending': total_tasks - completed_tasks - in_progress_tasks, + 'completion_rate': round((completed_tasks / total_tasks * 100), 1) if total_tasks > 0 else 0 + } + + return render_template('goals/view.html', goal=goal, tasks=tasks, stats=stats) + +@goals.route('//edit', methods=['GET', 'POST']) +@login_required +def edit_goal(goal_id): + goal = LearningGoal.query.get_or_404(goal_id) + if goal.user_id != current_user.id: + flash('无权编辑此目标') + return redirect(url_for('goals.list_goals')) + + if request.method == 'POST': + goal.title = request.form.get('title') + goal.description = request.form.get('description') + goal.status = request.form.get('status', 'active') + + end_date_str = request.form.get('end_date') + if end_date_str: + try: + goal.end_date = datetime.strptime(end_date_str, '%Y-%m-%d') + except ValueError: + flash('日期格式错误,请使用YYYY-MM-DD格式') + return redirect(url_for('goals.edit_goal', goal_id=goal.id)) + + db.session.commit() + flash('学习目标更新成功!') + return redirect(url_for('goals.view_goal', goal_id=goal.id)) + + return render_template('goals/edit.html', goal=goal) + +@goals.route('//delete', methods=['POST']) +@login_required +def delete_goal(goal_id): + goal = LearningGoal.query.get_or_404(goal_id) + if goal.user_id != current_user.id: + flash('无权删除此目标') + return redirect(url_for('goals.list_goals')) + + db.session.delete(goal) + db.session.commit() + flash('学习目标已删除') + return redirect(url_for('goals.list_goals')) diff --git a/app/routes/main.py b/app/routes/main.py new file mode 100644 index 0000000..d183472 --- /dev/null +++ b/app/routes/main.py @@ -0,0 +1,41 @@ +from flask import render_template, redirect, url_for, flash, request +from flask_login import login_required, current_user +from app import db +from app.routes import main +from app.models import LearningGoal, LearningTask, StudyRecord +from datetime import datetime, timedelta + +@main.route('/') +@main.route('/index') +@login_required +def index(): + today = datetime.utcnow().date() + start_of_day = datetime.combine(today, datetime.min.time()) + + today_records = StudyRecord.query.join(LearningTask).join(LearningGoal).filter( + LearningGoal.user_id == current_user.id, + StudyRecord.start_time >= start_of_day + ).all() + + today_hours = sum((r.duration_minutes or 0) for r in today_records) / 60 + + active_goals = LearningGoal.query.filter_by( + user_id=current_user.id, + status='active' + ).all() + + pending_tasks = LearningTask.query.join(LearningGoal).filter( + LearningGoal.user_id == current_user.id, + LearningTask.status == 'pending' + ).order_by(LearningTask.deadline).limit(5).all() + + in_progress_tasks = LearningTask.query.join(LearningGoal).filter( + LearningGoal.user_id == current_user.id, + LearningTask.status == 'in_progress' + ).order_by(LearningTask.deadline).limit(5).all() + + return render_template('index.html', + today_hours=round(today_hours, 2), + active_goals_count=len(active_goals), + pending_tasks=pending_tasks, + in_progress_tasks=in_progress_tasks) diff --git a/app/routes/reports.py b/app/routes/reports.py new file mode 100644 index 0000000..6ee58ba --- /dev/null +++ b/app/routes/reports.py @@ -0,0 +1,177 @@ +from flask import render_template, redirect, url_for, flash, request +from flask_login import login_required, current_user +from app import db +from app.routes import reports +from app.models import LearningGoal, LearningTask, StudyRecord, StudyReport +from datetime import datetime, timedelta +from collections import defaultdict + +@reports.route('/') +@login_required +def list_reports(): + reports_list = StudyReport.query.filter_by( + user_id=current_user.id + ).order_by(StudyReport.created_at.desc()).all() + return render_template('reports/list.html', reports=reports_list) + +@reports.route('/generate', methods=['GET', 'POST']) +@login_required +def generate_report(): + if request.method == 'POST': + report_type = request.form.get('report_type', 'weekly') + custom_start = request.form.get('start_date') + custom_end = request.form.get('end_date') + + end_date = datetime.utcnow() + + if report_type == 'weekly': + start_date = end_date - timedelta(weeks=1) + elif report_type == 'monthly': + start_date = end_date - timedelta(days=30) + elif report_type == 'custom' and custom_start and custom_end: + try: + start_date = datetime.strptime(custom_start, '%Y-%m-%d') + end_date = datetime.strptime(custom_end, '%Y-%m-%d') + except ValueError: + flash('日期格式错误,请使用YYYY-MM-DD格式') + return redirect(url_for('reports.generate_report')) + else: + start_date = end_date - timedelta(weeks=1) + + report = create_study_report(current_user.id, start_date, end_date, report_type) + + flash('学习报告生成成功!') + return redirect(url_for('reports.view_report', report_id=report.id)) + + return render_template('reports/generate.html') + +@reports.route('/') +@login_required +def view_report(report_id): + report = StudyReport.query.get_or_404(report_id) + if report.user_id != current_user.id: + flash('无权访问此报告') + return redirect(url_for('reports.list_reports')) + + return render_template('reports/view.html', report=report) + +@reports.route('/history') +@login_required +def view_history(): + page = request.args.get('page', 1, type=int) + per_page = 20 + + goals = LearningGoal.query.filter_by( + user_id=current_user.id + ).order_by(LearningGoal.created_at.desc()).paginate(page=page, per_page=per_page, error_out=False) + + completed_goals = LearningGoal.query.filter_by( + user_id=current_user.id, + status='completed' + ).count() + + all_goals = LearningGoal.query.filter_by( + user_id=current_user.id + ).count() + + total_study_hours = db.session.query( + db.func.sum(StudyRecord.duration_minutes) + ).join(LearningTask).join(LearningGoal).filter( + LearningGoal.user_id == current_user.id + ).scalar() or 0 + + stats = { + 'total_goals': all_goals, + 'completed_goals': completed_goals, + 'completion_rate': round((completed_goals / all_goals * 100), 1) if all_goals > 0 else 0, + 'total_study_hours': round(total_study_hours / 60, 2) + } + + return render_template('reports/history.html', goals=goals, stats=stats) + +def create_study_report(user_id, start_date, end_date, report_type): + study_records = StudyRecord.query.join(LearningTask).join(LearningGoal).filter( + LearningGoal.user_id == user_id, + StudyRecord.start_time >= start_date, + StudyRecord.start_time <= end_date + ).all() + + total_minutes = sum((r.duration_minutes or 0) for r in study_records) + total_hours = round(total_minutes / 60, 2) + + tasks = LearningTask.query.join(LearningGoal).filter( + LearningGoal.user_id == user_id + ).all() + + total_tasks = len(tasks) + completed_tasks = len([t for t in tasks if t.status == 'completed']) + + daily_stats = defaultdict(int) + for record in study_records: + if record.start_time: + date_key = record.start_time.date().isoformat() + daily_stats[date_key] += (record.duration_minutes or 0) + + goals_progress = [] + goals = LearningGoal.query.filter_by(user_id=user_id, status='active').all() + for goal in goals: + goal_tasks = LearningTask.query.filter_by(goal_id=goal.id).all() + total_goal_tasks = len(goal_tasks) + completed_goal_tasks = len([t for t in goal_tasks if t.status == 'completed']) + progress = round((completed_goal_tasks / total_goal_tasks * 100), 1) if total_goal_tasks > 0 else 0 + + goals_progress.append({ + 'goal_title': goal.title, + 'total_tasks': total_goal_tasks, + 'completed_tasks': completed_goal_tasks, + 'progress': progress + }) + + recommendations = generate_recommendations(total_hours, completed_tasks, total_tasks, goals_progress) + + report = StudyReport( + user_id=user_id, + report_type=report_type, + start_date=start_date, + end_date=end_date, + total_study_hours=total_hours, + completed_tasks=completed_tasks, + total_tasks=total_tasks, + goals_progress=str(goals_progress), + recommendations=recommendations + ) + + db.session.add(report) + db.session.commit() + + return report + +def generate_recommendations(total_hours, completed_tasks, total_tasks, goals_progress): + recommendations = [] + + if total_hours < 5: + recommendations.append("本周学习时间较少,建议每天安排至少1-2小时的固定学习时间。") + elif total_hours < 10: + recommendations.append("学习时间尚可,继续保持,可以尝试增加学习深度。") + else: + recommendations.append("本周学习时间充足,继续保持良好的学习习惯!") + + if total_tasks > 0: + completion_rate = completed_tasks / total_tasks + if completion_rate < 0.3: + recommendations.append("任务完成率较低,建议先专注于完成高优先级任务,或减少每周任务量。") + elif completion_rate < 0.6: + recommendations.append("任务完成率一般,可以尝试将大任务拆分成更小的子任务。") + else: + recommendations.append("任务完成率不错,继续保持高效的执行力!") + + for goal in goals_progress: + if goal['progress'] < 30: + recommendations.append(f"目标「{goal['goal_title']}」进度较慢,建议增加对此目标的关注。") + elif goal['progress'] >= 80: + recommendations.append(f"目标「{goal['goal_title']}」进展良好,继续保持!") + + if not recommendations: + recommendations.append("继续保持当前的学习节奏,定期回顾和调整学习计划。") + + return "\n\n".join(recommendations) diff --git a/app/routes/tasks.py b/app/routes/tasks.py new file mode 100644 index 0000000..006b3e2 --- /dev/null +++ b/app/routes/tasks.py @@ -0,0 +1,251 @@ +from flask import render_template, redirect, url_for, flash, request, jsonify +from flask_login import login_required, current_user +from app import db +from app.routes import tasks +from app.models import LearningGoal, LearningTask, StudyRecord, TaskReminder +from datetime import datetime, timedelta + +@tasks.route('/create/', methods=['GET', 'POST']) +@login_required +def create_task(goal_id): + goal = LearningGoal.query.get_or_404(goal_id) + if goal.user_id != current_user.id: + flash('无权为此目标创建任务') + return redirect(url_for('goals.list_goals')) + + parent_task_id = request.args.get('parent_task_id', type=int) + parent_task = None + if parent_task_id: + parent_task = LearningTask.query.get(parent_task_id) + + if request.method == 'POST': + title = request.form.get('title') + description = request.form.get('description') + priority = request.form.get('priority', 'medium') + estimated_hours = request.form.get('estimated_hours', 0, type=float) + deadline_str = request.form.get('deadline') + parent_id = request.form.get('parent_task_id', type=int) + + if not title: + flash('请输入任务标题') + return redirect(url_for('tasks.create_task', goal_id=goal_id, parent_task_id=parent_task_id)) + + deadline = None + if deadline_str: + try: + deadline = datetime.strptime(deadline_str, '%Y-%m-%d') + except ValueError: + flash('日期格式错误,请使用YYYY-MM-DD格式') + return redirect(url_for('tasks.create_task', goal_id=goal_id, parent_task_id=parent_task_id)) + + task = LearningTask( + title=title, + description=description, + goal_id=goal_id, + parent_task_id=parent_id, + priority=priority, + estimated_hours=estimated_hours, + deadline=deadline + ) + db.session.add(task) + db.session.commit() + + flash('学习任务创建成功!') + return redirect(url_for('tasks.view_task', task_id=task.id)) + + return render_template('tasks/create.html', goal=goal, parent_task=parent_task) + +@tasks.route('/') +@login_required +def view_task(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + flash('无权访问此任务') + return redirect(url_for('goals.list_goals')) + + subtasks = LearningTask.query.filter_by(parent_task_id=task_id).order_by(LearningTask.created_at.desc()).all() + study_records = StudyRecord.query.filter_by(task_id=task_id).order_by(StudyRecord.start_time.desc()).all() + reminders = TaskReminder.query.filter_by(task_id=task_id).order_by(TaskReminder.reminder_time).all() + + total_duration = sum((r.duration_minutes or 0) for r in study_records) + total_hours = round(total_duration / 60, 2) + + return render_template('tasks/view.html', + task=task, + subtasks=subtasks, + study_records=study_records, + reminders=reminders, + total_hours=total_hours) + +@tasks.route('//edit', methods=['GET', 'POST']) +@login_required +def edit_task(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + flash('无权编辑此任务') + return redirect(url_for('goals.list_goals')) + + if request.method == 'POST': + task.title = request.form.get('title') + task.description = request.form.get('description') + task.status = request.form.get('status', 'pending') + task.progress = request.form.get('progress', 0, type=int) + task.priority = request.form.get('priority', 'medium') + task.estimated_hours = request.form.get('estimated_hours', 0, type=float) + + deadline_str = request.form.get('deadline') + if deadline_str: + try: + task.deadline = datetime.strptime(deadline_str, '%Y-%m-%d') + except ValueError: + flash('日期格式错误,请使用YYYY-MM-DD格式') + return redirect(url_for('tasks.edit_task', task_id=task.id)) + + if task.status == 'completed' and task.progress < 100: + task.progress = 100 + task.completed_at = datetime.utcnow() + + db.session.commit() + flash('学习任务更新成功!') + return redirect(url_for('tasks.view_task', task_id=task.id)) + + return render_template('tasks/edit.html', task=task) + +@tasks.route('//delete', methods=['POST']) +@login_required +def delete_task(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + flash('无权删除此任务') + return redirect(url_for('goals.list_goals')) + + goal_id = task.goal_id + db.session.delete(task) + db.session.commit() + flash('学习任务已删除') + return redirect(url_for('goals.view_goal', goal_id=goal_id)) + +@tasks.route('//progress', methods=['POST']) +@login_required +def update_progress(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + return jsonify({'success': False, 'message': '无权操作此任务'}), 403 + + progress = request.form.get('progress', type=int) + if progress is not None: + task.progress = min(100, max(0, progress)) + if task.progress >= 100: + task.status = 'completed' + task.completed_at = datetime.utcnow() + elif task.progress > 0: + task.status = 'in_progress' + + db.session.commit() + return jsonify({'success': True, 'progress': task.progress, 'status': task.status}) + + return jsonify({'success': False, 'message': '参数错误'}), 400 + +@tasks.route('//start-study', methods=['POST']) +@login_required +def start_study(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + return jsonify({'success': False, 'message': '无权操作此任务'}), 403 + + active_record = StudyRecord.query.filter_by( + task_id=task_id, + end_time=None + ).first() + + if active_record: + return jsonify({'success': False, 'message': '已有进行中的学习记录'}), 400 + + study_record = StudyRecord( + task_id=task_id, + start_time=datetime.utcnow() + ) + db.session.add(study_record) + + if task.status == 'pending': + task.status = 'in_progress' + + db.session.commit() + + return jsonify({ + 'success': True, + 'record_id': study_record.id, + 'start_time': study_record.start_time.isoformat() + }) + +@tasks.route('//stop-study', methods=['POST']) +@login_required +def stop_study(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + return jsonify({'success': False, 'message': '无权操作此任务'}), 403 + + active_record = StudyRecord.query.filter_by( + task_id=task_id, + end_time=None + ).first() + + if not active_record: + return jsonify({'success': False, 'message': '没有进行中的学习记录'}), 400 + + active_record.end_time = datetime.utcnow() + duration = (active_record.end_time - active_record.start_time).total_seconds() + active_record.duration_minutes = int(duration / 60) + + task.actual_hours += active_record.duration_minutes / 60 + + db.session.commit() + + return jsonify({ + 'success': True, + 'duration_minutes': active_record.duration_minutes, + 'total_hours': round(task.actual_hours, 2) + }) + +@tasks.route('//add-reminder', methods=['POST']) +@login_required +def add_reminder(task_id): + task = LearningTask.query.get_or_404(task_id) + if task.goal.user_id != current_user.id: + flash('无权为此任务添加提醒') + return redirect(url_for('tasks.view_task', task_id=task_id)) + + reminder_time_str = request.form.get('reminder_time') + message = request.form.get('message', '') + + try: + reminder_time = datetime.strptime(reminder_time_str, '%Y-%m-%d %H:%M') + except ValueError: + flash('提醒时间格式错误,请使用YYYY-MM-DD HH:MM格式') + return redirect(url_for('tasks.view_task', task_id=task_id)) + + reminder = TaskReminder( + task_id=task_id, + reminder_time=reminder_time, + message=message + ) + db.session.add(reminder) + db.session.commit() + + flash('提醒添加成功!') + return redirect(url_for('tasks.view_task', task_id=task_id)) + +@tasks.route('/reminder//delete', methods=['POST']) +@login_required +def delete_reminder(reminder_id): + reminder = TaskReminder.query.get_or_404(reminder_id) + if reminder.task.goal.user_id != current_user.id: + flash('无权删除此提醒') + return redirect(url_for('goals.list_goals')) + + task_id = reminder.task_id + db.session.delete(reminder) + db.session.commit() + + flash('提醒已删除') + return redirect(url_for('tasks.view_task', task_id=task_id)) diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..2490296 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,493 @@ + + + + + + {% block title %}个人学习计划管理系统{% endblock %} + + + +
+
+

📚 个人学习计划管理系统

+ +
+
+ +
+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ + {% block scripts %}{% endblock %} + + diff --git a/app/templates/goals/create.html b/app/templates/goals/create.html new file mode 100644 index 0000000..812615a --- /dev/null +++ b/app/templates/goals/create.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %}创建学习目标 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

创建学习目标

+
+
+ + +
+
+ + +
+
+ + +
+
+ + 取消 +
+
+
+{% endblock %} diff --git a/app/templates/goals/edit.html b/app/templates/goals/edit.html new file mode 100644 index 0000000..3578ba7 --- /dev/null +++ b/app/templates/goals/edit.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block title %}编辑学习目标 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

编辑学习目标

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + 取消 +
+
+
+ +
+

危险操作

+

删除此目标将同时删除所有关联的任务和学习记录,此操作不可撤销。

+
+ +
+
+{% endblock %} diff --git a/app/templates/goals/list.html b/app/templates/goals/list.html new file mode 100644 index 0000000..906d90f --- /dev/null +++ b/app/templates/goals/list.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} + +{% block title %}学习目标 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+
+

学习目标列表

+ 创建新目标 +
+ +
+ +
+ + {% if goals %} +
+ + + + + + + + + + + + + {% for goal in goals %} + + + + + + + + + {% endfor %} + +
目标名称创建日期截止日期状态任务数操作
+ + {{ goal.title }} + + {% if goal.description %} +
+ {{ goal.description[:50] }}{% if goal.description|length > 50 %}...{% endif %} + {% endif %} +
{{ goal.created_at.strftime('%Y-%m-%d') if goal.created_at else '-' }}{{ goal.end_date.strftime('%Y-%m-%d') if goal.end_date else '-' }} + {% if goal.status == 'active' %} + 进行中 + {% elif goal.status == 'completed' %} + 已完成 + {% elif goal.status == 'paused' %} + 已暂停 + {% else %} + {{ goal.status }} + {% endif %} + {{ goal.tasks.count() }} +
+ 查看 + 编辑 +
+
+
+ {% else %} +
+

还没有任何学习目标

+ 创建第一个学习目标 +
+ {% endif %} +
+{% endblock %} diff --git a/app/templates/goals/view.html b/app/templates/goals/view.html new file mode 100644 index 0000000..55065d1 --- /dev/null +++ b/app/templates/goals/view.html @@ -0,0 +1,188 @@ +{% extends "base.html" %} + +{% block title %}{{ goal.title }} - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+
+
+

{{ goal.title }}

+
+ 创建日期: {{ goal.created_at.strftime('%Y-%m-%d') if goal.created_at else '-' }} + {% if goal.end_date %} + 截止日期: {{ goal.end_date.strftime('%Y-%m-%d') }} + {% endif %} + 状态: + {% if goal.status == 'active' %} + 进行中 + {% elif goal.status == 'completed' %} + 已完成 + {% elif goal.status == 'paused' %} + 已暂停 + {% else %} + {{ goal.status }} + {% endif %} + +
+
+ +
+ + {% if goal.description %} +
+ 目标描述:
+ {{ goal.description }} +
+ {% endif %} +
+ +
+
+

{{ stats.total }}

+

总任务数

+
+
+

{{ stats.completed }}

+

已完成

+
+
+

{{ stats.in_progress }}

+

进行中

+
+
+

{{ stats.completion_rate }}%

+

完成率

+
+
+ +
+

进度概览

+
+
+
+

+ {% if stats.completion_rate == 100 %} + 恭喜!所有任务已完成! + {% elif stats.completion_rate >= 80 %} + 进度良好,继续保持! + {% elif stats.completion_rate >= 50 %} + 已过半,继续加油! + {% else %} + 还需要更多努力! + {% endif %} +

+
+ +
+
+

任务列表

+ 添加任务 +
+ + {% if tasks %} + {% for task in tasks %} +
+
+
+

+ + {% if task.status == 'completed' %}{% endif %} + {{ task.title }} + {% if task.status == 'completed' %}{% endif %} + +

+ {% if task.description %} +

{{ task.description[:100] }}{% if task.description|length > 100 %}...{% endif %}

+ {% endif %} +
+ 状态: + {% if task.status == 'pending' %} + 待开始 + {% elif task.status == 'in_progress' %} + 进行中 + {% elif task.status == 'completed' %} + 已完成 + {% else %} + {{ task.status }} + {% endif %} + + 优先级: + {% if task.priority == 'high' %} + + {% elif task.priority == 'medium' %} + + {% else %} + + {% endif %} + + 进度: {{ task.progress }}% + {% if task.deadline %} + 截止: {{ task.deadline.strftime('%Y-%m-%d') }} + {% endif %} + {% if task.subtasks|length > 0 %} + 子任务: {{ task.subtasks|length }} 个 + {% endif %} +
+
+
+
+
+
+
+
+ {% if task.status != 'completed' %} + + {% endif %} + 详情 + 添加子任务 +
+
+ + {% if task.subtasks %} +
+ 子任务: + {% for subtask in task.subtasks %} + + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + {% else %} +
+

还没有任何任务

+ 添加第一个任务 +
+ {% endif %} +
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..4d160df --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,238 @@ +{% extends "base.html" %} + +{% block title %}首页 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+
+

{{ today_hours }}

+

今日学习时长(小时)

+
+
+

{{ active_goals_count }}

+

进行中的学习目标

+
+
+

{{ pending_tasks|length }}

+

待完成的任务

+
+
+

{{ in_progress_tasks|length }}

+

进行中的任务

+
+
+ +
+
+

快速操作

+
+ +
+ +{% if in_progress_tasks %} +
+

进行中的任务

+ {% for task in in_progress_tasks %} +
+
+
+

{{ task.title }}

+
+ 所属目标: {{ task.goal.title }} + 优先级: + {% if task.priority == 'high' %} + + {% elif task.priority == 'medium' %} + + {% else %} + + {% endif %} + + {% if task.deadline %} + 截止日期: {{ task.deadline.strftime('%Y-%m-%d') }} + {% endif %} +
+
+
+
+
+ 进度: {{ task.progress }}% +
+
+
+ + 详情 +
+
+
+ {% endfor %} +
+{% endif %} + +{% if pending_tasks %} +
+

待开始的任务

+ {% for task in pending_tasks %} +
+
+
+

{{ task.title }}

+
+ 所属目标: {{ task.goal.title }} + 优先级: + {% if task.priority == 'high' %} + + {% elif task.priority == 'medium' %} + + {% else %} + + {% endif %} + + {% if task.deadline %} + 截止日期: {{ task.deadline.strftime('%Y-%m-%d') }} + {% endif %} +
+
+
+ + 详情 +
+
+
+ {% endfor %} +
+{% endif %} + +{% if not in_progress_tasks and not pending_tasks %} +
+
+

还没有任何学习任务

+ 创建第一个学习目标 +
+
+{% endif %} + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..2fc8c72 --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block title %}登录 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

用户登录

+
+
+ + +
+
+ + +
+
+ +
+ +
+

+ 还没有账号? 立即注册 +

+
+{% endblock %} diff --git a/app/templates/register.html b/app/templates/register.html new file mode 100644 index 0000000..2992a3b --- /dev/null +++ b/app/templates/register.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block title %}注册 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

用户注册

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+

+ 已有账号? 立即登录 +

+
+{% endblock %} diff --git a/app/templates/reports/generate.html b/app/templates/reports/generate.html new file mode 100644 index 0000000..00088e3 --- /dev/null +++ b/app/templates/reports/generate.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% block title %}生成学习报告 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

生成学习报告

+
+
+ + +
+ + + +
+ + 取消 +
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/templates/reports/history.html b/app/templates/reports/history.html new file mode 100644 index 0000000..f90f81f --- /dev/null +++ b/app/templates/reports/history.html @@ -0,0 +1,110 @@ +{% extends "base.html" %} + +{% block title %}历史学习记录 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

总体统计

+
+
+

{{ stats.total_goals }}

+

总目标数

+
+
+

{{ stats.completed_goals }}

+

已完成目标

+
+
+

{{ stats.completion_rate }}%

+

目标完成率

+
+
+

{{ stats.total_study_hours }}

+

总学习时长(小时)

+
+
+
+ +
+

历史学习目标

+ + {% if goals.items %} +
+ + + + + + + + + + + + + {% for goal in goals.items %} + + + + + + + + + {% endfor %} + +
目标名称创建日期截止日期状态任务数操作
+ + {{ goal.title }} + + {% if goal.description %} +
+ {{ goal.description[:50] }}{% if goal.description|length > 50 %}...{% endif %} + {% endif %} +
{{ goal.created_at.strftime('%Y-%m-%d') if goal.created_at else '-' }}{{ goal.end_date.strftime('%Y-%m-%d') if goal.end_date else '-' }} + {% if goal.status == 'active' %} + 进行中 + {% elif goal.status == 'completed' %} + 已完成 + {% elif goal.status == 'paused' %} + 已暂停 + {% else %} + {{ goal.status }} + {% endif %} + {{ goal.tasks.count() }} + 查看详情 +
+
+ + {% if goals.pages > 1 %} + + {% endif %} + + {% else %} +
+

还没有任何学习目标

+ 创建第一个学习目标 +
+ {% endif %} +
+{% endblock %} diff --git a/app/templates/reports/list.html b/app/templates/reports/list.html new file mode 100644 index 0000000..ff035bc --- /dev/null +++ b/app/templates/reports/list.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} + +{% block title %}学习报告列表 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+
+

学习报告列表

+ 生成新报告 +
+ + {% if reports %} + + + + + + + + + + + + + {% for report in reports %} + + + + + + + + + {% endfor %} + +
报告类型时间范围学习时长完成任务生成时间操作
+ {% if report.report_type == 'weekly' %} + 周报 + {% elif report.report_type == 'monthly' %} + 月报 + {% else %} + 自定义 + {% endif %} + + {{ report.start_date.strftime('%Y-%m-%d') }} 至 {{ report.end_date.strftime('%Y-%m-%d') }} + {{ report.total_study_hours }} 小时{{ report.completed_tasks }} / {{ report.total_tasks }}{{ report.created_at.strftime('%Y-%m-%d %H:%M') if report.created_at else '-' }} + 查看 +
+ {% else %} +
+

还没有任何学习报告

+ 生成第一份报告 +
+ {% endif %} +
+{% endblock %} diff --git a/app/templates/reports/view.html b/app/templates/reports/view.html new file mode 100644 index 0000000..a5a14a3 --- /dev/null +++ b/app/templates/reports/view.html @@ -0,0 +1,92 @@ +{% extends "base.html" %} + +{% block title %}学习报告 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+
+

学习报告详情

+
+ {% if report.report_type == 'weekly' %} + 周报 + {% elif report.report_type == 'monthly' %} + 月报 + {% else %} + 自定义报告 + {% endif %} +
+
+ +
+ 时间范围: {{ report.start_date.strftime('%Y-%m-%d') }} 至 {{ report.end_date.strftime('%Y-%m-%d') }} +
+ 生成时间: {{ report.created_at.strftime('%Y-%m-%d %H:%M') if report.created_at else '-' }} +
+
+ +
+
+

{{ report.total_study_hours }}

+

总学习时长(小时)

+
+
+

{{ report.completed_tasks }}

+

已完成任务数

+
+
+

{{ report.total_tasks }}

+

总任务数

+
+
+

+ {% if report.total_tasks > 0 %} + {{ ((report.completed_tasks / report.total_tasks) * 100)|round(1) }} + {% else %} + 0 + {% endif %}% +

+

任务完成率

+
+
+ +{% if report.goals_progress and report.goals_progress != '[]' %} +
+

各目标进度

+
+ {% set goals = report.goals_progress|replace("[", "")|replace("]", "")|replace("{", "")|replace("}", "")|replace("'", "")|split(", ") %} + {% for goal_str in goals %} + {% if goal_str %} + {% set parts = goal_str|split(": ") %} + {% if parts|length >= 2 %} + {% set key = parts[0] %} + {% set value = parts[1:]|join(": ") %} +
+ {{ key }}: {{ value }} +
+ {% endif %} + {% endif %} + {% endfor %} +
+
+{% endif %} + +{% if report.recommendations %} +
+

学习建议

+
+ {% for line in report.recommendations.split('\n\n') %} +

+ 💡 {{ line }} +

+ {% endfor %} +
+
+{% endif %} + + +{% endblock %} diff --git a/app/templates/tasks/create.html b/app/templates/tasks/create.html new file mode 100644 index 0000000..cf15fd0 --- /dev/null +++ b/app/templates/tasks/create.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} + +{% block title %}创建学习任务 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

创建学习任务

+
+ 所属目标: {{ goal.title }} + {% if parent_task %} +
父任务: {{ parent_task.title }} + {% endif %} +
+
+ {% if parent_task %} + + {% endif %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + 取消 +
+
+
+{% endblock %} diff --git a/app/templates/tasks/edit.html b/app/templates/tasks/edit.html new file mode 100644 index 0000000..aa1caec --- /dev/null +++ b/app/templates/tasks/edit.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} + +{% block title %}编辑学习任务 - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+

编辑学习任务

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + 取消 +
+
+
+{% endblock %} diff --git a/app/templates/tasks/view.html b/app/templates/tasks/view.html new file mode 100644 index 0000000..7a45b30 --- /dev/null +++ b/app/templates/tasks/view.html @@ -0,0 +1,361 @@ +{% extends "base.html" %} + +{% block title %}{{ task.title }} - 个人学习计划管理系统{% endblock %} + +{% block content %} +
+
+
+

{% if task.status == 'completed' %}{% endif %}{{ task.title }}{% if task.status == 'completed' %}{% endif %}

+
+ 所属目标: {{ task.goal.title }} + 创建日期: {{ task.created_at.strftime('%Y-%m-%d') if task.created_at else '-' }} + 状态: + {% if task.status == 'pending' %} + 待开始 + {% elif task.status == 'in_progress' %} + 进行中 + {% elif task.status == 'completed' %} + 已完成 + {% else %} + {{ task.status }} + {% endif %} + + 优先级: + {% if task.priority == 'high' %} + + {% elif task.priority == 'medium' %} + + {% else %} + + {% endif %} + +
+
+ +
+ + {% if task.description %} +
+ 任务描述:
+ {{ task.description }} +
+ {% endif %} +
+ +
+
+

{{ task.progress }}%

+

完成进度

+
+
+

{{ task.estimated_hours }}

+

预计时长(小时)

+
+
+

{{ total_hours }}

+

实际学习(小时)

+
+
+

{{ subtasks|length }}

+

子任务数

+
+
+ +
+

学习计时

+
+
00:00:00
+

点击开始学习按钮开始计时

+
+
+ + +
+
+ +
+

更新进度

+
+
+
+
+ + {{ task.progress }}% + +
+
+ + + + + +
+
+ +{% if subtasks %} +
+
+

子任务

+ 添加子任务 +
+ {% for subtask in subtasks %} + + {% endfor %} +
+{% endif %} + +{% if study_records %} +
+

学习记录

+ + + + + + + + + + + {% for record in study_records %} + + + + + + + {% endfor %} + +
开始时间结束时间时长备注
{{ record.start_time.strftime('%Y-%m-%d %H:%M') if record.start_time else '-' }}{{ record.end_time.strftime('%Y-%m-%d %H:%M') if record.end_time else '进行中' }} + {% if record.duration_minutes %} + {% set hours = record.duration_minutes // 60 %} + {% set minutes = record.duration_minutes % 60 %} + {% if hours > 0 %}{{ hours }}小时{% endif %}{{ minutes }}分钟 + {% else %} + - + {% endif %} + {{ record.notes or '-' }}
+
+{% endif %} + +
+

任务提醒

+ {% if reminders %} + + + + + + + + + + + {% for reminder in reminders %} + + + + + + + {% endfor %} + +
提醒时间消息状态操作
{{ reminder.reminder_time.strftime('%Y-%m-%d %H:%M') if reminder.reminder_time else '-' }}{{ reminder.message or '-' }} + {% if reminder.is_sent %} + 已发送 + {% else %} + 待发送 + {% endif %} + +
+ +
+
+ {% else %} +

还没有设置任何提醒

+ {% endif %} + +

添加新提醒

+
+
+ + +
+
+ + +
+ +
+
+ +
+

危险操作

+

删除此任务将同时删除所有关联的子任务、学习记录和提醒,此操作不可撤销。

+
+ +
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/config.py b/config.py new file mode 100644 index 0000000..3eb1f41 --- /dev/null +++ b/config.py @@ -0,0 +1,9 @@ +import os + +basedir = os.path.abspath(os.path.dirname(__file__)) + +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') or 'study-plan-secret-key-2024' + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ + 'sqlite:///' + os.path.join(basedir, 'study_plan.db') + SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..069b4cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask>=3.0.0 +Flask-SQLAlchemy>=3.1.1 +Flask-Login>=0.6.3 +Flask-WTF>=1.2.1 +Werkzeug>=3.0.0 +SQLAlchemy>=2.0.30 +python-dateutil>=2.8.2 diff --git a/run.py b/run.py new file mode 100644 index 0000000..ecad898 --- /dev/null +++ b/run.py @@ -0,0 +1,6 @@ +from app import create_app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5001) diff --git a/study_plan.db b/study_plan.db new file mode 100644 index 0000000..67d6c1b Binary files /dev/null and b/study_plan.db differ