1. 项目结构
myproject/
├── myproject/
│ ├── settings.py
│ ├── urls.py
│ └── ...
├── myapp/
│ ├── templates/
│ │ └── upload.html
│ ├── views.py
│ ├── urls.py
│ └── ...
└── media/ # 手动创建此目录
2. 配置Django设置(settings.py
)
# settings.py
import os
INSTALLED_APPS = [
'myapp', # 确保应用已注册
# ...
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 允许大文件上传(可选)
DATA_UPLOAD_MAX_MEMORY_SIZE = 52428800 # 50MB
3. 路由配置
项目路由(myproject/urls.py
)
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('', include('myapp.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
应用路由(myapp/urls.py
)
from django.urls import path
from . import views
urlpatterns = [
path('', views.upload_view, name='upload'),
path('upload/', views.file_upload, name='file_upload'),
]
4. 视图逻辑(myapp/views.py
)
from django.shortcuts import render
from django.http import JsonResponse
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
import os
def upload_view(request):
return render(request, 'upload.html')
@csrf_exempt # 临时禁用CSRF(生产环境需修复)
def file_upload(request):
if request.method == 'POST' and request.FILES.get('file'):
uploaded_file = request.FILES['file']
save_path = os.path.join(settings.MEDIA_ROOT, uploaded_file.name)
# 分块写入文件
with open(save_path, 'wb+') as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
return JsonResponse({
'status': 'success',
'filename': uploaded_file.name
})
return JsonResponse({'status': 'error'}, status=400)
5. 前端模板(myapp/templates/upload.html
)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- Bootstrap 5 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<title>文件上传</title>
</head>
<body>
<div class="container mt-5" style="max-width: 600px;">
<h2 class="mb-4">文件上传演示</h2>
<!-- 文件选择 -->
<div class="mb-3">
<input type="file" class="form-control" id="fileInput">
</div>
<!-- 进度条 -->
<div class="progress mb-3" style="height: 25px; display: none;" id="progressContainer">
<div id="progressBar" class="progress-bar progress-bar-striped"
role="progressbar" style="width: 0%">0%</div>
</div>
<!-- 状态提示 -->
<div id="statusMessage"></div>
<!-- 上传按钮 -->
<button class="btn btn-primary" onclick="startUpload()">开始上传</button>
</div>
<!-- 依赖库 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
function startUpload() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
showMessage('请先选择文件!', 'danger');
return;
}
const formData = new FormData();
formData.append('file', file);
// 显示进度条
$('#progressContainer').show();
$.ajax({
url: '/upload/',
type: 'POST',
data: formData,
contentType: false, // 必须设置
processData: false, // 必须设置
xhr: function() {
const xhr = new window.XMLHttpRequest();
// 进度事件监听
xhr.upload.addEventListener('progress', function(evt) {
if (evt.lengthComputable) {
const percent = Math.round((evt.loaded / evt.total) * 100);
updateProgress(percent);
}
}, false);
return xhr;
},
success: function(response) {
showMessage(`文件 ${response.filename} 上传成功!`, 'success');
resetUI();
},
error: function(xhr) {
showMessage('上传失败: ' + (xhr.responseJSON?.status || '服务器错误'), 'danger');
resetUI();
}
});
}
function updateProgress(percent) {
$('#progressBar')
.css('width', percent + '%')
.text(percent + '%');
}
function showMessage(text, type) {
const alertClass = `alert alert-${type} alert-dismissible fade show`;
$('#statusMessage').html(`
<div class="${alertClass}" role="alert">
${text}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`);
}
function resetUI() {
setTimeout(() => {
$('#progressContainer').hide();
updateProgress(0);
}, 1500);
}
</script>
</body>
</html>
运行 HTML
6. 运行步骤
创建媒体目录:
mkdir media
启动开发服务器:
- python manage.py runserver
- 访问
http://localhost:8000
测试上传功能
关键功能说明
- 进度条实现:
-
- 使用XMLHttpRequest的
progress
事件监听上传进度 - 动态更新Bootstrap进度条的宽度和文本
- 使用XMLHttpRequest的
- 安全增强(生产环境必做):
-
- 移除
@csrf_exempt
装饰器 - 在前端添加CSRF Token:
- 移除
// 在AJAX请求中添加headers
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
- 文件处理:
-
- 使用
file.chunks()
分块处理大文件 - 保存到
MEDIA_ROOT
指定目录
- 使用
- 用户体验优化:
-
- 上传完成后的自动状态重置
- 可关闭的Bootstrap提示组件
- 进度条动画效果(条纹动画)
常见问题解决
- 进度条不更新:
-
- 检查浏览器控制台是否有CORS错误
- 确认
xhr.upload.addEventListener
正确绑定
- 文件保存失败:
-
- 确保
MEDIA_ROOT
目录存在且有写入权限 - 检查Django的
settings.py
配置
- 确保
- CSRF验证失败:
-
- 在生产环境中务必处理CSRF Token
- 在模板中添加
{% csrf_token %}
通过以上步骤,您可以在Django中实现一个带Bootstrap进度条的文件上传功能,既保证基本功能又具备良好的用户体验。