Olivier Dony (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-server/trunk-cleanup-ir-values into 
lp:openobject-server.

Requested reviews:
  Naresh(OpenERP) (nch-openerp)
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-cleanup-ir-values/+merge/73809

= Summary =

Our ir.values internal model has always been a mess. Lately it is still used 
for two things: storing user-defined default values, and binding actions to 
certain 'action slots' in the UI (mostly wizards, reports, and related links 
visible in the sidebar for each model).
The model definition is totally opaque and cryptic (column names like key2, 
key, object, ..), and their API is not better, with two generic methods 
(get/set), just as opaque and cryptic.

This branch is a first step towards a clean refactoring.

== Proposed fix ==

Ideally, we should have separate models with their own clean and specific API: 
one for storing user defaults, and one for storing action bindings (for 
example: ir.user.defaults and ir.actions.bindings)
This could be the next step.

Until then, this branch attempts to preserve the current API for perfect 
backwards-compatibility, does not require any migration, but introduces two 
separate APIs and underlying implementations for the two aspects: default vs. 
action bindings.

== Implementation notes ==

The main changes are:
- obsolete columns have been removed totally from ir.values (the meta* stuff)
- existing columns have been documented, but preserved
- the set() and get() methods are deprecated, and internally delegate their 
tasks to the new set_action/get_actions and set_default/get_defaults.
- the API is now documented
- the administration UI for ir.values has been split in 2: defaults and action 
bindings now have their own menus and views

== Tests ==

- the existing YAML tests for ir.values have been extended to cover 
smoke-testing for both defaults and action bindings
- the tests do not use the newer API directly yet, but they do so indirectly 
because the old API relies on the new implementation now

== Next steps ==

1. The rest of the server code (and clients) should be adapted progressively to 
use the new API, but that does not need to happen immediately.
2. For next version we should split everything properly into separate models. 
There is already an rudimentary and unused "ir.defaults" model lying around, 
but it's not clean enough - should be removed/refactored in the process.
-- 
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-cleanup-ir-values/+merge/73809
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-server/trunk-cleanup-ir-values.
=== modified file 'openerp/addons/base/ir/ir.xml'
--- openerp/addons/base/ir/ir.xml	2011-08-22 14:52:44 +0000
+++ openerp/addons/base/ir/ir.xml	2011-09-02 13:27:32 +0000
@@ -7,44 +7,50 @@
             <field name="name">ir.values.form.action</field>
             <field name="model">ir.values</field>
             <field name="type">form</field>
-            <field name="priority">20</field>
             <field name="arch" type="xml">
-                <form string="Connect Events to Actions">
-                   <field name="name" required="1"/>
+                <form string="Action Bindings">
+                   <field name="name"/>
                    <newline/>
                     <group col="2" colspan="2">
-                        <separator string="Action Source" colspan="2"/>
+                        <separator string="Action Binding" colspan="2"/>
                         <field name="model_id" on_change="onchange_object_id(model_id)"/>
+                        <field name="model"/>
                         <field name="res_id"/>
-                        <field name="key2" required="1"/>
+                        <field name="key2"/>
                     </group>
                     <group col="2" colspan="2">
-                        <separator string="Action To Launch" colspan="2"/>
+                        <separator string="Action" colspan="2"/>
                         <field name="action_id" on_change="onchange_action_id(action_id)"/>
-
-                        <field name="object" readonly="1"/>
-
-                    </group>
-                    <group col="2" colspan="2">
-                        <separator string="Values for Event Type" colspan="2"/>
-                        <label string="client_action_multi, client_action_relate" colspan="2"/>
-                        <label string="tree_but_action, client_print_multi"  colspan="2"/>
-                    </group>
-                    <group col="2" colspan="2">
-                        <separator colspan="2" string="Value"/>
-                         <field name="value_unpickle" nolabel="1" colspan="4"/>
-                    </group>
-                    <group col="2" colspan="2">
-                        <separator colspan="2" string="Metadata"/>
-                        <field name="meta_unpickle" nolabel="1"/>
-                    </group>
-                    <group col="2" colspan="2">
-                    <separator colspan="2" string=""/>
-                    <field name="user_id"/>
-                    <field name="company_id" groups="base.group_multi_company"/>
-                    </group>
-              </form>
-
+                        <field name="value_unpickle" colspan="4" string="Action Reference"/>
+                    </group>
+                </form>
+            </field>
+        </record>
+
+        <record id="values_view_form_defaults" model="ir.ui.view">
+            <field name="name">ir.values.form.defaults</field>
+            <field name="model">ir.values</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="User-defined Defaults">
+                   <field name="name"/>
+                   <newline/>
+                    <group col="2" colspan="2">
+                        <separator string="Model" colspan="2"/>
+                        <field name="model_id" on_change="onchange_object_id(model_id)"/>
+                        <field name="model"/>
+                        <field name="key2" string="Condition"/>
+                    </group>
+                    <group col="2" colspan="2">
+                        <separator string="Default Value" colspan="2"/>
+                        <field name="value_unpickle" colspan="4" nolabel="1"/>
+                    </group>
+                    <group col="2" colspan="2">
+                        <separator colspan="2" string="Default Value Scope"/>
+                        <field name="user_id"/>
+                        <field name="company_id" groups="base.group_multi_company"/>
+                    </group>
+                </form>
             </field>
         </record>
 
@@ -53,10 +59,9 @@
             <field name="model">ir.values</field>
             <field name="type">tree</field>
             <field name="arch" type="xml">
-                <tree string="Client Actions">
+                <tree string="Action Bindings/Defaults">
                     <field name="name"/>
                     <field name="model"/>
-                    <field name="action_id"/>
                     <field name="key2"/>
                 </tree>
             </field>
@@ -73,7 +78,7 @@
                     <field name="key2"/>
                     <newline/>
                     <group expand="0" string="Group By...">
-                        <filter string="Object" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
+                        <filter string="Model" icon="terp-stock_align_left_24" domain="[]" context="{'group_by':'model'}"/>
                         <filter string="Type" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'key2'}"/>
                     </group>
                 </search>
