Django 2.1.5下Bootstrap动态行多文件AJAX上传实现问题
解决Django动态添加行实现AJAX多文件上传的方案
针对你在Django 2.1.5中遇到的动态文件上传问题,我会从后端处理、前端AJAX改造两个部分给出具体解决方案,完全不需要在表单类中写死10个字段:
一、Django后端:动态处理上传数据(无需写死表单字段)
首先我们不需要在forms.py中定义固定数量的字段,直接在视图函数中解析请求里的动态数据和文件即可。
1. 配置媒体文件(必做)
确保你的settings.py已经配置了媒体文件路径:
# settings.py import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
同时在项目根urls.py中添加媒体文件的路由(仅开发环境使用,生产环境请用Nginx等服务处理):
# urls.py from django.conf import settings from django.conf.urls.static import static from django.urls import path from .views import upload_books urlpatterns = [ path('upload-books/', upload_books, name='upload_books'), # 其他路由... ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2. 视图函数处理上传请求
如果你需要将书籍信息存入数据库,可以先定义模型;如果只需要保存文件,直接跳过模型部分即可:
(可选)定义书籍模型
# models.py from django.db import models class Book(models.Model): origin_bookname = models.CharField(max_length=255, verbose_name="原语言书名") origin_author = models.CharField(max_length=255, verbose_name="原语言作者") eng_bookname = models.CharField(max_length=255, verbose_name="英文书名") eng_author = models.CharField(max_length=255, verbose_name="英文作者") book_image = models.ImageField(upload_to='photos/', verbose_name="书籍封面") def __str__(self): return self.eng_bookname
核心上传视图
# views.py from django.shortcuts import render from django.http import JsonResponse from django.conf import settings from .models import Book import os def upload_books(request): if request.method == 'POST': response = {'success': False, 'message': ''} counter = int(request.POST.get('counter', 0)) # 遍历所有动态添加的行 for i in range(counter + 1): # 获取当前行的表单数据 origin_bookname = request.POST.get(f'orginBookname{i}', '') origin_author = request.POST.get(f'originAuthor{i}', '') eng_bookname = request.POST.get(f'engBookname{i}', '') eng_author = request.POST.get(f'engAuthor{i}', '') book_img = request.FILES.get(f'bookimg{i}') # 校验当前行数据完整性 if not all([origin_bookname, origin_author, eng_bookname, eng_author, book_img]): response['message'] = f'第{i+1}行信息不完整,请检查' return JsonResponse(response) try: # 方式1:存入数据库(使用模型) Book.objects.create( origin_bookname=origin_bookname, origin_author=origin_author, eng_bookname=eng_bookname, eng_author=eng_author, book_image=book_img ) # 方式2:直接保存文件(不使用模型) # save_path = os.path.join(settings.MEDIA_ROOT, 'photos', book_img.name) # with open(save_path, 'wb+') as f: # for chunk in book_img.chunks(): # f.write(chunk) except Exception as e: response['message'] = f'第{i+1}本书上传失败:{str(e)}' return JsonResponse(response) response['success'] = True response['message'] = '所有书籍上传成功!' return JsonResponse(response) # GET请求返回表单页面 return render(request, 'your_upload_page.html')
二、前端:改造AJAX提交逻辑
基于你现有的HTML和JS,我们需要补全删除行功能,并修改提交逻辑为AJAX上传:
1. 调整HTML表单
主要修改提交按钮类型,避免默认表单提交:
<form action="" method="post" enctype="multipart/form-data" id="bookform"> {% csrf_token %} <table id="createBookTable" class="table order-list"> <thead> <tr> <td>book title(in original lang.)</td> <td>author(in original lang.)</td> <td>book title(in Eng.)</td> <td>author(in Eng.)</td> <td>book image</td> </tr> </thead> <tbody> <tr style="display:none"> <td colspan="5" style="text-align: left;"> <input type="text" id="counter" name="counter" value=""/> </td> </tr> <tr class="bookTr" id="bookTr-0"> <td class="col-sm-3"> <input type="text" name="orginBookname0" class="form-control" /> </td> <td class="col-sm-3"> <input type="text" name="originAuthor0" class="form-control"/> <!-- 修正之前的type="mail"错误 --> </td> <td class="col-sm-3"> <input type="text" name="engBookname0" class="form-control"/> </td> <td class="col-sm-3"> <input type="text" name="engAuthor0" class="form-control"/> </td> <td> <input type="file" name="bookimg0"> </td> <td class="col-sm-1"><button type="button" class="ibtnDel btn btn-md btn-danger">Delete</button></td> </tr> </tbody> <tfoot> <tr> <td colspan="5" style="text-align: left;"> <button type="button" class="btn btn-lg btn-block" id="addrow">Add Row</button> </td> </tr> <tr> <td colspan="5" style="text-align: left;"> <button type="button" id="bookSubmitBtn" class="btn btn-lg btn-block btn-beautiful">Submit</button> <!-- 改为button类型 --> </td> </tr> </tfoot> </table> </form>
2. 完善JavaScript逻辑
补全删除行功能,并实现AJAX上传:
$(document).ready(function() { let counter = 0; $("#counter").val(counter); // 添加行逻辑 $("#addrow").on("click", function () { if(counter <= 9){ counter++; const newRow = $('<tr class="bookTr" id="bookTr-'+counter+'">'); let cols = ""; cols += '<td><input type="text" class="form-control" name="orginBookname' + counter + '"/></td>'; cols += '<td><input type="text" class="form-control" name="originAuthor' + counter + '"/></td>'; cols += '<td><input type="text" class="form-control" name="engBookname' + counter + '"/></td>'; cols += '<td><input type="text" class="form-control" name="engAuthor' + counter + '"/></td>'; cols += '<td><input type="file" name="bookimg' + counter + '"/></td>'; cols += '<td><button type="button" class="ibtnDel btn btn-md btn-danger">Delete</button></td>'; newRow.append(cols); $("table.order-list").append(newRow); $("#counter").val(counter); } else{ alert("You can only add 10 books per time."); } }); // 删除行逻辑(事件委托处理动态元素) $(document).on("click", ".ibtnDel", function() { $(this).closest("tr").remove(); counter--; $("#counter").val(counter); }); // AJAX提交逻辑 $("#bookSubmitBtn").on("click", function() { const form = $("#bookform")[0]; const formData = new FormData(form); // 自动收集所有表单数据(包括文件) $.ajax({ url: "{% url 'upload_books' %}", // 替换为你的上传路由 type: "POST", data: formData, contentType: false, // 必须设置,让浏览器自动处理文件边界 processData: false, // 必须设置,避免jQuery转换FormData为字符串 success: function(res) { if(res.success) { alert(res.message); // 重置表单和行 $("#bookform")[0].reset(); $(".bookTr:not(#bookTr-0)").remove(); counter = 0; $("#counter").val(counter); } else { alert(res.message); } }, error: function(xhr) { alert(`上传失败:${xhr.responseText || '未知错误'}`); } }); }); });
关键注意事项
- FormData使用:上传文件必须通过
FormData对象,且必须设置contentType: false和processData: false,否则文件无法正确传递到后端。 - CSRF令牌:模板中的
{% csrf_token %}会被自动加入FormData,无需额外处理。 - 动态行遍历:后端通过
counter值遍历所有行,不管用户添加了多少行(最多10行),都能正确处理每一行的数据。 - 文件存储路径:使用模型
ImageField时,upload_to='photos/'会自动将文件保存到media/photos目录,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Cindy
相关产品推荐
相关产品推荐

