Flask組件

 

flask-session

出現背景:你有沒有發現咱們在flask中是吧session保存在cookie中,並無本身保存一份,這樣太不安全了,這時候,就出現了request-sessioncss

做用

flask-session 的做用:將flask中默認保存在cookie中的值保存在redis/memcached/file/Mongodb/SQLAlchemy中html

安裝

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

 

第一種方法html5

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()

第二種方法:python

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給他賦值時,它不會生效了mysql

 數據庫鏈接池

在flask中並無djano中的orm,要鏈接數據庫有兩種方式,其中一個是pymysql模塊.jquery

blueprint

若是代碼不少要進行歸類,blueprint就是對flask的目錄結構進行分配(應用於小中型程序)linux

第一步 manage.pyweb

from blueprint_demo import create_app
app=create_app()
if __name__ == '__main__':
    app.run()

第二步:redis

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

咱們來寫一個簡單的藍圖

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

Flask-script 目的是幫咱們把flask啓動命令改變成像Django啓動命令同樣, django怎麼運行的呢,在terminal中輸入 python mange.py runserver  程序就運行起來了.

Flask-Scropt插件爲在Flask裏編寫額外的腳本提供了支持。這包括運行一個開發服務器,一個定製的Python命令行,用於執行初始化數據庫、定時任務和其餘屬於web應用以外的命令行任務的腳本

#第一步 只須要改變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修飾符

在傳參數的時候用

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 "小紅"

 這裏能夠用在登陸時須要用戶名和密碼的腳本中

 

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中,這步的主要做用是爲了導入app.config屬性,咱們就能夠把SQLalchemy中的鏈接數據庫的內容放到,app的配置文件中,這樣使代碼大大的間接
    return 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

咱們這樣來查詢數據和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"

總結

#第一步 
在init.py 中建立db對象
#第二步
在__init__.py 中create_app函數中將app傳入到db中
#第三步
寫配置文件,將鏈接字符串定義在配置文件中
#第四步
建立model類
#第5步 建立數據庫表,編寫離線腳本(利用上下文管理)
#第6步 在視圖函數中操做數據庫

 

離線腳本

離線腳本就是咱們本身定義的腳本操做數據庫時須要使用flask中定義好的功能,這時候寫的腳本就叫作離線腳本.

例子:

當咱們要刪除數據庫中的表時,

有兩種方法:第一種用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對象中. 這時候咱們就能夠刪除數據庫中的表了.

 flask-migrate

flask-migrate 做用作數據庫遷移

安裝

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
相關文章
相關標籤/搜索