Creating Form Buttons and Actions
Creating Form Buttons and Actions
http://www.odoo.yenthevg.com/creating-form-buttons-and-actions/
In this tutorial I will learn you how to create buttons in views. The form buttons are always shown at the
top of the form and are very handy to perform actions on your current record. After this tutorial you can
create your own buttons and actions behind the buttons.
1 # -*- coding: utf-8 -*2 from openerp import models, fields, api
3
4 class button_action_demo(models.Model):
5 _name = 'button.demo'
6 name = fields.Char(required=True,default='Click on generate name!')
7 password = fields.Char()
This creates a new model in the database named button.demo and will contain two custom added fields:
name and password. The field name will be a required field and by default it will contain the text Click
on generate name!)
1
9
2
0
2
1
So, how exactly does Odoo know that the buttons need to be at the top of the form? Because of the header
tag! Everything within this tag will be shown at the top of your form. So, this creates three buttons, but
how do we trigger an action from this button? Have a closer look at the code for one of those buttons:
Noticing anything? The type=object is telling us that we have an object and the
name=generate_record_name is a link to a Python function. When the user clicks on the button
Generate name Odoo will trigger the Python function generate_record_name and from there on we can
do whatever we want. So let us create the actions for the buttons!
1 @api.one
2 def generate_record_name(self):
3 #Generates a random name between 9 and 15 characters long and writes it to the record.
4 self.write({'name': ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(randint(9,15)))})
self is defining the current record, so this means we can access anything from that record and modify it.
The self.write will update the values for this record in the database. By saying name: we are telling to
update the field name in the view. In this example I created a simple function that generates a random
name between 9 and 15 characters long.
Tip: @api.one is used for one record and cannot be used for multiple records. If youd want this you would
need @api.multi.
If you would now click on the button a name would be generated from this function and would be shown
directly on the view.
Now create the second function generate_record_password for the button Generate password:
1 @api.one
2 def generate_record_password(self):
3 #Generates a random password between 12 and 15 characters long and writes it to the record.
4 self.write({'password': ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(randint(12,15)))})
This function does almost the same as the previous but it will generate a password between 12 and 15
characters and write it on the field password in place of name.
Next, lets create our third function clear_record_data for the button Clear data. This function will clear all
data from the fields:
1 @api.one
2 def clear_record_data(self):
3
self.write({
4
'name': '',
5
password': ''
6
})
That is all! You can now create buttons with actions and perform changes on the fields.
4. Conclusion
Buttons are very handy in Odoo and can do just about anything. It is easy to perform actions for the user,
change fields, show new views,
Tip: You can also use buttons in combination with attrs and flows to make them even more useful! You can
find this tutorial here.
Do you want to try a demo module and see the source code of this tutorial? You can view on my Github
account. Has this tutorial helped you, do you have any feedback or questions? Post away!
1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
class statusbar(models.Model):
_name = 'statusbar.demo'
name = fields.Char('Name', required=True)
"""
This selection field contains all the possible values for the statusbar.
The first part is the database value, the second is the string that is showed. Example:
('finished','Done'). 'finished' is the database key and 'Done' the value shown to the user
"""
state = fields.Selection([
('concept', 'Concept'),
('started', 'Started'),
('progress', 'In progress'),
('finished', 'Done'),
],default='concept')
This creates a new model with the name statusbar.demo which contains two fields, the field name and the field state.
The state field is of the type selection, which means it will contain multiple values which can be chosen. A selection is
always built up in the same way, the first part if the database name and the second part is the text that will be shown to the
user. For example:
If you wish to change the state to In progress you will need to tell the database about progress, since this is the name
the database listens to, while the name shown to the user will be In progress.
This is all you need on the database level. You have your selection values in the database so you only need to show them
to the user now!
<field name="name">Statusbar</field>
<field name="model">statusbar.demo</field>
<field name="type">form</field>
<!--The header tag is built to add buttons within. This puts them at the top -->
<header>
1
0
<!--The oe_highlight class gives the button a red color when it is saved.
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
</form>
</field>
2
1 </record>
2
2
2
3
So what does this tell us? Let us break this down part by part. The first thing you will notice is the field header.
1 <header>
2 </header>
Everything within these tags will be shown at the top of your Odoo form.
In this code example it will create four buttons and it will also show the field state. The field state has a widget set to it
that is made by Odoo and which will make the typical statusbar, just by adding a widget! This will generate the following
Now there is one more thing I should explain, the buttons. By using those buttons we will modify in which state we are on
the statusbar (selection) and we will control the whole flow. Let us take the button In progress as an example to explain
the code:
So what does this do exactly? It gives the button the text In progress and well give the button the type object, this will
make it accessible in a function from the Python code later on. The name of the button will be the name of the function in
your Python code and last but not least is the attrs. The attrs is a handy feature to make buttons visible/invisible when
youre in a state for example. In this example the button In progress will not be shown when the statusbar is in the state
progress.
Tip: If you have multiple states where buttons should be hidden you can use domains! For example:[(state,=,
[progress,finished])]
Makes sense right? Let us continue and create the button functions!
Now that the view is ready and you have buttons they should also trigger something. Open up your Python file and create
a function for all of those button names you have. Every button will have a separate Python function and will modify the
current record with a write. In this tutorial I will create four functions that simply change the state of what the current record
is in:
1 #This function is triggered when the user clicks on the button 'Set to concept'
2 @api.one
3 def concept_progressbar(self):
4
self.write({
5
6
'state': 'concept',
})
7
8 #This function is triggered when the user clicks on the button 'Set to started'
9 @api.one
10 def started_progressbar(self):
11
self.write({
12
13
'state': 'started'
})
14
15 #This function is triggered when the user clicks on the button 'In progress'
16 @api.one
17 def progress_progressbar(self):
18
self.write({
19
20
'state': 'progress'
})
21
22 #This function is triggered when the user clicks on the button 'Done'
23 @api.one
24 def done_progressbar(self):
25
self.write({
26
27
'state': 'finished',
})
When you now click on the button In progress it will trigger the function progress_progressbar. The self.write will be
triggered, which is telling Odoo that it should write on the current record, and it will modify the state to finished.
Depending on in which state you are youll see buttons showing up and dissapearing. This is controller by the attrs value
on the buttons. For example when I save my record and set it to in progress:
4. Conclusion
Statusbars (selections) are very handy in Odoo and give you the ability to show the progress to the user in a visual way.
Statusbars are ideal to use in combination with buttons which will modify the state where a record is in. Another great
thing about statusbars is that theyre always at the exact same location and you can find out what the progress is within
seconds.
Do you want to try a demo module and see the source code of this tutorial? You can view on my Github account.
Has this tutorial helped you, do you have any feedback or questions? Post away!
1 class many2many_default_data_demo(models.Model):
2
_name = 'sale.order.handle'
_order = 'sequence'
name = fields.Char('Naam')
As you can see I created a field named sequence which is an integer. The second thing that you need for
this field is to give it a default value, otherwise it will not work correctly. After youve created your model
and fields its time to create your many2many.
1
2
3
class print_order_sample(models.Model):
def _get_default_print_order_ids(self):
return self.pool.get('sale.order.handle').search(self.env.cr, self.env.uid, [])
4
5
6
_inherit = 'sale.order'
print_handle_ids = fields.Many2many('sale.order.handle','sale_handle','order_id','order_handle_id','Many2many order',help='This could be
used to create a print order for your report, for example.This is a drag an drop (handle) widget.',default=_get_default_print_order_ids)
As you can see Ive inherited the model sale.order since I want to create my many2many in this model. I
then created a new field and created a link to the table sale.order.handle. Ive also created a default which
links to a function. The idea behind this is to automatically fill the many2many handle with all the data
from the model sale.order.handle. Ofcourse you dont have to, this is just for the demo.
Tip: Out of safety uses you should never create a table longer than 16 characters in a many2many! The
postgreSQL database cannot handle a many2many link longer than 64 characters in total. For more
information see this bug report.
<field name="name">sale.order.form.inherit.many2many.handle</field>
<field name="model">sale.order</field>
<!--Adds a new page (tab) to the view after the tab Order Lines-->
<group>
10
<field name="print_handle_ids">
11
12
13
<field name="name"/>
14
</tree>
15
</field>
16
</group>
17
18
19
</page>
</xpath>
</field>
20 </record>
There isnt to much difference with a normal many2many, there are just three things you should always
do.
1. Create your own tree to render the many2many correctly.
2. always add the field sequence in this tree.
3. call the widget with widget=handle. This will make the many2many drag and droppable.
And thats it, youve created your own many2many handle which makes items drag and droppable. The
result will look something like this:
Do you want to see the source code or try this module for yourself? You can download / view it on Github.
Has this tutorial helped you, do you have any feedback or questions? Post away!
7
8
_inherit = 'product.template'
#Creates two new fields (CostPrice and ShippingCost) in the model product.template
1
0
1
1
1
2
This now links to the product module to the view product_template_form_view (which you can find in
product_view.xml under the product module). After weve inherited the view we can add the xpath
expressions.
3. Xpath expressions
xpath expressions are basically paths to the page / group / field inside the view that youve inherited and
are always built in the same way. Something along these lines:
Since the syntax is always a bit different I will simply give an example of each. The idea is to give the
name from the page / group / field / .. so that Odoo can find the correct location to insert your new fields.
<group>
<field name="FieldNewPage"/>
</group>
6 </page>
7 </xpath>
This code will create a new page after the currently existing page named Information. The new page will
be named Custom page and will show the field FieldNewPage.
<field name="FieldAfterGroup"/>
4 </group>
5 </xpath>
This code will create a new group after the group, inside the page with as title Information and will show
the field FieldAfterGroup in it.
Which will result in the following page:
This code will show the fields CostPrice and ShippingCost after the field standard_price. This is by far
the easiest xpath expression and in usual cases this will work just fine.
This code will result in the following page:
These are the most used xpath expressions but there are many more, as you can see in the source code of
Odoo.
xpath is very powerfull and has a lot of options but sadly there is barely any documentation about it. Ive
created a sample module which you can view on my Github account.
2. Open up the folder views and then open the XML fileim_odoo_support.xml. In this file you can directly
see that there is a function and it looks to Odoo Support. So go ahead and comment this block out, like
this:
<data>
4
5
6
7
8
9
1
0
1
1
(function() {
openerp.im_odoo_support.support = new openerp.im_odoo_support.OdooSupport(
1
2
"<t t-esc="request.session.login"/>",
1
3
1
4
1
5
1
6
})();
</script>
</xpath>
</template>-->
1
7
</data>
1
8 </openerp>
1
9
2
0
<t t-extend="UserMenu">
<t t-jquery=".dropdown-menu li:eq(3)" t-operation="after">
<!--
<li class="odoo_support_contact">
9
10
11
</li>-->
</t>
</t>
12 </templates>
3. Now restart your Odoo server and update your module. You can do it with this command:
./odoo.py -u im_odoo_support
4. Open Odoo in your browser and youll see there is no more Odoo Support user in the list!
This will create a new module from scratch and the default structure of your module is already there.
Now that youve found your report click on it. This will open a new view where you will find all the technical
information you need! For example with the quotation report:
At the right you will see a clickable link named Search associated QWeb views. Click on it. This will show
you a list of all records that are associated to this specific report. In the example of the quotation report:
So this usually shows you two XML records. How do you know which one you need? The one that ends with
_document is the correct XML record that you need to inherit. Under the column External ID you will see
there is an unique name, in this example sale.report_saleorder_document. The first part of this text (sale)
is the module where the report is from, the second part (report_saleorder_document) is the name of the
report that you want to inherit and modify.
Remember this value and now open up your XML file. To inherit a QWeb report you need two things: an
unique template id and the inherit_id. The template id can be chosen by yourself, just make sure its
unique. The inherit_id should contain the value youve just found on your report
(sale.report_saleorder_document).
That is it! Youre now already on the correct report and are inheriting it. So, how do you now add or remove
elements? To do this you will need Xpath expressions to find, modify, replace or add elements. Tip: Dont
know how Xpath expressions work? Follow my tutorial here!
For this example I will remove the columns that show the amount, the tax and the price per item. The first
step is to modify the table header:
1 <!-- Finds the first table with as class table table-condensed and gives the ability to modify it
2 This will replace everything withing tr (including tr)-->
3 <xpath expr="//table[@class='table table-condensed']//thead//tr" position="replace">
4
<tr style="background-color:lightgray;">
<th>Description</th>
<th class="text-right">Price</th>
</tr>
8 </xpath>
After modifying the table header the table content should also be modified.
This code will remove the fourth, third and second td element and all its content but only for the tbody
with class sale_tbody and inside the tr.
So this will replace the header and the table content from this report. Have a look at the full code to inherit
and modify your QWeb report:
1 <openerp>
2
<data>
5
6
7
8
<!-- Finds the first table with as class table table-condensed and gives the ability to modify it
This will replace everything withing tr (including tr)-->
<xpath expr="//table[@class='table table-condensed']//thead//tr" position="replace">
<tr style="background-color:lightgray;">
9
1
0
1
1
<th>Description</th>
1
2
1
3
1
4
1
5
1
6
1
7
1
8
<th class="text-right">Price</th>
</tr>
</xpath>
<xpath expr="//tbody[@class='sale_tbody']//tr//td[4]" position="replace">
</xpath>
<xpath expr="//tbody[@class='sale_tbody']//tr//td[3]" position="replace">
</xpath>
<xpath expr="//tbody[@class='sale_tbody']//tr//td[2]" position="replace">
</xpath>
</template>
</data>
1
9 </openerp>
2
0
2
1
If you would now print out the report you would get this as a result:
In this example my QWeb comes from the sale module, so I will add it as a dependency.
4. Conclusion
Thats it! Youre done with inheriting and modifying the QWeb report. When you now install this module in
your Odoo you will see the modified report.
Do you want to try a demo module and see the source code of this tutorial? You can view on my Github
account.
_inherit = 'product.template'
8
9
1
0
#Creates two new fields (CostPrice and ShippingCost) in the model product.template
CostPrice = fields.Float('Buy price')
ShippingCost = fields.Float('Shipping Cost')
This adds two fields (CostPrice and ShippingCost) to the model product.template, which I first inherited.
Both these fields are for numbers. After adding your new fields you need to add them to the view.
1
2
In case you have a new view you can simply add them but if you want to add them to an existing view you
will need to inherit the record from the other module, just like in my sample. For example:
1 <openerp>
2
3
<data>
<record id="view_product_form_inherit" model="ir.ui.view">
<field name="name">product.template.common.form.inherit</field>
<field name="model">product.template</field>
1
0
1
1
1
2
1
3 </openerp>
1
4
1
5
3. On_change function
Noticing anything in this code? Ive already added the on_change event in the view like this:
1 on_change="on_change_price(CostPrice,ShippingCost)"
This will search for the function on_change_price in your module and sends two values to the function,
CostPrice and ShippingCost. This are the two fields that will contain the values and in these variables are
now the values from the two fields. Were almost there now, the only thing you need to do now is create
the function on_change_price in your Python file. Return to your Python file and add the following code:
#This method will be called when either the field CostPrice or the field ShippingCost changes.
def on_change_price(self,cr,user,ids,CostPrice,ShippingCost,context=None):
3
4
6
7
res = {
'value': {
#This sets the total price on the field standard_price.
'standard_price': total
9
1
0
1
1
1
2
}
}
#Return the values to update it in the view.
return res
In the variables CostPrice and ShippingCost are the values that the user has entered. The variable total
simply contains the sum of CostPrice and ShippingCost and this should now be filled in to our third field,
which is named standard_price. After the value is calculated and added to the new field it should be
returned so that the user can see it.
The result:
1
2
3
4
#Import logger
import logging
#Get the logger
_logger = logging.getLogger(__name__)
You can use logging anywhere in Python and at any time as you wish. In this tutorial I will create a simple
field and button. When the button is printed I will print all kind of pre-built logging states to the terminal
for demo purposes. Ive created a button that calls the function print_log_data:
1
2
3
4
5
6
7
8
9
As you can see logging is very simple in Odoo. Choose what kind of logging it is and then simply add a text
to it. if you want to use variables from your view you can simply call them in Python and add them to the
message. For example:
When you click on the button the logging statements will be printed in your terminal. For example with my
function:
Thats it! It is as simple as that to log data in Odoo. If you want to go further though, there are quite some
extra options.
logfile: the log filename. If this is not filled in all the log details will be written to stdout (terminal).
log_level: any value in the list of [debug_rpc_answer, debug_rpc, debug, debug_sql, info,
warn, error, critical]. Odoo changed the log_level meaning here because these level values are
mapped to a set of predefined module:log_level pairs.
log_handler: can be a list of module:log_level pairs. The default value is :INFO it means the
database.
For example if I define a logfile in the terminal:
When you now go to this location you will see there is a new logfile created and that it is automatically
filled with the data that you defined:
Do you want to see the source code or try this module for yourself? You can download / view it on Github.
Has this tutorial helped you, do you have any feedback or questions? Post away!
Hi guys,
In this tutorial I will learn you how to create a handle widget. A handle widget gives you the option to reorder lines in a many2many, making records drag and droppable. This is a very hand feature to order
items and eventually change the order on reports for example.
An example from before re-arranging a many2many and after:
otherwise you can skip to part 2. The table where you want to link your many2many to needs atleast one
field (such as name) and what you absolutely need is a field named sequence. The sequence field is an
integer and will keep track of the order on your many2many. For example:
1 class many2many_default_data_demo(models.Model):
2 _name = 'sale.order.handle'
3 _description = 'Model for many2many (handle)'
4 _order = 'sequence'
5 name = fields.Char('Naam')
6 sequence = fields.Integer('sequence', help="Sequence for the handle.",default=10)
As you can see I created a field named sequence which is an integer. The second thing that you need for
this field is to give it a default value, otherwise it will not work correctly. After youve created your model
and fields its time to create your many2many.
1
2
3
4
5
6
class print_order_sample(models.Model):
def _get_default_print_order_ids(self):
return self.pool.get('sale.order.handle').search(self.env.cr, self.env.uid, [])
_inherit = 'sale.order'
print_handle_ids = fields.Many2many('sale.order.handle','sale_handle','order_id','order_handle_id','Many2many order',help='This could be
used to create a print order for your report, for example.This is a drag an drop (handle) widget.',default=_get_default_print_order_ids)
As you can see Ive inherited the model sale.order since I want to create my many2many in this model. I
then created a new field and created a link to the table sale.order.handle. Ive also created a default which
links to a function. The idea behind this is to automatically fill the many2many handle with all the data
from the model sale.order.handle. Ofcourse you dont have to, this is just for the demo.
Tip: Out of safety uses you should never create a table longer than 16 characters in a many2many! The
postgreSQL database cannot handle a many2many link longer than 64 characters in total. For more
information see this bug report.
7
<xpath expr="//page[@string='Order Lines']" position="after">
8
<page name="many2manyhandledemo" string="Many2many handle demo">
9
<group>
10
<field name="print_handle_ids">
11
<tree string="Printing order" editable="bottom">
12
<field name="sequence" widget="handle"/>
13
<field name="name"/>
14
</tree>
15
</field>
16
</group>
17
</page>
18
</xpath>
19 </field>
20 </record>
There isnt to much difference with a normal many2many, there are just three things you should always
do.
1. Create your own tree to render the many2many correctly.
2. always add the field sequence in this tree.
3. call the widget with widget=handle. This will make the many2many drag and droppable.
And thats it, youve created your own many2many handle which makes items drag and droppable. The
Do you want to see the source code or try this module for yourself? You can download / view it on Github.
Has this tutorial helped you, do you have any feedback or questions? Post away!
In this tutorial I will learn you how to automatically fill a Many2many with data. In some cases a
many2many always has to have data so in that case having it filled in automatically is really handy.
1 class many2many_default_data_demo(models.Model):
2
_name = 'sale.order.printorder'
3
_description = 'Model for many2many'
4
name = fields.Char('Name')
1
2
3
4
5
6
7
8
class print_order_sample(models.Model):
def _get_default_print_order_ids(self):
cr = self.pool.cursor()
self.env
return self.pool.get('sale.order.printorder').search(cr, self.env.uid, [])
_inherit = 'sale.order'
print_order_ids = fields.Many2many('sale.order.printorder','sale_order_print','print_id','order_print_id','Print order',help='This could be used
to create a print order for your report, for example.',default=_get_default_print_order_ids)
As you can see there is a default=_get_default_print_order_ids which links to a function in the class. This
function will automatically get all data from the model sale.order.printorder thanks to self.pool. After the
pool got all records we will return all the data and it will be added to the many2many.
Now lets create a view that shows the many2many:
<field name="print_order_ids"/>
10
</page>
11
</xpath>
12
</field>
13
</record>
14
When a user would now create a new quotation/order there will be a new tab named Many2many demo
which is automatically filled with all records from the model sale.order.printorder:
2
3
2
4
2
5
2
6
2
7
2
3
2
4
2
5
2
6
2
7
2
8
2
}
9
3
0
3
1
3
2
3
3
3
4
3
5
As you can see by this record with demo data it will create a new record with a name named Record #1
and a description with the text A description for record #1. These are the fields that Ive created in my
custom model (models.py) like this:
When you would now install this module it will automatically install the records that are defined.
Want to see an example module where you can see how everything works? You can download/view it on
Github!
Has this tutorial helped you, do you have any feedback or questions? Post away!