Flask快速入门和问答项目源码

Flask基础入门

源码:


目录


正文内容如下:

1.安装环境

python 复制代码
python - m venv .venv
pip install Flask

创建第一个实例

python 复制代码
from flask import Flask

# __name__:代表当前app.py这个模块
# 1.以后出现bug,他可以帮助我们快速定位
# 2.对于寻找模板文件,有一个相对路径
# 使用Flak类创建一个app对象
app = Flask(__name__)

@app.route('/')
def hello_world():  # put application's code here
    return 'Hello World!'

if __name__ == '__main__':
    app.run() 

2.【debug、host、port】

python 复制代码
from flask import Flask
# 使用Flak类创建一个app对象
app = Flask(__name__)

@app.route('/')
def hello_world():  # put application's code here
    return 'Hello World!'

if __name__ == '__main__':
    app.run(debug=True,host='0.0.0.0',port=8000)  # 开启Debug模式、修改地址host、访问端口号port

3.【路由params和query】

python 复制代码
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/user/<username>')
def user(username):
    return f"用户名是{username}"


# 年月日参数,http://127.0.0.1:5000/date/2025/05/16
@app.route('/date/<int:year>/<int:month>/<int:day>')
def date(year, month, day):
    return f"今天日期是{year}年{month}月{day}日"


# /book/list?page=1&size=10
@app.route('/book/list')
def book_list():
    # arguments = {'page': 1, 'size': 10}
    # request.args:类字典类型
    page = request.args.get('page', 1, type=int)
    size = request.args.get('size', 10, type=int)
    return 'book_list/page=%s,size=%s' % (page, size)

4.【模板】

python 复制代码
from flask import Flask, request, render_template

app = Flask(__name__)
app.debug = True  # 可选:显式设置 debug 模式

@app.route('/detail/')
def BookDetail():
    return render_template("book_detail.html", title='Flask入门', author='李华',context={'title': 'Flask高级', 'author': '王明'})

目录结构,需要创建templates

cmd 复制代码
├─static
├─templates
│  └─book_detail.html
├─app.py

5.【静态文件】

目录结构

cmd 复制代码
├─static
│  └─css
│  └─js
│  └─img
├─templates
│  └─static_img.html
├─app.py

app.py

python 复制代码
from flask import Flask, request, render_template

app = Flask(__name__)
app.debug = True  # 可选:显式设置 debug 模式

@app.route('/img/')
def img():
    return render_template('static_img.html')

if __name__ == '__main__':
    app.run(debug=True)  # 开启Debug模式

static_img.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <img src="{{ url_for('static', filename='img/1.jpg') }}" alt="">
</body>
</html>

结果如下:

当然这是一个图片的案例,如果想引入cssjs等或者其他文件内容的话,将filename='img/1.jpg'的值替换一下就可以

其中,模板中也有过滤器的使用,可以自行网上查找,和django类似

6.【数据库连接】

6.1.安装模块

需要安装两个模块 flask_sqlalchemypymysql

cmd 复制代码
pip install flask_sqlalchemy
pip install pymysql    

6.2.创建数据库并测试连接

创建数据库

cmd 复制代码
 create database flask_test default charset="utf8";

测试连接 ---- app.py

python 复制代码
from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.debug = True  # 可选:显式设置 debug 模式

# 配置数据库
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = '123456'
DATABASE = 'flask_test'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

db = SQLAlchemy(app)
# 将数据库操作放在应用上下文中
with app.app_context():
    with db.engine.connect() as conn:
        print("连接成功")

# 创建视图
@app.route('/')
def hello_world():  # put application's code here
    return 'Hello World!'

if __name__ == '__main__':

    app.run(debug=True)  # 开启Debug模式

注意:不要上下文缺失with app.app_context():db.engine 需要应用上下文来读取配置(如数据库URI),但你的代码在应用启动前或上下文外调用了它

6.3.创建数据表

python 复制代码
from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.debug = True  # 可选:显式设置 debug 模式

# 配置数据库
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = '123456'
DATABASE = 'flask_test'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD,HOSTNAME, PORT, DATABASE)

db = SQLAlchemy(app)

# 创建数据表
class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), unique=True, nullable=False)

with app.app_context():
    db.create_all()

6.4.ORM

