[MERGE] Forward-port of branch 13.0 to 14.0

This commit is contained in:
Antoine Vandevenne (anv)
2021-07-07 15:39:35 +02:00
220 changed files with 1841 additions and 2574 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,544 @@
.. _reference/jscs:
=====================
Javascript Cheatsheet
=====================
There are many ways to solve a problem in JavaScript, and in Odoo. However, the
Odoo framework was designed to be extensible (this is a pretty big constraint),
and some common problems have a nice standard solution. The standard solution
has probably the advantage of being easy to understand for an odoo developers,
and will probably keep working when Odoo is modified.
This document tries to explain the way one could solve some of these issues.
Note that this is not a reference. This is just a random collection of recipes,
or explanations on how to proceed in some cases.
First of all, remember that the first rule of customizing odoo with JS is:
*try to do it in python*. This may seem strange, but the python framework is
quite extensible, and many behaviours can be done simply with a touch of xml or
python. This has usually a lower cost of maintenance than working with JS:
- the JS framework tends to change more, so JS code needs to be more frequently
updated
- it is often more difficult to implement a customized behaviour if it needs to
communicate with the server and properly integrate with the javascript framework.
There are many small details taken care by the framework that customized code
needs to replicate. For example, responsiveness, or updating the url, or
displaying data without flickering.
.. note:: This document does not really explain any concepts. This is more a
cookbook. For more details, please consult the javascript reference
page (see :doc:`javascript_reference`)
Creating a new field widget
===========================
This is probably a really common usecase: we want to display some information in
a form view in a really specific (maybe business dependent) way. For example,
assume that we want to change the text color depending on some business condition.
This can be done in three steps: creating a new widget, registering it in the
field registry, then adding the widget to the field in the form view
- creating a new widget:
This can be done by extending a widget:
.. code-block:: javascript
var FieldChar = require('web.basic_fields').FieldChar;
var CustomFieldChar = FieldChar.extend({
_renderReadonly: function () {
// implement some custom logic here
},
});
- registering it in the field registry:
The web client needs to know the mapping between a widget name and its
actual class. This is done by a registry:
.. code-block:: javascript
var fieldRegistry = require('web.field_registry');
fieldRegistry.add('my-custom-field', CustomFieldChar);
- adding the widget in the form view
.. code-block:: xml
<field name="somefield" widget="my-custom-field"/>
Note that only the form, list and kanban views use this field widgets registry.
These views are tightly integrated, because the list and kanban views can
appear inside a form view).
Modifying an existing field widget
==================================
Another use case is that we want to modify an existing field widget. For
example, the voip addon in odoo need to modify the FieldPhone widget to add the
possibility to easily call the given number on voip. This is done by *including*
the FieldPhone widget, so there is no need to change any existing form view.
Field Widgets (instances of (subclass of) AbstractField) are like every other
widgets, so they can be monkey patched. This looks like this:
.. code-block:: javascript
var basic_fields = require('web.basic_fields');
var Phone = basic_fields.FieldPhone;
Phone.include({
events: _.extend({}, Phone.prototype.events, {
'click': '_onClick',
}),
_onClick: function (e) {
if (this.mode === 'readonly') {
e.preventDefault();
var phoneNumber = this.value;
// call the number on voip...
}
},
});
Note that there is no need to add the widget to the registry, since it is already
registered.
Modifying a main widget from the interface
==========================================
Another common usecase is the need to customize some elements from the user
interface. For example, adding a message in the home menu. The usual process
in this case is again to *include* the widget. This is the only way to do it,
since there are no registries for those widgets.
This is usually done with code looking like this:
.. code-block:: javascript
var HomeMenu = require('web_enterprise.HomeMenu');
HomeMenu.include({
render: function () {
this._super();
// do something else here...
},
});
Creating a new view (from scratch)
==================================
Creating a new view is a more advanced topic. This cheatsheet will only
highlight the steps that will probably need to be done (in no particular order):
- adding a new view type to the field ``type`` of ``ir.ui.view``::
class View(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(selection_add=[('map', "Map")])
- adding the new view type to the field ``view_mode`` of ``ir.actions.act_window.view``::
class ActWindowView(models.Model):
_inherit = 'ir.actions.act_window.view'
view_mode = fields.Selection(selection_add=[('map', "Map")])
- creating the four main pieces which makes a view (in JavaScript):
we need a view (a subclass of ``AbstractView``, this is the factory), a
renderer (from ``AbstractRenderer``), a controller (from ``AbstractController``)
and a model (from ``AbstractModel``). I suggest starting by simply
extending the superclasses:
.. code-block:: javascript
var AbstractController = require('web.AbstractController');
var AbstractModel = require('web.AbstractModel');
var AbstractRenderer = require('web.AbstractRenderer');
var AbstractView = require('web.AbstractView');
var MapController = AbstractController.extend({});
var MapRenderer = AbstractRenderer.extend({});
var MapModel = AbstractModel.extend({});
var MapView = AbstractView.extend({
config: {
Model: MapModel,
Controller: MapController,
Renderer: MapRenderer,
},
});
- adding the view to the registry:
As usual, the mapping between a view type and the actual class needs to be
updated:
.. code-block:: javascript
var viewRegistry = require('web.view_registry');
viewRegistry.add('map', MapView);
- implementing the four main classes:
The ``View`` class needs to parse the ``arch`` field and setup the other
three classes. The ``Renderer`` is in charge of representing the data in
the user interface, the ``Model`` is supposed to talk to the server, to
load data and process it. And the ``Controller`` is there to coordinate,
to talk to the web client, ...
- creating some views in the database:
.. code-block:: xml
<record id="customer_map_view" model="ir.ui.view">
<field name="name">customer.map.view</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<map latitude="partner_latitude" longitude="partner_longitude">
<field name="name"/>
</map>
</field>
</record>
Customizing an existing view
============================
Assume we need to create a custom version of a generic view. For example, a
kanban view with some extra *ribbon-like* widget on top (to display some
specific custom information). In that case, this can be done with 3 steps:
extend the kanban view (which also probably mean extending controllers/renderers
and/or models), then registering the view in the view registry, and finally,
using the view in the kanban arch (a specific example is the helpdesk dashboard).
- extending a view:
Here is what it could look like:
.. code-block:: javascript
var HelpdeskDashboardRenderer = KanbanRenderer.extend({
...
});
var HelpdeskDashboardModel = KanbanModel.extend({
...
});
var HelpdeskDashboardController = KanbanController.extend({
...
});
var HelpdeskDashboardView = KanbanView.extend({
config: _.extend({}, KanbanView.prototype.config, {
Model: HelpdeskDashboardModel,
Renderer: HelpdeskDashboardRenderer,
Controller: HelpdeskDashboardController,
}),
});
- adding it to the view registry:
as usual, we need to inform the web client of the mapping between the name
of the views and the actual class.
.. code-block:: javascript
var viewRegistry = require('web.view_registry');
viewRegistry.add('helpdesk_dashboard', HelpdeskDashboardView);
- using it in an actual view:
we now need to inform the web client that a specific ``ir.ui.view`` needs to
use our new class. Note that this is a web client specific concern. From
the point of view of the server, we still have a kanban view. The proper
way to do this is by using a special attribute ``js_class`` (which will be
renamed someday into ``widget``, because this is really not a good name) on
the root node of the arch:
.. code-block:: xml
<record id="helpdesk_team_view_kanban" model="ir.ui.view" >
...
<field name="arch" type="xml">
<kanban js_class="helpdesk_dashboard">
...
</kanban>
</field>
</record>
.. note::
Note: you can change the way the view interprets the arch structure. However,
from the server point of view, this is still a view of the same base type,
subjected to the same rules (rng validation, for example). So, your views still
need to have a valid arch field.
Promises and asynchronous code
===============================
For a very good and complete introduction to promises, please read this excellent article https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/async%20%26%20performance/ch3.md
Creating new Promises
-----------------------
- turn a constant into a promise
There are 2 static functions on Promise that create a resolved or rejected promise based on a constant:
.. code-block:: javascript
var p = Promise.resolve({blabla: '1'}); // creates a resolved promise
p.then(function (result) {
console.log(result); // --> {blabla: '1'};
});
var p2 = Promise.reject({error: 'error message'}); // creates a rejected promise
p2.catch(function (reason) {
console.log(reason); // --> {error: 'error message');
});
.. note:: Note that even if the promises are created already resolved or rejected, the `then` or `catch` handlers will still be called asynchronously.
- based on an already asynchronous code
Suppose that in a function you must do a rpc, and when it is completed set the result on this.
The `this._rpc` is a function that returns a `Promise`.
.. code-block:: javascript
function callRpc() {
var self = this;
return this._rpc(...).then(function (result) {
self.myValueFromRpc = result;
});
}
- for callback based function
Suppose that you were using a function `this.close` that takes as parameter a callback that is called when the closing is finished.
Now suppose that you are doing that in a method that must send a promise that is resolved when the closing is finished.
.. code-block:: javascript
:linenos:
function waitForClose() {
var self = this;
return new Promise (function(resolve, reject) {
self.close(resolve);
});
}
* line 2: we save the `this` into a variable so that in an inner function, we can access the scope of our component
* line 3: we create and return a new promise. The constructor of a promise takes a function as parameter. This function itself has 2 parameters that we called here `resolve` and `reject`
- `resolve` is a function that, when called, puts the promise in the resolved state.
- `reject` is a function that, when called, puts the promise in the rejected state. We do not use reject here and it can be omitted.
* line 4: we are calling the function close on our object. It takes a function as parameter (the callback) and it happens that resolve is already a function, so we can pass it directly. To be clearer, we could have written:
.. code-block:: javascript
return new Promise (function (resolve) {
self.close(function () {
resolve();
});
});
- creating a promise generator (calling one promise after the other *in sequence* and waiting for the last one)
Suppose that you need to loop over an array, do an operation *in sequence* and resolve a promise when the last operation is done.
.. code-block:: javascript
function doStuffOnArray(arr) {
var done = Promise.resolve();
arr.forEach(function (item) {
done = done.then(function () {
return item.doSomethingAsynchronous();
});
});
return done;
}
This way, the promise you return is effectively the last promise.
- creating a promise, then resolving it outside the scope of its definition (anti-pattern)
.. note:: we do not recommend using this, but sometimes it is useful. Think carefully for alternatives first...
.. code-block:: javascript
...
var resolver, rejecter;
var prom = new Promise(function (resolve, reject){
resolver = resolve;
rejecter = reject;
});
...
resolver("done"); // will resolve the promise prom with the result "done"
rejecter("error"); // will reject the promise prom with the reason "error"
Waiting for Promises
--------------------
- waiting for a number of Promises
if you have multiple promises that all need to be waited, you can convert them into a single promise that will be resolved when all the promises are resolved using Promise.all(arrayOfPromises).
.. code-block:: javascript
var prom1 = doSomethingThatReturnsAPromise();
var prom2 = Promise.resolve(true);
var constant = true;
var all = Promise.all([prom1, prom2, constant]); // all is a promise
// results is an array, the individual results correspond to the index of their
// promise as called in Promise.all()
all.then(function (results) {
var prom1Result = results[0];
var prom2Result = results[1];
var constantResult = results[2];
});
return all;
- waiting for a part of a promise chain, but not another part
If you have an asynchronous process that you want to wait to do something, but you also want to return to the caller before that something is done.
.. code-block:: javascript
function returnAsSoonAsAsyncProcessIsDone() {
var prom = AsyncProcess();
prom.then(function (resultOfAsyncProcess) {
return doSomething();
});
/* returns prom which will only wait for AsyncProcess(),
and when it will be resolved, the result will be the one of AsyncProcess */
return prom;
}
Error handling
--------------
- in general in promises
The general idea is that a promise should not be rejected for control flow, but should only be rejected for errors.
When that is the case, you would have multiple resolutions of your promise with, for instance status codes that you would have to check in the `then` handlers and a single `catch` handler at the end of the promise chain.
.. code-block:: javascript
function a() {
x.y(); // <-- this is an error: x is undefined
return Promise.resolve(1);
}
function b() {
return Promise.reject(2);
}
a().catch(console.log); // will log the error in a
a().then(b).catch(console.log); // will log the error in a, the then is not executed
b().catch(console.log); // will log the rejected reason of b (2)
Promise.resolve(1)
.then(b) // the then is executed, it executes b
.then(...) // this then is not executed
.catch(console.log); // will log the rejected reason of b (2)
- in Odoo specifically
In Odoo, it happens that we use promise rejection for control flow, like in mutexes and other concurrency primitives defined in module `web.concurrency`
We also want to execute the catch for *business* reasons, but not when there is a coding error in the definition of the promise or of the handlers.
For this, we have introduced the concept of `guardedCatch`. It is called like `catch` but not when the rejected reason is an error
.. code-block:: javascript
function blabla() {
if (someCondition) {
return Promise.reject("someCondition is truthy");
}
return Promise.resolve();
}
// ...
var promise = blabla();
promise.then(function (result) { console.log("everything went fine"); })
// this will be called if blabla returns a rejected promise, but not if it has an error
promise.guardedCatch(function (reason) { console.log(reason); });
// ...
var anotherPromise =
blabla().then(function () { console.log("everything went fine"); })
// this will be called if blabla returns a rejected promise,
// but not if it has an error
.guardedCatch(console.log);
.. code-block:: javascript
var promiseWithError = Promise.resolve().then(function () {
x.y(); // <-- this is an error: x is undefined
});
promiseWithError.guardedCatch(function (reason) {console.log(reason);}); // will not be called
promiseWithError.catch(function (reason) {console.log(reason);}); // will be called
Testing asynchronous code
--------------------------
- using promises in tests
In the tests code, we support the latest version of Javascript, including primitives like `async` and `await`. This makes using and waiting for promises very easy.
Most helper methods also return a promise (either by being marked `async` or by returning a promise directly.
.. code-block:: javascript
var testUtils = require('web.test_utils');
QUnit.test("My test", async function (assert) {
// making the function async has 2 advantages:
// 1) it always returns a promise so you don't need to define `var done = assert.async()`
// 2) it allows you to use the `await`
assert.expect(1);
var form = await testUtils.createView({ ... });
await testUtils.form.clickEdit(form);
await testUtils.form.click('jquery selector');
assert.containsOnce('jquery selector');
form.destroy();
});
QUnit.test("My test - no async - no done", function (assert) {
// this function is not async, but it returns a promise.
// QUnit will wait for for this promise to be resolved.
assert.expect(1);
return testUtils.createView({ ... }).then(function (form) {
return testUtils.form.clickEdit(form).then(function () {
return testUtils.form.click('jquery selector').then(function () {
assert.containsOnce('jquery selector');
form.destroy();
});
});
});
});
QUnit.test("My test - no async", function (assert) {
// this function is not async and does not return a promise.
// we have to use the done function to signal QUnit that the test is async and will be finished inside an async callback
assert.expect(1);
var done = assert.async();
testUtils.createView({ ... }).then(function (form) {
testUtils.form.clickEdit(form).then(function () {
testUtils.form.click('jquery selector').then(function () {
assert.containsOnce('jquery selector');
form.destroy();
done();
});
});
});
});
as you can see, the nicer form is to use `async/await` as it is clearer and shorter to write.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
.. _reference/mobile:
==================
Mobile JavaScript
==================
Introduction
============
In Odoo 10.0 we released a mobile app which allows you to access all **Odoo apps**
(even your customized modules).
The application is a combination of **Odoo Web** and **Native Mobile
components**. In other words it is a Odoo Web instance loaded inside a native, mobile, WebView container.
This page documents how you can access mobile native components like Camera,
Vibration, Notification and Toast through Odoo Web (via JavaScript). For this, you
do not need to be a mobile developer, if you know Odoo JavaScript API you can
access all available mobile features.
.. warning:: These features work with **Odoo Enterprise 10.0+** only
How does it work?
=================
Internal workings of the mobile application:
.. image:: images/mobile_working.jpg
Of course, it is a web page that loads on a Mobile Native Web container. But it
is integrated in such a way that you can access native resources from your web
JavaScript.
WebPages (Odoo Web) is on the top of each layer, where the second layer is a Bridge
between Odoo Web (JS) and the native mobile components.
When any call from JavaScript is triggered it passes through Bridge and Bridge
passes it to the native invoker to perform that action.
When the native component has done its work, it is passed to the Bridge again and
you get the output in JavaScript.
Process time taken by the Native component depends on what you are requesting
from the Native resources. For example the Camera or GPS Location.
How to use it?
==============
Just like the Odoo Web Framework, the Mobile API can be used anywhere by getting the object from
**web_mobile.rpc**
.. image:: images/odoo_mobile_api.png
The mobile RPC object provides a list of methods that are available (this only works with the mobile
app).
Check if the method is available and then execute it.
Methods
-------
.. note:: Each of the methods returns a JQuery Deferred object which returns
a data JSON dictionary
Show Toast in device
.....................
.. js:function:: showToast
:param object args: **message** text to display
A toast provides simple feedback about an operation in a small popup. It only
fills the amount of space required for the message and the current activity
remains visible and interactive.
.. code-block:: javascript
mobile.methods.showToast({'message': 'Message sent'});
.. image:: images/toast.png
Vibrating device
................
.. js:function:: vibrate
:param object args: Vibrates constantly for the specified period of time
(in milliseconds).
Vibrate mobile device with given duration.
.. code-block:: javascript
mobile.methods.vibrate({'duration': 100});
Show snackbar with action
.........................
.. js:function:: showSnackBar
:param object args: (*required*) **Message** to show in snackbar and action **button label** in Snackbar (optional)
:returns: ``True`` if the user clicks on the Action button, ``False`` if SnackBar auto dismissed after some time.
Snackbars provide lightweight feedback about an operation. They show a brief
message at the bottom of the screen on mobile or in the lower left corner on larger devices.
Snackbars appear above all the other elements on the screen and only one can be
displayed at a time.
.. code-block:: javascript
mobile.methods.showSnackBar({'message': 'Message is deleted', 'btn_text': 'Undo'}).then(function(result){
if(result){
// Do undo operation
}else{
// Snack Bar dismissed
}
});
.. image:: images/snackbar.png
Showing notification
.....................
.. js:function:: showNotification
:param object args: **title** (first row) of the notification, **message** (second row) of the notification, in a standard notification.
A notification is a message you can display to the user outside of your
application's normal UI. When you tell the system to issue a notification, it
first appears as an icon in the notification area. To see the details of the
notification, the user opens the notification drawer. Both the notification
area and the notification drawer are system-controlled areas that the user can
view at any time.
.. code-block:: javascript
mobile.showNotification({'title': 'Simple Notification', 'message': 'This is a test for a simple notification'})
.. image:: images/mobile_notification.png
Create contact in device
.........................
.. js:function:: addContact
:param object args: Dictionary with contact details. Possible keys (name, mobile, phone, fax, email, website, street, street2, country_id, state_id, city, zip, parent_id, function and image)
Create a new device contact with the given contact details.
.. code-block:: javascript
var contact = {
'name': 'Michel Fletcher',
'mobile': '9999999999',
'phone': '7954856587',
'fax': '765898745',
'email': 'michel.fletcher@agrolait.example.com',
'website': 'http://www.agrolait.com',
'street': '69 rue de Namur',
'street2': false,
'country_id': [21, 'Belgium'],
'state_id': false,
'city': 'Wavre',
'zip': '1300',
'parent_id': [8, 'Agrolait'],
'function': 'Analyst',
'image': '<<BASE 64 Image Data>>'
}
mobile.methods.addContact(contact);
.. image:: images/mobile_contact_create.png
Scanning barcodes
..................
.. js:function:: scanBarcode
:returns: Scanned ``code`` from any barcode
The barcode API detects barcodes in real-time, on the device, in any orientation.
The barcode API can read the following barcode formats:
* 1D barcodes: EAN-13, EAN-8, UPC-A, UPC-E, Code-39, Code-93, Code-128, ITF, Codabar
* 2D barcodes: QR Code, Data Matrix, PDF-417, AZTEC
.. code-block:: javascript
mobile.methods.scanBarcode().then(function(code){
if(code){
// Perform operation with the scanned code
}
});
Switching account in device
...........................
.. js:function:: switchAccount
Use switchAccount to switch from one account to another on the device.
.. code-block:: javascript
mobile.methods.switchAccount();
.. image:: images/mobile_switch_account.png

View File

@@ -0,0 +1,687 @@
.. highlight:: xml
.. _reference/qweb:
==============
QWeb Templates
==============
QWeb is the primary templating_ engine used by Odoo\ [#othertemplates]_. It
is an XML templating engine\ [#genshif]_ and used mostly to generate HTML_
fragments and pages.
Template directives are specified as XML attributes prefixed with ``t-``,
for instance ``t-if`` for :ref:`reference/qweb/conditionals`, with elements
and other attributes being rendered directly.
To avoid element rendering, a placeholder element ``<t>`` is also available,
which executes its directive but doesn't generate any output in and of
itself::
<t t-if="condition">
<p>Test</p>
</t>
will result in::
<p>Test</p>
if ``condition`` is true, but::
<div t-if="condition">
<p>Test</p>
</div>
will result in::
<div>
<p>Test</p>
</div>
.. _reference/qweb/output:
Data output
===========
QWeb has a primary output directive which automatically HTML-escape its
content limiting XSS_ risks when displaying user-provided content: ``esc``.
``esc`` takes an expression, evaluates it and prints the content::
<p><t t-esc="value"/></p>
rendered with the value ``value`` set to ``42`` yields::
<p>42</p>
There is one other output directive ``raw`` which behaves the same as
respectively ``esc`` but *does not HTML-escape its output*. It can be useful
to display separately constructed markup (e.g. from functions) or already
sanitized user-provided markup.
.. _reference/qweb/conditionals:
Conditionals
============
QWeb has a conditional directive ``if``, which evaluates an expression given
as attribute value::
<div>
<t t-if="condition">
<p>ok</p>
</t>
</div>
The element is rendered if the condition is true::
<div>
<p>ok</p>
</div>
but if the condition is false it is removed from the result::
<div>
</div>
The conditional rendering applies to the bearer of the directive, which does
not have to be ``<t>``::
<div>
<p t-if="condition">ok</p>
</div>
will give the same results as the previous example.
Extra conditional branching directives ``t-elif`` and ``t-else`` are also
available::
<div>
<p t-if="user.birthday == today()">Happy birthday!</p>
<p t-elif="user.login == 'root'">Welcome master!</p>
<p t-else="">Welcome!</p>
</div>
.. _reference/qweb/loops:
Loops
=====
QWeb has an iteration directive ``foreach`` which take an expression returning
the collection to iterate on, and a second parameter ``t-as`` providing the
name to use for the "current item" of the iteration::
<t t-foreach="[1, 2, 3]" t-as="i">
<p><t t-esc="i"/></p>
</t>
will be rendered as::
<p>1</p>
<p>2</p>
<p>3</p>
Like conditions, ``foreach`` applies to the element bearing the directive's
attribute, and
::
<p t-foreach="[1, 2, 3]" t-as="i">
<t t-esc="i"/>
</p>
is equivalent to the previous example.
``foreach`` can iterate on an array (the current item will be the current
value) or a mapping (the current item will be the current key). Iterating on an
integer (equivalent to iterating on an array between 0 inclusive and the
provided integer exclusive) is still supported but deprecated.
In addition to the name passed via ``t-as``, ``foreach`` provides a few other
variables for various data points:
.. warning:: ``$as`` will be replaced by the name passed to ``t-as``
:samp:`{$as}_all` (deprecated)
the object being iterated over
.. note:: This variable is only available on JavaScript QWeb, not Python.
:samp:`{$as}_value`
the current iteration value, identical to ``$as`` for lists and integers,
but for mappings it provides the value (where ``$as`` provides the key)
:samp:`{$as}_index`
the current iteration index (the first item of the iteration has index 0)
:samp:`{$as}_size`
the size of the collection if it is available
:samp:`{$as}_first`
whether the current item is the first of the iteration (equivalent to
:samp:`{$as}_index == 0`)
:samp:`{$as}_last`
whether the current item is the last of the iteration (equivalent to
:samp:`{$as}_index + 1 == {$as}_size`), requires the iteratee's size be
available
:samp:`{$as}_parity` (deprecated)
either ``"even"`` or ``"odd"``, the parity of the current iteration round
:samp:`{$as}_even` (deprecated)
a boolean flag indicating that the current iteration round is on an even
index
:samp:`{$as}_odd` (deprecated)
a boolean flag indicating that the current iteration round is on an odd
index
These extra variables provided and all new variables created into the
``foreach`` are only available in the scope of the``foreach``. If the
variable exists outside the context of the ``foreach``, the value is copied
at the end of the foreach into the global context.
::
<t t-set="existing_variable" t-value="False"/>
<!-- existing_variable now False -->
<p t-foreach="[1, 2, 3]" t-as="i">
<t t-set="existing_variable" t-value="True"/>
<t t-set="new_variable" t-value="True"/>
<!-- existing_variable and new_variable now True -->
</p>
<!-- existing_variable always True -->
<!-- new_variable undefined -->
.. _reference/qweb/attributes:
attributes
==========
QWeb can compute attributes on-the-fly and set the result of the computation
on the output node. This is done via the ``t-att`` (attribute) directive which
exists in 3 different forms:
:samp:`t-att-{$name}`
an attribute called ``$name`` is created, the attribute value is evaluated
and the result is set as the attribute's value::
<div t-att-a="42"/>
will be rendered as::
<div a="42"></div>
:samp:`t-attf-{$name}`
same as previous, but the parameter is a :term:`format string`
instead of just an expression, often useful to mix literal and non-literal
string (e.g. classes)::
<t t-foreach="[1, 2, 3]" t-as="item">
<li t-attf-class="row {{ (item_index % 2 === 0) ? 'even' : 'odd' }}">
<t t-esc="item"/>
</li>
</t>
will be rendered as::
<li class="row even">1</li>
<li class="row odd">2</li>
<li class="row even">3</li>
:samp:`t-att=mapping`
if the parameter is a mapping, each (key, value) pair generates a new
attribute and its value::
<div t-att="{'a': 1, 'b': 2}"/>
will be rendered as::
<div a="1" b="2"></div>
:samp:`t-att=pair`
if the parameter is a pair (tuple or array of 2 element), the first
item of the pair is the name of the attribute and the second item is the
value::
<div t-att="['a', 'b']"/>
will be rendered as::
<div a="b"></div>
setting variables
=================
QWeb allows creating variables from within the template, to memoize a
computation (to use it multiple times), give a piece of data a clearer name,
...
This is done via the ``set`` directive, which takes the name of the variable
to create. The value to set can be provided in two ways:
* a ``t-value`` attribute containing an expression, and the result of its
evaluation will be set::
<t t-set="foo" t-value="2 + 1"/>
<t t-esc="foo"/>
will print ``3``
* if there is no ``t-value`` attribute, the node's body is rendered and set
as the variable's value::
<t t-set="foo">
<li>ok</li>
</t>
<t t-esc="foo"/>
will generate ``&lt;li&gt;ok&lt;/li&gt;`` (the content is escaped as we
used the ``esc`` directive)
.. note:: using the result of this operation is a significant use-case for
the ``raw`` directive.
.. _reference/qweb/sub-templates:
calling sub-templates
=====================
QWeb templates can be used for top-level rendering, but they can also be used
from within another template (to avoid duplication or give names to parts of
templates) using the ``t-call`` directive::
<t t-call="other-template"/>
This calls the named template with the execution context of the parent, if
``other_template`` is defined as::
<p><t t-value="var"/></p>
the call above will be rendered as ``<p/>`` (no content), but::
<t t-set="var" t-value="1"/>
<t t-call="other-template"/>
will be rendered as ``<p>1</p>``.
However this has the problem of being visible from outside the ``t-call``.
Alternatively, content set in the body of the ``call`` directive will be
evaluated *before* calling the sub-template, and can alter a local context::
<t t-call="other-template">
<t t-set="var" t-value="1"/>
</t>
<!-- "var" does not exist here -->
The body of the ``call`` directive can be arbitrarily complex (not just
``set`` directives), and its rendered form will be available within the called
template as a magical ``0`` variable::
<div>
This template was called with content:
<t t-raw="0"/>
</div>
being called thus::
<t t-call="other-template">
<em>content</em>
</t>
will result in::
<div>
This template was called with content:
<em>content</em>
</div>
Python
======
Exclusive directives
--------------------
Asset bundles
'''''''''''''
.. todo:: have fme write these up because I've no idea how they work
"smart records" fields formatting
'''''''''''''''''''''''''''''''''
The ``t-field`` directive can only be used when performing field access
(``a.b``) on a "smart" record (result of the ``browse`` method). It is able
to automatically format based on field type, and is integrated in the
website's rich text editing.
``t-options`` can be used to customize fields, the most common option
is ``widget``, other options are field- or widget-dependent.
Debugging
---------
``t-debug``
invokes a debugger using PDB's ``set_trace`` API. The parameter should
be the name of a module, on which a ``set_trace`` method is called::
<t t-debug="pdb"/>
is equivalent to ``importlib.import_module("pdb").set_trace()``
Helpers
-------
Request-based
'''''''''''''
Most Python-side uses of QWeb are in controllers (and during HTTP requests),
in which case templates stored in the database (as
:ref:`views <reference/views/qweb>`) can be trivially rendered by calling
:meth:`odoo.http.HttpRequest.render`:
.. code-block:: python
response = http.request.render('my-template', {
'context_value': 42
})
This automatically creates a :class:`~odoo.http.Response` object which can
be returned from the controller (or further customized to suit).
View-based
''''''''''
At a deeper level than the previous helper is the ``render`` method on
``ir.ui.view``:
.. py:method:: render(cr, uid, id[, values][, engine='ir.qweb][, context])
Renders a QWeb view/template by database id or :term:`external id`.
Templates are automatically loaded from ``ir.ui.view`` records.
Sets up a number of default values in the rendering context:
``request``
the current :class:`~odoo.http.WebRequest` object, if any
``debug``
whether the current request (if any) is in ``debug`` mode
:func:`quote_plus <werkzeug.urls.url_quote_plus>`
url-encoding utility function
:mod:`json`
the corresponding standard library module
:mod:`time`
the corresponding standard library module
:mod:`datetime`
the corresponding standard library module
`relativedelta <https://labix.org/python-dateutil#head-ba5ffd4df8111d1b83fc194b97ebecf837add454>`_
see module
``keep_query``
the ``keep_query`` helper function
:param values: context values to pass to QWeb for rendering
:param str engine: name of the Odoo model to use for rendering, can be
used to expand or customize QWeb locally (by creating
a "new" qweb based on ``ir.qweb`` with alterations)
.. _reference/qweb/javascript:
.. todo:: the members below are no longer relevant, section to rewrite
.. API
.. ---
.. It is also possible to use the ``ir.qweb`` model directly (and extend it, and
.. inherit from it):
.. .. automodule:: odoo.addons.base.ir.ir_qweb
.. :members: QWeb, QWebContext, FieldConverter, QwebWidget
Javascript
==========
Exclusive directives
--------------------
Defining templates
''''''''''''''''''
The ``t-name`` directive can only be placed at the top-level of a template
file (direct children to the document root)::
<templates>
<t t-name="template-name">
<!-- template code -->
</t>
</templates>
It takes no other parameter, but can be used with a ``<t>`` element or any
other. With a ``<t>`` element, the ``<t>`` should have a single child.
The template name is an arbitrary string, although when multiple templates
are related (e.g. called sub-templates) it is customary to use dot-separated
names to indicate hierarchical relationships.
Template inheritance
''''''''''''''''''''
Template inheritance is used to either:
- Alter existing templates in-place, e.g. to add information to templates
created by other modules.
- Create a new template from a given parent template
Template inheritance is performed via the use of two directives:
- ``t-inherit`` which is the name of the template to inherit from,
- ``t-inherit-mode`` which is the behaviour of the inheritance: it can either be
set to ``primary`` to create a new child template from the parented one or
to ``extension`` to alter the parent template in place.
An optional ``t-name`` directive can also be specified. It will be the name of
the newly created template if used in primary mode, else it will be added as a
comment on the transformed template to help retrace inheritances.
For the inheritance itself, the changes are done using xpaths directives.
See the XPATH_ documentation for the complete set of available instructions.
Primary inheritance (child template)::
<t t-name="child.template" t-inherit="base.template" t-inherit-mode="primary">
<xpath expr="//ul" position="inside">
<li>new element</li>
</xpath>
</t>
Extension inheritance (in-place transformation)::
<t t-inherit="base.template" t-inherit-mode="extension">
<xpath expr="//tr[1]" position="after">
<tr><td>new cell</td></tr>
</xpath>
</t>
Old inheritance mechanism (deprecated)
''''''''''''''''''''''''''''''''''''''
Template inheritance is performed via the ``t-extend`` directive which takes
the name of the template to alter as parameter.
The directive ``t-extend`` will act as a primary inheritance when combined with
``t-name`` and as an extension one when used alone.
In both cases the alteration is then performed with any number of ``t-jquery``
sub-directives::
<t t-extend="base.template">
<t t-jquery="ul" t-operation="append">
<li>new element</li>
</t>
</t>
The ``t-jquery`` directives takes a `CSS selector`_. This selector is used
on the extended template to select *context nodes* to which the specified
``t-operation`` is applied:
``append``
the node's body is appended at the end of the context node (after the
context node's last child)
``prepend``
the node's body is prepended to the context node (inserted before the
context node's first child)
``before``
the node's body is inserted right before the context node
``after``
the node's body is inserted right after the context node
``inner``
the node's body replaces the context node's children
``replace``
the node's body is used to replace the context node itself
``attributes``
the nodes's body should be any number of ``attribute`` elements,
each with a ``name`` attribute and some textual content, the named
attribute of the context node will be set to the specified value
(either replaced if it already existed or added if not)
No operation
if no ``t-operation`` is specified, the template body is interpreted as
javascript code and executed with the context node as ``this``
.. warning:: while much more powerful than other operations, this mode is
also much harder to debug and maintain, it is recommended to
avoid it
debugging
---------
The javascript QWeb implementation provides a few debugging hooks:
``t-log``
takes an expression parameter, evaluates the expression during rendering
and logs its result with ``console.log``::
<t t-set="foo" t-value="42"/>
<t t-log="foo"/>
will print ``42`` to the console
``t-debug``
triggers a debugger breakpoint during template rendering::
<t t-if="a_test">
<t t-debug="">
</t>
will stop execution if debugging is active (exact condition depend on the
browser and its development tools)
``t-js``
the node's body is javascript code executed during template rendering.
Takes a ``context`` parameter, which is the name under which the rendering
context will be available in the ``t-js``'s body::
<t t-set="foo" t-value="42"/>
<t t-js="ctx">
console.log("Foo is", ctx.foo);
</t>
Helpers
-------
.. js:attribute:: core.qweb
(core is the ``web.core`` module) An instance of :js:class:`QWeb2.Engine` with all module-defined template
files loaded, and references to standard helper objects ``_``
(underscore), ``_t`` (translation function) and JSON_.
:js:func:`core.qweb.render <QWeb2.Engine.render>` can be used to
easily render basic module templates
.. _reference/qweb/api:
API
---
.. js:class:: QWeb2.Engine
The QWeb "renderer", handles most of QWeb's logic (loading,
parsing, compiling and rendering templates).
Odoo Web instantiates one for the user in the core module, and
exports it to ``core.qweb``. It also loads all the template files
of the various modules into that QWeb instance.
A :js:class:`QWeb2.Engine` also serves as a "template namespace".
.. js:function:: QWeb2.Engine.render(template[, context])
Renders a previously loaded template to a String, using
``context`` (if provided) to find the variables accessed
during template rendering (e.g. strings to display).
:param String template: the name of the template to render
:param Object context: the basic namespace to use for template
rendering
:returns: String
The engine exposes an other method which may be useful in some
cases (e.g. if you need a separate template namespace with, in
Odoo Web, Kanban views get their own :js:class:`QWeb2.Engine`
instance so their templates don't collide with more general
"module" templates):
.. js:function:: QWeb2.Engine.add_template(templates)
Loads a template file (a collection of templates) in the QWeb
instance. The templates can be specified as:
An XML string
QWeb will attempt to parse it to an XML document then load
it.
A URL
QWeb will attempt to download the URL content, then load
the resulting XML string.
A ``Document`` or ``Node``
QWeb will traverse the first level of the document (the
child nodes of the provided root) and load any named
template or template override.
:type templates: String | Document | Node
A :js:class:`QWeb2.Engine` also exposes various attributes for
behavior customization:
.. js:attribute:: QWeb2.Engine.prefix
Prefix used to recognize directives during parsing. A string. By
default, ``t``.
.. js:attribute:: QWeb2.Engine.debug
Boolean flag putting the engine in "debug mode". Normally,
QWeb intercepts any error raised during template execution. In
debug mode, it leaves all exceptions go through without
intercepting them.
.. js:attribute:: QWeb2.Engine.jQuery
The jQuery instance used during template inheritance processing.
Defaults to ``window.jQuery``.
.. js:attribute:: QWeb2.Engine.preprocess_node
A ``Function``. If present, called before compiling each DOM
node to template code. In Odoo Web, this is used to
automatically translate text content and some attributes in
templates. Defaults to ``null``.
.. [#genshif] it is similar in that to Genshi_, although it does not use (and
has no support for) `XML namespaces`_
.. [#othertemplates] although it uses a few others, either for historical
reasons or because they remain better fits for the
use case. Odoo 9.0 still depends on Jinja_ and Mako_.
.. _templating:
https://en.wikipedia.org/wiki/Template_processor
.. _Jinja: http://jinja.pocoo.org
.. _Mako: https://www.makotemplates.org
.. _Genshi: https://genshi.edgewall.org
.. _XML namespaces: https://en.wikipedia.org/wiki/XML_namespace
.. _HTML: https://en.wikipedia.org/wiki/HTML
.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting
.. _JSON: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
.. _CSS selector: https://api.jquery.com/category/selectors/
.. _XPATH: https://developer.mozilla.org/en-US/docs/Web/XPath