@@ -81,23 +86,21 @@
         </record>
 
         <record id="act_values_form_action" model="ir.actions.act_window">
-            <field name="name">Client Events</field>
+            <field name="name">Action Bindings</field>
             <field name="type">ir.actions.act_window</field>
             <field name="res_model">ir.values</field>
             <field name="view_type">form</field>
             <field name="view_mode">tree,form</field>
             <field name="search_view_id" ref="values_view_search_action"/>
             <field name="domain">[('key','=','action')]</field>
-            <field name="context">{'read':'default','default_object':1}</field>
+            <field name="context">{'default_object':1,'default_key':'action'}</field>
         </record>
-
         <record model="ir.actions.act_window.view" id="action_values_tree_view">
             <field name="sequence" eval="1"/>
             <field name="view_mode">tree</field>
             <field name="view_id" ref="values_view_tree_action"/>
             <field name="act_window_id" ref="act_values_form_action"/>
         </record>
-
         <record model="ir.actions.act_window.view" id="action_values_form_view">
             <field name="sequence" eval="2"/>
             <field name="view_mode">form</field>
@@ -105,54 +108,27 @@
             <field name="act_window_id" ref="act_values_form_action"/>
         </record>
 
-
-
-
-        <!-- Values -->
-
-        <record id="values_view_form" model="ir.ui.view">
-            <field name="name">ir.values.form</field>
-            <field name="model">ir.values</field>
-            <field name="type">form</field>
-            <field name="arch" type="xml">
-                <form string="Values">
-                    <field name="name" select="1"/>
-                    <field name="model" select="1"/>
-                    <field name="key" select="1"/>
-                    <field name="key2" select="2"/>
-                    <field name="object" select="2"/>
-                    <field name="res_id"/>
-                    <field name="user_id" select="2"/>
-                    <field name="company_id" select="2"/>
-                    <field name="value_unpickle"/>
-                    <field name="meta_unpickle"/>
-                </form>
-            </field>
-        </record>
-
-        <record id="values_view_tree" model="ir.ui.view">
-            <field name="name">ir.values.tree</field>
-            <field name="model">ir.values</field>
-            <field name="type">tree</field>
-            <field name="arch" type="xml">
-                <tree string="Values">
-                    <field name="name"/>
-                    <field name="model"/>
-                    <field name="key"/>
-                    <field name="key2"/>
-                    <field name="user_id"/>
-                    <field name="company_id"/>
-                </tree>
-            </field>
-        </record>
-
-        <record id="act_values_form" model="ir.actions.act_window">
-            <field name="name">Client Actions Connections</field>
+        <record id="act_values_form_defaults" model="ir.actions.act_window">
+            <field name="name">User-defined Defaults</field>
             <field name="type">ir.actions.act_window</field>
             <field name="res_model">ir.values</field>
             <field name="view_type">form</field>
