Skip to content

Instantly share code, notes, and snippets.

@firdavsDev
Last active June 24, 2024 13:38
Show Gist options
  • Save firdavsDev/873c71a44546c8630060f232ece3f367 to your computer and use it in GitHub Desktop.
Save firdavsDev/873c71a44546c8630060f232ece3f367 to your computer and use it in GitHub Desktop.
Generate PDF + QrCode .pdf file in Django projects. (hard codeing)
from pdf_generator import PDFGenerator
instance = Model.objacts.get(id=1) #example
context = {
"data": instance
} #use as {{ data }} in html_file_path
#Create object
pdf = PDFGenerator(
html_file_path="pdf.html",
css_file_path="css/bootstrap.min.css",
img_file_paths=[
"assets/img/logo.png", # use as {{ logo }} in html_file_path. {{ file_name}}
"assets/img/gerb.png"
# .....
],
context=context,
options={
"page-size": "A4",
"orientation": "portrait",
},
)
#Generate pdf file
pdf_file_path = pdf.generate_pdf(
filename=f"pdf_file_name",
output_path="output_folder",
unique_code_len=6, #randomly prefix for file name
)
# You can also save to FileField
# instance.pdf_file = pdf_file_path
# instance.save()
import base64
import os
import platform
import subprocess
from asyncio.log import logger
from io import BytesIO
from typing import List
from uuid import uuid4
import pdfkit # pip install pdfkit
import qrcode # pip install qrcode
from django.conf import settings
from django.template.loader import get_template
from PyPDF2 import PdfMerger, PdfReader #pip install PyPDF2
class PDFGenerator:
"""
HTML (string) to PDF file Generator class in Django
* sudo apt-get install -y wkhtmltopdf
Note:
- You can use {{ qr_code_img }} in html file.
Args:
* html_file_path (str): html file path in templates folder
* css_file_path (str): css file path in STATIC_ROOT folder | default None
* img_file_paths (List(str)): img file path in STATIC_ROOT folder | default None | in template html file you can use it as {{file_name}}
* context (dict): context data | default empty dict | QuerySet |
"""
def __init__(
self,
html_file_path: str,
css_file_path: str = None,
img_file_paths: List[str] = None,
context: dict = None,
options: dict = None,
):
if context is None:
context = {}
if options is None:
options = {"page-size": "A4", "encoding": "UTF-8", "quiet": True}
self.html_path = html_file_path
self.css_path = css_file_path
self.img_paths = img_file_paths
self.data = context
self._options = options
def _get_pdfkit_config(self):
"""
wkhtmltopdf lives and functions differently depending on Windows or Linux. We
need to support both since we develop on windows or Linux but deploy on Server.
Returns:
A pdfkit configuration
"""
if platform.system() == "Windows":
return pdfkit.configuration(
wkhtmltopdf=os.environ.get("WKHTMLTOPDF_BINARY", "C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe")
)
WKHTMLTOPDF_CMD = (
subprocess.Popen(["which", os.environ.get("WKHTMLTOPDF_BINARY", "wkhtmltopdf")], stdout=subprocess.PIPE)
.communicate()[0]
.strip()
)
return pdfkit.configuration(wkhtmltopdf=WKHTMLTOPDF_CMD)
def output_file_path_generator(self, filename: str, unique_code: int, output_path: str) -> str:
"""Generate path for pdf file save
Args:
* filename (str): file_name
* unique_code (int): uuid length
* output_path (str): save folder path
Returns:
output_for_pdfkit (str): path for pdfkit save pdf file
output_for_model (str): path for save Model field
"""
uuid_code = str(uuid4().hex)[:unique_code]
if output_path is not None:
# cheking output_path folder exists or not
if not os.path.exists(f"{settings.MEDIA_ROOT}{output_path}"):
os.makedirs(f"{settings.MEDIA_ROOT}{output_path}")
output_for_pdfkit = f"{settings.MEDIA_ROOT}/{output_path}/{filename}_{uuid_code}.pdf"
else:
output_for_pdfkit = f"{settings.MEDIA_ROOT}/{filename}_{uuid_code}.pdf"
output_for_model = f"{output_path}/{filename}_{uuid_code}.pdf"
return output_for_pdfkit, output_for_model
def convert_image_file_to_base64_data(self, file_path: str) -> str:
"""Get image file as base64 data
Args:
file_path (str): image file path
Returns:
str: base64 data
"""
with open(file_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode("utf-8")
def generate_qr_code_as_base64_data(self, pdf_file_url: str) -> str:
"""Generate pdf url as qr code
Args:
pdf_file_url (str): pdf_file_path in media url
Returns:
str: base64 data
"""
pdf_file_url = "https://" + os.environ["SERVER_DOMAIN"] + "/media/" + pdf_file_url
qr_code_IO = BytesIO()
qr_code_img = qrcode.make(pdf_file_url)
qr_code_img.save(qr_code_IO, "PNG")
return base64.b64encode(qr_code_IO.getvalue()).decode("utf-8")
def generate_pdf(self, filename: str, output_path: str = None, unique_code_len: int = 4) -> str:
"""Generate pdf file & save
Args:
* filename (str): pdf file name. Saved as <filename>_<uuid>.pdf
* unique_code_len: uuid4 code length. Default 4
* output_path (str): save pdf file path folder in settings.MEDIA_ROOT. Default save in settings.MEDIA_ROOT/<filename>_<uuid>.pdf
Raises:
e: Description of Error & Logging
Returns:
str: file saved path for save in model
"""
try:
if self.css_path is not None:
self.css_path = f"{settings.STATIC_ROOT}/{self.css_path}"
if self.img_paths is not None and len(self.img_paths) > 0:
for img_path in self.img_paths:
img_file_path = f"{settings.STATIC_ROOT}/{img_path}"
img_file_name = img_path[img_path.rfind("/") :].split(".")[0].replace("/", "")
self.data[img_file_name] = self.convert_image_file_to_base64_data(img_file_path)
# calling output_file_path_generator
output_for_pdfkit, output_for_model = self.output_file_path_generator(
filename=filename, unique_code=unique_code_len, output_path=output_path
)
# set qr code img
self.data["qr_code_img"] = self.generate_qr_code_as_base64_data(output_for_model)
# render data to html
template = get_template(self.html_path)
html = template.render(self.data)
# calling pdfkit package's from_string function
if pdfkit.from_string(
input=html,
output_path=output_for_pdfkit,
# configuration=self._get_pdfkit_config(),
options=self._options,
css=self.css_path,
):
return output_for_model
return None
except Exception as e:
logger.error(e)
raise e
pdfkit==1.0.0 #pip install pdfkit
Pillow==9.2.0 #pip install Pillow
PyPDF2
qrcode
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment