def _run_now(self, cr, uid, trigger_id, context):
        logger = SmileDBLogger(cr.dbname, self._name, trigger_id, uid)

        # Get sequence in order to differentiate logs per run
        context.setdefault('pid_list', []).append(str(logger.pid).rjust(8, '0'))
        pid = '-'.join((str(x) for x in context['pid_list']))
        if not pid:
            logger.critical('Action Trigger failed: impossible to get a pid for dbname %s' % (cr.dbname))
            return

        # Get sequence in order to differentiate logs per run
        if context.get('test_mode', False):
            logger.info('[%s] Trigger in test mode' % (pid,))
        logger.debug('[%s] Trigger on %s' % (pid, context.get('trigger', 'manual')))
        logger.debug('[%s] Context: %s' % (pid, context))
        trigger = self.browse(cr, uid, trigger_id, context)

        # Filter objects
        filtered_object_ids = []
        try:
            filtered_object_ids = self._get_filtered_object_ids(cr, uid, trigger, context)
            logger.debug('[%s] Successful Objects Filtering: %s' % (pid, filtered_object_ids))
        except Exception, e:
            logger.exception('[%s] Objects Filtering failed: %s' % (pid, _get_exception_message(e)))
            if trigger.exception_handling == 'continue' or trigger.exception_warning == 'none':
                return True
            else:
                cr.rollback()
                logger.time_info("[%s] Transaction rolled back" % (pid,))
                if trigger.exception_warning == 'custom':
                    raise orm.except_orm(_('Error'), _('%s\n[Pid: %s]') % (trigger.exception_message, pid))
                elif trigger.exception_warning == 'native':
                    raise orm.except_orm(_('Error'), _('%s\n[Pid: %s]') % (_get_exception_message(e), pid))
示例#2
0
    def _run_now(self, cr, uid, trigger_id, context):
        logger = SmileDBLogger(cr.dbname, self._name, trigger_id, uid)

        # Get sequence in order to differentiate logs per run
        context.setdefault('pid_list',
                           []).append(str(logger.pid).rjust(8, '0'))
        pid = '-'.join((str(x) for x in context['pid_list']))
        if not pid:
            logger.critical(
                'Action Trigger failed: impossible to get a pid for dbname %s'
                % (cr.dbname))
            return

        # Get sequence in order to differentiate logs per run
        if context.get('test_mode', False):
            logger.info('[%s] Trigger in test mode' % (pid, ))
        logger.debug('[%s] Trigger on %s' %
                     (pid, context.get('trigger', 'manual')))
        logger.debug('[%s] Context: %s' % (pid, context))
        trigger = self.browse(cr, uid, trigger_id, context)

        # Filter objects
        filtered_object_ids = []
        try:
            filtered_object_ids = self._get_filtered_object_ids(
                cr, uid, trigger, context)
            logger.debug('[%s] Successful Objects Filtering: %s' %
                         (pid, filtered_object_ids))
        except Exception, e:
            logger.exception('[%s] Objects Filtering failed: %s' %
                             (pid, _get_exception_message(e)))
            if trigger.exception_handling == 'continue' or trigger.exception_warning == 'none':
                return True
            else:
                cr.rollback()
                logger.time_info("[%s] Transaction rolled back" % (pid, ))
                if trigger.exception_warning == 'custom':
                    raise orm.except_orm(
                        _('Error'),
                        _('%s\n[Pid: %s]') % (trigger.exception_message, pid))
                elif trigger.exception_warning == 'native':
                    raise orm.except_orm(
                        _('Error'),
                        _('%s\n[Pid: %s]') % (_get_exception_message(e), pid))
 def _run_action_in_new_thread(self, dbname, uid, action, object_ids, context, logger, pid):
     try:
         db = pooler.get_db(dbname)
     except Exception:
         return
     cr = db.cursor()
     try:
         self._run_action_for_object_ids(cr, uid, action, object_ids, context)
         cr.commit()
         logger.time_info('[%s] Successful Action: %s - Objects: %s,%s' % (pid, action.name, action.model_id.model, object_ids))
     except Exception, e:
         logger.exception('[%s] Action failed: %s - %s' % (pid, action.name, _get_exception_message(e)))
