让Django提供可下载的文件

我希望网站上的用户能够下载路径被遮蔽的文件,这样他们就不能直接下载。

例如,我希望URL是这样的:http://example.com/download/?f=somefile.txt

在服务器上,我知道所有可下载的文件都位于文件夹/home/user/files/中。

有没有一种方法可以让Django为下载提供这个文件,而不是试图找到一个URL和视图来显示它?

186725 次浏览

“下载”只是一个HTTP报头更改。

关于如何响应下载,请参见http://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment

"/download"只需要一个URL定义。

请求的GETPOST字典将包含"f=somefile.txt"信息。

您的视图函数将简单地合并基本路径与“f”值,打开文件,创建并返回一个响应对象。它应该少于12行代码。

Django建议您使用另一台服务器来提供静态媒体(在同一台机器上运行另一台服务器是可以的)。他们建议使用lighttp这样的服务器。

设置起来非常简单。然而。如果'somefile.txt'是在请求时生成的(内容是动态的),那么你可能想让django来服务它。

Django Docs - Static Files

为了“两全其”,你可以将s.l ot的解决方案与xsendfile模块结合起来:django生成文件的路径(或文件本身),但实际的文件服务是由Apache/Lighttpd处理的。一旦你设置了mod_xsendfile,集成你的视图需要几行代码:

from django.utils.encoding import smart_str


response = HttpResponse(mimetype='application/force-download') # mimetype is replaced by content_type for django 1.7
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(path_to_file)
# It's usually a good idea to set the 'Content-Length' header too.
# You can also set any other required headers: Cache-Control, etc.
return response

当然,这只有在你能控制你的服务器,或者你的主机公司已经设置了mod_xsendfile的情况下才会起作用。

编辑:

在django 1.7中,Mimetype被content_type取代

response = HttpResponse(content_type='application/force-download')

<强>编辑: 对于nginx检查,它使用X-Accel-Redirect而不是apache X-Sendfile头

S.Lott有“好的”/简单的解决方案,elo80ka有“最好的”/高效的解决方案。这是一个“更好”/中间的解决方案-没有服务器设置,但更有效的大文件比天真的修复:

http://djangosnippets.org/snippets/365/

基本上,Django仍然处理文件的服务,但不会一次将整个文件加载到内存中。这允许您的服务器(缓慢地)在不增加内存使用的情况下提供大文件。

同样,S.Lott的X-SendFile对于较大的文件仍然更好。但如果你不能或不想为此费心,那么这个中间解决方案将为你带来更好的效率,而不会带来麻烦。

上面提到过,mod_xsendfile方法不允许在文件名中使用非ascii字符。

出于这个原因,我有一个补丁mod_xsendfile,将允许任何文件被发送,只要名称是url编码,和额外的头:

X-SendFile-Encoding: url

也会被发送。

< a href = " http://ben.timby.com/?p=149 " rel = " noreferrer > http://ben.timby.com/?p=149 < / >

试题:https://pypi.python.org/pypi/django-sendfile/

“一旦Django检查了权限,抽象的卸载文件上传到web服务器(例如Apache的mod_xsendfile)。”

另一个项目可以看看:http://readthedocs.org/docs/django-private-files/en/latest/usage.html 看起来很有希望,不过我还没有亲自测试过

基本上,该项目抽象了mod_xsendfile配置,并允许你做以下事情:

from django.db import models
from django.contrib.auth.models import User
from private_files import PrivateFileField


def is_owner(request, instance):
return (not request.user.is_anonymous()) and request.user.is_authenticated and
instance.owner.pk = request.user.pk


class FileSubmission(models.Model):
description = models.CharField("description", max_length = 200)
owner = models.ForeignKey(User)
uploaded_file = PrivateFileField("file", upload_to = 'uploads', condition = is_owner)

我遇到过同样的问题不止一次,所以使用xsendfile模块和认证视图装饰器django-filelibrary实现。请随意使用它作为您自己的解决方案的灵感。

https://github.com/danielsokolowski/django-filelibrary

尝试@Rocketmonkeys解决方案,但下载的文件被存储为*.bin并随机命名。这当然不好。从@elo80ka添加另一行就解决了这个问题 下面是我现在使用的代码:

from wsgiref.util import FileWrapper
from django.http import HttpResponse


filename = "/home/stackoverflow-addict/private-folder(not-porn)/image.jpg"
wrapper = FileWrapper(file(filename))
response = HttpResponse(wrapper, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
response['Content-Length'] = os.path.getsize(filename)
return response
你现在可以把文件存储在私有目录中(不是在/media或/public_html中),并通过django将它们暴露给特定的用户或在特定的情况下 希望能有所帮助。< br > < br > 感谢@elo80ka, @S。Lott和@Rocketmonkeys的答案,得到了完美的解决方案,结合所有这些=)

def qrcodesave(request):
import urllib2;
url ="http://chart.apis.google.com/chart?cht=qr&chs=300x300&chl=s&chld=H|0";
opener = urllib2.urlopen(url);
content_type = "application/octet-stream"
response = HttpResponse(opener.read(), content_type=content_type)
response["Content-Disposition"]= "attachment; filename=aktel.png"
return response

对于一个非常简单的但没有效率和可扩展性解决方案,你可以使用django内置的serve视图。这对于快速原型或一次性工作是非常好的,但正如在这个问题中所提到的,你应该在生产中使用apache或nginx之类的东西。

from django.views.static import serve
filepath = '/some/path/to/local/file.txt'
return serve(request, os.path.basename(filepath), os.path.dirname(filepath))

你应该在生产环境中使用流行服务器(如apachenginx)提供的sendfile api。多年来,我一直在使用这些服务器的sendfile api来保护文件。然后基于django创建了一个简单的中间件应用程序,既适合开发,也适合开发;生产的目的。你可以访问源代码在这里

更新:在新版本中,python提供商使用django FileResponse,如果可用,也增加了对许多服务器实现的支持,从lighthttp, caddy到hiawatha

使用

pip install django-fileprovider
  • 添加fileprovider应用程序到INSTALLED_APPS设置,
  • fileprovider.middleware.FileProviderMiddleware添加到MIDDLEWARE_CLASSES设置
  • 在生产环境中将FILEPROVIDER_NAME设置为nginxapache,在开发环境中默认为python

在基于类或函数的视图中,将响应头X-File值设置为文件的绝对路径。例如:

def hello(request):
# code to check or protect the file from unauthorized access
response = HttpResponse()
response['X-File'] = '/absolute/path/to/file'
return response

django-fileprovider以一种你的代码只需要最小修改的方式实现。

Nginx配置

要保护文件不被直接访问,可以将配置设置为

location /files/ {
internal;
root   /home/sideffect0/secret_files/;
}

这里nginx设置了一个位置url /files/只能在内部访问,如果你使用上述配置,你可以将X-File设置为:

response['X-File'] = '/files/filename.extension'

通过nginx配置这样做,文件将被保护&你也可以从django views中控制文件

这里只提到Django 1.10中可用的FileResponse对象

编辑:在寻找一种简单的通过Django进行文件流处理的方法时,我遇到了自己的答案,所以这里有一个更完整的例子(给未来的我)。它假设FileField名称为imported_file

views.py

from django.views.generic.detail import DetailView
from django.http import FileResponse
class BaseFileDownloadView(DetailView):
def get(self, request, *args, **kwargs):
filename=self.kwargs.get('filename', None)
if filename is None:
raise ValueError("Found empty filename")
some_file = self.model.objects.get(imported_file=filename)
response = FileResponse(some_file.imported_file, content_type="text/csv")
# https://docs.djangoproject.com/en/1.11/howto/outputting-csv/#streaming-large-csv-files
response['Content-Disposition'] = 'attachment; filename="%s"'%filename
return response


class SomeFileDownloadView(BaseFileDownloadView):
model = SomeModel

urls . py

...
url(r'^somefile/(?P<filename>[-\w_\\-\\.]+)$', views.SomeFileDownloadView.as_view(), name='somefile-download'),
...

我做过一个关于这个的项目。你可以看看我的github回购:

https://github.com/nishant-boro/django-rest-framework-download-expert

这个模块提供了一种使用Apache模块Xsendfile在django rest框架中下载文件的简单方法。它还有一个额外的功能,即只向属于特定群组的用户提供下载服务