-
博客上传文章配置
# 博客上传文章配置 本地图片目录,图片上传方法 <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 -
部署django项目
# 部署django项目 将 django 项目从windows迁移并部署到linux,本项目核心基于 django + vue3 + mysql搭建 本项目部署主要分为四步:1.传代码,2.配置环境3.传数据库4.配置并运行项目 此外在数据库迁移时出现了问题,因此还涉及 linux 的 python 源码编译安装,以及 mysql 的安装过程 项目目录: <img src="/media/uploads/articles_image/202601/20260127_025104_image.png" height="500" alt="项目目录"> ## 一、传代码 这一部分摘要:这一部分主要是通过 `requirements.txt` 提前备份版本,通过 git 将文件传到 linux 上 ### 1.requirements.txt备份 首先在本地项目里生成一个requirements.txt文件,用来备份虚拟环境中用到的 python 库的版本: ```shell pip freeze > requirements.txt ``` 文件结构大概这样: ```Plaintext asgiref==3.11.0 <-- 库名 == 版本号 cssselect==1.3.0 Django==6.0 ... ``` ### 2.把代码推送到 Gitee 或 GitHub #### 1.1 创建.gitignore文件 不需要上传 venv 文件夹(虚拟环境)和 __pycache__ 文件夹 db.sqlite3: 数据 media: 用户上传文件目录 ```Plaintext venv/ __pycache__/ *.pyc db.sqlite3 media/ .DS_Store .vscode/ ``` #### 1.2 上传 - 创建github仓库 - 在本地项目文件夹: ```bash git init git add . git commit -m "首次部署" git branch -M main git remote add origin <你的仓库地址> git push -u origin master ``` ### 2 克隆代码 ```bash cd ~ git clone https://github.com/LuminovaBeta/my_blog_v1.git my_blog ``` 如果github连接慢,直接告诉 Git 使用代理,防止以后直连出问题,克隆完成后,取消代理 ```Bash cd ~ git config --global http.proxy http://192.168.1.101:7890 git config --global https.proxy http://192.168.1.101:7890 git clone https://github.com/LuminovaBeta/my_blog_v1.git my_blog git config --global --unset http.proxy git config --global --unset https.proxy ``` ## 二、配置虚拟环境和系统依赖 首先由于虚拟环境必须前提是电脑自己带的环境,因此需要装以下根项目的版本比较接近的python版本,如果版本差距比较大,建议装一下版本差不多的例如我的项目是`Python 3.12.4`,但是我的系统是 `Python 3.8.1` 这个时候就需要在保留系统自带 Python 3.8 的同时,安装你指定的 Python 3.12.4,因此需要使用 源码编译安装 (Source Compilation) 并配合 altinstall 命令。 源码构建官网教程地址 ```url https://devguide.python.org/getting-started/setup-building/#unix ``` 1. 安装编译依赖 Python 编译需要很多底层库的支持(比如 SSL 加密、SQLite 数据库、解压支持等)。如果这一步缺了东西,装出来的 Python 会出现“缺胳膊少腿”的情况(比如 pip 用不了,或者连不上 HTTPS)。 ```bash sudo apt update sudo apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev liblzma-dev tk-dev uuid-dev libmysqlclient-dev ``` 2. 下载Python源码 ```bash cd ~ wget https://www.python.org/ftp/python/3.12.4/Python-3.12.4.tgz tar -xvf Python-3.12.4.tgz cd Python-3.12.4 ``` 3. 配置与编译 配置环境(开启优化): ```bash ./configure --enable-optimizations --with-lto ``` 编译 ```bash make -j4 ``` 安装必须使用 altinstall,绝对不要用 install ```bash sudo make altinstall ``` 验证安装 ```bash python3 --version # 输出应该是原版: Python 3.8.10 python3.12 --version # 输出应该是: Python 3.12.4 ``` 4. 为你的项目创建**虚拟**环境 首先确保没有旧的虚拟环境 创建新的环境: ```bash /usr/local/bin/python3.12 -m venv venv ``` 激活环境: ```bash source venv/bin/activate ``` 5. 进入虚拟环境并安装依赖 根据 `requirements.txt` 文件安装依赖 ```bash sudo apt update pip install -r requirements.txt ```  ## 三、传数据库 由于本项目并没有用django默认的db.sqlite3,用的是MySQL 本次迁移windows上用的是 MySQL 8.0 香橙派上用的版本是 MariaDB 然后进行数据库传输 MySQL 是一个服务,不能直接把 Windows 里的文件夹复制到香橙派。你必须在香橙派上重新搭建一套一样的数据库环境,然后把数据导入进去。 ### 1. 在香橙派上安装 MySQL 服务 #### 1.1 卸载旧的 MariaDB 检查版本: ```bash mysql --version ``` 这里我的输出是:(版本不合适) ```bash mysql Ver 15.1 Distrib 10.3.39-MariaDB, for debian-linux-gnu (aarch64) using readline 5.2 ``` 如果当前有比较老的版本,比如 `10.3.39-MariaDB` 的话建议先卸载 ```bash # 停止服务 sudo systemctl stop mariadb # 卸载软件 sudo apt purge mariadb-server mariadb-client mariadb-common -y sudo apt autoremove -y # 关键:删除旧数据目录(否则新版安装会冲突) # 注意:这会删除你之前尝试导入的所有数据 sudo rm -rf /var/lib/mysql sudo rm -rf /etc/mysql ``` #### 1.1 安装 MySQL 8.0 ##### 安装服务和开发库 ```bash cd ~ # 下载配置包 wget https://dev.mysql.com/get/mysql-apt-config_0.8.28-1_all.deb # 安装配置包 sudo dpkg -i mysql-apt-config_0.8.28-1_all.deb ``` 执行后会弹出蓝色菜单,直接选 OK(默认就是 MySQL 8.0)即可。   ##### 正式安装 ```bash sudo apt update sudo apt install mysql-server -y ``` 如果刚刚删除过数据库这个时候直接运行安装命令可能会报错`mysqld: Can't read dir of '/etc/mysql/conf.d/'` 这是因为刚刚把整个 `/etc/mysql` 目录删除了,需要先创建 `sudo mkdir -p /etc/mysql/conf.d/`, `sudo mkdir -p /etc/mysql/mysql.conf.d/` 启动并进行安全配置 ##### 检查 版本输出: ```bash orangepi@orangepi5pro:~$ mysql --version mysql Ver 8.0.42-0ubuntu0.20.04.1 for Linux on aarch64 ((Ubuntu)) ``` 后台是否启动? ```bash sudo systemctl status mysql ``` #### 1.2 创建用户和数据库 创建root用户: ```bash sudo mysql -u root -p ``` ```python DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'my_blog', # 数据库的名字 'USER': 'root', # 用户名 'PASSWORD': 'root', # 密码 'HOST': '127.0.0.1',# 地址 'PORT': 3306 # 端口 } } ``` 根据在 `setting.py` 创建一个不需要root权限登录的用户: ```SQL -- 创建用户: 这里的 'my_blog_user' 和 'password' 对应你 settings.py 里的配置 CREATE USER 'my_blog_user'@'localhost' IDENTIFIED BY 'password'; -- 授予权限 GRANT ALL PRIVILEGES ON my_blog.* TO 'my_blog_user'@'localhost'; -- 刷新权限 FLUSH PRIVILEGES; ``` 创建数据库: 首先需要先进入数据库 ```sql show databases; create database my_blog; ``` ### 2.数据库迁移 #### 数据库版本兼容问题(弃) 在之前的版本兼容问题参考,可以忽略这一部分 在windows上用的是 MySQL 8.0 而香橙派上用的版本是 MariaDB 之间存在版本兼容性问题,并且如果你的数据库比较老的话,django 建表的命令会报错无法使用 主要是直接生成的 `.sql` 文件里面还包括很多SQL 的语法,这些 SQL 语法差异会导致迁移数据库报错,因此先用django自带的建表命令 `python manage.py migrate` 创建数据结构,再只导出“数据”,不导出“结构” 创建数据结构: ```bash python manage.py makemigrations python manage.py migrate ``` ##### 问题与解决-加密验证驱动库安装 ```bash python manage.py makemigrations python manage.py migrate ``` 如果使用上面创建数据表的命令,报错: ```bash # 报错信息 RuntimeError: 'cryptography' package is required for sha256_password or caching_sha2_password auth methods ``` 解决(用清华园比较快): ```bash pip install cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple ``` #### 2.1 数据库导出 在原服务器上导出(Dump)仅导出数据文件,使用 mysqldump 工具将数据库打包成文件: ```bash # 2. 使用 -r 确保编码是 UTF-8,避免之前的 \0 乱码报错 mysqldump -u root -p --databases my_blog -r my_blog_full.sql ``` #### 2.2 数据库导入 按照 `settings.py` 中的配置 上面已经在服务器上创建一个数据库,运行: ```bash # my_blog_db 为真实数据库名称 sudo mysql -u root -p my_blog_db < blog_data_only.sql sudo mysql -u root -p < /home/orangepi/my_blog_full.sql ``` #### 2.3 数据库检查 查看是否成功创建,运行:`use my_blog;`, `SHOW tables;` 看看输出全不全,可以根原数据库对比  ## 四、运行项目 ### 终端运行 #### 仅在本机访问 ```bash python manage.py runserver ``` - 默认端口:127.0.0.1:8000。 #### 允许局域网访问 ```bash python manage.py runserver 0.0.0.0:8000 ``` - 0.0.0.0:告诉 Django 监听香橙派所有的网络接口。 - :8000:手动指定端口号。如果你想用别的端口(比如 8080),直接修改这个数字即可。 这个时候你可以尝试访问,但如果没有配置django项目应该会报错 ##### 允许ip访问 打开 settings.py 找到 `ALLOWED_HOSTS` 这一行 原来的内容: ```python ALLOWED_HOSTS = [] ``` 修改后的内容: ```python ALLOWED_HOSTS = ['*'] ``` ### 服务运行 ```bash sudo vim /etc/systemd/system/my_blog.service ``` 复制下面的内容进去 ``` [Unit] Description=Django My Blog Service After=network.target mysql.service [Service] # 你的香橙派用户名 User=orangepi # 项目根目录路径 WorkingDirectory=/home/orangepi/my_blog # 使用虚拟环境里的 python 运行项目 ExecStart=/home/orangepi/my_blog/venv/bin/python /home/orangepi/my_blog/manage.py runserver 0.0.0.0:8000 # 崩溃后 5 秒自动重启 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` 启动服务: ```bash # 1. 重新加载系统配置,让它识别新写的服务 sudo systemctl daemon-reload # 2. 启动博客服务 sudo systemctl start my_blog # 3. 设置开机自启 sudo systemctl enable my_blog # 4. 查看运行状态 sudo systemctl status my_blog ``` 重启服务 `sudo systemctl daemon-reload`,`sudo systemctl restart my_blog` ## 五、用户 media 目录迁移 可以直接用命令 `scp` 完整的传过去
35 666 6324 2026-01-27