study_platform/app/routes.py
superlishunqin e62a101da0 0422-1010
2025-04-22 22:10:16 +08:00

290 lines
9.4 KiB
Python

from flask import Blueprint, render_template, redirect, url_for, flash, request, session, jsonify
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug.urls import url_parse
from app import db
from app.models import User
from app.forms import LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm
from app.translations import get_text
from datetime import datetime
from app.utils.email import generate_verification_code, send_verification_email, save_verification_code
from app.forms import EmailVerificationForm, RegistrationForm
import re
from flask import send_file
from PIL import Image, ImageDraw
import io
from flask import current_app as app
main_bp = Blueprint('main', __name__)
auth_bp = Blueprint('auth', __name__)
# 辅助函数
def _get_translation(key):
lang = session.get('language', 'zh')
return get_text(key, lang)
# 主页路由
@main_bp.route('/')
def index():
if not current_user.is_authenticated:
return redirect(url_for('auth.login'))
return render_template('main/index.html')
# 切换语言
@main_bp.route('/language/<lang>')
def set_language(lang):
# 确保只接受有效的语言
if lang in ['zh', 'en']:
session['language'] = lang
if current_user.is_authenticated:
current_user.language = lang
db.session.commit()
# 确保重定向回正确的页面
next_page = request.args.get('next') or request.referrer or url_for('main.index')
return redirect(next_page)
# 切换主题
@main_bp.route('/theme/<theme>')
def set_theme(theme):
if theme not in ['light', 'dark']:
theme = 'light'
session['theme'] = theme
if current_user.is_authenticated:
current_user.theme = theme
db.session.commit()
next_page = request.args.get('next') or request.referrer or url_for('main.index')
return redirect(next_page)
# 认证路由
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None or not user.check_password(form.password.data):
flash(_get_translation('invalid_credentials'))
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
# 更新最后登录时间
user.last_login = datetime.utcnow()
db.session.commit()
# 同步用户的语言和主题设置
session['language'] = user.language
session['theme'] = user.theme
flash(_get_translation('login_success'))
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('main.index')
return redirect(next_page)
# 添加对模板的函数
def _(key):
return _get_translation(key)
return render_template('auth/login.html', form=form, _=_, hide_language_switch=True, hide_theme_switch=True)
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
email_form = EmailVerificationForm()
registration_form = RegistrationForm()
# 处理发送验证码请求
if 'send_code' in request.form and email_form.validate_on_submit():
email = email_form.email.data
code = generate_verification_code()
# 保存验证码
save_verification_code(email, code)
# 发送验证码邮件
if send_verification_email(email, code):
flash(_get_translation('verification_code_sent'))
# 将邮箱保存到表单,用于下一步注册
return render_template(
'auth/register.html',
email_form=email_form,
registration_form=registration_form,
email_verified=True,
verified_email=email
)
else:
flash(_get_translation('email_send_failed'))
# 处理注册表单提交
if 'register' in request.form and registration_form.validate_on_submit():
user = User(
email=registration_form.email.data,
username=registration_form.username.data,
language=session.get('language', 'zh'),
theme=session.get('theme', 'light')
)
user.set_password(registration_form.password.data)
db.session.add(user)
db.session.commit()
flash(_get_translation('account_created'))
return redirect(url_for('auth.login'))
# 检查是否有保存的已验证邮箱
email_verified = False
verified_email = request.args.get('email', '')
if verified_email:
email_verified = True
registration_form.email.data = verified_email
# 添加对模板的函数
def _(key):
return _get_translation(key)
return render_template(
'auth/register.html',
email_form=email_form,
registration_form=registration_form,
email_verified=email_verified,
verified_email=verified_email,
_=_,
hide_language_switch=True,
hide_theme_switch=True
)
@auth_bp.route('/send_verification_code', methods=['POST'])
def send_verification_code():
try:
email = request.form.get('email')
if not email or not re.match(r'[^@]+@[^@]+\.[^@]+', email):
return jsonify({'success': False, 'message': _get_translation('invalid_email')})
# 检查邮箱是否已被注册
try:
if User.query.filter_by(email=email).first():
return jsonify({'success': False, 'message': _get_translation('email_already_registered')})
except Exception as e:
app.logger.error(f"数据库查询错误: {str(e)}")
return jsonify({'success': False, 'message': _get_translation('server_error')})
# 生成并保存验证码
try:
code = generate_verification_code()
save_verification_code(email, code)
except Exception as e:
app.logger.error(f"验证码保存错误: {str(e)}")
return jsonify({'success': False, 'message': _get_translation('server_error')})
# 发送验证码邮件
try:
if send_verification_email(email, code):
return jsonify({'success': True, 'message': _get_translation('verification_code_sent')})
else:
return jsonify({'success': False, 'message': _get_translation('email_send_failed')})
except Exception as e:
app.logger.error(f"邮件发送错误: {str(e)}")
return jsonify({'success': False, 'message': _get_translation('email_send_failed')})
except Exception as e:
app.logger.error(f"验证码发送路由错误: {str(e)}")
return jsonify({'success': False, 'message': _get_translation('server_error')})
@auth_bp.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = ResetPasswordRequestForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
token = user.generate_reset_token()
db.session.commit()
# 这里应该发送邮件,但目前只是模拟
# 实际项目中需要集成邮件发送功能
flash(_get_translation('password_reset_sent'))
# 用于演示,实际项目中应该通过邮件发送这个链接
reset_url = url_for('auth.reset_password', token=token, _external=True)
print(f"Password reset URL: {reset_url}")
return redirect(url_for('auth.login'))
return render_template('auth/reset_password_request.html', form=form)
@auth_bp.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
if current_user.is_authenticated:
return redirect(url_for('main.index'))
user = User.query.filter_by(reset_token=token).first()
if not user:
flash(_get_translation('invalid_reset_token'))
return redirect(url_for('auth.login'))
form = ResetPasswordForm()
if form.validate_on_submit():
user.set_password(form.password.data)
user.reset_token = None
db.session.commit()
flash(_get_translation('password_reset_success'))
return redirect(url_for('auth.login'))
return render_template('auth/reset_password.html', form=form)
@auth_bp.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
@main_bp.route('/api/placeholder/<width>/<height>')
def placeholder(width, height):
"""生成并返回指定尺寸的占位图像"""
try:
width = int(width)
height = int(height)
except ValueError:
width = 100
height = 100
# 限制最大尺寸
width = min(width, 1200)
height = min(height, 1200)
# 创建简单的占位图
img = Image.new('RGB', (width, height), color=(220, 220, 220))
d = ImageDraw.Draw(img)
# 绘制边框
d.rectangle([0, 0, width - 1, height - 1], outline=(200, 200, 200))
# 绘制文本
text = f"{width}x{height}"
# 将图像转换为字节流
img_byte_arr = io.BytesIO()
img.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
return send_file(img_byte_arr, mimetype='image/png')