Flask-CKEditor 4 在Flask项目中的应用与问题解决
编辑:梨涡 分类:python 日期:2022-01-15 15:31:06 访问量:467

1、安装

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

$ pip install -U flask-ckeditor

2、初始化扩展

一般情况下,你只需要导入并实例化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

3、引入CKEditor资源

3.1、本地化引入

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

本文章使用的版本是4,现在最新的版本有5了,可根据自己的需要选择下载!

在模版文件中加入:

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

或者根据自己静态文件存放的目录直接引入

<script src="/static/ckeditor/ckeditor.js"></script>

3.2、通过CDN引入

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

<script src="http://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')) }}

4、创建CKEditor文本区域

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

4.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。

4.2. 手动创建

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

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

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

在表单被提交后,你可以使用ckeditor作为键从表单数据中获取对应的值,即:

content = request.form.get('ckeditor')

4.3.配置变量

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

配置 默认值 说明
CKEDITOR_SERVE_LOCAL False 使用内置的ckeditor.load()方法时,设置是否使用本地资源,默认从CDN加载
CKEDITOR_PKG_TYPE 'standard' CKEditor资源包的类型,basic、standard和full中的一个
CKEDITOR_LANGUAGE None 设置CKEditor文本编辑器的语言,默认会自动探测用户浏览器语言,所以一般不需要设置。你也可以设置ISO 639格式的语言码,比如zh、enjp等
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中开启的额外扩展列表,对应的扩展需要被安装

4.4 配置的使用

在后台代码中加入以下配置信息:

# ----------------------------------------
# 编辑框配置
ckeditor = CKEditor(app)
# app.config['CKEDITOR_SERVE_LOCAL'] = True       # 使用本地模式,对应的为CDN模式
# app.config['CKEDITOR_PKG_TYPE'] = 'full-all'    # 编辑器功能数量
app.config['CKEDITOR_LANGUAGE'] = 'zh-cn'         # 编辑器语言
app.config['CKEDITOR_HEIGHT'] = 200               # 编辑器高度,宽度不配置自适应
# ----------------------------------------
# 给上传文件开启跨域保护
app.config['CKEDITOR_ENABLE_CSRF'] = True         # 开启跨域保护
# ----------------------------------------
# 配置CKEDITOR中需要处理上传功能的函数名称def upload()
app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'

不出意外,你将可以看到正确的编辑器界面。

4.5 编辑器内容回传(再编辑)出现的问题及解决办法

 通常情况下,如果不做处理,对于内容中(content)包含有{{ ** }},或者html标签源码,如果直接返回到编辑器中,我们可以在模版文件中加入参数:

{{ ckeditor.create(value=content) }}

但是会出现一个问题,即返回的代码类型的数据会被flask给渲染掉,如下:

无论我们在模版文件中通过什么办法,如:

{{ ckeditor.create(value=content|safe) }}

还是:

{% autoescape false %}
{{ ckeditor.create(value=content)}}
{% endautoescape%}

都不能解决问题,尝试了很多办法,最后发现参考CKEditor5的一个方案,在我们返回数据的时候做一下处理,即渲染之前给数据做一下替换即可轻松解决问题:

return render_template('xxxx.html', content = content.replace( '&', '&amp;'))

5、图片上传

5.1 上传图片和文件

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

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

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

from flask_ckeditor import upload_success, upload_fail
# ----------------------------------------
# 配置CKEDITOR中需要处理上传功能的函数名称def upload()
app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'
# 设置上传文件的存储路径,static_folder为Flask(__name__,static_folder= static_folder)中static_folder配置的路径,否者文件存储会找不到目录,后面/uploads为静态目录下的子目录。
UPLOAD_FILES_PATH = static_folder + '/uploads'

@app.route('/files/<path:filename>')
def uploaded_files(filename):
    ''' 此方法用于返回文件的真实路径,其实也起到了伪装真实文件路径的作用 '''
    return send_from_directory(UPLOAD_FILES_PATH, filename)


