[MERGE] base: raise an exception if the format of the bank account is wrong
[odoo/odoo.git] / openerp / addons / base / res / res_config.py
index 89f197b..3f4a052 100644 (file)
@@ -19,7 +19,7 @@
 #
 ##############################################################################
 import logging
-from operator import attrgetter
+from operator import attrgetter, itemgetter
 
 from osv import osv, fields
 from tools.translate import _
@@ -27,6 +27,7 @@ import netsvc
 from tools import ustr
 import pooler
 
+_logger = logging.getLogger(__name__)
 
 class res_config_configurable(osv.osv_memory):
     ''' Base classes for new-style configuration items
@@ -37,89 +38,48 @@ class res_config_configurable(osv.osv_memory):
     '''
     _name = 'res.config'
     _inherit = 'ir.wizard.screen'
-    logger = netsvc.Logger()
-    __logger = logging.getLogger(_name)
 
-    def get_current_progress(self, cr, uid, context=None):
-        '''Return a description the current progress of configuration:
-        a tuple of (non_open_todos:int, total_todos: int)
-        '''
-        return (self.pool.get('ir.actions.todo')\
-                .search_count(cr, uid, [('state','<>','open')], context),
-                self.pool.get('ir.actions.todo')\
-                .search_count(cr, uid, [], context))
+    def _next_action(self, cr, uid, context=None):
+        Todos = self.pool['ir.actions.todo']
+        _logger.info('getting next %s', Todos)
 
-    def _progress(self, cr, uid, context=None):
-        closed, total = self.get_current_progress(cr, uid, context=context)
-        if total:
-            return round(closed*100./total)
-        return 100.
+        active_todos = Todos.browse(cr, uid,
+            Todos.search(cr, uid, ['&', ('type', '=', 'automatic'), ('state','=','open')]),
+                                    context=context)
 
