[Python] Generate html to pdf using xhtml2pdf in django project

Discussão em 'Python' iniciado por Stack, Fevereiro 10, 2025.

  1. Stack

    Stack Membro Participativo

    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?

    Continue reading...

Compartilhe esta Página