Django REST Framework如何返回生成的文件下载?

32

我需要将生成的文件下载作为Django REST Framework响应返回。我尝试了以下方法:

def retrieve(self, request, *args, **kwargs):
    template = webodt.ODFTemplate('test.odt')
    queryset = Pupils.objects.get(id=kwargs['pk'])
    serializer = StudentSerializer(queryset)
    context = dict(serializer.data)
    document = template.render(Context(context))
    doc = converter().convert(document, format='doc')
    res = HttpResponse(
        FileWrapper(doc),
        content_type='application/msword'
    )
    res['Content-Disposition'] = u'attachment; filename="%s_%s.zip"' % (context[u'surname'], context[u'name'])
    return res

但它返回一个json文件作为msword文档。

我该如何让它开始下载文件而不是显示在页面上?


你的意思是说你已经创建了一个Word文件,需要将其传递给前端,以便前端用户可以下载它? - Piyush S. Wanare
@PiyushS.Wanare 确切无误 - Viktor
也许在文件生成后,如果它可以从您的Web服务器公开访问(不需要Django代码、授权等),您可以发送302重定向响应。 - Owen
8个回答

25

这里是直接从DRF返回文件下载的示例。诀窍是使用自定义渲染器,以便您可以直接从视图中返回Response:

from django.http import FileResponse
from rest_framework import viewsets, renderers
from rest_framework.decorators import action

class PassthroughRenderer(renderers.BaseRenderer):
    """
        Return data as-is. View should supply a Response.
    """
    media_type = ''
    format = ''
    def render(self, data, accepted_media_type=None, renderer_context=None):
        return data

class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Example.objects.all()

    @action(methods=['get'], detail=True, renderer_classes=(PassthroughRenderer,))
    def download(self, *args, **kwargs):
        instance = self.get_object()

        # get an open file handle (I'm just using a file attached to the model for this example):
        file_handle = instance.file.open()

        # send file
        response = FileResponse(file_handle, content_type='whatever')
        response['Content-Length'] = instance.file.size
        response['Content-Disposition'] = 'attachment; filename="%s"' % instance.file.name

        return response

注意,我正在使用自定义端点download而不是默认端点retrieve,因为这使得仅为此端点覆盖渲染器变得容易,而不是为整个视图集进行覆盖 -- 而且对于列表和详细信息返回常规JSON通常是有意义的。如果您想有选择地返回文件下载,则可以向自定义渲染器添加更多逻辑。


断言错误:Response上未设置.accepted_media_type属性 有时会出现此错误。 - Sandeep Balagopal

13

这可能适合你:

file_path = file_url
FilePointer = open(file_path,"r")
response = HttpResponse(FilePointer,content_type='application/msword')
response['Content-Disposition'] = 'attachment; filename=NameOfFile'

return response.

前端代码请参考这里


我不理解这行代码 yourFilePointer.write(response,text)。我的文件已经在服务器上生成并保存了。那么我应该在text里写什么呢? - Viktor
文本将是您的Word文件文本。 - Piyush S. Wanare
我们可以通过如下代码来编写文本文件:f = open('c:\file.doc', "w") f.write(text) - Piyush S. Wanare
正如我所说,我的文件已经存储在磁盘上。我不需要再写入它。 - Viktor
2
应该这样写:´response = HttpResponse(FilePointer.read(), content_type='application/msword')´。 - Tobias Ernst
你如何使用 CSRF 进行保护?根据 Django 文档,您需要使用 render() 函数才能使其正常工作。 - Xhark

10

我正在使用DRF,发现一个用于下载文件的视图代码,它可能是这样的:

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

class FileDownloadListAPIView(generics.ListAPIView):

    def get(self, request, id, format=None):
        queryset = Example.objects.get(id=id)
        file_handle = queryset.file.path
        document = open(file_handle, 'rb')
        response = HttpResponse(FileWrapper(document), content_type='application/msword')
        response['Content-Disposition'] = 'attachment; filename="%s"' % queryset.file.name
        return response

url.py将会是:

