一、需求来源

近期有一个 django 前台使用 markdown 编辑器的需求,网上查了很多资料很多都是在后台使用 markdown 编辑器,然后在前台展示,可以参考。但是这种的教程对于想在前台页面使用 markdown 编辑器的人来说,属实是很难看懂啊。
所以我整理了一个完整的项目流程,包括前台编写前台展示。附项目。


编写时间:2021年11月17日11:46:33
支持转发,但请注明出处。

二、先看效果

话不多少,先放效果图,看看是不是自己想要的那种效果,我这里放三张图【编辑页面、查看页面、管理页面】。

  1. 编辑页面

  2. 查看页面

  3. 管理页面

三、整体思路介绍

首先理清楚概念, markdown 编辑器是什么?

  • 简单理解 markdown 编辑器也只是一个编辑器,是使用js、css等配置出来的一个编辑器。
  • 所以和其他的富文本编辑器没有什么本质区别。

本文思路如下:

  1. 使用 markdown 模块 + django 的 Form 类生成一个编辑器
  2. 将生成的编辑器源码进行魔改
  3. 自定义markdown展示结果

四、配置 django + markdown

安装必要的模块
pip3 install markdown # view视图中获取到数据库的数据,修饰为html语句,传到前端
pip3 install Pygments # 实现代码高亮
其他的提示缺啥就补啥吧,我就不全部列举了

参考 https://www.jianshu.com/p/a0ca9d1c4d72

参考:利用Python实现漂亮的Django Markdown富文本app插件 https://zhuanlan.zhihu.com/p/55158579

五、生成markdown编辑器源代码

Model 模型的中定义的类型需要修改成:

bug_detail = MDTextField()    # 注意为MDTextField()

首先,新建一个 forms.py 文件,然后写入代码:

from django import forms
from .models import BugManage
from mdeditor.fields import MDTextFormField
class CreateBugForm(forms.Form):# https://zhuanlan.zhihu.com/p/55158579bug_detail = MDTextFormField(label="", widget=forms.TextInput(attrs={'class': 'form-control'}), max_length=65535)# 其实还有另一种写法:仅供参考
from django import forms
from .models import BugManage
from mdeditor.fields import MDTextFormField
class CreateBugForm(forms.ModelForm):
# 代码中CreateBugForm类继承了Django的表单类forms.ModelForm,并在类中定义了内部类classMeta,指明了数据模型的来源,以及表单中应该包含数据模型的哪些字段。class Meta:model = BugManagefields = '__all__'# fields = ('bug_name', 'bug_detail')

下面在 views.py 中引用

@csrf_exempt
#@login_required
def bugcreatePage(request):# https://zhuanlan.zhihu.com/p/55158579bug_name = request.GET.get("bug_name")form = CreateBugForm()# 生成的markdown 编辑器的源码:logger.highlight(form)# 生成的markdown 编辑器的文件依赖logger.wa(form.media)# 这里可以看到 生成的完整的 markdown 源代码,然后在下面通过  return render()将网页源码传到 html 中进行展示result = {"form": form}return render(request, 'ruleroam/bugcreate.html', result)

前端这么写就行,只要是两个 {{ }} 中的内容,放哪都可以。
参考:利用Python实现漂亮的Django Markdown富文本app插件 https://zhuanlan.zhihu.com/p/55158579

