對Flask感興趣的,能夠看下這個視頻教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002css
# 從 flask 框架中導入 flask 類 from flask import Flask # 用 flask() 初始化一個 flask 對象,並賦給 app # 需傳遞一個參數 __name__ # 1. 方便 flask 框架去尋找資源 # 2. 方便 flask 插件去定位問題 app = Flask(__name__) # @app.route() 是一個裝飾器,做用是對 url 與 視圖函數進行映射 # 將 `/` 映射到 hello_world() 函數上 # 即用戶訪問 http://example:80/ 的時候,用 hello_world() 函數來響應 @app.route('/') def hello_world(): return 'Hello World!' # 若是當前文件做爲程序入口,那麼就執行 app.run() if __name__ == '__main__': # app.run() 是啓動一個應用服務器來響應用戶請求並不斷監聽 app.run()
使用 debug 模式有不少好處:html
- 將報錯信息顯示到瀏覽器上,而不須要進入編輯器中查看報錯信息,方便開發者查看錯誤
- 當檢測到程序代碼(.py文件)發生改動,程序會自動加載而不須要手動重啓服務器
對 flask 程序使用 debug 模式有 2 種方式,以下:前端
在 app.run()
中直接傳入一個關鍵字參數: app.run(debug=True)
。python
在相同目錄下,新建一個 python
文件,建議命名爲 config,並在裏面指定該程序配置了 DEBUG
模式,即 `config.py 文件的內容以下:mysql
config.py # encoding:utf-8 DEBUG = True # SECRET_KEY # SQLALCHEMY_DB # 數據庫的一些參數配置
而後在主 app 文件中導入這個文件並配置到 app 中,主 app 文件內容以下:jquery
First_Flask.py # encoding:utf-8 from flask import Flask import config # 導入 config 配置文件 app = Flask(__name__) app.config.from_object(config) # 將該配置文件的配置信息應用到 app 中 @app.route('/') def hello_world(): a = 3 b = 0 c = a/b return 'Hello,World.' if __name__ == '__main__': app.run()
config.py
文件的用處很是大,須要掌握這種配置方法,在後期的 SECRET_KEY
和 SQLALCHEMY_DB
(與數據庫有關)都須要在這個文件中作配置.web
如:
http://localhost:8000/article/abc
和http://localhost:8000/article/def
中,兩條 URL 的參數不一樣,咱們能夠獲取這個參數並渲染後返回客戶瀏覽器sql
如何在 flask 中使用參數?代碼以下數據庫
@app.route('/article/<id>') def article(id): return u'<h1>你請求的參數是:%s<h1>' % id
- 參數須要放置在兩個尖括號中
- 視圖函數中須要放和 URL 參數同名的參數
正轉指的是:在獲取到用戶輸入的 URL 後將該 URL 映射到對應的視圖函數中,讓對應的視圖函數去處理該用戶的請求;
反轉指的是:與正轉相反,經過視圖函數來查找對應的 URL。
反轉的做用是:1. 在頁面重定向的時候會使用 URL 反轉;2. 在模板中會使用 URL 反轉編程
實現反轉的方法:
url_for
模塊用 url_for('FunctionName')
反轉
First_Flask.py
源碼以下:
# encoding:utf-8 from flask import Flask,url_for import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def hello_world(): print url_for('article',id='123') print url_for('my_list') return 'Hello,World.' @app.route('/article/<id>/') def article(id): return u'<h1>你請求的參數是:%s<h1>' % id @app.route('/list/') def my_list(): return '<h1>list</h1>' if __name__ == '__main__': app.run()
做用:在用戶訪問某些須要登陸的頁面時,若是用戶沒登陸,則可讓他重定向到登陸頁面
實現:
import redirect,url_for redirect(url_for('login'))
模板實際上就是一些被編寫好的具備必定格式的 html
文件。
在 pycharm 左側一欄,項目下有兩個文件夾: static
和 template
,分別用於存放靜態文件(如css,js,img文件)和模板文件(如html),因此咱們的 html 文件應該放在 template
文件夾下。
如何在主程序中調用模板文件呢?
template
文件夾下新建一個 html 文件render_template
模塊render_template('abc.html')
,注意不用寫路徑,flask 會自動去 template
文件夾下查找 abc.html
,但有文件夾除外代碼以下:
import render_template return render_template('index.html')
在 web 項目開發的大多數狀況下,咱們須要在 html 文件中從後臺程序傳入一些參數,而後將帶有這些參數的 html 文件返回瀏覽器。這時候就須要在 html 文件中引用這些後臺的參數,方法是 {{ Params }}
用 2 個花括號括起來,同時還要在後臺程序作一些傳參的動做。具體以下:
後端傳參:
render_template('index',username=u'螞蟻有毒',gender=u'男',age=18)
前端引用:
<h1>用戶名:{{ username }}</h1> <h1>性別:{{ gender }}</h1> <h1>年齡:{{ age }}</h1>
可是要是參數愈來愈多,則代碼會變得很複雜,可讀性差,管理難度大。那麼咱們能夠用一個字典(DICT)來定義一組參數。以下:
user = { 'username':id, 'gender':u'男', 'age':18 } # 調用時傳入一個關鍵字參數便可 return render_template('index.html',**user)
上面所演示的都是調用一些簡單的參數,若是在更復雜的環境下,如調用類的屬性呢?或者是調用字典中的字典的值呢?應該怎麼作?
在主程序中能夠先定義一個類並實例化:
class Person(object){ name = u'螞蟻有毒' gender = u'男' age = 18 } p = Person()
再定義一個字典:
content = { 'person':p, 'websites':{ 'baidu':'www.baidu.com', 'google':'www.google.com' } }
而後傳參時進行調用:
return render_template('index',**content)
最後在 index.html
中調用:
<p>姓名:{{person.name}}</p> <p>性別:{{person.gender}}</p> <p>年齡:{{person.age}}</p> <hr> <p>百度:{{websites.baidu}}</p> <p>{{websites.google}}</p>
實際上,咱們還能夠在模板(html文件)中嵌入python的代碼:{% code %}
,這是 jinja2
的語法,能夠嵌入 if 語句和 for 語句來在 html 文件中執行相關的邏輯操做。
主程序代碼:
# encoding:utf-8 from flask import Flaskrender_template app = Flask(__name__) app.config.from_object(config) @app.route('/<is_login>') def index(is_login): if is_login == '1': user = { 'username':u'螞蟻有毒', 'age':18 } return render_template('index.html',user=user) else: return render_template('index.html') if __name__ == '__main__': app.run(debug=True)
html 代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>螞蟻有毒的首頁</title> </head> <body> <!-- 若是用戶存在而且年齡大於18就顯示用戶名 --> {% if user and user.age > 18 %} <a href="#">{{ user.username }}</a> <a href="#">註銷</a> {% else %} <a href="#">登陸</a> <a href="#">註冊</a> {% endif %} <h1>歡迎來到螞蟻有毒的首頁。</h1> </body> </html>
for 循環的語法,在 html 中調用 for 語句和 if 語句的語法是同樣的,都是在 {% %}
中寫入 for 關鍵字。
咱們能夠藉助 html 中的 for 語句來遍歷一些變量(List or Dict or Truple)。
主程序代碼:
users = { 'username':u'螞蟻有毒', 'gender':u'男', 'age':18 } websites = ['www.baidu.com','www.google.com','www.qq.com'] return render_template('index.html',user=users,website=websites)
html 代碼:
{% for k,v in user.items() %} <p>{{ k }}:{{ v }}</p> {% endfor %} <hr> {% for website in websites %} <p>{{ website }}</p> {% endfor %}
完整代碼參照上一節
題目:渲染一個 四大名著 給前端,而後前端用一個表格展現出來。
先在主程序中定義一個變量用於存放四大名著的基本信息:
books = { u'三國演義':{ 'author':u'羅貫中', 'price':109 }, u'西遊記':{ 'author':u'吳承恩', 'price':120 }, u'紅樓夢':{ 'author':u'曹雪芹', 'price':113 }, u'水滸傳':{ 'author':u'施耐庵', 'price':135 } }
再在主程序中將其傳給前端 html 文件
return render_template('index.html',books = books)
最後在前端模板中調用
<table> <tbody> <tr> <th>書名</th> <th>做者</th> <th>價格</th> </tr> {% for k,v in books.items() %} <tr> <td>{{ k }}</td> <td>{{ v.author }}</td> <td>{{ v.price }}</td> </tr> {% endfor %} </tbody> </table>
過濾器能夠理解爲 Linux 中的管道符 |
,將獲取的變量通過管道符後篩選出想要的內容。在 flask 中有不少過濾器,這裏介紹 2 個比較經常使用的過濾器:default
和 length
。要注意的是,過濾器只能針對變量{{ params }}
使用。
default 過濾器的做用:若是當前變量不存在,可使用指定的默認值
對於 default 過濾器,咱們作一個實驗:若是用戶有頭像則顯示本身的頭像,若是用戶沒有頭像則顯示默認頭像。
在 html 文件中使用以下所示: <img src="{{ avatar | default('https://i.imgur.com/ROhBvig.png') }}"> 該行代碼表示: 若是後端主程序有傳遞 avatar 變量過來,那麼就使用 avatar 變量的值; 若是後端主程序沒有傳遞 avatar 變量過來,那麼就使用 default 過濾器指定的內容 對於本例而言,default 後面跟的圖片地址應該是默認頭像的地址,avatar 變量內保存的值應該是用戶頭像
能夠統計有長度屬性的變量的長度。語法與 default 過濾器同樣,但不用在後面跟上指定的變量。對於 length 過濾器,咱們作一個實驗:統計評論的數量並顯示評論內容。
主程序代碼:
comments = [ { 'user':u'螞蟻有毒', 'content':u'我不喜歡這個東西' }, { 'user':u'楊烺', 'content':u'有同感,我也是' } ] return render_template('index.html',comments = comments)
html 模板代碼:
<p> 評論數:({{ comments | length }}) </p> <hr> {% for comment in comments %} <li> {{ comment.user }}: </li> <ol> {{ comment.content }} </ol> {% endfor %}
此外還有其餘不少過濾器,能夠自行查找資料。
繼承的概念和麪向對象編程的類的繼承是同樣的,只不過在這裏繼承的對象是模板。能夠建立一個經常使用模板,而且定義相關接口,能夠供其餘模板所使用。繼承的做用是:能夠把一些公共的代碼放在父模板中,避免編寫重複的代碼。
當建立好了一個模板後,在子模板中可使用
{% extends 'base.html' %}
來繼承該模板
可是若是我要在子模板中編寫本身所特有的內容,應該怎麼辦?這時候就須要在父模板中寫一個接口,來讓子模板實現:
- 在父模板須要的地方定義接口的方式是:
{% block abc %}{% endblock %}
- 在子模板中一樣須要寫上
{% block abc %} code {% endblock %}
,而且在code
處寫子模板須要的代碼。- 須要注意的是,子模板必須在父模板定義的接口中寫代碼,否則沒有做用。以下所示:
1. 父模板(Base.html): <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> {% block title %} <title>Base</title> {% endblock %} <style> .nav{ background: #3a3a3a; height: 65px; } ul{ overflow: hidden; } ul li{ float: left; list-style: none; padding: 0 10px; line-height: 65px; } ul li a{ color: #fff; } </style> </head> <body> <div class="nav"> <ul> <li><a href="#">首頁</a></li> <li><a href="#">發佈問答</a></li> </ul> </div> {% block content %}{% endblock %} </body> </html> 2. 子模板(index.html): {% extends 'base.html' %} {% block title %} <title>首頁</title> {% endblock %} {% block content %} <h1>螞蟻有毒的首頁</h1> {% endblock %}
咱們若是想要實現:點擊一個按鈕就能跳轉到另外一個頁面上。那麼咱們就要使用連接跳轉技術,在 flask 中,能夠經過 <a href={{ url_for('視圖函數名') }}>哈哈</a>
來實現
css,js,img 這 3 個文件都屬於靜態文件,都要放在項目的 static
文件夾下。若是咱們要對模板中的一些內容進行渲染,如:對 <a>
標籤的內容進行渲染,那麼咱們能夠將本來寫在 <head>
標籤中的 <style>
標籤的內容放到 css
文件中,再在模板中使用 url_for()
將 css
文件的渲染方式連接進來。步驟以下:
1. 加載 css 文件:
<link>
標籤
- 在
static
文件夾下建立一個新的文件夾css
,再在css
文件夾下建立一個 css 文件,可隨意命名(好記就行),如:head.css
。在使用了
<a></a>
標籤的模板中,若想引用 css 文件的內容,能夠用url_for()
連接進來,但格式與連接網址略有不一樣。正確連接靜態文件的方式爲:在模板 <head> 標籤範文內: <link rel=''stylesheet href='{{ url_for('static',filename='css/head.css') }}'> 注意!文件路徑
2. 加載 img 文件
<img>
標籤
- 第一步和加載 css 文件的第一步同樣,建立文件夾和文件:
/static/img/index.jpg
第二步在模板中須要插入圖片的地方使用以下代碼:
<img src='{{ url_for('static',filename='img/index.jpg') }}' alt=''>
3. 加載 js 文件
<script>
標籤
- 第一步和加載 css 文件的第一步同樣,建立文件夾和文件:
static/js/index.js
第二步在模板中須要插入腳本的地方使用以下代碼:
<script src='{{ url_for('static',filename='js/index.js') }}'>
通常靜態文件也就這三樣,使用方法如上所示。
安裝 MySQL
數據庫咱們使用的是 MySQL
,下載地址:https://dev.mysql.com/downloads/mysql/
,下載社區版就好了。注意要下載安裝包(.msi)而不是壓縮包(.zip)
若是在安裝過程當中提示咱們要安裝 windows 插件,那就按照它提示的網址去下載,沒有給網址的話就百度谷歌吧。
安裝 MySQL-python
MySQL-python 是一個驅動或者說是一個插件,若是咱們想要經過 python 去操做 MySQL 就須要藉助這個軟件去作。在 Windows 下安裝 MySQL-python 的步驟以下:進入 python 的 flask 虛擬環境並啓動:
cd C:\Virtualenv\flask-env\Script\ active pip install mysql-python
www.lfd.uci.edu/~gohlke/pythonlibs/#mysql-python
這個網站上下載一個非官方的插件MySQL_python-1.2.5-cp27-none-win_amd64.whl
,來使 mysql-python 支持 Windows 。能夠下載到 Virtualenv 所在盤的任意目錄。安裝這個插件:進入到該文件所在目錄執行命令 pip install MySQL_python-1.2.5-cp27-none-win_amd64.whl
便可。
安裝 Flask-SQLALchemy
安裝
cd C:\Virtualenv\flask-env\Script\ active pip install flask-sqlalchemy
先建立一個數據庫:
進入 MySQL 的命令行 -> 輸入密碼 -> create database [db_demo1(數據庫名)] charset utf8;
在主 app 程序中從 flask_sqlalchemy
導入 SQLAlchemy
類並初始化
from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) db = SQLAlchemy(app)
在 config.py
文件中配置數據庫信息並鏈接
DIALECT = 'mysql' DRIVER = 'mysqldb' USERNAME = 'root' PASSWORD = 'root' HOST = '127.0.0.1' PORT = '3306' DATABASE = 'db_demo1' SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
在主 app 文件中,添加配置文件
import config
app.config.from_objece(config)
作測試,看有沒有出現問題
db.create_all()
對於一個表,咱們能夠建立一個類(模型)來與之對應(一張表對應一個類),以下表:
先定義好表格是怎麼樣的
articel 表: create table article( id int primary key autoincrement, title varchar(100) not null, content text not null )
再建立出對應的(模型)類
class Article(db.Model): # 必定要繼承自 db.Model __tablename__ = 'article' # 指定表名,默認爲類的小寫字符 id = db.Column(db.Integer,primary_key=True,autuincrement=True) # 表中的每一個字段都要用 Column 建立,而後指定數據類型主鍵自增加等屬性 title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
將全部建立的模型(類)都映射到數據庫中成爲一個個的表格
db.create_all()
鏈接數據庫的總結:
- 模型(類)需繼承自
db.Medel
,表格中的字段必需要用db.Column
映射數據類型:
db.Integer 表明 int, db.String(length) 表明 varchar,須要指定長度 db.Text 表明 text
其餘屬性:
primary_key=True 表明 主鍵 nullabel=True 表明 可爲空,默承認以 autoincrement=True 表明 主鍵自增加將全部的模型(類)都在數據庫中建立:
db.create_all()
對數據庫的增刪改查,通常都在視圖函數裏面寫,這樣在瀏覽器上對某個 URL 進行訪問時,就會執行對應的視圖函數裏面的代碼,從而達到操做數據庫的目的。
flask 對數據庫的增刪改查,包括提交,都使用的是 db.session
操做。
增長
article1 = Article(title='Monday',content='something')
db.session.add(article1)
db.session.commit()
刪除
article1 = Article.query.filter(Article.title=='Monday').firsr()
db.session.delete(article1)
db.session.commit()
修改
article1 = Article.query.filter(Article.title=='Monday').firsr()
article1.title = 'Sunday'
db.session.commit()
查詢
result = Article.query.filter(Article.title=='Monday') # 查找是針對模型(類)的,使用 query 屬性,該屬性繼承自 db.Model,並用 filter() 來過濾條件 article1 = result.first() # 實際上篩選出來的對象是放在一個 list 中,可用 .first() 取 list 中的第一個值 print 'title:%s' % article.title print 'content:%s' % article.content
建立 2 張表:user
和 article
,其中 article
的 author_id
引用是 user
的 id
,即 2 者是外鍵關係。
數據庫語句建立:
建立用戶表: create table users( id int primary key autoincrement username varchar(20) not null ) 建立文章表: create table article( id int primary key autoincrement title varchar(100) not null content text not null author_id int, foreign key `author_id` reference `users.id` # 用外鍵相關聯 )
flask-sqlalchemy 模型建立:
建立用戶表: class Users(db.Model): __tablename__ = 'users' id = db.Column(db.Integer,primary_key=True,autoincrement=True) usernmae = db.Column(db.String(20),nullable=False) 建立文章表: class Article(db.Model): __tablename__ = 'article' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(100),nullabel=False) content = ab.Column(db.Text,nullabel=False) author_id = db.Column(db.Integer,db.ForeignKey('users.id')) # 外鍵關係
實例化模型(類)
由於 article
依賴與 user.id
存在,因此先建立 user,在建立 article。
user1 = User(username='user1') db.session.add(user1) db.session.commit() article = Article(title='aaa',content='bbb',author_id=1) db.session.add(article) db.session.commit()
需求:查詢 title ='aaa'
的文章的做者
4.1 傳統方法
article = Article.query.filter(Article.title=='aaa').first() # 找出標題爲 aaa 的文章 author = article.author_id # 找出文章的做者 user = User.query.filter(User.id==author).fitst() # 找出 id 爲做者 id 的做者 print 'username:%s' % user.id # 打印做者名
固然了,這個方法實在是太過於繁瑣,做爲一個優秀的框架模型,SQLAlchemy 是能夠用更先進的方法去找的,以下所示:
4.2 用 SQLAlchemy 語法
article = Article.query.filter(Article.title=='aaa') article.author.username # 找出標題爲 aaa 的文章做者 user = User.query.filter(User.username=='user1') user1.articles # 找出做者 user1 寫過的全部文章
固然了,這個只是理想中的方法,這樣的方式更方便咱們去獲取需求,不過 SQLAlchemy 已經實現了這種方法,只須要作以下映射便可使用:
# 先在 Article 模型中添加屬性 author = db.relationship('User',backref=db.backref('articles')) # 再查找某篇文章的做者 article = Article.query.filter(Article.title=='aaa').first() print 'username : %s' % article.author.username # 查找某個做者的文章 user = User.query.filter(User.id=='1').first() print u'%s 的文章:'% user.username for article in user.articles: print article.title
當對兩個 table
的關係用了 relationship
關聯外鍵以後,那麼 article
的 author
就沒必要在實例化的時候指定了,因此當你添加一篇文章,能夠進行以下操做:
article1 = Article(title='111',content='222') article1.author = User.query.filter(User.username=='myyd').first().id
簡介:Flask_Script
可讓程序經過命令行的形式來操做 Flask
,例如:經過命令跑一個開發版本的服務器、設置數據庫,定時任務等等。要使用 Flask_Script
,能夠在 Flask
虛擬環境中經過 pip install flask-script
安裝最新版本。
新建一個 manage.py
文件,將代碼寫在該文件中,而不是寫在主 app
文件中。內容以下:
# encoding:utf-8 from flask_script import Manager # 從 flask_script 導入 Manager from flask_script_demo1 import app # 從 flask_script_demo1 導入 app manage = Manager(app) # 初始化 app @manage.command # 裝飾器 def runserver(): # 執行命令的程序寫在這個函數下 print u'服務器跑起來了。' @manage.command # 裝飾器 def stopserver(): # 執行命令的程序寫在這個函數下 print u'服務器關閉了。' if __name__ == '__main__': manage.run()
命令行調用 manage.py
文件:
在虛擬環境的命令行下,用 python manage.py command
執行 manage.py
文件下的某個程序,如:python manage.py runserver
和 python manage.py stopserver
分別會執行 manage.py
文件中的 runserver()
和 stopserver()
方法。
若是有一些關於數據庫的操做,咱們能夠放在一個文件中執行。如 db_script.py
文件:
# encoding: utf-8 from flask_script import Manager # 由於本文件不是做爲主 app 文件,因此不須要寫 if __name__ == '__main__' # 也不須要在初始化的時候傳入 app 文件 DBManage = Manager() @DBManage.command def init(): print u'服務器初始化完成。' @DBManage.command def migrate(): print u'數據庫遷移完成。'
這時候要想用上 db_script.py
裏定義的命令,須要在主 manage.py
文件中導入該文件並引用該文件的命令:
from db_scripts import DBManage # 導入 db_script.py 文件 manage.add_command('db',DBManage) # 引用該文件 # 以後要是想使用 db_script.py 中的命令,命令行中就要經過 python manage.py db init 來調用 # 其中,db 是 manage.add_command() 中引號內的值,調用子命令的方法就是這種格式
總結:
若是直接在主 manage.py
文件中寫命令,調用時只須要執行 python manage.py command_name
便可
若是將一些命令集中在另外一個文件中,那麼就須要輸入一個父命令,好比 python manage.py db init
例子:兩個文件的完整代碼以下
1. manage.py # encoding:utf-8 from flask_script import Manager from flask_script_demo1 import app from db_scripts import DBManage manage = Manager(app) @manage.command def runserver(): print u'服務器跑起來了。' @manage.command def stopserver(): print u'服務器中止了。' manage.add_command('db',DBManage) if __name__ == '__main__': manage.run() 2. db_script.py # encoding: utf-8 from flask_script import Manager DBManage = Manager() @DBManage.command def init(): print u'服務器初始化完成。' @DBManage.command def migrate(): print u'數據庫遷移完成。'
以前咱們都是將數據庫的模型(類)放在主 app 文件中,可是隨着項目愈來愈大,若是對於加入的新的模型,咱們還放在主 app 文件中,就會使主 app 文件愈來愈大,同時也愈來愈很差管理。因此咱們能夠新建一個專門存放模型的文件。如 models.py
文件用來專門存放模型的文件。
將本來在主 app 文件中定義的模型(類)移動到 models.py
文件中,可是會提示錯誤,因此咱們在 models.py
文件中導入主 app 文件的 db:from models_sep.py import db
,兩個文件的完整代碼下所示:
1. # models_sep.py 文件 from flask import Flask from flask_sqlalchemy import SQLAlchemy from models import Article app = Flask(__name__) db = SQLAlchemy(app) db.create_all() @app.route('/') def index(): return 'index!' if __name__ == '__main__': app.run() 2. # models.py 文件 from flask_script_demo1 import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
執行以上文件,會報錯。
報錯提示:ImportError: cannot import name Article
,出現此類報錯,先排查路徑和導入的內容是否有錯,若保證沒錯,則極可能是出現循環引用。
報錯緣由:循環引用,即 models_sep.py
引用了 models.py
中的 Article
,而 models.py
又引用了 models_sep.py
中的 db
,從而形成循環引用。
解決循環引用:
解決方法:將 db
放在另外一個文件 exts.py
中,而後 models_sep.py
和 models.py
都從 exts.py
中引用 db
變量,這樣就能夠打破引用的循環。
三個文件的代碼以下:
1. # exts.py 文件 from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() 2. # models_sep.py 文件 from flask import Flask from models import Article from exts import db import config app = Flask(__name__) app.config.from_object(config) db.init_app(app) @app.route('/') def index(): return 'index' if __name__ == '__main__': app.run() 3. # models.py 文件 from exts import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincre) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
總結:
分開 models 的目的是:讓代碼更方便管理。
解決循環引用的方法:把 db 放在一個單獨文件中如 exts.py
,讓主 app 文件
和 models 文件
都從 exts.py
中引用。
在以上基礎上,咱們將模型(類)映射到數據庫中,調用 db.create_all()
進行映射。而後運行,發現報錯:No application found. Either work inside a view function or push an application context.
,是由於在 db 的上下文中,沒有將 app 推入到棧中。
當用戶訪問了服務器,則服務器會將當前 app 並加載到 app 棧中;若是用戶沒有訪問服務器,那麼即便 app 已經建立,可是沒有被服務器加載到 app 棧中。若這時候運行服務器,則 app 棧中沒有加載 app,當 db.init_app(app)
去 app 棧中取 app 對象的時候沒有成功獲取,因此報錯。
緣由:
狀況一:db = SQLAlchemy(app)
這句代碼運行的時候,會將 app 推到app棧中;db.create_all()
就能夠經過 app 棧獲取到棧頂元素 app 了。db = SQLAlchemy()
時沒有傳入 app,因此沒有將 app 推到棧中;db.init_app(app)
將 app 與 db 綁定時,會去 app 棧中取 app,可是沒有獲取到,因此報錯。解決方法:將 app 手動推到 app 棧中:
# 將 db.create_all() 替換爲如下代碼 with app.app_context(): db.create_all()
這個時候若是咱們的模型(類)要根據需求添加一個做者
字段,這時候咱們須要去修改模型 Article
,修改完成咱們須要再映射一遍。可是對於 flask-sqlalchemy
而言,當數據庫中存在了某個模型(類)後,再次映射不會修改該模型的字段,即再次映射不會奏效。
傳統解決辦法:
在數據庫中刪除該模型對應的表格,再將帶有新字段的模型從新進行映射。
很顯然,這種方式明顯很簡單粗暴,很是不安全,由於在企業中一個數據庫中的表格是含有大量數據的,若是刪除可能會形成重大損失。因此咱們須要一個能夠動態修改模型字段的方法,使用 flask-migrate
。先安裝:在虛擬環境下使用命令 pip install flask-migrate
便可。
flask-migrate
動態修改模型字段使用 flask-migrate
的最簡單方法是:藉助 flask-script
使用命令行來對 flask-migrate
進行操做。一共有好幾個步驟,分別說明一下:
新建文件 manage.py
:
新建 manage.py
文件後:
導入相應的包並初始化 manager
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app)
要使用 flask_migrate
必須綁定 app
和 db
migrate = Migrate(app,db)
把 MigrateCommand
命令添加到 manager
中
manager.add_command('db',MigrateCommand)
manage.py
文件代碼以下:
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app) # 1. 要使用 flask_migrate 必須綁定 app 和 db migrate = Migrate(app,db) # 2. 把 MigrateCommand 命令添加到 manager 中 manager.add_command('db',MigrateCommand) if __name__ == '__main__': manager.run()
主 app 文件不須要再對模型進行映射,因此能夠將如下語句給刪除:
with app.app_context(): db.create_all() # 將模型映射到數據庫中
在 manage.py
文件中,導入須要映射的模型(類):
由於在主 app 文件中已經再也不須要對模型進行映射,而對模型的操做是在 manage.py
文件中進行的,包括 flask-migrate
動態映射,因此要導入須要映射的模型。
from models import Article
完成以上步驟後,便可到命令行中更新數據庫中的模型了:
python manage.py db init
,初始化 flask-migrate
環境,僅在第一次執行的時候使用。python manage.py db migrate
,生成遷移文件python manage.py db upgrade
,將遷移文件映射到數據庫的表格中背景知識:HTTP 協議是無狀態的,每次鏈接斷開後再從新鏈接,服務器是不記得瀏覽器的歷史狀態的。也就是說:即便第一次瀏覽器訪問服務器時登錄成功後,只要斷開與服務器的鏈接,服務器就會忘記瀏覽器的信息,那麼當瀏覽器與服務器再次成功鏈接時,服務器不知道瀏覽器的相關信息。
cookie
而 cookie 的出現就是爲了解決這個問題:當瀏覽器第一次在服務器上登陸成功時,服務器會將一些數據(cookie)返回給瀏覽器,而後瀏覽器將 cookie 保存在本地,第二次訪問服務器時瀏覽器會自動地將上次存儲的 cookie 發送給服務器,服務器經過 cookie 判斷瀏覽器的信息從而提供更好的服務(如自動記錄用戶上次瀏覽的位置等)。
服務器和瀏覽器交互 cookie 的過程對用戶而言是透明的。
cookie 信息保存在用戶的瀏覽器中。
session
session 的做用和 cookie 相似,都是爲了保存用戶的相關信息。不一樣的是:cookie 是將用戶的信息保存在用戶的瀏覽器本地,而 session 是將用戶的信息保存在服務器上。相對而言,session 比 cookie 更加安全和穩定,可是會佔用服務器的一些資源,不過影響不大。同時,session 還支持設置過時時間,也從另外一方面保證了用戶帳號的安全。
flaks 中 session 的工做機制是:服務器將用戶的敏感信息加密後存入 session 中,而後把 session 放在 cookie 中,再將 cookie 返回給瀏覽器,下次瀏覽器訪問的時候將 cookie 發送給服務器,服務器再根據瀏覽器發送的 cookie 取出裏面的 session,再從 session 中取出用戶的信息。
這樣作能夠節省服務器的一些資源。在安全問題上,由於用戶的信息先通過加密再存入 session 中的,因此仍是有必定的安全性能的。
想要在 flask
中操做 session
,咱們必須先導入 session
模塊:from flask import session
。
當用戶訪問某一個 URL
的時候,相應的視圖函數就會有相應的動做,咱們能夠在視圖函數中進行 session
的操做。實際上操做 session
的方法和操做字典是同樣的。
不過對 session
進行操做以前,要先設置一個 SECRET_KEY
,這個 SECRET_KEY
就是用來對用戶的信息進行加密的密鑰,加密後的數據再保存到 session
中。設置 SECRET_KEY
的方法有兩種:
- 新建一個
config
文件,在config
文件中添加SECRET_KEY = 'xxx'
這條語句,再在主 app 文件中使用config
文件的配置app.config.from_object(config)
就好了。- 直接在主 app 文件中:
app.config['SECRET_KEY'] = 'xxx'
但實際上,SECRET_KEY
要求用 24 個字符來賦值,咱們可使用 os 模塊,即 import os
。
session 添加數據:
session['username'] = 'myyd'
session 獲取數據:
有 2 種方式,第一種和操做字典的方法同樣:return session['username]
;第二種是用session
中的 get() 方法:session.get('username)
。
推薦使用第二種方式:session.get('username')
,由於當 username
不存在時,第一種方法會拋出異常,而第二種方法是返回 None。
session 刪除數據:
session.pop('username')
對於 session
的數據刪除操做,咱們能夠設計一個小實驗,即在刪除先後分別打印 session
中的數據,看看結果如何。
session 清除全部數據:
session.clear()
一樣能夠設計一個和刪除數據同樣的小實驗,來對清除操做進行驗證。
源代碼以下:
# encoding:utf-8 from flask import Flask,session import os # 該模塊用於生成隨機字符串 app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) # SECRET_KEY 爲隨機生成的字符串 @app.route('/') def index(): session['username'] = 'myyd' # 添加 session 數據 return 'index' @app.route('/get/') def get(): username2 = session.get('username') # 獲取 session 數據 return username2 @app.route('/delete/') def delete(): print session.get('username') session.pop('username') # 刪除 session 中指定的數據 print session.get('username') return 'success.' @app.route('/clear/') def clear(): print session.get('username') session.clear() # 清除 session 中全部的數據 print session.get('username') return 'success.' if __name__ == '__main__': app.run(debug=True)
以上是對 session 的一些基本操做。
同時還須要注意一點的是:
若服務器重啓後,且瀏覽器沒有從新獲取 session 時,咱們直接去獲取瀏覽器的 session,會報錯!
ValueError: View function did not return a response
,提示視圖函數沒有返回值。
這是由於:python 是腳本語言,運行時從頭開始解釋,則當服務器重啓後,SECRET_KEY
會被重置,服務器想要讀取瀏覽器發送過來的 session 中的數據時,會使用新的 SECRET_KEY
進行解密,而新的 SECRET_KEY
並不可以對 session 中的數據進行解密,因此服務器獲取失敗,不能返回值。
GET 和 POST 的區別:
使用場景
GET 請求:單純從服務器獲取資源,沒有對服務器產生影響,即沒有對服務器的數據進行修改。
POST 請求:從服務器獲取資源,而且傳給服務器一些須要處理的數據。服務器對瀏覽器傳過來的數據進行相關邏輯判斷或者處理後返回給瀏覽器。此時是對服務器產生了某些影響的,而不是單純的訪問瀏覽器某些資源。
傳參
GET 請求:請求的參數跟在 URL 後面並用 ?
號鏈接。
POST 請求:請求的參數不是放在 URL 後,而是將參數放在 Form 表單中。
經過一個小例子來幫助理解:
在 index.html
首頁設置一個 <a>
標籤,跳轉至 search
的頁面,同時在該 <a>
標籤中攜帶參數 q=hello
,在服務器端獲取瀏覽器請求的參數並返回給瀏覽器。
先建立一個首頁 index.html
並在主 app 程序中用 render_template
返回首頁:
from flask import render_template # 導入該特性 return render_template('index.html') # 在視圖函數 index 中返回首頁
同時在首頁用 <a>
標籤跳轉到 search
頁面,並攜帶參數傳給服務器的 search()
視圖函數。
<a href="{{ url_for('search',q=hello) }}">點擊查詢</a>
而後在主 app 程序的 search 視圖函數中,用 request 對象獲取用戶提交的值,不過要先從 flask 中導入該特性
from flask import request # 導入 return 'the parameter you have submit is:%s' % request.args.get('q') # 獲取並返回
注意這個 request 對象:
request 能夠獲取用戶提交的請求參數,包括 GET 和 POST 提交的參數信息。reques 對象實際上一個字典,其獲取的數據是以字典的格式保存下來的。以下所示:
# 若是提交的是這樣的數據 request.args = { 'q':'hello' 'a':'world' } # 那麼打印出來是下面這個樣子的 print requesr.args ImmutableMultiDict([('q', u'hello'), ('a', u'world')])
因此咱們能夠向字典同樣經過 request.args.get('q')
來獲取用戶提交的參數。
參考代碼以下:
# 主 app 文件 from flask import Flask,request,render_template @app.route('/') def index(): return render_template('index.html') @app.route('/search/') def search(): return u'你要查詢的參數是:%s' % request.args.get('q') # index.html 文件 <a href="{{ url_for('search',q='hello') }}">點擊查詢</a>
POST 請求能夠藉助模擬登陸的過程來作一個小實驗:在 login.html
中建立一個表單,該表單要指定後臺處理的視圖函數,同時還須要指定提交請求的方式爲 POST。還要在主 app 程序的視圖函數中顯式地標註該視圖函數支持 POST 和 GET。
在 login.html
中建立表單:
建立代碼略,後面一塊兒給出。
若是僅僅作了 render_template
的映射和 url_for
的反轉,還不夠,由於視圖函數 login()
還未支持 POST
請求方式,而默認的視圖函數只能支持 GET
請求。因此還須要進行以下配置:
@app.route('/login/',method=['GET','POST'])
須要注意的是:
咱們能夠在 login()
這個視圖函數中根據用戶的請求方式來判斷用戶是否登陸,從而返回不一樣的內容。
由於用戶請求 login
這個頁面的時候是用 GET,而在 login
頁面中進行登陸所提交的請求是 POST。獲取用戶請求提交的方法,也是用 request 對象:request.methods
這裏要注意,咱們獲取 POST 請求的參數時,雖然也是用 request 對象,可是這裏應該用 request.form.get('username')
,並且在 login.html
的表單中,須要爲 <input>
標籤指定名字這樣才能夠在後臺經過 name
獲取到內容。因此 login()
視圖函數應該這麼寫:
@app.route('/login/,methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: return u'你好,%s' % request.form.get('username')
源代碼:
1. 主 app 文件: # encoding:utf-8 from flask import Flask,render_template,request app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/search/') def search(): return u'你要查詢的參數是:%s' % request.args.get('q') @app.route('/login/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: return u'你好,%s' % request.form.get('username') if __name__ == '__main__': app.run(debug=True) 2. index.html 文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <a href="{{ url_for('search',q='hello') }}">點擊查詢</a> <a href="{{ url_for('login') }}">點擊登陸</a> </body> </html> 3. login.html 文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="{{ url_for('login') }}" method="post"> <table> <tbody> <tr> <td>用戶名:</td> <td><input type="text" placeholder="請輸入用戶名" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" placeholder="請輸入密碼" name="password"></td> </tr> <tr> <td></td> <td><input type="submit" value="登陸"></td> </tr> </tbody> </table> </form> </body> </html>
獲取一切和請求相關的信息,使用 request
對象。如:
獲取請求方法:request.method 獲取GET的參數:request.args 獲取POST的參數:request.form
對於須要支持 POST 請求的視圖函數,要在其裝飾器上顯式指定:
@app.route('/login/',methods=['GET','POST'])
而且要在模板裏 <input>
標籤中,用 name
來標識 value
的 key
,方便後臺獲取。同時在寫表單 form
的時候要指定 action
爲用來處理的視圖函數名。
鉤子函數能夠插入到執行過程之間,即在正常的程序執行過程當中插入鉤子函數的執行。舉歌例子,原來是執行了 A 函數後就接着執行 B 函數,即執行過程爲 A->B
;可是因爲一個鉤子函數 C 的介入,那麼執行過程改變爲 A->C->B
。這裏介紹 2 個鉤子函數 before_request
和 context_processor
。
before_request
顧名思義,就是在 request
執行以前執行,實際上就是在視圖函數執行以前執行的。不過 before_request
和 route
同樣,只是一個裝飾器,它的做用就是把須要的在視圖函數執行以前執行的動做放入一個函數並對該函數實現'鉤子'
的功能。
在執行每一個視圖函數以前都先執行 before_request
定義的函數。
經過一個小實驗,就能知道 before_request
的功能了:
from flask import Flask app = Flask(__name__) @app.route('/') def index(): print 'index' return 'index' @app.before_request def My_before_request(): print 'hello,world' if __name__ == '__main__': app.run(debug=True)
能夠在 pycharm
的控制檯中看到:先打印了 hello,world
,纔打印的 index
;說明了 My_before_request()
是在 index
以前執行的。
這邊擴展一下小案例。
需求:網站中有一個look
頁面須要登陸以後才能訪問,利用 before_request
來實現對用戶判斷用戶是否登陸。
思路:
定義一個首頁 index.html
,一個登陸頁面 login.html
和一個查看頁面 look
以及他們對應的視圖函數。
在 index
視圖函數中返回 index.html
頁面,並在 index.html
中定義兩個 <a>
標籤用以分別跳轉至 login.html
和 look.html
。
<a href="{{ url_for('login') }}">點擊登陸</a> <a href="{{ url_for('look') }}">點擊查看</a>
在 login
視圖函數中對請求的方法作判斷:
GET
方法表明用戶須要獲取到這個頁面,則返回 login.html
頁面讓用戶登陸;
POST
方法表明用戶提交了登陸的信息,則判斷用戶是否合法;若合法則發送 session,若非法則提示非法。
@app.route('/login/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: username = request.form.get('username') password = request.form.get('password') if username == 'MYYD' and password == '123456': session['username'] = username return u'登陸成功!' else: return u'用戶名或密碼錯誤!'
在 before_request
中定義 My_before_request
,而後取出 session
的信息放入對象 g
中。
@app.before_request def My_before_request(): if session.get('username'): g.username = session.get('username')
在 look
是視圖函數中,判斷根據對象 g
提供的信息判斷該用戶是否已經登陸,若登陸則返回 look.html
頁面;若沒登陸則重定向至 login.html
頁面。
@app.route('/look/') def look(): if hasattr(g,'username'): return u'修改爲功!' else: return redirect(url_for('login'))
完整代碼以下:
# hook.py from flask import Flask,render_template,request,session,redirect,url_for,g import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) @app.route('/') def index(): return render_template('index.html') @app.route('/login/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: username = request.form.get('username') password = request.form.get('password') if username == 'MYYD' and password == '123456': session['username'] = username return u'登陸成功!' else: return u'用戶名或密碼錯誤!' @app.route('/look/') def look(): if hasattr(g,'username'): return u'修改爲功!' else: return redirect(url_for('login')) @app.before_request def My_before_request(): if session.get('username'): g.username = session.get('username') if __name__ == '__main__': app.run(debug=True) # index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <a href="{{ url_for('login') }}">點擊登陸</a> <a href="{{ url_for('look') }}">點擊查看</a> </body> </html> # login.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form action="" method="post"> <table> <tbody> <tr> <td>用戶名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"></td> </tr> <tr> <td></td> <td><input type="submit" value="登陸"></td> </tr> </tbody> </table> </form> </body> </html>
在首頁的模板 index.html
中,先粘貼如下內容:
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
在 v3.bootcss.com
中的組件下找本身想要的導航條