Flask是一個基於Python開發而且依賴jinja2模板和Werkzeug WSGI服務的一個微型框架,對於Werkzeug本質是Socket服務端,其用於接收http請求並對請求進行預處理,而後觸發Flask框架,開發人員基於Flask框架提供的功能對請求進行相應的處理,並返回給用戶,若是要返回給用戶複雜的內容時,須要藉助jinja2模板來實現對模板的處理,即:將模板和數據進行渲染,將渲染後的字符串返回給用戶瀏覽器。html
「微」(micro) 並不表示你須要把整個 Web 應用塞進單個 Python 文件(雖然確實能夠 ),也不意味着 Flask 在功能上有所欠缺。微框架中的「微」意味着 Flask 旨在保持核心簡單而易於擴展。Flask 不會替你作出太多決策——好比使用何種數據庫。而那些 Flask 所選擇的——好比使用何種模板引擎——則很容易替換。除此以外的一切都由可由你掌握。如此,Flask 能夠與您珠聯璧合。html5
默認狀況下,Flask 不包含數據庫抽象層、表單驗證,或是其它任何已有多種庫能夠勝任的功能。然而,Flask 支持用擴展來給應用添加這些功能,如同是 Flask 自己實現的同樣。衆多的擴展提供了數據庫集成、表單驗證、上傳處理、各類各樣的開放認證技術等功能。Flask 也許是「微小」的,但它已準備好在需求繁雜的生產環境中投入使用python
pip3 install flaskmysql
Werkzeug是一個WSGI工具包,他能夠做爲一個Web框架的底層庫。這裏稍微說一下, werkzeug 不是一個web服務器,也不是一個web框架,而是一個工具包,官方的介紹說是一個 WSGI 工具包,它能夠做爲一個 Web 框架的底層庫,由於它封裝好了不少 Web 框架的東西,例如 Request,Response 等等 web
代碼示例:正則表達式
from werkzeug.wrappers import Request, Response
from flask import Flask
# 實例化產生一個Flask對象
app = Flask(__name__)
# 將 '/'和視圖函數hello_workd的對應關係添加到路由中
main.pyredis
from flask import Flask,render_template,request,redirect,session,url_for
app = Flask(__name__)
app.debug = True
app.secret_key = 'sdfsdfsdfsdf'
USERS = {
1:{'name':'張三','age':18,'gender':'男','text':"道路千萬條"},
2:{'name':'李四','age':28,'gender':'男','text':"安全第一條"},
3:{'name':'王五','age':18,'gender':'女','text':"行車不規範"},
}
detail.htmlsql
index.html數據庫
login.htmldjango
flask中的配置文件是一個flask.config.Config對象(繼承字典),默認配置爲:
{
'DEBUG': get_debug_flag(default=False), 是否開啓Debug模式
'TESTING': False, 是否開啓測試模式
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
app.config['DEBUG'] = True
PS: 因爲Config對象本質上是字典,因此還可使用app.config.update(...)
#經過py文件配置
app.config.from_pyfile("python文件名稱")
如:
settings.py
DEBUG = True
app.config.from_pyfile("settings.py")
#經過環境變量配置
app.config.from_envvar("環境變量名稱")
#app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
環境變量的值爲python文件名稱名稱,內部調用from_pyfile方法
app.config.from_json("json文件名稱")
JSON文件名稱,必須是json格式,由於內部會執行json.loads
app.config.from_mapping({'DEBUG': True})
字典格式
app.config.from_object("python類或類的路徑")
app.config.from_object('pro_flask.settings.TestingConfig')
settings.py
class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
PS: 從sys.path中已經存在路徑開始寫
PS: settings.py文件默認路徑要放在程序root_path目錄,若是instance_relative_config爲True,則就是instance_path目錄(Flask對象init方法的參數)
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
"""
1. decorator = app.route('/',methods=['GET','POST'],endpoint='n1')
def route(self, rule, **options):
# app對象
# rule= /
# options = {methods=['GET','POST'],endpoint='n1'}
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
2. @decorator
decorator(index)
"""
#同理
def login():
return '登陸'
app.add_url_rule('/login', 'n2', login, methods=['GET',"POST"])
#與django路由相似
#django與flask路由:flask路由基於裝飾器,本質是基於:add_url_rule
#add_url_rule 源碼中,endpoint若是爲空,endpoint = _endpoint_from_view_func(view_func),最終取view_func.__name__(函數名)
def auth(func):
def inner(*args, **kwargs):
print('before')
result = func(*args, **kwargs)
print('after')
return result
return inner
class IndexView(views.View):
methods = ['GET']
decorators = [auth, ]
def dispatch_request(self):
print('Index')
return 'Index!'
app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
#或者,一般用此方式
class IndexView(views.MethodView):
methods = ['GET']
decorators = [auth, ]
def get(self):
return 'Index.GET'
def post(self):
return 'Index.POST'
app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
#1 寫類,繼承BaseConverter
#2 註冊:app.url_map.converters['regex'] = RegexConverter
# 3 使用:@app.route('/index/<regex("\d+"):nid>') 正則表達式會看成第二個參數傳遞到類中
from flask import Flask, views, url_for
from werkzeug.routing import BaseConverter
app = Flask(import_name=__name__)
class RegexConverter(BaseConverter):
"""
自定義URL匹配正則表達式
"""
def __init__(self, map, regex):
super(RegexConverter, self).__init__(map)
self.regex = regex
def to_python(self, value):
"""
路由匹配時,匹配成功後傳遞給視圖函數中參數的值
"""
return int(value)
def to_url(self, value):
"""
使用url_for反向生成URL時,傳遞的參數通過該方法處理,返回的值用於生成URL中的參數
"""
val = super(RegexConverter, self).to_url(value)
return val
# 添加到flask中
app.url_map.converters['regex'] = RegexConverter
比django中多能夠加括號,執行函數,傳參數
from flask import Flask,render_template,Markup,jsonify,make_response
app = Flask(__name__)
def func1(arg):
return Markup("<input type='text' value='%s' />" %(arg,))
index.html
注意:
1.Markup等價django的mark_safe ,
2.extends,include如出一轍
from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response
app = Flask(__name__)
除請求對象以外,還有一個 session 對象。它容許你在不一樣請求間存儲特定用戶的信息。它是在 Cookies 的基礎上實現的,而且對 Cookies 進行密鑰簽名要使用會話,須要設置一個密鑰。 (app.session_interface對象)
app.secret_key='xxxxx'
設置:session['username'] = 'xxx'
取值:username=session['username']
刪除:session.pop('username', None)
del session['username']
-設置:flash('aaa')
-取值:get_flashed_message()
-假設在a頁面操做出錯,跳轉到b頁面,在b頁面顯示a頁面的錯誤信息
示例:
from flask import Flask,flash,get_flashed_messages,request,redirect
app = Flask(__name__)
app.secret_key = 'asdfasdf'
類比django中間件中的process_request,在請求收到以前綁定一個函數作一些事情
#基於它作用戶登陸認證
類比django中間件中的process_response,每個請求以後綁定一個函數,若是請求沒有異常
第一次請求時,跟瀏覽器無關
每個請求以後綁定一個函數,即便遇到了異常(每一次請求不管如何都會通過此函數)
路徑不存在時404,服務器內部錯誤500
標籤
過濾器
總結:
1 重點掌握before_request和after_request,
2 注意有多個的狀況,執行順序
3 before_request請求攔截後(也就是有return值),response全部都執行
from flask import Flask
app = Flask(__name__)
對程序進行目錄結構劃分
目錄結構:
-templates
-views
-__init__.py
-user.py
-order.py
-app.py
app.py
from views import app
if __name__ == '__main__':
app.run()
init.py
from flask import Flask,request
app = Flask(__name__)
#不導入這個不行
from . import account
from . import order
from . import user
user.py
from . import app
order.py
from . import app
詳見代碼:pro_flask_簡單應用程序目錄示例.zip
目錄結構:
-flask_pro
-flask_test
-__init__.py
-static
-templates
-views
-order.py
-user.py
-manage.py
_init.py
from flask import Flask
app=Flask(__name__)
from flask_test.views import user
from flask_test.views import order
#註冊藍圖
app.register_blueprint(user.us)
app.register_blueprint(order.ord)
manage.py
from flask_test import app
if __name__ == '__main__':
app.run(port=8008)
user.py
from flask import Blueprint
us=Blueprint('user',__name__)
order.py
from flask import Blueprint
ord=Blueprint('order',__name__)
詳見代碼:pro_flask_大型應用目錄示例.zip
總結:
1 xxx = Blueprint('account', name,url_prefix='/xxx') :藍圖URL前綴,表示url的前綴,在該藍圖下全部url都加前綴
2 xxx = Blueprint('account', name,url_prefix='/xxx',template_folder='tpls'):給當前藍圖單獨使用templates,向上查找,當前找不到,會找總templates
3 藍圖的befort_request,對當前藍圖有效
4 大型項目,能夠模擬出相似於django中app的概念
第一階段:將ctx(request,session)放到Local對象上
第二階段:視圖函數導入:request/session
request.method
-LocalProxy對象.method,執行getattr方法,getattr(self._get_current_object(), name)
-self._get_current_object()返回return self.__local(),self.__local(),在LocakProxy實例化的時候,object.__setattr__(self, '_LocalProxy__local', local),此處local就是:partial(_lookup_req_object, 'request')
-def _lookup_req_object(name):
top = _request_ctx_stack.top #_request_ctx_stack 就是LocalStack()對象,top方法把ctx取出來
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)#獲取ctx中的request或session對象
第三階段:請求處理完畢
- 獲取session並保存到cookie
- 將ctx刪除
程序運行,兩個LocalStack()對象,一個裏面放request和session,另外一個放g和current_app
專門用來存儲用戶信息的g對象,g的全稱的爲global
g對象在一次請求中的全部的代碼的地方,都是可使用的
session對象是能夠跨request的,只要session還未失效,不一樣的request的請求會獲取到同一個session,可是g對象不是,g對象不須要管過時時間,請求一次就g對象就改變了一次,或者從新賦值了一次
做用:將默認保存的簽名cookie中的值 保存到 redis/memcached/file/Mongodb/SQLAlchemy
安裝:pip3 install flask-session
使用1:
from flask import Flask,session
from flask_session import RedisSessionInterface
import redis
app = Flask(__name__)
conn=redis.Redis(host='127.0.0.1',port=6379)
#use_signer是否對key簽名
app.session_interface=RedisSessionInterface(conn,key_prefix='lqz')
使用2:
from redis import Redis
from flask.ext.session import Session
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379')
Session(app)
問題:設置cookie時,如何設定關閉瀏覽器則cookie失效。
response.set_cookie('k','v',exipre=None)#這樣設置便可
#在session中設置
app.session_interface=RedisSessionInterface(conn,key_prefix='lqz',permanent=False)
#通常不用,咱們通常都設置超時時間,多長時間後失效
問題:cookie默認超時時間是多少?如何設置超時時間
#源碼expires = self.get_expiration_time(app, session)
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),#這個配置文件控制
import pymysql
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='s8day127db')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# cursor.execute("select id,name from users where name=%s and pwd=%s",['lqz','123',])
cursor.execute("select id,name from users where name=%(user)s and pwd=%(pwd)s",{'user':'lqz','pwd':'123'})
obj = cursor.fetchone()
conn.commit()
cursor.close()
conn.close()
print(obj)
setting.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
utils/sql.py
import pymysql
from settings import Config
class SQLHelper(object):
使用:
obj = SQLHelper.fetch_one("select id,name from users where name=%(user)s and pwd=%(pwd)s", form.data)
安裝:pip3 install wtforms
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app = Flask(__name__, template_folder='templates')
app.debug = True
class LoginForm(Form):
# 字段(內部包含正則表達式)
name = simple.StringField(
label='用戶名',
validators=[
validators.DataRequired(message='用戶名不能爲空.'),
validators.Length(min=6, max=18, message='用戶名長度必須大於%(min)d且小於%(max)d')
],
widget=widgets.TextInput(), # 頁面上顯示的插件
render_kw={'class': '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'}
)
login.html
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
class RegisterForm(Form):
name = simple.StringField(
label='用戶名',
validators=[
validators.DataRequired()
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'},
default='alex'
)
pwd = simple.PasswordField(
label='密碼',
validators=[
validators.DataRequired(message='密碼不能爲空.')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
pwd_confirm = simple.PasswordField(
label='重複密碼',
validators=[
validators.DataRequired(message='重複密碼不能爲空.'),
validators.EqualTo('pwd', message="兩次密碼輸入不一致")
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
email = html5.EmailField(
label='郵箱',
validators=[
validators.DataRequired(message='郵箱不能爲空.'),
validators.Email(message='郵箱格式錯誤')
],
widget=widgets.TextInput(input_type='email'),
render_kw={'class': 'form-control'}
)
gender = core.RadioField(
label='性別',
choices=(
(1, '男'),
(2, '女'),
),
coerce=int # 「1」 「2」
)
city = core.SelectField(
label='城市',
choices=(
('bj', '北京'),
('sh', '上海'),
)
)
hobby = core.SelectMultipleField(
label='愛好',
choices=(
(1, '籃球'),
(2, '足球'),
),
coerce=int
)
favor = core.SelectMultipleField(
label=