专栏首页大猪的笔记django admin集成markdown

django admin集成markdown

前言

用Markdown语法来写博客,既通用又能装B。弄了一个上午,把自己的网站弄上了markdown编辑器。相当的嗨森。

主要步骤

主体思想是:用js完成一切。

步骤:下载安装 -> 覆盖admin的templates -> 用js代码替换控件 -> 写后台处理文件上传 -> 配置url -> 完工

下载安装

  • 搜索editor.md(不用pip中的django_markdown,因为太老了,一大堆兼容性问题)
  • 把editor.md解压到static/editor.md目录。

覆写目标admin的样式

在django中,可以覆写样式来改变admin的页面。django默认会先从本地的templates文件夹中取样式文件。在templates文件夹下创建文件(链接):

/templates/admin/blog/blogpost/change_form.html

关键内容:

<script src="/static/js/jquery.min.js" type="text/javascript"></script>
<script src="/static/editor.md/editormd.min.js"></script>
<script src="/static/js/blog/blogpost_admin_changeform.js" type="text/javascript"></script>

此文件意在载入editor.md和用户自定义的js。使得可以用blogpost_admin_changeform.js中完成操作。 顺便一说,做网站学点Jquery很重要。

自定义 js 内容

能载入js文件,就可以大显身手了。

blogpost_admin_changeform.js(链接)关键内容:

//处理markdown
var body_parent = $("#id_body").parent();
var body_val = $("#id_body").val();
$("#id_body").remove();
var body = "<div id='bodycontent'><div>";
body_parent.append(body);

testEditor = editormd("bodycontent", {
    width   : "70%",
    value   : body_val,
    name    : "body",
    emoji   : true,
    height  : 640,
    syncScrolling : "single",
    path    : "/static/editor.md/lib/",
    imageUpload    : true,
    imageFormats   : ["jpg", "jpeg", "gif", "png", "bmp", "webp", "txt"],
    imageUploadURL : "/blog/upload?aid="+$("#id_guid").val(),
});

这个文件采用js的方式,将admin生成的body控件替换成了editor.md的控件。

处理显示界面

如果顺利,在admin刷新一下,就能看到美腻的editor.md了。然保存在数据库的文本,还是markdown的格式,在显示页面,需要把markdown转换成html才能正常显示

法1 前台转换

editor.md自带了js库,可以完成markdown to html。具体不表。

法2 后台转换

因为某些原因,我用的是后台转换:

安装Python库

apt-get install markdown
# 尝试这几个命令
# apt-get install python-pygments
# pip install Pygments
apt-get install Pygments #用作代码着色

修改后台代码

blog.body = markdown.markdown(blog.body, extensions=['markdown.extensions.extra',"markdown.extensions.nl2br",'markdown.extensions.sane_lists','markdown.extensions.codehilite'])

附带相关的css

显示页面载入本文附带的css:colorful.css

完成上传功能

创建后台代码

在django中创建upload_file.py(仅用做示例,有安全隐患):

import json
import os
from urllib import quote
from django.http import HttpResponse
import tools.webTools as tools
from ueditor.models import attachment
from django.views.decorators.csrf import csrf_exempt
import random
from models import BlogPost
import dazhu.settings

def convert_name_html_valid(input_name):
    file_name = os.path.split(input_name)
    file_name_arr = os.path.splitext(file_name[1])
    quote_name_arr = [quote(x) for x in file_name_arr]
    quote_name_arr[0] = "%s_%s" % (quote_name_arr[0], random.randint(1, 99))
    return quote_name_arr[0] + quote_name_arr[1]

@csrf_exempt
def upload_files(request):
    class _Result(object):
        def __init__(self):
            self.success = 0
            self.message = ""
            self.url = ""
        def tojson(self):
            return json.dumps(self.__dict__)

    ret = _Result()
    aid = request.GET["aid"]
    tools.debug("upload_files guid", aid)

    fileObj = request.FILES.get('editormd-image-file')
    tools.debug("upload_files fileObj {}".format(fileObj.chunks()))
    source_filename = quote(fileObj.name.encode("utf8"))    
    rnd_file_name = convert_name_html_valid(source_filename)
    tools.debug("upload_files file_name {}".format(rnd_file_name))
    try:
        blog = BlogPost.objects.get(guid=aid)
    except Exception as errors:
        ret.message = "target blog isnt exist {}".format(errors)
        return HttpResponse(ret.tojson())

    tempAttachment = attachment()
    tempAttachment.blog = blog
    tempAttachment.sourceName = source_filename
    tempAttachment.rndName = rnd_file_name      
    tempAttachment.save()

    upload_folder = dazhu.settings.BASE_DIR + "/dazhu/static/upload/"        
    if not os.path.exists(upload_folder):
        os.makedirs(upload_folder)
    file_path = str(upload_folder + rnd_file_name)

    try:
        with open(file_path, 'wb+') as f:
            for chunk in fileObj.chunks():
                f.write(chunk)
    except Exception as errors:
        ret.message = "write file error {}".format(errors)
        return HttpResponse(ret.tojson())

    ret.success = 1
    ret.url = "/static/upload/"+rnd_file_name

    return HttpResponse(ret.tojson())

def get_attachment(request):
    aid = request.GET["aid"]
    attachment_list = BlogPost.objects.get(guid=aid).attachment_set.all()
    ret = []
    for attachment in attachment_list:
        ret.append({"rndName":attachment.rndName,"sourceName":attachment.sourceName})

    return HttpResponse(json.dumps(ret))

修改url

在blog.urls 增加

url(r"^upload$", upload_files),

附录 colorful.css

