#
##############################################################################
import logging
-from operator import attrgetter
+from operator import attrgetter, itemgetter
from osv import osv, fields
from tools.translate import _
from tools import ustr
import pooler
+_logger = logging.getLogger(__name__)
class res_config_configurable(osv.osv_memory):
''' Base classes for new-style configuration items
'''
_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):
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)
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)
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)
"""
_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')
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
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):
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
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)]),
'''
_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)
}
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: