Search code examples
htmldjangopython-3.xxhtml2pdf

Why some of the column in html table collapsing when rendered to pdf using xhtml2pdf in django?


I am trying to generate pdf in Django using xhtml2pf. The HTML template consists mostly of HTML table. Django PhoneField and EmailField are getting collapsed while pdf is rendered.

Below is the image of pdf generated when passing Django PhoneField and EmailField in context-

enter image description here

The context for html template is as follows-

user_dict = {
            "full_name": user.full_name,
            "email": str(user.email),
            "phone_number": str(user.phone_number),
            "emergency_number": "very very long header",
            "id_proof_number": user.id_proof_number

        }

When I am using the plain string in place of django field it is rendered correctly as below- enter image description here

Context used is as follows-

user_dict = {
            "full_name": user.full_name,
            "email": "[email protected]",
            "phone_number": "+14151234567",
            "emergency_number": "very very long header",
            "id_proof_number": user.id_proof_number

        }

Django Model-

class User(models.Model):
    trip = models.ForeignKey(
    Trip, on_delete=models.CASCADE, related_name="users")
    id_proof_number = models.CharField(
    _("Id Proof"), max_length=100, null=True, blank=True)
    full_name = models.CharField(_("Full name"), max_length=255)
    email = models.EmailField(_("email"), max_length=70)
    phone_number = PhoneNumberField(_("Phone Number"))
    emergency_number = PhoneNumberField(_("Emergency Number"),  default=None, null=True)

HTML Template-

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>User Details</title>
    <style type="text/css">
        @page {
            size: A4;
            margin: 1cm;
        }
        .card-header{
            text-align: center;
        }
        .table{
            border: 0.5px solid #ababab;
            text-align: center;
            width: 100%;
            max-width: 100%;
            margin-bottom: 5px;
            table-layout:fixed;
        }
        .table tr th td{
            white-space: nowrap;
        }
        .table th {
            padding: 5px;
            vertical-align: top;
            background-color: #e8e8e8;
        }
        .table td {
            padding: 5px;
            vertical-align: top;
            -pdf-keep-with-next: false;
        }
        .five {
              width: 5%;
            }
        .twenty {
              width: 20%;
            }
         .fifteen {
              width: 15%;
            }


    </style>
</head>
<body>
<div class="container">
    <div class="card">
        <div class="card-header">
            <h3>Header</h3>
        </div>
    </div>
    <table class = "table">
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Phone number</th>
                <th>Email</th>
                <th>ID</th>
                <th>emergency number</th>
            </tr>
        </thead>
        <tbody>
            {% for trip in trips %}
                {% for member in trip.users %}
                    <tr>
                        {% if forloop.counter == 1 %}
                            <td rowspan="{{trip.users|length}}">{{trip.id}}</td>
                        {% endif %}
                        <td>{{member.full_name}}</td>
                        <td>{{member.phone_number}}</td>
                        <td>{{member.email}}</td>
                        <td>{{member.id_proof_number}}</td>
                        <td>very very long header</td>
                    </tr>
                {% endfor %}
            {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

Django view for rendering pdf-

def trip_members_pdf(request, id):

    trip_qs = Trip.objects.filter(parent_id=id)
    trips = []
    for trip in trip_qs:
        trip_dict = {
            "id": trip.id,
            "users": []
        }
        users = trip.users.all()
        for user in users:
            user_dict = {
                "full_name": user.full_name,
                "email": "[email protected]",
                "phone_number": "+14151234567",
                "emergency_number": user.emergency_number,
                "id_proof_number": user.id_proof_number

            }
            trip_dict["users"].append(user_dict)
        trips.append(trip_dict)
    return PDFRender.render('docs/trip_member_pdf.html', { 'tour': tour, 'trips': trips})

PDF Renderer-

from io import BytesIO
from django.http import HttpResponse
from django.template.loader import get_template
import xhtml2pdf.pisa as pisa


class PDFRender:

    @staticmethod
    def render(path: str, params: dict = {}):
        template = get_template(path)
        html = template.render(params)
        response = BytesIO()
        pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), response)
        if not pdf.err:
            return HttpResponse(response.getvalue(), content_type='application/pdf')
        else:
            return HttpResponse("Error Rendering PDF", status=400)

Solution

  • I found the reason for this behaviour. If any of the value in the column is None then renderer is collapsing the column. I passed a blank space in case of None and things were working fine. I changed context as follows-

    user_dict = {
            "full_name": user.full_name,
            "email": user.email or " ",
            "phone_number": user.phone_number or " ",
            "emergency_number": "very very long header",
            "id_proof_number": user.id_proof_number
    
        }