《Flask Web開發》-基于python的web應用開發實戰-筆記

《Flask Web開發》這本書中的一些坑

1. 需要安裝的包

  • pip install flask
  • pip install flask_script
  • pip install flask_bootstrap
  • pip install flask_moment
  • pip install flask_wtf
  • pip install flask_sqlalchemy
  • pip install flask_mail
  • pip install flask_login
  • pip install pymysql
  • pip install flask_migrate
  • pip install hashlib
  • pip install faker #生成虛擬數據的
  • pip install flask_pagedown
  • pip install markdown
  • pip install bleach
  • pip install flask_httpauth
  • pip install httpie
  • pip install selenium

2. bootstrap

Bootstrap

3. 響應abort函數

一種特殊的響應由abort函數生成,用于處理錯誤

4. @app.errorhandler

用來處理狀態碼錯誤的

@app.errorhandler(400)
def page_not_found(e):
      return render_template('404.html'),404

5.{% import "bootstrap/wtf.html" as wtf %}

在模板中使用{% import "bootstrap/wtf.html" as wtf %}.
import指令的使用方法和普通Python代碼一樣,允許導入模板中的元素并用在多個模板中。

6. 數據庫

(1).

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role')

    def __repr__(self):
        return '<Role %r>' % self.name

Flask-SQLAlchemy創建的數據庫實例為模型提供了一個基類以及一系列輔助類和輔助函數,可用于定義模型結構。

類變量__tablename__定義在數據庫中使用的表名。如果沒有定義__tablename__,Flask-SQLAlchemy會使用一個默認名字,但默認的表名沒有遵守使用復數形式進行命名的約定。其余的類變量都是該模型的屬性,被定義db.Column類的實例。

(2).要讓Flask-SQLAlchemy根據模型類創建數據庫。方法是使用db.create_all()函數。如果數據庫表已經存在與數據中,那么db.create_all()不會重新創建或者更新這個表。如果修改模型后要把改動應用到現有的數據庫中,這一特性會帶來不便。更新現有的數據庫表的粗暴方式是先刪除舊表再重新創建:

>>>db.drop_all()
>>>db.create_all()

數據庫的操作是先創建模型的實例,然后將數據加入到數據庫會話中db.session。要將對象寫入數據庫,最后要使用db.commit()提交會話。

數據庫會話db.session和Flask session對象沒有關系。數據庫會話也稱為事務。

數據庫會話可以回滾,調用db.session.rollback()后,添加到數據庫會話中的所有對象都會還原到它們在數據庫時的狀態。

(3). python hello.py shell使用與運行
要想使用python hello.py shell但沒有沒有進入 shell 環境。原因為沒有調用manager.run(),應該要使用如下代碼,才能進入shell環境。

from flask_script import Manager
if __name__ == '__main__':
    manager.run()

(4).數據庫插入行admin_role = Role(name = 'Admin')

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(64),unique = True)
    users = db.relationship('User',backref='role')
    def __repr__(self):
        return '<Role %r>'%self.name

admin_role = Role(name = 'Admin')

模型的構造函數接受的參數是使用關鍵字參數指定的模型屬性初始值。

7. 使用Flask_mail發送電子郵件

from flask_mail import Mail, Message

app.config['MAIL_SERVER'] = 'smtp.126.com'
app.config['MAIL_PORT'] = 25
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'MAIL_USERNAME'
app.config['MAIL_PASSWORD'] = 'MAIL_PASSWORD'

成功用上述代碼在python shell中發送郵件

8. WTForm表單的field的取值form.name.data

官方文檔的解答

9.藍本Blueprint的使用

(1)創建藍本

# app/main/__init__.py
from flask import Blueprint

main = Blueprint('main',__name__)
from . import views,errors

錯誤與路由處理的程序在app/main/init.py腳本的末尾導入,這是為了避免循環導入依賴,因為在views.py和errors.py中還要導入藍本main。

--Python是腳本語言,只會由上往下執行
(2)藍本中的錯誤處理程序

from flask import render_template
from .import main

@main.app_errorhandler(404)
def page_not_found(e):
    return render_template('404.html'),404

@main.app_errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'),500

只有藍本中的錯誤才能觸發處理程序,要想注冊程序全局的錯誤處理程序,必須使用app_errorhandler
(3)藍本中定義路由

from flask import render_template, session, redirect, url_for, current_app
from .. import db
from ..models import User
from ..email import send_email
from . import main
from .forms import NameForm


