Every time I have to install third-party modules in Odoo, I start praying for 12 hours in advance.
It is not uncommon, in fact, that the installation of some modules leads to the breaking of pre-existing views and, in the presence of already customized views, the risk becomes even greater.
The more view inheritance is nested, the more likely it is that a programmer in the world will die.
I'll list a series of simple rules to follow to avoid releasing code that could kill some installations and that doesn't make us bring on our conscience the lives of our colleagues distributed around the world.
Replace
Replace
is like a bazooka. It destroys everything. That means if someone after us tries to find what we replaced, they'll never find it. It is not difficult to understand that replace
should be used sparingly and that, if possible, it should not be used at all. It is rare that the only solution to our problem is to replace a node. It is much more elegant to hide it with due care and with much more elegance.
Add attributes
Let's imagine that we have a div, with its own class, to which we want to add a new one. What often happens is that we use the "replace" to replace the node with the same node to which we add our class. This is wrong for two reasons: the original node is no longer recognizable from the previous xpath
and two modules that apply the same modification tend to obscure each other.
The best thing to do is to use the addition of value through the use of attributes.
Wrong:
<xpath expr="//div[@id='node_id']" position="replace">
<div id="node_id" class="row d-none" />
</xpath>
Right:
<xpath expr="//div[@id='node_id']" position="attributes">
<attribute name="class" separator=" " add="d-none" />
</xpath>
You can use it to extend if condition in view, too.
Example:
<xpath expr="//button[@id='o_payment_form_pay']" position="attributes">
<attribute name="t-if" separator=" " add="and accept_terms_conditions" />
</xpath>
Hasclass
As we have seen previously, a node can be extended by adding a new class. This highlights another problem. Very often we refer to nodes by indicating their class.
Example:
<xpath expr="//div[@class='myclass']" position="...">
...
</xpath>
If a module, before ours, adds a new class to the node we are looking for, we may never find it again. In this case, the solution is given by the hasclass
function that allows us to control the nodes that have the class we are looking for among all those available.
Example:
<xpath expr="//div[hasclass('myclass')]" position="...">
...
</xpath>
ID and Name abuse (Do it!)
When using xpath
, you always try to hook into a node that is uniquely recognizable throughout the entire XML structure. This is not always so easy because some nodes are very anonymous (they have too generic class, they are the same as other nodes in other places, they are repeated in the structure).
In this case, it is a good practice to use the id
or name
attribute on nodes that we believe can be hooked so as to facilitate the work of developers who will extend our views.
Do not obscure
When the starting structure is very complex and there are many changes to be made, there may be the temptation to obscure a view by creating a new one with the same XML ID. It is useless to explain how dangerous and harmful this practice is. The XML ID is the parameter used to inherit views and forms that come after yours would expect a structure that is actually completely disrupted.
If there are so many changes to make, you're probably not using the correct view and the best way is to create a new one and convince the software to use yours instead of the original one.
Variables, not hardcore
Whenever you need a value to perform operations (compare values, numbers of cycles for a loop, etc.) it would be convenient to use variables so that the behavior in the operations can be more easily modified.
Wrong:
<ul>
<t t-foreach="['a', 'b', 'c']" t-as="val">
<li><span t-esc="val" /></li>
</t>
</ul>
Right:
<t t-set="vals_to_loop" t-value="['a', 'b', 'c']"/>
<ul>
<t t-foreach="vals_to_loop" t-as="val">
<li><span t-esc="val" /></li>
</t>
</ul>
Final considerations
Unfortunately, this list is only part of the good practice to keep in mind. No code is perfect, and in many cases, even Odoo's core code is not free from these problems.
I only hope that these rules can serve as a springboard for writing better code.
Top comments (3)
@opencode
Thanks. How can I add a field to a template (base.view_partner_form) that has already been inherited by template_x using position="replace"?
Trying to change to template_x to position="attributes" results in lose of the changes contained within template_x
You have to inherit the view that change the position and use the xpath to recognize the field you're searching for
Thanks for this, very helpful.