flask基礎二

請求鉤子

flask沒有django中的中間件,可是卻有相似的機制(在請求以前作點事,請求完成以後再作點事)。flask給咱們預留的鉤子能完成這些事。對於鉤子的簡單理解:flask預留了一些佔位的空白空間,當咱們往這段空間放代碼的時候,那麼流程在走的時候就會通過咱們的代碼。鉤子的形象意義就是一段代碼執行的時候,會順帶着執行鉤子上的一系列代碼,而不是單純的那一段代碼。html

before_first_request:在處理第一個請求前運行。
before_request:在每次請求前運行。
after_request(response):若是沒有未處理的異常拋出,在每次請求後運行。
teardown_request(response):在每次請求後運行,即便有未處理的異常拋出,在這個鉤子裏並不能捕獲異常進行處理,並且一旦發生異常頁面總會是定製的錯誤頁,而不是這個函數的返回值。須要運行在debug=False的狀況才生效,並且teardown_request是運行在after_request以後的。

before系列的鉤子若是有return,那麼視圖函數就不會被執行。after系列鉤子必須有return,這是最終返回給瀏覽器的內容前端

from flask import Flask, request

app = Flask(__name__)

@app.route('/index')
def index():
    print('執行視圖函數')
    return 'index'

@app.route('/login')
def login():
    print('執行視圖函數')
    return 'login'

@app.before_first_request
def hanlde_before_first_request():
    print('before_first_request')

@app.before_request
def handel_before_request():
    print('before_request')

@app.after_request
def handle_after_request(response):
    print('handle_after_request')
    if request.path == '/login':
        print('login')
    if request.path == '/index':
        print('index')
    return response

@app.teardown_request
def handle_teardown_request(response):
    print('teardown_request')
    return 'hahah'

if __name__ == '__main__':
    app.run()

有了鉤子,一次完成的http請求的後臺處理就不單純是視圖函數了,一次完整的請求流程還包括鉤子函數html5

Flask-Script擴展命令行

安裝:pip3 install Flask-Scriptpython

from flask import Flask, request
from flask_script import Manager
app = Flask(__name__)

# 建立一個管理者用來管理app
manager = Manager(app)

@app.route('/index')
def index():
    print('執行視圖函數')
    return 'index'

if __name__ == '__main__':
    manager.run()

執行腳本python flask_test.py的結果爲mysql

usage: flask_test.py [-?] {shell,runserver} ...

positional arguments:
  {shell,runserver}
    shell            Runs a Python shell inside Flask application context.
    runserver        Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help         show this help message and exit

因此用了flask_script,原先的腳本就不是單純可直接執行的腳本了,而是須要接受命令並按照命令去執行flask程序。Manager默認只提供了兩條命令:shell 和 runserver,咱們能夠對其進行擴展。nginx

數據庫遷移

在開發過程當中,須要修改數據庫模型,並且還要在修改以後更新數據庫。最直接的方式就是刪除舊錶,但這樣會丟失數據。更好的解決辦法是使用數據庫遷移框架,它能夠追蹤數據庫模式的變化,而後把變更應用到數據庫中。
在Flask中可使用Flask-Migrate擴展,來實現數據遷移。而且集成到Flask-Script中,全部操做經過命令就能完成。爲了導出數據庫遷移命令,Flask-Migrate提供了一個MigrateCommand類,能夠附加到flask-script的manager對象上。
安裝Flask-Migrate:pip3 install flask-migrateweb

from flask_script import Manager
from flask import Flask
from flask_migrate import Migrate,MigrateCommand
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