示例#4
0
 def _run_action_in_new_thread(self, dbname, uid, action, object_ids,
                               context, logger, pid):
     try:
         db = pooler.get_db(dbname)
     except Exception:
         return
     cr = db.cursor()
     try:
         self._run_action_for_object_ids(cr, uid, action, object_ids,
                                         context)
         cr.commit()
         logger.time_info(
             '[%s] Successful Action: %s - Objects: %s,%s' %
             (pid, action.name, action.model_id.model, object_ids))
     except Exception, e:
         logger.exception('[%s] Action failed: %s - %s' %
                          (pid, action.name, _get_exception_message(e)))
示例#5
0
class SartreTrigger(orm.Model):
    _name = 'sartre.trigger'
    _description = 'Action Trigger'
    _decorated_methods = {}

    def _get_trigger_date_type(self, cr, uid, ids, name, args, context=None):
        res = {}
        if isinstance(ids, (int, long)):
            ids = [ids]
        for trigger in self.browse(cr, uid, ids, context):
            if trigger.on_date_type_display1 == 'other_date':
                res[trigger.id] = trigger.on_date_type_display2_id.name
            else:
                res[trigger.id] = trigger.on_date_type_display1
        return res

    def onchange_model_id(self,
                          cr,
                          uid,
                          ids,
                          model_id,
                          on_function,
                          on_other,
                          context=None):
        """Dynamic domain for the field on_function_field_id"""
        res = {'value': {}}
        if model_id:
            model = self.pool.get('ir.model').read(cr, uid, model_id,
                                                   ['model'])['model']
            obj = self.pool.get(model)
            if on_function:
                function_fields = [
                    field for field in obj._columns
                    if isinstance(obj._columns[field], (fields.function,
                                                        fields.related,
                                                        fields.property))
                ]
                res['domain'] = {
                    'on_function_field_id':
                    "[('model_id', '=', %s),('name', 'in', %s)]" %
                    (model_id, function_fields)
                }
            if on_other:
                method_names = [
                    attr for attr in dir(obj)
                    if inspect.ismethod(getattr(obj, attr))
                ]
                model_methods_obj = self.pool.get('ir.model.methods')
                model_methods_ids = model_methods_obj.search(
                    cr, uid, [('model_id', '=', model_id),
                              ('name', 'in', method_names)])
                existing_method_names = ['create', 'write', 'unlink']
                existing_method_names += [
                    method['name'] for method in model_methods_obj.read(
                        cr, uid, model_methods_ids, ['name'])
                ]
                for method in method_names:
                    method_args = inspect.getargspec(getattr(obj, method))[0]
                    if method not in existing_method_names and not method.startswith(
                            '__') and ('ids' in method_args
                                       or 'id' in method_args):
                        model_methods_obj.create(cr, uid, {
                            'name': method,
                            'model_id': model_id
                        })
        return res

    def _get_logs(self, cr, uid, ids, name, args, context=None):
        res = {}
        for trigger_id in ids:
            res[trigger_id] = self.pool.get('smile.log').search(
                cr,
                uid, [
                    ('model_name', '=', 'sartre.trigger'),
                    ('res_id', '=', trigger_id),
                ],
                context=context)
        return res

    def _is_dynamic_field(self, filter_field, model_obj):
        if filter_field:
            # Old values
            if filter_field.startswith('OLD_'):
                return True
            # Function field without fnct_search and not stored
            obj = model_obj
            for field in filter_field.split('.'):
                if field in obj._columns:
                    item_field = obj._columns[field]
                    if isinstance(item_field, fields.function):
                        if not item_field._fnct_search and not item_field.store:
                            return True
                    obj = self.pool.get(item_field._obj)
        return False

    def _is_dynamic_filter(self, cr, uid, item, model_obj, context=None):
        if isinstance(item, tuple) and model_obj:
            # First element
            if self._is_dynamic_field(item[0], model_obj):
                return True
            # Python operator
            operator = self.pool.get('sartre.operator')._get_operator(
                cr, uid, item[1])
            if operator and operator[0] and operator[
                    0].native_operator == 'none':
                return True
            # Dynamic comparison value
            if re.match('(\[\[.+?\]\])', str(item[2]) or ''):
                return True
        return False

    def _is_python_domain(self, cr, uid, ids, name, args, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]
        res = {}.fromkeys(ids, False)
        for trigger in self.browse(cr, uid, ids, context):
            model_obj = self.pool.get(trigger.model_id.model)
            if not model_obj:
                continue
            if trigger.on_date and trigger.on_date_type_display1 == 'other_date':
                #other date is function field without fnct_search and not stored
                if self._is_dynamic_field(trigger.on_date_type, model_obj):
                    res[trigger.id] = True
                    continue
            domain = eval(trigger.domain_force or '[]')
            if not domain:
                for filter_ in trigger.filter_ids:
                    domain.extend(eval(filter_.domain or '[]'))
            for item in domain:
                if self._is_dynamic_filter(cr, uid, item, model_obj, context):
                    res[trigger.id] = True
                    break
        return res

    def _get_trigger_ids_from_filters(self, cr, uid, ids, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]
        return list(
            set([
                filter_['trigger_id'][0]
                for filter_ in self.read(cr, uid, ids, ['trigger_id'], context)
            ]))

    def _get_trigger_ids_from_operators(self, cr, uid, ids, context=None):
        return self.pool.get('sartre.trigger').search(
            cr, uid, [], context={'active_test': False})

    def _dummy(self, cr, uid, ids, name, arg, context=None):
        return {}.fromkeys(isinstance(ids, (int, long)) and [ids] or ids, '')

    def _search_pid(self, cr, uid, obj, name, domain, context=None):
        trigger_ids = []
        if domain and isinstance(domain[0], tuple):
            value = domain[0][2]
            try:
                value = int(value)
            except ValueError:
                raise orm.except_orm(
                    _('Error !'),
                    _('Pid search field: please enter an integer'))
            log_obj = self.pool.get('smile.log')
            log_ids = log_obj.search(cr,
                                     uid,
                                     [('pid', '=', value),
                                      ('model_name', '=', 'sartre.trigger')],
                                     context=context)
            trigger_ids = [
                log['res_id']
                for log in log_obj.read(cr, uid, log_ids, ['res_id'], context)
            ]
        return [('id', 'in', trigger_ids)]

    _columns = {
        'name':
        fields.char("Name", size=64, required=True),
        'model_id':
        fields.many2one('ir.model',
                        'Object',
                        domain=[('osv_memory', '=', False)],
                        required=True,
                        ondelete='cascade'),
        'model':
        fields.related('model_id', 'model', type='char', string="Model"),
        'active':
        fields.boolean("Active"),
        'on_create':
        fields.boolean("Creation"),
        'on_write':
        fields.boolean("Update"),
        'on_unlink':
        fields.boolean("Deletion"),
        'on_function':
        fields.boolean("Function Field"),
        'on_function_type':
        fields.selection([('set', 'Manually'), ('get', 'Automatically'),
                          ('both', 'Both')],
                         "updated",
                         size=16),
        'on_function_field_id':
        fields.many2one('ir.model.fields',
                        "Function field",
                        domain="[('model_id', '=', model_id)]",
                        help="Function, related or property field"),
        'on_other':
        fields.boolean(
            "Other method",
            help=
            "Only methods with an argument 'id' or 'ids' in their signatures"),
        'on_other_method_id':
        fields.many2one('ir.model.methods',
                        "Object method",
                        domain="[('model_id', '=', model_id)]"),
        'on_other_method':
        fields.related('on_other_method_id',
                       'name',
                       type='char',
                       string='Method'),
        'on_client_action':
        fields.boolean("Client Action"),
        'on_client_action_id':
        fields.many2one('ir.values', "Client Action"),
        'on_client_action_type':
        fields.selection([('client_print_multi', 'Report'),
                          ('client_action_multi', 'Action'),
                          ('client_action_relate', 'Link')], "Type"),
        'on_date':
        fields.boolean("Date"),
        'on_date_type':
        fields.function(_get_trigger_date_type,
                        method=True,
                        type='char',
                        size=64,
                        string='Trigger Date Type',
                        store=True),
        'on_date_type_display1':
        fields.selection([('create_date', 'Creation Date'),
                          ('write_date', 'Update Date'),
                          ('other_date', 'Other Date')],
                         'Trigger Date Type 1',
                         size=16),
        'on_date_type_display2_id':
        fields.many2one(
            'ir.model.fields',
            'Trigger Date Type 2',
            domain=
            "[('ttype','in',['date','datetime']),('model_id','=',model_id)]"),
        'on_date_range':
        fields.integer('Delay'),
        'on_date_range_type':
        fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'),
                          ('work_days', 'Work Days'), ('days', 'Days'),
                          ('weeks', 'Weeks'), ('months', 'Months')],
                         'Delay Type'),
        'on_date_range_operand':
        fields.selection([('after', 'After'), ('before', 'Before')],
                         'Delay Operand'),
        'interval_number':
        fields.integer('Interval Number'),
        'interval_type':
        fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'),
                          ('work_days', 'Work Days'), ('days', 'Days'),
                          ('weeks', 'Weeks'), ('months', 'Months')],
                         'Interval Unit'),
        'nextcall':
        fields.datetime("Next Call"),
        'filter_ids':
        fields.one2many(
            'sartre.filter',
            'trigger_id',
            "Filters",
            help="The trigger is satisfied if all filters are True"),
        'domain_force':
        fields.char(
            'Force Domain',
            size=256,
            help="Warning: using a Force domain makes all other filters useless"
        ),
        'action_ids':
        fields.many2many('ir.actions.server',
                         'sartre_trigger_server_action_rel', 'trigger_id',
                         'action_id', "Actions"),
        'force_actions_execution':
        fields.boolean('Force actions execution when resources list is empty'),
        'executions_max_number':
        fields.integer('Max executions',
                       help="Number of time actions are runned, "
                       "indicates that actions will always be executed"),
        'log_ids':
        fields.function(_get_logs,
                        method=True,
                        type='one2many',
                        relation='smile.log',
                        string="Logs"),
        'test_mode':
        fields.boolean('Test Mode'),
        'exception_handling':
        fields.selection([('continue', 'Ignore actions in exception'),
                          ('rollback', 'Rollback transaction')],
                         'Exception Handling',
                         required=True),
        'exception_warning':
        fields.selection([('custom', 'Custom'), ('native', 'Native'),
                          ('none', 'None')],
                         'Exception Warning',
                         required=True),
        'exception_message':
        fields.char('Exception Message',
                    size=256,
                    translate=True,
                    required=True),
        'python_domain':
        fields.function(_is_python_domain,
                        method=True,
                        type='boolean',
                        string='Python domain',
                        store={
                            'sartre.trigger':
                            (lambda self, cr, uid, ids, context=None: ids,
                             ['domain_force', 'filter_ids'], 10),
                            'sartre.filter':
                            (_get_trigger_ids_from_filters, None, 10),
                            'sartre.operator':
                            (_get_trigger_ids_from_operators, None, 10),
                        }),
        'pid_search':
        fields.function(_dummy,
                        fnct_search=_search_pid,
                        method=True,
                        type='char',
                        string='Pid'),
        'category_id':
        fields.many2one('sartre.category', 'Category'),
    }

    def _get_default_category_id(self, cr, uid, context=None):
        category_id = False
        try:
            dummy, category_id = self.pool.get(
                'ir.model.data').get_object_reference(
                    cr, uid, 'smile_action_trigger',
                    'sartre_category_default0')
        except Exception:
            pass
        return category_id

    _defaults = {
        'active': True,
        'on_function_type': 'both',
        'on_client_action_type': 'client_action_multi',
        'on_date_type_display1': 'create_date',
        'on_date_range': 2,
        'on_date_range_type': 'days',
        'on_date_range_operand': 'after',
        'interval_number': 1,
        'interval_type': 'hours',
        'nextcall': lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        'exception_handling': 'continue',
        'exception_warning': 'custom',
        'exception_message':
        'Action failed. Please, contact your administrator.',
        'category_id': _get_default_category_id,
    }

    def create_client_action(self, cr, uid, ids, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]
        for trigger in self.browse(cr, uid, ids, context):
            if not trigger.on_client_action_id:
                vals = {
                    'name':
                    trigger.name,
                    'model_id':
                    trigger.model_id.id,
                    'state':
                    'code',
                    'code':
                    "self.pool.get('sartre.trigger').run_now(cr, uid, %d, context)"
                    % (trigger.id, ),
                }
                server_action_id = self.pool.get('ir.actions.server').create(
                    cr, uid, vals, context)
                vals2 = {
                    'name': trigger.name,
                    'object': True,
                    'model_id': trigger.model_id.id,
                    'model': trigger.model,
                    'key2': trigger.on_client_action_type,
                    'value': 'ir.actions.server,%d' % server_action_id,
                }
                client_action_id = self.pool.get('ir.values').create(
                    cr, uid, vals2, context)
                trigger.write({'on_client_action_id': client_action_id})
        return True

    @tools.cache(skiparg=3)
    def get_trigger_ids(self, cr, uid, model, method):
        domain = [('model', '=', model)]
        if method in ['create', 'write', 'unlink', 'function']:
            domain.append(('on_' + method, '=', True))
        else:
            domain.append(('on_other_method', '=', method))
        return self.search(cr, uid, domain, context={'active_test': True})

    @tools.cache(skiparg=3)
    def get_fields_to_save_old_values(self, cr, uid, ids):
        res = []
        if isinstance(id, (int, long)):
            ids = [ids]
        for trigger in self.browse(cr, uid, ids, context={'active_test':
                                                          True}):
            for method in ['write', 'unlink', 'function', 'other']:
                if getattr(trigger, 'on_' + method):
                    for filter_ in trigger.filter_ids:
                        domain = eval(filter_.domain)
                        if isinstance(domain, list):
                            for condition in domain:
                                if condition[0].startswith('OLD_'):
                                    res.append(condition[0].replace(
                                        'OLD_', '').split('.')[0])
        return list(set(res))

    def _decorate_trigger_method(self, method):
        method_name = method.__name__
        if method_name == 'trigger_method':
            return
        object_class = method.im_class
        object_name = getattr(object_class, '_name',
                              None) or object_class.__name__
        if method_name in self._decorated_methods.get(object_name, {}):
            original_class = self._decorated_methods[object_name][method_name][
                'original_class']
            original_method = self._decorated_methods[object_name][
                method_name]['original_method']
            setattr(original_class, method_name, original_method)
        self._decorated_methods.setdefault(object_name, {})[method_name] = {
            'original_class': object_class,
            'original_method': method
        }
        setattr(object_class, method_name, sartre_decorator(method))

    def decorate_trigger_methods(self, cr):
        methods_to_decorate = []
        trigger_ids = self.search(cr, 1, [], context={'active_test': True})
        for trigger in self.browse(cr, 1, trigger_ids):
            for orm_method in ('create', 'write', 'unlink'):
                if getattr(trigger, 'on_%s' % orm_method):
                    methods_to_decorate.append(getattr(orm.Model, orm_method))
            if trigger.on_function:
                for field_method in ('get', 'set'):
                    if trigger.on_function_type in (field_method, 'both'):
                        methods_to_decorate.append(
                            getattr(fields.function, field_method))
            if trigger.on_other:
                class_obj = self.pool.get(trigger.model_id.model)
                if not class_obj:
                    continue
                m_class = class_obj.__class__
                m_name = trigger.on_other_method
                if m_name and hasattr(m_class, m_name):
                    methods_to_decorate.append(getattr(m_class, m_name))
        methods_to_decorate = list(set(methods_to_decorate))
        for method in methods_to_decorate:
            self._decorate_trigger_method(method)
        return True

    def cache_restart(self, cr):
        self.get_trigger_ids.clear_cache(self)
        self.get_fields_to_save_old_values.clear_cache(self)
        self.decorate_trigger_methods(cr)
        return True

    def __init__(self, pool, cr):
        super(SartreTrigger, self).__init__(pool, cr)
        cr.execute("SELECT relname FROM pg_class WHERE relname=%s",
                   (self._table, ))
        if cr.fetchall():
            columns = [
                column for column in self._columns
                if not self._columns[column]._type.endswith('2many') and (
                    not isinstance(self._columns[column], (
                        fields.function, fields.property,
                        fields.related)) or self._columns[column].store)
            ]
            cr.execute(
                "SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname=%s) AND attname IN %s",
                (self._table, tuple(columns)))
            res = cr.fetchall()
            if res and len(res) == len(columns):
                self.decorate_trigger_methods(cr)
        setattr(Registry, 'load', cache_restarter(getattr(Registry, 'load')))

    def create(self, cr, uid, vals, context=None):
        trigger_id = super(SartreTrigger, self).create(cr, uid, vals, context)
        self.cache_restart(cr)
        return trigger_id

    def write(self, cr, uid, ids, vals, context=None):
        res = super(SartreTrigger, self).write(cr, uid, ids, vals, context)
        self.cache_restart(cr)
        return res

    def unlink(self, cr, uid, ids, context=None):
        res = super(SartreTrigger, self).unlink(cr, uid, ids, context)
        self.cache_restart(cr)
        return res

    def _add_trigger_date_filter(self, cr, uid, trigger, context):
        """Build trigger date filter"""
        if trigger.on_date:
            datetime_format = '%Y-%m-%d %H:%M:%S'
            now = datetime.now()
            interval_number = trigger.on_date_range
            interval_type = str(trigger.on_date_range_type)
            interval_operand = trigger.on_date_range_operand
            # Update trigger next call
            try:
                nextcall = datetime.strptime(trigger.nextcall, datetime_format)
            except ValueError:  # To be compatible with the old version of this module
                nextcall = datetime.strptime(trigger.nextcall,
                                             datetime_format + '.%f')
            while nextcall <= now:
                nextcall += relativedelta(
                    **{str(trigger.interval_type): trigger.interval_number})
            self.write(cr, 1, trigger.id,
                       {'nextcall': nextcall.strftime(datetime_format)},
                       context)
            # Add datetime filter
            field = trigger.on_date_type
            limit_date = now
            if interval_operand == 'after':
                limit_date -= relativedelta(**{interval_type: interval_number})
            if interval_operand == 'before':
                limit_date += relativedelta(**{interval_type: interval_number})
            return (field, '<=', limit_date.strftime(datetime_format))
        return

    def _add_max_executions_filter(self, cr, uid, trigger, context):
        """Build max executions filter"""
        if trigger.executions_max_number:
            cr.execute(
                "SELECT res_id FROM sartre_execution WHERE trigger_id=%s AND executions_number>=%s",
                (trigger.id, trigger.executions_max_number))
            res_ids_off = cr.fetchall()
            if context.get('active_object_ids'):
                res_ids = list(
                    set(context.get('active_object_ids', [])) -
                    set([item[0] for item in res_ids_off]))
                return ('id', 'in', res_ids)
            else:
                return ('id', 'not in', [item[0] for item in res_ids_off])
        return

    def _build_python_domain(self, cr, uid, trigger, domain, context):
        """Updated filters based on old or dynamic values, or Python operators"""
        domain = domain or []
        operator_obj = self.pool.get('sartre.operator')
        old_values = context.get('old_values', {})
        arg_values = context.get('arg_values', {})
        model_obj = self.pool.get(trigger.model_id.model)
        if 'active_object_ids' in context:
            all_active_object_ids = context['active_object_ids']
        else:
            all_active_object_ids = model_obj.search(cr,
                                                     uid, [],
                                                     context=context)
        fields_list = self.get_fields_to_save_old_values(
            cr, SUPERUSER_ID, [trigger.id])
        current_values = _get_browse_record_dict(model_obj,
                                                 cr,
                                                 uid,
                                                 all_active_object_ids,
                                                 fields_list,
                                                 context=context)
        for index, condition in enumerate(domain):
            if self._is_dynamic_filter(cr, uid, condition, model_obj, context):
                active_object_ids = all_active_object_ids[:]

                field, operator_symbol, other_value = condition
                fields_list = field.replace('OLD_', '').split('.')
                field, remote_field = fields_list[0], len(
                    fields_list) > 1 and '.'.join(fields_list[1:]) or ''
                try:
                    operator_inst, opposite_operator = operator_obj._get_operator(
                        cr, uid, operator_symbol)
                except Exception:
                    raise orm.except_orm(
                        _('Warning!'),
                        _("The operator %s doesn't exist!") % operator_symbol)
                dynamic_other_value = other_value and re.match(
                    '(\[\[.+?\]\])', str(other_value))
                for object_ in self.pool.get(trigger.model_id.model).browse(
                        cr, uid, active_object_ids, context):
                    if dynamic_other_value:
                        other_value = _get_id_from_browse_record(
                            eval(
                                str(dynamic_other_value.group()[2:-2]).strip(),
                                {
                                    'uid': uid,
                                    'object': object_,
                                    'context': context,
                                    'time': time,
                                    'relativedelta': relativedelta,
                                }))
                    current_field_value = current_values.get(object_.id,
                                                             {}).get(field)
                    old_field_value = old_values.get(object_.id, {}).get(field)
                    arg_value = arg_values.get(field)
                    if remote_field:
                        current_field_value = _get_id_from_browse_record(
                            getattr(current_field_value, remote_field))
                        old_field_value = _get_id_from_browse_record(
                            old_field_value
                            and getattr(old_field_value, remote_field))
                    localdict = {
                        'selected_field_value':
                        'OLD_' in condition[0] and old_field_value
                        or current_field_value,
                        'current_field_value':
                        current_field_value,
                        'old_field_value':
                        old_field_value,
                        'other_value':
                        other_value,
                        'arg_value':
                        arg_value
                    }
                    if operator_inst:
                        exec operator_inst.expression in localdict
                    if bool(opposite_operator) == bool(
                            localdict.get('result', opposite_operator)):
                        active_object_ids.remove(object_.id)
                domain[index] = ('id', 'in', active_object_ids)
        return domain

    def _build_domain_expression(self, cr, uid, trigger, context):
        """Build domain expression"""
        # Define domain from domain_force
        domain = trigger.domain_force and eval(
            trigger.domain_force.replace(
                '%today', time.strftime('%Y-%m-%d %H:%M:%S'))) or []
        # Add filters one by one if domain_force is empty
        if not domain:
            for filter_ in trigger.filter_ids:
                domain.extend(
                    eval(
                        filter_.domain.replace(
                            '%today', time.strftime('%Y-%m-%d %H:%M:%S'))))
        # Add general filters
        for filter_name in ('_add_trigger_date_filter',
                            '_add_max_executions_filter'):
            domain_extension = getattr(self, filter_name)(cr, uid, trigger,
                                                          context)
            if domain_extension:
                domain.append(domain_extension)
        # Update filters based on old or dynamic values or Python operators
        if trigger.python_domain:
            domain = self._build_python_domain(cr, uid, trigger, domain,
                                               context)
        if 'active_object_ids' in context:
            domain.append(('id', 'in', context['active_object_ids']))
        return domain

    def run_now(self, cr, uid, ids, context=None):
        """Execute now server actions"""
        context = context or {}
        context = dict(
            context)  # Can't use deepcopy because of browse_record_list
        context.setdefault('active_test', False)
        if isinstance(ids, (int, long)):
            ids = [ids]
        for trigger in self.read(cr, uid, ids, ['test_mode'], context):
            context['test_mode'] = trigger['test_mode']
            self._run_now(cr, uid, trigger['id'], context)
        return True

    def _get_filtered_object_ids(self, cr, uid, trigger, context):
        # Build domain expression
        domain = self._build_domain_expression(cr, uid, trigger, context)
        # Search objects which validate trigger filters
        res_ids = self.pool.get(trigger.model_id.model).search(cr,
                                                               uid,
                                                               domain,
                                                               context=context)
        # To avoid infinite recursion
        context.setdefault('triggers', {}).setdefault(trigger.id, [])
        res_ids = list(set(res_ids) - set(context['triggers'][trigger.id]))
        context['triggers'][trigger.id].extend(res_ids)
        return res_ids

    def _run_now(self, cr, uid, trigger_id, context):
        logger = SmileDBLogger(cr.dbname, self._name, trigger_id, uid)

        # Get sequence in order to differentiate logs per run
        context.setdefault('pid_list',
                           []).append(str(logger.pid).rjust(8, '0'))
        pid = '-'.join((str(x) for x in context['pid_list']))
        if not pid:
            logger.critical(
                'Action Trigger failed: impossible to get a pid for dbname %s'
                % (cr.dbname))
            return

        # Get sequence in order to differentiate logs per run
        if context.get('test_mode', False):
            logger.info('[%s] Trigger in test mode' % (pid, ))
        logger.debug('[%s] Trigger on %s' %
                     (pid, context.get('trigger', 'manual')))
        logger.debug('[%s] Context: %s' % (pid, context))
        trigger = self.browse(cr, uid, trigger_id, context)

        # Filter objects
        filtered_object_ids = []
        try:
            filtered_object_ids = self._get_filtered_object_ids(
                cr, uid, trigger, context)
            logger.debug('[%s] Successful Objects Filtering: %s' %
                         (pid, filtered_object_ids))
        except Exception, e:
            logger.exception('[%s] Objects Filtering failed: %s' %
                             (pid, _get_exception_message(e)))
            if trigger.exception_handling == 'continue' or trigger.exception_warning == 'none':
                return True
            else:
                cr.rollback()
                logger.time_info("[%s] Transaction rolled back" % (pid, ))
                if trigger.exception_warning == 'custom':
                    raise orm.except_orm(
                        _('Error'),
                        _('%s\n[Pid: %s]') % (trigger.exception_message, pid))
                elif trigger.exception_warning == 'native':
                    raise orm.except_orm(
                        _('Error'),
                        _('%s\n[Pid: %s]') % (_get_exception_message(e), pid))
        # Execute server actions for filtered objects
        if filtered_object_ids or trigger.force_actions_execution:
            logger.info('[%s] Trigger on %s for objects %s,%s' %
                        (pid, context.get('trigger', 'manual'),
                         trigger.model_id.model, filtered_object_ids))
            if context.get('test_mode', False):
                cr.execute("SAVEPOINT smile_action_trigger_test_mode_%s",
                           (trigger.id, ))
            for action in trigger.action_ids:
                if action.active:
                    if trigger.exception_handling == 'continue':
                        cr.execute("SAVEPOINT smile_action_trigger_%s",
                                   (trigger.id, ))
                    try:
                        logger.debug(
                            '[%s] Launch Action: %s - Objects: %s,%s' %
                            (pid, action.name, action.model_id.model,
                             filtered_object_ids))
                        self._run_action(cr, uid, action, filtered_object_ids,
                                         context, logger, pid)
                        if not action.specific_thread:
                            logger.time_info(
                                '[%s] Successful Action: %s - Objects: %s,%s' %
                                (pid, action.name, action.model_id.model,
                                 filtered_object_ids))
                        else:
                            logger.info(
                                '[%s] Action launched in a new thread: %s - Objects: %s,%s'
                                % (pid, action.name, action.model_id.model,
                                   filtered_object_ids))
                    except Exception, e:
                        logger.exception(
                            '[%s] Action failed: %s - %s' %
                            (pid, action.name, _get_exception_message(e)))
                        if trigger.exception_handling == 'continue' and not action.force_rollback:
                            cr.execute(
                                "ROLLBACK TO SAVEPOINT smile_action_trigger_%s",
                                (trigger.id, ))
                        else:
                            cr.rollback()
                            logger.time_info("[%s] Transaction rolled back" %
                                             (pid, ))
                            if trigger.exception_warning == 'custom':
                                raise orm.except_orm(
                                    _('Error'),
                                    _('%s\n[Pid: %s]') %
                                    (trigger.exception_message, pid))
                            elif trigger.exception_warning == 'native':
                                raise orm.except_orm(
                                    _('Error'),
                                    _('%s\n[Pid: %s]') %
                                    (_get_exception_message(e), pid))
                            else:  # elif trigger.exception_warning == 'none':
                                return True
            if context.get('test_mode', False):
                cr.execute(
                    "ROLLBACK TO SAVEPOINT smile_action_trigger_test_mode_%s",
                    (trigger.id, ))
                logger.time_info("[%s] Actions execution rolled back" %
                                 (pid, ))
            if trigger.executions_max_number:
                for object_id in filtered_object_ids:
                    self.pool.get(
                        'sartre.execution').update_executions_counter(
                            cr, uid, trigger, object_id)