创建博客-文章编辑

使用MarkDown和Flask-PageDown支持富文本

对于发布短消息和状态更新来说,纯文本足够用了,但如果用户想发布长文章,就会觉得在格式上受到了限制,所以我们要将输入文章的多行文本输入框升级,让其支持MarkDown语法,还要添加富文本文章的预览功能

实现这个功能要用到一些新包

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

使用Flask-PageDown

Flask-PageDown拓展定义了一个PageDownField类,这个类和WTForms中的TextAreaField接口一致,使用PageDownField字段之前,先要初始化拓展,如下:

# app/__init__.py
from flask_pagedown import PageDown
#...
pagedown = PageDown()
#...
def create_app(config_name):
#...
pagedown.init_app(app)
#...

若想把首页中的多行文本控件转换成MarkDown富文本编辑器,PostForm表单中的body字段要进行修改,如下所示:

# app/main/forms.py

from flask_pagedown.fields import PageDownField

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

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

#app/templates/index.html
#...

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

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

在服务器上处理富文本

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

把Markdown格式的博客文章转换成HTML的过程可以在_posts.html模板中完成,但这么做效率不高,因为每次渲染页面时都要转换一次,为了避免重复工作,我们可在创建博客文章时做一次性转换,转换后的博客文章HTML代码缓存在Post模型的一个新字段中,在模板中可以直接调用,文章的Markdown源文本还要保存在数据库中,以防需要编辑,下例是对Post模型的改动:

from markdown import markdown
import bleach

body_html = db.Column(db.Text)
class Post(db.Model):
    #...    
    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字段设了新值,函数就会自动被调用,on_changed_body函数把body字段中的文本渲染成HTML格式,结果保存在body_html中,自动且高效地完成Markdown文本到HTML的转换

真正的转换过程分三步完成,首先,markdown()函数初步把Markdown文本转换成HTML,然后把得到的结果和允许使用的HTML标签列表传给clean函数,clean()函数删除所有不在白名单中的标签,转换的最后一步由linkify()函数完成,这个函数由Bleach提供,把纯文本中的URL转换成适当的<a>链接,最后一步是很有必要的,因为Markdown规范没有为自动生成链接提供官方支持,PageDown以拓展的形式实现了这个功能,因此在服务器上要调用linkify()函数

最后,如果post.body.html字段存在,还要把post.body换成post.body_html,如下例:

# app/templates/_posts.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在服务器上生成,因此可以放心渲染

博客文章的固定链接

用户有时希望能够在社交网络中和朋友分享某篇博客文章的链接,为此,每篇文章都要有一个专页,使用唯一的URL引用,支持固定链接功能的路由和视图函数如下例:

# app/main/views.py

#...
@main.route('/post/<int:id>')
def post(id):
    post = Post.query.get_or_404(id)
    return render_template('post.html', post=[post])

博客文章的URL使用插入数据库时分配的唯一id字段构建

某些类型的程序使用可读性高的字符串而不是数字ID构建固定链接,除了数字ID之外,程序还为博客文章起了个独特的字符串别名

注意,post.html模板接收一个列表作为参数,这个列表就是要渲染的文章,这里必须要传入列表,因为只有这样,index.htmluser.html引用的_posts.html模板才能在这个页面中使用

固定链接添加到通用模板_posts.html中,显示在文章下方:

# app/templates/_posts.html

<ul class="posts">
#...
    <div class="post-content">    
        <div class="post-footer">
            <a href="{{ url_for('.post', id=post.id) }}">
            <span class="label label-default">Permalink</span></a>
        </div>
    </div>
</ul>

渲染固定页面的post.html模板如下,其中引入了上述模板:

{% extends "base.html" %}

{% block title %}Flasky - Post{% endblock %}

{% block page_content %}
{% include '_posts.html' %}
{% endblock %}

博客文章编辑器

与博客文章相关的最后一个功能是文字编辑器,它允许用户编辑自己的文章,博客文章编辑器显示在单独的页面中,在这个页面的上部会显示文章的当前版本,以供参考,下面跟着一个Markdown编辑器,用于修改Markdown源,这个编辑器基于Flask-PageDown实现,所以页面下部还会显示一个编辑后的文章预览,edit_post.html模板如下所示:

# app/templates/edit_post.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky - Edit Post{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Edit Post</h1>
</div>
<div>
{{ wtf.quick.form(form) }}</div>
{% endblock %}

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

博客文章编辑器使用的路由如下:

# app/main/views.py
@main.route('/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit(id):
    post = Post.query.get_or_404(id)
    if current_user != post.author and \
            not current_app.can(Permission.ADMINISTER):
        abort(403)
    form = PostForm()
    if form.validate_on_submit():
        post.body = form.body.data
        db.session.add(post)
        flash('The post has been Updated')
        return redirect(url_for('.post', id=post.id))
    form.body.data = post.body
    return render_template('edit_post.html', form=form)

这个视图函数的作用是只允许博客文章的作者编辑文章,但管理员例外,管理员能编辑所有用户的文章,如果用户试图编辑其他用户的文章,就会返回403错误,这里使用的PostForm表单类和首页中使用的是同一个

问了功能完整,我们还可以在每篇文章的下面、固定链接的旁边添加一个指向编辑页面的链接,代码如下:

#app/templates/_posts.html
#...
#...
<div class="post-footer">
        <a href="{{ url_for('.post', id=post.id) }}">
        <span class="label label-default">Permalink</span></a>
        {% if current_user == post.author %}
        <a href="{{ url_for('.edit', id=post.id) }}">
        <span class="label label-primary">Edit</span></a>
        {% elif current_user.is_administrator() %}
        <a href="{{ url_for('.edit', id=post.id) }}">
        <span class = " label label-danger">Edit[Admin]</span>
        </a>

        {% endif %}

</div>        
#...

通过这次修改,我们在当前用户发布的博客文章下面添加了一个“Edit”链接,如果当前用户是管理员,所有文章下面都会有编辑链接,为管理员显示的链接样式有点不同,以从视觉上表明这是管理功能,效果如下:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值