CodeMirror实现Django后台代码编辑器

0

在Django管理后台时,如果需要处理代码相关的内容(如为网站添加额外CSS),往往会需要嵌入一个简单的代码编辑器。

这里以CodeMirror为例介绍如何为Django后台提供一个代码编辑器。最终效果为:

对于其余类似的富文本管理,解决方法是相似的。

涉及内容:

  • Django
  • JavaScript

CodeMirror简介

本地运行

CodeMirror是一个纯JavaScript实现的Web编辑器,与之类似的还有Ace editor,以及它的二次开发项目MDeditor。

CodePen等许多在线代码编辑网站使用的编辑器都是CodeMirror。基于CodeMirror,也可以使用MDeditor在后台直接编写基于Markdown的文章。

可以在CodeMirror官方网站 https://codemirror.net/ 下载到它的源码:

注意下载按钮在太极图的左半部分

或者在GitHub上查看源码:https://github.com/codemirror/codemirror

将下载的源码解压,得到一个文件夹。在文件夹的同级目录下新建一个HTML文件,使用以下代码可以编写一个基本的代码编辑器:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel=stylesheet href="codemirror/lib/codemirror.css">
    <script src="codemirror/lib/codemirror.js"></script>
    <script src="codemirror/mode/javascript/javascript.js"></script>
    <script src="codemirror/mode/css/css.js"></script>
    <script src="codemirror/mode/htmlmixed/htmlmixed.js"></script>
    <script src="codemirror/addon/edit/matchbrackets.js"></script>
</head>
<body>

<textarea id=code></textarea>

<script>
    var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
        lineNumbers: true,  // 显示行号
        lineWrapping: true, // 强制换行
        mode: "text/html",  // 代码类型
        matchBrackets: true // 括号匹配
    });
</script>
</body>
</html>

在浏览器中打开该HTML文件,如果出现如下所示的编辑器,表示CodeMirror可以正常使用:

运行效果

该编辑器默认可以高亮HTML代码、括号对和不匹配的HTML标签。

编辑器简单配置

完整的使用手册可以在CodeMirror的官网查阅。这里仅介绍一些基本的配置。

一个基本的代码编辑器需要包含 lib/codemirror.csslib/codemirror.js 两个文件。mode 目录下的文件用于为编辑器添加编程语言支持;addon 目录下的文件为编辑器附带额外功能;theme 目录下的文件可以更改代码高亮的颜色主题。

editor 变量从一个 <textarea> DOM结点中得到了一个变量,第二个参数是一些初始化配置。可以对该变量调用一些方法来动态更改编辑器的一些属性。常用的有:editor.setOption("option","value"); 修改配置,editor.setSize(width, height) 改变大小。

以下示例在编辑器的HTML代码做了一些配置:

<script src="codemirror/mode/python/python.js"></script>
<link rel="stylesheet" href="codemirror/theme/lesser-dark.css">
<style>
    .CodeMirror.CodeMirror-wrap.cm-s-lesser-dark {
        border: 1px solid #ccc;
        border-radius: 5px;
        width: 70%;
        overflow-x: hidden;
    }
    .CodeMirror-linenumber.CodeMirror-gutter-elt,
    span[role="presentation"] {
        font-family: Consolas, "Courier New", Courier, monospace;
        font-size: 14px;
    }
</style>
<script>
    var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
        lineNumbers: true, // 是否显示行号
        mode:"python",     // 默认脚本编码
        lineWrapping:true, // 是否强制换行
        matchBrackets: true,
    });
    editor.setSize(550, null);
    editor.setOption("theme", "lesser-dark")  // just an example
</script>

配置完成后,编辑器的效果为:

应用到Django后台

设计组件

要在Django后台中应用自定义的编辑器,最好的方法是编写自定义控件替换默认的 <textarea><input> 。在应用文件夹 app 下新建 widgets.py ,存放所有的组件类:

# widgets.py
from django.forms import Textarea
from django.template.loader import render_to_string
from django.utils.encoding import force_str
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

