Dynamically Modify a view

This a tech tutorial on how to dynamically modify your view on Odoo. In Odoo, once a view record is defined, there is not much possibility of dynamically changing it according to the record(s) that it is showcasing. For example, in its XML definition, you can’t make the ‘string‘ attribute of a field to be the value of another field.

Most of the time, our needs can be solved using selection fields, notably state/type, if the changes we would like to impose are limited to a few variations. But it does not resolve the need to dynamically change the view according to model contents.

It is therefore needed that we override the fields_view_get function in the models.Model. Since apparently its documentation is “scarce” on line, we shall take a detailed tour around it.

This function, being inside the models.Model class, is called whenever odoo is rendering a view of the model. It gets the correct view given the context and returns the data of the field after essential logic treatments.

For example, suppose we have a pricelist model defined as:

class our_pricelist(models.Model):

And we have defined form view and tree view for it. We might even have defined 2 form views, one for default viewing and another to be used when called elsewhere, say, another model.

def fields_view_get(self, view_id=None, view_type=’tree’, context=None, toolbar=False,submenu=False):

The fields_view_get will take as argument the view_id and view_type from the system, to do its rendering. These arguments depend on where and how it is called. For example if the menuitem action calls for view_mode=”tree,form”, then the view_type ‘tree’ is given when we click on the menu

Most of time (and in the entirety of this post), we shall not touch the toolbar and submenu here. Their presence is necessary, and we only use it once in calling the super:

result = super(our_pricelist,self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)

Now we have done what the old fields_view_get does. However, we wouldn’t override this function if we were content with what it returns! As you have probably guessed, we are going to tinker around this result.

Suppose there are two fields in our_pricelist:

field_a = fields.Char(string=”Field A Char”)
field_b = fields.Date(string=”Field B Date”)

And we would like the label/string of field_a to be the value of field_b. Of course this example doesn’t make much practical sense, but it is an idea easily suited to other needs. We shall come to that later.

Before we hastily modifies the result returned by super’s fields_view_get, bear in mind that this function is called for whichever view under the model (i.e. our_pricelist). For the sake of avoiding making unwanted changes to views that we don’t want to touch, and moreover, prevent error as we might refer to fields that exist in target view but not necessarily in other views, we need to make restriction conditions to ensure that the view we are treating is indeed the target view.

If we have only defined one tree view and one form view, this can be easily done via an ‘if’:

if view_type == ‘form’:

However, if there are more than one form views defined, or there exists the prospect additional views to be added in future modules, it is better to verify the view_id. Both view_type and view_id are provided by system via the function call.

Having ensured that we are treating the right view, we can now set out to modify the string of field_a:

1) First, get the value of field_b via active_id:
active_id = self.env.context.get(‘active_id’, False)
new_string = self.env[‘our.pricelist’].browse(active_id).field_b

2) Add the following to import
from lxml import etree

3) Change the string attribute of field_a
doc = etree.XML(result[‘arch’])
if new_string:
for node in doc.xpath(“//field[@name=’field_a’]”):
node.set(‘string’, new_string)

4) Pack up your modifications and save it in the result string!
result[‘arch’] = etree.tostring(doc)

Done! Now in the our.pricelist’s form view, the label if field_a is always the value of field_b.

IMPORTANT: since the function fields_view_get is only called during the initial rendering, any change in the value of field_b is not reflected unless the view is reloaded. i.e. You have to refresh the page, manually or by python code, to update the string of field_a to field_b

Congratulations on reading till now! However, you might say where is the point of changing the field’s label by the value of another field? Indeed this seems an odd functionality, but it does open the door for dynamical modification of other stuff, like adding, rearranging the view according to your model data!

A useful implementation is the dynamical change of tree view column names!

Suppose the our.pricelist model has a one2many field called:
price_history_ids = fields.One2many(‘price.history’, ‘pricelist_id’)

And the pricelist keeps up to five columns of pricelist, each with a different date. Therefore it should also keep five date fields which probably need to be invisible in the XML form, but need to be the column header in the tree view of price_history_ids!

Do take note that the embedded tree view of price_history is NOT DEFINED in our.pricelist! Which means we need to override fields_view_get not in our.pricelist but in price.history now. The procedure is about the same, replace the string attribute of price.history’s tree view’s column fields and you are done.

Just remember to use this tree view in your our.pricelist form view by adding a context attribute to the field price_history_ids:
<field name=”price_history_ids” widget=”one2many” context=”{‘current_id’: active_id, ‘tree_view_ref’ : ‘your_module.your_tree_view’}”>


Below is the example of what can be achieved using that function: