这篇文章教你如何一步一步地使用搭建自己的博客。由于Flask是一个动态网页的Web框架,所以关于Flask搭建静态博客的文章少之又少。(这是Flask的神奇之处,动态静态网页都支持,日后转换时可省去不必要的麻烦)。本系列以Steven loria的Flask静态博客实例为基础并加以改进,增添了一些更加有用的功能并详细介绍
搭建一个Flask静态博客,你需要:
- Python3 (Steven loria采用python2.x)
- Flask
- Frozen-Flask
- Flask-Flatpages
- Pygments(可选,用于代码语法高亮)
- Github 或 Coding
首先来看我们的目录结构:
博客应用的目录结构
| ---project │ |---blog │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── views.py │ │ ├── templates │ │ │ ├── base.html │ │ │ ├── page.html │ │ │ └── posts.html │ │ ├── static │ │ │ ├── home.css │ │ │ └── home.js │ │--- posts │ │ ├── pages │ │ │ ├── firstpost │ │ │ │ ├── index.html │ │ ├── posts.html │ │ ├── index.html │ ├── app.py │ ├── freezer.py
根目录project
在这个目录中project是我们的根目录,根目录包含了:
- blog: 项目主要内容文件夹,包含了模板,样式,和文章文本
- posts: 我们运行生成器(冻结器)后生成的文件
- app.py: 使用Flask动态加载网页
- freezer.py 冻结器,生成静态网站
blog - 生成器应用
作为整个项目最主要的一环,blog包含以下内容:
- static: 包含了css、js、图片等静态网页所需的文件
- templates: 包含了网页的基础模板和子网页模板
- init.py: 应用初始化文件
- settings.py: 配置文件
- views.py: 视图文件,为我们的网站配置了路由
init.py初始化我们的应用
了解init.py基本用法,请自行搜索或移步这里。
首先我们需要导入Flask,FlatPages和Frozen-Flask的模块:
from flask import Flask from flask_flatpages import FlatPages from flask_frozen import Freezer
初始化Flask(app是初始化后的Flask实例):
app = Flask(__name__)
加载配置文件:
app.config.from_pyfile('settings.py')
以创建的Flask实例app为参数初始化Flatpages,Freezer(Frozen-Flask),在这里我们可以看到app是作为参数的,所以表示Flatpages,Freezer只对被传入的app有效果:
articles = FlatPages(app) freezer = Freezer(app)
调用我们的视图文件views
from blog import views
最后在这里我们调用了blog文件夹下的视图脚本views,由于这里是循环调用(init.py -> 调用views ->调用init.py),所以在调用views之前必须先把所有内容初始化好,否则当views调用init.py时找不到实例会报错。
下面是init.py完整代码
from flask import Flask from flask_flatpages import FlatPages from flask_frozen import Freezer #init the flask app = Flask(__name__) #load the settings from .py file app.config.from_pyfile('settings.py') #initialize the article pages articles = FlatPages(app) #FlatPages use 'app' as arguments #initialize the freezer freezer = Freezer(app)
配置文件settings.py
配置文件是我们设置整个博客程序的核心部分,它设置了我们搜索文档的路径,生成文件的位置,使用的扩展样式,调试模式是否生效等。
由于我们要对路径进行修改,我们必须使用os模块来调用并修改参数的绝对路径
import os
并开启调试模式
REPO_NAME = "test" DEBUG = True APP_DIR = os.path.dirname(os.path.abspath(__file__))
编写一个返回上一级目录的函数,参数为路径
def parent_dir(path): return os.path.abspath(os.path.join(path,os.pardir))
你要将生成的文件放在根目录下的posts文件夹里面,使用FREEZER_DESTINATION来配置生成路径。
PROJECT_ROOT = parent_dir(APP_DIR)+'/posts' FREEZER_DESTINATION = PROJECT_ROOT
将FREEZER_REMOVE_EXTRA_FILES设为False,否则它会在生成文件后清除项目中的其它文件(脚本白写了)..
FREEZER_REMOVE_EXTRA_FILES = False
使用CodeHilite作为markdown语法高亮的插件,
FLATPAGES_MARKDOWN_EXTENSIONS = ['codehilite']
设置你的博客文档所在位置,设在/project/blog/pages/目录下:
FLATPAGES_ROOT = os.path.join(APP_DIR,'pages') #FLATPAGES_ROOT = APP_DIR+'/pages'
设博客文档所使用的格式为'.md'(Markdown)
FLATPAGES_EXTENSION ='.md'
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import os REPO_NAME = "what-is-this" DEBUG = True APP_DIR = os.path.dirname(os.path.abspath(__file__)) def parent_dir(path): return os.path.abspath(os.path.join(path,os.pardir)) PROJECT_ROOT = parent_dir(APP_DIR)+'/posts' FREEZER_DESTINATION = PROJECT_ROOT FREEZER_REMOVE_EXTRA_FILES = False FLATPAGES_MARKDOWN_EXTENSIONS = ['codehilite'] FLATPAGES_ROOT = os.path.join(APP_DIR,'pages') #FLATPAGES_ROOT = APP_DIR+'/pages' FLATPAGES_EXTENSION ='.md' |
views文件(路由)
从我们的blog包(init.py)里面引入初始化的实例
from blog import app,articles
引入flask的模版调用函数render_template
from flask import render_template
主页
为我们的根目录设置网页,
@app.route('/') def index(): return render_template('index.html')
以上代码的内容是将生成的index.html配置到'/'根目录下,这将是你未来的主页,在网站上就是http://你的网站名.com所显示的页面.
文章列表页
@app.route('/posts.html') def posts(): posts = [article for article in articles if 'date' in article.meta] #sort posts by date,descending
其中这一句将以date为关键字来识别文档,没有date的文档不会被识别:
posts = [article for article in articles if 'date' in article.meta]
可以写成:
posts=[] for article in articles: if 'date' in article.meta: posts.append(article)
然后将我们的文章列表按照关键字-日期(key = lambda page:page.meta['date']),来进行降序排列(sorted是升序,‘reverse = True’ 使之改为降序)
sorted_posts = sorted(posts,reverse = True,key = lambda page:page.meta['date'])
然后调用posts.html模板,将模板中pages以我们获得的文章集合填充 return render_template('posts.html',pages = sorted_posts)
文章页
设置文章的路由,由于文章的路由是变量,每一篇文章都有不同的地址,所以我们使用\
@app.route('/article/<path:path>/')
接着我们定义一个调用文章页模板的函数
def page(path):
是否返回404错误:
article = articles.get_or_404(path)
返回文章页,每篇提取的文章都将会作为文章页模板的填充,然后生成新的文章页
return render_template('article.html',page=article)
完整代码如下:
#for view parts, we need 3 things:templates,flask,flatpages from flask import render_template #for using render_template #from __init__.py import app ('flask'), articles ('flatpages') from app import app,articles #set the articlelist route @app.route('/') def index(): return render_template('index.html') @app.route('/posts.html') def posts(): #posts = [article for article in articles if 'date' in article.meta] posts=[] for article in articles: if 'date' in article.meta: posts.append(article) #sort posts by date,descending sorted_posts = sorted(posts,reverse = True,key = lambda page:page.meta['date'])#Because of key is date, so in .md file date cannot be write in wrong format like Date #pages may related to template index.html return render_template('posts.html',pages = sorted_posts) #where does path come from @app.route('/article/<path:path>/') def page(path): #path is the filename of a page,https://github.com/ChenghaoQ/ChenghaoQ.git without the file extension #e.g."first-post article = articles.get_or_404(path) #page may related to the template referal #article is the data we extracted by python,and now we need to use template, in template, the name is page->\{\{ page.meta.title }} #So page = article return render_template('article.html',page=article)
博客文档格式及模板
先看我们的文档格式例子:
1 2 3 4 5 6 | title: Bacon Ipsum date: 2013-07-14 12:00:00 Bacon ipsum dolor sit amet ball tip tongue pancetta jowl sirloin rump. Chuck tail pork cow, fatback jerky hamburger pancetta leberkas pig . [Read more](http://baconipsum.com/) |
Markdown和YAML
- Markdown:一种标记型语言,通过简单的字符将文本文档识别为html,以空白行开始,每个一个空白行都会生成一个p标签的段落
- YAML是一個可讀性高,用來表達資料序列的格式。在我们文档的格式中,title:和date: 都被YAML提取生成dict,然后在模板里使用{{page.meta.title}}和{{page.meta.date}}调用,YAML通过冒号':'识别,不可有空白行间隔,否则会被markdown识别为段落
模板简单语法
Jinja支持和python类似的条件语句和循环语句。
if语句:
上面的代码表示
for循环:
模板与Markdown,YAML的关系
还记不记得在我们的视图文件views里面,关于文章页的路由模板调用
return render_template('article.html',page=article)
这里article是我们使用Flatpage读出来的文档内容,而page则是该内容在模板里对应的名称,于是在模板里,我们使用:
- {{page.meta.title}} 表示标题
- {{page.meta.date}} 表示日期
- {{page}} 表示文章正文部分
代码高亮
Markdown默认使用四个空格或者一个制表符的缩进来表示代码部分,由于使用codehilite,我们不可以用3个'`'(反引号)来申明代码语言,但我们可以使用':::'三个冒号来声明
:::python from blog import app
效果如下
from blog import app
然后你可以对你文章中类名为codehilite的div块设置你喜爱的css样式.
添加博客首页摘要功能
目前很多静态博客都支持博客首页的文章摘要功能,Flask没有内置此功能。但是这并不难实现,因为Flask使用的Jinja模板有过滤器功能,我们可以向Flask实例注册一个自定义摘要过滤器:
向Flask实例app注册一个名为excerpt的过滤器,但是我们需要先引入app.
from blog import app
注册:
@app.template_filter('excerpt')
excerpt过滤器调用的函数,使用python来对文章进行切片:
def excerpt_spliter(article): sep='<!--More-->' if sep in article: pass else: sep = '\n' return Markup(pygmented_markdown(article.split(sep,1)[0]))
由于单独提取并切片后的内容只是普通文本,并不是Markdown,所以我们需要使用markdown里的markdown函数重新将其生成为HTML格式
from markdown import markdown
但是如果我们想要使用pygments来高亮语法,我们需要使用flask-Flatpages内带有Codehilite扩展的Markdown
from flask_flatpages import pygmented_markdown
完整代码如下
from markupsafe import Markup from flask_flatpages import pygmented_markdown @app.template_filter('excerpt') def excerpt_spliter(article): sep='<!--More-->' if sep in article: pass else: sep = '\n' return Markup(pygmented_markdown(article.split(sep,1)[0]))
冻结器
冻结器非常简单,引入blog包然后使用Freezer加载即可
import blog if __name__ =='__main__': blog.freezer.freeze()