-            <field name="view_id" ref="values_view_tree"/>
-            <field name="context">{'read':'default'}</field>
+            <field name="view_mode">tree,form</field>
+            <field name="search_view_id" ref="values_view_search_action"/>
+            <field name="domain">[('key','=','default')]</field>
+            <field name="context">{'default_object':0,'default_key':'default','default_key2':''}</field>
+        </record>
+        <record model="ir.actions.act_window.view" id="action_values_defaults_tree_view">
+            <field name="sequence" eval="1"/>
+            <field name="view_mode">tree</field>
+            <field name="view_id" ref="values_view_tree_action"/>
+            <field name="act_window_id" ref="act_values_form_defaults"/>
+        </record>
+        <record model="ir.actions.act_window.view" id="action_values_defaults_form_view">
+            <field name="sequence" eval="2"/>
+            <field name="view_mode">form</field>
+            <field name="view_id" ref="values_view_form_defaults"/>
+            <field name="act_window_id" ref="act_values_form_defaults"/>
         </record>
 
         <!-- Sequences -->
@@ -342,6 +318,7 @@
         <menuitem id="next_id_6" name="Actions" parent="base.next_id_4" sequence="1"/>
         <menuitem action="ir_sequence_actions" id="menu_ir_sequence_actions" parent="next_id_6"/>
         <menuitem action="act_values_form_action" id="menu_values_form_action" parent="next_id_6"/>
+        <menuitem action="act_values_form_defaults" id="menu_values_form_defaults" parent="next_id_6"/>
 
         <!--Filters form view-->
 

=== modified file 'openerp/addons/base/ir/ir_values.py'
--- openerp/addons/base/ir/ir_values.py	2011-06-10 10:19:24 +0000
+++ openerp/addons/base/ir/ir_values.py	2011-09-02 13:27:32 +0000
@@ -28,7 +28,76 @@
     'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml',
     'report_sxw_content_data', 'report_rml_content_data', 'search_view', ))
 
+#: Possible slots to bind an action to with :meth:`~.set_action`
+ACTION_SLOTS = [
+                "client_action_multi",  # sidebar wizard action
+                "client_print_multi",   # sidebar report printing button
+                "client_action_relate", # sidebar related link
+                "tree_but_open",        # double-click on item in tree view
+                "tree_but_action",      # deprecated: same as tree_but_open
+               ]
+
+
 class ir_values(osv.osv):
+    """Holds internal model-specific action bindings and user-defined default
+       field values. definitions. This is a legacy internal model, mixing
+       two different concepts, and will likely be updated or replaced in a
+       future version by cleaner, separate models. You should not depend
+       explicitly on it.
+
+       The purpose of each ``ir.values`` entry depends on its type, defined
+       by the ``key`` column:
+
+        * 'default': user-defined default values, used when creating new
+          records of this model:
+        * 'action': binding of an action to a particular *action slot* of
+          this model, making the action easily available in the user
+          interface for this model.
+
+       The ``key2`` column acts as a qualifier, further refining the type
+       of the entry. The possible values are:
+
+        * for 'default' entries: an optional condition restricting the
+          cases where this particular default value will be applicable,
+          or ``False`` for no condition
+        * for 'action' entries: the ``key2`` qualifier is one of the available
+          action slots, defining how this action can be invoked:
+
+            * ``'client_print_multi'`` for report printing actions that will
+              be available on views displaying items from this model
+            * ``'client_action_multi'`` for assistants (wizards) actions
+              that will be available in views displaying objects of this model
+            * ``'client_action_relate'`` for links towards related documents
+              that should be available in views displaying objects of this model
+            * ``'tree_but_open'`` for actions that will be triggered when
+              double-clicking an item from this model in a hierarchical tree view
+
+       Each entry is specific to a model (``model`` column), and for ``'actions'``
+       type, may even be made specific to a given record of that model when the
+       ``res_id`` column contains a record ID (``False`` means it's global for
+       all records).
+
+       The content of the entry is defined by the ``value`` column, which may either
+       contain an arbitrary value (for default values - when the ``object`` column
+       is False), or a reference string defining the action that should be executed.
+
+       .. rubric:: Usage: default values
+       
+       The ``'default'`` entries are usually defined manually by the
+       users, and set by their UI clients calling :meth:`~.set_default`.
+       These default values are then automatically used by the
+       ORM every time a new record is about to be created, i.e. when
+       :meth:`~openerp.osv.osv.osv.default_get`
+       or :meth:`~openerp.osv.osv.osv.create` are called.
+
+       .. rubric:: Usage: action bindings
+
+       Business applications will usually bind their actions during
+       installation, and OpenERP UI clients will apply them as defined,
+       based on the list of actions included in the result of
+       :meth:`~openerp.osv.osv.osv.fields_view_get`,
+       or directly returned by explicit calls to :meth:`~.get_actions`.
+    """
     _name = 'ir.values'
 
     def _value_unpickle(self, cursor, user, ids, name, arg, context=None):
