- Added comprehensive README files for all three sync modules - Implemented advanced logging system with multiple log levels (DEBUG, INFO, WARNING, ERROR) - Added automatic sensitive data masking for security in log payloads - Implemented log retention policy (90 days local, 7 years in AWS S3 Glacier) - Added AWS S3 integration for long-term log archiving - Fixed validation error with bemade_instance_id in sync model creation - Refactored model inheritance structure from _inherit to _inherits for proper delegation - Fixed duplicate sync model prevention logic - Restored Test Connection button in instance view - Fixed connection test logging to properly record success/failure - Fixed queue creation for connection test logs - Removed unnecessary Synchronized Module tab from odoo_to_odoo_bemade - Added database indexing on create_date for performance optimization - Fixed foreign key constraint violations in field mappings creation - Enhanced error handling throughout the synchronization process
168 lines
6.6 KiB
Python
168 lines
6.6 KiB
Python
# Copyright 2025 Bemade
|
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.html)
|
|
|
|
"""Model Change Observer System.
|
|
|
|
This module implements the observer pattern to detect changes in Odoo models
|
|
and trigger synchronization operations. It hooks into the create, write, and
|
|
unlink methods of models to detect changes and queue them for synchronization.
|
|
"""
|
|
|
|
import logging
|
|
from functools import wraps
|
|
|
|
from odoo import api, models, tools
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# Global registry to avoid duplicate patching
|
|
PATCHED_MODELS = set()
|
|
|
|
class SyncObserver:
|
|
"""Observer class for model changes.
|
|
|
|
This class provides methods to patch standard Odoo methods (create, write, unlink)
|
|
to detect changes and trigger synchronization operations.
|
|
"""
|
|
|
|
@staticmethod
|
|
def _observe_create(original_method):
|
|
"""Observe the create method of a model.
|
|
|
|
Args:
|
|
original_method: The original create method to wrap
|
|
|
|
Returns:
|
|
function: Wrapped create method with synchronization logic
|
|
"""
|
|
@wraps(original_method)
|
|
def wrapper(self, vals):
|
|
# Call the original method first
|
|
result = original_method(self, vals)
|
|
|
|
# Debug log
|
|
_logger.debug("[SYNC DEBUG] Create called for model: %s", self._name)
|
|
|
|
# Check if this model is configured for synchronization
|
|
# Skip synchronization for models in the odoo_to_odoo_sync module to prevent recursive loops
|
|
if not self.env.context.get('no_sync') and not self._name.startswith('odoo.sync.'):
|
|
try:
|
|
_logger.debug("[SYNC DEBUG] Checking sync model for %s", self._name)
|
|
sync_model = self.env['odoo.sync.model'].sudo().search([
|
|
('model_id.model', '=', self._name),
|
|
('active', '=', True)
|
|
])
|
|
|
|
_logger.debug("[SYNC DEBUG] Found sync models: %s", sync_model)
|
|
|
|
if sync_model:
|
|
# Get the sync manager
|
|
sync_manager = self.env['odoo.sync.manager'].sudo()
|
|
|
|
# Queue the synchronization (Notify Change -> Create SyncRecord in sequence diagram)
|
|
for record in result:
|
|
_logger.info("[SYNC OBSERVER] Notify Change: create for record %s (model %s)", record.id, self._name)
|
|
sync_manager._queue_sync(record, 'create')
|
|
else:
|
|
_logger.info("[SYNC DEBUG] No sync model found for %s", self._name)
|
|
|
|
except Exception as e:
|
|
_logger.error("Error in sync observer (create): %s", str(e))
|
|
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
@staticmethod
|
|
def _observe_write(original_method):
|
|
"""Observe the write method of a model.
|
|
|
|
Args:
|
|
original_method: The original write method to wrap
|
|
|
|
Returns:
|
|
function: Wrapped write method with synchronization logic
|
|
"""
|
|
@wraps(original_method)
|
|
def wrapper(self, vals):
|
|
# Call the original method first
|
|
result = original_method(self, vals)
|
|
|
|
# Check if this model is configured for synchronization
|
|
if not self.env.context.get('no_sync') and not self._name.startswith('odoo.sync.'):
|
|
try:
|
|
sync_model = self.env['odoo.sync.model'].sudo().search([
|
|
('model_id.model', '=', self._name),
|
|
('active', '=', True)
|
|
])
|
|
|
|
if sync_model:
|
|
# Get the sync manager
|
|
sync_manager = self.env['odoo.sync.manager'].sudo()
|
|
|
|
# Queue the synchronization for each record
|
|
for record in self:
|
|
_logger.info("[SYNC OBSERVER] Notify Change: write for record %s (model %s)", record.id, self._name)
|
|
sync_manager._queue_sync(record, 'write', vals.keys())
|
|
|
|
except Exception as e:
|
|
_logger.error("Error in sync observer (write): %s", str(e))
|
|
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
@staticmethod
|
|
def _observe_unlink(original_method):
|
|
"""Observe the unlink method of a model.
|
|
|
|
Args:
|
|
original_method: The original unlink method to wrap
|
|
|
|
Returns:
|
|
function: Wrapped unlink method with synchronization logic
|
|
"""
|
|
@wraps(original_method)
|
|
def wrapper(self):
|
|
# Check if this model is configured for synchronization
|
|
if not self.env.context.get('no_sync') and not self._name.startswith('odoo.sync.'):
|
|
try:
|
|
sync_model = self.env['odoo.sync.model'].sudo().search([
|
|
('model_id.model', '=', self._name),
|
|
('active', '=', True)
|
|
])
|
|
|
|
if sync_model:
|
|
# Get the sync manager
|
|
sync_manager = self.env['odoo.sync.manager'].sudo()
|
|
|
|
# Queue the synchronization for each record before deletion
|
|
for record in self:
|
|
sync_manager._queue_sync(record, 'unlink')
|
|
|
|
except Exception as e:
|
|
_logger.error("Error in sync observer (unlink): %s", str(e))
|
|
|
|
# Call the original method after queueing
|
|
return original_method(self)
|
|
|
|
return wrapper
|
|
|
|
def patch_models():
|
|
"""Patch all models with synchronization observers.
|
|
|
|
This function is called during module initialization to patch
|
|
the create, write, and unlink methods of the BaseModel class.
|
|
"""
|
|
if models.BaseModel in PATCHED_MODELS:
|
|
return
|
|
|
|
# Patch the methods
|
|
models.BaseModel.create = SyncObserver._observe_create(models.BaseModel.create)
|
|
models.BaseModel.write = SyncObserver._observe_write(models.BaseModel.write)
|
|
models.BaseModel.unlink = SyncObserver._observe_unlink(models.BaseModel.unlink)
|
|
|
|
# Mark as patched
|
|
PATCHED_MODELS.add(models.BaseModel)
|
|
|
|
_logger.info("Model synchronization observers installed")
|