path('download/<int:id>/',FileDownloadListAPIView.as_view())

我在前端使用React.js,并得到以下响应:
handleDownload(id, filename) {
  fetch(`http://127.0.0.1:8000/example/download/${id}/`).then(
    response => {
      response.blob().then(blob => {
      let url = window.URL.createObjectURL(blob);
      let a = document.createElement("a");
      console.log(url);
      a.href = url;
      a.download = filename;
      a.click();
    });
  });
}

我成功下载了一个文件,并且它也可以正确打开,我希望这会起作用。谢谢。


如果您不知道 content_type 是什么,该如何处理自动 content_type? - Ryan Aquino

6

对于我来说,使用Python 3.6、Django 3.0和DRF 3.10,问题出现在使用了错误类型的响应。我需要使用django.http.HttpResponse,如下所示:

from django.http import HttpResponse
...
with open('file.csv', 'r') as file:
    response = HttpResponse(file, content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename=file.csv'
    return response

4
我通过将文件保存在媒体文件夹中,并将其链接发送给前端,解决了我的问题。
@permission_classes((permissions.IsAdminUser,))
class StudentDocxViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    def retrieve(self, request, *args, **kwargs):
        template = webodt.ODFTemplate('test.odt')
        queryset = Pupils.objects.get(id=kwargs['pk'])
        serializer = StudentSerializer(queryset)
        context = dict(serializer.data)
        document = template.render(Context(context))
        doc = converter().convert(document, format='doc')
        p = u'docs/cards/%s/%s_%s.doc' % (datetime.now().date(), context[u'surname'], context[u'name'])
        path = default_storage.save(p, doc)
        return response.Response(u'/media/' + path)

我在前端(AngularJS SPA)中处理过类似的问题。

$http(req).success(function (url) {
    console.log(url);
    window.location = url;
})

1
但是你是如何让媒体路径对前端可访问的呢? 就像我在静态文件夹中使用路径时一样,它可以工作,但是当我在媒体文件夹中使用时,它会显示“页面未找到”! - Aditya Sinha

2
在models.py文件中
class Attachment(models.Model):
    file = models.FileField(upload_to=attachment_directory_path, blank=True, null=True)
    ...

    @property
    def filename(self):
        return self.file.name.split('/')[-1:][0]

in views.py

import mimetypes
from django.http import FileResponse


class AttachmentViewSet(ModelViewSet):
    ...

    @action(methods=['GET'], detail=True)
    def download(self, request, **kwargs):
        att = self.get_object()
        file_handle = att.file.open()

        mimetype, _ = mimetypes.guess_type(att.file.path)
        response = FileResponse(file_handle, content_type=mimetype)
        response['Content-Length'] = att.file.size
        response['Content-Disposition'] = "attachment; filename={}".format(att.filename)
        return response

在前端,我使用axios下载文件。api是axios客户端。

export function fileDownload(url, filename){
  return api.get(url, { responseType: 'blob' })
    .then((response)=>{
      const url = window.URL.createObjectURL(new Blob([response.data]));
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute('download', filename);
      document.body.appendChild(link);
      link.click();
    })
}

希望这能有所帮助。


0

使用django-downloadview可以这样做:

from rest_framework.decorators import action
from django_downloadview import ObjectDownloadView


class DocumentViewSet(viewsets.ReadOnlyModelViewSet):

    @action(detail=True)
    def download(self, request, pk):
        return ObjectDownloadView.as_view(
            model=, # your model here
        )(request, pk=pk)

然后可以通过DRF路由器注册视图集。


0
class FileDownloadListView(generics.ListAPIView):

    def get(self, request, id):
        media = Media.objects.get(id=id)
        filepath = media.video.path
        mimetype, _ = mimetypes.guess_type(filepath)
        filename = os.path.basename(media.video.name)
        with open(filepath, 'rb') as file:
            response = HttpResponse(FileWrapper(file), content_type=mimetype)
            response['Content-Disposition'] = f'attachment; filename={filename}'
            return response


    path('media/download/<int:id>/', FileDownloadListView.as_view()),

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接