数据

python 复制代码
from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.debug = True  # 可选:显式设置 debug 模式

# 配置数据库
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = '123456'
DATABASE = 'flask_test'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD,
                                                                                             HOSTNAME, PORT, DATABASE)

db = SQLAlchemy(app)

with app.app_context():
    db.create_all()

# 创建数据表
class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), unique=True, nullable=False)


with app.app_context():
    db.create_all()


if __name__ == '__main__':
    # app.run(debug=True,host='0.0.0.0',port=8000)  # 开启Debug模式、修改地址host、访问端口号port
    app.run(debug=True)  # 开启Debug模式
py 复制代码
@app.route('/user/add/')
def add_user():
    # 1.创建ORM对象
    user = User(username='admin', password='123456')
    # 2.添加到会话
    db.session.add(user)
    # 3.提交事务
    db.session.commit()
    return "添加成功"
py 复制代码
@app.route('/user/delete/')
def delete_user():
    # 1.查询用户
    user = User.query.get(1)
    # 2.删除用户
    db.session.delete(user)
    # 3.提交事务
    db.session.commit()
    return "删除成功"
py 复制代码
@app.route('/user/update/')
def update_user():
    # 1.查询用户
    user = User.query.get(1)
    # 2.更新用户信息
    user.password = '666666'
    # 3.提交事务
    db.session.commit()
    return "更新成功"
py 复制代码
@app.route('/user/query/')
def query_user():
    # 1.查询所有用户,get单个查找
    user = User.query.get(1)
    print(user.id, user.username, user.password)
    #  filter过滤查找,可以批量查找
    users = User.query.filter().all()
    for u in users:
        print(u.id, u.username, u.password)
    return "查询成功"

6.5.ORM模型外键

  • 第一种方式back_populates

    py 复制代码
    class User(db.Model):
        __tablename__ = 'user'
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        username = db.Column(db.String(80), unique=True, nullable=False)
        password = db.Column(db.String(120), unique=True, nullable=False)
        # 添加外键文章
        articles =db.relationship('Article', back_populates='author')
    
    class Article(db.Model):
        __tablename__ = 'article'
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        title = db.Column(db.String(80), unique=True, nullable=False)
        content = db.Column(db.Text, unique=True, nullable=False)
    
        # 添加外键作者
        author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
        # 添加关系属性 ,如果使用的是back_populates的话,那么在User类中也要添加articles属性
        author = db.relationship('User', back_populates='articles')
  • 第二种backref

python 复制代码
class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), unique=True, nullable=False)

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(200), unique=True, nullable=False)
    content = db.Column(db.Text, unique=True, nullable=False)

    # 添加外键作者
    author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    # 添加关系属性 ,如果使用的是back_populates的话,那么在User类中也要添加articles属性
    author = db.relationship('User', backref='articles')

文章的

py 复制代码
@app.route('/article/add/')
def add_article():
    article = Article(title='Flask框架基础', content='教大家如何快速掌握Flask')
    article.author = User.query.get(1)
    # article2 = Article(title='Django框架基础',content='教大家如何快速掌握Django',author_id=1)
    article2 = Article(title='Django框架基础', content='教大家如何快速掌握Django', author_id=1)
    #  一次性添加多个数据使用add_all,传入的内容是一个列表
    db.session.add_all([article, article2])
    db.session.commit()
    return "添加成功"


@app.route('/article/query/')
def query_article():
    # 通过user表种的userid获取所有的文章
    user = User.query.get(1)
    for a in user.articles:
        print(a.id, a.title, a.content, a.author.username)
    #  通过article表获取查询文章
    articles = Article.query.filter().all()
    for a in articles:
        print(a.id, a.title, a.content, a.author.username)
    return "查询成功"

6.6.映射和迁移

安装模块flask-migrate

cmd 复制代码
pip install flask-migrate 

之前使用的是

py 复制代码
# 练习创建数据表使用
with app.app_context():
    db.create_all()

在开发过程中使用映射

py 复制代码
from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.debug = True  # 可选:显式设置 debug 模式

# 配置数据库
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = '123456'
DATABASE = 'flask_test'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD,
                                                                                             HOSTNAME, PORT, DATABASE)

db = SQLAlchemy(app)
migrate = Migrate(app, db) // 创建就行

