在Django管理后台时,如果需要处理代码相关的内容(如为网站添加额外CSS),往往会需要嵌入一个简单的代码编辑器。
这里以CodeMirror为例介绍如何为Django后台提供一个代码编辑器。最终效果为:
对于其余类似的富文本管理,解决方法是相似的。
涉及内容:
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.css 和 lib/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 : 1 px solid #ccc ;
border-radius : 5 px ;
width : 70 % ;
overflow-x : hidden ;
}
.CodeMirror-linenumber.CodeMirror-gutter-elt ,
span [role = " presentation " ] {
font-family : Consolas, " Courier New " , Courier , monospace ;
font-size : 14 px ;
}
</ 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/