QWeb Templates
Basic Template Structure
Simple Template
xml
<template id="basic_template" name="Basic Template">
<div class="container">
<h1>Welcome to Our Site</h1>
<p>This is a basic QWeb template.</p>
</div>
</template>
Template with Variables
xml
<template id="user_greeting" name="User Greeting">
<div class="greeting">
<h2>Hello, <t t-esc="user_name"/>!</h2>
<p>Today is <t t-esc="current_date"/></p>
</div>
</template>
Website Template
xml
<template id="website_page" name="Website Page" page="True">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<section class="s_banner">
<div class="container">
<h1>Page Title</h1>
<p>Page content goes here</p>
</div>
</section>
</div>
</t>
</template>
QWeb Directives
t-esc (Escape HTML)
xml
<template id="escape_example">
<div>
<p>Safe text: <t t-esc="user_input"/></p>
<p>User name: <span t-esc="user.name"/></p>
<p>Formatted amount: <t t-esc="amount" t-options="{'widget': 'monetary'}"/></p>
</div>
</template>
t-raw (Raw HTML)
xml
<template id="raw_html_example">
<div>
<div class="content" t-raw="html_content"/>
<div class="description" t-raw="product.description_sale"/>
</div>
</template>
t-if Conditional
xml
<template id="conditional_example">
<div>
<div t-if="user.is_admin" class="admin-panel">
<h3>Admin Panel</h3>
<p>Admin controls here</p>
</div>
<div t-if="not user.partner_id.is_company">
<p>Individual customer content</p>
</div>
<div t-if="order.state == 'draft'" class="alert alert-info">
<p>Order is in draft state</p>
</div>
</div>
</template>
t-elif and t-else
xml
<template id="multiple_conditions">
<div class="status">
<div t-if="order.state == 'draft'" class="badge badge-secondary">
Draft
</div>
<div t-elif="order.state == 'confirmed'" class="badge badge-primary">
Confirmed
</div>
<div t-elif="order.state == 'done'" class="badge badge-success">
Completed
</div>
<div t-else="" class="badge badge-warning">
Unknown Status
</div>
</div>
</template>
Loops and Iteration
t-foreach Basic Loop
xml
<template id="product_list">
<div class="product-grid">
<div t-foreach="products" t-as="product" class="product-card">
<h4 t-esc="product.name"/>
<p t-esc="product.description"/>
<span class="price" t-esc="product.list_price"/>
</div>
</div>
</template>
Loop with Index
xml
<template id="numbered_list">
<ol>
<li t-foreach="items" t-as="item">
<span class="index">Item #<t t-esc="item_index + 1"/>:</span>
<span class="name" t-esc="item.name"/>
</li>
</ol>
</template>
Loop Variables
xml
<template id="loop_variables">
<table class="table">
<tr t-foreach="records" t-as="record"
t-att-class="'odd' if record_odd else 'even'">
<td t-esc="record_index"/>
<td t-esc="record.name"/>
<td>
<span t-if="record_first" class="badge">First</span>
<span t-if="record_last" class="badge">Last</span>
</td>
</tr>
</table>
</template>
Attributes and Dynamic Content
t-att- Dynamic Attributes
xml
<template id="dynamic_attributes">
<div>
<img t-att-src="product.image_url"
t-att-alt="product.name"
t-att-class="'featured' if product.is_featured else 'regular'"/>
<a t-att-href="'/product/' + slug(product)"
t-att-title="product.name">
<t t-esc="product.name"/>
</a>
<input t-att-value="form_data.get('email', '')"
t-att-required="'required' if field_required else None"/>
</div>
</template>
t-attf- Formatted Attributes
xml
<template id="formatted_attributes">
<div>
<div t-attf-class="product-card #{product.category.code} #{product.state}">
<h3 t-esc="product.name"/>
</div>
<a t-attf-href="/shop/product/#{product.id}?category=#{category.id}">
View Product
</a>
<div t-attf-style="color: #{product.color}; background: #{product.bg_color};">
Styled content
</div>
</div>
</template>
t-att Dictionary Attributes
xml
<template id="dict_attributes">
<div t-att="{'class': 'product-card ' + product.state,
'data-id': product.id,
'data-price': product.price}">
<t t-esc="product.name"/>
</div>
</template>
Template Inheritance
Template Extension
xml
<template id="extended_layout" inherit_id="website.layout">
<xpath expr="//head" position="inside">
<link rel="stylesheet" href="/custom_module/static/src/css/custom.css"/>
<script src="/custom_module/static/src/js/custom.js"/>
</xpath>
<xpath expr="//header" position="after">
<div class="custom-banner">
<p>Custom banner content</p>
</div>
</xpath>
</template>
Replacing Content
xml
<template id="custom_footer" inherit_id="website.footer_custom">
<xpath expr="//div[@class='container']" position="replace">
<div class="container">
<div class="row">
<div class="col-md-6">
<h4>Contact Info</h4>
<p>Custom footer content</p>
</div>
<div class="col-md-6">
<h4>Follow Us</h4>
<p>Social media links</p>
</div>
</div>
</div>
</xpath>
</template>
Multiple Inheritance Points
xml
<template id="product_page_custom" inherit_id="website_sale.product">
<xpath expr="//div[@id='product_details']" position="before">
<div class="product-badges">
<span t-if="product.is_new" class="badge badge-success">New</span>
<span t-if="product.on_sale" class="badge badge-danger">Sale</span>
</div>
</xpath>
<xpath expr="//div[@class='product-price']" position="after">
<div class="product-savings" t-if="product.compare_list_price > product.list_price">
<span class="savings">
Save <t t-esc="product.compare_list_price - product.list_price"/>
</span>
</div>
</xpath>
</template>
Advanced Directives
t-call Template Inclusion
xml
<template id="main_template">
<div class="page-wrapper">
<t t-call="custom_module.header_template"/>
<main class="content">
<t t-call="custom_module.breadcrumb_template">
<t t-set="breadcrumb_items" t-value="page_breadcrumbs"/>
</t>
<div class="main-content">
<t t-raw="0"/>
</div>
</main>
<t t-call="custom_module.footer_template"/>
</div>
</template>
t-set Variable Assignment
xml
<template id="variable_assignment">
<div>
<t t-set="currency" t-value="request.env.user.company_id.currency_id"/>
<t t-set="is_mobile" t-value="request.httprequest.user_agent.is_mobile"/>
<t t-set="current_year" t-value="datetime.datetime.now().year"/>
<div t-att-class="'mobile-view' if is_mobile else 'desktop-view'">
<p>Copyright <t t-esc="current_year"/> - All rights reserved</p>
<p>Currency: <t t-esc="currency.name"/></p>
</div>
</div>
</template>
t-field Model Field Rendering
xml
<template id="model_fields">
<div class="record-display">
<h2 t-field="record.name"/>
<div class="content" t-field="record.description"/>
<div class="date" t-field="record.date" t-options="{'format': 'MMMM d, y'}"/>
<div class="amount" t-field="record.amount" t-options="{'widget': 'monetary'}"/>
<div class="image" t-field="record.image" t-options="{'widget': 'image'}"/>
</div>
</template>
Forms and User Input
Basic Form
xml
<template id="contact_form">
<form action="/contact/submit" method="post" class="s_website_form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group">
<label for="name">Name *</label>
<input type="text" class="form-control" name="name" required="True"/>
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" class="form-control" name="email" required="True"/>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea class="form-control" name="message" rows="5"/>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
</template>
Form with Validation
xml
<template id="validated_form">
<form action="/submit" method="post" class="s_website_form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group">
<label for="email">Email</label>
<input type="email"
class="form-control"
name="email"
t-att-value="form_data.get('email', '')"
t-att-class="'is-invalid' if errors.get('email') else ''"/>
<div t-if="errors.get('email')" class="invalid-feedback">
<t t-esc="errors['email']"/>
</div>
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="tel"
class="form-control"
name="phone"
pattern="[0-9\-\+\s\(\)]*"
t-att-value="form_data.get('phone', '')"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
JavaScript Integration
Template with JavaScript
xml
<template id="interactive_template">
<div class="interactive-component" data-component="custom-widget">
<div class="content">
<h3 t-esc="title"/>
<div class="data" t-att-data-config="json.dumps(config)">
<t t-foreach="items" t-as="item">
<div class="item" t-att-data-id="item.id">
<span t-esc="item.name"/>
</div>
</t>
</div>
</div>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
initCustomWidget();
});
</script>
</div>
</template>
AJAX Template
xml
<template id="ajax_content">
<div class="ajax-container" data-url="/api/data">
<div class="loading" style="display: none;">
<i class="fa fa-spinner fa-spin"/> Loading...
</div>
<div class="content" style="display: none;">
<!-- Content loaded via AJAX -->
</div>
<div class="error" style="display: none;">
<p class="text-danger">Error loading content</p>
</div>
</div>
</template>
E-commerce Templates
Product Card
xml
<template id="product_card_custom">
<div class="product-card" t-att-data-product-id="product.id">
<div class="product-image">
<img t-att-src="website.image_url(product, 'image_1024')"
t-att-alt="product.name"
class="img-fluid"/>
<div class="product-badges" t-if="product.is_published">
<span t-if="product.website_ribbon_id"
class="badge"
t-att-style="'background-color: %s' % product.website_ribbon_id.bg_color"
t-esc="product.website_ribbon_id.name"/>
</div>
</div>
<div class="product-info">
<h5 class="product-title">
<a t-att-href="product.website_url" t-esc="product.name"/>
</h5>
<div class="product-price">
<span t-if="product.compare_list_price > product.list_price"
class="text-muted text-decoration-line-through">
<t t-esc="product.compare_list_price"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
</span>
<span class="current-price">
<t t-esc="product.list_price"
t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
</span>
</div>
<div class="product-actions">
<a t-att-href="product.website_url" class="btn btn-primary btn-sm">
View Details
</a>
</div>
</div>
</div>
</template>
Shopping Cart Summary
xml
<template id="cart_summary_custom">
<div class="cart-summary">
<h4>Order Summary</h4>
<div class="cart-items">
<div t-foreach="website_sale_order.order_line" t-as="line" class="cart-item">
<div class="item-info">
<span class="item-name" t-esc="line.name"/>
<span class="item-qty">Qty: <t t-esc="int(line.product_uom_qty)"/></span>
</div>
<div class="item-price">
<t t-esc="line.price_subtotal"
t-options="{'widget': 'monetary', 'display_currency': website_sale_order.currency_id}"/>
</div>
</div>
</div>
<div class="cart-totals">
<div class="subtotal">
<span>Subtotal:</span>
<span t-esc="website_sale_order.amount_untaxed"
t-options="{'widget': 'monetary', 'display_currency': website_sale_order.currency_id}"/>
</div>
<div class="taxes" t-if="website_sale_order.amount_tax">
<span>Taxes:</span>
<span t-esc="website_sale_order.amount_tax"
t-options="{'widget': 'monetary', 'display_currency': website_sale_order.currency_id}"/>
</div>
<div class="total">
<strong>
<span>Total:</span>
<span t-esc="website_sale_order.amount_total"
t-options="{'widget': 'monetary', 'display_currency': website_sale_order.currency_id}"/>
</strong>
</div>
</div>
</div>
</template>
Utility Functions
Helper Functions
xml
<template id="utility_examples">
<div>
<!-- Date formatting -->
<p>Date: <t t-esc="datetime.datetime.now().strftime('%B %d, %Y')"/></p>
<!-- URL building -->
<a t-att-href="'/shop/category/%s' % slug(category)">
<t t-esc="category.name"/>
</a>
<!-- Text manipulation -->
<p t-esc="product.description[:100] + '...' if len(product.description) > 100 else product.description"/>
<!-- Number formatting -->
<span t-esc="'{:,.2f}'.format(product.list_price)"/>
<!-- Boolean display -->
<span t-esc="'Yes' if product.is_available else 'No'"/>
</div>
</template>
Common Patterns
Responsive Layout
xml
<template id="responsive_layout">
<div class="container-fluid">
<div class="row">
<div class="col-12 col-md-8">
<main class="main-content">
<t t-raw="0"/>
</main>
</div>
<div class="col-12 col-md-4">
<aside class="sidebar">
<t t-call="custom_module.sidebar_content"/>
</aside>
</div>
</div>
</div>
</template>
Error Handling
xml
<template id="error_template">
<div class="error-container">
<t t-if="error_message">
<div class="alert alert-danger">
<h4>Error</h4>
<p t-esc="error_message"/>
</div>
</t>
<t t-if="not error_message and not data">
<div class="alert alert-info">
<p>No data available</p>
</div>
</t>
<t t-if="data">
<div class="content">
<!-- Normal content -->
</div>
</t>
</div>
</template>