命令使用,

  • 第一步,生成环境,类似git的init

    python 复制代码
    flask db init

    会生成一个migrations文件

    cmd 复制代码
    ├─migrations
    │  └─versions
    │  └─alembic.ini
    │  └─env.py
    │  └─README
    │  └─script.py.mako
    ├─static
    ├─templates
  • 第二步,识别ORM模型的改变,生成迁移脚本

    py 复制代码
    flask db migrate
  • 第三步,运行迁移脚本,同步到数据库中

    py 复制代码
    flask db upgrade

7.【问答平台项目】

7.1.环境准备

7.1.1 安装python环境

创建项目,其实就是一个文件夹的创建,进入文件夹后,需要配置下虚拟环境,和安装Flask模块

python 复制代码
python - m venv .venv

进入虚拟环境

cmd 复制代码
.venv\Scripts\activate

安装依赖库

cmd 复制代码
pip install Flask
# 数据库连接
pip install flask_sqlalchemy
pip install pymysql   
# 数据库迁移和映射
pip install flask-migrate 
# 邮箱模块
pip install flask-mail
# 表单验证
pip install flask-wtf
# 邮箱验证
pip install email_validator
pip install cryptography 
7.1.2.创建文件
cmd 复制代码
├─static        # 静态资源
├─templates     # 模板
├─app.py        # 根
├─config.py     # 配置文件
├─exts.py		# 数据库配置信息
├─models.py		# 数据库表配置
├─blueprints    # 视图函数 & 后端请求
│  └─auth.py    # 视图函数
│  └─qa.py		# 视图函数
│  └─forms.py   # 表单验证模块
├─decorators.py # 拓展:装饰器,拦截功能
7.1.3.绑定配置文件
  • config.py配置

    python 复制代码
    # 配置数据库
    HOSTNAME = '127.0.0.1'
    PORT = 3306
    USERNAME = 'root'
    PASSWORD = '123456'
    DATABASE = 'flask_test'
    DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
    SQLALCHEMY_DATABASE_URI = DB_URI
    
    # 发送邮箱配置
    # 授权码:ydevxpkfezjydddd ,授权码不是邮箱密码
    MAIL_SERVER = 'smtp.qq.com'
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = '你的邮箱'
    MAIL_PASSWORD = '授权码'
    MAIL_DEFAULT_SENDER = MAIL_USERNAME

    使用qq邮箱作为服务器,打开设置

找到账号下的POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,开启服务,并获取授权码

  • exts.py

    py 复制代码
    # 这个文件存在的意义就是为了解决循环引用的问题
    from flask_sqlalchemy import SQLAlchemy
    # 导入邮箱实例模块
    from flask_mail import Mail
    # 创建实例
    db = SQLAlchemy()
    mail = Mail()
  • app.py

    将配置的信息绑定app.py

    py 复制代码
    # 导入 flask模块
    from flask import Flask
    # 导入 配置文件
    import config
    # 导入 数据库实例和发送邮箱实例
    from exts import db, mail
    # 导入 视图函数,并使用别名
    from blueprints.auth import bp as auth_bp
    from blueprints.qa import bp as qa_bp
    # 导入 迁移和映射数据模块
    from flask_migrate import Migrate
    
    # 实例Flask对象
    app = Flask(__name__)
    app.debug = True  # 可选:显式设置 debug 模式,在开发过程中开启即可,上线时删掉这行或者设置为False
    
    # 绑定config配置文件
    app.config.from_object(config)
    
    # 初始化数据库db且绑定app
    db.init_app(app)
    # 初始化邮箱mail且绑定app
    mail.init_app(app)
    
    # 注册蓝图(相当于django中的视图)
    app.register_blueprint(auth_bp)
    app.register_blueprint(qa_bp)
    
    # 初始化数据库迁移(映射数据库)
    migrate = Migrate(app, db)
    
    if __name__ == '__main__':
        app.run()  

7.2.创建数据库

使用命令行创建数据库,打开cmd

py 复制代码
pymysql -u用户名 -p密码

创建utf8编码的数据库

python 复制代码
create database flask_test default charset="utf8";

进入models.py创建数据库表,没有这个文件的话创建一下