-    _columns = dict(
-        progress = fields.float('Configuration Progress', readonly=True),
-    )
+        user_groups = set(map(
+            lambda g: g.id,
+            self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
 
-    _defaults = dict(
-        progress = _progress,
-    )
+        valid_todos_for_user = [
+            todo for todo in active_todos
+            if not todo.groups_id or bool(user_groups.intersection((
+                group.id for group in todo.groups_id)))
+        ]
 
-    def _next_action(self, cr, uid, context=None):
-        todos = self.pool.get('ir.actions.todo')
-        self.__logger.info('getting next %s', todos)
-        # Don't forget to change the domain in search view, if this condition is changed
-        active_todos = todos.search(cr, uid, [('state','=','open')],
-                                    limit=1)
-        if active_todos:
-            todo_obj = todos.browse(cr, uid, active_todos[0], context=None)
-            todo_groups = map(lambda x:x.id, todo_obj.groups_id)
-            dont_skip_todo = True
-            if todo_groups:
-                cr.execute("select 1 from res_groups_users_rel where uid=%s and gid IN %s",(uid, tuple(todo_groups),))
-                dont_skip_todo = bool(cr.fetchone())
-            if dont_skip_todo:
-                res = todos.browse(cr, uid, active_todos[0], context=None)
-                # Wizards that directly opens a form stays in Todo state even if its called, 
-                # as next_action is not called,  so, setting state as done 'manually' 
-                if res.action_id.target == 'current':
-                    res.write({'state': 'done'})
-                return res
-            else:
-                todos.write(cr, uid, active_todos[0], {'state':'skip'}, context=None)
-                return self._next_action(cr, uid)
-        return None
+        if valid_todos_for_user:
+            return valid_todos_for_user[0]
 
-    def _set_previous_todo(self, cr, uid, state, context=None):
-        """ lookup the previous (which is still the next at this point)
-        ir.actions.todo, set it to whatever state was provided.
-        """
-        # this is ultra brittle, but apart from storing the todo id
-        # into the res.config view, I'm not sure how to get the
-        # "previous" todo
-        previous_todo = self._next_action(cr, uid, context=context)
-        if not previous_todo:
-            self.__logger.warn(_("Couldn't find previous ir.actions.todo"))
-            return
-        previous_todo.write({'state':state})
+        return None
 
     def _next(self, cr, uid, context=None):
-        self.__logger.info('getting next operation')
-        next = self._next_action(cr, uid)
-        self.__logger.info('next action is %s', next)
+        _logger.info('getting next operation')
+        next = self._next_action(cr, uid, context=context)
+        _logger.info('next action is %s', next)
         if next:
             res = next.action_launch(context=context)
-            res.update({'nodestroy': False})
+            res['nodestroy'] = False
             return res
-        self.__logger.info('all configuration actions have been executed')
-
-        current_user_menu = self.pool.get('res.users').browse(cr, uid, uid).menu_id
-        # return the action associated with the menu
-        return self.pool.get(current_user_menu.type).read(cr, uid, current_user_menu.id)
+        # reload the client; open the first available root menu
+        menu_obj = self.pool.get('ir.ui.menu')
+        menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
+        return {
+            'type': 'ir.actions.client',
+            'tag': 'reload',
+            'params': {'menu_id': menu_ids and menu_ids[0] or False},
+        }
 
     def start(self, cr, uid, ids, context=None):
-        ids2 = self.pool.get('ir.actions.todo').search(cr, uid, [], context=context)
-        for todo in self.pool.get('ir.actions.todo').browse(cr, uid, ids2, context=context):
-            if (todo.type=='normal_recurring'):
-                todo.write({'state':'open'})
         return self.next(cr, uid, ids, context)
 
     def next(self, cr, uid, ids, context=None):
@@ -161,8 +121,7 @@ class res_config_configurable(osv.osv_memory):
         an action dictionary -- executes the action provided by calling
         ``next``.
         """
-        self._set_previous_todo(cr, uid, state='done', context=context)
-        next = self.execute(cr, uid, ids, context=None)
+        next = self.execute(cr, uid, ids, context=context)
         if next: return next
         return self.next(cr, uid, ids, context=context)
 
@@ -174,8 +133,7 @@ class res_config_configurable(osv.osv_memory):
         an action dictionary -- executes the action provided by calling
         ``next``.
         """
-        self._set_previous_todo(cr, uid, state='skip', context=context)
-        next = self.cancel(cr, uid, ids, context=None)
+        next = self.cancel(cr, uid, ids, context=context)
         if next: return next
         return self.next(cr, uid, ids, context=context)
 
@@ -190,8 +148,7 @@ class res_config_configurable(osv.osv_memory):
         an action dictionary -- executes the action provided by calling
         ``next``.
         """
-        self._set_previous_todo(cr, uid, state='cancel', context=context)
-        next = self.cancel(cr, uid, ids, context=None)
+        next = self.cancel(cr, uid, ids, context=context)
         if next: return next
         return self.next(cr, uid, ids, context=context)
 
@@ -291,14 +248,27 @@ class res_config_installer(osv.osv_memory):
     """
     _name = 'res.config.installer'
     _inherit = 'res.config'
-    __logger = logging.getLogger(_name)
 
     _install_if = {}
 
+    def already_installed(self, cr, uid, context=None):
+        """ For each module, check if it's already installed and if it
+        is return its name
+
+        :returns: a list of the already installed modules in this
+                  installer
+        :rtype: [str]
+        """
+        return map(attrgetter('name'),
+                   self._already_installed(cr, uid, context=context))
+
     def _already_installed(self, cr, uid, context=None):
         """ For each module (boolean fields in a res.config.installer),
-        check if it's already installed (either 'to install', 'to upgrade' or 'installed')
-        and if it is, check it by default
+        check if it's already installed (either 'to install', 'to upgrade'
+        or 'installed') and if it is return the module's browse_record
+
+        :returns: a list of all installed modules in this installer
+        :rtype: [browse_record]
         """
         modules = self.pool.get('ir.module.module')
 
@@ -350,8 +320,8 @@ class res_config_installer(osv.osv_memory):
                    if base.issuperset(requirements)
                    for module in consequences)
 
-        return (base | hooks_results | additionals) - set(
-            map(attrgetter('name'), self._already_installed(cr, uid, context)))
+        return (base | hooks_results | additionals).difference(
+                    self.already_installed(cr, uid, context))
 
     def default_get(self, cr, uid, fields_list, context=None):
         ''' If an addon is already installed, check it by default
@@ -361,8 +331,7 @@ class res_config_installer(osv.osv_memory):
 
         return dict(defaults,
                     **dict.fromkeys(
-                        map(attrgetter('name'),
-                            self._already_installed(cr, uid, context=context)),
+                        self.already_installed(cr, uid, context=context),
                         True))
 
     def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
@@ -373,12 +342,12 @@ class res_config_installer(osv.osv_memory):
         fields = super(res_config_installer, self).fields_get(
             cr, uid, fields, context, write_access)
 
-        for module in self._already_installed(cr, uid, context=context):
-            if module.name not in fields:
+        for name in self.already_installed(cr, uid, context=context):
+            if name not in fields:
                 continue
-            fields[module.name].update(
+            fields[name].update(
                 readonly=True,
-                help= ustr(fields[module.name].get('help', '')) +
+                help= ustr(fields[name].get('help', '')) +
                      _('\n\nThis addon is already installed on your system'))
         return fields
 
@@ -386,7 +355,7 @@ class res_config_installer(osv.osv_memory):
         modules = self.pool.get('ir.module.module')
         to_install = list(self.modules_to_install(
             cr, uid, ids, context=context))
-        self.__logger.info('Selecting addons %s to install', to_install)
+        _logger.info('Selecting addons %s to install', to_install)
         modules.state_update(
             cr, uid,
             modules.search(cr, uid, [('name','in',to_install)]),
@@ -408,7 +377,6 @@ class ir_actions_configuration_wizard(osv.osv_memory):
     '''
     _name='ir.actions.configuration.wizard'
     _inherit = 'res.config'
-    __logger = logging.getLogger(_name)
 
     def _next_action_note(self, cr, uid, ids, context=None):
         next = self._next_action(cr, uid)
@@ -428,8 +396,161 @@ class ir_actions_configuration_wizard(osv.osv_memory):
         }
 
     def execute(self, cr, uid, ids, context=None):
