Inheritance behavior for nested templates in odoo v8

Explaination using the example of the website blog module

Robert Rübner

Preparation and requirements

If you want to extend odoo with new functionalities you create a new module with the changes. These changes can be new model fields, extra behavior for controller actions, updates of templates etc. Creating a new module is not a topic of this blog post. So you already should have created and installed your own module (my_module). I want to go into detail about updates of templates, especially updates of frontend templates (template) like the website blog.

First if you want to extend templates you should read the documentation about qweb, it is the primary template render engine for odoo. Furthermore you should read about xpath expression because it is used to find the update location in the parent template. Another useful information is the fact that the template update (inheritance) is an odoo shortcut feature. It is a shortcut for creating views which means that you also could create and update templates by creating record entries like you would do it for backend views (tree views, form views, search views etc.). Normally you wouldn't do it but maybe you can solve a template problem in this way. Due to the fact that templates are views you will find all templates in: Settings ⇢ Technical ⇢ User Interface ⇢ Views.
 

QWeb template inheritance using the example of the website blog module

Let's start. First we go to the blog page as administrator and activate the right column and all options for the right column via the frontend customize menuSo we can see our changes later easily.
 


Now we create a new directory called views in my_module. This directory will contain all xml files with our template changes. 
After that we create a new file called website_blog_templates.xml in the views directory. The file name is our choice but I think it is always good to name the file like this to point out what this file is used for. We want to extend the Follow Us section in the right column of the blog page. So we search the parent template part in the website_blog module. We will find it in ‑ addons/website_blog/views/website_blog_templates.xml ‑, it looks like:
 

<template id="opt_blog_rc_follow_us" name="Follow us" priority="4" inherit_id="website_blog.index_right" active="False" customize_show="True">
    <xpath expr="//div[@id='blog_right_column']" position="inside">
        <section class="mt32">
            <h4>Follow us<small t-if="blog">: <t t-esc="blog.name"/></small></h4>
            <t t-if="blog">
                <t t-call="website_mail.follow">
                    <t t-set="email" t-value="user_id.email"/>
                    <t t-set="object" t-value="blog"/>
                </t>
            </t>
            <p class="text-muted mb0 mt16">
                Participate on our social stream.
            </p>
            <h2 class="mt4">
                <a t-att-href="website.social_facebook" t-if="website.social_facebook"><i class="fa fa-facebook-square"/></a>
                <a t-att-href="website.social_twitter" t-if="website.social_twitter"><i class="fa fa-twitter"/></a>
                <a t-att-href="website.social_linkedin" t-if="website.social_linkedin"><i class="fa fa-linkedin"/></a>
                <a t-att-href="website.social_youtube" t-if="website.social_youtube"><i class="fa fa-youtube-play"/></a>
                <a t-att-href="website.social_googleplus" t-if="website.social_googleplus"><i class="fa fa-google-plus-square"/></a>
                <a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
            </h2>
        </section>
    </xpath>
</template>


First we have to create our template entry in my_module which inherits the parent template. So we open my_module/views/website_blog_templates.xml and we add the following code:
 

​<?xml version="1.0" encoding="utf-8"?>
<openerp>
    <data>
        <template id="opt_blog_rc_follow_us" inherit_id="website_blog.opt_blog_rc_follow_us">
        </template>
    </data>
</openerp>


The attributes "id" and "inherit_id" of the <template> tag are required. "id" has to be unique otherwise we would overwrite an existing template. Now you would say stop, opt_blog_rc_follow_us is used as id in the parent template. Wouldn't we overwrite the parent template directly instead of inherit it? No, because odoo automatically adds the module name before the template id separated by a dot when referencing a template, this is called external identifier. The external identifier of our new template is my_module.opt_blog_rc_follow_us and the external identifier of the parent template is website_blog.opt_blog_rc_follow_us. "inherit_id" is the external identifier of the parent template.

If you want an additional option entry in the frontend customize menu to activate or deactivate your changes you have to use the attribute customize_showIf you use this attribute you should also use the attribute name. Otherwise the template id is taken for the option label in the frontend customize menu. If you later decide to not use the customize option you have to delete the attribute customize_show. It is possible that the option label won't be removed in the frontend customize menu immediately. If that is the case you should delete your template first and update my_module again.

Now we don't want the "website_mail.follow" part and we don't want the "Participate on our social stream." part of the parent template. We have two possibilities to achieve our goals. We can delete each part or we replace the whole section with our own code. We use the last possibility and update our code in my_module/views/website_blog_templates.xml
 

<?xml version="1.0" encoding="utf-8"?>
<openerp>
    <data>
        <template id="opt_blog_rc_follow_us" inherit_id="website_blog.opt_blog_rc_follow_us">
            <xpath expr="//section" position="replace">
                <section class="mt32">
                    <div class="rc_border">
                        <h4>Follow us now</h4>
                    </div>
                    <h2 class="mt4">
                        <a t-att-href="website.social_facebook" t-if="website.social_facebook"><i class="fa fa-facebook-square"/></a>
                        <a t-att-href="website.social_twitter" t-if="website.social_twitter"><i class="fa fa-twitter"/></a>
                        <a t-att-href="website.social_linkedin" t-if="website.social_linkedin"><i class="fa fa-linkedin"/></a>
                        <a t-att-href="website.social_youtube" t-if="website.social_youtube"><i class="fa fa-youtube-play"/></a>
                        <a t-att-href="website.social_googleplus" t-if="website.social_googleplus"><i class="fa fa-google-plus-square"/></a>
                        <a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
                    </h2>
                </section>
            </xpath>
        </template>
    </data>
