【问题标题】:How to return generated file download with Django REST Framework?如何使用 Django REST Framework 返回生成的文件下载?
【发布时间】:2016-08-01 11:19:55
【问题描述】:

我需要将生成的文件下载作为 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

但它返回一个 msword 文档为json

如何让它开始作为文件下载?

【问题讨论】:

  • 你的意思是说你已经创建了一个word文件,你需要将它传递给前端以便前端用户能够下载它?
  • @PiyushS.Wanare 完全正确
  • 也许在文件生成之后,如果它可以从您的网络服务器公开访问(没有 Django 代码、授权等),您可以发送 302 重定向响应。

标签: python django ms-word django-rest-framework download


【解决方案1】:

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

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。如果您想有选择地返回文件下载,您可以向自定义渲染器添加更多逻辑。

【讨论】:

    【解决方案2】:

    这可能对你有用:

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

    前端代码请参考this

    【讨论】:

    • 我没有收到这条线 yourFilePointer.write(response,text) 。我的文件已经生成并保存在服务器上。我应该在那里写什么text
    • 文本将是您的 word 文件文本。
    • 当我们写像f = open('c:\file.doc', "w") f.write(text)这样的文本文件时
    • 正如我所说,我的文件已经在磁盘上。我不用写了
    • 应该是:´response = HttpResponse(FilePointer.read(), content_type='application/msword')´
    【解决方案3】:

    我正在使用 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
    【解决方案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;
    })
    

    【讨论】:

    • 但是你是如何让前端可以访问媒体路径的呢?就像我对静态文件夹中的路径做了同样的事情并且它有效但是当我为媒体文件夹做这件事时,它说“找不到页面”!
    【解决方案5】:

    对我来说,使用 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
    

    【讨论】:

      【解决方案6】:

      在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]
      

      在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();
          })
      }
      

      希望对你有帮助

      【讨论】:

        猜你喜欢
        • 2014-08-08
        • 1970-01-01
        • 2017-03-20
        • 2010-10-28
        • 2013-03-24
        • 2022-01-03
        • 2019-08-19
        • 2021-07-11
        • 2021-04-09
        相关资源
        最近更新 更多