定义视图函数及路由 基础 定义一个普通的视图函数及路由规则
1 2 3 4 5 6 7 from flask import Flaskapp = Flask(__name__) @app.route('/hello') def hello () : return 'Hello World!'
动态路由匹配
1 2 3 @app.route('/hello/<string:name>') def hello (name) : return 'Hello %s!' % name
其他类型转换器:
int 整型
float 浮点型
string 除/以外的字符串
path 包含/的字符串
uuid UUID字符串
any 匹配一系列给定值中的一个元素
any转换器
1 2 3 @app.route('/color/<any(blue, white, black):color>') def choice_color (color) : return 'Your Choice %s!' % color
视图函数默认会监听GET/POST/HEAD的请求方法, 如果想限制视图函数只能通过某个方法访问可以通过methods
参数限制, 其类型为一个列表。
1 @app.route('/hello', methods=['GET', 'POST'])
视图钩子 每个钩子可以处理多个函数, 函数名无需和钩子名一致。
钩子
说明
before_first_request
注册一个函数, 在处理第一次请求时运行
before_request
在处理每次请求书时运行
after_request
如果没有未处理的异常抛出则会在每个请求结束后运行
teardown_request
即时出现未处理的异常也会在每个请求结束后运行。如果发生异常则会把异常对象传入到注册的函数中
after_this_request
在视图函数内注册一个函数, 会在这个请求结束后运行
HTTP响应 视图函数的返回值可以自定义响应内容如指定响应状态码为201:
1 2 3 @app.route('/hello') def hello () : return 'Hello!' , 201
自定义响应状态码及响应头:
1 2 def follow () : return '' , 302 , {'Location' : 'https://www.baidu.com' }
使用redirect函数重定向:
1 2 def hello () : return redirect('https://www.baidu.com' )
redirect + url_for 重定向到其他视图:
1 2 def redirect_hello () : return redirect(url_for('hello' ))
make_response函数 使用make_response函数生成响应对象
1 2 3 4 5 6 7 8 9 10 11 12 import jsonfrom flask import Flask, make_response@app.route('/foo') def foo () : data = { 'name' : 'Join' , 'age' : 21 } response = make_response(json.dumps(data)) response.mimetype = 'application/json' return response
jsonify函数简化上述操作:
1 2 def foo () : return jsonify({'name' : 'Join' , 'age' : 21 })
Response对象
方法/属性
说 明
headers
WerkZeug 的 Headers对象, 表示响应头, 可以像字典操作
status
状态码, 文本型
status_code
状态码, 整型
mimetype
MIME类型
set_cookie()
设置Cookie
Flask上下文变量
变 量 名
上下文类别
说 明
current_app
程序上下文
指向处理请求的上下文实例
g
程序上下文
替代Python 的全局变量用法, 确保仅在当前请求中可用。用于存储全局数据, 每次请求都会重置
request
请求上下文
封装客户端发出的请求报文数据
session
请求上下文
用于记住请求之间的数据, 通过签名的Cookie实现
上图四个对象为真实对象的代理对象, 即通过代理对象(Proxy Object)访问真实对象。如果需要获取真实对象可以通过调用代理对象的_get_current_object()方法获取。
模板内置上下文 Flask中使用的模板引擎为JinJa2
, Flask在模板上下文中提供了一些内置变量, 可以在模板中直接使用。
变 量
说 明
config
当前的配置对象
request
当前的请求对象, 在已激活的请求环境下可用
sessio
当前的会话对象, 在已激活的请求环境下可用
g
与请求绑定的全局变量, 在已激活的请求环境下可用
模板自定义上下文 如果多个模板需要使用同一变量时, 避免在视图函数中重复传递变量, 更好的方法是设置一个模板全局变量。Flask提供了一个app.context_processor装饰器, 可以用来注册模板上下文处理函数。它可以帮我们完成统一传入变量的工作。模板上下文处理函数需要返回一个键值对的字典。
1 2 3 4 @app.context_processor def inject_foo () : foo = 'I am foo' return dict(foo=foo)
当调用render_template
渲染任意一个模板时, 所有使用app.context_processor
装饰器注册的模板上下文处理函数都会被执行。这些函数的返回值会被添加到模板中可直接使用。
除了装饰器的方法以外, 你也可以直接调用该函数传入对象以注册。
1 2 3 def inject_foo () : return dict(foo=foo) app.context_processor(inject_foo)
JinJa2模板引擎全局函数 全局函数列表
Flask内置的模板全局函数
函 数
说 明
url_for()
生成URL
get_flashed_messages
用于获取flash消息
自定义模板全局函数 方式一:app.template_global装饰器
1 2 3 @app.template_global def foo () : return 'I am foo'
方式二:app.add_template_global函数
1 2 app.add_template_global(func[,name])
过滤器 在Jinja2中,过滤器(filter)是一些可以用来修改和过滤变量值的特殊函数,过滤器和变量用一个竖线(管道符号)隔开,需要参数的过滤器可以像函数一样使用括号传递。下面是一个对name变量使用title过滤器的例子:
另一种用法是使用标签声明
1 2 3 {% filter upper %} This text becomes uppercase. {% endfilter %}
完整过滤器列表
同时使用多个过滤器
1 <h1>Hello, {{ name|default('默认值')|title }}!</h1>
不对变量进行转义的两种方法:
转换为Markup对象:
1 2 3 4 5 6 from flask import Flask, Markup@app.route('/hello') def hello () : text = Markup('<h1>Hello, Flask!</h1>' ) return render_template('index.html' , text=text)
自定义过滤器 使用app.template_filter
装饰器可以注册自定义过滤器。
1 2 3 4 5 from flask import Markup@app.template_filter def musical (s) : return s + Markup('♫' )
和注册全局函数类型, 可以使用name
可选参数为过滤器命名, 默认则为函数名称。
测试器 测试器(Test)可用于测试一些变量或表达式, 返回布尔值类型(True or False)的特殊函数。例如使用number
测试一个变量是否为数字。
1 2 3 4 5 {% if age is number %} {{ age * 365 }} {% else %} 无效数字 {% endif %}
完整测试器列表
如果测试器包含多个参数则可以使用如下两种形式:
1 2 3 4 {% if foo is sameas(bar) %} {% if foo is sameas bar %}
自定义注册器 跟前面的步骤一样, 代码:
1 2 3 4 5 @app.template_test() def baz (n) : if n == 'baz' : return True return False
模板环境对象 在Jinja2中,渲染行为由jinja2.Enviroment类控制,所有的配置选项、上下文变量、全局函数、过滤器和测试器都存储在Enviroment实例上。当与Flask结合后,我们并不单独创建Enviroment对象,而是使用Flask创建的Enviroment对象,它存储在app.jinja_env属性上。
在程序中,我们可以使用app.jinja_env更改Jinja2设置。比如,你可以自定义所有的定界符。下面使用variable_start_string和variable_end_string分别自定义变量定界符的开始和结束符号:
1 2 3 app = Flask(__name__) app.jinja_env.variable_start_string = '[[' app.jinja_env.variable_end_string = ']]'
模板环境中的全局函数、过滤器和测试器分别存储在Enviroment对象的globals、filters和tests属性中,这三个属性都是字典对象。除了使用Flask提供的装饰器和方法注册自定义函数,我们也可以直接操作这三个字典来添加相应的函数或变量,这通过向对应的字典属性中添加一个键值对实现,传入模板的名称作为键,对应的函数对象或变量作为值。下面是几个简单的示例。
添加自定义注册对象
1 2 3 4 5 def bar () : return 'I am bar.' foo = 'I am foo' app.jinja_env.globals['bar' ] = bar app.jinja_env.globals['foo' ] = foo
添加自定义过滤器
1 2 3 def smiling (s) : return s + ' :)' app.jinja_env.filters['smiling' ] = smiling
添加自定义测试器
1 2 3 4 5 def baz (n) : if n == 'baz' : return True return False app.jinja_env.tests['baz' ] = baz
Enviroment
局部模板 通常在Web应用程序中会出现很多模板代码复用的问题, 例如需要在多个模板中显示另一个模板的内容(假设为首页模板)此时如果在每个模板中都重复编写代码则会造成冗余且不易维护。因此可以定义一个局部模板, 当多个模板需要使用相同的内容时只需包含该模板即可。
Tip: 为了区分局部模板, 通常使用前导下划线命名
1 {% include '_banner.html' %}
模板继承 简单理解为局部模板的Plus版就好了。
编写基模板, 使用block和endblock标签声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html > <head > {% block head %} <meta charset ="utf-8" > <title > {% block title %}Template - HelloFlask {% endblock title %}</title > {% endblock head %} <nav > <ul > <li > <a href ="{{ url_for('index') }}" > Home</a > </li > </ul > </nav > <main > {% block context %} {% endblock context %} </main > </html >
当子模板继承基模板后, 子模板会自动包含基模板的内容和结构, 通过声明标签的形式可以修改基模板中的内容。如果在子模板中修改了基模板定义的块则会覆盖。
1 2 3 4 5 {% extends 'base.html' %} <h1 > Template</h1 > {% block context %} <p > DoSomethink</p > {% endblock context %}
追加内容(不会覆盖基模板中原先定义的)
1 2 3 4 {% block context %} {{ super() }} <p > Chaned Somethink</p > {% endblock context %}
加载静态文件 在Flask项目的根目录下新建static
文件夹, 便可使用url_for
函数生成静态文件的URL。
1 2 url_for('static' , filename='avatar.jpg' )
而在模板文件中引入CSS则可以通过如下方式:
1 <link rel ="stylesheet" type ="text/css" href ="{{ url_for('static', filename='style.css') }}" >
消息闪现 在视图函数中通过flash
函数传递一个闪现消息
, 该消息会存储在session中, 在模板文件中可以通过循环get_flashed_message
函数的返回值获取消息。当调用该函数时存储在session中的消息便会销毁。
1 2 3 4 5 6 app.config['SECRET_KEY' ] = os.urandom(24 ) @app.route('/flash') def just_flash () : flash('I am flash, who is looking for me?' ) return redirect(url_for('index' ))
模板文件:
1 2 3 {% for message in get_flashed_message() %} <div class ="alert" > {{ message }}<div > {% endfor %}
自定义错误页面 使用app.errorhandler
装饰器传入指定的错误状态码装饰视图函数, 视图函数中需要接收异常对象。
1 2 3 @app.errorhandler(404) def page_not_found (e) : return render_template('errors/404.html' ), 404
异常对象常用属性
属 性
说 明
code
状态码
name
原因短语
description
错误描述, 另外使用get_description()还可以获取HTML格式的错误描述的代码
Flask-WTF处理表单 安装拓展pip install flask_wtf
字段类常用参数
WTForms验证器
验证器
说明
DataRequired(message=None)
验证数据是否有效
Email(message=None)
验证Email地址
EqualTo(fieldname, message=None)
验证两个字段值是否相同
InputRequired(message=None)
验证是否有数据
Length(min=-1, max=-1, message=None)
验证输入值是否在给定范围内
NumerRange(min=None, max=None, message=None)
验证数字是否在给定范围内
Optional(strip_whitespace=True)
允许输入值为空, 并跳过其他验证
Regexp(regex, flags=0, message=None)
使用正则验证输入值
URL(require_tld=True, message=None)
验证URL
AnyOf(values, message=None, values_formatter=None)
确保输入值在可选值列表值中
NoneOf(values, message=None, values_formatter=None)
确保输入值不在可选值列表中
定义一个表单类, 实例化对象传入模板文件中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from wtforms import Form, StringField, PasswordField, SubmitField, BooleanFieldfrom wtforms.validators import DataRequired, Lengthclass LoginForm (Form) : username = StringField('Username' , validators=[DataRequired()]) password = PasswordField('Password' , validators=[DataRequired(), length(8 , 120 )]) remember = BooleanField('Rememeber me' ) submit = SubmitField('Log in' ) @app.route('/basic') def basic () : form = LoginForm() return render_template('login.html' , form=form)
1 2 3 4 5 6 7 8 <form method ="POST" > {{ form.csrf_token }} {{ form.username }}<br /> {{ form.password }}<br /> {{ form.remember }}<br /> {{ form.submit }} </form >
使用validate_on_submit
函数判断表单字段是否通过验证, 验证成功则返回True, 反则会将错误消息添加到表单类的errors属性中。
1 2 3 4 5 6 7 8 @app.route('/basic', methods=['GET', 'POST']) def basic () : form = LoginForm() if form.validate_on_submit(): username = form.username.data flash('Welcome Home, %s!' % username) return redirect(url_for('index' )) return render_template('login.html' , form=form)
验证不通过打印错误消息
1 2 3 {% for message in form.username.errors %} <font color ="red" > {{ message }}</font > {% endfor %}
使用宏渲染表单字段, 可节省代码工作, 让其接收一个field字段对象和关键字参数。
1 2 3 4 5 6 7 8 9 10 {% macro form_field(field) %} {{ field.label }}<br /> {{ field.(**kwargs) }} {% if field.errors %} {% for message in field.errors %} <font color ="red" > message</font > {% endfor %} {% endif %} {% endmacro %}
在模板文件中使用宏:
1 2 3 4 5 6 {% from 'macro.html' import form_field %} <form method ="POST" > {{ form.csrf_token }} {{ form_field(form.username) }} {{ form_field(form.password) }} </form >
自定义验证器 1 2 3 4 5 6 7 8 9 from flask_wtf import FlaskFormfrom wtforms.validators import ValidationErrorclass FortyTwoForm (FlaskForm) : answer = IntegerField('The Number' ) submit = SubmitField() def validate_answer (form, field) : if field.data != 42 : return ValidationError('Must be 42.' )
自定义通用验证器 1 2 3 4 5 6 7 8 9 10 11 12 from wtforms.validators import ValidationErrordef is_42 (message=None) : if message is None : message = 'Must be 42.' def _is_42 (form, field) : if field != 42 : raise ValidationError(message) return _is_42 class Form (FlaskForm) : answer = IntegerField('The Number' , validators=[is_42()])
上传表单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import uuidfrom flask import Flask, send_from_directoryfrom flask_wtf import FlaskFormdef random_filename (filename) : ext = os.path.splitext(filename)[1 ] return uuid.uuid4().hex + ext class UploadForm (FlaskForm) : photo = FileField('Image' , validators=[FileRequired(), FileAllowed(['jpg' , 'png' , 'gif' ])]) submit = SubmitField('Submit' ) @app.route('uploads/<path:filename>') def get_file (filename) : return send_from_directory(app.config['UPLOAD_PATH' ], filename) @app.route('/show_images') def show_images () : return render_template('uploaded.html' ) @app.route('/upload', methods=['GET', 'POST']) def upload_image () : form = UploadForm() if form.validate_on_submit(): f = form.photo.data filename = random_filename(f.filename) f.save(os.path.join(app.config['UPLOAD_PATH' ], filename)) session['filename' ] = [filename] return redirect(url_for('show_images' )) return render_template('upload.html' , form=form)
模板文件uploaded.html
1 2 3 4 5 6 7 8 9 10 <html > <head > <meta charset ="utf-8" > <title > Show Images</title > </head > <body > <img src ="{{ url_for('get_file', filename=session.filename)}}" alt ="" > </body > </html >
SQLAlchemy拓展 安装拓展
1 pip3 install flask_sqlalchemy
配置数据库URI及实例化:
1 2 3 import osapp.config['SQLALCHEMY_DATABASE_URI' ] = os.getenv('DATABASE_URL' , 'sqlite:///' + os.path.join(app.root_path + 'data.db' ))
定义数据库模型
模型类对应数据库中的表, 需继承自db.Model
基类。
1 2 3 4 5 6 class Node (db.Model) : id = db.Column(db.Integer, primary_key=True ) body = db.Column(db.Text) db.create_all()
SQLAlchemy常用字段类型
字段
说明
Integer
整数
String
字符串, 可选参数length可设置最大长度
Text
较长的Unicode文本
Date
存储Python的datetime.date对象
Time
datetime.time对象
DateTime
datetime对象
Interval
datetime.timedelta对象
Float
浮点数
Boolean
布尔值
PickleType
存储Pickle列化的Python对象
LargeBinary
存储二进制数据
db.Column类参数
参数名
说明
primary_key
设为True则该字段为主键
unique
设为True则该字段内容不可重复
index
设为True则为该字段设置索引, 提高查询效率
nullable
设为True则字段可为空
default
为字段设置默认值
***
SQLAlchemy查询方法, <模型类>.query.<过滤方法>.<查询方法>
查询方法
说明
all()
返回包含所有查询记录的列表
first()
返回查询的第一条记录, 如果未找到则返回None
one()
返回第一条记录, 且只允许有一条记录。不为一条则抛出错误
get(ident)
传入主键的值作为参数, 返回指定主键值的记录。未找到则为None
count()
返回查询结果集的数量
one_or_none()
类似one, 结果集不为1则返回None
first_or_404()
返回查询的第一条记录, 未找到则返回404响应
get_or_404(ident)
同上
paginate()
返回一个Paginate对象, 可对记录分页处理
SQLAlchemy常用过滤方法
过滤器名称
说明
filter()
使用指定的规则过滤, 返回新产生的查询对象
filter_by()
使用指定规则过滤记录(以关键字表达式的形式), 返回新产生的查询对象
order_by()
根据指定条件对记录进行排序, 返回新产生的查询对象
limit(limit)
使用指定的值限定原查询记录数量, 返回新产生的查询对象
group_by()
根据指定条件对记录进行分组, 返回新产生的查询对象
offset(offset)
使用指定的值偏移原查询的结果集, 返回新产生的查询对象
使用filter过滤器查询body字段为SHAVE
的记录。filter过滤器还可以使用如like
, in_
, not in
, and
, or
等条件。
1 2 3 4 5 6 7 8 9 10 11 Note.query.filter(Note.body=='SHAVE' ).first() print(Note.query.filter(Note.body=='SHAVE' )) Note.query.filter(Note.body.like('%foo%' )).first() Note.query.filter(Note.body.in_(['foo' , 'bar' ])).first() Note.query.filter(~Note.body.in_['foo' , 'bar' ]).first() Note.query.first(and_(Note.body == 'foo' , Note.title == 'Foo' ))
更新记录
1 2 3 note = Note.query.get(2 ) note.body = 'New Content' db.session.commit()
删除记录
1 2 3 note = Note.query.get(2 ) db.session.delete(note) db.session.commit()
实现添加新笔记视图 模型类:app/models/NoteModel.py
1 2 3 4 5 6 from flask_sqlalchemy import SQLAlchemyclass NoteModel (db.Model) : __tablename__ = 'note' id = db.Column(db.Integer, primary_key=True ) body = db.Column(db.Text)
表单类:app\forms\NoteForm.py
1 2 3 4 5 6 7 from flask_wtf import FlaskFormfrom wtforms import TextAreaField, SubmitFieldfrom wtforms.validators import DataRequiredclass NoteForm (FlaskForm) : body = TextAreaField('Body' , validators=[DataRequired()]) submit = SubmitField('Save' )
应用程序:app\app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import osfrom flask import Flask, flash, render_template, redirect, url_for from models import NoteModel app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI' ] = os.getenv('DATABASE_URL' , 'sqlite:///' + os.path.join(app.root_path, 'data.db' )) app.config['SECRET_KEY' ] = os.urandom(24 ) db = SQLAlchemy(app) @app.route('/new', methods=['GET', 'POST']) def new_note () : form = Note() if form.validate_on_submit(): body = form.body.data note = NoteModel(body=body) db.session.add(note) db.session.commit() flash('Your Note Saved.' ) return redirect(url_for('index' )) return render_template('new_note.html' , form=form)
模板:new_note.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <html > <head > <meta charset ="utf-8" > <title > New Note</title > </head > <body > <h2 > New Note</h2 > <form action ="#" method ="POST" > {{ form.csrf_token }} {{ form.body.label }} {{ form.body(rows=5, cols=50) }}<br /> {{ form.submit }} </form > </body > </html >
index视图:
1 2 3 @app.route('/index') def index () : return render_template('index.html' )
模板:index.html
1 2 3 4 5 6 7 8 9 10 11 <html > <head > <meta charset ="utf-8" > <title > Note Book</title > </head > <body > <h1 > Note Book</h1 > <a href ="{{ url_for('new_note') }}" > New Note</a > </body > </html >
程序执行流程
客户端(浏览器)访问路由http://127.0.0.1/new
, new_note视图函数开始执行。 实例化Note类, 定义表和字段类型。当表单的提交按钮被点击时则form.validate_on_submit
方法返回True, 进入分支代码块。 获取表单输入的内容后添加到数据库中, 然后跳转到index视图。
修改首页视图和模板文件 1 2 3 4 @app.route('/index') def index () : notes = Node.query.all() return render_template('index.html' , notes=notes)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <html > <head > <meta charset ="utf-8" > <title > Note Book</title > </head > <body > <h1 > Note Book</h1 > <a href ="{{ url_for('new_note') }}" > New Note</a > <h4 > {{ notes|length }} notes:</h4 > {% for note in notes %} <div > <p > ID: {{ note.id }} - 内容: {{ note.body }}</p > </div > {% endfor %} </body > </html >
编辑笔记内容 编辑表单:app\forms\EditNote.py
1 2 class EditNoteForm (Note) : submit = SubmitField('Update' )
编辑视图:app\app.py
1 2 3 4 5 6 7 8 9 10 @app.route('/edit/<int:note_id>', methods=['GET', 'POST']) def edit_note (note_id) : form = EditNoteForm() note = Note.query.get(note_id) if form.validate_on_submit(): note.body = form.body.data db.session.commit() return redirect(url_for('index' )) form.body.data = note.body return render_template('edit_note.html' , form=form)
模板文件:edit_note.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <html > <head > <meta charset ="utf-8" > <title > Note Book</title > </head > <body > <h2 > Edit Note</h2 > <form action ="#" method ="POST" > {{ form.csrf_token }} {{ form.body.label }} {{ form.body }}<br /> {{ form.submit }} </form > </body > </html >
删除笔记 删除表单:
1 2 class DeleteNoteForm (FlaskForm) : submit = SubmitField('Delete' )
删除视图:
1 2 3 4 5 6 7 8 9 10 @app.route('/delete/<int:note_id>', methods=['POST']) def delete_note (note_id) : form = DeleteNoteForm() if form.validate_on_submit(): note = Note.query.get(note_id) db.session.delete(note) db.session.commit() else : abort(404 ) return redirect(url_for('index' ))
在首页视图中实例化删除表单对象
传入模板:
1 2 3 4 5 @app.route('/index') def index () : note = Note.query.all() form = DeleteNoteForm() return render_template('index.html' , note=note, form=form)
一对多表关系示例 以作者表和文章表演示数据库一对多关系, 将Article
表的author_id
字段作为外键关联Author
表的id
:
1 2 3 4 5 6 7 8 9 10 class Author(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=True) phone = db.Column(db.String(20)) class Article(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(50), index=True) body = db.Column(db.Text) author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
定义关系属性:
1 2 class Author (db.Model) : articles = db.relationship('Article' )
添加数据并关联表:
1 2 3 4 5 6 7 8 9 10 11 12 >>> foo = Author(name='Foo' , phone='18888888888' )>>> spam = Article(title='Spam' )>>> ham = Article(title='Ham' )>>> db.session.add(foo)>>> db.session.add(spam)>>> db.session.add(ham)>>> spam.author_id(1 )>>> foo.articles.append(ham)>>> db.session.commit()
relationship关系函数参数
参数名
说明
back_populates
定义反向引用, 用于建立双向关系。在关系的另一侧也必须显示定义属性
backref
添加反向引用, 自动在另一侧建立关系属性。是back_populates的简版
lazy
指定如何加载相关记录
uselist
指定是否使用列表的形式加载记录, 设为False则使用标量(scalar)
cascade
设置级联操作
order_by
指定加载相关记录时的排序方式
secondary
在多对多关系中指定关联表
primaryjoin
指定多对多关系中的一级联结条件
secondaryjoin
指定多对多关系中的二级联结条件
上述表格中lazy参数的可选值
关系加载方式
说明
select
在必要时一次性加载记录, 返回包含记录的列表, 等于lazy=True
joined
和父查询一样加载记录, 但使用联结, 等于lazy=False
immediate
一旦父查询加载就加载
subquery
类似于joined, 但使用子查询
dynamic
不直接加载记录, 而是返回一个包含相关记录的query对象, 以便再继续附加查询函数对结果进行过滤
一对多关系的双向关系示例, 每本书对应多个作者, 每个作者对应多本书。
1 2 3 4 5 6 7 8 9 10 class Writer (db.Model) : id = db.Column(db.Integer, primary_key=True ) name = db.Column(db.String(70 ), unique=True ) books = db.relationship('Book' , back_populates='writer' ) class Book (db.Model) : id = db.Column(db.Integer, primary_key=True ) title = db.Column(db.String(50 ), index=True ) writer_id = db.Column(db.Integer, db.ForeignKey('writer.id' )) writer = db.relationship('Writer' , back_populates='books' )
设置双向关系后, 即可通过将某个Writer
对象赋值给Book
对象的writer
属性建立关系。
1 2 3 4 5 6 7 8 9 10 >>> king = Writer(name='Stephen King' )>>> carrie = Book(title='Carrie' )>>> it = Book(title='IT' )>>> db.session.add(king)>>> db.session.add(carrie)>>> db.session.add(it)>>> db.session.commit()>>> carrie.writer = king>>> king.books
实战项目-SayHello 目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 . ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── requirements.txt ├── sayhello │ ├── __init__.py │ ├── commands.py │ ├── errors.py │ ├── forms.py │ ├── models.py │ ├── settings.py │ ├── static │ ├── templates │ └── views.py └── test_sayhello.py
首先编写项目的包构造文件, 实例化Flask类并导入全局配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import sysimport osfrom sayhello import app platform = sys.platform.startswith('win' ) if platform: prefix = 'sqlite:///' else : prefix = 'sqlite:////' SECRET_KEY = os.urandom(24 ) SQLALCHEMY_DATABASE_URI = prefix + os.path.join(os.path.dirname(app.root_path), 'data.db' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from flask import Flaskfrom flask_bootstrap import Bootstrapfrom flask_sqlalchemy import SQLAlchemyfrom flask_moment import Momentapp = Flask(__name__) app.config.from_pyfile('settings.py' ) app.jinja_env.rstrip_blocks = True bootstrap = Bootstrap() db = SQLAlchemy() moment = Moment() from sayhello import views, errors, commands
下面编写模型类并注册flask命令:
1 2 3 4 5 6 7 8 9 from datetime import datetimefrom sayhello import app, dbclass Message (db.Model) : id = db.Column(db.Integer, primary_key=True ) name = db.Column(db.String(20 )) body = db.Column(db.String(200 )) timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import clickfrom faker import Fakerfrom sayhello import dbfrom sayhello.models import Message@app.cli.command() @click.option('--drop', is_flag=True, help='清空数据表生成虚拟数据') def forge (drop) : db.drop_all() db.create_all() click.echo('数据已清空!' ) fake = Faker('zh_CN' ) for data in range(20 ): message = Message( name = fake.name(), body = fake.sentence(), timestamp = fake.date_time_this_year() ) db.session.add(message) db.session.commit() click.echo('已生成虚拟数据添加至数据库中!' )
运行flask forge
命令即可生成20条虚假数据至数据库中, 下面编写表单及视图类:
1 2 3 4 5 6 7 8 9 from flask_wtf import FlaskFormfrom wtforms.valiadtors import DataRequired, Lengthfrom wtforms import StringField, TextAreaField, SubmitFieldclass HelloForm (FlaskForm) : name = StringField('Name' , validators=[DataRequired(), Length(1 ,20 )]) body = TextAreaField('Message' , validators=[DataRequired, Length(1 ,200 )]) submit = SubmitField()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from flask import render_template, redirect, url_for, flashfrom sayhello import app, dbfrom sayhello.forms import HelloFormfrom sayhello.models import Message@app.route('/') def index () : form = HelloForm() message = Message.query.order_by(Message.timestamp.desc()).all() if form.validate_on_submit(): name = form.name.data body = form.body.data message = Message( name = name, body = body ) db.session.add(message) db.session.commit() flash('你的留言已发送至全世界!' ) return redirect(url_for('index' )) return render_template('index.html' , form=form, messages=messages)
蓝图 Blueprint 是一种组织一组相关视图及其他代码的方式。与把视图及其他 代码直接注册到应用的方式不同,蓝图方式是把它们注册到蓝图,然后在工厂函数中 把蓝图注册到应用。 – Flask官方文档
注册博客前台蓝图:
1 2 3 from flask import Blueprintblog_bp = Blueprint('blog' , __name__)
在实例化蓝图时可以指定url_prefix
或subdomain
, 在对应的视图函数中则会自动添加这些前缀无需声明。
1 2 3 4 5 6 7 from flask import Blueprintauth_bp = Blueprint('auth' , __name__, url_prefix='auth' ) """ admin_bp = Blueprint('admin', __name__, subdomain='admin') """
Blueprint API Documentation