如何从JavaScript数组中删除已上传文件,解决Django中删除后仍被上传问题
多文件上传移除功能异常的修复方案
问题根因
- 移除逻辑存在漏洞:遍历
filesToUpload数组执行splice操作后,数组长度发生变化,未调整遍历索引会导致后续元素被跳过;仅通过文件名匹配的逻辑遇到重名文件时会出现误删。 - 自定义文件数组未实际参与提交:表单默认提交时会直接读取
input type="file"控件内置的只读FileList对象,你自行维护的filesToUpload数组没有被用到,删除数组元素自然不影响提交结果。 - 后端直接读取原生文件流:Django代码中直接取
request.FILES.getlist('files1'),拿到的是前端input控件提交的全量文件,不会过滤掉你前端页面删除的条目。
修复方案
第一步:修复前端JS逻辑
替换原有JS代码为如下版本,同时调整提交逻辑使用FormData携带筛选后的文件提交:
$(document).ready(function (){ $.fn.fileUploader = function (filesToUpload) { this.closest(".files").change(function (evt) { // 保存当前数组长度,作为新增文件的唯一标识前缀,避免重名冲突 const currentLength = filesToUpload.length; for (var i = 0; i < evt.target.files.length; i++) { filesToUpload.push(evt.target.files[i]); }; var output = []; for (var i = 0, f; f = evt.target.files[i]; i++) { // 用数组真实索引作为data-fileid,不用本次遍历的i const realIndex = currentLength + i; var removeLink = `<a class="removeFile" href="#" data-fileid="${realIndex}">Remove</a>`; output.push("<li><strong>", escape(f.name), "</strong> - ", f.size, " bytes. ", removeLink, "</li> "); } $(this).children(".fileList") .append(output.join("")); // 清空input的值,避免重复提交相同文件 $(this).find('input[type="file"]').val(''); }); }; var filesToUpload = []; // 修复移除逻辑 $(document).on("click",".removeFile", function(e){ e.preventDefault(); // 直接取绑定的真实数组索引,不用匹配文件名 const fileIndex = parseInt($(this).data("fileid")); if(fileIndex >=0 && fileIndex < filesToUpload.length) { filesToUpload.splice(fileIndex, 1); // 移除后更新所有后续移除按钮的data-fileid,适配数组索引变化 $(this).parent().nextAll('li').each(function() { const curIndex = parseInt($(this).find('.removeFile').data("fileid")); $(this).find('.removeFile').data("fileid", curIndex -1); }) } $(this).parent().remove(); }); $("#files1").fileUploader(filesToUpload); $("#files2").fileUploader(filesToUpload); // 重写提交逻辑,用FormData提交筛选后的文件 $("#uploadBtn").click(function (e) { e.preventDefault(); const formData = new FormData($('form')[0]); // 替换为你的表单选择器 // 先删除默认的files1文件列表 formData.delete('files1'); // 把筛选后的文件逐个加入FormData filesToUpload.forEach(file => { formData.append('files1', file); }); // 提交请求,注意加Django CSRF头 $.ajax({ url: window.location.href, // 替换为你的提交地址 type: 'POST', data: formData, processData: false, contentType: false, headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() }, success: function(res) { // 提交成功后的跳转逻辑,可自行修改 window.location.href = "/procedures/main/"; }, error: function(err) { // 错误处理逻辑,可自行修改 console.log(err); } }) }); });
第二步:调整Django后端逻辑(可选,适配AJAX提交)
如果采用上述AJAX提交方式,后端可以保留原有逻辑,也可以增加AJAX请求适配:
def post(self, request, *args, **kwargs): if "cancel" in request.POST: return HttpResponseRedirect(reverse('Procedures:procedure_main_menu')) else: self.object = None user = request.user form_class = self.get_form_class() form = self.get_form(form_class) file_form = NewProcedureFilesForm(request.POST, request.FILES) files = request.FILES.getlist('files1') if form.is_valid() and file_form.is_valid(): procedure_instance = form.save(commit=False) procedure_instance.user = user procedure_instance.save() for f in files: procedure_file_instance = NewProcedureFiles(attachments=f, new_procedure=procedure_instance) procedure_file_instance.save() # 适配AJAX请求返回 if request.headers.get('x-requested-with') == 'XMLHttpRequest': return JsonResponse({'status':'success'}, status=200) return self.form_valid(form) else: if request.headers.get('x-requested-with') == 'XMLHttpRequest': return JsonResponse({'status':'error', 'errors': form.errors}, status=400) form = self.get_form(form_class) file_form = NewProcedureFilesForm() return self.form_invalid(form)
内容的提问来源于stack exchange,提问作者Steve Smith
相关产品推荐
相关产品推荐

