Learn Python 3 :Flask Web開(kāi)發(fā)小記

最近看了Flask Web開(kāi)發(fā):基于Python的Web應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)锐墙,書中詳細(xì)介紹了Web程序的開(kāi)發(fā)礁哄、測(cè)試、部署過(guò)程溪北,值得一讀桐绒!我在書中例子的基礎(chǔ)上做了些更改,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的個(gè)人博客:NiceBlog之拨,僅作為個(gè)人學(xué)習(xí)茉继,還有許多不足的地方待完善,這里做一些簡(jiǎn)單的記錄蚀乔,方便以后查閱烁竭,代碼放在了Github上:https://github.com/SheHuan/NiceBlog

一吉挣、功能

1派撕、對(duì)于普通用戶,主要有如下功能:

  • 注冊(cè)睬魂、登錄终吼、重置密碼(郵箱驗(yàn)證)
  • 文章列表、詳情
  • 評(píng)論
  • 喜歡

2氯哮、對(duì)于管理員际跪,除了有普通用戶的功能,主要有如下功能:

  • 寫文章(Markdown編輯)
  • 用戶權(quán)限管理(管理喜歡喉钢、評(píng)論的權(quán)限)
  • 評(píng)論管理(刪除垫卤、屏蔽)

3、為移動(dòng)端提供相關(guān)api接口

二出牧、項(xiàng)目結(jié)構(gòu)

遵循了書中多文件Flask程序的基本結(jié)構(gòu),下邊是NiceBlog的項(xiàng)目結(jié)構(gòu):
|-NiceBlog
???|-app/ 主目錄
??????|-api/ 為移動(dòng)端提供接口的藍(lán)本
??????|-auth/ 權(quán)限認(rèn)證的藍(lán)本
??????|-main/ 主體功能的藍(lán)本
??????|-manage/ 管理相關(guān)功能的藍(lán)本
??????|-static/ 靜態(tài)資源目錄(icon歇盼、js舔痕、css)
??????|-templates/ html模板目錄
??????|-__init__.py 初始化項(xiàng)目的工廠函數(shù)
??????|-decorators.py 自定義的裝飾器
??????|-email.py 發(fā)送郵件功能
??????|-excepitions.py 自定義異常處理
??????|-models.py 數(shù)據(jù)模型
???|-migrations/ 數(shù)據(jù)庫(kù)遷移腳本目錄
???|-nb_env/ 虛擬環(huán)境
???|-tests/ 單元測(cè)試目錄
???|-config.py 配置文件
???|-manage.py 啟動(dòng)程序以及其他的程序任務(wù)
???|-requirements.txt 項(xiàng)目的依賴包列表

三、實(shí)現(xiàn)

1豹缀、工廠函數(shù)

一個(gè)簡(jiǎn)單的Flask Web程序可以寫在單文件中伯复,test.py

app = Flask(__name__)

# 定義的路由
@app.route('/')
def index():
    return '<h1>Hello World!</h1>'

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

但是執(zhí)行程序時(shí),由于在全局作用域創(chuàng)建導(dǎo)致無(wú)法動(dòng)態(tài)修改配置邢笙,也導(dǎo)致了單元測(cè)試時(shí)無(wú)法在不同配置環(huán)境運(yùn)行程序啸如。所以可以把程序的創(chuàng)建轉(zhuǎn)移到可顯示調(diào)用的工廠函數(shù)中,也就是前邊項(xiàng)目結(jié)構(gòu)中的__init__.py氮惯,在工廠函數(shù)中導(dǎo)入需要的Flask擴(kuò)展:

def create_app(config_name):
    app = Flask(__name__)
    # 導(dǎo)致指定的配置對(duì)象
    app.config.from_object(config[config_name])
    # 調(diào)用config.py的init_app()
    config[config_name].init_app(app)

    # 初始化擴(kuò)展
    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)
    login_manager.init_app(app)
    pagedown.init_app(app)
    return app

2叮雳、藍(lán)本

