You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 || '未知错误'}`);
            }
        });
    });
});

关键注意事项

  1. FormData使用:上传文件必须通过FormData对象,且必须设置contentType: false和processData: false,否则文件无法正确传递到后端。
  2. CSRF令牌:模板中的{% csrf_token %}会被自动加入FormData,无需额外处理。
  3. 动态行遍历:后端通过counter值遍历所有行,不管用户添加了多少行(最多10行),都能正确处理每一行的数据。
  4. 文件存储路径:使用模型ImageField时,upload_to='photos/'会自动将文件保存到media/photos目录,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Cindy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:47:09