Generate html to pdf using xhtml2pdf in django project
I have an HTML page containing multiple tables. When converting it to a PDF using xhtml2pdf, it works fine if only a few tables are expanded. However, if I expand more tables, I run into a size limitation where the PDF turns out blank if the content exceeds a certain size(30Mb).
Views.py
def generate_pdf(request):
template_path = 'app.html'
context = {
'data': 'This is dynamic data passed to the template'
}
pdf_stylesheets = """
<style>
@page {
size: A4 landscape;
margin: 1.5cm;
}
.table-wrapper {
width: 100%;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed !important;
}
thead {
display: table-header-group;
}
tbody {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
tr {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
td {
padding: 8px;
border: 1px solid #ddd;
font-size: 14px;
vertical-align: top;
}
/* Column widths */
td:nth-child(1) { width: 10%; }
td:nth-child(2) { width: 15%; }
td:nth-child(3) { width: 10%; }
td:nth-child(4) {
width: 45%;
max-width: 45%;
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
white-space: pre-wrap !important;
}
td:nth-child(5) { width: 20%; }
.message-cell {
position: relative;
max-width: 45%;
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
white-space: pre-wrap !important;
}
.message-content {
display: inline-block;
width: 100%;
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
white-space: pre-wrap !important;
line-height: 1.4;
}
th {
padding: 8px;
background-color: #355b7a;
color: white;
border: 1px solid #ddd;
font-size: 14px;
}
@media print {
table {
table-layout: fixed !important;
}
thead {
display: table-header-group !important;
}
tbody {
page-break-inside: avoid !important;
break-inside: avoid !important;
display: block !important;
min-height: fit-content !important;
}
tr {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
td {
border: 0.5pt solid #ddd !important;
font-size: 14px !important;
}
.message-cell, .message-content {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
}
</style>
"""
html = render_to_string(template_path, context)
html = pdf_stylesheets + html
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="app.pdf"'
pdf_options = {
'page-size': 'A4',
'orientation': 'Landscape',
'margin-top': '1.5cm',
'margin-right': '1.5cm',
'margin-bottom': '1.5cm',
'margin-left': '1.5cm',
'encoding': 'UTF-8',
'keep-with-next': 'true',
'no-split-tables': 'true',
'enable-smart-shrinking': 'true',
'print-media-type': 'true'
}
pisa_status = pisa.CreatePDF(
html.encode('UTF-8'),
dest=response,
encoding='UTF-8',
show_error_as_pdf=True,
pdf_options=pdf_options
)
if pisa_status.err:
return HttpResponse('We had some errors <pre>' + html + '</pre>')
return response
html template
{% load static %}
<html>
<head>
<meta charset="UTF-8">
<title>Test Report</title>
</head>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
display: flex;
flex-direction: column;
align-items: left;
margin: 0;
padding: 0;
}
.header-content {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
margin: 0;
padding: 0;
}
h1 {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 1.5em;
}
.environment-details {
align-self: flex-start;
color: black;
font-size: 0.9em;
}
header {
background: #5ea3db;
color: white;
padding-top: 10px;
min-height: 60px;
border-bottom: #e8491d 3px solid;
}
.header-buttons {
position: absolute;
top: 2%;
right: 0;
transform: translateY(-50%);
display: flex;
gap: 3px;
}
.header-buttons button {
border: none;
border-radius: 7px;
padding: 5px 10px;
font-size: 14px;
transition-duration: 0.4s;
cursor: pointer;
}
.table-container {
width: 95%;
margin: 20px auto;
background: white;
padding: 15px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.4);
}
.table-wrapper {
width: auto;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin-bottom: 15px;
page-break-inside: avoid;
overflow: visible;
}
.main_table {
min-width: 100%;
margin-bottom: 15px;
}
.main_table th {
background-color: #355b7a;
color: white;
font-size: 0.9em;
white-space: nowrap;
padding: 8px 15px;
position: sticky;
top: 0;
}
.main_table td {
padding: 8px 15px;
text-align: justify;
font-size: 0.8em;
white-space: nowrap;
}
.main_table td:nth-child(1) {
width: 40%;
word-break: break-word;
white-space: normal;
overflow-wrap: break-word;
}
.main_table td:nth-child(2) { width: 10%; }
.main_table td:nth-child(3) { width: 15%; }
.main_table td:nth-child(4) { width: 15%; }
.main_table td:nth-child(5) {
width: 20%;
word-break: break-word;
white-space: normal;
overflow-wrap: break-word;
}
.summary_table td {
padding: 15px;
font-size: 0.8em;
}
table {
border-collapse: collapse;
background-color: white;
}
table, th, td {
border: 1px solid #ddd;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
.table-wrapper::-webkit-scrollbar {
height: 8px;
}
.table-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* @media print {
body { zoom: 0.75; }
} */
@media print {
body {
margin: 0;
padding: 0;
}
.page-break {
page-break-before: always;
}
table, img {
page-break-inside: avoid;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>TEST REPORT</h1>
<div class="environment-details">
<b>Project Name:</b> {{ project_name }}<br>
<b>Environment:</b> {{environment}}<br>
<b>Verification Type:</b> {{VERIFICATION_TYPE}}<br>
</div>
</div>
<div class="header-buttons">
<button>
{% if log_file_name %}
<a href="{{ log_file_name }}" style="text-decoration: none; color: black;" target="_blank">Robot Log</a>
{% else %}
<span>Robot Log Not Available</span>
{% endif %}
</button>
<button id="downloadBtn">Download PDF</button>
</div>
</div>
</header>
<div class="table-container">
<p style="text-align:center;">This report includes the test case details and individual summary tables for each test case.</p>
<div class="table-wrapper">
{{ main_table_html|safe }}
</div>
<div>
{% for table in summary_tables_html %}
<div class="table-wrapper">
{{ table|safe }}
</div>
{% endfor %}
</div>
</div>
</body>
<script>
const script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js";
script.onload = function () {
document.getElementById('downloadBtn').addEventListener('click', function() {
// Temporarily remove collapsed tables
document.querySelectorAll('details:not([open])').forEach(detail => {
detail.style.display = 'none';
});
const element = document.body;
const opt = {
margin: [0.2, 0.2, 0.2, 0.2],
filename: 'test_report.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 4, scrollX: 0, scrollY: 0, useCORS: true },
jsPDF: { unit: 'in', format: 'a4', orientation: 'landscape' }
};
// Generate and estimate PDF size before downloading
html2pdf().from(element).set(opt).toPdf().output('datauristring').then(dataUri => {
let estimatedSizeKB = (dataUri.length * (3 / 4)) / 1024; // Convert Base64 size to KB
console.log("PDF Size (KB):", estimatedSizeKB);
// If the estimated size is too large, show an alert and stop the process
if (estimatedSizeKB >= 100 && estimatedSizeKB <= 300) {
alert("The generated PDF is too large. Try again with fewer expanded test cases.");
// Restore hidden elements after canceling the process
document.querySelectorAll('details:not([open])').forEach(detail => {
detail.style.display = '';
});
return; // Stop execution, do NOT generate the PDF
}
// If the size is acceptable, proceed with the download
html2pdf().from(element).set(opt).save().then(() => {
// Restore hidden elements after generating the PDF
document.querySelectorAll('details').forEach(detail => {
detail.style.display = '';
});
});
});
});
};
document.head.appendChild(script);
</script>
</html>
How can I ensure that large PDFs download correctly?