首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用StreamingHttpResponse与Django Rest框架CSV

使用StreamingHttpResponse与Django Rest框架CSV
EN

Stack Overflow用户
提问于 2017-10-11 18:10:39
回答 4查看 8.7K关注 0票数 10

我有一个标准的DRF网络应用程序,输出CSV数据的一个路由。呈现整个CSV表示需要一段时间。数据集相当大,所以我希望有一个流HTTP响应,这样客户端就不会超时。

然而,使用csv/renderers.py#L 197中提供的示例并不能完全实现这一点。数据仍然是一个大的有效负载,而不是分块,客户端在接收字节之前等待响应。

其结构类似于以下内容:

models.py

代码语言:javascript
运行
复制
class Report(models.Model):
  count = models.PostiveIntegerField(blank=True)
  ...

renderers.py

代码语言:javascript
运行
复制
class ReportCSVRenderer(CSVStreamingRenderer):
  header = ['count']

serializers.py

代码语言:javascript
运行
复制
class ReportSerializer(serializers.ModelSerializer):
  count = fields.IntegerField()

  class Meta:
    model = Report

views.py

代码语言:javascript
运行
复制
class ReportCSVView(generics.Viewset, mixins.ListModelMixin):
  def get_queryset(self):
    return Report.objects.all()

  def list(self, request, *args, **kwargs):
    queryset = self.get_queryset()
    data = ReportSerializer(queryset, many=True)
    renderer = ReportCSVRenderer()

    response = StreamingHttpResponse(renderer.render(data), content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="f.csv"'

    return response

注意:不得不评论或者改变一些事情。

谢谢

EN

回答 4

Stack Overflow用户

发布于 2021-01-04 14:29:15

一个简单的解决方案,灵感来源于@3066d0的一个:

renderers.py

代码语言:javascript
运行
复制
class ReportsRenderer(CSVStreamingRenderer):
    header = [ ... ]
    labels = { ... }

views.py

代码语言:javascript
运行
复制
class ReportCSVViewset(ListModelMixin, GenericViewSet):
    queryset = Report.objects.select_related('stuff')
    serializer_class = ReportCSVSerializer
    renderer_classes = [ReportsRenderer]
    PAGE_SIZE = 1000

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        response = StreamingHttpResponse(
            request.accepted_renderer.render(self._stream_serialized_data(queryset)),
            status=200,
            content_type="text/csv",
        )
        response["Content-Disposition"] = 'attachment; filename="reports.csv"'
        return response

    def _stream_serialized_data(self, queryset):
        serializer = self.get_serializer_class()
        paginator = Paginator(queryset, self.PAGE_SIZE)
        for page in paginator.page_range:
            yield from serializer(paginator.page(page).object_list, many=True).data

关键是,您需要将一个生成器作为data参数传递给呈现器,该生成器生成序列化的数据,然后CSVStreamingRenderer执行它的操作并自己流响应。我更喜欢这种方法,因为这样您不需要重写第三方库的代码。

票数 5
EN

Stack Overflow用户

发布于 2018-12-28 16:10:25

对于较小的响应,Django的StreamingHttpResponse可能比传统的HttpResponse慢得多。

如果不需要的话,不要使用它;Django Docs实际上建议,只有在向客户端传输数据之前绝对需要不迭代整个内容时,才应该使用StreamingHttpResponse。“

对于您的问题,您可能会发现设置chunk_size、切换到FileResponse或返回到正常响应(如果使用REST框架)或HttpResponse是有用的。

编辑1:关于设置块大小的

文件api中,您可以以块的形式打开文件,这样就不会将所有文件加载到内存中。

我希望你觉得这很有用。

票数 2
EN

Stack Overflow用户

发布于 2019-01-18 06:49:37

因此,我最终找到了一个解决方案,我很高兴在queryset中使用Paginator类。首先,我编写了一个对CSVStreamingRenderer子类的呈现器,然后在我的CSVViewset的渲染器中使用它。

renderers.py

代码语言:javascript
运行
复制
from rest_framework_csv.renderers import CSVStreamingRenderer

# *****************************************************************************
# BatchedCSVRenderer
# *****************************************************************************


class BatchedCSVRenderer(CSVStreamingRenderer):

    """
    a CSV renderer that works with large querysets returning a generator
    function. Used with a streaming HTTP response, it provides response bytes
    instead of the client waiting for a long period of time
    """

    def render(self, data, renderer_context={}, *args, **kwargs):
        if 'queryset' not in data:
            return data

        csv_buffer = Echo()
        csv_writer = csv.writer(csv_buffer)

        queryset = data['queryset']
        serializer = data['serializer']

        paginator = Paginator(queryset, 50)

        #  rendering the header or label field was taken from the tablize
        #  method in django rest framework csv

        header = renderer_context.get('header', self.header)
        labels = renderer_context.get('labels', self.labels)

        if labels:
            yield csv_writer.writerow([labels.get(x, x) for x in header])
        else:
            yield csv_writer.writerow(header)

        for page in paginator.page_range:
            serialized = serializer(
                paginator.page(page).object_list, many=True
            ).data

            #  we use the tablize function on the parent class to get a
            #  generator that we can use to yield a row

            table = self.tablize(
                serialized,
                header=header,
                labels=labels,
            )

            #  we want to remove the header from the tablized data so we use
            #  islice to take from 1 to the end of generator

            for row in itertools.islice(table, 1, None):
                yield csv_writer.writerow(row)

# *****************************************************************************
# ReportsRenderer
# *****************************************************************************


class ReportsRenderer(BatchedCSVRenderer):

    """
    A render for returning CSV data for reports

    """

    header = [ ... ]
    labels = { ... }

views.py

代码语言:javascript
运行
复制
from django.http import StreamingHttpResponse
from rest_framework import mixins, viewsets

# *****************************************************************************
# CSVViewSet
# *****************************************************************************


class CSVViewSet(
        mixins.ListModelMixin,
        viewsets.GenericViewSet,
):

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()

        return StreamingHttpResponse(
            request.accepted_renderer.render({
                'queryset': queryset,
                'serializer': self.get_serializer_class(),
            })
)

# *****************************************************************************
# ReportsViewset
# *****************************************************************************


class ReportCSVViewset(CSVViewSet):

    """
    Viewset for report CSV output

    """

    renderer_classes = [ReportCSVRenderer]
    serializer_class = serializers.ReportCSVSerializer

    def get_queryset(self):
        queryset = Report.objects.filter(...)

对于流响应来说,这似乎是很多事情,但是我们在很多其他地方使用了BatchedCSVRenderCSVViewset。如果您正在nginx后面运行您的服务器,那么调整那里的设置以允许流响应也可能是有用的。

希望这能帮助任何有相同目标的人。如果我能提供任何其他信息,请告诉我。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46694898

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档