【问题标题】:Generate multiple PDFs and zip them for download, all in a single view生成多个 PDF 并压缩它们以供下载,所有这些都在一个视图中
【发布时间】:2020-04-28 22:35:45
【问题描述】:

我正在使用xhtml2pdf 在我的 Django 视图中生成 PDF。这个想法是遍历查询中的所有实例,然后为每个实例创建一个 PDF,然后将所有生成的 PDF 添加到一个 zip 文件中以供下载。 xtml2pdf 逻辑工作正常,但循环逻辑让我头疼。

到目前为止,这是我的功能:

def bulk_cover_letter(request, ward_id, school_cat_id, cheque_number):
    school_type = SchoolType.objects.get(id=school_cat_id)

    schools_in_school_type = Applicant.objects.filter(
        school_type=school_type, ward_id=ward_id, award_status='awarded'
    ).order_by().values_list('school_name', flat=True).distinct()

    for school in schools_in_school_type:
        beneficiaries = Applicant.objects.filter(school_type=school_type, ward_id=ward_id, award_status='awarded', school_name=school)
        total_amount_to_beneficiaries = Applicant.objects.filter(school_type=school_type, ward_id=ward_id, award_status='awarded', school_name=school).aggregate(total=Sum('school_type__amount_allocated'))
        context = {
            'school_name' : school,
            'beneficiaries' : beneficiaries,
            'total_amount_to_beneficiaries' : total_amount_to_beneficiaries,
            'title' : school + ' Disbursement Details',
            'cheque_number': cheque_number
        }

        response = HttpResponse('<title>Cover Letter</title>', content_type='application/pdf')
        filename = "%s.pdf" %(cheque_number)
        content = "inline; filename=%s" %(filename)
        response['Content-Disposition'] = content
        template = get_template('cover_letter.html')
        html = template.render(context)
        result = io.BytesIO()
        pdf = pisa.CreatePDF(
            html, dest=response, link_callback=link_callback)
        if not pdf.error:
            # At this point I can generate a single PDF.
            # But no idea on what to do next.

    # The zipping logic should follow here after looping all the instances - (schools)

从那时起,我不知道下一步该做什么。任何帮助将不胜感激。

【问题讨论】:

    标签: django python-3.x zipfile xhtml2pdf


    【解决方案1】:

    试试这个:

    Utils.py

    def render_to_pdf(template_src, context_dict={}):
        template = get_template(template_src)
        html  = template.render(context_dict)
        buffer = BytesIO()
        p = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), buffer)
        pdf = buffer.getvalue()
        buffer.close()
        if not p.err:
            return pdf#HttpResponse(result.getvalue(), content_type='application/pdf')
        return None
    
    
    def generate_zip(files):
        mem_zip = BytesIO()
    
        with zipfile.ZipFile(mem_zip, mode="w",compression=zipfile.ZIP_DEFLATED) as zf:
            for f in files:
                zf.writestr(f[0], f[1])
    
        return mem_zip.getvalue()
    

    Views.py

    def generate_attendance_pdf(modeladmin, request, queryset):
    
        template_path = 'student/pdf_template.html'
    
        files = []
    
        for q in queryset:
            context = {
                'firstname': q.firstname,
                'lastname': q.lastname,
                'p_firstname': q.bceID.firstname
            }
            pdf = render_to_pdf(template_path, context)
            files.append((q.firstname + ".pdf", pdf))
    
        full_zip_in_memory = generate_zip(files)
    
        response = HttpResponse(full_zip_in_memory, content_type='application/force-download')
        response['Content-Disposition'] = 'attachment; filename="{}"'.format('attendnace.zip')
    
        return response
    

    显然,您必须根据需要修改上下文/名称。

    归功于 -> Neil Grogan https://www.neilgrogan.com/py-bin-zip/

    【讨论】:

      【解决方案2】:

      如果您需要生成多个 PDF 文件并将它们作为 zip 文件中的响应发送,则可以将报告存储在内存中,并在调用 pisa.CreatePDF 时将其设置为 dest。然后在内存中有一个报告列表,zip 它们,并作为指定另一种内容类型的 Django 响应发送。

      例如:

      reports = tempfile.TemporaryDirectory()
      report_files = {}
      for school in schools_in_school_type:
          # ... same code that renerates `html`
          mem_fp = BytesIO()
          pisa.CreatePDF(html, dest=mem_fp)
          report_files[filename] = mem_fp
      mem_zip = BytesIO()
      with zipfile.ZipFile(mem_zip, mode="w") as zf:
          for filename, content in report_files.items():
                  zf.write(filename, content)
      response = HttpResponse(mem_zip, content_type='application/force-download')
      response['Content-Disposition'] = 'attachment; filename="{}"'.format('cover_letters.zip')
      

      这仍然会产生[Errno 2] No such file or directory: 'cheque_number.pdf'的错误。

      【讨论】:

      • 您应该能够从BytesIO 对象创建一个zipfile.ZipFile,您可以直接将PDF 写入我认为不需要临时文件
      • @IainShelvington 你能分享你的功能吗?
      • @Yann,你能详细说明一下这部分吗? you can create temporary files using tempfile and set it as dest when you call pisa.CreatePDF. Then have a list of these temp file paths 。可能使用一些代码?
      • @Yann 我收到此错误:stat: path should be string, bytes, os.PathLike or integer, not _io.BytesIO
      • 我将zf.write(content, filename) 编辑为zf.write(filename, content),但现在我收到错误-File not found, &lt;cheque_number&gt;.pdf。有什么见解吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-03
      相关资源
      最近更新 更多