在Django中下载CSV文件

9

我正在尝试使用HttpResponse下载CSV文件,以确保浏览器将其作为附件处理。我按照这里提供的说明操作,但是我的浏览器没有提示“另存为”对话框。我无法确定我的函数有什么问题。感谢所有帮助。

  def savefile(request):
        try:
            myfile = request.GET['filename']
            filepath = settings.MEDIA_ROOT + 'results/'
            destpath = os.path.join(filepath, myfile)
            response = HttpResponse(FileWrapper(file(destpath)), content_type='text/csv' ) 
            response['Content-Disposition'] = 'attachment; filename="%s"' %(myfile)
            return response
        except Exception as err:
            errmsg = f"{err}"
            return HttpResponse(errmsg)

愉快的爱尔兰国庆节!

6个回答

11
如果文件是静态的(即不是为此请求专门生成的),你不应该通过django来提供它。你应该配置一些路径(比如/static/)由你的Web服务器提供服务,并且尽可能减少Django的开销。
如果文件是动态的,有两种选择:
1. 在内存中创建它并从Django中提供服务。 2. 在磁盘上创建它,并返回HttpResponseRedirect以便您的Web服务器自己处理下载(如果文件非常大,则应使用此选项)。
至于动态提供它,我一直在使用以下代码(这是ExcelResponse的简化版本)。
import StringIO
from django.db.models.query import ValuesQuerySet, QuerySet

class CSVResponse(HttpResponse):

  def __init__(self, data, output_name='data', headers=None, encoding='utf8'):

    # Make sure we've got the right type of data to work with
    valid_data = False
    if isinstance(data, ValuesQuerySet):
        data = list(data)
    elif isinstance(data, QuerySet):
        data = list(data.values())
    if hasattr(data, '__getitem__'):
        if isinstance(data[0], dict):
            if headers is None:
                headers = data[0].keys()
            data = [[row[col] for col in headers] for row in data]
            data.insert(0, headers)
        if hasattr(data[0], '__getitem__'):
            valid_data = True
    assert valid_data is True, "CSVResponse requires a sequence of sequences"

    output = StringIO.StringIO()
    for row in data:
        out_row = []
        for value in row:
            if not isinstance(value, basestring):
                value = unicode(value)
            value = value.encode(encoding)
            out_row.append(value.replace('"', '""'))
        output.write('"%s"\n' %
                     '","'.join(out_row))            
    mimetype = 'text/csv'
    file_ext = 'csv'
    output.seek(0)
    super(CSVResponse, self).__init__(content=output.getvalue(),
                                        mimetype=mimetype)
    self['Content-Disposition'] = 'attachment;filename="%s.%s"' % \
        (output_name.replace('"', '\"'), file_ext)

要使用它,只需使用 return CSVResponse(...),并传递一个列表的列表、具有相同键的字典列表、查询集或值查询集。


它是根据“此”请求即时生成的,并且暂时存储在 /static/ 目录下。 - spyder

6
你是否尝试指定内容类型? 例如:
response['Content-Type'] = 'application/x-download';

编辑:

注意,这段代码可以成功地触发“另存为”对话框。请注意,在mimetype参数中直接指定“application/x-download”。您还可能需要重新检查您的代码,并确保您的文件路径是正确的,并且FileWrapper()没有做一些奇怪的事情。

def save_file(request):
    data = open(os.path.join(settings.PROJECT_PATH,'data/table.csv'),'r').read()
    resp = django.http.HttpResponse(data, mimetype='application/x-download')
    resp['Content-Disposition'] = 'attachment;filename=table.csv'
    return resp

尝试过了,仍然不起作用。我可以在 FireBug 中看到响应和标头,但我没有得到对话框。 - spyder
我尝试了,但没有成功。请看我的回复,我发现问题与Django无关。谢谢。 - spyder
如果你使用我的方法来处理一个普通链接,它会产生类似Ajax的效果,即不会离开当前页面。 - Cerin

3
感谢大家的建议。我学到了一些新技巧 :) 然而,我认为我在这里找到了解决我的问题的答案:通过Ajax下载CSV文件。我的“savefile”函数是通过Ajax请求调用的,似乎ajax有一个限制,即“另存为对话框”无论HTTP头是什么都不会出现。

我应该提到我使用Ajax来调用此函数,但从未想过这可能是个问题:) 谢谢StackOverflow!


1
Thomas,我使用了一个Ajax函数来保存和下载这个文件。在这种情况下,无论头部是什么,"另存为"框都不会出现。我只需使用JavaScript来下载该文件。 window.open("path/to/file"); 就可以解决问题。我在IE6和Firefox上进行了测试,对话框出现了。

0

试一下这个

def serve_tsv(request):
    # Create the HttpResponse object with the appropriate CSV header.
    response = HttpResponse(
        content_type='text/csv',
        headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'},
    )


    writer = csv.writer(response)
    writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
    writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])

    return response

0

如果您不用双引号括起文件名,会有什么区别吗?示例代码没有引用文件名:

response['Content-Disposition'] = 'attachment; filename=foo.xls'

但是你的代码却:

response['Content-Disposition'] = 'attachment; filename="foo.xls"'

我不得不用引号将文件名括起来才能使其正常工作。 - epi.log

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接