flask-session
出现背景:你有没有发现我们在flask中是吧session保存在cookie中,并没有自己保存一份,这样太不安全了,这时候,就出现了request-session
作用
flask-session 的作用:将flask中默认保存在cookie中的值保存在redis/memcached/file/Mongodb/SQLAlchemy中
安装
pip install flask-session
存储方式
redis
源码解析
储存在redis中的流程: 在open_session 中 如果第一次登陆它会生成一个随机字符串sid=uuid4,然后返回一个字典,这个字典以sid为键,uuid为值,然后再视图函数中把我写的session放到这个字典中 在save_session中 首先会把session 先dict然后把它序列化赋值给val,注意这次序列化用的是pickle. 然后把val 传到redis中 最后把session中的sid设置到cookie中去 这次把数据存到redis 把数据中的sid存到cookie
第一种方法
from flask import Flask,session from flask_session import RedisSessionInterface from redis import Redis app = Flask(__name__) app.debug=True app.secret_key="llldf" #app.session_interface=SecureCookieSessionInterface() #默认是等于session等于这个类,我们想要改变session的储存方式就可以改变这个类 app.session_interface=RedisSessionInterface\ (redis=Redis(host='127.0.0.1',port=6379), key_prefix='luxien') @app.route('/index') def index(): session['k1']=123 return '中国你好' if __name__ == '__main__': app.run()
第二种方法:
from flask_session import Session from redis import Redis from flask import Flask,session app = Flask(__name__) app.debug=True app.secret_key="llldf" app.config['SESSION_TYPE'] = 'redis' # session类型为redis app.config['SESSION_PERMANENT'] = False # 如果设置为True,则关闭浏览器session就失效。 app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密 app.config['SESSION_KEY_PREFIX'] = 'session:' # 保存到session中的值的前缀 app.config['SESSION_REDIS'] = Redis(host='127.0.0.1', port='6379') # 用于连接redis的配置 Session(app) @app.route('/index') def index(): session['k1']=123 return '中国你好' if __name__ == '__main__': app.run()
注意这里有一个问题,当你在index里给session赋值时,第一次附的值会保存,当你再在index给他赋值时,它不会生效了
数据库连接池
在flask中并没有djano中的orm,要连接数据库有两种方式,其中一个是pymysql模块.
blueprint
如果代码很多要进行归类,blueprint就是对flask的目录结构进行分配(应用于小中型程序)
第一步 manage.py
from blueprint_demo import create_app app=create_app() if __name__ == '__main__': app.run()
第二步:
init.py
from flask import Flask ####导入蓝图 from .views.acount import ac from .views . home import home def create_app(): app=Flask(__name__) app.config.from_object('settings.DevelopmentConfig') #导入配置文件 app.register_blueprint(ac) #把蓝图挂载到app中 app.register_blueprint(home) return app
account.py
from flask import Blueprint,render_template,request,session,redirect from uuid import uuid4 from blueprint_demo.utils.sql import SqlHelper ac=Blueprint('ac',__name__) #初始化Blueprint对象的第一个参数,指定了这个蓝图的名称,第二个参数指定了该蓝图所在的模块名,这里自然是当前文件。 @ac.route('/login',methods=['GET',"POST"]) def login(): if request.method=="GET": return render_template('login.html') user=request.form.get('user') password=request.form.get('password') print(user,password) #连接数据库查询,用pymsql连接 obj=SqlHelper.fetch_one("select id,name from student WHERE NAME =%s and password=%s",[user,password]) if obj: sid=str(uuid4()) session['user_info']={ 'id':obj['id'], 'name':user } return redirect('/index') else: return render_template('login.html',msg='用户名或密码错误')
home.py
from flask import Blueprint,render_template,request,session,redirect home=Blueprint('home',__name__) @home.route('/index') def index(): #注意视图名字不要和蓝图对象的名字相同 user_info = session.get('user_info') return "index"
settings.py
from datetime import timedelta from redis import Redis import pymysql from DBUtils.PooledDB import PooledDB, SharedDBConnection class Config(object): DEBUG = True SECRET_KEY = "umsuldfsdflskjdf" PERMANENT_SESSION_LIFETIME = timedelta(minutes=20) #默认持续时间 # SESSION_REFRESH_EACH_REQUEST= True # SESSION_TYPE = "redis" # PYMYSQL_POOL = PooledDB( # creator=pymysql, # 使用链接数据库的模块 # maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 # mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 # maxcached=5, # 链接池中最多闲置的链接,0和None不限制 # maxshared=3, # # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 # blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 # maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 # setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] # ping=0, # # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always # host='127.0.0.1', # port=3306, # user='root', # password='123456', # database='s8day127db', # charset='utf8' # ) class ProductionConfig(Config): SESSION_REDIS = Redis(host='192.168.0.94', port='6379') class DevelopmentConfig(Config): SESSION_REDIS = Redis(host='127.0.0.1', port='6379') class TestingConfig(Config): pass
以上就是用pymysql来连接数据库用来的查询的,但是这样有一个弊端就是我们每次都要连接数据库浪费资源
所以必须用数据库连接池
DBUtils
DBUtils是python的一个用于实现数据库连接池的模块
首先安装DBUtils
pip install dbutils
此连接池有两种连接模式:
- 模式一:为每个线程创建一个连接,线程即使调用了close方法,也不会关闭,只是把连接重新放到连接池,供自己线程再次使用。当线程终止时,连接自动关闭。只有线程死掉连接才真正的关闭模式一这种模式很少用,基于threading.local实现的
-
from DBUtils.PooledDB import PooledDB,SharedDBConnection from DBUtils.PersistentDB import PersistentDB import pymysql POOL = PersistentDB( creator=pymysql, # 使用链接数据库的模块 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always closeable=False, # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接) threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置 host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8' ) def func(): conn = POOL.connection(shareable=False) cursor = conn.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() conn.close() func()
模式二:创建一批连接到连接池,供所有线程共享使用。使用时来进行获取,使用完毕后在放回到连接池
PS:由于pymysql、MySQLdb等threadsafety值为1,所以该模式连接池中的线程会被所有线程共用 -
from DBUtils.PooledDB import PooledDB,SharedDBConnection from DBUtils.PersistentDB import PersistentDB import pymysql POOL = PersistentDB( creator=pymysql, # 使用链接数据库的模块 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always closeable=False, # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接) threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置 host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8' ) def func(): conn = POOL.connection(shareable=False) cursor = conn.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() conn.close() func()
要想在视图函数中获取配置文件的值,都是通过app.config来拿。但是如果视图函数和Flask创建的对象app不在一个模块。就得
导入来拿。可以不用导入,。直接导入一个current_app,这个就是当前的app对象,用current_app.config就能查看到了当前app的所有的配置文件
from flask import Flask,current_app
@app.route('/index',methods=["GET","POST"]) def index(): print(current_app.config) #当前的app的所有配置 session["xx"] = "fdvbn" return "index"
wtforms
WTForms主要用于对python web框架做表单信息验证,不仅仅Flask能用
安装 wtforms
pip install wtforms
用户登录注册示例
1. 用户登录
当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。如:
密码不能为空;密码长度必须大于12;密码必须包含 字母、数字、特殊字符等(自定义正则);
注意wtforms中的变量不能开头带有_ 源码规定的
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录</h1> <form method="post"> <!--<input type="text" name="name">--> <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p> <!--<input type="password" name="pwd">--> <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p> <input type="submit" value="提交"> </form> </body> </html> login.html
app.py
#!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, render_template, request, redirect from wtforms import Form from wtforms.fields import core from wtforms.fields import html5 from wtforms.fields import simple from wtforms import validators from wtforms import widgets app = Flask(__name__, template_folder='templates') app.debug = True #写一个wtforms类 class LoginForm(Form):#1.1 name = simple.StringField( label='用户名', validators=[ validators.DataRequired(message='用户名不能为空.'), validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d') ], widget=widgets.TextInput(), #用户渲染input的标签 render_kw={'class': 'form-control'} #用于渲染出input的标签中含有form-control的类 ) pwd = simple.PasswordField( label='密码', validators=[ validators.DataRequired(message='密码不能为空.'), validators.Length(min=8, message='用户名长度必须大于%(min)d'), validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}", message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符') ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} ) @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': form = LoginForm() return render_template('login.html', form=form) else: form = LoginForm(formdata=request.form) if form.validate():#验证表单信息的有效性 obj = SQLHelper.fetch_one("select id,name from users where name=%(user)s and pwd=%(pwd)s", form.data) #这里要这样传参 if obj: session.permanent = True session['user_info'] = {'id':obj['id'], 'name':obj['name']} return redirect('/index') else: return render_template('login.html',msg='用户名或密码错误',form=form) else: print(form.errors) return render_template('login.html', form=form) if __name__ == '__main__': app.run() app.py
wtfrom 源码分析
我们拿登录视图来做wtform源码分析.
首先程序启动后,运行到1.1阶段:
我们要查看一下LoginForm 是由哪个type创建出来的.
点击进入form
然后进入with_metaclass 中
从这里我们就可以看出点猫腻了, 其实这步就是做了这样的操作,生成了一个叫NewBase 类,这里如果看不懂的话就去看这篇博客https://www.cnblogs.com/sticker0726/articles/8982399.html
class NewBase(BaseForm,metaclass=FormMeta):
这个类metaclass指定了他的type类, 那么上一步 其实操作这样的
class Form(NewBase):
我们知道程序刚启动时 当创造类时,会先执行metaclass类的init 方法,下面我们就来看一下
它的__init__方法其实为我们的LoginForm添加了两个属性.
_unbound_fields=None ,
_wtforms_meta =None
也就是我们loginForm变成了这样
class LoginForm(Form): name = simple.StringField( ) #里边的内容以省略 pwd = simple.PasswordField() _unbound_fields=None _wtforms_meta =None
然后我们写的代码走到了这里
我们要先看有没有指定metaclass如果指定类还要走StringField的metaclass指定的type类,我们先看了没有,
这是在实例化对象的过程,会先执行 该类的__new__方法
这步结果是什么:
name和pwd变成了这样
class LoginForm(Form): name =UnboundField(simple.StringField, *args, **kwargs) pwd = UnboundField(simple.PasswordField, *args, **kwargs) _unbound_fields=None _wtforms_meta =None
继续让下走
class UnboundField(object): _formfield = True creation_counter = 0 #初始值 creation_counter def __init__(self, field_class, *args, **kwargs): UnboundField.creation_counter += 1 # 这步也就是说每实例化一次UnboundField creation_counter就会加一 self.field_class = field_class self.args = args self.kwargs = kwargs self.creation_counter = UnboundField.creation_counter
这步的LoginForm变为后:
class LoginForm(Form): name =UnboundField(simple.StringField, **kwargs,creation_counter=1) pwd = UnboundField(simple.PasswordField, **kwargs,creation_counter=2) _unbound_fields=None _wtforms_meta =None
我们再分析init方法,发现没什么用
实例化对象后
也就是执行我们写的这句代码
1.实例化对象后首先调用metaclass指定元类的__call__方法
#这是__call__的上半部分
def __call__(cls, *args, **kwargs): if cls._unbound_fields is None: fields = [] for name in dir(cls): #这步得到当前类的所有属性和方法的名称 if not name.startswith('_'): #如果属性名中不包含_ 也就是得到是我们name 和password 字段 unbound_field = getattr(cls, name) #得到这些属性值 if hasattr(unbound_field, '_formfield'): # 判断这些属性值中是否含有_formfield 属性名,我们判断后,我们name和password字段都含有 fields.append((name, unbound_field)) #这里的fields=[(name,simple.StringField( )),( pwd ,simple.PasswordField())] fields.sort(key=lambda x: (x[1].creation_counter, x[0])) #通过creation_counter的值来排序 cls._unbound_fields = fields
结果
class LoginForm(Form): name = UnboundField(simple.StringField, *args, **kwargs) pwd = UnboundField(simple.PasswordField, *args, **kwargs) _unbound_fields = [(name, UnboundField(simple.StringField, *args, **kwargs)), (pwd, UnboundField(simple.PasswordField, *args, **kwargs))] _wtforms_meta = None
接下来执行__call__方法的下半部分
if cls._wtforms_meta is None: bases = [] for mro_class in cls.__mro__: #for循环继承类,是一个元组(LoginForm,Form,Newbase,BaseForm,object) if 'Meta' in mro_class.__dict__: #有Meta 在form 中 bases.append(mro_class.Meta) #这步就得到了 bases=[DefaultMeta] cls._wtforms_meta = type('Meta', tuple(bases), {}) #这里就得到了 _wtforms_meta=class Meta(bases) 也就是得到 _wtforms_meta= class Meta(DefaultMeta metaclass=type) return type.__call__(cls, *args, **kwargs)
结果变成:
class LoginForm(Form): name = UnboundField(simple.StringField, *args, **kwargs) pwd = UnboundField(simple.PasswordField, *args, **kwargs) _unbound_fields = [(name, UnboundField(simple.StringField, *args, **kwargs)), (pwd, UnboundField(simple.PasswordField, *args, **kwargs))] _wtforms_meta = class Meta(DefaultMeta) #注意_wtform_meta等于一个类而不是实例化对象
2.然后执行LoginForm __new__方法,发现没有__new__方法,
3. loginForm.__init__()
我们进入到BaseForm 中 找init方法
class BaseForm(object): def __init__(self, fields, prefix='', meta=DefaultMeta()): if prefix and prefix[-1] not in '-_;:/.': prefix += '-' self.meta = meta #注意这里 self._prefix = prefix self._errors = None self._fields = OrderedDict() #注意这里是个字典 if hasattr(fields, 'items'): fields = fields.items() translations = self._get_translations() extra_fields = [] if meta.csrf: self._csrf = meta.build_csrf(self) extra_fields.extend(self._csrf.setup_form(self)) for name, unbound_field in itertools.chain(fields, extra_fields): options = dict(name=name, prefix=prefix, translations=translations) field = meta.bind_field(self, unbound_field, options) self._fields[name] = field
进入图中红框区
得到结果:
我们继续走form中的 init
for name, field in iteritems(self._fields): # Set all the fields to attributes so that they obscure the class # attributes with the same names. setattr(self, name, field) #这步其实把name和pwd赋值给了form 对象了 self.process(formdata, obj, data=data, **kwargs)#data相当于给input框加上默认值,这步的只要是当前端返回用户数据的时候用的
我们知道在页面上我们如果打印form.name 是执行 name对应类中也就是stringfiedl的__str__方法
我们在field中找到了str
这样就会执行当前类的call() 方法
注意这里的后边的self是谁,就是当前的stringfild的对象
进入render_field
对象加括号执行TextINput 的__call__方法
这样就生成了一个input框
最后把所有的东西都封装到了form中,这就是为什么我们在前端页面 这样写{{form.name}} 可以生成出一个input框
关于后边的isvlaild我没有写
flask_wtf
前后端不分离的项目容易出现csrf(Cross-site request forgery)(跨站请求伪造攻击)
flask_wtf本身提供了生成表单HTML页面的功能(基于wtforms),常用于开发前后端不分离的表单页面,同时它还提供了一套完善的csrf防护体系,对于我们开发来说,可以更好的解决CSRF攻击
如果是前后端分离的项目的话,使用token不要用cookie,每次访问都要把token放到请求头中去,让后端去验证
flask-script
flask-script的作用是可以通过命令行的形式来操作flask.就像Django启动命令一样,在命令行中输入Python managr runserver程序就运行起来了,有了flask-script也可以这样
Flask-Scropt插件为在Flask里编写额外的脚本提供了支持。这包括运行一个开发服务器,一个定制的Python命令行,用于执行初始化数据库、定时任务和其他属于web应用之外的命令行任务的脚本
注意:在flask0.11版本后已不再用该模块,用内置click替代
我们来写一个简单的蓝图
manage.py
from flask_script_demo import create_app app =create_app() if __name__ == '__main__': app.run()
init.py
from flask import Flask from .views.account import ac def create_app(): app=Flask(__name__) app.register_blueprint(ac) #把ac蓝图注册到App中 return app
在views 中的account.py
from flask import blueprints ac=blueprints.Blueprint('ac',__name__) @ac.route('/login/',methods=["GET","POST"]) def login(): return "login"
#第一步 只需要改变mange.py就可以了:
在该文件中,必须有一个Manager实例,Manager类追踪所有在命令行中调用的命令和处理过程的调用运行情况;
Manager只有一个参数——Flask实例,也可以是一个函数或其他的返回Flask实例;
调用manager.run()启动Manager实例接收命令行中的命令
from flask_script_demo import create_app from flask_script import Manager app =create_app() manager=Manager(app)#把app加入到这里 if __name__ == '__main__': #app.run() manager.run() #这里就不用app.run启动了直接manager.run()启动
你只需要在Teminal命令行中输入
python manage.py runserver
但是你在pycharm中右键运行 manage.py文件不生效了.
这样就可以启动了
创建命令行命令
创建命令行命令总共有三种方法:
第一种创建command子类
from flask_script_demo import create_app from flask_script import Manager,Command app =create_app() manager=Manager(app)#把app加入到这里 class Hello(Command): print("hello world") manager.add_command("hello",Hello()) #把这个类挂载到manager中,第一个参数是命令行命令,第二个是类的实例化 if __name__ == '__main__': #app.run() manager.run() #这里就不用app.run启动了直接manager.run()启动
在terminal中运行代码
python manage.py hello
方法二:使用Command实例的@command修饰符
from flask_script_demo import create_app from flask_script import Manager,Command app =create_app() manager=Manager(app)#把app加入到这里 @manager.command def hello(): print("hello") if __name__ == '__main__': #app.run() manager.run() #这里就不用app.run启动了直接manager.run()启动
在terminal中运行代码
python manage.py hello #hello函数就会执行
第三种:使用Command实例的@option修饰符
当你需要向你定义的命令行函数传递不同的参数,进而控制命令行函数的行为时,这个时候option装饰器就出现了.
from flask_script_demo import create_app from flask_script import Manager,Command app =create_app() manager=Manager(app)#把app加入到这里 @manager.option("-n","--name",help="your name") #命令既可以用-n,也可以用--name,来输入参数 def hi(name): print("hello",name) if __name__ == '__main__': manager.run()
在terminal输入 在输入命令之前在代码中一定在启动了服务器
python manage.py hi -n "小红" #或者 python manage.py hi --name "小红"
当然你可以这样简单的做:
click
flask在0.11后使用click来替代flask-script自定义终端命令
手动添加超级用户
import click @app.cli.command("add_user") # 先注册命令 @click.argument("username") # 注册位置参数 @click.option('-a', 'age', type=int, default=18, help="年龄") #可选的参数 def add_user(username, age): """ 命令说明文档:添加用户 :param username: 用户名 :param age: 年龄 :return: """ if age: print(f"要添加的用户{username},年龄为{age}") else: print(f"要添加的用户{username}")
在终端输入 flask
可以看到已经有创建的方法嘞add_user
Commands: add_user 命令说明文档:添加用户 :param username: 用户名 :param age: 年龄 :return: routes Show the routes for the app. run Run a development server. shell Run a shell in the app context. (flask_env) sticker@zhangdeMacBook-Pro flask_demo % flask add_user alex
在终端输入 flask add_user alex -a 22
要添加的用户alex,年龄为22
Flask-sqlalchemy
在 linux系统上使用Flask-sqalchemy需要安装
a. pip3 install Flask-SQLAlchemy b. yum -y install mysql-devel gcc gcc-devel python-devel c. pip3 install mysqlclient
flask-sqlalchemy 其实就是简化了sqlalchemy的操作的一个组件
作用: 将SQLAlchemy的所有操作封装到了一个db=SQLAlchemy() 对象中,大大简化了创建数据库和查询数据的操作
manage.py
rom flask_script_demo import create_app from flask_script import Manager app =create_app() manager=Manager(app)#把app加入到这里 if __name__ == '__main__': #app.run() manager.run() #这里就不用app.run启动了直接manager.run()启动
settings.py
class BaseConfig(object): # SESSION_TYPE = 'redis' # session类型为redis # SESSION_KEY_PREFIX = 'session:' # 保存到session中的值的前缀 # SESSION_PERMANENT = True # 如果设置为False,则关闭浏览器session就失效。 # SESSION_USE_SIGNER = False # 是否对发送到浏览器上 session:cookie值进行加密 SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/day130?charset=utf8" SQLALCHEMY_POOL_SIZE = 5 SQLALCHEMY_POOL_TIMEOUT = 30 SQLALCHEMY_POOL_RECYCLE = -1 # 追踪对象的修改并且发送信号 SQLALCHEMY_TRACK_MODIFICATIONS = False class ProductionConfig(BaseConfig): pass class DevelopmentConfig(BaseConfig): pass class TestingConfig(BaseConfig): pass
init.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy #导入SQLAlchemy类 #包含了sqlalchey的所有操作 db=SQLAlchemy() #实例化对象
from .views.account import ac #######注意这里千万别写在db的上面因为它要先实例化db对象
def create_app(): app=Flask(__name__) app.config.from_object("settins.DevelopmentConfig") #导入配置 app.register_blueprint(ac) db.init_app(app)#把app加在到db中,这步的主要作用是:因为sqlalchemy对象是全局的,我们不能把sqlalchemy加入到app中,因为app可能有多个,我们可以把app加入到sql对象中, return app
# 创建数据库表格
db.create_all(app=create_app())
我来解释一下这里的db.init_app的作用,刚开始学的这里很懵逼,
我们来看Flask_sqlalchemy的源码
def __init__(self, app=None, use_native_unicode=True, session_options=None, metadata=None, query_class=BaseQuery, model_class=Model): self.use_native_unicode = use_native_unicode self.Query = query_class self.session = self.create_scoped_session(session_options) self.Model = self.make_declarative_base(model_class, metadata) self._engine_lock = Lock() self.app = app _include_sqlalchemy(self, query_class) if app is not None: self.init_app(app) #如果我们不把app传进去的话,就会调用该方法.
你会发现SQLAlchemy这个类,的参数中有app.它在实例话的时候调用了self.init_app(app),这个函数里边主要是导入了app.config的配置文件.
但是我们在代码中必须调用这个方法把app传进入,db才能使用app的配置文件. 然后就可以把我们db的数据写到配置文件中了. 也就是你SQLAlchemy这个类中定义了,就不用在函数中再写db.init_app(app)了.
models.py 我们可以这样创建数据库'
from sqlalchemy import Column, Integer, String from flask_script_demo import db #导入db class Classes(db.Model):#导入了db __tablename__ = 'classesday130' id = Column(Integer, primary_key=True,autoincrement=True) name = Column(String(32),nullable=False,unique=True)
views中的account.py
添加数据
from mysql_flask.test import Userlask,db me = Userlask(username='alex4', email='28693387223@qq.com') db.session.add(me) db.session.commit()
删除数据
db.session.delete(me)
db.session.commit()
查询数据
那么我们怎么从数据库中查询数据?为此,Flask-SQLAlchemy 在您的 Model
类上提供了 query
属性。
有两种方法可以查询:
#第一种方法 obj = Userlask.query.filter_by(username='alex').first() print(obj.id)
第二种:
我们这样来查询数据和sqlalchemy中的使用方法一样.
@ac.route("/index/",methods=["GET","POST"]) def index(): #####以前我们这样来查数据 # from sqlalchemy.orm import sessionmaker # from sqlalchemy import create_engine # engine=create_engine("mysql+pymysql://root:123456@127.0.0.1:3306/day130?charset=utf8") # Session=sessionmaker(bind=engine) # session=Session() # result=session.query(Classes).filter(Classes.name=="韩信").first() ######现在我们这样来查数据#### result=db.session.query(Classes).filter(Classes.name=="韩信").first() print(result.name) return "index"
更新数据
obj = Userlask.query.filter_by(username='alex').first() obj.username='程咬金' db.session.commit()
总结
#第一步 在init.py 中创建db对象 #第二步 在__init__.py 中create_app函数中将app传入到db中 #第三步 写配置文件,将连接字符串定义在配置文件中 #第四步 创建model类 #第5步 创建数据库表,编写离线脚本(利用上下文管理) #第6步 在视图函数中操作数据库
离线脚本
离线脚本就是当我们的项目没有运行时,这时候需要用到项目中的某个功能,比如删除数据库.这时候如果你直接去删除就会遇到这个情况
mongoengine.connection.MongoEngineConnectionError: You have not defined a default connection
例子:
当我们要删除数据库中的表时,
有两种方法:第一种用SQLalchemy的方式删除表,
第二种用flask_sqlalchemy的方式删除即使用离线脚本删除
""" web运行时,flask程序运行起来,用户通过浏览器访问 离线脚本,自定义的一个py文件,+使用flak中定义好的功能 """ from flask_script_demo import db from flask_script_demo import create_app from ..models import Classes app=create_app() #obj=app.app_context() #这是一个对象 with app.app_context():#凡是能with 对象的说明这个类中含有__enter__和__exit__()方法,把当前app加载到local对象中去,请求结束时删除local对象中的app db.drop_all()#删除表 db.create_all()#创建表 result = db.session.query(Classes).filter(Classes.name == "韩信").first()#查询数据 print(result.name)
db就我们在__init__文件中实例化的对象,它包含了create_all(创建表)和drop_all(删除表)的命令,但是由于在使用db时我们需要用到app中关于数据库的配置(从上下文中取),但是这时项目没有运行,没有请求,在local类中没有app的内容,无法使用app,所以我们使用with方法,利用上下文管理,主动将app添加到loacl对象中. 这时候我们就可以删除数据库中的表了.
在项目中遇到的真是的例子:
我们要给MongoDB中一个集合添加一个新的字段:不要运行项目直接写一个脚本:
from models import * from config import config from app import create_app from common.utils import generate_rode_code app = create_app(config) with app.app_context(): us = Users.objects.all() for u in us: u.invite_code = generate_rode_code(6) u.save()
flask-migrate
flask-migrate 是一个处理sqlalchemy数据库迁移的一个扩展,只能处理sql不能处理nosql
在开发程序的过程中,你会发现有时需要修改数据库模型(比如增加字段),而且修改之后还需要更新数据库.仅当数据库表不存在时,flask-sqlalchemy才会根据模型进行创建.因此,更新表的唯一的方式就是先删除旧表,不过这样做会丢失数据库中的所有数据.跟新数据表的更好的方法是使用数据库迁移框架.
安装
pip install flask-migrate
只需要修改manage.py文件就可以了
from flask_script_demo import create_app from flask_script import Manager,Command from flask_migrate import Migrate,MigrateCommand #导入,Migrate,MigrateCommand from flask_script_demo import db app =create_app() manager=Manager(app)#把app加入到这里 Migrate(app,db) manager.add_command('db',MigrateCommand)#添加了db命令 if __name__ == '__main__': #app.run() manager.run() #这里就不用app.run启动了直接manager.run()启动
在terminal中输入
在terminal中执行以下代码 python manage.py db init #初始hua python manage.py db migrate # makemigrations python manage.py db upgrade # migrate
总结:
会生成两张表,一张我们创建的表,另一张flask_migrate给我们生成的数据库记录表,还可以可以添加字段了
自定义扩展组件
我们拿用户登录,访问主页来做例子,看如何自定义扩展组件
项目目录结构
auth.py
from flask import request,session,redirect class Auth(object): def __init__(self,app=None): self.app=app if app: self.init_app(app) def init_app(self,app): app.auth_manager=self#把auth对象传到app中,以后auth对象中的所有属性和方法app都可以调用了 self.app=app app.before_request(self.check_login) #用户验证 app.context_processor(self.context_processor)#设置全局变量 def check_login(self): if request.path=="/login/": return user=session.get('user') if not user: return redirect('/login/') def login_session(self,data): session['user']=data def context_processor(self): """ 设置全局变量 :return: """ data=session.get('user') return dict(current_user=data)
setting.py
class BaseConfig(object): # SESSION_TYPE = 'redis' # session类型为redis # SESSION_KEY_PREFIX = 'session:' # 保存到session中的值的前缀 # SESSION_PERMANENT = True # 如果设置为False,则关闭浏览器session就失效。 # SESSION_USE_SIGNER = False # 是否对发送到浏览器上 session:cookie值进行加密 SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/day130?charset=utf8" SQLALCHEMY_POOL_SIZE = 5 SQLALCHEMY_POOL_TIMEOUT = 30 SQLALCHEMY_POOL_RECYCLE = -1 # 追踪对象的修改并且发送信号 SQLALCHEMY_TRACK_MODIFICATIONS = False #session SECRET_KEY="sddjljd" class ProductionConfig(BaseConfig): pass class DevelopmentConfig(BaseConfig): pass class TestingConfig(BaseConfig): pass
manage.py
from flask_script_demo import create_app from flask_script import Manager,Command from flask_migrate import Migrate,MigrateCommand #导入,Migrate,MigrateCommand from flask_script_demo import db app =create_app() manager=Manager(app)#把app加入到这里 Migrate(app,db) manager.add_command('db',MigrateCommand)#添加了db命令 if __name__ == '__main__': app.debug = True #app.run() manager.run() #这里就不用app.run启动了直接manager.run()启动
login.py
from flask import blueprints from ..models import Classes from flask import render_template,request,redirect,current_app#导入当前app对象 from flask_script_demo import db ac=blueprints.Blueprint('ac',__name__) @ac.route('/login/',methods=["GET","POST"]) def login(): if request.method=="GET": return render_template('login.html') else: username=request.form.get("user") password=request.form.get("pwd") obj=db.session.query(Classes).filter(Classes.name==username,Classes.password==password).first() db.session.remove() if not obj: return render_template('login.html',msg="用户名或密码错误") #加session current_app.auth_manager.login_session(username) return redirect('/index/')
login.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <form method="post"> <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交">{{msg}} </form> </body> </html>
index.py
from flask import Blueprint from flask import render_template,request,redirect,current_app#导入当前app对象 hm=Blueprint('hm',__name__) @hm.route('/index/',methods=['GET','POST']) def index(): return render_template('index.html')
index.html
<html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!--IE浏览器最高渲染--> <meta name="viewport" content="width=device-width, initial-scale=1"> <!--为了确保适当的绘制和缩放--> <title>Title</title> <link rel="stylesheet" href="../bootstrap-3.3.7-dist/css/bootstrap.min.css"> </head> <body> <h1>欢迎来到主页, {{current_user}}</h1> <script src="../jquery-3.2.1.min.js"></script> </body> </html>
init.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy #导入SQLAlchemy类 from components .auth import Auth db=SQLAlchemy() #实例化对象 from .views.login import ac from .views.index import hm def create_app(): app=Flask(__name__) app.config.from_object("settings.DevelopmentConfig") #导入配置 app.register_blueprint(ac) app.register_blueprint(hm) Auth(app) #把app挂载到组件auth中 db.init_app(app)#把app中的内容放到db中 return app
models.py
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String from sqlalchemy import create_engine from flask_script_demo import db #导入db class Classes(db.Model):#导入了db __tablename__ = 'classesday130' id = Column(Integer, primary_key=True,autoincrement=True) name = Column(String(32),nullable=False,unique=True) password=Column(String(32),nullable=False)