@@ -38,7 +107,7 @@
             if not report.object and value:
                 try:
                     value = str(pickle.loads(value))
-                except:
+                except Exception:
                     pass
             res[report.id] = value
         return res
@@ -53,44 +122,69 @@
             value = pickle.dumps(value)
         self.write(cursor, user, id, {name[:-9]: value}, context=ctx)
 
-    def onchange_object_id(self, cr, uid, ids, object_id, context={}):
+    def onchange_object_id(self, cr, uid, ids, object_id, context=None):
         if not object_id: return {}
         act = self.pool.get('ir.model').browse(cr, uid, object_id, context=context)
         return {
                 'value': {'model': act.model}
         }
 
-    def onchange_action_id(self, cr, uid, ids, action_id, context={}):
+    def onchange_action_id(self, cr, uid, ids, action_id, context=None):
         if not action_id: return {}
         act = self.pool.get('ir.actions.actions').browse(cr, uid, action_id, context=context)
         return {
                 'value': {'value_unpickle': act.type+','+str(act.id)}
         }
 
+    def onchange_key(self, cr, uid, ids, key, context=None):
+        return {
+                'value': {'object': key == 'action'}
+        }
+
     _columns = {
-        'name': fields.char('Name', size=128),
-        'model_id': fields.many2one('ir.model', 'Object', size=128,
-            help="This field is not used, it only helps you to select a good model."),
-        'model': fields.char('Object Name', size=128, select=True),
-        'action_id': fields.many2one('ir.actions.actions', 'Action',
-            help="This field is not used, it only helps you to select the right action."),
-        'value': fields.text('Value'),
+        'name': fields.char('Name', size=128, required=True),
+        'model': fields.char('Model Name', size=128, select=True, required=True,
+                             help="Model to which this entry applies"),
+
+        # TODO: model_id and action_id should be read-write function fields
+        'model_id': fields.many2one('ir.model', 'Model (change only)', size=128,
+                                    help="Model to which this entry applies - "
+                                         "helper field for setting a model, will "
+                                         "automatically set the correct model name"),
+        'action_id': fields.many2one('ir.actions.actions', 'Action (change only)',
+                                     help="Action bound to this entry - "
+                                         "helper field for binding an action, will "
+                                         "automatically set the correct reference"),
+
+        'value': fields.text('Value', help="Default value (pickled) or reference to an action"),
         'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle,
-            method=True, type='text', string='Value'),
-        'object': fields.boolean('Is Object'),
-        'key': fields.selection([('action','Action'),('default','Default')], 'Type', size=128, select=True),
-        'key2' : fields.char('Event Type',help="The kind of action or button in the client side that will trigger the action.", size=128, select=True),
-        'meta': fields.text('Meta Datas'),
-        'meta_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle,
-            method=True, type='text', string='Metadata'),
-        'res_id': fields.integer('Object ID', help="Keep 0 if the action must appear on all resources.", select=True),
-        'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True),
-        'company_id': fields.many2one('res.company', 'Company', select=True)
+                                          type='text',
+                                          string='Default value or action reference'),
+        'object': fields.boolean('No value serialization', help="Should be enabled when Type is Action"),
+        'key': fields.selection([('action','Action'),('default','Default')],
+                                'Type', size=128, select=True, required=True,
+                                help="- Action: an action attached to one slot of the given model\n"
+                                     "- Default: a default value for a model field"),
+        'key2' : fields.char('Qualifier', size=128, select=True,
+                             help="For actions, one of the possible action slots: \n"
+                                  "  - client_action_multi\n"
+                                  "  - client_print_multi\n"
+                                  "  - client_action_relate\n"
+                                  "  - tree_but_open\n"
+                                  "For defaults, an optional condition"
+                             ,),
+        'res_id': fields.integer('Record ID', select=True,
+                                 help="Database identifier of the record to which this applies. "
+                                      "0 = for all records"),
+        'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True,
+                                   help="If set, action binding only applies for this user."),
+        'company_id': fields.many2one('res.company', 'Company', ondelete='cascade', select=True,
+                                      help="If set, action binding only applies for this company")
     }
     _defaults = {
-        'key': lambda *a: 'action',
-        'key2': lambda *a: 'tree_but_open',
-        'company_id': lambda *a: False
+        'key': 'action',
+        'key2': 'tree_but_open',
+        'object': True,
     }
 
     def _auto_init(self, cr, context=None):
