Flask中使用Session
需要先设置SECRET_KEY
class DefaultConfig(object): SECRET_KEY = 'fih9fh9eh9gh2' app.config.from_object(DefaultConfig) # 或者直接设置 app.secret_key='xihwidfw9efw'
设置、修改
from flask import session @app.route('/set_session/') def set_session(): session['username'] = 'zs' return 'set session ok'
读取
@app.route('/get_session/') def get_session(): username = session.get('username') return 'get session username {}'.format(username)
删除
@app.route('/del_session/') def delete_session(): #删除指定的key的session # session.pop('uname') #删除session中的所有的key 【删除所有】 session.clear() return '删除成功'
Flask设置Session的有效期
如果没有设置session的有效期。那么默认就是浏览器关闭后过期。 如果设置session.permanent=True,那么就会默认在31天后过 期。 如果不想在31天后过期,按如下步骤操作。
1 session.permanent=True
2 可以设置 app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hour=2) 在两个小时后过期。
from flask import Flask,session from datetime import timedelta app = Flask(__name__) app.secret_key = 'sdfdfdsfsss' app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=2) @app.route('/') def index(): return 'Hello!!' @app.route('/set_session/') def set_session(): # 设置session的持久化,默认是增加了31天 session.permanent = True session['uname'] = '10001' return '设置一个Session的信息' @app.route('/get_session/') def get_session(): # 如果服务器关闭掉了,session的有效期,依然是之前系统保存日期 # 如果secret_key设置是一个固定的值,那么服务器重启不会影响session的有效器 # 如果secret_key设置不是一个固定的值,那么服务器之前设置的session将全部过期 return session.get('uname') if __name__ == '__main__': app.run(debug=True)
Session实战
login.html
<!-- login.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initialscale=1.0"> <title>Document</title> </head> <body> <form action="/login/" method="post"> <table> <tr> <td>账号:</td> <td><input type="text" name="uname"></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="pwd"></td> </tr> <tr> <td></td> <td><input type="submit" value="立即登录"></td> </tr> <tr> <td colspan="2"> {% if msg %} <span color="red">{{ msg }}</span> {% endif %} </td> </tr> </table> </form> </body> </html>
from flask import Flask, session, request,redirect,url_for,views,render_template app = Flask(__name__) # 定义一个基于方法调度的 类视图 class LoginView(views.MethodView): def __jump(self,msg=None): return render_template('login.html',msg = msg) def get(self): msg = request.args.get('msg') return self.__jump(msg) def post(self): uname = request.form.get('uname') pwd = request.form.get('pwd') if uname == "zs" and pwd == "123": session['uname'] = uname return render_template('index.html') else: return self.__jump(msg="用户名或者密码错误") @app.route('/index/') def index(): uname = session.get('uname') if uname: return '这个是主页!!!' return redirect(url_for('login',msg='请先登录')) # 注册类视图 app.add_url_rule('/login/',view_func=LoginView.as_view('login')) if __name__ == '__main__': app.secret_key = 'xihwidfw9efw' app.run(debug=True)
Local对象
需求
要实现并发效果, 每一个请求进来的时候我们都开启一个进程, 这显然是不合理的, 于是就可以使用 线程 那么线程中数据互相不隔离,存在修改数据的时候数据不安全的问题
Local对象
在Flask中,类似于 request 对象,其实是绑定到了一个 werkzeug.local.Local 对象上。 这样,即使是同一个对象,那么在多个线程中都是隔离的。类似的 对象还有 session 对象。
ThreadLocal变量
Python提供了ThreadLocal 变量,它本身是一个全局变量, 但是每个线程却可以利用它来保存属于自己的私有数据, 这些私有数据对其他线程也是不可见的。
from threading import Thread,local local =local() local.request = '具体用户的请求对象' class MyThread(Thread): def run(self): local.request = 'zs' print('子线程:',local.request) mythread = MyThread() mythread.start() mythread.join() print('主线程:',local.request) from werkzeug.local import Local local = Local() local.request = '具体用户的请求对象' class MyThread(Thread): def run(self): local.request = 'sxt' print('子线程:',local.request) mythread = MyThread() mythread.start() mythread.join() print('主线程:',local.request)
总结
只要满足绑定到"local"或"Local"对象上的属性,在每个线程中都是 隔离的,那么他就叫做 ThreadLocal 对象,也叫'ThreadLocal'变量。
Flask_app上下文
App上下文,也叫应用上下文
上下文(感性的理解)
上下文的一个典型应用场景就是用来缓存一些我们需要在发生请求 之前或者要使用的资源。举个例子,比如数据库连接。当我们在应 用上下文中来存储东西的时候你得选择一个唯一的名字,这是因为 应用上下文为 Flask 应用和扩展所共享。
应用上下文:
应用上下文是存放到一个 LocalStack 的栈中。和应用app相关的操作就 必须要用到应用上下文
比如:
通过 current_app 获取当前的这个 app 名字。
注意
在视图函数中,不用担心应用上下文的问题。因为视图函数要 执行,那么肯定是通过访问url的方式执行的, 那么这种情况下,Flask底层就已经自动的帮我们把应用上下文 都推入到了相应的栈中。
如果想要在视图函数外面执行相关的操作, 比如: 获取当前的app名称,那么就必须要手动推入应用上下文
第一种方式:便于理解的写法
from flask import Flask,current_app app = Flask(__name__) #app上下文 app_context = app.app_context() app_context.push() print(current_app.name) @app.route('/') def hello_world(): print(current_app.name) #获取应用的名称 return 'Hello World!' if __name__ == '__main__': app.run(debug=True)
第二种方式:用with语句
from flask import Flask,current_app app = Flask(__name__) #app上下文 #换一种写法 with app.app_context(): print(current_app.name) @app.route('/') def hello_world(): print(current_app.name) #获取应用的名称 return 'Hello World!' if __name__ == '__main__': app.run(debug=True)
Flask_线程隔离的g对象
保存为全局对象g对象的好处:
g对象是在整个Flask应用运行期间都是可以使用的。 并且也跟request一样,是线程隔离的。 这个对象是专门用来存储开发者自己定义的一些数据,方便在整个 Flask程序中都可以使用。 一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接 从g上面取就可以了,而不需要通过传参的形式,这样更加方便。
g对象使用场景:
有一个工具类utils.py 和 用户办理业务:
def funa(uname): print(f'funa {uname}') def funb(uname): print(f'funb {uname}') def func(uname): print(f'func {uname}')
用户办理业务
from flask import Flask,request from utils import funa,funb,func app = Flask(__name__) #Flask_线程隔离的g对象使用详解 @app.route("/profile/") def my_profile(): #从url中取参 uname = request.args.get('uname') #调用功能函数办理业务 funa(uname) funb(uname) func(uname) #每次都得传参 麻烦,引入g对象进行优化 return "办理业务成功" if __name__ == '__main__': app.run(debug=True)
优化工具类utils.py
from flask import g def funa(): print(f'funa {g.uname}') def funb(): print(f'funb {g.uname}') def func(): print(f'func {g.uname}')
Flask_钩子函数介绍
钩子函数概念
在Flask中钩子函数是使用特定的装饰器装饰的函数。 为什么叫做钩子函数呢,是因为钩子函数可以在正常执行的代码 中,插入一段自己想要执行的代码。 那么这种函数就叫做钩子函数。
常见的钩子函数
before_first_request:处理项目的第一次请求之前执行。
@app.before_first_request def first_request(): print('first time request')
before_request:在每次请求之前执行。通常可以用这个装饰 器来给视图函数增加一些变量。请求已经到达了Flask,但是还 没有进入到具体的视图函数之前调用。一般这个就是在视图函数 之前,我们可以把一些后面需要用到的数据先处理好,方便视图 函数使用。
@app.before_request def before_request(): if not hasattr(g,'glo1'): setattr(g,'glo1','想要设置的')
teardown_appcontext:不管是否有异常,注册的函数都会在 每次请求之后执行。
@app.teardown_appcontext def teardown(exc=None): if exc is None: db.session.commit() else: db.session.rollback() db.session.remove()
template_filter:在使用Jinja2模板的时候自定义过滤器。
@app.template_filter("upper") def upper_filter(s): return s.upper()
context_processor:上下文处理器。使用这个钩子函数,必须 返回一个字典。这个字典中的值在所有模版中都可以使用。这个 钩子函数的函数是,如果一些在很多模版中都要用到的变量,那 么就可以使用这个钩子函数来返回,而不用在每个视图函数中 的 render_template 中去写,这样可以让代码更加简洁和好维护。
@app.context_processor def context_processor(): if hasattr(g,'user'): return {"current_user":g.user} else: return {}
errorhandler:errorhandler接收状态码,可以自定义返回这 种状态码的响应的处理方法。在发生一些异常的时候,比如404 错误,比如500错误,那么如果想要优雅的处理这些错误,就可以 使用 errorhandler 来出来。
@app.errorhandler(404) def page_not_found(error): return 'This page does not exist',404
Flask_信号机制
信号机制
大白话来说,类似于两方属于敌对关系时,某人在敌对方阵营进行 交谈,一旦遇到特殊情况,某人便会发送信号,他的同伙接收(监 听)到他发的信号后,同伙便会做出一系列的应对策略(进攻|撤 退)。 flask中的信号使用的是一个第三方插件,叫做blinker。通过pip list看一下,如果没有安装,通过以下命令即可安装blinker
pip install blinker
自定义信号步骤
自定义信号可分为3步来完成。
第一是创建一个信号,第二是监听一个信号,第三是发送一个信 号。
以下将对这三步进行讲解:
创建信号:定义信号需要使用到blinker这个包的Namespace类来创建一个命名空间。比如定义一 个在访问了某个视图函数的时候的信号。示例代码如下:
# Namespace的作用:为了防止多人开发的时候,信号名字 冲突的问题 from blinker import Namespace mysignal = Namespace() signal1 = mysignal.signal('信号名称')
监听信号:监听信号使用signal1对象的connect方法,在这个方法中需要传递一个函数,用来监听 到这个信号后做该做的事情。示例代码如下:
def func1(sender,uname): print(sender) print(uname) signal1.connect(func1)
发送信号:发送信号使用signal1对象的send方法,这个方法可以传递一些其他参数过去。示例代 码如下:
signal1.send(uname='momo')
Flask信号使用场景_存储用户登录日志
信号使用场景
定义一个登录的信号,以后用户登录进来以后 就发送一个登录信号,然后能够监听这个信号 在监听到这个信号以后,就记录当前这个用户登录的信息 用信号的方式,记录用户的登录信息即登录日志。
编写一个signals.py文件创建登录信号
from blinker import Namespace from datetime import datetime from flask import request,g namespace = Namespace() #创建登录信号 login_signal = namespace.signal('login') def login_log(sender): # 用户名 登录时间 ip地址 now = datetime.now() ip = request.remote_addr log_data = "{uname}*{now}*{ip}".format(uname=g.uname, now=now, ip=ip) with open('login_log.txt','a') as f: f.write(log_data + "\n") f.close() #监听信号 login_signal.connect(login_log)
使用信号存储用户登录日志
from flask import Flask,request,g from signals import login_signal app = Flask(__name__) @app.route('/login/') def login(): # 通过查询字符串的形式来传递uname这个参数 uname = request.args.get('uname') if uname: g.uname = uname # 发送信号 login_signal.send() return '登录成功!' else: return '请输入用户名!' if __name__ == '__main__': app.run(debug=True)
Flask_内置信号
Flask内置了10个常用的信号:
1 template_rendered:模版渲染完成后的信号。
2 before_render_template:模版渲染之前的信号。
3 request_started:请求开始之前,在到达视图函数之前发送信号。
4 request_finished:请求结束时,在响应发送给客户端之前发送信号。
5 request_tearing_down:请求对象被销毁时发送的信号,即使在请求过程中发生异常也会发送信 号。
6 got_request_exception:在请求过程中抛出异常时发送信号,异常本身会通过exception传递到订 阅(监听)的函数中。一般可以监听这个信号,来记录网站异常信息。
7 appcontext_tearing_down:应用上下文被销毁时发送的信号。
8 appcontext_pushed:应用上下文被推入到栈上时发送的信号。
9 appcontext_popped:应用上下文被推出栈时发送的信号。
10 message_flashed:调用了Flask的 flash 方法时发送的信号。
WTForms介绍和基本使用
WTForms介绍
这个插件库主要有两个作用。 第一个是做表单验证,将用户提交上来的数据进行验证是否符合系 统要求。 第二个是做模版渲染。 (了解即可) 官网:https://wtforms.readthedocs.io/en/latest/index.html
Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单 的两个主要功能是验证用户提交数据的合法性以及渲染模板。而 Flask-WTF还包括一些其他的功能:CSRF保护,文件上传等。 安装Flask-WTF默认也会安装WTForms,因此使用以下命令来安装 Flask-WTF和WTForms:
pip install flask-wtf
WTForms表单验证的基本使用
1 自定义一个表单类,继承自wtforms.Form类。
2 定义好需要验证的字段,字段的名字必须和模版中那些需要验证的input标签的name属性值保持一 致。
3 在需要验证的字段上,需要指定好具体的数据类型。
4 在相关的字段上,指定验证器。
5 以后在视图函数中,只需要使用这个表单类的对象,并且把需要验证的数据,也就是request.form 传给这个表单类,再调用表单类对象.validate()方法进行,如果返回True,那么代表用户输入的数 据都是符合格式要求的,Flase则代表用户输入的数据是有问题的。如果验证失败了,那么可以通 过表单类对象.errors来获取具体的错误信息。
from flask import Flask,render_template,request from wtforms import Form,StringField from wtforms.validators import Length,EqualTo app = Flask(__name__) @app.route('/') def index(): return 'Hello! ' class RegisterForm(Form): uname = StringField(validators=[Length(min=2,max=10,message='用户名长度2-10之间')]) pwd = StringField(validators=[Length(min=2,max=10)]) pwd2 = StringField(validators=[Length(min=2,max=10),EqualTo('pwd',message='2次密码不一致')]) @app.route('/register/', methods=['GET','POST']) def register(): if request.method == 'GET': return render_template('register.html') else: form = RegisterForm(request.form) if form.validate(): # 验证成功:True,失败:False return '验证成功!' else: return f'验证失败!{form.errors}' if __name__ == '__main__': app.run(debug=True)
WTForms常用验证器
页面把数据提交上来,需要经过表单验证,进而需要借助验证器来 进行验证,以下是常用的内置验证器:
1. Length:字符串长度限制,有min和max两个值进行限制。
username = StringField(validators=[Length(min=3,max=10,message="用户名长度必须在3到10位之间")])
2. EqualTo:验证数据是否和另外一个字段相等,常用的就是密码 和确认密码两个字段是否相等。
password_repeat = StringField(validators=[Length(min=6,max=10),EqualTo("password")])
3. Email:验证上传的数据是否为邮箱数据格式 如:223333@qq. com。
email = StringField(validators=[Email()])
4. InputRequired:验证该项数据为必填项,即要求该项非空。
username = StringField(validators=[input_required()])
5. NumberRange:数值的区间,有min和max两个值限制,如果 处在这两个数字之间则满足。
age = IntegerField(validators=[NumberRange(12,18)])
6. Regexp:定义正则表达式进行验证,如验证手机号码。
phone = StringField(validators=[Regexp(r'1[34578]\d{9}')])
7. URL:必须是URL的形式 如http://www.bjsxt.com。
home_page = StringField(validators=[URL()])
8. UUID:验证数据是UUID类型。
uuid = StringField(validators=[UUID()])
WTForms自定义验证器
只有当WTForms内置的验证器不够使的时候,才需要使用自定义验 证器。 如果想要对表单中的某个字段进行更细化的验证,那么可以针对这 个字段进行单独的验证。
自定义验证器步骤如下:
1 定义一个方法,方法的名字规则是: validate_字段名(self,field) 。
2 在方法中,使用 field.data 可以获取到这个字段的具体的值。
3 验证时,如果数据满足条件,那么可以什么都不做。如果验证失败,那么应该抛出一个 wtforms.validators.ValidationError 的异常,并且把验证失败 的信息传到这个异常类中。
场景:验证码实现
关键代码演示:(实现验证码 验证)
from flask import session from wtforms import Form,StringField,IntegerField from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRan ge,Regexp,URL,UUID,ValidationError class RegisterForm2(Form): email = StringField(validators=[Email()]) uname = StringField(validators=[InputRequired()]) age = IntegerField(validators=[NumberRange(18,40)]) phone = StringField(validators=[Regexp(r'1[34578]\d{9}')]) phomepage = StringField(validators=[URL()]) uuid = StringField(validators=[UUID()]) code = StringField(validators=[Length(4,4)]) #取到的值 和服务器上 session上存储的值对比 def validate_code(self,field): print(field.data,session.get('code')) if field.data !=session.get('code'): raise ValidationError('验证码不一致!')
Flask安全上传文件
上传文件步骤:
1. 在模版html中,表单需要指定 enctype='multipart/form-data' 才能上传文 件。
2. 在后台如果想要获取上传的文件,那么应该使用 request.files.get('文件 名') 来获取。
3. 保存文件之前,先要使用 werkzeug.utils.secure_filename 来对上传上来的文 件名进行一个过滤。能保证不会有安全问题。
4. 获取到上传上来的文件后,使用 文件对象.save(路径) 方法来保存文件。 路径=完整路径=路径名+文件名
upload.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>上传文件</title> </head> <body> <form action="" method="post" enctype="multipart/form-data"> <table> <tr> <td>头像:</td> <td><input type="file" name="pichead"></td> </tr> <tr> <td>描述:</td> <td><input type="text" name="desc"></td> </tr> <tr> <td></td> <td><input type="submit" value="提交"></td> </tr> </table> </form> </body> </html>
app.py文件
from flask import Flask,request,render_template import os from werkzeug.utils import secure_filename app = Flask(__name__) UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') @app.route('/upload/',methods=['GET','POST']) def upload(): if request.method == 'GET': return render_template('upload.html') else: desc = request.form.get("desc") pichead = request.files.get("pichead") filename = secure_filename(pichead.filename) #包装一下 保证文件安全 #pichead.save(os.path.join(UPLOAD_PATH,pichead.filename)) #可优化 pichead.save(os.path.join(UPLOAD_PATH,filename)) #已优化 print(desc) return '文件上传成功' if __name__ == '__main__': app.run(debug=True)
访问文件
从服务器上读取文件,应该定义一个url与视图函数,来获取指定的 文件。 在这个视图函数中,使用 send_from_directory(文件的目录,文件名) 来获取。
from flask import Flask import os from flask import send_from_directory app = Flask(__name__) UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') @app.route('/images/<filename>/') def get_image(filename): return send_from_directory(UPLOAD_PATH,filename) if __name__ == '__main__': app.run(debug=True)
利用flask-wtf验证上传的文件
关键点:
1 定义验证表单类的时候,对文件类型的字段,需要采用 FileField 这个类型,即wtforms.FileField 2 验证器需要从 flask_wtf.file 中导入。 flask_wtf.file.FileRequired 和 flask_wtf.file.FileAllowed
3 flask_wtf.file.FileRequired 是用来验证文件上传不能为空。
4 flask_wtf.file.FileAllowed 用来验证上传的文件的后缀名, 如常见图片后缀 .jpg 和.png以及.gif等。
5 在视图函数中,需要使用 from werkzeug.datastructures import CombinedMultiDict 来把 request.form 与 request.files 来进行合并。
6 最后使用 表单验证对象.validate()进行验证。
代码如下:
upload.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>上传文件</title> </head> <body> <form action="" method="post" enctype="multipart/form-data"> <table> <tr> <td>头像:</td> <td><input type="file" name="pichead"></td> </tr> <tr> <td>描述:</td> <td><input type="text" name="desc"></td> </tr> <tr> <td></td> <td><input type="submit" value="提交"></td> </tr> </table> </form> </body> </html>
formscheck.py文件
from wtforms import Form,FileField,StringField from wtforms.validators import InputRequired # flask_wtf from flask_wtf.file import FileRequired,FileAllowed class UploadForm(Form): pichead = FileField(validators= [FileRequired(),FileAllowed(['jpg','png','gif'])]) desc = StringField(validators=[InputRequired()])
app.py文件
from flask import Flask,request,render_template import os from werkzeug.utils import secure_filename from formscheck import UploadForm from werkzeug.datastructures import CombinedMultiDict app = Flask(__name__) UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') #利用flask-wtf验证上传的文件 @app.route('/upload/',methods=['GET','POST']) def upload(): if request.method == 'GET': return render_template('upload.html') else: form = UploadForm(CombinedMultiDict([request.form,request.files])) if form.validate(): # desc = request.form.get("desc") # pichead = request.files.get("pichead") desc = form.desc.data pichead = form.pichead.data filename = secure_filename(pichead.filename) pichead.save(os.path.join(UPLOAD_PATH,filename)) print(desc) return '文件上传成功' else: print(form.errors) return "文件上传失败" if __name__ == '__main__': app.run(debug=True)
Restful介绍
1.Restful接口规范
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的 应用程序或设计就是 RESTful。 RESTful是一种软件架构风格、设计风格,而不是标准,只是提供了 一组设计原则和约束条件。 它主要用于客户端和服务器交互类的软件。基于这个风格设计的软 件可以更简洁,更有层次。 RESTful接口规范是用于在前端与后台进行通信的一套规范。使用这 个规范可以让前后端开发变得更加轻松。
2.适用场景:一个系统的数据库数据,展现的平台有PC端、移动 端、app端、ios端。 前端工程师:都遵循RESTful编程规范 后端工程师:都遵循RESTful编程规范 最终结果:开发效率高,便于管理。
3.协议:用http或者https协议。
4.数据传输格式: 数据传输的格式应该都用json格式。
5.url链接规则:url链接中,不能有动词,只能有名词。 并且对于一些名词,如果出现复数,那么应该在后面加s。 比如:获取新闻列表,应该使用 /news/ ,而不应该使用/get_news/
6.HTTP请求方式: GET:从服务器上获取资源。 POST:在服务器上新增或者修改一个资源。 PUT:在服务器上更新资源。(客户端提供所有改变后的数据) PATCH:在服务器上更新资源。(客户端只提供需要改变的属性) DELETE:从服务器上删除资源。
7.状态码:
Restful的基本使用
1.介绍:
优势: Flask-Restful是一个专门用来写restful api的一个插件。 使用它可以快速的集成restful api接口功能。 在系统的纯api的后台中,这个插件可以帮助我们节省很多时间。
缺点: 如果在普通的网站中,这个插件就没有优势了,因为在普通的网站 开发中,是需要去渲染HTML代码的, 而Flask-Restful在每个请求中都是返回json格式的数据。
2.安装:pip install flask-restful
3.基本使用:
定义Restful的类视图:
1. 从 flask_restful 中导入 Api ,来创建一个 api 对象。
2. 写一个类视图,让他继承自 Resource 类,然后在这个里面,使用 你想要的请求方式来定义相应的方法,比如你想要将这个类视图只 能采用 post 请求,那么就定义一个 post 方法。
3. 使用 api.add_resource 来添加类视图与 url 。
from flask import Flask,url_for # pip install flask-restful from flask_restful import Resource,Api app = Flask(__name__) # 建立Api对象,并绑定应用APP api = Api(app) class LoginView(Resource): def get(self): return {"flag":True} def post(self): return {"flag":False} # 建立路由映射 # api.add_resource(LoginView,'/login/') api.add_resource(LoginView,'/login/','/login2/',endpoint='login') with app.test_request_context(): # werkzeug.routing.BuildError: Could not build url for endpoint 'LoginView'. # Did you mean 'loginview' instead? # 默认没有写endpoint反向url_for函数通过小写函数名 # 如果有多个url,会返回第1个URL # print(url_for('loginview')) print(url_for('login')) if __name__ == '__main__': app.run(debug=True)
注意
1 如果你想返回json数据,那么就使用flask_restful,如果你是想渲染模版,那么还是采用之前 的方式,就是 app.route 的方式。
2 url还是跟之前的一样,可以传递参数。也跟之前的不一样,可以指定多个url。
3 endpoint是用来给url_for反转url的时候指定的。如果不写endpoint,那么将会使用视图的 名字的小写来作为endpoint。
4 add_resource的第二个参数是访问这个视图函数的url,这个url可以跟之前的route一样,可 以传递参数,并且还有一点不同的是,这个方法可以传递多个url来指定这个视图函数。
Flask_RESTful参数验证
参数验证
参数验证也叫参数解析 Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法 的包,叫做reqparse。
基本用法
1 通过 flask_restful.reqparse 中 RequestParser 建立解析器
2 通过 RequestParser 中的 add_argument 方法定义字段与解析规则
3 通过 RequestParser 中的 parse_args 来解析参数
1 解析正确,返回正确参数
2 解析错误,返回错误信息给前端
from flask import Flask from flask_restful import Api,Resource from flask_restful.reqparse import RequestParser app = Flask(__name__) api = Api(app) class RegisterView(Resource): def post(self): # 建立解析器 parser = RequestParser() # 定义数据的解析规则 parser.add_argument('uname',type=str,required=True,help='用户名验证错误',trim=True) # 解析数据 args = parser.parse_args() # 正确,直接获取参数 print(args) # 错误,回馈到前端 # 响应数据 return {'msg':'注册成功!!'} # 建立映射关系 api.add_resource(RegisterView,'/register/') if __name__ == '__main__': app.run(debug=True)
add_argument方法参数详解
add_argument方法可以指定这个字段的名字,这个字段的数据类 型等,验证错误提示信息等,具体如下:
1 default:默认值,如果这个参数没有值,那么将使用这个参数 指定的默认值。
2 required:是否必须。默认为False,如果设置为True,那么这 个参数就必须提交上来。
3 type:这个参数的数据类型,如果指定,那么将使用指定的数 据类型来强制转换提交上来的值。可以使用python自带的一些 数据类型(如str或者int),也可以使用flask_restful.inputs下的一 些特定的数据类型来强制转换。
url:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。
regex:正则表达式。
date:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常.
4 choices:固定选项。提交上来的值只有满足这个选项中的值才 符合验证通过,否则验证不通过。
5 help:错误信息。如果验证失败后,将会使用这个参数指定的 值作为错误信息。
6 trim:是否要去掉前后的空格
from flask import Flask from flask_restful import Api,Resource,inputs from flask_restful.reqparse import RequestParser app = Flask(__name__) api = Api(app) class RegisterView(Resource): def post(self): # 建立解析器 parser = RequestParser() # 定义解析规则 parser.add_argument('uname',type=str,required=True,trim=True,help='用户名不符合规范') parser.add_argument('pwd',type=str,help='密码错误',default='123456') parser.add_argument('age',type=int,help='年龄验证错误!') parser.add_argument('gender',type=str,choices=['男', '女','保密'],help='性别验证错误') parser.add_argument('birthday',type=inputs.date,help='生日验证错误') parser.add_argument('phone',type=inputs.regex('^1[356789]\d{9}$'),help='电话验证错误') parser.add_argument('homepage',type=inputs.url,help='个人主页验证错误') # 解析数据 args = parser.parse_args() print(args) return {'msg':'注册成功!'} api.add_resource(RegisterView,'/register/') if __name__ == '__main__': app.run(debug=True)
Flask_SQLAlchemy
SQLAlchemy的使用
数据库是一个网站的基础。 比如MySQL、MongoDB、SQLite、PostgreSQL等,这里我们以 MySQL为例进行讲解。 SQLAlchemy是一个ORM框架。
对象关系映射(英语:Object Relational Mapping,简称 ORM,或O/RM,或O/R mapping),是一种程序设计技术, 用于实现面向对象编程语言里不同类型系统的数据之间的转 换。 从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟 对象数据库”。
大白话 对象模型与数据库表的映射
为什么要有SQLAlchemy?
随着项目的越来越大,采用写原生SQL的方式在代码中会出现大量 重复的SQL语句,那么,问题就出现了:
1.SQL语句重复利用率不高,越复杂的SQL语句条件越多,代码越长,会出现很多相近的SQL语句。
2.很多SQL语句 是在业务逻辑中拼接出来的,如果数据库需要更改,就要去修改这些逻辑,这会容易 漏掉对某些SQL语句的修改。
3 写SQL时容易忽略web安全问题,造成隐患。
而ORM可以通过类的方式去操作数据库而不用再写原生的SQL语 句,通过把表映射成类,把行作为实例(一条数据),把字段作为属 性,ORM在执行对象操作的时候最终还是会把对象的操作转换为数 据库的原生语句,但使用ORM有许多优点:
1.易用性:使用ORM做数据库开发可以有效减少重复SQL语句的概率,写出来的模型也更加直观、清 晰
2.性能损耗小:ORM转换成底层数据库操作指令确实会有一些开销。但是从实际情况来看,这种性能 损耗很少(不足5%),只要不是针对性能有严苛的要求,综合考虑开发效率、代码阅读性,带来 的好处远大于性能损耗,而且项目越大作用越明显。
3 设计灵活:可以轻松的写出复杂的查询。
4.可移植性:SQLAlchemy封装了底层的数据库实现,支持多个关系数据库引擎,包括流行的 Mysql、PostgreSQL和SQLite,可以非常轻松的切换数据库。
使用ORM操作数据库将变得非常简单!
class Person: name = 'xx' age = 18 country ='xx' # Person类 -> 数据库中的一张表 # Person类中的属性 -> 数据库中一张表字段 # Person类的一个对象 -> 数据库中表的一条数据 # p = Person('xx',xx) # p.save() # insert into table values ('xx',xx)
我们会以 MySQL+ SQLAlchemy 组合进行讲解。
在操作数据库操作之前,先确保你已经安装了以下软件:
mysql:如果是在windows上,到官网下载
pymysql:pymysql是用Python来操作mysql的包 pip install pymysqlpip install pymysql
SQLAlchemy:SQLAlchemy是一个数据库的ORM框架,我们在 后面会用到
pip install pymysql pip install SQLAlchemy
SQLAlchemy操作数据库
连接数据库
from sqlalchemy import create_engine def conn_db1(): # 数据库的变量 HOST = '192.168.30.151' # 127.0.0.1/localhost PORT = 3306 DATA_BASE = 'flask_db' USER = 'root' PWD = '123' # DB_URI = f'数据库的名+驱动名://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}' DB_URI = f'mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}' engine = create_engine(DB_URI) # 执行一个SQL sql = 'select 2;' conn = engine.connect() rs = conn.execute(sql) print(rs.fetchone())
执行原生SQL
def conn_db2(): # 数据库的变量 HOST = '192.168.30.151' # 127.0.0.1/localhost PORT = 3306 DATA_BASE = 'flask_db' USER = 'root' PWD = '123' # DB_URI = f'数据库的名+驱动名://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}' DB_URI = f'mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}' ''' # 创建一个引擎,专门链接数据库用的 engine = create_engine(DB_URI) sql = 'create table t_user(id int primary key auto_increment, name varchar(32));' # 链接数据库 conn = engine.connect() # 执行SQL即可 conn.execute(sql) ''' def conn_db3(): # 数据库的变量 HOST = '192.168.30.151' # 127.0.0.1/localhost PORT = 3306 DATA_BASE = 'flask_db' USER = 'root' PWD = '123' # DB_URI = f'数据库的名+驱动名://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}' DB_URI = f'mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}' # 创建一个引擎,专门链接数据库用的 engine = create_engine(DB_URI) sql = 'create table t_user1(id int primary key auto_increment, name varchar(32));' # 链接数据库 with engine.connect() as conn: # 执行SQL即可 conn.execute(sql)
ORM模型映射到数据库中
- 用 declarative_base 根据 engine 创建一个ORM基类
from sqlalchemy.ext.declarative import declarative_base engine = create_engine(DB_URI) Base = declarative_base(engine)
用这个 Base 类作为基类来写自己的ORM类。要定义 __tablename__ 类 属性,来指定这个模型映射到数据库中的表名
class Person(Base): __tablename__ ='t_person'
创建属性来映射到表中的字段,所有需要映射到表中的属性都应 该为Column类型
class Person(Base): __tablename__ ='t_person' # 在这个ORM模型中创建一些属性,来跟表中的字段进行一一映射。 # 这些属性必须是sqlalchemy给我们提供好的数据类型 id = Column(Integer,primary_key=True,autoincrement=True) name = Column(String(50)) age = Column(Integer) country = Column(String(50))
- 使用 Base.metadata.create_all() 来将模型映射到数据库中
Base.metadata.create_all()
注意
一旦使用 Base.metadata.create_all() 将模型映射到数据库中后,即使改变 了模型的字段,也不会重新映射了
SQLAlchemy对数据的增删改查操作
构建session对象
所有和数据库的ORM操作都必须通过一个叫做 session 的会话对象 来实现,通过以下代码来获取会话对象
from sqlalchemy.orm import sessionmaker engine = create_engine(DB_URI) Base = declarative_base(engine) session = sessionmaker(engine)()
添加对象
def create_data_one(): with Session() as session: p1 = Person(name = '刘备',age = 6 ,country='北京') session.add(p1) session.commit() def create_data_many(): with Session() as session: p2 = Person(name = '吕布',age = 19 ,country='北京') p3 = Person(name = '貂蝉',age = 18 ,country='北京') session.add_all([p2,p3]) session.commit()
查找对象
def query_data_all(): with Session() as session: all_person = session.query(Person).all() for p in all_person: print(p.name) def query_data_one(): with Session() as session: p1 = session.query(Person).first() print(p1.name) def query_data_by_params(): with Session() as session: # p1 = session.query(Person).filter_by(name='吕布').first() p1 = session.query(Person).filter(Person.name == '吕布').first() print(p1.age)
修改对象
def update_data(): with Session() as session: p1 = session.query(Person).filter(Person.name == '吕布').first() p1.age = 20 # 提交事务 session.commit()
删除对象
将需要删除的数据从数据库中查找出来,然后使用 session.delete 方法将 这条数据从session中删除,最后做commit操作就可以了
def delete_data(): with Session() as session: p1 = session.query(Person).filter(Person.name == '貂蝉').first() session.delete(p1) session.commit()
SQLAlchemy常用数据类型
Integer:整形,映射到数据库中是int类型。
Float:浮点类型,映射到数据库中是float类型。他占据的32 位。Double:双精度浮点类型,映射到数据库中是double类型,占 据64位 (SQLALCHEMY中没有)。 String:可变字符类型,映射到数据库中是varchar类型.
Boolean:布尔类型,映射到数据库中的是tinyint类型。
DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问 题的。在存储钱相关的字段的时候建议大家都使用这个数据类 型。
这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数 字,第二个参数表示小数点后有多少位。
Enum:枚举类型。指定某个字段只能是枚举中指定的几个值, 不能为其他值。在ORM模型中,使用Enum来作为枚举,示例代 码如下:
class News(Base): __tablename__ = 't_news' tag = Column(Enum("python",'flask','django'))
在Python3中,已经内置了enum这个枚举的模块,我们也可以 使用这个模块去定义相关的字段。示例代码如下:
class TagEnum(enum.Enum): python = "python" flask = "flask" django = "django" class News(Base): __tablename__ = 't_news' id = Column(Integer,primary_key=True,autoincrement=True) tag = Column(Enum(TagEnum))
Date:存储时间,只能存储年月日。映射到数据库中是date类 型。在Python代码中,可以使用 datetime.date 来指定。
DateTime:存储时间,可以存储年月日时分秒毫秒等。映射到 数据库中也是datetime类型。在Python代码中,可以使用 datetime.datetime 来指定。
Time:存储时间,可以存储时分秒。映射到数据库中也是time 类型。在Python代码中,可以使用 datetime.time 来至此那个。示例 代码如下:
class News(Base): __tablename__ = 't_news' create_time = Column(Time) news =News(create_time=time(hour=11,minute=11,second=11))
Text:存储长字符串。一般可以存储6W多个字符。如果超出了 这个范围,可以使用LONGTEXT类型。映射到数据库中就是text 类型。
LONGTEXT:长文本类型,映射到数据库中是longtext类型。
from sqlalchemy import create_engine,Column,Integer,String,Float,Enum,Boolean,DECIMAL,Text,Date,DateTime,Time from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.orm import sessionmaker import enum from datetime import date from datetime import datetime from datetime import time # 准备数据库的一堆信息 ip port user pwd 数据库的名称 按要求组织格式 HOSTNAME = '127.0.0.1' PORT = '3306' DATABASE = 'first_sqlalchemy' USERNAME = 'root' PASSWORD = 'root' # dialect+driver://username:password@host:port/database?charset=utf8 # 按照上述的格式来 组织数据库信息 DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD, host=HOSTNAME,port=PORT,db=DATABASE) # 创建数据库引擎 engine = create_engine(DB_URI) # 创建会话对象 session = sessionmaker(engine)() # 定义一个枚举类 class TagEnum(enum.Enum): python="PYHTON2" flask="FLASK2" django ="DJANGO" # 创建一个ORM模型 说明基于sqlalchemy 映射到mysql数据库的常用字段类型有哪些? Base = declarative_base(engine) class News(Base): __tablename__='news' id = Column(Integer,primary_key=True,autoincrement=True) price1 = Column(Float) #存储数据时存在精度丢失问题 price2 = Column(DECIMAL(10,4)) title = Column(String(50)) is_delete =Column(Boolean) tag1 =Column(Enum('PYTHON','FLASK','DJANGO')) # 枚举常规写法 tag2 =Column(Enum(TagEnum)) #枚举另一种写法 create_time1=Column(Date) create_time2=Column(DateTime) create_time3=Column(Time) content1 =Column(Text) content2 =Column(LONGTEXT) # Base.metadata.drop_all() # Base.metadata.create_all() # 新增数据到表news中 # a1 = News(price1=1000.0078,price2=1000.0078,title ='测试数据',is_delete=True,tag1="PYTHON",tag2=TagEnum.flask, # create_time1=date(2018,12,12),create_time2=datetime(2019,2,20,12,12,30),create_time3=time(hour=11,minute=12,second=13), # content1="hello",content2="hello hi nihao") a1 = News(price1=1000.0078,price2=1000.0078,title='测试数据' ,is_delete=False,tag1="PYTHON",tag2=TagEnum.python, create_time1=date(2018,12,12),create_time2=datetime(2019,2,20,12,12,30),create_time3=time(hour=11,minute=12,second=13), content1="hello",content2="hello hi nihao") session.add(a1) session.commit()
Column常用参数
primary_key:True设置某个字段为主键。
autoincrement:True设置这个字段为自动增长的。
default:设置某个字段的默认值。在发表时间这些字段上面经 常用。
nullable:指定某个字段是否为空。默认值是True,就是可以为 空。
unique:指定某个字段的值是否唯一。默认是False。
onupdate:在数据更新的时候会调用这个参数指定的值或者函 数。在第一次插入这条数据的时候,不会用onupdate的值,只 会使用default的值。常用于是 update_time 字段(每次更新数据的 时候都要更新该字段值)。
name:指定ORM模型中某个属性映射到表中的字段名。如果不 指定,那么会使用这个属性的名字来作为字段名。如果指定了, 就会使用指定的这个值作为表字段名。这个参数也可以当作位置 参数,在第1个参数来指定。
title = Column(String(50),name='title',nullable=False) title = Column('my_title',String(50),nullable=False)
from datetime import datetime from sqlalchemy import Column,Integer,DateTime,String from db_util import Base,Session class News(Base): __tablename__ = 't_news2' id = Column(Integer,primary_key = True,autoincrement = True) phone = Column(String(11),unique = True) title = Column(String(32),nullable = False) read_count = Column(Integer,default=1) create_time = Column(DateTime,default = datetime.now) update_time = Column(DateTime,default = datetime.now, onupdate =datetime.now ) # 当数据更新后,参数的内容才会更改 def create_data(): new1 = News(phone='16866666666',title='测试列参数') with Session() as session: session.add(new1) session.commit() def create_data2(): # new1 = News(phone='16866666666',title='测试列参数') # 不允许重复 # new1 = News(phone='16866666668') # title不能为空 # with Session() as session: # session.add(new1) # session.commit() with Session() as session: new1 = session.query(News).first() new1.read_count = 2 session.commit() if __name__ == '__main__': # Base.metadata.create_all() # create_data() create_data2()
query函数的使用
模型名。指定查找这个模型中所有的属性(对应查询表为全表查 询)
模型中的属性。可以指定只查找某个模型的其中几个属性
聚合函数
func.count:统计行的数量。
func.avg:求平均值。
func.max:求最大值。
func.min:求最小值。
func.sum:求和。
提示
func 上,其实没有任何聚合函数。但是因为他底层做了一些 魔术,只要mysql中有的聚合函数,都可以通过func调用
from random import randint from sqlalchemy import Column,Integer,String,func from db_util import Base,Session class Item(Base): __tablename__ = 't_item' id = Column(Integer,primary_key = True,autoincrement = True) title = Column(String(32)) price = Column(Integer) def create_data(): with Session() as ses: for i in range(10): item = Item(title = f'产品:{i+1}',price=randint(1,100)) ses.add(item) ses.commit() def query_model_name(): # 获取所有的字段 with Session() as ses: rs = ses.query(Item).all() for r in rs: print(r.title) def query_model_attr(): # 获取指定的字段 with Session() as ses: rs = ses.query(Item.title,Item.price).all() for r in rs: print(r.price) def query_by_func(): # 统计指定的列数据 with Session() as ses: # rs =ses.query(func.count(Item.id)).first() # rs =ses.query(func.max(Item.price)).first() # rs =ses.query(func.avg(Item.price)).first() rs =ses.query(func.sum(Item.price)).first() print(rs) if __name__ =='__main__': # Base.metadata.create_all() # create_data() # query_model_name() # query_model_attr() query_by_func()