问答平台项目结构搭建
先创建一个配置文件config.py,后面有些配置写在这里
python
#app.py
from flask import Flask
import config
app = Flask(__name__)
#绑定配置文件
app.config.from_object(config)
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'
if __name__ == '__main__':
app.run()
python
#exts.py
#flask-sqlalchemy
from flask_sqlalchemy import SQLAlchemy
db=SQLAlchemy()
#models.py
from exts import db
class UserModel(db.Model):
pass
#app.py
from flask import Flask
import config
from exts import db
from models import UserModel
app = Flask(__name__)
#绑定配置文件
app.config.from_object(config)
#可以让你先创建,再绑定
db.init_app(app)
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'
if __name__ == '__main__':
app.run()
防止循环引用,如果不加exts.py会导致下面这种情况
加了就可以变成这样子
下面设置蓝图,用来做模块化的
再在这里新建两个py文件,一个是auth.py和授权相关的,一个是qa.py和问答相关的
python
#auth.py
from flask import Blueprint
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login")
def login():
pass
#qa.py
from flask import Blueprint
bp=Blueprint("qa",__name__,url_prefix="/")
@bp.route("/")
def index():
pass
再在app.py中导入和绑定
python
#app.py
from flask import Flask
import config
from exts import db
from models import UserModel
from blueprints.qa import bp as qa_bp
from blueprints.auth import bp as auth_bp
app = Flask(__name__)
#绑定配置文件
app.config.from_object(config)
#可以让你先创建,再绑定
db.init_app(app)
app.register_blueprint(qa_bp)
app.register_blueprint(auth_bp)
if __name__ == '__main__':
app.run()
User模型创建
先在navicat里新建数据库
python
#config.py
# 数据库的配置信息
HOSTNAME = '127.0.0.1'
PORT = '3306'
USERNAME = 'root'
PASSWORD = '123456'
DATABASE = 'zhiliaooa_course'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_DATABASE_URI = DB_URI
#model.py
from exts import db
from datetime import datetime
class UserModel(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
password = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), nullable=False)
join_time = db.Column(db.DateTime, default=datetime.now)
#app.py
from flask import Flask
import config
from exts import db
from models import UserModel
from blueprints.qa import bp as qa_bp
from blueprints.auth import bp as auth_bp
from flask_migrate import Migrate
app = Flask(__name__)
# 绑定配置文件
app.config.from_object(config)
# 可以让你先创建,再绑定
db.init_app(app)
migrate = Migrate(app, db)
app.register_blueprint(qa_bp)
app.register_blueprint(auth_bp)
if __name__ == '__main__':
app.run()
然后运行三步
flask db init(只需第一次操作一下)
flask db migrate
flask db upgrade
然后数据库刷新下就可以看到
注册页面模型渲染
我们已经准备好了html文件和其静态样式,将其放入自己的文件内
其中本来只有红框标记的这几个
register.js是为了登录自己写的
base.html是为了减少代码量,能复用父组件base.html写的
(这部分可先跳过,后面会说)
javascript
#register.js
function bindEmailCaptchaClick(){
$("#captcha-btn").click(function (event){
// $this:代表的是当前按钮的jquery对象
var $this = $(this);
// 阻止默认的事件
event.preventDefault();
var email = $("input[name='email']").val();
$.ajax({
// http://127.0.0.1:500
// /auth/captcha/email?email=xx@qq.com
url: "/auth/captcha/email?email="+email,
method: "GET",
success: function (result){
var code = result['code'];
if(code == 200){
var countdown = 5;
// 开始倒计时之前,就取消按钮的点击事件
$this.off("click");
var timer = setInterval(function (){
$this.text(countdown);
countdown -= 1;
// 倒计时结束的时候执行
if(countdown <= 0){
// 清掉定时器
clearInterval(timer);
// 将按钮的文字重新修改回来
$this.text("获取验证码");
// 重新绑定点击事件
bindEmailCaptchaClick();
}
}, 1000);
// alert("邮箱验证码发送成功!");
}else{
alert(result['message']);
}
},
fail: function (error){
console.log(error);
}
})
});
}
// 整个网页都加载完毕后再执行的
$(function (){
bindEmailCaptchaClick();
});
html
#base.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/bootstrap.4.6.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/init.css') }}">
{% block head %}{% endblock %}
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">知了问答</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">首页 <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('qa.index') }}">发布问答</a>
</li>
<li class="nav-item ml-2">
<form class="form-inline my-2 my-lg-0" method="GET" action="{{ url_for('qa.index') }}">
<input class="form-control mr-sm-2" type="search" placeholder="关键字" aria-label="Search" name="q">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
</form>
</li>
</ul>
<ul class="navbar-nav">
{% if user %}
<li class="nav-item">
<span class="nav-link">{{ user.username }}</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">退出登录</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container">
{% block body %}{% endblock %}
</div>
</body>
</html>
然后再auth.py里渲染前端页面
python
from flask import Blueprint,render_template
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login")
def login():
return "11"
@bp.route("/register")
def register():
return render_template("register.html")
Flask发送邮件功能实现
pip install flask_mail
然后准备好qq邮箱,其中一个这样子设置,是邮件的发送方
开启这个服务,我这里是之前已经开启过,如果没有开启的话开启
记住你的授权码
然后再config.py里进行邮箱配置
python
#邮箱配置
MAIL_SERVER = 'smtp.qq.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = '填入你刚刚操作的邮箱'
MAIL_PASSWORD = '填入你刚刚的授权码'
MAIL_DEFAULT_SENDER = '填入你刚刚操作的邮箱,和上面一致'
接下去进行如下操作
python
#exts.py
# flask-sqlalchemy
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
db = SQLAlchemy()
mail = Mail()
#app.py
from flask import Flask
import config
from exts import db,mail
from models import UserModel
from blueprints.qa import bp as qa_bp
from blueprints.auth import bp as auth_bp
from flask_migrate import Migrate
app = Flask(__name__)
# 绑定配置文件
app.config.from_object(config)
# 可以让你先创建,再绑定
db.init_app(app)
mail.init_app(app)
migrate = Migrate(app, db)
app.register_blueprint(qa_bp)
app.register_blueprint(auth_bp)
if __name__ == '__main__':
app.run()
去auth.py测试一下
python
from flask import Blueprint,render_template
from exts import mail
from flask_mail import Message
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login")
def login():
return "11"
@bp.route("/register")
def register():
return render_template("register.html")
@bp.route("mail/test")
def mail_test():
message=Message(subject="邮箱测试",recipients=["填入你想发送人的邮箱,自己的也行"],body="这是一条测试邮件")
mail.send(message)
return "邮件发送成功"
发送邮箱验证码功能实现(1)
python
#auth.py
import random
import string
from flask import Blueprint,render_template
from exts import mail
from flask_mail import Message
from flask import request
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login")
def login():
return "11"
@bp.route("/register")
def register():
return render_template("register.html")
@bp.route("/captcha/email")
def get_email_captcha():
#/captcha/email/<email>
#/captcha/email?email=123@qq.com
email=request.args.get("email")
#4/6:随机数组、字母、数组和字母的组合
source=string.digits*4
captcha=random.sample(source,4)
captcha="".join(captcha)
print(captcha)
return "success"
@bp.route("mail/test")
def mail_test():
message=Message(subject="邮箱测试",recipients=["123......@qq.com"],body="这是一条测试邮件")
mail.send(message)
return "邮件发送成功"
测试了一下验证码获取正常,继续完善
python
@bp.route("/captcha/email")
def get_email_captcha():
#/captcha/email/<email>
#/captcha/email?email=123@qq.com
email=request.args.get("email")
#4/6:随机数组、字母、数组和字母的组合
source=string.digits*4
captcha=random.sample(source,4)
captcha="".join(captcha)
message = Message(subject="知了传课注册验证码", recipients=[email], body=f"您的验证码是:{captcha}")
mail.send(message)
return "success"
http://127.0.0.1:5000/auth/captcha/email?email=123......@qq.com
网页中输入这段代码
现在有一个问题,如何验证用户提交的邮箱和验证码是否对应且正确
答:memcached/redis用数据库表的方式存储
发送邮箱验证码功能实现(2)
去model.py新建一个类
python
class EmailCaptchaModel(db.Model):
__tablename__ = "email_captcha"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(100), nullable=False)
captcha = db.Column(db.String(100), nullable=False)
然后终端执行如下代码
数据库里可以看到新建了一个
python
#auth.py
import random
import string
from flask import Blueprint,render_template,jsonify
from exts import mail,db
from flask_mail import Message
from flask import request
from models import EmailCaptchaModel
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login")
def login():
return "11"
@bp.route("/register")
def register():
return render_template("register.html")
@bp.route("/captcha/email")
def get_email_captcha():
#/captcha/email/<email>
#/captcha/email?email=123@qq.com
email=request.args.get("email")
#4/6:随机数组、字母、数组和字母的组合
source=string.digits*4
captcha=random.sample(source,4)
captcha="".join(captcha)
message = Message(subject="知了传课注册验证码", recipients=[email], body=f"您的验证码是:{captcha}")
mail.send(message)
# 用数据库表的方式存储
email_captcha=EmailCaptchaModel(email=email,captcha=captcha)
db.session.add(email_captcha)
db.session.commit()
#RESTful API
#{code:200/400/500,message:"",data:{}}
return jsonify({"code":200,"message":"","data":None})
@bp.route("mail/test")
def mail_test():
message=Message(subject="邮箱测试",recipients=["1065088270@qq.com"],body="这是一条测试邮件")
mail.send(message)
return "邮件发送成功"
发送邮箱验证码功能实现(3)
javascript
#register.js
function bindEmailCaptchaClick(){
$("#captcha-btn").click(function (event){
// $this:代表的是当前按钮的jquery对象
var $this = $(this);
// 阻止默认的事件
event.preventDefault();
var email = $("input[name='email']").val();
$.ajax({
// http://127.0.0.1:500
// /auth/captcha/email?email=xx@qq.com
url: "/auth/captcha/email?email="+email,
method: "GET",
success: function (result){
var code = result['code'];
if(code == 200){
var countdown = 5;
// 开始倒计时之前,就取消按钮的点击事件
$this.off("click");
var timer = setInterval(function (){
$this.text(countdown);
countdown -= 1;
// 倒计时结束的时候执行
if(countdown <= 0){
// 清掉定时器
clearInterval(timer);
// 将按钮的文字重新修改回来
$this.text("获取验证码");
// 重新绑定点击事件
bindEmailCaptchaClick();
}
}, 1000);
// alert("邮箱验证码发送成功!");
}else{
alert(result['message']);
}
},
fail: function (error){
console.log(error);
}
})
});
}
// 整个网页都加载完毕后再执行的
$(function (){
bindEmailCaptchaClick();
});
实现功能如下
发送邮箱验证码功能实现(4)
这部分是加入倒计时,也就是上面的内容,代码合在一块了
后端注册表单验证器实现
验证用户提交的邮箱和验证码是否对应且正确
pip install flask-wtf
python
#forms.py
import wtforms
from wtforms.validators import Email,Length,EqualTo
from models import UserModel,EmailCaptchaModel
from exts import db
# Form:主要就是用来验证前端提交的数据是否符合要求
class RegisterForm(wtforms.Form):
email=wtforms.StringField(validators=[Email(message="邮箱格式不正确")])
captcha=wtforms.StringField(validators=[Length(min=4,max=4,message="验证码长度必须为4位")])
username=wtforms.StringField(validators=[Length(min=3,max=30,message="用户名长度必须在3-30之间")])
password=wtforms.StringField(validators=[Length(min=6,max=30,message="密码长度必须在6-30之间")])
password_confirm=wtforms.StringField(validators=[EqualTo("password",message="两次密码不一致")])
# 自定义验证:
#1、邮箱是否已经被注册
def validate_email(self,field):
email=field.data
user=UserModel.query.filter_by(email=email).first()
if user:
raise wtforms.ValidationError(message="邮箱已经被注册")
# 2、验证码是否正确
def validate_captcha(self,field):
captcha=field.data
email=self.email.data
captcha_model=EmailCaptchaModel.query.filter_by(email=email).first()
if not captcha_model:
raise wtforms.ValidationError(message="邮箱或验证码错误!")
# 最好是写一个脚本,自动多长时间清除
# else:
# db.session.delete(captcha_model)
# db.session.commit()
后端注册功能完成
pip install email_validator
然后记得model.py里的password长度设置成200,因为用哈希加密后会超过原来设置的100,会报错(然后记得重新提交更新下数据库)
python
#auth.py
import random
import string
from flask import Blueprint,render_template,jsonify,redirect,url_for
from exts import mail,db
from flask_mail import Message
from flask import request
from models import EmailCaptchaModel
from werkzeug.security import generate_password_hash
from .forms import RegisterForm
from models import UserModel
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login")
def login():
return "11"
# GET:从服务器上获取数据
# POST:将客户端的数据向服务器提交
@bp.route("/register",methods=["GET","POST"])
def register():
if request.method=="GET":
return render_template("register.html")
else:
form=RegisterForm(request.form)
if form.validate():
email=form.email.data
username=form.username.data
password=form.password.data
user=UserModel(email=email,username=username,password=generate_password_hash(password))
db.session.add(user)
db.session.commit()
return redirect(url_for("auth.login"))
else:
print(form.errors)
return redirect(url_for("auth.register"))
@bp.route("/captcha/email")
def get_email_captcha():
#/captcha/email/<email>
#/captcha/email?email=123@qq.com
email=request.args.get("email")
#4/6:随机数组、字母、数组和字母的组合
source=string.digits*4
captcha=random.sample(source,4)
captcha="".join(captcha)
message = Message(subject="知了传课注册验证码", recipients=[email], body=f"您的验证码是:{captcha}")
mail.send(message)
# 用数据库表的方式存储
email_captcha=EmailCaptchaModel(email=email,captcha=captcha)
db.session.add(email_captcha)
db.session.commit()
#RESTful API
#{code:200/400/500,message:"",data:{}}
return jsonify({"code":200,"message":"","data":None})
@bp.route("mail/test")
def mail_test():
message=Message(subject="邮箱测试",recipients=["1065088270@qq.com"],body="这是一条测试邮件")
mail.send(message)
return "邮件发送成功"
登录页面模板渲染完成
就是把login.html用base.html复用,然后渲染出来,给的资料里已经操作好了
python
#app.py
@bp.route("/login")
def login():
return render_template("login.html")
登录功能后端实现
python
#forms.py新增一个类
class LoginForm(wtforms.Form):
email=wtforms.StringField(validators=[Email(message="邮箱格式不正确")])
password=wtforms.StringField(validators=[Length(min=6,max=30,message="密码长度必须在6-30之间")])
#auth.py
import random
import string
from flask import Blueprint,render_template,jsonify,redirect,url_for,session
from exts import mail,db
from flask_mail import Message
from flask import request
from models import EmailCaptchaModel
from werkzeug.security import generate_password_hash,check_password_hash
from .forms import RegisterForm,LoginForm
from models import UserModel
# /auth,后面所有的路由都是以这个开头,比如/auth/login
bp=Blueprint("auth",__name__,url_prefix="/auth")
@bp.route("/login",methods=["GET","POST"])
def login():
if request.method=="GET":
return render_template("login.html")
else:
form=LoginForm(request.form)
if form.validate():
email=form.email.data
password=form.password.data
user=UserModel.query.filter_by(email=email).first()
if not user:
print("邮箱在数据库中不存在")
return redirect(url_for("auth.login"))
if check_password_hash(user.password,password):
#cookie:
#cookie中不适合存储太多的数据,只适合存储少量的数据
#cookie一般用来存放登录授权的东西
#flask中的session,是经过加密后存储在cookie中的
session["user_id"]=user.id
return redirect("/")
else:
print("密码错误")
return redirect(url_for("auth.login"))
else:
print(form.errors)
return redirect(url_for("auth.login"))
# GET:从服务器上获取数据
# POST:将客户端的数据向服务器提交
@bp.route("/register",methods=["GET","POST"])
def register():
if request.method=="GET":
return render_template("register.html")
else:
form=RegisterForm(request.form)
if form.validate():
email=form.email.data
username=form.username.data
password=form.password.data
user=UserModel(email=email,username=username,password=generate_password_hash(password))
db.session.add(user)
db.session.commit()
return redirect(url_for("auth.login"))
else:
print(form.errors)
return redirect(url_for("auth.register"))
@bp.route("/captcha/email")
def get_email_captcha():
#/captcha/email/<email>
#/captcha/email?email=123@qq.com
email=request.args.get("email")
#4/6:随机数组、字母、数组和字母的组合
source=string.digits*4
captcha=random.sample(source,4)
captcha="".join(captcha)
message = Message(subject="知了传课注册验证码", recipients=[email], body=f"您的验证码是:{captcha}")
mail.send(message)
# 用数据库表的方式存储
email_captcha=EmailCaptchaModel(email=email,captcha=captcha)
db.session.add(email_captcha)
db.session.commit()
#RESTful API
#{code:200/400/500,message:"",data:{}}
return jsonify({"code":200,"message":"","data":None})
@bp.route("mail/test")
def mail_test():
message=Message(subject="邮箱测试",recipients=["1065088270@qq.com"],body="这是一条测试邮件")
mail.send(message)
return "邮件发送成功"
#config.py新增一个
SECRET_KEY="adaasdwedda::@" #输入什么都行
你可以通过f12进去,查看cookie存储情况
两个钩子函数
python
#app.py
from flask import Flask,session,g
import config
from exts import db,mail
from models import UserModel
from blueprints.qa import bp as qa_bp
from blueprints.auth import bp as auth_bp
from flask_migrate import Migrate
app = Flask(__name__)
# 绑定配置文件
app.config.from_object(config)
# 可以让你先创建,再绑定
db.init_app(app)
mail.init_app(app)
migrate = Migrate(app, db)
app.register_blueprint(qa_bp)
app.register_blueprint(auth_bp)
# before_request/before_first_request/after_request
# hook
@app.before_request
def my_before_request():
user_id=session.get("user_id")
if user_id:
user=UserModel.query.get(user_id)
setattr(g,"user",user)
else:
setattr(g,"user",None)
@app.context_processor
def my_context_processor():
return {"user":g.user}
if __name__ == '__main__':
app.run()
这两段代码是使用 Python 的 Flask 框架编写的,用于处理Web应用程序的请求和上下文管理。让我分别解释它们的作用:
@app.before_request
装饰器函数:
这是一个 Flask 路由装饰器,用于注册一个在每个请求处理之前执行的函数。具体来说,这段代码定义了一个名为my_before_request
的函数,该函数会在每个请求处理之前执行。在函数内部,它首先尝试从会话(session
)中获取用户ID(user_id),这通常是通过用户登录认证过程中设置的。然后,它检查是否成功获取了用户ID。
- 如果成功获取了用户ID,它使用这个用户ID查询数据库中的用户数据(通过
UserModel.query.get(user_id)
)。然后,使用setattr(g, "user", user)
将用户对象存储在 Flask 的上下文变量g
中,以便在请求处理过程中的其他部分可以轻松地访问用户对象。- 如果未成功获取用户ID(即用户未登录或未认证),它将在上下文变量
g
中设置一个空值用户对象。这个函数的主要目的是在每个请求之前检查用户的登录状态,如果用户已登录,将用户对象存储在
g
上下文变量中,以便后续请求处理可以使用该用户对象。
@app.context_processor
装饰器函数:这是另一个 Flask 装饰器,用于注册一个上下文处理器函数,该函数可以将一些数据添加到模板上下文中,以便在渲染模板时访问这些数据。上下文处理器通常用于在所有模板中共享一些通用数据,以简化模板的代码。
- 这段代码定义了一个名为
my_context_processor
的函数,该函数不接受任何参数。在函数内部,它返回一个字典,其中包含了一个键为 "user" 的项,其值为g.user
,即在前面的my_before_request
函数中设置的用户对象。- 这意味着在渲染模板时,您可以在模板中访问
user
变量,以获取当前登录用户的相关信息。例如,您可以在模板中使用{``{ user.username }}
来显示当前登录用户的用户名。总的来说,这两段代码结合使用,用于在每个请求处理之前检查用户的登录状态,并将用户对象存储在上下文变量
g
中,然后在模板中使用上下文处理器函数将用户对象添加到模板上下文中,以便在模板中方便地访问用户信息。这对于构建需要用户认证的 Web 应用程序非常有用。
登录和非登录状态切换
前端修改部分已改好
python
#auth.py新增一个
@bp.route("/logout")
def logout():
session.clear()
return redirect("/")
#和base.html的 这部分呼应
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">退出登录</a>
</li>
发布问答页面渲染
python
#qa.py
from flask import Blueprint,request,render_template
bp=Blueprint("qa",__name__,url_prefix="/")
@bp.route("/")
def index():
return "00"
@bp.route("/qa/public",methods=['GET','POST'])
def public_qa():
if request.method=='GET':
return render_template("public_question.html")
发布问答后端功能实现
python
#models.py加入
class QuestionModel(db.Model):
__tablename__="question"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title=db.Column(db.String(100), nullable=False)
content=db.Column(db.Text,nullable=False)
create_time=db.Column(db.DateTime,default=datetime.now)
# 外键
author_id=db.Column(db.Integer,db.ForeignKey("user.id"))
author=db.relationship(UserModel,backref="questions")
flask db migrate
flask db upgrade
然后我们就可以写发布问答的逻辑了
先设置表单验证
python
#forms.py
class QuestionForm(wtforms.Form):
title=wtforms.StringField(validators=[Length(min=3,max=100,message="标题格式错误")])
content=wtforms.StringField(validators=[Length(min=3,message="内容格式错误!")])
然后写问答逻辑
python
#qa.py
from flask import Blueprint,request,render_template,g,redirect,url_for
from .forms import QuestionForm
from models import QuestionModel
from exts import db
bp=Blueprint("qa",__name__,url_prefix="/")
@bp.route("/")
def index():
return "00"
@bp.route("/qa/public",methods=['GET','POST'])
def public_question():
if request.method=='GET':
return render_template("public_question.html")
else:
form=QuestionForm(request.form)
if form.validate():
title=form.title.data
content=form.content.data
question=QuestionModel(title=title,content=content,author=g.user)
db.session.add(question)
db.session.commit()
return redirect("/")
else:
print(form.errors)
return redirect(url_for("qa.public_question"))
登录装饰器的实现
和config.py同级的地方新建一个decorators.py文件
python
#decorators.py
from functools import wraps
from flask import g,redirect,url_for
def login_required(func):
#保留func的信息
@wraps(func)
#func(a,b,c)
#func(1,2,c=3)
def inner(*args,**kwargs):
if g.user:
return func(*args,**kwargs)
else:
return redirect(url_for("auth.login"))
return inner
python
#qa.py
from flask import Blueprint,request,render_template,g,redirect,url_for
from .forms import QuestionForm
from models import QuestionModel
from exts import db
from decorators import login_required
bp=Blueprint("qa",__name__,url_prefix="/")
@bp.route("/")
def index():
return "00"
@bp.route("/qa/public",methods=['GET','POST'])
@login_required
def public_question():
if request.method=='GET':
return render_template("public_question.html")
else:
form=QuestionForm(request.form)
if form.validate():
title=form.title.data
content=form.content.data
question=QuestionModel(title=title,content=content,author=g.user)
db.session.add(question)
db.session.commit()
return redirect("/")
else:
print(form.errors)
return redirect(url_for("qa.public_question"))
首页问答列表渲染完成
python
#qa.py修改下这里
@bp.route("/")
def index():
questions=QuestionModel.query.order_by(QuestionModel.create_time.desc()).all()
return render_template("index.html",questions=questions)
问答列表页渲染
前端部分修改直接修改好了放在文件夹里的
python
#qa.py里加入
@bp.route("/qa/detail/<qa_id>")
def qa_detail(qa_id):
question=QuestionModel.query.get(qa_id)
return render_template("detail.html",question=question)
答案模型创建
python
#models.py新增
class AnswerModel(db.Model):
__tablename__="answer"
id=db.Column(db.Integer,primary_key=True,autoincrement=True)
content=db.Column(db.Text,nullable=False)
create_time=db.Column(db.DateTime,default=datetime.now)
#外键
question_id=db.Column(db.Integer,db.ForeignKey("question.id"))
author_id=db.Column(db.Integer,db.ForeignKey("user.id"))
#关系
question=db.relationship(QuestionModel,backref=db.backref("answers",order_by=create_time.desc()))
author=db.relationship(UserModel,backref="answers")
发布答案功能完成
python
#forms.py
import wtforms
from wtforms.validators import Email,Length,EqualTo,InputRequired
from models import UserModel,EmailCaptchaModel
from exts import db
# Form:主要就是用来验证前端提交的数据是否符合要求
class RegisterForm(wtforms.Form):
email=wtforms.StringField(validators=[Email(message="邮箱格式不正确")])
captcha=wtforms.StringField(validators=[Length(min=4,max=4,message="验证码长度必须为4位")])
username=wtforms.StringField(validators=[Length(min=3,max=30,message="用户名长度必须在3-30之间")])
password=wtforms.StringField(validators=[Length(min=6,max=30,message="密码长度必须在6-30之间")])
password_confirm=wtforms.StringField(validators=[EqualTo("password",message="两次密码不一致")])
# 自定义验证:
#1、邮箱是否已经被注册
def validate_email(self,field):
email=field.data
user=UserModel.query.filter_by(email=email).first()
if user:
raise wtforms.ValidationError(message="邮箱已经被注册")
# 2、验证码是否正确
def validate_captcha(self,field):
captcha=field.data
email=self.email.data
captcha_model=EmailCaptchaModel.query.filter_by(email=email).first()
if not captcha_model:
raise wtforms.ValidationError(message="邮箱或验证码错误!")
# 最好是写一个脚本,自动多长时间清除
# else:
# db.session.delete(captcha_model)
# db.session.commit()
class LoginForm(wtforms.Form):
email=wtforms.StringField(validators=[Email(message="邮箱格式不正确")])
password=wtforms.StringField(validators=[Length(min=6,max=30,message="密码长度必须在6-30之间")])
class QuestionForm(wtforms.Form):
title=wtforms.StringField(validators=[Length(min=3,max=100,message="标题格式错误")])
content=wtforms.StringField(validators=[Length(min=3,message="内容格式错误!")])
class AnswerForm(wtforms.Form):
content=wtforms.StringField(validators=[Length(min=3,message="内容格式错误!")])
question_id=wtforms.IntegerField(validators=[InputRequired(message="问题id不能为空")])
python
#qa.py
from flask import Blueprint,request,render_template,g,redirect,url_for
from .forms import QuestionForm,AnswerForm
from models import QuestionModel,AnswerModel
from exts import db
from decorators import login_required
bp=Blueprint("qa",__name__,url_prefix="/")
@bp.route("/")
def index():
questions=QuestionModel.query.order_by(QuestionModel.create_time.desc()).all()
return render_template("index.html",questions=questions)
@bp.route("/qa/public",methods=['GET','POST'])
@login_required
def public_question():
if request.method=='GET':
return render_template("public_question.html")
else:
form=QuestionForm(request.form)
if form.validate():
title=form.title.data
content=form.content.data
question=QuestionModel(title=title,content=content,author=g.user)
db.session.add(question)
db.session.commit()
return redirect("/")
else:
print(form.errors)
return redirect(url_for("qa.public_question"))
@bp.route("/qa/detail/<qa_id>")
def qa_detail(qa_id):
question=QuestionModel.query.get(qa_id)
return render_template("detail.html",question=question)
# @bp.route("/answer/public",methods=['POST'])
@bp.post("answer/public")
@login_required
def public_answer():
form=AnswerForm(request.form)
if form.validate():
content=form.content.data
question_id=form.question_id.data
answer=AnswerModel(content=content,question_id=question_id,author_id=g.user.id)
db.session.add(answer)
db.session.commit()
return redirect(url_for("qa.qa_detail",qa_id=question_id))
else:
print(form.errors)
return redirect(url_for("qa.qa_detail",qa_id=request.form.get("question_id")))
搜索功能实现
主要是前端代码的修改,主要是完善上一个步骤
python
答案列表的渲染
python
#qa.py新增
@bp.route("/search")
def search():
#/search?q=flask
#/search/<q>
#post,request.form
q=request.args.get("q")
questions=QuestionModel.query.filter(QuestionModel.title.contains(q)).all()
return render_template("index.html",questions=questions)
完结!