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

Django StreamingHttpResponse无法正确设置CSV文件名问题求助

DRF自定义CSVRenderer结合StreamingHttpResponse时,不同浏览器下载文件名异常(IE/Firefox)

问题描述

我在使用Django Rest Framework(DRF)的自定义CSVRenderer结合StreamingHttpResponse输出CSV数据时遇到了棘手问题:文件内容正常,但不同浏览器的文件名设置异常——IE下文件名为mydata.txt(文件名和扩展名均错误),Firefox下则是随机名称的.csv文件。我参考Django官方文档编写了CSVRenderer,也尝试过使用djangorestframework-csv库,但问题依旧。当前环境是Django 1.10.8、DRF 3.5.3,相关代码及settings.py中的中间件配置如下:

自定义CSVRenderer代码

import csv
from rest_framework.renderers import BaseRenderer
from django.http import StreamingHttpResponse

class Echo(object):
    '''An object that implements just the write method of the file-like interface. '''
    def write(self, value):
        '''Write the value by returning it, instead of storing in a buffer. '''
        return value

class CSVRenderer(BaseRenderer):
    '''Renderer class to generate basic csvs of the data supplied by the serializer '''
    media_type = 'text/csv'
    format = 'csv'
    def render(self, data, media_type=None, renderer_context=None):
        rows = []
        first_data_row = next(iter(data['results']), None)
        # Create header rows if data present
        if first_data_row:
            rows.append(list(first_data_row.keys()))
        # print the data to rows
        for row in data['results']:
            rows.append(list(row.values()))
        pseudo_buffer = Echo()
        writer = csv.writer(pseudo_buffer)
        response = StreamingHttpResponse((writer.writerow(row) for row in rows), content_type='text/csv')
        response['Content-Type'] = 'text/csv; name="export.csv"'
        response['Content-Disposition'] = 'attachment; filename="export.csv"'
        return response

中间件配置

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
)

解决方案

核心问题分析

你遇到的问题根源在于违反了DRF的Renderer设计逻辑:DRF的render()方法应该返回格式对应的内容(比如字节流、字符串、生成器),而不是直接返回StreamingHttpResponse对象。当你在render()里直接返回响应对象时,DRF的响应处理链会被打乱,后续中间件或DRF自身可能修改你的响应头,导致不同浏览器解析文件名时出现异常。

另外,IE和Firefox对Content-Disposition头的解析有兼容性要求,缺少特定响应头也会导致文件名识别错误。

修正后的实现步骤

1. 修改CSVRenderer,返回内容生成器

调整CSVRenderer的逻辑,只负责生成CSV内容的迭代器,不处理响应对象:

import csv
from rest_framework.renderers import BaseRenderer

class Echo(object):
    '''An object that implements just the write method of the file-like interface. '''
    def write(self, value):
        '''Write the value by returning it, instead of storing in a buffer. '''
        return value

class CSVRenderer(BaseRenderer):
    '''Renderer class to generate basic csvs of the data supplied by the serializer '''
    media_type = 'text/csv'
    format = 'csv'

    def render(self, data, media_type=None, renderer_context=None):
        pseudo_buffer = Echo()
        writer = csv.writer(pseudo_buffer)
        
        first_data_row = next(iter(data['results']), None)
        # 先写入表头
        if first_data_row:
            yield writer.writerow(list(first_data_row.keys()))
        # 逐个写入数据行
        for row in data['results']:
            yield writer.writerow(list(row.values()))

2. 在视图中手动构建StreamingHttpResponse并设置兼容响应头

在API视图中,调用渲染器生成内容后,手动创建StreamingHttpResponse,并添加针对不同浏览器的兼容头:

from rest_framework.views import APIView
from django.http import StreamingHttpResponse
from .renderers import CSVRenderer

class ExportCSVView(APIView):
    renderer_classes = [CSVRenderer]

    def get(self, request):
        # 替换为你实际的数据源(比如序列化后的queryset数据)
        data = {
            'results': [
                {'id': 1, 'name': 'Alice', 'age': 25},
                {'id': 2, 'name': 'Bob', 'age': 30}
            ]
        }
        # 调用渲染器生成CSV内容迭代器
        renderer = CSVRenderer()
        csv_content = renderer.render(data)
        
        # 构建流式响应
        response = StreamingHttpResponse(csv_content, content_type='text/csv; charset=utf-8')
        # 设置下载文件名,兼容多浏览器
        response['Content-Disposition'] = 'attachment; filename="export.csv"'
        # 阻止IE嗅探文件类型,避免把CSV识别为TXT
        response['X-Content-Type-Options'] = 'nosniff'
        
        return response

关键说明

  • 不直接在Renderer中返回Response:DRF的Renderer职责是数据格式转换,而非响应对象构建,遵守这个设计逻辑可以避免响应头被意外修改。
  • 添加X-Content-Type-Options: nosniff:这是解决IE文件名错误的关键,它会强制IE使用你指定的Content-Type,而不是自行嗅探文件类型。
  • 指定字符集:明确charset=utf-8可以确保文件编码正确,同时帮助浏览器更准确识别文件类型。

如果你之前尝试过djangorestframework-csv库,也可以用类似思路:在视图中手动构建StreamingHttpResponse,并手动设置上述响应头,而非依赖库的默认响应处理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:18:59