通过本文,你应该掌握:

  • Flask项目框架
  • 插件配置
  • Flask注册

项目布局

写在前面

不阅读前两篇,不耽误学习本文内容。

上一篇已经在我的Ahoh项目中加入了Flask-SQLAlchemy插件,并且可以利用Flask Shell实现Mysql的增、删、改、查操作。

在拥有了数据库的加持之后,我接下了就会加速内容的输出了。

不过,在真正开干之前,还有一步非常重要,就是项目布局调整,一个好的布局是项目最重要的开端。

一个最简单的Flask应用可以是单个文件,但是当项目越来越大的时候,把所有代码放在单个文件中就有点不堪重负了。Python项目使用包来管理代码,把代码分为不同的模块,然后在需要的地方导入模块,本文也会按这一方式管理代码。

应用设置

先来看看我们现在项目的布局。

ahoh/
├── app.py       # 项目app模块
├── migrations   # 数据库版本文件夹
└── venv         # Python虚拟环境

当前项目的总体布局实际上是没有什么问题的,主要问题在于app模块是单个的文件,里面集成了太多的东西。

由于Python还支持文件夹作为一个模块,所以我们应该把app.py模块换成文件夹模块,如果不理解这里也没有关系,开干就完事了。

以解决问题为导向!!

我希望在项目根目录下应该有以下元素:

  • app/ ,包含应用所有代码的Python
  • tests/ ,包含测试模块的文件夹
  • venv/Python虚拟环境(现在已经有了)
  • 版本控制配置,(.gitignore文件)
  • 项目需要的其他文件(配置文件)

第一步,在项目根目录(我这里是~/ahoh)下创建文件夹,命名app,然后将app.py移动到app文件夹下,并改名为__init__.py,指令如下:

~/ahoh$ mkdir app                   # 创建app文件夹
~/ahoh$ mv app.py app/__init__.py   # 移动app.py并改名

虽然代码做了更改,其实项目现在还是可以正常运行的,不过需要先激活虚拟环境。

然后,使用应用工厂函数代替之前的全局变量app,修改__init__.py如下(之前的代码可以先注释掉):

from flask import Flask

# 应用工厂函数
def create_app():
    app = Flask(__name__)
    return app

然后使用Blueprint拆分视图函数,通常情况下,用户博客是博客系统最主要的两个实体。
我们就用两个独立的文件分别处理用户相关的视图和博客相关的视图。

app文件夹下创建auth.pyblog.py,分别编辑其中的内容如下:

# ~/ahoh/app/auth.py 用户蓝图
from flask import Blueprint

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

@bp.route('/login',methods=['GET','POST'])
def login():

    return 'login'

@bp.route('/register',methods=['GET','POST'])
def register():

    return 'register'
# ~/ahoh/app/blog.py  博客蓝图
from flask import Blueprint

# 使用/访问博客蓝图
bp = Blueprint('blog',__name__,url_prefix='/')

@bp.route('/')
def index():
    
    return 'index'

@bp.route('/detail/<int:id>')
def detail(id):
    
    return 'detail'

然后在__init__.py中注册蓝图,在create_app()函数中添加如下代码:

from . import blog,auth  # Python 讲究随用随定义,这句可以直接写函数里
app.register_blueprint(blog.bp)
app.register_blueprint(auth.bp)

做完这些之后,蓝图就配置好了,之后的代码里,所有和用户相关的视图都放在auth.py里,其他就放在blog.py里面,如果随着功能扩展,有新的重要实体,还可以再新建一个模块。

先测试一下改装是否成功,使用flask run启动服务,然后在浏览器中输入localhost:5000/auth/login,或者输入localhost:5000/试试有什么效果吧!

python的fastapi项目层级结构 python项目结构设计_Python


python的fastapi项目层级结构 python项目结构设计_flask_02


然后就是重新配置Flask-SQLAlchemyFlask-Migrate插件,我这里准备单独用一个模块管理这些插件。

首先,在app文件夹下创建exts.py,然后编辑代码如下:

# ~/ahoh/app/exts.py  插件模块
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

然后,在__init__.py中的create_app()函数中插入以下代码:

from .exts import db,migrate
db.init_app(app)
migrate.init_app(app,db)

这样,两个插件也就准备好了,接下来是配置文件,在app文件夹下创建settings.py,编辑内容如下:

class Default(object):
    SQLALCHEMY_DATABASE_URI='mysql+pymysql://root:_Why62345@localhost/ahoh?charset=utf8'
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    SECRET_KEY='asl;nadflkgaasdfasyfgher' # 这里需要一个非常复杂的密码字符串

class Development(Default):
    ENV='development'
    DEBUG=True

class Production(Default):
    ENV='production'
    DEBUG=False

然后,在__init__.py文件中的create_app()函数中,app=Flask(__name__)后,插入如下代码:

app.config.from_object(settings.Default)

这样,配置文件也搞定了。

后面就是模型的配置了,先在app文件夹下创建models.py文件,然后编辑内容如下:

from exts import db
from datetime import datetime


class TimestampMixin(object):
    created = db.Column(
        db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
    status = db.Column(db.Integer, default=0)


class User(TimestampMixin, db.Model):
    __tablename__ = 't_users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True)
    password = db.Column(db.String(127), nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

这样我们的模型也就绪了,因为我这里把之前的数据全删掉了,所以还需要走一遍上一篇的创建流程,大家可以试试使用flask db migrate/upgrade能不能成功。

如果不能成功升级,直接删掉migrations文件夹,删掉数据库,重来。

想要在Flask Shell中使用db.create_all()创建所有对象,还需要在__init__.py中引入User

from .models import User # 这句看起来没什么用,没有还真不行

下面再介绍一遍流程:

(venv)~/ahoh$ flask shell
>>> from app.exts import db
>>> db.create_all()

这样就可以创建表了

然后是,使用Flask-Migrate插件:

(venv)~/ahoh$ flask db init 
(venv)~/ahoh$ flask db migrate
(venv)~/ahoh$ flask db upgrade

最后还要添加git管理的.gitignore文件,方便后面使用仓库。

ahoh文件夹下创建git的版本控制文件.gitignore,然后把下面的内容贴进去:

venv/
migrations/

*.pyc
__pycache__/

instance/

.pytest_cache/
.coverage
htmlcov/

dist/
build/
*.egg-info/

最后,把templatesstatic文件夹复制到app文件夹下,并在templates文件夹下创建authblog文件夹。

这样所有的项目设置就全部完成了,当前项目的结构:

ahoh/
├── README.en.md
├── README.md
├── app
│   ├── __init__.py
│   ├── templates   # 模板文件
│	│	├── auth    # 用户页面
│	│	├── blog    # 博客页面
│	│	└── bash.html  # 模板基类
│   ├── static      # 静态文件
│	│	└── style.css  # css文件 
│   ├── auth.py		# 用户蓝图
│   ├── blog.py     # 博客蓝图
│   ├── exts.py     # 插件模块
│   ├── models.py   # 模型模块
│   └── settings.py # 默认设置
├── migrations      # 数据版本
└── venv            # 虚拟环境

这里还少了test文件夹,由于短时间内用不到,就先不搞了。

当前状态的代码保存到了Gitee仓库quickstart分支。如果没有办法根据教程复现代码,可以直接从这个分支开始。

下面就要测试写一些功能,测试以下改版后的结构有没有问题。

用户注册

首先就是用户注册视图(/register),修改视图函数代码如下:

@bp.route('/register', methods=['GET', 'POST'])
def register():
    print('regist')
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        error = None

        if not username:
            error = '用户名不能为空'
        elif not password:
            error = '密码不能为空'
        elif User.query.filter_by(username=username).first() is not None:
            error = '用户 "{}" 已存在'.format(username)

        if error is None:
            u = User(username=username,
                     password=generate_password_hash(password))
            db.session.add(u)
            db.session.commit()
            return redirect(url_for('auth.login'))

        flash(error)

    return render_template('auth/register.html')

base.html内容如下:

<!DOCTYPE html>
<html lang="en">

<head>
    {% block head %}
    <link rel="stylesheet" href="{{url_for('static',filename='style.css')}}" />
    <title>{% block title %}{{title}}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>

<body>
    <div id="main">
        {% block body_content %}

        {% endblock %}
    </div>

    <div id="footer">
        {% block footer %}
        {% endblock %}
    </div>
</body>

</html>

register.html内容如下:

{% extends 'base.html'%}

{% block body_content %}
<form method="post">
    <input type="text" name="username" id="username">
    <input type="password" name="password" id="password">
    <input type="submit" value="注册">
</form>
{% endblock %}

然后,启动服务器,访问链接:http://localhost:5000/auth/register

可以看到如下界面:

python的fastapi项目层级结构 python项目结构设计_html_03


然后填上数据,点击注册,如果没有错误的话就会跳转到登录页面,我在这里填了xiaoming123456

此时可以从数据库查到注册数据:

mysql> select * from t_users;
+---------------------+---------+--------+----+----------+-------+--------------------------------------------------------------------------------------------------------+
| created             | updated | status | id | username | email | password                                                                                               |
+---------------------+---------+--------+----+----------+-------+--------------------------------------------------------------------------------------------------------+
| 2022-02-18 14:54:12 | NULL    |      0 |  1 | xiaoming | NULL  | pbkdf2:sha256:260000$RWe2k5pxwLUL4cjY$0fc8d3156599d3b5c36120e45c7404ce7085a8eca41d939cdc76d0b5e349de5f |
+---------------------+---------+--------+----+----------+-------+--------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

这样注册功能就成功啦😄,当前这个注册既没有字段验证,又没有错误提示,这些特性会在下一篇使用wtforms解决的!

写在最后

当前状态的代码保存到了Gitee仓库quickstart分支。如果没有办法根据教程复现代码,可以直接从这个分支开始。
肝啊~~