博客上传文章配置
# 博客上传文章配置
本地图片目录,图片上传方法
<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