Odoo 15 is out, and big internal rework has been done to the WebClient. You can still write Views in the old MVC Architecture but now you also can write pure OWL Component Views.
The architecture is a bit different from the old MVC but with some helpers, and the good rewrite of the WebClient, it is actually not that hard, as you will see in this follow-along tutorial.
As an example, we will completely migrate the module we created in our JavaScript 101 Series, Creating an OWL View that was a Hierarchical Tree View, let's go!
Introduction
This article is meant to be followed along, so please consider pulling the Odoo 14 version of our Module and coding along with each change. To pull directly the main branch:
git clone https://github.com/Coding-Dodo/owl_tutorial_views.git
Then cd into the folder and create a new branch that will be your basis of work:
cd owl_tutorial_views
git checkout -b 15.0-mig-owl_tutorial_views
Finally, try to run the module again often so you can see from your own eyes what breaks and how we fix it.
Updating our assets bundling to the manifest
The first action is to use the new assets
key in the __manifest__
file. Let's take everything that was inside our assets.xml
(and remove the call to that file) and put it inside the manifest file inside the web.assets_backend
bundle.
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -4,14 +4,24 @@
"author": "Coding Dodo",
"website": "https://codingdodo.com",
"category": "Tools",
- "version": "14.0.1",
+ "version": "15.0.1",
"depends": ["base", "web", "mail", "product"],
"qweb": [
"static/src/components/tree_item/TreeItem.xml",
"static/src/xml/owl_tree_view.xml",
],
"data": [
- "views/assets.xml",
"views/product_views.xml",
],
+ "assets": {
+ "web.assets_backend": [
+ "/owl_tutorial_views/static/src/components/tree_item/tree_item.scss",
+ "/owl_tutorial_views/static/src/owl_tree_view/owl_tree_view.scss",
+ "/owl_tutorial_views/static/src/components/tree_item/TreeItem.js",
+ "/owl_tutorial_views/static/src/owl_tree_view/owl_tree_view.js",
+ "/owl_tutorial_views/static/src/owl_tree_view/owl_tree_model.js",
+ "/owl_tutorial_views/static/src/owl_tree_view/owl_tree_controller.js",
+ "/owl_tutorial_views/static/src/owl_tree_view/owl_tree_renderer.js",
+ ],
+ },
}
Now for the Qweb XML files
The qweb
key in the manifest should also be emptied, instead, the QWeb templates will go inside the web.assets_qweb
bundle:
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -6,14 +6,14 @@
"category": "Tools",
"version": "15.0.1",
"depends": ["base", "web", "mail", "product"],
- "qweb": [
- "static/src/components/tree_item/TreeItem.xml",
- "static/src/xml/owl_tree_view.xml",
- ],
"data": [
"views/product_views.xml",
],
"assets": {
+ "web.assets_qweb": [
+ "/owl_tutorial_views/static/src/components/tree_item/TreeItem.xml",
+ "/owl_tutorial_views/static/src/xml/owl_tree_view.xml",
+ ],
"web.assets_backend": [
"/owl_tutorial_views/static/src/components/tree_item/tree_item.scss",
"/owl_tutorial_views/static/src/owl_tree_view/owl_tree_view.scss",
We would be tempted to try and run the module right now but we would get an error:
Missing dependencies: ["web.patchMixin"]
PatchMixin disappeared!
The patchMixin
doesn't exist anymore in Odoo v15, it was a temporary solution in v14. So we have to remove import
statements and usage reference from our OWLTreeRenderer
and our TreeItem
Components:
--- a/static/src/components/tree_item/TreeItem.js
+++ b/static/src/components/tree_item/TreeItem.js
@@ -3,7 +3,6 @@ odoo.define(
function (require) {
"use strict";
const { Component } = owl;
- const patchMixin = require("web.patchMixin");
const { useState } = owl.hooks;
class TreeItem extends Component {
@@ -67,6 +66,6 @@ odoo.define(
template: "owl_tutorial_views.TreeItem",
});
- return patchMixin(TreeItem);
+ return TreeItem;
}
);
Same for the OWLTreeRenderer
:
--- a/static/src/owl_tree_view/owl_tree_renderer.js
+++ b/static/src/owl_tree_view/owl_tree_renderer.js
@@ -2,7 +2,6 @@ odoo.define("owl_tutorial_views.OWLTreeRenderer", function (require) {
"use strict";
const AbstractRendererOwl = require("web.AbstractRendererOwl");
- const patchMixin = require("web.patchMixin");
const QWeb = require("web.QWeb");
const session = require("web.session");
@@ -52,5 +51,5 @@ odoo.define("owl_tutorial_views.OWLTreeRenderer", function (require) {
template: "owl_tutorial_views.OWLTreeRenderer",
});
- return patchMixin(OWLTreeRenderer);
+ return OWLTreeRenderer;
});
Migrating to the Native JavaScript Module system
Odoo 15 introduced a new way of defining our JavaScript module instead of the usual odoo.define
, we can now use a syntax similar to ES6 modules and import.
According to the official documentation:
Most new Odoo javascript code should use the native javascript module system. This is simpler, and brings the benefits of a better developer experience with a better integration with IDE.
So let's migrate the syntax.
Basic conversion for the TreeItem Component
So, let's change our TreeItem
Component accordingly.
--- a/static/src/components/tree_item/TreeItem.js
+++ b/static/src/components/tree_item/TreeItem.js
@@ -1,11 +1,8 @@
-odoo.define(
- "owl_tutorial_views/static/src/components/tree_item/TreeItem.js",
- function (require) {
- "use strict";
+/** @odoo-module **/
const { Component } = owl;
const { useState } = owl.hooks;
- class TreeItem extends Component {
+export class TreeItem extends Component {
/**
* @override
*/
@@ -65,7 +62,3 @@ odoo.define(
},
template: "owl_tutorial_views.TreeItem",
});
-
- return TreeItem;
- }
-);
Now that our TreeItem
is converted to odoo-module
how do we import it? Let's update OwlTreeRenderer
:
--- a/static/src/owl_tree_view/owl_tree_renderer.js
+++ b/static/src/owl_tree_view/owl_tree_renderer.js
@@ -4,6 +4,9 @@ odoo.define("owl_tutorial_views.OWLTreeRenderer", function (require) {
const AbstractRendererOwl = require("web.AbstractRendererOwl");
const QWeb = require("web.QWeb");
const session = require("web.session");
+ const {
+ TreeItem,
+ } = require("@owl_tutorial_views/components/tree_item/TreeItem");
const { useState } = owl.hooks;
@@ -23,9 +26,7 @@ odoo.define("owl_tutorial_views.OWLTreeRenderer", function (require) {
}
}
- const components = {
- TreeItem: require("owl_tutorial_views/static/src/components/tree_item/TreeItem.js"),
- };
+ const components = { TreeItem };
Object.assign(OWLTreeRenderer, {
components,
defaultProps: {
Notice the conversion to @owl_tutorial_views/components/tree_item/TreeItem
This is the rule:
@name_of_your_module
- + relative path beginning inside
static/src
. - don't put the
.js
extension
So for a file inside owl_tutorial_views/static/src/components/tree_item/TreeItem.js
, we get @owl_tutorial_views/components/tree_item/TreeItem
Aliased JavaScript Module conversion for the OWLTreeRender
Component.
To reduce some of the friction caused by redefining the import/require that are already here in the code, as described before, you can alias your odoo-module
so it keeps the same name as before.
Let's update our OWLTreeRenderer
to the new odoo-module
syntax but, this time, we use an alias corresponding to the old name of the module.
First, for the owl_tree_renderer.js
file:
--- a/static/src/owl_tree_view/owl_tree_renderer.js
+++ b/static/src/owl_tree_view/owl_tree_renderer.js
@@ -1,16 +1,11 @@
-odoo.define("owl_tutorial_views.OWLTreeRenderer", function (require) {
- "use strict";
-
- const AbstractRendererOwl = require("web.AbstractRendererOwl");
- const QWeb = require("web.QWeb");
- const session = require("web.session");
- const {
- TreeItem,
- } = require("@owl_tutorial_views/components/tree_item/TreeItem");
-
+/** @odoo-module alias=owl_tutorial_views.OWLTreeRenderer default=0 **/
const { useState } = owl.hooks;
+import AbstractRendererOwl from "web.AbstractRendererOwl";
+import QWeb from "web.QWeb";
+import session from "web.session";
+import { TreeItem } from "@owl_tutorial_views/components/tree_item/TreeItem";
- class OWLTreeRenderer extends AbstractRendererOwl {
+export default class OWLTreeRenderer extends AbstractRendererOwl {
constructor(parent, props) {
super(...arguments);
this.qweb = new QWeb(this.env.isDebug(), { _s: session.origin });
@@ -51,6 +46,3 @@ odoo.define("owl_tutorial_views.OWLTreeRenderer", function (require) {
},
template: "owl_tutorial_views.OWLTreeRenderer",
});
-
- return OWLTreeRenderer;
-});
export default
. The default
will allow us to directly require the object and not have to destructure it like { ComponentToImport } = require(...)
. There is less friction like that.
As you can see we also transformed every require
into import
.
Now inside the main View file owl_tree_view.js
we don't have to change anything, our old require will still work.
const OWLTreeRenderer = require("owl_tutorial_views.OWLTreeRenderer");
Refresh your page and your module should now work correctly.
That's it for the minimal migration path that we could take on our JavaScript Module. The module is functional and no features were lost in the process but it still uses the old MVC architecture...
Transitioning from old MVC View to full OWL
This is where things are going to change a lot in our code. Let's have an overview of what we will do, then we will go through each step:
- Migrate our Model to the
@web/views/helpers/model
and use theuseModel
hook. - Use the
Layout
Component, so our View has access to the SearchPanel and ControlPanel. - Getting rid of the Controller, the View will now react to events and call Model functions.
Doing this will bring a lot of changes mainly in the Model and View files but it is important to note that all the other files should be very lightly impacted by this transition.
What I want to highlight here is that, if you wrote your OWL Component from Odoo 14 version, you should be able to migrate your code to v15 with minimal friction.
Migrating to the new Model class
Switch to odoo-module and new Model Class.
Let's migrate our OWLTreeModel
Class to the new Model from @web/views/helpers/model
and transform the file into an odoo-module
file.