- ERPNext Jinja Templates - Implementation
- This skill helps you determine HOW to implement Jinja templates. For exact syntax, see
- erpnext-syntax-jinja
- .
- Version
- v14/v15/v16 compatible (with V16-specific features noted) Main Decision: What Are You Trying to Create? ┌─────────────────────────────────────────────────────────────────────────┐ │ WHAT DO YOU WANT TO CREATE? │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ► Printable document (invoice, PO, report)? │ │ ├── Standard DocType → Print Format (Jinja) │ │ └── Query/Script Report → Report Print Format (JavaScript!) │ │ │ │ ► Automated email with dynamic content? │ │ └── Email Template (Jinja) │ │ │ │ ► Customer-facing web page? │ │ └── Portal Page (www/.html + .py) │ │ │ │ ► Reusable template functions/filters? │ │ └── Custom jenv methods in hooks.py │ │ │ │ ► Notification content? │ │ └── Notification Template (uses Jinja syntax) │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ⚠️ CRITICAL: Report Print Formats use JAVASCRIPT templating, NOT Jinja! - Jinja: {{ variable }} - JS Report: {%= variable %} Decision Tree: Print Format Type WHAT ARE YOU PRINTING? │ ├─► Standard DocType (Invoice, PO, Quotation)? │ │ │ │ WHERE TO CREATE? │ ├─► Quick/simple format → Print Format Builder (Setup > Print) │ │ - Drag-drop interface │ │ - Limited customization │ │ │ └─► Complex layout needed → Custom HTML Print Format │ - Full Jinja control │ - Custom CSS styling │ - Dynamic logic │ ├─► Query Report or Script Report? │ └─► Report Print Format (JAVASCRIPT template!) │ ⚠️ NOT Jinja! Uses {%= %} and {% %} │ └─► Letter or standalone document? └─► Letter Head + Print Format combination Decision Tree: Where to Store Template IS THIS A ONE-OFF OR REUSABLE? │ ├─► Site-specific, managed via UI? │ └─► Create via Setup > Print Format / Email Template │ - Stored in database │ - Easy to edit without code │ ├─► Part of your custom app? │ │ │ │ WHAT TYPE? │ ├─► Print Format → myapp/fixtures or db records │ │ │ ├─► Portal Page → myapp/www/pagename/ │ │ - index.html (template) │ │ - index.py (context) │ │ │ └─► Custom methods/filters → myapp/jinja/ │ - Registered via hooks.py jenv │ └─► Template for multiple sites? └─► Include in app, export as fixture Implementation Workflow: Print Format Step 1: Create via UI (Recommended Start) Setup > Printing > Print Format > New - DocType: Sales Invoice - Module: Accounts - Standard: No (Custom) - Print Format Type: Jinja Step 2: Basic Template Structure
{# Document header #}
{{ doc.select_print_heading or _("Invoice") }}
{{ doc.name }}
{{ _("Date") }}: {{ doc.get_formatted("posting_date") }}
{# Items table #}
| {{ _("Item") }} | {{ _("Qty") }} | {{ _("Amount") }} |
|---|---|---|
| {{ row.item_name }} | {{ row.qty }} | {{ row.get_formatted("amount", doc) }} |
{# Totals #}
{{ _("Grand Total") }}: {{ doc.get_formatted("grand_total") }}
Step 3: Test and Refine 1. Open a document (e.g., Sales Invoice) 2. Menu > Print > Select your format 3. Check layout, adjust CSS as needed 4. Test PDF generation Implementation Workflow: Email Template Step 1: Create via UI Setup > Email > Email Template > New - Name: Payment Reminder - Subject: Invoice {{ doc.name }} - Payment Due - DocType: Sales Invoice Step 2: Template Content
{{ _("Dear") }} {{ doc.customer_name }},
{{ _("This is a reminder that invoice") }} {{ doc.name }} {{ _("for") }} {{ doc.get_formatted("grand_total") }} {{ _("is due.") }}
| {{ _("Due Date") }} | {{ frappe.format_date(doc.due_date) }} |
| {{ _("Outstanding") }} | {{ doc.get_formatted("outstanding_amount") }} |
{% if doc.items %}
{{ _("Items") }}:
-
{% for item in doc.items %}
- {{ item.item_name }} ({{ item.qty }}) {% endfor %}
{% endif %}
{{ _("Best regards") }},
{{ frappe.db.get_value("Company", doc.company, "company_name") }}
Step 3: Use in Notifications or Code
In Server Script or Controller
frappe . sendmail ( recipients = [ doc . email ] , subject = frappe . render_template ( frappe . db . get_value ( "Email Template" , "Payment Reminder" , "subject" ) , { "doc" : doc } ) , message = frappe . get_template ( "Payment Reminder" ) . render ( { "doc" : doc } ) ) Implementation Workflow: Portal Page Step 1: Create Directory Structure myapp/ └── www/ └── projects/ ├── index.html # Jinja template └── index.py # Python context Step 2: Create Template (index.html) {% extends "templates/web.html" %} {% block title %}{{ _("Projects") }}{% endblock %}
{{ title }}
{% if frappe.session.user != 'Guest' %}{{ _("Welcome") }}, {{ frappe.get_fullname() }}
{% endif %}{{ _("No projects found.") }}
{% endfor %}{% endblock %} Step 3: Create Context (index.py) import frappe def get_context ( context ) : context . title = "Projects" context . no_cache = True
Dynamic content
Fetch data
context . projects = frappe . get_all ( "Project" , filters = { "is_public" : 1 } , fields = [ "name" , "title" , "description" ] , order_by = "creation desc" ) return context Step 4: Test Visit: https://yoursite.com/projects Implementation Workflow: Custom Jinja Methods Step 1: Register in hooks.py
myapp/hooks.py
jenv
{ "methods" : [ "myapp.jinja.methods" ] , "filters" : [ "myapp.jinja.filters" ] } Step 2: Create Methods Module
myapp/jinja/methods.py
import
frappe
def
get_company_logo
(
company
)
:
"""Returns company logo URL - usable in any template"""
return
frappe
.
db
.
get_value
(
"Company"
,
company
,
"company_logo"
)
or
""
def
get_address_display
(
address_name
)
:
"""Format address for display"""
if
not
address_name
:
return
""
return
frappe
.
get_doc
(
"Address"
,
address_name
)
.
get_display
(
)
def
get_outstanding_amount
(
customer
)
:
"""Get total outstanding for customer"""
result
=
frappe
.
db
.
sql
(
"""
SELECT COALESCE(SUM(outstanding_amount), 0)
FROM tabSales Invoice
WHERE customer = %s AND docstatus = 1
"""
,
customer
)
return
result
[
0
]
[
0
]
if
result
else
0
Step 3: Create Filters Module
myapp/jinja/filters.py
def
format_phone
(
value
)
:
"""Format phone number: 1234567890 → (123) 456-7890"""
if
not
value
:
return
""
digits
=
''
.
join
(
c
for
c
in
str
(
value
)
if
c
.
isdigit
(
)
)
if
len
(
digits
)
==
10
:
return
f"(
{
digits
[
:
3]
}
)
{
digits
[
3
:
6]
}
-
{
digits
[
6
:
]
}
"
return
value
def
currency_words
(
amount
,
currency
=
"EUR"
)
:
"""Convert number to words (simplified)"""
return
f"
{
currency
}
{
amount
:
,.2f
}
"
Step 4: Use in Templates
{# Methods - called as functions #}
{{ get_address_display(doc.customer_address) }}
Outstanding: {{ get_outstanding_amount(doc.customer) }}
{# Filters - piped after values #}
Phone: {{ doc.phone | format_phone }}
Amount: {{ doc.grand_total | currency_words }}
Step 5: Deploy bench --site sitename migrate Quick Reference: Context Variables Template Type Available Objects Print Format doc , frappe , () Email Template doc , frappe (limited) Portal Page frappe.session , frappe.form_dict , custom context Notification doc , frappe Quick Reference: Essential Methods Need Method Format currency/date doc.get_formatted("fieldname") Format child row row.get_formatted("field", doc) Translate string ("String") Get linked doc frappe.get_doc("DocType", name) Get single field frappe.db.get_value("DT", name, "field") Current date frappe.utils.nowdate() Format date frappe.format_date(date) Critical Rules 1. ALWAYS use get_formatted for display values {# ❌ Raw database value #} {{ doc.grand_total }} {# ✅ Properly formatted with currency #} {{ doc.get_formatted("grand_total") }} 2. ALWAYS pass parent doc for child table formatting {% for row in doc.items %} {# ❌ Missing currency context #} {{ row.get_formatted("rate") }} {# ✅ Has currency context from parent #} {{ row.get_formatted("rate", doc) }} {% endfor %} 3. ALWAYS use translation function for user text
Invoice
{# ✅ Translatable #}
{{ _("Invoice") }}
- NEVER use Jinja in Report Print Formats
{% for(var i=0; i < data.length; i++) { %}
{% } %} 5. NEVER execute queries in loops {# ❌ N+1 query problem #} {% for item in doc.items %} {% set stock = frappe.db.get_value("Bin", ...) %} {% endfor %} {# ✅ Prefetch data in controller/context #} {% for item in items_with_stock %} {{ item.stock_qty }} {% endfor %} Version Differences Feature V14 V15 V16 Jinja templates ✅ ✅ ✅ get_formatted() ✅ ✅ ✅ jenv hooks ✅ ✅ ✅ wkhtmltopdf PDF ✅ ✅ ⚠️ Chrome PDF ❌ ❌ ✅ V16 Chrome PDF Considerations See erpnext-syntax-jinja for detailed Chrome PDF documentation. Reference Files File Contents decision-tree.md Complete template type selection workflows.md Step-by-step implementation patterns examples.md Complete working examples anti-patterns.md Common mistakes to avoid
← 返回排行榜