新的問(wèn)題來(lái)了想暗,使用工廠函數(shù)后,程序在運(yùn)行時(shí)創(chuàng)建帘不,而不是在全局作用域说莫,必須等到執(zhí)行create_app()后才能使用@app.route()裝飾器,這時(shí)就要使用藍(lán)本了寞焙,在藍(lán)本中也可以定義路由储狭,但是定義的路由處于休眠狀態(tài)直到藍(lán)本注冊(cè)到程序后在成為程序一部分,例如main藍(lán)本的目錄結(jié)構(gòu)如下:
|-NiceBlog
???|-app/ 主目錄
??????|-main/ 主體功能的藍(lán)本
?????????|-__init__.py 創(chuàng)建藍(lán)本
?????????|-errors.py 藍(lán)本的錯(cuò)誤處理
?????????|-forms.py 藍(lán)本的表單
?????????|-views.py 藍(lán)本的路由

首先看一下__init__.py

# 兩個(gè)參數(shù)分別指定藍(lán)本的名字捣郊、藍(lán)本所在的包或模塊(使用 __name__即可)
main = Blueprint('main', __name__)
# 導(dǎo)入路由模塊辽狈、錯(cuò)誤處理模塊,將其和藍(lán)本關(guān)聯(lián)起來(lái)
# 在藍(lán)本的末尾導(dǎo)入在兩個(gè)模塊里還要導(dǎo)入藍(lán)本呛牲,防止循環(huán)導(dǎo)入依賴
from app.main import views, errors
2.1刮萌、表單

forms.py是當(dāng)前藍(lán)本中的表單,項(xiàng)目中使用了FlaskForm侈净,可以方便的完成表單校驗(yàn)尊勿,例如創(chuàng)建、編輯文章的表單:

class BlogForm(FlaskForm):
    title = StringField('請(qǐng)輸入文章標(biāo)題', validators=[DataRequired(), Length(1, 128)])
    labels = StringField('文章標(biāo)簽(標(biāo)簽之間用空格隔開(kāi))', validators=[DataRequired()])
    summary = TextAreaField('文章概要', validators=[DataRequired()])
    content = TextAreaField('文章內(nèi)容', validators=[DataRequired()])
    preview = TextAreaField('文章預(yù)覽', validators=[DataRequired()])
    publish = SubmitField('發(fā)布')
    save = SubmitField('保存')
2.2畜侦、路由

views.py就是在藍(lán)本中定義的路由元扔,例如主頁(yè)的路由:

@main.route('/create-blog', methods=['GET', 'POST'])
@admin_required
def create_blog():
    """
    寫新文章
    """
    form = BlogForm()
    if form.validate_on_submit():
        blog = None
        if form.publish.data:
            # 發(fā)布
        elif form.save.data:
            # 保存草稿
        return redirect(url_for('main.index'))
    return render_template('markdown_editor.html', form=form, type='create')

注意裝飾器為當(dāng)前藍(lán)本的名字main,而不是之前的app旋膳。create_blog()稱為視圖函數(shù)澎语,一個(gè)路由保存了URL到視圖函數(shù)的映射關(guān)系。redirect(url_for('main.index'))代表重定向到主頁(yè)验懊,url_for()的參數(shù)為要跳轉(zhuǎn)到的URL對(duì)應(yīng)的視圖函數(shù)名擅羞,但需要加上視圖函數(shù)所在的藍(lán)本名,即main.index义图。render_template()是Flask提供的函數(shù)减俏,把Jinja2模板引擎集成到了程序中,第一個(gè)參數(shù)是模板名稱對(duì)應(yīng)一個(gè)html文件碱工,即執(zhí)行該視圖函數(shù)后最終要渲染的頁(yè)面娃承,后邊的參數(shù)為傳遞給模板的參數(shù)。

2.3怕篷、錯(cuò)誤處理

errors.py是藍(lán)本中的錯(cuò)誤處理程序历筝,例如:

@main.app_errorhandler(404)
def page_not_found(e):
    if request.url.find('api') != -1:
        return jsonify({'error': '請(qǐng)求的資源不存在', 'code': '404', 'data': ''})
    return render_template('error/404.html'), 404