py 复制代码
# 导入已经实例化的对象
from exts import db
# 导入python内置的时间模块
from datetime import datetime


class UserModel(db.Model):
    """用户表"""
    __tablename__ = 'user'
    # id:主键primary_key=True,自增:autoincrement=True
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(20), nullable=False)
    password = db.Column(db.String(200), nullable=False)
    email = db.Column(db.String(100), nullable=False, unique=True)
    join_time = db.Column(db.DateTime, default=datetime.now())


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)


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'))  # 外键
    # 可以通过questions取到UserModel的username,如:question.author.username 
    author = db.relationship('UserModel', backref='questions')  # 反向引用


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=db.backref('answers'))

在创建数据表的时候需要注意下外键的关系,外键的方向引用的用法,以及关联的数据表,当然,创建数据表还需要执行以下命令:

  • 第二步,创建并初始化文件【执行一次即可】
py 复制代码
flask db init

会在根目录下生成一个migrations的迁移文件

cmd 复制代码
├─migrations
│  └─versions
│  └─alembic.ini
│  └─env.py
│  └─README
│  └─script.py.mako
  • 第二步,识别ORM模型的改变,生成迁移脚本
py 复制代码
flask db migrate
  • 第三步,运行迁移脚本,同步到数据库中

    flask db upgrade

注意:如果后续需要修改models.py中的内容,只需执行第二步第三步即可,在Flask框架中初始化文件只执行一次

7.3.创建蓝图

蓝图其实就是一个python程序包,命名为:blueprints,在蓝图中创建两个视图文件(auth.py、qa.py)和一个表单验证文件(forms.py),目录结构为:

py 复制代码
├─blueprints     # 视图函数 & 后端请求
│  └─__init__.py 
│  └─auth.py     # 视图函数
│  └─qa.py		 # 视图函数
│  └─forms.py    # 表单验证模块
7.3.1.模板导航栏

templates中创建base.html,并写下以下内容,简单的导航栏就完成了,所有模板都是继承自这里

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='/bootstrap-3.4.1-dist/css/bootstrap.min.css') }}">
    <script type="text/javascript" src="{{ url_for('static', filename='/jquery/jquery-3.7.1.min.js') }}"></script>
    <script type="text/javascript"
            src="{{ url_for('static', filename='/bootstrap-3.4.1-dist/js/bootstrap.min.js') }}"></script>

    <style>
        body {
            padding-top: 20px;
            padding-bottom: 20px;
        }

        {% block css %}{% endblock %}
    </style>
</head>
<body>
<div class="container">
    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
                        aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Flask问答</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="/">首页</a></li>
                    <li><a href="{{ url_for('qa.public') }}">发布问答</a></li>
                </ul>
                <form class="navbar-form navbar-left" method="GET" action="{{ url_for('qa.search') }}">
                    <div class="form-group">
                        <input type="search" class="form-control" name="search" placeholder="Search">
                    </div>
                    <button type="submit" class="btn btn-default">搜索</button>
                </form>
                <ul class="nav navbar-nav navbar-right">
                    {% if user %}
                        <li><a href="{{ url_for('auth.logout') }}">退出登录</a></li>
                    {% else %}
                        <li><a href="{{ url_for('auth.login') }}">登录</a></li>
                        <li><a href="{{ url_for('auth.register') }}">注册</a></li>
                    {% endif %}
                </ul>
            </div><!--/.nav-collapse -->
        </div><!--/.container-fluid -->
    </nav>
    {% block content %}
    {% endblock %}
</div>
</body>
{% block js %}{% endblock %}
</html>
7.3.2.用户注册和登录

auth.py--用户注册,注册视图中有一个邮箱验证码的功能,在写用户注册的时候可以使用一个视图来验证邮箱验证码发送是否成功:

在发送之前还是需要环境准备中准备下邮箱服务器的配置,在config

py 复制代码
from flask import Blueprint, render_template, request, redirect, url_for, session, jsonify
from exts import mail
from flask_mail import Message

bp = Blueprint("auth", __name__, url_prefix="/auth")

@bp.route("/mail/test/")
def send_mail():
    message = Message(subject="Hello", sender="服务器邮箱", recipients=["目的地邮箱"],
                      body="This is a test email.")
    mail.send(message)
    return "Mail sent successfully!"

