flask实战(问答平台)

课程链接

问答平台项目结构搭建

先创建一个配置文件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()

再新建一个exts.py和models.py

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 protected]
      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 protected]
    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=["[email protected]"],body="这是一条测试邮件")
    mail.send(message)
    return "邮件发送成功"

测试了一下验证码获取正常,继续完善

python 复制代码
@bp.route("/captcha/email")
def get_email_captcha():
    #/captcha/email/<email>
    #/captcha/[email protected]
    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 protected]网页中输入这段代码

现在有一个问题,如何验证用户提交的邮箱和验证码是否对应且正确

答: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 protected]
    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=["[email protected]"],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 protected]
      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

在blueprints文件夹里新建forms.py

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 protected]
    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=["[email protected]"],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 protected]
    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=["[email protected]"],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应用程序的请求和上下文管理。让我分别解释它们的作用:

  1. @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 上下文变量中,以便后续请求处理可以使用该用户对象。

  1. @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)

完结!

相关推荐
fish_study_csdn5 小时前
pytest 技术总结
开发语言·python·pytest
柏油5 小时前
MySQL InnoDB 行锁
数据库·后端·mysql
咖啡调调。5 小时前
使用Django框架表单
后端·python·django
BO_S__5 小时前
python调用ffmpeg对截取视频片段,可批量处理
python·ffmpeg·音视频
白泽talk5 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务
摆烂工程师5 小时前
全网最详细的5分钟快速申请一个国际 “edu教育邮箱” 的保姆级教程!
前端·后端·程序员
一只叫煤球的猫5 小时前
你真的会用 return 吗?—— 11个值得借鉴的 return 写法
java·后端·代码规范
就叫飞六吧6 小时前
如何判断你的PyTorch是GPU版还是CPU版?
人工智能·pytorch·python
Asthenia04126 小时前
HTTP调用超时与重试问题分析
后端
颇有几分姿色6 小时前
Spring Boot 读取配置文件的几种方式
java·spring boot·后端