* changes exception and raise statements to python 3 style * changes except statement to python 3 style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * adds six.reraise to fix python 3 style raise statements with traceback * fixes indentation
161 lines
4.2 KiB
Python
161 lines
4.2 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# MIT License. See license.txt
|
|
from __future__ import unicode_literals
|
|
|
|
import pdfkit, os, frappe
|
|
from frappe.utils import scrub_urls
|
|
from frappe import _
|
|
from bs4 import BeautifulSoup
|
|
from pyPdf import PdfFileWriter, PdfFileReader
|
|
|
|
def get_pdf(html, options=None, output = None):
|
|
html = scrub_urls(html)
|
|
html, options = prepare_options(html, options)
|
|
fname = os.path.join("/tmp", "frappe-pdf-{0}.pdf".format(frappe.generate_hash()))
|
|
|
|
try:
|
|
pdfkit.from_string(html, fname, options=options or {})
|
|
if output:
|
|
append_pdf(PdfFileReader(file(fname,"rb")),output)
|
|
else:
|
|
with open(fname, "rb") as fileobj:
|
|
filedata = fileobj.read()
|
|
|
|
except IOError as e:
|
|
if ("ContentNotFoundError" in e.message
|
|
or "ContentOperationNotPermittedError" in e.message
|
|
or "UnknownContentError" in e.message
|
|
or "RemoteHostClosedError" in e.message):
|
|
|
|
# allow pdfs with missing images if file got created
|
|
if os.path.exists(fname):
|
|
with open(fname, "rb") as fileobj:
|
|
filedata = fileobj.read()
|
|
|
|
else:
|
|
frappe.throw(_("PDF generation failed because of broken image links"))
|
|
else:
|
|
raise
|
|
|
|
finally:
|
|
cleanup(fname, options)
|
|
|
|
if output:
|
|
return output
|
|
|
|
return filedata
|
|
|
|
def append_pdf(input,output):
|
|
# Merging multiple pdf files
|
|
[output.addPage(input.getPage(page_num)) for page_num in range(input.numPages)]
|
|
|
|
def prepare_options(html, options):
|
|
if not options:
|
|
options = {}
|
|
|
|
options.update({
|
|
'print-media-type': None,
|
|
'background': None,
|
|
'images': None,
|
|
'quiet': None,
|
|
# 'no-outline': None,
|
|
'encoding': "UTF-8",
|
|
#'load-error-handling': 'ignore',
|
|
|
|
# defaults
|
|
'margin-right': '15mm',
|
|
'margin-left': '15mm',
|
|
})
|
|
|
|
html, html_options = read_options_from_html(html)
|
|
options.update(html_options or {})
|
|
|
|
# cookies
|
|
if frappe.session and frappe.session.sid:
|
|
options['cookie'] = [('sid', '{0}'.format(frappe.session.sid))]
|
|
|
|
# page size
|
|
if not options.get("page-size"):
|
|
options['page-size'] = frappe.db.get_single_value("Print Settings", "pdf_page_size") or "A4"
|
|
|
|
return html, options
|
|
|
|
def read_options_from_html(html):
|
|
options = {}
|
|
soup = BeautifulSoup(html, "html5lib")
|
|
|
|
# extract pdfkit options from html
|
|
for html_id in ("margin-top", "margin-bottom", "margin-left", "margin-right", "page-size"):
|
|
try:
|
|
tag = soup.find(id=html_id)
|
|
if tag and tag.contents:
|
|
options[html_id] = tag.contents
|
|
except:
|
|
pass
|
|
|
|
options.update(prepare_header_footer(soup))
|
|
|
|
toggle_visible_pdf(soup)
|
|
|
|
return soup.prettify(), options
|
|
|
|
def prepare_header_footer(soup):
|
|
options = {}
|
|
|
|
head = soup.find("head").contents
|
|
styles = soup.find_all("style")
|
|
|
|
bootstrap = frappe.read_file(os.path.join(frappe.local.sites_path, "assets/frappe/css/bootstrap.css"))
|
|
fontawesome = frappe.read_file(os.path.join(frappe.local.sites_path, "assets/frappe/css/font-awesome.css"))
|
|
|
|
# extract header and footer
|
|
for html_id in ("header-html", "footer-html"):
|
|
content = soup.find(id=html_id)
|
|
if content:
|
|
# there could be multiple instances of header-html/footer-html
|
|
for tag in soup.find_all(id=html_id):
|
|
tag.extract()
|
|
|
|
toggle_visible_pdf(content)
|
|
html = frappe.render_template("templates/print_formats/pdf_header_footer.html", {
|
|
"head": head,
|
|
"styles": styles,
|
|
"content": content,
|
|
"html_id": html_id,
|
|
"bootstrap": bootstrap,
|
|
"fontawesome": fontawesome
|
|
})
|
|
|
|
# create temp file
|
|
fname = os.path.join("/tmp", "frappe-pdf-{0}.html".format(frappe.generate_hash()))
|
|
with open(fname, "w") as f:
|
|
f.write(html.encode("utf-8"))
|
|
|
|
# {"header-html": "/tmp/frappe-pdf-random.html"}
|
|
options[html_id] = fname
|
|
|
|
else:
|
|
if html_id == "header-html":
|
|
options["margin-top"] = "15mm"
|
|
|
|
elif html_id == "footer-html":
|
|
options["margin-bottom"] = "15mm"
|
|
|
|
return options
|
|
|
|
def cleanup(fname, options):
|
|
if os.path.exists(fname):
|
|
os.remove(fname)
|
|
|
|
for key in ("header-html", "footer-html"):
|
|
if options.get(key) and os.path.exists(options[key]):
|
|
os.remove(options[key])
|
|
|
|
def toggle_visible_pdf(soup):
|
|
for tag in soup.find_all(attrs={"class": "visible-pdf"}):
|
|
# remove visible-pdf class to unhide
|
|
tag.attrs['class'].remove('visible-pdf')
|
|
|
|
for tag in soup.find_all(attrs={"class": "hidden-pdf"}):
|
|
# remove tag from html
|
|
tag.extract()
|