diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py
index c757702d52f..5b357263569 100644
--- a/openerp/addons/base/__openerp__.py
+++ b/openerp/addons/base/__openerp__.py
@@ -54,7 +54,6 @@
'module/wizard/base_import_language_view.xml',
'module/wizard/base_module_upgrade_view.xml',
'module/wizard/base_module_configuration_view.xml',
- 'module/wizard/base_module_shortcut_data.xml',
'module/wizard/base_export_language_view.xml',
'module/wizard/base_update_translations_view.xml',
'res/res_request_view.xml',
diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql
index 17378061b29..54dcc3a55ae 100644
--- a/openerp/addons/base/base.sql
+++ b/openerp/addons/base/base.sql
@@ -106,6 +106,11 @@ CREATE TABLE ir_act_server (
)
INHERITS (ir_actions);
+CREATE TABLE ir_act_client (
+ primary key(id)
+)
+INHERITS (ir_actions);
+
CREATE TABLE ir_ui_view (
id serial NOT NULL,
diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml
index 1280ad5f5e7..d3ebd6af868 100644
--- a/openerp/addons/base/ir/ir.xml
+++ b/openerp/addons/base/ir/ir.xml
@@ -1889,7 +1889,7 @@
-
@@ -1913,7 +1913,7 @@
-
diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py
index 7c8a2414d25..81519d7f575 100644
--- a/openerp/addons/base/ir/ir_actions.py
+++ b/openerp/addons/base/ir/ir_actions.py
@@ -18,20 +18,21 @@
# along with this program. If not, see .
#
##############################################################################
-
-from osv import fields,osv
-from tools.safe_eval import safe_eval as eval
-import tools
-import time
-from tools.config import config
-from tools.translate import _
-import netsvc
-import logging
-import re
+import ast
import copy
+import logging
import os
+import re
+import time
+import tools
from xml import dom
+
+import netsvc
+from osv import fields,osv
from report.report_sxw import report_sxw, report_rml
+from tools.config import config
+from tools.safe_eval import safe_eval as eval
+from tools.translate import _
class actions(osv.osv):
_name = 'ir.actions.actions'
@@ -162,13 +163,13 @@ class act_window(osv.osv):
def _invalid_model_msg(self, cr, uid, ids, context=None):
return _('Invalid model name in the action definition.')
-
+
_constraints = [
(_check_model, _invalid_model_msg, ['res_model','src_model'])
]
def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
- """Returns an ordered list of the specific view modes that should be
+ """Returns an ordered list of the specific view modes that should be
enabled when displaying the result of this action, along with the
ID of the specific view to use for each mode, if any were required.
@@ -208,7 +209,7 @@ class act_window(osv.osv):
if act.search_view_id:
search_view_id = act.search_view_id.id
else:
- res_view = self.pool.get('ir.ui.view').search(cr, uid,
+ res_view = self.pool.get('ir.ui.view').search(cr, uid,
[('model','=',act.res_model),('type','=','search'),
('inherit_id','=',False)], context=context)
if res_view:
@@ -808,7 +809,7 @@ class ir_actions_todo_category(osv.osv):
_name = 'ir.actions.todo.category'
_description = "Configuration Wizard Category"
_columns = {
- 'name':fields.char('Name', size=64, translate=True, required=True),
+ 'name':fields.char('Name', size=64, translate=True, required=True),
'sequence': fields.integer('Sequence'),
'wizards_ids': fields.one2many('ir.actions.todo', 'category_id', 'Configuration Wizards'),
}
@@ -816,10 +817,9 @@ ir_actions_todo_category()
# This model use to register action services.
TODO_STATES = [('open', 'To Do'),
- ('done', 'Done'),
- ('skip','Skipped'),
- ('cancel','Cancelled')]
-
+ ('done', 'Done')]
+TODO_TYPES = [('manual', 'Launch Manually'),
+ ('automatic', 'Launch Automatically')]
class ir_actions_todo(osv.osv):
"""
Configuration Wizards
@@ -833,10 +833,9 @@ class ir_actions_todo(osv.osv):
'sequence': fields.integer('Sequence'),
'state': fields.selection(TODO_STATES, string='State', required=True),
'name': fields.char('Name', size=64),
- 'type': fields.selection([('special','Special'),('normal','Normal'),('normal_recurring','Normal Recurring')], 'Type', required=True,
- help="""Special: the wizard is run whenever the system is reconfigured.
-Normal: the wizard is visible in the configuration panel until it is done.
-Normal Recurring: the wizard is visible in the configuration panel regardless of its state."""),
+ 'type': fields.selection(TODO_TYPES, 'Type', required=True,
+ help="""Manual: Launched manually.
+Automatic: Runs whenever the system is reconfigured."""),
'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
'note': fields.text('Text', translate=True),
'category_id': fields.many2one('ir.actions.todo.category','Category'),
@@ -844,33 +843,108 @@ Normal Recurring: the wizard is visible in the configuration panel regardless of
_defaults={
'state': 'open',
'sequence': 10,
- 'type': 'special',
+ 'type': 'manual',
}
_order="sequence,name,id"
def action_launch(self, cr, uid, ids, context=None):
""" Launch Action of Wizard"""
- if context is None:
- context = {}
wizard_id = ids and ids[0] or False
wizard = self.browse(cr, uid, wizard_id, context=context)
+ if wizard.type == 'automatic':
+ wizard.write({'state': 'done'})
+
res = self.pool.get('ir.actions.act_window').read(cr, uid, wizard.action_id.id, [], context=context)
- res.update({'nodestroy': True})
+ res['nodestroy'] = True
# Open a specific record when res_id is provided in the context
if res.get('context'):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
ctx = eval(res['context'], {'user': user})
if ctx.get('res_id'):
- res.update({'res_id': ctx.pop('res_id')})
- res.update({'context': ctx})
+ res.update(
+ res_id=ctx.pop('res_id'),
+ context=ctx)
return res
def action_open(self, cr, uid, ids, context=None):
""" Sets configuration wizard in TODO state"""
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
+ def progress(self, cr, uid, context=None):
+ """ Returns a dict with 3 keys {todo, done, total}.
+
+ These keys all map to integers and provide the number of todos
+ marked as open, the total number of todos and the number of
+ todos not open (which is basically a shortcut to total-todo)
+
+ :rtype: dict
+ """
+ user_groups = set(map(
+ lambda x: x.id,
+ self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
+ def groups_match(todo):
+ """ Checks if the todo's groups match those of the current user
+ """
+ return not todo.groups_id \
+ or bool(user_groups.intersection((
+ group.id for group in todo.groups_id)))
+
+ done = filter(
+ groups_match,
+ self.browse(cr, uid,
+ self.search(cr, uid, ['&', ('state', '!=', 'open'), ('type', '=', 'manual')], context=context),
+ context=context))
+
+ total = filter(
+ groups_match,
+ self.browse(cr, uid,
+ self.search(cr, uid, [('type', '=', 'manual')], context=context),
+ context=context))
+
+ return {
+ 'done': len(done),
+ 'total': len(total),
+ 'todo': len(total) - len(done)
+ }
+
ir_actions_todo()
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+class act_client(osv.osv):
+ _name = 'ir.actions.client'
+ _inherit = 'ir.actions.actions'
+ _table = 'ir_act_client'
+ _sequence = 'ir_actions_id_seq'
+ _order = 'name'
+ def _get_params(self, cr, uid, ids, field_name, arg, context):
+ return dict([
+ ((record.id, ast.literal_eval(record.params_store))
+ if record.params_store else (record.id, False))
+ for record in self.browse(cr, uid, ids, context=context)
+ ])
+
+ def _set_params(self, cr, uid, ids, field_name, field_value, arg, context):
+ assert isinstance(field_value, dict), "params can only be dictionaries"
+ for record in self.browse(cr, uid, ids, context=context):
+ record.write({field_name: repr(field_value)})
+
+ _columns = {
+ 'tag': fields.char('Client action tag', size=64, required=True,
+ help="An arbitrary string, interpreted by the client"
+ " according to its own needs and wishes. There "
+ "is no central tag repository across clients."),
+ 'params': fields.function(_get_params, fnct_inv=_set_params,
+ type='binary', method=True,
+ string="Supplementary arguments",
+ help="Arguments sent to the client along with"
+ "the view tag"),
+ 'params_store': fields.binary("Params storage", readonly=True)
+ }
+ _defaults = {
+ 'type': 'ir.actions.client',
+
+ }
+act_client()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py
index d67f0c247d1..9ed4d7806bc 100644
--- a/openerp/addons/base/ir/ir_ui_menu.py
+++ b/openerp/addons/base/ir/ir_ui_menu.py
@@ -273,6 +273,7 @@ class ir_ui_menu(osv.osv):
('ir.actions.wizard', 'ir.actions.wizard'),
('ir.actions.url', 'ir.actions.url'),
('ir.actions.server', 'ir.actions.server'),
+ ('ir.actions.client', 'ir.actions.client'),
]),
}
diff --git a/openerp/addons/base/module/wizard/base_module_configuration.py b/openerp/addons/base/module/wizard/base_module_configuration.py
index 20388737ca6..f38023294f9 100644
--- a/openerp/addons/base/module/wizard/base_module_configuration.py
+++ b/openerp/addons/base/module/wizard/base_module_configuration.py
@@ -27,7 +27,8 @@ class base_module_configuration(osv.osv_memory):
_name = "base.module.configuration"
def start(self, cr, uid, ids, context=None):
- todo_ids = self.pool.get('ir.actions.todo').search(cr, uid, ['|', '|', ('type','=','normal_recurring'), ('state', '=', 'open'), '&', ('state', '=', 'skip'), ('type', '=', 'special')])
+ todo_ids = self.pool.get('ir.actions.todo').search(cr, uid,
+ ['|', ('type','=','recurring'), ('state', '=', 'open')])
if not todo_ids:
# When there is no wizard todo it will display message
data_obj = self.pool.get('ir.model.data')
diff --git a/openerp/addons/base/module/wizard/base_module_configuration_view.xml b/openerp/addons/base/module/wizard/base_module_configuration_view.xml
index be86145a156..5b1c6d51cfe 100644
--- a/openerp/addons/base/module/wizard/base_module_configuration_view.xml
+++ b/openerp/addons/base/module/wizard/base_module_configuration_view.xml
@@ -16,28 +16,5 @@
-
-
- Start Configuration
-
- code
- action = obj.start()
-
-
-
-
-
-
-
- Start Configuration
- client_action_multi
-
- action
- ir.actions.todo
-
-
diff --git a/openerp/addons/base/module/wizard/base_module_shortcut_data.xml b/openerp/addons/base/module/wizard/base_module_shortcut_data.xml
deleted file mode 100644
index b8d8a9c70e1..00000000000
--- a/openerp/addons/base/module/wizard/base_module_shortcut_data.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- Add More Features
- ir.ui.menu
-
-
-
-
-
-
diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py
index 24b4c677072..df508bad5c9 100644
--- a/openerp/addons/base/res/res_config.py
+++ b/openerp/addons/base/res/res_config.py
@@ -19,7 +19,7 @@
#
##############################################################################
import logging
-from operator import attrgetter
+from operator import attrgetter, itemgetter
from osv import osv, fields
from tools.translate import _
@@ -37,16 +37,14 @@ 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)
'''
- todo_pool = self.pool.get('ir.actions.todo')
- return (todo_pool.search_count(cr, uid, [('state','<>','open')], context),
- todo_pool.search_count(cr, uid, [], context))
+ return itemgetter('done', 'total')(
+ self.pool.get('ir.actions.todo').progress(cr, uid, context=context))
def _progress(self, cr, uid, context=None):
closed, total = self.get_current_progress(cr, uid, context=context)
@@ -63,51 +61,35 @@ class res_config_configurable(osv.osv_memory):
)
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)
- # A wizard opening directly a form instead of calling
- # next_action will remain in the todo state so we set it to
- # done ourselves.
- 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
+ Todos = self.pool['ir.actions.todo']
+ self.__logger.info('getting next %s', Todos)
- 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})
+ active_todos = Todos.browse(cr, uid,
+ Todos.search(cr, uid, ['&', ('type', '=', 'automatic'), ('state','=','open')]),
+ context=context)
+
+ user_groups = set(map(
+ lambda g: g.id,
+ self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
+
+ 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)))
+ ]
+
+ if valid_todos_for_user:
+ return valid_todos_for_user[0]
+
+ return None
def _next(self, cr, uid, context=None):
self.__logger.info('getting next operation')
- next = self._next_action(cr, uid)
+ next = self._next_action(cr, uid, context=context)
self.__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')
@@ -116,11 +98,6 @@ class res_config_configurable(osv.osv_memory):
return self.pool.get(current_user_menu.type).read(cr, uid, current_user_menu.id)
def start(self, cr, uid, ids, context=None):
- todo_pool = self.pool.get('ir.actions.todo')
- ids2 = todo_pool.search(cr, uid, [], context=context)
- for todo in todo_pool.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):
@@ -162,8 +139,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)
@@ -175,8 +151,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)
@@ -191,8 +166,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)
@@ -296,10 +270,24 @@ class res_config_installer(osv.osv_memory):
_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')
@@ -351,8 +339,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
@@ -362,8 +350,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):
@@ -374,12 +361,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
diff --git a/openerp/netsvc.py b/openerp/netsvc.py
index 3a2add26421..359813e7d41 100644
--- a/openerp/netsvc.py
+++ b/openerp/netsvc.py
@@ -95,8 +95,8 @@ class Service(object):
raise
class LocalService(object):
- """ Proxy for local services.
-
+ """ Proxy for local services.
+
Any instance of this class will behave like the single instance
of Service(name)
"""
@@ -115,20 +115,20 @@ class LocalService(object):
return getattr(self, method)(*params)
class ExportService(object):
- """ Proxy for exported services.
+ """ Proxy for exported services.
All methods here should take an AuthProxy as their first parameter. It
will be appended by the calling framework.
- Note that this class has no direct proxy, capable of calling
- eservice.method(). Rather, the proxy should call
+ Note that this class has no direct proxy, capable of calling
+ eservice.method(). Rather, the proxy should call
dispatch(method,auth,params)
"""
-
+
_services = {}
_groups = {}
_logger = logging.getLogger('web-services')
-
+
def __init__(self, name, audience=''):
ExportService._services[name] = self
self.__name = name
@@ -143,7 +143,7 @@ class ExportService(object):
def dispatch(self, method, auth, params):
raise Exception("stub dispatch at %s" % self.__name)
-
+
def new_dispatch(self,method,auth,params):
raise Exception("stub dispatch at %s" % self.__name)
@@ -247,16 +247,16 @@ def init_alternative_logger():
class Agent(object):
""" Singleton that keeps track of cancellable tasks to run at a given
timestamp.
-
+
The tasks are characterised by:
-
+
* a timestamp
* the database on which the task run
* the function to call
* the arguments and keyword arguments to pass to the function
Implementation details:
-
+
- Tasks are stored as list, allowing the cancellation by setting
the timestamp to 0.
- A heapq is used to store tasks, so we don't need to sort
@@ -357,7 +357,7 @@ class Server:
def start(self):
self.__logger.debug("called stub Server.start")
-
+
def _late_start(self):
self.start()
for thr in Server.__starter_threads:
@@ -449,7 +449,7 @@ class OpenERPDispatcher:
self.log('exception', tools.exception_to_unicode(e))
tb = getattr(e, 'traceback', sys.exc_info())
tb_s = "".join(traceback.format_exception(*tb))
- if tools.config['debug_mode'] and isinstance(tb, types.TracebackType):
+ if tools.config['debug_mode'] and isinstance(tb[2], types.TracebackType):
import pdb
pdb.post_mortem(tb[2])
raise OpenERPDispatcherException(e, tb_s)
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 48d66f30e32..3aee2ec1899 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -1555,8 +1555,10 @@ class orm_template(object):
attrs['selection'].append((False, ''))
fields[node.get('name')] = attrs
- field = model_fields[node.get('name')]
- transfer_field_to_modifiers(field, modifiers)
+ field = model_fields.get(node.get('name'))
+ if field:
+ transfer_field_to_modifiers(field, modifiers)
+
elif node.tag in ('form', 'tree'):
result = self.view_header_get(cr, user, False, node.tag, context)