@@ -99,140 +193,246 @@
         if not cr.fetchone():
             cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)')
 
-    def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
+    def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False):
+        """Defines a default value for the given model and field_name. Any previous
+           default will be replaced and lost in the process.
+
+           :param string model: model name
+           :param string field_name: field name to which the default applies
+           :param value: the default field value to set
+           :type value: any serializable Python value
+           :param bool for_all_users: whether the default should apply to everybody or only
+                                      the user calling the method
+           :param int company_id: optional ID of the company to which the default should
+                                  apply. If omitted, the default will be global. If True
+                                  is passed, the current user's company will be used.
+           :param string condition: optional condition specification that can be used to
+                                    restrict the applicability of the default values
+                                    (e.g. based on another field's value)
+           :return: id of the newly created ir.values entry
+        """
         if isinstance(value, unicode):
             value = value.encode('utf8')
-        if not isobject:
-            value = pickle.dumps(value)
-        if meta:
-            meta = pickle.dumps(meta)
-        assert isinstance(models, (list, tuple)), models
-        ids_res = []
-        for model in models:
+        if company_id is True:
+            # should be company-specific, need to get company id
+            user = self.pool.get('res.users').browse(cr, uid, uid)
+            company_id = user.company_id.id
+
+        # remove existing defaults for the same scope
+        search_criteria = [
+            ('key', '=', 'default'),
+            ('key2', '=', condition),
+            ('model', '=', model),
+            ('name', '=', field_name),
+            ('user_id', '=', False if for_all_users else uid),
+            ('company_id','=', company_id)
+            ]
+        self.unlink(cr, uid, self.search(cr, uid, search_criteria))
+
+        return self.create(cr, uid, {
+            'name': field_name,
+            'value': pickle.dumps(value),
+            'model': model,
+            'object': False,
+            'key': 'default',
+            'key2': condition and condition[:200],
+            'user_id': False if for_all_users else uid,
+            'company_id': company_id,
+        })
+
+    def get_defaults(self, cr, uid, model, condition=False):
+        """Returns any user-defined values registered for the given model.
+           This is not field-specific, but an optional ``condition`` can be
+           provided to query for default values that should be applied only
+           when the given condition is met (usually another field's value).
+           Defaults that have been set for the user herself will have higher
+           priority that those that have been set for everyone
+           (see :meth:`~.set_default`).
+
+           :param string model: model name
+           :param string condition: optional condition specification that can be used to
+                                    restrict the applicability of the default values
+                                    (e.g. based on another field's value)
+           :return: list of default values tuples of the form ``(id, field_name, value)``
+                    (``id`` is the ID of the default entry, usually irrelevant)
+        """
+        # use a direct SQL query for performance reasons,
+        # this is called very often
+        query = """SELECT v.id, v.name, v.value FROM ir_values v
+                      LEFT JOIN res_users u ON (v.user_id = u.id)
+                   WHERE v.key = %%s AND v.model = %%s
+                      AND (v.user_id = %%s OR v.user_id IS NULL)
+                      AND (u.company_id IS NULL OR
+                           u.company_id =
+                             (SELECT company_id from res_users where id = %%s)
+                          )
+                      %s
+                   ORDER BY v.user_id, u.company_id"""
+        query = query % ('AND v.key2 = %s' if condition else '')
+        params = ('default', model, uid, uid)
+        if condition:
+            params += (condition,)
+        cr.execute(query, params)
+
+        # keep only the highest priority default for each field
+        defaults = {}
+        for row in cr.dictfetchall():
+            defaults.setdefault(row['name'],
+                (row['id'], row['name'], pickle.loads(row['value'].encode('utf-8'))))
+        return defaults.values()
+
+    def set_action(self, cr, uid, name, action_slot, model, action, res_id=False):
+        """Binds an the given action to the given model's action slot - for later
+           retrieval via :meth:`~.get_actions`. Any existing binding of the same action
+           to the same slot is first removed, allowing an update of the action's name.
+           See the class description for more details about the various action
+           slots: :class:`~ir_values`.
+
+           :param string name: action label, usually displayed by UI client
+           :param string action_slot: the action slot to which the action should be
+                                      bound to - one of ``client_action_multi``,
+                                      ``client_print_multi``, ``client_action_relate``,
+                                      ``tree_but_open``.
+           :param string model: model name
+           :param string action: action reference, in the form ``'model,id'``
+           :param int res_id: optional record id - will bind the action only to a
+                              specific record of the model, not all records.
+           :return: id of the newly created ir.values entry
+        """
+        assert isinstance(action, basestring) and ',' in action, \
+               'Action definition must be an action reference, e.g. "ir.actions.act_window,42"'
+        assert action_slot in ACTION_SLOTS, \
+               'Action slot (%s) must be one of: %r' % (action_slot, ACTION_SLOTS)
+
+        # remove existing action definition of same slot and value
+        search_criteria = [
+            ('key', '=', 'action'),
+            ('key2', '=', action_slot),
+            ('model', '=', model),
+            ('res_id', '=', res_id or 0), # int field -> NULL == 0
+            ('value', '=', action),
+            ]
+        self.unlink(cr, uid, self.search(cr, uid, search_criteria))
+
+        return self.create(cr, uid, {
+            'key': 'action',
+            'key2': action_slot,
+            'object': True,
+            'model': model,
+            'res_id': res_id,
+            'name': name,
+            'value': action,
+        })
+
+    def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None):
+        """Retrieves the list of actions bound to the given model's action slot.
+           See the class description for more details about the various action
+           slots: :class:`~.ir_values`.
+
+           :param string action_slot: the action slot to which the actions should be
+                                      bound to - one of ``client_action_multi``,
+                                      ``client_print_multi``, ``client_action_relate``,
+                                      ``tree_but_open``.
+           :param string model: model name
+           :param int res_id: optional record id - will bind the action only to a
+                              specific record of the model, not all records.
+           :return: list of action tuples of the form ``(id, name, action_def)``,
+                    where ``id`` is the ID of the default entry, ``name`` is the
+                    action label, and ``action_def`` is a dict containing the
+                    action definition as obtained by calling
+                    :meth:`~openerp.osv.osv.osv.read` on the action record.
+        """
+        assert action_slot in ACTION_SLOTS, 'Illegal action slot value: %s' % action_slot
+        # use a direct SQL query for performance reasons,
+        # this is called very often
+        query = """SELECT v.id, v.name, v.value FROM ir_values v
+                   WHERE v.key = %s AND v.key2 = %s
+                        AND v.model = %s
+                        AND (v.res_id = %s
+                             OR v.res_id IS NULL
+                             OR v.res_id = 0)
+                   ORDER BY v.id"""
+        cr.execute(query, ('action', action_slot, model, res_id or None))
+        results = {}
+        for action in cr.dictfetchall():
+            action_model,id = action['value'].split(',')
+            fields = [
+                    field
+                    for field in self.pool.get(action_model)._all_columns
+                    if field not in EXCLUDED_FIELDS]
+            # FIXME: needs cleanup
+            try:
+                action_def = self.pool.get(action_model).read(cr, uid, int(id), fields, context)
+                if action_def:
+                    if action_model in ('ir.actions.report.xml','ir.actions.act_window',
+                                        'ir.actions.wizard'):
+                        groups = action_def.get('groups_id')
+                        if groups:
+                            cr.execute('SELECT 1 FROM res_groups_users_rel WHERE gid IN %s AND uid=%s',
+                                       (tuple(groups), uid))
+                            if not cr.fetchone():
+                                if action['name'] == 'Menuitem':
+                                    raise osv.except_osv('Error !',
+                                                         'You do not have the permission to perform this operation !!!')
+                                continue
+                # keep only the first action registered for each action name
+                results[action['name']] = (action['id'], action['name'], action_def)
+            except except_orm, e:
+                continue
+        return results.values()
+
+    def _map_legacy_model_list(self, model_list, map_fn, merge_results=False):
+        """Apply map_fn to the various models passed, according to
+           legacy way to specify models/records.
+        """
+        assert isinstance(model_list, (list, tuple)), \
+            "model_list should be in the form [model,..] or [(model,res_id), ..]"
+        results = []
+        for model in model_list:
+            res_id = False
             if isinstance(model, (list, tuple)):
