mobile app database models admin_contests admin_dashboard admin_exams admin_posts admin_teacher_applications admin_users apply_contest apply_teacher base chat contest_detail contest_list exam_result forum notifications profile themes

This commit is contained in:
2026-03-03 16:02:23 +08:00
parent 0764566742
commit 9a08140623
30 changed files with 1531 additions and 834 deletions

85
app.py
View File

@@ -554,6 +554,16 @@ def exam_list():
subject_filter = request.args.get('subject', '').strip()
query = Exam.query
# 只显示公开考试或用户自己创建的私有考试
from sqlalchemy import or_
query = query.filter(
or_(
Exam.visibility == 'public',
Exam.creator_id == user.get('id')
)
)
if subject_filter:
query = query.filter_by(subject=subject_filter)
if search_query:
@@ -1059,6 +1069,23 @@ def apply_contest():
flash('满分分数必须大于0')
return redirect(url_for('apply_contest'))
# 处理图片上传
image_url = None
if 'image' in request.files:
file = request.files['image']
if file and file.filename:
if allowed_file(file.filename):
filename = secure_filename(file.filename)
# 生成唯一文件名
ext = filename.rsplit('.', 1)[1].lower()
unique_filename = f"contest_{user_id}_{int(time.time())}.{ext}"
filepath = os.path.join(UPLOAD_FOLDER, unique_filename)
file.save(filepath)
image_url = f'/static/uploads/{unique_filename}'
else:
flash('不支持的图片格式,请上传 JPG、PNG 或 GIF 格式的图片')
return redirect(url_for('apply_contest'))
# 如果存在申请,更新它;否则创建新申请
if existing:
# 更新现有申请
@@ -1073,6 +1100,8 @@ def apply_contest():
existing.responsible_phone = responsible_phone
existing.responsible_email = responsible_email
existing.organization = organization
if image_url:
existing.image = image_url
existing.status = 'pending'
existing.applied_at = datetime.utcnow()
existing.reviewed_at = None
@@ -1092,7 +1121,8 @@ def apply_contest():
responsible_person=responsible_person,
responsible_phone=responsible_phone,
responsible_email=responsible_email,
organization=organization
organization=organization,
image=image_url
)
db.session.add(app)
flash_msg = '申请已提交,请等待管理员审核'
@@ -2501,20 +2531,26 @@ def api_create_exam():
return jsonify({'success': False, 'message': '请先登录'}), 401
data = request.get_json(force=True, silent=True)
contest_id = data.get('contest_id')
visibility = data.get('visibility', 'private') # 默认私有
# 杯赛考试:只有杯赛负责人或系统管理员可以创建
# 杯赛考试:只有杯赛负责人、老师或系统管理员可以创建
if contest_id:
contest = Contest.query.get(contest_id)
if not contest or contest.status == 'abolished':
return jsonify({'success': False, 'message': '杯赛不存在或已废止'}), 400
membership = ContestMembership.query.filter_by(
user_id=user['id'], contest_id=contest_id, role='owner').first()
user_id=user['id'], contest_id=contest_id).first()
# 检查是否是负责人、老师或管理员
if not membership and user.get('role') != 'admin':
return jsonify({'success': False, 'message': '只有杯赛负责人才能组织考试'}), 403
return jsonify({'success': False, 'message': '只有杯赛成员才能创建杯赛考试'}), 403
# 杯赛考试默认为公开(杯赛内可见)
if visibility == 'private':
visibility = 'public'
else:
# 普通考试:需要 teacher 或 admin 角色
if user.get('role') not in ('teacher', 'admin'):
return jsonify({'success': False, 'message': '无权限'}), 403
# 普通考试:所有登录用户都可以创建私有考试
# 只有 teacher 或 admin 可以创建公开考试
if visibility == 'public' and user.get('role') not in ('teacher', 'admin'):
return jsonify({'success': False, 'message': '只有教师和管理员可以创建公开考试'}), 403
title = data.get('title', '')
subject = data.get('subject', '')
@@ -2532,7 +2568,8 @@ def api_create_exam():
duration=duration,
total_score=total_score,
creator_id=user.get('id'),
contest_id=contest_id
contest_id=contest_id,
visibility=visibility
)
# 解析预定时间
if scheduled_start:
@@ -2708,6 +2745,30 @@ def api_update_exam_status(exam_id):
db.session.commit()
return jsonify({'success': True, 'message': f'试卷已{"发布" if new_status == "available" else "关闭"}'})
@app.route('/api/exams/<int:exam_id>/visibility', methods=['POST'])
@login_required
def api_update_exam_visibility(exam_id):
user = session.get('user')
exam = Exam.query.get(exam_id)
if not exam:
return jsonify({'success': False, 'message': '试卷不存在'}), 404
if exam.creator_id != user.get('id'):
return jsonify({'success': False, 'message': '只能修改自己创建的试卷'}), 403
data = request.get_json(force=True, silent=True)
new_visibility = data.get('visibility', '')
if new_visibility not in ('private', 'public'):
return jsonify({'success': False, 'message': '无效的可见性设置'}), 400
# 检查权限:只有教师和管理员可以设置为公开
if new_visibility == 'public' and user.get('role') not in ('teacher', 'admin'):
return jsonify({'success': False, 'message': '只有教师和管理员可以创建公开考试'}), 403
exam.visibility = new_visibility
db.session.commit()
return jsonify({'success': True, 'message': f'试卷已设置为{"公开" if new_visibility == "public" else "私有"}'})
@app.route('/api/exams/<int:exam_id>', methods=['DELETE'])
def api_delete_exam(exam_id):
user = session.get('user')
@@ -3229,12 +3290,20 @@ def api_search_posts():
uid = user.get('id') if user else None
data = []
for p in posts:
# 计算作者等级
author_post_count = Post.query.filter_by(author_id=p.author_id).count()
author_reply_count = Reply.query.filter_by(author_id=p.author_id).count()
author_likes = db.session.query(db.func.sum(Post.likes)).filter_by(author_id=p.author_id).scalar() or 0
author_points = author_post_count * 10 + author_reply_count * 3 + author_likes * 2
author_level = calc_level(author_points)
p_dict = {
'id': p.id,
'title': p.title,
'content': p.content[:200] + ('...' if len(p.content) > 200 else ''),
'author': p.author.name,
'author_id': p.author_id,
'author_level': author_level,
'tag': p.tag,
'is_official': p.is_official,
'pinned': p.pinned,