.codehilite {width:101%; padding: 3px; background-color: #F7F7F7;border: 1px solid #bbbbbb;white-space:normal;}
.codehilite span{word-break: normal;word-wrap:break-word; white-space:normal;}

.codehilite .hll { background-color: #ffffcc }
.codehilite .c { color: #808080 } /* Comment */
.codehilite .err { color: #F00000; background-color: #F0A0A0 } /* Error */
.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
.codehilite .o { color: #303030 } /* Operator */
.codehilite .cm { color: #808080 } /* Comment.Multiline */
.codehilite .cp { color: #507090 } /* Comment.Preproc */
.codehilite .c1 { color: #808080 } /* Comment.Single */
.codehilite .cs { color: #cc0000; font-weight: bold } /* Comment.Special */
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
.codehilite .ge { font-style: italic } /* Generic.Emph */
.codehilite .gr { color: #FF0000 } /* Generic.Error */
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
.codehilite .go { color: #808080 } /* Generic.Output */
.codehilite .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.codehilite .gs { font-weight: bold } /* Generic.Strong */
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.codehilite .gt { color: #0040D0 } /* Generic.Traceback */
.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
.codehilite .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */
.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
.codehilite .kt { color: #303090; font-weight: bold } /* Keyword.Type */
.codehilite .m { color: #6000E0; font-weight: bold } /* Literal.Number */
.codehilite .s { background-color: #fff0f0;} /* Literal.String */
.codehilite .na { color: #0000C0 } /* Name.Attribute */
.codehilite .nb { color: #007020 } /* Name.Builtin */
.codehilite .nc { color: #B00060; font-weight: bold } /* Name.Class */
.codehilite .no { color: #003060; font-weight: bold } /* Name.Constant */
.codehilite .nd { color: #505050; font-weight: bold } /* Name.Decorator */
.codehilite .ni { color: #800000; font-weight: bold } /* Name.Entity */
.codehilite .ne { color: #F00000; font-weight: bold } /* Name.Exception */
.codehilite .nf { color: #0060B0; font-weight: bold } /* Name.Function */
.codehilite .nl { color: #907000; font-weight: bold } /* Name.Label */
.codehilite .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.codehilite .nt { color: #007000 } /* Name.Tag */
.codehilite .nv { color: #906030 } /* Name.Variable */
.codehilite .ow { color: #000000; font-weight: bold } /* Operator.Word */
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
.codehilite .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */
.codehilite .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */
.codehilite .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */
.codehilite .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */
.codehilite .sb { background-color: #fff0f0 } /* Literal.String.Backtick */
.codehilite .sc { color: #0040D0 } /* Literal.String.Char */
.codehilite .sd { color: #D04020 } /* Literal.String.Doc */
.codehilite .s2 { background-color: #fff0f0 } /* Literal.String.Double */
.codehilite .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */
.codehilite .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */
.codehilite .si { background-color: #e0e0e0 } /* Literal.String.Interpol */
.codehilite .sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */
.codehilite .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */
.codehilite .s1 { background-color: #fff0f0 } /* Literal.String.Single */
.codehilite .ss { color: #A06000 } /* Literal.String.Symbol */
.codehilite .bp { color: #007020 } /* Name.Builtin.Pseudo */
.codehilite .vc { color: #306090 } /* Name.Variable.Class */
.codehilite .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */
.codehilite .vi { color: #3030B0 } /* Name.Variable.Instance */
.codehilite .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【JS】395-重温基础:事件

    事件流描述的是从页面中接收事件的顺序,通常有这样两种完全相反的事件流概念:事件冒泡流(IE团队提出)和事件捕获流(网景团队提出)。

    pingan8787
  • 测试开发进阶(三十一)

    使用base64加密之后的header + . + 使用base64加密之后的playload + 使用HS256算法加密,同时secret加盐处理

    zx钟
  • 爬虫入门(Java)

    网络爬虫是什么?是一种按照一定规则,自动抓取网页信息的脚本。对于获取公开数据,是一个效率很高的工具。本篇文章先介绍HttpClient,Jsoup这两个开源工具...

    营器
  • 干货技巧 | phpinfo信息利用

    php扩展的路径,图省事没用lamp包有点捞…(这里还是说下linux不推荐用phpstudy,很多linux装了phpstudy系统会崩)

    HACK学习
  • 讲述前后端分离的区别

    hello,你好,我们今天谈谈前后端为什么分离?其实做javaWeb开发的可能都会经历MVC模式的洗礼,犹新记得前后端的东西都是自己写,好像一个全栈。

    用户3625239
  • 【Web技术】400- 浅谈Shadow DOM

    你在实际的开发中很可能遇到过这样的需求:实现一个可以拖拽的滑块,以实现范围选择、音量控制等需求。

    pingan8787
  • 使用JWT来实现对API的授权访问

    JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各个系统之间用JSON作为对象安全地传输信息,并且...

    java思维导图
  • 四种高性能数据类型,Python collections助你优化代码、简洁任务

    Python 的最大优势之一就是它有各种各样的模块和软件包可供选择。这些模块和包将 Python 的功能扩展到了许多流行领域,包括机器学习、数据科学、Web 开...

    机器之心
  • ES索引管理工具 - curator

    官方文档:https://www.elastic.co/guide/en/elasticsearch/client/curator/current/action...

    二狗不要跑
  • 5分钟详解chrome浏览器架构知识

    在从事前端开发过程中,浏览器作为最重要的开发环境,浏览器基础是前端开发人员必须掌握的基础知识点,它贯穿着前端的整个网络体系。对浏览器原理的了解,决定...

    薛定喵君

扫码关注云+社区

领取腾讯云代金券