如果使用@main.errorhandler裝飾器只有當(dāng)前藍(lán)本的錯(cuò)誤才能觸發(fā),為了使其它錯(cuò)誤也能觸發(fā)所以使用了@main.app_errorhandler裝飾器

2.4廊谓、注冊(cè)藍(lán)本

其它藍(lán)本的定義也類似梳猪,最后需要在工廠函數(shù)中重注冊(cè)藍(lán)本,例如:

def create_app(config_name):
    # ......
    # 注冊(cè)main藍(lán)本
    from app.main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    # 注冊(cè)auth藍(lán)本
    from app.auth import auth as auth_blueprint
    # 使用url_prefix注冊(cè)后蒸痹,藍(lán)本中定義的所有路由都會(huì)加上指定前綴春弥,/login --> /auth/login
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    return app

3诈茧、前端

3.1里伯、Jinja2

Flask使用Jinja2作為模板引擎任岸,模板是一個(gè)包含響應(yīng)文本的HTML文件纤虽,其中包含只有在請(qǐng)求的上下文才知道的動(dòng)態(tài)占位變量。默認(rèn)情況下俺祠,模板保存在templates目錄公给。

Jinja2模板中{{ 變量名 }}代表一個(gè)變量(注意變量名兩邊有一個(gè)空格,可以識(shí)別任意類型的變量)蜘渣,從渲染模板時(shí)使用的數(shù)據(jù)中獲取淌铐。如果變量的值是HTML,由于轉(zhuǎn)義的原因?qū)е聻g覽器不能正常顯示HTML代碼蔫缸,所以需要使用safe過(guò)濾器腿准,例如文章詳情的HTML顯示就需要這樣處理,過(guò)濾器寫在變量名后用豎線隔開(kāi){{ 變量名|過(guò)濾器名 }}

Jinja2中用{% 控制語(yǔ)句 %}代表控制結(jié)構(gòu)來(lái)改變模板的渲染流程拾碌,例如:

# 條件控制
{% if xxx %}
    <h1>Android</h1>
{% else %}
    <h1>iOS</h1>
{% endif %}
# for循環(huán)
{% for x in xs %}
    <li>{{ x }}</li>
{% endfor %}
# 導(dǎo)入
{% import 'xxx.html' %}
# 包含
{% include 'xxx.html' %}

導(dǎo)入吐葱、包含的目的都是為了復(fù)用,還可以通過(guò)繼承實(shí)現(xiàn)復(fù)用校翔,類似于類的繼承:

# 繼承
{% extends "base.html" %}

通過(guò)繼承弟跑,模板中重復(fù)的代碼都可以寫在父模板里,例如導(dǎo)航條和頁(yè)面底部footer就可以放在父模板里防症。

3.2孟辑、Bootstrap

前端使用了Bootstrap框架,它提供了良好的CSS規(guī)范蔫敲,可以幫助我們更好的美化界面饲嗽,具體的可參考:
https://v3.bootcss.com/,要在項(xiàng)目中集成它可以使用Flask的Flask-Bootstrap擴(kuò)展奈嘿,直接在PyCharm安裝貌虾,并在工廠函數(shù)中初始化,還要讓項(xiàng)目的父模板繼承Bootstrap的基類模板:

# common_base.html
{% extends "bootstrap/base.html" %}

Bootstrap的基類模板base.html提供了一個(gè)網(wǎng)頁(yè)框架裙犹,包含了Bootstrap中的所有CSS和JS文件酝惧。除此之外基類模板還定義了許多可在其子類模板中重定義的塊,使用格式如下:

{% block 塊名稱 %}
{% endblock %}

常用的塊如下:

塊名稱 含義
head <head>標(biāo)簽中的內(nèi)容
title <title>標(biāo)簽中的內(nèi)容
body <body>標(biāo)簽中的內(nèi)容
styles css樣式單的定義
navbar 自定義的導(dǎo)航條
content 自定義的頁(yè)面內(nèi)容
page_content 定義content在內(nèi)部
scripts JS聲明伯诬,一般在模板尾部

