diff --git a/doc/howtos/backend.rst b/doc/howtos/backend.rst index a3e2e5abf24..e723017d455 100644 --- a/doc/howtos/backend.rst +++ b/doc/howtos/backend.rst @@ -615,7 +615,7 @@ The second inheritance mechanism (delegation) allows to link every record of a model to a record in a parent model, and provides transparent access to the fields of the parent record. -.. image:: backend/inheritance_methods.png +.. image:: ../images/inheritance_methods.png :align: center .. seealso:: diff --git a/doc/howtos/backend/inheritance_methods.png b/doc/images/inheritance_methods.png similarity index 100% rename from doc/howtos/backend/inheritance_methods.png rename to doc/images/inheritance_methods.png diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst index 3a1e1e6fd2a..d010e8f5d0e 100644 --- a/doc/reference/orm.rst +++ b/doc/reference/orm.rst @@ -34,6 +34,8 @@ Model * If :attr:`._name` is unset, name of a single model to extend in-place + See :ref:`reference/orm/inheritance`. + .. attribute:: _order Ordering field when searching without an ordering specified (default: @@ -140,6 +142,8 @@ Model .. automethod:: name_get .. automethod:: name_create + .. _reference/orm/model/automatic: + .. rubric:: Automatic fields .. attribute:: id @@ -297,6 +301,110 @@ Relational fields .. autoclass:: openerp.fields.Reference :show-inheritance: +.. _reference/orm/inheritance: + +Inheritance and extension +========================= + +Odoo provides three different mechanisms to extend models in a modular way: + +* creating a new model from an existing one, adding new information to the + copy but leaving the original module as-is +* extending models defined in other modules in-place, replacing the previous + version +* delegating some of the model's fields to records it contains + +.. image:: ../images/inheritance_methods.png + :align: center + +Classical inheritance +--------------------- + +When using the :attr:`~openerp.models.Model._inherit` and +:attr:`~openerp.models.Model._name` attributes together, Odoo creates a new +model using the existing one (provided via +:attr:`~openerp.models.Model._inherit`) as a base. The new model gets all the +fields, methods and meta-information (defaults & al) from its base. + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/inheritance.py + :language: python + :lines: 5- + +and using them: + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_inheritance.py + :language: python + :lines: 8,12,9,19 + +will yield: + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_inheritance.py + :language: text + :lines: 15,22 + +the second model has inherited from the first model's ``check`` method and its +``name`` field, but overridden the ``call`` method, as when using standard +:ref:`Python inheritance `. + +Extension +--------- + +When using :attr:`~openerp.models.Model._inherit` but leaving out +:attr:`~openerp.models.Model._name`, the new model replaces the existing one, +essentially extending it in-place. This is useful to add new fields or methods +to existing models (created in other modules), or to customize or reconfigure +them (e.g. to change their default sort order): + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/extension.py + :language: python + :lines: 5- + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_extension.py + :language: python + :lines: 8,13 + +will yield: + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_extension.py + :language: text + :lines: 11 + +.. note:: it will also yield the various :ref:`automatic fields + ` unless they've been disabled + +Delegation +---------- + +The third inheritance mechanism provides more flexibility (it can be altered +at runtime) but less power: using the :attr:`~openerp.models.Model._inherits` +a model *delegates* the lookup of any field not found on the current model +to "children" models. The delegation is performed via +:class:`~openerp.fields.Reference` fields automatically set up on the parent +model: + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/delegation.py + :language: python + :lines: 5- + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_delegation.py + :language: python + :lines: 9-12,21,26 + +will result in: + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_delegation.py + :language: text + :lines: 23,28 + +and it's possible to write directly on the delegated field: + +.. literalinclude:: ../../openerp/addons/test_documentation_examples/tests/test_delegation.py + :language: python + :lines: 47 + +.. warning:: when using delegation inheritance, methods are *not* inherited, + only fields + .. _reference/orm/domains: Domains diff --git a/openerp/addons/test_documentation_examples/__init__.py b/openerp/addons/test_documentation_examples/__init__.py new file mode 100644 index 00000000000..a95caccaabe --- /dev/null +++ b/openerp/addons/test_documentation_examples/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +import inheritance +import extension +import delegation diff --git a/openerp/addons/test_documentation_examples/__openerp__.py b/openerp/addons/test_documentation_examples/__openerp__.py new file mode 100644 index 00000000000..41bf4b33fa0 --- /dev/null +++ b/openerp/addons/test_documentation_examples/__openerp__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Documentation examples test", + 'description': """ + Contains pieces of code to be used as technical documentation examples + (via the ``literalinclude`` directive) in situations where they can be + syntax-checked and tested. + """, + + 'author': "Odoo", + 'website': "http://odoo.com", + + 'category': 'Tests', + 'version': '0.1', + 'data': [ + 'ir.model.access.csv', + ], +} diff --git a/openerp/addons/test_documentation_examples/delegation.py b/openerp/addons/test_documentation_examples/delegation.py new file mode 100644 index 00000000000..7b759787d7f --- /dev/null +++ b/openerp/addons/test_documentation_examples/delegation.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from openerp import models, fields + +class Child0(models.Model): + _name = 'delegation.child0' + + field_0 = fields.Integer() + +class Child1(models.Model): + _name = 'delegation.child1' + + field_1 = fields.Integer() + +class Delegating(models.Model): + _name = 'delegation.parent' + + _inherits = { + 'delegation.child0': 'child0_id', + 'delegation.child1': 'child1_id', + } + + child0_id = fields.Many2one('delegation.child0', required=True, ondelete='cascade') + child1_id = fields.Many2one('delegation.child1', required=True, ondelete='cascade') diff --git a/openerp/addons/test_documentation_examples/extension.py b/openerp/addons/test_documentation_examples/extension.py new file mode 100644 index 00000000000..c634609da90 --- /dev/null +++ b/openerp/addons/test_documentation_examples/extension.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from openerp import models, fields + +class Extension0(models.Model): + _name = 'extension.0' + + name = fields.Char(default="A") + +class Extension1(models.Model): + _inherit = 'extension.0' + + description = fields.Char(default="Extended") diff --git a/openerp/addons/test_documentation_examples/inheritance.py b/openerp/addons/test_documentation_examples/inheritance.py new file mode 100644 index 00000000000..b183e26fbed --- /dev/null +++ b/openerp/addons/test_documentation_examples/inheritance.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from openerp import models, fields + +class Inheritance0(models.Model): + _name = 'inheritance.0' + + name = fields.Char() + + def call(self): + return self.check("model 0") + + def check(self, s): + return "This is {} record {}".format(s, self.name) + +class Inheritance1(models.Model): + _name = 'inheritance.1' + _inherit = 'inheritance.0' + + def call(self): + return self.check("model 1") diff --git a/openerp/addons/test_documentation_examples/ir.model.access.csv b/openerp/addons/test_documentation_examples/ir.model.access.csv new file mode 100644 index 00000000000..ae82fd2c3a9 --- /dev/null +++ b/openerp/addons/test_documentation_examples/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_inheritance_0,access_inheritance_0,model_inheritance_0,,1,1,1,1 +access_inheritance_1,access_inheritance_1,model_inheritance_1,,1,1,1,1 +access_extension_0,access_extension_0,model_extension_0,,1,1,1,1 +access_delegation_child0,access_delegation_child0,model_delegation_child0,,1,1,1,1 +access_delegation_child1,access_delegation_child1,model_delegation_child1,,1,1,1,1 +access_delegation_parent,access_delegation_parent,model_delegation_parent,,1,1,1,1 diff --git a/openerp/addons/test_documentation_examples/tests/__init__.py b/openerp/addons/test_documentation_examples/tests/__init__.py new file mode 100644 index 00000000000..b9b095e284d --- /dev/null +++ b/openerp/addons/test_documentation_examples/tests/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +from . import test_inheritance, test_extension, test_delegation + +fast_suite = [ +] + +checks = [ + test_inheritance, + test_extension, + test_delegation, +] diff --git a/openerp/addons/test_documentation_examples/tests/test_delegation.py b/openerp/addons/test_documentation_examples/tests/test_delegation.py new file mode 100644 index 00000000000..df9f7e314fd --- /dev/null +++ b/openerp/addons/test_documentation_examples/tests/test_delegation.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from openerp.tests import common + +class TestDelegation(common.TransactionCase): + + def setUp(self): + super(TestDelegation, self).setUp() + env = self.env + record = env['delegation.parent'].create({ + 'child0_id': env['delegation.child0'].create({'field_0': 0}).id, + 'child1_id': env['delegation.child1'].create({'field_1': 1}).id, + }) + self.record = record + + def test_delegating_record(self): + env = self.env + record = self.record + + # children fields can be looked up on the parent record directly + self.assertEqual( + record.field_0 + , + 0 + ) + self.assertEqual( + record.field_1 + , + 1 + ) + + def test_swap_child(self): + env = self.env + record = self.record + + record.write({ + 'child0_id': env['delegation.child0'].create({'field_0': 42}).id + }) + self.assertEqual( + record.field_0 + , + 42 + ) + + def test_write(self): + record = self.record + + record.write({'field_1': 4}) + self.assertEqual( + record.field_1 + , + 4 + ) + self.assertEqual( + record.child1_id.field_1 + , + 4 + ) diff --git a/openerp/addons/test_documentation_examples/tests/test_extension.py b/openerp/addons/test_documentation_examples/tests/test_extension.py new file mode 100644 index 00000000000..6b977da74e7 --- /dev/null +++ b/openerp/addons/test_documentation_examples/tests/test_extension.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from openerp.tests import common + +class TestBasicInheritance(common.TransactionCase): + def test_extend_fields(self): + env = self.env + + record = env['extension.0'].create({}) + + self.assertDictContainsSubset( + {'name': "A", 'description': "Extended"} + , + record.read()[0] + ) diff --git a/openerp/addons/test_documentation_examples/tests/test_inheritance.py b/openerp/addons/test_documentation_examples/tests/test_inheritance.py new file mode 100644 index 00000000000..b740d003392 --- /dev/null +++ b/openerp/addons/test_documentation_examples/tests/test_inheritance.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from openerp.tests import common + +class TestBasicInheritance(common.TransactionCase): + def test_inherit_method(self): + env = self.env + + a = env['inheritance.0'].create({'name': 'A'}) + b = env['inheritance.1'].create({'name': 'B'}) + + self.assertEqual( + a.call() + , + """ + This is model 0 record A + """.strip() + ) + self.assertEqual( + b.call() + , + """ + This is model 1 record B + """.strip() + )