在实现注册功能的时候,需要准备下前端视图模板

🌟注册模板

register.html

html 复制代码
{% extends "base.html" %}
{% block title %}注册{% endblock %}
{% block css %}
    .form-signin {
    max-width: 400px;
    padding: 15px;
    margin: 0 auto;
    }
{% endblock %}

{% block content %}
    <div class="bs-example" data-example-id="panel-with-list-group">
        <!-- Default panel contents -->
        <div class="panel-heading"><h3 style="text-align: center">注册</h3></div>
        <div class="panel-body">
            <form class="form-signin" method="post">
                <div class="form-group">
                    <label for="email">邮箱:</label>
                    <input type="email" class="form-control" name="email" id="email" placeholder="Email">
                </div>
                <div class="form-group">
                    <label for="captcha">验证码:</label>
                    <div class="input-group">
                        <input type="text" id="captcha" name="captcha" class="form-control" placeholder="captcha">
                        <span class="input-group-btn">
            <button class="btn btn-primary" id="get_captcha" type="button">获取验证码</button>
          </span>
                    </div>
                </div>
                <div class="form-group">
                    <label for="username">用户名:</label>
                    <input type="text" name="username" class="form-control" id="username" placeholder="username">
                </div>
                <div class="form-group">
                    <label for="password">密码:</label>
                    <input type="password" name="password" class="form-control" id="password" placeholder="password">
                </div>
                <div class="form-group">
                    <label for="password_confirm">确认密码:</label>
                    <input type="password" name="password_confirm" class="form-control" id="password_confirm"
                           placeholder="password_confirm">
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </div>
    </div>


{% endblock %}
{% block js %}
    <script type="text/javascript">

        $(function () {
            bindEmailCaptchaClick()
        })

        function bindEmailCaptchaClick() {
            $("#get_captcha").click(function (event) {
                var $this = $(this)
                // 阻止表单提交
                event.preventDefault()
                const email = $("input[name='email']").val()
                $.ajax({
                    url: "/auth/captcha/email?email=" + email, // 请求地址,
                    method: "POST",
                    success: function (result) {
                        var code = result.code
                        if (code === 200) {
                            var countdown = 60
                            // 开始倒计时之前,就取消按钮的点击事件
                            $this.off("click")
                            var timer = setInterval(function () {
                                $this.text(countdown + "s后重试")
                                countdown--
                                // 倒计时结束的时候执行
                                if (countdown === 0) {
                                    clearInterval(timer)
                                    $this.text("获取验证码")
                                    bindEmailCaptchaClick()
                                }
                            }, 1000)
                        }
                    },
                    fail: function (error) {
                        console.log(error)
                    }
                })
            })
        }
    </script>
{% endblock %}
🌟表单验证

forms.py

py 复制代码
import wtforms
from wtforms.validators import Email, Length, EqualTo,InputRequired
from models import UserModel, EmailCaptchaModel
from exts import db


class RegisterForm(wtforms.Form):
    email = wtforms.StringField(validators=[Email(message='邮箱格式错误')])
    captcha = wtforms.StringField(validators=[Length(min=4, max=4, message='验证码格式错误')])
    username = wtforms.StringField(validators=[Length(min=3, max=20, message='用户名格式错误')])
    password = wtforms.StringField(validators=[Length(min=6, max=20, message='密码格式错误')])
    password_confirm = wtforms.StringField(validators=[EqualTo('password', message='两次输入的密码不一致')])

    # 自定义验证:
    # 邮箱是否已经被注册
    def validate_email(self, field):
        email = field.data
        user = UserModel.query.filter_by(email=email).first()
        if user:
            raise wtforms.ValidationError(message='邮箱已经被注册')

    # 验证码是否正确
    def validate_captcha(self, field):
        captcha = field.data
        email = self.email.data
        captcha_model = EmailCaptchaModel.query.filter_by(email=email, captcha=captcha).first()
        if not captcha_model:
            raise wtforms.ValidationError(message='邮箱验证码错误')

class LoginForm(wtforms.Form):
    email = wtforms.StringField(validators=[Email(message='邮箱格式错误')])
    password = wtforms.StringField(validators=[Length(min=6, max=20, message='密码格式错误')])
🌟登录模板

用户登录模板login.html

