使用StreamingHttpResponse和Django Rest Framework CSV

306*_*6d0 10 python csv django rest django-rest-framework

我有一个标准的DRF Web应用程序,可以输出其中一条路径的CSV数据.渲染整个CSV表示需要一段时间.数据集非常大,所以我希望有一个流式HTTP响应,因此客户端不会超时.

但是,使用https://github.com/mjumbewu/django-rest-framework-csv/blob/2ff49cff4b81827f3f450fd7d56827c9671c5140/rest_framework_csv/renderers.py#L197中提供的示例并不能完全实现此目的.数据仍然是一个大的有效载荷而不是分块,并且客户端在接收到字节之前最终等待响应.

结构类似于以下内容:

models.py

class Report(models.Model):
  count = models.PostiveIntegerField(blank=True)
  ...
Run Code Online (Sandbox Code Playgroud)

renderers.py

class ReportCSVRenderer(CSVStreamingRenderer):
  header = ['count']
Run Code Online (Sandbox Code Playgroud)

serializers.py

class ReportSerializer(serializers.ModelSerializer):
  count = fields.IntegerField()

  class Meta:
    model = Report
Run Code Online (Sandbox Code Playgroud)

views.py

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
Run Code Online (Sandbox Code Playgroud)

注意:必须注释或更改某些内容.

谢谢

小智 6

一个更简单的解决方案,受到 @3066d0 的启发:

渲染器.py

class ReportsRenderer(CSVStreamingRenderer):
    header = [ ... ]
    labels = { ... }
Run Code Online (Sandbox Code Playgroud)

视图.py

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
Run Code Online (Sandbox Code Playgroud)

要点是,您需要传递一个生成序列化数据的生成器作为data渲染器的参数,然后渲染器CSVStreamingRenderer执行其操作并传输响应本身。我更喜欢这种方法,因为这样你不需要重写第三方库的代码。


tre*_*tix 2

对于小响应, DjangoStreamingHttpResponse可能比传统的慢得多HttpResponse

如果不需要就不要使用;Django 文档实际上建议StreamingHttpResponse仅当绝对需要在将数据传输到客户端之前不迭代整个内容时才应使用它。”

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

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

File api中,您可以分块打开文件,这样并非所有文件都会加载到内存中。

希望这个对你有帮助。