-        self.__logger.warn(DEPRECATION_MESSAGE)
+        _logger.warning(DEPRECATION_MESSAGE)
 
 ir_actions_configuration_wizard()
 
+
+
+class res_config_settings(osv.osv_memory):
+    """ Base configuration wizard for application settings.  It provides support for setting
+        default values, assigning groups to employee users, and installing modules.
+        To make such a 'settings' wizard, define a model like::
+
+            class my_config_wizard(osv.osv_memory):
+                _name = 'my.settings'
+                _inherit = 'res.config.settings'
+                _columns = {
+                    'default_foo': fields.type(..., default_model='my.model'),
+                    'group_bar': fields.boolean(..., group='base.group_user', implied_group='my.group'),
+                    'module_baz': fields.boolean(...),
+                    'other_field': fields.type(...),
+                }
+
+        The method ``execute`` provides some support based on a naming convention:
+
+        *   For a field like 'default_XXX', ``execute`` sets the (global) default value of
+            the field 'XXX' in the model named by ``default_model`` to the field's value.
+
+        *   For a boolean field like 'group_XXX', ``execute`` adds/removes 'implied_group'
+            to/from the implied groups of 'group', depending on the field's value.
+            By default 'group' is the group Employee.  Groups are given by their xml id.
+
+        *   For a boolean field like 'module_XXX', ``execute`` triggers the immediate
+            installation of the module named 'XXX' if the field has value ``True``.
+
+        *   For the other fields, the method ``execute`` invokes all methods with a name
+            that starts with 'set_'; such methods can be defined to implement the effect
+            of those fields.
+
+        The method ``default_get`` retrieves values that reflect the current status of the
+        fields like 'default_XXX', 'group_XXX' and 'module_XXX'.  It also invokes all methods
+        with a name that starts with 'get_default_'; such methods can be defined to provide
+        current values for other fields.
+    """
+    _name = 'res.config.settings'
+
+    def copy(self, cr, uid, id, values, context=None):
+        raise osv.except_osv(_("Cannot duplicate configuration!"), "")
+
+    def _get_classified_fields(self, cr, uid, context=None):
+        """ return a dictionary with the fields classified by category::
+
+                {   'default': [('default_foo', 'model', 'foo'), ...],
+                    'group':   [('group_bar', browse_group, browse_implied_group), ...],
+                    'module':  [('module_baz', browse_module), ...],
+                    'other':   ['other_field', ...],
+                }
+        """
+        ir_model_data = self.pool.get('ir.model.data')
+        ir_module = self.pool.get('ir.module.module')
+        def ref(xml_id):
+            mod, xml = xml_id.split('.', 1)
+            return ir_model_data.get_object(cr, uid, mod, xml, context)
+
+        defaults, groups, modules, others = [], [], [], []
+        for name, field in self._columns.items():
+            if name.startswith('default_') and hasattr(field, 'default_model'):
+                defaults.append((name, field.default_model, name[8:]))
+            elif name.startswith('group_') and isinstance(field, fields.boolean) and hasattr(field, 'implied_group'):
+                field_group = getattr(field, 'group', 'base.group_user')
+                groups.append((name, ref(field_group), ref(field.implied_group)))
+            elif name.startswith('module_') and isinstance(field, fields.boolean):
+                mod_ids = ir_module.search(cr, uid, [('name', '=', name[7:])])
+                modules.append((name, ir_module.browse(cr, uid, mod_ids[0], context)))
+            else:
+                others.append(name)
+
+        return {'default': defaults, 'group': groups, 'module': modules, 'other': others}
+
+    def default_get(self, cr, uid, fields, context=None):
+        ir_values = self.pool.get('ir.values')
+        classified = self._get_classified_fields(cr, uid, context)
+
+        res = super(res_config_settings, self).default_get(cr, uid, fields, context)
+
+        # defaults: take the corresponding default value they set
+        for name, model, field in classified['default']:
+            value = ir_values.get_default(cr, uid, model, field)
+            if value is not None:
+                res[name] = value
+
+        # groups: which groups are implied by the group Employee
+        for name, group, implied_group in classified['group']:
+            res[name] = implied_group in group.implied_ids
+
+        # modules: which modules are installed/to install
+        for name, module in classified['module']:
+            res[name] = module.state in ('installed', 'to install', 'to upgrade')
+
+        # other fields: call all methods that start with 'get_default_'
+        for method in dir(self):
+            if method.startswith('get_default_'):
+                res.update(getattr(self, method)(cr, uid, fields, context))
+
+        return res
+
+    def execute(self, cr, uid, ids, context=None):
+        ir_values = self.pool.get('ir.values')
+        ir_model_data = self.pool.get('ir.model.data')
+        ir_module = self.pool.get('ir.module.module')
+        res_groups = self.pool.get('res.groups')
+        classified = self._get_classified_fields(cr, uid, context)
+
+        config = self.browse(cr, uid, ids[0], context)
+
+        # default values fields
+        for name, model, field in classified['default']:
+            ir_values.set_default(cr, uid, model, field, config[name])
+
+        # group fields: modify group / implied groups
+        for name, group, implied_group in classified['group']:
+            if config[name]:
+                group.write({'implied_ids': [(4, implied_group.id)]})
+            else:
+                group.write({'implied_ids': [(3, implied_group.id)]})
+                implied_group.write({'users': [(3, u.id) for u in group.users]})
+
+        # other fields: execute all methods that start with 'set_'
+        for method in dir(self):
+            if method.startswith('set_'):
+                getattr(self, method)(cr, uid, ids, context)
+
+        # module fields: install/uninstall the selected modules
+        to_install_ids = []
+        to_uninstall_ids = []
+        for name, module in classified['module']:
+            if config[name]:
+                if module.state == 'uninstalled': to_install_ids.append(module.id)
+            else:
+                if module.state in ('installed','upgrade'): to_uninstall_ids.append(module.id)
+
+        if to_install_ids or to_uninstall_ids:
+            ir_module.button_uninstall(cr, uid, to_uninstall_ids, context=context)
+            ir_module.button_immediate_install(cr, uid, to_install_ids, context=context)
+
+        # force client-side reload (update user menu and current view)
+        return {
+            'type': 'ir.actions.client',
+            'tag': 'reload',
+        }
+
+    def cancel(self, cr, uid, ids, context=None):
+        # ignore the current record, and send the action to reopen the view
+        act_window = self.pool.get('ir.actions.act_window')
+        action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)])
+        if action_ids:
+            return act_window.read(cr, uid, action_ids[0], [], context=context)
+        return {}
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: