Abedy
Abedy

Reputation: 371

Generate multiple PDFs and zip them for download, all in a single view

I am using xhtml2pdf to generate PDFs in my Django View. The idea is to loop over all the instances that are there in the query, then for each instance create a PDF, then add all the generated PDFs to one zip File for download. The xtml2pdf logic is working okay but the looping logic is what gives me headache.

So this is my function so far:

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)

From that Point I have no idea on what to do next. Any help will be highly appreciated.

Upvotes: 8

Views: 3216

Answers (2)

Alvin Benedicto
Alvin Benedicto

Reputation: 41

Try this:

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

Obviously, you have to modify the context/names to what you need.

Credit to -> Neil Grogan https://www.neilgrogan.com/py-bin-zip/

Upvotes: 3

Yann
Yann

Reputation: 2522

If you need to generate several PDF files and send them as a response in a zip file then you can store the reports in memory and set it as dest when you call pisa.CreatePDF. Then have a list of reports in memory, zip them, and send as a Django response specifying another content type.

For example:

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')

This still generates an error of [Errno 2] No such file or directory: 'cheque_number.pdf'.

Upvotes: 0

Related Questions