1. 程式人生 > >Python之Flask框架應用(三)_Boortstrap與Flask_wtf

Python之Flask框架應用(三)_Boortstrap與Flask_wtf

#######Flask框架#######

## flask-bootstrap

# 如何在flask中使用Bootstrap

        要想在程式中整合Bootstrap,顯然要對模板做所有必要的改動。不過,更簡單的方法是使用一個名為Flask-Bootstrap 的Flask 擴充套件,簡化整合的過程。初始化Flask-Bootstrap 之後,就可以在程式中使用一個包含所有Bootstrap 檔案的基模板。這個模板利用Jinja2 的模板繼承機制,讓程式擴充套件一個具有基本頁面結構的基模板,其中就有用來引入Bootstrap 的元素。

安裝命令:  pip install flask_bootstrap

# 如何引用bootstrap的基模板

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

{%block title %}Flask{% endblock %}

這兩個塊分別表示頁面中的導航條和主體內容。

# Flask-Bootstrap定義的其他可用塊: 參考連結: https://pythonhosted.org/Flask-Bootstrap/basic-usage.html#available-blocks 塊名                  說明 doc                    整個html文件 html_attribs       html標籤屬性 html                    html標籤中的內容 head                  head標籤中的內容 title                      title標籤中的內容 metas                 一組meta標籤 styles                  層疊樣式表定義 body_attribs      body標籤的屬性 body                   body標籤中的內容 navbar                使用者定義的導航條 content               使用者定義的頁面內容 scripts                文件底部的JavaScript 宣告

# 如何繼承原有內容:         上表中的很多塊都是Flask-Bootstrap 自用的,如果直接重定義可能會導致一些問題。例如,Bootstrap 所需的檔案在styles 和scripts 塊中宣告。如果程式需要向已經有內容的塊中新增新內容,必須使用Jinja2 提供的super() 函式。例如,如果要在衍生模板中新增新的JavaScript 檔案,需要這麼定義scripts 塊:

{% block scripts %} {{ super() }} <script type="text/javascript" src="my-script.js"></script> {% endblock %}

from flask_bootstrap import Bootstrap

app = Flask(__name__)
app.config['SECRET_KEY'] = random._urandom(24)
bootstrap = Bootstrap(app)

base.html:

{#{ 利用bootstrap基模板 }#}
{% extends "bootstrap/base.html" %}
{# 繼承main.html #}
{% block styles %}
    {{ super() }}
    <link rel="stylesheet" href="../static/css/main.css">
{% endblock %}

{# 定義的導航條內容 #}
{% block navbar %}
    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                        data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="index.html"></a>
            </div>

            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li><a href="#">首頁<span class="sr-only">(current)</span></a></li>
                    <li><a href="#">新聞</a></li>
                    <li><a href="#">國際</a></li>
                    <li><a href="#">國內</a></li>
                    <li><a href="/sysinfo/">系統資訊</a></li>
                    <li><a href="#">登陸使用者</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right">
                    {% if 'user' in session %}
                        <li><a href="/login/"><span class="glyphicon glyphicon-log-in"></span>
                            &nbsp;&nbsp;{{ session['user'] }}</a></li>
                        <li><a href="/logout/"><span class="glyphicon glyphicon-log-in"></span>
                            &nbsp;&nbsp;登出</a></li>

                    {% else %}

                        <li><a href="/login/"><span class="glyphicon glyphicon-log-in"></span>
                            &nbsp;&nbsp;登陸</a></li>

                    {% endif %}

                    <li><a href="/register/"><span class="glyphicon glyphicon-log-out"></span>
                        &nbsp;&nbsp;註冊</a></li>
                </ul>
            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
{% endblock %}

{# 使用者定義頁面內容 #}
{% block content %}
    {#  定義屬於自己的block  #}
    {% block newcontent %}


    {% endblock %}

    {% block footer %}

        <div class="footer">


            京ICP備11008151號京公網安備11010802014853


        </div>
    {% endblock %}

{% endblock %}

## 表單處理flask_wtk

-  Flask-WTF理解

       request物件公開了所有客戶端傳送的請求資訊。特別是request.form可以訪問POST請求提交的表單資料。儘管Flask的request物件提供的支援足以處理web表單,但依然有許多工會變得單調且重複。Flask-WTF擴充套件使得處理web表單能獲得更愉快的體驗。該擴充套件是一個封裝了與框架無關的WTForms包的Flask整合。

-  表單處理

    在網頁中,為了和使用者進行資訊互動總是不得不出現一些表單。flask設計了WTForm表單庫來使flask可以更加簡便地管理操作表單資料。     WTForm中最重要的幾個概念如下:

1). Form類,開發者自定義的表單必須繼承自Form類或者其子類。     Form類最主要的功能是通過其所包含的Field類提供對錶單內資料的快捷訪問方式。

2). 各種Field類,即欄位。一般而言每個Field類都對應一個input的HTML標籤。     比如WTForm自帶的一些Field類比如BooleanField就對應<input type="checkbox">,     SubmitField就對應<input type="submit">等等。

3). Validator類。這個類用於驗證使用者輸入的資料的合法性。     比如Length驗證器可以用於驗證輸入資料的長度,     FileAllowed驗證上傳檔案的型別等等。

另外,flask為了防範csfr(cross-site request forgery)攻擊,    預設在使用flask-wtf之前要求app一定要設定過secret_key。    最簡單地可以通過app.config['SECRET_KEY'] = 'xxxx'來配置。

-  常見的field類

PasswordField        密碼欄位,自動將輸入轉化為小黑點

DateField                 文字欄位,格式要求為datetime.date一樣

IntergerField            文字欄位,格式要求是整數

DecimalField           文字欄位,格式要求和decimal.Decimal一樣

FloatField                 文字欄位,值是浮點數

BooleanField            複選框,值為True或者False

RadioField               一組單選框

SelectField               下拉列表,需要注意一下的是choices引數確定了下拉選項, 但是和HTML中的<select> 標籤一樣,其是一個tuple組成的列表,可以認為每個tuple的第一項是選項的真正的值,而第二項是alias。

MultipleSelectField 可選多個值的下拉列表

-  Validator是驗證函式:

          Validator是驗證函式,把一個欄位繫結某個驗證函式之後,flask會在接收表單中的資料之前對資料做一個驗證,           如果驗證成功才會接收資料。驗證函式Validator如下,具體的validator可能需要的引數不太一樣,這裡只給出           一些常用的,更多詳細的用法可以參見wtforms/validators.py檔案的原始碼,參看每一個validator類需要哪些引數:

*基本上每一個validator都有message引數,指出當輸入資料不符合validator要求時顯示什麼資訊。

Email 驗證電子郵件地址的合法性,要求正則模式是^[email protected]([^[email protected]][^@]+)$

EqualTo 比較兩個欄位的值,通常用於輸入兩次密碼等場景,可寫引數fieldname,不過注意其是一個字串變數,指向同表單中的另一個欄位的欄位名

IPAddress 驗證IPv4地址,引數預設ipv4=True,ipv6=False。如果想要驗證ipv6可以設定這兩個引數反過來。

Length 驗證輸入的字串的長度,可以有min,max兩個引數指出要設定的長度下限和上限,注意引數型別是字串,不是INT!!

NumberRange 驗證輸入數字是否在範圍內,可以有min和max兩個引數指出數字上限下限,注意引數型別是字串,不是INT!!然後在這個validator的message引數裡可以設定%(min)s和%(max)s兩個格式化部分,來告訴前端這個範圍到底是多少。其他validator也有這種類似的小技巧,可以參看原始碼。

Optional 無輸入值時跳過同欄位的其他驗證函式

Required 必填欄位

Regexp 用正則表示式驗證值,引數regex='正則模式'

URL 驗證URL,要求正則模式是^[a-z]+://(?P<host>[^/:]+)(?P<port>:[0-9]+)?(?P<path>\/.*)?$

AnyOf 確保值在可選值列表中。引數是values(一個可選值的列表)。特別提下,和SelectField進行配合使用時,不知道為什麼SelectField的choices中項的值不能是數字。。否則AnyOf的values引數中即使有相關數字也無法識別出當前選項是合法選項。我懷疑NoneOf可能也是一樣的套路。

NoneOf 確保值不在可選值列表中

# 程式碼要求:

在新建專案時我們一般建立一個form類以便於利用flask_wtf處理表單內容

from.py:

from flask_wtf import FlaskForm
# 確定表單的型別, 相當於input標籤中type的作用
from wtforms import StringField, SubmitField, PasswordField, FileField
# 確定表但內容的合法性
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
from flask_wtf.file import FileAllowed, FileRequired


class LoginForm(FlaskForm):
    user = StringField(
        label="使用者名稱/手機/郵箱",
        validators=[
            DataRequired(message="使用者名稱不能為空"),
            Length(4, 12)
        ]
    )
    passwd = PasswordField(
        label="密碼",
        validators=[
            Length(6)
        ]
    )
    submit = SubmitField(
        label="登陸"
    )


class RegisterForm(FlaskForm):
    user = StringField(
        label="使用者名稱/手機/郵箱",
        validators=[
            Length(4, 12)
        ]
    )
    email = StringField(
        label="郵箱",
        # Email 驗證電子郵件地址的合法性,要求正則模式是^[email protected]([^[email protected]][^@]+)$
        validators=[
            Email("郵箱格式不正確")
        ]
    )
    phone = StringField(
        label="電話",
        validators=[
            # Regexp == regular experssion (用於編寫正則表示式的)
            Regexp(r'1\d{10}', message="手機格式不正確")
        ]
    )
    passwd = PasswordField(
        label="密碼",
        validators=[
            Length(6)
        ]
    )
    repasswd = PasswordField(
        label="確認密碼",
        validators=[
            EqualTo('passwd', message="兩次密碼不一致")
        ]
    )
    # 使用者頭像
    # <input type="file>
    face = FileField(
        label="上傳頭像",
        validators=[
            FileAllowed(['png', 'jpg'], message="檔案非圖片")
        ]
    )
    submit = SubmitField(
        label="註冊"
    )

login.html:

{% extends 'base.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}登陸{% endblock %}

{% block newcontent %}
    <div class="container container-small">
        <h1>登入
            <small>沒有賬號?<a href="/register/">註冊</a></small>
        </h1>
        {{ wtf.quick_form(form) }}

        <!--獲取伺服器傳遞給後臺的message變數,jinja2模板引擎裡的語法-->
        {% if message %}
            <p style="color: red">{{ message }}</p>
        {% endif %}
    </div>
{% endblock %}

register.html:

{% extends 'base.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}註冊{% endblock %}
{% block newcontent %}
    <div class="container container-small">
        {{ wtf.quick_form(form) }}
        {% if message %}
            <p style="color:  red;">{{ message }}</p>
        {% endif %}
    </div>
{% endblock %}

主執行函式run;

from flask import Flask, render_template, redirect, request, abort, url_for, session
from models import isPasswdOk, isUserExist, addUser
from flask_bootstrap import Bootstrap
from forms import LoginForm, RegisterForm

app = Flask(__name__)
app.config['SECRET_KEY'] = random._urandom(24)
bootstrap = Bootstrap(app)



# 使用者主頁
@app.route('/')
def index():
    return render_template('index.html')


# 使用者登陸按鈕
@app.route('/login/', methods=['GET', 'POST'])
def login():
    # 例項化LoginForm
    form = LoginForm()
    # 如果是post方法並且表單驗證通過的話, 返回True;
    if form.validate_on_submit():
        # 1). 獲取使用者提交的表單資訊
        # print(form.data) 是字典型別, 內容如下:
        # {'user': 'root', 'passwd': 'redhat', 'submit': True }
        user = form.data['user']
        passwd = form.data['passwd']
        # 判斷使用者名稱和密碼是否正確
        if isPasswdOk(user, passwd):
            # 將使用者和密碼資訊儲存到session中
            session['user'] = user
            session['passwd'] = passwd
            # 登陸成功,跳轉主頁
            return redirect(url_for('index'))
        else:
            # 如果登陸失敗,顯示錯誤資訊
            return render_template('login.html', form=form, message='使用者密碼輸入有誤')
    else:
        return render_template('login.html', form=form)


# 使用者登出
@app.route('/logout/')
def logout():
    session.pop('user', None)
    session.pop('passwd', None)
    # None的意思是如果字典裡沒有這個key值,返回的value值是None
    return redirect(url_for('index'))


# 使用者註冊 get,post
@app.route('/register/', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = form.data['user']
        email = form.data['email']
        phone = form.data['phone']
        passwd = form.data['passwd']
        repasswd = form.data['repasswd']
        if isUserExist(user):
            message = "使用者已經存在"
            return render_template('register.html', form=form, message=message)
        else:
            # 如果遍歷所有的字典, 沒有發現與之前的使用者名稱衝突;
            # 將使用者資訊加入列表userInfo中;
            addUser(user, passwd)
            return redirect(url_for('login'))
    else:
        return render_template('register.html', form=form)


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

執行結果:

#######################################