-                model,res_id = model
+                model, res_id = model
+            result = map_fn(model, res_id)
+            # some of the functions return one result at a time (tuple or id)
+            # and some return a list of many of them - care for both
+            if merge_results:
+                results.extend(result)
             else:
-                res_id = False
-            if replace:
-                search_criteria = [
-                    ('key', '=', key),
-                    ('key2', '=', key2),
-                    ('model', '=', model),
-                    ('res_id', '=', res_id),
-                    ('user_id', '=', preserve_user and uid)
-                ]
-                if key in ('meta', 'default'):
-                    search_criteria.append(('name', '=', name))
-                else:
-                    search_criteria.append(('value', '=', value))
-
-                self.unlink(cr, uid, self.search(cr, uid, search_criteria))
-            vals = {
-                'name': name,
-                'value': value,
-                'model': model,
-                'object': isobject,
-                'key': key,
-                'key2': key2 and key2[:200],
-                'meta': meta,
-                'user_id': preserve_user and uid,
-            }
-            if company:
-                cid = self.pool.get('res.users').browse(cr, uid, uid, context={}).company_id.id
-                vals['company_id']=cid
-            if res_id:
-                vals['res_id']= res_id
-            ids_res.append(self.create(cr, uid, vals))
-        return ids_res
+                results.append(result)
+        return results
+
+    # Backards-compatibility adapter layer to retrofit into split API
+    def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
+        """Deprecated legacy method to set default values and bind actions to models' action slots.
+           Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default`
+           (``key=='default'``) or :meth:`~.set_action` (``key == 'action'``).
+
+          :deprecated: As of v6.1, ``set_default()`` or ``set_action()`` should be used directly.
+        """
+        assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
+        if key == 'default':
+            def do_set(model,res_id):
+                return self.set_default(cr, uid, model, field_name=name, value=value,
+                                        for_all_users=(not preserve_user), company_id=company,
+                                        condition=key2)
+        elif key == 'action':
+            def do_set(model,res_id):
+                return self.set_action(cr, uid, name, action_slot=key2, model=model, action=value, res_id=res_id)
+        return self._map_legacy_model_list(models, do_set)
 
     def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
