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
View Code

以上就是用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()
    View Code

要想在视图函数中获取配置文件的值,都是通过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. 用户登录

当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。如:

用户不能为空;用户长度必须大于6;

密码不能为空;密码长度必须大于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()
View Code

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
View Code

 

在views 中的account.py

from flask import blueprints
ac=blueprints.Blueprint('ac',__name__)
@ac.route('/login/',methods=["GET","POST"])
def login():
    return "login"
View Code

 

#第一步 只需要改变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
View Code

 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)
auth.py

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
setting.py

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()启动
manage.py

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.py

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>
login

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.py

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>
index

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
init

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)
models.py

 

posted on 2018-05-02 13:04  程序员一学徒  阅读(866)  评论(0编辑  收藏  举报