</openerp>


The attributes "expr" and "position" of the <xpath> tag are required. "expr" is used to find the specific location in the parent template where we want to start our updates. The specific location is searched by a xpath expression. You can only search for elements which are within the <xpath> tag, you can't search the <xpath> tag itself of the parent template. "position" indicates how we want to update the parent template. Here you will find more information about the values you  can use for "position".

Like mentioned before we want to replace the whole section with our own code. So we use "//section" for the xpath expression to select the section tag in the parent template. We use "replace" for the position to replace the whole section. The new content of our Follow Us section is inside the <xpath> tag.

If you update templates with new content you always have to keep in mind that you must not delete or rename parts which are needed by other templates. For example: for whatever reasons if you update the html id "main_column" to "blog_main" in website_blog.blog_post_short you will get trouble because the template website_blog.index_right uses this html id in the xpath expression.

At last we have to update our __openerp__.py manifest file. We have to register our new template file and we have to add the website blog module as dependency. At least our manifest file should look like this:
 

{
    'name': 'My Module',
    'description': """
My Module Update
================

* Update existing "Follow Us" section in the right column of the blog page
    """,
    # the website_blog module must be installed
    'depends': ['website_blog'],
    # register our new template file
    'data': [
        'views/website_blog_templates.xml'
    ]
}


Thats all. Now we restart the server and update my_module. If you get errors while updating the module you should check the following points:

  • look at your xpath expression, it is one main source for problems when updating templates
  • you also have to ensure that you use the correct inherit_id, means the correct external identifier of the parent template

If the update of my_module is fine we go to the blog page in the frontend and check our changes of the Follow Us section in the right column. Look's nice or not? It looks anything but nice ;). We now see that the blog page title is replaced with our changes instead of the Follow Us section in the right column.
 

How is that possible? We inherited the Follow Us template and not the blog page template. What is wrong?

Inheritance problems and solutions

The steps described above are valid to update templates. But the inheritance behavior of odoo can end up in unforeseen results because odoo includes all parent templates in the inheritance process. What does it mean for our example? Let's look which templates are included

  • (a) website_blog.blog_post_short
  • (b) website_blog.index_right
  • (c) website_blog.opt_blog_rc_follow_us (the template we want to update)
  • (d) my_module.opt_blog_rc_follow_us (our new template)

(a) is the start template and the parent template of (b), (b) is the parent template of (c) and (c) is the parent template of (d). The xpath expression "//section" is not only used in the parent template (c), it is also used in (b) and (a). A <section> tag is also in (a) with the blog page title information which is changed with our template updates.

As I figured out this problem my first approach was to change the xpath expression to "//section[@class='mt32']", it is more precise. After updating my_module the blog page title was restored successfully but now there was something wrong in the right column. I see our new section Follow us now but I also see the old section Follow us. Furthermore the About us section was missing now.
 


After some more research I came to the conclusion that next to all parent templates 
also other templates are included in the inheritance process, other templates like the website blog About us template which inherits (b). I have no idea why this happens at the moment but hopefully I can give an answer in one of my blog post in the near future. By the way these problems are not only related to the website blog templates. It is a common problem for all templates which inherit other templates. If you inherit a template (a) which doesn't inherit another template you won't have problems.

But how can we update the Follow us section in the meantime? We overwrite the parent template directly. So we open my_module/views/website_blog_templates.xml and update our code with: 
 

<?xml version="1.0" encoding="utf-8"?>
<openerp>
    <data>
        <template id="website_blog.opt_blog_rc_follow_us" name="Follow us now" priority="4" inherit_id="website_blog.index_right"
            customize_show="True">
            <xpath expr="//div[@id='blog_right_column']" position="inside">
                <section class="mt32">
                    <div class="rc_border">
                        <h4>Follow us now</h4>
                    </div>
                    <h2 class="mt4">
                        <a t-att-href="website.social_facebook" t-if="website.social_facebook"><i class="fa fa-facebook-square"/></a>
                        <a t-att-href="website.social_twitter" t-if="website.social_twitter"><i class="fa fa-twitter"/></a>
                        <a t-att-href="website.social_linkedin" t-if="website.social_linkedin"><i class="fa fa-linkedin"/></a>
                        <a t-att-href="website.social_youtube" t-if="website.social_youtube"><i class="fa fa-youtube-play"/></a>
                        <a t-att-href="website.social_googleplus" t-if="website.social_googleplus"><i class="fa fa-google-plus-square"/></a>
                        <a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
                    </h2>
                </section>
            </xpath>
        </template>
    </data>
</openerp>


The attributes "id" and "inherit_id" of the <template> tag are required. We use website_blog.opt_blog_rc_follow_us for the "id" here. In this way odoo knows that we reference the template with the id opt_blog_rc_follow_us in the website_blog module. If we don't use "website_blog." we only would create a new template without any relation to the "Follow us" section. "inherit_id" is the same external identifier like the "inherit_id" of the overwritten template. This is different of the inherit_id we used in our example above.

You can leave out the attributes name, priority and active, odoo will use default values for them. But at least you should use the attribute name. Like described above it is used as option label in the frontend customize menu. In this case we also use the attribute priority because it is used in the overwritten template. The <xpath> tag is the same like in the overwritten template.
 


That's all and after updating my_module all looks fine. The blog page title is shown, the About us section in the right column is shown and our changes in the 
Follow us section are also shown.

About Robert Rübner

The python and odoo ninja/ We've heard some people call him Yoda/ Got a smartphone some months ago.