注意如在子模板在模板已有的塊中添加新內(nèi)容,需要使用super()函數(shù):

{% block scripts %}
    {{ super() }}
    <!-- 新加的內(nèi)容 -->
{% endblock %}
3.3巫财、Flask-WTF

2.1中我們已經(jīng)看到了用Flask-WTF定義表單的方式盗似,即自定義的表單類繼承FlaskForm類,并添加需要的類變量平项,Flask-WTF定義了許多標(biāo)準(zhǔn)字段可以被渲染成指定的表單類HTML標(biāo)簽赫舒,例如:

字段名 對(duì)應(yīng)的H5標(biāo)簽
StringField 文本框
TextAreaField 多行文本框
PasswordField 密碼輸入框
BooleanField 復(fù)選框
SubmitField 表單提交按鈕

同時(shí)Flask-WTF還提供了許多常用的表單校驗(yàn)函數(shù)悍及,例如:Email()EqualTo()接癌、DataRequired()心赶、Length()等等,當(dāng)點(diǎn)擊提交按鈕時(shí)缺猛,會(huì)自動(dòng)校驗(yàn)表單是否滿足預(yù)定義的條件缨叫。

2.2中,我們通過(guò)參數(shù)把表單類的實(shí)例同步form參數(shù)傳入模板:

render_template('markdown_editor.html', form=form, type='create')

在模板中可以通過(guò)如下方式生表單(只保留了部分核心代碼):

<form method="post" role="form" class="height-full">
        {{ form.hidden_tag() }}
        {{ form.title(id="title", class="form-control editor-blog-title", placeholder=form.title.label.text) }}
        {{ form.labels(class="form-control editor-blog-area", placeholder=form.labels.label.text) }}
        {{ form.summary(class="form-control editor-blog-area", placeholder=form.summary.label.text, rows=3) }}
        {{ form.publish(class="btn btn-info") }}
        {{ form.save(class="btn btn-success") }}
    </form>

這樣的好處是我們能自定義表單的樣式等等荔燎,但是工作量蠻大的耻姥,如果對(duì)表單樣式?jīng)]有特殊的需求,Bootstrap中的表單樣式可以滿足需求有咨,可以通過(guò)Flask-Bootstrap提供的輔助函數(shù)快速的渲染表單琐簇,只需要如下兩步:

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}

例如登錄的H5模板就是這樣做的。
form.hidden_tag() 模板參數(shù)將被替換為一個(gè)隱藏字段座享,用來(lái)實(shí)現(xiàn)在配置中激活的 CSRF 保護(hù)婉商。如果你已經(jīng)激活了CSRF,這個(gè)字段需要出現(xiàn)在你所有的表單中渣叛。

2.2中丈秩,如果點(diǎn)擊表單提交按鈕,所有的表單都能成功通過(guò)校驗(yàn)诗箍,則form.validate_on_submit()的值為True癣籽,否則校驗(yàn)失敗,網(wǎng)頁(yè)會(huì)出現(xiàn)對(duì)應(yīng)提示滤祖。如果有兩個(gè)提交按鈕筷狼,那么在校驗(yàn)成功后,還需要判斷點(diǎn)擊的是哪個(gè)按鈕匠童,否則所有的按鈕都執(zhí)行了同一個(gè)操作埂材。例如我們的文章發(fā)布和保存按鈕,當(dāng)表單類中的按鈕字段的data屬性為True則代表該按鈕被點(diǎn)擊汤求,例如:

if form.publish.data:
    # 發(fā)布
elif form.save.data:
    # 保存草稿
3.4俏险、jQuery

有些頁(yè)面需要在相關(guān)操作后修改控件的CSS樣式,例如文章詳情的喜歡取消喜歡按鈕扬绪,最簡(jiǎn)單的方式是操作成功后直接刷新整個(gè)頁(yè)面竖独,但這樣體驗(yàn)并不好,更好的方式是局部刷新挤牛。這里直接使用jQuery(Bootstrap也提供了類似的操作莹痢,同時(shí)包含了jQuery,不需要單獨(dú)導(dǎo)入jQuery)來(lái)實(shí)現(xiàn)。使用jQuery強(qiáng)大的選擇器功能可以方便的得到要操作的DOM節(jié)點(diǎn)竞膳,按鈕的點(diǎn)擊也是發(fā)起一個(gè)請(qǐng)求航瞭,jQuery也集成了ajax,可以方便的處理請(qǐng)求坦辟,在請(qǐng)求完成后根據(jù)響應(yīng)結(jié)果來(lái)更改DOM節(jié)點(diǎn)的樣式刊侯。看下按鈕的點(diǎn)擊事件:

favourite = function (id) {
        if ($('.blog-favourite-btn').length > 0) {//取消喜歡
            $.get('/manage/blog/cancel_favourite', {
                id: id
            }).done(function (data) {
                $('.blog-favourite-btn span').removeClass('glyphicon-heart').addClass('glyphicon-heart-empty');
                $('.blog-favourite-btn').removeClass('blog-favourite-btn').addClass('blog-unfavourite-btn');
            })
        } else if ($('.blog-unfavourite-btn').length > 0) {//喜歡
            $.get('/manage/blog/favourite', {
                id: id
            }).done(function (data) {
                if ('200' === data) {
                    $('.blog-unfavourite-btn span').removeClass('glyphicon-heart-empty').addClass('glyphicon-heart');
                    $('.blog-unfavourite-btn').removeClass('blog-unfavourite-btn').addClass('blog-favourite-btn');
                }

                if ('403' === data) {
                    alert('沒(méi)有操作權(quán)限');
                }
            })
        }
    }

4锉走、Markdown

書中使用的是Flask-PageDown滨彻、Markdown兩個(gè)庫(kù)來(lái)實(shí)現(xiàn)對(duì)Markdown功能的支持,但是不夠理想挠日,有些Markdown語(yǔ)法并不能很好的支持疮绷,例如Flask-PageDown實(shí)時(shí)預(yù)覽時(shí)并不支持代碼塊和表格等。最后使用了marked這個(gè)庫(kù)嚣潜,它是一個(gè)全功能的Markdown解析器和編譯器冬骚,用JavaScript編寫,構(gòu)建速度快懂算,其實(shí)就是實(shí)時(shí)將用Markdown語(yǔ)法編輯的內(nèi)容轉(zhuǎn)換成對(duì)應(yīng)的HTML預(yù)覽只冻,但是沒(méi)有CSS樣式的HTML還是有點(diǎn)丑,github-markdown-css是一個(gè)不錯(cuò)的選擇计技,可以幫助我們實(shí)現(xiàn)github風(fēng)格的Markdwon預(yù)覽喜德。既然是要編輯文章那么直接使用HTML里的<textarea>肯定難以實(shí)現(xiàn)理想的效果,這里使用了ace垮媒,它是一個(gè)用JavaScript編寫的獨(dú)立代碼編輯器舍悯,下載ace-builds/arc-min即可。核心的幫助工具就這些了睡雇,接下來(lái)就是把他們組合起來(lái)萌衬,首先看HTML界面主要有編輯和預(yù)覽兩部分:

<!--編輯-->
<div class="col-md-6 markdown-panel">
     <div id="markdown-edit"></div>
</div>
<!--預(yù)覽-->
<div class="col-md-6 markdown-panel">
     <div id="markdown-preview" class="markdown-body"></div>
</div>

接下來(lái)就是編輯器的初始化了:

<script>
    //編輯器配置
    var ace_edit = ace.edit('markdown-edit');
    ace_edit.setTheme('ace/theme/chrome');
    ace_edit.getSession().setMode('ace/mode/markdown');
    ace_edit.renderer.setShowPrintMargin(false);
    //字體大小
    ace_edit.setFontSize(15);
    //自動(dòng)換行
    ace_edit.setOption('wrap', 'free');

    $("#markdown-edit").keyup(function () {
        // 實(shí)現(xiàn)Markdown到HTML的預(yù)覽
        $("#preview").text(marked(ace_edit.getValue()));
    });
