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
![](http://static.javashuo.com/static/loading.gif)
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-script
咱們來寫一個簡單的藍圖
manage.py
![](http://static.javashuo.com/static/loading.gif)
from flask_script_demo import create_app app =create_app() if __name__ == '__main__': app.run()
init.py
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
from flask import blueprints ac=blueprints.Blueprint('ac',__name__) @ac.route('/login/',methods=["GET","POST"]) def login(): return "login"
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
![](http://static.javashuo.com/static/loading.gif)
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中,這步的主要做用是爲了導入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
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
<!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
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
<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
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
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)