• Editor-md使用
    # Editor-md使用 开源网址 ``` https://pandao.github.io/editor.md/ ``` ## TeX公式渲染 如果不配置的话默认是无法进行渲染的,就像下面这样 ![TeX公式不渲染的问题](image.png) ### 文章编写和展示页面配置 配置文章编写页面,首先下载Tex公式渲染需要的程序,建议用最新的网址,不要下载版本太低的,如果版本太低可能会不支持中文 cdn网址: ```0.16.9 https://cdn.bootcdn.net/ajax/libs/KaTeX/0.16.9/katex.min.js https://cdn.bootcdn.net/ajax/libs/KaTeX/0.16.9/katex.min.css ``` 由于cdn引用加载比较慢,建议保存到本地路径 `/static/editor.md-master/lib/KaTeX/0.16.9/` 下 js代码配置,启用tex,并引入刚刚下载的本地文件 ```javascript <script type="text/javascript"> $(function() { var editor = editormd("editor", { /*** 其他配置 ***/ tex : true, }); editormd.katexURL = { js : "/static/editor.md-master/lib/KaTeX/0.16.9/katex.min", // 注意:不需要写 .js 后缀 css : "/static/editor.md-master/lib/KaTeX/0.16.9/katex.min" // 注意:不需要写 .css 后缀 }; }); </script> ``` ## 代码一键复制 ![标签结构](/media/uploads/articles_image/202601/20260124_234941_屏幕截图_2026-01-24_234907.png "标签结构") 基本思想,给每个 pre 创建一个i标签,并创建一个点击事件,点击事件找到父亲并从父亲下的所有的 code 里面获取内容,最终添加到剪切板,遍历 `pre` 代码实现: ``` code_copy(){ $('pre').each(function (){ // 使用elementplus的图标库 let iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" ><path fill="currentColor" d="巴拉巴拉巴拉巴拉..."></path></svg>' let copy_btn = $('<i title="copy" class="code_copy">' + iconSvg + '</i>'); $(this).append(copy_btn) // 给刚才创建的按钮绑定点击事件 copy_btn.on('click', function() { // 克隆一份当前的 pre 标签 let clone = $(this).parent().clone(); // 在克隆体中删掉“复制按钮”自己,防止把按钮的乱码也复制进去 clone.find('.code_copy').remove(); let codeText = ''; let $dom = clone.find('code'); $dom.each(function() { // 遍历每一行,获取文本并手动加上换行符 codeText += $(this).text().trimEnd() + '\n'; }); // 创建一个临时的 textarea 元素 let textarea = $('<textarea>' + codeText + '</textarea>') // 挂载 -> 选中 -> 执行命令 -> 移除 $('body').append(textarea) textarea[0].select() document.execCommand('Copy') textarea.remove() ElementPlus.ElMessage.success('复制成功!'); }); }) } ```
    4 666 0 2026-01-21
  • 博客上传文章配置
    # 博客上传文章配置 本地图片目录,图片上传方法 <img src="/media/uploads/articles_image/202601/本地文章.png" width="600" alt="本地文章"> 问题的核心在于:Markdown 里的图片链接是“本地相对路径”,而网页需要的是“服务器 URL 路径”。一般用户的上传文件都放在 `/media/` 路径下,目录一般的结构: ```Plaintext my_blog_project/ ├── ... ├── static/ 静态文件 (CSS, JS, 网站Logo) └── media/ (所有用户文件) ├── .../... <-- 其他用户文件 └── uploads/ <-- 上传的 ├── /articles_image <-- 文章的图片 │ ├── 202601/ <-- 按年月分文件夹(推荐,防止一个文件夹几千张图卡死) │ │ ├── drone-schematic.jpg │ │ └── pixhawk-wiring.png │ └── 202602/ └── ... ``` ## 一、setting.py 配置 首先django项目自身需要配置,这个一般是一个工程的前提 在settings.py中,提前配置路径,这是一个项目的基本前提,不仅仅是图片上传需要这个配置,包括头像、封面等都需要配置,否则将会找不到 ```python # 用户自己上传文件的话需要自己配置,添加以下两行 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' ``` ## 二、手动管理图片 将图片复制到 `media/uploads_img/` 下面的目录下然后给 `.md` 文章中图片的目录前面加上图片的url路径,比如; 原来的内容: ```html <img src="position_mc.DC-hD8a2.png" width="600" alt="position"> ``` 修改后的内容 (文件路径为media下的实际路径): ```html <img src="/media/uploads/articles_image/202601/position_mc.DC-hD8a2.png" width="600" alt="position"> ``` ### img的html格式报错问题 这里用标准的markdown格式可以渲染图片,但是 html 格式的图片就无法进行。由于标准的markdown无法用宽度调节的代码,并且为了更好的适配不同的格式,因此还是需要兼容img标签,这里editor.md本身是支持html语法的 注:虽然此功能能极大地扩展 Markdown 语法,但也面临着安全上的风险,所以默认是不开启的。 <img src="/media/uploads/articles_image/202601/error-1.png" width="600" alt="error"> 这是由于 editor.md 默认不启用 HTML 语法, 因此需要配置editor.md的js代码引用部分 下面是官网的说明 { htmlDecode : true // Decode all html tags & attributes // Filter tags/attributes expression : tagName,tagName,...|attrName,attrName,... htmlDecode : "style,script,iframe,sub,sup|on*" // Filter tags, and all on* attributes //htmlDecode : "style,script,iframe,sub,sup|*" // Filter tags, and all attributes //htmlDecode : "style,script,iframe,sub,sup,embed|onclick,title,onmouseover,onmouseout,style" // Filter tags, and your custom attributes } 因此只需要在原本的JS代码中配置 `htmlDecode : "style,script,iframe,sub,sup|on*"` 即可 ## 三、图片自动上传配置 核心部分,通过editor.md的接口进行配置 ### 1.前端配置 ```javascript var editor = editormd("test-editormd", { width : "100%", height : 720, path : "/static/editor.md-master/lib/", // --- 核心配置开始 --- // 1. 开启图片上传功能 imageUpload : true, // 默认为关闭 imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], // 允许的格式 // 2. 指向 Django 的上传接口,然后再django的urls.py里面配置 imageUploadURL : "/api/upload_image/", // --- 核心配置结束 --- // ... 其他配置 ... }); ``` 然后图片中就会有这个"本地上传"的按钮,点击可以发现后台会报错误,这是因为还没有配置后端的代码 <img src="/media/uploads/articles_image/202601/error-not-found.png" width="600" alt="error not found"> <img src="/media/uploads/articles_image/202601/本地上传.png" width="400px" alt="本地上传"> ### 2.后端配置 前提:django下有api这个app,并且所有以api开头的请求都分发到api下面 editor.md 官方图片上传示例 <img src="/media/uploads/articles_image/202601/editor.md图片上传示例.png" width="700px" alt="editor.md图片上传示例"> #### 2.1 创建视图函数 在api这个app下目录下,创建文件 `/api/views/uploads.py` ```python import os import uuid import datetime from django.conf import settings from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.clickjacking import xframe_options_exempt @csrf_exempt @xframe_options_exempt def upload_image(request): if request.method == 'POST': res={ 'success': 0, # 0表示失败,1表示成功 'message': "上传失败", 'url': "" } # 获取上传的文件 (注意这个 key 是 editor.md 固定的) img_file = request.FILES.get('editormd-image-file') if not img_file: res['message'] = "未获取到图片" return JsonResponse(res) # root: 文件名, ext: 文件后缀 root, ext = os.path.splitext(img_file.name) # 安全清洗: 把文件名里的空格替换成下划线,防止 Linux 路径报错 safe_root = root.replace(' ', '_') now = datetime.datetime.now() image_name = f"{now.strftime('%Y%m%d_%H%M%S')}_{safe_root}{ext}" relative_path = os.path.join('uploads', 'articles_image', now.strftime('%Y%m')) abs_image_path = os.path.join(settings.MEDIA_ROOT, relative_path) if not os.path.exists(abs_image_path): os.makedirs(abs_image_path) file_path = os.path.join(abs_image_path, image_name) with open(file_path, 'wb+') as f: for chunk in img_file.chunks(): f.write(chunk) url_path = os.path.join(settings.MEDIA_URL, relative_path, image_name) # 强制替换反斜杠为正斜杠 (兼容 Windows 开发环境) url_path = url_path.replace('\\', '/') # 测试 # print(img_file, root, ext, safe_root, image_name) # print('上传文件名字:' + root + ext) # print('文件保存名称:' + image_name) # print('相对路径:' + relative_path) # print('绝对路径:' + abs_image_path) # print('返回路径:' + url_path) res['success'] = 1 res['message'] = "上传成功" res['url'] = url_path return JsonResponse(res) ``` <img src="/media/uploads/articles_image/202601/print.png" alt="print"> 代码中用到的工具 - os.path.splitext(...): 这是一个 Python 工具,专门把文件名切成两半:(文件名, 后缀)。 - os.path.join(absolute_path, image_name) - os.path.join(...): 路径拼接 - 把‘文件夹路径’和‘文件名’拼在一起 - 'wb+': ##### Django 安全机制拦截允许 由于 editor.md 是一个第三方的 JavaScript 库,它内部写死了一套上传图片的逻辑,根据后面返回到前端的报错提示, 大概率是用的比较古老的hidden iframe表单提交,并且他默认也不知道项目是Django项目,因此不会带Token,就需要 Django 不再验证请求的来源是否绝对合法,直接放行,因此使用装饰器 `@csrf_exempt` 来免去验证请求。 但是这里发现仅仅免去验证请求还不够,在前端当后端返回一个URL时前端也报错 <img src="/media/uploads/articles_image/202601/error-2.png" width="800px" alt="error-2"> editor.md 的图片上传机制是创建一个看不见的 `<iframe>` 来提交图片的, Django 发现这个请求要在 iframe 里显示,直接拒绝了 (deny)。然后 Editor.md 的 JS 代码试图去读取 iframe 里的上传结果(JSON),结果发现 iframe 被浏览器封锁了,于是报错说“无法跨域访问”。 因此这里还需要再创建一个装饰器 `@xframe_options_exempt` 因此需要引入下面两个装饰器 ```python from django.views.decorators.csrf import csrf_exempt from django.views.decorators.clickjacking import xframe_options_exempt ``` #### 2.2 配置路由(urls.py) 前提在 setting.py 目录下的 urls.py 中以及将所有以/api开头的分发到api/urls.py中,再由他进行分发 在setting.py 目录下 `urls.py` 中 ```python from django.urls import include, re_path urlpatterns = [ # ... 其他路由 ... re_path(r'^api/', include('api.urls')), # 将所有api开头的api,都分发到api的urls.py中 ] ``` 在 `api/urls.py` 中 ```python from django.urls import path from api.views import uploads urlpatterns = [ # ... 其他路由 ... path('upload_image/', uploads.upload_editor_image, name='upload_editor_image'), ] ```
    6 666 0 2026-01-22
  • django的CDN配置
    # django的CDN配置 ## 修改 settings.py 在 `settings.py`中配置 ```python # 设为 True 则使用 CDN,False 则使用本地路径 USE_CDN = False # cdn CDN_LINKS = { 'vue': 'https://cdn.staticfile.org/vue/3.4.21/vue.global.prod.js', 'axios': 'https://cdn.staticfile.org/axios/1.6.8/axios.min.js', 'jquery': '/static/js/jquery/jquery-3.7.1.min.js', 'element_plus_index': 'https://cdn.staticfile.org/element-plus/2.3.0/index.full.min.js', 'element_plus_icons': 'https://cdn.staticfile.org/element-plus-icons-vue/2.3.1/index.iife.min.js', 'katex': 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min', # 不需要加后缀,js和css都通用 } # 本地 LOCAL_LINKS = { 'vue': '/static/vue/vue.global.js', 'axios': '/static/js/axios/axios.min.js', 'jquery': '/static/js/jquery/jquery-3.7.1.min.js', 'element_plus_index': '/static/element-plus/element-plus@2.13.0/dist/index.full.js', 'element_plus_icons': '/static/element-plus/icons-vue@2.3.2/dist/index.iife.min.js', 'katex': '/static/editor.md-master/lib/KaTeX/0.16.9/katex.min', # 不需要加后缀,js和css都通用 } ``` ## 创建自定义 Context Processor 在app下创建 `context_processors.py` ## 在ettings中注册 ``` TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ # 其他配置 ... # 用户自定义函数,加上这一行 # 格式:'应用名.文件名.函数名' 'app01.context_processors.js_libs', ], }, }, ] ``` ## 引用 ```html <script src="{{ libs.vue }}"></script> <script src="{{ libs.element_plus_index }}"></script> <script src="{{ libs.element_plus_icons }}"></script> <script src="{{ libs.jquery }}"></script> <script src="{{ libs.axios }}"></script> ```
    17 666 0 2026-01-22
  • django 局部钩子和全局钩子
    # django 局部钩子和全局钩子 钩子(hook)的概念在编程界非常普遍,钩子的含义就是,你不需要手动调用,当系统运行到某个地方时,会自动帮你“钩起”这段代码。 Django 固定的钩子用法为,在自定义的Form类中写 `clean_字段名`、 `clean` 以用来对特定的字段进行校验。以 clean (干净) 命名代表清晰数据的含义 ## 局部钩子 在Fom类中定义 `clean_字段名()` 方法,就能够实现对特定字段进行校验。(校验函数正常必须返回当前字段值) 局部钩子处理文章简介的字段, 例如: ``` # 局部钩子,name不能重复 def clean_name(self): name = self.cleaned_data.get('name') user_query = UserInfo.objects.filter(username=name) if user_query: self.add_error('name', "该用户已注册") return self.cleaned_data ``` ## 全局钩子 局部钩子只能“自扫门前雪”,它看不见别的字段。有些场景你需要校验两个字段是否相等时就没有办法使用,比如以下场景必须动用全局钩子: 密码一致性校验:你需要同时对比 password 和 re_password ``` def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd != re_pwd: self.add_error('re_pwd', "两次密码不一致") return self.cleaned_data ``` ## 参考网址 ```url https://www.cnblogs.com/open-yang/p/11223175.html ```
    6 666 0 2026-01-26