Execute the following steps to complete the recipe:
- We will create the template with which the document will be rendered, as follows:
{# templates/cv/cv_pdf.html #}
{% load static %}
{% get_media_prefix as MEDIA_URL %}
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>{{ cv }}</title>
<style>
@page {
size: a4 portrait;
margin: 2.5cm 1.5cm;
@frame footer_frame {
-pdf-frame-content: footer_content;
bottom: 0;
margin-left: 0;
margin-right: 0;
height: 1cm;
}
}
#footer_content {
color: #666;
font-size: 10pt;
text-align: center;
}
h1 { text-align: center; }
th, td { vertical-align: top; }
/* ... additional styles here ... */
</style>
</head>
<body>
<div>
<h1>Curriculum Vitae for {{ cv }}</h1>
<table><tr>
<td>
<h2>Contact Information</h2>
<p><b>Email:</b> {{ cv.email }}</p>
</td>
<td align="right">
<img src="{% static 'site/img/smiley.jpg' %}"
width="100" height="100" />
</td>
</tr></table>
<h2>Experience</h2>
{% for experience in cv.experience_set.all %}
<h3>{{ experience.position }} at {{ experience.company }}</h3>
<p><b>
{{ experience.from_date|date:"F Y" }} -
{{ experience.till_date|date:"F Y"|default:"present" }}
</b></p>
<p>
<b>Skills gained</b><br>
{{ experience.skills|linebreaksbr }}
</p>
{% endfor %}
</div>
<pdf:nextpage>
<div>
This is an empty page to make a paper plane.
</div>
<div id="footer_content">
Document generated at {% now "Y-m-d" %} |
Page <pdf:pagenumber> of <pdf:pagecount>
| Smiley obtained from clipartextras.com
</div>
</body>
</html>
- Let's create the download_cv_pdf() view. This view renders the HTML template and then passes the rendered string to the pisa PDF creator:
# cv/views.py import os
from django.conf import settings
from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import get_object_or_404, render_to_response
from django.template.loader import render_to_string
from django.utils.text import slugify
from xhtml2pdf import pisa
from .models import CurriculumVitae
def link_callback(uri, rel):
# convert URIs to absolute system paths
if uri.startswith(settings.MEDIA_URL):
path = os.path.join(settings.MEDIA_ROOT,
uri.replace(settings.MEDIA_URL, ""))
elif uri.startswith(settings.STATIC_URL):
path = os.path.join(settings.STATIC_ROOT,
uri.replace(settings.STATIC_URL, ""))
else:
# handle absolute uri (ie: http://my.tld/a.png)
return uri
# make sure that file exists
if not os.path.isfile(path):
raise Exception(
"Media URI must start with "
f"'{settings.STATIC_URL}' or '{settings.MEDIA_URL}'")
return path
def download_cv_pdf(request, cv_id):
cv = get_object_or_404(CurriculumVitae, pk=cv_id)
response = HttpResponse(content_type="application/pdf")
response["Content-Disposition"] =
f"attachment; filename='{slugify(cv, True)}.pdf'"
html = render_to_string("cv/cv_pdf.html", {"cv": cv})
status = pisa.CreatePDF(html,
dest=response,
link_callback=link_callback)
if status.err:
response = HttpResponseServerError(
"The PDF could not be generated.")
return response
- Create a rule in urls.py for the view that will download a PDF document of a résumé by the ID of the CurriculumVitae model instance, as follows:
# cv/urls.py from django.urls import path
from .views import download_cv_pdf
urlpatterns = [
path('<int:pk>/pdf/', download_cv_pdf, name="cv-pdf"),
]
- Add our cv URLs to the project:
# project/urls.py
from django.urls import include, path
urlpatterns = [
# ...
path('cv/', include('cv.urls')),
]