在Django中延迟文件下载的正确方法 [英] Right way to delay file download in Django

查看:91
本文介绍了在Django中延迟文件下载的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个基于类的视图,该视图触发了用户报告的撰写和下载。

I have a class-based view which triggers the composition and downloading of a report for a user.

通常在 def get ,添加 response ['Content-Disposition'] ='attachment; filename = somefilename.pdf'并返回给用户。

Normally in def get of the class I just compile the report, add response['Content-Disposition'] = 'attachment; filename="somefilename.pdf"' and return response to a user.

问题是某些报告很大

The problem is that some reports are large and while they are compiling the request timeout happens.

我知道处理此问题的 right 方法是将其委托给一个后台进程(例如 Celery )。但是问题在于,这意味着与其创建一个在用户下载报告后便不再存在的临时文件,不如将这些报告存储在某个地方,并编写一个cronjob来定期清理报告目录。

I know that the right way of dealing with this is to delegate it to a background process (like Celery). But the problem is that it means that instead of creating a temporary file which ceases to exist the moment the user downloads a report, I have to store these reports somewhere, and write a cronjob which will regularly clean the reports directory.

Django中是否还有其他更优雅的方式来解决此问题?

Is there any more elegant way in Django to deal with this issue?

推荐答案

比使用celery少花哨的一种解决方案是Django的 StreamingHttpResponse

One solution less fancy than using celery is to use is Django's StreamingHttpResponse:

https://docs.djangoproject.com/en/2.0/ref/request- response /#django.http.StreamingHttpResponse

使用此功能,您将使用生成器函数,该函数是使用 yield的python函数以迭代器的形式返回结果。这样,您就可以在生成数据时返回数据,而不是在完成后立即返回所有数据。您可以 yiel d 在报告的每一行或每一节之后..从而保持数据流返回到浏览器。

With this, you use a generator function, which is a python function that uses yield to return its results as an iterator. This allows you to return the data as you generate it, rather than all at once at after you're finished. You can yield after each line or section of the report.. thus keeping a flow of data back to the browser.

但是..这仅适用如果您要一点一点地建立完成的文件,例如CSV文件。如果返回的内容需要一次全部格式化,例如,完成后使用 wkhtmltopdf 之类的东西来生成pdf文件,则这不是那么容易。

But.. this only works if you are building up the finished file bit by bit.. for example, a CSV file. If you're returning something that you need to format all at once, for example if you're using something like wkhtmltopdf to generate a pdf file after you're done, then it's not as easy.

但是仍然有解决方案:

在这种情况下,您可以使用 StreamingHttpReponse 以及生成器功能,可将您的报告生成为临时文件,而不是返回到浏览器。但是,在执行此操作时, yield HTML片段返回到浏览器,使用户知道进度,例如:

What you can do in that case is, use StreamingHttpReponse along with a generator function to generate your report into a temporary file, instead of back to the browser. But as you are doing this, yield HTML snippets back to the browser which lets the user know the progress, eg:

def get(self, request, **kwargs):

    # first you need a tempfile name.. do that however you like
    tempfile = "kfjkdsjfksjfks"
    # then you need to create a view which will open that file and serve it
    # but I won't show that here.  
    # For security reasons it has to serve only out of one directory 
    # that is dedicated to this.
    fetchurl = reverse('reportgetter_url') + '?file=' + tempfile

    def reportgen():
        yield 'Starting report generation..<br>'
        # do some stuff to generate your report into the tempfile
        yield 'Doing this..<br>'
        # do this
        yield 'Doing that..<br>'
        # do that
        yield 'Finished.<br>'
        # when the browser receives this script, it'll go to fetchurl where
        # you will send them the finished report.
        yield '<script>document.location="%s";</script>' % fetchurl

    return http.StreamingHttpResponse(reportgen())

显然,这不是一个完整的例子,但是应该可以给你这个想法。

That's not a complete example obviously, but should give you the idea.

当您的用户获取此视图时,他们将看到报告的进度。最后,您将发送Javacript,该Javacript会将浏览器重定向到您必须编写的其他视图,该视图将返回包含完成文件的响应。当浏览器获取此Javacript时,如果返回临时文件的视图在返回响应之前将响应内容处置设置为附件,例如:

When your user fetches this view, they will see the progress of the report as it comes along. At the end, you're sending the javacript which redirect the browser to the other view you will have to write which returns the response containing the finished file. When the browser gets this javacript, if the view returning the tempfile is setting the response Content-Disposition as an attachment before returning it, eg:

response['Content-Disposition'] = 'attachment; filename="%s"' % filename

。然后浏览器将停留在当前页面上,显示您的进度..并简单地为用户弹出一个文件保存对话框。

..then the browser will stay on the current page showing your progress.. and simply pop up a file save dialog for the user.

为了进行清理,您将需要进行cron作业,..因为如果人们不需要请稍等,他们将永远不会拿起报告。有时情况无法解决...因此您可以清理比1小时更久的文件。对于许多系统来说,这是可以接受的。

For cleanup, you'll need a cron job regardless.. because if people don't wait around, they'll never pick up the report. Sometimes things don't work out... So you could just clean up files older than let's say 1 hour. For a lot of systems this is acceptable.

但是,如果您想立即进行清理,那么,如果您使用的是unix / linux,可以使用一个古老的unix文件系统技巧:当它们打开时被删除直到被关闭才真正消失。因此,打开您的tempfile ..然后将其删除。然后返回您的回复。响应完成发送后,文件所占用的空间将被释放。

But if you want to clean up right away, what you can do, if you are on unix/linux, is to use an old unix filesystem trick: Files which are deleted while they are open are not really gone until they are closed. So, open your tempfile.. then delete it. Then return your response. As soon as the response has finished sending, the space used by the file will be freed.

PS:我应该补充..如果采用第二种方法,则您可以使用一个视图来完成两项工作。.Just:

PS: I should add.. that if you take this second approach, you could use one view to do both jobs.. just:

if `file` in request.GET:
    # file= was in the url.. they are trying to get an already generated report
    with open(thepathname) as f:
        os.unlink(f)
        # file has been 'deleted' but f is still a valid open file
        response = HttpResponse( etc etc etc)
        response['Content-Disposition'] = 'attachment; filename="thereport"'
        response.write(f)
        return response
else:
   # generate the report
   # as above

这篇关于在Django中延迟文件下载的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