Jinja templates

The Jinja templating language is used in Dokos to create elaborate email templates, notifications and custom print formats by allowing the integration of variables. It allows a mix of HTML language and variables in Python.

To learn more about the Jinja language and a get access to the documentation: Jinja

Variables

When you send an email from a document, a notification is sent or when you print a document, you have access to all fields composing your reference document. You can call them by using the syntax:

{{ doc.field_name }}

doc is a reference to the reference document - some documents can also give access to other variables - and field_name must be replaced by the name of the corresponding field.
The list of all fields composing a document is available when clicking on "Menu > Customize". The name of the field is the value of the column "Name" in the fields table.

Methods

Often, the access to a variable is not sufficient to display the information we are interested in and we need to change either the information format of this value or have access to values entered in other documents.

Dokos gives access to a number of methods that can be called for such purposes in Jinja:

json # json module
dict # internal dict
frappe._ # translator method
frappe._dict # frappe._dict internal method
frappe.flags # global flags

# FORMATTING
frappe.format # frappe.format_value(value, dict(fieldtype='Currency'))
frappe.format_value # frappe.format_value(value, dict(fieldtype='Currency'))
frappe.date_format # default date format
frappe.format_date # returns date as "1st September 2019"

# SESSION
frappe.form_dict # form / request parameters
frappe.request # request object
frappe.response # response object
frappe.session.user # current user
frappe.session.csrf_token # CSRF token of the current session
frappe.user  # current user
frappe.get_fullname # fullname of the current user
frappe.get_gravatar # frappe.utils.get_gravatar_url
frappe.full_name = # fullname of the current user

# ORM
frappe.get_meta # get metadata object
frappe.get_doc
frappe.get_cached_doc
frappe.get_list
frappe.get_all
frappe.get_system_settings

# DB
frappe.db.get_list
frappe.db.get_all
frappe.db.get_value
frappe.db.get_single_value
frappe.db.get_default
frappe.db.escape

# UTILITIES
frappe.msgprint # msgprint
frappe.get_hooks # app hooks
frappe.utils # methods in frappe.utils
frappe.render_template # frappe.render_template,
frappe.get_url # frappe.utils.get_url
frappe.socketio_port # port for socketio
frappe.sanitize_html # Sanitize HTML
style.border_color # '#d1d8dd'
guess_mimetype = mimetypes.guess_type,
html2text = html2text,
dev_server # True if in developer mode

Email template example

In this example, we can use this template in all documents with a "contact_person" field. In our case we plan to use this template from a sales invoice.

The script will first fetch the content of the "Contact" document corresponding to the person entered in the field "contact_person" and then register it in a variable called "contact".

We then have access to the values of all fields in this contact document and we can display the first name of our contact in our email.

{% set contact = frappe.get_doc("Contact", contact_person) %}
Hello {{ contact.first_name }},

Please find your invoice attached.

The total amount is {{ grand_total }}

Best regards,

XXX

WARNING

You can notice that in email template, we don't use the doc. prefix before calling the fields in our reference document.
This is a historical API difference between email templates and other documents that may be corrected in the future.

Notification example

We wish to send an automatic notification whenever someone makes an online room booking.
Unlike email templates, email notifications can not be edited in the email editore before sending since they are automatically sent. Therefore we need to make sure they are correctly formatted.
We will use HTML in our template to guarantee the layout of our email.

This example is an email sent after the creation of the sales order corresponding to the item booking confirmation - when the use clicks on "Order" in his·her shopping cart.

<!--We display the contact display field or nothing if this field is not filled-->
<p>Hi {{ doc.contact_display or "" }},</p>

<p>Thanks for your order, it has been well taken into account.</p>
<p>Please find the detail below :</p>

