如何在Django中点击按钮实现表单提交前的文件预上传?
Django实现文件预上传(先保存后处理)的方案
核心思路是把文件先通过AJAX提交到服务器临时存储(数据库+文件系统),生成唯一标识关联该文件;点击「运行」按钮时,再通过这个标识找到文件进行后续处理。以下是具体实现步骤:
1. 调整模型(新增标识与状态字段)
在你的UploadFile模型中添加唯一UUID和状态字段,用来追踪文件状态和关联文件:
# models.py import uuid from django.db import models class UploadFile(models.Model): file = models.FileField(upload_to='temp_uploads/') # 文件存到临时目录 uuid = models.UUIDField(default=uuid.uuid4, unique=True) # 唯一标识文件 status = models.CharField(max_length=20, default='uploaded') # 状态:uploaded/processed/error uploaded_at = models.DateTimeField(auto_now_add=True)
2. 后端视图实现两个接口
分别处理预上传文件和运行文件处理的请求:
# views.py from django.http import JsonResponse from django.views.decorators.csrf import ensure_csrf_cookie from .models import UploadFile import os # 预上传文件接口 @ensure_csrf_cookie def pre_upload_file(request): if request.method == 'POST' and request.FILES.get('file_upload'): upload_file = request.FILES['file_upload'] # 验证文件类型 allowed_extensions = ['.zip', '.rar', '.7z', '.gz'] ext = os.path.splitext(upload_file.name)[1].lower() if ext not in allowed_extensions: return JsonResponse({'status': 'error', 'message': '不支持的文件类型'}) # 保存文件到数据库和文件系统 upload_request = UploadFile() upload_request.file = upload_file upload_request.save() return JsonResponse({ 'status': 'success', 'file_uuid': str(upload_request.uuid), 'message': '文件预上传成功' }) return JsonResponse({'status': 'error', 'message': '无效请求'}) # 运行文件处理接口 def run_processing(request): if request.method == 'POST': file_uuid = request.POST.get('file_uuid') try: # 找到未处理的文件 upload_request = UploadFile.objects.get(uuid=file_uuid, status='uploaded') except UploadFile.DoesNotExist: return JsonResponse({'status': 'error', 'message': '文件不存在或已处理'}) # 这里写入你的文件处理逻辑 # 示例:process_zip(upload_request.file.path) # 更新文件状态为已处理 upload_request.status = 'processed' upload_request.save() return JsonResponse({'status': 'success', 'message': '文件处理完成'}) return JsonResponse({'status': 'error', 'message': '无效请求'})
3. 配置URL路由
在项目的urls.py中添加两个接口的路由:
# urls.py from django.urls import path from . import views urlpatterns = [ path('pre-upload/', views.pre_upload_file, name='pre_upload'), path('run-processing/', views.run_processing, name='run_processing'), # 其他已有路由... ]
4. 前端模板修改(AJAX处理交互)
把原来的表单提交改成AJAX异步上传,新增「运行」按钮并关联逻辑:
<!-- 模板文件 --> {% csrf_token %} <input type="file" required name="file_upload" id="choose_upload_file" accept=".zip,.rar,.7z,.gz"></br> <button class="btn btn-secondary btn-block" id="pre_upload_btn">预上传文件</button> <button class="btn btn-primary btn-block" id="run_btn" disabled>运行处理</button> <div id="status_msg" style="margin-top:10px;"></div> <script> // 获取CSRF Token(Django跨域验证需要) function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } const csrftoken = getCookie('csrftoken'); let fileUuid = ''; // 预上传按钮点击事件 document.getElementById('pre_upload_btn').addEventListener('click', function() { const fileInput = document.getElementById('choose_upload_file'); const file = fileInput.files[0]; if (!file) { document.getElementById('status_msg').textContent = '请先选择文件'; return; } const formData = new FormData(); formData.append('file_upload', file); // 禁用按钮防止重复提交 this.disabled = true; document.getElementById('status_msg').textContent = '上传中...'; fetch('{% url "pre_upload" %}', { method: 'POST', headers: {'X-CSRFToken': csrftoken}, body: formData }) .then(response => response.json()) .then(data => { this.disabled = false; if (data.status === 'success') { fileUuid = data.file_uuid; document.getElementById('status_msg').textContent = data.message; document.getElementById('run_btn').disabled = false; // 启用运行按钮 } else { document.getElementById('status_msg').textContent = data.message; } }) .catch(error => { this.disabled = false; document.getElementById('status_msg').textContent = '上传失败:' + error; }); }); // 运行处理按钮点击事件 document.getElementById('run_btn').addEventListener('click', function() { if (!fileUuid) { document.getElementById('status_msg').textContent = '请先完成预上传'; return; } const formData = new FormData(); formData.append('file_uuid', fileUuid); this.disabled = true; document.getElementById('status_msg').textContent = '处理中...'; fetch('{% url "run_processing" %}', { method: 'POST', headers: {'X-CSRFToken': csrftoken}, body: formData }) .then(response => response.json()) .then(data => { this.disabled = false; document.getElementById('status_msg').textContent = data.message; if (data.status === 'success') { this.disabled = true; // 处理完成后禁用按钮 } }) .catch(error => { this.disabled = false; document.getElementById('status_msg').textContent = '处理失败:' + error; }); }); </script>
注意事项
- 临时文件清理:可以通过Django信号(如
post_save)或定时任务(如Celery)清理已处理/过期的文件,避免服务器空间浪费。 - 存储配置:确保
MEDIA_ROOT和MEDIA_URL配置正确,temp_uploads/目录会自动创建在MEDIA_ROOT下。 - 权限控制:可以给视图添加
@login_required装饰器,限制只有登录用户能上传和处理文件。
内容的提问来源于stack exchange,提问作者Prasanth Nelli
相关产品推荐
相关产品推荐