manager = Manager(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

db = SQLAlchemy(app)
# 綁定數據庫和app
Migrate(app, db)
# 增長一條管理命令
manager.add_command('db', MigrateCommand)

@app.route('/index')
def index():
    return 'index'

#定義模型Role
class Role(db.Model):
    # 定義表名
    __tablename__ = 'roles'
    # 定義列對象
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    def __repr__(self):
        return 'Role:'.format(self.name)

#定義用戶
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    def __repr__(self):
        return 'User:'.format(self.username)
if __name__ == '__main__':
    manager.run()
  1. python database.py db init -- 這個命令會建立migrations文件夾,全部遷移文件都放在裏面。
  2. python database.py db migrate -m 'initial migration' -- 建立修改版本,-m後面的是本次更改的提示信息
  3. python database.py db upgrade -- 向前更新數據庫
  4. python database.py db history -- 查看歷史版本的具體版本號
  5. python database.py db downgrade 版本號 -- 版本回退

jinja2模板

如今愈來愈多都是先後端分離,日常使用模板方式進行開發的場景愈來愈少。基本瞭解便可。redis

變量

flask_test.pysql

from flask import Flask,render_template
app = Flask(__name__)

@app.route('/')
def index():
    mydict = {'key':'silence is gold'}
    mylist = ['Speech', 'is','silver']
    myintvar = 0

    return render_template('vars.html',
                           mydict=mydict,
                           mylist=mylist,
                           myintvar=myintvar
                           )
if __name__ == '__main__':
    app.run(debug=True)

vars.htmlshell

<p>{{mydict['key']}}</p>

<p>{{mydict.key}}</p>

<p>{{mylist[1]}}</p>

<p>{{mylist[myvariable]}}</p>

過濾器

safe:禁用轉義;
  <p>{{ '<em>hello</em>' | safe }}</p>

capitalize:把變量值的首字母轉成大寫,其他字母轉小寫;
  <p>{{ 'hello' | capitalize }}</p>

lower:把值轉成小寫;
  <p>{{ 'HELLO' | lower }}</p>

upper:把值轉成大寫;
  <p>{{ 'hello' | upper }}</p>

title:把值中的每一個單詞的首字母都轉成大寫;
  <p>{{ 'hello' | title }}</p>

trim:把值的首尾空格去掉;
  <p>{{ ' hello world ' | trim }}</p>

reverse:字符串反轉;
  <p>{{ 'olleh' | reverse }}</p>
format:格式化輸出;
  <p>{{ '%s is %d' | format('name',17) }}</p>

striptags:渲染以前把值中全部的HTML標籤都刪掉;
  <p>{{ '<em>hello</em>' | striptags }}</p>

對於xss攻擊在後端也能夠作:v = Markup("<input type='text' />")

支持鏈式使用過濾器

<p>{{ 「 hello world  「 | trim | upper }}</p>

列表過濾器

first:取第一個元素
  <p>{{ [1,2,3,4,5,6] | first }}</p>

last:取最後一個元素
  <p>{{ [1,2,3,4,5,6] | last }}</p>

length:獲取列表長度
  <p>{{ [1,2,3,4,5,6] | length }}</p>

sum:列表求和
  <p>{{ [1,2,3,4,5,6] | sum }}</p>

sort:列表排序
  <p>{{ [6,2,3,1,5,4] | sort }}</p>

自定義

@app.template_filter()
def add(x, y, z):
    return x + y + z

@app.template_global()
def sub(x, y):
    return x - y

調用方式是{{ 1|add(2,3)}}{{ sub(3,2) }}。和django不一樣,flask的filter能夠接受不止兩個參數。template_filter和template_global後面都須要加括號

macro.html

{% macro input(name,value='',type='text',size=20) %}
    <input type="{{ type }}"
           name="{{ name }}"
           value="{{ value }}"
           size="{{ size }}"/>
{% endmacro %}

在其它模板文件中先導入,再調用

{% import 'macro.html' as func %}
{% func.input('name', 10) %}

request和url_for能夠在前端模版直接使用,至關於全局變量

表單

使用Flask-WTF表單擴展,能夠幫助進行CSRF驗證,幫助咱們快速定義表單模板,並且能夠幫助咱們在視圖中驗證表的數據。說白了,其角色就至關因而django的forms表單驗證。

flask_test.py

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
    )
    city = core.SelectField(
        label='城市',
        choices=(
            ('bj', '北京'),
            ('sh', '上海'),
        )
    )

    hobby = core.SelectMultipleField(
        label='愛好',
        choices=(
            (1, '籃球'),
            (2, '足球'),
        ),
        coerce=int
    )

    favor = core.SelectMultipleField(
        label='喜愛',
        choices=(
            (1, '籃球'),
            (2, '足球'),
        ),
        widget=widgets.ListWidget(prefix_label=False),
        option_widget=widgets.CheckboxInput(),
        coerce=int,
        default=[1, 2]
    )

    def __init__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.favor.choices = ((1, '籃球'), (2, '足球'), (3, '羽毛球'))

    def validate_pwd_confirm(self, field):
        """
        自定義pwd_confirm字段規則,例:與pwd字段是否一致
        :param field:
        :return:
        """
        # 最開始初始化時,self.data中已經有全部的值

        if field.data != self.data['pwd']:
            # raise validators.ValidationError("密碼不一致") # 繼續後續驗證
            raise validators.StopValidation("密碼不一致")  # 再也不繼續後續驗證