<!--We display the details of the booking in a table. You can use the Bootstrap library for formatting-->
<table class="table table-bordered">
	<thead>
		<tr>
            <th>Description</th>
            <th>Time and Date</th>
		</tr>
	</thead>
	<tbody>
        <!--We loop on the different lines of the sales order to print the details-->
        {% for item in doc.items %}
        <!--We use the frappe.db.get_value method to fetch the start and end time of the item booking corresponding to each sold items-->
        {% set booking_start = frappe.db.get_value("Item Booking", item.item_booking, "starts_on") %}
        {% set booking_end = frappe.db.get_value("Item Booking", item.item_booking, "ends_on") %}
        <tr>
            <td>{{ item.description or "" }}</td>
            <td>
                <!--We use the method frappe.utils.formatdate to format the booking date-->
                <span>{{ frappe.utils.formatdate(booking_start) }} </span>
                <!--We use the method frappe.utils.get_time to fetch the start time and end time of the booking and we format it with the standard strftime Python method-->
                <span>{{ frappe.utils.get_time(booking_start).strftime("%H:%M") }} - {{ frappe.utils.get_time(booking_end).strftime("%H:%M") }}</span>
            </td>
        </tr>
        <!--We tell Jinja that our loop stops here-->
        {% endfor %}
	</tbody>
</table>


<br>
<p>If you have any question or need information to prepare your arrival, please don't hesitate to get back to me.</p>

<p>Best regards,</p>

<p>XXXXX</p>

It is possible to create your own custom print formats in Jinja in Dokos. Here is an example of entirely customized sales invoice:

{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
<div class="row">
	<div class="col-xs-6 col-xs-push-6">
		<div class="row">
			{% set salutation = frappe.db.get_value("Customer", doc.customer, "salutation") %}
			<div class="col-xs-12 text-left "><strong>{{ salutation or '' }} {{ doc.customer_name }}</strong></div>
		</div>
		{% if doc.customer_address %}
            {% set client_address = frappe.get_doc("Address", doc.customer_address) %}
            <div class="row">
                <div class="col-xs-12 text-left">
                    {{ client_address.address_line1 }}<br> {% if client_address.address_line2 %}{{ client_address.address_line2 }}<br>{% endif %} {{ client_address.pincode }} {{ client_address.city }}<br> {{ client_address.country }}
                </div>
            </div>
		{% endif %}
	</div>
</div>
{% if doc.print_heading_template %}
    {{ frappe.render_template(doc.print_heading_template, {"doc":doc}) }}
{% else %}
    <div class="print-heading">
        <h2>{{ doc.select_print_heading or (doc.print_heading if doc.print_heading != None else _(doc.doctype)) }}</h2>
    </div>
{% endif %}
{%- if doc.meta.is_submittable and doc.docstatus==0 and (print_settings==None or print_settings.add_draft_heading) -%}
    <div class="text-center" document-status="draft">
        <h4 style="margin: 0px;">{{ _("DRAFT") }}</h4>
    </div>
{%- endif -%}
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
    <div class="text-center" document-status="cancelled">
        <h4 style="margin: 0px;">{{ _("CANCELLED") }}</h4>
    </div>
{%- endif -%}
<div>
	<div id="footer-html" class="visible-pdf">
		{% if not no_letterhead and footer %}
		<div class="letter-head-footer visible-pdf">
			{{ footer }}
		</div>
		{% endif %}
		<p class="text-center small page-number visible-pdf">
			{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
		</p>
	</div>

	<div class="row">
		<div class="col-xs-6">
            {% set account_resp = frappe.db.get_value("Customer", doc.customer, "account_responsible") %}
            {% set user = frappe.get_doc("User", account_resp) %}
			<div class="row">
				<div class="col-xs-4 text-left"> <label>{{ _("Contact") }}</label> </div>
				<div class="col-xs-8 text-left ">
					<strong>{{ user.first_name or ''}} {{ user.last_name or '' }}</strong></div>
			</div>
			<div class="row">
				<div class="col-xs-4 text-left"> <label>{{ _("Email") }}</label> </div>
				<div class="col-xs-8 text-left ">
					{{ user.email or ''}}</div>
			</div>
			<div class="row">
				<div class="col-xs-4 text-left"> <label>{{ _("Phone") }}</label> </div>
				<div class="col-xs-8 text-left ">
					{{ user.phone or ''}}</div>
			</div>
		</div>

		<div class="col-xs-6">
			<div class="row">
				<div class="col-xs-5 text-left"><label>{{ _("Invoice Number") }}</label></div>
				<div class="col-xs-7 text-left ">{{ doc.sub_heading if doc.sub_heading != None else doc.name }}</div>
			</div>
			<div class="row">
				<div class="col-xs-5 text-left"> <label>{{ _("Date") }}</label> </div>
				<div class="col-xs-7 text-left ">{{ doc.get_formatted("posting_date") or ''}}</div>
			</div>
			<div class="row">
				<div class="col-xs-5 text-left"> <label>{{ _("Due Date") }}</label> </div>
				<div class="col-xs-7 text-left">
					{{ doc.get_formatted("due_date") or ''}}</div>
			</div>
		</div>
	</div>

	<table class="table table-condensed table-bordered">
		<tr>
			<th>{{ _("Description") }}</th>
			<th>{{ _("Quantity") }}</th>
			<th class="text-right">{{ _("Rate") }}</th>
			<th class="text-right">{{ _("Amount") }}</th>
		</tr>
		{%- for row in doc.items -%}
		<tr>
			<td style="width: 55%;">{{ row.item_name or '' }}</td>
			<td style="width: 15%;">{{ row.get_formatted("qty") }} {{ row.stock_uom }}</td>
			<td style="width: 15%; text-align: right;">

				{{ row.get_formatted("rate", doc) or ''}}
				<td style="width: 15%; text-align: right;">{{ row.get_formatted("amount", doc) or ''}}</td>
		</tr>
		{%- endfor -%}
		</tbody>
	</table>

	<div class="row">
		<div class="col-xs-6 text-right"></div>
		<div class="col-xs-4 text-right"><label>{{ _("Total") }}</label></div>
		<div class="col-xs-2 text-right">{{ doc.get_formatted("total") or '' }}</div>
	</div>
	<br>
    {%- if doc.discount_amount -%}
        <div class="row">
            <div class="col-xs-6 text-right"></div>
            <div class="col-xs-4 text-right"><label>{{ _("Discount") }}</label></div>
            <div class="col-xs-2 text-right">- {{ doc.get_formatted("discount_amount") }}</div>
        </div>
        <div class="row">
            <div class="col-xs-6 text-right"></div>
            <div class="col-xs-4 text-right"><label>{{ _("Net Total") }}</label></div>
            <div class="col-xs-2 text-right">{{ doc.get_formatted("net_total") or '' }}</div>
        </div>
        <br>
    {%- endif -%}
    {%- for row in doc.taxes -%}
        {%- if row.tax_amount_after_discount_amount -%}
            <div class="row">
                <div class="col-xs-6 text-right"></div>
                <div class="col-xs-4 text-right"><label>{{ row.description }}</label></div>
                <div class="col-xs-2 text-right">{{ row.get_formatted("tax_amount_after_discount_amount", doc) }}</div>
            </div>
	    {%- endif -%}
    {%- endfor -%}

	{%- if doc.taxes or doc.discount_amount -%}
        <br>
        <div class="row">
            <div class="col-xs-6 text-right"></div>
            <div class="col-xs-4 text-right"><label>{{ _("Grand Total") }}</label></div>
            <div class="col-xs-2 text-right"><strong>{{ doc.get_formatted("grand_total") }}</strong></div>
        </div>
	{% endif %}
	{% set customer = frappe.get_doc("Customer", doc.customer) %}
	{% if customer.tax_id %}
        <br>
        <div class="row">
            <div class="col-xs-2 text-left"><label>{{ _("Customer VAT N°") }}</label></div>
            <div class="col-xs-10">{{ customer.tax_id }}</div>
        </div>
        </div>
	{% endif %}
	<br>
    {% if doc.remarks -%}
        <div class="row">
            <div class="col-xs-2 text-left"><label>{{ _("Remarks") }}</label></div>
            <div class="col-xs-10">{{ doc.remarks }}</div>
        </div>
	{%- endif %}

	<br>
	<div class="row">
		<div class="col-xs-12 text-left"><label>{{ _("IBAN") }}</label></div>
	</div>
	<div class="row">
		<div class="col-xs-12 text-left">
            <p>ABCXXXXXXXXXXXXXX</p>
        </div>
	</div>

	<p style="page-break-after:always;"></p>

	<div class="row">
		<div class="col-xs-12 text-left">{{ doc.terms or '' }}</div>
	</div>
</div>