html 复制代码
{% extends "base.html" %}
{% block title %}注册{% endblock %}
{% block css %}
    .form-signin {
    max-width: 400px;
    padding: 15px;
    margin: 0 auto;
    }
{% endblock %}

{% block content %}


    <div class="bs-example" data-example-id="panel-with-list-group">
        <!-- Default panel contents -->
        <div class="panel-heading"><h3 style="text-align: center">登录</h3></div>
        <div class="panel-body">
            <form class="form-signin" method="post">
                <div class="form-group">
                    <label for="email">邮箱:</label>
                    <input type="email" class="form-control" name="email" id="email" placeholder="Email">
                </div>
                <div class="form-group">
                    <label for="password">密码:</label>
                    <input type="password" name="password" class="form-control" id="password"
                           placeholder="password">
                </div>
                <div class="checkbox">
                    <label>
                        <input type="checkbox"> Check me out
                    </label>
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </div>
    </div>
{% endblock %}
🌟后端实现

实现用户登录/注册功能,auth.py

py 复制代码
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify
# 导入数据和邮箱实例对象
from exts import mail, db
# 导入邮箱信息发送模块
from flask_mail import Message
# python内置库
import string
import random
# 导入两个数据表
from models import EmailCaptchaModel, UserModel
# 表单验证模块
from .forms import RegisterForm, LoginForm
# 写入数据库中的密码加密
from werkzeug.security import generate_password_hash, check_password_hash

bp = Blueprint("auth", __name__, url_prefix="/auth")

# 只接受两种请求,分别是GET和POST
@bp.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        # 如果是GET请求,则是返回模板
        return render_template("login.html")
    else:
        form = LoginForm(request.form)
        if form.validate():
            # 获取到表单填写的内容,注意前端的input的name属性是和这里的form.name.data,中的name是一一对应的
            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):
                session['user_id'] = user.id
                return redirect('/')
                # return "登录成功"
            else:
                print("密码错误")
                return redirect(url_for("auth.login"))
        else:
            print(form.errors)
            return redirect(url_for("auth.login"))


@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(username=username, password=generate_password_hash(password), email=email)
            db.session.add(user)
            db.session.commit()
            return redirect(url_for("auth.login"))
        else:
            print(form.errors)
            return redirect(url_for("auth.register"))


# http://127.0.0.1:5000/auth/captcha/[email protected]
@bp.route("/captcha/email", methods=["POST"])
def get_captcha():
    email = request.args.get("email")
    source = random.sample(string.digits * 4, 4)
    captcha = "".join(source)
    message = Message(subject="注册验证码", sender="[email protected]", recipients=[email],
                      body=f"您的邮箱验证码为:{captcha},有效期1分钟。")
    mail.send(message)
    email_captcha = EmailCaptchaModel(email=email, captcha=captcha)
    db.session.add(email_captcha)
    db.session.commit()
    return jsonify({"code": 200, "message": "", "data": ""})


@bp.route("/mail/test/")
def send_mail():
    message = Message(subject="Hello", sender="[email protected]", recipients=["[email protected]"],
                      body="This is a test email.")
    mail.send(message)
    return "Mail sent successfully!"


@bp.route("/logout")
def logout():
    session.clear()
    return redirect(url_for('auth.login'))

app.py中还需要添加

py 复制代码
from models import UserModel

@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}

效果:

7.3.3.用户问答和搜索

在写问答模块和搜索功能的时候,流程是:

问答模板----> 问答视图 ----> 权限设置(用户登录后才能访问问答页面) ----> 问答详情模板 ----> 问答详情视图 ----> 解答/评论模板 ----> 解答/评论视图 ----> 搜索功能实现

🌟问答和主页模板

index.html

html 复制代码
{% extends 'base.html' %}

{% block title %}首页{% endblock %}
{% block content %}
    <div class="bs-example" data-example-id="panel-with-list-group">
        <!-- Default panel contents -->
        <div class="media-body">
            <div class="panel-heading"><h3 style="text-align: center">问答列表</h3></div>
            <ul class="list-group">
                {% for question in questions %}
                    <li class="list-group-item">
                        <a href="{{ url_for('qa.detail', question_id=question.id) }}">{{ question.title }}</a>
                        <div class="media-body">
                            <p class="media-heading">{{ question.content|truncate(length=100) }}</p>
                        </div>
                        <p class=" text-right"><span>{{ question.author.username }}  &nbsp&nbsp&nbsp  </span> {{ question.create_time }}</p>
                    </li>
                {% endfor %}
            </ul>
        </div>

    </div>
{% endblock %}

