Skip to content

Coding Standards

Style guidelines for JDX Odoo modules and Field PWA development.

Python (Odoo Modules)

General Rules

  • Follow PEP 8 style guide
  • Maximum line length: 120 characters
  • Use 4 spaces for indentation (no tabs)
  • UTF-8 encoding for all files

Imports

# Standard library
import logging
from datetime import datetime

# Third-party
import requests

# Odoo
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError

# Local
from .utils import helper_function

Order: Standard library → Third-party → Odoo → Local

Model Definitions

class ResPartner(models.Model):
    _inherit = 'res.partner'
    _description = 'Partner Extension'

    # Fields (ordered by type)
    # 1. Basic fields
    custom_code = fields.Char(string='Custom Code', size=20)
    is_vip = fields.Boolean(string='VIP Customer', default=False)
    credit_limit = fields.Float(string='Credit Limit', digits=(12, 2))

    # 2. Relational fields
    sales_rep_id = fields.Many2one('res.users', string='Sales Rep')
    tag_ids = fields.Many2many('res.partner.category', string='Tags')

    # 3. Computed fields
    total_orders = fields.Integer(compute='_compute_total_orders', store=True)

    # Constraints
    _sql_constraints = [
        ('custom_code_unique', 'UNIQUE(custom_code)', 'Custom code must be unique!')
    ]

    @api.constrains('credit_limit')
    def _check_credit_limit(self):
        for record in self:
            if record.credit_limit < 0:
                raise ValidationError("Credit limit cannot be negative.")

    # Compute methods
    @api.depends('sale_order_ids')
    def _compute_total_orders(self):
        for record in self:
            record.total_orders = len(record.sale_order_ids)

    # CRUD overrides
    @api.model
    def create(self, vals):
        if not vals.get('custom_code'):
            vals['custom_code'] = self.env['ir.sequence'].next_by_code('res.partner.custom')
        return super().create(vals)

    # Business methods
    def action_mark_vip(self):
        """Mark selected partners as VIP."""
        self.write({'is_vip': True})

Method Order in Models

  1. Private attributes (_name, _inherit, _description)
  2. Fields definitions
  3. SQL constraints
  4. Python constraints (@api.constrains)
  5. Compute methods
  6. Onchange methods
  7. CRUD overrides (create, write, unlink)
  8. Action methods
  9. Business methods

Naming Conventions

Type Convention Example
Model dot.separated sale.order.line
Field snake_case total_amount
Method snake_case compute_total
Compute method _compute_* _compute_total
Onchange method _onchange_* _onchange_partner_id
Action method action_* action_confirm
Private method _* _prepare_invoice

Docstrings

def calculate_discount(self, amount, percentage):
    """Calculate discount amount.

    Args:
        amount (float): Original amount
        percentage (float): Discount percentage (0-100)

    Returns:
        float: Discounted amount

    Raises:
        ValidationError: If percentage is out of range
    """
    if not 0 <= percentage <= 100:
        raise ValidationError("Percentage must be between 0 and 100")
    return amount * (1 - percentage / 100)

Logging

_logger = logging.getLogger(__name__)

# Usage
_logger.debug("Processing partner: %s", partner.name)
_logger.info("Order %s confirmed", order.name)
_logger.warning("Credit limit exceeded for %s", partner.name)
_logger.error("Failed to send SMS: %s", str(e))

Python (Flask PWA)

Project Structure

pwa/
├── app.py              # Application factory
├── config.py           # Configuration
├── routes/
│   ├── __init__.py
│   ├── auth.py         # Authentication routes
│   ├── jobs.py         # Jobs blueprint
│   └── api.py          # API endpoints
├── services/
│   ├── odoo.py         # Odoo API client
│   └── s3.py           # S3 service
├── templates/
└── static/

Flask Patterns

# Blueprint definition
from flask import Blueprint, render_template, jsonify

jobs_bp = Blueprint('jobs', __name__, url_prefix='/jobs')

@jobs_bp.route('/')
def list_jobs():
    """List active jobs for current user."""
    try:
        jobs = odoo_service.get_jobs(current_user.id)
        return render_template('jobs/list.html', jobs=jobs)
    except OdooAPIError as e:
        flash(f"Error loading jobs: {e}", 'error')
        return redirect(url_for('dashboard.index'))

@jobs_bp.route('/api/jobs')
def api_list_jobs():
    """API endpoint for jobs list."""
    jobs = odoo_service.get_jobs(current_user.id)
    return jsonify({'jobs': jobs})

Error Handling

from flask import jsonify

@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(500)
def server_error(error):
    app.logger.error(f"Server error: {error}")
    return jsonify({'error': 'Internal server error'}), 500

JavaScript

General Rules

  • ES6+ syntax
  • Use const by default, let when reassignment needed
  • No var
  • Semicolons required
  • Single quotes for strings

Code Style

// Constants
const API_BASE_URL = '/api';
const MAX_RETRIES = 3;

// Functions
async function fetchJobs() {
  try {
    const response = await fetch(`${API_BASE_URL}/jobs`);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Failed to fetch jobs:', error);
    throw error;
  }
}

// Event handlers
document.getElementById('submit-btn').addEventListener('click', async (e) => {
  e.preventDefault();
  await submitForm();
});

// Classes
class JobManager {
  constructor(apiUrl) {
    this.apiUrl = apiUrl;
    this.jobs = [];
  }

  async loadJobs() {
    this.jobs = await fetchJobs();
    this.render();
  }

  render() {
    const container = document.getElementById('jobs-container');
    container.innerHTML = this.jobs.map(job => this.renderJob(job)).join('');
  }

  renderJob(job) {
    return `
      <div class="job-card" data-id="${job.id}">
        <h3>${job.name}</h3>
        <p>${job.status}</p>
      </div>
    `;
  }
}

Offline Handling (PWA)

// Check online status
function isOnline() {
  return navigator.onLine;
}

// Queue actions for offline
const offlineQueue = [];

async function saveJob(jobData) {
  if (isOnline()) {
    return await fetch('/api/jobs', {
      method: 'POST',
      body: JSON.stringify(jobData)
    });
  } else {
    offlineQueue.push({ type: 'saveJob', data: jobData });
    localStorage.setItem('offlineQueue', JSON.stringify(offlineQueue));
    showNotification('Saved offline. Will sync when online.');
  }
}

// Sync when back online
window.addEventListener('online', async () => {
  const queue = JSON.parse(localStorage.getItem('offlineQueue') || '[]');
  for (const item of queue) {
    await processQueueItem(item);
  }
  localStorage.removeItem('offlineQueue');
});

HTML/CSS

HTML Structure

<!-- Use semantic elements -->
<header class="app-header">
  <nav class="main-nav">...</nav>
</header>

<main class="content">
  <section class="jobs-list">
    <h1>Active Jobs</h1>
    <article class="job-card">...</article>
  </section>
</main>

<footer class="app-footer">...</footer>

CSS Naming (BEM)

/* Block */
.job-card { }

/* Element */
.job-card__title { }
.job-card__status { }

/* Modifier */
.job-card--urgent { }
.job-card--completed { }

SQL

Safe Queries in Odoo

# GOOD - Parameterized query
self.env.cr.execute("""
    SELECT id, name FROM res_partner
    WHERE company_id = %s AND active = %s
""", (company_id, True))

# BAD - SQL injection risk
self.env.cr.execute(f"""
    SELECT * FROM res_partner WHERE name = '{user_input}'
""")

Use ORM When Possible

# Prefer ORM
partners = self.env['res.partner'].search([
    ('company_id', '=', company_id),
    ('active', '=', True)
])

# Use SQL only for performance-critical queries

File Headers

Python Files

# -*- coding: utf-8 -*-
# Part of JDX Odoo. See LICENSE file for full copyright.

"""
Module description here.
"""

JavaScript Files

/**
 * Module description
 * @module jobs
 */

Code Review Checklist

  • Follows naming conventions
  • Has appropriate docstrings/comments
  • No hardcoded values (use config/constants)
  • Proper error handling
  • No SQL injection vulnerabilities
  • Logging for important operations
  • Tests added for new functionality