class EditorWidget(Textarea):
    class Media:
        css = {
            'all': (
                'assets/codemirror/lib/codemirror.css',
            )
        }
        js = (
            'assets/codemirror/lib/codemirror.js',
            'assets/codemirror/mode/xml/xml.js',
            'assets/codemirror/mode/javascript/javascript.js',
            'assets/codemirror/mode/css/css.js',
            'assets/codemirror/mode/htmlmixed/htmlmixed.js',
            'assets/codemirror/addon/edit/matchbrackets.js',
        )
    def render(self, name, value, renderer=None, attrs=None):
        if value is None:
            value = ''
        return mark_safe(render_to_string('frame/editor.html', {
            'value': conditional_escape(force_str(value)),
            'name': name
        }))

这里自定义的组件类基于 <textarea> ,元类 Media 声明了组件需要用到的所有额外 CSS 和 JS 资源。注意,js 属性只需要简单包含所有文件即可,而 css 字典则确定对于不同媒体分别应用哪些 CSS 。Django会将这些静态资源提前安排在网页的 <head> 中。

注意这些资源需要放在静态资源文件夹下。如果不确定需要放在哪里,可以调用一个实例的 .media 属性查询实际引用的URL路径:

(http) PS D:\Http\server\django_demo> py -u manage.py shell
(InteractiveConsole)
>>> from app.widgets import EditorWidget
>>> w = EditorWidget()
>>> print(w.media)
<link href="/static/assets/codemirror/lib/codemirror.css" type="text/css" media="all" rel="stylesheet">
<script src="/static/assets/codemirror/lib/codemirror.js"></script>
<script src="/static/assets/codemirror/mode/xml/xml.js"></script>
<script src="/static/assets/codemirror/mode/javascript/javascript.js"></script>
<script src="/static/assets/codemirror/mode/css/css.js"></script>
<script src="/static/assets/codemirror/mode/htmlmixed/htmlmixed.js"></script>
<script src="/static/assets/codemirror/addon/edit/matchbrackets.js"></script>

对于一个自定义的组件,最重要的就是重写 .render() 方法。该方法决定了组件会生成如何的HTML代码。name 参数为组件对应表单字段的 name 值,为不同字段做区分;value 参数为组件对应表单字段的 value 值,负责提交这些数据。由于Django后台的组件既需要发送数据到服务端,也需要接收服务端发来的数据,因此需要根据这两个参数生成合适的表单元素。

这里将模板代码单独存放,使用 render_to_string() 函数配合传入的上下文生成合适的HTML片段:

{# templates/frame/editor.html #}
<div id="editor">
    <textarea id="code" name="{{ name }}">{% if value %}{{ value }}{% endif %}</textarea>
</div>
<script>
    var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
        lineNumbers: true,
        lineWrapping:true,
        mode: "text/html",
        matchBrackets: true
    });
</script>

创建表单

如果要把组件应用到后台,需要作为一个表单中一个合适的字段。这里新建一个表单类,用包含自定义组件的字段替换原有的字段:

# forms.py
from django import forms
from .widgets import EditorWidget

class EditorForm(forms.ModelForm):
    body = forms.CharField(required=True, widget=EditorWidget)

注意,Django后台编辑一个 Model 时,其表单对应Django内的 ModelForm 类。

应用到后台

最后一步,将得到的表单应用到 ModelAdmin 站点上,替换原有的 .form 值。

# admin.py
from django.contrib import admin
from .models import ArticleModel
from .forms import EditorForm

@admin.register(ArticleModel)
class ArticleAdmin(admin.ModelAdmin):
    form = EditorForm

以上步骤完成后,等Django更新服务后刷新后台,即可使用基于CodeMirror的代码编辑器了:

最后

注意

在后台与服务器交换数据时,数据会在表单中交互,因此注意生成表单时字段需要给定正确的 name 属性,否则数据无法正确显示与提交

CodeMirror编辑器会在生成时,提取 <textarea> 中的数据作为编辑器的初始代码;并在表单提交时,将编辑器的数据实时更新到 <textarea> 中。这也就是为什么编辑器不在 <textarea> 中操作,但是后台可以正确与编辑器同步数据。

延伸阅读

CodeMirror用户参考手册:

https://codemirror.net/doc/manual.html

Django文档中关于此部分的内容:

https://docs.djangoproject.com/en/4.0/ref/forms/api/

https://docs.djangoproject.com/en/4.0/ref/forms/widgets/

https://docs.djangoproject.com/en/4.0/topics/forms/media/

京ICP备2021034974号
contact me by hello@frozencandles.fun