flashexam 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. flashexam/__init__.py +198 -0
  2. flashexam/admin.py +198 -0
  3. flashexam/answer_preservation_system.py +770 -0
  4. flashexam/api.py +392 -0
  5. flashexam/auth.py +337 -0
  6. flashexam/cli.py +186 -0
  7. flashexam/code_executor.py +209 -0
  8. flashexam/database.py +2591 -0
  9. flashexam/encoding_detector.py +294 -0
  10. flashexam/english_questions.py +574 -0
  11. flashexam/exam_reliability_system.py +728 -0
  12. flashexam/grading_engine.py +769 -0
  13. flashexam/improved_grading_system.py +690 -0
  14. flashexam/intelligent_grading_enhanced.py +127 -0
  15. flashexam/intelligent_grading_ollama.py +718 -0
  16. flashexam/intelligent_grading_qwen.py +1218 -0
  17. flashexam/keyword_focused_grader.py +84 -0
  18. flashexam/main.py +34 -0
  19. flashexam/models.py +82 -0
  20. flashexam/new_exam_creation_api.py +347 -0
  21. flashexam/new_question_system.py +386 -0
  22. flashexam/quick_test.py +160 -0
  23. flashexam/reading_passage_database.py +377 -0
  24. flashexam/reading_passage_models.py +98 -0
  25. flashexam/reading_passages.py +343 -0
  26. flashexam/redis_store.py +245 -0
  27. flashexam/reset_system.py +351 -0
  28. flashexam/run.py +74 -0
  29. flashexam/setup_database.py +370 -0
  30. flashexam/smart_import_manager.py +463 -0
  31. flashexam/static/css/app.css +240 -0
  32. flashexam/static/css/libs/bootstrap/bootstrap-5.1.3.min.css +7 -0
  33. flashexam/static/css/libs/bootstrap/bootstrap-5.3.0.min.css +6 -0
  34. flashexam/static/css/libs/bootstrap/bootstrap-5.3.2.min.css +6 -0
  35. flashexam/static/css/libs/bootstrap/bootstrap.min.css +796 -0
  36. flashexam/static/css/libs/bootstrap-icons/bootstrap-icons-1.10.0.css +2018 -0
  37. flashexam/static/css/libs/bootstrap-icons/bootstrap-icons-1.11.0.css +2078 -0
  38. flashexam/static/css/libs/bootstrap-icons/bootstrap-icons-1.7.2.css +1556 -0
  39. flashexam/static/css/libs/bootstrap-icons/bootstrap-icons-1.8.1.css +1704 -0
  40. flashexam/static/css/libs/bootstrap-icons/bootstrap-icons.min.css +77 -0
  41. flashexam/static/css/libs/bootstrap-icons/fonts/1.10.0/bootstrap-icons.woff +0 -0
  42. flashexam/static/css/libs/bootstrap-icons/fonts/1.10.0/bootstrap-icons.woff2 +0 -0
  43. flashexam/static/css/libs/bootstrap-icons/fonts/1.11.0/bootstrap-icons.woff +0 -0
  44. flashexam/static/css/libs/bootstrap-icons/fonts/1.11.0/bootstrap-icons.woff2 +0 -0
  45. flashexam/static/css/libs/bootstrap-icons/fonts/1.7.2/bootstrap-icons.woff +0 -0
  46. flashexam/static/css/libs/bootstrap-icons/fonts/1.7.2/bootstrap-icons.woff2 +0 -0
  47. flashexam/static/css/libs/bootstrap-icons/fonts/1.8.1/bootstrap-icons.woff +0 -0
  48. flashexam/static/css/libs/bootstrap-icons/fonts/1.8.1/bootstrap-icons.woff2 +0 -0
  49. flashexam/static/css/libs/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  50. flashexam/static/css/libs/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  51. flashexam/static/css/libs/prism/prism-tomorrow.min.css +1 -0
  52. flashexam/static/css/libs/prism/prism.min.css +1 -0
  53. flashexam/static/css/libs/sweetalert2.min.css +1 -0
  54. flashexam/static/css/student_info_display.css +207 -0
  55. flashexam/static/js/app.js +161 -0
  56. flashexam/static/js/exam_monitoring.js +198 -0
  57. flashexam/static/js/exam_optimized.js +576 -0
  58. flashexam/static/js/libs/bootstrap/bootstrap-5.1.3.bundle.min.js +7 -0
  59. flashexam/static/js/libs/bootstrap/bootstrap-5.3.0.bundle.min.js +7 -0
  60. flashexam/static/js/libs/bootstrap/bootstrap-5.3.2.bundle.min.js +7 -0
  61. flashexam/static/js/libs/bootstrap/bootstrap.bundle.min.js +391 -0
  62. flashexam/static/js/libs/bootstrap-shim.js +59 -0
  63. flashexam/static/js/libs/chart/chart.js +20 -0
  64. flashexam/static/js/libs/chart/chart.min.js +20 -0
  65. flashexam/static/js/libs/init.js +156 -0
  66. flashexam/static/js/libs/jquery/jquery-3.7.1.min.js +2 -0
  67. flashexam/static/js/libs/jquery/jquery.min.js +2 -0
  68. flashexam/static/js/libs/prism/components/prism-bash.min.js +1 -0
  69. flashexam/static/js/libs/prism/components/prism-c.min.js +1 -0
  70. flashexam/static/js/libs/prism/components/prism-clike.min.js +1 -0
  71. flashexam/static/js/libs/prism/components/prism-core.min.js +1 -0
  72. flashexam/static/js/libs/prism/components/prism-cpp.min.js +1 -0
  73. flashexam/static/js/libs/prism/components/prism-csharp.min.js +1 -0
  74. flashexam/static/js/libs/prism/components/prism-css.min.js +1 -0
  75. flashexam/static/js/libs/prism/components/prism-go.min.js +1 -0
  76. flashexam/static/js/libs/prism/components/prism-java.min.js +1 -0
  77. flashexam/static/js/libs/prism/components/prism-javascript.min.js +1 -0
  78. flashexam/static/js/libs/prism/components/prism-json.min.js +1 -0
  79. flashexam/static/js/libs/prism/components/prism-markdown.min.js +1 -0
  80. flashexam/static/js/libs/prism/components/prism-markup.min.js +1 -0
  81. flashexam/static/js/libs/prism/components/prism-php.min.js +1 -0
  82. flashexam/static/js/libs/prism/components/prism-python.min.js +1 -0
  83. flashexam/static/js/libs/prism/components/prism-ruby.min.js +1 -0
  84. flashexam/static/js/libs/prism/components/prism-rust.min.js +1 -0
  85. flashexam/static/js/libs/prism/components/prism-sql.min.js +1 -0
  86. flashexam/static/js/libs/prism/components/prism-typescript.min.js +1 -0
  87. flashexam/static/js/libs/prism/plugins/autoloader/prism-autoloader.min.js +1 -0
  88. flashexam/static/js/libs/prism/prism-core.min.js +1 -0
  89. flashexam/static/js/libs/sweetalert2/sweetalert2.min.js +5 -0
  90. flashexam/static/js/offline-detection.js +68 -0
  91. flashexam/static/js/prism-init.js +53 -0
  92. flashexam/static/js/prism-local-config.js +16 -0
  93. flashexam/storage/.gitkeep +0 -0
  94. flashexam/stress_test_submission.py +163 -0
  95. flashexam/student.py +1670 -0
  96. flashexam/teacher.py +8947 -0
  97. flashexam/teacher_fix.py +1 -0
  98. flashexam/templates/admin/index.html +180 -0
  99. flashexam/templates/admin/settings.html +214 -0
  100. flashexam/templates/admin/teachers.html +354 -0
  101. flashexam/templates/auth/login.html +267 -0
  102. flashexam/templates/auth/login_conflict.html +83 -0
  103. flashexam/templates/auth/setup_password.html +339 -0
  104. flashexam/templates/base.html +217 -0
  105. flashexam/templates/errors/404.html +25 -0
  106. flashexam/templates/errors/500.html +25 -0
  107. flashexam/templates/index.html +236 -0
  108. flashexam/templates/student/dashboard.html +315 -0
  109. flashexam/templates/student/exam_continue_choice.html +131 -0
  110. flashexam/templates/student/exam_result.html +412 -0
  111. flashexam/templates/student/exam_result_detail.html +244 -0
  112. flashexam/templates/student/exam_result_detail_secure.html +427 -0
  113. flashexam/templates/student/exams.html +211 -0
  114. flashexam/templates/student/profile.html +224 -0
  115. flashexam/templates/student/results.html +293 -0
  116. flashexam/templates/student/take_exam.html +964 -0
  117. flashexam/templates/student/take_exam_new.html +2243 -0
  118. flashexam/templates/student/take_exam_tpo.html +1025 -0
  119. flashexam/templates/teacher/dashboard.html +375 -0
  120. flashexam/templates/teacher/english_questions.html +1227 -0
  121. flashexam/templates/teacher/exam_edit.html +1009 -0
  122. flashexam/templates/teacher/exam_monitoring_config.html +288 -0
  123. flashexam/templates/teacher/exam_submissions.html +1334 -0
  124. flashexam/templates/teacher/exams.html +2534 -0
  125. flashexam/templates/teacher/grading.html +372 -0
  126. flashexam/templates/teacher/grading_detail.html +1967 -0
  127. flashexam/templates/teacher/new_exam_interface.html +2105 -0
  128. flashexam/templates/teacher/questions.html +1609 -0
  129. flashexam/templates/teacher/reading_passages.html +592 -0
  130. flashexam/templates/teacher/results.html +895 -0
  131. flashexam/templates/teacher/student_exam_details.html +295 -0
  132. flashexam/templates/teacher/students.html +1360 -0
  133. flashexam/templates/teacher/test_export.html +127 -0
  134. flashexam/templates/teacher/test_update_score.html +179 -0
  135. flashexam/templates/teacher/unified_grading.html +276 -0
  136. flashexam/tools.py +1 -0
  137. flashexam/user.py +172 -0
  138. flashexam/utils.py +1397 -0
  139. flashexam/verify_submission_queue.py +86 -0
  140. flashexam-2.0.1.dist-info/METADATA +402 -0
  141. flashexam-2.0.1.dist-info/RECORD +145 -0
  142. flashexam-2.0.1.dist-info/WHEEL +5 -0
  143. flashexam-2.0.1.dist-info/entry_points.txt +2 -0
  144. flashexam-2.0.1.dist-info/licenses/LICENSE +20 -0
  145. flashexam-2.0.1.dist-info/top_level.txt +1 -0