</script>

更多細(xì)節(jié)可參考markdown_editor.html,看一下效果:

Markdwon

5它抱、數(shù)據(jù)庫(kù)

數(shù)據(jù)庫(kù)使用的是MySql秕豫,同時(shí)使用了ORM框架SQLAlchemy把關(guān)系數(shù)據(jù)庫(kù)的表結(jié)構(gòu)映射到對(duì)象上,來(lái)簡(jiǎn)化數(shù)據(jù)庫(kù)的操作观蓄,F(xiàn)lask有一個(gè)Flask-SQLAlchemy擴(kuò)展可以方便的在程序中使用SQLAlchemy混移,首先需要指定數(shù)據(jù)庫(kù)URL,這一步在config.py完成:

SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@127.0.0.1:3306/niceblog_dev'

然后在工廠函數(shù)完成配置侮穿。之后就是定義數(shù)據(jù)模型了歌径,項(xiàng)目中一共定義了6個(gè)數(shù)據(jù)模型:UserRole亲茅、Blog沮脖、Comment金矛、FavouriteLabel勺届,都繼承db.Model,在數(shù)據(jù)模型中指定表名稱娶耍、列名稱等信息免姿,例如保存文章信息的Blog

class Blog(db.Model):
    """
    博客數(shù)據(jù)Model
    """
    __tablename__ = 'blogs'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))
    summary = db.Column(db.Text)
    content = db.Column(db.Text)
    content_html = db.Column(db.Text)
    # 發(fā)布日期
    publish_date = db.Column(db.DateTime, index=True)
    # 最后的編輯日期
    edit_date = db.Column(db.DateTime, index=True)
    # 外鍵,和User表對(duì)應(yīng)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    # 是否是草稿
    draft = db.Column(db.Boolean)
    # 是否禁用評(píng)論
    disable_comment = db.Column(db.Boolean, default=False)
    # 被瀏覽的次數(shù)
    views = db.Column(db.Integer, default=0)
    comments = db.relationship('Comment', backref='blog', lazy='dynamic')
    favourites = db.relationship('Favourite', backref='blog', lazy='dynamic')

配置好了數(shù)據(jù)庫(kù)榕酒、定義好了數(shù)據(jù)模型胚膊,就可以通過(guò)如下命令來(lái)操作數(shù)據(jù)庫(kù)了:

  • db.create_all():創(chuàng)建表
  • db.session.add():插入行、修改行想鹰,最后需要執(zhí)行db.session.commit()
  • db.session.delete():刪除行紊婉,最后需要執(zhí)行db.session.commit()
  • 數(shù)據(jù)模型名.query().查詢過(guò)濾器.查詢執(zhí)行函數(shù):查詢行

常用的查詢過(guò)濾器有:filter()filter_by()辑舷、limit喻犁、offset()order_by()何缓、group_by()
常用的查詢執(zhí)行函數(shù)有:all()肢础、first()first_or_404()碌廓、get()传轰、get_or_404()count()谷婆、paginate()

如果在shell中操作數(shù)據(jù)庫(kù)慨蛙,每次都要導(dǎo)入數(shù)據(jù)庫(kù)實(shí)例和數(shù)據(jù)模型,如何避免這個(gè)問(wèn)題呢纪挎?由于項(xiàng)目使用了Flask-Script命令行解釋器期贫,支持自定義命令,可以讓Flask-Script的shell命令自動(dòng)導(dǎo)入特定對(duì)象即可:

def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role, Blog=Blog, Comment=Comment, Favourite=Favourite, Label=Label,
                Permission=Permission)
manager.add_command('shell', Shell(make_context=make_shell_context))

開(kāi)發(fā)中修改數(shù)據(jù)模型是不可避免的廷区,為了不發(fā)生刪表重建導(dǎo)致數(shù)據(jù)丟失的問(wèn)題唯灵,我們需要使用數(shù)據(jù)庫(kù)遷移框架,增量式的把數(shù)據(jù)模型的改變應(yīng)用到數(shù)據(jù)庫(kù)中隙轻,我們可以直接使用Flask-Migrate來(lái)完成埠帕,在Flask-Script集成數(shù)據(jù)庫(kù)遷移功能:

migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

數(shù)據(jù)庫(kù)遷移只要有如下三個(gè)命令:

  1. python manage.py db init:創(chuàng)建遷移倉(cāng)庫(kù),初始執(zhí)行一次即可
  2. python manage.py db migrate --message "initial migration":創(chuàng)建遷移腳本
  3. python manage.py db upgrade:更新數(shù)據(jù)庫(kù)

每次修改數(shù)據(jù)模型后需要更新數(shù)據(jù)庫(kù)時(shí)執(zhí)行命令2玖绿、3即可敛瓷。

6、接口開(kāi)發(fā)

api藍(lán)本的目錄結(jié)構(gòu)如下:
|-NiceBlog
???|-app/ 主目錄
??????|-api/ 為移動(dòng)端提供接口的藍(lán)本
?????????|-__init__.py 創(chuàng)建藍(lán)本
?????????|-authentication.py 登錄斑匪、注冊(cè)呐籽、token檢驗(yàn)
?????????|-blogs.py 文章列表、詳情的接口
?????????|-comments.py 評(píng)論相關(guān)接口
?????????|-decorators.py 自定義裝飾器
?????????|-favourites.py 喜歡操作相關(guān)的接口
?????????|-labels.py 文章分類標(biāo)簽接口
?????????|-responses.py 幫助返回JSON數(shù)據(jù)

Flask提供的jsonify()函數(shù)可以方便的把一個(gè)字典轉(zhuǎn)換成JSON串返,例如返回文章分類標(biāo)簽的路由可以這么寫:

@api.route('/labels/')
def get_labels():
    labels = Label.query.all()
    data = {'labels': [label.to_json() for label in labels]}
    return jsonify({'error': '', 'code': '200', 'data': data})

to_json()方法是數(shù)據(jù)模型中Label類的方法狡蝶,完成數(shù)據(jù)模型到JSON格式化的序列化字典轉(zhuǎn)換:

    def to_json(self):
        json_label = {
            'id': self.id,
            'name': self.name,
        }
        return json_label

為了保證接口有一定的安全性庶橱,不被隨意訪問(wèn),除了登錄贪惹、注冊(cè)苏章、以及文章預(yù)覽的html頁(yè)面外其他接口都需要一個(gè)token參數(shù),token可以在登錄后得到奏瞬,token過(guò)期后需要重新請(qǐng)求枫绅。由于要對(duì)請(qǐng)求攜帶的token參數(shù)校驗(yàn),可以定義一個(gè)before_request鉤子硼端,在每次請(qǐng)求前統(tǒng)一完成token的校驗(yàn):

@api.before_request
def before_request():
    url = request.url
    if url.find('login') == -1 and url.find('register') == -1 and url.find('preview') == -1:
        token = request.args.get('token')
        if token is None:
            return unauthorized('token缺失')
        user = User.verify_auth_token(token)
        if user is None:
            return forbidden('token過(guò)期并淋,請(qǐng)重新登錄')
        else:
            # g是程序上下文,用作臨時(shí)存儲(chǔ)對(duì)象珍昨,
            # 保存當(dāng)前的請(qǐng)求對(duì)應(yīng)的user县耽,每次請(qǐng)求都會(huì)更新
            g.current_user = user

測(cè)試接口可以使用HTTPie,通過(guò)PyCharm在虛擬環(huán)境安裝HTTPie后曼尊,啟動(dòng)Web服務(wù)酬诀,windows下通過(guò)cmd進(jìn)入虛擬環(huán)境目錄,執(zhí)行Scripts\activate激活虛擬環(huán)境(退出虛擬環(huán)境執(zhí)行deactivate):

激活虛擬環(huán)境

執(zhí)行登錄請(qǐng)求骆撇,命令如下:

http POST http://127.0.0.1:5000/api/login/ email==shehuan320@163.com password==123456

響應(yīng)如下:


登錄

使用登錄的得到的token請(qǐng)求文章分類標(biāo)簽接口:

