# QWeb Template Best Practices - Odoo 18 **Reference**: website_sale_aplicoop template error fix **Odoo Version**: 18.0+ **Created**: 2026-02-16 --- ## Table of Contents 1. [Attribute Expression Best Practices](#attribute-expression-best-practices) 2. [None/Null Safety Patterns](#nonenull-safety-patterns) 3. [Variable Computation Patterns](#variable-computation-patterns) 4. [Common Pitfalls](#common-pitfalls) 5. [Real-World Examples](#real-world-examples) --- ## Attribute Expression Best Practices ### The Problem: t-attf-* Operator Issues **Issue**: QWeb's `t-attf-*` (template attribute) directives don't handle chained `or` operators well when expressions can evaluate to None. ```xml
``` ### The Solution: Pre-compute Safe Variables **Key Pattern**: Use `` to compute safe values **before** using them in attributes. ```xml ``` ### Why This Works 1. **Separation of Concerns**: Logic (t-set) is separate from rendering (t-attf-*) 2. **Explicit Evaluation**: QWeb evaluates the conditional expression fully before passing to t-set 3. **Type Safety**: Pre-computed value is guaranteed to be non-None 4. **Readability**: Clear intent of what value is being used --- ## None/Null Safety Patterns ### Pattern 1: Intermediate Variable + Simple Fallback (RECOMMENDED) **Scenario**: Value might be None, need a default ```python # Python context price_value = None # or any value that could be None product_price = 100.0 ``` ```xml
``` **Why this works**: - Step 1 extracts without defaults (returns None if missing) - Step 2 uses Python's short-circuit `or` for safe None-handling - Step 3 uses simple variable reference in attribute - QWeb can reliably evaluate each step ### Pattern 2: Nested Object Access (Safe Chaining) **Scenario**: Need to access nested attributes safely (e.g., `product.uom_id.category_id.name`) ```python # Python context product.uom_id = UoM(...) # Valid UoM with category_id product.uom_id.category_id = None # Category is None ``` ```xml
``` ### Pattern 3: Type Coercion **Scenario**: Value might be wrong type, need guaranteed type ```python # Python context quantity = "invalid_string" # Should be int/float ``` ```xml ``` --- ## Variable Computation Patterns ### Pattern 1: Extract Then Fallback (The Safe Pattern) When values might be None, use extraction + fallback: ```xml
``` **Why it works**: - Extraction returns None cleanly - `or` operator handles None values using Python's short-circuit evaluation - Each step is simple enough for QWeb to parse - No complex conditionals that might fail ### Pattern 2: Sequential Computation with Dependencies When multiple variables depend on each other: ```xml ``` ### Pattern 3: Conditional Blocks with t-set For complex branching logic: ```xml
``` --- ## Common Pitfalls ### Pitfall 1: Complex Conditionals in t-set **Problem**: Nested `if-else` expressions in t-set fail ```xml ``` **Why**: QWeb's expression parser gets confused by nested `if-else`. Python's `or` operator is simpler and works reliably. ### Pitfall 2: Using `or` Directly in Attributes **Problem**: The `or` operator might not work in `t-attf-*` contexts ```xml
``` **Why**: Attribute parsing is stricter than body content. Always pre-compute to be safe. ### Pitfall 3: Assuming Nested Attributes Exist **Problem**: Not checking intermediate objects before accessing ```python # Context: product.uom_id might be None ``` ```xml
``` ### Pitfall 3: Complex Logic in t-att (non-template attributes) **Problem**: Using complex expressions in non-template attributes ```xml
``` ### Pitfall 4: Forgetting t-attf- Prefix **Problem**: Using `data-*` instead of `t-attf-data-*` ```xml ``` --- ## Real-World Examples ### Example 1: E-commerce Product Card **Scenario**: Displaying product with optional fields ```xml ``` ### Example 2: Nested Data Attributes **Scenario**: Form with deeply nested object access ```xml
...
``` ### Example 3: Conditional Styling **Scenario**: Attribute value depends on conditions ```xml
...
``` --- ## Summary Table | Pattern | ❌ Don't | ✅ Do | |---------|---------|-------| | **Fallback values** | `t-attf-x="{{ a or b or c }}"` | `` then `{{ x }}` | | **Nested objects** | `{{ obj.nested.prop }}` | `` | | **Type checking** | `` | `` | | **Complex logic** | `{{ function(a, b) if condition else default }}` | Pre-compute in Python, reference in template | | **Chained operators** | `{{ a or b if c else d or e }}` | Break into multiple t-set statements | --- ## Tools & Validation ### XML Validation ```bash # Validate XML syntax python3 -m xml.dom.minidom template.xml # Or use pre-commit hooks pre-commit run check-xml ``` ### QWeb Template Testing ```python # In Odoo shell from odoo.tools import misc arch = env['ir.ui.view'].search([('name', '=', 'template_name')])[0].arch # Check if template compiles without errors ``` ### Debugging Template Issues ```xml ``` --- ## References - [Odoo QWeb Documentation](https://www.odoo.com/documentation/18.0/developer/reference/frontend/qweb.html) - [Odoo Templates](https://www.odoo.com/documentation/18.0/developer/reference/backend/orm.html#templates) - [Python Ternary Expressions](https://docs.python.org/3/tutorial/controlflow.html#more-on-conditions) --- ## Related Issues & Fixes - [website_sale_aplicoop Template Error Fix](./FIX_TEMPLATE_ERROR_SUMMARY.md) - Real-world example of this pattern - [Git Commit 0a0cf5a](../../../.git/logs/HEAD) - Implementation of these patterns --- **Last Updated**: 2026-02-16 **Odoo Version**: 18.0+ **Status**: ✅ Documented and tested