有没有对着flask自动生成的极简项目结构一脸懵逼不知道从哪里开始扩展起?
有没有好不容易拼凑起一个框架发现,欸,运行不起来?
有没有好不容易运行起来了,要往数据库里加数据,发现,欸db从哪里来?为什么db没有drivername?
在看了数十篇中外博客加上一通摸索后终于成功运行并写了个除了登陆注册之外一片空白的flask小项目,登录成功的一刹那眼泪差点掉下来!
于是决定写篇博客,让大家少走弯路一遍成功!(空白的只包含登陆注册的项目上传到github了 链接: https://github.com/JoJoJun/flask-example.git
# project/models/__init__.py里
from .model import db
def init_app(app):
db.init_app(app)
with app.app_context():
db.create_all()
# proje/routes/__init__.py里
from .login_views import login_bp,login_manager
from .views import bp
import flask_login
def init_app(app):
app.register_blueprint(bp)
app.register_blueprint(login_bp)
login_manager.init_app(app)
# /project/__init_.py里
from flask import Flask
def create_app():
from . import models, routes, services
app = Flask(__name__)
app.config.from_object('project.config.Config')
models.init_app(app)
routes.init_app(app)
return app
#最后app.py里
app = project.create_app()
app.run(debug=True)
下面是详细部分 还挺长的
flask自己生成的项目结构非常简单,逻辑部分全写在app.py里也完全可以运行。但项目一大这就容易出现各种问题,也不适合多人一起开发。
参考教程
https://lepture.com/en/2018/structure-of-a-flask-project,我们可以根据功能结构来组织整个项目结构。
从大的方面看分为两层,project文件夹和app.py app.py就是原本生成的,用于启动整个项目的文件,运行python app.py就可以运行程序。 project文件就是YourApplication,下面放所有的逻辑。
是不是很清晰!
首先看app.py 这就是个启动文件,要import整个project,然后run
在运行整个项目时,就只要在flask_example下python run app.py
app.py里的东西也非常少,尽职尽责的做一个入口,多余的事一点不做:
import project
if __name__ == '__main__':
# app.run(host, port, debug, options)
# host要监听的主 机名。 默认为127.0.0.1(localhost)
# debug 默认为false。 如果设置为true,则提供调试信息 options 要转发到底层的Werkzeug服务器
app = project.create_app()
app.run(debug=True)
三件事,一是import project时会执行project下__init__.py的代码,其实也就是main里这个create_app;
二就是调用这个create_app()把 该初始化的部分全给他初始化了(主要就是调用子文件夹下的__init__)
三,当然就是main函数主入口,启动
是不是很简洁!
下面进入project文件夹下面
我们按照功能进行划分。
_ init_.py是做什么的
简单来说,_ init_.py所在的整个文件夹会被当做一个模块包,在import这个模块时,会自动执行__init__里的代码。适合以模块为整体来初始化、注册等
再继续向下展开之前,我们还是先看这一层
首先是配置文件config.py
这是我从某个博客上复制的
from os import environ
class Config:
"""Set Flask configuration vars from .env file."""
# General
TESTING = environ.get('TESTING')
FLASK_DEBUG = environ.get('FLASK_DEBUG')
SECRET_KEY = environ.get('SECRET_KEY')
if not SECRET_KEY:
SECRET_KEY = 'a-secret-key'
SESSION_TYPE = 'filesystem'
# Database
# SQLALCHEMY_DATABASE_URI = environ.get('mysql+pymysql://')
# 之前一直Nonetype 是因为环境变量里没有叫这个的environ
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost/database_name'
SQLALCHEMY_TRACK_MODIFICATIONS = False
关于配置文件的坑有以下几个:
1、这是新建了py文件后又新建了一个Config类,与project下的__init__.py同级,需要再__init__.py中使用。(意思就是将来在app.py中导入project时,会自动执行__init__,也就读入了项目配置)
2、在__init__.py中的导入方式是这样的:
app.config.from_object('project.config.Config')
相信我 我还试过ap.congig其他形式,以及’project/config.py’ 'config.py/Config’等
不,人家只需要这几个‘.’, 直接导入这个类
3、这里很重要的是配置数据库URI的格式。一开始博客里给的方式是注释掉的那行environ.get(…)
但是我在注册用户的时候一直显示’db has no drivername’之类的,最后发现是这样写不对,因为我的环境里并没有。所以URI直接写字符串就好了。
另一个需要注意的点是,没错,就是mysql+pymysql这样数据库+数据库处理驱动的格式,真真实实的有个“+”号。
而且没有驱动比如pymysql的话,报错没商量!
其他的几行感觉问题不大,配置文件就酱紫。处理好数据库就没问题了!
现在进入一个比较重要其实也没几行的文件,整个project也就是yourapplication的__init__.py文件,这里定义了app.py里寸土寸金几行代码里最重要的那个project.create_app()
具体长这样:
from flask import Flask
# db = SQLAlchemy()
def create_app():
from . import models, routes, services
app = Flask(__name__)
app.config.from_object('project.config.Config')
# db.init_app(app)
models.init_app(app)
routes.init_app(app)
# services.init_app(app)
# with app.app_context():
#
# app.register_blueprint(bp)
# Create tables for our models
# db.create_all()
return app
看到我注释掉的那几行了嘛?就是常规极简一个app.py走天下的项目组织里常见的几句。
这里全都改成了
from . import models, routes, services
models.init_app(app)
routes.init_app(app)
没错,就是models和routes下各自的__init__.py里定义的初始化语句。只不过把它们从原本app.py一人包圆改成了要初始化谁就呆在哪,然后在project/init.py里集合调用,最后在app.py里create_app。
//至于为什么注释掉services.init_app 因为区区小小登录啥也没有,暂时不需要services,摆这里是为了兄弟姐妹就要整整齐齐,要初始化都得在这儿
剩下的就是不可或缺的定义app,以及读入配置了。
好了。终于可以进入project下各个子文件夹了!
就很简单,也是一个__init__.py,一个model就是定义了数据库中的user表。
当然,我只有一个model是因为我只需要一个表,如果你的表们差别挺大,分开建不一样的model用起来更方便。
另外一点是,我这里是从已经建好的数据库里建model,也是步步为坑的踩上来的。
首先,可以通过flask-sqlacodegen从已有的数据库生成model
大概长这样:
flask-sqlacodegen "mysql+pymysql://USERNAME:PASSWORD@HOST/DB_name" --outfile "models.py"
然后你就会在运行的文件位置得到一个完整的model.py
但是它只有class table_name(Base):以及下面的字段定义,没有配备好的engine和db。
比如这样:
class User(Base):
__tablename__ = 'user'
account = Column(String(255), primary_key=True, unique=True)
password = Column(String(255), nullable=False)
name = Column(String(255), server_default=FetchedValue())
vip_flag = Column(Integer, nullable=False, server_default=FetchedValue())
create_time = Column(DateTime)
update_time = Column(DateTime)
state = Column(Integer)
也就是只是静态的反应了你数据库里的表里的字段,遗世独立的呆在那里岁月静好,但是不能用来做增删查改!
我要你有何用!
需要手动在这里添加engine和db,大概这样:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from flask_sqlalchemy import SQLAlchemy
engine = create_engine('mysql+pymysql://root:password@localhost/database_name', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,autoflush=False,bind=engine))
Base = declarative_base()
metadata = Base.metadata
Base.query = db_session.query_property()
db = SQLAlchemy()
其中Base.query允许你直接用类名+query来查询。比如User.query.filter什么的,没有这一行你的User就会显示不能query
然后db=SQLAlchemy()就很经典的定义了db,根据常规思路下一步应该立马初始化,是的,在models/init.py里就是这样的:
# models/__init.py
from .model import db
def init_app(app):
db.init_app(app)
with app.app_context():
db.create_all()
u1s1,我觉得把db直接定义在这里可能也行,毕竟model.py里也就只有一句声明,不需要它做什么。
这里就是后面在project/init.py里 import models后执行的models.init_app(app)的内容。
在这里定义后,会在项目一开始app.py里就通过调用project.create_app()给初始化,然后就可以在需要操作数据库的地方,简单的
from project.models import db
然后愉快的操作数据库,收获惊喜的泪水
另外,生成的model.py的类下面可能没什么函数,需要自己去手动完善,尤其是构造函数。
好了,与数据库有关的就这些了!
去看个路由的例子吧!
routes下面的init主要为了注册blueprint,
如果你的项目全是/后面一层的话,注册一个总的就行。
如果想分开,比如:
/account/login
/account/regist
/user/center
/user/add
之类的,就可以分成不同的py,每个定义自己的blueprint,然后来这里注册,大概长这样:
from .login_views import login_bp,login_manager
from .views import bp
import flask_login
def init_app(app):
app.register_blueprint(bp)
app.register_blueprint(login_bp)
login_manager.init_app(app)
这里还顺便初始化了login_manager,因为我要用flask_login来管理登录登出(当然,也是踩着坑过来的)
顾名思义,这里就是处理用户管理的路由部分,全都是/account开头
先看需要import些什么:
import flask_login # 为了管理登录登出
from project.models.model import User #要操作的数据库模型 也就是user表
from flask import Flask, redirect, url_for,render_template,request,Blueprint #一些跳转什么的
from datetime import datetime #数据库里加时间字段
from project.models import db #操作数据库
login_bp = Blueprint('login', __name__, url_prefix='/account') #定义的blueprint,在init里注册
login_manager = flask_login.LoginManager() #定义的loginmanager,在Init里初始化
然后举个例子,拿登录来说:
#实话实说 这个user_loader我也不是特别懂 博客们写法也不完全一样,不过目前我觉得这个没有问题
@login_manager.user_loader
def load_user(account):
return User.query.get(account)
# 路由/account/login
@login_bp.route('/login/', methods=['GET', 'POST'])
def login():
print("in login!!!!!")
if request.method == 'GET':
return render_template('helloworld.html',user=flask_login.current_user)
email = request.form['username']
password = request.form['password']
#这里用到数据库的查询
user_in_db = User.query.filter_by(account=email).first()
print(user_in_db)
if not user_in_db:
msg = '用户不存在'
return render_template('helloworld.html',msg = msg)
elif password != user_in_db.get_password():
msg='密码错误'
return render_template('helloworld.html',msg = msg)
if user_in_db and request.form['password'] == user_in_db.get_password():
flask_login.login_user(user_in_db)
return redirect(url_for('login.protected'))
@login_bp.route('/protected')
@flask_login.login_required
def protected():
print(flask_login.current_user)
return 'Logged in as: ' + flask_login.current_user.account
print()里 这么多! 感受到我路由进去的激动了吗?!!!谁试谁知道
分Get,Post两种请求,get的时候只是访问这个url时,应该返回你的登录前端页面。
然后用户填完账号密码提交后,才是post,就可以拿到前端的数据。
前端helloworld.html自然写的很随意,长这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello world</title>
</head>
<body>
<h1>hello world</h1>
<form method='POST'>
<input type='text' name='username' id='email' placeholder='email'/>
<input type='password' name='password' id='password' placeholder='password'/>
<input type='submit' name='submit'/>
</form>
{{ msg }}
</body>
</html>
//本来只想加个form的,仔细一想对不起人家helloworld.html的大名
登录完是这样:
注册其实差不太多,就涉及到往数据库里添加,大概这样:
dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
new_user = User(email=email, password=password, name=username, create_time=dt, update_time=dt)
db.session.add(new_user)
db.session.commit()
msg = '注册成功'
这个user的构造函数自己再model.py里定义的
db就是从models里导入那个db
OK! 没了!
全部的代码在这里:
github
可以运行的空白框架!可以下载下来试一下!
就酱!
恭喜你读完了四舍五入万字长文!(也不知道有没有人看得到