-        if context is None:
-            context = {}
-        result = []
-        assert isinstance(models, (list, tuple)), models
-
-        for m in models:
-            if isinstance(m, (list, tuple)):
-                m, res_id = m
-            else:
-                res_id = False
-
-            where = ['key=%s','model=%s']
-            params = [key, str(m)]
-            if key2:
-                where.append('key2=%s')
-                params.append(key2[:200])
-            elif key2_req and not meta:
-                where.append('key2 is null')
-            if res_id_req and (models[-1][0] == m):
-                if res_id:
-                    where.append('res_id=%s')
-                    params.append(res_id)
-                else:
-                    where.append('(res_id is NULL)')
-            elif res_id:
-                if (models[-1][0]==m):
-                    where.append('(res_id=%s or (res_id is null))')
-                    params.append(res_id)
-                else:
-                    where.append('res_id=%s')
-                    params.append(res_id)
-            order = 'id'
-            if key == 'default':
-                # Make sure we get first the values for specific users, then
-                # the global values. The map/filter below will retain the first
-                # value for any given name. The 'order by' will put the null
-                # values last; this may be postgres specific (it is the
-                # behavior in postgres at least since 8.2).
-                order = 'user_id'
-            where.append('(user_id=%s or (user_id IS NULL)) order by '+ order)
-            params.append(uid)
-            clause = ' and '.join(where)
-            cr.execute('select id,name,value,object,meta, key from ir_values where ' + clause, params)
-            result = cr.fetchall()
-            if result:
-                break
-
-        if not result:
-            return []
-
-        def _result_get(x, keys):
-            if x[1] in keys:
-                return False
-            keys.append(x[1])
-            if x[3]:
-                model,id = x[2].split(',')
-                # FIXME: It might be a good idea to opt-in that kind of stuff
-                # FIXME: instead of arbitrarily removing random fields
-                fields = [
-                    field
-                    for field in self.pool.get(model).fields_get_keys(cr, uid)
-                    if field not in EXCLUDED_FIELDS]
-
-                try:
-                    datas = self.pool.get(model).read(cr, uid, [int(id)], fields, context)
-                except except_orm, e:
-                    return False
-                datas = datas and datas[0]
-                if not datas:
-                    return False
-            else:
-                datas = pickle.loads(x[2].encode('utf-8'))
-            if meta:
-                return (x[0], x[1], datas, pickle.loads(x[4]))
-            return (x[0], x[1], datas)
-        keys = []
-        res = filter(None, map(lambda x: _result_get(x, keys), result))
-        res2 = res[:]
-        for r in res:
-            if isinstance(r[2], dict) and r[2].get('type') in ('ir.actions.report.xml','ir.actions.act_window','ir.actions.wizard'):
-                groups = r[2].get('groups_id')
-                if groups:
-                    cr.execute('SELECT COUNT(1) FROM res_groups_users_rel WHERE gid IN %s AND uid=%s',(tuple(groups), uid))
-                    cnt = cr.fetchone()[0]
-                    if not cnt:
-                        res2.remove(r)
-                    if r[1] == 'Menuitem' and not res2:
-                        raise osv.except_osv('Error !','You do not have the permission to perform this operation !!!')
-        return res2
-ir_values()
+        """Deprecated legacy method to get the list of default values or actions bound to models' action slots.
+           Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults`
+           (``key=='default'``) or :meth:`~.get_actions` (``key == 'action'``)
+
+          :deprecated: As of v6.1, ``get_defaults()`` or ``get_actions()`` should be used directly.
+
+        """
+        assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
+        if key == 'default':
+            def do_get(model,res_id):
+                return self.get_defaults(cr, uid, model, condition=key2)
+        elif key == 'action':
+            def do_get(model,res_id):
+                return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context)
+        return self._map_legacy_model_list(models, do_get, merge_results=True)

