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

Django视图返回HttpResponse却提示返回None的问题排查

解决Django视图返回None而非HttpResponse的问题

我在Django视图中设置了按钮,触发compileUpdate函数后返回文件作为响应。该功能之前正常运行,但现在出现错误:ValueError: The view didn't return an HttpResponse object. It returned None instead.

代码逻辑为:获取对应Campaign、格式化待编译文件路径、检查指定目录(不存在则创建)、调用compileUpdate函数、返回文件作为HttpResponse。


无法正常工作的文件创建与响应代码

if req == "Bulk Update":
    cmp_id = request.headers.get('cmp')

    campaign = Campaign.objects.get(id=cmp_id)

    parent_dir = os.path.normpath(os.getcwd() + os.sep + os.pardir)

    submittedFolder = os.path.join(parent_dir, "SubmittedReviews", "", str(cmp_id) + "-" + campaign.system.name, "")

    cmp_files = glob.glob(os.path.join(submittedFolder,'*'))

    archive = os.path.join(parent_dir, "Archive", "", str(campaign.id) + "-" + campaign.system.name, "")

    if os.path.exists(archive) == False:
        os.mkdir(archive)

    bulk_update = os.path.join(archive, str(campaign.id) + "-" + campaign.system.name + "_bulk_update.csv")
    print(bulk_update)
    with open(bulk_update, 'w') as bulk_out:
        writer = csv.writer(bulk_out)

        compileUpdate(cmp_files, campaign, writer)
    bulk_out.close()

    time.sleep(2)

    file = str(bulk_update).split("/")[-1]
    
    with open(bulk_update, 'rb') as fh:
        response = HttpResponse(fh.read())
        response['Content-Type'] = 'application/vnd.ms-excel'
        response['Content-Disposition'] = 'inline; filename="{}"'.format(os.path.basename(bulk_update))
        response['X-Accel-Redirect'] = f'/archive/{file}'
    return response

正常工作的对比代码

另一个视图中类似的文件返回代码可正常运行:

if file_type == "responses":
    data = request.GET.getlist("campaigns")[0].split(",")
    campaigns = []
    for item in data:
        cmp_id = item.split(' ')[0]
        campaigns.append(Campaign.objects.get(id=cmp_id))
    files = compileResponses(campaigns)
    files = joinFiles(files)
    file = files[0]

    f = str(file).split("/")[-1]

    time.sleep(2)

    with open(file, 'rb') as fh:
        response = HttpResponse(fh.read())
        response['Content-Type'] = 'application/vnd.ms-excel'
        response['Content-Disposition'] = 'inline; filename="{}"'.format(os.path.basename(file))
        response['X-Accel-Redirect'] = f'/archive/{f}'
    return response

打印第一个响应得到<HttpResponse status_code=200, "application/vnd.ms-excel">,说明确实生成了HttpResponse对象。


前端触发脚本

触发该功能的模板脚本:

<script>
    function exportData(event) {

        var btn
        btn = event.target
        value = $(btn).parent().parent().parent().find('.export-select').val()
        cmp = $(btn).parent().parent().parent().parent().find('td')[0].textContent
        dict = {}
        
        if (value == "Bulk Update") {
            // Create the Post request, pass the csrf_token in the header of the request
            $.ajax({
                url: '/campaignmanager/download/' + cmp + '/',
                type: 'GET',
                headers: {'X-CSRFtoken': '{{ csrf_token }}', 'data': 'Bulk Update', 'cmp': cmp},
                data: dict,
                dataType: 'json'
            });
        }

        else if (value == "Bulk Delete") {
            // Create the Post request, pass the csrf_token in the header of the request
            $.ajax({
                url: '/campaignmanager/download/' + cmp + '/',
                type: 'GET',
                headers: {'X-CSRFtoken': '{{ csrf_token }}', 'data': 'Bulk Delete', 'cmp': cmp},
                data: dict,
                dataType: 'json'
            })

        }

        else {
            return false;
        }
        
        window.location = "/campaignmanager/download/" + cmp + "/"
        return false;
    }
</script>

完整视图代码

