Initial commit
This commit is contained in:
357
skills/odoo-code-reviewer/SKILL.md
Normal file
357
skills/odoo-code-reviewer/SKILL.md
Normal file
@@ -0,0 +1,357 @@
|
||||
---
|
||||
name: odoo-code-reviewer
|
||||
description: Reviews Odoo 16.0 code for best practices, security issues, performance problems, and OCA guidelines compliance. This skill should be used when the user requests code review, such as "Review this code" or "Check this module for issues" or "Is this code optimized?" or "Security review needed for this module".
|
||||
---
|
||||
|
||||
# Odoo Code Reviewer
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides comprehensive code review for Odoo 16.0 modules, checking for security vulnerabilities, performance issues, OCA guideline compliance, and general best practices.
|
||||
|
||||
## Review Categories
|
||||
|
||||
### 1. Security Issues
|
||||
SQL injection, XSS vulnerabilities, improper sudo() usage, missing input validation.
|
||||
|
||||
### 2. Performance Problems
|
||||
N+1 queries, inefficient searches, unnecessary database operations.
|
||||
|
||||
### 3. OCA Guidelines Compliance
|
||||
Code style, structure, naming conventions, documentation.
|
||||
|
||||
### 4. Best Practices
|
||||
Proper API usage, error handling, logging, testing.
|
||||
|
||||
### 5. Maintainability
|
||||
Code organization, readability, documentation, modularity.
|
||||
|
||||
## Review Process
|
||||
|
||||
### Step 1: Identify Review Scope
|
||||
|
||||
Determine what to review:
|
||||
- Complete module
|
||||
- Specific model files
|
||||
- View files
|
||||
- Security configuration
|
||||
- Specific functionality
|
||||
|
||||
### Step 2: Systematic Review
|
||||
|
||||
Check each category systematically following the patterns below.
|
||||
|
||||
## Review Patterns
|
||||
|
||||
### Security Review Checklist
|
||||
|
||||
**1. SQL Injection Risk**
|
||||
```python
|
||||
# BAD - SQL injection vulnerability
|
||||
self.env.cr.execute("SELECT * FROM table WHERE id = %s" % record_id)
|
||||
|
||||
# GOOD - Parameterized query
|
||||
self.env.cr.execute("SELECT * FROM table WHERE id = %s", (record_id,))
|
||||
```
|
||||
|
||||
**2. XSS Vulnerabilities**
|
||||
```python
|
||||
# BAD - Unescaped HTML field
|
||||
description = fields.Char(string='Description')
|
||||
|
||||
# GOOD - Use Text or Html field with sanitization
|
||||
description = fields.Html(string='Description', sanitize=True)
|
||||
```
|
||||
|
||||
**3. Improper sudo() Usage**
|
||||
```python
|
||||
# BAD - sudo() without justification
|
||||
records = self.env['model'].sudo().search([])
|
||||
|
||||
# GOOD - Check permissions properly
|
||||
if self.env.user.has_group('base.group_system'):
|
||||
records = self.env['model'].search([])
|
||||
```
|
||||
|
||||
**4. Missing Input Validation**
|
||||
```python
|
||||
# BAD - No validation
|
||||
def process(self, value):
|
||||
return int(value)
|
||||
|
||||
# GOOD - Proper validation
|
||||
def process(self, value):
|
||||
if not value or not isinstance(value, (int, str)):
|
||||
raise ValueError('Invalid value')
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
raise ValidationError('Value must be a valid integer')
|
||||
```
|
||||
|
||||
### Performance Review Checklist
|
||||
|
||||
**1. N+1 Query Problem**
|
||||
```python
|
||||
# BAD - N+1 queries
|
||||
for order in orders:
|
||||
print(order.partner_id.name) # Database query for each iteration
|
||||
|
||||
# GOOD - Prefetch
|
||||
for order in orders:
|
||||
pass # partner_id prefetched automatically
|
||||
print([o.partner_id.name for o in orders])
|
||||
|
||||
# EVEN BETTER - Explicit prefetch
|
||||
orders = orders.with_prefetch(['partner_id'])
|
||||
```
|
||||
|
||||
**2. Inefficient Searches**
|
||||
```python
|
||||
# BAD - Search in loop
|
||||
for partner in partners:
|
||||
orders = self.env['sale.order'].search([('partner_id', '=', partner.id)])
|
||||
|
||||
# GOOD - Single search
|
||||
orders = self.env['sale.order'].search([('partner_id', 'in', partners.ids)])
|
||||
```
|
||||
|
||||
**3. Unnecessary Database Operations**
|
||||
```python
|
||||
# BAD - Multiple writes
|
||||
for line in lines:
|
||||
line.write({'processed': True})
|
||||
|
||||
# GOOD - Batch write
|
||||
lines.write({'processed': True})
|
||||
```
|
||||
|
||||
**4. Inefficient Computed Fields**
|
||||
```python
|
||||
# BAD - Not stored, recalculated every time
|
||||
total = fields.Float(compute='_compute_total')
|
||||
|
||||
# GOOD - Stored with proper depends
|
||||
total = fields.Float(compute='_compute_total', store=True)
|
||||
|
||||
@api.depends('line_ids.amount')
|
||||
def _compute_total(self):
|
||||
for record in self:
|
||||
record.total = sum(record.line_ids.mapped('amount'))
|
||||
```
|
||||
|
||||
### OCA Guidelines Checklist
|
||||
|
||||
**1. Naming Conventions**
|
||||
- Module: `snake_case` (e.g., `stock_batch_tracking`)
|
||||
- Model: `model.name` (e.g., `stock.batch`)
|
||||
- Fields: `snake_case`
|
||||
- Methods: `snake_case` with verb prefix
|
||||
- Private methods: `_method_name`
|
||||
|
||||
**2. Import Order**
|
||||
```python
|
||||
# Standard library
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
# Third-party
|
||||
from lxml import etree
|
||||
|
||||
# Odoo
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import float_compare
|
||||
```
|
||||
|
||||
**3. Docstrings**
|
||||
```python
|
||||
class Model(models.Model):
|
||||
"""Brief description of model."""
|
||||
|
||||
_name = 'model.name'
|
||||
_description = 'Model Description'
|
||||
|
||||
def method(self, param):
|
||||
"""Brief description of method.
|
||||
|
||||
Args:
|
||||
param: Description of parameter
|
||||
|
||||
Returns:
|
||||
Description of return value
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
**4. Field Attributes**
|
||||
```python
|
||||
# GOOD - Complete field definition
|
||||
name = fields.Char(
|
||||
string='Name',
|
||||
required=True,
|
||||
index=True,
|
||||
tracking=True,
|
||||
help='Detailed help text'
|
||||
)
|
||||
```
|
||||
|
||||
### Best Practices Checklist
|
||||
|
||||
**1. Error Handling**
|
||||
```python
|
||||
# BAD - Generic exception
|
||||
try:
|
||||
value = int(data)
|
||||
except:
|
||||
pass
|
||||
|
||||
# GOOD - Specific exception with logging
|
||||
try:
|
||||
value = int(data)
|
||||
except ValueError as e:
|
||||
_logger.error('Invalid data: %s', e)
|
||||
raise ValidationError('Please provide a valid number')
|
||||
```
|
||||
|
||||
**2. Logging**
|
||||
```python
|
||||
# BAD - Print statements
|
||||
print("Processing record", record.id)
|
||||
|
||||
# GOOD - Proper logging
|
||||
_logger.info('Processing record %s', record.id)
|
||||
_logger.debug('Record data: %s', record.read())
|
||||
```
|
||||
|
||||
**3. Method Decorators**
|
||||
```python
|
||||
# Ensure proper decorator usage
|
||||
@api.depends('field1', 'field2') # For computed fields
|
||||
def _compute_field(self): pass
|
||||
|
||||
@api.onchange('field1') # For onchange methods
|
||||
def _onchange_field(self): pass
|
||||
|
||||
@api.constrains('field1') # For constraints
|
||||
def _check_field(self): pass
|
||||
|
||||
@api.model # For class-level methods
|
||||
def create_from_ui(self, vals): pass
|
||||
```
|
||||
|
||||
**4. Transaction Safety**
|
||||
```python
|
||||
# BAD - Commit in method
|
||||
def method(self):
|
||||
self.process()
|
||||
self.env.cr.commit() # Don't do this!
|
||||
|
||||
# GOOD - Let Odoo handle transactions
|
||||
def method(self):
|
||||
self.process()
|
||||
# Transaction committed automatically
|
||||
```
|
||||
|
||||
## Review Output Format
|
||||
|
||||
Provide review results in this format:
|
||||
|
||||
### Critical Issues
|
||||
- **Security**: List any security vulnerabilities
|
||||
- **Data Loss Risk**: Operations that could cause data loss
|
||||
|
||||
### High Priority Issues
|
||||
- **Performance**: Major performance problems
|
||||
- **Incorrect Logic**: Business logic errors
|
||||
|
||||
### Medium Priority Issues
|
||||
- **OCA Compliance**: Guideline violations
|
||||
- **Code Quality**: Maintainability issues
|
||||
|
||||
### Low Priority Issues
|
||||
- **Style**: Minor style issues
|
||||
- **Documentation**: Missing or incomplete docs
|
||||
|
||||
### Recommendations
|
||||
- Suggested improvements
|
||||
- Best practice suggestions
|
||||
- Refactoring opportunities
|
||||
|
||||
## Common Anti-Patterns
|
||||
|
||||
1. **Using search() in loops**
|
||||
2. **Not using prefetch**
|
||||
3. **Missing translations** (`string` without `_()` for translatable text)
|
||||
4. **Hardcoded values** instead of configuration
|
||||
5. **Incorrect sudo() usage**
|
||||
6. **Missing input validation**
|
||||
7. **Poor error messages**
|
||||
8. **Inefficient computed fields**
|
||||
9. **Missing access rights**
|
||||
10. **No unit tests**
|
||||
|
||||
## Example Review
|
||||
|
||||
```python
|
||||
# CODE BEING REVIEWED
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
total_weight = fields.Float(compute='_compute_weight')
|
||||
|
||||
def _compute_weight(self):
|
||||
for order in self:
|
||||
weight = 0
|
||||
for line in order.order_line:
|
||||
product = self.env['product.product'].search([('id', '=', line.product_id.id)])
|
||||
weight += product.weight * line.product_uom_qty
|
||||
order.total_weight = weight
|
||||
```
|
||||
|
||||
**Review Findings:**
|
||||
|
||||
**HIGH - Performance Issues:**
|
||||
1. Unnecessary search in loop (line 10)
|
||||
- FIX: Use `line.product_id.weight` directly
|
||||
2. Not storing computed field
|
||||
- FIX: Add `store=True` and `@api.depends` decorator
|
||||
|
||||
**MEDIUM - Best Practices:**
|
||||
1. Missing `@api.depends` decorator
|
||||
- FIX: Add `@api.depends('order_line.product_id.weight', 'order_line.product_uom_qty')`
|
||||
2. Variable could be clearer
|
||||
- FIX: Rename `weight` to `total_weight`
|
||||
|
||||
**Improved Code:**
|
||||
```python
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
total_weight = fields.Float(
|
||||
string='Total Weight',
|
||||
compute='_compute_weight',
|
||||
store=True,
|
||||
help='Total weight of all order lines'
|
||||
)
|
||||
|
||||
@api.depends('order_line.product_id.weight', 'order_line.product_uom_qty')
|
||||
def _compute_weight(self):
|
||||
"""Compute total weight from order lines."""
|
||||
for order in self:
|
||||
order.total_weight = sum(
|
||||
line.product_id.weight * line.product_uom_qty
|
||||
for line in order.order_line
|
||||
)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### references/oca_guidelines.md
|
||||
Complete OCA (Odoo Community Association) coding guidelines for Odoo modules.
|
||||
|
||||
### references/security_checklist.md
|
||||
Comprehensive security checklist for Odoo development.
|
||||
|
||||
### references/performance_patterns.md
|
||||
Common performance patterns and anti-patterns with examples and fixes.
|
||||
Reference in New Issue
Block a user