Flask
1.介绍
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
官网: https://flask.palletsprojects.com/en/1.1.x/
官方文档: [http://docs.jinkan.org/docs/flask/]
2.准备工作
2.1创建虚拟环境
mkvirtualenv flask -p python3
或
mkvirtualenv flask
安装flask
pip3 install flask==0.12.5
2.2创建flask项目
与django框架不同,flask不会自动创建项目目录,需要手动创建启动项目的管理文件
创建项目文件夹flask_demo,
用pycharm打开该文件夹,选择创建好的虚拟环境
创建一个flask框架的主程序,main.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'hello world'
if __name__=='__mian__':
#app.run(debug=True,port=8000,host='0.0.0.0') #可以指定两个参数debug和端口(默认端口是5000)
app.run()
注释:
#导入Flask类
from flask import Flask
"""
import_name Flask程序所在的包(模块),传 __name__ 就可以
其可以决定 Flask 在访问静态文件时查找的路径
static_path 静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path 静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder 静态文件存储的文件夹,可以不传,默认为 static
template_folder 模板文件存储的文件夹,可以不传,默认为 templates
"""
app = Flask(import_name=__name__)
#编写路由视图
@app.route('/')
def index():
return '<h2>这是一个二级标题</h2>'
#加载项目配置
class Config(object):
#开启调试模式
DEBUG=True
#注册,flask中支持多种配置方式,通过app.config来进行加载;
app.config.from_object(Config)
#app.run中可以指定配置,端口,ip等
if __name__=='__main__':
#运行flask
app.run(host='0.0.0.0',port=5000,debug=True)
3.路由的基本定义
路由:一种绑定url访问地址和应用程序[视图]的一对一映射关系
1.路由和视图唯一性
路由和视图的名称必须全局唯一,不能出现重复,否则报错
@app.route('/index')
def index():
return 'index'
2.url中传递路由参数的两种方式
路由参数就是url路径的一部分
方式一.任意路由参数接收
[没有限定类型]路由传递参数
@app.route('/user/<user_id>') #传递多个参数(/user/<user_id>-<mobile>),在进行路径上传递的时候两个实参数之间用-隔开
def user_info(user_id): #这里参数名必须和路由上的参数名保持一致
return 'hello_%s' % user_id
方式二:限定类型传递参数
[限定类型]路由传递参数
限定路由参数的类型,flask系统自带转换器编写在werkzeug.routing.py文件中。底部可以看到以下字典:
DEFAULT_CONVERTERS = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}
转换器名称 | 描述 |
---|---|
string | 默认类型,接受不带斜杠的任何文本 |
int | 接受正整数 |
float | 接受正浮点值 |
path | 接收string 但也接受斜线 |
uuid | 接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx |
代码:
#flask提供了路由转换器可以对路由参数进行限定
@app.route('/user/<int:user_id>')
def user_info(user_id):
return 'hello_%s' % user_id
自定义路由参数转换器
在 web 开发中,可能会出现限制用户访问规则的场景,那么这个时候就需要用到正则匹配,根据自己的规则去限定请求参数再进行访问
(1)导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录
(2)自定义转换器:自定义类继承于转换器基类
(3)添加转化器到默认的转化器字典中,也就和原来的int .float等放在一块
(4)使用自定义转化器实现自定义匹配规则
限定了正则的格式:
#(1)导入转换器基类
from werkzeug.routing import BaseConverter
#(2)自定义正则转换器
class MobileConverter(BaseConverter):
'''手机号类型的限制'''
def __init__(self,**kwargs,*args):
super().__init__(**kwargs,*args): #在初始化中必须调用父类对象进行初始化
self.regex='^1[3-9]\d{9}' #regex必须有参数
#(3)把自定义的转化器添加到flask默认的转化器字典中;进行注册
#app.url_map.converters['别名']=转换器类名
app.url_map.converters['mob'] = MobileConverter
#(4)调用自定义转化器
@app.route('/user/<mob:mobile>')
def index(mobile):
return mobile
不限定正则,直接在路由里面写正则参数:
#(1)导入转换器基类
from werkzeug.routing import BaseConverter
#(2)自定义正则转换器
class RegexConverter(BaseConverter):
def __init__(self,map,*args):
super().__init__(map) #map相当于map_url
self.regex=args[0]
#(3)把自定义的转化器添加到flask默认的转化器字典中;
app.url_map.converters['re'] = RegexConverter
#(4)调用自定义转化器
@app.route(rule='/user/<re('^1[1-9]\d{9}'):mobile>)
def user(mobile):
print(app.url_map) #获取所有的路由列表
return email
3.路由限定请求方式
from flask import Flask,request
@app.route(rule='/user',methods=['post','put','get','delete','patch']) #没有写第二个参数methods默认只能由get方法请求过来
def user():
print(request.method) # 获取本次客户端的http请求方法
print(request.query_string) #获取本次客户端的查询字符串
print(request.path) #获取本次客户端请求的路由路径部分[去掉域名端口] /user
print(request.url) #获取本次客户端请求的http url地址 http://127.0.0.1:5000/user?user=1
return request.method
视图与url分离:
from flask import Flask
app=Flask(__name__)
def index():
return 'ok'
app.add_url_rule(rule='/',view_func='index')
if __name__=='__main__':
app.run(debug=True)
4.http的请求与响应
文档: http://docs.jinkan.org/docs/flask/api.html#flask.request
请求
1.request
(1)代表flask中当前请求的request对象
(2)作用:在视图函数中期初本次请求的数据
(3)导入:from flask import request
(4)代码位置:from flask.app import Request
2.常用属性
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求体的数据,并转换为字符串 只要是通过其他属性无法识别转换的请求体数据 最终都是保留到data属性中 | bytes类型 |
form | 记录请求中的html表单数据 | MultiDict |
args | 记录请求中的查询字符串,也可以是query_string | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的请求头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件列表 | * |
json | 记录ajax请求的json数据 | json |
from flask import Flask,request
app = Flask(__name__)
@app.route('/')
def index():
return ('hello,flask')
#携带查询参数
@app.route(rule='/args',methods=['post','get'])
def args():
#
print(request.args) # ImmutableMultiDict([('name', 'haci'), ('age', '12'), ('hobby', 'shopping'), ('hobby', 'study')])
#思路来源from collection import OrderedDict
# 把ImmutableMultiDict转换成普通字典,转换过程如果出现一个键对应多个值的情况,则取第一个值
print(request.args.to_dict()) # {'name': 'haci', 'age': '12', 'hobby': 'shopping'}
print(request.args.to_dict(flat=False)) # {'name': ['haci'], 'age': ['12'], 'hobby': ['shopping', 'study']}
print(request.args.get('name')) # haci
#getlist方法获取多选框的数据列表
print(request.args.getlist('hobby')) # ['shopping', 'study']
#获取请求方法
print(request.method) # GET
#获取请求url路径
print(request.url) # http://127.0.0.1:5000/args?name=haci&age=12&hobby=shopping&hobby=study
print(request.path) # /args
return 'ok'
#请求体数据data
@app.route(rule='/data',methods=['put','patch','post'])
def data():
"""接受客户端发送过来的请求体数据,是request.json,request.form,request.files等无法接受的数据,全部会保留到这里"""
print(request.data)
# 接受表单提交的数据
print(request.form) #ImmutableMultiDict([('name', 'liu'), ('age', '234')])
## 接受ajax或其他客户端提交过来的json数据
print(request.json) #{'name': 'haci', 'age': 12}
# 接受上传文件
print(request.files['video']) #postman中测试,form_data中选择file格式上传
# 获取请求头信息
print(request.headers) #Content-Type
print(request.headers['company']) # 获取自定义请求头
return 'ok'
# 声明和加载配置
class Config(object):
DEBUG=True
app.config.from_object(Config)
if __name__=='__main__':
app.run()
响应
flask默认支持2中响应方式:
数据响应: 默认响应html文本,也可以返回 JSON格式,或其他格式
页面响应:重定向(重定向到外站,url_for站内跳转–视图之间的跳转)
1.数据响应
(1)响应html文本
from flask import make_response
@app.route('/')
def index():
return "<img src=''>" #调用了make_response,是下面一句的简写
#return make_response("<img src=''>") #先判断是不是response的实力对象,如果不是就把它实例化一下
#return Response('ok') #make_response本质上就是Response
(2)返回json数据
在 Flask 中可以直接使用 jsonify 生成一个 JSON 的响应
form flask import Flask,request,jsonify
@app.route('/')
def index():
data={'name':'小白白','age':23}
return jsonify(data)
(3)返回其他类型数据
# 关于Response常用的参数
# Response(response="内容", status="http响应状态码",headers=自定义响应头,mimetype="数据格式")
#return Response(response="ok",status=201,headers={"company":"hello"})
# 返回图片信息
with open('./1.zip',"rb") as f:
content=f.read()
# 判断权限,身份...
return Response(response=content,mimetype="application/zip")
2.重定向
(1)重定向到外部网站
from flask import Flask,request,redirect
app=Flask(__name__)
@app.route('/')
def user():
return redirect('http://www.baidu.com')
(2)重定向到自己写的视图函数
可以直接填写自己的url路径;也可以使用url_for生成指定视图函数所对应的url
from flask import url_for
@app.route('/')
def user1():
return 'my life'
@app.user2('/user2')
def user2():
return redirect(url_for('user1',name='xiaoming ')) #携带查询参数
重定向到带有参数的视图函数
#路由传递参数
@app.route('/user/<user_id>')
def user_info(user_id):
return 'hello_%s' % user_id
#重定向
@app.demo4():
return redirect(url_for(endpoint='user_info',user_id=100)) #携带的参数为路径的一部分
3.自定义状态码和响应头
在 Flask 中,可以很方便的返回自定义状态码,以实现不符合 http 协议的状态码,例如:status code: 666
@app.route('/demo4')
def demo4():
return '状态码为 666', 400
"""还可以使用make_response创建Response对象,然后通过response对象返回数据"""
from flask import make_response
@app.route("/rep")
def index7():
response = make_response("ok")
print(response)
response.headers["Company"] = "oldboy" # 自定义响应头
response.status_code = 201 # 自定义响应状态码
return response
5.Http的会话控制
http会话,就是客户端浏览器和服务端网站之间一次完整的交互过程,会话开始于用户通过浏览器第一次访问服务端网站;会话结束于用户关闭浏览器.
会话控制,是在客户端浏览器和服务端网站之间,进行多次http请求响应之间,记录,跟踪和识别用户的信息而已.
http是一种无状态协议,浏览器请求服务器是无状态的.
无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象
实现状态保持主要有两种类型:
- 在客户端存储信息使用
url
,Cookie
,token令牌[jwt.csrf,oauth]
- 在服务器端存储信息使用
Session
cookie
Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie的key/value可以由服务器端自己定义。
Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用.
Cookie基于域名安全,不同域名的Cookie是不能互相访问的.
(如访问luffy.com时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到luffy.com写的Cookie信息)
浏览器的同源策略针对cookie也有限制作用.
当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息
设置cookie:
response=make_response('ok')
response.set_cookie('键','值',有效期)
获取cookie:
request.cookies
request.cookies.get('username')
删除cookie:(把对应名称的cookie设置为过期时间,就可以删除对应的cookie)
response.set_cookie('键','',0)
代码:(设置cookie,获取cookie,删除cookie)
from flask import Flask,make_response,request
app = Flask(__name__)
@app.route('/set_cookie')
def set_cookie():
"""设置cookie"""
response = make_response('ok')
# response.set_cookie(key='',value='',max_age)
response.set_cookie('username','haci',100)
response.set_cookie('age','100')
return response
@app.route('/get_cookie')
def get_cookie():
"""获取cookie"""
print(request.cookies)
print(request.cookies.get('username'))
print(request.cookies['age'])
return 'ok'
@app.route('/del_cookie')
def del_cookie():
'''删除cookie'''
response=make_response('ok')
#把对应名称的cookie设置为过期时间,则可以达到删除cookie
response.set_cookie('age','',0)
return response
if __name__=='__main__':
app.run(debug=True)
session
session相关配置文档:https://dormousehole.readthedocs.io/en/latest/config.html?highlight=session_cookie_path
session是密文存储,且体积小,session依赖于cookie,session_id一般默认通过cookie来保存到客户端.
flask中的session需要加密,所以使用session之前必须配置SECRET_KEY选项,否则报错.
session的有效期默认是会话器,会话结束了,session就废弃了.
设置session:
session['键'] = 值
获取session:
session.get('键')
删除session:
del session['键']
session.clear() #清除所有
代码:
from flask import Flask,session,Session
app = Flask(__name__)
#加载配置
class Config():
SECRET_KEY = '123wertyhngf'
DEBUG = True
app.config.from_object(Config)
@app.route(rule='/set_session')
def set_session():
"""设置session"""
session['username'] = 'haci'
session['age'] = 18
session['info'] = {
'number':90,
'gender':1,
}
return ''
@app.route('/get_session')
def get_session():
"""获取session"""
print(session.get('username'))
print(session['age'])
print(app.session_cookie_name)
return 'ok'
@app.route('/del_session')
def del_session():
try:
"""删除session"""
del session['username']
#session.clear() # 删除所有
except:
pass
return ''
if __name__ == '__main__':
app.run()
查看当前flask默认支持的所有配置项
print(app.config)
"""
<Config {
'DEBUG': False,
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': '__main__',
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None
"""
6.请求钩子
在客户端和服务端交互的过程中,有些准备工作或扫尾工作需要处理,比如:
-
在请求开始时,建立数据库连接;
-
在请求开始时,根据需求进行权限校验;
-
在请求结束时,指定数据的交互格式;
为了避免每个视图函数写重复功能的代码,Flask提供了通设置的功能,即请求钩子:
请求钩子以装饰器的形式实现,Flask支持如下戏中请求钩子:
(1)before_first_request:在处理第一个请求前执行[项目初始化时的钩子],可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
(2)before_request:在每次请求之前执行;如果在某修饰的函数中反悔了一个响应,视图函数将不再被调用
(3)after_request:如果没有抛出错误,则在每次请求后执行;接受一个参数(视图函数做出的响应);在此函数中可以对象用值在返回之前做最后一步的修改处理
(4)teardown_request:在每次请求后执行;接受一个参数:错误信息,如果有相关错误抛出;需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。
代码:
from flask import Flask,request,make_response app = Flask(__name__) class Config(): DEBUG=True app.config.from_object(Config) @app.before_first_request def first_request(): print('1.项目启动以后,首次被请求时,[项目全局初始化工作]') @app.before_request def before_request(): print('2.每次客户端请求时,都会自动执行,常用于记录访问日志,进行权限判断,身份识别,访问限流...') @app.after_request def after_request(response): # after_request执行以后,必须要返回结果给客户端!! print('4.每次视图执行以后,会自动执行') return response @app.teardown_request def teardown_request(exc): print('5.after_request完成以后,如果有发生异常,在关闭DEBUG模式的情况下可以接受异常对象,进行异常的记录,异常通知') print(exc) @app.route('/') def index(): print('3.视图执行了.......') return 'success' if __name__ == '__main__': app.run()
7.异常捕获
主动抛出HTTP异常
abort方法:抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)
abort(400) abort(500)
捕获错误
errorhandler装饰器:
注册一个错误处理程序,当程序抛出指定错误状态码或者指定异常的时候,就会调用该装饰器所装饰的方法.
参数:错误状态码或者指定异常
from flask import Flask,abort app = Flask(__name__) #创建一个flask应用 #加载配置 class Config(): DEBUG=True app.config.from_object(Config) #在主函数中抛出400的错误abort(400),在此处进行定义来捕获该错误 @app.errorhandler(400) def error_400(e): return 'you are worry!!!' #捕获指定异常类型ZeroDivisionError @app.errorhandler(ZeroDivisionError) def zero_division_error(e): return '除数不能为0' @app.route('/') def index(): # abort(400) # 7/0 return 'ok' if __name__=='__main__': app.run()
7.context
执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。
Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。
1.application指的是创建Flask对象:app=Flask(__name__)的app; 2.request指的是每次http请求发生时,WSGI server调用Flask.__call__()之后,在Flask对象内部创建的Request对象; 3.application表示用于响应WSGI请求的应用本身,request表示每次http请求; 4.application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以也会有多个request 5.请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端;应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等
请求上下文(request context)
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
- request
- 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get(‘user’),获取的是get请求的参数。
- session
- 用来记录请求会话中的信息,针对的是用户信息。举例:session[‘name’] = user.id,可以记录用户信息。还可以通过session.get(‘name’)获取用户信息。
请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连接了哪个数据库
- 有哪些可以调用的工具类、常量
- 当前flask应用在哪个机器上,哪个IP上运行,内存多大
from flask import Flask #创建flask应用 app = Flask(__name__) #声明和加载配置 class Config(): DEBUG=True app.config.from_object(Config) #编写路由视图 @app.route(rule='/') def index(): print(current_app.config) print(current_app.url_map) return 'ok' if __name__=='__main__': app.run()
- request
g变量
g作为flask程序全局的一个临时变量,充当着中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
from flask import Flask,g,current_app
app = Flask(__name__)
class Config():
DEBUG=True
app.config.from_object(Config)
def func2():
print('func2')
print(g.abc)
def func1():
print('func1')
func2()
@app.route('/')
def index():
g.abc='视图中的数据'
func1()
return 'ok'
if __name__ == '__main__':
with app.app_context():
print(g)
app.run()
8.Flask-script扩展
文档: https://flask-script.readthedocs.io/en/latest/
安装命令:
pip install flask-script
集成 Flask-Script到flask应用中,创建一个主应用程序,一般我们叫manage.py/run.py/main.py
都行。
1.通过终端来控制flask项目的运行,类似于django的manage.py
from flask import Flask
from flask_script import Manager
app= Flask(__name__)
class Config():
DEBUG=True
app.config.from_object(Config)
# 注册终端脚本工具到app中
manager = Manager(app)
@app.route('/')
def index():
return 'okkkkkk'
if __name__=='__main__':
manager.run()
终端启动程序命令:
# 端口和域名不写,默认为127.0.0.1:5000
python run.py runserver
# 通过-h设置启动域名,-p设置启动端口
python run.py runserver -h127.0.0.1 -p8000
2.为当前应用程序添加脚本命令
1.引入Command基类
2.创建命令类必须直接或间接继承Command,并子啊内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3.将创建的命令注册,并设置调用终端别名
代码:
from flask import Flask
from flask_script import Manager,Command,Option
app=Flask(__name__)
"""基于flask_script创建自定义终端命令"""
class HelloCommand(Command):
option_list = [
Option('--name','-n',help='名称'),
Option('--num','-m',help='数量')
]
def run(self,name,num):
print('name=%s' % name)
print(num)
print('命令执行了')
# 注册终端脚本工具到app中
manager = Manager(app)
manager.add_command('hello',HelloCommand)
class Config():
DEBUG=True
app.config.from_object(Config)
@app.route('/')
def index():
return 'okhhhhhh'
if __name__=='__main__':
manager.run()
执行:
python main.py hello
python main.py hello -n=100 -m=23 #添加了option_list后给run函数中传递参数
3.自定义脚手架
from flask import Flask
from flask_script import Manager,Command,Option
import os
app = Flask(__name__)
class Config():
DEBUG = True
app.config.from_object(Config)
class BluePrintCommand(Command):
option_list=[
Option('--name','-n',help='蓝图名称')
]
def run(self,name=None):
if name is None:
print('蓝图名称不能为空')
return
if not os.path.isdir(name):
os.mkdir(name)
open('%s/views.py' % name,'w')
open('%s/models.py' % name,'w')
with open('%s/urls.py' % name,'w') as f:
f.write('''from . import views
urlpatterns=[
]
''')
manager = Manager(app)
manager.add_command('blue',BluePrintCommand)
@app.route('/')
def index():
manager.run()
if __name__ == '__main__':
manager.run()
9.Jinja2模板引擎
Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能。
渲染模版函数
Flask提供的 render_template 函数封装了该模板引擎;
render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值
9.1模板的基本使用
1.在flask对象应用创建的时候,设置存放模板的文件夹,template_folder参数
app=Flask(__name__,template_folder='templates')
2.在视图函数中返回模板时,需要使用引入的render_template
return render_template('index.html')
9.2加载模板并传递数据到模板
在模板中,{{}} 来表示变量名,这种 {{}} 语法叫做 变量代码块
在视图函数中在render_template的第二个参数中存放的是要传递到模板中的键值对
main.py
from flask import Flask,render_template
app = Flask(__name__,template_folder='templates')
class Config():
DEBUG = True
app.config.from_object(Config)
@app.route('/')
def index():
data ={}
data['title'] = '我的第一个flask网页'
data['num'] = 100
return render_template('index1.html',**data)
if __name__ == '__main__':
app.run()
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{title}}</h1>
<h2>{{num}}</h2>
</body>
</html>
9.2使用模板语句if/for
{%for i in data%}
{%endfor%}
{%if%}
{%endif%}
for循环中的特殊变量:
变量 | 描述 |
---|---|
loop.index | 当前循环迭代的次数(从 1 开始) |
loop.index0 | 当前循环迭代的次数(从 0 开始) |
loop.revindex | 到循环结束需要迭代的次数(从 1 开始) |
loop.revindex0 | 到循环结束需要迭代的次数(从 0 开始) |
loop.first | 如果是第一次迭代,为 True 。 |
loop.last | 如果是最后一次迭代,为 True 。 |
loop.length | 序列中的项目数。 |
loop.cycle | 在一串序列间期取值的辅助函数。见下面示例程序。 |
Main.py
from flask import Flask,render_template
app=Flask(__name__,template_folder='templates')
@app.route('/')
def index():
data={}
data['data_list']=['this','that','he','she']
data['info']={
'name':'haci',
'age':12,
}
data['data_dict']=[
{'id':1,'name':'第一个课程'},
{'id':2,'name':'第二个课程'},
{'id':3,'name':'第四个课程'},
{'id':4,'name':'第个课程'},
{'id':5,'name':'第五个课程'}
]
return render_template('index2.html',**data)
if __name__=='__main__':
app.run(debug=True)
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>访问字典数据中的成员</p>
<h3>{{info["name"]}}</h3>
<h3>{{info.age}}</h3>
<p>访问列表数中的成员,点语法不支持负数下标</p>
<p>{{data_list.0}}</p>
<p>{{data_list[0]}}</p>
<p>{{data_list[-1]}}</p>
<p>判断</p>
{%if info.age < 18%}
<p>年龄太小了</p>
{%elif info.age < 40%}
<p>年龄刚好</p>
{%else%}
<p>result</p>
{%endif%}
<p>for循环</p>
<table border="1" align="center" width="680px">
<tr>
<th>序号</th>
<th>ID</th>
<th>名称</th>
</tr>
{%for data in data_dict%}
{%if loop.index%2 %} #隔行换色
<tr bgcolor="#ff7f50">
{%else%}
<tr>
{%endif%}
<td>{{loop.index0}}</td>
<td>{{data.id}}</td>
<td>{{data.name}}td>
</tr>
{%endfor%}
</table>
<ul>
{%for data in data_list%}
{%if loop.first%}
<li style="background-color: #000;color: coral">{{data}}</li>
{%elif loop.last%}
<li style="background-color:#000000;color: aqua;">{{data}}</li>
{%else%}
<li>{{data}}</li>
{%endif%}
{%endfor%}
</ul>
</body>
</html>
9.4模板中显示内置变量
request, #flask中代表当前请求的request对象
config, config.DEBUG, #从模板中直接访问Flask当前的config对象:
session,session[], #为Flask的session对象,显示session数据
url_for('...'), #url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接
#{{ url_for('index', post_id=1)}} -->/1
g变量 #在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出
Main.py
from flask import Flask,render_template,session
app=Flask(__name__,template_folder='templates')
class Config():
DEBUG=True
SECRET_KEY='123dhsjadhaslde'
app.config.from_object(Config)
@app.route('/')
def index():
data={}
session['username']='haci'
return render_template('index3.html',**data)
if __name__=='__main__':
app.run()
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>{{request.url}}</p>
<p>{{request.path}}</p>
<p>{{session}}</p>
<p>{{session.username}}</p>
<p>{{request.method}}</p>
<p>{{url_for("index")}}</p>
<p>{{config}}</p>
<p>{{config.DEBUG}}</p>
</body>
</html>
设置当前项目中模板语言:
9.5过滤器
1.常见的内置过滤器
字符串操作:
(1)safe:禁止转义
<p>{{ '<em>hello</em>' | safe }}</p>
(2)lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
(3)upper:把值转成大写
(4)title:把值中的每个单词的首字母都转成大写
(5)reverse:字符串反转
(6)format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
(7)striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>
<p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p> //出现大于号小于号,容易误删内容
(8)truncate:字符截断
<p>{{ '床前明月光,疑是地上霜。' | truncate(5,False,'...', 0)}}</p>
列表操作
(1)first:取出第一个元素
(2)last
(3)length:获取列表长度
(4)sum:列表求和
(5)sort:列表排序
main.py
from flask import Flask,render_template
app=Flask(__name__,template_folder='templates')
@app.route('/')
def index():
data={}
data['msg1']='gdgds7271hsahs'
data['msg2']='my life is so beautiful'
data['msg3']=[1,2,3,8,4,3,2]
data['msg4']='<a href="http://www.baidu.com">baidu</a>'
return render_template('index4.html',**data)
if __name__=='__main__':
app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>{{msg1 | upper}}}</p>
<p>{{msg2 | title}}</p>
<p>{{msg2 | capitalize}}</p>
<p>{{msg2 | reverse}}</p>
<h1>列表操作</h1>
<p>{{msg3 | unique | list}}</p>
<!-- [1, 2, 3, 8, 4]-->
<p>{{msg3 | first}}</p>
<p>{{msg3 | last}}</p>
<p>{{msg3 | length}}</p>
<p>{{msg3 | sum}}</p>
<p>{{msg3 | sort}}</p>
<p>{{msg4 | safe}}</p>
<p>{{msg4}}</p>
<p>{{msg4 | striptags}}</p>
</body>
</html>
2.自定义过滤器
过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:
- 一种是通过Flask应用对象的 add_template_filter 方法
- 通过装饰器来实现自定义过滤器
重要:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。
示例:给手机号进行部分屏蔽
#注意:
静态文件的存储需要进行设置:
在实例化Flask对象的时候,用static_folder参数来保存当前flask应用静态资源的文件夹;static_url_path来指定当前flask应用提供给外界访问该静态资源的路径前缀,必须以/开头
main.py
from flask import Flask,render_template
app = Flask(__name__,# 当前flask应用运行的模块
template_folder='templates',
static_folder='static',# 当前flask应用保存静态资源[css/js/img/音视频]
static_url_path='/static', # 当前flask应用提供给外界访问的路径前缀,必须以/开头
)
class Config():
DEBUG=True
SECRET_KEY='dhsajdhaud71237bsa'
app.config.from_object(Config)
#自定义过滤器
def do_mobile(content,string):
return content[:3] + string + content[-3:]
#将过滤器注册到app
app.add_template_filter(do_mobile,'mobile')
@app.route('/')
def index():
data={}
data['img']='<img src="/static/images/1.jpg">'
data['info_list']=[
{'id':14,'name':'xiaobaibai','num':'18434391584'},
{'id':26,'name':'hsci','num':'13935228374'}
]
return render_template('index5.html',**data)
if __name__=='__main__':
app.run(debug=True)
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>{{img | safe}}</p>
<table border="1px" width="680px">
<tr>
<th>序号</th>
<th>id</th>
<th>姓名</th>
<th>手机号</th>
</tr>
{%for info in info_list%}
<tr>
<td>{{loop.index}}</td>
<td>{{info.id}}</td>
<td>{{info.name}}</td>
<td>{{info.num | mobile('*****')}}</td>
</tr>
{%endfor%}
</table>
</body>
</html>
9.6模板的继承
在模板中,可能会遇到以下情况:
- 多个模板具有完全相同的顶部和底部内容
- 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
- 多个模板中具有完全相同的 html 代码块内容
像遇到这种情况,可以使用 JinJa2 模板中的 继承 来进行实现
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
- 标签定义的内容
{% block top %} {% endblock %}
-
相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
-
子模板使用 extends 指令声明这个模板继承自哪个模板
{%extends 'base.html'%} {%block title%} 填充自己的内容 {{super()}} #继承父模板中的内容 {%endblock title%}
-
父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()
Main.py
from flask import Flask,render_template
app=Flask(__name__,
static_folder='static',
static_url_path='/static',
template_folder='templates'
)
@app.route('/')
def index():
return render_template('index6.html')
if __name__=='__main__':
app.run(debug=True)
Base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
{%block title%}
模板标题
{%endblock title%}
</title>
</head>
<body>
{%block content%}
<p>父级模板的content</p>
{%endblock content%}
</body>
</html>
Index.html
{%extends "base.html%"}
{%block content%}
{{ super() }}
<p>子模板的内容</p>
{{ super() }}
{% endblock content %}
9.7在 Flask 项目中解决 CSRF 攻击
pip install flask_wtf
在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单
- 设置应用程序的 secret_key,用于加密生成的 csrf_token 的值
# 1. session加密的时候已经配置过了.如果没有在配置项中设置,则如下:
app.secret_key = "#此处可以写随机字符串#"
# 2. 也可以写在配置类中。
class Config(object):
DEBUG = True
SECRET_KEY = "dsad32DASSLD*13%^32"
"""加载配置"""
app.config.from_object(Config)
- 导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app
from flask.ext.wtf import CSRFProtect
CSRFProtect(app)
3.在表单中使用 CSRF 令牌:
<form method="post" action="/">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>
main.py
from flask import Flask,render_template,request
from flask_wtf import CSRFProtect
app=Flask(__name__,template_folder='templates')
csrf=CSRFProtect(app)
app.config['SECRET_KEY']='hashdasdh7283das73has'
@app.route('/')
def index():
data={}
return render_template('index7.html',**data)
@app.route('/login',methods=['POST'])
def login():
print(request.form)
return 'ok'
if __name__=='__main__':
app.run(debug=True)
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="{{url_for('login')}}" method="post">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
账号:<input type="text" name="username" value=""> <br>
密码: <input type="password" name="password" value=""> <br>
<input type="submit" value="login">
</form>
</body>
</html>
10.数据库操作
ORM 全拼Object-Relation Mapping
,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射
优点 :
- 只需要面向对象编程, 不需要面向数据库编写代码.
- 对数据库的操作都转化成对类属性和方法的操作.
- 不用编写各种数据库的
sql语句
.
- 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
- 不再需要关注当前项目使用的是哪种数据库。
- 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.
缺点 :
- 相比较直接使用SQL语句操作数据库,有性能损失.
- 根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.
Flask-SQLAlchemy
flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。
SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
SQLAlchemy: https://www.sqlalchemy.org/
中文文档: https://www.osgeo.cn/sqlalchemy/index.html
安装 flask-sqlalchemy【清华源】
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
如果连接的是 mysql 数据库,需要安装 mysqldb 驱动
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
安装flask-mysqldb时,注意
安装 flask-mysqldb的时候,python底层依赖于一个底层的模块 mysql-client模块
如果没有这个模块,则会报错如下:
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/
解决方案:
sudo apt-get install libmysqlclient-dev python3-dev
运行上面的安装命令如果再次报错如下:
dpkg 被中断,您必须手工运行 ‘sudo dpkg --configure -a’ 解决此问题。
则根据提示执行命令以下命令,再次安装mysqlclient
sudo dpkg --configure -a
apt-get install libmysqlclient-dev python3-dev
解决了mysqlclient问题以后,重新安装 flask-mysqldb即可。
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
数据库连接设置
数据库连接设置
-
在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中
config.py,配置文件代码:
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"
- 其他设置:
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
#查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
- 配置完成需要去 MySQL 中创建项目所使用的数据库
$ mysql -uroot -p123
mysql > create database students charset=utf8mb4;
常用的SQLAlchemy字段类型
模型字段类型名 | python中数据类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 普通数值,一般是32位 |
String | str | 变长字符串 |
Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 日期 |
Time | datetime.time | 时间 |
DateTime | datetime.datetime | 日期和时间 |
LargeBinary | str | 二进制文件内容 |
常用的SQLAlchemy列约束选项
选项名 | 说明 |
---|---|
primary_key | 如果为True,代表表的主键 |
unique | 如果为True,代表这列不允许出现重复的值 |
index | 如果为True,为这列创建索引,提高查询效率 |
nullable | 如果为True,允许有空值,如果为False,不允许有空值 |
default | 为这列定义默认值 |
数据库基本操作
- 在Flask-SQLAlchemy中,添加、修改、删除操作,均由数据库会话管理。
- 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.commit() 方法提交会话。
- 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
- 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
1.定义模型类(创建和删除表)
from flask import Flask,render_template,request
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)
class Config():
DEBUG=True
#数据库连接配置
SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/students?charset=utf8mb4'
#动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS=True
#显示原始SQL语句
SQLCHEMY_ECHO=True
app.config.from_object(Config)
# 初始化SQLAlchemy
db=SQLAlchemy() # 初始化数据库操作对象
db.init_app(app) # 初始化数据库链接
"""创建模型类"""
class Student(db.Model):
# 表结构声明
__tablename__='tb_student'
# 字段声明
id=db.Column(db.Integer,primary_key=True,comment='主键ID')
name=db.Column(db.String(250),comment='姓名')
age=db.Column(db.Integer,comment='年龄')
sex=db.Column(db.Boolean,default=True,comment='性别')
money=db.Column(db.DECIMAL(8,2),nullable=True,comment='钱包')
# 自定义方法
def __repr__(self):
return self.name
class Teacher(db.Model):
__tablename__='tb_teacher'
id=db.Column(db.Integer,primary_key=True,comment='ID')
name=db.Column(db.String(250),comment='姓名')
sex=db.Column(db.Boolean,default=False,comment='性别')
option=db.Column(db.Enum("讲师","助教","班主任"),default='讲师',comment="教职")
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__='tb_course'
id=db.Column(db.Integer,primary_key=True,comment='ID')
name=db.Column(db.String(250),unique=True,comment='课程名称')
price=db.Column(db.Numeric(6,2))
def __repr__(self):
return self.name
@app.route('/')
def index():
return 'ok'
if __name__=="__main__":
with app.app_context():
db.create_all() # 根据模型创建所有的数据表
#db.drop_all() # 删除模型对应的所有数据表
app.run()
2.数据库基本操作(视图函数中)
添加数据:
#添加一条数据:
student1=Student(name='xiaobaibai',age=12,money=567)
db.session.add(student1)
db.session.commit()
#批量添加多条数据:
data_list=[
Student(name='haci',age=43,money=897),
Student(name='haci222',age=23,money=90),
Student(name='dhjsa',sex=True,money=82822,age=25)
]
db.session.add_all(data_list)
db.session.commit()
查询数据:
#根据主键ID查询一条数据,如果ID不存在,则返回None不会报错!
student=Student.query.get(100)
if student is None:
print('the studnet dosenot exist!!!')
else:
print(student,type(student)) #Student的对象
print(student.name,student.age)
#根据查询条件获取一条数据
# student=Student.query.filter(Student.id==1) #不能使用get
print(student) #打印的是sql语句
print(student[0],'----',type(student[0])) #Student的对象
print(student.first(),'=====')
print(student.first().name)
#根据查询条件获取多条数据
#模型.query.filter(模型.字段==条件值).all()
studnet_list=Student.query.filter(Student.id<5).all()
print(studnet_list)
for i in studnet_list:
print(i.age,i.money)
更新数据:
#先查询后修改
student=Student.query.filter(Student.name=='haci').first()
student.money+=1000
db.session.commit()
#直接根据条件修改(乐观锁---边查询边修改)
Student.query.filter(Student.name=='xiaobaibai').update({Student.money:2000})
Student.query.filter(Student.name=='haci').update({Student.money:Student.money+890})
db.session.commit()
删除数据:
# 先查询后删除
student=Student.query.filter(Student.age==23).first()
db.session.delete(student)
db.session.commit()
# 直接根据条件进行删除操作
Student.query.filter(Student.id==1).delete()
db.session.commit()
3.数据库的进阶查询
1.filter设置判断条件查询
== , >=, <, >, <=, !=
2.filter设置模糊查询
#like模糊条件
# 模型.字段.like("%值%") 等价于 模型.字段.contains("值") 包含xxx
# 模型.字段.like("值%") 等价于 模型.字段.startswith("值") 以xxx开头
# 模型.字段.like("%值") 等价于 模型.字段.endswith("值") 以xxx结尾
# 模型.字段.like("__") 值长度为2个字符的.几个下划线代表几个字符
3.filter多条件查询
from sqlalchemy import and_,or_,not_
#and_
student_list=Student.query.filter(and_(Student.age==12,Student.sex==1)).all()
#等价于
student_list=Student.query.filter(Student.age==12,Student.sex==1).all()
#or
stduent_list=Student.query.filter(or_(Student.age==23,Student.name=='haci')).all()
#not_
student_list=Student.query.filter(not_(Student.age==23)).all()
#in_ 值范围查询
student_list=Student.query.filter(Student.age.in_([23,14,6])).all()
4.filter_by查询
filter_by只支持一个符号作为判断条件,而且字段左边不需要声明模型类型;
可用于获取单条数据也可用于获取多条数据;
student=Student.query.filter_by(name=='haci').all()
5.order_by结果排序
#order_by(模型.字段.desc()) db.desc(模型.字段) 倒序
#order_by(模型.字段.asc()) db.asc(模型.字段) 升序
Student_list=Student.query.order_by(Student.age.desc()).all()
student_list=Student.query.order_by(db.desc(Student.money)).all()
6.count 统计结果数量
ret=Student.query.filter(Student.age==12).count()
7.limit对结果数量进行限制,offset对开始位置进行限制
student_list=Student.query.order_by(Student.money).offset(1).limit(3).all()
8.paginate分页器
#paginate(page=当前页面,per_page=每一页数据量,max_per_page=每一页最大数据量)
#当前页码.默认是从request.args['per_page']中读取,如果当前参数没有设置值,则默认为1;
#每页的数据量.默认是100条
分页器提供了request.args['per_page']和request.args['page']给客户端,客户端可以进行设置来限定;
#代码:
main.py
from flask import Flask,jsonify,render_template,request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__,template_folder='templates')
class Config():
DEBUG=True
JSON_AS_ASCII=False
SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/students?charset=utf8mb4'
SQLALCHEMY_TRACK_MODIFICATIONS=True
SQLALCHEMY_ECHO=True
app.config.from_object(Config)
db=SQLAlchemy()
db.init_app(app)
"""创建模型类"""
class Student(db.Model):
__tablename__ = "tb_student"
id = db.Column(db.Integer, primary_key=True,comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
age = db.Column(db.Integer, comment="年龄")
sex = db.Column(db.Boolean, default=False, comment="性别")
money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
def __repr__(self):
return self.name
class Teacher(db.Model):
__tablename__ = "tb_teacher"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
sex = db.Column(db.Boolean, default=False, comment="性别")
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), unique=True, comment="课程名称")
price = db.Column(db.Numeric(6, 2))
def __repr__(self):
return self.name
@app.route('/')
def index():
"""
filter设置判断条件
==,
>=,
<
>
<=
!=
"""
# student=Student.query.filter(Student.name=='xiaobaibai').first()
# if student is None:
# return jsonify({'error':100404,'errormsg':''})
# print(student)
# print(student.age)
"""filter设置模糊查询"""
#like模糊条件
#模型.字段.like("%值%") 等价于 模型.字段.contains("值") 包含xxx
# 模型.字段.like("值%") 等价于 模型.字段.startswith("值") 以xxx开头
# 模型.字段.like("%值") 等价于 模型.字段.endswith("值") 以xxx结尾
# 模型.字段.like("__") 值长度为2个字符的.几个下划线代表几个字符
# student_list=Student.query.filter(Student.name.like('%a%')).all()
# student_list=Student.query.filter(Student.name.startswith('xiao')
# student_list=Student.query.filter(Student.name.like('____')).all()
# print(student_list)
'''
filter_by
filter_by 只支持一个等号作为判断条件,而且字段左边不需要声明模型类名
可以用于获取一条数据,也可以获取多条数据
'''
# student=Student.query.filter_by(name='haci')[0]
# student=Student.query.filter_by(name='haci').all()
'''
filter多条件查询
'''
from sqlalchemy import and_,or_,not_
'''and_'''
# student_list=Student.query.filter(and_(Student.age==12,Student.sex==1)).all()
# student_list=Student.query.filter(Student.age==12,Student.sex==1).all()
'''or_'''
# student_list=Student.query.filter(or_(Student.name=='haci',Student.age==12)).all()
'''not_'''
# student_list=Student.query.filter(not_(Student.name.startswith('ha'))).all()
'''值范围查询 in_'''
# student_list=Student.query.filter(Student.age.in_([23,12,56])).all()
'''order_by结果排序'''
# order_by(模型.字段.desc()) db.desc(模型.字段) 倒序
# order_by(模型.字段.asc()) db.asc(模型.字段) 升序
# student_list=Student.query.order_by(Student.money.desc()).all()
# student_list=Student.query.order_by(db.desc(Student.money)).all()
# print(student_list)
"""count 统计结果数量"""
# ret=Student.query.filter(Student.sex==1).count()
# print(ret)
'''limit 结果数量进行限制'''
'''offset 对查询开始位置进行设置'''
#对学生的钱包从大到小排序,第2-3名的学生
# student_list=Student.query.order_by(Student.money.desc()).offset(1).limit(2).all()
# print(student_list)
'''paginate分页器'''
#paginate(page=当前页码,per_page=每一页数据量,max_per_page=每一页最大数据量)
#当前页码,默认是从request.args["page"],如果当前参数没有值,则默认为1
# 每一页数据量,默认是100条
# 因为分页器有提供了一个 request.args["per_page"]给客户端设置每一页数据量,所以再次限定客户端最多能设置的每一页数据量
# pagination=Student.query.paginate(page=1)
pagination=Student.query.paginate(per_page=2)
# print(request.args['page'],'-----------------------------')
# print('当前页面对象:', pagination)
# print('前页数据量:', pagination.items)
# print('还有下一页数据:', pagination.has_next)
# print('还有上一页数据:', pagination.has_prev)
# print('当前页页码:', pagination.page) # 当前页页码 request.args.get("page",1)
# print('被分页的数据量总数:', pagination.total)
# print('总页码:', pagination.pages)
# print('上一页的分页器对象:', pagination.prev()) # 如果没有上一页,则默认为None
# print('下一页的分页器对象:', pagination.next()) # 如果没有下一页,则默认为None
# if pagination.next():
# print('下一页的数据列表:', pagination.next().items())
return render_template('list.html',pagination=pagination)
return 'ok'
if __name__=='__main__':
# with app.app_context():
# db.create_all()
# db.drop_all()
app.run()
9.分组查询和执行SQL语句
一般分组查询会结合聚合函数来一起使用。SQLAlchemy中所有的聚合函数都在func
模块中声明的。
from sqlalchemy import func
函数名 | 说明 | |
---|---|---|
func.count | 统计总数 | |
func.avg | 平均值 | |
func.min | 最小值 | |
func.max | 最大值 | |
func.sum | 和 |
func函数只能针对db.session.query来使用,而不能通过模型.query来使用
#查询男生女生最大年龄:
ret=db.session.query(Student.sex,func.max(Student.age)).group_by(Student.sex).all()
# 查询当前不同年龄的学生数量
ret = db.session.query(Student.age,func.count(Student.id)).group_by(Student.age).having(Student.age>19).all()
#having能进行过滤的字段只能是进行分组的字段,否则报错;
#查询男生和女生中,年龄最小的是几岁?
ret=db.session.query(Student.sex,func.min(Student.age)).group_by(Student.sex).all()
执行原生SQL语句
#返回结果不是模型对象, 是列表和元祖
#查询多条
ret=db.session.execute('select id,name,IF(sex,'男','女)from tb_studnet').fetchall()
ret = db.session.execute('select id,name,age,IF(sex,"男","女") from tb_student where sex=1').fetchall()
#查询单条
ret=db.session.execute('select id,name,IF(sex,'男','女')from tb_student where id=2').fetchone()
#添加/修改/删除
db.session.execute("UPDATE tb_student SET money=(money + %s) WHERE age=%s" % (200,23))
db.session.commit()
#查询出女生和男生中大于18岁的人数
ret=db.session.execute('SELECT id,name,age,IF(sex,"男","女") FROM `tb_student` WHERE age>18 ').fetchall()
ret2=db.session.execute('SELECT IF(sex,"男","女"),count(id) from (SELECT id,name,age,sex FROM `tb_student` WHERE age>18) as stu group by sex').fetchall()
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)
class Config():
DEBUG=True
JSON_AS_ASCII=False
SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/students'
SQLALCHEMY_TRACK_MODIFICATIONS=True
SQLALCHEMY_ECHO=True
app.config.from_object(Config)
db=SQLAlchemy()
db.init_app(app)
"""创建模型类"""
class Student(db.Model):
__tablename__ = "tb_student"
id = db.Column(db.Integer, primary_key=True,comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
age = db.Column(db.Integer, comment="年龄")
sex = db.Column(db.Boolean, default=False, comment="性别")
money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
def __repr__(self):
return self.name
class Teacher(db.Model):
__tablename__ = "tb_teacher"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
sex = db.Column(db.Boolean, default=False, comment="性别")
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), unique=True, comment="课程名称")
price = db.Column(db.Numeric(6, 2))
def __repr__(self):
return self.name
@app.route('/')
def index():
from sqlalchemy import func
'''group_by'''
#查询男生和女生的最大年龄
# ret=db.session.query(Student.sex,func.max(Student.age)).group_by(Student.sex).all()
# print(ret)
#having是针对分组的结果进行过滤处理,所以having能调用的字段,必须是分组查询结果中的字段,否则报错!!
# ret=db.session.query(Student.sex,Student.age,func.count(Student.age)).group_by(Student.sex,Student.age).having(Student.age>12).all()
# print(ret)
"""执行原生SQL语句,返回结果不是模型对象, 是列表和元祖"""
#查询多条
# ret = db.session.execute('select id,name,age,IF(sex,"男","女") from tb_student').fetchall()
# ret = db.session.execute('select id,name,age,IF(sex,"男","女") from tb_student where sex=1').fetchall()
# print(ret)
#查询单条
# ret=db.session.execute('select * from tb_student where id=2').fetchone()
# print(ret)
# print(ret.name)
#添加/修改/删除
# db.session.execute('UPDATE tb_student SET money=(money + %s) WHERE age = %s' % (200,12))
# db.session.commit()
#查询出女生和男生中大于18岁的人数
ret=db.session.execute('SELECT id,name,age,IF(sex,"男","女") FROM `tb_student` WHERE age>18 ').fetchall()
ret2=db.session.execute('SELECT IF(sex,"男","女"),count(id) from (SELECT id,name,age,sex FROM `tb_student` WHERE age>18) as stu group by sex').fetchall()
print(ret2)
return 'ok'
if __name__=='__main__':
app.run()
4.一对一关系表
4.1创建
学生表和学生信息表:
from flask import Flask
from flask_sqlalchemy import SQLAMchemy
app=Flask(__name__)
class Config():
DEBUG=True
JSON_AS_ASCII=False
SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/studnets'
SQLALCHEMY_TRACK_MODIFICATIONS=True
SQLALCHEMY_ECHO=True
app.config.from_object(Config)
db=SQLALchemy()
db.init_app(app)
'''创建模型'''
class Student(db.Model):
__tablename__='tb_student'
id=db.Column(db.Integer,primary_key=True,comment='主键ID')
name=db.Column(db.String(255),comment='姓名')
age=db.Column(db.Integer,comment='年龄')
sex=db.Column(db.Boolean,default=False,comment='性别')
money=db.Column(db.DECIMAL(8,2),bullable=True,comment='工资')
# 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
# backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
info=db.relationship('StudnetInfo',backref='own',uselist=False)
def __repr__:
return self.name
class StudentInfo(db.Model):
__tablename__='tb_student_info'
id=db.Column(db.Integer,primary_key=True,comment='主键ID')
#外键
sid=db.Column(db.Integer,db.ForeignKey(Student.id),comment='学生')
address=db.Column(db.String(250),nullable=True,comment='家庭地址')
mobile=db.Column(db.String(15),unique=True,comment='手机号')
def __repr__(self):
return self.own.name
@app.route('/')
def index():
return 'ok'
if __name__=='__main__':
app.run()
4.2添加数据(视图函数中)
studnet=Student(
name='haci',
age='32',
sex=True,
money=67896,
info=StudentInfo(
mobile='18434391584',
address='山西'
)
)
db.session.add(student)
db.session.commit()
4.3查询数据
#正向查询
student=Student.query.get(1)
print(student)
print(student.name)
print(student.info.address)
#反向查询(地址--->姓名)
student_info=StudentInfo.query.filter(StudentInfo.address=='山西').first()
print(student_info.own.name)
4.4修改更新
student=Student.query.get(1)
student.age=89
student.info.mobile='12345678908'
db.session.commit()
4.5删除
student=Student.query.get(2)
db.session.delete(student.info)
db.session.delete(student)
db.session.commit()
5.一对多关系表
5.1建表
class Teacher(db.Model):
__tablename__ = "tb_teacher"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
sex = db.Column(db.Boolean, default=False, comment="性别")
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")
course_list=db.relationship('Course',uselist=True,backref='teacher',lazy='subquery')
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), unique=True, comment="课程名称")
price = db.Column(db.Numeric(6, 2))
teacher_id=db.Column(db.Integer,db.ForeignKey(Teacher.id),comment='老师')
def __repr__(self):
return self.name
5.2添加数据
#既添加主模型数据,也添加外键模型
teacher=Teacher(
name='ww',
option='讲师',
course_list=[
Course(name='python',price=8900),
Course(name='Linux',price=5000),
Course(name='Java',price=6789)
]
)
db.session.add(teacher)
db.session.commit()
#反向添加数据
course=Course(
name='web',
price=908,
teacher=Teacher(
name='haci',
option='班主任'
)
)
db.session.add(course)
db.session.commit()
5.3查询数据
teacher=Teacher.query.filter(Teacher.name=='ww').first()
print(teacher.name)
print(teacher.course_list)
for i in teacher.course_list:
print(i.name,i.price)
#反向查询
course=Course.query.filter(Course.name=='python').first()
print(course.price,'================')
print(course.teacher.name)
5.4更新数据
teacher=Teacher.query.get(1)
teacher.course_list[0].name='C++'
db.session.commit()
#反向查询
course=Course.query.get(3)
course.teacher.name='er'
db.session.commit()
5.4删除数据
teacher=Teacher.query.get(1)
for course in teacher.course_list:
db.session.delete(course)
db.session.delete(teacher)
db.session.commit()
6.多对多关系表
6.1利用tb_table第三张表
1.建表
# db.Table(
# 表名,
# db.Column("字段名",字段类型,外键声明),
# db.Column("字段名",字段类型,外键声明),
# )
achievement=db.Table(
'tb_achievement',
db.Column('student_id',db.Integer,db.ForeignKey('tb_student.id')),
db.Column('course_id',db.Integer,db.ForeignKey('tb_course.id')),
)
class Student(db.Model):
__tablename__ = "tb_student"
id = db.Column(db.Integer, primary_key=True,comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
age = db.Column(db.Integer, comment="年龄")
sex = db.Column(db.Boolean, default=False, comment="性别")
money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
# 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
# backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
info=db.relationship('StudentInfo',backref='own',uselist=False)
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), unique=True, comment="课程名称")
price = db.Column(db.Numeric(6, 2))
teacher_id=db.Column(db.Integer,db.ForeignKey(Teacher.id),comment='老师')
student_list=db.relationship('Student',secondary=achievement,backref='course_list',lazy='dynamic')
def __repr__(self):
return self.name
2.添加数据
course1=Course(name='python',price=990,teacher=Teacher(name='ww',option='讲师'))
course2=Course(name='linux',price=890,teacher=Teacher(name='ll',option='讲师'))
course3=Course(name='web',price=87,teacher=Teacher(name='ll',option='讲师'))
student=Student(
name='xmh',
age=9,
money=234456,
sex=False,
info=StudentInfo(
address='中国',
mobile='18434391584'
),
course_list=[
course1,
course2,
course3,
]
)
db.session.add(student)
db.session.commit()
#正向添加
student1=Student(name='hh',age=16,sex=0,info=StudentInfo(address='伊犁',mobile='12345678965'))
student2=Student(name='xx',age=19,sex=0,info=StudentInfo(address='河北',mobile='15525251185'))
course=Course(
name='C++',
price=998,
teacher=Teacher(
name='bb',
option='班主任',
),
student_list=[
student1,
student2
]
)
db.session.add(course)
db.session.commit()
3.查询
student=Student.query.get(1)
course_list=student.course_list
print(course_list,'===========')
for i in course_list:
print(i.name,i.price)
#查询学习某课的学生的列表
course=Course.query.filter(Course.name=='C++').first()
student_list=course.student_list.all()
print(student_list)
4.更新
student=Student.query.filter(Student.name=='hh').first()
student.course_list[0].name='java'
db.session.commit()
6.2新建一个模型
1.建表
class Achievement(db.Model):
__tablename__='tb_acievement'
id=db.Column(db.Integer,primary_key=True,comment='主键ID')
student_id=db.Column(db.Integer,db.ForeignKey('tb_student.id'),comment='学生')
course_id=db.Column(db.Integer,db.ForeignKey('tb_course.id'),comment='课程')
score=db.Column(db.DECIMAL(5,2),nullable=True,comment='成绩分数')
created_time=db.Column(db.DateTime,default=datetime.now(),comment='考试时间')
def __repr__(self):
return '%s的成绩为%s' % (self.student.name,self.score)
class Student(db.Model):
__tablename__ = "tb_student"
id = db.Column(db.Integer, primary_key=True,comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
age = db.Column(db.Integer, comment="年龄")
sex = db.Column(db.Boolean, default=False, comment="性别")
money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
# 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
# backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
info=db.relationship('StudentInfo',backref='own',uselist=False)
achievement_list=db.relationship('Achievement',uselist=True,backref='student',lazy='select')
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), unique=True, comment="课程名称")
price = db.Column(db.Numeric(6, 2))
teacher_id=db.Column(db.Integer,db.ForeignKey(Teacher.id),comment='老师')
achievement_list=db.relationship('Achievement',uselist=True,backref='course',lazy='select')
def __repr__(self):
return self.name
2.添加数据
student1=Student(name='hh',age=90,sex=1,money=90000)
student2=Student(name='xx',age=25,sex=0,money=89076)
course=Course(
name='python',
price=90,
achievement_list=[
Achievement(
student=student1,
score=89,
),
Achievement(
student=student2,
score=100,
)
],
teacher=Teacher(
name='ww',
option='讲师'
)
)
db.session.add(course)
db.session.commit()
3.更新
student=Student.query.filter(Student.name=='hh').first()
student.achievement_list[0].score=85.7
db.session.commit()