第十一章 博客文章(四)

本小节将对输入文章的多行文本输入框升级,让其支持MarkDown语法,并使用MarkDown和Flask-PageDown支持富文本文章,此处需要安装的几个新包如下:

  • PageDown:使用JS实现的客户端MarkDown到HTML的转换程序;
  • Flask-PageDown:Flask包装的PageDown,把PageDown集成到Flask-WTF表单中;
  • MarkDown:使用Python实现的服务器端MarkDown到HTML的转换程序;
  • Bleach:使用Python实现的HTML清理器。

安装:

pip install flask-pagedown markdown bleach

一. 使用FLask-PageDown

Flask-PageDown定义了一个PageDownField类,作为MarkDown富文本编辑器。这个类和WTForms中的TextAreaField接口一致。使用PageDownField字段之前,先要初始化扩展。

app/__init__.py:初始化Flask-PageDown

...
from flask_pagedown import PageDown

pagedown = PageDown()

def create_app(config_name):
    app = Flask(__name__)
    ...
    pagedown.init_app(app)
    ...
    return app

若想把首页中的多行文本输入框转换成MarkDown富文本输入框,PostForm表单中的body字段要进行扩展:

app/main/forms.py:启用MarkDown的文本表单:

class PostForm(FlaskForm):
    body = PageDownField("What's on your mind?", validators=[DataRequired()])
    submit = SubmitField('Submit')

 MarkDown预览使用PageDown库生成,因此需要修改模板。Flask-PageDown简化了这一过程,提供了一个模板宏,从CDN中加载所需文件。

app/index.html:Flask-PageDown模板声明

{% block scripts %}
{{ super() }}
{{ pagedown.include_pagedown() }}
{% endblock %}

注: 做了上述修改后,多行文本字段中输入MarkDown格式的文本会立即被渲染成HTML并显示在输入框的下方。

二. 在服务端处理富文本

提交表单后,POST请求只会发送纯MarkDown文本,页面中显示的HTML预览会被丢掉。和表单一起发送生成的HTML预览会有安全隐患,因为攻击者能轻易修改HTML代码,使其和MarkDown源不匹配,然后再提交表单。安全起见,只提交MarkDown源文本,在服务器上使用MarkDown将其转换成HTML。得到HTML后使用Bleach进行清洗,确保其中只包含几个允许使用的HTML标签。

把MarkDown文本转换成HTML的过程可以在_posts.html模板中完成,但这么做效率不高,因为每次渲染模板都要转换一次。为了避免重复工作,我们可以在建博客文章时做一次转换,转换后的博客文章HTML字段代码缓存在POST模型的一个新字段中,在模板中可以直接调用。文章的MarkDown源文本还要保存在数据库中,以防要编辑。

app/models.py:在Post模型中处理MarkDown文本

from markdown import markdown
import bleach

class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.Text)
    body_html = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))

    @staticmethod
    def on_changed_body(target, value, oldvalue, initiator):
        allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code',
                        'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul',
                        'h1', 'h2', 'h3', 'p']
        target.body_html = bleach.linkify(bleach.clean(
            markdown(value, output_format='html'),
            tags=allowed_tags, strip=True))

db.event.listen(Post.body, 'set', Post.on_changed_body)

on_changed_body()函数注册在body字段上,是SQLAlchemy “set”事件的监听程序,这意味着只要这个类实例的body字段设了新值,函数就会自动被调用。注:该回调函数的4个参数是缺一不可的,在程序运行时,依次打印4个参数的值如下:

on_changed_body()将MarkDown文本转换成HTML格式的过程分三步完成:

  • markdown()函数初步把MarkDown文本转换成HTML;
  • 把得到的结果和允许使用的HTML标签列表传给clean()函数,clean函数删除所有不再白名单中的标签;
  • linkify函数完成转换的最后一步,该函数有Bleach提供,把纯文本中的URL转换成适当的<a>链接;

注:linkify函数的使用是很有必要的,因为MarkDown规范没有为自动生成链接提供官方支持。

app/templates/_posts.html:在模板中使用文章内容的 HTML 格式

...
            <div class="post-body">
                {% if post.body_html %}
                    {{ post.body_html | safe }}
                {% else %}
                    {{ post.body }}
                {% endif %}
            </div>
...

渲染 HTML 格式内容时使用 | safe 后缀,其目的是告诉 Jinja2 不要转义 HTML 元素。出于安全考虑,默认情况下 Jinja2 会转义所有模板变量。Markdown 转换成的 HTML 在服务 器上生成,因此可以放心渲染。

发布了132 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geroge_lmx/article/details/101062969