这时候页面上就已经出现了一个markdown 编辑器了。
通过print 查看到的网页源码:

 <tr><th></th><td><style type="text/css">.wmd-wrapper  ul {margin-left: 0px !important;}.wmd-wrapper ul li{list-style: disc !important;}.wmd-wrapper ul ul li{list-style: circle !important;}.wmd-wrapper h1,.wmd-wrapper h2,.wmd-wrapper h3,.wmd-wrapper h4,.wmd-wrapper h5,.wmd-wrapper h6 {background: #ffffff !important;color: #000000 !important;}.wmd-wrapper h2,.wmd-wrapper h3,.wmd-wrapper h4{padding: 0px !important;}.wmd-wrapper h5{letter-spacing: 0px !important;text-transform: none !important;font-size: 1em !important;}.wmd-wrapper h6{font-size: 1em !important;color: #777 !important;}
</style><div class="wmd-wrapper"  id="id_bug_detail-wmd-wrapper"><textarea  cols="40" id="id_bug_detail" maxlength="65535" name="bug_detail" rows="10" required></textarea>
</div><script type="text/javascript">$(function () {editormd("id_bug_detail-wmd-wrapper", {watch: true, // 关闭实时预览lineNumbers: false,lineWrapping: false,width: "100%",height: 500,placeholder: '',// 当有多个mdeditor时,全屏后,其他mdeditor仍然显示,解决此问题。onfullscreen : function() {this.editor.css("border-radius", 0).css("z-index", 9999);},onfullscreenExit : function() {this.editor.css({zIndex : 10,border : "1px solid rgb(221,221,221)"})},syncScrolling: "single",path: "/static/mdeditor/js/lib/",// themetheme : "default",previewTheme : "default",editorTheme : "default",saveHTMLToTextarea: true, // editor.md 有问题没有测试成功toolbarAutoFixed: true,searchReplace: true,emoji: true,tex: true,taskList: false,flowChart: true,sequenceDiagram: true,// image uploadimageUpload: true,imageFormats: ['jpg', 'JPG', 'jpeg', 'JPEG', 'gif', 'GIF', 'png', 'PNG', 'bmp', 'BMP', 'webp', 'WEBP'],imageUploadURL: "/mdeditor/uploads/",toolbarIcons: function () {return ['undo', 'redo', '|', 'bold', 'del', 'italic', 'quote', 'ucwords', 'uppercase', 'lowercase', '|', 'h1', 'h2', 'h3', 'h5', 'h6', '|', 'list-ul', 'list-ol', 'hr', '|', 'link', 'reference-link', 'image', 'code', 'preformatted-text', 'code-block', 'table', 'datetime', 'emoji', 'html-entities', 'pagebreak', 'goto-line', '|', 'help', 'info', '||', 'preview', 'watch', 'fullscreen']},onload: function () {console.log('onload', this);//this.fullscreen();//this.unwatch();//this.watch().fullscreen();//this.setMarkdown("#PHP");//this.width("100%");//this.height(480);//this.resize("100%", 640);}});});
</script>
</td></tr>

通过print 看到的文件依赖

<link href="/static/mdeditor/css/editormd.css" type="text/css" media="all" rel="stylesheet">
<script src="/static/mdeditor/js/jquery.min.js"></script>
<script src="/static/mdeditor/js/editormd.min.js"></script>

这些文件的位置是在 python3.8/site-packages/mdeditor/static 路径下的

通过分析源码可以看到,编辑器中的文件上传url 和路径都是固定的,所以,如果使用原始的代码,就可以使用这个url编写路由,imageUploadURL: "/mdeditor/uploads/", 只需要url保持一致就行了。
细节我就啰不嗦了,可以查看项目分析。

六、抄源码改为自己用

经过上面的分析,知道了Form 的原理也是制作出一个网页源码然后返回到html中,这时候我们直接抄不就好了。

这里为什么要抄源码呢? 其实是因为在编辑数据时,从数据库中加载的数据不能直接用 .val() .text() 等形式赋值给前端的 textarea 中。

抄源码的时候,依赖的 css 文件也要复制到项目目录下。
这些文件的位置是在 python3.8/site-packages/mdeditor/static 路径下

将 编辑器源码直接放到 html 中之后,后端就不需要 Form 类的,就跟一个很普通的 input 标签一样的使用了。

def bugcreatePage(request):bug_name = request.GET.get("bug_name")if bug_name:request_type = "edit"bug_obj = BugManage.objects.filter(bug_name__exact=bug_name).first()result = {"request_type": request_type, "request_data": bug_obj}else:request_type = "create"result = {"request_type": request_type}return render(request, 'ruleroam/bugcreate.html', result)

直接放我改过的源码:


<div class="layuimini-container layuimini-content-page"><div class="layuimini-main"><div class="layui-card-header">需求编写</div><form class="layui-form layuimini-form" id="myFormId" method="get" lay-filter="myFormFilter"><div class="layui-form-item"><label class="layui-form-label required">需求名称</label><div class="layui-input-block"><label for="bug_name_Id"></label><input type="text" name="bug_name" id="bug_name_Id" lay-verify="required" lay-reqtext="需求名不能为空" placeholder="请输入需求名称" value="{{ request_data.bug_name }}" class="layui-input"></div></div><div class="layui-form-item layui-form-text"><label class="layui-form-label">需求摘要</label><div class="layui-input-block"><textarea name="bug_digest" id="bug_digest_Id" class="layui-textarea" placeholder="请输入摘要信息">{{ request_data.bug_digest }}</textarea></div></div><div class="layui-form-item layui-form-text">{# 这里是 forms 里自动生成的, 然后粘贴在这里的#}<style type="text/css">.wmd-wrapper  ul {margin-left: 0px !important;}.wmd-wrapper ul li{list-style: disc !important;}.wmd-wrapper ul ul li{list-style: circle !important;}.wmd-wrapper h1,.wmd-wrapper h2,.wmd-wrapper h3,.wmd-wrapper h4,.wmd-wrapper h5,.wmd-wrapper h6 {background: #ffffff !important;color: #000000 !important;}.wmd-wrapper h2,.wmd-wrapper h3,.wmd-wrapper h4{padding: 0px !important;}.wmd-wrapper h5{letter-spacing: 0px !important;text-transform: none !important;font-size: 1em !important;}.wmd-wrapper h6{font-size: 1em !important;color: #777 !important;}</style><link href="/static/mdeditor/css/editormd.css" type="text/css" media="all" rel="stylesheet"><script src="/static/mdeditor/js/jquery.min.js"></script><script src="/static/mdeditor/js/editormd.min.js"></script><label class="layui-form-label">需求详情</label><div class="layui-input-block"><div class="wmd-wrapper"  id="id_bug_detail-wmd-wrapper"><textarea  cols="40" id="id_bug_detail" maxlength="65535" name="bug_detail" rows="10" required>{{ request_data.bug_detail }}</textarea></div><script type="text/javascript">{# 这里是 forms 里自动生成的, 然后粘贴在这里的#}$(function () {editormd("id_bug_detail-wmd-wrapper", {watch: true, // 关闭实时预览lineNumbers: false,lineWrapping: false,width: "100%",height: 500,placeholder: '',// 当有多个mdeditor时,全屏后,其他mdeditor仍然显示,解决此问题。onfullscreen : function() {this.editor.css("border-radius", 0).css("z-index", 9999);},onfullscreenExit : function() {this.editor.css({zIndex : 10,border : "1px solid rgb(221,221,221)"})},syncScrolling: "single",path: "/static/mdeditor/js/lib/",// themetheme : "default",previewTheme : "default",editorTheme : "default",saveHTMLToTextarea: true, // editor.md 有问题没有测试成功toolbarAutoFixed: true,searchReplace: true,emoji: true,tex: true,taskList: false,flowChart: true,sequenceDiagram: true,// image uploadimageUpload: true,imageFormats: ['jpg', 'JPG', 'jpeg', 'JPEG', 'gif', 'GIF', 'png', 'PNG', 'bmp', 'BMP', 'webp', 'WEBP'],imageUploadURL: "/mdeditor/uploads/",toolbarIcons: function () {return ['undo', 'redo', '|', 'bold', 'del', 'italic', 'quote', 'ucwords', 'uppercase', 'lowercase', '|', 'h1', 'h2', 'h3', 'h5', 'h6', '|', 'list-ul', 'list-ol', 'hr', '|', 'link', 'reference-link', 'image', 'code', 'preformatted-text', 'code-block', 'table', 'datetime', 'emoji', 'html-entities', 'pagebreak', 'goto-line', '|', 'help', 'info', '||', 'preview', 'watch', 'fullscreen']},onload: function () {{#console.log('onload', this);#}//this.fullscreen();//this.unwatch();//this.watch().fullscreen();//this.setMarkdown("#PHP");//this.width("100%");//this.height(480);//this.resize("100%", 640);}});});</script></div></div><div align="center" style="margin-top: 30px"><button class="layui-btn layui-btn-sm" id="saveBtnId" lay-submit lay-filter="saveBtn">发表文章</button><a href="javascript:" class="layui-btn layui-btn-sm layui-btn-checked"  layuimini-content-href="ruleroam/bugs/">返回列表</a></div></form></div>
</div><script>layui.use(['form', 'table','miniPage','element'], function () {var $ = layui.jquery,form = layui.form,table = layui.table,miniPage = layui.miniPage;/*** 初始化表单,要加上,不然刷新部分组件可能会不加载*/form.render();if("{{ request_type }}" === "edit"){//设置input 不可编辑有三种写法{#$("#usernameId").attr("disabled", "disabled");#}{#$("#usernameId").attr("onfocus", "this.blur()");#}$("#bug_name_Id").attr("readonly", "true");//设置input属性$("#bug_name_Id").attr("style", "background-color: #e4e4e4; color:#000000;");}//监听提交form.on('submit(saveBtn)', function (obj) {$.ajax({url: '/ruleroam/bugadd/',type: 'post',dataType: 'json', //预期返回类型data: {"request_type": "{{ request_type }}", "request_data": JSON.stringify(obj.field).toString()},success:function(data){if(data.status === "success"){layer.alert(data.msg,{ icon:1,title:'提示', btn:["查看", "取消"]},function(index){layer.closeAll();window.location.href = "/#/ruleroam/bugdetail/?bug_name=" + obj.field.bug_name});}else{layer.alert(data.msg,{ icon:2,title:'提示'},function(index){ layer.close(index);});}},});return false;});});
</script>

这时候编辑器就出现了,就可以实现自定义的修改,比如大小布局之类的。

七、数据展示

前面主要说的是将 markdown 编辑器放在前台使用,那么数据展示有啥要注意的呢?

  1. 前端的html 中要加入高亮引用

     <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"><link rel="stylesheet" type="text/css" href="/static/mdeditor/css/markdown_emacs.css"><div style="margin-left: 100px">{{ bug_detail | safe }}</div>
    

    还要注意,那个 | safe 必须要加,适用于html转义用的
    那么高亮引用的文件从哪里来呢,使用这个工具生成。
    生成方式:

    #
    # pip install markdown #view视图中获取到数据库的数据,修饰为html语句,传到前端
    # pip install Pygments #实现代码高亮
    # 安装第二个包后还要执行
    # pygmentize -S default -f html -a .codehilite > markdown_highlighy.css
    # pygmentize -S default -f html -a .codehilite > default.css
    # pygmentize -S monokai  -f hl -a .codehilite > monokai.css
    #
    # 查看支持的风格
    # from pygments.styles import STYLE_MAP
    # for key in STYLE_MAP.keys():
    #     print(key)
    # https://blog.csdn.net/mouday/article/details/83114164
    #
    # 在文件夹下会发现生成了code.css文件,将这个css文件加入到你的static文件夹下css里面(路径自己定,只要用的时候引入正确就行了)
    # 最后一步在需要高亮的html文件里面导入刚刚生成的css文件,例如我的是->要在
    # <link rel="stylesheet" type="text/css" href="{% static 'static/css/markdown_highlighy.css' %}">  {#语法高亮#}
    

    如果要查看一共有多少种风格,可以看到网址 https://blog.csdn.net/mouday/article/details/83114164

  2. 后端要增加扩展

    网上看到的实例中的扩展总是只有那么三四个,但是根据我的实践,可以把所有的扩展全部加上。页面布局会变好看很多。这些是从 markdown 的官网扩展官网 https://python-markdown.github.io/extensions/查到的

    def bugdetailPage(request):bug_name = request.GET.get("bug_name")logger.warn(bug_name)bug_obj = BugManage.objects.filter(bug_name__exact=bug_name).first()# 将markdown语法渲染成html样式extensions=[# # 包含 缩写、表格等常用扩展# 'markdown.extensions.extra',# # 语法高亮扩展# 'markdown.extensions.codehilite',# # 自动生成目录# 'markdown.extensions.toc','markdown.extensions.extra','markdown.extensions.abbr','markdown.extensions.attr_list','markdown.extensions.def_list','markdown.extensions.fenced_code','markdown.extensions.footnotes','markdown.extensions.md_in_html','markdown.extensions.tables','markdown.extensions.admonition','markdown.extensions.codehilite','markdown.extensions.legacy_attrs','markdown.extensions.legacy_em','markdown.extensions.meta','markdown.extensions.nl2br','markdown.extensions.sane_lists','markdown.extensions.smarty','markdown.extensions.toc','markdown.extensions.wikilinks']bug_detail = markdown.markdown(bug_obj.bug_detail, extensions=extensions)bug_detail = bug_detail.replace('<img alt="" src=', '<img alt="" style="width: 40%;" src=')bug_detail = bug_detail.replace('/></p>', '/></p></br>')logger.warn(bug_detail)result = {'bug_detail': bug_detail}# logger.warn(result)return render(request, 'ruleroam/bugdetail.html', result)
    
  3. 图片大小控制

    默认的图片是 100% 展示的,图片太大了
    在网上看到有解说怎么控制图片大小的,上传的时候,在图片格式后面,加上{:width=“100%” align=center}
    比如:[](http://pxpfco2u1.bkt.clouddn.com/markdown20190921144356.png){:width="100%" align=center}
    参考 https://www.jianshu.com/p/442bc083c835

    理论上可以直接修改 css 文件,但是我不太熟悉。
    想来想去还是直接用最原始的笨方法。后端给前端返回数据的时候,进行替换:

    bug_detail = bug_detail.replace('<img alt="" src=', '<img alt="" style="width: 40%;" src=')
    bug_detail = bug_detail.replace('/></p>', '/></p></br>')
    

八、项目

我还是不放在github 里面了,我传CSDN共享文件进去,然后想看的可以直接下载。

我设置的是0积分,是免费下载的。

https://download.csdn.net/download/Kevinhanser/43587340

Django + markdown 前台使用markdown编辑器深入浅出详解相关推荐

  1. 微信公众平台菜单编辑php,Vue.js实现微信公众号菜单编辑器步骤详解(上)

    这次给大家带来Vue.js实现微信公众号菜单编辑器步骤详解(上),Vue.js实现微信公众号菜单编辑器的注意事项有哪些,下面就是实战案例,一起来看一下. 学习一段时间Vue.js,于是想尝试着做一个像 ...

  2. Django新手入门(五)——Models详解

    Django新手入门(五)--Models详解 数据库 ORM Django中的ORM Django中的Models 定义models.py中的类 常用数据字段 常用设置选项 常用函数以及修饰词 其他 ...

  3. GBDT!深入浅出详解梯度提升决策树

    AI有道 一个有情怀的公众号 1 Adaptive Boosted Decision Tree Random Forest的算法流程我们之前已经详细介绍过,就是先通过bootstrapping&quo ...

  4. 8s pod 查看 的yaml_Kubernetes入门到实战(五)深入浅出详解Pod

    作者:Happy老师 链接:https://blog.51cto.com/happylab/2500457 写在前面 前面的系列文章已介绍kubernetes架构,安装,升级和快速入门,读者通过文章的 ...

  5. 深入浅出 详解Android Surface系统(1)

    文章来自:http://mobile.51cto.com/android-259922.htm 本文详细介绍了Android中的Surface系统,采用情景分析的办法,详解了何为SurfaceFlin ...

  6. 深入浅出 详解Android Surface系统(2)

    文章来自:http://mobile.51cto.com/android-259922_1.htm 本文详细介绍了Android中的Surface系统,采用情景分析的办法,详解了何为SurfaceFl ...

  7. Markdown中的LaTeX公式——希腊字母详解

    若要在Markdown中使用,则在两个美元符号之间敲入对应LaTeX代码实现公式行显示效果,若为公式块,则要在四个美元符号中间敲入,类似Markdown代码行和代码块. 共24个希腊字母,通过敲入 反 ...

  8. 【MarkDown】CSDN Markdown之思维导图mindmap详解

    文章目录 思维导图(Mindmap) 一个思维导图的例子 语法 形状 矩形 圆角矩形 圆形 爆炸 云朵 六边形 默认 图标和类 图标 类 不清晰的缩进 Markdown字符串 与库或网站资源集成 思维 ...

  9. 【MarkDown】CSDN Markdown之时间轴图timeline详解

    文章目录 时间轴图 一个关于时间轴图的例子 语法 分组 长`时间段`或`事件文本`换行 `时间段`和`事件文本`样式 自定义颜色方案 主题 基础主题 森林主题 黑色主题 默认主题 中性主题 与库或网站 ...

最新文章

  1. 腾讯2013暑期实习笔试面试总结
  2. Oracle 判断字符串是否能转成数字。
  3. 简要分析电话光端机的常见问题
  4. Navicat 9.1、10.0 简体中文最新版,注册码(For Mysql)
  5. PID控制器开发笔记之二:积分分离PID控制器的实现
  6. android服务器连接失败,Android Studio服务器连接失败
  7. 手写 new 操作符
  8. 3.1 Ext JS 组件总览
  9. oracle根据中文获取拼音全拼函数
  10. Struts的增删改查
  11. 计算机一级考试图片水印怎么加,图片水印怎么添加?一起来看看这几个方法
  12. 测温监控摄像头_温度湿度远程监控摄像头
  13. 【有奖征询】可查询商票及企业境外债软件有奖征询
  14. cad net 绘制带圆弧的多段线
  15. 阿里云服务器购买合同怎么申请
  16. 防止电脑辐射的最好方法
  17. 小白都能看懂的关于Mixins机制的理解
  18. NGS测序嵌合体是个需要去除的错误扩增序列
  19. mysql 赋予用户权限 grant all privileges on
  20. 地图下载器工具-Java

热门文章

  1. 衡山德华盛星源高中2021高考成绩查询,十年磨砺树凌云壮志, 今朝亮剑必蟾宫折桂——德华盛星源高中召开2021届平安高考暨考前动员大会...
  2. 让爱呼吸| 气质星妈,与美同“形
  3. 英语六级词汇总结(此文持续更新)
  4. runtime error: member access within null pointer of type ‘struct ListNode‘
  5. 乞力马扎罗的雪 - 笔记
  6. 谷歌浏览器无法在地址栏输入搜索的解决办法
  7. flash游戏基本操作:上下左右的移动
  8. 论文笔记 -- Learning Representations for Time Series Clustering
  9. python args函数_Python函数参数*args 和**kwargs的用法
  10. Linux虚拟机新增磁盘分区格式化和挂载