From e95fc488dbc668932f0f8f8b2223d752c4745347 Mon Sep 17 00:00:00 2001
From: Raphael Collet <rco@openerp.com>
Date: Thu, 10 Jul 2014 22:04:03 +0200
Subject: [PATCH] [IMP] fields: add mechanism to extend a selection field

If a selection field is defined by a list as selection, such as:

    state = fields.Selection([('a', 'A'), ('b', 'B')])

one can extend it by inheritance by redefining the field, as:

    state = fields.Selection(selection_add=[('c', 'C')])

The result is that the selection field will have the list
[('a', 'A'), ('b', 'B'), ('c', 'C')] as selection.
---
 openerp/addons/test_inherit/models.py         | 11 +++++++++
 .../addons/test_inherit/tests/test_inherit.py | 16 +++++++++----
 openerp/fields.py                             | 23 ++++++++++++++++++-
 3 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py
index ebbe71e0521d..f620dfe53a97 100644
--- a/openerp/addons/test_inherit/models.py
+++ b/openerp/addons/test_inherit/models.py
@@ -7,6 +7,7 @@ class mother(models.Model):
 
     name = fields.Char('Name', required=True)
     surname = fields.Char(compute='_compute_surname')
+    state = fields.Selection([('a', 'A'), ('b', 'B')])
 
     @api.one
     @api.depends('name')
@@ -35,6 +36,9 @@ class mother(models.Model):
     # extend the name field by adding a default value
     name = fields.Char(default='Unknown')
 
+    # extend the selection of the state field
+    state = fields.Selection(selection_add=[('c', 'C')])
+
     # override the computed field, and extend its dependencies
     @api.one
     @api.depends('field_in_mother')
@@ -44,4 +48,11 @@ class mother(models.Model):
         else:
             super(mother, self)._compute_surname()
 
+
+class mother(models.Model):
+    _inherit = 'test.inherit.mother'
+
+    # extend again the selection of the state field
+    state = fields.Selection(selection_add=[('d', 'D')])
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py
index b663d911f60e..8039e698669d 100644
--- a/openerp/addons/test_inherit/tests/test_inherit.py
+++ b/openerp/addons/test_inherit/tests/test_inherit.py
@@ -9,15 +9,15 @@ class test_inherits(common.TransactionCase):
         # is accessible from the child model. This test has been written
         # to verify the purpose of the inheritance computing of the class
         # in the openerp.osv.orm._build_model.
-        mother = self.registry('test.inherit.mother')
-        daugther = self.registry('test.inherit.daugther')
+        mother = self.env['test.inherit.mother']
+        daugther = self.env['test.inherit.daugther']
 
         self.assertIn('field_in_mother', mother._fields)
         self.assertIn('field_in_mother', daugther._fields)
 
     def test_field_extension(self):
         """ check the extension of a field in an inherited model """
-        mother = self.registry('test.inherit.mother')
+        mother = self.env['test.inherit.mother']
         field = mother._fields['name']
 
         # the field should inherit required=True, and have a default value
@@ -26,11 +26,19 @@ class test_inherits(common.TransactionCase):
 
     def test_depends_extension(self):
         """ check that @depends on overridden compute methods extends dependencies """
-        mother = self.registry('test.inherit.mother')
+        mother = self.env['test.inherit.mother']
         field = mother._fields['surname']
 
         # the field dependencies are added
         self.assertItemsEqual(field.depends, ['name', 'field_in_mother'])
 
+    def test_selection_extension(self):
+        """ check that attribute selection_add=... extends selection on fields. """
+        mother = self.env['test.inherit.mother']
+        field = mother._fields['state']
+
+        # the extra values are added
+        self.assertEqual(field.selection, [('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')])
+
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/fields.py b/openerp/fields.py
index bf6e29826b54..71ded7c4a5e4 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -1116,12 +1116,16 @@ class Selection(Field):
             It is given as either a list of pairs (`value`, `string`), or a
             model method, or a method name.
 
+        :param selection_add: provides an extension of the selection in the case
+            of an overridden field. It is a list of pairs (`value`, `string`).
+
         The attribute `selection` is mandatory except in the case of related
         fields (see :ref:`field-related`) or field extensions
         (see :ref:`field-incremental-definition`).
     """
     type = 'selection'
-    selection = None        # [(value, string), ...], model method or method name
+    selection = None        # [(value, string), ...], function or method name
+    selection_add = None    # [(value, string), ...]
 
     def __init__(self, selection=None, string=None, **kwargs):
         if callable(selection):
@@ -1135,6 +1139,23 @@ class Selection(Field):
         field = self.related_field
         self.selection = lambda model: field._description_selection(model.env)
 
+    def _setup_regular(self, env):
+        super(Selection, self)._setup_regular(env)
+        # determine selection (applying extensions)
+        cls = type(env[self.model_name])
+        selection = None
+        for field in resolve_all_mro(cls, self.name, reverse=True):
+            if isinstance(field, type(self)):
+                # We cannot use field.selection or field.selection_add here
+                # because those attributes are overridden by `set_class_name`.
+                if 'selection' in field._attrs:
+                    selection = field._attrs['selection']
+                if 'selection_add' in field._attrs:
+                    selection = selection + field._attrs['selection_add']
+            else:
+                selection = None
+        self.selection = selection
+
     def _description_selection(self, env):
         """ return the selection list (pairs (value, label)); labels are
             translated according to context language
-- 
GitLab