开学一个月时间,课余时间,半摸鱼半学习,大概过了下Flask的基础知识.
这是我的学习笔记,在此做一个记录.
第一个Flask程序
from flask import Flask
app = Flask(__name__) //初始化Flask对象(__name__用于确定程序根目录)
@app.route('/') //路由
def hello_world(): //视图函数
return "Hello World";
if __name__=='__main__':
app.run() //run可以传入debug,port,host等参数
/*主函数运行程序,导入模块不执行*/
URL传递参数
@app.route('/sayhi/<name>')
def sayhi(name):
return "<h1>Hi!%s</h1>"%name;
- 在路由地址中,使用<>包含参数
- 在视图函数中,传入该参数,以供调用
URL反转和重定向
@app.route('home')
def home():
return redirect(url_for(endpoint="sayhi",name="Jack"))
- 通常我们通过路由跳转到指定的视图函数,但是有时候需要重定向到指定网页时,通过传入视图函数,获得对应路由地址,这个过程就叫做URL反转(需要用到Flask的url_for函数)
- 重定向的话则需要用到flask的redirect函数
Jinjia2 模板引擎
- Flask使用Jinjia2模板引擎来实现复杂页面的渲染
- 渲染:使用真实值替代网页模板中的变量,生成对应数据的HTML片段
- Flask通过render_template()函数实现模板的渲染
@app.route('/')
def index():
return render_template('index.html',name="Jack"); //传入参数渲染模板
<!-- index.html -->
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>
{% if name %}
Hi!{{ name }}
{% else %}
Please Input Your Name
{% endif %}
</h1>
</body>
</html>
- 通过{{ }}在模板内接受参数
- 可以通过在视图函数中使用**locals()函数,向模板传入所有变量值
- 也可以在模板中使用if判断语句,for循环,过滤器等
- 可以通过宏(Marco)实现模板内控件的简化和复用
- 宏的导入类似于import
- include可以将一个模板导入到另一个模板(index.html导入navbar.html,header.html...)
- 静态文件(css,js,images)加载,可在模板中通过url_for()与标签相结合进行调用
- 模板中使用set定义变量
- 模板继承 {{% extends "TemplateName" %}}
- 模板块{{% block BlockName %}}{{% endblock %}}实现模板块的复用
app.route和add_url_rule
def hello_world():
return "HelloWorld!";
app.add_url_rule('/helloworld/',endpoint='helloworld',view_func=hello_world);
- 在app.route()中或者app.add_url_rule()中指定了endpoint的话,之后便无法通过传递ViewFunction给url_for获得对应URL,而是要通过设定的endpoint
- app.route修饰器会将URL和视图函数的关系保存到app.url_map
- app.add_url_for()还可以设置指定method
Flask类视图
标准视图函数
class Index(views.View):
def dispatch_request(self):
return render_template('home.html');
app.add_url_rule(rule='/',endpoint='index',view_func=Index.as_view('index'));
- 必须继承flask.view.View
- 必须实现dispatch_request方法,相当于之前的视图函数
- 类视图支持继承,但必须通过app.add_url_rule()注册
基于方法的类视图
class LoginView(views.MethodView):
def get(self):
return render_template('index.html');
def post(self):
username=request.form.get("username");
password=request.form.get("pwd");
if(username=='admin' and password=='admin'):
return "Welcome Admin!";
else:
return "Username or Password is wrong,try again.";
app.add_url_rule('/login',view_func=LoginView.as_view('loginview'));
<!-- index.html -->
<html>
<body>
<form action="login" method="post">
<input type="text" class="input" name="username" placeholder="Please Input Username">
<input type="password" class="input" name="pwd" placeholder="Please Input Password">
<input type="submit" class="input button" value="Login">
</form>
</body>
</html>
- 继承flask.views.MethodView
- 对每个HTTP Method执行不同函数
Flask装饰器
通过装饰器实现代码复用.
Blueprint
- Flask蓝图体哦那个了模块化管理程序路由的功能.
- 让各个模块的视图函数写在不同的py文件中
- 在主视图中导入分路由视图额模块,并注册蓝图对象
- 分路由中建立Blueprint对象代替Flask App对象
- 主路由中导入各分路由模块,并通过app.register_blueprint()方法注册各分路由blueprint对象到app
import blueprint_test
app = Flask(__name__)
app.register_blueprint(blueprint_test.testlist)
#blueprint_test.py
from flask import Flask,Blueprint
testlist=Blueprint('test',__name__)
@testlist.route('/test')
def test():
return "This is a Blueprint Test.";
Flask-WTF 表单
from flask_bootstrap import Bootstrap
import config
from form import BaseLogin
app = Flask(__name__)
app.config.from_object(config)
Bootstrap(app)
@app.route('/', methods=['GET', 'POST'])
def index():
form = BaseLogin()
if form.validate_on_submit():
return "表单提交成功"
else:
return render_template('BootstrapTemplate.html', form=form)
#form.py
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField
from wtforms.validators import DataRequired,Length
class BaseLogin(FlaskForm):
name=StringField('name',validators=[DataRequired(message="用户名不能为空"),Length(6,16,message="长度位于6~16之间")],render_kw={'placeholder':"输入用户名"})
password=PasswordField('password',validators=[DataRequired(message="用户名不能为空"),Length(6,16,message="长度位于6~16之间")],render_kw={'placeholder':"输入密码"})
submit=SubmitField("Submit")
<!-- BootstrapTemplate.html -->
{% extends"bootstrap/base.html" %}
{% block title %}This is an example page{% endblock %}
{% block navbar %}
<div class="navbar navbar-fixed-top">
<!-- ... -->
</div>
{% endblock %}
{% block content %}
<h1>Hello, Bootstrap</h1>
{% import 'bootstrap/wtf.html' as wtf %}
{{ wtf.quick_form(form) }}
{% endblock %}
- Bootstrap-WTF导入wtf.html(包含wtf宏)后,可以快速渲染WTF表单
- WTF可以通过各类Field和Validator快速制作表单,记得新建表单要继承FlaskForm
- 表单相关路由要指定方法,并指定表单对象,并可通过form.validate_on_submit()判断渲染提交后效果
- 必须先设定app.config.from_object(config),SECRET_KEY,防止CSRF后才可以向渲染函数传入表单
Flask-WTF CSRF保护
#config.py
CSRF_ENABLED = True
SECRET_KEY = 'helloworld'
#main.py
from flask_wtf.csrf impot CSRFProtect
import config
app = Flask(__name__)
app.config.from_object(config)
CSRFProtect(app)
{{ form.csrf_token }}
<!-- 或者通过导入bootstrap,wtf.quick_form(form)会自动调用宏渲染 -->
Flask-WTF 上传文件
from flask_wtf.file import FileField, FileRequired
from wtforms import SubmitField
from werkzeug.utils import secure_filename
import os
class UploadForm(FlaskForm):
textFile = FileField(validators=[FileRequired()])
submit = SubmitField('Submit')
@app.route('/upload', methods=['GET', 'POST'],endpoint='upload')
def upload():
form = UploadForm()
if form.validate_on_submit():
f = form.textFile.data
filename = secure_filename(f.filename)
f.save(os.path.join(app.instance_path, 'text', filename))
return redirect(url_for('upload'))
return render_template('Upload.html', form=form)
<!-- Upload.html -->
{% extends "bootstrap/base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block body %}
{{ wtf.quick_form(form) }}
{% endblock %}
- 可以使用filename.rsplit('.',1)来获取文件后缀
- 可以调用flask_wtf.file.FileRequired验证文件上传是否为空
- 调用flask_wtf.file.FileAllowed指定上传文件类型
Python文件路径
- os.path.join(path1[, path2[, ...]]) //把各个目录,文件名连接起来,返回一个完整路径
- app.route_path //获取Flask的根目录
- Runoob-Python路径
Flask Cookies
@app.route('/<name>')
def SetCookie(name):
response = make_response(render_template('home.html'))
response.set_cookie("name", name)
return redirect(url_for('CookieTest',name=name))
@app.route('/')
def CookieTest():
name = request.cookies.get('name')
if name:
response = make_response(render_template('home.html', name=name))
return response
print(url_for('SetCookie',name=""))
return redirect()
<!-- home.html -->
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>
{% if name %}
Hi!{{name|replace('J','H')}}
{% else %}
Please Input Your Name
{% endif %}
</h1>
<h2>
{% block content %}
{% endblock %}
</h2>
</body>
</html>
- render_template返回的是Unicode字符串,想要添加Cookie需要先经过make_response(),转换成Response对象后再进行添加
- 查看Cookie值使用request.cookies.get()方法
Flask Session
- 与之前的Flask Cookies差距不大,同样是设置,获取,清除几个操作
- 不同之处在于,Cookie存在客户端本地,Session存在服务器,不做设置的话,Session在浏览器关闭后便会自动清除
钩子函数
每次正常执行代码之前或者之后都会执行的函数,一种特定的修饰器函数.
- before_first_request()
- before_request()
- after_request()
- teardown_request()
SQLAlchemy
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SQLALCHEMY_DATABASE_URI = 'sqlite:///'+os.path.join(basedir,'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS= False
#main.py
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
app.config.from_object(config.Config)
#models.py
from main import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
#Python Console
from models import db
db.create_all() #创建并初始化sqlite数据库
from models import User
user = User(id=1,username="Jack",email="jack@gmail.com") #建立User实例
db.session.add(user) #向db添加user实例
db.session.commit() #向db提交user实例
User.query.all()[0].username #查找所有User实例,中第一项的username参数
User.query.delete() #删除所有User实例
db.session.commit() #确认删除所有User实例
- SQLAlchemy是Python下的一个SQL ORM(Object Relational Mapping)工具
- 将SQL面向过程的概念映射到Python面向对象的概念上去
- 类=>表
- 对象=>表的行(记录)
- 属性=>表的列(字段)
- 使用CRUD的API,代替冗长的SQL语句
- 将SQL面向过程的概念映射到Python面向对象的概念上去
- 在初始化时一样是使用app.config.from_object()导入config.py文件当中的配置
- config.py当中需要的配置是
- SQLALCHEMY_DATABASE_URI
- ('数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名')
- 'mysql+mysqlconnector://root:password@localhost:3306/test'
- 'sqlite:///'+os.path.join(basedir,'app.db')
- ('数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名')
- SQLALCHE_TRACK_MODIFICATIONS
- 动态追踪修改设置,未设置会提示未设置
- SQLALCHEMY_DATABASE_URI
- config.py当中需要的配置是
- 建立类时
- 继承db.Model(db=SQLlchemy(app))
- 字段为db.Column
- 最后使用db.create_all()方法建表
- 添加记录时,先调用db.session.add()将对象添加到session,之后再调用commit方法提交
- 数据查询
- ClassName/TableName.query.fliter(ClassName.FieldName=='Value').first/all()
- 返回的是查询结果,经过滤后的一个或者所有对象
- 之后可以再访问它们的具体字段
- 可以不经过滤返回所有对象
- 返回的是查询结果,经过滤后的一个或者所有对象
- ClassName/TableName.query.fliter(ClassName.FieldName=='Value').first/all()
- 数据修改
- 直接对返回对象进行赋值,然后commit修改即可完成对数据的修改
- 数据删除
- db.session.delete(obj)删除对象后commit即可完成删除
- 多对多关系
- 通过relationship("TableName",backref="__tablename")和Column(,ForeignKey('TABLE.COLUMN'))的组合可以建立一个相互的映射,从而实现两个表之间的相互调用
循环引用
项目变大可能会遇到循环引用的问题,这个时候就要注意架构是否合理.
MVC分离,按照flask官方推荐,forms,models,errors几个模块建立在utils之上,views是最顶层,上级模块可以调用下级模块,下级模块不能调用上级模块,这样合理架构,就可以避免循环引用.(简言之,按照模型规范来,不要没想要,瞎调用)
Flask RESTful API
Requirements
- Flask(Flask,jsonify)
- Flask-SQLAlchemy
- Flask-Marshmallow=>Serialization/Deserialization library
How to use Marshmallow
# Create your app
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
# Write your models
class User(db.Model):
id=db.Column(db.Integer,primary_key=True)
name=db.Column(db.String(100),unique=True)
email=db.Column(db.String(100),unique=True)
# Define your output format with marshmallow
class UserSchema(ma.Schema):
class Meta:
#Fields to expose
fields = ("name","email")
user_schema = UserSchema()
users_schema = UserSchema(many=True)
# Output the data in your views
@app.route("/api/user/<id>")
def user_detail(id):
user = User.get(id)
return user_schema.dump(user)
How to write RESTful CRUD API
from flask import Flask,jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
# Init app
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
# Database
app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///'+os.path.join(basedir,'db.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False
# Init DB
# Init ma
ma = Marshmallow(app)
# Product Class/Model =>Flask-SQLAlchemy Document
class Product(db.Model):
id=db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(100),unique=True)
description = db.Column(db.String(200))
price = db.Column(db.Float)
qty = db.Column(db.Integer)
def __init__(self,name,description,price,qty):
self.name=name;
self.description=description
self.price=price
self.qty=qty
# Product Schema
class ProductSchema(ma.Schema):
class Meta:
fields = ('id','name','description','price','qty')
# Init schema
product_schema = ProductSchema(strict=True)
products_schema = ProductSchema(many=True,strict=True)
# Create a Product
@app.route('/product',methods=['POST'])
def app_product():
name =request.json['name']
description =request.json['description']
price =request.json['price']
qty =request.json['qty']
new_product = Product(name,description,price,qty)
db.session.add(new_product)
db.session.commit()
return product_schema.jsonify(new_product)
# Get All Products
@app.route('/product',methods=['GET'])
def get_products():
app_products = Product.query.all()
result = products_schema.dump(all_products)
return jsonify(result.data)
# Get Single Products
@app.route('/product/<id>',methods=['GET'])
def get_product(id):
product = Product.query.get(id)
return product_schema.jsonify()
# Update a Product
@app.route('/product/<id>',methods=['PUT'])
def update_product(id):
product = Product.query.get(id)
name =request.json['name']
description =request.json['description']
price =request.json['price']
qty =request.json['qty']
product.name = name;
product.description = description;
product.price = price;
product.qty = qty;
db.session.commit()
db.session.add(new_product)
db.session.commit()
return product_schema.jsonify(new_product)
# Delete Product
@app.route('/product/<id>',methods=['DELETE'])
def delete_product(id):
product = Product.query.get(id)
db.session.delete(product)
db.session.commit()
return product_scheema.jsonify()
# Run Server
if __name__ == '__main__':
app.run(debug=True)
MVC架构的RESTful API
前面这些大都是单文件的程序,但具体工程中肯定是需要对各个功能模块进行分离的,否则整个项目将变得难以维护.
所以在看了一些别人的项目,也学习了解了MVC的基础知识后,我重新写了个功能模块划分得比较清楚的demo
因为就是一个比较小的TODO,对Controller进行拆分,分为Controller和Service显得有些多余,但是这样的结构应该是合理的.在比较大的项目就会发挥它的优势了.
对后端的具体实现大概了解了,学习暂且告一段落.
之前了解过Flutter,现在想要了解下Vue,然后再决定以后主攻方向.
可能会再回来学习各个中间件的使用和优化,或者跑去学Go.
就先这样啦.