一、需求来源

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


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

二、先看效果

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

编辑页面
在这里插入图片描述

查看页面
在这里插入图片描述

管理页面
在这里插入图片描述

三、整体思路介绍

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

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

本文思路如下:

使用 markdown 模块 + django 的 Form 类生成一个编辑器 将生成的编辑器源码进行魔改 自定义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 模型的中定义的类型需要修改成:

text
1 2
bug_detail = MDTextField()   # 注意为MDTextField()

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

text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
from django import forms
from .models import BugManage
from mdeditor.fields import MDTextFormField
class CreateBugForm(forms.Form):
      # https://zhuanlan.zhihu.com/p/55158579
      bug_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 = BugManage
      fields = '__all__'
      # fields = ('bug_name', 'bug_detail')

下面在 views.py 中引用

text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@csrf_exempt
#@login_required
def bugcreatePage(request):
   # https://zhuanlan.zhihu.com/p/55158579
   bug_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 查看到的网页源码:

text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
 <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/",
         // theme
         theme : "default",
         previewTheme : "default",
         editorTheme : "default",

         saveHTMLToTextarea: true, // editor.md 有问题没有测试成功
         toolbarAutoFixed: true,
         searchReplace: true,
         emoji: true,
         tex: true,
         taskList: false,
         flowChart: true,
         sequenceDiagram: true,

         // image upload
         imageUpload: 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 看到的文件依赖
 
 
text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 
 
这些文件的位置是在 python3.8/site-packages/mdeditor/static 路径下的
![在这里插入图片描述](https://img-blog.csdnimg.cn/7f89b00fcdce4fff890aaceea0dc06e5.png)
 通过分析源码可以看到,编辑器中的文件上传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)

text
1 2 3 4 5
 
 
直接放我改过的源码:
 
 
需求编写
text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
  <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>
        
        <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/",
                    // theme
                    theme : "default",
                    previewTheme : "default",
                    editorTheme : "default",

                    saveHTMLToTextarea: true, // editor.md 有问题没有测试成功
                    toolbarAutoFixed: true,
                    searchReplace: true,
                    emoji: true,
                    tex: true,
                    taskList: false,
                    flowChart: true,
                    sequenceDiagram: true,

                    // image upload
                    imageUpload: 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>
text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
 
 
这时候编辑器就出现了,就可以实现自定义的修改,比如大小布局之类的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/bf46507096c74868b31e901b0e3406ff.png)
 
 
## 七、数据展示
 
 
前面主要说的是将 markdown 编辑器放在前台使用,那么数据展示有啥要注意的呢?
 
 
   
前端的html 中要加入高亮引用
 
{{ bug_detail | safe }}
text
1 2 3 4 5
 
还要注意,那个 | 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文件,例如我的是->要在

{#语法高亮#}

text
1 2 3 4 5 6 7
 
如果要查看一共有多少种风格,可以看到网址 https://blog.csdn.net/mouday/article/details/83114164
   
后端要增加扩展
 
网上看到的实例中的扩展总是只有那么三四个,但是根据我的实践,可以把所有的扩展全部加上。页面布局会变好看很多。这些是从 markdown 的官网<a href="https://python-markdown.github.io/extensions/">扩展官网 https://python-markdown.github.io/extensions/</a>查到的
 

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=', '

', '/>

')

logger.warn(bug_detail)

result = {'bug_detail': bug_detail}

logger.warn(result)

return render(request, 'ruleroam/bugdetail.html', result)

text
1 2 3 4 5 6 7 8 9 10 11
   
图片大小控制
 
默认的图片是 100% 展示的,图片太大了
 在网上看到有解说怎么控制图片大小的,上传的时候,在图片格式后面,加上{:width=“100%” align=center}
 比如:[](http://pxpfco2u1.bkt.clouddn.com/markdown20190921144356.png){:width="100%" align=center}
 参考 <a href="https://www.jianshu.com/p/442bc083c835">https://www.jianshu.com/p/442bc083c835</a>
 
理论上可以直接修改 css 文件,但是我不太熟悉。
 想来想去还是直接用最原始的笨方法。后端给前端返回数据的时候,进行替换:
 

bug_detail = bug_detail.replace('
<img alt="" src=', '

', '/>

')

```

八、项目

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

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

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

原文地址:https://blog.csdn.net/Kevinhanser/article/details/121374767?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168905670416800182729683%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168905670416800182729683&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-21-121374767-null-null.142^v88^control_2,239^v2^insert_chatgpt&utm_term=markdown