@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        form = RegisterForm(data={'gender': 1})
        return render_template('register.html', form=form)
    else:
        form = RegisterForm(formdata=request.form)
        if form.validate():
            print('用戶提交數據經過格式驗證,提交的值爲:', form.data)
        else:
            print(form.errors)
        return render_template('register.html', form=form)

if __name__ == '__main__':
    app.run()

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用戶註冊</h1>
<form method="post" novalidate style="padding:0  50px">
    {% for item in form %}
    <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>

藍圖

在沒有藍圖以前,咱們考慮一個問題:django的目錄都是有必定規範的,flask的代碼都在一個py文件裏面,這確定不能用於生產。因此,咱們的想法是把一個py文件的代碼分開,有些代碼寫到別的py文件,這樣看起來也更加清晰。最直白的就是,咱們把處理不一樣業務邏輯的視圖放到不一樣的py文件中,好比處理訂單相關的放到一個文件,處理用戶相關的放到另一個文件等等。那麼咱們能夠這樣作
flask_test.py

from flask import Flask

# 引入order
from order import order

app = Flask(__name__)

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    print(app.url_map)
    app.run()

order.py

from flask_test import app

@app.route('/order')
def order():
    return 'order'

print('in order')

運行flask_test.py出錯:

Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/flask_test/flask_test.py", line 4, in <module>
    from order import order
  File "C:\Users\Administrator\Desktop\flask_test\order.py", line 4, in <module>
    from flask_test import app
  File "C:\Users\Administrator\Desktop\flask_test\flask_test.py", line 4, in <module>
    from order import order
ImportError: cannot import name 'order'


解決辦法之一是延遲導入

解決辦法之二是在order.py裏面只定義函數,在flask_test.py對函數綁定路由
解決辦法之三就是藍圖,也是推薦的方式。

在flask應用程序中國,app就相似於django的工程,藍圖就相似於django的app。
藍圖是保存了一組未來能夠在應用對象上執行的操做。註冊路由就是一種操做,當在程序實例上調用route裝飾器註冊路由時,這個操做將修改對象的url_map路由映射列表。當咱們在藍圖對象上調用route裝飾器註冊路由時,它只是在內部的一個延遲操做記錄列表defered_functions中添加了一個項。當執行應用對象的 register_blueprint() 方法時,應用對象從藍圖對象的 defered_functions 列表中取出每一項,即調用應用對象的 add_url_rule() 方法,這將會修改程序實例的路由映射列表。
flask_test.py

from flask import Flask
from order import order
app = Flask(__name__)
app.register_blueprint(order, url_prefix='/order')
@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    print(app.url_map)
    app.run()

order.py

from flask import Blueprint

# 'order' 是藍圖的名字,體如今url_map上,靜態目錄和模版目錄都須要手動指定
# 和app不一樣,藍圖的模版和靜態目錄都沒有默認值,,找模版文件先從app目錄找,隨後到藍圖目錄找
order = Blueprint('order', __name__, template_folder='templates', static_folder='static')

@order.route('/get_order')
def get_order():
    return 'get order'

單元測試

介紹

在Web開發過程當中,單元測試實際上就是一些「斷言」(assert)代碼。
斷言就是判斷一個函數或對象的一個方法所產生的結果是否符合你指望的那個結果。 python中assert斷言是聲明布爾值爲真的斷定,若是表達式爲假會發生異常。單元測試中,通常使用assert來斷言結果。
經常使用的斷言方法

assertEqual     若是兩個值相等,則pass
assertNotEqual  若是兩個值不相等,則pass
assertTrue      判斷bool值爲True,則pass
assertFalse     判斷bool值爲False,則pass
assertIsNone    不存在,則pass
assertIsNotNone 存在,則pass

基本使用

import unittest
class TestClass(unittest.TestCase):

    #該方法會首先執行,至關於作測試前的準備工做
    def setUp(self):
        pass

    #該方法會在測試代碼執行完後執行,至關於作測試後的掃尾工做
    def tearDown(self):
        pass
    #測試代碼,必須以test開頭
    def test_xxx(self):
        pass
# coding:utf-8

import unittest
from login import app
import json


