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

AJAX上传图片至Django后端丢失,request.FILES为空求助

AJAX向Django传文件时request.FILES为空的解决方案

问题现象

通过new FormData()收集表单数据并附加额外字段,AJAX提交时前端控制台能看到图片文件信息,但Django后端:

  • json.loads(request.body)中图片对应的键为空字典
  • request.FILES为空

模板表单代码

<form method="POST" action="" onsubmit="set_table(event)" id="myform" enctype="multipart/form-data">
        {% csrf_token %}
        <div class="sections1" name="titre">
            <label for="title">项目名称</label>
            <input type="text" name="title" maxlength="30" required class="postgrid" style="width:250px;">
        </div>
        <div class="sections1">
            <label for="cathegorie">选择项目对应的类别</label>
            <select class="postgrid" name="cathegorie" >
                {% for cats in cat %}
                    <option value="{{ cats}}">{{ cats }}</option>
                {% endfor %}
            </select>
        </div>
        <div class="sections" name="pb">
            <label for="probleme">项目针对的问题</label>
            <textarea rows="10" cols="30" name="probleme" required class="postgrid" data-name="probleme_name"></textarea>
            <div name="shower" id="show" class="rowbench"></div>
        </div>
        <div class="sections" name="ds">
            <label for="description">项目描述</label>
            <textarea rows="30" cols="40" name="description" data-name="description_name" required class="postgrid"></textarea>
            <div name="shower" id="show" class="rowbench"></div>
        </div>
        <div class="sections" name="sv">
            <label for="services">项目创建的服务</label>
            <textarea rows="10" cols="40" name="services" required data-name="services_name" class="postgrid"></textarea>
            <div name="shower" id="show" class="rowbench"></div>
        </div>
        <div class="sections1" name="titre">
            <table style="border: 1px solid black; border-collapse:collapse; margin:auto; width:80%;" id="table">
                <tbody id="tb">
                    <tr name="head">
                        <th>人员</th>
                        <th>数量</th>
                        <th>用途</th>
                    </tr>
                    <tr>
                        <td><input type="text" style="margin:auto; width:95%;" placeholder="例:数据分析师" id="personnel"></td>
                        <td><input type="text" style="margin:auto;width:95%;" placeholder="例:5" id="number"></td>
                        <td><input type="text" style="margin:auto;width:95%;" placeholder="例:数据收集与处理" id="for"></td>
                    </tr>
                </tbody>
            </table>
            <button style="color:rgb(32, 148, 171); 
                            background-color:black; 
                            position:absolute; 
                            right:10px;
                            bottom:10px;" onclick="add_row(event)">&plus;</button>
        </div>
        <button Class="post_creator"  style="position:relative; bottom:4px; margin:85px; " type="submit">提交</button>
    </form>
    <script src="{% static 'projectcreation.js' %}"></script>
    <script>
        arr=document.querySelectorAll('.sections');
        for (let i=0; i<arr.length; i++){
            let a=arr[i];
            a.innerHTML+=`
                        <div style="position: relative;">
                            <div style="width:40px; 
                                        height: 35px; ">
                                                <input type="file" accept="image/*" class="invisible" multiple oninput="showinput(event)" name="${a.getAttribute('name')}_image" data-name="${a.getAttribute('name')}">
                                        </div>
                            <img src="{% static 'im.png' %}" class="pinner">
                        </div>`;
            //console.log(a.getAttribute('name'));
        }
    </script>

原JS代码

function accept(formdata){
    
    const data1=Object.fromEntries(formdata.entries());
    console.log(data1);//打印所有内容,能看到图片名称
    $.ajax(
        {type:'POST',
        url:document.getElementById('url').getAttribute('data-url'),
        data:JSON.stringify({"formdata":data1}),
        headers:{
            "X-Requested-With":"XMLHttpRequest",
            "X-CSRFToken":getCookie("csrftoken"),
        },
        success:(data) =>{
            console.log('data');
        },
        dataType:'json',
        contentType:false,
        processData:false
    }
    );
}