public.html

html 复制代码
{% extends  'base.html' %}
{% block title %}发布问答{% endblock %}
{% block content %}
    <div class="panel panel-warning">
        <div class="panel-heading">
            <h3 class="text-center">发布问答</h3>
        </div>
        <div class="panel-body">
            <form class="form-signin" method="post">
                <div class="form-group">
                    <label for="title">标题:</label>
                    <input type="text" class="form-control" name="title" id="title" placeholder="请输入标题">
                </div>
                <div class="form-group">
                    <label for="content">内容:</label>
                    <textarea name="content" rows="14" cols="60" class="form-control" id="content" placeholder="请输入内容"></textarea>
                </div>
                <div class="text-right">
                    <button type="submit" class="btn btn-primary ">发布</button>
                </div>
            </form>
        </div>
    </div>
{% endblock %}

🌟问答和主页视图

这里有一个权限控制需要注意下,自定义权限,装饰器@login_required

存放在decorators.py

py 复制代码
from functools import wraps
from flask import g, redirect, url_for


def login_required(func):
    # 保留func的信息
    @wraps(func)
    # *args, **kwargs是装饰器的参数,*args表示位置参数,**kwargs表示关键字参数
    def inner(*args, **kwargs):
        # 在func执行之前,先判断用户是否登录
        if g.user:
            return func(*args, **kwargs)
        else:
            return redirect(url_for('auth.login'))

    return inner

forms.py

py 复制代码
import wtforms
from wtforms.validators import Email, Length, EqualTo,InputRequired
from models import UserModel, EmailCaptchaModel

class QuestionForm(wtforms.Form):
    title = wtforms.StringField(validators=[Length(min=3, max=100, message='标题长度在3-100之间')])
    content = wtforms.StringField(validators=[Length(min=5, message='内容至少需要5个字符')])


class AnswerForm(wtforms.Form):
    content = wtforms.StringField(validators=[Length(min=5, message='内容至少需要5个字符')])
    question_id = wtforms.IntegerField(validators=[InputRequired(message='必须指明属于哪个问题')])

qa.py

py 复制代码
from flask import Blueprint, render_template, request, redirect, url_for, g
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("/public", methods=['GET', 'POST'])
# 必须登录后才能访问
@login_required
def public():
    if request.method == 'POST':
        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(url_for('qa.index'))
        else:
            print(form.errors)
    else:
        return render_template('public.html')

🌟问答详情和解答模板

detail.html

html 复制代码
{% extends  "base.html" %}
{% block title %}Flask-{{ question.title|truncate(10) }}{% endblock %}
{% block content %}
    <div class="jumbotron">
        <div>
            <h2 class="text-center">{{ question.title }}</h2>
            <p class="lead text-center small">
                作者:{{ question.author.username }}&nbsp&nbsp时间:{{ question.create_time }}</p>
            <hr>
            <p class="text-center">{{ question.content }}</p>

        </div>
        <hr>
        <div>
            <h3>评论({{ question.answers|length }})</h3>
            <form class="form-signin" method="post" action="{{ url_for('qa.answer') }}">
                <div class="form-group">
                    <div class="input-group">
                        <input type="text" class="form-control" name="content"/>
                        <input type="hidden" class="form-control" name="question_id" value="{{ question.id }}"/>
                        <span class="input-group-btn">
                        <button class="btn btn-primary" type="submit">评论</button>
                        </span>
                    </div>
                </div>
            </form>

        </div>
        <hr>
        <div class="bs-example" data-example-id="media-alignment">
            {% for answer in question.answers %}
                <div class="media">
                    <div class="media-left">
                        <img class="img-circle" data-src="holder.js/64x64" alt="64x64"
                             src="{{ url_for('static', filename='img/1.jpg') }}" data-holder-rendered="true"
                             style="width: 64px; height: 64px;">
                    </div>
                    <div class="media-body ">
                        <h5 class="media-heading">{{ answer.author.username }}</h5>
                        <div class="row">
                        <div class="col-md-10"><p>{{ answer.content }}</p></div>
                        <div class="col-md-2">{{ answer.create_time }}</div>
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>
{% endblock %}	
🌟问答详情和解答视图

