Define onchange methods

When writing Odoo models, it is often the case that some fields are interrelated. We have seen how to specify constraints between fields in the Add constraint validations to a Model recipe in Chapter 4, Application Models. This recipe illustrates a slightly different concept—onchange methods are called when a field is modified in the user interface to update the values of other fields of the record in the web client, usually in a form view.

We will illustrate this by providing a wizard similar to the one defined in the preceding recipe, Write a wizard to guide the user, but which can be used to record loan returns. When the member is set on the wizard, the list of books is updated to the books currently borrowed by the member. While we are demonstrating onchange methods on a TransientModel, these features are also available on normal Models.

Getting ready

If you want to follow the recipe, make sure you have the my_module addon from Chapter 3, Creating Odoo Modules, with the Write a wizard to guide the user recipe's changes applied.

You will also want to prepare your work by defining the following transient model for the wizard:

class LibraryReturnsWizard(models.TransientModel):
    _name = 'library.returns.wizard'
    member_id = fields.Many2one('library.member', 'Member')
    book_ids = fields.Many2many('library.book', 'Books')
    @api.multi
    def record_returns(self):
        loan = self.env['library.book.loan']
        for rec in self:
            loans = loan.search(
                [('state', '=', 'ongoing'),
                 ('book_id', 'in', rec.book_ids.ids),
                 ('member_id', '=', rec.member_id.id)]
            )
            loans.write({'state': 'done'})

Finally, you will need to define a view, an action, and a menu entry for the wizard. This is left as an exercise.

How to do it…

To automatically populate the list of books to return when the user is changed, you need to add an onchange method in the LibraryReturnsWizard step with the following definition:

    @api.onchange('member_id')
    def onchange_member(self):
        loan = self.env['library.book.loan']
        loans = loan.search(
            [('state', '=', 'ongoing'),
             ('member_id', '=', self.member_id.id)]
        )
        self.book_ids = loans.mapped('book_id')

How it works…

An onchange method uses the @api.onchange decorator, which is passed the names of the fields that change and thus will trigger the call to the method. In our case, we say that whenever member_id is modified in the user interface, the method must be called.

In the body of the method, we search the books currently borrowed by the member, and we use an attribute assignment to update the book_ids attribute of the wizard.

Note

The @api.onchange decorator takes care of modifying the view sent to the web client to add an on_change attribute to the field. This used to be a manual operation in the "old API".

There's more…

The basic use of onchange methods is to compute new values for fields when some other fields are changed in the user interface, as we've seen in the recipe.

Inside the body of the method, you get access to the fields displayed in the current view of the record, but not necessarily all the fields of the model. This is because onchange methods can be called while the record is being created in the user interface before it is stored in the database! Inside an onchange method, self is in a special state, denoted by the fact that self.id is not an integer, but an instance of openerp.models.NewId. Therefore, you must not make any changes to the database in an onchange method, because the user may end up canceling the creation of the record, which would not roll back any changes made by the onchanges called during the edition. To check for this, you can use self.env.in_onchange() and self.env.in_draft()—the former returns True if the current context of execution is an onchange method and the latter returns True if self is not yet committed to the database.

Additionally, onchange methods can return a Python dictionary. This dictionary can have the following keys:

  • warning: The value must be another dictionary with the keys title and message respectively containing the title and the content of a dialog box, which will be displayed when the onchange method is run. This is useful for drawing the attention of the user to inconsistencies or to potential problems.
  • domain: The value must be another dictionary mapping field names to domains. This is useful when you want to change the domain of a One2many field depending on the value of another field.

For instance, suppose we have a fixed value set for expected_return_date in our library.book.loan model, and we want to display a warning when a member has some books that are late. We also want to restrict the choice of books to the ones currently borrowed by the user. We can rewrite the onchange method as follows:

    @api.onchange('member_id')
    def onchange_member(self):
        loan = self.env['library.book.loan']
        loans = loan.search(
            [('state', '=', 'ongoing'),
             ('member_id', '=', self.member_id.id)]
        )
        self.book_ids = loans.mapped('book_id')
        result = {
            'domain': {'book_ids': [
                          ('id', 'in', self.book_ids.ids)]
                      }
        }
        late_domain = [
            ('id', 'in', loans.ids),
            ('expected_return_date', '<', fields.Date.today())
        ]
        late_loans = loans.search(late_domain)
        if late_loans:
            message = ('Warn the member that the following '
                       'books are late:
')
            titles = late_loans.mapped('book_id.name')
            result['warning'] = {
                'title': 'Late books',
                'message': message + '
'.join(titles)
            }
        return result
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.59.217