@main.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            db.session.commit()
            session['known'] = False
            if current_app.config['FLASKY_ADMIN']:
                send_email(current_app.config['FLASKY_ADMIN'], 'New User',
                           'mail/new_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        return redirect(url_for('.index'))
    return render_template('index.html',
                           form=form, name=session.get('name'),
                           known=session.get('known', False))

藍本中編寫的視圖函數主要有兩點不同:

  • 1 路由修飾器是由藍本提供的
  • 2 url_for()函數的用法不同。url_for()函數的第一個參數是路由的端點名,在程序的路由中,默認為視圖函數的名字。例如,在單腳本程序中,index()視圖函數的URL可使用url_for('index')獲取

而在藍本中就不一樣,Flask會為藍本中的全部端點加上一個命名空間,這樣就可以在不同的藍本中使用相同的端點名定義視圖函數,而不會產生沖突。命名空間就是藍本的名字(Blueprint構造函數的第一個參數),所以視圖函數index()注冊的端點名是main.index,其URL使用`url_for('main.index')獲得

9. 認證藍本

render_template()指定的模板文件保存在auth文件夾中,這個文件夾必須在app/templates中創建,因為Flask認為模板的路徑是相對魚程序模板文件夾而言的。render_template()函數會首先搜索程序配置的模板文件夾,然后在搜索藍本的配置的模板文件夾

10. 數據庫migrations時遭遇錯誤(Can't locate revision identified by ‘xxx’)

Can't locate revision identified by ‘xxx’

這種錯誤最簡單的解決辦法就是把數據庫中的alembic_version表刪掉,然后就可以繼續后面的操作了


image.png

參考:Flask框架+mySQL數據庫:誤刪migrations文件夾后再次創建時遭遇錯誤(Can't locate revision identified by ‘xxx’)

11.添加登錄表單

登錄頁面使用的模板保存在auth/login.html文件中。這個模板只需要是使用Flask-Bootstrap提供的wtf.quick_form()宏渲染表單即可

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky - Login{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Login</h1>
</div>
<div class="col-md-4">
    {{ wtf.quick_form(form) }}
</div>
{% endblock %}

12.保護路由

Flask-login提供了一個login_required修飾器。

from flask_login import login_required
@app.route('/secret')
@login_required
def secret():
      return 'Only authenticated users are allowed!'

13.表單類

  1. 表單的創建,可以通過繼承從 Flask-WTF 導入的FlaskForm父類實現
from flask_wtf import FlaskForm # 表單類,從第三方擴展的命名空間 導入
  1. 表單類中需要定義 屬性/字段,值是字段類型類,就是將要在 HTML 中顯示的表單各個字段,其實就是對 HTML 表單各種標簽的包裝
from wtforms import StringField,PasswordField,BooleanField,SubmitField
  1. 字段類型類(說明文本,驗證器列表)
    驗證器列表,檢查用戶填寫表單時輸入的內容是否符合我們的期望,有多個驗證器時,需要同時通過驗證
from wtforms.validators import DataRequired,Length,Email,Regexp,EqualTo
# 普通用戶的資料編輯表單
class EditProfileForm(Form):
    name = StringField('Real name', validators=[Length(0, 64)]) # 因為是可選,允許長度為0
    location = StringField('Location', validators=[Length(0, 64)])
    about_me = TextAreaField('About me') # 文本區域,可以多行,可以拉動
    submit = SubmitField('Submit')

在表單類中定義了以validate_開頭并且后面跟著字段名的方法,這個方法就和常規的驗證函數一起調用。自定義的驗證函數要想表示驗證失敗,可以拋出ValidationError異常,其參數就是錯誤信息。

class RegistrationForm(FlaskForm):
    email = StringField('Email',validators=[DataRequired(),Length(1,64),Email()])
    username = StringField('Username',validators=[DataRequired(),Length(1,64),Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0,
               'Usernames must have only letters, numbers, dots or '
               'underscores')])
    password = PasswordField('Password',validators=[DataRequired(),EqualTo('password2',message='Passwords must match.')])
    password2 = PasswordField('Confirm password',validators=[DataRequired()])
    submit=SubmitField('Register')

    def validate_email(self,field):
        if User.query.filter_by(email = field.data).first():
            raise ValidationError('Email already registered.')
    
    def validate_username(self,field):
        if User.query.filter_by(username = field.data).first():
            raise ValidationError('Username already in use.')

14.沒有繼承的類class

class Permission:
    FOLLOW = 1
    COMMENT = 2
    WRITE = 4
    MODERATE = 8
    ADMIN = 16

在寫Flask的用戶權限中看到上述的類定義。在Python中定義類class的時候,加上()與不加上()有沒有區別?

Python創建類的時候,加()和不加有什么區別、聯系?
python的class(類)中繼承object 與不繼承的區別
通過查看上述的文章,可以得知的是事實上是沒有區別的,以下三種寫法是等價的

class A:
    pass
    
class A():
    pass
    
class A(object):
    pass

15.類的方法

方法 init()
類中的函數稱為方法;你前面學到的有關函數的一切都適用于方法,就目前而言,唯一重要
的差別是調用方法的方式。 ?處的方法 init() 是一個特殊的方法,每當你根據 Dog 類創建新實
例時,Python都會自動運行它。在這個方法的名稱中,開頭和末尾各有兩個下劃線,這是一種約
定,旨在避免Python默認方法與普通方法發生名稱沖突。

16.Jinja2 宏方法

https://blog.csdn.net/hello_albee/article/details/51638358/
https://blog.csdn.net/whtqsq/article/details/76278374

17. 用戶資料編輯器(Edit Profile)

@main.route('/edit-profile',methods=['GET','POST'])
@login_required
def edit_profile():
    form = EditProfileForm()
    if form.validate_on_submit():
        current_user.name = form.name.data
        current_user.location = form.location.data
        current_user.about_me = form.about_me.data
        db.session.add(current_user._get_current_object())
        db.session.commit()
        flash('Your profile has been updated.')
        return redirect(url_for('.user',username = current_user.username))
    form.name.data = current_user.name
    form.location.data = current_user.location
    form.about_me.data = current_user.about_me
    return render_template('edit_profile.html',form = form)

剛寫代碼的時候一直不是太懂為什么在顯示表單之前,這個視圖函數要為所有字段設定了初始值。后來運行了代碼,在網頁界面嘗試修改用戶profile才明白,初始值就是讓你在修改前,能夠看到以前的用戶信息。
Edit Profile

在這里還有一個問題,當用戶第一次訪問程序時,服務器會收到一個沒有表單數據的GET請求,所以validate_on_submit()將返回False。if語句的內容將被跳過,通過渲染模板處理請求!
當用戶提交表單后,服務器收到一個包含數據的POST請求。通過所有驗證之后,validate_on_submit()返回True,然后執行后續表單的數據的處理。這里當時自己有一個問題,就是執行完return redirect(url_for('.user',username = current_user.username))之后,后面的form.<field>.name數據還會不會執行賦值?是不會的,因為每一個函數只會執行返回一個值。當執行到return語句并返回了值,就會跳出函數。所以后續的語句是不會再執行了。

18. Flask 'endpoint'端點的理解

Flask中'endpoint'(端點)的理解1
Flask中endpoint的理解2

回到flask接受用戶請求地址并查詢函數的問題。實際上,當請求傳來一個url的時候,會先通過rule找到endpoint(url_map),然后再根據endpoint再找到對應的view_func(view_functions)。通常,endpoint的名字都和視圖函數名一樣。
這時候,這個endpoint也就好理解了:
實際上這個endpoint就是一個Identifier,每個視圖函數都有一個endpoint,
當有請求來到的時候,用它來知道到底使用哪一個視圖函數

19. Flask Request

flask框架的請求上下文request中的args獲取請求參數方式

request.form 和request.args.get的區別

用url_for()來給指定的函數構造URL。它接受函數名作為第一個參數,也接受對應URL規則的變量部分的命名參數。未知變量部分會添加到URL末尾作為查詢參數。

20.Flask中flash不顯示問題

flask中有個flash功能用來提示用戶一些信息,比如用戶退出登陸或者登陸用戶名密碼不匹配等。。。
在開發過程中,后臺里邊寫入消息提示flash,但是觸發了flash條件但是頁面并沒有顯示需要提示的信息。
原因:flash消息需要在模板中進行渲染。

例子:用方法get_flashed_messages()來獲取后臺堆積的flash消息,然后用一個for循環來顯示需要提示的信息。

應用代碼:

@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('無效用戶名或密碼.')
    return render_template('auth/login.html', form=form)

模板渲染:

<div class="container">
    {% for message in get_flashed_messages() %}
    <div class="alert alert-warning">
        <button type="button" class="close" data-dismiss="alert">&times;</button>
        {{ message }}
    </div>
    {% endfor %}
    {% block page_content %}{% endblock %}
</div>

21. Flask_script

flask_script的作用是可以通過命令行的形式來操作flask例如通過一個命令跑一個開發版本的服務器,設置數據庫,定時任務等.安裝flask_script比較簡單,直接通過pip install flask_script安裝就可以了。
具體可以參考下面有關Flask_script的使用方法:
Flask之flask-script模塊使用

然后就是
從Flask-Script遷移到Flask-Cli

Flask 中的click模塊與Flask中的Flask_cli不知道是不是一樣的

還有遇到上面的這個錯誤:

Error: Could not locate Flask application. You did not provide the FLASK_APP environment variable.

參考如何的文檔:
indows下Flask run報錯'Could not locate flask application' 原因?

https://blog.csdn.net/AuserBB/article/details/79524470

像這樣設置環境變量:

(venv) $ export FLASK_APP=manage.py

windows就比較坑爹了,官方文檔中說這么寫:

(venv) $ set FLASK_APP=manage.py

但是實際也許不會成功,會提示以下錯誤:

    Usage: flask run [OPTIONS]
     
    Error: Could not locate Flask application. You did not provide the FLASK_APP environment variable.
     
    For more information see http://flask.pocoo.org/docs/latest/quickstart/

我用的是PowerShell,網上找了一些方法,都不成功,后來按照Stack Overflow上這個方法來就成功了:

(venv) $env:FLASK_APP = "manage.py"

Click模塊
Flask_cli是Click模塊的一個backport(補丁)

Flask-CLI is a backport of Flask 1.0 new click integration to v0.10.
Do not install this package if you use Flask 1.0+.

shell_context_processor
這個的作用是避免了每次啟動shell會話時都要導入數據庫和模型

使用Flask的內置click命令行啟動Flask時總提示找不到'flask_bootstrap'
參考一下的解決方法
https://segmentfault.com/q/1010000015568164/a-1020000015595092

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容