【问题标题】:Adding rows manually to StreamingHttpResponse (Django)手动将行添加到 StreamingHttpResponse (Django)
【发布时间】:2018-01-16 14:33:27
【问题描述】:

我正在使用 Django 的 StreamingHttpResponse 实时流式传输大型 CSV 文件。根据the docs,将一个迭代器传递给响应的streaming_content参数:

import csv
from django.http import StreamingHttpResponse

def get_headers():
    return ['field1', 'field2', 'field3']

def get_data(item):
    return {
        'field1': item.field1,
        'field2': item.field2,
        'field3': item.field3,
    }

# StreamingHttpResponse requires a File-like class that has a 'write' method
class Echo(object):
    def write(self, value):
        return value


def get_response(queryset):
    writer = csv.DictWriter(Echo(), fieldnames=get_headers())
    writer.writeheader() # this line does not work

    response = StreamingHttpResponse(
        # the iterator
        streaming_content=(writer.writerow(get_data(item)) for item in queryset),
        content_type='text/csv',
    )
    response['Content-Disposition'] = 'attachment;filename=items.csv'

    return response

我的问题是:如何在 CSV 写入器上手动写入一行?手动调用 writer.writerow(data) 或 writer.writeheader()(内部也调用 writerow())似乎不会写入数据集,而是仅将来自 streaming_content 的生成/流式数据写入输出数据集。

【问题讨论】:

    标签: python django csv


    【解决方案1】:

    答案是使用生成器函数产生结果,而不是动态计算它们(在 StreamingHttpResponse 的 streaming_content 参数中)并使用我们创建的伪缓冲区(Echo 类)向响应中写入一行:

    import csv
    from django.http import StreamingHttpResponse
    
    def get_headers():
        return ['field1', 'field2', 'field3']
    
    def get_data(item):
        return {
            'field1': item.field1,
            'field2': item.field2,
            'field3': item.field3,
        }
    
    # StreamingHttpResponse requires a File-like class that has a 'write' method
    class Echo(object):
        def write(self, value):
            return value
    
    def iter_items(items, pseudo_buffer):
        writer = csv.DictWriter(pseudo_buffer, fieldnames=get_headers())
        yield pseudo_buffer.write(get_headers())
    
        for item in items:
            yield writer.writerow(get_data(item))
    
    def get_response(queryset):
        response = StreamingHttpResponse(
            streaming_content=(iter_items(queryset, Echo())),
            content_type='text/csv',
        )
        response['Content-Disposition'] = 'attachment;filename=items.csv'
        return response
    

    【讨论】:

    • 虽然看起来很奇怪,但这个答案是针对同一主题的众多答案中的正确答案。奇怪的是,在 Django 中创建流式下载是如此模糊......
    • 很好的解决方案,但我将更改:yield pseudo_buffer.write(get_headers()) 为:yield pseudo_buffer.writerow(get_headers()),以便在文件的下一行写入并避免将 ['field1', 'field2', 'field3'] 作为标题写入,当 rigth 标题将是'field1', 'field2', 'field3'。您看到pseudo_buffer.write(...) 写入了您传递的原始参数。
    • 非常感谢您的回复。超级有帮助:)
    【解决方案2】:

    建议的解决方案实际上会导致 CSV 不正确/不匹配(标题与数据不匹配)。您想用以下内容替换受影响的部分:

    header = dict(zip(fieldnames, fieldnames))
    yield writer.writerow(header)
    

    相反。这是来自writeheaderhttps://github.com/python/cpython/blob/08045391a7aa87d4fbd3e8ef4c852c2fa4e81a8a/Lib/csv.py#L141:L143的实现

    由于某种原因,yield 的表现不佳

    希望这对将来的人有所帮助:)

    另请注意,如果使用 python 3.8+,则无需修复,因为此 PR:https://bugs.python.org/issue27497

    【讨论】:

      猜你喜欢
      • 2018-09-12
      • 1970-01-01
      • 1970-01-01
      • 2013-02-13
      • 2011-03-24
      • 2012-04-12
      • 2013-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多