summaryrefslogtreecommitdiff
path: root/doc/reference/javascript.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/reference/javascript.rst')
-rw-r--r--doc/reference/javascript.rst1412
1 files changed, 0 insertions, 1412 deletions
diff --git a/doc/reference/javascript.rst b/doc/reference/javascript.rst
deleted file mode 100644
index b2d08bb255b..00000000000
--- a/doc/reference/javascript.rst
+++ /dev/null
@@ -1,1412 +0,0 @@
-:banner: banners/javascript.jpg
-
-.. highlight:: javascript
-
-.. default-domain:: js
-
-==========
-Javascript
-==========
-
-.. automodule:: *
- :members:
-
-=======
-Widgets
-=======
-
-.. class:: Widget
-
- Exported in ``web.Widget``, the base class for all visual components.
- It corresponds to an MVC view, and provides a number of service to
- simplify handling of a section of a page:
-
- * Handles parent/child relationships between widgets
- * Provides extensive lifecycle management with safety features (e.g.
- automatically destroying children widgets during the destruction of a
- parent)
- * Automatic rendering with :ref:`qweb <reference/qweb>`
- * Backbone-compatible shortcuts
-
-DOM Root
---------
-
-A :class:`~Widget` is responsible for a section of the page
-materialized by the DOM root of the widget.
-
-A widget's DOM root is available via two attributes:
-
-.. attribute:: Widget.el
-
- raw DOM element set as root to the widget
-
-.. attribute:: Widget.$el
-
- jQuery wrapper around :attr:`~Widget.el`
-
-There are two main ways to define and generate this DOM root:
-
-.. attribute:: Widget.template
-
- Should be set to the name of a :ref:`QWeb template <reference/qweb>`.
- If set, the template will be rendered after the widget has been
- initialized but before it has been started. The root element generated by
- the template will be set as the DOM root of the widget.
-
-.. attribute:: Widget.tagName
-
- Used if the widget has no template defined. Defaults to ``div``,
- will be used as the tag name to create the DOM element to set as
- the widget's DOM root. It is possible to further customize this
- generated DOM root with the following attributes:
-
- .. attribute:: Widget.id
-
- Used to generate an ``id`` attribute on the generated DOM
- root.
-
- .. attribute:: Widget.className
-
- Used to generate a ``class`` attribute on the generated DOM root.
-
- .. attribute:: Widget.attributes
-
- Mapping (object literal) of attribute names to attribute
- values. Each of these k:v pairs will be set as a DOM attribute
- on the generated DOM root.
-
- None of these is used in case a template is specified on the widget.
-
-The DOM root can also be defined programmatically by overridding
-
-.. function:: Widget.renderElement
-
- Renders the widget's DOM root and sets it. The default
- implementation will render a set template or generate an element
- as described above, and will call
- :func:`~Widget.setElement` on the result.
-
- Any override to :func:`~Widget.renderElement` which
- does not call its ``_super`` **must** call
- :func:`~Widget.setElement` with whatever it
- generated or the widget's behavior is undefined.
-
- .. note::
-
- The default :func:`~Widget.renderElement` can
- be called repeatedly, it will *replace* the previous DOM root
- (using ``replaceWith``). However, this requires that the
- widget correctly sets and unsets its events (and children
- widgets). Generally, :func:`~Widget.renderElement` should
- not be called repeatedly unless the widget advertizes this feature.
-
-Using a widget
-''''''''''''''
-
-A widget's lifecycle has 3 main phases:
-
-* creation and initialization of the widget instance
-
- .. function:: Widget.init(parent)
-
- initialization method of widgets, synchronous, can be overridden to
- take more parameters from the widget's creator/parent
-
- :param parent: the new widget's parent, used to handle automatic
- destruction and event propagation. Can be ``null`` for
- the widget to have no parent.
- :type parent: :class:`~Widget`
-
-* DOM injection and startup, this is done by calling one of:
-
- .. function:: Widget.appendTo(element)
-
- Renders the widget and inserts it as the last child of the target, uses
- `.appendTo()`_
-
- .. function:: Widget.prependTo(element)
-
- Renders the widget and inserts it as the first child of the target, uses
- `.prependTo()`_
-
- .. function:: Widget.insertAfter(element)
-
- Renders the widget and inserts it as the preceding sibling of the target,
- uses `.insertAfter()`_
-
- .. function:: Widget.insertBefore(element)
-
- Renders the widget and inserts it as the following sibling of the target,
- uses `.insertBefore()`_
-
- All of these methods accept whatever the corresponding jQuery method accepts
- (CSS selectors, DOM nodes or jQuery objects). They all return a deferred_
- and are charged with three tasks:
-
- * rendering the widget's root element via
- :func:`~Widget.renderElement`
- * inserting the widget's root element in the DOM using whichever jQuery
- method they match
- * starting the widget, and returning the result of starting it
-
- .. function:: Widget.start()
-
- asynchronous startup of the widget once it's been injected in the DOM,
- generally used to perform asynchronous RPC calls to fetch whatever
- remote data is necessary for the widget to do its work.
-
- Must return a deferred_ to indicate when its work is done.
-
- A widget is *not guaranteed* to work correctly until its
- :func:`~Widget.start` method has finished executing. The
- widget's parent/creator must wait for a widget to be fully started
- before interacting with it
-
- :returns: deferred_ object
-
-* widget destruction and cleanup
-
- .. function:: Widget.destroy()
-
- destroys the widget's children, unbinds its events and removes its root
- from the DOM. Automatically called when the widget's parent is destroyed,
- must be called explicitly if the widget has no parents or if it is
- removed but its parent remains.
-
- A widget being destroyed is automatically unlinked from its parent.
-
-Related to widget destruction is an important utility method:
-
-.. function:: Widget.alive(deferred[, reject=false])
-
- A significant issue with RPC and destruction is that an RPC call may take
- a long time to execute and return while a widget is being destroyed or
- after it has been destroyed, trying to execute its operations on a widget
- in a broken/invalid state.
-
- This is a frequent source of errors or strange behaviors.
-
- :func:`~Widget.alive` can be used to wrap an RPC call,
- ensuring that whatever operations should be executed when the call ends
- are only executed if the widget is still alive::
-
- this.alive(this.model.query().all()).then(function (records) {
- // would break if executed after the widget is destroyed, wrapping
- // rpc in alive() prevents execution
- _.each(records, function (record) {
- self.$el.append(self.format(record));
- });
- });
-
- :param deferred: a deferred_ object to wrap
- :param reject: by default, if the RPC call returns after the widget has
- been destroyed the returned deferred_ is left in limbo
- (neither resolved nor rejected). If ``reject`` is set to
- ``true``, the deferred_ will be rejected instead.
- :returns: deferred_ object
-
-.. function:: Widget.isDestroyed()
-
- :returns: ``true`` if the widget is being or has been destroyed, ``false``
- otherwise
-
-Accessing DOM content
-'''''''''''''''''''''
-
-Because a widget is only responsible for the content below its DOM root, there
- is a shortcut for selecting sub-sections of a widget's DOM:
-
-.. function:: Widget.$(selector)
-
- Applies the CSS selector specified as parameter to the widget's
- DOM root::
-
- this.$(selector);
-
- is functionally identical to::
-
- this.$el.find(selector);
-
- :param String selector: CSS selector
- :returns: jQuery object
-
- .. note:: this helper method is similar to ``Backbone.View.$``
-
-Resetting the DOM root
-''''''''''''''''''''''
-
-.. function:: Widget.setElement(element)
-
- Re-sets the widget's DOM root to the provided element, also
- handles re-setting the various aliases of the DOM root as well as
- unsetting and re-setting delegated events.
-
- :param Element element: a DOM element or jQuery object to set as
- the widget's DOM root
-
- .. note:: should be mostly compatible with `Backbone's
- setElement`_
-
-DOM events handling
--------------------
-
-A widget will generally need to respond to user action within its
-section of the page. This entails binding events to DOM elements.
-
-To this end, :class:`~Widget` provides a shortcut:
-
-.. attribute:: Widget.events
-
- Events are a mapping of an event selector (an event name and an optional
- CSS selector separated by a space) to a callback. The callback can
- be the name of a widget's method or a function object. In either case, the
- ``this`` will be set to the widget::
-
- events: {
- 'click p.oe_some_class a': 'some_method',
- 'change input': function (e) {
- e.stopPropagation();
- }
- },
-
- The selector is used for jQuery's `event delegation`_, the
- callback will only be triggered for descendants of the DOM root
- matching the selector\ [#eventsdelegation]_. If the selector is left out
- (only an event name is specified), the event will be set directly on the
- widget's DOM root.
-
-.. function:: Widget.delegateEvents
-
- This method is in charge of binding :attr:`~Widget.events` to the
- DOM. It is automatically called after setting the widget's DOM root.
-
- It can be overridden to set up more complex events than the
- :attr:`~Widget.events` map allows, but the parent should always be
- called (or :attr:`~Widget.events` won't be handled correctly).
-
-.. function:: Widget.undelegateEvents
-
- This method is in charge of unbinding :attr:`~Widget.events` from
- the DOM root when the widget is destroyed or the DOM root is reset, in
- order to avoid leaving "phantom" events.
-
- It should be overridden to un-set any event set in an override of
- :func:`~Widget.delegateEvents`.
-
-.. note:: this behavior should be compatible with `Backbone's
- delegateEvents`_, apart from not accepting any argument.
-
-Subclassing Widget
-------------------
-
-:class:`~Widget` is subclassed in the standard manner (via the
-:func:`~Class.extend` method), and provides a number of
-abstract properties and concrete methods (which you may or may not want to
-override). Creating a subclass looks like this::
-
- var MyWidget = Widget.extend({
- // QWeb template to use when rendering the object
- template: "MyQWebTemplate",
- events: {
- // events binding example
- 'click .my-button': 'handle_click',
- },
-
- init: function(parent) {
- this._super(parent);
- // insert code to execute before rendering, for object
- // initialization
- },
- start: function() {
- var sup = this._super();
- // post-rendering initialization code, at this point
-
- // allows multiplexing deferred objects
- return $.when(
- // propagate asynchronous signal from parent class
- sup,
- // return own's asynchronous signal
- this.rpc(/* … */))
- }
- });
-
-The new class can then be used in the following manner::
-
- // Create the instance
- var my_widget = new MyWidget(this);
- // Render and insert into DOM
- my_widget.appendTo(".some-div");
-
-After these two lines have executed (and any promise returned by
-:func:`~Widget.appendTo` has been resolved if needed), the widget is
-ready to be used.
-
-.. note:: the insertion methods will start the widget themselves, and will
- return the result of :func:`~Widget.start()`.
-
- If for some reason you do not want to call these methods, you will
- have to first call :func:`~Widget.render()` on the
- widget, then insert it into your DOM and start it.
-
-If the widget is not needed anymore (because it's transient), simply terminate
-it::
-
- my_widget.destroy();
-
-will unbind all DOM events, remove the widget's content from the DOM and
-destroy all widget data.
-
-Development Guidelines
-----------------------
-
-* Identifiers (``id`` attribute) should be avoided. In generic applications
- and modules, ``id`` limits the re-usability of components and tends to make
- code more brittle. Most of the time, they can be replaced with nothing,
- classes or keeping a reference to a DOM node or jQuery element.
-
- If an ``id`` is absolutely necessary (because a third-party library requires
- one), the id should be partially generated using ``_.uniqueId()`` e.g.::
-
- this.id = _.uniqueId('my-widget-')
-* Avoid predictable/common CSS class names. Class names such as "content" or
- "navigation" might match the desired meaning/semantics, but it is likely an
- other developer will have the same need, creating a naming conflict and
- unintended behavior. Generic class names should be prefixed with e.g. the
- name of the component they belong to (creating "informal" namespaces, much
- as in C or Objective-C).
-* Global selectors should be avoided. Because a component may be used several
- times in a single page (an example in Odoo is dashboards), queries should be
- restricted to a given component's scope. Unfiltered selections such as
- ``$(selector)`` or ``document.querySelectorAll(selector)`` will generally
- lead to unintended or incorrect behavior. Odoo Web's
- :class:`~Widget` has an attribute providing its DOM root
- (:attr:`~Widget.$el`), and a shortcut to select nodes directly
- (:func:`~Widget.$`).
-* More generally, never assume your components own or controls anything beyond
- its own personal :attr:`~Widget.$el`
-* html templating/rendering should use QWeb unless absolutely trivial.
-* All interactive components (components displaying information to the screen
- or intercepting DOM events) must inherit from :class:`~Widget`
- and correctly implement and use its API and life cycle.
-
-.. _.appendTo():
- http://api.jquery.com/appendTo/
-
-.. _.prependTo():
- http://api.jquery.com/prependTo/
-
-.. _.insertAfter():
- http://api.jquery.com/insertAfter/
-
-.. _.insertBefore():
- http://api.jquery.com/insertBefore/
-
-.. _event delegation:
- http://api.jquery.com/delegate/
-
-.. _Backbone's setElement:
- http://backbonejs.org/#View-setElement
-
-.. _Backbone's delegateEvents:
- http://backbonejs.org/#View-delegateEvents
-
-.. _deferred: http://api.jquery.com/category/deferred-object/
-
-RPC
-===
-
-To display and interact with data, calls to the Odoo server are necessary.
-This is performed using :abbr:`RPC <Remote Procedure Call>`.
-
-Odoo Web provides two primary APIs to handle this: a low-level
-JSON-RPC based API communicating with the Python section of Odoo
-Web (and of your module, if you have a Python part) and a high-level
-API above that allowing your code to talk directly to high-level Odoo models.
-
-All networking APIs are :ref:`asynchronous <reference/async>`. As a result,
-all of them will return Deferred_ objects (whether they resolve those with
-values or not). Understanding how those work before before moving on is
-probably necessary.
-
-High-level API: calling into Odoo models
--------------------------------------------
-
-Access to Odoo object methods (made available through XML-RPC from the server)
-is done via :class:`Model`. It maps onto the Odoo server objects via two primary
-methods, :func:`~Model.call` (exported in ``web.Model``) and :func:`~Model.query`
-(exported in ``web.DataModel``, only available in the backend client).
-
-:func:`~Model.call` is a direct mapping to the corresponding method of
-the Odoo server object. Its usage is similar to that of the Odoo Model API,
-with three differences:
-
-* The interface is :ref:`asynchronous <reference/async>`, so instead of
- returning results directly RPC method calls will return
- Deferred_ instances, which will themselves resolve to the
- result of the matching RPC call.
-
-* Because ECMAScript 3/Javascript 1.5 doesnt feature any equivalent to
- ``__getattr__`` or ``method_missing``, there needs to be an explicit
- method to dispatch RPC methods.
-
-* No notion of pooler, the model proxy is instantiated where needed,
- not fetched from an other (somewhat global) object::
-
- var Users = new Model('res.users');
-
- Users.call('change_password', ['oldpassword', 'newpassword'],
- {context: some_context}).then(function (result) {
- // do something with change_password result
- });
-
-:func:`~Model.query` is a shortcut for a builder-style
-interface to searches (``search`` + ``read`` in Odoo RPC terms). It
-returns a :class:`~odoo.web.Query` object which is immutable but
-allows building new :class:`~odoo.web.Query` instances from the
-first one, adding new properties or modifiying the parent object's::
-
- Users.query(['name', 'login', 'user_email', 'signature'])
- .filter([['active', '=', true], ['company_id', '=', main_company]])
- .limit(15)
- .all().then(function (users) {
- // do work with users records
- });
-
-The query is only actually performed when calling one of the query
-serialization methods, :func:`~odoo.web.Query.all` and
-:func:`~odoo.web.Query.first`. These methods will perform a new
-RPC call every time they are called.
-
-For that reason, it's actually possible to keep "intermediate" queries
-around and use them differently/add new specifications on them.
-
-.. class:: Model(name)
-
- .. attribute:: Model.name
-
- name of the model this object is bound to
-
- .. function:: Model.call(method[, args][, kwargs])
-
- Calls the ``method`` method of the current model, with the
- provided positional and keyword arguments.
-
- :param String method: method to call over rpc on the
- :attr:`~Model.name`
- :param Array<> args: positional arguments to pass to the
- method, optional
- :param Object<> kwargs: keyword arguments to pass to the
- method, optional
- :rtype: Deferred<>
-
- .. function:: Model.query(fields)
-
- :param Array<String> fields: list of fields to fetch during
- the search
- :returns: a :class:`~odoo.web.Query` object
- representing the search to perform
-
-.. class:: odoo.web.Query(fields)
-
- The first set of methods is the "fetching" methods. They perform
- RPC queries using the internal data of the object they're called
- on.
-
- .. function:: odoo.web.Query.all()
-
- Fetches the result of the current :class:`~odoo.web.Query` object's
- search.
-
- :rtype: Deferred<Array<>>
-
- .. function:: odoo.web.Query.first()
-
- Fetches the **first** result of the current
- :class:`~odoo.web.Query`, or ``null`` if the current
- :class:`~odoo.web.Query` does have any result.
-
- :rtype: Deferred<Object | null>
-
- .. function:: odoo.web.Query.count()
-
- Fetches the number of records the current
- :class:`~odoo.web.Query` would retrieve.
-
- :rtype: Deferred<Number>
-
- .. function:: odoo.web.Query.group_by(grouping...)
-
- Fetches the groups for the query, using the first specified
- grouping parameter
-
- :param Array<String> grouping: Lists the levels of grouping
- asked of the server. Grouping
- can actually be an array or
- varargs.
- :rtype: Deferred<Array<odoo.web.QueryGroup>> | null
-
- The second set of methods is the "mutator" methods, they create a
- **new** :class:`~odoo.web.Query` object with the relevant
- (internal) attribute either augmented or replaced.
-
- .. function:: odoo.web.Query.context(ctx)
-
- Adds the provided ``ctx`` to the query, on top of any existing
- context
-
- .. function:: odoo.web.Query.filter(domain)
-
- Adds the provided domain to the query, this domain is
- ``AND``-ed to the existing query domain.
-
- .. function:: opeenrp.web.Query.offset(offset)
-
- Sets the provided offset on the query. The new offset
- *replaces* the old one.
-
- .. function:: odoo.web.Query.limit(limit)
-
- Sets the provided limit on the query. The new limit *replaces*
- the old one.
-
- .. function:: odoo.web.Query.order_by(fields…)
-
- Overrides the model's natural order with the provided field
- specifications. Behaves much like Django's :py:meth:`QuerySet.order_by
- <django.db.models.query.QuerySet.order_by>`:
-
- * Takes 1..n field names, in order of most to least importance
- (the first field is the first sorting key). Fields are
- provided as strings.
-
- * A field specifies an ascending order, unless it is prefixed
- with the minus sign "``-``" in which case the field is used
- in the descending order
-
- Divergences from Django's sorting include a lack of random sort
- (``?`` field) and the inability to "drill down" into relations
- for sorting.
-
-Aggregation (grouping)
-''''''''''''''''''''''
-
-Odoo has powerful grouping capacities, but they are kind-of strange
-in that they're recursive, and level n+1 relies on data provided
-directly by the grouping at level n. As a result, while
-:py:meth:`odoo.models.Model.read_group` works it's not a very intuitive
-API.
-
-Odoo Web eschews direct calls to :py:meth:`~odoo.models.Model.read_group`
-in favor of calling a method of :class:`~odoo.web.Query`, :py:meth:`much
-in the way it is done in SQLAlchemy <sqlalchemy.orm.query.Query.group_by>`
-[#terminal]_::
-
- some_query.group_by(['field1', 'field2']).then(function (groups) {
- // do things with the fetched groups
- });
-
-This method is asynchronous when provided with 1..n fields (to group
-on) as argument, but it can also be called without any field (empty
-fields collection or nothing at all). In this case, instead of
-returning a Deferred object it will return ``null``.
-
-When grouping criterion come from a third-party and may or may not
-list fields (e.g. could be an empty list), this provides two ways to
-test the presence of actual subgroups (versus the need to perform a
-regular query for records):
-
-* A check on ``group_by``'s result and two completely separate code
- paths::
-
- var groups;
- if (groups = some_query.group_by(gby)) {
- groups.then(function (gs) {
- // groups
- });
- }
- // no groups
-
-* Or a more coherent code path using :func:`when`'s ability to
- coerce values into deferreds::
-
- $.when(some_query.group_by(gby)).then(function (groups) {
- if (!groups) {
- // No grouping
- } else {
- // grouping, even if there are no groups (groups
- // itself could be an empty array)
- }
- });
-
-The result of a (successful) :func:`~odoo.web.Query.group_by` is
-an array of :class:`~odoo.web.QueryGroup`:
-
-.. class:: odoo.web.QueryGroup
-
- .. function:: odoo.web.QueryGroup.get(key)
-
- returns the group's attribute ``key``. Known attributes are:
-
- ``grouped_on``
- which grouping field resulted from this group
- ``value``
- ``grouped_on``'s value for this group
- ``length``
- the number of records in the group
- ``aggregates``
- a {field: value} mapping of aggregations for the group
-
- .. function:: odoo.web.QueryGroup.query([fields...])
-
- equivalent to :func:`Model.query` but pre-filtered to
- only include the records within this group. Returns a
- :class:`~odoo.web.Query` which can be further manipulated as
- usual.
-
- .. function:: odoo.web.QueryGroup.subgroups()
-
- returns a deferred to an array of :class:`~odoo.web.QueryGroup`
- below this one
-
-Low-level API: RPC calls to Python side
----------------------------------------
-
-While the previous section is great for calling core OpenERP code
-(models code), it does not work if you want to call the Python side of
-Odoo Web.
-
-For this, a lower-level API exists on on
-:class:`~Session` objects (the class is exported in ``web.Session``, but
-an instance isusually available through ``web.session``): the ``rpc`` method.
-
-This method simply takes an absolute path (the absolute URL of the JSON
-:ref:`route <reference/http/routing>` to call) and a mapping of attributes to
-values (passed as keyword arguments to the Python method). This function
-fetches the return value of the Python methods, converted to JSON.
-
-For instance, to call the ``resequence`` of the
-:class:`~web.controllers.main.DataSet` controller::
-
- session.rpc('/web/dataset/resequence', {
- model: some_model,
- ids: array_of_ids,
- offset: 42
- }).then(function (result) {
- // resequence didn't error out
- }, function () {
- // an error occured during during call
- });
-
-.. _reference/javascript/client:
-
-Web Client
-==========
-
-Javascript module system overview
----------------------------------
-
-A new module system (inspired from requirejs) has now been deployed.
-It has many advantages over the Odoo version 8 system.
-
-* loading order: dependencies are guaranteed to be loaded first, even if
- files are not loaded in the correct order in the bundle files.
-* easier to split a file into smaller logical units.
-* no global variables: easier to reason.
-* it is possible to examine every dependencies and dependants. This
- makes refactoring much simpler, and less risky.
-
-It has also some disadvantages:
-
-* files are required to use the module system if they want to interact
- with odoo, since the various objects are only available in the module
- system, and not in global variables
-* circular dependencies are not supported. It makes sense, but it means
- that one needs to be careful.
-
-This is obviously a very large change and will require everyone to
-adopt new habits. For example, the variable odoo does not exist
-anymore. The new way of doing things is to import explicitely the module
-you need, and declaring explicitely the objects you export. Here is a
-simple example::
-
- odoo.define('addon_name.service', function (require) {
- var utils = require('web.utils');
- var Model = require('web.Model');
-
- // do things with utils and Model
- var something_useful = 15;
- return {
- something_useful: something_useful,
- };
- });
-
-This snippet shows a module named ``addon_name.service``. It is defined
-with the ``odoo.define`` function. ``odoo.define`` takes a name and a
-function for arguments:
-
-* The name is the concatenation of the name of the addon it is defined in
- and a name describing its purpose.
-* The function is the place where the javascript module is actually
- defined. It takes a function ``require`` as first argument, and
- returns something (or not, depending if it needs to export something).
- The ``require`` function is used to get a handle on the dependencies.
- In this case, it gives a handle on two javascript modules from the
- ``web`` addon, namely ``web.utils`` and ``web.Model``.
-
-The idea is that you define what you need to import (by using the
-``require`` function) and declare what you export (by returning
-something). The web client will then make sure that your code is loaded
-properly.
-
-Modules are contained in a file, but a file can define several modules
-(however, it is better to keep them in separate files).
-
-Each module can return a deferred. In that case, the module is marked as loaded
-only when the deferred is resolved, and its value is equal to the resolved value.
-The module can be rejected (unloaded). This will be logged in the console as info.
-
-* ``Missing dependencies``:
- These modules do not appear in the page. It is possible that the JavaScript
- file is not in the page or that the module name is wrong
-* ``Failed modules``:
- A javascript error is detected
-* ``Rejected modules``:
- The module returns a rejected deferred. It (and its dependent modules) is not
- loaded.
-* ``Rejected linked modules``:
- Modules who depend on a rejected module
-* ``Non loaded modules``:
- Modules who depend on a missing or a failed module
-
-
-Web client structure
---------------------
-
-The web client files have been refactored into smaller and simpler files.
-Here is a description of the current file structure:
-
-* the ``framework/`` folder contains all basic low level modules. The
- modules here are supposed to be generic. Some of them are:
-
- * ``web.ajax`` implements rpc calls
- * ``web.core`` is a generic modules. It exports various useful
- objects and functions, such as ``qweb``, ``_t`` or the main bus.
- * ``web.Widget`` contains the widget class
- * ``web.Model`` is an abstraction over ``web.ajax`` to make
- calls to the server model methods
- * ``web.session`` is the former ``odoo.session``
- * ``web.utils`` for useful code snippets
- * ``web.time`` for every time-related generic functions
-* the ``views/`` folder contains all view definitions
-* ``widgets/`` is for standalone widgets
-
-The ``js/`` folder also contains some important files:
-
-* ``action_manager.js`` is the ActionManager class
-* ``boot.js`` is the file actually implementing the module system
-* ``menu.js`` is the definition of the top menu
-* ``web_client.js`` is for the root widget WebClient
-* ``view_manager.js`` contains the ViewManager
-
-The two other files are ``tour.js`` for the tours and ``compatibility.js``.
-The latter file is a compatibility layer bridging the old system to the
-new module system. This is where every module names are exported to the
-global variable ``odoo``. In theory, our addons should work without
-ever using the variable ``odoo``, and the compatibility module can be
-disabled safely.
-
-Javascript conventions
-----------------------
-
-Here are some basic conventions for the javascript code:
-
-* declare all your dependencies at the top of the module. Also, they
- should be sorted alphabetically by module name. This makes it
- easier to understand your module.
-* declare all exports at the end.
-* add the ``use strict`` statement at the beginning of every module
-* always name your module properly: ``addon_name.description``.
-* use capital letters for classes (for example, ``ActionManager`` is
- defined in the module ``web.ActionManager``), and lowercase for
- everything else (for example, ``ajax`` is defined in ``web.ajax``).
-* declare one module per file
-
-
-
-
-
-Testing in Odoo Web Client
-==========================
-
-Javascript Unit Testing
------------------------
-
-Odoo Web includes means to unit-test both the core code of
-Odoo Web and your own javascript modules. On the javascript side,
-unit-testing is based on QUnit_ with a number of helpers and
-extensions for better integration with Odoo.
-
-To see what the runner looks like, find (or start) an Odoo server
-with the web client enabled, and navigate to ``/web/tests``
-This will show the runner selector, which lists all modules with javascript
-unit tests, and allows starting any of them (or all javascript tests in all
-modules at once).
-
-.. image:: ./images/runner.png
- :align: center
-
-Clicking any runner button will launch the corresponding tests in the
-bundled QUnit_ runner:
-
-.. image:: ./images/tests.png
- :align: center
-
-Writing a test case
--------------------
-
-The first step is to list the test file(s). This is done through the
-``test`` key of the Odoo manifest, by adding javascript files to it:
-
-.. code-block:: python
-
- {
- 'name': "Demonstration of web/javascript tests",
- 'category': 'Hidden',
- 'depends': ['web'],
- 'test': ['static/tests/demo.js'],
- }
-
-and to create the corresponding test file(s)
-
-.. note::
-
- Test files which do not exist will be ignored, if all test files
- of a module are ignored (can not be found), the test runner will
- consider that the module has no javascript tests.
-
-After that, refreshing the runner selector will display the new module
-and allow running all of its (0 so far) tests:
-
-.. image:: ./images/runner2.png
- :align: center
-
-The next step is to create a test case::
-
- odoo.testing.section('basic section', function (test) {
- test('my first test', function () {
- ok(false, "this test has run");
- });
- });
-
-All testing helpers and structures live in the ``odoo.testing``
-module. Odoo tests live in a :func:`~odoo.testing.section`,
-which is itself part of a module. The first argument to a section is
-the name of the section, the second one is the section body.
-
-:func:`test <odoo.testing.case>`, provided by the
-:func:`~odoo.testing.section` to the callback, is used to
-register a given test case which will be run whenever the test runner
-actually does its job. Odoo Web test case use standard `QUnit
-assertions`_ within them.
-
-Launching the test runner at this point will run the test and display
-the corresponding assertion message, with red colors indicating the
-test failed:
-
-.. image:: ./images/tests2.png
- :align: center
-
-Fixing the test (by replacing ``false`` to ``true`` in the assertion)
-will make it pass:
-
-.. image:: ./images/tests3.png
- :align: center
-
-Assertions
-----------
-
-As noted above, Odoo Web's tests use `qunit assertions`_. They are
-available globally (so they can just be called without references to
-anything). The following list is available:
-
-.. function:: ok(state[, message])
-
- checks that ``state`` is truthy (in the javascript sense)
-
-.. function:: strictEqual(actual, expected[, message])
-
- checks that the actual (produced by a method being tested) and
- expected values are identical (roughly equivalent to ``ok(actual
- === expected, message)``)
-
-.. function:: notStrictEqual(actual, expected[, message])
-
- checks that the actual and expected values are *not* identical
- (roughly equivalent to ``ok(actual !== expected, message)``)
-
-.. function:: deepEqual(actual, expected[, message])
-
- deep comparison between actual and expected: recurse into
- containers (objects and arrays) to ensure that they have the same
- keys/number of elements, and the values match.
-
-.. function:: notDeepEqual(actual, expected[, message])
-
- inverse operation to :func:`deepEqual`
-
-.. function:: throws(block[, expected][, message])
-
- checks that, when called, the ``block`` throws an
- error. Optionally validates that error against ``expected``.
-
- :param Function block:
- :param expected: if a regexp, checks that the thrown error's
- message matches the regular expression. If an
- error type, checks that the thrown error is of
- that type.
- :type expected: Error | RegExp
-
-.. function:: equal(actual, expected[, message])
-
- checks that ``actual`` and ``expected`` are loosely equal, using
- the ``==`` operator and its coercion rules.
-
-.. function:: notEqual(actual, expected[, message])
-
- inverse operation to :func:`equal`
-
-Getting an Odoo instance
-------------------------
-
-The Odoo instance is the base through which most Odoo Web
-modules behaviors (functions, objects, …) are accessed. As a result,
-the test framework automatically builds one, and loads the module
-being tested and all of its dependencies inside it. This new instance
-is provided as the first positional parameter to your test
-cases. Let's observe by adding javascript code (not test code) to the
-test module:
-
-.. code-block:: python
-
- {
- 'name': "Demonstration of web/javascript tests",
- 'category': 'Hidden',
- 'depends': ['web'],
- 'js': ['static/src/js/demo.js'],
- 'test': ['static/tests/demo.js'],
- }
-
-::
-
- // src/js/demo.js
- odoo.web_tests_demo = function (instance) {
- instance.web_tests_demo = {
- value_true: true,
- SomeType: instance.web.Class.extend({
- init: function (value) {
- this.value = value;
- }
- })
- };
- };
-
-and then adding a new test case, which simply checks that the
-``instance`` contains all the expected stuff we created in the
-module::
-
- // test/demo.js
- test('module content', function (instance) {
- ok(instance.web_tests_demo.value_true, "should have a true value");
- var type_instance = new instance.web_tests_demo.SomeType(42);
- strictEqual(type_instance.value, 42, "should have provided value");
- });
-
-DOM Scratchpad
---------------
-
-As in the wider client, arbitrarily accessing document content is
-strongly discouraged during tests. But DOM access is still needed to
-e.g. fully initialize :class:`widgets <~odoo.Widget>` before
-testing them.
-
-Thus, a test case gets a DOM scratchpad as its second positional
-parameter, in a jQuery instance. That scratchpad is fully cleaned up
-before each test, and as long as it doesn't do anything outside the
-scratchpad your code can do whatever it wants::
-
- // test/demo.js
- test('DOM content', function (instance, $scratchpad) {
- $scratchpad.html('<div><span class="foo bar">ok</span></div>');
- ok($scratchpad.find('span').hasClass('foo'),
- "should have provided class");
- });
- test('clean scratchpad', function (instance, $scratchpad) {
- ok(!$scratchpad.children().length, "should have no content");
- ok(!$scratchpad.text(), "should have no text");
- });
-
-.. note::
-
- The top-level element of the scratchpad is not cleaned up, test
- cases can add text or DOM children but shoud not alter
- ``$scratchpad`` itself.
-
-Loading templates
------------------
-
-To avoid the corresponding processing costs, by default templates are
-not loaded into QWeb. If you need to render e.g. widgets making use of
-QWeb templates, you can request their loading through the
-:attr:`~TestOptions.templates` option to the :func:`test case
-function <odoo.testing.case>`.
-
-This will automatically load all relevant templates in the instance's
-qweb before running the test case:
-
-.. code-block:: python
-
- {
- 'name': "Demonstration of web/javascript tests",
- 'category': 'Hidden',
- 'depends': ['web'],
- 'js': ['static/src/js/demo.js'],
- 'test': ['static/tests/demo.js'],
- 'qweb': ['static/src/xml/demo.xml'],
- }
-
-.. code-block:: xml
-
- <!-- src/xml/demo.xml -->
- <templates id="template" xml:space="preserve">
- <t t-name="DemoTemplate">
- <t t-foreach="5" t-as="value">
- <p><t t-esc="value"/></p>
- </t>
- </t>
- </templates>
-
-::
-
- // test/demo.js
- test('templates', {templates: true}, function (instance) {
- var s = instance.web.qweb.render('DemoTemplate');
- var texts = $(s).find('p').map(function () {
- return $(this).text();
- }).get();
-
- deepEqual(texts, ['0', '1', '2', '3', '4']);
- });
-
-Asynchronous cases
-------------------
-
-The test case examples so far are all synchronous, they execute from
-the first to the last line and once the last line has executed the
-test is done. But the web client is full of :ref:`asynchronous code
-<reference/async>`, and thus test cases need to be async-aware.
-
-This is done by returning a :class:`deferred <Deferred>` from the
-case callback::
-
- // test/demo.js
- test('asynchronous', {
- asserts: 1
- }, function () {
- var d = $.Deferred();
- setTimeout(function () {
- ok(true);
- d.resolve();
- }, 100);
- return d;
- });
-
-This example also uses the :class:`options parameter <TestOptions>`
-to specify the number of assertions the case should expect, if less or
-more assertions are specified the case will count as failed.
-
-Asynchronous test cases *must* specify the number of assertions they
-will run. This allows more easily catching situations where e.g. the
-test architecture was not warned about asynchronous operations.
-
-.. note::
-
- Asynchronous test cases also have a 2 seconds timeout: if the test
- does not finish within 2 seconds, it will be considered
- failed. This pretty much always means the test will not
- resolve. This timeout *only* applies to the test itself, not to
- the setup and teardown processes.
-
-.. note::
-
- If the returned deferred is rejected, the test will be failed
- unless :attr:`~TestOptions.fail_on_rejection` is set to
- ``false``.
-
-RPC
----
-
-An important subset of asynchronous test cases is test cases which
-need to perform (and chain, to an extent) RPC calls.
-
-.. note::
-
- Because they are a subset of asynchronous cases, RPC cases must
- also provide a valid :attr:`assertions count
- <TestOptions.asserts>`.
-
-To enable mock RPC, set the :attr:`rpc option <TestOptions.rpc>` to
-``mock``. This will add a third parameter to the test case callback:
-
-.. function:: mock(rpc_spec, handler)
-
- Can be used in two different ways depending on the shape of the
- first parameter:
-
- * If it matches the pattern ``model:method`` (if it contains a
- colon, essentially) the call will set up the mocking of an RPC
- call straight to the Odoo server (through XMLRPC) as
- performed via e.g. :func:`odoo.web.Model.call`.
-
- In that case, ``handler`` should be a function taking two
- arguments ``args`` and ``kwargs``, matching the corresponding
- arguments on the server side and should simply return the value
- as if it were returned by the Python XMLRPC handler::
-
- test('XML-RPC', {rpc: 'mock', asserts: 3}, function (instance, $s, mock) {
- // set up mocking
- mock('people.famous:name_search', function (args, kwargs) {
- strictEqual(kwargs.name, 'bob');
- return [
- [1, "Microsoft Bob"],
- [2, "Bob the Builder"],
- [3, "Silent Bob"]
- ];
- });
-
- // actual test code
- return new instance.web.Model('people.famous')
- .call('name_search', {name: 'bob'}).then(function (result) {
- strictEqual(result.length, 3, "shoud return 3 people");
- strictEqual(result[0][1], "Microsoft Bob",
- "the most famous bob should be Microsoft Bob");
- });
- });
-
- * Otherwise, if it matches an absolute path (e.g. ``/a/b/c``) it
- will mock a JSON-RPC call to a web client controller, such as
- ``/web/webclient/translations``. In that case, the handler takes
- a single ``params`` argument holding all of the parameters
- provided over JSON-RPC.
-
- As previously, the handler should simply return the result value
- as if returned by the original JSON-RPC handler::
-
- test('JSON-RPC', {rpc: 'mock', asserts: 3, templates: true}, function (instance, $s, mock) {
- var fetched_dbs = false, fetched_langs = false;
- mock('/web/database/get_list', function () {
- fetched_dbs = true;
- return ['foo', 'bar', 'baz'];
- });
- mock('/web/session/get_lang_list', function () {
- fetched_langs = true;
- return [['vo_IS', 'Hopelandic / Vonlenska']];
- });
-
- // widget needs that or it blows up
- instance.webclient = {toggle_bars: odoo.testing.noop};
- var dbm = new instance.web.DatabaseManager({});
- return dbm.appendTo($s).then(function () {
- ok(fetched_dbs, "should have fetched databases");
- ok(fetched_langs, "should have fetched languages");
- deepEqual(dbm.db_list, ['foo', 'bar', 'baz']);
- });
- });
-
-.. note::
-
- Mock handlers can contain assertions, these assertions should be
- part of the assertions count (and if multiple calls are made to a
- handler containing assertions, it multiplies the effective number
- of assertions).
-
-Testing API
------------
-
-.. function:: odoo.testing.section(name[, options], body)
-
- A test section, serves as shared namespace for related tests (for
- constants or values to only set up once). The ``body`` function
- should contain the tests themselves.
-
- Note that the order in which tests are run is essentially
- undefined, do *not* rely on it.
-
- :param String name:
- :param TestOptions options:
- :param body:
- :type body: Function<:func:`~odoo.testing.case`, void>
-
-.. function:: odoo.testing.case(name[, options], callback)
-
- Registers a test case callback in the test runner, the callback
- will only be run once the runner is started (or maybe not at all,
- if the test is filtered out).
-
- :param String name:
- :param TestOptions options:
- :param callback:
- :type callback: Function<instance, $, Function<String, Function, void>>
-
-.. class:: TestOptions
-
- the various options which can be passed to
- :func:`~odoo.testing.section` or
- :func:`~odoo.testing.case`. Except for
- :attr:`~TestOptions.setup` and
- :attr:`~TestOptions.teardown`, an option on
- :func:`~odoo.testing.case` will overwrite the corresponding
- option on :func:`~odoo.testing.section` so
- e.g. :attr:`~TestOptions.rpc` can be set for a
- :func:`~odoo.testing.section` and then differently set for
- some :func:`~odoo.testing.case` of that
- :func:`~odoo.testing.section`
-
- .. attribute:: TestOptions.asserts
-
- An integer, the number of assertions which should run during a
- normal execution of the test. Mandatory for asynchronous tests.
-
- .. attribute:: TestOptions.setup
-
- Test case setup, run right before each test case. A section's
- :func:`~TestOptions.setup` is run before the case's own, if
- both are specified.
-
- .. attribute:: TestOptions.teardown
-
- Test case teardown, a case's :func:`~TestOptions.teardown`
- is run before the corresponding section if both are present.
-
- .. attribute:: TestOptions.fail_on_rejection
-
- If the test is asynchronous and its resulting promise is
- rejected, fail the test. Defaults to ``true``, set to
- ``false`` to not fail the test in case of rejection::
-
- // test/demo.js
- test('unfail rejection', {
- asserts: 1,
- fail_on_rejection: false
- }, function () {
- var d = $.Deferred();
- setTimeout(function () {
- ok(true);
- d.reject();
- }, 100);
- return d;
- });
-
- .. attribute:: TestOptions.rpc
-
- RPC method to use during tests, one of ``"mock"`` or
- ``"rpc"``. Any other value will disable RPC for the test (if
- they were enabled by the suite for instance).
-
- .. attribute:: TestOptions.templates
-
- Whether the current module (and its dependencies)'s templates
- should be loaded into QWeb before starting the test. A
- boolean, ``false`` by default.
-
-The test runner can also use two global configuration values set
-directly on the ``window`` object:
-
-* ``oe_all_dependencies`` is an ``Array`` of all modules with a web
- component, ordered by dependency (for a module ``A`` with
- dependencies ``A'``, any module of ``A'`` must come before ``A`` in
- the array)
-
-Running through Python
-----------------------
-
-The web client includes the means to run these tests on the
-command-line (or in a CI system), but while actually running it is
-pretty simple the setup of the pre-requisite parts has some
-complexities.
-
-#. Install PhantomJS_. It is a headless
- browser which allows automating running and testing web
- pages. QUnitSuite_ uses it to actually run the qunit_ test suite.
-
- The PhantomJS_ website provides pre-built binaries for some
- platforms, and your OS's package management probably provides it as
- well.
-
- If you're building PhantomJS_ from source, I recommend preparing
- for some knitting time as it's not exactly fast (it needs to
- compile both `Qt <http://qt-project.org/>`_ and `Webkit
- <http://www.webkit.org/>`_, both being pretty big projects).
-
- .. note::
-
- Because PhantomJS_ is webkit-based, it will not be able to test
- if Firefox, Opera or Internet Explorer can correctly run the
- test suite (and it is only an approximation for Safari and
- Chrome). It is therefore recommended to *also* run the test
- suites in actual browsers once in a while.
-
- .. note::
-
- The version of PhantomJS_ this was build through is 1.7,
- previous versions *should* work but are not actually supported
- (and tend to just segfault when something goes wrong in
- PhantomJS_ itself so they're a pain to debug).
-
-#. Install a new database with all relevant modules (all modules with
- a web component at least), then restart the server
-
- .. note::
-
- For some tests, a source database needs to be duplicated. This
- operation requires that there be no connection to the database
- being duplicated, but Odoo doesn't currently break
- existing/outstanding connections, so restarting the server is
- the simplest way to ensure everything is in the right state.
-
-#. Launch ``oe run-tests -d $DATABASE -mweb`` with the correct
- addons-path specified (and replacing ``$DATABASE`` by the source
- database you created above)
-
- .. note::
-
- If you leave out ``-mweb``, the runner will attempt to run all
- the tests in all the modules, which may or may not work.
-
-If everything went correctly, you should now see a list of tests with
-(hopefully) ``ok`` next to their names, closing with a report of the
-number of tests run and the time it took:
-
-.. literalinclude:: test-report.txt
- :language: text
-
-Congratulation, you have just performed a successful "offline" run of
-the OpenERP Web test suite.
-
-.. note::
-
- Note that this runs all the Python tests for the ``web`` module,
- but all the web tests for all of Odoo. This can be surprising.
-
-.. _qunit: http://qunitjs.com/
-
-.. _qunit assertions: http://api.qunitjs.com/category/assert/
-
-.. _QUnitSuite: http://pypi.python.org/pypi/QUnitSuite/
-
-.. _PhantomJS: http://phantomjs.org/
-
-.. [#eventsdelegation] not all DOM events are compatible with events delegation
-
-.. [#terminal]
- with a small twist: :py:meth:`sqlalchemy.orm.query.Query.group_by` is not
- terminal, it returns a query which can still be altered.