@app.route('/upload/', methods=['POST'])
def upload():
    ''' 接收上传的文件,并保存到相关的文件夹里面 '''
    file = request.files.get('upload')  # 获取上传图片文件对象
    prfix_filename = time.strftime("%Y-%m-%d %H-%M-%S",time.localtime())+'+'+file.filename  # 给文件名加一个时间戳前缀,防止重名
    file_full_path = os.path.join(UPLOAD_FILES_PATH, prfix_filename)
    extension = file.filename.split('.')[-1].lower()    # 提取扩展名
    if extension not in ['jpg', 'gif', 'png', 'jpeg','pdf','zip']:  # 验证扩展名
        return upload_fail(message='只支持jpg/jpeg/png/gif图片格式,以及pdf/zip文件格式,上传文件时候注意url地址引用!')  # 返回upload_fail调用
    if not os.path.exists(UPLOAD_FILES_PATH):   # 判断文件夹是否存在,用于第一次的创建,后面就不需要了
        os.makedirs(UPLOAD_FILES_PATH)
    if len(file_full_path) > 5*1024*1024: # 判断文件大小
        return upload_fail(message='文件大小不能超过5M!')
    file.save(file_full_path)
    url = url_for('uploaded_files', filename = prfix_filename)
    return upload_success(url = url,filename = prfix_filename)  # 返回upload_success
注意 传入request.files.get()的键必须为'upload', 这是CKEditor定义的上传字段name值。

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

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

以本地化引入CKEditor下,找到CKEditor目录下的config.js文件,修改如下参数,注意filebrowserUploadUrl值为必填,其他两项不填的话,会默认使用filebrowserUploadUrl的路径。

CKEDITOR.editorConfig = function( config ) {
   // Define changes to default configuration here. For example:
   // config.language = 'fr';
   // config.uiColor = '#AADC6E';
   config.filebrowserUploadUrl = '/upload/';  
   //config.filebrowserImageUploadUrl = '/upload/imgs/'      // 图片上传路径。若不设置,则使用filebrowserUploadUrl值。
   //config.filebrowserFlashUploadUrl = '/upload/flash/'
};

如果不配置这个,则在CKEditor窗口中找不到‘上传’选项!

5.2 基于linux服务器,处理400、413、500错误

在实际应用中,有可能会出现400、413、500等错误,一般情况下解决方法为:

400、413错误一般出现在配置有ngnix服务器上,由于Nginx配置的问题,找到etc/ngnix/nginx.conf配置文件(根据你自己的安装目录,一般人是安装在这个目录)

# user www-data;
user root;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # 配置请求体缓存区大
        client_body_buffer_size 20M;
        # 设置客户端请求体最大值
        client_max_body_size 100M;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##
        
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml applicatio$

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

找到http部分,将以下代码添加到文件中:

# 配置请求体缓存区大
client_body_buffer_size 20M;
# 设置客户端请求体最大值
client_max_body_size 100M;

添加完毕了,记得保存并重启ngnix服务

root@hecs-31078:/etc/nginx# service nginx restart

500错误基本是由于代码错误导致的,根据错误提示进行排查,如果是服务器错误,可以通过服务器日志去排插错误,以Nginx为例,找到ngnix.conf文件中指定的error.log文件,找到并打开,查看提示错误,解决即可!

6、为图片上传请求添加 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。

7、代码语法高亮

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

下载地址:Code Snippet | CKEditor.com

# ----------------------------------------
# 开启代码语法高亮
app.config['CKEDITOR_ENABLE_CODESNIPPET'] = True

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

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

其它如文章中的代码高亮,比这个更简单,只需要在详细页<head>中添加下列代码即可:

<link href="/static/ckeditor/plugins/codesnippet/lib/highlight/styles/monokai.css" rel="stylesheet">
<script src="/static/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

注意Code Snippet下载后解压的文件夹里面,里面的样式很多,可以根据自己的选择更换代码显示的样式(更换css文件链接),本文采用了monokai样式

返回顶部