从Django视图返回的JavaScript / jQuery下载PDF文件

时间:2019-04-23 15:34:48

标签: javascript jquery ajax django pdf

我有一个芹菜任务,只需单击一个按钮即可创建PDF文件。

单击按钮时,javascript端将继续检查直到任务完成为止;当任务完成时,它将下载文件。我想一些递归可以做到:

$(".getPdf").on('click', function(event) {

    // Grab the clicked object ID and the filename stored on the data attributes
    var thisId = $(this).data('runid');
    var filename = $(this).data('filename');
    var taskID = "None";
    var pdfStatus = "None";

    // Run download function
    downloadPdf(filename, taskID, pdfStatus);
    function downloadPdf(filename, taskID, pdfStatus) {

        // Send a POST request to Django's RunsView with the Run ID, pdf filename, task ID and pdfStatus (if any)
        $.post({
            url: "{% url 'runs' %}",
            data: {
                csrfmiddlewaretoken: "{{ csrf_token }}",
                id: thisId,
                filename: filename,
                task_id: taskID,
            },
            success: function(data) {
                // Split the returned string to separate the task ID [0] from the status [1]
                var taskID = data.split(";")[0];
                var pdfStatus = data.split(";")[1];

                // Convert the pdfStatus Python bools into JavaScript bools
                if (pdfStatus == "False") {
                    pdfStatus = false;
                } else if (pdfStatus == "True") {
                    pdfStatus = true;
                };

                if (!pdfStatus) {
                    console.log("Repeat function.");
                    downloadPdf(filename, taskID, pdfStatus);
                } else {
                    console.log("Download PDF");
                    window.open("data:application/pdf;base64," + data);
                };
            },
            traditional: true
        }).done();
    };
});

PDF下载的实际JavaScript方面非常简单。我发送要生成PDF的对象的ID(“ 运行”)以及该文件应具有的文件名(该文件名在 get 上生成)该页面的Django视图的一部分),以及一个空的celery任务ID(当然,该任务尚未创建)。我得到的响应是由“ celery task ID”组成的字符串;如果尚未完成任务,则为False,因此我重复POST请求。

在后端,我根据任务ID是否存在来处理POST请求:

def post(self, request):
    # Get the args from the incoming request
    run_id = request.POST.get('id')
    filename = request.POST.get('filename')
    task_id = request.POST.get('task_id')

    if run_id and task_id == "None":

        # Database and Django stuff happens here for a few lines...

        # Fire off a Celery task to generate the PDF file asynchronously
        pdf_task = create_pdf.delay(sensor, range_period)
        response_string = str(pdf_task.task_id) + ";" + str(AsyncResult(pdf_task.task_id).ready())

        return HttpResponse(response_string)

    elif task_id != "None":
        pdf_done = AsyncResult(task_id).ready() # If this is false, send the task ID back with a False
        if not pdf_done:
            response_string = str(task_id) + ";" + str(pdf_done)
            return HttpResponse(response_string)
        # Otherwise send the PDF back
        else:
            pdf_task = AsyncResult(task_id)
            pdf_file = pdf_task.result
            pdf_file_location = settings.MEDIA_ROOT + '/reports/' + pdf_file


            # response = HttpResponse(content_type='application/pdf')
            # response['Content-Disposition'] = 'attachment; filename={}'.format(pdf_task.result)
            # # return response

            try:
                # with open(pdf_file_location, 'r') as pdf:
                #   response = HttpResponse(pdf.read(), content_type='application/pdf')
                #   response['Content-Disposition'] = 'attachment; filename="' + pdf_task.result + '"'
                # pdf.close()
                return FileResponse(open(pdf_file_location, 'rb'), content_type='application/pdf')

            except IOError:
                print("File does not exist, check above.")

            return response

我一直在努力使文件下载与AJAX请求一起工作。到目前为止,没有被注释掉的代码实际上已经将一串编码文本返回到前端,但是我不确定如何解码,最后得到一个看起来像这样的标签:

enter image description here

因此,在完成文件处理之后,编码的PDF到达了前端(那很好),但是我对JavaScript的了解还不够,无法弄清楚如何将编码的字符串转换为PDF文件。

1 个答案:

答案 0 :(得分:0)

您可以将base64解码为Blob并将其下载为文件。

    import React, { Component } from 'react';
    import {FaUpload} from 'react-icons/fa';
    import Dropzone from 'react-dropzone';
    import ImagePreview from './ImagePreview.js';
    const baseStyle = {
      width: "100%",
      height: 400,
      borderWidth: 2,
      borderColor: '#808080',
      borderStyle: 'dashed',
      backgroundColor: "#dcdcdc",
      borderRadius: 5
    };
    const activeStyle = {
      borderStyle: 'solid',
      borderColor: '#6c6',
      backgroundColor: '#eee'
    };
    const rejectStyle = {
      borderStyle: 'solid',
      borderColor: '#c66',
      backgroundColor: '#eee'
    };
    class FileUploader extends Component{
      constructor(props){
        super(props);
        this.removeImage = this.removeImage.bind(this);
        let files = [];
        if(props.fileList !== null && props.fileList !== undefined){
          files = props.fileList;
        }
        this.state = {uploadedImages: files};
      }
      onDrop = (acceptedFiles, rejectedFiles) =>{
        let arr = this.state.uploadedImages;
        acceptedFiles.forEach(file => {
        arr.push(file);
          });
        this.setState({uploadedImages: arr},function(){
          this.props.onUpload(this.state.uploadedImages);
        });
      }
      getPreviews(){
        if(this.state.uploadedImages === null || this.state.uploadedImages === 
    undefined){
          return (
            <div>
              <ImagePreview width="100px" height="100px"/>
              {/*<div style={emptyBox}/>*/}
            </div>);
        }else{
          const previews = this.state.uploadedImages.map(function(img, index) {
            return <ImagePreview img={URL.createObjectURL(img)} key={index} 
    width="100px" height="100px" onClick={() =>this.removeImage(index)}/>;
          }.bind(this));


      return (
        <div>
        {previews}
        <ImagePreview width="100px" height="100px"/>
        </div>
      );
    }
  }
  removeImage(index){
    let arr = this.state.uploadedImages;
    arr.splice(index,1);
    this.setState({uploadedImages: arr});
  }
  render(){



    return(
      <div>
        <Dropzone accept="image/*" onDrop={this.onDrop.bind(this)}>
        {({ getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, acceptedFiles, rejectedFiles }) => {
          let styles = {...baseStyle}
          styles = isDragActive ? {...styles, ...activeStyle} : styles
          styles = isDragReject ? {...styles, ...rejectStyle} : styles
            return (
              <div {...getRootProps()} style={styles}>
                <input {...getInputProps()} />
                <div style={{margin: "auto", textAlign: "center", position: "relative", top: "30%"}}>
                  <FaUpload size="100"/><br/>
                  {!isDragReject &&<strong>Drag and drop or click here</strong>}
                  {isDragReject && <strong>You can only upload Images</strong>}
                </div>
              </div>
            )
          }}
        </Dropzone>
        <div style={{marginTop: "10px", border: "1px solid gray", padding: "5px"}}>
          {this.getPreviews()}
        </div>
      </div>
    );
      }
    }
    export default FileUploader;