class TestLogin(unittest.TestCase):
    """定義測試案例"""
    def setUp(self):
        """在執行具體的測試方法前,先被調用"""
        # 可使用python的http標準客戶端進行測試
        # urllib  urllib2  requests
        # 開啓測試模式,這樣flask程序報的錯就會徹底在測試程序顯示出來,而不是顯示測試程序自己的錯誤
        app.config['TESTING'] = True
        # 使用flask提供的測試客戶端進行測試
        self.client = app.test_client()

    def test_empty_name_password(self):
        """測試模擬場景,用戶名或密碼不完整"""
        # 使用客戶端向後端發送post請求, data指明發送的數據,會返回一個響應對象
        response = self.client.post("/login", data={})

        # respoonse.data是響應體數據
        resp_json = response.data

        # 按照json解析
        resp_dict = json.loads(resp_json)

        # 使用斷言進行驗證
        self.assertIn("code", resp_dict)

        code = resp_dict.get("code")
        self.assertEqual(code, 1)

        # 測試只傳name
        response = self.client.post("/login", data={"name": "admin"})

        # respoonse.data是響應體數據
        resp_json = response.data

        # 按照json解析
        resp_dict = json.loads(resp_json)

        # 使用斷言進行驗證
        self.assertIn("code", resp_dict)

        code = resp_dict.get("code")
        self.assertEqual(code, 1)

    def test_wrong_name_password(self):
        """測試用戶名或密碼錯誤"""
        # 使用客戶端向後端發送post請求, data指明發送的數據,會返回一個響應對象
        response = self.client.post("/login", data={"name": "admin", "password": "itcast"})

        # respoonse.data是響應體數據
        resp_json = response.data

        # 按照json解析
        resp_dict = json.loads(resp_json)

        # 使用斷言進行驗證
        self.assertIn("code", resp_dict)

        code = resp_dict.get("code")
        self.assertEqual(code, 2)


if __name__ == '__main__':
    unittest.main()

login.py

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/login", methods=["POST"])
def login():
    """登陸"""
    name = request.form.get("name")
    password = request.form.get("password")

    # ""  0  [] () {} None 在邏輯判斷時都是假
    if not all([name, password]):
        # 表示name或password中有一個爲空或者都爲空
        return jsonify(code=1, message=u"參數不完整")

    if name == "admin" and password =="python":
        return jsonify(code=0, message=u"OK")
    else:
        return jsonify(code=2, message=u"用戶名或密碼錯誤")

if __name__ == '__main__':
    app.run()

部署

當咱們執行下面的hello.py時,使用的flask自帶的服務器,完成了web服務的啓動。在生產環境中,flask自帶的服務器,沒法知足性能要求,咱們這裏採用Gunicorn作wsgi容器,來部署flask程序。Gunicorn(綠色獨角獸)是一個Python WSGI的HTTP服務器。從Ruby的獨角獸(Unicorn )項目移植。該Gunicorn服務器與各類Web框架兼容,實現很是簡單,輕量級的資源消耗。Gunicorn直接用命令啓動,不須要編寫配置文件,相對uWSGI要容易不少。
web開發中,部署方式大體相似。簡單來講,前端代理使用Nginx主要是爲了實現分流、轉發、負載均衡,以及分擔服務器的壓力。Nginx部署簡單,內存消耗少,成本低。Nginx既能夠作正向代理,也能夠作反向代理。

正向代理:請求通過代理服務器從局域網發出,而後到達互聯網上的服務器。
特色:服務端並不知道真正的客戶端是誰。
反向代理:請求從互聯網發出,先進入代理服務器,再轉發給局域網內的服務器。
特色:客戶端並不知道真正的服務端是誰。
區別:正向代理的對象是客戶端。反向代理的對象是服務端。

通常部署的時候不是用一臺服務器進行部署,業務服務器好比說有兩臺,mysql服務器有一臺,redis服務器有一臺,其中業務服務器部署看gunicorn和flask程序,nginx就會把請求均衡地分發到這兩臺部署環境同樣的服務器,而且nginx設計之初就是爲了提供靜態文件的支持,因此讓nginx幫忙處理靜態資源。

安裝gunicorn: pip3 install gunicorn
啓動:gunicorn -w 4 -b 127.0.0.1:5001 -D --access_log='/tmp/log' 運行文件名稱:Flask程序實例名 此時共有5個進程,一個父進程,一個子進程

upstream flask {
        server 10.20.1.11:5000;
        server 10.20.1.12:6000;
    }

server {
    # 監聽80端口
    listen 80;    
    # 本機
    server_name localhost; 
    # 默認請求的url
    location / {
        #請求轉發到gunicorn服務器
        proxy_pass http://flask; 
        #設置請求頭,並將頭信息傳遞給服務器端 
        proxy_set_header Host $host; 
    }
}
相關文章
相關標籤/搜索