flashexam/__init__.py ADDED
@@ -0,0 +1,198 @@
1
+ """FlashExam - Intelligent Online Examination System with AI Grading."""
2
+
3
+ __version__ = "2.0.1"
4
+
5
+ from flask import Flask
6
+ from flask_login import LoginManager
7
+ from flask_session import Session
8
+ import os
9
+ import sys
10
+ from datetime import timedelta
11
+ import json
12
+ import time
13
+
14
+ current_dir = os.path.dirname(os.path.abspath(__file__))
15
+ sys.path.insert(0, current_dir)
16
+
17
+ try:
18
+ import psutil
19
+ PSUTIL_AVAILABLE = True
20
+ except ImportError:
21
+ PSUTIL_AVAILABLE = False
22
+ print("[WARNING] psutil未安装,将使用模拟数据")
23
+
24
+ _app_start_time = time.time()
25
+ _request_count = 0
26
+ _active_connections = 0
27
+
28
+ def load_development_config(app, static_folder):
29
+ """加载开发环境配置"""
30
+ app.config.update({
31
+ 'SECRET_KEY': os.environ.get('SECRET_KEY') or 'flashexam-dev-key-2024',
32
+ 'UPLOAD_FOLDER': os.path.join(static_folder, 'uploads'),
33
+ 'MAX_CONTENT_LENGTH': 16 * 1024 * 1024,
34
+ 'TEMPLATES_AUTO_RELOAD': True,
35
+ 'JSON_AS_ASCII': False,
36
+ 'DEBUG': True
37
+ })
38
+
39
+ def load_production_config(app, static_folder):
40
+ """加载生产环境配置"""
41
+ is_https = os.environ.get('HTTPS', '').lower() in ('1', 'true', 'on')
42
+
43
+ app.config.update({
44
+ 'SECRET_KEY': os.environ.get('SECRET_KEY') or 'flashexam-prod-key-2024',
45
+ 'UPLOAD_FOLDER': os.path.join(static_folder, 'uploads'),
46
+ 'MAX_CONTENT_LENGTH': 32 * 1024 * 1024,
47
+ 'TEMPLATES_AUTO_RELOAD': False,
48
+ 'JSON_AS_ASCII': False,
49
+ 'SEND_FILE_MAX_AGE_DEFAULT': 43200,
50
+ 'SESSION_COOKIE_SECURE': is_https,
51
+ 'SESSION_COOKIE_HTTPONLY': True,
52
+ 'SESSION_COOKIE_SAMESITE': 'Lax',
53
+ 'DEBUG': False
54
+ })
55
+
56
+ def load_high_performance_config(app, static_folder):
57
+ """加载高性能环境配置"""
58
+ is_https = os.environ.get('HTTPS', '').lower() in ('1', 'true', 'on')
59
+
60
+ app.config.update({
61
+ 'SECRET_KEY': os.environ.get('SECRET_KEY') or 'flashexam-hp-key-2024',
62
+ 'UPLOAD_FOLDER': os.path.join(static_folder, 'uploads'),
63
+ 'MAX_CONTENT_LENGTH': 64 * 1024 * 1024,
64
+ 'TEMPLATES_AUTO_RELOAD': False,
65
+ 'JSON_AS_ASCII': False,
66
+ 'SEND_FILE_MAX_AGE_DEFAULT': 86400,
67
+ 'SESSION_COOKIE_SECURE': is_https,
68
+ 'SESSION_COOKIE_HTTPONLY': True,
69
+ 'SESSION_COOKIE_SAMESITE': 'Lax',
70
+ 'PERMANENT_SESSION_LIFETIME': timedelta(hours=8),
71
+ 'DEBUG': False
72
+ })
73
+
74
+ def create_app(config_name='development'):
75
+ """
76
+ Flask应用工厂函数
77
+ 支持多种配置模式:development, production, high-performance
78
+ """
79
+ template_folder = os.path.join(os.path.dirname(__file__), 'templates')
80
+ static_folder = os.path.join(os.path.dirname(__file__), 'static')
81
+
82
+ app = Flask(__name__,
83
+ template_folder=template_folder,
84
+ static_folder=static_folder)
85
+
86
+ if config_name == 'production':
87
+ load_production_config(app, static_folder)
88
+ elif config_name == 'high-performance':
89
+ load_high_performance_config(app, static_folder)
90
+ else:
91
+ load_development_config(app, static_folder)
92
+
93
+ app.config['SESSION_TYPE'] = 'filesystem'
94
+ app.config['SESSION_PERMANENT'] = True
95
+ if 'PERMANENT_SESSION_LIFETIME' not in app.config:
96
+ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24)
97
+ app.config['SESSION_USE_SIGNER'] = True
98
+ app.config['SESSION_KEY_PREFIX'] = 'flashexam:'
99
+ app.config['SESSION_FILE_DIR'] = os.path.join(os.path.dirname(__file__), 'storage', 'sessions')
100
+ app.config['DATABASE_PATH'] = os.path.join(os.path.dirname(__file__), 'storage', 'exam_database.db')
101
+
102
+ Session(app)
103
+
104
+ login_manager = LoginManager()
105
+ login_manager.init_app(app)
106
+ login_manager.login_view = 'auth.login'
107
+ login_manager.login_message = '请先登录系统'
108
+ login_manager.login_message_category = 'warning'
109
+
110
+ app.jinja_env.globals['hasattr'] = hasattr
111
+
112
+ @app.before_request
113
+ def before_request():
114
+ global _request_count, _active_connections
115
+ _request_count += 1
116
+ _active_connections += 1
117
+
118
+ @app.after_request
119
+ def after_request(response):
120
+ global _active_connections
121
+ _active_connections = max(0, _active_connections - 1)
122
+ return response
123
+
124
+ @app.template_filter('from_json')
125
+ def from_json_filter(value):
126
+ try:
127
+ if isinstance(value, str):
128
+ return json.loads(value)
129
+ return value
130
+ except (json.JSONDecodeError, TypeError):
131
+ return []
132
+
133
+ @app.template_filter('rjust')
134
+ def rjust_filter(value, width, fillchar=' '):
135
+ try:
136
+ return str(value).rjust(int(width), str(fillchar))
137
+ except (ValueError, TypeError):
138
+ return str(value)
139
+
140
+ @app.template_filter('ljust')
141
+ def ljust_filter(value, width, fillchar=' '):
142
+ try:
143
+ return str(value).ljust(int(width), str(fillchar))
144
+ except (ValueError, TypeError):
145
+ return str(value)
146
+
147
+ @app.template_filter('zfill')
148
+ def zfill_filter(value, width):
149
+ try:
150
+ return str(value).zfill(int(width))
151
+ except (ValueError, TypeError):
152
+ return str(value)
153
+
154
+ @login_manager.user_loader
155
+ def load_user(user_id):
156
+ from .user import User
157
+ return User.get_by_id(user_id)
158
+
159
+ from .auth import auth_bp
160
+ from .teacher import teacher_bp
161
+ from .student import student_bp
162
+ from .api import api_bp
163
+ from .main import main_bp
164
+ from .admin import admin_bp
165
+
166
+ app.register_blueprint(auth_bp, url_prefix='/auth')
167
+ app.register_blueprint(teacher_bp, url_prefix='/teacher')
168
+ app.register_blueprint(student_bp, url_prefix='/student')
169
+ app.register_blueprint(api_bp, url_prefix='/api')
170
+ app.register_blueprint(main_bp)
171
+ app.register_blueprint(admin_bp, url_prefix='/admin')
172
+
173
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
174
+ os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
175
+
176
+ @app.errorhandler(404)
177
+ def not_found_error(error):
178
+ try:
179
+ from flask import render_template
180
+ return render_template('errors/404.html'), 404
181
+ except:
182
+ return '<h1>404 - 页面不存在</h1><p>抱歉,您访问的页面不存在。</p><a href="/">返回首页</a>', 404
183
+
184
+ @app.errorhandler(500)
185
+ def internal_error(error):
186
+ try:
187
+ from flask import render_template
188
+ return render_template('errors/500.html'), 500
189
+ except:
190
+ return '<h1>500 - 服务器错误</h1><p>抱歉,服务器遇到了错误。</p><a href="/">返回首页</a>', 500
191
+
192
+ print("[SUCCESS] FlashExam 初始化完成")
193
+ return app
194
+
195
+ __all__ = [
196
+ "__version__",
197
+ "create_app",
198
+ ]
flashexam/admin.py ADDED
@@ -0,0 +1,198 @@
1
+ """
2
+ 管理员管理模块
3
+ 提供教师账户管理和系统管理功能
4
+ """
5
+
6
+ from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
7
+ from flask_login import login_required, current_user
8
+ import os
9
+ from datetime import datetime
10
+
11
+ from .database import Database
12
+
13
+ admin_bp = Blueprint('admin', __name__)
14
+
15
+ def require_admin():
16
+ """检查是否为管理员(教师角色)"""
17
+ if not current_user.is_authenticated or not current_user.is_teacher():
18
+ return False
19
+ return True
20
+
21
+ @admin_bp.route('/')
22
+ @login_required
23
+ def index():
24
+ """管理面板首页"""
25
+ if not require_admin():
26
+ flash('需要管理员权限', 'error')
27
+ return redirect(url_for('auth.login'))
28
+
29
+ db = Database()
30
+
31
+ stats = {
32
+ 'total_students': len(db.get_all_students()),
33
+ 'total_exams': len(db.get_exams()),
34
+ 'active_exams': len([e for e in db.get_exams() if e.is_active]),
35
+ 'total_questions': len(db.get_questions())
36
+ }
37
+
38
+ return render_template('admin/index.html', stats=stats)
39
+
40
+ @admin_bp.route('/teachers')
41
+ @login_required
42
+ def teachers():
43
+ """教师账户列表"""
44
+ if not require_admin():
45
+ return jsonify({'success': False, 'message': '需要管理员权限'}), 403
46
+
47
+ db = Database()
48
+
49
+ return render_template('admin/teachers.html')
50
+
51
+ @admin_bp.route('/teachers/add', methods=['POST'])
52
+ @login_required
53
+ def add_teacher():
54
+ """添加教师账户"""
55
+ if not require_admin():
56
+ return jsonify({'success': False, 'message': '需要管理员权限'}), 403
57
+
58
+ try:
59
+ data = request.get_json() if request.is_json else request.form
60
+ username = data.get('username', '').strip()
61
+ password = data.get('password', '').strip()
62
+ name = data.get('name', '').strip()
63
+ email = data.get('email', '').strip()
64
+
65
+ if not all([username, password, name]):
66
+ return jsonify({'success': False, 'message': '请填写所有必填字段'}), 400
67
+
68
+ if len(password) < 6:
69
+ return jsonify({'success': False, 'message': '密码长度至少6位'}), 400
70
+
71
+ db = Database()
72
+
73
+ if db.get_teacher_info(username):
74
+ return jsonify({'success': False, 'message': '用户名已存在'}), 400
75
+
76
+ success = db.set_teacher_password(username, password, name, email)
77
+
78
+ if success:
79
+ return jsonify({'success': True, 'message': '教师账户添加成功'})
80
+ else:
81
+ return jsonify({'success': False, 'message': '添加失败,请重试'}), 500
82
+
83
+ except Exception as e:
84
+ return jsonify({'success': False, 'message': str(e)}), 500
85
+
86
+ @admin_bp.route('/teachers/<username>/password', methods=['PUT'])
87
+ @login_required
88
+ def update_teacher_password(username):
89
+ """更新教师密码"""
90
+ if not require_admin():
91
+ return jsonify({'success': False, 'message': '需要管理员权限'}), 403
92
+
93
+ try:
94
+ data = request.get_json()
95
+ new_password = data.get('new_password', '').strip()
96
+
97
+ if not new_password:
98
+ return jsonify({'success': False, 'message': '新密码不能为空'}), 400
99
+
100
+ if len(new_password) < 6:
101
+ return jsonify({'success': False, 'message': '密码长度至少6位'}), 400
102
+
103
+ db = Database()
104
+
105
+ if not db.get_teacher_info(username):
106
+ return jsonify({'success': False, 'message': '用户不存在'}), 404
107
+
108
+ success = db.update_teacher_password(username, new_password)
109
+
110
+ if success:
111
+ return jsonify({'success': True, 'message': '密码更新成功'})
112
+ else:
113
+ return jsonify({'success': False, 'message': '更新失败,请重试'}), 500
114
+
115
+ except Exception as e:
116
+ return jsonify({'success': False, 'message': str(e)}), 500
117
+
118
+ @admin_bp.route('/students')
119
+ @login_required
120
+ def manage_students():
121
+ """学生管理页面"""
122
+ if not require_admin():
123
+ flash('需要管理员权限', 'error')
124
+ return redirect(url_for('auth.login'))
125
+
126
+ return redirect(url_for('teacher.students'))
127
+
128
+ @admin_bp.route('/settings')
129
+ @login_required
130
+ def settings():
131
+ """系统设置"""
132
+ if not require_admin():
133
+ flash('需要管理员权限', 'error')
134
+ return redirect(url_for('auth.login'))
135
+
136
+ return render_template('admin/settings.html')
137
+
138
+ @admin_bp.route('/api/teachers')
139
+ @login_required
140
+ def api_get_teachers():
141
+ """获取教师列表API"""
142
+ if not require_admin():
143
+ return jsonify({'success': False, 'message': '需要管理员权限'}), 403
144
+
145
+ db = Database()
146
+
147
+ try:
148
+ db.conn.row_factory = None
149
+ cursor = db.conn.execute('''
150
+ SELECT username, name, email, created_at
151
+ FROM teachers
152
+ ORDER BY created_at DESC
153
+ ''')
154
+ rows = cursor.fetchall()
155
+
156
+ teachers_list = []
157
+ for row in rows:
158
+ teachers_list.append({
159
+ 'username': row[0],
160
+ 'name': row[1],
161
+ 'email': row[2] or '',
162
+ 'created_at': row[3] or ''
163
+ })
164
+
165
+ return jsonify({
166
+ 'success': True,
167
+ 'teachers': teachers_list,
168
+ 'count': len(teachers_list)
169
+ })
170
+
171
+ except Exception as e:
172
+ return jsonify({'success': False, 'message': str(e)}), 500
173
+
174
+ @admin_bp.route('/api/stats')
175
+ @login_required
176
+ def api_get_stats():
177
+ """获取系统统计API"""
178
+ if not require_admin():
179
+ return jsonify({'success': False, 'message': '需要管理员权限'}), 403
180
+
181
+ db = Database()
182
+
183
+ try:
184
+ stats = {
185
+ 'total_students': len(db.get_all_students()),
186
+ 'total_exams': len(db.get_exams()),
187
+ 'active_exams': len([e for e in db.get_exams() if e.is_active]),
188
+ 'total_questions': len(db.get_questions()),
189
+ 'database_size': os.path.getsize(db.db_path) if hasattr(db, 'db_path') else 0
190
+ }
191
+
192
+ return jsonify({
193
+ 'success': True,
194
+ 'stats': stats
195
+ })
196
+
197
+ except Exception as e:
198
+ return jsonify({'success': False, 'message': str(e)}), 500