欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

程序员文章站 2022-04-20 10:06:53
文章页: 构思 生成功能 我们就先来实现一个转换md文件生成html文件的生成类。首先先实现转换md为html的功能: Meta扩展 extensions参数传入的是markdown库的扩展,其中最主要的是meta。该扩展添加了 用于定义文章数据信息的语法,meta语法包含一系列键值对,形如: 注意 ......
flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

文章页:

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

构思

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的功能:

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

Meta扩展

extensions参数传入的是markdown库的扩展,其中最主要的是meta。该扩展添加了 用于定义文章数据信息的语法,meta语法包含一系列键值对,形如:

title: 我好帅
summary: 我真的好帅啊
author: 我啊
datetime: 2018-02-21
tag: 随笔
 生活
category: 无分类
balabala: xxxx


正文开始

注意:

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

>>> 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扩展

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

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信息,那么我们就要给他生 成默认的信息。

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

os.path.splitext(os.path.basename(file))[0]得到的是去掉扩展名的文件名。

保存文件

既然生成了html,接下来就是保存它。

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

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
flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

存储数据

静态博客不需要进行数据库的交互,但是一直将博客的文章索引信息保存在本地列表中, 不是好的选择。幸运的是,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()
flask可是搭建博客的神器!你想拥有一个牛逼的博客!这篇必学!

 

# 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  即可获取源码!