When defining a model that extends another model, it is often necessary to customize the behavior of some methods defined on the original model. This is a very easy task in Odoo, and one of the most powerful features of the underlying framework.
We will demonstrate this by extending a method that creates records to add a new field in the created records.
If you want to follow the recipe, make sure you have the my_module
addon from Chapter 3, Creating Odoo Modules, with the loan wizard defined in the Writing a wizard to guide the user recipe from Chapter 6, Advanced Server Side Development Techniques.
Create a new addon module called library_loan_return_date
that depends on my_module
. In this module, extend the library.book.loan
model as follows:
class LibraryBookLoan(models.Model): _inherit = 'library.book.loan' expected_return_date = fields.Date('Due for', required=True)
Extend the library.member
model as follows:
class LibraryMember(models.Model): _inherit = 'library.member' loan_duration = fields.Integer('Loan duration', default=15, required=True)
Since the expected_return_date
field is required and no default is provided, the wizard to record loans will cease functioning because it does not provide a value for that field when it creates loans.
To extend the business logic in the library.loan.wizard
model, you need to perform the following steps:
my_module
, modify the method record_loans()
in the LibraryLoanWizard
class. The original code is in the Writing a wizard to guide the user recipe in Chapter 6, Advanced Server Side Development Techniques. The new version is:@api.multi def record_loans(self): for wizard in self: books = wizard .book_ids loan = self.env['library.book.loan'] for book in wizard.book_ids: values = self._prepare_loan(book) loan.create(values) @api.multi def _prepare_loan(self, book): return {'member_id': self.member_id.id, 'book_id': book.id}
library_loan_return_date
, create a class which extends library.loan.wizard
and defines the _prepare_loan
method as follows:from datetime import timedelta from openerp import models, fields, api class LibraryLoanWizard(models.TransientModel): _inherit = 'library.load.wizard' def _prepare_loan(self, book): values = super(LibraryLoanWizard, self )._prepare_loan(book) loan_duration = self.member_id.loan_duration today_str = fields.Date.context_today() today = fields.Date.from_string(today_str) expected = today + timedelta(days=loan_duration) values.update( {'expected_return_date': fields.Date.to_string(expected)} ) return values
In step 1, we refactor the code from the Writing a wizard to guide the user recipe in Chapter 6, Advanced Server Side Development Techniques, to use a very common and useful coding pattern to create the library.book.loan
records: the creation of the dictionary of values is extracted in a separate method rather than hardcoded in the method calling create()
. This eases extending the addon module in case new values have to be passed at creation time, which is exactly the case we are facing.
Step 2 then does the extension of the business logic. We define a model that extends library.loan.wizard
and redefines the _prepare_loan()
method. The redefinition starts by calling the implementation from the parent class:
values = super(LibraryLoanWizard, self)._prepare_loan(book)
In the case of Odoo models, the parent class is not what you'd expect by looking at the Python class definition. The framework has dynamically generated a class hierarchy for our recordset, and the parent class is the definition of the model from the modules on which we depend. So the call to super()
brings back the implementation of library.loan.wizard
from my_module
. In this implementation, _prepare_loan()
returns a dictionary of values with 'member_id'
and 'book_id'
. We update this dictionary by adding the 'expected_return_date'
key before returning it.
In this recipe, we chose to extend the behavior by calling the normal implementation and modifying the returned result afterwards. It is also possible to perform some actions before calling the normal implementation, and of course, we can also do both.
However, what we saw in this recipe is that it is harder to change the behavior of the middle of a method. We had to refactor the code to extract an extension point to a separate method and override this new method in the extension module.
You may be tempted to completely rewrite a method. Always be very cautious when doing so—if you do not call the super()
implementation of your method, you are breaking the extension mechanism and potentially breaking addons for which their extension of the same method will never be called. Unless you are working in a controlled environment, in which you know exactly which addons are installed and you've checked that you are not breaking them, avoid doing this. And if you have to, be sure to document what you are doing in a very visible way.
What can you do before and after calling the original implementation of the method? Lots of things, including (but not limited to):
UserError
to cancel the execution in forbidden cases (before, after)self
in smaller recordsets, and calling the original implementation on each of the subsets in a different way (before)