def campaignManager(request, **kwargs):


    elevated = checkPerms(request)
    sysAdmin = checkAdmin(request)

    if not request.user.is_authenticated:
        return HttpResponseRedirect('/login')

    elif elevated != True and sysAdmin != True and request.user.is_superuser == False:
        return HttpResponseRedirect('/login')


    if "download" in str(request.path):
        req = request.headers.get('data')

        if req == "Bulk Update":
            cmp_id = request.headers.get('cmp')

            campaign = Campaign.objects.get(id=cmp_id)

            parent_dir = os.path.normpath(os.getcwd() + os.sep + os.pardir)

            submittedFolder = os.path.join(parent_dir, "SubmittedReviews", "", str(cmp_id) + "-" + campaign.system.name, "")

            cmp_files = glob.glob(os.path.join(submittedFolder,'*'))

            archive = os.path.join(parent_dir, "Archive", "", str(campaign.id) + "-" + campaign.system.name, "")

            if os.path.exists(archive) == False:
                os.mkdir(archive)

            bulk_update = os.path.join(archive, str(campaign.id) + "-" + campaign.system.name + "_bulk_update.csv")
            print(bulk_update)
            with open(bulk_update, 'w') as bulk_out:
                writer = csv.writer(bulk_out)

                compileUpdate(cmp_files, campaign, writer)
            bulk_out.close()

            with open(bulk_update, 'r') as bulk:
                reader = csv.DictReader(bulk)
                for row in reader:
                    print(row)
            bulk.close()

            time.sleep(2)

            file = str(bulk_update).split("/")[-1]
            
            with open(bulk_update, 'rb') as fh:
                response = HttpResponse(fh.read())
                response['Content-Type'] = 'application/vnd.ms-excel'
                response['Content-Disposition'] = 'inline; filename="{}"'.format(os.path.basename(bulk_update))
                response['X-Accel-Redirect'] = f'/archive/{file}'
            return response
            


        elif req == "Bulk Delete":
            cmp_id = request.headers.get('cmp')

            campaign = Campaign.objects.get(id=cmp_id)

            pass

    else:
        # Uses campaign manager template
        template = loader.get_template('AccessReview/campaignManager.html')
        # Create empty dict for context
        context = {}

        context['user'] = employee.objects.get(pid__iexact=request.user.username)

        if sysAdmin == True:
            context["parent_template"] = "AccessReview/sysAdminBase.html"
        elif request.user.is_superuser == True:
            context["parent_template"] = "AccessReview/adminbase.html"
        elif elevated == True:
            context["parent_template"] = "AccessReview/elevatedBase.html"

        # Pass campaign and review instances to campaign management template
        context['Campaigns'] = Campaign.objects.all()
        context['Reviews'] = review.objects.all()
        context['Systems'] = System.objects.all()


            

        # Handle campaign creations through POST request
        if request.method == "POST":
            data = request.POST

            req = request.headers.get('data')

            if req == "campaign":

                allowed = checkPerms(request)

                if allowed == True:
                    date = str(data['expectedEndDate']).split("/")[2] + "-" + str(data['expectedEndDate']).split("/")[0] + "-" + str(data['expectedEndDate']).split("/")[1]
                    systemName = data['system']
                    reviewLevel = data['reviewLevel']

                    # Create campaign with the one required value
                    campaign = Campaign.createCampaign(expectedEndDate=date, system=systemName, reviewLevel=reviewLevel)
                    
                    management.debug("%s created a campaign for %s expected to be completed by %s", request.user, systemName, date)
                    time.sleep(1)
                    return redirect('/campaignmanager/')

                else:
                    return redirect('/campaignmanager/')

        return HttpResponse(template.render(context, request))

问题分析与修复方案

核心问题

前端脚本执行了两次请求:

  1. 先通过AJAX发送带data和cmp头的GET请求(实际正常返回了HttpResponse,但因为设置dataType: 'json',文件流解析失败,用户无感知)
  2. 紧接着执行window.location跳转,发送第二次不带自定义头的GET请求

第二次请求进入视图后,req = request.headers.get('data')返回None,既不匹配Bulk Update也不匹配Bulk Delete,视图没有返回任何HttpResponse,最终返回None触发报错。

修复步骤

  1. 修改前端脚本:移除多余的window.location跳转,调整AJAX配置以正确处理文件下载:
<script>
    function exportData(event) {
        event.preventDefault();
        
        var btn = event.target;
        var value = $(btn).parent().parent().parent().find('.export-select').val();
        var cmp = $(btn).parent().parent().parent().parent().find('td')[0].textContent;
        
        if (value == "Bulk Update" || value == "Bulk Delete") {
            $.ajax({
                url: '/campaignmanager/download/' + cmp + '/',
                type: 'GET',
                headers: {
                    'X-CSRFtoken': '{{ csrf_token }}', 
                    'data': value, 
                    'cmp': cmp
                },
                xhrFields: {
                    responseType: 'blob'
                },
                success: function(blob) {
                    var url = window.URL.createObjectURL(blob);
                    var a = document.createElement('a');
                    a.href = url;
                    a.download = value === "Bulk Update" ? `${cmp}-bulk_update.csv` : `${cmp}-bulk_delete.csv`;
                    document.body.appendChild(a);
                    a.click();
                    window.URL.revokeObjectURL(url);
                    document.body.removeChild(a);
                }
            });
        }
        return false;
    }
</script>
  1. 完善视图分支返回:在download路径逻辑中添加默认返回,避免无匹配分支时返回None:
if "download" in str(request.path):
    req = request.headers.get('data')

    if req == "Bulk Update":
        # 原有逻辑...
        return response
    elif req == "Bulk Delete":
        # 补充Bulk Delete的实现逻辑,或临时返回占位响应
        return HttpResponse("Bulk Delete功能待实现", status=200)
    else:
        # 处理无效请求类型
        return HttpResponseBadRequest("无效的请求类型")
  1. 清理冗余代码:删除with open(...)块后的手动close()调用(with语句会自动管理文件句柄)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 07:50:31