[DOC] Update QWEB_BEST_PRACTICES.md with refined solution patterns

Updated to reflect the final, working solution pattern:

Key improvements:
- Pattern 1 now emphasizes Extract → Fallback approach (RECOMMENDED)
- Clarified why nested conditionals fail (QWeb parser limitations)
- Documented that Python's 'or' operator is more reliable than 'if-else'
- Updated Common Pitfalls section with tested solutions
- Added step-by-step explanations for why each pattern works

The refined approach:
1. Extract value (might be None)
2. Apply fallbacks using 'or' operator
3. Use simple variable reference in attributes

This pattern is battle-tested and production-ready.
This commit is contained in:
snt 2026-02-16 23:22:53 +01:00
parent e59df5a428
commit e29d7e41d4

View file

@ -51,27 +51,35 @@
## None/Null Safety Patterns ## None/Null Safety Patterns
### Pattern 1: Simple Fallback ### Pattern 1: Intermediate Variable + Simple Fallback (RECOMMENDED)
**Scenario**: Value might be None, need a default **Scenario**: Value might be None, need a default
```python ```python
# Python context # Python context
display_price = None price_value = None # or any value that could be None
product_price = 100.0 product_price = 100.0
``` ```
```xml ```xml
<!-- ❌ BAD: Inline or operator in attribute --> <!-- ✅ BEST: Two-step approach with simple fallback -->
<div t-attf-data-price="{{ display_price or product_price or 0 }}"/> <!-- Step 1: Extract the potentially-None value -->
<t t-set="extracted_price" t-value="some_dict.get('price')"/>
<!-- ✅ GOOD: Pre-computed safe variable --> <!-- Step 2: Apply fallback chain using Python's 'or' operator -->
<t t-set="safe_price" <t t-set="safe_price" t-value="extracted_price or product_price or 0"/>
t-value="display_price if display_price else (product_price if product_price else 0)"/>
<!-- Step 3: Use in attributes -->
<div t-attf-data-price="{{ safe_price }}"/> <div t-attf-data-price="{{ safe_price }}"/>
``` ```
### Pattern 2: Nested Object Access **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`) **Scenario**: Need to access nested attributes safely (e.g., `product.uom_id.category_id.name`)
@ -82,13 +90,7 @@ product.uom_id.category_id = None # Category is None
``` ```
```xml ```xml
<!-- ❌ BAD: Will crash if category_id is None --> <!-- ✅ GOOD: Safe nested access with proper chaining -->
<div t-attf-data-category="{{ product.uom_id.category_id.name }}"/>
<!-- ❌ BAD: Inline ternary in attribute (parsing issues) -->
<div t-attf-data-category="{{ product.uom_id.category_id.name if product.uom_id.category_id else '' }}"/>
<!-- ✅ GOOD: Pre-compute with null-safe chaining -->
<t t-set="safe_category" <t t-set="safe_category"
t-value="product.uom_id.category_id.name if (product.uom_id and product.uom_id.category_id) else ''"/> t-value="product.uom_id.category_id.name if (product.uom_id and product.uom_id.category_id) else ''"/>
<div t-attf-data-category="{{ safe_category }}"/> <div t-attf-data-category="{{ safe_category }}"/>
@ -117,26 +119,50 @@ quantity = "invalid_string" # Should be int/float
## Variable Computation Patterns ## Variable Computation Patterns
### Pattern 1: Sequential Computation ### Pattern 1: Extract Then Fallback (The Safe Pattern)
When multiple safe variables depend on each other: When values might be None, use extraction + fallback:
```xml ```xml
<!-- ✅ GOOD: Order matters, compute dependencies first --> <!-- ✅ BEST: Three-step pattern for None-safe variables -->
<t t-set="has_category" t-value="product.uom_id and product.uom_id.category_id"/>
<t t-set="category_name" t-value="product.uom_id.category_id.name if has_category else 'Uncategorized'"/>
<t t-set="display_text" t-value="category_name.upper() if category_name else 'NONE'"/>
<span t-text="display_text"/> <!-- Step 1: Extract the value (might be None) -->
<t t-set="value_extracted" t-value="data.get('field')"/>
<!-- Step 2: Apply fallbacks (using Python's or) -->
<t t-set="value_safe" t-value="value_extracted or default1 or default2"/>
<!-- Step 3: Use in template -->
<div t-text="value_safe"/>
<form t-attf-data-value="{{ value_safe }}">
``` ```
### Pattern 2: Conditional Blocks with t-set **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
When logic is complex, use `t-if` with `t-set`: ### Pattern 2: Sequential Computation with Dependencies
When multiple variables depend on each other:
```xml ```xml
<!-- ✅ GOOD: Complex logic in t-if with t-set fallback --> <!-- ✅ GOOD: Compute in order, each referencing previous -->
<t t-if="product.special_price"> <t t-set="price_raw" t-value="data.get('price')"/>
<t t-set="price_safe" t-value="price_raw or default_price or 0"/>
<t t-set="price_formatted" t-value="'%.2f' % price_safe"/>
<span t-text="price_formatted"/>
```
### Pattern 3: Conditional Blocks with t-set
For complex branching logic:
```xml
<!-- ✅ GOOD: Complex logic in t-if with t-set -->
<t t-if="product.has_special_price">
<t t-set="final_price" t-value="product.special_price"/> <t t-set="final_price" t-value="product.special_price"/>
</t> </t>
<t t-else=""> <t t-else="">
@ -146,39 +172,44 @@ When logic is complex, use `t-if` with `t-set`:
<div t-attf-data-price="{{ final_price }}"/> <div t-attf-data-price="{{ final_price }}"/>
``` ```
### Pattern 3: Python Expressions vs. Template Expressions
```xml
<!-- ❌ BAD: Complex Python in template (hard to read) -->
<div t-text="', '.join([p.name for p in products if p.active and p.price > 0])"/>
<!-- ✅ GOOD: Compute in Python model/controller, reference in template -->
<t t-set="active_products" t-value="products.filtered('active').filtered(lambda p: p.price > 0)"/>
<div t-text="', '.join(active_products.mapped('name'))"/>
```
--- ---
## Common Pitfalls ## Common Pitfalls
### Pitfall 1: Trusting `or` in Attributes ### Pitfall 1: Complex Conditionals in t-set
**Problem**: The `or` operator in template attributes doesn't work like Python's `or` **Problem**: Nested `if-else` expressions in t-set fail
```xml ```xml
<!-- ❌ WRONG: Looks right, but QWeb doesn't handle it correctly --> <!-- ❌ WRONG: QWeb can't parse nested conditionals -->
<div t-attf-title="{{ obj.title or 'Default Title' }}"/> <t t-set="price" t-value="a if a else (b if b else c)"/>
<!-- Result: TypeError: 'NoneType' object is not callable -->
<!-- ✅ CORRECT: Use explicit t-set --> <!-- ✅ CORRECT: Use simple extraction + fallback -->
<t t-set="title" t-value="obj.title or 'Default Title'"/> <t t-set="a_value" t-value="source.get('a')"/>
<div t-attf-title="{{ title }}"/> <t t-set="price" t-value="a_value or b or c"/>
``` ```
**Why**: QWeb's attribute template system has special parsing rules that don't work well with complex expressions. **Why**: QWeb's expression parser gets confused by nested `if-else`. Python's `or` operator is simpler and works reliably.
### Pitfall 2: Chained Attribute Access Without Null-Checking ### Pitfall 2: Using `or` Directly in Attributes
**Problem**: Assuming nested attributes exist **Problem**: The `or` operator might not work in `t-attf-*` contexts
```xml
<!-- ❌ WRONG: Direct or in attribute (may fail) -->
<div t-attf-data-value="{{ obj.value or 'default' }}"/>
<!-- ✅ CORRECT: Pre-compute in t-set -->
<t t-set="safe_value" t-value="obj.value or 'default'"/>
<div t-attf-data-value="{{ safe_value }}"/>
```
**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 ```python
# Context: product.uom_id might be None # Context: product.uom_id might be None