Flask-CKEditor:为Flask项目集成富文本编辑器

这篇文章属于“Flask常用扩展介绍系列”,这个系列的文章目录索引可以在《Flask常用扩展介绍系列文章索引》看到。
 

富文本编辑器即WYSIWYG(What You See Is What You Get)编辑器(所见即所得编辑器)。在Web程序中可用的开源富文本编辑器中,CKEditor是一个流行的选择。Flask-CKEditor简化了将CKEditor集成到Flask项目中的过程,可以让你方便的在Flask项目中添加富文本编辑器。它包含下面这些特性:

  • 提供WTForms/Flask-WTF集成支持
  • 支持图片上传与插入
  • 通过Flask配置来设置编辑器的语言、高度等参数
  • 支持代码块语法高亮

《Flask Web开发实战》中的第2个示例程序(博客程序Bluelog)使用了这个扩展。

基本用法

安装

首先使用pip或Pipenv等工具安装:

$ pip install flask-ckeditor

初始化扩展

一般情况下,你只需要导入并实例化CKEditor类,并传入程序实例即可:

from flask_ckeditor import CKEditor

app = Flask(__name__)
ckeditor = CKEditor(app)

如果你使用了工厂函数,那么也可以调用init_app()方法来进行初始化:

from flask_ckeditor import CKEditor

ckeditor = CKEditor()

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

引入CKEditor资源

为了使用CKEditor,我们首先要在模板中引入CKEditor的JavaScript等资源文件。推荐的做法是自己编写资源引用语句,你可以在CKEditor提供的Online Builder构建一个自定义的资源包,下载解压后放到项目的static目录下, 并引入资源包内的ckeditor.js文件,比如(实际路径按需调整):

<script src="{{ url_for('static', filename='ckeditor/ckeditor.js') }}"></script>

如果你不需要自定义,那么也可以从CDN加载:

<script src="//cdn.ckeditor.com/4.9.2/standard/ckeditor.js"></script>

最后,作为替代选项,你也可以使用Flask-CKEditor提供的ckeditor.load()方法来生成引用语句:

{{ ckeditor.load() }}

它默认从CDN加载资源,将配置变量CKEDITOR_SERVE_LOCAL设为True会使用扩展内置的本地资源。另外,你也可以使用custom_url参数来使用自定义资源包:

{{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/ckeditor.js')) }}

创建CKEditor文本区域

Flask-CKEditor提供了两种方式来CKEditor文本区域:

1. 与WTForms/Flask-WTF集成

Flask-CKEditor提供了一个CKEditorField字段类,和你平时从WTForms导入的StringField、SubmitField用法相同。事实上,它就是对WTForms提供的TextAreaField进行了包装。

作为示例,我们可以创建一个写文章的表单类。这个表单类包含一个标题字段(StringField),一个正文字段(CKEditorField)和一个提交字段(SubmitField)。你会看到,其中的正文字段使用了CKEditorField。

from flask_wtf import FlaskForm
from flask_ckeditor import CKEditorField
from wtforms import StringField, SubmitField

class PostForm(FlaskForm):
    title = StringField('Title')
    body = CKEditorField('Body')
    submit = SubmitField('Submit')

 

在渲染文本编辑区域的模板中,我们可以像往常一样渲染表单:

<form method="post">
    {{ form.title.label }}{{ form.title() }}
    {{ form.body.label }}{{ form.body() }}
    {{ form.submit() }}
</form>

{{ ckeditor.load() }}
{{ ckeditor.config(name='body') }}

唯一需要注意的是,我们需要在资源引用语句后调用ckeditor.config()方法来让对CKEditor进行配置和初始化,并将name参数的值设为CKEditor字段的属性名,这里即body。

当表单提交后,你可以像其他字段一样通过form.attr.data属性来获取数据,这里的文本区域数据即form.body.data。

2. 手动创建

如果你不使用WTForms/Flask-WTF,那么可以直接使用Flask-CKEditor提供的ckeditor.create()方法在模板中创建文本编辑区域:

<form method="post">
    {{ ckeditor.create() }}
    <input type="submit">
</form>

{{ ckeditor.load() }}
{{ ckeditor.config() }}  <!-- 这时不用设置name参数 -->

在表单被提交后,你可以使用ckeditor作为键从表单数据中获取对应的值,即request.form.get('ckeditor')。

提示 完整的示例程序在examples/basic/examples/without-flask-wtf目录下。

配置变量

Flask-CKEditor提供了下面这些配置变量:

配置 默认值 说明
CKEDITOR_SERVE_LOCAL False 使用内置的ckeditor.load()方法时,设置是否使用本地资源,默认从CDN加载
CKEDITOR_PKG_TYPE 'standard' CKEditor资源包的类型,basicstandardfull中的一个
CKEDITOR_LANGUAGE None 设置CKEditor文本编辑器的语言,默认会自动探测用户浏览器语言,所以一般不需要设置。你也可以设置ISO 639格式的语言码,比如zhenjp等
CKEDITOR_HEIGHT CKEditor默认 编辑器高度,单位为px
CKEDITOR_WIDTH CKEditor默认 编辑器宽度,单位为px
CKEDITOR_FILE_UPLOADER None 处理上传文件的URL或端点
CKEDITOR_FILE_BROWSER None 处理文件浏览的URL或端点
CKEDITOR_ENABLE_MARKDOWN False 设置是否开启markdown插件,需要安装对应插件
CKEDITOR_ENABLE_CODESNIPPET False 设置是否开启codesnippet插件(插入代码块),需要安装对应插件
CKEDITOR_CODE_THEME 'monokai_sublime' 当使用codesnippet插件时,设置语法高亮的主题
CKEDITOR_EXTRA_PLUGINS [] 在CKEditor中开启的额外扩展列表,对应的扩展需要被安装

图片上传

在使用文本编辑器写文章时,上传图片是一个很常见的需求。在CKEditor中,图片上传可以通过File Browser插件实现。在服务器端的Flask程序中,你需要做三件事:

  1. 创建一个视图函数来处理并保存上传文件
  2. 创建一个视图函数来获取图片文件,类似Flask内置的static端点
  3. 将配置变量CKEDITOR_FILE_UPLOADER设为这个视图函数的URL或端点值

完整的代码示例如下所示:

from flask_ckeditor import upload_success, upload_fail

app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'

@app.route('/files/<path:filename>')
def uploaded_files(filename):
    path = '/the/uploaded/directory'
    return send_from_directory(path, filename)

@app.route('/upload', methods=['POST'])
def upload():
    f = request.files.get('upload')  # 获取上传图片文件对象
    # Add more validations here
    extension = f.filename.split('.')[1].lower()
    if extension not in ['jpg', 'gif', 'png', 'jpeg']:  # 验证文件类型示例
        return upload_fail(message='Image only!')  # 返回upload_fail调用
    f.save(os.path.join('/the/uploaded/directory', f.filename))
    url = url_for('uploaded_files', filename=f.filename)
    return upload_success(url=url) # 返回upload_success调用

注意 传入request.files.get()的键必须为'upload', 这是CKEditor定义的上传字段name值。

在处理上传文件的视图函数中,你必须返回upload_success()调用,每将url参数设置为获取上传文件的URL。通常情况下,除了保存文件,你还需要对上传的图片进行验证和处理(大小、格式、文件名处理等等,具体可以访问这篇《Flask文件上传(一):原生实现》了解),在验证未通过时,你需要返回upload_fail()调用,并使用message参数传入错误消息。

当设置了CKEDITOR_FILE_UPLOADER配置变量后,你可以在编辑区域点开图片按钮打开的弹窗中看到一个新的上传标签。另外,你也可以直接将图片文件拖拽到编辑区域进行上传,或复制文件并粘贴到文本区域进行上传(CKEditor >= 4.5)。

提示 对应的示例程序在examples/image-upload/目录下。

如果你使用的 CKEditor 版本小于 4.5,则使用下面的方式实现:

from flask import send_from_directory

app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'  # this value can be endpoint or url

@app.route('/files/<filename>')
def uploaded_files(filename):
    path = '/the/uploaded/directory'
    return send_from_directory(path, filename)

@app.route('/upload', methods=['POST'])
@ckeditor.uploader
def upload():
    f = request.files.get('upload')
    f.save(os.path.join('/the/uploaded/directory', f.filename))
    url = url_for('uploaded_files', filename=f.filename)
    return url

为图片上传请求添加 CSRF 保护

如果你想为图片上传的请求添加 CSRF 保护,可以通过 CSRFProtect 实现(Flask-WTF 内置),首先安装 Flask-WTF:

$ pip install flask-wtf

然后初始化扩展:

from flask_wtf import CSRFProtect

csrf = CSRFProtect(app) 

Flask-CKEditor 0.4.3 版本内置了对 CSRFProtect 的支持,当使用 CSRFProtect 时,只需要把配置变量 `CKEDITOR_ENABLE_CSRF` 设为 `True` 即可开启 CSRF 保护:

app.config['CKEDITOR_ENABLE_CSRF'] = True

顺便说一句,在 Flask-CKEditor 内部需要把 CSRF 令牌放到上传图片的 AJAX 请求首部,这通过 CKEditor 4.9.0 版本新添加的一个配置选项 fileTools_requestHeaders 实现,这个配置可以用来想文件上传请求插入自定义的首部字段 。所以,如果想要实现 CSRF 保护,CKEditor 的版本需要大于或等于 4.9.0。

代码语法高亮

代码语法高亮可以通过Code Snippet插件实现(基于hightlight.js),你可以将配置变量CKEDITOR_ENABLE_CODESNIPPET设为Ture来开启。在此之前,你需要确保安装了这个插件(内置的资源包包含了这个插件)。

为了正确渲染代码块,你还需要引入对应的资源文件,最简单的方式是使用Flask-CKEditor提供的ckeditor.load_code_theme()方法:

<head>
 ...
 {{ ckeditor.load_code_theme() }}
</head>

你可以通过配置变量CKEDITOR_CODE_THEME来设置语法高亮的主题,默认为monokai_sublime,你可以在这个页面看到所有可用的主题对应的字符串。

提示 对应的示例程序在examples/codesnippet/目录下。

使用示例程序

项目仓库中提供了5个示例程序,分别展示基本用法、图片上传插入、代码语法高亮、Markdown模式和不使用Flask-WTF/WTForms。以基本示例程序为例,你可以通过下面的命令来获取并运行它:

$ git clone https://github.com/greyli/flask-ckeditor
$ cd flask-ckeditor/examples
$ pip install -r requirements.txt
$ python basic/app.py

然后在浏览器访问http://127.0.0.1:5000。

另外,helloflask仓库里在demos/form目录下的示例程序也包含一个Flask-CKEditor使用示例。

相关链接

 

Flask-CKEditor:为Flask项目集成富文本编辑器》上有43条评论

  1. 头像vookang

    您好,请问一下,在使用Flask-CKEditor上传图片的时候,目前发现在上传标签页中,只有“选择文件”和“上传服务器”的两个按钮。是否有选择目标文件夹的功能呢?比如选择一个文件后,想把这个文件放到不同的目标文件夹下,方便管理。如果有这个功能,是需要设置什么条件才能显示出来呢?如果暂时没有这个功能,是否有什么思路可以提供参考呢?谢谢!!

    回复
  2. 头像auth

    单独安装flask_ckeditor时,在工厂函数使用时页面报错,提示未安装WTForm模块,用pip安装flask_wtf后才能正常使用。

    回复
  3. 头像许杭锟

    博主你好
    我在ckeditor编辑好的文章显示时发现格式就与编辑时不一样,请问有什么解决办法吗。具体就是图片的问题,比如上传了一张图图片显示在左侧,但是显示文章的时候,图片的大小位置都跟编辑时候不一样。用的是你的ckeditor教程里的image-upload。

    回复
      1. 头像许杭锟

        博主你好,问题已经解决,是CSS的问题。但是现在不太满足于ckeditor的默认的图片上传方式,想改用easyimage那种简单好用的图片上传方式。按照你的博文所述,发现改用easyimage之后,图片可以上传到本地服务器,但是生成的文章的html中图片的链接却不对

        。如果用ckeditor默认的图片上传扩展的话,图片链接就是没有问题的。请问这有办法解决吗。easyimage上传图片我觉得操作上友好很多。

        回复
          1. 头像许杭锟

            我用easyimage上传一张图片之后产生的语句如下
            (figure class=”easyimage easyimage-full”/)
            (img alt=”” src=”blob:http://127.0.0.1:5000/d67282b6-290c-465f-9e74-4e52718f4ad1” width=”1920″ /)
            (figcaption)(/figcaption)
            (/figure)
            然后下次再显示文章,图片就没了。
            我把<改成(了,不然好像发不出来

            如果不用easyimage而用ckeditor的standard的扩展,图片的链接形式就是像下面这样
            src="/files/users/picture.png"。
            这样文章就能正常显示图片。

            但我觉得easyimage处理图片的方式友好很多,我想用easyimage.

      1. 头像123

        请问这里那里有错误
        config:
        CKEDITOR_SERVE_LOCAL = True # 加载本地ckeditor
        CKEDITOR_PKG_TYPE = ‘full’
        CKEDITOR_ENABLE_CODESNIPPET = True

        html :

        {{ form.csrf_token }}

        {{ form.title.label() }}
        {{ form.title(class=’form-control’) }}
        {%for err in form.title.errors%}
        {{err}}
        {% endfor %}

        {{ form.article.label(style=’font-size:16px’) }}

        {# 显示博客输入内容#}
        {{ form.article() }}
        {{ ckeditor.load() }}
        {{ ckeditor.config() }}

        {%for err in form.article.errors%}
        {{err}}
        {% endfor %}

        发表

        回复
    1. 头像李辉 文章作者

      在 CKEditor 网站下载扩展,放到扩展文件夹,然后在把配置 CKEDITOR_EXTRA_PLUGINS 定义为列表,包含扩展名称。

      回复
      1. 头像阿呆啊

        你好,我下载完整版的ckeditor包加到static文件夹下,编辑文章的时候ckeditor还是没有完整的插件,我在settings.py中添加CKEDITOR_PKG_TYPE = ‘full’没有用,我按照网上搜索的教程修改ckeditor文件夹下的config.js也没有效果。
        当我设置CKEDITOR_SERVE_LOCAL = True,并且在edit_post.html中添加{{ ckeditor.load(pkg_type=”full”) }}时就可以显示所有插件。我想显示所有插件,如果我从本地加载而不是从CDN加载ckeditor时要怎么做呢?

        回复
  4. 头像ding

    请问flask-ckeditor怎么拿到数据的时候去掉html标签,就是通过表单获取数据,是带html标签的,而且jinja统计字数也会算上标签=

    回复
    1. 头像李辉 文章作者

      可以用 bleach 之类的工具清除 HTML 标签。如果只是为了统计正确的字数,可以用 Jinja2 内置的 striptags 过滤器。

      回复
      1. 头像ding

        已经想到用爬虫库清理标签了=,顺便统计了字数,用striptags过滤标签来统计字数应该更方便

        回复
  5. 头像ssew

    在手动创建这里:

    {{ ckeditor.create() }}

    请问如何设置默认值,比如说数据库中以前的内容要再次编辑就让以前的内容作为默认值加载到这里,请问如何实现?
    谢谢

    回复
      1. 头像ssew

        确实是这样,因为我是用的从后端传过来的变量,所以是用的{{ ckeditor.create(value=default) }}形式。
        谢谢!!

        回复
  6. 头像不明真相的小白

    请问一下,flask-ckeditor4.9版本,配合flask-admin使用,是不能用blueprint.route的吗

    回复
  7. 头像任小牛

    我感觉现在的ckeditor存在一些问题,如果能对应几个比较合适的bootstrap的img相关的标签就好了。

    回复
  8. 头像任小牛

    请问作者,我在代码中用到了一个ckeditor的插件image2,但是一直请求不到。我先是在官网上进行了定制,后来发现不行,又手动下载的那个插件,并解压到了plugins文件夹下,不过还是请求不到;
    而且每个请求后面还有一个参数t=I3I8。
    不太明白是哪里出了问题

    回复
  9. 头像Tom

    你好,使用flask-ckeditor和wtforms以及flask-wtf的话,如何升级ckeditor5呢,我尝试了直接更换资源包以及从SDN加载ckeditor5都不成功呢。但是ckeditor4是可以的,请问如果是ckeditor5的话该怎么配置呢?

    回复
    1. 头像李辉 文章作者

      你好,CKEditor 5 的使用方式和 4 不一样,Flask-CKEditor 目前只支持 CKEditor 4。你可以参考 Flask-CKEditor 的代码和 CKEditor 5 的文档自己写集成代码,或者等我有时间为 Flask-CKEditor 添加对 5 的支持。

      回复
  10. 头像小小陈

    你好!按照教程使用flask-ckeditor插件。怎样将编辑器语言设置为简体中文?已经在ckeditor网站自定义下载了简体中文语言包,页面展示默认的仍然是英文。

    回复
  11. 头像helloworld

    你好,关于最新升级的3.0版本,其中我最想要那个图片上传功能,有几行代码我不知道在哪加,我是按狗书那个结构写的,其中
    ckeditor = CKEditor(app),这句代码应该加在哪?init.py中吗?还是manage.py?还有
    app.config[CKEDITOR_FILE_UPLOADER]=‘upload’,这句我感觉要加到config里,但是调试了好半天,都出不来upload那个选项

    回复
    1. 头像李辉 文章作者

      实例化扩展类可以放到程序包的__init__.py里,和其他扩展的实例化语言放在一起。配置的问题已经回复邮件给你了。另外,请使用pip install -U flask-ckeditor升级到0.3.1版本。

      回复
  12. 头像ihxn

    博主你好,请教一下,这个没有代码高亮的吗,而且这个编辑器貌似也不安全吧,直接用safe过滤器避免转义,这会导致xss攻击?代码高亮能给个思路吗

    回复
    1. 头像李辉 文章作者

      代码高亮可以在服务器端使用Pygemnts处理,但使用CKEditor社区的code snippet等扩展比较方便。下周我会发布0.3版本,新版本会包含一个本地js包,会内置代码高亮,图片上传等功能。另外也会支持加载自定义资源。

      关于XXS,编辑器自身有基本的保护机制,你可以测试一下。如果你想做到绝对安全,可以再服务器端再对数据进行二次清理,比如使用Blench。

      回复
    2. 头像ginger

      请问开启Flask-CKeditor图片CSRF的保护功能后 将CSRF_Token的值传进请求头中,这边上传图片一直报The CSRF token is missing

      回复

李辉进行回复 取消回复

电子邮件地址不会被公开,必填项已用 * 标出。