Jinja and ERPNext print formats demystified
One of the extremely powerful aspects of ERPNext is its ability to provide users a robust print template designer. However, for the uninitiated (we have all been there) Jinja can be quite confusing. Here, we discuss Jinja in the context of Frappe/ERPNext print format usage.
Jinja is a templating engine. It allows users to format data with full unicode support, with Python, CSS and HTML.
In ERPNext, the user can define print templates in HTML and and CSS with Jinja templating for server-side rendering of data with Python.
Breakdown
HTML allows the user control of which kind of elements appear on a printable page
CSS helps define styles for wide usage in the HTML page that will be rendered, including page size and other attributes.
Python runs on the server and executes functions to access, process and write data.
Jinja allows the server to run Python code specified within the HTML at the moment of rendering, such that when the user requests an HTML page (like a print Format for a DocType in ERPNext) the python code is run first, and when it is done running, the HTML is assembled and sent to the client requesting it. This allows the user to obtain context-relevant data.
Notice how this is perfect for print formats. You can specify the specific print format for a Sales Invoice or Purchase Order, and then Jinja with Python can take care of injecting the specific Sales Invoice number, customer, products, prices, totals upon rendering. Same goes for the purchase order or any other doctype in Frappe / ERPNext for that matter. Once you have an HTML rendered on your machine, you can easily use your browser’s print function to create a paper page or even sent it in an e-mail. You can also render everything on the server and send e-mail print formats.
In our particular case, having jinja available in a print format allows us to pull data from the DocType being printed, and beyond.
Jinja Synopsis Squared²
The original synopsis for Jinja is here. A Jinja template is simply a text file with components where Python code is specified. Here is a list of the main components used by Jinja within text or HTML files served from our server. We will be generally using the first three.
- Statements {% … %}
- Expressions {{ … }}
- Comments {# … #}
- Line Statements # … ##
Examples
Now that we have clarified what the elements are, we will run several examples for print formats that will allow us to create virtually any format desired
Page Formats and Sizes
I personally see each page for our print format as a painting canvas where we will draw our artwork. Just like a painter requires his brushes and colors to apply, the painter requires a canvas. The size of the canvas is important in this specific application, and to ensure consistency it is crucial to define a specific size with CSS styles on the Print Format page itself. In our region of the world we use two main sizes for printing:
∙ US Letter (216mm / 8½ inches wide by 279mm / 11 inches tall)
∙ Foolscap (Oficio) (216mm / 8½ inches wide by 330mm / 13 inches tall)
Below are the code blocks that I use for each:
US Letter
/* US Letter PORTRAIT page size BEGINS */
@media all {
.print-format {
/*216mm or 21.6cm or 8.5in (inches)*/
max-width: 21.6cm;
width: 21.6cm;
/*279mm or 27.9cm or 11.0in (inches)*/
min-height: 27.9cm;
height: 27.9cm;
/* Margin from edge of page (padding) 0.0cm*/
padding: 0.0cm;
margin: 0.0cm 0.0cm 0.0cm 0.0cm;
float: left;
background-color: rgba(255,2555,255,1.0);
box-shadow: 0px 0px 9px rgba(0,0,0,0.5);
}
.print-format.landscape {
max-width: 27.9cm;
width: 27.9cm;
}
.print-format.portrait {
max-width: 21.6cm;
width: 21.6cm;
}
}@page {
size: 21.6cm 27.9cm portrait;
}
/* US Letter PORTRAIT page size ENDS */
UK Foolscap (Oficio)
/* UK Foolscap / Oficio PORTRAIT page size BEGINS */
@media all {
.print-format {
/*216mm or 21.6cm or 8.5in (inches)*/
max-width: 21.6cm;
/*330mm or 33.0cm or 13.0in (inches)*/
min-height: 33.0cm;
/* Margin from edge of page (padding) 0.0cm*/
padding: 0.0cm;
margin: 0.0cm 0.0cm 0.0cm 0.0cm;
float: left;
background-color: rgba(255,255,255,1.0);
box-shadow: 0px 0px 9px rgba(0,0,0,0.5);
}
.print-format.landscape {
max-width: 33.0cm;
width: 33.0cm;
}
.print-format.portrait {
max-width: 21.6cm;
width: 21.6cm;
}
}@page {
size: 21.6cm 33.0cm portrait;
}
/* UK Foolscap / Oficio PORTRAIT page size ENDS */
Of course, you need to specify different sizes in the @media all {}
as well as different orientation, even though the .print-format.landscape method is defined. You might have to tweak it a bit to obtain specific results.
The @page {}
will need to have the numbers switched accordingly.
This has to go on each Print Format doctype, because this applies to the predetermined .print-format
CSS class that ERPNext uses when showing print formats.
Python Methods and Jinja on Print Formats
One of the powerful aspects of Jinja and Print Formats in ERPNext goes beyond obtaining DocType variables to print contextual data. You can also apply filters or execute methods (functions) and have their result inserted into your print format.
Using Frappe methods in Print Formats
The easiest way to learn about running functions while rendering Print Formats on ERPNext with Jinja is to use the ones already made available by frappe / ERPNext out of the box.
The best source for this is the Frappe Jinja API documentation, as it has several examples. Below you find a couple of those examples:
frappe.format
frappe.format(value, def, doc)
This method will obtain data and convert it to a user-presentable format based on Frappe fieldtypes
Add this to your Custom Print Template
<div>{{ frappe.format('2020–04–17', {'fieldtype': 'Date'}</div>
Depending on your pre-set region, the result printed on page is:
17–04–2020
Using your own Python Functions in Print Formats
Frequently, it is necessary to define our own functions in Python to execute certain calculations or tasks, or obtain specific information at the exact moment of rendering your custom Print Format view.
It’s necessary to whitelist your function so it can be called successfully from a custom Print Format.
1.- Develop your function in any python file (.py) within your custom app, and add the following decorator above the function:
@frappe.whitelist()
2.- If the file where you defined the function is /apps/your-app/your-app/your_module.py and the function is called my_function() the route that you will use is this: your_app.your_module.my_function
3.- Add the following to hooks.py
jenv = {
“methods”: [
“my_function:your_app.your_module.my_function”
]
}
Please note that the definition to pass your defined Python function to the Jinja environment is defined by the following structure:
[virtual_environment_desired_function_name]:[route_and_name_of_python_function]
4.- On your terminal, execute the following commands:
bench restart
bench migrate
Even though bench migrate is not required, some reports exist about it improving the desired functionality outcome.
5.- On the custom Print Format where you wish to access your function, execute it in the following manner:
{{ virtual_environment_desired_function_name() }}
You may pass arguments to it, and you will receive the returned results (if such is the purpose of your function)
Main Source: [Call your own functions in Frappe](https://discuss.erpnext.com/t/how-to-use-frappe-call-in-print-format-or-the-equivalent/55681/7)
Using External Style definitions
Each print format can have its own CSS defined. However, adding CSS individually to each print format can become tedious very fast. Except for the @media
and @page
decorators, as explained above, styles you use inside can be called from a specifically given external CSS Style definition providing consistency. One of the best tricks we have come across at Si Hay Sistema is the use of a single style sheet for both web and print formats. This really helps with consistency. All you have to do is to include your desired extenal CSS stylesheet, and add the class or id to the elements in your HTML to which you desire to apply your style.
The best way for this is with a custom app. Place a CSS file within the css directory in the public folder for your custom application with your styles. Then, include it in the build.json file, build it and call it from your print format. Step by step:
1.- In the following folder, make sure that there is a css directory, otherwise, create it.
frappe-bench/apps/[your-app]/[your-app]/public/
2.- Create a file with your desired styles [your-app-your-styles].css
For example:
frappe-bench/apps/[your-app]/[your-app]/public/css/your-app-your-styles.css
Add your basic desired style, being careful in differentiating by ID or CLASS each style definition to prevent conflicts with previously named classes. I suggest you use selector names like your-app-your-class or for example
body.your-app-your-class {
background-color: powderblue;
}
h1.your-app-your-class {
color: blue;
}
p.your-app-your-class {
color: red;
}
3.- In the public folder there is a file named build.json. Open the file with a text editor and add the path and filename to the css style file, and a significant non-duplicate name for the public style file:
"css/your-app-styles.css": [
"public/css/your-app-your-styles.css"
]
This will create the your-app-styles.css in the directory open to the web:
http://[your-erpnext-server]/assets/css/your-app-styles.css
4.- Add the route to your frappe-bench/apps/[your-app]/[your-app]/hooks.py file to enable the inclusion in desk.html, the HTML file that rules all the vistas within ERPNext.
app_include_css = "/assets/css/your_app_styles.css"
5.- Execute the following commands within the terminal and push the changes.
bench build
bench restart
6.- Your file will now be available for use in any print format or the web at the following path.
assets/css/your_app_styles.css
7.- Refresh your browser, and if assembling custom print formats, use your styles as you normally would with any HTML page.
Appendix
Adding the name /email of the users who created or last modified the document to your print format. Reference here. These will show the user email address.
Creator
{{ doc.owner }}
Last user to modify
{{ doc.modified_by }}
Full Name:
{{ frappe.get_fullname(doc.owner) }}
Creation Date and Time
{{ doc.creation }}
Last modification Date and Time
{{ doc.modified }}
Document Status.
0 = Draft
1= Submitted
2= Cancelled
{{ doc.docstatus }}