所以我在 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
所以我想我需要:
实现这一目标的最佳方法是什么?
更新的想法:
我最近用一个
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 中实现的 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。