bemade-addons/odoo_to_odoo_sync/models/sync_observer.py
mathis b792e43873 [IMP] Odoo Sync Modules: Major Enhancements and Fixes
- 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
2025-08-15 08:40:17 -04:00

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")