=== modified file 'openerp/addons/base/test/test_ir_values.yml'
--- openerp/addons/base/test/test_ir_values.yml	2011-05-23 12:09:40 +0000
+++ openerp/addons/base/test/test_ir_values.yml	2011-09-02 13:27:32 +0000
@@ -2,24 +2,71 @@
     Create some default value for some (non-existing) model, for all users.
 -
     !python {model: ir.values }: |
-        self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'global value')
+        self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'global value')
 -
     Retrieve it.
 -
     !python {model: ir.values }: |
         # d is a list of triple (id, name, value)
         d = self.get(cr, uid, 'default', False, ['unexisting_model'])
-        assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value."
+        assert d[0][1] == 'my_test_field', "Can't retrieve the created default value."
         assert d[0][2] == 'global value', "Can't retrieve the created default value."
 -
     Do it again but for a specific user.
 -
     !python {model: ir.values }: |
-        self.set(cr, uid, 'default', False, 'my_test_ir_value',['unexisting_model'], 'specific value', preserve_user=True)
+        self.set(cr, uid, 'default', False, 'my_test_field',['unexisting_model'], 'specific value', preserve_user=True)
 -
     Retrieve it and check it is the one for the current user.
 -
     !python {model: ir.values }: |
         d = self.get(cr, uid, 'default', False, ['unexisting_model'])
-        assert d[0][1] == u'my_test_ir_value', "Can't retrieve the created default value."
+        assert len(d) == 1, "Only one default must be returned per field"
+        assert d[0][1] == 'my_test_field', "Can't retrieve the created default value."
         assert d[0][2] == 'specific value', "Can't retrieve the created default value."
+-
+    Create some action bindings for a non-existing model
+-
+    !python {model: ir.values }: |
+        self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
+        self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True)
+        self.set(cr, uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True)
+        self.set(cr, uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,2', isobject=True)
+        self.set(cr, uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True)
+-
+    Replace one action binding to set a new name
+-
+    !python {model: ir.values }: |
+        self.set(cr, uid, 'action', 'tree_but_open', 'OnDblClick Action New', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
+-
+    Retrieve the action bindings and check they're correct
+-
+    !python {model: ir.values }: |
+        actions = self.get(cr, uid, 'action', 'tree_but_open', ['unexisting_model'])
+        assert len(actions) == 2, "Mismatching number of bound actions"
+        #first action
+        assert len(actions[0]) == 3, "Malformed action definition"
+        assert actions[0][1] == 'OnDblClick Action 2', 'Bound action does not match definition'
+        assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 11, 'Bound action does not match definition'
+        #second action - this ones comes last because it was re-created with a different name
+        assert len(actions[1]) == 3, "Malformed action definition"
+        assert actions[1][1] == 'OnDblClick Action New', 'Re-Registering an action should replace it'
+        assert isinstance(actions[1][2], dict) and actions[1][2]['id'] == 10, 'Bound action does not match definition'
+
+        actions = self.get(cr, uid, 'action', 'client_action_multi', ['unexisting_model'])
+        assert len(actions) == 1, "Mismatching number of bound actions"
+        assert len(actions[0]) == 3, "Malformed action definition"
+        assert actions[0][1] == 'Side Wizard', 'Bound action does not match definition'
+        assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 12, 'Bound action does not match definition'
+
+        actions = self.get(cr, uid, 'action', 'client_print_multi', ['unexisting_model'])
+        assert len(actions) == 1, "Mismatching number of bound actions"
+        assert len(actions[0]) == 3, "Malformed action definition"
+        assert actions[0][1] == 'Nice Report', 'Bound action does not match definition'
+        assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 2, 'Bound action does not match definition'
+
+        actions = self.get(cr, uid, 'action', 'client_action_relate', ['unexisting_model'])
+        assert len(actions) == 1, "Mismatching number of bound actions"
+        assert len(actions[0]) == 3, "Malformed action definition"
+        assert actions[0][1] == 'Related Stuff', 'Bound action does not match definition'
+        assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 14, 'Bound action does not match definition'

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to