[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:
parent
e59df5a428
commit
e29d7e41d4
1 changed files with 78 additions and 47 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue