flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!
文章页:
构思
quiet /quiet -- flask项目目录 /static -- 存放静态文件目录包括生成的html /templates -- html 模版目录 __init__.py views.py -- 路由 /source -- 存放 md 文件目录 generate.py -- 生成 html 程序 config.py -- 配置文件 run.py -- 启动文件
生成功能
我们就先来实现一个转换md文件生成html文件的生成类。首先先实现转换md为html的功能:
Meta扩展
extensions参数传入的是markdown库的扩展,其中最主要的是meta。该扩展添加了 用于定义文章数据信息的语法,meta语法包含一系列键值对,形如:
title: 我好帅 summary: 我真的好帅啊 author: 我啊 datetime: 2018-02-21 tag: 随笔 生活 category: 无分类 balabala: xxxx 正文开始
注意:
>>> md = Markdown(extensions = ['meta']) >>> html = md.convert(text) >>> print(html) <p>正文开始</p> >>> print(md.Meta) { 'title' : ['我好帅'], 'summary' : ['我真的好帅啊'], 'author' : ['我啊'], 'datetime' : ['2018-02-21'], 'tag' : ['随笔','生活'], 'category' : ['无分类'], 'balabala': ['xxx'] }
fenced_code扩展
admonition和tables扩展
admonition是一个警告样式的语法,示例如:
!!! 注意 我真的很帅。
解析为:
<div class="admonition note"> <p class="admonition-title">注意</p> <p>我真的很帅。</p> </div>
tables扩展顾名思义就是提供了在markdown中创建表的功能:
| 默认列 | 左对齐列 | 右对齐列 | 居中列 | |:----|:----|----:|:----:| | Hello | Hello | Hello | Hello |
除了这些扩展外,还有比如生成文章目录的toc扩展,还有extra扩展,大家自行了 解。
配置Jinja2
from jinja2 import Environment, FileSystemLoader def markdown_to_html(): ... ... # 解析meta获得信息 data = parse_meta(file, meta) env = Environment(loader=FileSystemLoader("./quiet/templates")) template = self.env.get_template('post.html') html = template.render( article=content, data=data, title=data.get('title') ) return html def parse_meta(file, meta): pass
env是创建的一个默认jinja2模板环境,我们把他设为./quiet/templates目录,这 里面存放我们自己写的所有模版,比如post.html。
我们只需要调用get_template()方法从这个环境中加载模板,并会返回已加载的template。用若干变量来渲染它,则需要调用render()方法。我们像模板中传入解析 后的html文档,和解析后的meta信息,和文章标题。
解析Meta
为什么要有这一步呢,主要是有些文件可能本身没有写meta信息,那么我们就要给他生 成默认的信息。
os.path.splitext(os.path.basename(file))[0]得到的是去掉扩展名的文件名。
保存文件
既然生成了html,接下来就是保存它。
def markdown_to_html(file): ... ... # 解析meta获得信息 data = parse_meta(file, meta) update_post_data(file, data) ... _posts = [] def update_post_data(file, data): generate_file = os.path.splitext(os.path.basename(file))[0] + '.html' data['filename'] = generate_file _posts.append(data) render_tag_posts(data['tag']) render_cate_posts(data['category']) render_index_html() def render_tag_posts(tag): pass def render_cate_posts(category): pass def render_index_html(): pass
存储数据
静态博客不需要进行数据库的交互,但是一直将博客的文章索引信息保存在本地列表中, 不是好的选择。幸运的是,python的内置模块shelve为我们提供了简单的数据存储方 案。这个库接收一个文件名作为存储数据的对象,相当于一个字典类型,帮我们保存需要 保存的数据,然后关闭它:
>>> import shelve >>> dat = shelve.open('index.dat') >>> dat['tag'] = ['音乐','电影','书籍'] >>> dat['tag'] ['音乐','电影','书籍'] >>> dat.close()
调用数据:
>>> data = shelve.open('index.dat') >>> data['tag'] ['音乐','电影','书籍']
我们用它来存放博客数据:
def dump_data(self): dat =shelve.open('./quiet/static/blog.dat') dat['post_data'] = self._posts dat['tag_data'] = self._tags dat['category_data'] = self._categories dat.close()
# generate.py ... from config import * class Generate(object): def __init__(self): self._generated_folder = GENERATED_PATH self._post_folder = POST_PATH self._page_folder = PAGE_PATH self._posts = [] ... def markdown_to_html(self, file): ... ... def generate(self): ... def __call__(self): self.generate()
模板
我们来写一些博客基本的html结构模板:
继承模版base.html:
<!DOCTYPE html> <html lang="zh-cmn-hans"> <head> <meta charset="UTF-8"> <title>{% if title %}{{ title }}{% else %}Quiet{% endif %} - 安静 </title> {% block head %} <link rel="shortcut icon" href="../static/images/favicon.ico" type="image/x-icon"> <link rel="icon" href="../static/images/favicon.ico" type="image/x- icon"> <link rel="stylesheet" href="../static/css/blog.css"> {% endblock %} </head> <body> <div id="header"> <div class="header-container"> <div class="header-nav"> <div class="header-logo"> <a href="/" class="float-left"> Quiet 个人博客 </a> </div> <div class="nav float-right"> <a href="/">首页</a> <a href="/categories">分类</a> <a href="/tags">标签</a> <a href="/page/about">关于</a> </div> </div> <div class="header-wrapper"> <div class="header-content"> <h1 class="header-title"> {% block header_title %} {% endblock %} </h1> {% block data %}{% endblock %} <div class="underline"></div> <p class="header-subtitle"> {% block header_subtitle %} {% endblock %} </p> </div> </div> </div> </div> <div id="main"> <div class="main-container"> {% block content %} {% endblock %} </div> </div> <div id="footer"> <div class="footer-container"> <p> ©2018 <a href="#">Quiet</a> 基于 flask 框架的 quiet 构建 </p> <p> <a href="#">备案号</a> </p> </div> </div> </body> </html>
首页模板index.html:
{% extends "base.html" %} {% block head %} <link rel="shortcut icon" href="../../../static/images/favicon.ico" type="image/x-icon"> <link rel="icon" href="../../../static/images/favicon.ico" type="image/x-icon"> <link href="../../../static/css/blog.css" rel="stylesheet"> <link href="../../../static/lib/fontello.css" rel="stylesheet"> <link href="../../../static/lib/pygments/default.css" rel="stylesheet"> {% endblock %} {% block header_title %} {{ data.title }} {% endblock %} {% block data %} <p class="header-date"> <span class="post-time"> <i class="demo-icon icon-calendar"></i> 发表于{{ data.datetime }} </span> <span class="post-category"> <i class="demo-icon icon-folder-empty"></i> 分类: <a href="/category/{{ data.category }}">{{ data.category }}</a> </span> </p> {% endblock %} {% block header_subtitle %} {% if data.tag %} {% for tag in data.tag %} <a href="/tag/{{tag}}"><i class="demo-icon icon-tags"></i> {{ tag }}</a> {% endfor %} {% endif %} {% endblock %} {% block content %} {{ article }} {% endblock %}
其他模板类似。其中favicon.ico是站点图标,blog.css是博客的样式,fontello.css是引入的一个矢量图标 css 库,default.css是pygments库生成的代 码高亮样式。
路由
静态博客的路由实现起来就很简单了,只要将对应的静态资源返回就可以了。返回静态资 源,我在之前的文章中提到过,大家可以看看[这里] (https://www.yukunweb.com/2017/12/flask-web-sitemap/)。使用的是flask的send_from_directory方法。
# quiet/main.py from flask import send_from_directory from . import app @app.route('/') @app.route('/index') def index(): """首页""" return send_from_directory('static', 'generated/index.html') @app.route('/tags') def tag(): """标签页""" return send_from_directory('static', 'generated/tags.html') @app.route('/categories') def category(): """分类页""" pass @app.route('/tag/<tag>') def tag_post(tag): """tag标签所有文章索引页""" return send_from_directory('static', 'generated/{tag}.html'.format(tag=tag)) @app.route('/post/<post>') def post(year, month, post): pass ...
我们将实例Flask应用放入__init__.py中:
# quiet__init__.py from flask import Flask from generate import Generate app = Flask(__name__, template_folder='templates') app.config.from_object('config') gen = Generate() from . import main # 这里注册蓝本
上传文件
严格地说,上传文件的路由并不会暴露给所有人,这样所有人都可以上传自己的md文 件,那还得了。所以上传文件的路由只会让管理员访问,进入上传页会被程序判断是否是 管理员,如是就进入上传页,如不是,就跳转到管理员登录页。这个功能如果不想麻烦自 己写个装饰器的话,就老老实实的用flask-login扩展。
对于管理员登录路由我这里不去赘述,大家可以自己实现或者直接查看quiet。
我们将管理员蓝本注册到__init__.py中去:
# quiet/__init__.py # 这里注册蓝本 from .admin import admin app.register_blueprint(admin, url_prefix='/admin')
上传路由:
imprt os from flask import Blueprint, request, url_for, redirect, render_template from . import app, gen admin = Blueprint('admin', __name__) @admin.route('/upload/post', methods=['GET', 'POST']) @login_required def upload_post(): """ 支持用户上传 md 文件并生成 html """ source_folder = app.config['POST_PATH'] if request.method == 'POST': file = request.files['file'] filename = file.filename path = os.path.join(source_folder, filename) file.save(path) # 生成 html gen() return redirect(url_for('index')) return render_template('upload_post.html', title="上传文章")
上传html页面大家自己完成,只需要一个上传表单就可以了。
文件的上传操作可以查看文档:here
flask的reauest方法接收管理员上传文章,并且保存到存放md文件的文件夹。然后 调用分装好的Generate类,生成保存并且渲染html页面资源。
博客api索引
如果想要实现restful应用架构,就用到我们保存的blog.dat数据了。使用flask做 api 实在是很简单的,但是我们需要先封装一下载入数据获取索引信息的类:
# utils.py import shelve from config import BLOG_DAT # 保存索引信息的文件 class ImportData(object): """ 载入 shelve 保存的博客数据 """ _data = {} @classmethod def _load_data(cls): """载入数据""" data = shelve.open(BLOG_DAT) for i in data: cls._data[i] = data[i] return cls._data @classmethod def get_data(cls): """获取数据""" if len(cls._data) == 0: cls._load_data() return cls._data @classmethod def reload_data(cls): """重新载入数据""" cls._load_data()
@classmethod是用来指定一个类的方法为类方法,调用他可以直接使用:ImportData.get_data()来返回一个字典数据。而不需要实例一个对象来调用。
api路由
首先需要注册蓝本:
# quiet/__init__.py ... from . import main, api, admin # 这里注册蓝本 from .api import api from .admin import admin app.register_blueprint(api, url_prefix='/api') app.register_blueprint(admin, url_prefix='/admin')
api 路由:
from flask import Blueprint, jsonify from utils import ImportData api = Blueprint('api', __name__) @api.route('/posts') def get_posts(): """ 所有文章信息 :return: json """ return jsonify(ImportData.get_data().get('post_data')) @api.route('/post/<int:id>') def get_post(id): """ 指定 id 文章信息 """ for p in ImportData.get_data().get('post_data'): if p.get('id') == id: return jsonify(p) return jsonify({'msg': '没有数据'}) @api.route('/pages') def get_pages(): """ 所有页面 """ return jsonify(ImportData.get_data().get('page_data')) ... ...
flask的jsonify方法可以很简单的将字典数据格式化为json数据,返回给客户端。
大功告成
到这里,静态博客的大部分功能都已经完成了,剩下来的就是运行程序了:
# run.py from quiet import app from generate import Generate if __name__ == '__main__': gen = Generate() gen() app.run()
先生成静态资源,接着启动flask程序。这时候访问:127.0.0.1:5000就可以看到博客 效果了。由于上面我并没有写css样式,所以样式可以基于大家的喜好,文章的扩展大家 也可以基于喜好自行扩展。
如果大家对整体逻辑或者是代码不明白的可以查看项目:quiet
如果喜欢不妨star,如果有建议不妨fork给我提PR。
祝大家早日找到对象QAQ~。
进群:125240963 即可获取源码!