http GET http://127.0.0.1:5000/api/labels/ token==eyJhbGciOiJIUzI1NiIsImlhdCI6MTUxODEzOTQwNiwiZXhwIjoxNTE4NzQ0MjA2fQ.eyJpZCI6MX0.EujL1Pb4lg20Bb2QWngop1N79os0LdFWniA8bL4JQHo

響應(yīng)如下:


文章分類標(biāo)簽

四瞒御、安裝

以下的安裝步驟是基于Windows環(huán)境的!

  1. 從Guthub clone NiceBlog到本地
  2. 安裝Python 3 的開(kāi)發(fā)環(huán)境
  3. 安裝PyCharm開(kāi)發(fā)工具神郊,導(dǎo)入項(xiàng)目肴裙,建議使用虛擬環(huán)境,可直接在 PyCharm 中創(chuàng)建一個(gè)虛擬環(huán)境涌乳,或者使用命令行創(chuàng)建蜻懦。
  4. 在虛擬環(huán)境中安裝requestments.txt中的擴(kuò)展包,直接在 PyCharm 的 Terminal 執(zhí)行如下命令:
    pip install -r requirements.txt
  5. 安裝MySql夕晓,創(chuàng)建數(shù)據(jù)庫(kù)宛乃,并在config.py中替換為自己創(chuàng)建的數(shù)據(jù)名,并修改用戶名和密碼
  6. 在 Terminal 執(zhí)行python manage.py shell切換到 shell 環(huán)境蒸辆,再執(zhí)行db.create_all()創(chuàng)建數(shù)據(jù)表
  7. 由于注冊(cè)賬號(hào)使用了qq郵箱驗(yàn)證征炼,請(qǐng)?jiān)?code>config.py中替換自己的qq郵箱和授權(quán)登錄密碼,并更改管理員郵箱為自己的郵箱躬贡。
  8. 執(zhí)行exit()退出 shell 環(huán)境谆奥,再執(zhí)行python manage.py runserver就可以啟動(dòng) Web 服務(wù)了,默認(rèn)運(yùn)行在http://127.0.0.0.1:5000拂玻。
  9. 在瀏覽器訪問(wèn)http://127.0.0.0.1:5000酸些,你就可以進(jìn)行注冊(cè)賬號(hào)宰译,創(chuàng)建文章等操作了,希望一切順利吧魄懂!

最后附上幾張截圖:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沿侈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子市栗,更是在濱河造成了極大的恐慌肋坚,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肃廓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诲泌,警方通過(guò)查閱死者的電腦和手機(jī)盲赊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)敷扫,“玉大人哀蘑,你說(shuō)我怎么就攤上這事】冢” “怎么了绘迁?”我有些...
    開(kāi)封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)卒密。 經(jīng)常有香客問(wèn)我缀台,道長(zhǎng),這世上最難降的妖魔是什么哮奇? 我笑而不...
    開(kāi)封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任膛腐,我火速辦了婚禮,結(jié)果婚禮上鼎俘,老公的妹妹穿的比我還像新娘哲身。我一直安慰自己,他們只是感情好贸伐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布勘天。 她就那樣靜靜地躺著,像睡著了一般捉邢。 火紅的嫁衣襯著肌膚如雪脯丝。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天歌逢,我揣著相機(jī)與錄音巾钉,去河邊找鬼。 笑死秘案,一個(gè)胖子當(dāng)著我的面吹牛砰苍,可吹牛的內(nèi)容都是我干的潦匈。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赚导,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茬缩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起吼旧,我...
    開(kāi)封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凰锡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后圈暗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掂为,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年员串,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勇哗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寸齐,死狀恐怖欲诺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渺鹦,我是刑警寧澤扰法,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站毅厚,受9級(jí)特大地震影響塞颁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卧斟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一殴边、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧珍语,春花似錦锤岸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至募逞,卻和暖如春蛋铆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背放接。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工刺啦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纠脾。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓玛瘸,卻偏偏與公主長(zhǎng)得像蜕青,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糊渊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容