Creating and using smartbuttons in Odoo
Intermediate
Smartbuttons allow you to see how many related records you have in the form view in one overview. They allow you to directly open all the related records in just one click. It is often used within Odoo to quickly navigate to related documents or data. In this tutorial you will learn how to create a smartbutton, how to compute the total in the button and how to open another view after clicking on the smartbutton.
In this tutorial we will create a smartbutton on the form view of the country. The form will have a smartbutton with a counter for the amount of contacts you have in this country. Clicking on the smartbutton will directly open the tree view with the matching contacts.
'depends': ['contacts'],
The first thing that we need is a new field on the existing model - or on your custom model - that will hold the counter of the amount of contacts for that country. By default in Odoo this is an integer field which you give a compute function. The compute will recalculate the result every time you open the view. so that it gets recalculated every time you open up the view. Create a new Python file named 'res_country.py' in the models folder of your custom module and add it in the __init.py__ file so that your new file gets loaded by Odoo. Inherit the model 'res.country' in this file and create a new integer field with a compute function:
# -*- coding: utf-8 -*-from odoo import models, fields, apiclass ResCountry(models.Model): _inherit = 'res.country' # A smart button is almost always a computed value # (as it links to other records from another table) contacts_counter = fields.Integer(string='Contacts', compute='_compute_contacts')
Now we have to compute the value to be shown in this new field. We should search through all contacts and find the contacts that have the country set to the one we'll open up in the form view:
# As it is a function that is called internally we start the name of the function with an underscore.def _compute_contacts(self): # Make sure that we do country by country or we could get a singleton error from other functions. for country in self: # Find all res.partner records that have the country id set of the current country country.contacts_counter = self.env['res.partner'].search_count([('country_id', '=', country.id)])
So, why the for loop over self? If we wouldn't do this it can be that 'self' contains multiple records. If there are multiple records in self and if you do not loop over them you'll get a singleton error resulting in breaking views for the users. We're also using a search_count instead of a search in the search because this is faster and lighter for the ORM. It only counts and doesn't return all the field values and therefore makes loading the view faster.
Great! The next step is to add this field to the view and to create our smartbutton for the user. Inherit the existing country view and add an xpath expression to add your smartbutton in the form. In our example the countries view has no smartbuttons or header in the form yet so we will need to xpath on the first element and create our own button box to hold the smartbutton. Create a new XML file named 'res__country_view.xml' in the views folder of your custom module and add it in your manifest.py file (under the data key). Just have a look at the example code for now and try to understand it, I'll explain it afterwards:
Does it make sense to you? We will first tell Odoo that we want to inherit the view 'base.view_country_form' and that we want to do an xpath expression on the element 'div'. We'll add elements to the view before the first 'div' element that is found in the view. Within this xpath expression we tell Odoo that we want to add a div with the name 'button_box' and with the class 'oe_button_box', which is a built in option from Odoo itself. This will tell Odoo that we want to add a bar at the top of the form.
Now we add a button inside which is of the type 'object'. This will allow the user to click on the smartbutton and open another view. The name 'action_open_contacts' will be the function name of the function - that we still have to create - to handle the click and the class will tell Odoo it is a statistics button. Finally, within this button we add our field that contains the computed amount of contacts related to this company. The widget="statinfo" will tell Odoo to show the value in the button and to layout it as the default smartbuttons.
We just have to do one more thing now and that is to create the function for 'action_open_contacts'.
We're almost done already! If the user would now click on this smartbutton he would get a traceback as we haven't created a function for our smartbutton yet. Open up your 'res_country.py' file again. We should now create a function that will call an action in order to open the tree or form view of the contact(s) that match for this country. Have a look at the code and try to understand it:
def action_open_contacts(self): related_customers = self.env['res.partner'].search([('country_id', '=', self.id)]) action = self.env.ref('contacts.action_contacts').read()[0] if len(related_customers) > 1: # If we have more than one customer for this country we'll open the contacts tree view. action['domain'] = [('id', 'in', related_customers.ids)] elif len(related_customers) == 1: # If we would click on the smart button and create a contact from the tree it would automatically be filled # in with the current country thanks to its context. action['context'] = {'default_country_id': self.id} # If we have just one customer for this country we'll open the contacts form view directly. action['views'] = [(self.env.ref('base.view_partner_form').id, 'form')] # Pass along the ID of the contact record for the action. action['res_id'] = related_customers.ids[0] return action
The first thing that we have to do in our function is to get all related contacts that match to the country we have opened. When we have all the contacts we have to link to an action which we want to trigger (to open up the right view). In this tutorial we want to open up the contacts so we call the 'action_contacts' action of the module contacts.
TIP: You can find the action of a menuitem by activating developer mode. Click on the debug icon at the top right. There is an option 'Edit action' here which will open up a dialog with the action name set in the field 'External ID'.
As we know how many contacts are linked to this country we have a great option! Depending on the amount of contacts that are found we can open other views. It doesn't have a lot of purpose to open a tree view if there is just one contact found right? We might as well open up the form right away then. By setting an if and elif statement with a check for the length of 'related_customers', so the amount of found contacts, we can open the tree or the form.
If there is more than one contact we have to add a domain to the action. The domain will make sure that when the tree view is opened after clicking on the smartbutton only shows the contacts that match to our country. If there is just one contact we can pass the form view along in the action and link to the form view with '[(self.env.ref('base.view_partner_form').id, 'form')]'. The 'res_id' in the context will hold the ID of the contact so Odoo knows which form to open.
By the way, did you notice the action['context'] line? This will automatically fill in the current country when creating a new contact from the smartbutton. How nice is that?
That's it, great job! If you save the code and install your module you'll have a working smartbutton:
Smartbuttons are a great way to quickly see how many related records you have. They give insights to the user about related records from one form view. Smartbuttons are quite easy to develop in Odoo. They open up a broad range of possibilities so you'll probably need them quite often. Smartbuttons need to be computed every time though. This results in longer loading times so use them wisely.