如何在不影响磁盘的情况下开始下载并呈现响应?

问题描述 投票:0回答:1

所以我在 django 中有一个科学数据 Excel 文件验证表单,效果很好。它迭代地工作。用户可以在积累添加到研究中的新数据时上传文件。

DataValidationView
每次都会检查文件并向用户提供错误报告,其中列出了他们必须修复的数据中的问题。

我们最近意识到许多错误(但不是全部)可以自动修复,因此我一直在研究一种方法来生成包含许多修复的文件副本。因此,我们将“验证”表单页面重新命名为“构建提交页面”。每次他们上传一组新文件时,目的是让他们仍然获得错误报告,但也会自动收到包含许多修复程序的下载文件。

我今天才了解到,无法同时渲染模板并开始下载,这是有道理的。但是,我一直计划不让生成的修复文件写入磁盘。

有没有办法呈现带有错误的模板并自动触发下载,而无需事先将文件保存到磁盘?

这是我目前的

form_valid
方法(没有触发下载,但在我意识到下载和渲染模板都不起作用之前我已经开始创建文件):

    def form_valid(self, form):
        """
        Upon valid file submission, adds validation messages to the context of
        the validation page.
        """

        # This buffers errors associated with the study data
        self.validate_study()

        # This generates a dict representation of the study data with fixes and
        # removes the errors it fixed
        self.perform_fixes()

        # This sets self.results (i.e. the error report)
        self.format_validation_results_for_template()

        # HERE IS WHERE I REALIZED MY PROBLEM.  I WANTED TO CREATE A STREAM HERE
        # TO START A DOWNLOAD, BUT REALIZED I CANNOT BOTH PRESENT THE ERROR REPORT
        # AND START THE DOWNLOAD FOR THE USER

        return self.render_to_response(
            self.get_context_data(
                results=self.results,
                form=form,
                submission_url=self.submission_url,
            )
        )

在解决这个问题之前,我正在编译一些伪代码来流式传输文件......这完全未经测试:

import pandas as pd
from django.http import HttpResponse
from io import BytesIO

def download_fixes(self):
    excel_file = BytesIO()
    xlwriter = pd.ExcelWriter(excel_file, engine='xlsxwriter')

    df_output = {}
    for sheet in self.fixed_study_data.keys():
        df_output[sheet] = pd.DataFrame.from_dict(dfs_dict[sheet])
        df_output[sheet].to_excel(xlwriter, sheet)

    xlwriter.save()
    xlwriter.close()

    # important step, rewind the buffer or when it is read() you'll get nothing
    # but an error message when you try to open your zero length file in Excel
    excel_file.seek(0)

    # set the mime type so that the browser knows what to do with the file
    response = HttpResponse(excel_file.read(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')

    # set the file name in the Content-Disposition header
    response['Content-Disposition'] = 'attachment; filename=myfile.xlsx'

    return response

所以我想我需要:

  1. 将文件保存到磁盘,然后找到一种方法使结果页面开始下载
  2. 以某种方式发送嵌入结果模板中的数据,并通过 javascript 将其发送回文件下载流
  3. 以某种方式将文件保存在内存中并从结果模板触发其下载?

实现这一目标的最佳方法是什么?

更新的想法

我最近用一个

tsv
文件做了一个简单的技巧,我将文件内容嵌入到生成的模板中,并使用一个下载按钮,该按钮使用 JavaScript 来抓取数据周围标签的
innerHTML
并开始“下载”。

我想,如果我对数据进行编码,我可能会对 Excel 文件内容做类似的事情。我可以对其进行 Base64 编码。

我回顾了过去提交的研究报告。最大的一个有115kb。该大小可能会增长一个数量级,但目前 115kb 是上限。

我在谷歌上搜索找到一种将数据嵌入模板的方法,我得到了this

import base64
with open(image_path, "rb") as image_file:
    image_data = base64.b64encode(image_file.read()).decode('utf-8')
ctx["image"] = image_data
return render(request, 'index.html', ctx)

我最近在 javascript 中尝试使用 base64 编码来完成一些不相关的工作,这让我相信嵌入是可行的。我什至可以自动触发它。有人这样做有什么注意事项吗?

django django-templates stream
1个回答
0
投票

为了后代,在 Django 中实现的 HTTP 1.1 multipart/byteranges

Response
指南。有关 multipart/byteranges 的更多信息,请参阅 RFC 7233

multipart/byteranges有效负载的格式如下:

HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5

--3d6b6a416f9b5
Content-Type: application/octet-stream
Content-Range: bytes 0-999/2000

<octet stream data 1>

--3d6b6a416f9b5
Content-Type: application/octet-stream
Content-Range: bytes 1000-1999/2000

<octet stream data 2>

--3d6b6a416f9b5
Content-Type: application/json
Content-Range: bytes 0-441/442

<json data>

--3d6b6a416f9b5
Content-Type: text/html 
Content-Range: bytes 0-543/544

<html string>
--3d6b6a416f9b5--

你明白了。前两个是相同的二进制数据,分为两个流,第三个是在一个流中发送的 JSON 字符串,第四个是在一个流中发送的 HTML 字符串。

就您而言,您将连同

File
模板一起发送
HTML

from io import BytesIO, StringIO
from django.http import StreamingHttpResponse


def octet_stream_generator(streams):
    boundary = "3d6b6a416f9b5"
    for stream in streams:
        if isinstance(stream, BytesIO):
            data = stream.getvalue()
            content_type = 'application/octet-stream'
        elif isinstance(stream, StringIO):
            data = stream.getvalue().encode('utf-8')
            content_type = 'text/html'
        else:
            continue
        
        stream_length = len(data)
        yield f'--{boundary}\r\n'
        yield f'Content-Type: {content_type}\r\n'
        yield f'Content-Range: bytes 0-{stream_length-1}/{stream_length}\r\n'
        yield f'\r\n'
        yield data
        yield f'\r\n'

    yield f'--{boundary}--\r\n'

def multi_octet_stream_response(request):
    streams = [
        excel_file, # The File provided in the OP. It is a BytesIO object.
        StringIO(render_to_string('index.html', request=request)
    ]
    response = StreamingHttpResponse(octet_stream_generator(streams), content_type='multipart/byteranges; boundary=3d6b6a416f9b5')
    return response

请参阅此 示例 [stackoverflow] 在客户端上解析 multipart/byteranges

© www.soinside.com 2019 - 2024. All rights reserved.