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-
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-
Context used is as follows-
user_dict = {
"full_name": user.full_name,
"email": "joe@gmail.com",
"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": "joe@gmail.com",
"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)
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
}