qa.py

py 复制代码
from flask import Blueprint, render_template, request, redirect, url_for, g
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('/detail/<question_id>')
def detail(question_id):
    question = QuestionModel.query.get(question_id)
    return render_template('detail.html', question=question)


@bp.route('/answer/public', methods=["post"])
@login_required
def answer():
    form = AnswerForm(request.form)
    if form.validate():
        content = form.content.data
        # question_id = request.form.get('question_id')
        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.detail', question_id=question_id))
    else:
        print(form.errors)
        return redirect(url_for('qa.detail', question_id=request.form.get('question_id')))
🌟搜索功能

搜索功能的视图是index.html,但表单提交是在base.html中们需要注意下表单提交的地址

base.html

html 复制代码
<form class="navbar-form navbar-left" method="GET" action="{{ url_for('qa.search') }}">
    <div class="form-group">
        <input type="search" class="form-control" name="search" placeholder="Search">
    </div>
    <button type="submit" class="btn btn-default">搜索</button>
</form>

qa.py

py 复制代码
from flask import Blueprint, render_template, request, redirect, url_for, g
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('/search')
def search():
    q= request.args.get('search')
    questions = QuestionModel.query.filter(QuestionModel.title.contains(q)).all()
    return render_template('index.html', questions=questions)
🌟总视图代码

qa.py

py 复制代码
from flask import Blueprint, render_template, request, redirect, url_for, g
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("/public", methods=['GET', 'POST'])
@login_required
def public():
    if request.method == 'POST':
        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(url_for('qa.index'))
        else:
            print(form.errors)
    else:
        return render_template('public.html')


@bp.route('/detail/<question_id>')
def detail(question_id):
    question = QuestionModel.query.get(question_id)
    return render_template('detail.html', question=question)


@bp.route('/answer/public', methods=["post"])
@login_required
def answer():
    form = AnswerForm(request.form)
    if form.validate():
        content = form.content.data
        # question_id = request.form.get('question_id')
        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.detail', question_id=question_id))
    else:
        print(form.errors)
        return redirect(url_for('qa.detail', question_id=request.form.get('question_id')))

@bp.route('/search')
def search():
    q= request.args.get('search')
    questions = QuestionModel.query.filter(QuestionModel.title.contains(q)).all()
    return render_template('index.html', questions=questions)

效果:


8.【总结】

博主是已经学习了很多Django知识,再来学习Flask知识的,所以了解起来很快,本次文档是跟着2025版-零基础玩转Python Flask框架-学完可就业_哔哩哔哩_bilibili这个视频学Flask基础知识并完成问答项目

博主觉得这个Flask课程完全是可以快速入门Flask框架的,而且视频博主也很贴心,有很多bug都是一起带着大家解决。

Flask完结👏👏🥳🥳

相关推荐
duapple1 小时前
Golang基于反射的ioctl实现
开发语言·后端·golang
Dxy12393102161 小时前
Python 条件语句详解
开发语言·python
龙泉寺天下行走1 小时前
Python 翻译词典小程序
python·oracle·小程序
践行见远2 小时前
django之视图
python·django·drf
love530love3 小时前
Windows避坑部署CosyVoice多语言大语言模型
人工智能·windows·python·语言模型·自然语言处理·pycharm
my_styles4 小时前
docker-compose部署项目(springboot服务)以及基础环境(mysql、redis等)ruoyi-ry
spring boot·redis·后端·mysql·spring cloud·docker·容器
掘金-我是哪吒4 小时前
分布式微服务系统架构第132集:Python大模型,fastapi项目-Jeskson文档-微服务分布式系统架构
分布式·python·微服务·架构·系统架构
xhdll5 小时前
egpo进行train_egpo训练时,keyvalueError:“replay_sequence_length“
python·egpo
Cchaofan5 小时前
lesson01-PyTorch初见(理论+代码实战)
人工智能·pytorch·python
网络小白不怕黑5 小时前
Python Socket编程:实现简单的客户端-服务器通信
服务器·网络·python