summaryrefslogtreecommitdiff
path: root/doc/reference
diff options
context:
space:
mode:
Diffstat (limited to 'doc/reference')
-rw-r--r--doc/reference/actions.rst401
-rw-r--r--doc/reference/async.rst341
-rw-r--r--doc/reference/cmdline.rst4
-rw-r--r--doc/reference/http.rst4
-rw-r--r--doc/reference/images/runner.pngbin0 -> 3963 bytes
-rw-r--r--doc/reference/images/runner2.pngbin0 -> 6807 bytes
-rw-r--r--doc/reference/images/tests.pngbin0 -> 65388 bytes
-rw-r--r--doc/reference/images/tests2.pngbin0 -> 20114 bytes
-rw-r--r--doc/reference/images/tests3.pngbin0 -> 20382 bytes
-rw-r--r--doc/reference/javascript.rst1267
-rw-r--r--doc/reference/orm.rst49
-rw-r--r--doc/reference/qweb.rst550
-rw-r--r--doc/reference/test-report.txt25
-rw-r--r--doc/reference/testing.rst73
14 files changed, 2702 insertions, 12 deletions
diff --git a/doc/reference/actions.rst b/doc/reference/actions.rst
new file mode 100644
index 00000000000..8e31d58b36c
--- /dev/null
+++ b/doc/reference/actions.rst
@@ -0,0 +1,401 @@
+.. _reference/actions:
+
+=======
+Actions
+=======
+
+Actions define the behavior of the system in response to user actions: login,
+action button, selection of an invoice, ...
+
+Actions can be stored in the database or returned directly as dictionaries in
+e.g. button methods. All actions share two mandatory attributes:
+
+``type``
+ the category of the current action, determines which fields may be
+ used and how the action is interpreted
+``name``
+ short user-readable description of the action, may be displayed in the
+ client's interface
+
+A client can get actions in 4 forms:
+
+``False``
+ if any action dialog is currently open, close it
+A string
+ if a :ref:`client action <reference/actions/client>` matches, interpret as
+ a client action's tag, otherwise treat as a number
+A number
+ read the corresponding action record from the database, may be a database
+ identifier or an :term:`external id`
+A dictionary
+ treat as a client action descriptor and execute
+
+.. _reference/actions/window:
+
+Window Actions (``ir.actions.act_window``)
+==========================================
+
+The most common action type, used to present visualisations of a model through
+:ref:`views <reference/views>`: a window action defines a set of view types
+(and possibly specific views) for a model (and possibly specific record of the
+model).
+
+Its fields are:
+
+``res_model``
+ model to present views for
+``views``
+ a list of ``(view_id, view_type)`` pairs. The second element of each pair
+ is the category of the view (tree, form, graph, ...) and the first is
+ an optional database id (or ``False``). If no id is provided, the client
+ should fetch the default view of the specified type for the requested
+ model (this is automatically done by
+ :meth:`~openerp.models.Model.fields_view_get`). The first type of the
+ list is the default view type and will be open by default when the action
+ is executed. Each view type should be present at most once in the list
+``res_id`` (optional)
+ if the default view is ``form``, specifies the record to load (otherwise
+ a new record should be created)
+``search_view_id`` (optional)
+ ``(id, name)`` pair, ``id`` is the database identifier of a specific
+ search view to load for the action. Defaults to fetching the default
+ search view for the model
+``target`` (optional)
+ whether the views should be open in the main content area (``current``)
+ or in a dialog/popup (``new``). Defaults to ``current``.
+``context`` (optional)
+ additional context data to pass to the views
+``domain`` (optional)
+ filtering domain to implicitly add to all view search queries
+``limit`` (optional)
+ number of records to display in lists by default. Defaults to 80 in the
+ web client
+``auto_search`` (optional)
+ whether a search should be performed immediately after loading the default
+ view. Defaults to ``True``
+
+For instance, to open customers (partner with the ``customer`` flag set) with
+list and form views::
+
+ {
+ "type": "ir.actions.act_window",
+ "res_model": "res.partner",
+ "views": [[False, "tree"], [False, "form"]],
+ "domain": [["customer", "=", true]],
+ }
+
+Or to open the form view of a specific product (obtained separately) in a new
+dialog::
+
+ {
+ "type": "ir.actions.act_window",
+ "res_model": "product.product",
+ "views": [[False, "form"]],
+ "res_id": a_product_id,
+ "target": "new",
+ }
+
+In-database window actions have a few different fields which should be ignored
+by clients, mostly to use in composing the ``views`` list:
+
+``view_mode``
+ comma-separated list of view types (tree, form, ...) as a string. All of
+ these types will be present in the generated ``views`` list (with at least
+ a ``False`` view_id)
+``view_ids``
+ M2M\ [#notquitem2m]_ to view objects, defines the initial content of
+ ``views``
+``view_id``
+ specific view added to the ``views`` list in case its type is part of the
+ ``view_mode`` list and not already filled by one of the views in
+ ``view_ids``
+
+These are mostly used when defining actions from :ref:`reference/data`:
+
+.. code-block:: xml
+
+ <record model="ir.actions.act_window" id="test_action">
+ <field name="name">A Test Action</field>
+ <field name="res_model">some.model</field>
+ <field name="view_mode">graph</field>
+ <field name="view_id" ref="my_specific_view"/>
+ </record>
+
+will use the "my_specific_view" view even if that's not the default view for
+the model.
+
+The server-side composition of the ``views`` sequence is the following:
+
+* get each ``(id, type)`` from ``view_ids`` (ordered by ``sequence``)
+* if ``view_id`` is defined and its type isn't already filled, append its
+ ``(id, type)``
+* for each unfilled type in ``view_mode``, append ``(False, type)``
+
+.. todo::
+
+ * ``src_model``, ``multi`` seem linked to "sidebar" actions?
+ * ``auto_refresh`` looks ignored/deprecated
+ * ``usage``?
+ * ``groups_id``?
+ * ``filter``?
+
+.. _reference/actions/url:
+
+URL Actions (``ir.actions.act_url``)
+====================================
+
+Allow opening a URL (website/web page) via an Odoo action. Can be customized
+via two fields:
+
+``url``
+ the address to open when activating the action
+``target``
+ opens the address in a new window/page if ``new``, replaces
+ the current content with the page if ``self``. Defaults to ``new``
+
+::
+
+ {
+ "type": "ir.actions.act_url",
+ "url": "http://odoo.com",
+ "target": "self",
+ }
+
+will replace the current content section by the Odoo home page.
+
+.. _reference/actions/server:
+
+Server Actions (``ir.actions.server``)
+======================================
+
+Allow triggering complex server code from any valid action location. Only
+two fields are relevant to clients:
+
+``id``
+ the in-database identifier of the server action to run
+``context`` (optional)
+ context data to use when running the server action
+
+In-database records are significantly richer and can perform a number of
+specific or generic actions based on their ``state``. Some fields (and
+corresponding behaviors) are shared between states:
+
+``model_id``
+ Odoo model linked to the action, made available in
+ :ref:`evaluation contexts <reference/actions/server/context>`
+``condition`` (optional)
+ evaluated as Python code using the server action's
+ :ref:`evaluation context <reference/actions/server/context>`. If
+ ``False``, prevents the action from running. Default: ``True``
+
+Valid action types (``state`` field) are extensible, the default types are:
+
+``code``
+--------
+
+The default and most flexible server action type, executes arbitrary Python
+code with the action's :ref:`evaluation context
+<reference/actions/server/context>`. Only uses one specific type-specific
+field:
+
+``code``
+ a piece of Python code to execute when the action is called
+
+.. code-block:: xml
+
+ <record model="ir.actions.server" id="print_instance">
+ <field name="model_id" ref="model_res_partner"/>
+ <field name="code">
+ print self, object
+ </field>
+ </record>
+
+.. note::
+
+ The code segment can define a variable called ``action``, which will be
+ returned to the client as the next action to execute:
+
+ .. code-block:: xml
+
+ <record model="ir.actions.server" id="print_instance">
+ <field name="model_id" ref="model_res_partner"/>
+ <field name="code">
+ if object.some_condition():
+ action = {
+ "type": "ir.actions.act_window",
+ "view_mode": "form",
+ "res_model": object._name,
+ "res_id": object.id,
+ }
+ </field>
+ </record>
+
+ will ask the client to open a form for the record if it fullfils some
+ condition
+
+This tends to be the only action type created from :ref:`data files
+<reference/data>`, other types aside from
+:ref:`reference/actions/server/multi` are simpler than Python code to define
+from the UI, but not from :ref:`data files <reference/data>`.
+
+.. _reference/actions/server/object_create:
+
+``object_create``
+-----------------
+
+Creates a new record, from scratch (via :meth:`~openerp.models.Model.create`)
+or by copying an existing record (via :meth:`~openerp.models.Model.copy`)
+
+``use_create``
+ the creation policy, one of:
+
+ ``new``
+ creates a record in the model specified by ``model_id``
+ ``new_other``
+ creates a record in the model specified by ``crud_model_id``
+ ``copy_current``
+ copies the record on which the action was invoked
+ ``copy_other``
+ copies an other record, obtained via ``ref_object``
+``fields_lines``
+ fields to override when creating or copying the record.
+ :class:`~openerp.fields.One2many` with the fields:
+
+ ``col1``
+ ``ir.model.fields`` to set in the model implied by ``use_create``
+ ``value``
+ value for the field, interpreted via ``type``
+ ``type``
+ If ``value``, the ``value`` field is interpreted as a literal value
+ (possibly converted), if ``equation`` the ``value`` field is
+ interpreted as a Python expression and evaluated
+``crud_model_id``
+ model in which to create a new record, if ``use_create`` is set to
+ ``new_other``
+``ref_object``
+ :class:`~openerp.fields.Reference` to an arbitrary record to copy, used if
+ ``use_create`` is set to ``copy_other``
+``link_new_record``
+ boolean flag linking the newly created record to the current one via a
+ many2one field specified through ``link_field_id``, defaults to ``False``
+``link_field_id``
+ many2one to ``ir.model.fields``, specifies the current record's m2o field
+ on which the newly created record should be set (models should match)
+
+``object_write``
+----------------
+
+Similar to :ref:`reference/actions/server/object_create` but alters an
+existing records instead of creating one
+
+``use_write``
+ write policy, one of:
+
+ ``current``
+ write to the current record
+ ``other``
+ write to an other record selected via ``crud_model_id`` and
+ ``ref_object``
+ ``expression``
+ write to an other record whose model is selected via ``crud_model_id``
+ and whose id is selected by evaluating ``write_expression``
+``write_expression``
+ Python expression returning a record or an object id, used when
+ ``use_write`` is set to ``expression`` in order to decide which record
+ should be modified
+``fields_lines``
+ see :ref:`reference/actions/server/object_create`
+``crud_model_id``
+ see :ref:`reference/actions/server/object_create`
+``ref_object``
+ see :ref:`reference/actions/server/object_create`
+
+.. _reference/actions/server/multi:
+
+``multi``
+---------
+
+Executes multiple actions one after the other. Actions to execute are defined
+via the ``child_ids`` m2m. If sub-actions themselves return actions, the last
+one will be returned to the client as the multi's own next action
+
+``trigger``
+-----------
+
+Sends a signal to a workflow.
+
+``wkf_transition_id``
+ :class:`~openerp.fields.Many2one` to a ``workflow.transition`` to trigger
+``use_relational_model``
+ if ``base`` (the default), trigger the signal on behalf of the current
+ record. If ``relational``, trigger the signal on behalf of a field of the
+ current record selected through ``wkf_model_id`` and ``wkf_field_id``
+
+``client_action``
+-----------------
+
+Indirection for directly returning an other action defined using
+``action_id``. Simply returns that action to the client for execution.
+
+.. _reference/actions/server/context:
+
+Evaluation context
+------------------
+
+A number of keys are available in the evaluation context of or surrounding
+server actions:
+
+``self``
+ the model object linked to the action via ``model_id``
+``object``, ``obj``
+ only available if ``active_model`` and ``active_id`` are provided (via
+ context) otherwise ``None``. The actual record selected by ``active_id``
+``pool``
+ the current database registry
+``datetime``, ``dateutil``, ``time``
+ corresponding Python modules
+``cr``
+ the current cursor
+``user``
+ the current user record
+``context``
+ execution context
+``Warning``
+ constructor for the ``Warning`` exception
+
+.. ignored uid (available through ``user``), workflow (available through
+ workflow methods on models)
+
+.. _reference/actions/report:
+
+Report Actions
+==============
+
+.. todo:: sle-odoo
+
+.. _reference/actions/client:
+
+Client Actions (``ir.actions.client``)
+======================================
+
+Triggers an action implemented entirely in the client.
+
+``tag``
+ the client-side identifier of the action, an arbitrary string which
+ the client should know how to react to
+``params`` (optional)
+ a Python dictionary of additional data to send to the client, alongside
+ the client action tag
+
+::
+
+ {
+ "type": "ir.actions.client",
+ "tag": "pos.ui"
+ }
+
+tells the client to start the Point of Sale interface, the server has no idea
+how the POS interface works.
+
+.. [#notquitem2m] technically not an M2M: adds a sequence field and may be
+ composed of just a view type, without a view id.
diff --git a/doc/reference/async.rst b/doc/reference/async.rst
new file mode 100644
index 00000000000..c2aded839aa
--- /dev/null
+++ b/doc/reference/async.rst
@@ -0,0 +1,341 @@
+:orphan:
+
+.. _reference/async:
+
+Asynchronous Operations
+=======================
+
+As a language (and runtime), javascript is fundamentally
+single-threaded. This means any blocking request or computation will
+blocks the whole page (and, in older browsers, the software itself
+even preventing users from switching to an other tab): a javascript
+environment can be seen as an event-based runloop where application
+developers have no control over the runloop itself.
+
+As a result, performing long-running synchronous network requests or
+other types of complex and expensive accesses is frowned upon and
+asynchronous APIs are used instead.
+
+The goal of this guide is to provide some tools to deal with
+asynchronous systems, and warn against systemic issues or dangers.
+
+Deferreds
+---------
+
+Deferreds are a form of `promises`_. OpenERP Web currently uses
+`jQuery's deferred`_.
+
+The core idea of deferreds is that potentially asynchronous methods
+will return a :js:class:`Deferred` object instead of an arbitrary
+value or (most commonly) nothing.
+
+This object can then be used to track the end of the asynchronous
+operation by adding callbacks onto it, either success callbacks or
+error callbacks.
+
+A great advantage of deferreds over simply passing callback functions
+directly to asynchronous methods is the ability to :ref:`compose them
+<reference/async/composition>`.
+
+Using deferreds
+~~~~~~~~~~~~~~~
+
+Deferreds's most important method is :js:func:`Deferred.then`. It is
+used to attach new callbacks to the deferred object.
+
+* the first parameter attaches a success callback, called when the
+ deferred object is successfully resolved and provided with the
+ resolved value(s) for the asynchronous operation.
+
+* the second parameter attaches a failure callback, called when the
+ deferred object is rejected and provided with rejection values
+ (often some sort of error message).
+
+Callbacks attached to deferreds are never "lost": if a callback is
+attached to an already resolved or rejected deferred, the callback
+will be called (or ignored) immediately. A deferred is also only ever
+resolved or rejected once, and is either resolved or rejected: a given
+deferred can not call a single success callback twice, or call both a
+success and a failure callbacks.
+
+:js:func:`~Deferred.then` should be the method you'll use most often
+when interacting with deferred objects (and thus asynchronous APIs).
+
+Building deferreds
+~~~~~~~~~~~~~~~~~~
+
+After using asynchronous APIs may come the time to build them: for
+mocks_, to compose deferreds from multiple source in a complex
+manner, in order to let the current operations repaint the screen or
+give other events the time to unfold, ...
+
+This is easy using jQuery's deferred objects.
+
+.. note:: this section is an implementation detail of jQuery Deferred
+ objects, the creation of promises is not part of any
+ standard (even tentative) that I know of. If you are using
+ deferred objects which are not jQuery's, their API may (and
+ often will) be completely different.
+
+Deferreds are created by invoking their constructor [#]_ without any
+argument. This creates a :js:class:`Deferred` instance object with the
+following methods:
+
+:js:func:`Deferred.resolve`
+
+ As its name indicates, this method moves the deferred to the
+ "Resolved" state. It can be provided as many arguments as
+ necessary, these arguments will be provided to any pending success
+ callback.
+
+:js:func:`Deferred.reject`
+
+ Similar to :js:func:`~Deferred.resolve`, but moves the deferred to
+ the "Rejected" state and calls pending failure handlers.
+
+:js:func:`Deferred.promise`
+
+ Creates a readonly view of the deferred object. It is generally a
+ good idea to return a promise view of the deferred to prevent
+ callers from resolving or rejecting the deferred in your stead.
+
+:js:func:`~Deferred.reject` and :js:func:`~Deferred.resolve` are used
+to inform callers that the asynchronous operation has failed (or
+succeeded). These methods should simply be called when the
+asynchronous operation has ended, to notify anybody interested in its
+result(s).
+
+.. _reference/async/composition:
+
+Composing deferreds
+~~~~~~~~~~~~~~~~~~~
+
+What we've seen so far is pretty nice, but mostly doable by passing
+functions to other functions (well adding functions post-facto would
+probably be a chore... still, doable).
+
+Deferreds truly shine when code needs to compose asynchronous
+operations in some way or other, as they can be used as a basis for
+such composition.
+
+There are two main forms of compositions over deferred: multiplexing
+and piping/cascading.
+
+Deferred multiplexing
+`````````````````````
+
+The most common reason for multiplexing deferred is simply performing
+multiple asynchronous operations and wanting to wait until all of them are
+done before moving on (and executing more stuff).
+
+The jQuery multiplexing function for promises is :js:func:`when`.
+
+.. note:: the multiplexing behavior of jQuery's :js:func:`when` is an
+ (incompatible, mostly) extension of the behavior defined in
+ `CommonJS Promises/B`_.
+
+This function can take any number of promises [#]_ and will return a
+promise.
+
+The returned promise will be resolved when *all* multiplexed promises
+are resolved, and will be rejected as soon as one of the multiplexed
+promises is rejected (it behaves like Python's ``all()``, but with
+promise objects instead of boolean-ish).
+
+The resolved values of the various promises multiplexed via
+:js:func:`when` are mapped to the arguments of :js:func:`when`'s
+success callback, if they are needed. The resolved values of a promise
+are at the same index in the callback's arguments as the promise in
+the :js:func:`when` call so you will have:
+
+.. code-block:: javascript
+
+ $.when(p0, p1, p2, p3).then(
+ function (results0, results1, results2, results3) {
+ // code
+ });
+
+.. warning::
+
+ in a normal mapping, each parameter to the callback would be an
+ array: each promise is conceptually resolved with an array of 0..n
+ values and these values are passed to :js:func:`when`'s
+ callback. But jQuery treats deferreds resolving a single value
+ specially, and "unwraps" that value.
+
+ For instance, in the code block above if the index of each promise
+ is the number of values it resolves (0 to 3), ``results0`` is an
+ empty array, ``results2`` is an array of 2 elements (a pair) but
+ ``results1`` is the actual value resolved by ``p1``, not an array.
+
+Deferred chaining
+`````````````````
+
+A second useful composition is starting an asynchronous operation as
+the result of an other asynchronous operation, and wanting the result
+of both: with the tools described so far, handling e.g. OpenERP's
+search/read sequence with this would require something along the lines
+of:
+
+.. code-block:: javascript
+
+ var result = $.Deferred();
+ Model.search(condition).then(function (ids) {
+ Model.read(ids, fields).then(function (records) {
+ result.resolve(records);
+ });
+ });
+ return result.promise();
+
+While it doesn't look too bad for trivial code, this quickly gets
+unwieldy.
+
+But :js:func:`~Deferred.then` also allows handling this kind of
+chains: it returns a new promise object, not the one it was called
+with, and the return values of the callbacks is important to this behavior:
+whichever callback is called,
+
+* If the callback is not set (not provided or left to null), the
+ resolution or rejection value(s) is simply forwarded to
+ :js:func:`~Deferred.then`'s promise (it's essentially a noop)
+
+* If the callback is set and does not return an observable object (a
+ deferred or a promise), the value it returns (``undefined`` if it
+ does not return anything) will replace the value it was given, e.g.
+
+ .. code-block:: javascript
+
+ promise.then(function () {
+ console.log('called');
+ });
+
+ will resolve with the sole value ``undefined``.
+
+* If the callback is set and returns an observable object, that object
+ will be the actual resolution (and result) of the pipe. This means a
+ resolved promise from the failure callback will resolve the pipe,
+ and a failure promise from the success callback will reject the
+ pipe.
+
+ This provides an easy way to chain operation successes, and the
+ previous piece of code can now be rewritten:
+
+ .. code-block:: javascript
+
+ return Model.search(condition).then(function (ids) {
+ return Model.read(ids, fields);
+ });
+
+ the result of the whole expression will encode failure if either
+ ``search`` or ``read`` fails (with the right rejection values), and
+ will be resolved with ``read``'s resolution values if the chain
+ executes correctly.
+
+:js:func:`~Deferred.then` is also useful to adapt third-party
+promise-based APIs, in order to filter their resolution value counts
+for instance (to take advantage of :js:func:`when` 's special
+treatment of single-value promises).
+
+jQuery.Deferred API
+~~~~~~~~~~~~~~~~~~~
+
+.. js:function:: when(deferreds…)
+
+ :param deferreds: deferred objects to multiplex
+ :returns: a multiplexed deferred
+ :rtype: :js:class:`Deferred`
+
+.. js:class:: Deferred
+
+ .. js:function:: Deferred.then(doneCallback[, failCallback])
+
+ Attaches new callbacks to the resolution or rejection of the
+ deferred object. Callbacks are executed in the order they are
+ attached to the deferred.
+
+ To provide only a failure callback, pass ``null`` as the
+ ``doneCallback``, to provide only a success callback the
+ second argument can just be ignored (and not passed at all).
+
+ Returns a new deferred which resolves to the result of the
+ corresponding callback, if a callback returns a deferred
+ itself that new deferred will be used as the resolution of the
+ chain.
+
+ :param doneCallback: function called when the deferred is resolved
+ :param failCallback: function called when the deferred is rejected
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.done(doneCallback)
+
+ Attaches a new success callback to the deferred, shortcut for
+ ``deferred.then(doneCallback)``.
+
+ .. note:: a difference is the result of :js:func:`Deferred.done`'s
+ is ignored rather than forwarded through the chain
+
+ This is a jQuery extension to `CommonJS Promises/A`_ providing
+ little value over calling :js:func:`~Deferred.then` directly,
+ it should be avoided.
+
+ :param doneCallback: function called when the deferred is resolved
+ :type doneCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.fail(failCallback)
+
+ Attaches a new failure callback to the deferred, shortcut for
+ ``deferred.then(null, failCallback)``.
+
+ A second jQuery extension to `Promises/A <CommonJS
+ Promises/A>`_. Although it provides more value than
+ :js:func:`~Deferred.done`, it still is not much and should be
+ avoided as well.
+
+ :param failCallback: function called when the deferred is rejected
+ :type failCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.promise()
+
+ Returns a read-only view of the deferred object, with all
+ mutators (resolve and reject) methods removed.
+
+ .. js:function:: Deferred.resolve(value…)
+
+ Called to resolve a deferred, any value provided will be
+ passed onto the success handlers of the deferred object.
+
+ Resolving a deferred which has already been resolved or
+ rejected has no effect.
+
+ .. js:function:: Deferred.reject(value…)
+
+ Called to reject (fail) a deferred, any value provided will be
+ passed onto the failure handler of the deferred object.
+
+ Rejecting a deferred which has already been resolved or
+ rejected has no effect.
+
+.. [#] or simply calling :js:class:`Deferred` as a function, the
+ result is the same
+
+.. [#] or not-promises, the `CommonJS Promises/B`_ role of
+ :js:func:`when` is to be able to treat values and promises
+ uniformly: :js:func:`when` will pass promises through directly,
+ but non-promise values and objects will be transformed into a
+ resolved promise (resolving themselves with the value itself).
+
+ jQuery's :js:func:`when` keeps this behavior making deferreds
+ easy to build from "static" values, or allowing defensive code
+ where expected promises are wrapped in :js:func:`when` just in
+ case.
+
+.. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
+.. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
+.. _CommonJS Promises/A: http://wiki.commonjs.org/wiki/Promises/A
+.. _CommonJS Promises/B: http://wiki.commonjs.org/wiki/Promises/B
+.. _mocks: http://en.wikipedia.org/wiki/Mock_object
diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst
index 140405d0037..e3f2952c5d3 100644
--- a/doc/reference/cmdline.rst
+++ b/doc/reference/cmdline.rst
@@ -38,6 +38,10 @@ Running the server
(:file:`{$HOME}/.openerp_serverrc` by default, overridable using
:option:`-c`)
+.. option:: --test-enable
+
+ runs tests after installing modules
+
.. _reference/cmdline/scaffold:
Scaffolding
diff --git a/doc/reference/http.rst b/doc/reference/http.rst
index c179691c4cb..9f83a303d92 100644
--- a/doc/reference/http.rst
+++ b/doc/reference/http.rst
@@ -2,6 +2,8 @@
Web Controllers
===============
+.. _reference/http/routing:
+
Routing
=======
@@ -59,7 +61,7 @@ and defining methods decorated with :func:`~openerp.http.route`::
return stuff()
To *override* a controller, :ref:`inherit <python:tut-inheritance>` from its
-class and override relevant methods::
+class and override relevant methods, re-exposing them if necessary::
class Extension(MyController):
@route()
diff --git a/doc/reference/images/runner.png b/doc/reference/images/runner.png
new file mode 100644
index 00000000000..bd48e9d2922
--- /dev/null
+++ b/doc/reference/images/runner.png
Binary files differ
diff --git a/doc/reference/images/runner2.png b/doc/reference/images/runner2.png
new file mode 100644
index 00000000000..38ea2949cfc
--- /dev/null
+++ b/doc/reference/images/runner2.png
Binary files differ
diff --git a/doc/reference/images/tests.png b/doc/reference/images/tests.png
new file mode 100644
index 00000000000..84083d9e5ed
--- /dev/null
+++ b/doc/reference/images/tests.png
Binary files differ
diff --git a/doc/reference/images/tests2.png b/doc/reference/images/tests2.png
new file mode 100644
index 00000000000..c8a6f8ae9ee
--- /dev/null
+++ b/doc/reference/images/tests2.png
Binary files differ
diff --git a/doc/reference/images/tests3.png b/doc/reference/images/tests3.png
new file mode 100644
index 00000000000..247f70716af
--- /dev/null
+++ b/doc/reference/images/tests3.png
Binary files differ
diff --git a/doc/reference/javascript.rst b/doc/reference/javascript.rst
index ae7b02c63a4..938c2237912 100644
--- a/doc/reference/javascript.rst
+++ b/doc/reference/javascript.rst
@@ -1,3 +1,7 @@
+.. highlight:: javascript
+
+.. default-domain:: js
+
==========
Javascript
==========
@@ -5,10 +9,1273 @@ Javascript
Widgets
=======
+.. class:: openerp.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:`~openerp.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:: openerp.Widget.el
+
+ raw DOM element set as root to the widget
+
+.. attribute:: openerp.Widget.$el
+
+ jQuery wrapper around :attr:`~openerp.Widget.el`
+
+There are two main ways to define and generate this DOM root:
+
+.. attribute:: openerp.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:: openerp.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:: openerp.Widget.id
+
+ Used to generate an ``id`` attribute on the generated DOM
+ root.
+
+ .. attribute:: openerp.Widget.className
+
+ Used to generate a ``class`` attribute on the generated DOM root.
+
+ .. attribute:: openerp.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:: openerp.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:`~openerp.Widget.setElement` on the result.
+
+ Any override to :func:`~openerp.Widget.renderElement` which
+ does not call its ``_super`` **must** call
+ :func:`~openerp.Widget.setElement` with whatever it
+ generated or the widget's behavior is undefined.
+
+ .. note::
+
+ The default :func:`~openerp.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:`~openerp.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:: openerp.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:`~openerp.Widget`
+
+* DOM injection and startup, this is done by calling one of:
+
+ .. function:: openerp.Widget.appendTo(element)
+
+ Renders the widget and inserts it as the last child of the target, uses
+ `.appendTo()`_
+
+ .. function:: openerp.Widget.prependTo(element)
+
+ Renders the widget and inserts it as the first child of the target, uses
+ `.prependTo()`_
+
+ .. function:: openerp.Widget.insertAfter(element)
+
+ Renders the widget and inserts it as the preceding sibling of the target,
+ uses `.insertAfter()`_
+
+ .. function:: openerp.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:`~openerp.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:: openerp.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:`~openerp.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:: openerp.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:: openerp.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:`~openerp.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:: openerp.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:: openerp.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:: openerp.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:`~openerp.Widget` provides a shortcut:
+
+.. attribute:: openerp.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:: openerp.Widget.delegateEvents
+
+ This method is in charge of binding :attr:`~openerp.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:`~openerp.Widget.events` map allows, but the parent should always be
+ called (or :attr:`~openerp.Widget.events` won't be handled correctly).
+
+.. function:: openerp.Widget.undelegateEvents
+
+ This method is in charge of unbinding :attr:`~openerp.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:`~openerp.Widget.delegateEvents`.
+
+.. note:: this behavior should be compatible with `Backbone's
+ delegateEvents`_, apart from not accepting any argument.
+
+Subclassing Widget
+------------------
+
+:class:`~openerp.Widget` is subclassed in the standard manner (via the
+:func:`~openerp.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 = openerp.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:`~openerp.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:`~openerp.Widget.start()`.
+
+ If for some reason you do not want to call these methods, you will
+ have to first call :func:`~openerp.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:`~openerp.web.Widget` has an attribute providing its DOM root
+ (:attr:`~openerp.web.Widget.$el`), and a shortcut to select nodes directly
+ (:func:`~openerp.web.Widget.$`).
+* More generally, never assume your components own or controls anything beyond
+ its own personal :attr:`~openerp.web.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:`~openerp.web.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:`openerp.Model`. It maps onto the Odoo server objects via
+two primary methods, :func:`~openerp.Model.call` and
+:func:`~openerp.Model.query`.
+
+:func:`~openerp.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 openerp.Model('res.users');
+
+ Users.call('change_password', ['oldpassword', 'newpassword'],
+ {context: some_context}).then(function (result) {
+ // do something with change_password result
+ });
+
+:func:`~openerp.Model.query` is a shortcut for a builder-style
+interface to searches (``search`` + ``read`` in Odoo RPC terms). It
+returns a :class:`~openerp.web.Query` object which is immutable but
+allows building new :class:`~openerp.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:`~openerp.web.Query.all` and
+:func:`~openerp.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:: openerp.Model(name)
+
+ .. attribute:: openerp.Model.name
+
+ name of the OpenERP model this object is bound to
+
+ .. function:: openerp.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:`~openerp.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:: openerp.Model.query(fields)
+
+ :param Array<String> fields: list of fields to fetch during
+ the search
+ :returns: a :class:`~openerp.web.Query` object
+ representing the search to perform
+
+.. class:: openerp.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:: openerp.web.Query.all()
+
+ Fetches the result of the current :class:`~openerp.web.Query` object's
+ search.
+
+ :rtype: Deferred<Array<>>
+
+ .. function:: openerp.web.Query.first()
+
+ Fetches the **first** result of the current
+ :class:`~openerp.web.Query`, or ``null`` if the current
+ :class:`~openerp.web.Query` does have any result.
+
+ :rtype: Deferred<Object | null>
+
+ .. function:: openerp.web.Query.count()
+
+ Fetches the number of records the current
+ :class:`~openerp.web.Query` would retrieve.
+
+ :rtype: Deferred<Number>
+
+ .. function:: openerp.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<openerp.web.QueryGroup>> | null
+
+ The second set of methods is the "mutator" methods, they create a
+ **new** :class:`~openerp.web.Query` object with the relevant
+ (internal) attribute either augmented or replaced.
+
+ .. function:: openerp.web.Query.context(ctx)
+
+ Adds the provided ``ctx`` to the query, on top of any existing
+ context
+
+ .. function:: openerp.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:: openerp.web.Query.limit(limit)
+
+ Sets the provided limit on the query. The new limit *replaces*
+ the old one.
+
+ .. function:: openerp.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:`openerp.models.Model.read_group` works it's not a very intuitive
+API.
+
+Odoo Web eschews direct calls to :py:meth:`~openerp.models.Model.read_group`
+in favor of calling a method of :class:`~openerp.web.Query`, :py:meth:`much
+in the way it is one 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:`~openerp.web.Query.group_by` is
+an array of :class:`~openerp.web.QueryGroup`:
+
+.. class:: openerp.web.QueryGroup
+
+ .. function:: openerp.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:: openerp.web.QueryGroup.query([fields...])
+
+ equivalent to :func:`openerp.web.Model.query` but pre-filtered to
+ only include the records within this group. Returns a
+ :class:`~openerp.web.Query` which can be further manipulated as
+ usual.
+
+ .. function:: openerp.web.QueryGroup.subgroups()
+
+ returns a deferred to an array of :class:`~openerp.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:`~openerp.web.Session` objects (usually available through
+``openerp.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::
+
+ openerp.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
==========
+
+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/test/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::
+
+ openerp.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 ``openerp.testing``
+module. Odoo tests live in a :func:`~openerp.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 <openerp.testing.case>`, provided by the
+:func:`~openerp.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/test/demo.js'],
+ }
+
+::
+
+ // src/js/demo.js
+ openerp.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 <~openerp.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 <openerp.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/test/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:`openerp.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: openerp.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:: openerp.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:`~openerp.testing.case`, void>
+
+.. function:: openerp.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:`~openerp.testing.section` or
+ :func:`~openerp.testing.case`. Except for
+ :attr:`~TestOptions.setup` and
+ :attr:`~TestOptions.teardown`, an option on
+ :func:`~openerp.testing.case` will overwrite the corresponding
+ option on :func:`~openerp.testing.section` so
+ e.g. :attr:`~TestOptions.rpc` can be set for a
+ :func:`~openerp.testing.section` and then differently set for
+ some :func:`~openerp.testing.case` of that
+ :func:`~openerp.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 unittest2_ in your Python environment. Both
+ can trivially be installed via `pip <http://pip-installer.org>`_ or
+ `easy_install
+ <http://packages.python.org/distribute/easy_install.html>`_.
+
+#. 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/
+
+.. _unittest2: http://pypi.python.org/pypi/unittest2
+
+.. _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.
+
diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst
index d010e8f5d0e..03548d1e399 100644
--- a/doc/reference/orm.rst
+++ b/doc/reference/orm.rst
@@ -138,7 +138,6 @@ Model
.. automethod:: default_get
.. automethod:: copy
- .. automethod:: add_default_value
.. automethod:: name_get
.. automethod:: name_create
@@ -410,7 +409,7 @@ and it's possible to write directly on the delegated field:
Domains
=======
-A domain is a list of criterion, each criterion being a triple (either a
+A domain is a list of criteria, each criterion being a triple (either a
``list`` or a ``tuple``) of ``(field_name, operator, value)`` where:
``field_name`` (``str``)
@@ -418,9 +417,51 @@ A domain is a list of criterion, each criterion being a triple (either a
a :class:`~openerp.fields.Many2one` using dot-notation e.g. ``'street'``
or ``'partner_id.country'``
``operator`` (``str``)
- a comparison operator between the criterion's field and value.
+ an operator used to compare the ``field_name`` with the ``value``. Valid
+ operators are:
+
+ ``=``
+ equals to
+ ``!=``
+ not equals to
+ ``>``
+ greater than
+ ``>=``
+ greater than or equal to
+ ``<``
+ less than
+ ``<=``
+ less than or equal to
+ ``=?``
+ unset or equals to (returns true if ``value`` is either ``None`` or
+ ``False``, otherwise behaves like ``=``)
+ ``=like``
+ matches ``field_name`` against the ``value`` pattern. An underscore
+ ``_`` in the pattern stands for (matches) any single character; a
+ percent sign ``%`` matches any string of zero or more characters.
+ ``like``
+ matches ``field_name`` against the ``%value%`` pattern. Similar to
+ ``=like`` but wraps ``value`` with '%' before matching
+ ``not like``
+ doesn't match against the ``%value%`` pattern
+ ``ilike``
+ case insensitive ``like``
+ ``not ilike``
+ case insensitive ``not like``
+ ``=ilike``
+ case insensitive ``=like``
+ ``in``
+ is equal to any of the items from ``value``, ``value`` should be a
+ list of items
+ ``not in``
+ is unequal to all of the items from ``value``
+ ``child_of``
+ is a child (descendant) of a ``value`` record.
+
+ Takes the semantics of the model into account (i.e following the
+ relationship field named by
+ :attr:`~openerp.models.Model._parent_name`).
- .. todo:: list and detail operators, original list is way incomplete
``value``
variable type, must be comparable (through ``operator``) to the named
field
diff --git a/doc/reference/qweb.rst b/doc/reference/qweb.rst
index c993c0c3c22..47a4d0a2774 100644
--- a/doc/reference/qweb.rst
+++ b/doc/reference/qweb.rst
@@ -1,37 +1,573 @@
+.. highlight:: xml
+
.. _reference/qweb:
====
QWeb
====
-Basics
-======
+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.
.. _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), a mapping (the current item will be the current key) or an integer
+(equivalent to iterating on an array between 0 inclusive and the provided
+integer exclusive).
+
+In addition to the name passed via ``t-as``, ``foreach`` provides a few other
+variables for various data points (``$as`` is the name passed to ``t-as``):
+
+:samp:`{$as}_all`
+ the object being iterated over
+: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`
+ either ``"even"`` or ``"odd"``, the parity of the current iteration round
+:samp:`{$as}_even`
+ a boolean flag indicating that the current iteration round is on an even
+ index
+:samp:`{$as}_odd`
+ a boolean flag indicating that the current iteration round is on an odd
+ index
+
+.. _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_parity }}"><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.
+
+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
======
-Bundles
+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 edition.
+
+``t-field-options`` can be used to customize fields, the most common option
+is ``widget``, other options are field- or widget-dependent.
+
+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:`openerp.http.HttpRequest.render`:
+
+.. code-block:: python
+
+ response = http.request.render('my-template', {
+ 'context_value': 42
+ })
+
+This automatically creates a :class:`~openerp.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:`~openerp.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:
+API
+---
+
+It is also possible to use the ``ir.qweb`` model directly (and extend it, and
+inherit from it):
+
+.. automodule:: openerp.addons.base.ir.ir_qweb
+ :members: QWeb, QWebContext, FieldConverter, QwebWidget
+
Javascript
==========
-loading
+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 alter existing templates in-place, e.g. to
+add information to templates created by an other modules.
+
+Template inheritance is performed via the ``t-extend`` directive which takes
+the name of the template to alter as parameter.
+
+The alteration is then performed with any number of ``t-jquery``
+sub-directives::
+
+ <t t-extends="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
+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-debug``
+ triggers a debugger breakpoint during template rendering
+``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
+
+Helpers
-------
+
+.. js:attribute:: openerp.qweb
+
+ 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:`openerp.qweb.render <QWeb2.Engine.render>` can be used to
+ easily render basic module templates
+
+API
+---
+
+.. js:class:: QWeb2.Engine
+
+ The QWeb "renderer", handles most of QWeb's logic (loading,
+ parsing, compiling and rendering templates).
+
+ OpenERP Web instantiates one for the user, and sets it to
+ ``instance.web.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
+ OpenERP 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 OpenERP 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 8.0 still depends on Jinja_ and Mako_.
+
+.. _templating:
+ http://en.wikipedia.org/wiki/Template_processor
+
+.. _Jinja: http://jinja.pocoo.org
+.. _Mako: http://www.makotemplates.org
+.. _Genshi: http://genshi.edgewall.org
+.. _XML namespaces: http://en.wikipedia.org/wiki/XML_namespace
+.. _HTML: http://en.wikipedia.org/wiki/HTML
+.. _XSS: http://en.wikipedia.org/wiki/Cross-site_scripting
+.. _JSON: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
+.. _CSS selector: http://api.jquery.com/category/selectors/
diff --git a/doc/reference/test-report.txt b/doc/reference/test-report.txt
new file mode 100644
index 00000000000..ce52618fe20
--- /dev/null
+++ b/doc/reference/test-report.txt
@@ -0,0 +1,25 @@
+test_empty_find (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
+test_ids_shortcut (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
+test_regular_find (openerp.addons.web.tests.test_dataset.TestDataSetController) ... ok
+web.testing.stack: direct, value, success ... ok
+web.testing.stack: direct, deferred, success ... ok
+web.testing.stack: direct, value, error ... ok
+web.testing.stack: direct, deferred, failure ... ok
+web.testing.stack: successful setup ... ok
+web.testing.stack: successful teardown ... ok
+web.testing.stack: successful setup and teardown ... ok
+
+[snip ~150 lines]
+
+test_convert_complex_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_convert_complex_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_convert_literal_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_convert_literal_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_retrieve_nonliteral_context (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+test_retrieve_nonliteral_domain (openerp.addons.web.tests.test_view.DomainsAndContextsTest) ... ok
+
+----------------------------------------------------------------------
+Ran 181 tests in 15.706s
+
+OK
+
diff --git a/doc/reference/testing.rst b/doc/reference/testing.rst
new file mode 100644
index 00000000000..2676c2b4084
--- /dev/null
+++ b/doc/reference/testing.rst
@@ -0,0 +1,73 @@
+.. _reference/testing:
+
+===============
+Testing Modules
+===============
+
+Odoo provides support for testing modules using unittest2_.
+
+To write tests, simply define a ``tests`` sub-package in your module, it will
+be automatically inspected for test modules. Test modules should have a name
+starting with ``test_`` and should be imported from ``tests/__init__.py``,
+e.g.
+
+.. code-block:: text
+
+ your_module
+ |-- ...
+ `-- tests
+ |-- __init__.py
+ |-- test_bar.py
+ `-- test_foo.py
+
+and ``__init__.py`` contains::
+
+ from . import test_foo, test_bar
+
+.. note:: test modules which are not imported from ``tests/__init__.py`` will
+ not be run
+
+The test runner will simply run any test case, as described in the official
+`unittest documentation`_, but Odoo provides a number of utilities and helpers
+related to testing Odoo content (modules, mainly):
+
+.. autoclass:: openerp.tests.common.TransactionCase
+ :members: browse_ref, ref
+
+.. autoclass:: openerp.tests.common.SingleTransactionCase
+ :members: browse_ref, ref
+
+By default, tests are run once right after the corresponding module has been
+installed. Test cases can also be configured to run after all modules have
+been installed, and not run right after the module installation:
+
+.. autofunction:: openerp.tests.common.at_install
+
+.. autofunction:: openerp.tests.common.post_install
+
+The most common situation is to use
+:class:`~openerp.tests.common.TransactionCase` and test a property of a of a
+model in each method::
+
+ class TestModelA(common.TransactionCase):
+ def test_some_action(self):
+ record = self.env['model.a'].create({'field': 'value'})
+ record.some_action()
+ self.assertEqual(
+ record.field,
+ expected_field_value)
+
+ # other tests...
+
+Running tests
+-------------
+
+Tests are automatically run when installing or updating modules if
+:option:`--test-enable <odoo.py --test-enable>` was enabled when starting the
+Odoo server.
+
+As of Odoo 8, running tests outside of the install/update cycle is not
+
+
+.. _unittest2: http://pypi.python.org/pypi/unittest2
+.. _unittest documentation: https://docs.python.org/2/library/unittest.html