如果你也想寫Flask
上次我在編程派發表了一篇關于如何備份文件至七牛的文章,不說好壞,文章發表出去之后我思考了很多,最重要的一點是:如果我是讀者,我會愿意閱讀完整這篇文章嗎?
如果自己是讀者該怎樣去文章,我是為了什么去讀文章,讀文章之后我能獲得什么?解決現有的問題?還是照搬一些代碼到自己的程序上?我們到底為什么要寫代碼和閱讀?我最近一直在思考這些問題。
所以,今天我想以一個全新的視角去寫一篇文章,也許會不成功。但我想嘗試一下不同的寫作視角。
本文的標題是《如果你也在寫Flask》。顧名思義,這是一篇關于Flask開發的文章。本文將包含但不限于以下內容:
- Flask是什么
- 使用Flask制作自己的博客
- 擴展性到底在哪里
- 從0到1該如何學習
Flask是什么
Flask是由Python語言編寫開發而成的輕量級
Web服務框架,Flask是由Armin Ronacher制造的一個愚人節玩笑而發展至今。關于Flask更多權威的介紹請訪問Wiki瀏覽-點擊訪問。
EarlGrey:我在這篇文章 >>> 這可能是開發者社區最成功的愚人節玩笑 中較為詳細地介紹了Flask背后的由來。
我心中的Flask是什么
Flask的快捷輕便可擴展性高的優點,可供于我想到什么去開發什么的想法,我不用考慮太多。只用想現在我可能想要做一個什么了,那么Flask就可以做到。Flask擁有太多擴展包,你只需要了解這些擴展包的使用方法就可以做到很多你意想不到的功能。
Flask是基于Python所編寫的快捷Web框架,那出現一個疑問了,Flask和Python到底有多大的關聯?這是一個很深入的問題,而我的理解是Flask即Python,Python非Flask,而我也不會去解釋為什么。因為這是每個人的看法,我不想每位看文章的朋友因為閱讀了我的文章就給思維上了個鎖,我發現太多文章都時讀者在閱讀的過程中把思維給鎖住了,這非常影響閱讀者的思考。
當然,我并不是說Flask不可以構建大型項目,而大型項目的構建準備工作需要的更多,這些并不在本文的討論范圍之內。以后如果有時間可以跟大家再來探討“該怎樣去思考構建大型項目”。
現在,請思考你對于Flask的理解是?
使用Flask開發自己的博客
本章可能會遇到很多困難,在實際操作過程當中如果遇到問題,建議使用Google去搜索問題,搜索時注意關鍵詞的使用可以有助你更好尋找到問題的答案,例如搜索“Flask-Login 文檔”,“Flask-SQLalchemy 字段說明”等等……又或者給我發mail或者去sf.gg問一下問題,發問時請不要用一些愚蠢的提問方式例如“請問我這里是哪里錯了?”,“這個錯誤該如何解決?”,我們應在標題寫入對問題的精要部分,使得為你解決問題的人們更有興致的幫助你。
這里我推薦一本書學會提問,如果你有時間可以下載到Kindle或者手機閱讀,你當然可以作為廁所讀物。慢慢看,細細品。
好了,進入正題,構建Flask-Blog的流程分為以下步驟:
- 搭建Flask開發環境 / 完全說明
- 思考數據庫模型 / 完全說明
- 編寫邏輯代碼 / 完全說明
- 測試、完善 / 簡單描述
- 部署到公網服務器 / 簡單描述
- 開始擴展你的Blog程序/ 自己完成
所有步驟的注明,會和本文密切相關。我希望讀者們可以邊看邊互動,這樣才會有學習的意義。
搭建Flask開發環境
無論你是使用Linux,Mac,Windows搭建環境都是很輕松的,閱讀官方文檔就能做到環境的部署這里我在說明一遍。
安裝virtualenv
Linux and Mac:
sudo pip install virtualenv
Windows:
首先需要把Python根目錄下的Script目錄指定到系統PATH內,然后執行。最重要的是,你需要在windows下安裝GIT,利用Git bash來代替原始CMD。
easy_install pip #安裝pip
pip install virtualenv
創建項目文件夾
Mac、Linux
mkdir -p ~/Document/flask-bb & cd ~/Document/flask-bb #創建文件夾并移動到文件夾
virtualenv venv #創建virtualenv獨立環境
Windows
在你想要的盤符創建一個文件夾名為flask-bb,路徑中不要帶中文。
在項目文件夾內右擊選擇Git Bash Here
virtualenv venv #創建virtualenv獨立環境
使用virtualenv
Mac、Linux在項目目錄下輸入
. venv/bin/activate #注意前面有個 . 并空格
終端會進入virtualenv環境,并在提示符最前面加入(venv)
Windows系統下,同樣在項目文件夾內打開Git Bash
. venv/Script/activate #注意 . 和空格
現在我們已經成功的部署并使用到了virtualenv環境,至于virtualenv到底有什么用呢?它其實就是一個便捷的Python虛擬環境,因為Flask的特性,每個項目里都會有不同擴展包來擴展項目本身。為了潔癖精神,不把每一個使用的擴展包都安裝到根Python環境里。所以我們進行了一個小型的虛擬Python環境,讓這些針對于當前項目的Flask擴展包得以應用安裝。
提示:virtualenv不僅僅不適用于Flask,還可以是任何Python的開發環境,只要你有需求。你可以針對你不同的項目設定不同的virtualenv環境。
你需要一個數據庫
在本文中,我選擇使用Mysql作為數據庫,雖然sqlite更簡單,可隨意創建文件,有了問題直接刪除。但為了我們在本地開發和實際部署在生產保持數據庫的一致,所以在本地和服務器上都使用Mysql,而基于Mysql的GUI管理軟件也有很多,大家自行搜索一下就可以在自己的系統環境中安裝好Mysql。
本次我使用的是Mysql 5.6版本,并創建一個數據庫編碼格式為utf8mb4
默認排序規則為utf8mb4_bin
的數據庫表。建議創建一個新的賬戶來管理此數據庫表。
運行Flask
部署好Flask環境后,我們需要怎樣去運用它呢?
首先我們需要安裝Flask。進入virtualenv環境后,運行如下命令:
pip install flask #安裝Flask
pip install flask-script #安裝Flask-Script 來代替原生啟動管理
pip install flask-SQLAlchemy #安裝Flask-SQLAlchemy來管理數據庫
pip install mysql-python #安裝mysql-python驅動數據庫
接下來要特別注意,對于我們現在所需求的FlaskBlog的全部功能的代碼都可以寫到一個.py文件里,但我非常不推薦這樣(我相信也沒人會推薦這么做)。如果這樣寫非常不便于擴展功能面,本文全部描述的功能雖然僅限于非常基礎的內容,但想要擴展是非常容易的,而把整個項目的文件及文件夾規劃好了,更便于我們后期再次擴展開發時的效率!
以下文章所有文件,我會基于根目錄來標注文件路徑。
例如:
/config.py
則在項目目錄根上。
/app/main.py
則在項目目錄創建一個app的文件夾下創建main.py
開始編寫吧!
/config.py
# -*- coding=utf-8 -*-
'''
要注意的是,這里可以寫入多個配置,就仿照DevelopmentConfig這個類一樣,繼承Config類即可。
并在最下方的Config字典里添加對應的key:value。
'''
class Config:
SECRET_KEY = '' #填入密鑰
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = True
@staticmethod
def init_app(app):
_handler = RotatingFileHandler(
'app.log', maxBytes=10000, backupCount=1)
_handler.setLevel(logging.WARNING)
app.logger.addHandler(_handler)
class DevelopmentConfig(Config):
SQLALCHEMY_DATABASE_URI = 'mysql://flask:flask@127.0.0.1/flask_dev'
#SQLALCHEMY鏈接數據庫都是以URI方式格式為'mysql://賬戶名:密碼@地址/數據庫表名'
config = {
'default': DevelopmentConfig
}
/manage.py
:
# -*- coding=utf-8 -*-
from app import create_app, db
from flask.ext.script import Manager, Shell
app = create_app('default')
manager = Manager(app)
if __name__ == '__main__':
manager.run()
/app/__init__.py
# -*- coding=utf-8 -*-
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from config import config
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
/app/main/__init__.py
# -*- coding=utf-8 -*-
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errors
/app/errors.py #創建該文件
/app/views.py
# -*- coding=utf-8 -*-
from . import main
@main.route('/')
def index():
return 'Hello World'
現在,在項目根目錄下執行python manage.py runserver
即可成功運行Flask,并訪問127.0.0.1:5000。
好了,我們完成了第一步,在第一步中我們使用Flask的特性藍圖實現模塊化應用,例如我們剛創建的藍圖main
就作為我們展示模塊,當有訪客瀏覽我們Blog的時候都是main
在工作,而我們在后臺編寫文章時就不能使用該藍圖了,應該創建一個新的如admin
來模塊化運行。藍圖有很多特性,可以點擊上方的鏈接瀏覽官方藍圖文檔來讓自己更了解藍圖的特性。
思考數據庫模型
開始想象
為什么這一段我要讓大家開始想象數據庫模型,無論你是否有過開發經驗,對數據庫的了解或對任何的了解。你在一開始都要定義數據庫模型,現在我們就開始想象一下到底需要哪些數據庫模型。
Blog都有哪些內容?站點名字、介紹、文章標題、內容、分類、評論、標簽。嗯,還得帶個用戶。~~要不然怎么識別自己是管理員呢?總不能輸入一個通用密碼登入吧?對這里提醒了我們,一個人的Blog到底需不需要所謂的“管理員賬戶”既然只有我們知道管理員地址,也只有我一個人發表文章,為什么我們還需要用戶呢?我們形成一個專有密碼不就可以作為后臺登入的鑰匙了嗎?也許這會降低所謂的安全性,但我認為是一個非常好的想法。你既然都是一個人寫了,干嘛不要更簡單點的登入模式?
如刪除線內容一樣,我在寫文章的時候最初考慮的是用一個簡單的一串密鑰密碼來驗證后臺管理,但我在寫的過程當中考慮到了為了能在本文中對于用戶登入注冊這一塊產生一塊小的思考點,會使讀者在后期自己開發時有更好的思維方向。
OK,我來一點一點解釋需求的字段到底該不該保留。站點名字和介紹我們可以寫在Base.html模板里把,文章標題、分類、內容、標簽是需要的,但評論是否需要嗎?現在有很多評論系統,國內的多說,國外的Disqus都可以引用到Blog里作為評論,那我是個懶人不想寫標簽這一塊的字段,就留給你們最后去自己思考怎么寫吧。
按我上面所說的,我們定義一個極簡Blog大概就需要3張表“文章表”、“分類表”、“用戶表”用來儲存標題、內容、分類然后進行關聯,而登入我們使用flask-login擴展來幫助我們完成自己定義好模型即可,評論用第三方擴展來處理,標簽自己想辦法。
注:不要被我的思維而定死了,請盡情想象,如果手邊有筆紙我建議你寫下來。并用關聯線去自由的關聯你想要產生出關系的數據庫字段。
文章表
字段名 | 屬性 | 說明 |
---|---|---|
id | 數字 | 主鍵字段,自增 |
title | 文本 | 標題字段 |
body | 多行文本 | 內容字段 |
create_time | 時間 | 創建文章時間 |
user_id | 數字 | 關聯用戶表主鍵ID |
category_id | 數字 | 關聯分類表主鍵ID |
分類表
字段名 | 屬性 | 說明 |
---|---|---|
id | 數字 | 主鍵字段,自增 |
name | 文本 | 分類名字 |
用戶表
字段名 | 屬性 | 說明 |
---|---|---|
id | 數字 | 主鍵字段,自增 |
username | string | 用戶名字段 |
password | string | 密碼字段 |
real_name | string | 真實姓名 |
在三個表之間我們有兩個一對多的關系,也就是一(分類)對多(文章)。現在再想想有什么更好的點子?或者你需要的字段更多?這些你都可以盡可能的發揮自己的想象及聯合你本身實際的需求來增減。
那么我們把字段定出來了,怎么寫到Flask生成數據庫呢?
/app/models.py
# -*- coding=utf-8 -*-
from . import db
from datetime import datetime
class Article(db.Model):
__tablename__ = 'articles'
id = db.Column(db.Integer, primary_key=True)
titile = db.Column(db.String(64), unique=True)
body = db.Column(db.Text)
create_time = db.Column(db.DATETIME, default=datetime.utcnow())
category_id = db.Column(db.Integer, db.ForeignKey('categorys.id'))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
class Category(db.Model):
__tablename__ = 'categorys'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
articles = db.relationship('Article', backref='category')
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
password_hash = db.Column(db.String(128))
real_name = db.Column(db.String(64), unique=True)
articles = db.relationship('Article', backref='user')
我寫了三個類,分別為文章、分類、用戶,其中有兩條一對多的關系。 關于一對多、多對多的關系詳細解釋可以去官方文檔進行查閱了解。 接下來我們來把我們定義好的數據庫表寫入到數據庫。
pip安裝
pip install flask-migrate #使用migrate來管理升級遷移數據庫
編輯 /manage.py
# -*- coding=utf-8 -*-
from app import create_app, db
from app.models import Article , Category , User #注冊數據庫模型
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand #載入migrate擴展
app = create_app('default')
manager = Manager(app)
migrate = Migrate(app, db) #注冊migrate到flask
manager.add_command('db', MigrateCommand) #在終端環境下添加一個db命令
if __name__ == '__main__':
manager.run()
接下來,我們到終端里依次輸入
python manage.py db init
python manage.py db migrate -m "first init db"
python manage.py db upgrade
'''
單項目內只需要第一次執行db init,如果你在未來的日子里需要修改models.py并使其生效,只需要在改過models.py后執行指令的后面2步即可。
'''
現在你用任何一個可視化或終端去檢查一下mysql數據庫是否成功創建了數據庫表。
順帶我解釋一下unique、primary_key、default這些關鍵字:
unique是否允許重復,如果為True則在該表內不允許該字段用重復的數據出現。 primary_key是否為主鍵 default默認值
到這里,我們已經完成了兩項任務。從中我們學習到了如何用virtualenv來部署flask環境,學習到了藍圖模塊及設計數據庫。我希望到這里你的數據庫跟我設計的不一樣,實例代碼只是為了完成演示步驟。而在閱讀的你,我希望你真的能夠通過自己的想象去擴建一些字段,并自己理解字段中一些關鍵詞的意義。
編寫邏輯代碼
設想一下,我們大概需要哪些邏輯代碼,吶我想想。
- 文章展示頁
- 文章詳情頁
- 后臺登入
- 增、改、刪文章、分類
在這里我只提供增、刪的代碼塊,改這個代碼塊作為練習題由讀者自己完成。
喏,這些應該就差不多了吧。 一步一步來,按道理我們應該先寫后臺。有后臺,就應該有注冊和登入,那注冊開放了,怎么去解決任何人都可以注冊的問題呢?有2種辦法。
-
擁有注冊碼的才可以注冊(你可以在文件內自定一串隨機碼作為注冊碼)
-
你可以完成注冊后,注釋掉注冊邏輯代碼。
當然這里我推薦第一種啦,因為比較簡單,而且如果你想和你的朋友一起寫這個博客,就不需要那么麻煩。只需要在告訴他你的注冊碼是多少就行了。
寫好了后臺之后,我們就應該要能增、改、刪文章或者分類,我們來一步一步設計我們所需求的功能。
編寫后臺模塊
pip安裝
pip install flask-wtf #安裝flask-wtf表單快速渲染生成
pip install WTForms-SQLAlchemy #安裝flask-wtf-sqlalchemy用于通過數據庫數據返回生成表單內容
pip install flask-bootstrap #安裝flask-bootstrap,快速渲染bootstrap樣式頁面。
pip install flask-login #安裝flask-login 用于登入及權限管理的擴展
修改-> /app/__init__.py
# -*- coding=utf-8 -*-
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.bootstrap import Bootstrap #引入Flask-Bootstrap
from flask.ext.login import LoginManager #引入Flask-Login
from config import config
db = SQLAlchemy() #實例化對象
bootstrap = Bootstrap() #實例化對象
login_manager = LoginManager() #實例化對象
login_manager.session_protection = 'strong' #設置flask-login session等級
login_manager.login_view = 'admin.login' #如果未登入轉跳到指定方法
login_manager.login_message = u'請登入賬號再進行下一步操作!' #未登入提示語
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
bootstrap.init_app(app)
login_manager.init_app(app)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .admin import admin as admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
'''
載入一個名為'admin'的藍圖作為后臺管理模塊
'''
return app
新建-> /app/admin/__init__.py
# -*- coding=utf-8 -*-
from flask import Blueprint
admin = Blueprint('admin', __name__)
from . import views, errors
'''
前面我們載入了一個名為admin的藍圖模塊
在這里我們需要構建這個模塊
'''
新建-> /app/admin/forms.py
# -*- coding=utf-8 -*-
from flask.ext.wtf import Form
from ..models import Category
from wtforms import StringField, SubmitField, PasswordField, TextAreaField
from wtforms.validators import Required, length, Regexp, EqualTo
from wtforms.ext.sqlalchemy.fields import QuerySelectField
class LoginForm(Form):
username = StringField(u'帳號', validators=[Required(), length(6, 64)])
password = PasswordField(u'密碼', validators=[Required()])
submit = SubmitField(u'登入')
class RegistrationForm(Form):
username = StringField(u'用戶名', validators=[Required(), length(6, 18), Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, u'用戶名只允許字母',u'用戶名不允許特殊符號')])
password = PasswordField(
u'密碼', validators=[Required(), EqualTo('password2', message=u'密碼錯誤提示1')])
password2 = PasswordField(u'重復密碼', validators=[Required()])
real_name = StringField(u'昵稱', validators=[Required()])
registerkey = StringField(u'注冊碼', validators=[Required()])
submit = SubmitField(u'注冊')
class PostArticleForm(Form):
title = StringField(u'標題', validators=[Required(), length(6, 64)])
body = TextAreaField(u'內容')
category_id = QuerySelectField(u'分類', query_factory=lambda: Category.query.all(
), get_pk=lambda a: str(a.id), get_label=lambda a: a.name)
submit = SubmitField(u'發布')
class PostCategoryForm(Form):
name = StringField(u'分類名', validators=[Required(), length(6, 64)])
submit = SubmitField(u'發布')
forms.py里構建表單內容,然后在前端渲染時直接調用對應form即可快速生成表單內容。
在PostArticleForm
表單里我們更引入了wtf-sqlalchemy的特性,通過查找數據庫內容來生成對應的select表單。
修改-> /app/models.py
*
# -*- coding=utf-8 -*-
from . import db, login_manager
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash # 引入密碼加密 驗證方法
from flask.ext.login import UserMixin # 引入flask-login用戶模型繼承類方法
class Article(db.Model):
__tablename__ = 'articles'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(64), unique=True)
body = db.Column(db.Text)
create_time = db.Column(db.DATETIME, default=datetime.utcnow())
category_id = db.Column(db.Integer, db.ForeignKey('categorys.id'))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
class Category(db.Model):
__tablename__ = 'categorys'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
articles = db.relationship('Article', backref='category')
class User(UserMixin, db.Model):
# 在使用Flask-Login作為登入功能時,在user模型要繼承UserMimix類.
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
password_hash = db.Column(db.String(128))
real_name = db.Column(db.String(64), unique=True)
articles = db.relationship('Article', backref='user')
@property
def password(self):
raise AttributeError(u'密碼屬性不正確')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
# 增加password會通過generate_password_hash方法來加密儲存
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
# 在登入時,我們需要驗證明文密碼是否和加密密碼所吻合
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
新建-> /app/admin/views.py
編寫路由視圖代碼塊,包括登入、注冊、增刪文章、分類業務。
# -*- coding=utf-8 -*-
from . import admin
from flask import render_template, flash, redirect, url_for, request
from flask.ext.login import login_required, current_user, login_user, logout_user
from forms import LoginForm, RegistrationForm, PostArticleForm, PostCategoryForm
from ..models import User, Article, Category
from .. import db
@admin.route('/')
def index():
return render_template('admin/index.html')
@admin.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user)
return redirect(request.args.get('next') or url_for('admin.index'))
flash(u'用戶密碼不正確')
return render_template('admin/login.html', form=form)
@admin.route('/register', methods=['GET', 'POST'])
def register():
register_key = 'zhucema'
form = RegistrationForm()
if form.validate_on_submit():
if form.registerkey.data != register_key:
flash(u'注冊碼不符,請返回重試.')
return redirect(url_for('admin.register'))
else:
if form.password.data != form.password2.data:
flash(u'兩次輸入密碼不一')
return redirect(url_for('admin.register'))
else:
user = User(username=form.username.data, password=form.password.data, real_name=form.real_name.data)
db.session.add(user)
flash(u'您已經注冊成功')
return redirect(url_for('admin.login'))
return render_template('admin/register.html', form=form)
@admin.route('/logout')
@login_required
def logout():
logout_user()
flash(u'您已經登出了系統')
return redirect(url_for('main.index'))
@admin.route('/article', methods=['GET', 'POST'])
@login_required
def article():
form = PostArticleForm()
alist = Article.query.all()
if form.validate_on_submit():
acticle = Article(title=form.title.data, body=form.body.data, category_id=str(form.category_id.data.id),
user_id=current_user.id)
db.session.add(acticle)
flash(u'文章添加成功')
redirect(url_for('admin.index'))
return render_template('admin/article.html', form=form, list=alist)
@admin.route('/article/del', methods=['GET'])
@login_required
def article_del():
if request.args.get('id') is not None and request.args.get('a') == 'del':
x = Article.query.filter_by(id=request.args.get('id')).first()
if x is not None:
db.session.delete(x)
db.session.commit()
flash(u'已經刪除' + x.title)
return redirect(url_for('admin.article'))
flash(u'請檢查輸入')
return redirect(url_for('admin.article'))
@admin.route('/category', methods=['GET', 'POST'])
def category():
clist = Category.query.all()
form = PostCategoryForm()
if form.validate_on_submit():
category = Category(name=form.name.data)
db.session.add(category)
flash(u'分類添加成功')
return redirect(url_for('admin.index'))
return render_template('admin/category.html', form=form, list=clist)
@admin.route('/category/del', methods=['GET'])
@login_required
def category_del():
if request.args.get('id') is not None and request.args.get('a') == 'del':
x = Category.query.filter_by(id=request.args.get('id')).first()
if x is not None:
db.session.delete(x)
db.session.commit()
flash(u'已經刪除' + x.name)
return redirect(url_for('admin.category'))
flash(u'請檢查輸入')
return redirect(url_for('admin.category'))
新建-> /app/templates/base.html
定義基礎模板,從后我們所有的渲染模板都會基于基礎模板來繼續渲染出內容頁面。
{% extends "bootstrap/base.html" %}
{% block title %} Flask-Blog{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<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="/">Blog</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="{{ url_for('main.index') }}">首頁</a></li>
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('admin.index') }}">后臺首頁</a></li>
<li><a href="{{ url_for('admin.article') }}">文章</a></li>
<li><a href="{{ url_for('admin.category') }}">分類</a></li>
{% endif %}
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('admin.logout') }}">登出</a></li>
{% else %}
<li><a href="{{ url_for('admin.register') }}">注冊</a></li>
<li><a href="{{ url_for('admin.login') }}">登入</a></li>
{% endif %}
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}
{% endblock %}
</div>
{% endblock %}
在base.html里,運用到了jinja模板引擎的if語法,并檢測用戶是否登入。如果登入則顯示后臺管理鏈接。并判斷狀態顯示注冊、登入還是登出。
新建-> /app/templates/admin/register.html
前端注冊頁面直接繼承base.html,并在base.html的原有基礎上增加渲染表單,我們在register視圖里返回結果時,增加了注冊表單的返回,所以在前端頁面直接使用flask-wtf特性渲染出來即可使用。
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block page_content %}
<div class="col-md-6">
<h1>注冊賬戶</h1>
<hr>
{{ wtf.quick_form(form) }}
</div>
<div class="col-md-6">
</div>
{% endblock %}
新建-> /app/templates/admin/login.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block page_content %}
<div class="col-md-6">
<h1>登入博客</h1>
<hr>
{{ wtf.quick_form(form) }}
</div>
<div class="col-md-6">
</div>
{% endblock %}
新建-> /app/templates/admin/index.html
{% extends 'base.html' %}
{% block page_content %}
{% if current_user.is_authenticated %}
<p>感謝登入</p>
{% else %}
您還沒有登入,請點擊 <a href="{{ url_for('admin.login') }}">登入</a>
{% endif %}
{% endblock %}
新建-> /app/templates/admin/article.html
新建文章時左側為表單界面,右側為已發表文章表格,可以在表格內點擊鏈接刪除該文章。
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block page_content %}
<div class="col-md-6">
{{ wtf.quick_form(form) }}
</div>
<div class="col-md-6">
<table class="table">
<thead>
<tr>
<th>文章編號</th>
<th>文章標題</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for foo in list %}
<tr>
<td>{{ foo.id }}</td>
<th>{{ foo.title }}</th>
<td><a href="{{ url_for('admin.article_del',id=foo.id,a='del') }}">刪除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
新建-> /app/templates/admin/category.html
{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block page_content %}
<div class="col-md-6">
{{ wtf.quick_form(form) }}
</div>
<div class="col-md-6">
<table class="table">
<thead>
<tr>
<th>文章編號</th>
<th>文章標題</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for foo in list %}
<tr>
<td>{{ foo.id }}</td>
<th>{{ foo.title }}</th>
<td><a href="{{ url_for('admin.article_del',id=foo.id,a='del') }}">刪除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
至此,后臺功能模塊全部制作完成。
如果你現在運行Flask服務器,你訪問127.0.0.1:5000/admin時就可以看到一個提示,提示未登入。接下來您可以去注冊,注冊時需要填寫正確的注冊碼,否則注冊會不成功。
我沒有描寫關于修改部分,而且介于目前網絡流行以markdown的格式去編寫文章,在該程序中我也沒有引入Markdown作為編輯器,這些都可以作為讀者自己的練習題。我相信,只要細心閱讀本文章及本文章代碼的讀者朋友們配合搜索引擎及書籍都可以自己把編輯及Markdown功能寫入到后臺模塊里。我寫本片文章是為了讓大家對于Flask做博客有一定了解,其實也就是讓大家對于Flask的整個項目構造方法及sql增、刪、減作一個了解。真正的網站程序還有很多很多需要大家去自己去研究的地方。
在我編寫的代碼里,有很多是重復代碼及無效代碼。我希望大家在編寫時能盡量優化代碼結構,而不是直接復制粘貼我的代碼。需要讀者去閱讀我的代碼,我為什么這么寫,那我這么寫的作用在哪里。明白代碼的意義之后,開始思考我用什么更好的代碼來代替現有的代碼行才是讀者朋友們該去思考的問題。
寫到這時,我依然在考慮要不要繼續編寫博客文章的展示部分。
我個人認為是沒有必要寫的,因為我在寫后端模塊時所使用的代碼行已經完全的表達了如何從數據庫讀取數據及渲染到前端頁面。完全可以依靠上面的代碼邏輯改改參數和對象作為文章的展示邏輯代碼。
最終我還是決定,寫吧,反正也沒多少了。
編寫前臺模塊
前臺顯示文章這一塊非常非常的簡單。我是希望各位直接略過我這一部分,自己去思考一下該怎么寫。
編輯-> /app/main/views.py
# -*- coding=utf-8 -*-
from flask import render_template, redirect, url_for, flash, request
from . import main
from ..models import Article
@main.route('/')
def index():
a = Article.query.all()
return render_template('index.html', list=a)
@main.route('/read/', methods=['GET'])
def read():
a = Article.query.filter_by(id=request.args.get('id')).first()
if a is not None:
return render_template('read.html', a=a)
flash(u'未找到相關文章')
return redirect(url_for('main.index'))
↑創建2個路由視圖,首頁和詳細頁。
創建-> /app/templates/index.html
{% extends 'base.html' %}
{% block page_content %}
<div class="col-md-8">
{% for foo in list %}
<div class="act">
<h3><a href="{{ url_for('main.read' , id=foo.id) }}">{{ foo.title }}</a></h3>
</div>
{% endfor %}
</div>
{% endblock %}
↓在文章詳細頁表里,需要注意的是我是如何通過數據庫關聯取得到分類名的,我們的數據庫里文章表對應的條目內分類只是一個ID,而我們要通過這個分類ID獲取分類的name,怎么做?看下面的代碼關鍵部分。
創建-> /app/templates/read.html
{% extends 'base.html' %}
{% block page_content %}
<div class="container">
<div class="col-md-12">
<h2>{{ a.title }}</h2>
<hr>
<p>{{ a.create_time }} | {{ a.category.name }}</p>
{{ a.body }}
</div>
</div>
{% endblock %}
到此,一個非常簡單的Blog已經編寫完成。
接下來的文章里,不會再包含任何代碼塊的展示。
至于代碼塊會不會有BUG,寫的好不好,在下面的測試及完善章節里討論。
測試和完善都是什么
測試
其實Flask還有單元測試的方法,我沒有去描述。因為這些不在本文的討論范圍里,我只是陳述如何去寫一個Blog而已。
那么如何測試我們的寫的Blog呢?運行服務器,去注冊賬號,寫文章發布你會發現有問題,為什么?因為你沒有填分類。那么分類是不是一定要寫?Models.py里是怎么定義文章表里的分類字段?Flask會不會報錯??這些都是要你去測試并修復的代碼問題。我說過,我如果寫教程一定不會寫最完美的代碼出來,因為這樣就沒有意思了,當然我認為也并不存在最完美的代碼,而只有適時的代碼。我只想在本文里所有的代碼給與讀者的感覺是比較好理解的。不寫過多復雜的代碼,去避免讀者無謂的復制粘貼。
完善
這個Blog需要完善的地方實在是太多太多了,你可以完善Markdown編輯器、前端樣式、文章編輯、分類名修改功能、引入第三方評論系統等等…………我非常歡迎如果有讀者看我的文章時,一遍看一遍自己思考編寫一個Blog,直至到最后發現有了問題要和我探討,那么請給我發Mail。我會很熱情的回復你的問題,共同探討。 現在那你會說哎,作者你這個坑,你寫的我都懂,那你就告訴我了現在我要去引入一個Markdown編輯器到后臺,我哪里懂?其實很簡單,你去搜索一下關鍵詞,例如‘Flask-Markdown’ 、 ‘Flask使用Markdown’等等關鍵詞找網絡博主們寫的文章或者問答。
不要告訴我你不會使用搜索引擎,這些問題你就算不會KX上網用百度都能解決。更何況用Google?
部署到公網服務器
部署到公網服務器的方法有很多,當然你需要一臺VPS。騰訊云、阿里云各種云或者BAE,SAE這種應用商店。
我不知各位是否對Http服務器有一定了解如果不了解就去了解一下Nginx,你可以直接用Flask-script的管理啟動服務器,然后用Nginx去監聽本地端口映射到外網訪問。也可以用wsgi服務器網關接口去部署Flask Web程序。
這一塊,我只做提示,不做教程。各位自行Google、百度查閱方法。
擴展你的Blog
最后一章,終于快寫完了。這是我第一次寫這么長篇幅的文章。
自己很感謝自己能夠堅持下來,也很感謝我在群內聊天時大家給出的鼓勵和支持。
在最后一章,我會說的比較啰嗦。這也是整篇文章的核心所在。
前面所有的代碼邏輯,我都是為了給大家心里種下一個模子,而Blog只是一個最基本的網絡程序,還有很多很多需要大家去了解的地方。
那么,我們到底該如何擴展我們的Blog?
我給各位打個比喻,例如我現在想要做一個會員系統,包含更詳細的會員資料,拿目前的程序來修改可不可以? 我的回答是絕對可以,我們這么考慮,詳細會員資料就是一個名為info的表,而會員還是保持user表。我們去掉文章及分類表的模型,只保留user info并對user和info編寫一對一關系,每一個info表關聯一個user.id。每次查詢時只需要傳入user.id然后由user.id關聯查詢對應的info表,從而輸出用戶的詳細資料。
這只是非常簡單的一個比喻,對于數據庫關聯方面的擴展。
那我們繼續來設想,還能怎么做?
再來一個栗子,我公司最近要求我寫一個內部OA系統,要求員工可以在網站內在線請假,那么我的用戶系統就是引用的上面我所說的栗子。
而用戶請假這個業務該怎么去編寫代碼?首先我們來理解一下,在現實生活中,紙質的請假條就是作于請假的數據。那么我們要存在網上,請假條就應該是一個數據庫表,而數據庫表里的字段就應該是請假條需要填寫的內容,例如請假時間、回崗時間、請假類別、請假人、創建時間、假條狀態、審批人、審批時間這么些字段,而請假人及審批人的信息我們就可以關聯user.id來進行關聯查詢,假條狀態我們應該是以數字來代替,例如默認是0(待審核)、1(通過)、2(否決)。
請假類別也是數字來代替1(事假)、2(婚假)、3(工傷)、4(產假)、5(病假)等等……我們設定好了字段,在來考慮他該怎樣去產生一個請假條。設想寫請假條跟在后臺寫博客是不是一個道理呢?只是展示的位置不同而已,博客是在前臺可以展示給任何人瀏覽,而請假條必須要有一定權限(主管或經理職位)級別的用戶在后臺的審批界面查看。那么我們是不是可以對我們剛才設計出來的models從而設計一個forms?
接下來在前端渲染出Form,并加上一些訪問權限,有賬戶的才能訪問,無賬戶的則不能訪問該頁面。這個業務流程就完成一半了呢?還差審批,我們知道審批肯定是有一定權限的人才可以去做到。那我們修改user表里增加一個Permission(權限)字段,并設定好字權限等級,例如1(普通)、2(主管)、3(經理)、4(總裁),2能審批1、3能審批2和1、4能審批3、2、1。在用戶訪問路由時直接檢查用戶權限,達到權限范圍的才可以進行訪問。是不是整套流程就清晰了。
這些就叫我所謂的擴展,因為這個Blog只是展示了最基本的sql數據的增、刪過程。不同的網站只是對于數據庫的字段有不同的設定,而針對你自己所需求的內容。你要最早考慮的是數據庫模型,這也是為什么本文中我們在部署好Flask開發環境后,我們第一件事情就是做的思考數據庫模型的原因了。
我相信,在本文發表后的不久。會有不少讀者能夠做出屬于自己的Flask-Blog。
感謝你們的閱讀,謝謝!
來自:http://www.tuicool.com//articles/R3Mzau7