diff --git a/docs/FINAL_SOLUTION_SUMMARY.md b/docs/FINAL_SOLUTION_SUMMARY.md deleted file mode 100644 index f038cd6..0000000 --- a/docs/FINAL_SOLUTION_SUMMARY.md +++ /dev/null @@ -1,290 +0,0 @@ -# Template Error - FINAL SOLUTION - -**Status**: ✅ **PERMANENTLY FIXED** via commit 5721687 - -## Problem Statement - -The `TypeError: 'NoneType' object is not callable` error in the `website_sale_aplicoop.eskaera_shop_products` template was caused by QWeb's strict parsing limitations. - -**Error Location**: -``` -Template: website_sale_aplicoop.eskaera_shop_products -Path: /t/t/div/div/form -Node:
-``` - -## Root Cause Analysis - -QWeb template engine cannot parse: - -1. **Complex nested conditionals in t-set**: - ```xml - ❌ FAILS - - ``` - -2. **Chained 'or' operators in t-attf-* attributes**: - ```xml - ❌ FAILS - t-attf-data-price="{{ price_info.get('price') or product.list_price or 0 }}" - ``` - -3. **Deep object attribute chains with conditionals**: - ```xml - ❌ FAILS - t-set="uom" t-value="product.uom_id.category_id.name if (product.uom_id and product.uom_id.category_id) else ''" - ``` - -## Solution Approach - -**Move ALL logic from template to controller** where Python can safely process it. - -### Previous Failed Attempts - -| Commit | Approach | Result | -|--------|----------|--------| -| df57233 | Add `or` operators in attributes | ❌ Still failed | -| 0a0cf5a | Complex nested conditionals in t-set | ❌ Still failed | -| 8e5a4a3 | Three-step pattern with `or` chains | ⚠️ Partially worked but template still had logic | - -### Final Solution (Commit 5721687) - -**Strategy**: Let Python do all the work, pass clean data to template - -#### Step 1: Create Helper Method in Controller - -```python -def _prepare_product_display_info(self, product, product_price_info): - """Pre-process all display values for QWeb safety. - - Returns dict with: - - display_price: float, never None - - safe_uom_category: string, never None - """ - # Get price - all logic here, not in template - price_data = product_price_info.get(product.id, {}) - price = price_data.get("price", product.list_price) if price_data else product.list_price - price_safe = float(price) if price else 0.0 - - # Get UoM - all logic here, not in template - uom_category_name = "" - if product.uom_id: - if product.uom_id.category_id: - uom_category_name = product.uom_id.category_id.name or "" - - return { - "display_price": price_safe, - "safe_uom_category": uom_category_name, - } -``` - -#### Step 2: Build Dict in Both Endpoints - -```python -# In eskaera_shop() method -product_display_info = {} -for product in products: - display_info = self._prepare_product_display_info(product, product_price_info) - product_display_info[product.id] = display_info - -# In load_eskaera_page() method (lazy loading) -product_display_info = {} -for product in products_page: - display_info = self._prepare_product_display_info(product, product_price_info) - product_display_info[product.id] = display_info -``` - -#### Step 3: Pass to Template - -```python -return request.render( - "website_sale_aplicoop.eskaera_shop", - { - # ... other variables ... - "product_display_info": product_display_info, - } -) -``` - -#### Step 4: Simplify Template to Simple Variable References - -```xml - - - - - - - -``` - -## Why This Works - -1. **Python handles complexity**: Conditional logic runs in Python where it's safe -2. **Template gets clean data**: Only simple variable references, no expressions -3. **QWeb is happy**: `.get()` method calls are simple enough for QWeb parser -4. **No None values**: Values are pre-processed to never be None -5. **Maintainable**: Clear separation: Controller = logic, Template = display - -## Files Modified - -### website_sale_aplicoop/controllers/website_sale.py - -**Added**: -- `_prepare_product_display_info(product, product_price_info)` method (lines 390-417) -- Calls to `_prepare_product_display_info()` in `eskaera_shop()` (lines 1062-1065) -- Calls to `_prepare_product_display_info()` in `load_eskaera_page()` (lines 1260-1263) -- Pass `product_display_info` to both template renders - -**Total additions**: ~55 lines of Python - -### website_sale_aplicoop/views/website_templates.xml - -**Changed**: -- Line ~1170: `display_price` - from complex conditional to simple `dict.get()` -- Line ~1225: `safe_uom_category` - from nested conditional to simple `dict.get()` - -**Total changes**: -10 lines of complex XML, +5 lines of simple XML - -## Verification - -✅ Module loads without parsing errors: -``` -Module website_sale_aplicoop loaded in 0.62s, 612 queries (+612 other) -``` - -✅ Template variables in database match expectations - -✅ No runtime errors when accessing eskaera_shop page - -## Key Learnings - -### QWeb Parsing Rules - -**Safe in t-set**: -- ✅ `dict.get('key')` -- ✅ `dict.get('key', default)` -- ✅ Simple method calls with literals -- ✅ Basic `or` between simple values (with caution) - -**Unsafe in t-set**: -- ❌ Nested `if-else` conditionals -- ❌ Complex boolean expressions -- ❌ Chained method calls with conditionals - -**For attributes (t-attf-*)**: -- ✅ Simple variable references: `{{ var }}` -- ✅ Simple method calls: `{{ obj.method() }}` -- ⚠️ `or` operators may work but unreliable -- ❌ Anything complex - -### Best Practice Pattern - -``` -CONTROLLER (Python): -┌─────────────────────────────────────────┐ -│ Process data │ -│ Handle None/defaults │ -│ Build clean dicts │ -│ Return display-ready values │ -└──────────────────┬──────────────────────┘ - │ - ↓ - product_display_info - │ - ↓ -TEMPLATE (QWeb): -┌──────────────────────────────────────────┐ -│ Simple dict.get() calls only │ -│ NO conditional logic │ -│ NO complex expressions │ -│ Just display variables │ -└──────────────────────────────────────────┘ -``` - -This pattern ensures QWeb stays happy while keeping code clean and maintainable. - -## Deployment Checklist - -- ✅ Code committed (5721687) -- ✅ Module loads without errors -- ✅ Template renders without 500 error -- ✅ Pre-commit hooks satisfied -- ✅ Ready for production - -## Future Prevention - -When adding new display logic to templates: - -1. **Ask**: "Does this involve conditional logic?" - - If NO → Can go in template - - If YES → Must go in controller - -2. **Never put in template**: - - `if-else` statements - - Complex `or` chains - - Deep attribute chains with fallbacks - - Method calls that might return None - -3. **Always process in controller**: - - Pre-calculate values - - Handle None cases - - Build display dicts - - Pass to template - ---- - -**Solution Complexity**: ⭐⭐ (Simple and elegant) -**Code Quality**: ⭐⭐⭐⭐⭐ (Clean separation of concerns) -**Maintainability**: ⭐⭐⭐⭐⭐ (Easy to extend) -**Production Ready**: ✅ YES - ---- - -## FINAL VERIFICATION (2026-02-16 - Session Complete) - -### ✅ All Tests Passing - -**1. Database Template Verification**: -``` -SELECT id, key FROM ir_ui_view -WHERE key = 'website_sale_aplicoop.eskaera_shop_products'; -Result: 4591 | website_sale_aplicoop.eskaera_shop_products ✅ -``` - -**2. Template Content Check**: -``` -SELECT arch_db::text LIKE '%order_id_safe%' FROM ir_ui_view -WHERE id = 4591; -Result: t (TRUE) ✅ -``` - -**3. Module Load Test**: -``` -odoo -d odoo -u website_sale_aplicoop --stop-after-init -Result: Module loaded in 0.63s, 612 queries, NO ERRORS ✅ -``` - -**4. Web Interface Test**: -``` -curl -s -i http://localhost:8069/web | head -1 -Result: HTTP/1.1 200 OK - No 500 errors ✅ -``` - -**5. Lazy Loading Pages**: -``` -/eskaera/2/load-page?page=2 HTTP/1.1" 200 ✅ -/eskaera/2/load-page?page=3 HTTP/1.1" 200 ✅ -``` - -**6. Odoo Log Verification**: -- No TypeError in logs ✅ -- No traceback in logs ✅ -- No NoneType is not callable errors ✅ - -### Status: ✅ PRODUCTION READY - -The template error has been fully resolved and verified. All systems operational. diff --git a/docs/FIX_TEMPLATE_ERROR_SUMMARY.md b/docs/FIX_TEMPLATE_ERROR_SUMMARY.md deleted file mode 100644 index 58f2222..0000000 --- a/docs/FIX_TEMPLATE_ERROR_SUMMARY.md +++ /dev/null @@ -1,332 +0,0 @@ -# Fix Template Error Summary - website_sale_aplicoop - -**Date**: 2026-02-16 -**Final Status**: ✅ PERMANENTLY RESOLVED -**Solution Commit**: 5721687 -**Version**: 18.0.1.1.1 - ---- - -## Problem - -The `eskaera_shop_products` QWeb template was throwing a `TypeError: 'NoneType' object is not callable` error when loading the store page. - -### Root Cause - QWeb Parsing Limitations - -QWeb has strict limitations on what expressions it can parse: - -1. **Complex nested conditionals in t-set fail** - ```xml - ❌ - ``` - -2. **Direct 'or' in attributes unreliable** - ```xml - ❌
- ``` - -3. **Deep object chains with conditionals fail** - ```xml - ❌ t-set="uom" t-value="product.uom_id.category_id.name if product.uom_id.category_id else ''" - ``` - ---- - -## Solution - -### Architecture: Move Logic to Controller - -**Final insight**: Don't fight QWeb's limitations. Move ALL complex logic to the Python controller where it belongs. - -#### The Pattern - -``` -CONTROLLER (Python) - ↓ (process data, handle None) -product_display_info = { - product.id: { - 'display_price': 10.99, # Always a float, never None - 'safe_uom_category': 'Weight' # Always a string, never None - } -} - ↓ (pass clean data to template) -TEMPLATE (QWeb) - ↓ (simple dict.get() calls, no logic) - -``` - -#### Implementation - -**In Controller** - Added `_prepare_product_display_info()` method: - -```python -def _prepare_product_display_info(self, product, product_price_info): - """Pre-process all display values for QWeb safety. - - All logic happens HERE in Python, not in template. - Returns dict with safe values ready for display. - """ - # Get price - handle None safely - price_data = product_price_info.get(product.id, {}) - price = price_data.get("price", product.list_price) if price_data else product.list_price - price_safe = float(price) if price else 0.0 - - # Get UoM category - handle None/nested attributes safely - uom_category_name = "" - if product.uom_id: - if product.uom_id.category_id: - uom_category_name = product.uom_id.category_id.name or "" - - return { - "display_price": price_safe, # Never None - "safe_uom_category": uom_category_name, # Never None - } -``` - -**In Template** - Simple dict.get() calls: - -```xml - - - - - - - -``` - ---- - -## What Changed - -### Files Modified - -1. **website_sale_aplicoop/controllers/website_sale.py** - - Added `_prepare_product_display_info()` method (lines 390-417) - - Generate `product_display_info` dict in `eskaera_shop()` (lines 1062-1065) - - Generate `product_display_info` dict in `load_eskaera_page()` (lines 1260-1263) - - Pass to template renders - -2. **website_sale_aplicoop/views/website_templates.xml** - - Removed complex conditional expressions from template - - Replaced with simple `dict.get()` calls - - No business logic remains in template - -### Iteration History - -| Commit | Approach | Result | -|--------|----------|--------| -| df57233 | Add `or` operators in attributes | ❌ Error persisted | -| 0a0cf5a | Complex nested conditionals in t-set | ❌ Error persisted | -| 8e5a4a3 | Three-step pattern with `or` chains | ⚠️ Error persisted | -| 5721687 | Move logic to controller | ✅ SOLVED | - - - -### Why This Works - -1. **Two-step computation**: Separates extraction from fallback logic -2. **Python short-circuit evaluation**: `or` operator properly handles None values -3. **Avoids complex conditionals**: Simple `or` chains instead of nested `if-else` -4. **QWeb-compatible**: The `or` operator works reliably when value is pre-extracted -5. **Readable**: Clear intent - extract value, then fall back - ---- - -## Changes Made - -### File: `website_sale_aplicoop/views/website_templates.xml` - -**Location 1**: Price computation (lines 1165-1177) - -**Before**: -```xml - - - -``` - -**After**: -```xml - - - - -``` - -**Location 2**: Form element (lines 1215-1228) - -**Before**: -```xml - - - -``` - -**After**: -```xml - - -``` - ---- - -## Verification - -### Template Validation -✅ XML validation: Passed -✅ Pre-commit hooks: Passed (check xml) - -### Runtime Verification -✅ Module loaded successfully without parsing errors -✅ Template compiled correctly in ir.ui.view -✅ Safe variables present in rendered template -``` -FOUND: safe_display_price in Eskaera Shop Products -FOUND: safe_uom_category in Eskaera Shop Products -``` - -### Module Status -``` -Module: website_sale_aplicoop -State: installed -Version: 18.0.1.1.1 -``` - ---- - -## Git Commits - -### Commit 1: First Fix Attempt (df57233) -- Used simple `or` operators in t-attf-* attributes -- Result: Error persisted - direct attribute operators don't work in QWeb - -### Commit 2: Complex Conditionals Attempt (0a0cf5a) -- Added safe variables with nested `if-else` expressions -- Result: Error persisted - complex conditionals in t-set fail -- Issue: QWeb can't properly evaluate `if var else (if var2 else val)` patterns - -### Commit 3: Final Fix - Intermediate Variable Pattern (8e5a4a3) ✅ -``` -[FIX] website_sale_aplicoop: Simplify price handling using Python or operator in t-set - -- Create intermediate variable: display_price_value = price_info.get('price') -- Then compute: display_price = display_price_value or product.list_price or 0.0 -- Use simple reference in t-attf attribute: {{ display_price }} - -This approach: -1. Avoids complex nested conditionals in t-set -2. Uses Python's native short-circuit evaluation for None-safety -3. Keeps template expressions simple and readable -4. Properly handles fallback values in the right evaluation order -``` -- Result: ✅ Template loads successfully, no errors - ---- - -## Testing - -### Test Status -- ✅ All 85 unit tests passed (executed in previous iteration) -- ✅ Template parsing: No errors -- ✅ Variable rendering: Safe variables correctly computed -- ✅ Docker services: All running - -### Next Steps (if needed) -Run full test suite: -```bash -docker-compose exec -T odoo odoo -d odoo --test-enable --test-tags=website_sale_aplicoop --stop-after-init -``` - ---- - -## Key Learnings - -### QWeb Rendering Limitations - -QWeb's template attribute system (`t-attf-*`) has specific limitations: - -1. **Direct `or` operators in attributes don't work reliably** - - ❌ Bad: `t-attf-value="{{ var1 or var2 or default }}"` - - Issue: QWeb doesn't parse `or` correctly in attribute context - -2. **Complex conditionals in t-set can fail** - - ❌ Bad: `` - - Issue: Nested conditionals confuse QWeb's expression parser - -3. **Simple fallbacks work best** - - ✅ Good: `` - - ✅ Good: `` - - These are simple expressions QWeb can reliably evaluate - -4. **Intermediate variables solve the problem** - - Extract the value first (with `.get()`) - - Then apply fallbacks (with `or`) - - Finally reference in attributes - - Keeps each step simple and QWeb-safe - -### The Pattern - -When you need safe None-handling in attributes: - -```xml - - - - - - - -
-``` - -This three-step pattern ensures: -- Each computation is simple (QWeb-compatible) -- None values are handled correctly (Python's `or`) -- Attributes are never nil (fallback chain) -- Code is readable and maintainable - ---- - -## Related Files - -- [website_templates.xml](../website_sale_aplicoop/views/website_templates.xml) - Template file (modified) -- [__manifest__.py](../website_sale_aplicoop/__manifest__.py) - Module manifest -- [README.md](../website_sale_aplicoop/README.md) - Module documentation - ---- - -## Environment - -**Odoo**: 18.0.20251208 -**Docker**: Compose v2+ -**Python**: 3.10+ -**Module Version**: 18.0.1.1.1 - ---- - -## Conclusion - -The template error has been successfully fixed by applying the proper QWeb pattern for None-safe value handling. The solution is: - -- ✅ **Simple**: Three-step intermediate variable pattern -- ✅ **Tested**: Module loads without errors, all tests passing -- ✅ **Robust**: Handles None values, missing attributes, and type conversions -- ✅ **Maintainable**: Clear intent, easy to understand and modify -- ✅ **Production-ready**: Deployed and verified - -The module is now ready for production use. Future templates should follow this pattern to avoid similar issues. - -**Key Takeaway**: In QWeb templates, keep variable computations simple by using intermediate variables and let Python's native operators (`or`, `.get()`) handle the logic. diff --git a/docs/QWEB_BEST_PRACTICES.md b/docs/QWEB_BEST_PRACTICES.md deleted file mode 100644 index a2a4eef..0000000 --- a/docs/QWEB_BEST_PRACTICES.md +++ /dev/null @@ -1,399 +0,0 @@ -# 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 diff --git a/docs/TEMPLATE_FIX_INDEX.md b/docs/TEMPLATE_FIX_INDEX.md deleted file mode 100644 index 66f32fc..0000000 --- a/docs/TEMPLATE_FIX_INDEX.md +++ /dev/null @@ -1,235 +0,0 @@ -# Template Error Fix - Complete Reference Index - -**Status**: ✅ RESOLVED -**Module**: website_sale_aplicoop v18.0.1.1.1 -**Date**: 2026-02-16 - ---- - -## Quick Links - -### 📋 Problem & Solution -- **Main Reference**: [FIX_TEMPLATE_ERROR_SUMMARY.md](FIX_TEMPLATE_ERROR_SUMMARY.md) - - Root cause analysis - - Solution explanation - - Verification results - -### 📚 Development Standards -- **Best Practices Guide**: [QWEB_BEST_PRACTICES.md](QWEB_BEST_PRACTICES.md) - - QWeb patterns and examples - - Common pitfalls to avoid - - Real-world code samples - -### 🔧 Implementation Details -- **Modified File**: [website_templates.xml](../website_sale_aplicoop/views/website_templates.xml) - - Lines 1217-1224: Safe variable definitions - - Template: `eskaera_shop_products` - -### 📦 Git History -``` -6fed863 [DOC] Add QWeb template best practices and error fix documentation -0a0cf5a [FIX] website_sale_aplicoop: Replace or operators with t-set safe variables -df57233 [FIX] website_sale_aplicoop: Fix NoneType error in eskaera_shop_products template -``` - ---- - -## The Problem (Short Version) - -**Error**: `TypeError: 'NoneType' object is not callable` - -**Cause**: QWeb parsing of `or` operators in `t-attf-*` attributes fails when values are None - -**Example**: -```xml - -
-``` - ---- - -## The Solution (Short Version) - -**Pattern**: Pre-compute safe values with `t-set` before using in attributes - -**Example**: -```xml - - - -``` - ---- - -## Key Changes Summary - -| Aspect | Before | After | -|--------|--------|-------| -| **Pattern** | Inline `or` operators | Pre-computed `t-set` | -| **Error** | TypeError on None values | Safe handling of None | -| **Code lines** | 7 | 15 | -| **QWeb compatible** | ❌ No | ✅ Yes | -| **Testable** | ❌ Hard | ✅ Easy | - ---- - -## Files in This Series - -1. **THIS FILE** (TEMPLATE_FIX_INDEX.md) - - Quick navigation and overview - - Links to detailed documentation - - Summary reference - -2. [FIX_TEMPLATE_ERROR_SUMMARY.md](FIX_TEMPLATE_ERROR_SUMMARY.md) - - Complete analysis of the error - - Step-by-step solution explanation - - Verification and testing results - - Debugging information - -3. [QWEB_BEST_PRACTICES.md](QWEB_BEST_PRACTICES.md) - - QWeb template development guide - - 3 None-safety patterns with examples - - 3 Variable computation patterns - - Common pitfalls and solutions - - Real-world code examples - - Summary reference table - ---- - -## When to Use Each Document - -### 📋 Read FIX_TEMPLATE_ERROR_SUMMARY.md if: -- You want to understand what the problem was -- You need to verify the fix is applied -- You're debugging similar template errors -- You want the full error-to-solution journey - -### 📚 Read QWEB_BEST_PRACTICES.md if: -- You're writing new QWeb templates -- You want to avoid similar issues in future -- You need QWeb patterns and examples -- You're doing code review of templates -- You want to improve template code quality - -### 🔧 Read template file directly if: -- You need to modify the fixed code -- You want to see the exact syntax -- You're learning from working code - ---- - -## One-Page Summary - -### The Error -``` -Traceback (most recent call last): - File "...", line XX, in ... - ValueError: TypeError: 'NoneType' object is not callable - eskaera_shop_products template at line ... -``` - -### The Root Cause -QWeb's `t-attf-*` (template attribute) directives evaluate expressions in a way that doesn't handle chained `or` operators well when values are `None`. - -### The Fix -Replace inline operators with pre-computed safe variables using `t-set`: - -```xml - - - - - - -``` - -### The Result -✅ Template loads without errors -✅ All tests passing -✅ Safe pattern documented -✅ Best practices established - ---- - -## Quick Reference Cards - -### Safe Variable Pattern -```xml - -``` - -### Safe Nested Access -```xml - -``` - -### Safe Chained Fallback -```xml - -``` - ---- - -## Testing the Fix - -### Verification Steps -1. Module loads without parsing errors ✅ -2. Template compiles in ir.ui.view ✅ -3. Safe variables are present ✅ -4. All 85 unit tests pass ✅ -5. Docker services stable ✅ - -### How to Re-verify -```bash -# Check template in database -docker-compose exec -T odoo odoo shell -d odoo -c /etc/odoo/odoo.conf << 'SHELL' -template = env['ir.ui.view'].search([('name', '=', 'Eskaera Shop Products')]) -print('safe_display_price' in template.arch) # Should print True -SHELL -``` - ---- - -## Common Questions - -**Q: Why not just fix the template in code?** -A: We did - that's the fix! But the pattern is important for preventing future issues. - -**Q: Can I use this pattern in other templates?** -A: Yes! This is now the standard pattern for all Odoo templates in this project. - -**Q: What if I need more complex logic?** -A: You can chain multiple `t-set` statements, each computing one safe variable. - -**Q: Does this impact performance?** -A: No - `t-set` is evaluated once during template compilation, not on each render. - ---- - -## Related Resources - -- [Odoo QWeb Documentation](https://www.odoo.com/documentation/18.0/developer/reference/frontend/qweb.html) -- [Odoo Template Reference](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) - ---- - -## Navigation - -``` -docs/ -├── TEMPLATE_FIX_INDEX.md (YOU ARE HERE) -├── FIX_TEMPLATE_ERROR_SUMMARY.md (Complete analysis) -├── QWEB_BEST_PRACTICES.md (Development guide) -├── README.md (Project documentation index) -└── ... (other documentation) -``` - ---- - -**Last Updated**: 2026-02-16 -**Status**: ✅ Production Ready -**Version**: Odoo 18.0.20251208 diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index 2ee901e..262d0ee 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -388,40 +388,6 @@ class AplicoopWebsiteSale(WebsiteSale): ) return False - def _prepare_product_display_info(self, product, product_price_info): - """Prepare all display information for a product in a QWeb-safe way. - - This function pre-processes all values that might be None or require - conditional logic, so the template can use simple variable references - without complex expressions that confuse QWeb's parser. - - Args: - product: product.template record - product_price_info: dict with 'price', 'list_price', etc. - - Returns: - dict with all pre-processed display values ready for template - """ - # Safety: Get price, ensure it's a float - price_data = product_price_info.get(product.id, {}) - price = ( - price_data.get("price", product.list_price) - if price_data - else product.list_price - ) - price_safe = float(price) if price else 0.0 - - # Safety: Get UoM category name - uom_category_name = "" - if product.uom_id: - if product.uom_id.category_id: - uom_category_name = product.uom_id.category_id.name or "" - - return { - "display_price": price_safe, - "safe_uom_category": uom_category_name, - } - def _validate_confirm_request(self, data): """Validate all requirements for confirm order request. @@ -1063,15 +1029,6 @@ class AplicoopWebsiteSale(WebsiteSale): "tax_included": False, } - # Prepare display info for each product (QWeb-safe: all values pre-processed) - # This ensures the template can use simple variable references without complex conditionals - product_display_info = {} - for product in products: - display_info = self._prepare_product_display_info( - product, product_price_info - ) - product_display_info[product.id] = display_info - # Calculate available tags with product count (only show tags that are actually used and visible) # Build a dict: {tag_id: {'id': tag_id, 'name': tag_name, 'count': num_products}} available_tags_dict = {} @@ -1142,7 +1099,6 @@ class AplicoopWebsiteSale(WebsiteSale): "day_names": self._get_day_names(env=request.env), "product_supplier_info": product_supplier_info, "product_price_info": product_price_info, - "product_display_info": product_display_info, "labels": labels, "labels_json": json.dumps(labels, ensure_ascii=False), "lazy_loading_enabled": lazy_loading_enabled, @@ -1269,14 +1225,6 @@ class AplicoopWebsiteSale(WebsiteSale): "published_tags": published_tags, } - # Prepare display info for each product (QWeb-safe: all values pre-processed) - product_display_info = {} - for product in products_page: - display_info = self._prepare_product_display_info( - product, product_price_info - ) - product_display_info[product.id] = display_info - # Get labels labels = self.get_checkout_labels() @@ -1284,12 +1232,10 @@ class AplicoopWebsiteSale(WebsiteSale): return request.render( "website_sale_aplicoop.eskaera_shop_products", { - "group_order": group_order, "products": products_page, "filtered_product_tags": filtered_products, "product_supplier_info": product_supplier_info, "product_price_info": product_price_info, - "product_display_info": product_display_info, "labels": labels, "has_next": has_next, "next_page": page + 1, diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index cda9359..8719540 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -1167,7 +1167,7 @@ />
- -