function set_table(event){
    event.preventDefault();
    const table = document.getElementById('tb');
    const list=table.children;
    console.log(list.length);
    let arr=[];
    for (let row =0; row<list.length;row++ ){
        let rows=list[row];
        if (rows.getAttribute('name') !='head'){
            let data={
                "agent":rows.querySelector('#personnel').value,
                "number":rows.querySelector('#number').value ,
                "work":rows.querySelector('#for').value
            };
            arr.push(JSON.stringify(data));
        }
        
    }
    //console.log(event.target.data);
    const formdata=new FormData(event.target);
    formdata.append('tab',arr);
    accept(formdata);
    
}

原Django视图代码

def project_creation_page(request):
    context={"cat":project_cat}
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if is_ajax:
        print(34)
        if request.method=='POST':
            data=json.loads(request.body)
            print(data)
            for file ,files in request.FILES:
                print(file, files.name)#无输出
    return render(request,"Projectscreation.html",context)

问题根源

  1. File对象无法JSON序列化:把FormData转成Object后用JSON.stringify()发送,File对象会被序列化成空字典,后端自然接收不到文件内容。
  2. 错误的请求格式:发送JSON格式的请求,Django不会把文件解析到request.FILES里,只有multipart/form-data格式的请求才会触发文件解析。

解决方案

1. 修改JS代码,直接发送FormData

去掉FormData转Object和JSON序列化的步骤,直接将FormData作为AJAX的data参数发送:

function accept(formdata){
    // 直接发送FormData,不要转成JSON
    $.ajax({
        type:'POST',
        url:document.getElementById('url').getAttribute('data-url'),
        data: formdata, // 直接传FormData对象
        headers:{
            "X-Requested-With":"XMLHttpRequest",
            "X-CSRFToken":getCookie("csrftoken"),
        },
        success:(data) =>{
            console.log('提交成功:', data);
        },
        contentType:false, // 必须设为false,让浏览器自动设置multipart/form-data边界
        processData:false // 必须设为false,避免jQuery处理FormData
    });
}

function set_table(event){
    event.preventDefault();
    const table = document.getElementById('tb');
    const list=table.children;
    let arr=[];
    for (let row =0; row<list.length;row++ ){
        let rows=list[row];
        if (rows.getAttribute('name') !='head'){
            let data={
                "agent":rows.querySelector('#personnel').value,
                "number":rows.querySelector('#number').value ,
                "work":rows.querySelector('#for').value
            };
            arr.push(data); // 这里不用提前JSON.stringify,后面统一处理
        }
    }
    const formdata=new FormData(event.target);
    // 把数组转成JSON字符串再append,避免FormData传数组时后端收到多个tab参数
    formdata.append('tab', JSON.stringify(arr));
    accept(formdata);
}

2. 修改Django视图代码,正确获取数据

不再解析request.body为JSON,而是从request.POST获取普通表单字段,request.FILES获取文件:

import json

def project_creation_page(request):
    context={"cat":project_cat}
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if is_ajax and request.method=='POST':
        # 获取普通表单数据
        title = request.POST.get('title')
        cathegorie = request.POST.get('cathegorie')
        probleme = request.POST.get('probleme')
        # 获取表格数据:解析JSON字符串
        tab_data = json.loads(request.POST.get('tab', '[]'))
        # 获取文件
        pb_image = request.FILES.get('pb_image')
        ds_image = request.FILES.get('ds_image')
        sv_image = request.FILES.get('sv_image')
        
        # 打印测试
        print("表单数据:", title, cathegorie)
        print("表格数据:", tab_data)
        if pb_image:
            print("pb_image文件名:", pb_image.name)
        if ds_image:
            print("ds_image文件名:", ds_image.name)
        if sv_image:
            print("sv_image文件名:", sv_image.name)
        
        # 后续可以处理保存文件等逻辑
        # 比如保存pb_image到媒体目录:
        # with open(f'media/{pb_image.name}', 'wb+') as destination:
        #     for chunk in pb_image.chunks():
        #         destination.write(chunk)
    
    return render(request,"Projectscreation.html",context)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 10:30:55