diff options
| author | Victor Feyens <vfe@odoo.com> | 2021-04-30 13:24:57 +0000 |
|---|---|---|
| committer | Antoine Vandevenne (anv) <anv@odoo.com> | 2021-05-04 08:09:20 +0000 |
| commit | 27bad8c3e32049ec69a588df0558e57a0b928309 (patch) | |
| tree | b60e430163d486c8c1fd052738c9854aeb8300f5 /doc/reference | |
| parent | 75a122bbae3a32bfa79da3506179fc63a8bced5d (diff) | |
[MOV] doc: move documentation to a dedicated repository
In odoo/documentation-user#945, the developer documentation is merged
with the user documentation in a single repository and build config.
This commit then removes source files for the developer documentation
from the odoo/odoo repository.
DOC PR: https://github.com/odoo/documentation-user/pull/945
task-2351938
task-2352371
task-2205684
task-2352544
Co-authored-by: Victor Feyens <vfe@odoo.com>
Co-authored-by: Antoine Vandevenne <anv@odoo.com>
Diffstat (limited to 'doc/reference')
32 files changed, 0 insertions, 12837 deletions
diff --git a/doc/reference/actions.rst b/doc/reference/actions.rst deleted file mode 100644 index fef80a12737..00000000000 --- a/doc/reference/actions.rst +++ /dev/null @@ -1,445 +0,0 @@ -:banner: banners/actions.jpg - -.. _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/bindings: - -Bindings -======== - -Aside from their two mandatory attributes, all actions also share *optional* -attributes used to present an action in an arbitrary model's contextual menu: - -``binding_model_id`` - specifies which model the action is bound to - - .. note:: For Server Actions, use ``model_id``. - -``binding_type`` - specifies the type of binding, which is mostly which contextual menu the - action will appear under - - ``action`` (default) - Specifies that the action will appear in the :menuselection:`Action` - contextual menu of the bound model. - ``report`` - Specifies that the action will appear in the :menuselection:`Print` - contextual menu of the bound model. -``binding_view_types`` - a comma-separated list of view types for which the action appears in the - contextual menu, mostly "list" and / or "form". Defaults to ``list,form`` - (both list and form ) - -.. _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:`~odoo.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``), - in full screen mode (``fullscreen``) or in a dialog/popup (``new``). Use - ``main`` instead of ``current`` to clear the breadcrumbs. 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 - -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`` (default= ``tree,form`` ) - comma-separated list of view types as a string (/!\\ No spaces /!\\). 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`` - - .. note:: Act_window views can also be defined cleanly through ``ir.actions.act_window.view``. - - If you plan to allow multiple views for your model, prefer using - ir.actions.act_window.view instead of the action ``view_ids`` - - .. code-block:: xml - - <record model="ir.actions.act_window.view" id="test_action_tree"> - <field name="sequence" eval="1"/> - <field name="view_mode">tree</field> - <field name="view_id" ref="view_test_tree"/> - <field name="act_window_id" ref="test_action"/> - </record> - -``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:: - - * ``usage``? - * ``groups_id``? - * ``filter``? - -.. [#notquitem2m] technically not an M2M: adds a sequence field and may be - composed of just a view type, without a view id. - -.. _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": "https://odoo.com", - "target": "self", - } - -will replace the current content section by the Odoo home page. - -.. _reference/actions/server: - -Server Actions (``ir.actions.server``) -====================================== - -.. autoclass:: odoo.addons.base.models.ir_actions.IrActionsServer - -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. - -``state`` - -* ``code``: Executes python code given through the ``code`` argument. - -* ``object_create``: Creates a new record of model ``crud_model_id`` following ``fields_lines`` specifications. - -* ``object_write``: Updates the current record(s) following ``fields_lines`` specifications - -* ``multi``: Executes serveral actions given through the ``child_ids`` argument. - -State fields ------------- - -Depending on its state, the behavior is defined through different fields. -The concerned state is given after each field. - -``code`` (code) - Specify 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="name">Res Partner Server Action</field> - <field name="model_id" ref="model_res_partner"/> - <field name="state">code</field> - <field name="code"> - raise Warning(record.name) - </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="name">Res Partner Server Action</field> - <field name="model_id" ref="model_res_partner"/> - <field name="state">code</field> - <field name="code"> - if record.some_condition(): - action = { - "type": "ir.actions.act_window", - "view_mode": "form", - "res_model": record._name, - "res_id": record.id, - } - </field> - </record> - - will ask the client to open a form for the record if it fulfills 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>`. - -``crud_model_id`` (create)(required) - model in which to create a new record -``link_field_id`` (create) - many2one to ``ir.model.fields``, specifies the current record's m2o field - on which the newly created record should be set (models should match) - -``fields_lines`` (create/write) - fields to override when creating or copying the record. - :class:`~odoo.fields.One2many` with the fields: - - ``col1`` - ``ir.model.fields`` to set in the concerned model - (``crud_model_id`` for creates, ``model_id`` for updates) - ``value`` - value for the field, interpreted via ``type`` - ``type`` (value|reference|equation) - 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 - -``child_ids`` (multi) - Specify the multiple sub-actions (``ir.actions.server``) to enact in state multi. - If sub-actions themselves return actions, the last - one will be returned to the client as the multi's own next action - -.. _reference/actions/server/context: - -Evaluation context ------------------- - -A number of keys are available in the evaluation context of or surrounding -server actions: - -* ``model`` model object linked to the action via ``model_id`` -* ``record``/``records`` record/recorset on which the action is triggered, can be void. -* ``env`` Odoo Environment -* ``datetime``, ``dateutil``, ``time``, ``timezone`` corresponding Python modules -* ``log: log(message, level='info')`` logging function to record debug information in ir.logging table -* ``Warning`` constructor for the ``Warning`` exception - -.. _reference/actions/report: - -Report Actions (``ir.actions.report``) -====================================== - -Triggers the printing of a report. - -If you define your report through a `<record>` instead of a `<report>` tag and -want the action to show up in the Print menu of the model's views, you will -also need to specify ``binding_model_id`` from :ref:`reference/bindings`. It's -not necessary to set ``binding_type`` to ``report``, since -``ir.actions.report`` will implicitly default to that. - - -``name`` (mandatory) - used as the file name if ``print_report_name`` is not specified. - Otherwise, only useful as a mnemonic/description of the report - when looking for one in a list of some sort -``model`` (mandatory) - the model your report will be about -``report_type`` (default=qweb-pdf) - either ``qweb-pdf`` for PDF reports or ``qweb-html`` for HTML -``report_name`` (mandatory) - the name (:term:`external id`) of the qweb template used to render the report -``print_report_name`` - python expression defining the name of the report. -``groups_id`` - :class:`~odoo.fields.Many2many` field to the groups allowed to view/use - the current report -``multi`` - if set to ``True``, the action will not be displayed on a form view. -``paperformat_id`` - :class:`~odoo.fields.Many2one` field to the paper format you wish to - use for this report (if not specified, the company format will be used) -``attachment_use`` - if set to ``True``, the report is only generated once the first time it is - requested, and re-printed from the stored report afterwards instead of - being re-generated every time. - - Can be used for reports which must only be generated once (e.g. for legal - reasons) -``attachment`` - python expression that defines the name of the report; the record is - accessible as the variable ``object`` - -.. _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 -``target`` (optional) - whether the client action should be open in the main content area - (``current``), in full screen mode (``fullscreen``) or in a dialog/popup - (``new``). Use ``main`` instead of ``current`` to clear the breadcrumbs. - Defaults to ``current``. - -:: - - { - "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. - -.. seealso:: - - :ref:`Tutorial: Client Actions <howtos/web/client_actions>` - -.. _reference/actions/cron: - -Automated Actions (``ir.cron``) -====================================== - -Actions triggered automatically on a predefined frequency. - -``name`` - Name of the automated action (Mainly used in log display) - -``interval_number`` - Number of *interval_type* uom between two executions of the action - -``interval_type`` - Unit of measure of frequency interval (``minutes``, ``hours``, ``days``, ``weeks``, ``months``, - -``numbercall`` - Number of times this action has to be run. - If the action is expected to run indefinitely, set to ``-1``. - -``doall`` - Boolean precising whether the missed actions have to be executed in case of - server restarts. - -``model_id`` - Model on which this action will be called - -``code`` - Code content of the action. - Can be a simple call to the model's method : - - .. code-block:: python - - model.<method_name>() - -``nextcall`` - Next planned execution date of this action (date/time format) diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst deleted file mode 100644 index 7936a32b888..00000000000 --- a/doc/reference/cmdline.rst +++ /dev/null @@ -1,745 +0,0 @@ -:banner: banners/cmdline.jpg - -.. _reference/cmdline: - -================================ -Command-line interface: odoo-bin -================================ - -.. _reference/cmdline/server: - -Running the server -================== - -.. program:: odoo-bin - -.. option:: -d <database>, --database <database> - - database(s) used when installing or updating modules. - Providing a comma-separated list restrict access to databases provided in - list. - - For advanced database options, take a look :ref:`below <reference/cmdline/server/database>`. - -.. option:: -i <modules>, --init <modules> - - comma-separated list of modules to install before running the server - (requires :option:`-d`). - -.. option:: -u <modules>, --update <modules> - - comma-separated list of modules to update before running the server - (requires :option:`-d`). - -.. option:: --addons-path <directories> - - comma-separated list of directories in which modules are stored. These - directories are scanned for modules. - - .. (nb: when and why?) - -.. option:: -c <config>, --config <config> - - provide an alternate :ref:`configuration file <reference/cmdline/config>` - -.. option:: -s, --save - - saves the server configuration to the current configuration file - (:file:`{$HOME}/.odoorc` by default, and can be overridden using - :option:`-c`). - -.. option:: --without-demo - - disables demo data loading for modules installed - comma-separated, use ``all`` for all modules. - -.. option:: --test-enable - - runs tests after installing modules - -.. option:: --test-tags 'tag_1,tag_2,...,-tag_n' - - select the tests to run by using tags. - -.. option:: --screenshots - - Specify directory where to write screenshots when an HttpCase.browser_js test - fails. It defaults to :file:`/tmp/odoo_tests/{db_name}/screenshots` - -.. option:: --screencasts - - Enable screencasts and specify directory where to write screencasts files. - The ``ffmpeg`` utility needs to be installed to encode frames into a video - file. Otherwise frames will be kept instead of the video file. - -.. _reference/cmdline/server/database: - -Database --------- - -.. option:: -r <user>, --db_user <user> - - database username, used to connect to PostgreSQL. - -.. option:: -w <password>, --db_password <password> - - database password, if using `password authentication`_. - -.. option:: --db_host <hostname> - - host for the database server - - * ``localhost`` on Windows - * UNIX socket otherwise - -.. option:: --db_port <port> - - port the database listens on, defaults to 5432 - -.. option:: --db-filter <filter> - - hides databases that do not match ``<filter>``. The filter is a - `regular expression`_, with the additions that: - - - ``%h`` is replaced by the whole hostname the request is made on. - - ``%d`` is replaced by the subdomain the request is made on, with the - exception of ``www`` (so domain ``odoo.com`` and ``www.odoo.com`` both - match the database ``odoo``). - - These operations are case sensitive. Add option ``(?i)`` to match all - databases (so domain ``odoo.com`` using ``(?i)%d`` matches the database - ``Odoo``). - - Since version 11, it's also possible to restrict access to a given database - listen by using the --database parameter and specifying a comma-separated - list of databases - - When combining the two parameters, db-filter supersedes the comma-separated - database list for restricting database list, while the comma-separated list - is used for performing requested operations like upgrade of modules. - - .. code-block:: bash - - $ odoo-bin --db-filter ^11.*$ - - Restrict access to databases whose name starts with 11 - - .. code-block:: bash - - $ odoo-bin --database 11firstdatabase,11seconddatabase - - Restrict access to only two databases, 11firstdatabase and 11seconddatabase - - .. code-block:: bash - - $ odoo-bin --database 11firstdatabase,11seconddatabase -u base - - Restrict access to only two databases, 11firstdatabase and 11seconddatabase, - and update base module on one database: 11firstdatabase. - If database 11seconddatabase doesn't exist, the database is created and base modules - is installed - - .. code-block:: bash - - $ odoo-bin --db-filter ^11.*$ --database 11firstdatabase,11seconddatabase -u base - - Restrict access to databases whose name starts with 11, - and update base module on one database: 11firstdatabase. - If database 11seconddatabase doesn't exist, the database is created and base modules - is installed - -.. option:: --db-template <template> - - when creating new databases from the database-management screens, use the - specified `template database`_. Defaults to ``template0``. - -.. option:: --pg_path </path/to/postgresql/binaries> - - Path to the PostgreSQL binaries that are used by the database manager to - dump and restore databases. You have to specify this option only if these - binaries are located in a non-standard directory. - -.. option:: --no-database-list - - Suppresses the ability to list databases available on the system - -.. option:: --db_sslmode - - Control the SSL security of the connection between Odoo and PostgreSQL. - Value should bve one of 'disable', 'allow', 'prefer', 'require', - 'verify-ca' or 'verify-full' - Default value is 'prefer' - -.. _reference/cmdline/server/emails: - -Emails ------- - -.. option:: --email-from <address> - - Email address used as <FROM> when Odoo needs to send mails - -.. option:: --smtp <server> - - Address of the SMTP server to connect to in order to send mails - -.. option:: --smtp-port <port> - -.. option:: --smtp-ssl - - If set, odoo should use SSL/STARTSSL SMTP connections - -.. option:: --smtp-user <name> - - Username to connect to the SMTP server - -.. option:: --smtp-password <password> - - Password to connect to the SMTP server - -.. _reference/cmdline/server/internationalisation: - -Internationalisation --------------------- - -Use these options to translate Odoo to another language. See i18n section of -the user manual. Option '-d' is mandatory. Option '-l' is mandatory in case -of importation - -.. option:: --load-language <languages> - - specifies the languages (separated by commas) for the translations you - want to be loaded - -.. option:: -l, --language <language> - - specify the language of the translation file. Use it with --i18n-export - or --i18n-import - -.. option:: --i18n-export <filename> - - export all sentences to be translated to a CSV file, a PO file or a TGZ - archive and exit. - -.. option:: --i18n-import <filename> - - import a CSV or a PO file with translations and exit. The '-l' option is - required. - -.. option:: --i18n-overwrite - - overwrites existing translation terms on updating a module or importing - a CSV or a PO file. - -.. option:: --modules - - specify modules to export. Use in combination with --i18n-export - -.. _reference/cmdline/advanced: - -Advanced Options ----------------- - -.. _reference/cmdline/dev: - -Developer features -'''''''''''''''''' - -.. option:: --dev <feature,feature,...,feature> - - * ``all``: all the features below are activated - - * ``xml``: read template qweb from xml file directly instead of database. - Once a template has been modified in database, it will be not be read from - the xml file until the next update/init. - - * ``reload``: restart server when python file are updated (may not be detected - depending on the text editor used) - - * ``qweb``: break in the evaluation of qweb template when a node contains ``t-debug='debugger'`` - - * ``(i)p(u)db``: start the chosen python debugger in the code when an - unexpected error is raised before logging and returning the error. - - -.. _reference/cmdline/server/http: - -HTTP -'''' - -.. option:: --no-http - - do not start the HTTP or long-polling workers (may still start :ref:`cron <reference/actions/cron>` - workers) - - .. warning:: has no effect if :option:`--test-enable` is set, as tests - require an accessible HTTP server - -.. option:: --http-interface <interface> - - TCP/IP address on which the HTTP server listens, defaults to ``0.0.0.0`` - (all addresses) - -.. option:: --http-port <port> - - Port on which the HTTP server listens, defaults to 8069. - -.. option:: --longpolling-port <port> - - TCP port for long-polling connections in multiprocessing or gevent mode, - defaults to 8072. Not used in default (threaded) mode. - -.. option:: --proxy-mode - - enables the use of ``X-Forwarded-*`` headers through `Werkzeug's proxy - support`_. - - .. warning:: proxy mode *must not* be enabled outside of a reverse proxy - scenario - -.. _reference/cmdline/server/logging: - -Logging -''''''' - -By default, Odoo displays all logging of level_ ``info`` except for workflow -logging (``warning`` only), and log output is sent to ``stdout``. Various -options are available to redirect logging to other destinations and to -customize the amount of logging output. - -.. option:: --logfile <file> - - sends logging output to the specified file instead of stdout. On Unix, the - file `can be managed by external log rotation programs - <https://docs.python.org/3/library/logging.handlers.html#watchedfilehandler>`_ - and will automatically be reopened when replaced - -.. option:: --syslog - - logs to the system's event logger: `syslog on unices <https://docs.python.org/3/library/logging.handlers.html#sysloghandler>`_ - and `the Event Log on Windows <https://docs.python.org/3/library/logging.handlers.html#nteventloghandler>`_. - - Neither is configurable - -.. option:: --log-db <dbname> - - logs to the ``ir.logging`` model (``ir_logging`` table) of the specified - database. The database can be the name of a database in the "current" - PostgreSQL, or `a PostgreSQL URI`_ for e.g. log aggregation. - -.. option:: --log-handler <handler-spec> - - :samp:`{LOGGER}:{LEVEL}`, enables ``LOGGER`` at the provided ``LEVEL`` - e.g. ``odoo.models:DEBUG`` will enable all logging messages at or above - ``DEBUG`` level in the models. - - * The colon ``:`` is mandatory - * The logger can be omitted to configure the root (default) handler - * If the level is omitted, the logger is set to ``INFO`` - - The option can be repeated to configure multiple loggers e.g. - - .. code-block:: console - - $ odoo-bin --log-handler :DEBUG --log-handler werkzeug:CRITICAL --log-handler odoo.fields:WARNING - -.. option:: --log-request - - enable DEBUG logging for RPC requests, equivalent to - ``--log-handler=odoo.http.rpc.request:DEBUG`` - -.. option:: --log-response - - enable DEBUG logging for RPC responses, equivalent to - ``--log-handler=odoo.http.rpc.response:DEBUG`` - -.. option:: --log-web - - enables DEBUG logging of HTTP requests and responses, equivalent to - ``--log-handler=odoo.http:DEBUG`` - -.. option:: --log-sql - - enables DEBUG logging of SQL querying, equivalent to - ``--log-handler=odoo.sql_db:DEBUG`` - -.. option:: --log-level <level> - - Shortcut to more easily set predefined levels on specific loggers. "real" - levels (``critical``, ``error``, ``warn``, ``debug``) are set on the - ``odoo`` and ``werkzeug`` loggers (except for ``debug`` which is only - set on ``odoo``). - - Odoo also provides debugging pseudo-levels which apply to different sets - of loggers: - - ``debug_sql`` - sets the SQL logger to ``debug`` - - equivalent to ``--log-sql`` - ``debug_rpc`` - sets the ``odoo`` and HTTP request loggers to ``debug`` - - equivalent to ``--log-level debug --log-request`` - ``debug_rpc_answer`` - sets the ``odoo`` and HTTP request and response loggers to - ``debug`` - - equivalent to ``--log-level debug --log-request --log-response`` - - .. note:: - - In case of conflict between :option:`--log-level` and - :option:`--log-handler`, the latter is used - -.. _reference/cdmline/workers: - -Multiprocessing -''''''''''''''' - -.. option:: --workers <count> - - if ``count`` is not 0 (the default), enables multiprocessing and sets up - the specified number of HTTP workers (sub-processes processing HTTP - and RPC requests). - - .. note:: multiprocessing mode is only available on Unix-based systems - - A number of options allow limiting and recycling workers: - - .. option:: --limit-request <limit> - - Number of requests a worker will process before being recycled and - restarted. - - Defaults to *8196*. - - .. option:: --limit-memory-soft <limit> - - Maximum allowed virtual memory per worker. If the limit is exceeded, - the worker is killed and recycled at the end of the current request. - - Defaults to *2048MiB*. - - .. option:: --limit-memory-hard <limit> - - Hard limit on virtual memory, any worker exceeding the limit will be - immediately killed without waiting for the end of the current request - processing. - - Defaults to *2560MiB*. - - .. option:: --limit-time-cpu <limit> - - Prevents the worker from using more than <limit> CPU seconds for each - request. If the limit is exceeded, the worker is killed. - - Defaults to *60*. - - .. option:: --limit-time-real <limit> - - Prevents the worker from taking longer than <limit> seconds to process - a request. If the limit is exceeded, the worker is killed. - - Differs from :option:`--limit-time-cpu` in that this is a "wall time" - limit including e.g. SQL queries. - - Defaults to *120*. - -.. option:: --max-cron-threads <count> - - number of workers dedicated to :ref:`cron <reference/actions/cron>` jobs. Defaults to *2*. - The workers are threads in multi-threading mode and processes in multi-processing mode. - - For multi-processing mode, this is in addition to the HTTP worker processes. - -.. _reference/cmdline/config: - -Configuration file -================== - -.. program:: odoo-bin - -Most of the command-line options can also be specified via a configuration -file. Most of the time, they use similar names with the prefix ``-`` removed -and other ``-`` are replaced by ``_`` e.g. :option:`--db-template` becomes -``db_template``. - -Some conversions don't match the pattern: - -* :option:`--db-filter` becomes ``dbfilter`` -* :option:`--no-http` corresponds to the ``http_enable`` boolean -* logging presets (all options starting with ``--log-`` except for - :option:`--log-handler` and :option:`--log-db`) just add content to - ``log_handler``, use that directly in the configuration file -* :option:`--smtp` is stored as ``smtp_server`` -* :option:`--database` is stored as ``db_name`` -* :option:`--i18n-import` and :option:`--i18n-export` aren't available at all - from configuration files - -The default configuration file is :file:`{$HOME}/.odoorc` which -can be overridden using :option:`--config <odoo-bin -c>`. Specifying -:option:`--save <odoo-bin -s>` will save the current configuration state back -to that file. The configuration items relative to the command-line are to be -specified in the section ``[options]``. - -Here is a sample file: - -.. code-block:: ini - - [options] - db_user=odoo - dbfilter=odoo - -.. _jinja2: http://jinja.pocoo.org -.. _regular expression: https://docs.python.org/3/library/re.html -.. _password authentication: - https://www.postgresql.org/docs/9.3/static/auth-methods.html#AUTH-PASSWORD -.. _template database: - https://www.postgresql.org/docs/9.3/static/manage-ag-templatedbs.html -.. _level: - https://docs.python.org/3/library/logging.html#logging.Logger.setLevel -.. _a PostgreSQL URI: - https://www.postgresql.org/docs/9.2/static/libpq-connect.html#AEN38208 -.. _Werkzeug's proxy support: - http://werkzeug.pocoo.org/docs/contrib/fixers/#werkzeug.contrib.fixers.ProxyFix -.. _pyinotify: https://github.com/seb-m/pyinotify/wiki - - -Shell -===== - -Odoo command-line also allows to launch odoo as a python console environment. -This enables direct interaction with the :ref:`orm <reference/orm>` and its functionalities. - - -.. code-block:: console - - $ odoo_bin shell - -.. option:: --shell-interface (ipython|ptpython|bpython|python) - - Specify a preferred REPL to use in shell mode. - - -.. _reference/cmdline/scaffold: - -Scaffolding -=========== - -.. program:: odoo-bin scaffold - -Scaffolding is the automated creation of a skeleton structure to simplify -bootstrapping (of new modules, in the case of Odoo). While not necessary it -avoids the tedium of setting up basic structures and looking up what all -starting requirements are. - -Scaffolding is available via the :command:`odoo-bin scaffold` subcommand. - -.. code-block:: console - - $ odoo_bin scaffold my_module /addons/ - -.. option:: name (required) - - the name of the module to create, may munged in various manners to - generate programmatic names (e.g. module directory name, model names, …) - -.. option:: destination (default=current directory) - - directory in which to create the new module, defaults to the current - directory - -.. option:: -t <template> - - a template directory, files are passed through jinja2_ then copied to - the ``destination`` directory - - -This will create module *my_module* in directory */addons/*. - -.. _reference/cmdline/populate: - -Database Population -=================== - -.. program:: odoo-bin populate - -Odoo CLI supports database population features. If the feature is -:ref:`implemented on a given model <reference/testing/populate/methods>`, it allows automatic data -generation of the model's records to test your modules in databases containing non-trivial amounts of records. - -.. code-block:: console - - $ odoo_bin populate - -.. option:: --models - - list of models for which the database should be filled - -.. option:: --size (small|medium|large) - - population size, the actual records number depends on the model's `_populate_sizes` attribute. - The generated records content is specified by the :meth:`~odoo.models._populate_factories` method - of a given model (cf. the :file:`populate` folder of modules for further details). - -.. seealso:: - - :ref:`reference/testing/populate` - - -Cloc -==== - -.. program:: odoo-bin cloc - -Odoo Cloc is a tool to count the number of relevant lines written in -Python, Javascript or XML. This can be used as a rough metric for pricing -maintenance of extra modules. - -Command-line options --------------------- -.. option:: -d <database>, --database <database> - -| Process the code of all extra modules installed on the provided database, - and of all server actions and computed fields manually created in the provided - database. -| The :option:`--addons-path` option is required to specify the path(s) to the - module folder(s). -| If combined with :option:`--path`, the count will be that of the sum of both - options' results (with possible overlaps). At least one of these two options is - required to specify which code to process. - -.. code-block:: console - - $ odoo-bin cloc --addons-path=addons -d my_database - -.. seealso:: - - :ref:`reference/cmdline/cloc/database-option` - - -.. option:: -p <path>, --path <path> - -| Process the files in the provided path. -| If combined with :option:`--database`, the count will be that of the sum of both - options' results (with possible overlaps). At least one of these two options is - required to specify which code to process. - -.. code-block:: console - - $ odoo-bin cloc -p addons/account - - -Multiple paths can be provided by repeating the option. - -.. code-block:: console - - $ odoo-bin cloc -p addons/account -p addons/sale - -.. seealso:: - - :ref:`reference/cmdline/cloc/path-option` - - -.. option:: --addons-path <directories> - -| Comma-separated list of directories in which modules are stored. These directories - are scanned for modules. -| Required if the :option:`--database` option is used. - - -.. option:: -c <directories> - -Specify a configuration file to use in place of the :option:`--addons-path` option. - -.. code-block:: console - - $ odoo-bin cloc -c config.conf -d my_database - - -.. option:: -v, --verbose - -Show the details of lines counted for each file. - - -Processed files ---------------- - -.. _reference/cmdline/cloc/database-option: - -With the :option:`--database` option -'''''''''''''''''''''''''''''''''''' - -Odoo Cloc counts the lines in each file of extra installed modules in a -given database. In addition, it counts the Python lines of server actions and -custom computed fields that have been directly created in the database or -imported. - -Some files are excluded from the count by default: - -- The manifest (:file:`__manifest__.py` or :file:`__openerp__.py`) -- The contents of the folder :file:`static/lib` -- The tests defined in the folder :file:`tests` and :file:`static/tests` -- The migrations scripts defined in the folder :file:`migrations` -- The XML files declared in the ``demo`` or ``demo_xml`` sections of the manifest - -For special cases, a list of files that should be ignored by Odoo Cloc can be defined -per module. This is specified by the ``cloc_exclude`` entry of the manifest: - -.. code-block:: python - - "cloc_exclude": [ - "lib/common.py", # exclude a single file - "data/*.xml", # exclude all XML files in a specific folder - "example/**/*", # exclude all files in a folder hierarchy recursively - ] - -| The pattern ``**/*`` can be used to ignore an entire module. This can be useful - to exclude a module from maintenance service costs. -| For more information about the pattern syntax, see `glob - <https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob>`_. - - - -.. _reference/cmdline/cloc/path-option: - -With the :option:`--path` option -'''''''''''''''''''''''''''''''' - -This method works the same as with the :ref:`--database option -<reference/cmdline/cloc/database-option>` if a manifest file is present in the given -folder. Otherwise, it counts all files. - - -Identifying Extra Modules -------------------------- - -To distinguish between standard and extra modules, Odoo Cloc uses the following heuristic: -modules that are located (real file system path, after following symbolic links) -in the same parent directory as the ``base``, ``web`` or ``web_enterprise`` -standard modules are considered standard. Other modules are treated as extra modules. - - -Error Handling --------------- - -Some file cannot be counted by Odoo Cloc. -Those file are reported at the end of the output. - -Max file size exceeded -'''''''''''''''''''''' - -Odoo Cloc rejects any file larger than 25MB. Usually, source files are smaller -than 1 MB. If a file is rejected, it may be: - -- A generated XML file that contains lots of data. It should be excluded in the manifest. -- A JavaScript library that should be placed in the :file:`static/lib` folder. - -Syntax Error -'''''''''''' - -Odoo Cloc cannot count the lines of code of a Python file with a syntax problem. -If an extra module contains such files, they should be fixed to allow the module to -load. If the module works despite the presence of those files, they are probably -not loaded and should therefore be removed from the module, or at least excluded -in the manifest via ``cloc_exclude``. - diff --git a/doc/reference/data.rst b/doc/reference/data.rst deleted file mode 100644 index 6dd0d7ccd9b..00000000000 --- a/doc/reference/data.rst +++ /dev/null @@ -1,301 +0,0 @@ -:banner: banners/data_files.jpg - -.. _reference/data: - -========== -Data Files -========== - -Odoo is greatly data-driven, and a big part of modules definition is thus -the definition of the various records it manages: UI (menus and views), -security (access rights and access rules), reports and plain data are all -defined via records. - -Structure -========= - -The main way to define data in Odoo is via XML data files: The broad structure -of an XML data file is the following: - -* Any number of operation elements within the root element ``odoo`` - -.. code-block:: xml - - <!-- the root elements of the data file --> - <?xml version="1.0" encoding="UTF-8"?> - <odoo> - <operation/> - ... - </odoo> - -Data files are executed sequentially, operations can only refer to the result -of operations defined previously - -.. note:: - - If the content of the data file is expected to be applied only once, you - can specify the odoo flag ``noupdate`` set to 1. If part of - the data in the file is expected to be applied once, you can place this part - of the file in a <data noupdate="1"> domain. - - .. code-block:: xml - - <odoo> - <data noupdate="1"> - <!-- Only loaded when installing the module (odoo-bin -i module) --> - <operation/> - </data> - - <!-- (Re)Loaded at install and update (odoo-bin -i/-u) --> - <operation/> - </odoo> - -Core operations -=============== - -.. _reference/data/record: - -``record`` ----------- - -``record`` appropriately defines or updates a database record, it has the -following attributes: - -``model`` (required) - name of the model to create (or update) -``id`` - the :term:`external identifier` for this record. It is strongly - recommended to provide one - - * for record creation, allows subsequent definitions to either modify or - refer to this record - * for record modification, the record to modify -``context`` - context to use when creating the record -``forcecreate`` - in update mode whether the record should be created if it doesn't exist - - Requires an :term:`external id`, defaults to ``True``. - -``field`` ----------- - -Each record can be composed of ``field`` tags, defining values to set when -creating the record. A ``record`` with no ``field`` will use all default -values (creation) or do nothing (update). - -A ``field`` has a mandatory ``name`` attribute, the name of the field to set, -and various methods to define the value itself: - -Nothing - if no value is provided for the field, an implicit ``False`` will be set - on the field. Can be used to clear a field, or avoid using a default value - for the field. -``search`` - for :ref:`relational fields <reference/fields/relational>`, should be - a :ref:`domain <reference/orm/domains>` on the field's model. - - Will evaluate the domain, search the field's model using it and set the - search's result as the field's value. Will only use the first result if - the field is a :class:`~odoo.fields.Many2one` -``ref`` - if a ``ref`` attribute is provided, its value must be a valid - :term:`external id`, which will be looked up and set as the field's value. - - Mostly for :class:`~odoo.fields.Many2one` and - :class:`~odoo.fields.Reference` fields -``type`` - if a ``type`` attribute is provided, it is used to interpret and convert - the field's content. The field's content can be provided through an - external file using the ``file`` attribute, or through the node's body. - - Available types are: - - ``xml``, ``html`` - extracts the ``field``'s children as a single document, evaluates - any :term:`external id` specified with the form ``%(external_id)s``. - ``%%`` can be used to output actual *%* signs. - ``file`` - ensures that the field content is a valid file path in the current - model, saves the pair :samp:`{module},{path}` as the field value - ``char`` - sets the field content directly as the field's value without - alterations - ``base64`` - base64_-encodes the field's content, useful combined with the ``file`` - *attribute* to load e.g. image data into attachments - ``int`` - converts the field's content to an integer and sets it as the field's - value - ``float`` - converts the field's content to a float and sets it as the field's - value - ``list``, ``tuple`` - should contain any number of ``value`` elements with the same - properties as ``field``, each element resolves to an item of a - generated tuple or list, and the generated collection is set as the - field's value -``eval`` - for cases where the previous methods are unsuitable, the ``eval`` - attributes simply evaluates whatever Python expression it is provided and - sets the result as the field's value. - - The evaluation context contains various modules (``time``, ``datetime``, - ``timedelta``, ``relativedelta``), a function to resolve :term:`external - identifiers` (``ref``) and the model object for the current field if - applicable (``obj``) - -``delete`` ----------- - -The ``delete`` tag can remove any number of records previously defined. It -has the following attributes: - -``model`` (required) - the model in which a specified record should be deleted -``id`` - the :term:`external id` of a record to remove -``search`` - a :ref:`domain <reference/orm/domains>` to find records of the model to - remove - -``id`` and ``search`` are exclusive - -``function`` ------------- - -The ``function`` tag calls a method on a model, with provided parameters. -It has two mandatory parameters ``model`` and ``name`` specifying respectively -the model and the name of the method to call. - -Parameters can be provided using ``eval`` (should evaluate to a sequence of -parameters to call the method with) or ``value`` elements (see ``list`` -values). - -.. code-block:: xml - - <odoo> - <data noupdate="1"> - <record name="partner_1" model="res.partner"> - <field name="name">Odude</field> - </record> - - <function model="res.partner" name="send_inscription_notice" - eval="[[ref('partner_1'), ref('partner_2')]]"/> - - <function model="res.users" name="send_vip_inscription_notice"> - <function eval="[[('vip','=',True)]]" model="res.partner" name="search"/> - </function> - </data> - - <record id="model_form_view" model="ir.ui.view"> - - </record> - </odoo> - -.. ignored assert - -.. _reference/data/shortcuts: - -Shortcuts -========= - -Because some important structural models of Odoo are complex and involved, -data files provide shorter alternatives to defining them using -:ref:`record tags <reference/data/record>`: - -``menuitem`` ------------- - -Defines an ``ir.ui.menu`` record with a number of defaults and fallbacks: - -``parent`` - * If a ``parent`` attribute is set, it should be the :term:`external id` - of an other menu item, used as the new item's parent - * If no ``parent`` is provided, tries to interpret the ``name`` attribute - as a ``/``-separated sequence of menu names and find a place in the menu - hierarchy. In that interpretation, intermediate menus are automatically - created - * Otherwise the menu is defined as a "top-level" menu item (*not* a menu - with no parent) -``name`` - If no ``name`` attribute is specified, tries to get the menu name from - a linked action if any. Otherwise uses the record's ``id`` -``groups`` - A ``groups`` attribute is interpreted as a comma-separated sequence of - :term:`external identifiers` for ``res.groups`` models. If an - :term:`external identifier` is prefixed with a minus (``-``), the group - is *removed* from the menu's groups -``action`` - if specified, the ``action`` attribute should be the :term:`external id` - of an action to execute when the menu is open -``id`` - the menu item's :term:`external id` - -.. _reference/data/template: - -``template`` ------------- - -Creates a :ref:`QWeb view <reference/views/qweb>` requiring only the ``arch`` -section of the view, and allowing a few *optional* attributes: - -``id`` - the view's :term:`external identifier` -``name``, ``inherit_id``, ``priority`` - same as the corresponding field on ``ir.ui.view`` (nb: ``inherit_id`` - should be an :term:`external identifier`) -``primary`` - if set to ``True`` and combined with a ``inherit_id``, defines the view - as a primary -``groups`` - comma-separated list of group :term:`external identifiers` -``page`` - if set to ``"True"``, the template is a website page (linkable to, - deletable) -``optional`` - ``enabled`` or ``disabled``, whether the view can be disabled (in the - website interface) and its default status. If unset, the view is always - enabled. - -.. deprecated act_window & report -.. ignored url, act_window and ir_set - -CSV data files -============== - -XML data files are flexible and self-descriptive, but very verbose when -creating a number of simple records of the same model in bulk. - -For this case, data files can also use csv_, this is often the case for -:ref:`access rights <reference/security/acl>`: - -* the file name is :file:`{model_name}.csv` -* the first row lists the fields to write, with the special field ``id`` - for :term:`external identifiers` (used for creation or update) -* each row thereafter creates a new record - -Here's the first lines of the data file defining US states -``res.country.state.csv`` - -.. literalinclude:: static/res.country.state.csv - :language: text - -rendered in a more readable format: - -.. csv-table:: - :file: static/res.country.state.csv - :header-rows: 1 - :class: table-striped table-hover table-condensed - -For each row (record): - -* the first column is the :term:`external id` of the record to create or - update -* the second column is the :term:`external id` of the country object to link - to (country objects must have been defined beforehand) -* the third column is the ``name`` field for ``res.country.state`` -* the fourth column is the ``code`` field for ``res.country.state`` - -.. _base64: https://tools.ietf.org/html/rfc3548.html#section-3 -.. _csv: https://en.wikipedia.org/wiki/Comma-separated_values diff --git a/doc/reference/guidelines.rst b/doc/reference/guidelines.rst deleted file mode 100644 index 09d8fbe1fd8..00000000000 --- a/doc/reference/guidelines.rst +++ /dev/null @@ -1,1158 +0,0 @@ -:banner: banners/odoo_guideline.jpg - -.. highlight:: python - -.. _reference/guidelines: - -=============== -Odoo Guidelines -=============== - -This page introduces the Odoo Coding Guidelines. Those aim to improve the -quality of Odoo Apps code. Indeed proper code improves readability, eases -maintenance, helps debugging, lowers complexity and promotes reliability. -These guidelines should be applied to every new module and to all new development. - -.. warning:: - - When modifying existing files in **stable version** the original file style - strictly supersedes any other style guidelines. In other words please never - modify existing files in order to apply these guidelines. It avoids disrupting - the revision history of code lines. Diff should be kept minimal. For more - details, see our `pull request guide <https://odoo.com/submit-pr>`_. - -.. warning:: - - When modifying existing files in **master (development) version** apply those - guidelines to existing code only for modified code or if most of the file is - under revision. In other words modify existing files structure only if it is - going under major changes. In that case first do a **move** commit then apply - the changes related to the feature. - -Module structure -================ - -Directories ------------ -A module is organized in important directories. Those contain the business logic; -having a look at them should make you understand the purpose of the module. - -- *data/* : demo and data xml -- *models/* : models definition -- *controllers/* : contains controllers (HTTP routes) -- *views/* : contains the views and templates -- *static/* : contains the web assets, separated into *css/, js/, img/, lib/, ...* - -Other optional directories compose the module. - -- *wizard/* : regroups the transient models (``models.TransientModel``) and their views -- *report/* : contains the printable reports and models based on SQL views. Python objects and XML views are included in this directory -- *tests/* : contains the Python tests - - -File naming ------------ - -File naming is important to quickly find information through all odoo addons. -This section explains how to name files in a standard odoo module. As an -example we use a `plant nursery <https://github.com/tivisse/odoodays-2018/tree/master/plant_nursery>`_ application. -It holds two main models *plant.nursery* and *plant.order*. - -Concerning *models*, split the business logic by sets of models belonging to -a same main model. Each set lies in a given file named based on its main model. -If there is only one model, its name is the same as the module name. Each -inherited model should be in its own file to help understanding of impacted -models. - -.. code-block:: text - - addons/plant_nursery/ - |-- models/ - | |-- plant_nursery.py (first main model) - | |-- plant_order.py (another main model) - | |-- res_partner.py (inherited Odoo model) - -Concerning *security* and access rights and rules two main files should be used. -First one is the definition of access rights done in a ``ir.model.access.csv`` -file. User groups are defined in ``<module>_groups.xml``. Access rules are -defined in ``<model>_security.xml``. - -.. code-block:: text - - addons/plant_nursery/ - |-- security/ - | |-- ir.model.access.csv - | |-- plant_nursery_groups.xml - | |-- plant_nursery_security.xml - | |-- plant_order_security.xml - -Concerning *views*, backend views should be split like models and suffixed -by ``_views.xml``. Backend views are list, form, kanban, activity, graph, pivot, .. -views. To ease split by model in views main menus not linked to specific actions -may be extracted into an optional ``<module>_menus.xml`` file. Templates (QWeb -pages used notably for portal / website display) and bundles (import of JS and -CSS assets) are put in separate files. Those are respectively -``<model>_templates.xml`` and ``assets.xml`` files. - -.. code-block:: text - - addons/plant_nursery/ - |-- views/ - | | -- assets.xml (import of JS / CSS) - | | -- plant_nursery_menus.xml (optional definition of main menus) - | | -- plant_nursery_views.xml (backend views) - | | -- plant_nursery_templates.xml (portal templates) - | | -- plant_order_views.xml - | | -- plant_order_templates.xml - | | -- res_partner_views.xml - -Concerning *data*, split them by purpose (demo or data) and main model. Filenames -will be the main_model name suffixed by ``_demo.xml`` or ``_data.xml``. For instance -for an application having demo and data for its main model as well as subtypes, -activities and mail templates all related to mail module: - -.. code-block:: text - - addons/plant_nursery/ - |-- data/ - | |-- plant_nursery_data.xml - | |-- plant_nursery_demo.xml - | |-- mail_data.xml - -Concerning *controllers*, generally all controllers belong to a single controller -contained in a file named ``<module_name>.py``. An old convention in Odoo is to -name this file ``main.py`` but it is considered as outdated. If you need to inherit -an existing controller from another module do it in ``<inherited_module_name>.py``. -For example adding portal controller in an application is done in ``portal.py``. - -.. code-block:: text - - addons/plant_nursery/ - |-- controllers/ - | |-- plant_nursery.py - | |-- portal.py (inheriting portal/controllers/portal.py) - | |-- main.py (deprecated, replaced by plant_nursery.py) - -Concerning *static files*, Javascript files follow globally the same logic as -python models. Each component should be in its own file with a meaningful name. -For instance, the activity widgets are located in ``activity.js`` of mail module. -Subdirectories can also be created to structure the 'package' (see web module -for more details). The same logic should be applied for the templates of JS -widgets (static XML files) and for their styles (scss files). Don't link -data (image, libraries) outside Odoo: do not use an URL to an image but copy -it in the codebase instead. - -Concerning *wizards*, naming convention is the same of for python models: -``<transient>.py`` and ``<transient>_views.xml``. Both are put in the wizard -directory. This naming comes from old odoo applications using the wizard -keyword for transient models. - -.. code-block:: text - - addons/plant_nursery/ - |-- wizard/ - | |-- make_plant_order.py - | |-- make_plant_order_views.xml - -Concerning *statistics reports* done with python / SQL views and classic views -naming is the following : - -.. code-block:: text - - addons/plant_nursery/ - |-- report/ - | |-- plant_order_report.py - | |-- plant_order_report_views.xml - -Concerning *printable reports* which contain mainly data preparation and Qweb -templates naming is the following : - -.. code-block:: text - - addons/plant_nursery/ - |-- report/ - | |-- plant_order_reports.xml (report actions, paperformat, ...) - | |-- plant_order_templates.xml (xml report templates) - -The complete tree of our Odoo module therefore looks like - -.. code-block:: text - - addons/plant_nursery/ - |-- __init__.py - |-- __manifest__.py - |-- controllers/ - | |-- __init__.py - | |-- plant_nursery.py - | |-- portal.py - |-- data/ - | |-- plant_nursery_data.xml - | |-- plant_nursery_demo.xml - | |-- mail_data.xml - |-- models/ - | |-- __init__.py - | |-- plant_nursery.py - | |-- plant_order.py - | |-- res_partner.py - |-- report/ - | |-- __init__.py - | |-- plant_order_report.py - | |-- plant_order_report_views.xml - | |-- plant_order_reports.xml (report actions, paperformat, ...) - | |-- plant_order_templates.xml (xml report templates) - |-- security/ - | |-- ir.model.access.csv - | |-- plant_nursery_groups.xml - | |-- plant_nursery_security.xml - | |-- plant_order_security.xml - |-- static/ - | |-- img/ - | | |-- my_little_kitten.png - | | |-- troll.jpg - | |-- lib/ - | | |-- external_lib/ - | |-- src/ - | | |-- js/ - | | | |-- widget_a.js - | | | |-- widget_b.js - | | |-- scss/ - | | | |-- widget_a.scss - | | | |-- widget_b.scss - | | |-- xml/ - | | | |-- widget_a.xml - | | | |-- widget_a.xml - |-- views/ - | |-- assets.xml - | |-- plant_nursery_menus.xml - | |-- plant_nursery_views.xml - | |-- plant_nursery_templates.xml - | |-- plant_order_views.xml - | |-- plant_order_templates.xml - | |-- res_partner_views.xml - |-- wizard/ - | |--make_plant_order.py - | |--make_plant_order_views.xml - -.. note:: File names should only contain ``[a-z0-9_]`` (lowercase - alphanumerics and ``_``) - -.. warning:: Use correct file permissions : folder 755 and file 644. - -.. _reference/guidelines/xml: - -XML files -========= - -Format ------- -To declare a record in XML, the **record** notation (using *<record>*) is recommended: - -- Place ``id`` attribute before ``model`` -- For field declaration, ``name`` attribute is first. Then place the - *value* either in the ``field`` tag, either in the ``eval`` - attribute, and finally other attributes (widget, options, ...) - ordered by importance. - -- Try to group the record by model. In case of dependencies between - action/menu/views, this convention may not be applicable. -- Use naming convention defined at the next point -- The tag *<data>* is only used to set not-updatable data with ``noupdate=1``. - If there is only not-updatable data in the file, the ``noupdate=1`` can be - set on the ``<odoo>`` tag and do not set a ``<data>`` tag. - -.. code-block:: xml - - <record id="view_id" model="ir.ui.view"> - <field name="name">view.name</field> - <field name="model">object_name</field> - <field name="priority" eval="16"/> - <field name="arch" type="xml"> - <tree> - <field name="my_field_1"/> - <field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" /> - </tree> - </field> - </record> - -Odoo supports custom tags acting as syntactic sugar: - -- menuitem: use it as a shortcut to declare a ``ir.ui.menu`` -- template: use it to declare a QWeb View requiring only the ``arch`` section of the view. -- report: use to declare a :ref:`report action <reference/actions/report>` -- act_window: use it if the record notation can't do what you want - -The 4 first tags are preferred over the *record* notation. - - -XML IDs and naming ------------------- - -Security, View and Action -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Use the following pattern : - -* For a menu: :samp:`{<model_name>}_menu`, or :samp:`{<model_name>}_menu_{do_stuff}` for submenus. -* For a view: :samp:`{<model_name>}_view_{<view_type>}`, where *view_type* is - ``kanban``, ``form``, ``tree``, ``search``, ... -* For an action: the main action respects :samp:`{<model_name>}_action`. - Others are suffixed with :samp:`_{<detail>}`, where *detail* is a - lowercase string briefly explaining the action. This is used only if - multiple actions are declared for the model. -* For window actions: suffix the action name by the specific view information - like :samp:`{<model_name>}_action_view_{<view_type>}`. -* For a group: :samp:`{<module_name>}_group_{<group_name>}` where *group_name* - is the name of the group, generally 'user', 'manager', ... -* For a rule: :samp:`{<model_name>}_rule_{<concerned_group>}` where - *concerned_group* is the short name of the concerned group ('user' - for the 'model_name_group_user', 'public' for public user, 'company' - for multi-company rules, ...). - -Name should be identical to xml id with dots replacing underscores. Actions -should have a real naming as it is used as display name. - -.. code-block:: xml - - <!-- views --> - <record id="model_name_view_form" model="ir.ui.view"> - <field name="name">model.name.view.form</field> - ... - </record> - - <record id="model_name_view_kanban" model="ir.ui.view"> - <field name="name">model.name.view.kanban</field> - ... - </record> - - <!-- actions --> - <record id="model_name_action" model="ir.act.window"> - <field name="name">Model Main Action</field> - ... - </record> - - <record id="model_name_action_child_list" model="ir.actions.act_window"> - <field name="name">Model Access Childs</field> - </record> - - <!-- menus and sub-menus --> - <menuitem - id="model_name_menu_root" - name="Main Menu" - sequence="5" - /> - <menuitem - id="model_name_menu_action" - name="Sub Menu 1" - parent="module_name.module_name_menu_root" - action="model_name_action" - sequence="10" - /> - - <!-- security --> - <record id="module_name_group_user" model="res.groups"> - ... - </record> - - <record id="model_name_rule_public" model="ir.rule"> - ... - </record> - - <record id="model_name_rule_company" model="ir.rule"> - ... - </record> - -Inheriting XML -~~~~~~~~~~~~~~ - -Xml Ids of inheriting views should use the same ID as the original record. -It helps finding all inheritance at a glance. As final Xml Ids are prefixed -by the module that creates them there is no overlap. - -Naming should contain an ``.inherit.{details}`` suffix to ease understanding -the override purpose when looking at its name. - -.. code-block:: xml - - <record id="model_view_form" model="ir.ui.view"> - <field name="name">model.view.form.inherit.module2</field> - <field name="inherit_id" ref="module1.model_view_form"/> - ... - </record> - -New primary views do not require the inherit suffix as those are new records -based upon the first one. - -.. code-block:: xml - - <record id="module2.model_view_form" model="ir.ui.view"> - <field name="name">model.view.form.module2</field> - <field name="inherit_id" ref="module1.model_view_form"/> - <field name="mode">primary</field> - ... - </record> - -.. _reference/guidelines/python: - -Python -====== - -.. warning:: - - Do not forget to read the :ref:`Security Pitfalls <reference/security/pitfalls>` - section as well to write secure code. - -PEP8 options ------------- - -Using a linter can help show syntax and semantic warnings or errors. Odoo -source code tries to respect Python standard, but some of them can be ignored. - -- E501: line too long -- E301: expected 1 blank line, found 0 -- E302: expected 2 blank lines, found 1 - -Imports -------- -The imports are ordered as - -#. External libraries (one per line sorted and split in python stdlib) -#. Imports of ``odoo`` -#. Imports from Odoo modules (rarely, and only if necessary) - -Inside these 3 groups, the imported lines are alphabetically sorted. - -.. code-block:: python - - # 1 : imports of python lib - import base64 - import re - import time - from datetime import datetime - # 2 : imports of odoo - import odoo - from odoo import api, fields, models, _ # alphabetically ordered - from odoo.tools.safe_eval import safe_eval as eval - # 3 : imports from odoo addons - from odoo.addons.website.models.website import slug - from odoo.addons.web.controllers.main import login_redirect - -Idiomatics of Programming (Python) ----------------------------------- - -- Each python file should have ``# -*- coding: utf-8 -*-`` as first line. -- Always favor *readability* over *conciseness* or using the language features or idioms. -- Don't use ``.clone()`` - -.. code-block:: python - - # bad - new_dict = my_dict.clone() - new_list = old_list.clone() - # good - new_dict = dict(my_dict) - new_list = list(old_list) - -- Python dictionary : creation and update - -.. code-block:: python - - # -- creation empty dict - my_dict = {} - my_dict2 = dict() - - # -- creation with values - # bad - my_dict = {} - my_dict['foo'] = 3 - my_dict['bar'] = 4 - # good - my_dict = {'foo': 3, 'bar': 4} - - # -- update dict - # bad - my_dict['foo'] = 3 - my_dict['bar'] = 4 - my_dict['baz'] = 5 - # good - my_dict.update(foo=3, bar=4, baz=5) - my_dict = dict(my_dict, **my_dict2) - -- Use meaningful variable/class/method names -- Useless variable : Temporary variables can make the code clearer by giving - names to objects, but that doesn't mean you should create temporary variables - all the time: - -.. code-block:: python - - # pointless - schema = kw['schema'] - params = {'schema': schema} - # simpler - params = {'schema': kw['schema']} - -- Multiple return points are OK, when they're simpler - -.. code-block:: python - - # a bit complex and with a redundant temp variable - def axes(self, axis): - axes = [] - if type(axis) == type([]): - axes.extend(axis) - else: - axes.append(axis) - return axes - - # clearer - def axes(self, axis): - if type(axis) == type([]): - return list(axis) # clone the axis - else: - return [axis] # single-element list - -- Know your builtins : You should at least have a basic understanding of all - the Python builtins (http://docs.python.org/library/functions.html) - -.. code-block:: python - - value = my_dict.get('key', None) # very very redundant - value = my_dict.get('key') # good - -Also, ``if 'key' in my_dict`` and ``if my_dict.get('key')`` have very different -meaning, be sure that you're using the right one. - -- Learn list comprehensions : Use list comprehension, dict comprehension, and - basic manipulation using ``map``, ``filter``, ``sum``, ... They make the code - easier to read. - -.. code-block:: python - - # not very good - cube = [] - for i in res: - cube.append((i['id'],i['name'])) - # better - cube = [(i['id'], i['name']) for i in res] - -- Collections are booleans too : In python, many objects have "boolean-ish" value - when evaluated in a boolean context (such as an if). Among these are collections - (lists, dicts, sets, ...) which are "falsy" when empty and "truthy" when containing - items: - -.. code-block:: python - - bool([]) is False - bool([1]) is True - bool([False]) is True - -So, you can write ``if some_collection:`` instead of ``if len(some_collection):``. - - -- Iterate on iterables - -.. code-block:: python - - # creates a temporary list and looks bar - for key in my_dict.keys(): - "do something..." - # better - for key in my_dict: - "do something..." - # accessing the key,value pair - for key, value in my_dict.items(): - "do something..." - -- Use dict.setdefault - -.. code-block:: python - - # longer.. harder to read - values = {} - for element in iterable: - if element not in values: - values[element] = [] - values[element].append(other_value) - - # better.. use dict.setdefault method - values = {} - for element in iterable: - values.setdefault(element, []).append(other_value) - -- As a good developer, document your code (docstring on methods, simple - comments for tricky part of code) -- In additions to these guidelines, you may also find the following link - interesting: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html - (a little bit outdated, but quite relevant) - -Programming in Odoo -------------------- - -- Avoid to create generators and decorators: only use the ones provided by - the Odoo API. -- As in python, use ``filtered``, ``mapped``, ``sorted``, ... methods to - ease code reading and performance. - - -Make your method work in batch -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When adding a function, make sure it can process multiple records by iterating -on self to treat each record. - -.. code-block:: python - - def my_method(self) - for record in self: - record.do_cool_stuff() - -For performance issue, when developing a 'stat button' (for instance), do not -perform a ``search`` or a ``search_count`` in a loop. It -is recommended to use ``read_group`` method, to compute all value in only one request. - -.. code-block:: python - - def _compute_equipment_count(self): - """ Count the number of equipment per category """ - equipment_data = self.env['hr.equipment'].read_group([('category_id', 'in', self.ids)], ['category_id'], ['category_id']) - mapped_data = dict([(m['category_id'][0], m['category_id_count']) for m in equipment_data]) - for category in self: - category.equipment_count = mapped_data.get(category.id, 0) - - -Propagate the context -~~~~~~~~~~~~~~~~~~~~~ -The context is a ``frozendict`` that cannot be modified. To call a method with -a different context, the ``with_context`` method should be used : - -.. code-block:: python - - records.with_context(new_context).do_stuff() # all the context is replaced - records.with_context(**additionnal_context).do_other_stuff() # additionnal_context values override native context ones - -.. warning:: - Passing parameter in context can have dangerous side-effects. - - Since the values are propagated automatically, some unexpected behavior may appear. - Calling ``create()`` method of a model with *default_my_field* key in context - will set the default value of *my_field* for the concerned model. - But if during this creation, other objects (such as sale.order.line, on sale.order creation) - having a field name *my_field* are created, their default value will be set too. - -If you need to create a key context influencing the behavior of some object, -choice a good name, and eventually prefix it by the name of the module to -isolate its impact. A good example are the keys of ``mail`` module : -*mail_create_nosubscribe*, *mail_notrack*, *mail_notify_user_signature*, ... - - -Think extendable -~~~~~~~~~~~~~~~~ - -Functions and methods should not contain too much logic: having a lot of small -and simple methods is more advisable than having few large and complex methods. -A good rule of thumb is to split a method as soon as it has more than one -responsibility (see http://en.wikipedia.org/wiki/Single_responsibility_principle). - -Hardcoding a business logic in a method should be avoided as it prevents to be -easily extended by a submodule. - -.. code-block:: python - - # do not do this - # modifying the domain or criteria implies overriding whole method - def action(self): - ... # long method - partners = self.env['res.partner'].search(complex_domain) - emails = partners.filtered(lambda r: arbitrary_criteria).mapped('email') - - # better but do not do this either - # modifying the logic forces to duplicate some parts of the code - def action(self): - ... - partners = self._get_partners() - emails = partners._get_emails() - - # better - # minimum override - def action(self): - ... - partners = self.env['res.partner'].search(self._get_partner_domain()) - emails = partners.filtered(lambda r: r._filter_partners()).mapped('email') - -The above code is over extendable for the sake of example but the readability -must be taken into account and a tradeoff must be made. - -Also, name your functions accordingly: small and properly named functions are -the starting point of readable/maintainable code and tighter documentation. - -This recommendation is also relevant for classes, files, modules and packages. -(See also http://en.wikipedia.org/wiki/Cyclomatic_complexity) - - -Never commit the transaction -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Odoo framework is in charge of providing the transactional context for -all RPC calls. The principle is that a new database cursor is opened at the -beginning of each RPC call, and committed when the call has returned, just -before transmitting the answer to the RPC client, approximately like this: - -.. code-block:: python - - def execute(self, db_name, uid, obj, method, *args, **kw): - db, pool = pooler.get_db_and_pool(db_name) - # create transaction cursor - cr = db.cursor() - try: - res = pool.execute_cr(cr, uid, obj, method, *args, **kw) - cr.commit() # all good, we commit - except Exception: - cr.rollback() # error, rollback everything atomically - raise - finally: - cr.close() # always close cursor opened manually - return res - -If any error occurs during the execution of the RPC call, the transaction is -rolled back atomically, preserving the state of the system. - -Similarly, the system also provides a dedicated transaction during the execution -of tests suites, so it can be rolled back or not depending on the server -startup options. - -The consequence is that if you manually call ``cr.commit()`` anywhere there is -a very high chance that you will break the system in various ways, because you -will cause partial commits, and thus partial and unclean rollbacks, causing -among others: - -#. inconsistent business data, usually data loss -#. workflow desynchronization, documents stuck permanently -#. tests that can't be rolled back cleanly, and will start polluting the - database, and triggering error (this is true even if no error occurs - during the transaction) - -Here is the very simple rule: - You should **NEVER** call ``cr.commit()`` yourself, **UNLESS** you have - created your own database cursor explicitly! And the situations where you - need to do that are exceptional! - - And by the way if you did create your own cursor, then you need to handle - error cases and proper rollback, as well as properly close the cursor when - you're done with it. - -And contrary to popular belief, you do not even need to call ``cr.commit()`` -in the following situations: -- in the ``_auto_init()`` method of an *models.Model* object: this is taken -care of by the addons initialization method, or by the ORM transaction when -creating custom models -- in reports: the ``commit()`` is handled by the framework too, so you can -update the database even from within a report -- within *models.Transient* methods: these methods are called exactly like -regular *models.Model* ones, within a transaction and with the corresponding -``cr.commit()/rollback()`` at the end -- etc. (see general rule above if you have in doubt!) - -All ``cr.commit()`` calls outside of the server framework from now on must -have an **explicit comment** explaining why they are absolutely necessary, why -they are indeed correct, and why they do not break the transactions. Otherwise -they can and will be removed ! - - -Use translation method correctly -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Odoo uses a GetText-like method named "underscore" ``_( )`` to indicate that -a static string used in the code needs to be translated at runtime using the -language of the context. This pseudo-method is accessed within your code by -importing as follows: - -.. code-block:: python - - from odoo import _ - -A few very important rules must be followed when using it, in order for it to -work and to avoid filling the translations with useless junk. - -Basically, this method should only be used for static strings written manually -in the code, it will not work to translate field values, such as Product names, -etc. This must be done instead using the translate flag on the corresponding -field. - -The method accepts optional positional or named parameter -The rule is very simple: calls to the underscore method should always be in -the form ``_('literal string')`` and nothing else: - -.. code-block:: python - - # good: plain strings - error = _('This record is locked!') - - # good: strings with formatting patterns included - error = _('Record %s cannot be modified!', record) - - # ok too: multi-line literal strings - error = _("""This is a bad multiline example - about record %s!""", record) - error = _('Record %s cannot be modified' \ - 'after being validated!', record) - - # bad: tries to translate after string formatting - # (pay attention to brackets!) - # This does NOT work and messes up the translations! - error = _('Record %s cannot be modified!' % record) - - # bad: formatting outside of translation - # This won't benefit from fallback mechanism in case of bad translation - error = _('Record %s cannot be modified!') % record - - # bad: dynamic string, string concatenation, etc are forbidden! - # This does NOT work and messes up the translations! - error = _("'" + que_rec['question'] + "' \n") - - # bad: field values are automatically translated by the framework - # This is useless and will not work the way you think: - error = _("Product %s is out of stock!") % _(product.name) - # and the following will of course not work as already explained: - error = _("Product %s is out of stock!" % product.name) - - # Instead you can do the following and everything will be translated, - # including the product name if its field definition has the - # translate flag properly set: - error = _("Product %s is not available!", product.name) - - -Also, keep in mind that translators will have to work with the literal values -that are passed to the underscore function, so please try to make them easy to -understand and keep spurious characters and formatting to a minimum. Translators -must be aware that formatting patterns such as ``%s`` or ``%d``, newlines, etc. -need to be preserved, but it's important to use these in a sensible and obvious -manner: - -.. code-block:: python - - # Bad: makes the translations hard to work with - error = "'" + question + _("' \nPlease enter an integer value ") - - # Ok (pay attention to position of the brackets too!) - error = _("Answer to question %s is not valid.\n" \ - "Please enter an integer value.", question) - - # Better - error = _("Answer to question %(title)s is not valid.\n" \ - "Please enter an integer value.", title=question) - -In general in Odoo, when manipulating strings, prefer ``%`` over ``.format()`` -(when only one variable to replace in a string), and prefer ``%(varname)`` instead -of position (when multiple variables have to be replaced). This makes the -translation easier for the community translators. - - -Symbols and Conventions ------------------------ - -- Model name (using the dot notation, prefix by the module name) : - - When defining an Odoo Model : use singular form of the name (*res.partner* - and *sale.order* instead of *res.partnerS* and *saleS.orderS*) - - When defining an Odoo Transient (wizard) : use ``<related_base_model>.<action>`` - where *related_base_model* is the base model (defined in *models/*) related - to the transient, and *action* is the short name of what the transient do. Avoid the *wizard* word. - For instance : ``account.invoice.make``, ``project.task.delegate.batch``, ... - - When defining *report* model (SQL views e.i.) : use - ``<related_base_model>.report.<action>``, based on the Transient convention. - -- Odoo Python Class : use camelcase (Object-oriented style). - - -.. code-block:: python - - class AccountInvoice(models.Model): - ... - -- Variable name : - - use camelcase for model variable - - use underscore lowercase notation for common variable. - - suffix your variable name with *_id* or *_ids* if it contains a record id or list of id. Don't use ``partner_id`` to contain a record of res.partner - -.. code-block:: python - - Partner = self.env['res.partner'] - partners = Partner.browse(ids) - partner_id = partners[0].id - -- ``One2Many`` and ``Many2Many`` fields should always have *_ids* as suffix (example: sale_order_line_ids) -- ``Many2One`` fields should have *_id* as suffix (example : partner_id, user_id, ...) -- Method conventions - - Compute Field : the compute method pattern is *_compute_<field_name>* - - Search method : the search method pattern is *_search_<field_name>* - - Default method : the default method pattern is *_default_<field_name>* - - Selection method: the selection method pattern is *_selection_<field_name>* - - Onchange method : the onchange method pattern is *_onchange_<field_name>* - - Constraint method : the constraint method pattern is *_check_<constraint_name>* - - Action method : an object action method is prefix with *action_*. - Since it uses only one record, add ``self.ensure_one()`` - at the beginning of the method. - -- In a Model attribute order should be - #. Private attributes (``_name``, ``_description``, ``_inherit``, ...) - #. Default method and ``_default_get`` - #. Field declarations - #. Compute, inverse and search methods in the same order as field declaration - #. Selection method (methods used to return computed values for selection fields) - #. Constrains methods (``@api.constrains``) and onchange methods (``@api.onchange``) - #. CRUD methods (ORM overrides) - #. Action methods - #. And finally, other business methods. - -.. code-block:: python - - class Event(models.Model): - # Private attributes - _name = 'event.event' - _description = 'Event' - - # Default methods - def _default_name(self): - ... - - # Fields declaration - name = fields.Char(string='Name', default=_default_name) - seats_reserved = fields.Integer(oldname='register_current', string='Reserved Seats', - store=True, readonly=True, compute='_compute_seats') - seats_available = fields.Integer(oldname='register_avail', string='Available Seats', - store=True, readonly=True, compute='_compute_seats') - price = fields.Integer(string='Price') - event_type = fields.Selection(string="Type", selection='_selection_type') - - # compute and search fields, in the same order of fields declaration - @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register') - def _compute_seats(self): - ... - - @api.model - def _selection_type(self): - return [] - - # Constraints and onchanges - @api.constrains('seats_max', 'seats_available') - def _check_seats_limit(self): - ... - - @api.onchange('date_begin') - def _onchange_date_begin(self): - ... - - # CRUD methods (and name_get, name_search, ...) overrides - def create(self, values): - ... - - # Action methods - def action_validate(self): - self.ensure_one() - ... - - # Business methods - def mail_user_confirm(self): - ... - -.. _reference/guidelines/js: - -Javascript and CSS -================== - -Static files organization --------------------------- - -Odoo addons have some conventions on how to structure various files. We explain -here in more details how web assets are supposed to be organized. - -The first thing to know is that the Odoo server will serve (statically) all files -located in a *static/* folder, but prefixed with the addon name. So, for example, -if a file is located in *addons/web/static/src/js/some_file.js*, then it will be -statically available at the url *your-odoo-server.com/web/static/src/js/some_file.js* - -The convention is to organize the code according to the following structure: - -- *static*: all static files in general - - - *static/lib*: this is the place where js libs should be located, in a sub folder. - So, for example, all files from the *jquery* library are in *addons/web/static/lib/jquery* - - *static/src*: the generic static source code folder - - - *static/src/css*: all css files - - *static/src/fonts* - - *static/src/img* - - *static/src/js* - - - *static/src/js/tours*: end user tour files (tutorials, not tests) - - - *static/src/scss*: scss files - - *static/src/xml*: all qweb templates that will be rendered in JS - - - *static/tests*: this is where we put all test related files. - - - *static/tests/tours*: this is where we put all tour test files (not tutorials). - -Javascript coding guidelines ----------------------------- - -- ``use strict;`` is recommended for all javascript files -- Use a linter (jshint, ...) -- Never add minified Javascript Libraries -- Use camelcase for class declaration - -More precise JS guidelines are detailed in the `github wiki <https://github.com/odoo/odoo/wiki/Javascript-coding-guidelines>`_. -You may also have a look at existing API in Javascript by looking Javascript -References. - -CSS coding guidelines ---------------------- - -- Prefix all your classes with *o_<module_name>* where *module_name* is the - technical name of the module ('sale', 'im_chat', ...) or the main route - reserved by the module (for website module mainly, i.e. : 'o_forum' for - *website_forum* module). The only exception for this rule is the - webclient: it simply uses *o_* prefix. -- Avoid using *id* tag -- Use Bootstrap native classes -- Use underscore lowercase notation to name class - -.. _reference/guidelines/git: - -Git -=== - -Configure your git ------------------- - -Based on ancestral experience and oral tradition, the following things go a long -way towards making your commits more helpful: - -- Be sure to define both the user.email and user.name in your local git config - - .. code-block:: text - - git config --global <var> <value> - -- Be sure to add your full name to your Github profile here. Please feel fancy - and add your team, avatar, your favorite quote, and whatnot ;-) - -Commit message structure ------------------------- - -Commit message has four parts: tag, module, short description and full -description. Try to follow the preferred structure for your commit messages - -.. code-block:: text - - [TAG] module: describe your change in a short sentence (ideally < 50 chars) - - Long version of the change description, including the rationale for the change, - or a summary of the feature being introduced. - - Please spend a lot more time describing WHY the change is being done rather - than WHAT is being changed. This is usually easy to grasp by actually reading - the diff. WHAT should be explained only if there are technical choices - or decision involved. In that case explain WHY this decision was taken. - - End the message with references, such as task or bug numbers, PR numbers, and - OPW tickets, following the suggested format: - task-123 (related to task) - Fixes #123 (close related issue on Github) - Closes #123 (close related PR on Github) - opw-123 (related to ticket) - -Tag and module name -------------------- - -Tags are used to prefix your commit. They should be one of the following - -- **[FIX]** for bug fixes: mostly used in stable version but also valid if you - are fixing a recent bug in development version; -- **[REF]** for refactoring: when a feature is heavily rewritten; -- **[ADD]** for adding new modules; -- **[REM]** for removing resources: removing dead code, removing views, - removing modules, ...; -- **[REV]** for reverting commits: if a commit causes issues or is not wanted - reverting it is done using this tag; -- **[MOV]** for moving files: use git move and do not change content of moved file - otherwise Git may loose track and history of the file; also used when moving - code from one file to another; -- **[REL]** for release commits: new major or minor stable versions; -- **[IMP]** for improvements: most of the changes done in development version - are incremental improvements not related to another tag; -- **[MERGE]** for merge commits: used in forward port of bug fixes but also as - main commit for feature involving several separated commits; -- **[CLA]** for signing the Odoo Individual Contributor License; -- **[I18N]** for changes in translation files; - -After tag comes the modified module name. Use the technical name as functional -name may change with time. If several modules are modified, list them or use -various to tell it is cross-modules. Unless really required or easier avoid -modifying code across several modules in the same commit. Understanding module -history may become difficult. - -Commit message header ---------------------- - -After tag and module name comes a meaningful commit message header. It should be -self explanatory and include the reason behind the change. Do not use single words -like "bugfix" or "improvements". Try to limit the header length to about 50 characters -for readability. - -Commit message header should make a valid sentence once concatenated with -``if applied, this commit will <header>``. For example ``[IMP] base: prevent to -archive users linked to active partners`` is correct as it makes a valid sentence -``if applied, this commit will prevent users to archive...``. - -Commit message full description -------------------------------- - -In the message description specify the part of the code impacted by your changes -(module name, lib, transversal object, ...) and a description of the changes. - -First explain WHY you are modifying code. What is important if someone goes back -to your commit in about 4 decades (or 3 days) is why you did it. It is the -purpose of the change. - -What you did can be found in the commit itself. If there was some technical choices -involved it is a good idea to explain it also in the commit message after the why. -For Odoo R&D developers "PO team asked me to do it" is not a valid why, by the way. - -Please avoid commits which simultaneously impact multiple modules. Try to split -into different commits where impacted modules are different. It will be helpful -if we need to revert changes in a given module separately. - -Don't hesitate to be a bit verbose. Most people will only see your commit message -and judge everything you did in your life just based on those few sentences. -No pressure at all. - -**You spend several hours, days or weeks working on meaningful features. Take -some time to calm down and write clear and understandable commit messages.** - -If you are an Odoo R&D developer the WHY should be the purpose of the task you -are working on. Full specifications make the core of the commit message. -**If you are working on a task that lacks purpose and specifications please -consider making them clear before continuing.** - -Finally here are some examples of correct commit messages : - -.. code-block:: text - - [REF] models: use `parent_path` to implement parent_store - - This replaces the former modified preorder tree traversal (MPTT) with the - fields `parent_left`/`parent_right`[...] - - [FIX] account: remove frenglish - - [...] - - Closes #22793 - Fixes #22769 - - [FIX] website: remove unused alert div, fixes look of input-group-btn - - Bootstrap's CSS depends on the input-group-btn - element being the first/last child of its parent. - This was not the case because of the invisible - and useless alert. - -.. note:: Use the long description to explain the *why* not the - *what*, the *what* can be seen in the diff diff --git a/doc/reference/http.rst b/doc/reference/http.rst deleted file mode 100644 index 0675847aaa0..00000000000 --- a/doc/reference/http.rst +++ /dev/null @@ -1,87 +0,0 @@ -:banner: banners/web_controllers.jpg - -.. _reference/controllers: - -=============== -Web Controllers -=============== - -Controllers -=========== - -Controllers need to provide extensibility, much like -:class:`~odoo.models.Model`, but can't use the same mechanism as the -pre-requisites (a database with loaded modules) may not be available yet (e.g. -no database created, or no database selected). - -Controllers thus provide their own extension mechanism, separate from that of -models: - -Controllers are created by :ref:`inheriting <python:tut-inheritance>` from :class:`~odoo.http.Controller`. -Routes are defined through methods decorated with :func:`~odoo.http.route`:: - - class MyController(odoo.http.Controller): - @route('/some_url', auth='public') - def handler(self): - return stuff() - -To *override* a controller, :ref:`inherit <python:tut-inheritance>` from its -class and override relevant methods, re-exposing them if necessary:: - - class Extension(MyController): - @route() - def handler(self): - do_before() - return super(Extension, self).handler() - -* decorating with :func:`~odoo.http.route` is necessary to keep the method - (and route) visible: if the method is redefined without decorating, it - will be "unpublished" -* the decorators of all methods are combined, if the overriding method's - decorator has no argument all previous ones will be kept, any provided - argument will override previously defined ones e.g.:: - - class Restrict(MyController): - @route(auth='user') - def handler(self): - return super(Restrict, self).handler() - - will change ``/some_url`` from public authentication to user (requiring a - log-in) - -API -=== - -.. _reference/http/routing: - -Routing -------- - -.. autofunction:: odoo.http.route - -.. _reference/http/request: - -Request -------- - -The request object is automatically set on :data:`odoo.http.request` at -the start of the request - -.. autoclass:: odoo.http.WebRequest - :members: - :member-order: bysource -.. autoclass:: odoo.http.HttpRequest - :members: -.. autoclass:: odoo.http.JsonRequest - :members: - -Response --------- - -.. autoclass:: odoo.http.Response - :members: - :member-order: bysource - - .. maybe set this to document all the fine methods on Werkzeug's Response - object? (it works) - :inherited-members: diff --git a/doc/reference/images/mobile_contact_create.png b/doc/reference/images/mobile_contact_create.png Binary files differdeleted file mode 100644 index 53ca9e4998f..00000000000 --- a/doc/reference/images/mobile_contact_create.png +++ /dev/null diff --git a/doc/reference/images/mobile_notification.png b/doc/reference/images/mobile_notification.png Binary files differdeleted file mode 100644 index ea72d7a6d24..00000000000 --- a/doc/reference/images/mobile_notification.png +++ /dev/null diff --git a/doc/reference/images/mobile_switch_account.png b/doc/reference/images/mobile_switch_account.png Binary files differdeleted file mode 100644 index 3c7da3e2c31..00000000000 --- a/doc/reference/images/mobile_switch_account.png +++ /dev/null diff --git a/doc/reference/images/mobile_working.jpg b/doc/reference/images/mobile_working.jpg Binary files differdeleted file mode 100644 index 8243530f715..00000000000 --- a/doc/reference/images/mobile_working.jpg +++ /dev/null diff --git a/doc/reference/images/odoo_mobile_api.png b/doc/reference/images/odoo_mobile_api.png Binary files differdeleted file mode 100644 index 8fdf37942f4..00000000000 --- a/doc/reference/images/odoo_mobile_api.png +++ /dev/null diff --git a/doc/reference/images/runner.png b/doc/reference/images/runner.png Binary files differdeleted file mode 100644 index fba0121caf5..00000000000 --- a/doc/reference/images/runner.png +++ /dev/null diff --git a/doc/reference/images/runner2.png b/doc/reference/images/runner2.png Binary files differdeleted file mode 100644 index 45bb5c5585b..00000000000 --- a/doc/reference/images/runner2.png +++ /dev/null diff --git a/doc/reference/images/snackbar.png b/doc/reference/images/snackbar.png Binary files differdeleted file mode 100644 index 76ad0f1a9ad..00000000000 --- a/doc/reference/images/snackbar.png +++ /dev/null diff --git a/doc/reference/images/tests.png b/doc/reference/images/tests.png Binary files differdeleted file mode 100644 index 11552c0539d..00000000000 --- a/doc/reference/images/tests.png +++ /dev/null diff --git a/doc/reference/images/tests2.png b/doc/reference/images/tests2.png Binary files differdeleted file mode 100644 index b51da6b9e72..00000000000 --- a/doc/reference/images/tests2.png +++ /dev/null diff --git a/doc/reference/images/tests3.png b/doc/reference/images/tests3.png Binary files differdeleted file mode 100644 index e89da505f52..00000000000 --- a/doc/reference/images/tests3.png +++ /dev/null diff --git a/doc/reference/images/toast.png b/doc/reference/images/toast.png Binary files differdeleted file mode 100644 index d6f5298a722..00000000000 --- a/doc/reference/images/toast.png +++ /dev/null diff --git a/doc/reference/iot.rst b/doc/reference/iot.rst deleted file mode 100644 index 2a7e8d66eb1..00000000000 --- a/doc/reference/iot.rst +++ /dev/null @@ -1,195 +0,0 @@ -:banner: banners/iot.jpg - -================== -Internet of Things -================== - -IoT Drivers allow any Odoo module to communicate in real-time with any device -connected to the IoT Box. Communication with the IoT Box goes both ways, so the -Odoo client can send commands to and receive information from any of the -supported devices. - -To add support for a device, all we need is: - -- an `Interface`, to detect connected devices of a specific type -- a `Driver`, to communicate with an individual device - -At each boot, the IoT Box will load all of the Interfaces and Drivers that can -be located on the connected Odoo instance. Each module can contain an -`iot_handlers` directory that will be copied to the IoT Box. The structure of -this directory is the following - -.. code-block:: text - - your_module - ├── ... - └── iot_handlers - ├── drivers - │ ├── DriverName.py - │ └── ... - │ - └── interfaces - ├── InterfaceName.py - └── ... - -Detect Devices -============== - -Devices connected to the IoT Box are detected through `Interfaces`. There is an -Interface for each supported connection type (USB, Bluetooth, Video, -Printers, Serial, etc.). The interface maintains a list of detected devices -and associates them with the right Driver. - -Supported devices will appear both on the IoT Box Homepage that you can access -through its IP address and in the IoT module of the connected Odoo instance. - -Interface ---------- - -The role of the Interface is to maintain a list of devices connected through a -determined connection type. Creating a new interface requires - -- Extending the `Interface` class -- Setting the `connection_type` class attribute -- Implementing the `get_devices` method, that should return a dictionary - containing data about each detected device. This data will be given as - argument to the constructors and `supported` method of the Drivers. - -.. note:: - Setting the `_loop_delay` attribute will modify the interval between calls - to `get_devices`. By default, this interval is set to 3 seconds. - -.. code-block:: python - - from odoo.addons.hw_drivers.interface import Interface - - class InterfaceName(Interface): - connection_type = 'ConnectionType' - - def get_devices(self): - return { - 'device_identifier_1': {...}, - ... - } - -Driver ------- - -Once the interface has retrieved the list of detected devices, it will loop -through all of the Drivers that have the same `connection_type` attribute and -test their respective `supported` method on all detected devices. If the -supported method of a Driver returns `True`, an instance of this Driver will be -created for the corresponding device. - -.. note:: - `supported` methods of drivers are given a priority order. The `supported` - method of a child class will always be tested before the one of its parent. - This priority can be adjusted by modifying the `priority` attribute of the - Driver. - -Creating a new Driver requires: - -- Extending `Driver` -- Setting the `connection_type` class attribute. -- Setting the `device_type`, `device_connection` and `device_name` attributes. -- Defining the `supported` method - -.. code-block:: python - - from odoo.addons.hw_drivers.driver import Driver - - class DriverName(Driver): - connection_type = 'ConnectionType' - - def __init__(self, identifier, device): - super(NewDriver, self).__init__(identifier, device) - self.device_type = 'DeviceType' - self.device_connection = 'DeviceConnection' - self.device_name = 'DeviceName' - - @classmethod - def supported(cls, device): - ... - -Communicate With Devices -======================== - -Once your new device is detected and appears in the IoT module, the next step -is to communicate with it. Since the box only has a local IP address, it can -only be reached from the same local network. Communication, therefore, needs to -happen on the browser-side, in JavaScript. - -The process depends on the direction of the communication: -- From the browser to the box, through `Actions`_ -- From the box to the browser, through `Longpolling`_ - -Both channels are accessed from the same JS object, the `DeviceProxy`, which is -instantiated using the IP of the IoT Box and the device identifier. - -.. code-block:: javascript - - var DeviceProxy = require('iot.DeviceProxy'); - - var iot_device = new DeviceProxy({ - iot_ip: iot_ip, - identifier: device_identifier - }); - -Actions -------- - -Actions are used to tell a selected device to execute a specific action, -such as taking a picture, printing a receipt, etc. - -.. note:: - It must be noted that no “answer” will be sent by the box on this route, - only the request status. The answer to the action, if any, has to be - retrieved via the longpolling. - -An action can be performed on the DeviceProxy Object. - -.. code-block:: javascript - - iot_device.action(data); - -In your driver, define an `action` method that will be executed when called -from an Odoo module. It takes the data given during the call as argument. - -.. code-block:: python - - def action(self, data): - ... - -Longpolling ------------ - -When any module in Odoo wants to read data from a specific device, it creates a -listener identified by the IP/domain of the box and the device identifier and -passes it a callback function to be called every time the device status -changes. The callback is called with the new data as argument. - -.. code-block:: javascript - - iot_device.add_listener(this._onValueChange.bind(this)); - - _onValueChange: function (result) { - ... - } - -In the Driver, an event is released by calling the `device_changed` function -from the `event_manager`. All callbacks set on the listener will then be called -with `self.data` as argument. - -.. code-block:: python - - from odoo.addons.hw_drivers.event_manager import event_manager - - class DriverName(Driver): - connection_type = 'ConnectionType' - - def methodName(self): - self.data = { - 'value': 0.5, - ... - } - event_manager.device_changed(self) diff --git a/doc/reference/javascript_cheatsheet.rst b/doc/reference/javascript_cheatsheet.rst deleted file mode 100644 index 1dfa1937d00..00000000000 --- a/doc/reference/javascript_cheatsheet.rst +++ /dev/null @@ -1,545 +0,0 @@ -:banner: banners/javascript.jpg - -.. _reference/jscs: - -===================== -Javascript Cheatsheet -===================== - -There are many ways to solve a problem in JavaScript, and in Odoo. However, the -Odoo framework was designed to be extensible (this is a pretty big constraint), -and some common problems have a nice standard solution. The standard solution -has probably the advantage of being easy to understand for an odoo developers, -and will probably keep working when Odoo is modified. - -This document tries to explain the way one could solve some of these issues. -Note that this is not a reference. This is just a random collection of recipes, -or explanations on how to proceed in some cases. - - -First of all, remember that the first rule of customizing odoo with JS is: -*try to do it in python*. This may seem strange, but the python framework is -quite extensible, and many behaviours can be done simply with a touch of xml or -python. This has usually a lower cost of maintenance than working with JS: - -- the JS framework tends to change more, so JS code needs to be more frequently - updated -- it is often more difficult to implement a customized behaviour if it needs to - communicate with the server and properly integrate with the javascript framework. - There are many small details taken care by the framework that customized code - needs to replicate. For example, responsiveness, or updating the url, or - displaying data without flickering. - - -.. note:: This document does not really explain any concepts. This is more a - cookbook. For more details, please consult the javascript reference - page (see :doc:`javascript_reference`) - -Creating a new field widget -=========================== - -This is probably a really common usecase: we want to display some information in -a form view in a really specific (maybe business dependent) way. For example, -assume that we want to change the text color depending on some business condition. - -This can be done in three steps: creating a new widget, registering it in the -field registry, then adding the widget to the field in the form view - -- creating a new widget: - This can be done by extending a widget: - - .. code-block:: javascript - - var FieldChar = require('web.basic_fields').FieldChar; - - var CustomFieldChar = FieldChar.extend({ - _renderReadonly: function () { - // implement some custom logic here - }, - }); - -- registering it in the field registry: - The web client needs to know the mapping between a widget name and its - actual class. This is done by a registry: - - .. code-block:: javascript - - var fieldRegistry = require('web.field_registry'); - - fieldRegistry.add('my-custom-field', CustomFieldChar); - -- adding the widget in the form view - .. code-block:: xml - - <field name="somefield" widget="my-custom-field"/> - - Note that only the form, list and kanban views use this field widgets registry. - These views are tightly integrated, because the list and kanban views can - appear inside a form view). - -Modifying an existing field widget -================================== - -Another use case is that we want to modify an existing field widget. For -example, the voip addon in odoo need to modify the FieldPhone widget to add the -possibility to easily call the given number on voip. This is done by *including* -the FieldPhone widget, so there is no need to change any existing form view. - -Field Widgets (instances of (subclass of) AbstractField) are like every other -widgets, so they can be monkey patched. This looks like this: - -.. code-block:: javascript - - var basic_fields = require('web.basic_fields'); - var Phone = basic_fields.FieldPhone; - - Phone.include({ - events: _.extend({}, Phone.prototype.events, { - 'click': '_onClick', - }), - - _onClick: function (e) { - if (this.mode === 'readonly') { - e.preventDefault(); - var phoneNumber = this.value; - // call the number on voip... - } - }, - }); - -Note that there is no need to add the widget to the registry, since it is already -registered. - -Modifying a main widget from the interface -========================================== - -Another common usecase is the need to customize some elements from the user -interface. For example, adding a message in the home menu. The usual process -in this case is again to *include* the widget. This is the only way to do it, -since there are no registries for those widgets. - -This is usually done with code looking like this: - -.. code-block:: javascript - - var HomeMenu = require('web_enterprise.HomeMenu'); - - HomeMenu.include({ - render: function () { - this._super(); - // do something else here... - }, - }); - - - -Creating a new view (from scratch) -================================== - -Creating a new view is a more advanced topic. This cheatsheet will only -highlight the steps that will probably need to be done (in no particular order): - -- adding a new view type to the field ``type`` of ``ir.ui.view``:: - - class View(models.Model): - _inherit = 'ir.ui.view' - - type = fields.Selection(selection_add=[('map', "Map")]) - -- adding the new view type to the field ``view_mode`` of ``ir.actions.act_window.view``:: - - class ActWindowView(models.Model): - _inherit = 'ir.actions.act_window.view' - - view_mode = fields.Selection(selection_add=[('map', "Map")]) - - -- creating the four main pieces which makes a view (in JavaScript): - we need a view (a subclass of ``AbstractView``, this is the factory), a - renderer (from ``AbstractRenderer``), a controller (from ``AbstractController``) - and a model (from ``AbstractModel``). I suggest starting by simply - extending the superclasses: - - .. code-block:: javascript - - var AbstractController = require('web.AbstractController'); - var AbstractModel = require('web.AbstractModel'); - var AbstractRenderer = require('web.AbstractRenderer'); - var AbstractView = require('web.AbstractView'); - - var MapController = AbstractController.extend({}); - var MapRenderer = AbstractRenderer.extend({}); - var MapModel = AbstractModel.extend({}); - - var MapView = AbstractView.extend({ - config: { - Model: MapModel, - Controller: MapController, - Renderer: MapRenderer, - }, - }); - -- adding the view to the registry: - As usual, the mapping between a view type and the actual class needs to be - updated: - - .. code-block:: javascript - - var viewRegistry = require('web.view_registry'); - - viewRegistry.add('map', MapView); - -- implementing the four main classes: - The ``View`` class needs to parse the ``arch`` field and setup the other - three classes. The ``Renderer`` is in charge of representing the data in - the user interface, the ``Model`` is supposed to talk to the server, to - load data and process it. And the ``Controller`` is there to coordinate, - to talk to the web client, ... - -- creating some views in the database: - .. code-block:: xml - - <record id="customer_map_view" model="ir.ui.view"> - <field name="name">customer.map.view</field> - <field name="model">res.partner</field> - <field name="arch" type="xml"> - <map latitude="partner_latitude" longitude="partner_longitude"> - <field name="name"/> - </map> - </field> - </record> - - -Customizing an existing view -============================ - -Assume we need to create a custom version of a generic view. For example, a -kanban view with some extra *ribbon-like* widget on top (to display some -specific custom information). In that case, this can be done with 3 steps: -extend the kanban view (which also probably mean extending controllers/renderers -and/or models), then registering the view in the view registry, and finally, -using the view in the kanban arch (a specific example is the helpdesk dashboard). - -- extending a view: - Here is what it could look like: - - .. code-block:: javascript - - var HelpdeskDashboardRenderer = KanbanRenderer.extend({ - ... - }); - - var HelpdeskDashboardModel = KanbanModel.extend({ - ... - }); - - var HelpdeskDashboardController = KanbanController.extend({ - ... - }); - - var HelpdeskDashboardView = KanbanView.extend({ - config: _.extend({}, KanbanView.prototype.config, { - Model: HelpdeskDashboardModel, - Renderer: HelpdeskDashboardRenderer, - Controller: HelpdeskDashboardController, - }), - }); - -- adding it to the view registry: - as usual, we need to inform the web client of the mapping between the name - of the views and the actual class. - - .. code-block:: javascript - - var viewRegistry = require('web.view_registry'); - viewRegistry.add('helpdesk_dashboard', HelpdeskDashboardView); - -- using it in an actual view: - we now need to inform the web client that a specific ``ir.ui.view`` needs to - use our new class. Note that this is a web client specific concern. From - the point of view of the server, we still have a kanban view. The proper - way to do this is by using a special attribute ``js_class`` (which will be - renamed someday into ``widget``, because this is really not a good name) on - the root node of the arch: - - .. code-block:: xml - - <record id="helpdesk_team_view_kanban" model="ir.ui.view" > - ... - <field name="arch" type="xml"> - <kanban js_class="helpdesk_dashboard"> - ... - </kanban> - </field> - </record> - -.. note:: - - Note: you can change the way the view interprets the arch structure. However, - from the server point of view, this is still a view of the same base type, - subjected to the same rules (rng validation, for example). So, your views still - need to have a valid arch field. - -Promises and asynchronous code -=============================== - -For a very good and complete introduction to promises, please read this excellent article https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/async%20%26%20performance/ch3.md - -Creating new Promises ------------------------ -- turn a constant into a promise - There are 2 static functions on Promise that create a resolved or rejected promise based on a constant: - - .. code-block:: javascript - - var p = Promise.resolve({blabla: '1'}); // creates a resolved promise - p.then(function (result) { - console.log(result); // --> {blabla: '1'}; - }); - - - var p2 = Promise.reject({error: 'error message'}); // creates a rejected promise - p2.catch(function (reason) { - console.log(reason); // --> {error: 'error message'); - }); - - - .. note:: Note that even if the promises are created already resolved or rejected, the `then` or `catch` handlers will still be called asynchronously. - - -- based on an already asynchronous code - Suppose that in a function you must do a rpc, and when it is completed set the result on this. - The `this._rpc` is a function that returns a `Promise`. - - .. code-block:: javascript - - function callRpc() { - var self = this; - return this._rpc(...).then(function (result) { - self.myValueFromRpc = result; - }); - } - -- for callback based function - Suppose that you were using a function `this.close` that takes as parameter a callback that is called when the closing is finished. - Now suppose that you are doing that in a method that must send a promise that is resolved when the closing is finished. - - .. code-block:: javascript - :linenos: - - function waitForClose() { - var self = this; - return new Promise (function(resolve, reject) { - self.close(resolve); - }); - } - - * line 2: we save the `this` into a variable so that in an inner function, we can access the scope of our component - * line 3: we create and return a new promise. The constructor of a promise takes a function as parameter. This function itself has 2 parameters that we called here `resolve` and `reject` - - `resolve` is a function that, when called, puts the promise in the resolved state. - - `reject` is a function that, when called, puts the promise in the rejected state. We do not use reject here and it can be omitted. - * line 4: we are calling the function close on our object. It takes a function as parameter (the callback) and it happens that resolve is already a function, so we can pass it directly. To be clearer, we could have written: - - .. code-block:: javascript - - return new Promise (function (resolve) { - self.close(function () { - resolve(); - }); - }); - - - -- creating a promise generator (calling one promise after the other *in sequence* and waiting for the last one) - Suppose that you need to loop over an array, do an operation *in sequence* and resolve a promise when the last operation is done. - - .. code-block:: javascript - - function doStuffOnArray(arr) { - var done = Promise.resolve(); - arr.forEach(function (item) { - done = done.then(function () { - return item.doSomethingAsynchronous(); - }); - }); - return done; - } - - This way, the promise you return is effectively the last promise. -- creating a promise, then resolving it outside the scope of its definition (anti-pattern) - .. note:: we do not recommend using this, but sometimes it is useful. Think carefully for alternatives first... - - .. code-block:: javascript - - ... - var resolver, rejecter; - var prom = new Promise(function (resolve, reject){ - resolver = resolve; - rejecter = reject; - }); - ... - - resolver("done"); // will resolve the promise prom with the result "done" - rejecter("error"); // will reject the promise prom with the reason "error" - -Waiting for Promises --------------------- -- waiting for a number of Promises - if you have multiple promises that all need to be waited, you can convert them into a single promise that will be resolved when all the promises are resolved using Promise.all(arrayOfPromises). - - .. code-block:: javascript - - var prom1 = doSomethingThatReturnsAPromise(); - var prom2 = Promise.resolve(true); - var constant = true; - - var all = Promise.all([prom1, prom2, constant]); // all is a promise - // results is an array, the individual results correspond to the index of their - // promise as called in Promise.all() - all.then(function (results) { - var prom1Result = results[0]; - var prom2Result = results[1]; - var constantResult = results[2]; - }); - return all; - - -- waiting for a part of a promise chain, but not another part - If you have an asynchronous process that you want to wait to do something, but you also want to return to the caller before that something is done. - - .. code-block:: javascript - - function returnAsSoonAsAsyncProcessIsDone() { - var prom = AsyncProcess(); - prom.then(function (resultOfAsyncProcess) { - return doSomething(); - }); - /* returns prom which will only wait for AsyncProcess(), - and when it will be resolved, the result will be the one of AsyncProcess */ - return prom; - } - -Error handling --------------- - -- in general in promises - The general idea is that a promise should not be rejected for control flow, but should only be rejected for errors. - When that is the case, you would have multiple resolutions of your promise with, for instance status codes that you would have to check in the `then` handlers and a single `catch` handler at the end of the promise chain. - - .. code-block:: javascript - - function a() { - x.y(); // <-- this is an error: x is undefined - return Promise.resolve(1); - } - function b() { - return Promise.reject(2); - } - - a().catch(console.log); // will log the error in a - a().then(b).catch(console.log); // will log the error in a, the then is not executed - b().catch(console.log); // will log the rejected reason of b (2) - Promise.resolve(1) - .then(b) // the then is executed, it executes b - .then(...) // this then is not executed - .catch(console.log); // will log the rejected reason of b (2) - - - -- in Odoo specifically - In Odoo, it happens that we use promise rejection for control flow, like in mutexes and other concurrency primitives defined in module `web.concurrency` - We also want to execute the catch for *business* reasons, but not when there is a coding error in the definition of the promise or of the handlers. - For this, we have introduced the concept of `guardedCatch`. It is called like `catch` but not when the rejected reason is an error - - .. code-block:: javascript - - function blabla() { - if (someCondition) { - return Promise.reject("someCondition is truthy"); - } - return Promise.resolve(); - } - - // ... - - var promise = blabla(); - promise.then(function (result) { console.log("everything went fine"); }) - // this will be called if blabla returns a rejected promise, but not if it has an error - promise.guardedCatch(function (reason) { console.log(reason); }); - - // ... - - var anotherPromise = - blabla().then(function () { console.log("everything went fine"); }) - // this will be called if blabla returns a rejected promise, - // but not if it has an error - .guardedCatch(console.log); - - - .. code-block:: javascript - - var promiseWithError = Promise.resolve().then(function () { - x.y(); // <-- this is an error: x is undefined - }); - promiseWithError.guardedCatch(function (reason) {console.log(reason);}); // will not be called - promiseWithError.catch(function (reason) {console.log(reason);}); // will be called - - - -Testing asynchronous code --------------------------- - -- using promises in tests - In the tests code, we support the latest version of Javascript, including primitives like `async` and `await`. This makes using and waiting for promises very easy. - Most helper methods also return a promise (either by being marked `async` or by returning a promise directly. - - .. code-block:: javascript - - var testUtils = require('web.test_utils'); - QUnit.test("My test", async function (assert) { - // making the function async has 2 advantages: - // 1) it always returns a promise so you don't need to define `var done = assert.async()` - // 2) it allows you to use the `await` - assert.expect(1); - - var form = await testUtils.createView({ ... }); - await testUtils.form.clickEdit(form); - await testUtils.form.click('jquery selector'); - assert.containsOnce('jquery selector'); - form.destroy(); - }); - - QUnit.test("My test - no async - no done", function (assert) { - // this function is not async, but it returns a promise. - // QUnit will wait for for this promise to be resolved. - assert.expect(1); - - return testUtils.createView({ ... }).then(function (form) { - return testUtils.form.clickEdit(form).then(function () { - return testUtils.form.click('jquery selector').then(function () { - assert.containsOnce('jquery selector'); - form.destroy(); - }); - }); - }); - }); - - - QUnit.test("My test - no async", function (assert) { - // this function is not async and does not return a promise. - // we have to use the done function to signal QUnit that the test is async and will be finished inside an async callback - assert.expect(1); - var done = assert.async(); - - testUtils.createView({ ... }).then(function (form) { - testUtils.form.clickEdit(form).then(function () { - testUtils.form.click('jquery selector').then(function () { - assert.containsOnce('jquery selector'); - form.destroy(); - done(); - }); - }); - }); - }); - - as you can see, the nicer form is to use `async/await` as it is clearer and shorter to write. diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst deleted file mode 100644 index 9e6e208648e..00000000000 --- a/doc/reference/javascript_reference.rst +++ /dev/null @@ -1,2425 +0,0 @@ -:banner: banners/javascript.jpg - -.. highlight:: javascript - -.. default-domain:: js - -===================== -Javascript Reference -===================== - -This document presents the Odoo Javascript framework. This -framework is not a large application in term of lines of code, but it is quite -generic, because it is basically a machine to turn a declarative interface -description into a live application, able to interact with every model and -records in the database. It is even possible to use the web client to modify -the interface of the web client. - -Overview -========= - -The Javascript framework is designed to work with three main use cases: - -- the *web client*: this is the private web application, where one can view and - edit business data. This is a single page application (the page is never - reloaded, only the new data is fetched from the server whenever it is needed) -- the *website*: this is the public part of Odoo. It allows an unidentified - user to browse some content, to shop or to perform many actions, as a client. - This is a classical website: various routes with controllers and some - javascript to make it work. -- the *point of sale*: this is the interface for the point of sale. It is a - specialized single page application. - -Some javascript code is common to these three use cases, and is bundled together -(see below in the assets section). This document will focus mostly on the web -client design. - -Web client -========== - -Single Page Application ------------------------ - -In short, the *webClient*, instance of *WebClient* is the root component of the -whole user interface. Its responsibility is to orchestrate all various -subcomponents, and to provide services, such as rpcs, local storage and more. - -In runtime, the web client is a single page application. It does not need to -request a full page from the server each time the user perform an action. Instead, -it only requests what it needs, and then replaces/updates the view. Also, it -manages the url: it is kept in sync with the web client state. - -It means that while a user is working on Odoo, the web client class (and the -action manager) actually creates and destroys many sub components. The state is -highly dynamic, and each widget could be destroyed at any time. - -Overview of web client JS code -------------------------------------- - -Here, we give a very quick overview on the web client code, in -the *web/static/src/js* addon. Note that it is deliberately not exhaustive. -We only cover the most important files/folders. - -- *boot.js*: this is the file that defines the module system. It needs to be - loaded first. -- *core/*: this is a collection of lower level building blocks. Notably, it - contains the class system, the widget system, concurrency utilities, and many - other class/functions. -- *chrome/*: in this folder, we have most large widgets which make up most of - the user interface. -- *chrome/abstract_web_client.js* and *chrome/web_client.js*: together, these - files define the WebClient widget, which is the root widget for the web client. -- *chrome/action_manager.js*: this is the code that will convert an action into - a widget (for example a kanban or a form view) -- *chrome/search_X.js* all these files define the search view (it is not a view - in the point of view of the web client, only from the server point of view) -- *fields*: all main view field widgets are defined here -- *views*: this is where the views are located - -Assets Management -================= - -Managing assets in Odoo is not as straightforward as it is in some other apps. -One of the reason is that we have a variety of situations where some, but not all -the assets are required. For example, the needs of the web client, the point of -sale, the website or even the mobile application are different. Also, some -assets may be large, but are seldom needed. In that case, we sometimes want them -to be loaded lazily. - -The main idea is that we define a set of *bundles* in xml. A bundle is here defined as -a collection of files (javascript, css, scss). In Odoo, the most important -bundles are defined in the file *addons/web/views/webclient_templates.xml*. It looks -like this: - -.. code-block:: xml - - <template id="web.assets_common" name="Common Assets (used in backend interface and website)"> - <link rel="stylesheet" type="text/css" href="/web/static/lib/jquery.ui/jquery-ui.css"/> - ... - <script type="text/javascript" src="/web/static/src/js/boot.js"></script> - ... - </template> - - -The files in a bundle can then be inserted into a template by using the *t-call-assets* -directive: - -.. code-block:: xml - - <t t-call-assets="web.assets_common" t-js="false"/> - <t t-call-assets="web.assets_common" t-css="false"/> - -Here is what happens when a template is rendered by the server with these directives: - -- all the *scss* files described in the bundle are compiled into css files. A file - named *file.scss* will be compiled in a file named *file.scss.css*. - -- if we are in *debug=assets* mode - - - the *t-call-assets* directive with the *t-js* attribute set to false will - be replaced by a list of stylesheet tags pointing to the css files - - - the *t-call-assets* directive with the *t-css* attribute set to false will - be replaced by a list of script tags pointing to the js files - -- if we are not in *debug=assets* mode - - - the css files will be concatenated and minified, then a stylesheet tag is - generated - - - the js files are concatenated and minified, then a script tag is generated - -Note that the assets files are cached, so in theory, a browser should only load -them once. - -Main bundles ------------- -When the Odoo server is started, it checks the timestamp of each file in a bundle, -and if necessary, will create/recreate the corresponding bundles. - -Here are some important bundles that most developers will need to know: - -- *web.assets_common*: this bundle contains most assets which are common to the - web client, the website, and also the point of sale. This is supposed to contain - lower level building blocks for the odoo framework. Note that it contains the - *boot.js* file, which defines the odoo module system. - -- *web.assets_backend*: this bundle contains the code specific to the web client - (notably the web client/action manager/views) - -- *web.assets_frontend*: this bundle is about all that is specific to the public - website: ecommerce, forum, blog, event management, ... - - -Adding files in an asset bundle -------------------------------- - -The proper way to add a file located in *addons/web* to a bundle is simple: -it is just enough to add a *script* or a *stylesheet* tag to the bundle in the -file *webclient_templates.xml*. But when we work in a different addon, we need -to add a file from that addon. In that case, it should be done in three steps: - -1. add a *assets.xml* file in the *views/* folder -2. add the string 'views/assets.xml' in the 'data' key in the manifest file -3. create an inherited view of the desired bundle, and add the file(s) with an - xpath expression. For example, - -.. code-block:: xml - - <template id="assets_backend" name="helpdesk assets" inherit_id="web.assets_backend"> - <xpath expr="//script[last()]" position="after"> - <link rel="stylesheet" type="text/scss" href="/helpdesk/static/src/scss/helpdesk.scss"/> - <script type="text/javascript" src="/helpdesk/static/src/js/helpdesk_dashboard.js"></script> - </xpath> - </template> - - -.. note :: - - Note that the files in a bundle are all loaded immediately when the user loads the - odoo web client. This means that the files are transferred through the network - everytime (except when the browser cache is active). In some cases, it may be - better to lazyload some assets. For example, if a widget requires a large - library, and that widget is not a core part of the experience, then it may be - a good idea to only load the library when the widget is actually created. The - widget class has actually builtin support just for this use case. (see section - :ref:`reference/javascript_reference/qweb`) - -What to do if a file is not loaded/updated ------------------------------------------- - -There are many different reasons why a file may not be properly loaded. Here -are a few things you can try to solve the issue: - -- once the server is started, it does not know if an asset file has been - modified. So, you can simply restart the server to regenerate the assets. -- check the console (in the dev tools, usually opened with F12) to make sure - there are no obvious errors -- try to add a console.log at the beginning of your file (before any module - definition), so you can see if a file has been loaded or not -- in the user interface, in debug mode (INSERT LINK HERE TO DEBUG MODE), there - is an option to force the server to update its assets files. -- use the *debug=assets* mode. This will actually bypass the asset bundles (note - that it does not actually solve the issue. The server still uses outdated bundles) -- finally, the most convenient way to do it, for a developer, is to start the - server with the *--dev=all* option. This activates the file watcher options, - which will automatically invalidate assets when necessary. Note that it does - not work very well if the OS is Windows. -- remember to refresh your page! -- or maybe to save your code file... - -.. note:: - Once an asset file has been recreated, you need to refresh the page, to reload - the proper files (if that does not work, the files may be cached). - - -Javascript Module System -======================== - -Once we are able to load our javascript files into the browser, we need to make -sure they are loaded in the correct order. In order to do that, Odoo has defined -a small module system (located in the file *addons/web/static/src/js/boot.js*, -which needs to be loaded first). - -The Odoo module system, inspired by AMD, works by defining the function *define* -on the global odoo object. We then define each javascript module by calling that -function. In the Odoo framework, a module is a piece of code that will be executed -as soon as possible. It has a name and potentially some dependencies. When its -dependencies are loaded, a module will then be loaded as well. The value of the -module is then the return value of the function defining the module. - - -As an example, it may look like this: - - -.. code-block:: javascript - - // in file a.js - odoo.define('module.A', function (require) { - "use strict"; - - var A = ...; - - return A; - }); - - // in file b.js - odoo.define('module.B', function (require) { - "use strict"; - - var A = require('module.A'); - - var B = ...; // something that involves A - - return B; - }); - -An alternative way to define a module is to give explicitly a list of dependencies -in the second argument. - -.. code-block:: javascript - - odoo.define('module.Something', ['module.A', 'module.B'], function (require) { - "use strict"; - - var A = require('module.A'); - var B = require('module.B'); - - // some code - }); - - -If some dependencies are missing/non ready, then the module will simply not be -loaded. There will be a warning in the console after a few seconds. - -Note that circular dependencies are not supported. It makes sense, but it means that one -needs to be careful. - -Defining a module ------------------ - -The *odoo.define* method is given three arguments: - -- *moduleName*: the name of the javascript module. It should be a unique string. - The convention is to have the name of the odoo addon followed by a specific - description. For example, 'web.Widget' describes a module defined in the *web* - addon, which exports a *Widget* class (because the first letter is capitalized) - - If the name is not unique, an exception will be thrown and displayed in the - console. - -- *dependencies*: the second argument is optional. If given, it should be a list - of strings, each corresponding to a javascript module. This describes the - dependencies that are required to be loaded before the module is executed. If - the dependencies are not explicitly given here, then the module system will - extract them from the function by calling toString on it, then using a regexp - to find all *require* statements. - -.. code-block:: javascript - - odoo.define('module.Something', ['web.ajax'], function (require) { - "use strict"; - - var ajax = require('web.ajax'); - - // some code here - return something; - }); - -- finally, the last argument is a function which defines the module. Its return - value is the value of the module, which may be passed to other modules requiring - it. Note that there is a small exception for asynchronous modules, see the - next section. - -If an error happens, it will be logged (in debug mode) in the console: - -* ``Missing dependencies``: - These modules do not appear in the page. It is possible that the JavaScript - file is not in the page or that the module name is wrong -* ``Failed modules``: - A javascript error is detected -* ``Rejected modules``: - The module returns a rejected Promise. It (and its dependent modules) is not - loaded. -* ``Rejected linked modules``: - Modules who depend on a rejected module -* ``Non loaded modules``: - Modules who depend on a missing or a failed module - - - -Asynchronous modules ---------------------- - -It can happen that a module needs to perform some work before it is ready. For -example, it could do a rpc to load some data. In that case, the module can -simply return a promise. In that case, the module system will simply -wait for the promise to complete before registering the module. - -.. code-block:: javascript - - odoo.define('module.Something', function (require) { - "use strict"; - - var ajax = require('web.ajax'); - - return ajax.rpc(...).then(function (result) { - // some code here - return something; - }); - }); - - -Best practices ----------------- - -- remember the convention for a module name: *addon name* suffixed with *module - name*. -- declare all your dependencies at the top of the module. Also, they should be - sorted alphabetically by module name. This makes it easier to understand your module. -- declare all exported values at the end -- try to avoid exporting too many things from one module. It is usually better - to simply export one thing in one (small/smallish) module. -- asynchronous modules can be used to simplify some use cases. For example, - the *web.dom_ready* module returns a promise which will be resolved when the - dom is actually ready. So, another module that needs the DOM could simply have - a `require('web.dom_ready')` statement somewhere, and the code will only be - executed when the DOM is ready. -- try to avoid defining more than one module in one file. It may be convenient - in the short term, but this is actually harder to maintain. - - -Class System -============ - -Odoo was developed before ECMAScript 6 classes were available. In Ecmascript 5, -the standard way to define a class is to define a function and to add methods -on its prototype object. This is fine, but it is slightly complex when we want -to use inheritance, mixins. - -For these reasons, Odoo decided to use its own class system, inspired by John -Resig. The base Class is located in *web.Class*, in the file *class.js*. - -Creating a subclass -------------------- - -Let us discuss how classes are created. The main mechanism is to use the -*extend* method (this is more or less the equivalent of *extend* in ES6 classes). - -.. code-block:: javascript - - var Class = require('web.Class'); - - var Animal = Class.extend({ - init: function () { - this.x = 0; - this.hunger = 0; - }, - move: function () { - this.x = this.x + 1; - this.hunger = this.hunger + 1; - }, - eat: function () { - this.hunger = 0; - }, - }); - - -In this example, the *init* function is the constructor. It will be called when -an instance is created. Making an instance is done by using the *new* keyword. - -Inheritance ------------ - -It is convenient to be able to inherit an existing class. This is simply done -by using the *extend* method on the superclass. When a method is called, the -framework will secretly rebind a special method: *_super* to the currently -called method. This allows us to use *this._super* whenever we need to call a -parent method. - - -.. code-block:: javascript - - var Animal = require('web.Animal'); - - var Dog = Animal.extend({ - move: function () { - this.bark(); - this._super.apply(this, arguments); - }, - bark: function () { - console.log('woof'); - }, - }); - - var dog = new Dog(); - dog.move() - -Mixins ------- - -The odoo Class system does not support multiple inheritance, but for those cases -when we need to share some behaviour, we have a mixin system: the *extend* -method can actually take an arbitrary number of arguments, and will combine all -of them in the new class. - -.. code-block:: javascript - - var Animal = require('web.Animal'); - var DanceMixin = { - dance: function () { - console.log('dancing...'); - }, - }; - - var Hamster = Animal.extend(DanceMixin, { - sleep: function () { - console.log('sleeping'); - }, - }); - -In this example, the *Hamster* class is a subclass of Animal, but it also mix -the DanceMixin in. - - -Patching an existing class --------------------------- - -It is not common, but we sometimes need to modify another class *in place*. The -goal is to have a mechanism to change a class and all future/present instances. -This is done by using the *include* method: - -.. code-block:: javascript - - var Hamster = require('web.Hamster'); - - Hamster.include({ - sleep: function () { - this._super.apply(this, arguments); - console.log('zzzz'); - }, - }); - - -This is obviously a dangerous operation and should be done with care. But with -the way Odoo is structured, it is sometimes necessary in one addon to modify -the behavior of a widget/class defined in another addon. Note that it will -modify all instances of the class, even if they have already been created. - - -Widgets -======= - -The *Widget* class is really an important building block of the user interface. -Pretty much everything in the user interface is under the control of a widget. -The Widget class is defined in the module *web.Widget*, in *widget.js*. - -In short, the features provided by the Widget class include: - -* parent/child relationships between widgets (*PropertiesMixin*) -* 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>` -* various utility functions to help interacting with the outside environment. - -Here is an example of a basic counter widget: - -.. code-block:: javascript - - var Widget = require('web.Widget'); - - var Counter = Widget.extend({ - template: 'some.template', - events: { - 'click button': '_onClick', - }, - init: function (parent, value) { - this._super(parent); - this.count = value; - }, - _onClick: function () { - this.count++; - this.$('.val').text(this.count); - }, - }); - -For this example, assume that the template *some.template* (and is properly -loaded: the template is in a file, which is properly defined in the *qweb* key -in the module manifest) is given by: - -.. code-block:: xml - - <div t-name="some.template"> - <span class="val"><t t-esc="widget.count"/></span> - <button>Increment</button> - </div> - -This example widget can be used in the following manner: - -.. code-block:: javascript - - // Create the instance - var counter = new Counter(this, 4); - // Render and insert into DOM - counter.appendTo(".some-div"); - -This example illustrates a few of the features of the *Widget* class, including -the event system, the template system, the constructor with the initial *parent* argument. - -Widget Lifecycle ----------------- - -Like many component systems, the widget class has a well defined lifecycle. The -usual lifecycle is the following: *init* is called, then *willStart*, then the -rendering takes place, then *start* and finally *destroy*. - -.. function:: Widget.init(parent) - - this is the constructor. The init method is supposed to initialize the - base state of the widget. It is synchronous and can be overridden to - take more parameters from the widget's creator/parent - - :param parent: the new widget's parent, used to handle automatic - destruction and event propagation. Can be ``null`` for - the widget to have no parent. - :type parent: :class:`~Widget` - -.. function:: Widget.willStart() - - this method will be called once by the framework when a widget is created - and in the process of being appended to the DOM. The *willStart* method is a - hook that should return a promise. The JS framework will wait for this promise - to complete before moving on to the rendering step. Note that at this point, - the widget does not have a DOM root element. The *willStart* hook is mostly - useful to perform some asynchronous work, such as fetching data from the server - -.. function:: [Rendering] - - This step is automatically done by the framework. What happens is - that the framework checks if a template key is defined on the widget. If that is - the case, then it will render that template with the *widget* key bound to the - widget in the rendering context (see the example above: we use *widget.count* - in the QWeb template to read the value from the widget). If no template is - defined, we read the *tagName* key and create a corresponding DOM element. - When the rendering is done, we set the result as the $el property of the widget. - After this, we automatically bind all events in the events and custom_events - keys. - -.. function:: Widget.start() - - when the rendering is complete, the framework will automatically call - the *start* method. This is useful to perform some specialized post-rendering - work. For example, setting up a library. - - Must return a promise to indicate when its work is done. - - :returns: promise - -.. function:: Widget.destroy() - - This is always the final step in the life of a widget. When a - widget is destroyed, we basically perform all necessary cleanup operations: - removing the widget from the component tree, unbinding all events, ... - - Automatically called when the widget's parent is destroyed, - must be called explicitly if the widget has no parent or if it is - removed but its parent remains. - -Note that the willStart and start method are not necessarily called. A widget -can be created (the *init* method will be called) and then destroyed (*destroy* -method) without ever having been appended to the DOM. If that is the case, the -willStart and start will not even be called. - -Widget API ----------- - -.. attribute:: Widget.tagName - - Used if the widget has no template defined. Defaults to ``div``, - will be used as the tag name to create the DOM element to set as - the widget's DOM root. It is possible to further customize this - generated DOM root with the following attributes: - - -.. attribute:: Widget.id - - Used to generate an ``id`` attribute on the generated DOM - root. Note that this is rarely needed, and is probably not a good idea - if a widget can be used more than once. - -.. attribute:: Widget.className - - Used to generate a ``class`` attribute on the generated DOM root. Note - that it can actually contain more than one css class: - *'some-class other-class'* - -.. attribute:: Widget.attributes - - Mapping (object literal) of attribute names to attribute - values. Each of these k:v pairs will be set as a DOM attribute - on the generated DOM root. - -.. attribute:: Widget.el - - raw DOM element set as root to the widget (only available after the start - lifecycle method) - -.. attribute:: Widget.$el - - jQuery wrapper around :attr:`~Widget.el`. (only available after the start - lifecycle method) - -.. attribute:: Widget.template - - Should be set to the name of a :ref:`QWeb template <reference/qweb>`. - If set, the template will be rendered after the widget has been - initialized but before it has been started. The root element generated by - the template will be set as the DOM root of the widget. - -.. attribute:: Widget.xmlDependencies - - List of paths to xml files that need to be loaded before the - widget can be rendered. This will not induce loading anything that has already - been loaded. This is useful when you want to load your templates lazily, - or if you want to share a widget between the website and the web client - interface. - - .. code-block:: javascript - - var EditorMenuBar = Widget.extend({ - xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], - ... - -.. attribute:: Widget.events - - Events are a mapping of an event selector (an event name and an optional - CSS selector separated by a space) to a callback. The callback can - be the name of a widget's method or a function object. In either case, the - ``this`` will be set to the widget: - - .. code-block:: javascript - - 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. If the selector is left out - (only an event name is specified), the event will be set directly on the - widget's DOM root. - - Note: the use of an inline function is discouraged, and will probably be - removed sometimes in the future. - -.. attribute:: Widget.custom_events - - this is almost the same as the *events* attribute, but the keys - are arbitrary strings. They represent business events triggered by - some sub widgets. When an event is triggered, it will 'bubble up' the widget - tree (see the section on component communication for more details). - -.. function:: Widget.isDestroyed() - - :returns: ``true`` if the widget is being or has been destroyed, ``false`` - otherwise - -.. function:: Widget.$(selector) - - Applies the CSS selector specified as parameter to the widget's - DOM root: - - .. code-block:: javascript - - this.$(selector); - - is functionally identical to: - - .. code-block:: javascript - - this.$el.find(selector); - - :param String selector: CSS selector - :returns: jQuery object - - .. note:: this helper method is similar to ``Backbone.View.$`` - -.. function:: Widget.setElement(element) - - Re-sets the widget's DOM root to the provided element, also - handles re-setting the various aliases of the DOM root as well as - unsetting and re-setting delegated events. - - :param Element element: a DOM element or jQuery object to set as - the widget's DOM root - - -Inserting a widget in the DOM ------------------------------ - -.. function:: Widget.appendTo(element) - - Renders the widget and inserts it as the last child of the target, uses - `.appendTo()`_ - -.. function:: Widget.prependTo(element) - - Renders the widget and inserts it as the first child of the target, uses - `.prependTo()`_ - -.. function:: Widget.insertAfter(element) - - Renders the widget and inserts it as the preceding sibling of the target, - uses `.insertAfter()`_ - -.. function:: Widget.insertBefore(element) - - Renders the widget and inserts it as the following sibling of the target, - uses `.insertBefore()`_ - -All of these methods accept whatever the corresponding jQuery method accepts -(CSS selectors, DOM nodes or jQuery objects). They all return a promise -and are charged with three tasks: - -* rendering the widget's root element via :func:`~Widget.renderElement` -* inserting the widget's root element in the DOM using whichever jQuery - method they match -* starting the widget, and returning the result of starting it - -Widget 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.: - - .. code-block:: javascript - - this.id = _.uniqueId('my-widget-'); - -* Avoid predictable/common CSS class names. Class names such as "content" or - "navigation" might match the desired meaning/semantics, but it is likely an - other developer will have the same need, creating a naming conflict and - unintended behavior. Generic class names should be prefixed with e.g. the - name of the component they belong to (creating "informal" namespaces, much - as in C or Objective-C). - -* Global selectors should be avoided. Because a component may be used several - times in a single page (an example in Odoo is dashboards), queries should be - restricted to a given component's scope. Unfiltered selections such as - ``$(selector)`` or ``document.querySelectorAll(selector)`` will generally - lead to unintended or incorrect behavior. Odoo Web's - :class:`~Widget` has an attribute providing its DOM root - (:attr:`~Widget.$el`), and a shortcut to select nodes directly - (:func:`~Widget.$`). - -* More generally, never assume your components own or controls anything beyond - its own personal :attr:`~Widget.$el` (so, avoid using a reference to the - parent widget) - -* Html templating/rendering should use QWeb unless absolutely trivial. - -* All interactive components (components displaying information to the screen - or intercepting DOM events) must inherit from :class:`~Widget` - and correctly implement and use its API and life cycle. - -* Make sure to wait for start to be finished before using $el e.g.: - - .. code-block:: javascript - - var Widget = require('web.Widget'); - - var AlmostCorrectWidget = Widget.extend({ - start: function () { - this.$el.hasClass(....) // in theory, $el is already set, but you don't know what the parent will do with it, better call super first - return this._super.apply(arguments); - }, - }); - - var IncorrectWidget = Widget.extend({ - start: function () { - this._super.apply(arguments); // the parent promise is lost, nobody will wait for the start of this widget - this.$el.hasClass(....) - }, - }); - - var CorrectWidget = Widget.extend({ - start: function () { - var self = this; - return this._super.apply(arguments).then(function() { - self.$el.hasClass(....) // this works, no promise is lost and the code executes in a controlled order: first super, then our code. - }); - }, - }); - -.. _reference/javascript_reference/qweb: - -QWeb Template Engine -==================== - -The web client uses the :doc:`qweb` template engine to render widgets (unless they -override the *renderElement* method to do something else). -The Qweb JS template engine is based on XML, and is mostly compatible with the -python implementation. - -Now, let us explain how the templates are loaded. Whenever the web client -starts, a rpc is made to the */web/webclient/qweb* route. The server will then -return a list of all templates defined in data files for each installed modules. -The correct files are listed in the *qweb* entry in each module manifest. - -The web client will wait for that list of template to be loaded, before starting -its first widget. - -This mechanism works quite well for our needs, but sometimes, we want to lazy -load a template. For example, imagine that we have a widget which is rarely -used. In that case, maybe we prefer to not load its template in the main file, -in order to make the web client slightly lighter. In that case, we can use the -*xmlDependencies* key of the Widget: - -.. code-block:: javascript - - var Widget = require('web.Widget'); - - var Counter = Widget.extend({ - template: 'some.template', - xmlDependencies: ['/myaddon/path/to/my/file.xml'], - - ... - - }); - -With this, the *Counter* widget will load the xmlDependencies files in its -*willStart* method, so the template will be ready when the rendering is performed. - - -Event system -============ - -There are currently two event systems supported by Odoo: a simple system which -allows adding listeners and triggering events, and a more complete system that -also makes events 'bubble up'. - -Both of these event systems are implemented in the *EventDispatcherMixin*, in -the file *mixins.js*. This mixin is included in the *Widget* class. - -Base Event system ------------------ - -This event system was historically the first. It implements a simple bus -pattern. We have 4 main methods: - -- *on*: this is used to register a listener on an event. -- *off*: useful to remove events listener. -- *once*: this is used to register a listener that will only be called once. -- *trigger*: trigger an event. This will cause each listeners to be called. - -Here is an example on how this event system could be used: - -.. code-block:: javascript - - var Widget = require('web.Widget'); - var Counter = require('myModule.Counter'); - - var MyWidget = Widget.extend({ - start: function () { - this.counter = new Counter(this); - this.counter.on('valuechange', this, this._onValueChange); - var def = this.counter.appendTo(this.$el); - return Promise.all([def, this._super.apply(this, arguments)]); - }, - _onValueChange: function (val) { - // do something with val - }, - }); - - // in Counter widget, we need to call the trigger method: - - ... this.trigger('valuechange', someValue); - - -.. warning:: - the use of this event system is discouraged, we plan to replace each - *trigger* method by the *trigger_up* method from the extended event system - -Extended Event System ---------------------- - -The custom event widgets is a more advanced system, which mimic the DOM events -API. Whenever an event is triggered, it will 'bubble up' the component tree, -until it reaches the root widget, or is stopped. - -- *trigger_up*: this is the method that will create a small *OdooEvent* and - dispatch it in the component tree. Note that it will start with the component - that triggered the event -- *custom_events*: this is the equivalent of the *event* dictionary, but for - odoo events. - -The OdooEvent class is very simple. It has three public attributes: *target* -(the widget that triggered the event), *name* (the event name) and *data* (the -payload). It also has 2 methods: *stopPropagation* and *is_stopped*. - -The previous example can be updated to use the custom event system: - -.. code-block:: javascript - - var Widget = require('web.Widget'); - var Counter = require('myModule.Counter'); - - var MyWidget = Widget.extend({ - custom_events: { - valuechange: '_onValueChange' - }, - start: function () { - this.counter = new Counter(this); - var def = this.counter.appendTo(this.$el); - return Promise.all([def, this._super.apply(this, arguments)]); - }, - _onValueChange: function(event) { - // do something with event.data.val - }, - }); - - // in Counter widget, we need to call the trigger_up method: - - ... this.trigger_up('valuechange', {value: someValue}); - - -Registries -=========== - -A common need in the Odoo ecosystem is to extend/change the behaviour of the -base system from the outside (by installing an application, i.e. a different -module). For example, one may need to add a new widget type in some views. In -that case, and many others, the usual process is to create the desired component, -then add it to a registry (registering step), to make the rest of the web client -aware of its existence. - -There are a few registries available in the system: - -field registry (exported by :js:data:`web.field_registry`) - The field registry contains all field widgets known to the web client. - Whenever a view (typically form or list/kanban) needs a field widget, this - is where it will look. A typical use case look like this: - - .. code-block:: javascript - - var fieldRegistry = require('web.field_registry'); - - var FieldPad = ...; - - fieldRegistry.add('pad', FieldPad); - - Note that each value should be a subclass of *AbstractField* - -view registry - This registry contains all JS views known to the web client - (and in particular, the view manager). Each value of this registry should - be a subclass of *AbstractView*. - -action registry - We keep track of all client actions in this registry. This - is where the action manager looks up whenever it needs to create a client - action. In version 11, each value should simply be a subclass of *Widget*. - However, in version 12, the values are required to be *AbstractAction*. - - -Communication between widgets -============================= - -There are many ways to communicate between components. - -From a parent to its child - This is a simple case. The parent widget can simply call a method on its - child: - - .. code-block:: javascript - - this.someWidget.update(someInfo); - -From a widget to its parent/some ancestor - In this case, the widget's job is simply to notify its environment that - something happened. Since we do not want the widget to have a reference to - its parent (this would couple the widget with its parent's implementation), - the best way to proceed is usually to trigger an event, which will bubble up - the component tree, by using the ``trigger_up`` method: - - .. code-block:: javascript - - this.trigger_up('open_record', { record: record, id: id}); - - This event will be triggered on the widget, then will bubble up and be - eventually caught by some upstream widget: - - .. code-block:: javascript - - var SomeAncestor = Widget.extend({ - custom_events: { - 'open_record': '_onOpenRecord', - }, - _onOpenRecord: function (event) { - var record = event.data.record; - var id = event.data.id; - // do something with the event. - }, - }); - -Cross component - Cross component communication can be achieved by using a bus. This is not - the preferred form of communication, because it has the disadvantage of - making the code harder to maintain. However, it has the advantage of - decoupling the components. In that case, this is simply done by triggering - and listening to events on a bus. For example: - - .. code-block:: javascript - - // in WidgetA - var core = require('web.core'); - - var WidgetA = Widget.extend({ - ... - start: function () { - core.bus.on('barcode_scanned', this, this._onBarcodeScanned); - }, - }); - - // in WidgetB - var WidgetB = Widget.extend({ - ... - someFunction: function (barcode) { - core.bus.trigger('barcode_scanned', barcode); - }, - }); - - - In this example, we use the bus exported by *web.core*, but this is not - required. A bus could be created for a specific purpose. - -Services -======== - -In version 11.0, we introduced the notion of *service*. The main idea is to -give to sub components a controlled way to access their environment, in a way -that allow the framework enough control, and which is testable. - -The service system is organized around three ideas: services, service providers -and widgets. The way it works is that widgets trigger (with *trigger_up*) -events, these events bubble up to a service provider, which will ask a service -to perform a task, then maybe return an answer. - -Service --------- - -A service is an instance of the *AbstractService* class. It basically only has -a name and a few methods. Its job is to perform some work, typically something -depending on the environment. - -For example, we have the *ajax* service (job is to perform a rpc), the -*localStorage* (interact with the browser local storage) and many others. - -Here is a simplified example on how the ajax service is implemented: - -.. code-block:: javascript - - var AbstractService = require('web.AbstractService'); - - var AjaxService = AbstractService.extend({ - name: 'ajax', - rpc: function (...) { - return ...; - }, - }); - -This service is named 'ajax' and define one method, *rpc*. - -Service Provider ----------------- - -For services to work, it is necessary that we have a service provider ready to -dispatch the custom events. In the *backend* (web client), this is done by the -main web client instance. Note that the code for the service provider comes from -the *ServiceProviderMixin*. - - -Widget ------- - -The widget is the part that requests a service. In order to do that, it simply -triggers an event *call_service* (typically by using the helper function *call*). -This event will bubble up and communicate the intent to the rest of the system. - -In practice, some functions are so frequently called that we have some helpers -functions to make them easier to use. For example, the *_rpc* method is a helper -that helps making a rpc. - -.. code-block:: javascript - - var SomeWidget = Widget.extend({ - _getActivityModelViewID: function (model) { - return this._rpc({ - model: model, - method: 'get_activity_view_id' - }); - }, - }); - -.. warning:: - If a widget is destroyed, it will be detached from the main component tree - and will not have a parent. In that case, the events will not bubble up, which - means that the work will not be done. This is usually exactly what we want from - a destroyed widget. - -RPCs ----- - -The rpc functionality is supplied by the ajax service. But most people will -probably only interact with the *_rpc* helpers. - -There are typically two usecases when working on Odoo: one may need to call a -method on a (python) model (this goes through a controller *call_kw*), or one -may need to directly call a controller (available on some route). - -* Calling a method on a python model: - -.. code-block:: javascript - - return this._rpc({ - model: 'some.model', - method: 'some_method', - args: [some, args], - }); - -* Directly calling a controller: - -.. code-block:: javascript - - return this._rpc({ - route: '/some/route/', - params: { some: kwargs}, - }); - -Notifications -============== - -The Odoo framework has a standard way to communicate various information to the -user: notifications, which are displayed on the top right of the user interface. - -There are two types of notifications: - -- *notification*: useful to display some feedback. For example, whenever a user - unsubscribed to a channel. - -- *warning*: useful to display some important/urgent information. Typically - most kind of (recoverable) errors in the system. - -Also, notifications can be used to ask a question to the user without disturbing -its workflow. Imagine a phone call received through VOIP: a sticky notification -could be displayed with two buttons *Accept* and *Decline*. - -Notification system -------------------- - -The notification system in Odoo is designed with the following components: - -- a *Notification* widget: this is a simple widget that is meant to be created - and displayed with the desired information - -- a *NotificationService*: a service whose responsibility is to create and - destroy notifications whenever a request is done (with a custom_event). Note - that the web client is a service provider. - -- a client action *display_notification*: this allows to trigger the display - of a notification from python (e.g. in the method called when the user - clicked on a button of type object). - -- two helper functions in *ServiceMixin*: *do_notify* and *do_warn* - - -Displaying a notification -------------------------- -The most common way to display a notification is by using two methods that come -from the *ServiceMixin*: - -- *do_notify(title, message, sticky, className)*: - Display a notification of type *notification*. - - - *title*: string. This will be displayed on the top as a title - - - *message*: string, the content of the notification - - - *sticky*: boolean, optional. If true, the notification will stay until the - user dismisses it. Otherwise, the notification will be automatically - closed after a short delay. - - - *className*: string, optional. This is a css class name that will be - automatically added to the notification. This could be useful for styling - purpose, even though its use is discouraged. - -- *do_warn(title, message, sticky, className)*: - Display a notification of type *warning*. - - - *title*: string. This will be displayed on the top as a title - - - *message*: string, the content of the notification - - - *sticky*: boolean, optional. If true, the notification will stay until the - user dismisses it. Otherwise, the notification will be automatically - closed after a short delay. - - - *className*: string, optional. This is a css class name that will be - automatically added to the notification. This could be useful for styling - purpose, even though its use is discouraged. - -Here are two examples on how to use these methods: - -.. code-block:: javascript - - // note that we call _t on the text to make sure it is properly translated. - this.do_notify(_t("Success"), _t("Your signature request has been sent.")); - - this.do_warn(_t("Error"), _t("Filter name is required.")); - -Here an example in python: - -.. code-block:: python - - # note that we call _(string) on the text to make sure it is properly translated. - def show_notification(self): - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('Success'), - 'message': _('Your signature request has been sent.'), - 'sticky': False, - } - } - -Systray -======= - -The Systray is the right part of the menu bar in the interface, where the web -client displays a few widgets, such as a messaging menu. - -When the SystrayMenu is created by the menu, it will look for all registered -widgets and add them as a sub widget at the proper place. - -There is currently no specific API for systray widgets. They are supposed to -be simple widgets, and can communicate with their environment just like other -widgets with the *trigger_up* method. - -Adding a new Systray Item -------------------------- - -There is no systray registry. The proper way to add a widget is to add it to -the class variable SystrayMenu.items. - -.. code-block:: javascript - - var SystrayMenu = require('web.SystrayMenu'); - - var MySystrayWidget = Widget.extend({ - ... - }); - - SystrayMenu.Items.push(MySystrayWidget); - - -Ordering --------- - -Before adding the widget to himself, the Systray Menu will sort the items by -a sequence property. If that property is not present on the prototype, it will -use 50 instead. So, to position a systray item to be on the right, one can -set a very high sequence number (and conversely, a low number to put it on the -left). - -.. code-block:: javascript - - MySystrayWidget.prototype.sequence = 100; - - -Translation management -====================== - -Some translations are made on the server side (basically all text strings rendered or -processed by the server), but there are strings in the static files that need -to be translated. The way it currently works is the following: - -- each translatable string is tagged with the special function *_t* (available in - the JS module *web.core* -- these strings are used by the server to generate the proper PO files -- whenever the web client is loaded, it will call the route */web/webclient/translations*, - which returns a list of all translatable terms -- in runtime, whenever the function *_t* is called, it will look up in this list - in order to find a translation, and return it or the original string if none - is found. - -Note that translations are explained in more details, from the server point of -view, in the document :doc:`translations`. - -There are two important functions for the translations in javascript: *_t* and -*_lt*. The difference is that *_lt* is lazily evaluated. - -.. code-block:: javascript - - var core = require('web.core'); - - var _t = core._t; - var _lt = core._lt; - - var SomeWidget = Widget.extend({ - exampleString: _lt('this should be translated'), - ... - someMethod: function () { - var str = _t('some text'); - ... - }, - }); - -In this example, the *_lt* is necessary because the translations are not ready -when the module is loaded. - -Note that translation functions need some care. The string given in argument -should not be dynamic. - -Session -======= - -There is a specific module provided by the web client which contains some -information specific to the user current *session*. Some notable keys are - -- uid: the current user ID (its ID as a *res.users*) -- user_name: the user name, as a string -- the user context (user ID, language and timezone) -- partner_id: the ID of the partner associated to the current user -- db: the name of the database currently being in use - -Adding information to the session ---------------------------------- - -When the /web route is loaded, the server will inject some session information -in the template a script tag. The information will be read from the method -*session_info* of the model *ir.http*. So, if one wants to add a specific -information, it can be done by overriding the session_info method and adding it -to the dictionary. - -.. code-block:: python - - from odoo import models - from odoo.http import request - - - class IrHttp(models.AbstractModel): - _inherit = 'ir.http' - - def session_info(self): - result = super(IrHttp, self).session_info() - result['some_key'] = get_some_value_from_db() - return result - -Now, the value can be obtained in javascript by reading it in the session: - -.. code-block:: javascript - - var session = require('web.session'); - var myValue = session.some_key; - ... - -Note that this mechanism is designed to reduce the amount of communication -needed by the web client to be ready. It is more appropriate for data which is -cheap to compute (a slow session_info call will delay the loading for the web -client for everyone), and for data which is required early in the initialization -process. - -Views -====== - -The word 'view' has more than one meaning. This section is about the design of -the javascript code of the views, not the structure of the *arch* or anything -else. - -In 2017, Odoo replaced the previous view code with a new architecture. The -main need was to separate the rendering logic from the model logic. - - -Views (in a generic sense) are now described with 4 pieces: a View, a -Controller, a Renderer and a Model. The API of these 4 pieces is described in -the AbstractView, AbstractController, AbstractRenderer and AbstractModel classes. - -.. raw:: html - - <svg width="550" height="173"> - <!-- Created with Method Draw - https://github.com/duopixel/Method-Draw/ --> - <path id="svg_1" d="m147.42498,79.79206c0.09944,-8.18859 -0.06363,-16.38812 0.81774,-24.5623c21.65679,2.68895 43.05815,7.08874 64.35,11.04543c1.14304,-4.01519 0.60504,-7.34585 1.59817,-11.05817c13.67878,7.81176 27.23421,15.73476 40.23409,24.03505c-12.47212,9.41539 -26.77809,17.592 -40.82272,25.96494c-0.4548,-3.89916 -0.90967,-7.79828 -1.36448,-11.69744c-20.69972,3.77225 -42.59036,7.6724 -63.42391,11.12096c-1.41678,-7.95741 -1.37514,-16.62327 -1.38888,-24.84846z" stroke-width="1.5" stroke="#000" fill="#fff"/> - <rect id="svg_3" height="41" width="110" y="57.5" x="7" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="#fff"/> - <rect stroke="#000" id="svg_5" height="41" width="135" y="20.5" x="328" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="#fff"/> - <rect stroke="#000" id="svg_6" height="41" width="128" y="102.5" x="262" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="#fff"/> - <rect stroke="#000" id="svg_7" height="41" width="119" y="100.5" x="417" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="#fff"/> - <line stroke-linecap="null" stroke-linejoin="null" id="svg_8" y2="96.5" x2="317" y1="65.5" x1="364" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> - <line stroke-linecap="null" stroke-linejoin="null" id="svg_9" y2="96.5" x2="467" y1="63.5" x1="425" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> - <text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_10" y="83.5" x="38" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">View</text> - <text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_11" y="44.5" x="346" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">Controller</text> - <text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_12" y="128.5" x="276" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">Renderer</text> - <text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_13" y="127.5" x="442" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">Model</text> - </svg> - -- the View is the factory. Its job is to get a set of fields, arch, context and - some other parameters, then to construct a Controller/Renderer/Model triplet. - - The view's role is to properly setup each piece of the MVC pattern, with the correct - information. Usually, it has to process the arch string and extract the - data necessary for each other parts of the view. - - Note that the view is a class, not a widget. Once its job has been done, it - can be discarded. - -- the Renderer has one job: representing the data being viewed in a DOM element. - Each view can render the data in a different way. Also, it should listen on - appropriate user actions and notify its parent (the Controller) if necessary. - - The Renderer is the V in the MVC pattern. - -- the Model: its job is to fetch and hold the state of the view. Usually, it - represents in some way a set of records in the database. The Model is the - owner of the 'business data'. It is the M in the MVC pattern. - -- the Controller: its job is to coordinate the renderer and the model. Also, it - is the main entry point for the rest of the web client. For example, when - the user changes something in the search view, the *update* method of the - controller will be called with the appropriate information. - - It is the C in the MVC pattern. - -.. note:: - The JS code for the views has been designed to be usable outside of the - context of a view manager/action manager. They could be used in a client action, - or, they could be displayed in the public website (with some work on the assets). - -.. _reference/js/widgets: - -Field Widgets -============= - -A good part of the web client experience is about editing and creating data. Most -of that work is done with the help of field widgets, which are aware of the field -type and of the specific details on how a value should be displayed and edited. - -AbstractField -------------- - -The *AbstractField* class is the base class for all widgets in a view, for all -views that support them (currently: Form, List, Kanban). - -There are many differences between the v11 field widgets and the previous versions. -Let us mention the most important ones: - -- the widgets are shared between all views (well, Form/List/Kanban). No need to - duplicate the implementation anymore. Note that it is possible to have a - specialized version of a widget for a view, by prefixing it with the view name - in the view registry: *list.many2one* will be chosen in priority over *many2one*. -- the widgets are no longer the owner of the field value. They only represent - the data and communicate with the rest of the view. -- the widgets do no longer need to be able to switch between edit and readonly - mode. Now, when such a change is necessary, the widget will be destroyed and - rerendered again. It is not a problem, since they do not own their value - anyway -- the field widgets can be used outside of a view. Their API is slightly - awkward, but they are designed to be standalone. - -Decorations ------------ - -Like the list view, field widgets have a simple support for decorations. The -goal of decorations is to have a simple way to specify a text color depending on -the record current state. For example, - -.. code-block:: xml - - <field name="state" decoration-danger="amount < 10000"/> - -The valid decoration names are: - -- decoration-bf -- decoration-it -- decoration-danger -- decoration-info -- decoration-muted -- decoration-primary -- decoration-success -- decoration-warning - -Each decoration *decoration-X* will be mapped to a css class *text-X*, which is -a standard bootstrap css class (except for *text-it* and *text-bf*, which are -handled by odoo and correspond to italic and bold, respectively). Note that the -value of the decoration attribute should be a valid python expression, which -will be evaluated with the record as evaluation context. - -Non relational fields ---------------------- - -We document here all non relational fields available by default, in no particular -order. - -integer (FieldInteger) - This is the default field type for fields of type *integer*. - - - Supported field types: *integer* - - Options: - - - type: setting the input type (*text* by default, can be set on *number*) - - On edit mode, the field is rendered as an input with the HTML attribute type - setted on *number* (so user can benefit the native support, especially on - mobile). In this case, the default formatting is disabled to avoid incompability. - - .. code-block:: xml - - <field name="int_value" options='{"type": "number"}'/> - - - step: set the step to the value up and down when the user click on buttons - (only for input of type number, 1 by default) - - .. code-block:: xml - - <field name="int_value" options='{"type": "number", "step": 100}'/> - - - format: should the number be formatted. (true by default) - - By default, numbers are formatted according to locale parameters. - This option will prevent the field's value from being formatted. - - .. code-block:: xml - - <field name="int_value" options='{"format": false}'/> - -float (FieldFloat) - This is the default field type for fields of type *float*. - - - Supported field types: *float* - - Attributes: - - - digits: displayed precision - - .. code-block:: xml - - <field name="factor" digits="[42,5]"/> - - Options: - - - type: setting the input type (*text* by default, can be set on *number*) - - On edit mode, the field is rendered as an input with the HTML attribute type - setted on *number* (so user can benefit the native support, especially on - mobile). In this case, the default formatting is disabled to avoid incompability. - - .. code-block:: xml - - <field name="int_value" options='{"type": "number"}'/> - - - step: set the step to the value up and down when the user click on buttons - (only for input of type number, 1 by default) - - .. code-block:: xml - - <field name="int_value" options='{"type": "number", "step": 0.1}'/> - - - format: should the number be formatted. (true by default) - - By default, numbers are formatted according to locale parameters. - This option will prevent the field's value from being formatted. - - .. code-block:: xml - - <field name="int_value" options='{"format": false}'/> - -float_time (FieldFloatTime) - The goal of this widget is to display properly a float value that represents - a time interval (in hours). So, for example, 0.5 should be formatted as 0:30, - or 4.75 correspond to 4:45. - - - Supported field types: *float* - -float_factor (FieldFloatFactor) - This widget aims to display properly a float value that converted using a factor - given in its options. So, for example, the value saved in database is 0.5 and the - factor is 3, the widget value should be formatted as 1.5. - - - Supported field types: *float* - -float_toggle (FieldFloatToggle) - The goal of this widget is to replace the input field by a button containing a - range of possible values (given in the options). Each click allows the user to loop - in the range. The purpose here is to restrict the field value to a predefined selection. - Also, the widget support the factor conversion as the *float_factor* widget (Range values - should be the result of the conversion). - - - Supported field types: *float* - - .. code-block:: xml - - <field name="days_to_close" widget="float_toggle" options='{"factor": 2, "range": [0, 4, 8]}'/> - -boolean (FieldBoolean) - This is the default field type for fields of type *boolean*. - - - Supported field types: *boolean* - -char (FieldChar) - This is the default field type for fields of type *char*. - - - Supported field types: *char* - -date (FieldDate) - This is the default field type for fields of type *date*. Note that it also - works with datetime fields. It uses the session timezone when formatting - dates. - - - Supported field types: *date*, *datetime* - - Options: - - - datepicker: extra settings for the datepicker_ widget. - - .. code-block:: xml - - <field name="datefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/> - -datetime (FieldDateTime) - This is the default field type for fields of type *datetime*. - - - Supported field types: *date*, *datetime* - - Options: - - - datepicker: extra settings for the datepicker_ widget. - - .. code-block:: xml - - <field name="datetimefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/> - -daterange (FieldDateRange) - This widget allows the user to select start and end date into a single picker. - - - Supported field types: *date*, *datetime* - - Options: - - - related_start_date: apply on end date field to get start date value which - is used to display range in the picker. - - related_end_date: apply on start date field to get end date value which - is used to display range in the picker. - - picker_options: extra settings for picker. - - .. code-block:: xml - - <field name="start_date" widget="daterange" options='{"related_end_date": "end_date"}'/> - -remaining_days (RemainingDays) - This widget can be used on date and datetime fields. In readonly, it displays - the delta (in days) between the value of the field and today. The widget is - intended to be used for informative purpose: therefore the value cannot be - modified in edit mode. - - - Supported field types: *date*, *datetime* - -monetary (FieldMonetary) - This is the default field type for fields of type 'monetary'. It is used to - display a currency. If there is a currency fields given in option, it will - use that, otherwise it will fall back to the default currency (in the session) - - - Supported field types: *monetary*, *float* - - Options: - - - currency_field: another field name which should be a many2one on currency. - - .. code-block:: xml - - <field name="value" widget="monetary" options="{'currency_field': 'currency_id'}"/> - -text (FieldText) - This is the default field type for fields of type *text*. - - - Supported field types: *text* - - -handle (HandleWidget) - This field's job is to be displayed as a *handle*, and allows reordering the - various records by drag and dropping them. - - .. warning:: It has to be specified on the field by which records are sorted. - .. warning:: Having more than one field with a handle widget on the same list is not supported. - - - Supported field types: *integer* - - -email (FieldEmail) - This field displays email address. The main reason to use it is that it - is rendered as an anchor tag with the proper href, in readonly mode. - - - Supported field types: *char* - -phone (FieldPhone) - This field displays a phone number. The main reason to use it is that it - is rendered as an anchor tag with the proper href, in readonly mode, but - only in some cases: we only want to make it clickable if the device can - call this particular number. - - - Supported field types: *char* - -url (UrlWidget) - This field displays an url (in readonly mode). The main reason to use it is - that it is rendered as an anchor tag with the proper css classes and href. - - Also, the text of the anchor tag can be customized with the *text* attribute - (it won't change the href value). - - .. code-block:: xml - - <field name="foo" widget="url" text="Some URL"/> - - Options: - - - website_path: (default:false) by default, the widget forces (if not already - the case) the href value to begin with http:// except if this option is set - to true, thus allowing redirections to the database's own website. - - - Supported field types: *char* - -domain (FieldDomain) - The "Domain" field allows the user to construct a technical-prefix domain - thanks to a tree-like interface and see the selected records in real time. - In debug mode, an input is also there to be able to enter the prefix char - domain directly (or to build advanced domains the tree-like interface does - not allow to). - - Note that this is limited to 'static' domain (no dynamic expression, or access - to context variable). - - - Supported field types: *char* - -link_button (LinkButton) - The LinkButton widget actually simply displays a span with an icon and the - text value as content. The link is clickable and will open a new browser - window with its value as url. - - - Supported field types: *char* - -image (FieldBinaryImage) - This widget is used to represent a binary value as an image. In some cases, - the server returns a 'bin_size' instead of the real image (a bin_size is a - string representing a file size, such as 6.5kb). In that case, the widget - will make an image with a source attribute corresponding to an image on the - server. - - - Supported field types: *binary* - - Options: - - - preview_image: if the image is only loaded as a 'bin_size', then this - option is useful to inform the web client that the default field name is - not the name of the current field, but the name of another field. - - - accepted_file_extensions: the file extension the user can pick from the file input dialog box (default value is `image/\*`) - (cf: ``accept`` attribute on <input type="file"/>) - - .. code-block:: xml - - <field name="image" widget='image' options='{"preview_image":"image_128"}'/> - -binary (FieldBinaryFile) - Generic widget to allow saving/downloading a binary file. - - - Supported field types: *binary* - - Options: - - - accepted_file_extensions: the file extension the user can pick from the file input dialog box - (cf: ``accept`` attribute on <input type="file"/>) - - Attribute: - - - filename: saving a binary file will lose its file name, since it only - saves the binary value. The filename can be saved in another field. To do - that, an attribute filename should be set to a field present in the view. - - .. code-block:: xml - - <field name="datas" filename="datas_fname"/> - -priority (PriorityWidget) - This widget is rendered as a set of stars, allowing the user to click on it - to select a value or not. This is useful for example to mark a task as high - priority. - - Note that this widget also works in 'readonly' mode, which is unusual. - - - Supported field types: *selection* - -attachment_image (AttachmentImage) - Image widget for many2one fields. If the field is set, this widget will be - rendered as an image with the proper src url. This widget does not have a - different behaviour in edit or readonly mode, it is only useful to view an - image. - - - Supported field types: *many2one* - - .. code-block:: xml - - <field name="displayed_image_id" widget="attachment_image"/> - -image_selection (ImageSelection) - Allow the user to select a value by clicking on an image. - - - Supported field types: *selection* - - Options: a dictionary with a mapping from a selection value to an object with - the url for an image (*image_link*) and a preview image (*preview_link*). - - Note that this option is not optional! - - .. code-block:: xml - - <field name="external_report_layout" widget="image_selection" options="{ - 'background': { - 'image_link': '/base/static/img/preview_background.png', - 'preview_link': '/base/static/pdf/preview_background.pdf' - }, - 'standard': { - 'image_link': '/base/static/img/preview_standard.png', - 'preview_link': '/base/static/pdf/preview_standard.pdf' - } - }"/> - -label_selection (LabelSelection) - This widget renders a simple non-editable label. This is only useful to - display some information, not to edit it. - - - Supported field types: *selection* - - Options: - - - classes: a mapping from a selection value to a css class - - .. code-block:: xml - - <field name="state" widget="label_selection" options="{ - 'classes': {'draft': 'default', 'cancel': 'default', 'none': 'danger'} - }"/> - -state_selection (StateSelectionWidget) - This is a specialized selection widget. It assumes that the record has some - hardcoded fields, present in the view: *stage_id*, *legend_normal*, - *legend_blocked*, *legend_done*. This is mostly used to display and change - the state of a task in a project, with additional information displayed in - the dropdown. - - - Supported field types: *selection* - - .. code-block:: xml - - <field name="kanban_state" widget="state_selection"/> - -kanban_state_selection (StateSelectionWidget) - This is exactly the same widget as state_selection - - - Supported field types: *selection* - -boolean_favorite (FavoriteWidget) - This widget is displayed as an empty (or not) star, depending on a boolean - value. Note that it also can be edited in readonly mode. - - - Supported field types: *boolean* - -boolean_button (FieldBooleanButton) - The Boolean Button widget is meant to be used in a stat button in a form view. - The goal is to display a nice button with the current state of a boolean - field (for example, 'Active'), and allow the user to change that field when - clicking on it. - - Note that it also can be edited in readonly mode. - - - Supported field types: *boolean* - - Options: - - - terminology: it can be either 'active', 'archive', 'close' or a customized - mapping with the keys *string_true*, *string_false*, *hover_true*, *hover_false* - - .. code-block:: xml - - <field name="active" widget="boolean_button" options='{"terminology": "archive"}'/> - -boolean_toggle (BooleanToggle) - Displays a toggle switch to represent a boolean. This is a subfield of - FieldBoolean, mostly used to have a different look. - -statinfo (StatInfo) - This widget is meant to represent statistical information in a *stat button*. - It is basically just a label with a number. - - - Supported field types: *integer, float* - - Options: - - - label_field: if given, the widget will use the value of the label_field as - text. - - .. code-block:: xml - - <button name="%(act_payslip_lines)d" - icon="fa-money" - type="action"> - <field name="payslip_count" widget="statinfo" - string="Payslip" - options="{'label_field': 'label_tasks'}"/> - </button> - -percentpie (FieldPercentPie) - This widget is meant to represent statistical information in a *stat button*. - This is similar to a statinfo widget, but the information is represented in - a *pie* chart (empty to full). Note that the value is interpreted as a - percentage (a number between 0 and 100). - - - Supported field types: *integer, float* - - .. code-block:: xml - - <field name="replied_ratio" string="Replied" widget="percentpie"/> - -progressbar (FieldProgressBar) - Represent a value as a progress bar (from 0 to some value) - - - Supported field types: *integer, float* - - Options: - - - editable: boolean if value is editable - - current_value: get the current_value from the field that must be present in the view - - max_value: get the max_value from the field that must be present in the view - - edit_max_value: boolean if the max_value is editable - - title: title of the bar, displayed on top of the bar --> not translated, - use parameter (not option) "title" instead - - .. code-block:: xml - - <field name="absence_of_today" widget="progressbar" - options="{'current_value': 'absence_of_today', 'max_value': 'total_employee', 'editable': false}"/> - -toggle_button (FieldToggleBoolean) - This widget is intended to be used on boolean fields. It toggles a button - switching between a green bullet / gray bullet. It also set up a tooltip, - depending on the value and some options. - - - Supported field types: *boolean* - - Options: - - - active: the string for the tooltip that should be set when boolean is true - - inactive: the tooltip that should be set when boolean is false - - .. code-block:: xml - - <field name="payslip_status" widget="toggle_button" - options='{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}' - /> - -dashboard_graph (JournalDashboardGraph) - This is a more specialized widget, useful to display a graph representing a - set of data. For example, it is used in the accounting dashboard kanban view. - - It assumes that the field is a JSON serialization of a set of data. - - - Supported field types: *char* - - Attribute - - - graph_type: string, can be either 'line' or 'bar' - - .. code-block:: xml - - <field name="dashboard_graph_data" - widget="dashboard_graph" - graph_type="line"/> - -ace (AceEditor) - This widget is intended to be used on Text fields. It provides Ace Editor - for editing XML and Python. - - - Supported field types: *char, text* - -- badge (FieldBadge) - Displays the value inside a bootstrap badge pill. - - - Supported field types: *char*, *selection*, *many2one* - - By default, the badge has a lightgrey background, but it can be customized - by using the decoration-X mechanism. For instance, to display a red badge - under a given condition: - - .. code-block:: xml - - <field name="foo" widget"badge" decoration-danger="state == 'cancel'"/> - -Relational fields ------------------ - -.. class:: web.relational_fields.FieldSelection - - Supported field types: *selection* - - .. attribute:: placeholder - - a string which is used to display some info when no value is selected - - .. code-block:: xml - - <field name="tax_id" widget="selection" placeholder="Select a tax"/> - -radio (FieldRadio) - This is a subfield of FielSelection, but specialized to display all the - valid choices as radio buttons. - - Note that if used on a many2one records, then more rpcs will be done to fetch - the name_gets of the related records. - - - Supported field types: *selection, many2one* - - Options: - - - horizontal: if true, radio buttons will be displayed horizontally. - - .. code-block:: xml - - <field name="recommended_activity_type_id" widget="radio" - options="{'horizontal':true}"/> - -selection_badge (FieldSelectionBadge) - This is a subfield of FieldSelection, but specialized to display all the - valid choices as rectangular badges. - - - Supported field types: *selection, many2one* - - .. code-block:: xml - - <field name="recommended_activity_type_id" widget="selection_badge"/> - -many2one (FieldMany2One) - Default widget for many2one fields. - - - Supported field types: *many2one* - - Attributes: - - - can_create: allow the creation of related records (take precedence over no_create - option) - - can_write: allow the editing of related records (default: true) - - Options: - - - no_create: prevent the creation of related records - - quick_create: allow the quick creation of related records (default: true) - - no_quick_create: prevent the quick creation of related records (don't ask me) - - no_create_edit: same as no_create, maybe... - - create_name_field: when creating a related record, if this option is set, the value of the *create_name_field* will be filled with the value of the input (default: *name*) - - always_reload: boolean, default to false. If true, the widget will always - do an additional name_get to fetch its name value. This is used for the - situations where the name_get method is overridden (please do not do that) - - no_open: boolean, default to false. If set to true, the many2one will not - redirect on the record when clicking on it (in readonly mode) - - .. code-block:: xml - - <field name="currency_id" options="{'no_create': True, 'no_open': True}"/> - -list.many2one (ListFieldMany2One) - Default widget for many2one fields (in list view). - - Specialization of many2one field for list views. The main reason is that we - need to render many2one fields (in readonly mode) as a text, which does not - allow opening the related records. - - - Supported field types: *many2one* - -many2one_barcode (FieldMany2OneBarcode) - Widget for many2one fields allows to open the camera from a mobile device (Android/iOS) to scan a barcode. - - Specialization of many2one field where the user is allowed to use the native camera to scan a barcode. - Then it uses name_search to search this value. - - If this widget is set and user is not using the mobile application, - it will fallback to regular many2one (FieldMany2One) - - - Supported field types: *many2one* - -many2one_avatar (Many2OneAvatar) - This widget is only supported on many2one fields pointing to a model which - inherits from 'image.mixin'. In readonly, it displays the image of the - related record next to its display_name. Note that the display_name isn't a - clickable link in this case. In edit, it behaves exactly like the regular - many2one. - - - Supported field types: *many2one* - -many2one_avatar_user (Many2OneAvatarUser) - This widget is a specialization of the Many2OneAvatar. When the avatar is - clicked, we open a chat window with the corresponding user. This widget can - only be set on many2one fields pointing to the 'res.users' model. - - - Supported field types: *many2one* (pointing to 'res.users') - -many2one_avatar_employee (Many2OneAvatarEmployee) - Same as Many2OneAvatarUser, but for many2one fields pointing to 'hr.employee'. - - - Supported field types: *many2one* (pointing to 'hr.employee') - -kanban.many2one (KanbanFieldMany2One) - Default widget for many2one fields (in kanban view). We need to disable all - editing in kanban views. - - - Supported field types: *many2one* - -many2many (FieldMany2Many) - Default widget for many2many fields. - - - Supported field types: *many2many* - - Attributes: - - - mode: string, default view to display - - domain: restrict the data to a specific domain - - Options: - - - create_text: allow the customization of the text displayed when adding a - new record - - link: domain determining whether or not records can be added to the relation (default: True). - - unlink: domain determining whether or not records can be removed from the relation (default: True). - -many2many_binary (FieldMany2ManyBinaryMultiFiles) - This widget helps the user to upload or delete one or more files at the same - time. - - Note that this widget is specific to the model 'ir.attachment'. - - - Supported field types: *many2many* - - Options: - - - accepted_file_extensions: the file extension the user can pick from the file input dialog box - (cf: ``accept`` attribute on <input type="file"/>) - -many2many_tags (FieldMany2ManyTags) - Display many2many as a list of tags. - - - Supported field types: *many2many* - - Options: - - - create: domain determining whether or not new tags can be created (default: True). - - .. code-block:: xml - - <field name="category_id" widget="many2many_tags" options="{'create': [['some_other_field', '>', 24]]}"/> - - - color_field: the name of a numeric field, which should be present in the - view. A color will be chosen depending on its value. - - .. code-block:: xml - - <field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}"/> - - - no_edit_color: set to True to remove the possibility to change the color of the tags (default: False). - - .. code-block:: xml - - <field name="category_id" widget="many2many_tags" options="{'color_field': 'color', 'no_edit_color': True}"/> - -form.many2many_tags (FormFieldMany2ManyTags) - Specialization of many2many_tags widget for form views. It has some extra - code to allow editing the color of a tag. - - - Supported field types: *many2many* - -kanban.many2many_tags (KanbanFieldMany2ManyTags) - Specialization of many2many_tags widget for kanban views. - - - Supported field types: *many2many* - -many2many_checkboxes (FieldMany2ManyCheckBoxes) - This field displays a list of checkboxes and allows the user to select a - subset of the choices. Note that the number of displayed values is limited to - 100. This limit isn't customizable. It simply allows to handle extreme cases - where this widget is wrongly set on a field with a huge comodel. In those - cases, a list view is more adequate as it allows pagination and filtering. - - - Supported field types: *many2many* - -one2many (FieldOne2Many) - Default widget for one2many fields. - - It usually displays data in a sub list view, or a sub kanban view. - - - Supported field types: *one2many* - - Options: - - - create: domain determining whether or not related records can be created (default: True). - - - delete: domain determining whether or not related records can be deleted (default: True). - - .. code-block:: xml - - <field name="turtles" options="{'create': [['some_other_field', '>', 24]]}"/> - - - create_text: a string that is used to customize the 'Add' label/text. - - .. code-block:: xml - - <field name="turtles" options="{\'create_text\': \'Add turtle\'}"> - -statusbar (FieldStatus) - This is a really specialized widget for the form views. It is the bar on top - of many forms which represent a flow, and allow selecting a specific state. - - - Supported field types: *selection, many2one* - -reference (FieldReference) - The FieldReference is a combination of a select (for the model) and a - FieldMany2One (for its value). It allows the selection of a record on an - arbitrary model. - - - Supported field types: *char, reference* - - -Client actions -============== - -The idea of a client action is a customized widget that is integrated in the -web client interface, just like a *act_window_action*. This is useful when -you need a component that is not closely linked to an existing view or a -specific model. For example, the Discuss application is actually a client -action. - -A client action is a term that has various meanings, depending on the context: - -- from the perspective of the server, it is a record of the model *ir_action*, - with a field *tag* of type char -- from the perspective of the web client, it is a widget, which inherit from - the class AbstractAction, and is supposed to be registered in the - action registry under the corresponding key (from the field char) - -Whenever a menu item is associated to a client action, opening it will simply -fetch the action definition from the server, then lookup into its action -registry to get the Widget definition at the appropriate key, and finally, it -will instantiate and append the widget to the proper place in the DOM. - -Adding a client action ----------------------- - -A client action is a widget which will control the part of the screen below the -menu bar. It can have a control panel, if necessary. Defining a client action -can be done in two steps: implementing a new widget, and registering the widget -in the action registry. - -Implementing a new client action. - This is done by creating a widget: - - .. code-block:: javascript - - var AbstractAction = require('web.AbstractAction'); - - var ClientAction = AbstractAction.extend({ - hasControlPanel: true, - ... - }); - -Registering the client action: - As usual, we need to make the web client aware of the mapping between - client actions and the actual class: - - .. code-block:: javascript - - var core = require('web.core'); - - core.action_registry.add('my-custom-action', ClientAction); - - - Then, to use the client action in the web client, we need to create a client - action record (a record of the model ``ir.actions.client``) with the proper - ``tag`` attribute: - - .. code-block:: xml - - <record id="my_client_action" model="ir.actions.client"> - <field name="name">Some Name</field> - <field name="tag">my-custom-action</field> - </record> - - -Using the control panel ------------------------ - -By default, the client action does not display a control panel. In order to -do that, several steps should be done. - -- Set the *hasControlPanel* to *true*. - In the widget code: - - .. code-block:: javascript - - var MyClientAction = AbstractAction.extend({ - hasControlPanel: true, - loadControlPanel: true, // default: false - ... - }); - - .. warning:: - when the ``loadControlPanel`` is set to true, the client action will automatically get the content of a search view or a control panel view. - In this case, a model name should be specified like this: - - .. code-block:: javascript - - init: function (parent, action, options) { - ... - this.controlPanelParams.modelName = 'model.name'; - ... - } - -- Call the method *updateControlPanel* whenever we need to update the control panel. - For example: - - .. code-block:: javascript - - var SomeClientAction = Widget.extend({ - hasControlPanel: true, - ... - start: function () { - this._renderButtons(); - this._update_control_panel(); - ... - }, - do_show: function () { - ... - this._update_control_panel(); - }, - _renderButtons: function () { - this.$buttons = $(QWeb.render('SomeTemplate.Buttons')); - this.$buttons.on('click', ...); - }, - _update_control_panel: function () { - this.updateControlPanel({ - cp_content: { - $buttons: this.$buttons, - }, - }); - } - -The ``updateControlPanel`` is the main method to customize the content in controlpanel. -For more information, look into the `control_panel_renderer.js <https://github.com/odoo/odoo/blob/13.0/addons/web/static/src/js/views/control_panel/control_panel_renderer.js#L130>`_ file. - -.. _.appendTo(): - https://api.jquery.com/appendTo/ - -.. _.prependTo(): - https://api.jquery.com/prependTo/ - -.. _.insertAfter(): - https://api.jquery.com/insertAfter/ - -.. _.insertBefore(): - https://api.jquery.com/insertBefore/ - -.. _event delegation: - https://api.jquery.com/delegate/ - -.. _datepicker: https://github.com/Eonasdan/bootstrap-datetimepicker diff --git a/doc/reference/mixins.rst b/doc/reference/mixins.rst deleted file mode 100644 index 953b604ffa3..00000000000 --- a/doc/reference/mixins.rst +++ /dev/null @@ -1,1076 +0,0 @@ -:banner: banners/mixins.jpg - -.. _reference/mixins: - -========================= -Mixins and Useful Classes -========================= - -Odoo implements some useful classes and mixins that make it easy for you to add -often-used behaviours on your objects. This guide will details most of them, with -examples and use cases. - -.. _reference/mixins/mail: - -Messaging features -================== - -.. _reference/mixins/mail/chatter: - -Messaging integration ---------------------- - -Basic messaging system -'''''''''''''''''''''' - -Integrating messaging features to your model is extremely easy. Simply inheriting -the ``mail.thread`` model and adding the messaging fields (and their appropriate -widgets) to your form view will get you up and running in no time. - -.. admonition:: Example - - Let's create a simplistic model representing a business trip. Since organizing - this kind of trip usually involves a lot of people and a lot of discussion, let's - add support for message exchange on the model. - - .. code-block:: python - - class BusinessTrip(models.Model): - _name = 'business.trip' - _inherit = ['mail.thread'] - _description = 'Business Trip' - - name = fields.Char() - partner_id = fields.Many2one('res.partner', 'Responsible') - guest_ids = fields.Many2many('res.partner', 'Participants') - - In the form view: - - .. code-block:: xml - - <record id="businness_trip_form" model="ir.ui.view"> - <field name="name">business.trip.form</field> - <field name="model">business.trip</field> - <field name="arch" type="xml"> - <form string="Business Trip"> - <!-- Your usual form view goes here - ... - Then comes chatter integration --> - <div class="oe_chatter"> - <field name="message_follower_ids" widget="mail_followers"/> - <field name="message_ids" widget="mail_thread"/> - </div> - </form> - </field> - </record> - -Once you've added chatter support on your model, users can easily add messages -or internal notes on any record of your model; every one of those will send a -notification (to all followers for messages, to employee (*base.group_user*) -users for internal notes). If your mail gateway and catchall address are correctly -configured, these notifications will be sent by e-mail and can be replied-to directly -from your mail client; the automatic routing system will route the answer to the -correct thread. - -Server-side, some helper functions are there to help you easily send messages and -to manage followers on your record: - -.. rubric:: Posting messages - -.. method:: message_post(self, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, **kwargs) - - Post a new message in an existing thread, returning the new - mail.message ID. - - :param str body: body of the message, usually raw HTML that will - be sanitized - :param str message_type: see mail_message.message_type field - :param int parent_id: handle reply to a previous message by adding the - parent partners to the message in case of private discussion - :param list(tuple(str,str)) attachments: list of attachment tuples in the form - ``(name,content)``, where content is NOT base64 encoded - :param `\**kwargs`: extra keyword arguments will be used as default column values for the - new mail.message record - :return: ID of newly created mail.message - :rtype: int - -.. method:: message_post_with_view(views_or_xmlid, **kwargs): - - Helper method to send a mail / post a message using a view_id to - render using the ir.qweb engine. This method is stand alone, because - there is nothing in template and composer that allows to handle - views in batch. This method will probably disappear when templates - handle ir ui views. - - :param str or ``ir.ui.view`` record: external id or record of the view that - should be sent - -.. method:: message_post_with_template(template_id, **kwargs) - - Helper method to send a mail with a template - - :param template_id: the id of the template to render to create the body of the message - :param `\**kwargs`: parameter to create a mail.compose.message wizzard (which inherit from mail.message) - -.. rubric:: Receiving messages - -These methods are called when a new e-mail is processed by the mail gateway. These -e-mails can either be new thread (if they arrive via an :ref:`alias <reference/mixins/mail/alias>`) -or simply replies from an existing thread. Overriding them allows you to set values -on the thread's record depending on some values from the email itself (i.e. update -a date or an e-mail address, add CC's addresses as followers, etc.). - -.. method:: message_new(msg_dict, custom_values=None) - - Called by ``message_process`` when a new message is received - for a given thread model, if the message did not belong to - an existing thread. - - The default behavior is to create a new record of the corresponding - model (based on some very basic info extracted from the message). - Additional behavior may be implemented by overriding this method. - - :param dict msg_dict: a map containing the email details and - attachments. See ``message_process`` and ``mail.message.parse`` for details - :param dict custom_values: optional dictionary of additional - field values to pass to create() when creating the new thread record; - be careful, these values may override any other values coming from - the message - :rtype: int - :return: the id of the newly created thread object - -.. method:: message_update(msg_dict, update_vals=None) - - Called by ``message_process`` when a new message is received - for an existing thread. The default behavior is to update the record - with ``update_vals`` taken from the incoming email. - - Additional behavior may be implemented by overriding this - method. - - :param dict msg_dict: a map containing the email details and attachments; - see ``message_process`` and ``mail.message.parse()`` for details. - :param dict update_vals: a dict containing values to update records given - their ids; if the dict is None or is void, no write operation is performed. - :return: True - -.. rubric:: Followers management - -.. method:: message_subscribe(partner_ids=None, channel_ids=None, subtype_ids=None, force=True) - - Add partners to the records followers. - - :param list(int) partner_ids: IDs of the partners that will be subscribed - to the record - :param list(int) channel_ids: IDs of the channels that will be subscribed - to the record - :param list(int) subtype_ids: IDs of the subtypes that the channels/partners - will be subscribed to (defaults to the default subtypes if ``None``) - :param force: if True, delete existing followers before creating new one - using the subtypes given in the parameters - :return: Success/Failure - :rtype: bool - - -.. method:: message_unsubscribe(partner_ids=None, channel_ids=None) - - Remove partners from the record's followers. - - :param list(int) partner_ids: IDs of the partners that will be subscribed - to the record - :param list(int) channel_ids: IDs of the channels that will be subscribed - to the record - :return: True - :rtype: bool - - -.. method:: message_unsubscribe_users(user_ids=None) - - Wrapper on message_subscribe, using users. - - :param list(int) user_ids: IDs of the users that will be unsubscribed - to the record; if None, unsubscribe the current user instead. - :return: True - :rtype: bool - - -Logging changes -''''''''''''''' - -The ``mail`` module adds a powerful tracking system on fields, allowing you -to log changes to specific fields in the record's chatter. To add tracking -to a field, simple set the tracking attribute to True. - -.. admonition:: Example - - Let's track changes on the name and responsible of our business trips: - - .. code-block:: python - - class BusinessTrip(models.Model): - _name = 'business.trip' - _inherit = ['mail.thread'] - _description = 'Business Trip' - - name = fields.Char(tracking=True) - partner_id = fields.Many2one('res.partner', 'Responsible', - tracking=True) - guest_ids = fields.Many2many('res.partner', 'Participants') - - From now on, every change to a trip's name or responsible will log a note - on the record. The ``name`` field will be displayed in the notification as - well to give more context about the notification (even if the name did not - change). - - -Subtypes -'''''''' - -Subtypes give you more granular control over messages. Subtypes act as a classification -system for notifications, allowing subscribers to a document to customize the -subtype of notifications they wish to receive. - -Subtypes are created as data in your module; the model has the following fields: - -``name`` (mandatory) - :class:`~odoo.fields.Char` - name of the subtype, will be displayed in the notification customization - popup -``description`` - :class:`~odoo.fields.Char` - description that will be added in the message posted for this - subtype. If void, the name will be added instead -``internal`` - :class:`~odoo.fields.Boolean` - messages with internal subtypes will be visible only by employees, - aka members of the ``base.group_user`` group -``parent_id`` - :class:`~odoo.fields.Many2one` - link subtypes for automatic subscription; for example project subtypes are - linked to task subtypes through this link. When someone is subscribed to - a project, he will be subscribed to all tasks of this project with - subtypes found using the parent subtype -``relation_field`` - :class:`~odoo.fields.Char` - as an example, when linking project and tasks subtypes, the relation - field is the project_id field of tasks -``res_model`` - :class:`~odoo.fields.Char` - model the subtype applies to; if False, this subtype applies to all models -``default`` - :class:`~odoo.fields.Boolean` - wether the subtype is activated by default when subscribing -``sequence`` - :class:`~odoo.fields.Integer` - used to order subtypes in the notification customization popup -``hidden`` - :class:`~odoo.fields.Boolean` - wether the subtype is hidden in the notification customization popup - - -Interfacing subtypes with field tracking allows to subscribe to different kind -of notifications depending on what might interest users. To do this, you -can override the ``_track_subtype()`` function: - -.. method:: _track_subtype(init_values) - - Give the subtype triggered by the changes on the record according - to values that have been updated. - - :param dict init_values: the original values of the record; only modified fields - are present in the dict - :returns: a subtype's full external id or False if no subtype is triggered - - -.. admonition:: Example - - Let's add a ``state`` field on our example class and trigger a notification - with a specific subtype when this field change values. - - First, let's define our subtype: - - .. code-block:: xml - - <record id="mt_state_change" model="mail.message.subtype"> - <field name="name">Trip confirmed</field> - <field name="res_model">business.trip</field> - <field name="default" eval="True"/> - <field name="description">Business Trip confirmed!</field> - </record> - - - Then, we need to override the ``track_subtype()`` function. This function - is called by the tracking system to know which subtype should be used depending - on the change currently being applied. In our case, we want to use our shiny new - subtype when the ``state`` field changes from *draft* to *confirmed*: - - .. code-block:: python - - class BusinessTrip(models.Model): - _name = 'business.trip' - _inherit = ['mail.thread'] - _description = 'Business Trip' - - name = fields.Char(tracking=True) - partner_id = fields.Many2one('res.partner', 'Responsible', - tracking=True) - guest_ids = fields.Many2many('res.partner', 'Participants') - state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')], - tracking=True) - - def _track_subtype(self, init_values): - # init_values contains the modified fields' values before the changes - # - # the applied values can be accessed on the record as they are already - # in cache - self.ensure_one() - if 'state' in init_values and self.state == 'confirmed': - return self.env.ref('my_module.mt_state_change') - return super(BusinessTrip, self)._track_subtype(init_values) - - -Customizing notifications -''''''''''''''''''''''''' - -When sending notifications to followers, it can be quite useful to add buttons in -the template to allow quick actions directly from the e-mail. Even a simple button -to link directly to the record's form view can be useful; however in most cases -you don't want to display these buttons to portal users. - -The notification system allows customizing notification templates in the following -ways: - -- Display *Access Buttons*: these buttons are visible at the top of the notification - e-mail and allow the recipient to directly access the form view of the record -- Display *Follow Buttons*: these buttons allow the recipient to - directly quickly subscribe from the record -- Display *Unfollow Buttons*: these buttons allow the recipient to - directly quickly unsubscribe from the record -- Display *Custom Action Buttons*: these buttons are calls to specific routes - and allow you to make some useful actions directly available from the e-mail (i.e. - converting a lead to an opportunity, validating an expense sheet for an - Expense Manager, etc.) - -These buttons settings can be applied to different groups that you can define -yourself by overriding the function ``_notification_recipients``. - -.. method:: _notification_recipients(message, groups) - - Give the subtype triggered by the changes on the record according - to values that have been updated. - - :param ``record`` message: ``mail.message`` record currently being sent - :param list(tuple) groups: list of tuple of the form (group_name, group_func,group_data) where: - - group_name - is an identifier used only to be able to override and manipulate - groups. Default groups are ``user`` (recipients linked to an employee user), - ``portal`` (recipients linked to a portal user) and ``customer`` (recipients not - linked to any user). An example of override use would be to add a group - linked to a res.groups like Hr Officers to set specific action buttons to - them. - group_func - is a function pointer taking a partner record as parameter. This - method will be applied on recipients to know whether they belong to a given - group or not. Only first matching group is kept. Evaluation order is the - list order. - group_data - is a dict containing parameters for the notification email with the following - possible keys - values: - - - has_button_access - whether to display Access <Document> in email. True by default for - new groups, False for portal / customer. - - button_access - dict with url and title of the button - - has_button_follow - whether to display Follow in email (if recipient is not currently - following the thread). True by default for new groups, False for - portal / customer. - - button_follow - dict with url adn title of the button - - has_button_unfollow - whether to display Unfollow in email (if recipient is currently following the thread). - True by default for new groups, False for portal / customer. - - button_unfollow - dict with url and title of the button - - actions - list of action buttons to display in the notification email. - Each action is a dict containing url and title of the button. - - :returns: a subtype's full external id or False if no subtype is triggered - - -The urls in the actions list can be generated automatically by calling the -``_notification_link_helper()`` function: - - -.. method:: _notification_link_helper(self, link_type, **kwargs) - - Generate a link for the given type on the current record (or on a specific - record if the kwargs ``model`` and ``res_id`` are set). - - :param str link_type: link type to be generated; can be any of these values: - - ``view`` - link to form view of the record - ``assign`` - assign the logged user to the ``user_id`` field of - the record (if it exists) - ``follow`` - self-explanatory - ``unfollow`` - self-explanatory - ``method`` - call a method on the record; the method's name should be - provided as the kwarg ``method`` - ``new`` - open an empty form view for a new record; you can specify - a specific action by providing its id (database id or fully resolved - external id) in the kwarg ``action_id`` - - :returns: link of the type selected for the record - :rtype: str - -.. admonition:: Example - - Let's add a custom button to the Business Trip state change notification; - this button will reset the state to Draft and will be only visible to a member - of the (imaginary) group Travel Manager (``business.group_trip_manager``) - - .. code-block:: python - - class BusinessTrip(models.Model): - _name = 'business.trip' - _inherit = ['mail.thread', 'mail.alias.mixin'] - _description = 'Business Trip' - - # Pevious code goes here - - def action_cancel(self): - self.write({'state': 'draft'}) - - def _notification_recipients(self, message, groups): - """ Handle Trip Manager recipients that can cancel the trip at the last - minute and kill all the fun. """ - groups = super(BusinessTrip, self)._notification_recipients(message, groups) - - self.ensure_one() - if self.state == 'confirmed': - app_action = self._notification_link_helper('method', - method='action_cancel') - trip_actions = [{'url': app_action, 'title': _('Cancel')}] - - new_group = ( - 'group_trip_manager', - lambda partner: bool(partner.user_ids) and - any(user.has_group('business.group_trip_manager') - for user in partner.user_ids), - { - 'actions': trip_actions, - }) - - return [new_group] + groups - - - Note that that I could have defined my evaluation function outside of this - method and define a global function to do it instead of a lambda, but for - the sake of being more brief and less verbose in these documentation files - that can sometimes be boring, I choose the former instead of the latter. - -Overriding defaults -''''''''''''''''''' - -There are several ways you can customize the behaviour of ``mail.thread`` models, -including (but not limited to): - -``_mail_post_access`` - :class:`~odoo.models.Model` attribute - the required access rights to be able to post a message on the model; by - default a ``write`` access is needed, can be set to ``read`` as well - -Context keys: - These context keys can be used to somewhat control ``mail.thread`` features - like auto-subscription or field tracking during calls to ``create()`` or - ``write()`` (or any other method where it may be useful). - - - ``mail_create_nosubscribe``: at create or message_post, do not subscribe - the current user to the record thread - - ``mail_create_nolog``: at create, do not log the automatic '<Document> - created' message - - ``mail_notrack``: at create and write, do not perform the value tracking - creating messages - - ``tracking_disable``: at create and write, perform no MailThread features - (auto subscription, tracking, post, ...) - - ``mail_auto_delete``: auto delete mail notifications; True by default - - ``mail_notify_force_send``: if less than 50 email notifications to send, - send them directly instead of using the queue; True by default - - ``mail_notify_user_signature``: add the current user signature in - email notifications; True by default - - -.. _reference/mixins/mail/alias: - -Mail alias ----------- - -Aliases are configurable email addresses that are linked to a specific record -(which usually inherits the ``mail.alias.mixin`` model) that will create new records when -contacted via e-mail. They are an easy way to make your system accessible from -the outside, allowing users or customers to quickly create records in your -database without needing to connect to Odoo directly. - -Aliases vs. Incoming Mail Gateway -''''''''''''''''''''''''''''''''' - -Some people use the Incoming Mail Gateway for this same purpose. You still need -a correctly configured mail gateway to use aliases, however a single -catchall domain will be sufficient since all routing will be done inside Odoo. -Aliases have several advantages over Mail Gateways: - -* Easier to configure - * A single incoming gateway can be used by many aliases; this avoids having - to configure multiple emails on your domain name (all configuration is done - inside Odoo) - * No need for System access rights to configure aliases -* More coherent - * Configurable on the related record, not in a Settings submenu -* Easier to override server-side - * Mixin model is built to be extended from the start, allowing you to - extract useful data from incoming e-mails more easily than with a mail - gateway. - - -Alias support integration -''''''''''''''''''''''''' - -Aliases are usually configured on a parent model which will then create specific -record when contacted by e-mail. For example, Project have aliases to create tasks -or issues, Sales Team have aliases to generate Leads. - -.. note:: The model that will be created by the alias **must** inherit the - ``mail_thread`` model. - -Alias support is added by inheriting ``mail.alias.mixin``; this mixin will -generate a new ``mail.alias`` record for each record of the parent class that -gets created (for example, every ``project.project`` record having its ``mail.alias`` -record initialized on creation). - -.. note:: Aliases can also be created manually and supported by a simple - :class:`~odoo.fields.Many2one` field. This guide assumes you wish a - more complete integration with automatic creation of the alias, record-specific - default values, etc. - -Unlike ``mail.thread`` inheritance, the ``mail.alias.mixin`` **requires** some -specific overrides to work correctly. These overrides will specify the values -of the created alias, like the kind of record it must create and possibly -some default values these records may have depending on the parent object: - -.. method:: _get_alias_model_name(vals) - - Return the model name for the alias. Incoming emails that are not - replies to existing records will cause the creation of a new record - of this alias model. The value may depend on ``vals``, the dict of - values passed to ``create`` when a record of this model is created. - - :param vals dict: values of the newly created record that will holding - the alias - :return: model name - :rtype: str - -.. method:: _get_alias_values() - - Return values to create an alias, or to write on the alias after its - creation. While not completely mandatory, it is usually required to make - sure that newly created records will be linked to the alias' parent (i.e. - tasks getting created in the right project) by setting a dictionary of - default values in the alias' ``alias_defaults`` field. - - :return: dictionnary of values that will be written to the new alias - :rtype: dict - -The ``_get_alias_values()`` override is particularly interesting as it allows you -to modify the behaviour of your aliases easily. Among the fields that can be set -on the alias, the following are of particular interest: - -``alias_name`` - :class:`~odoo.fields.Char` - name of the email alias, e.g. 'jobs' if you want to catch emails for - <jobs@example.odoo.com> -``alias_user_id`` - :class:`~odoo.fields.Many2one` (``res.users``) - owner of records created upon receiving emails on this alias; - if this field is not set the system will attempt to find the right owner - based on the sender (From) address, or will use the Administrator account - if no system user is found for that address -``alias_defaults`` - :class:`~odoo.fields.Text` - Python dictionary that will be evaluated to provide - default values when creating new records for this alias -``alias_force_thread_id`` - :class:`~odoo.fields.Integer` - optional ID of a thread (record) to which all incoming messages will be - attached, even if they did not reply to it; if set, this will disable the - creation of new records completely -``alias_contact`` - :class:`~odoo.fields.Selection` - Policy to post a message on the document using the mailgateway - - - *everyone*: everyone can post - - *partners*: only authenticated partners - - *followers*: only followers of the related document or members of following channels - -Note that aliases make use of :ref:`delegation inheritance <reference/orm/inheritance>`, -which means that while the alias is stored in another table, you have -access to all these fields directly from your parent object. This allows -you to make your alias easily configurable from the record's form view. - -.. admonition:: Example - - Let's add aliases on our business trip class to create expenses on the fly via - e-mail. - - .. code-block:: python - - class BusinessTrip(models.Model): - _name = 'business.trip' - _inherit = ['mail.thread', 'mail.alias.mixin'] - _description = 'Business Trip' - - name = fields.Char(tracking=True) - partner_id = fields.Many2one('res.partner', 'Responsible', - tracking=True) - guest_ids = fields.Many2many('res.partner', 'Participants') - state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')], - tracking=True) - expense_ids = fields.One2many('business.expense', 'trip_id', 'Expenses') - alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", - required=True) - - def _get_alias_model_name(self, vals): - """ Specify the model that will get created when the alias receives a message """ - return 'business.expense' - - def _get_alias_values(self): - """ Specify some default values that will be set in the alias at its creation """ - values = super(BusinessTrip, self)._get_alias_values() - # alias_defaults holds a dictionnary that will be written - # to all records created by this alias - # - # in this case, we want all expense records sent to a trip alias - # to be linked to the corresponding business trip - values['alias_defaults'] = {'trip_id': self.id} - # we only want followers of the trip to be able to post expenses - # by default - values['alias_contact'] = 'followers' - return values - - class BusinessExpense(models.Model): - _name = 'business.expense' - _inherit = ['mail.thread'] - _description = 'Business Expense' - - name = fields.Char() - amount = fields.Float('Amount') - trip_id = fields.Many2one('business.trip', 'Business Trip') - partner_id = fields.Many2one('res.partner', 'Created by') - - We would like our alias to be easily configurable from the form view of our - business trips, so let's add the following to our form view: - - .. code-block:: xml - - <page string="Emails"> - <group name="group_alias"> - <label for="alias_name" string="Email Alias"/> - <div name="alias_def"> - <!-- display a link while in view mode and a configurable field - while in edit mode --> - <field name="alias_id" class="oe_read_only oe_inline" - string="Email Alias" required="0"/> - <div class="oe_edit_only oe_inline" name="edit_alias" - style="display: inline;" > - <field name="alias_name" class="oe_inline"/> - @ - <field name="alias_domain" class="oe_inline" readonly="1"/> - </div> - </div> - <field name="alias_contact" class="oe_inline" - string="Accept Emails From"/> - </group> - </page> - - Now we can change the alias address directly from the form view and change - who can send e-mails to the alias. - - We can then override ``message_new()`` on our expense model to fetch the values - from our email when the expense will be created: - - .. code-block:: python - - class BusinessExpense(models.Model): - # Previous code goes here - # ... - - def message_new(self, msg, custom_values=None): - """ Override to set values according to the email. - - In this simple example, we simply use the email title as the name - of the expense, try to find a partner with this email address and - do a regex match to find the amount of the expense.""" - name = msg_dict.get('subject', 'New Expense') - # Match the last occurence of a float in the string - # Example: '50.3 bar 34.5' becomes '34.5'. This is potentially the price - # to encode on the expense. If not, take 1.0 instead - amount_pattern = '(\d+(\.\d*)?|\.\d+)' - expense_price = re.findall(amount_pattern, name) - price = expense_price and float(expense_price[-1][0]) or 1.0 - # find the partner by looking for it's email - partner = self.env['res.partner'].search([('email', 'ilike', email_address)], - limit=1) - defaults = { - 'name': name, - 'amount': price, - 'partner_id': partner.id - } - defaults.update(custom_values or {}) - res = super(BusinessExpense, self).message_new(msg, custom_values=defaults) - return res - -.. _reference/mixins/mail/activities: - -Activities tracking -------------------- - -Activities are actions users have to take on a document like making a phone call -or organizing a meeting. Activities come with the mail module as they are -integrated in the Chatter but are *not bundled with mail.thread*. Activities -are records of the ``mail.activity`` class, which have a type (``mail.activity.type``), -name, description, scheduled time (among others). Pending activities are visible -above the message history in the chatter widget. - -You can integrate activities using the ``mail.activity.mixin`` class on your object -and the specific widgets to display them (via the field ``activity_ids``) in the form -view and kanban view of your records (``mail_activity`` and ``kanban_activity`` -widgets, respectively). - -.. admonition:: Example - - Organizing a business trip is a tedious process and tracking needed activities - like ordering plane tickets or a cab for the airport could be useful. To do so, - we will add the activities mixin on our model and display the next planned activities - in the message history of our trip. - - .. code-block:: python - - class BusinessTrip(models.Model): - _name = 'business.trip' - _inherit = ['mail.thread', 'mail.activity.mixin'] - _description = 'Business Trip' - - name = fields.Char() - # [...] - - We modify the form view of our trips to display their next activites: - - .. code-block:: xml - - <record id="businness_trip_form" model="ir.ui.view"> - <field name="name">business.trip.form</field> - <field name="model">business.trip</field> - <field name="arch" type="xml"> - <form string="Business Trip"> - <!-- Your usual form view goes here --> - <div class="oe_chatter"> - <field name="message_follower_ids" widget="mail_followers"/> - <field name="activity_ids" widget="mail_activity"/> - <field name="message_ids" widget="mail_thread"/> - </div> - </form> - </field> - </record> - -You can find concrete examples of integration in the following models: - -* ``crm.lead`` in the CRM (*crm*) Application -* ``sale.order`` in the Sales (*sale*) Application -* ``project.task`` in the Project (*poject*) Application - - -.. _reference/mixins/website: - -Website features -================ - -.. _reference/mixins/website/utm: - -Visitor tracking ----------------- - -The ``utm.mixin`` class can be used to track online marketing/communication -campaigns through arguments in links to specified resources. The mixin adds -3 fields to your model: - -* ``campaign_id``: :class:`~odoo.fields.Many2one` field to a ``utm.campaign`` - object (i.e. Christmas_Special, Fall_Collection, etc.) -* ``source_id``: :class:`~odoo.fields.Many2one` field to a ``utm.source`` - object (i.e. Search Engine, mailing list, etc.) -* ``medium_id``: :class:`~odoo.fields.Many2one` field to a ``utm.medium`` - object (i.e. Snail Mail, e-Mail, social network update, etc.) - -These models have a single field ``name`` (i.e. they are simply there to -distinguish campaigns but don't have any specific behaviour). - -Once a customer visits your website with these parameters set in the url -(i.e. https://www.odoo.com/?campaign_id=mixin_talk&source_id=www.odoo.com&medium_id=website), -three cookies are set in the visitor's website for these parameters. -Once a object that inherits the utm.mixin is created from the website (i.e. lead -form, job application, etc.), the utm.mixin code kicks in and fetches the values -from the cookies to set them in the new record. Once this is done, you can then -use the campaign/source/medium fields as any other field when defining reports -and views (group by, etc.). - -To extend this behaviour, simply add a relational field to a simple model (the -model should support the *quick create* (i.e. call to ``create()`` with a single -``name`` value) and extend the function ``tracking_fields()``: - -.. code-block:: python - - class UtmMyTrack(models.Model): - _name = 'my_module.my_track' - _description = 'My Tracking Object' - - name = fields.Char(string='Name', required=True) - - - class MyModel(models.Models): - _name = 'my_module.my_model' - _inherit = ['utm.mixin'] - _description = 'My Tracked Object' - - my_field = fields.Many2one('my_module.my_track', 'My Field') - - @api.model - def tracking_fields(self): - result = super(MyModel, self).tracking_fields() - result.append([ - # ("URL_PARAMETER", "FIELD_NAME_MIXIN", "NAME_IN_COOKIES") - ('my_field', 'my_field', 'odoo_utm_my_field') - ]) - return result - -This will tell the system to create a cookie named *odoo_utm_my_field* with the -value found in the url parameter ``my_field``; once a new record of this model is -created by a call from a website form, the generic override of the ``create()`` -method of ``utm.mixin`` will fetch the default values for this field from the -cookie (and the ``my_module.my_track`` record will be creatwed on the fly if it -does not exist yet). - -You can find concrete examples of integration in the following models: - -* ``crm.lead`` in the CRM (*crm*) Application -* ``hr.applicant`` in the Recruitment Process (*hr_recruitment*) Application -* ``helpdesk.ticket`` in the Helpdesk (*helpdesk* - Odoo Enterprise only) Application - -.. _reference/mixins/website/published: - -Website visibility ------------------- - -You can quite easily add a website visibility toggle on any of your record. While -this mixin is quite easy to implement manually, it is the most often-used after -the ``mail.thread`` inheritance; a testament to its usefulness. The typical use -case for this mixin is any object that has a frontend-page; being able to control -the visibility of the page allows you to take your time while editing the page -and only publish it when you're satisfied. - -To include the functionnality, you only need to inherit ``website.published.mixin``: - -.. code-block:: python - - class BlogPost(models.Model): - _name = "blog.post" - _description = "Blog Post" - _inherit = ['website.published.mixin'] - -This mixin adds 2 fields on your model: - -* ``website_published``: :class:`~odoo.fields.Boolean` field which represents - the status of the publication -* ``website_url``: :class:`~odoo.fields.Char` field which represents - the URL through which the object is accessed - -Note that this last field is a computed field and must be implemented for your class: - -.. code-block:: python - - def _compute_website_url(self): - for blog_post in self: - blog_post.website_url = "/blog/%s" % (log_post.blog_id) - -Once the mechanism is in place, you just have to adapt your frontend and backend -views to make it accessible. In the backend, adding a button in the button box is -usually the way to go: - -.. code-block:: xml - - <button class="oe_stat_button" name="website_publish_button" - type="object" icon="fa-globe"> - <field name="website_published" widget="website_button"/> - </button> - -In the frontend, some security checks are needed to avoid showing 'Editing' -buttons to website visitors: - -.. code-block:: xml - - <div id="website_published_button" class="float-right" - groups="base.group_website_publisher"> <!-- or any other meaningful group --> - <t t-call="website.publish_management"> - <t t-set="object" t-value="blog_post"/> - <t t-set="publish_edit" t-value="True"/> - <t t-set="action" t-value="'blog.blog_post_action'"/> - </t> - </div> - -Note that you must pass your object as the variable ``object`` to the template; -in this example, the ``blog.post`` record was passed as the ``blog_post`` variable -to the ``qweb`` rendering engine, it is necessary to specify this to the publish -management template. The ``publish_edit`` variable allow the frontend -button to link to the backend (allowing you to switch from frontend to backend -and vice-versa easily); if set, you must specify the full external id of the action -you want to call in the backend in the ``action`` variable (note that a Form View -must exist for the model). - -The action ``website_publish_button`` is defined in the mixin and adapts its -behaviour to your object: if the class has a valid ``website_url`` compute function, -the user is redirected to the frontend when he clicks on the button; the user -can then publish the page directly from the frontend. This ensures -that no online publication can happen by accident. If there is not compute function, -the boolean ``website_published`` is simply triggered. - -.. _reference/mixins/website/seo: - -Website metadata ----------------- - -This simple mixin simply allows you to easily inject metadata in your frontend -pages. - -.. code-block:: python - - class BlogPost(models.Model): - _name = "blog.post" - _description = "Blog Post" - _inherit = ['website.seo.metadata', 'website.published.mixin'] - -This mixin adds 3 fields on your model: - -* ``website_meta_title``: :class:`~odoo.fields.Char` field that allow you to set - an additional title to your page -* ``website_meta_description``: :class:`~odoo.fields.Char` field that contains a - short description of the page (sometimes used in search engines results) -* ``website_meta_keywords``: :class:`~odoo.fields.Char` field that contains some - keywords to help your page to be classified more precisely by search engines; the - "Promote" tool will help you select lexically-related keywords easily - -These fields are editable in the frontend using the "Promote" tool from the Editor -toolbar. Setting these fields can help search engines to better index your pages. -Note that search engines do not base their results only on these metadata; the -best SEO practice should still be to get referenced by reliable sources. - -.. _reference/mixins/misc: - -Others -====== - -.. _reference/mixins/misc/rating: - -Customer Rating ---------------- - -The rating mixin allows sending email to ask for customer rating, automatic -transitioning in a kanban processes and aggregating statistics on your ratings. - -Adding rating on your model -''''''''''''''''''''''''''' - -To add rating support, simply inherit the ``rating.mixin`` model: - -.. code-block:: python - - class MyModel(models.Models): - _name = 'my_module.my_model' - _inherit = ['rating.mixin', 'mail.thread'] - - user_id = fields.Many2one('res.users', 'Responsible') - partner_id = fields.Many2one('res.partner', 'Customer') - -The behaviour of the mixin adapts to your model: - -* The ``rating.rating`` record will be linked to the ``partner_id`` field of your - model (if the field is present). - - - this behaviour can be overridden with the function ``rating_get_partner_id()`` - if you use another field than ``partner_id`` - -* The ``rating.rating`` record will be linked to the partner of the ``user_id`` - field of your model (if the field is present) (i.e. the partner who is rated) - - - this behaviour can be overridden with the function ``rating_get_rated_partner_id()`` - if you use another field than ``user_id`` (note that the function must return a - ``res.partner``, for ``user_id`` the system automatically fetches the partner - of the user) - -* The chatter history will display the rating event (if your model inherits from - ``mail.thread``) - -Send rating requests by e-mail -'''''''''''''''''''''''''''''' - -If you wish to send emails to request a rating, simply generate an e-mail with -links to the rating object. A very basic email template could look like this: - -.. code-block:: xml - - <record id="rating_my_model_email_template" model="mail.template"> - <field name="name">My Model: Rating Request</field> - <field name="email_from">${object.rating_get_rated_partner_id().email or '' | safe}</field> - <field name="subject">Service Rating Request</field> - <field name="model_id" ref="my_module.model_my_model"/> - <field name="partner_to" >${object.rating_get_partner_id().id}</field> - <field name="auto_delete" eval="True"/> - <field name="body_html"><![CDATA[ - % set access_token = object.rating_get_access_token() - <p>Hi,</p> - <p>How satsified are you?</p> - <ul> - <li><a href="/rate/${access_token}/5">Satisfied</a></li> - <li><a href="/rate/${access_token}/3">Not satisfied</a></li> - <li><a href="/rate/${access_token}/1">Very unsatisfied</a></li> - </ul> - ]]></field> - </record> - -Your customer will then receive an e-mail with links to a simple webpage allowing -them to provide a feedback on their interaction with your users (including a free-text -feedback message). - -You can then quite easily integrate your ratings with your form view by defining -an action for the ratings: - -.. code-block:: xml - - <record id="rating_rating_action_my_model" model="ir.actions.act_window"> - <field name="name">Customer Ratings</field> - <field name="res_model">rating.rating</field> - <field name="view_mode">kanban,pivot,graph</field> - <field name="domain">[('res_model', '=', 'my_module.my_model'), ('res_id', '=', active_id), ('consumed', '=', True)]</field> - </record> - - <record id="my_module_my_model_view_form_inherit_rating" model="ir.ui.view"> - <field name="name">my_module.my_model.view.form.inherit.rating</field> - <field name="model">my_module.my_model</field> - <field name="inherit_id" ref="my_module.my_model_view_form"/> - <field name="arch" type="xml"> - <xpath expr="//div[@name='button_box']" position="inside"> - <button name="%(rating_rating_action_my_model)d" type="action" - class="oe_stat_button" icon="fa-smile-o"> - <field name="rating_count" string="Rating" widget="statinfo"/> - </button> - </xpath> - </field> - </record> - -Note that there are default views (kanban,pivot,graph) for ratings which allow -you a quick bird's eye view of your customer ratings. - -You can find concrete examples of integration in the following models: - -* ``project.task`` in the Project (*rating_project*) Application -* ``helpdesk.ticket`` in the Helpdesk (*helpdesk* - Odoo Enterprise only) Application diff --git a/doc/reference/mobile.rst b/doc/reference/mobile.rst deleted file mode 100644 index 355164e82c9..00000000000 --- a/doc/reference/mobile.rst +++ /dev/null @@ -1,213 +0,0 @@ -:banner: banners/mobile.jpg -:types: api - -.. _reference/mobile: - -================== -Mobile JavaScript -================== - -Introduction -============ - -In Odoo 10.0 we released a mobile app which allows you to access all **Odoo apps** -(even your customized modules). - -The application is a combination of **Odoo Web** and **Native Mobile -components**. In other words it is a Odoo Web instance loaded inside a native, mobile, WebView container. - -This page documents how you can access mobile native components like Camera, -Vibration, Notification and Toast through Odoo Web (via JavaScript). For this, you -do not need to be a mobile developer, if you know Odoo JavaScript API you can -access all available mobile features. - -.. warning:: These features work with **Odoo Enterprise 10.0+** only - -How does it work? -================= - -Internal workings of the mobile application: - -.. image:: images/mobile_working.jpg - -Of course, it is a web page that loads on a Mobile Native Web container. But it -is integrated in such a way that you can access native resources from your web -JavaScript. - -WebPages (Odoo Web) is on the top of each layer, where the second layer is a Bridge -between Odoo Web (JS) and the native mobile components. - -When any call from JavaScript is triggered it passes through Bridge and Bridge -passes it to the native invoker to perform that action. - -When the native component has done its work, it is passed to the Bridge again and -you get the output in JavaScript. - -Process time taken by the Native component depends on what you are requesting -from the Native resources. For example the Camera or GPS Location. - -How to use it? -============== - -Just like the Odoo Web Framework, the Mobile API can be used anywhere by getting the object from -**web_mobile.rpc** - -.. image:: images/odoo_mobile_api.png - -The mobile RPC object provides a list of methods that are available (this only works with the mobile -app). - -Check if the method is available and then execute it. - -Methods -------- - -.. note:: Each of the methods returns a JQuery Deffered object which returns - a data JSON dictionary - -Show Toast in device -..................... - -.. js:function:: showToast - - :param object args: **message** text to display - -A toast provides simple feedback about an operation in a small popup. It only -fills the amount of space required for the message and the current activity -remains visible and interactive. - -.. code-block:: javascript - - mobile.methods.showToast({'message': 'Message sent'}); - -.. image:: images/toast.png - - -Vibrating device -................ - - -.. js:function:: vibrate - - :param object args: Vibrates constantly for the specified period of time - (in milliseconds). - -Vibrate mobile device with given duration. - -.. code-block:: javascript - - mobile.methods.vibrate({'duration': 100}); - -Show snackbar with action -......................... - -.. js:function:: showSnackBar - - :param object args: (*required*) **Message** to show in snackbar and action **button label** in Snackbar (optional) - :returns: ``True`` if the user clicks on the Action button, ``False`` if SnackBar auto dismissed after some time. - -Snackbars provide lightweight feedback about an operation. They show a brief -message at the bottom of the screen on mobile or in the lower left corner on larger devices. -Snackbars appear above all the other elements on the screen and only one can be -displayed at a time. - -.. code-block:: javascript - - mobile.methods.showSnackBar({'message': 'Message is deleted', 'btn_text': 'Undo'}).then(function(result){ - if(result){ - // Do undo operation - }else{ - // Snack Bar dismissed - } - }); - -.. image:: images/snackbar.png - -Showing notification -..................... - -.. js:function:: showNotification - - :param object args: **title** (first row) of the notification, **message** (second row) of the notification, in a standard notification. - -A notification is a message you can display to the user outside of your -application's normal UI. When you tell the system to issue a notification, it -first appears as an icon in the notification area. To see the details of the -notification, the user opens the notification drawer. Both the notification -area and the notification drawer are system-controlled areas that the user can -view at any time. - -.. code-block:: javascript - - mobile.showNotification({'title': 'Simple Notification', 'message': 'This is a test for a simple notification'}) - -.. image:: images/mobile_notification.png - - -Create contact in device -......................... - -.. js:function:: addContact - - :param object args: Dictionary with contact details. Possible keys (name, mobile, phone, fax, email, website, street, street2, country_id, state_id, city, zip, parent_id, function and image) - -Create a new device contact with the given contact details. - -.. code-block:: javascript - - var contact = { - 'name': 'Michel Fletcher', - 'mobile': '9999999999', - 'phone': '7954856587', - 'fax': '765898745', - 'email': 'michel.fletcher@agrolait.example.com', - 'website': 'http://www.agrolait.com', - 'street': '69 rue de Namur', - 'street2': false, - 'country_id': [21, 'Belgium'], - 'state_id': false, - 'city': 'Wavre', - 'zip': '1300', - 'parent_id': [8, 'Agrolait'], - 'function': 'Analyst', - 'image': '<<BASE 64 Image Data>>' - } - - mobile.methods.addContact(contact); - -.. image:: images/mobile_contact_create.png - -Scanning barcodes -.................. - -.. js:function:: scanBarcode - - :returns: Scanned ``code`` from any barcode - -The barcode API detects barcodes in real-time, on the device, in any orientation. - -The barcode API can read the following barcode formats: - -* 1D barcodes: EAN-13, EAN-8, UPC-A, UPC-E, Code-39, Code-93, Code-128, ITF, Codabar -* 2D barcodes: QR Code, Data Matrix, PDF-417, AZTEC - -.. code-block:: javascript - - mobile.methods.scanBarcode().then(function(code){ - if(code){ - // Perform operation with the scanned code - } - }); - -Switching account in device -........................... - -.. js:function:: switchAccount - -Use switchAccount to switch from one account to another on the device. - -.. code-block:: javascript - - mobile.methods.switchAccount(); - -.. image:: images/mobile_switch_account.png diff --git a/doc/reference/module.rst b/doc/reference/module.rst deleted file mode 100644 index 2c91e4f6d6e..00000000000 --- a/doc/reference/module.rst +++ /dev/null @@ -1,148 +0,0 @@ -:banner: banners/module.jpg - -================ -Module Manifests -================ - - - -.. _reference/module/manifest: - -Manifest -======== - -The manifest file serves to declare a python package as an Odoo module -and to specify module metadata. - -It is a file called ``__manifest__.py`` and contains a single Python -dictionary, where each key specifies module metadatum. - -:: - - { - 'name': "A Module", - 'version': '1.0', - 'depends': ['base'], - 'author': "Author Name", - 'category': 'Category', - 'description': """ - Description text - """, - # data files always loaded at installation - 'data': [ - 'views/mymodule_view.xml', - ], - # data files containing optionally loaded demonstration data - 'demo': [ - 'demo/demo_data.xml', - ], - } - -Available manifest fields are: - -``name`` (``str``, required) - the human-readable name of the module -``version`` (``str``) - this module's version, should follow `semantic versioning`_ rules -``description`` (``str``) - extended description for the module, in reStructuredText -``author`` (``str``) - name of the module author -``website`` (``str``) - website URL for the module author -``license`` (``str``, defaults: ``LGPL-3``) - distribution license for the module. - Possible values: - - * `GPL-2` - * `GPL-2 or any later version` - * `GPL-3` - * `GPL-3 or any later version` - * `AGPL-3` - * `LGPL-3` - * `Other OSI approved licence` - * `OEEL-1` (Odoo Enterprise Edition License v1.0) - * `OPL-1` (Odoo Proprietary License v1.0) - * `Other proprietary` - -``category`` (``str``, default: ``Uncategorized``) - classification category within Odoo, rough business domain for the module. - - Although using `existing categories`_ is recommended, the field is - freeform and unknown categories are created on-the-fly. Category - hierarchies can be created using the separator ``/`` e.g. ``Foo / Bar`` - will create a category ``Foo``, a category ``Bar`` as child category of - ``Foo``, and will set ``Bar`` as the module's category. -``depends`` (``list(str)``) - Odoo modules which must be loaded before this one, either because this - module uses features they create or because it alters resources they - define. - - When a module is installed, all of its dependencies are installed before - it. Likewise dependencies are loaded before a module is loaded. -``data`` (``list(str)``) - List of data files which must always be installed or updated with the - module. A list of paths from the module root directory -``demo`` (``list(str)``) - List of data files which are only installed or updated in *demonstration - mode* -``auto_install`` (``bool``, default: ``False``) - If ``True``, this module will automatically be installed if all of its - dependencies are installed. - - It is generally used for "link modules" implementing synergic integration - between two otherwise independent modules. - - For instance ``sale_crm`` depends on both ``sale`` and ``crm`` and is set - to ``auto_install``. When both ``sale`` and ``crm`` are installed, it - automatically adds CRM campaigns tracking to sale orders without either - ``sale`` or ``crm`` being aware of one another -``external_dependencies`` (``dict(key=list(str))``) - A dictionary containing python and/or binary dependencies. - - For python dependencies, the ``python`` key must be defined for this - dictionary and a list of python modules to be imported should be assigned - to it. - - For binary dependencies, the ``bin`` key must be defined for this - dictionary and a list of binary executable names should be assigned to it. - - The module won't be installed if either the python module is not installed - in the host machine or the binary executable is not found within the - host machine's PATH environment variable. -``application`` (``bool``, default: ``False``) - Whether the module should be considered as a fully-fledged application - (``True``) or is just a technical module (``False``) that provides some - extra functionality to an existing application module. -``css`` (``list(str)``) - Specify css files with custom rules to be imported, these files should be - located in ``static/src/css`` inside the module. -``images`` (``list(str)``) - Specify image files to be used by the module. -``installable`` (``bool`` default: ``True``) - Whether a user should be able to install the module from the Web UI or not. -``maintainer`` (``str``) - Person or entity in charge of the maintenance of this module, by default - it is assumed that the author is the maintainer. -``{pre_init, post_init, uninstall}_hook`` (``str``) - Hooks for module installation/uninstallation, their value should be a - string representing the name of a function defined inside the module's - ``__init__.py``. - - ``pre_init_hook`` takes a cursor as its only argument, this function is - executed prior to the module's installation. - - ``post_init_hook`` takes a cursor and a registry as its arguments, this - function is executed right after the module's installation. - - ``uninstall_hook`` takes a cursor and a registry as its arguments, this - function is executed after the module's uninstallation. - - These hooks should only be used when setup/cleanup required for this module - is either extremely difficult or impossible through the api. -``active`` (``bool``) - This indicates whether this module must install automatically or not. - -.. _semantic versioning: https://semver.org -.. _existing categories: - https://github.com/odoo/odoo/blob/14.0/odoo/addons/base/data/ir_module_category_data.xml diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst deleted file mode 100644 index 3ca49846fb6..00000000000 --- a/doc/reference/orm.rst +++ /dev/null @@ -1,1092 +0,0 @@ -:banner: banners/orm_api.jpg - -.. _reference/orm: - -======= -ORM API -======= - -.. automodule:: odoo.models - -.. _reference/orm/models: -.. _reference/orm/model: - -Models -====== - -Model fields are defined as attributes on the model itself:: - - from odoo import models, fields - class AModel(models.Model): - _name = 'a.model.name' - - field1 = fields.Char() - -.. warning:: this means you cannot define a field and a method with the same - name, the last one will silently overwrite the former ones. - -By default, the field's label (user-visible name) is a capitalized version of -the field name, this can be overridden with the ``string`` parameter. :: - - field2 = fields.Integer(string="Field Label") - -For the list of field types and parameters, see :ref:`the fields reference -<reference/fields>`. - -Default values are defined as parameters on fields, either as a value:: - - name = fields.Char(default="a value") - -or as a function called to compute the default value, which should return that -value:: - - def _default_name(self): - return self.get_value() - - name = fields.Char(default=lambda self: self._default_name()) - -.. rubric:: API - -.. autoclass:: odoo.models.BaseModel() - - .. autoattribute:: _auto - .. attribute:: _log_access - - Whether the ORM should automatically generate and update the - :ref:`reference/fields/automatic/log_access`. - - Defaults to whatever value was set for :attr:`~._auto`. - - .. autoattribute:: _table - .. autoattribute:: _sequence - .. autoattribute:: _sql_constraints - - .. autoattribute:: _register - .. autoattribute:: _abstract - .. autoattribute:: _transient - - .. autoattribute:: _name - .. autoattribute:: _description - - .. autoattribute:: _inherit - .. autoattribute:: _inherits - - .. autoattribute:: _rec_name - .. autoattribute:: _order - - .. autoattribute:: _check_company_auto - - .. autoattribute:: _parent_name - .. autoattribute:: _parent_store - - .. autoattribute:: _date_name - .. autoattribute:: _fold_name - -AbstractModel -------------- - -.. autoclass:: odoo.models.AbstractModel() - -Model ------ - -.. autoclass:: odoo.models.Model() - - .. autoattribute:: _auto - .. autoattribute:: _abstract - .. autoattribute:: _transient - -TransientModel --------------- - -.. autoclass:: odoo.models.TransientModel() - - .. autoattribute:: _auto - .. autoattribute:: _abstract - .. autoattribute:: _transient - -.. _reference/fields: -.. _reference/orm/fields: - -Fields -====== - -.. currentmodule:: odoo.fields - -.. autoclass:: Field() - -.. .. autoattribute:: Field._slots - :annotation: - -.. _reference/fields/basic: - -Basic Fields ------------- - -.. autoclass:: Boolean() - -.. autoclass:: Char() - -.. autoclass:: Float() - -.. autoclass:: Integer() - -.. _reference/fields/advanced: - -Advanced Fields ---------------- - -.. autoclass:: Binary() - -.. autoclass:: Html() - -.. autoclass:: Image() - -.. autoclass:: Monetary() - -.. autoclass:: Selection() - -.. autoclass:: Text() - -.. _reference/fields/date: - -Date(time) Fields -''''''''''''''''' - -:class:`Dates <odoo.fields.Date>` and :class:`Datetimes <odoo.fields.Datetime>` -are very important fields in any kind of business application. -Their misuse can create invisible yet painful bugs, this section -aims to provide Odoo developers with the knowledge required -to avoid misusing these fields. - -When assigning a value to a Date/Datetime field, the following options are valid: - -* A `date` or `datetime` object. -* A string in the proper server format: - - * ``YYYY-MM-DD`` for :class:`~odoo.fields.Date` fields, - * ``YYYY-MM-DD HH:MM:SS`` for :class:`~odoo.fields.Datetime` fields. - -* `False` or `None`. - -The Date and Datetime fields class have helper methods to attempt conversion -into a compatible type: - -* :func:`~odoo.fields.Date.to_date` will convert to a :class:`datetime.date` -* :func:`~odoo.fields.Datetime.to_datetime` will convert to a :class:`datetime.datetime`. - -.. admonition:: Example - - To parse date/datetimes coming from external sources:: - - fields.Date.to_date(self._context.get('date_from')) - -Date / Datetime comparison best practices: - -* Date fields can **only** be compared to date objects. -* Datetime fields can **only** be compared to datetime objects. - -.. warning:: Strings representing dates and datetimes can be compared - between each other, however the result may not be the expected - result, as a datetime string will always be greater than a - date string, therefore this practice is **heavily** - discouraged. - -Common operations with dates and datetimes such as addition, substraction or -fetching the start/end of a period are exposed through both -:class:`~odoo.fields.Date` and :class:`~odoo.fields.Datetime`. -These helpers are also available by importing `odoo.tools.date_utils`. - -.. note:: Timezones - - Datetime fields are stored as `timestamp without timezone` columns in the database and are stored - in the UTC timezone. This is by design, as it makes the Odoo database independent from the timezone - of the hosting server system. Timezone conversion is managed entirely by the client side. - -.. autoclass:: Date() - :members: today, context_today, to_date, to_string, start_of, end_of, add, subtract - -.. autoclass:: Datetime() - :members: now, today, context_timestamp, to_datetime, to_string, start_of, end_of, add, subtract - -.. _reference/fields/relational: - -Relational Fields -''''''''''''''''' - -.. autoclass:: Many2one() - -.. autoclass:: One2many() - -.. autoclass:: Many2many() - -Pseudo-relational fields -'''''''''''''''''''''''' - -.. autoclass:: Reference() - -.. autoclass:: Many2oneReference() - -.. _reference/fields/compute: - -Computed Fields -''''''''''''''' - -Fields can be computed (instead of read straight from the database) using the -``compute`` parameter. **It must assign the computed value to the field**. If -it uses the values of other *fields*, it should specify those fields using -:func:`~odoo.api.depends`. :: - - from odoo import api - total = fields.Float(compute='_compute_total') - - @api.depends('value', 'tax') - def _compute_total(self): - for record in self: - record.total = record.value + record.value * record.tax - -* dependencies can be dotted paths when using sub-fields:: - - @api.depends('line_ids.value') - def _compute_total(self): - for record in self: - record.total = sum(line.value for line in record.line_ids) - -* computed fields are not stored by default, they are computed and - returned when requested. Setting ``store=True`` will store them in the - database and automatically enable searching. -* searching on a computed field can also be enabled by setting the ``search`` - parameter. The value is a method name returning a - :ref:`reference/orm/domains`. :: - - upper_name = field.Char(compute='_compute_upper', search='_search_upper') - - def _search_upper(self, operator, value): - if operator == 'like': - operator = 'ilike' - return [('name', operator, value)] - - The search method is invoked when processing domains before doing an - actual search on the model. It must return a domain equivalent to the - condition: ``field operator value``. - -.. TODO and/or by setting the store to True for search domains ? - -* Computed fields are readonly by default. To allow *setting* values on a computed field, use the ``inverse`` - parameter. It is the name of a function reversing the computation and - setting the relevant fields:: - - document = fields.Char(compute='_get_document', inverse='_set_document') - - def _get_document(self): - for record in self: - with open(record.get_document_path) as f: - record.document = f.read() - def _set_document(self): - for record in self: - if not record.document: continue - with open(record.get_document_path()) as f: - f.write(record.document) - -* multiple fields can be computed at the same time by the same method, just - use the same method on all fields and set all of them:: - - discount_value = fields.Float(compute='_apply_discount') - total = fields.Float(compute='_apply_discount') - - @api.depends('value', 'discount') - def _apply_discount(self): - for record in self: - # compute actual discount from discount percentage - discount = record.value * record.discount - record.discount_value = discount - record.total = record.value - discount - -.. warning:: - - While it is possible to use the same compute method for multiple - fields, it is not recommended to do the same for the inverse - method. - - During the computation of the inverse, **all** fields that use - said inverse are protected, meaning that they can't be computed, - even if their value is not in the cache. - - If any of those fields is accessed and its value is not in cache, - the ORM will simply return a default value of `False` for these fields. - This means that the value of the inverse fields (other than the one - triggering the inverse method) may not give their correct value and - this will probably break the expected behavior of the inverse method. - -.. _reference/fields/related: - -Related fields -'''''''''''''' - -A special case of computed fields are *related* (proxy) fields, which provide -the value of a sub-field on the current record. They are defined by setting -the ``related`` parameter and like regular computed fields they can be -stored:: - - nickname = fields.Char(related='user_id.partner_id.name', store=True) - -The value of a related field is given by following a sequence of -relational fields and reading a field on the reached model. The complete -sequence of fields to traverse is specified by the ``related`` attribute. - -Some field attributes are automatically copied from the source field if -they are not redefined: ``string``, ``help``, ``readonly``, ``required`` (only -if all fields in the sequence are required), ``groups``, ``digits``, ``size``, -``translate``, ``sanitize``, ``selection``, ``comodel_name``, ``domain``, -``context``. All semantic-free attributes are copied from the source -field. - -By default, the values of related fields are not stored to the database. -Add the attribute ``store=True`` to make it stored, just like computed -fields. Related fields are automatically recomputed when their -dependencies are modified. - -.. note:: The related fields are computed in sudo mode. - -.. warning:: - - You cannot chain :class:`~odoo.fields.Many2many` or :class:`~odoo.fields.One2many` fields in ``related`` fields dependencies. - - ``related`` can be used to refer to a :class:`~odoo.fields.One2many` or - :class:`~odoo.fields.Many2many` field on another model on the - condition that it's done through a ``Many2one`` relation on the current model. - ``One2many`` and ``Many2many`` are not supported and the results will not be - aggregated correctly:: - - m2o_id = fields.Many2one() - m2m_ids = fields.Many2many() - o2m_ids = fields.One2many() - - # Supported - d_ids = fields.Many2many(related="m2o_id.m2m_ids") - e_ids = fields.One2many(related="m2o_id.o2m_ids") - - # Won't work: use a custom Many2many computed field instead - f_ids = fields.Many2many(related="m2m_ids.m2m_ids") - g_ids = fields.One2many(related="o2m_ids.o2m_ids") - -.. _reference/fields/automatic: - -Automatic fields ----------------- - -.. attribute:: id - - Identifier :class:`field <odoo.fields.Field>` - - If length of current recordset is 1, return id of unique record in it. - - Raise an Error otherwise. - -.. _reference/fields/automatic/log_access: - -Access Log fields -''''''''''''''''' - -These fields are automatically set and updated if -:attr:`~odoo.models.BaseModel._log_access` is enabled. It can be -disabled to avoid creating or updating those fields on tables for which they are -not useful. - -By default, :attr:`~odoo.models.BaseModel._log_access` is set to the same value -as :attr:`~odoo.models.BaseModel._auto` - -.. attribute:: create_date - - Stores when the record was created, :class:`~odoo.fields.Datetime` - -.. attribute:: create_uid - - Stores *who* created the record, :class:`~odoo.fields.Many2one` to a - ``res.users``. - -.. attribute:: write_date - - Stores when the record was last updated, :class:`~odoo.fields.Datetime` - -.. attribute:: write_uid - - Stores who last updated the record, :class:`~odoo.fields.Many2one` to a - ``res.users``. - -.. warning:: :attr:`~odoo.models.BaseModel._log_access` *must* be enabled on - :class:`~odoo.models.TransientModel`. - -.. _reference/orm/fields/reserved: - -Reserved Field names --------------------- - -A few field names are reserved for pre-defined behaviors beyond that of -automated fields. They should be defined on a model when the related -behavior is desired: - -.. attribute:: name - - default value for :attr:`~odoo.models.BaseModel._rec_name`, used to - display records in context where a representative "naming" is - necessary. - - :class:`~odoo.fields.Char` - -.. attribute:: active - - toggles the global visibility of the record, if ``active`` is set to - ``False`` the record is invisible in most searches and listing. - - :class:`~odoo.fields.Boolean` - -.. .. attribute:: sequence -.. -.. Alterable ordering criteria, allows drag-and-drop reordering of models -.. in list views. -.. -.. :class:`~odoo.fields.Integer` - -.. attribute:: state - - lifecycle stages of the object, used by the ``states`` attribute on - :class:`fields <odoo.fields.Field>`. - - :class:`~odoo.fields.Selection` - -.. attribute:: parent_id - - default_value of :attr:`~._parent_name`, used to organize - records in a tree structure and enables the ``child_of`` - and ``parent_of`` operators in domains. - - :class:`~odoo.fields.Many2one` - -.. attribute:: parent_path - - When :attr:`~._parent_store` is set to True, used to store a value reflecting - the tree structure of :attr:`~._parent_name`, and to optimize the operators - ``child_of`` and ``parent_of`` in search domains. - It must be declared with ``index=True`` for proper operation. - - :class:`~odoo.fields.Char` - -.. attribute:: company_id - - Main field name used for Odoo multi-company behavior. - - Used by `:meth:~odoo.models._check_company` to check multi company consistency. - Defines whether a record is shared between companies (no value) or only - accessible by the users of a given company. - - :class:`~odoo.fields.Many2one` - :type: :class:`~odoo.addons.base.models.res_company` - -Recordsets -========== - -Interactions with models and records are performed through recordsets, an ordered -collection of records of the same model. - -.. warning:: Contrary to what the name implies, it is currently possible for - recordsets to contain duplicates. This may change in the future. - -Methods defined on a model are executed on a recordset, and their ``self`` is -a recordset:: - - class AModel(models.Model): - _name = 'a.model' - def a_method(self): - # self can be anything between 0 records and all records in the - # database - self.do_operation() - -Iterating on a recordset will yield new sets of *a single record* -("singletons"), much like iterating on a Python string yields strings of a -single characters:: - - def do_operation(self): - print(self) # => a.model(1, 2, 3, 4, 5) - for record in self: - print(record) # => a.model(1), then a.model(2), then a.model(3), ... - -Field access ------------- - -Recordsets provide an "Active Record" interface: model fields can be read and -written directly from the record as attributes. - -.. note:: - - When accessing non-relational fields on a recordset of potentially multiple - records, use :meth:`~odoo.models.BaseModel.mapped`:: - - total_qty = sum(self.mapped('qty')) - -Field values can also be accessed like dict items, which is more elegant and -safer than ``getattr()`` for dynamic field names. -Setting a field's value triggers an update to the database:: - - >>> record.name - Example Name - >>> record.company_id.name - Company Name - >>> record.name = "Bob" - >>> field = "name" - >>> record[field] - Bob - -.. warning:: - - Trying to read a field on multiple records will raise an error for non relational - fields. - -Accessing a relational field (:class:`~odoo.fields.Many2one`, -:class:`~odoo.fields.One2many`, :class:`~odoo.fields.Many2many`) -*always* returns a recordset, empty if the field is not set. - -Record cache and prefetching ----------------------------- - -Odoo maintains a cache for the fields of the records, so that not every field -access issues a database request, which would be terrible for performance. The -following example queries the database only for the first statement:: - - record.name # first access reads value from database - record.name # second access gets value from cache - -To avoid reading one field on one record at a time, Odoo *prefetches* records -and fields following some heuristics to get good performance. Once a field must -be read on a given record, the ORM actually reads that field on a larger -recordset, and stores the returned values in cache for later use. The prefetched -recordset is usually the recordset from which the record comes by iteration. -Moreover, all simple stored fields (boolean, integer, float, char, text, date, -datetime, selection, many2one) are fetched altogether; they correspond to the -columns of the model's table, and are fetched efficiently in the same query. - -Consider the following example, where ``partners`` is a recordset of 1000 -records. Without prefetching, the loop would make 2000 queries to the database. -With prefetching, only one query is made:: - - for partner in partners: - print partner.name # first pass prefetches 'name' and 'lang' - # (and other fields) on all 'partners' - print partner.lang - -The prefetching also works on *secondary records*: when relational fields are -read, their values (which are records) are subscribed for future prefetching. -Accessing one of those secondary records prefetches all secondary records from -the same model. This makes the following example generate only two queries, one -for partners and one for countries:: - - countries = set() - for partner in partners: - country = partner.country_id # first pass prefetches all partners - countries.add(country.name) # first pass prefetches all countries - - -.. _reference/api/decorators: - -Method decorators -================= - -.. automodule:: odoo.api - :members: depends, depends_context, constrains, onchange, returns, autovacuum, model, model_create_multi - -.. .. currentmodule:: odoo.api - -.. .. autodata:: model -.. .. autodata:: depends -.. .. autodata:: constrains -.. .. autodata:: onchange -.. .. autodata:: returns -.. .. autodata:: autovacuum - -.. todo:: With sphinx 2.0 : autodecorator - -.. todo:: Add in Views reference - * It is possible to suppress the trigger from a specific field by adding - ``on_change="0"`` in a view:: - - <field name="name" on_change="0"/> - - will not trigger any interface update when the field is edited by the user, - even if there are function fields or explicit onchange depending on that - field. - -.. _reference/orm/environment: - -Environment -=========== - -The :class:`~odoo.api.Environment` stores various contextual data used by -the ORM: the database cursor (for database queries), the current user -(for access rights checking) and the current context (storing arbitrary -metadata). The environment also stores caches. - -All recordsets have an environment, which is immutable, can be accessed -using :attr:`~odoo.models.Model.env` and gives access to: - -* the current user (:attr:`~odoo.api.Environment.user`) -* the cursor (:attr:`~odoo.api.Environment.cr`) -* the superuser flag (:attr:`~odoo.api.Environment.su`) -* or the context (:attr:`~odoo.api.Environment.context`) - -.. code-block:: bash - - >>> records.env - <Environment object ...> - >>> records.env.user - res.user(3) - >>> records.env.cr - <Cursor object ...) - -When creating a recordset from an other recordset, the environment is -inherited. The environment can be used to get an empty recordset in an -other model, and query that model:: - - >>> self.env['res.partner'] - res.partner() - >>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]]) - res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74) - -.. currentmodule:: odoo.api - -.. automethod:: Environment.ref - -.. autoattribute:: Environment.lang - -.. autoattribute:: Environment.user - -.. autoattribute:: Environment.company - -.. autoattribute:: Environment.companies - -.. TODO cr, uid but not @property or methods of Environment class... - -Altering the environment ------------------------- - -.. currentmodule:: odoo.models - -.. automethod:: Model.with_context - -.. automethod:: Model.with_user - -.. automethod:: Model.with_company - -.. automethod:: Model.with_env - -.. automethod:: Model.sudo - -.. _reference/orm/sql: - -SQL Execution -------------- - -The :attr:`~odoo.api.Environment.cr` attribute on environments is the -cursor for the current database transaction and allows executing SQL directly, -either for queries which are difficult to express using the ORM (e.g. complex -joins) or for performance reasons:: - - self.env.cr.execute("some_sql", param1, param2, param3) - -Because models use the same cursor and the :class:`~odoo.api.Environment` -holds various caches, these caches must be invalidated when *altering* the -database in raw SQL, or further uses of models may become incoherent. It is -necessary to clear caches when using ``CREATE``, ``UPDATE`` or ``DELETE`` in -SQL, but not ``SELECT`` (which simply reads the database). - -.. note:: - Clearing caches can be performed using the - :meth:`~odoo.models.Model.invalidate_cache` method. - -.. automethod:: Model.invalidate_cache - -.. warning:: - Executing raw SQL bypasses the ORM, and by consequent, Odoo security rules. - Please make sure your queries are sanitized when using user input and prefer using - ORM utilities if you don't really need to use SQL queries. - - -.. _reference/orm/models/crud: - -Common ORM methods -================== - -.. currentmodule:: odoo.models - -Create/update -------------- - -.. todo:: api.model_create_multi information - -.. automethod:: Model.create - -.. automethod:: Model.copy - -.. automethod:: Model.default_get - -.. automethod:: Model.name_create - -.. automethod:: Model.write - -.. automethod:: Model.flush - -Search/Read ------------ - -.. automethod:: Model.browse - -.. automethod:: Model.search - -.. automethod:: Model.search_count - -.. automethod:: Model.name_search - -.. automethod:: Model.read - -.. automethod:: Model.read_group - -Fields/Views -'''''''''''' - -.. automethod:: Model.fields_get - -.. automethod:: Model.fields_view_get - -.. _reference/orm/domains: - -Search domains -'''''''''''''' - -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``) - a field name of the current model, or a relationship traversal through - a :class:`~odoo.fields.Many2one` using dot-notation e.g. ``'street'`` - or ``'partner_id.country'`` - -* ``operator`` (``str``) - 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 (value can be either - one item or a list of items). - - Takes the semantics of the model into account (i.e following the - relationship field named by - :attr:`~odoo.models.Model._parent_name`). - ``parent_of`` - is a parent (ascendant) of a ``value`` record (value can be either - one item or a list of items). - - Takes the semantics of the model into account (i.e following the - relationship field named by - :attr:`~odoo.models.Model._parent_name`). - -* ``value`` - variable type, must be comparable (through ``operator``) to the named - field. - -Domain criteria can be combined using logical operators in *prefix* form: - -``'&'`` - logical *AND*, default operation to combine criteria following one - another. Arity 2 (uses the next 2 criteria or combinations). -``'|'`` - logical *OR*, arity 2. -``'!'`` - logical *NOT*, arity 1. - - .. note:: Mostly to negate combinations of criteria - Individual criterion generally have a negative form (e.g. ``=`` -> - ``!=``, ``<`` -> ``>=``) which is simpler than negating the positive. - -.. admonition:: Example - - To search for partners named *ABC*, from belgium or germany, whose language - is not english:: - - [('name','=','ABC'), - ('language.code','!=','en_US'), - '|',('country_id.code','=','be'), - ('country_id.code','=','de')] - - This domain is interpreted as: - - .. code-block:: text - - (name is 'ABC') - AND (language is NOT english) - AND (country is Belgium OR Germany) - -Unlink ------- - -.. automethod:: Model.unlink - -.. _reference/orm/records/info: - -Record(set) information ------------------------ - -.. autoattribute:: Model.ids - -.. attribute:: env - - Returns the environment of the given recordset. - - :type: :class:`~odoo.api.Environment` - -.. todo:: Environment documentation - -.. automethod:: Model.exists - -.. automethod:: Model.ensure_one - -.. automethod:: Model.name_get - -.. automethod:: Model.get_metadata - -.. _reference/orm/records/operations: - -Operations ----------- - -Recordsets are immutable, but sets of the same model can be combined using -various set operations, returning new recordsets. - -.. addition preserves order but can introduce duplicates - -* ``record in set`` returns whether ``record`` (which must be a 1-element - recordset) is present in ``set``. ``record not in set`` is the inverse - operation -* ``set1 <= set2`` and ``set1 < set2`` return whether ``set1`` is a subset - of ``set2`` (resp. strict) -* ``set1 >= set2`` and ``set1 > set2`` return whether ``set1`` is a superset - of ``set2`` (resp. strict) -* ``set1 | set2`` returns the union of the two recordsets, a new recordset - containing all records present in either source -* ``set1 & set2`` returns the intersection of two recordsets, a new recordset - containing only records present in both sources -* ``set1 - set2`` returns a new recordset containing only records of ``set1`` - which are *not* in ``set2`` - -Recordsets are iterable so the usual Python tools are available for -transformation (:func:`python:map`, :func:`python:sorted`, -:func:`~python:itertools.ifilter`, ...) however these return either a -:class:`python:list` or an :term:`python:iterator`, removing the ability to -call methods on their result, or to use set operations. - -Recordsets therefore provide the following operations returning recordsets themselves -(when possible): - -Filter -'''''' - -.. automethod:: Model.filtered - -.. automethod:: Model.filtered_domain - -Map -''' - -.. automethod:: Model.mapped - -.. note:: - - Since V13, multi-relational field access is supported and works like a mapped call: - - .. code-block:: python3 - - records.partner_id # == records.mapped('partner_id') - records.partner_id.bank_ids # == records.mapped('partner_id.bank_ids') - records.partner_id.mapped('name') # == records.mapped('partner_id.name') - -Sort -'''' - -.. automethod:: Model.sorted - -.. _reference/orm/inheritance: - -Inheritance and extension -========================= - -Odoo provides three different mechanisms to extend models in a modular way: - -* creating a new model from an existing one, adding new information to the - copy but leaving the original module as-is -* extending models defined in other modules in-place, replacing the previous - version -* delegating some of the model's fields to records it contains - -.. image:: ../images/inheritance_methods.png - :align: center - -Classical inheritance ---------------------- - -When using the :attr:`~odoo.models.Model._inherit` and -:attr:`~odoo.models.Model._name` attributes together, Odoo creates a new -model using the existing one (provided via -:attr:`~odoo.models.Model._inherit`) as a base. The new model gets all the -fields, methods and meta-information (defaults & al) from its base. - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/inheritance.py - :language: python - :lines: 6- - -and using them: - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_inheritance.py - :language: python - :lines: 10,11,14,19 - -will yield: - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_inheritance.py - :language: text - :lines: 16,21 - -the second model has inherited from the first model's ``check`` method and its -``name`` field, but overridden the ``call`` method, as when using standard -:ref:`Python inheritance <python:tut-inheritance>`. - -Extension ---------- - -When using :attr:`~odoo.models.Model._inherit` but leaving out -:attr:`~odoo.models.Model._name`, the new model replaces the existing one, -essentially extending it in-place. This is useful to add new fields or methods -to existing models (created in other modules), or to customize or reconfigure -them (e.g. to change their default sort order): - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/extension.py - :language: python - :lines: 6- - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_extension.py - :language: python - :lines: 10,15 - -will yield: - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_extension.py - :language: text - :lines: 13 - -.. note:: - - It will also yield the various :ref:`automatic fields - <reference/fields/automatic>` unless they've been disabled - -Delegation ----------- - -The third inheritance mechanism provides more flexibility (it can be altered -at runtime) but less power: using the :attr:`~odoo.models.Model._inherits` -a model *delegates* the lookup of any field not found on the current model -to "children" models. The delegation is performed via -:class:`~odoo.fields.Reference` fields automatically set up on the parent -model. - -The main difference is in the meaning. When using Delegation, the model -**has one** instead of **is one**, turning the relationship in a composition -instead of inheritance: - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/delegation.py - :language: python - :lines: 5- - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_delegation.py - :language: python - :lines: 11-14,23,28 - -will result in: - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_delegation.py - :language: text - :lines: 25,30 - -and it's possible to write directly on the delegated field: - -.. literalinclude:: ../../odoo/addons/test_documentation_examples/tests/test_delegation.py - :language: python - :lines: 45 - -.. warning:: when using delegation inheritance, methods are *not* inherited, - only fields - -.. warning:: - - * `_inherits` is more or less implemented, avoid it if you can; - * chained `_inherits` is essentially not implemented, we cannot guarantee anything on the final behavior. - - -Fields Incremental Definition ------------------------------ - -A field is defined as class attribute on a model class. If the model -is extended, one can also extend the field definition by redefining -a field with the same name and same type on the subclass. -In that case, the attributes of the field are taken from the parent class -and overridden by the ones given in subclasses. - -For instance, the second class below only adds a tooltip on the field -``state``:: - - class First(models.Model): - _name = 'foo' - state = fields.Selection([...], required=True) - - class Second(models.Model): - _inherit = 'foo' - state = fields.Selection(help="Blah blah blah") - -.. _reference/exceptions: - -Error management -================ - -.. automodule:: odoo.exceptions - :members: AccessDenied, AccessError, CacheMiss, MissingError, RedirectWarning, UserError, ValidationError diff --git a/doc/reference/qweb.rst b/doc/reference/qweb.rst deleted file mode 100644 index b224f46f2d4..00000000000 --- a/doc/reference/qweb.rst +++ /dev/null @@ -1,686 +0,0 @@ -:banner: banners/qweb.jpg - -.. highlight:: xml - -.. _reference/qweb: - -==== -QWeb -==== - -QWeb is the primary templating_ engine used by Odoo\ [#othertemplates]_. It -is an XML templating engine\ [#genshif]_ and used mostly to generate HTML_ -fragments and pages. - -Template directives are specified as XML attributes prefixed with ``t-``, -for instance ``t-if`` for :ref:`reference/qweb/conditionals`, with elements -and other attributes being rendered directly. - -To avoid element rendering, a placeholder element ``<t>`` is also available, -which executes its directive but doesn't generate any output in and of -itself:: - - <t t-if="condition"> - <p>Test</p> - </t> - -will result in:: - - <p>Test</p> - -if ``condition`` is true, but:: - - <div t-if="condition"> - <p>Test</p> - </div> - -will result in:: - - <div> - <p>Test</p> - </div> - -.. _reference/qweb/output: - -Data output -=========== - -QWeb has a primary output directive which automatically HTML-escape its -content limiting XSS_ risks when displaying user-provided content: ``esc``. - -``esc`` takes an expression, evaluates it and prints the content:: - - <p><t t-esc="value"/></p> - -rendered with the value ``value`` set to ``42`` yields:: - - <p>42</p> - -There is one other output directive ``raw`` which behaves the same as -respectively ``esc`` but *does not HTML-escape its output*. It can be useful -to display separately constructed markup (e.g. from functions) or already -sanitized user-provided markup. - -.. _reference/qweb/conditionals: - -Conditionals -============ - -QWeb has a conditional directive ``if``, which evaluates an expression given -as attribute value:: - - <div> - <t t-if="condition"> - <p>ok</p> - </t> - </div> - -The element is rendered if the condition is true:: - - <div> - <p>ok</p> - </div> - -but if the condition is false it is removed from the result:: - - <div> - </div> - -The conditional rendering applies to the bearer of the directive, which does -not have to be ``<t>``:: - - <div> - <p t-if="condition">ok</p> - </div> - -will give the same results as the previous example. - -Extra conditional branching directives ``t-elif`` and ``t-else`` are also -available:: - - <div> - <p t-if="user.birthday == today()">Happy birthday!</p> - <p t-elif="user.login == 'root'">Welcome master!</p> - <p t-else="">Welcome!</p> - </div> - - -.. _reference/qweb/loops: - -Loops -===== - -QWeb has an iteration directive ``foreach`` which take an expression returning -the collection to iterate on, and a second parameter ``t-as`` providing the -name to use for the "current item" of the iteration:: - - <t t-foreach="[1, 2, 3]" t-as="i"> - <p><t t-esc="i"/></p> - </t> - -will be rendered as:: - - <p>1</p> - <p>2</p> - <p>3</p> - -Like conditions, ``foreach`` applies to the element bearing the directive's -attribute, and - -:: - - <p t-foreach="[1, 2, 3]" t-as="i"> - <t t-esc="i"/> - </p> - -is equivalent to the previous example. - -``foreach`` can iterate on an array (the current item will be the current -value) or a mapping (the current item will be the current key). Iterating on an -integer (equivalent to iterating on an array between 0 inclusive and the -provided integer exclusive) is still supported but deprecated. - -In addition to the name passed via ``t-as``, ``foreach`` provides a few other -variables for various data points: - -.. warning:: ``$as`` will be replaced by the name passed to ``t-as`` - -:samp:`{$as}_all` (deprecated) - the object being iterated over - - .. note:: This variable is only available on JavaScript QWeb, not Python. - -:samp:`{$as}_value` - the current iteration value, identical to ``$as`` for lists and integers, - but for mappings it provides the value (where ``$as`` provides the key) -:samp:`{$as}_index` - the current iteration index (the first item of the iteration has index 0) -:samp:`{$as}_size` - the size of the collection if it is available -:samp:`{$as}_first` - whether the current item is the first of the iteration (equivalent to - :samp:`{$as}_index == 0`) -:samp:`{$as}_last` - whether the current item is the last of the iteration (equivalent to - :samp:`{$as}_index + 1 == {$as}_size`), requires the iteratee's size be - available -:samp:`{$as}_parity` (deprecated) - either ``"even"`` or ``"odd"``, the parity of the current iteration round -:samp:`{$as}_even` (deprecated) - a boolean flag indicating that the current iteration round is on an even - index -:samp:`{$as}_odd` (deprecated) - a boolean flag indicating that the current iteration round is on an odd - index - -These extra variables provided and all new variables created into the -``foreach`` are only available in the scope of the``foreach``. If the -variable exists outside the context of the ``foreach``, the value is copied -at the end of the foreach into the global context. - -:: - - <t t-set="existing_variable" t-value="False"/> - <!-- existing_variable now False --> - - <p t-foreach="[1, 2, 3]" t-as="i"> - <t t-set="existing_variable" t-value="True"/> - <t t-set="new_variable" t-value="True"/> - <!-- existing_variable and new_variable now True --> - </p> - - <!-- existing_variable always True --> - <!-- new_variable undefined --> - -.. _reference/qweb/attributes: - -attributes -========== - -QWeb can compute attributes on-the-fly and set the result of the computation -on the output node. This is done via the ``t-att`` (attribute) directive which -exists in 3 different forms: - -:samp:`t-att-{$name}` - an attribute called ``$name`` is created, the attribute value is evaluated - and the result is set as the attribute's value:: - - <div t-att-a="42"/> - - will be rendered as:: - - <div a="42"></div> -:samp:`t-attf-{$name}` - same as previous, but the parameter is a :term:`format string` - instead of just an expression, often useful to mix literal and non-literal - string (e.g. classes):: - - <t t-foreach="[1, 2, 3]" t-as="item"> - <li t-attf-class="row {{ (item_index % 2 === 0) ? 'even' : 'odd' }}"> - <t t-esc="item"/> - </li> - </t> - - will be rendered as:: - - <li class="row even">1</li> - <li class="row odd">2</li> - <li class="row even">3</li> -:samp:`t-att=mapping` - if the parameter is a mapping, each (key, value) pair generates a new - attribute and its value:: - - <div t-att="{'a': 1, 'b': 2}"/> - - will be rendered as:: - - <div a="1" b="2"></div> -:samp:`t-att=pair` - if the parameter is a pair (tuple or array of 2 element), the first - item of the pair is the name of the attribute and the second item is the - value:: - - <div t-att="['a', 'b']"/> - - will be rendered as:: - - <div a="b"></div> - -setting variables -================= - -QWeb allows creating variables from within the template, to memoize a -computation (to use it multiple times), give a piece of data a clearer name, -... - -This is done via the ``set`` directive, which takes the name of the variable -to create. The value to set can be provided in two ways: - -* a ``t-value`` attribute containing an expression, and the result of its - evaluation will be set:: - - <t t-set="foo" t-value="2 + 1"/> - <t t-esc="foo"/> - - will print ``3`` -* if there is no ``t-value`` attribute, the node's body is rendered and set - as the variable's value:: - - <t t-set="foo"> - <li>ok</li> - </t> - <t t-esc="foo"/> - - will generate ``<li>ok</li>`` (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 -====== - -Exclusive directives --------------------- - -Asset bundles -''''''''''''' - -.. todo:: have fme write these up because I've no idea how they work - -"smart records" fields formatting -''''''''''''''''''''''''''''''''' - -The ``t-field`` directive can only be used when performing field access -(``a.b``) on a "smart" record (result of the ``browse`` method). It is able -to automatically format based on field type, and is integrated in the -website's rich text editing. - -``t-options`` can be used to customize fields, the most common option -is ``widget``, other options are field- or widget-dependent. - -Debugging ---------- - -``t-debug`` - invokes a debugger using PDB's ``set_trace`` API. The parameter should - be the name of a module, on which a ``set_trace`` method is called:: - - <t t-debug="pdb"/> - - is equivalent to ``importlib.import_module("pdb").set_trace()`` - -Helpers -------- - -Request-based -''''''''''''' - -Most Python-side uses of QWeb are in controllers (and during HTTP requests), -in which case templates stored in the database (as -:ref:`views <reference/views/qweb>`) can be trivially rendered by calling -:meth:`odoo.http.HttpRequest.render`: - -.. code-block:: python - - response = http.request.render('my-template', { - 'context_value': 42 - }) - -This automatically creates a :class:`~odoo.http.Response` object which can -be returned from the controller (or further customized to suit). - -View-based -'''''''''' - -At a deeper level than the previous helper is the ``render`` method on -``ir.ui.view``: - -.. py:method:: render(cr, uid, id[, values][, engine='ir.qweb][, context]) - - Renders a QWeb view/template by database id or :term:`external id`. - Templates are automatically loaded from ``ir.ui.view`` records. - - Sets up a number of default values in the rendering context: - - ``request`` - the current :class:`~odoo.http.WebRequest` object, if any - ``debug`` - whether the current request (if any) is in ``debug`` mode - :func:`quote_plus <werkzeug.urls.url_quote_plus>` - url-encoding utility function - :mod:`json` - the corresponding standard library module - :mod:`time` - the corresponding standard library module - :mod:`datetime` - the corresponding standard library module - `relativedelta <https://labix.org/python-dateutil#head-ba5ffd4df8111d1b83fc194b97ebecf837add454>`_ - see module - ``keep_query`` - the ``keep_query`` helper function - - :param values: context values to pass to QWeb for rendering - :param str engine: name of the Odoo model to use for rendering, can be - used to expand or customize QWeb locally (by creating - a "new" qweb based on ``ir.qweb`` with alterations) - -.. _reference/qweb/javascript: - -.. todo:: the members below are no longer relevant, section to rewrite - -.. API -.. --- - -.. It is also possible to use the ``ir.qweb`` model directly (and extend it, and -.. inherit from it): - -.. .. automodule:: odoo.addons.base.ir.ir_qweb -.. :members: QWeb, QWebContext, FieldConverter, QwebWidget - -Javascript -========== - -Exclusive directives --------------------- - -Defining templates -'''''''''''''''''' - -The ``t-name`` directive can only be placed at the top-level of a template -file (direct children to the document root):: - - <templates> - <t t-name="template-name"> - <!-- template code --> - </t> - </templates> - -It takes no other parameter, but can be used with a ``<t>`` element or any -other. With a ``<t>`` element, the ``<t>`` should have a single child. - -The template name is an arbitrary string, although when multiple templates -are related (e.g. called sub-templates) it is customary to use dot-separated -names to indicate hierarchical relationships. - -Template inheritance -'''''''''''''''''''' - -Template inheritance is used to either: - - Alter existing templates in-place, e.g. to add information to templates -created by other modules. - - Create a new template from a given parent template - -Template inheritance is performed via the use of two directives: - - ``t-inherit`` which is the name of the template to inherit from, - - ``t-inherit-mode`` which is the behaviour of the inheritance: it can either be - set to ``primary`` to create a new child template from the parented one or - to ``extension`` to alter the parent template in place. - -An optional ``t-name`` directive can also be specified. It will be the name of -the newly created template if used in primary mode, else it will be added as a -comment on the transformed template to help retrace inheritances. - -For the inheritance itself, the changes are done using xpaths directives. -See the XPATH_ documentation for the complete set of available instructions. - -Primary inheritance (child template):: - - <t t-name="child.template" t-inherit="base.template" t-inherit-mode="primary"> - <xpath expr="//ul" position="inside"> - <li>new element</li> - </xpath> - </t> - -Extension inheritance (in-place transformation):: - - <t t-inherit="base.template" t-inherit-mode="extension"> - <xpath expr="//tr[1]" position="after"> - <tr><td>new cell</td></tr> - </xpath> - </t> - -Old inheritance mechanism (deprecated) -'''''''''''''''''''''''''''''''''''''' - -Template inheritance is performed via the ``t-extend`` directive which takes -the name of the template to alter as parameter. - -The directive ``t-extend`` will act as a primary inheritance when combined with -``t-name`` and as an extension one when used alone. - -In both cases the alteration is then performed with any number of ``t-jquery`` -sub-directives:: - - <t t-extend="base.template"> - <t t-jquery="ul" t-operation="append"> - <li>new element</li> - </t> - </t> - -The ``t-jquery`` directives takes a `CSS selector`_. This selector is used -on the extended template to select *context nodes* to which the specified -``t-operation`` is applied: - -``append`` - the node's body is appended at the end of the context node (after the - context node's last child) -``prepend`` - the node's body is prepended to the context node (inserted before the - context node's first child) -``before`` - the node's body is inserted right before the context node -``after`` - the node's body is inserted right after the context node -``inner`` - the node's body replaces the context node's children -``replace`` - the node's body is used to replace the context node itself -``attributes`` - the nodes's body should be any number of ``attribute`` elements, - each with a ``name`` attribute and some textual content, the named - attribute of the context node will be set to the specified value - (either replaced if it already existed or added if not) -No operation - if no ``t-operation`` is specified, the template body is interpreted as - javascript code and executed with the context node as ``this`` - - .. warning:: while much more powerful than other operations, this mode is - also much harder to debug and maintain, it is recommended to - avoid it - -debugging ---------- - -The javascript QWeb implementation provides a few debugging hooks: - -``t-log`` - takes an expression parameter, evaluates the expression during rendering - and logs its result with ``console.log``:: - - <t t-set="foo" t-value="42"/> - <t t-log="foo"/> - - will print ``42`` to the console -``t-debug`` - triggers a debugger breakpoint during template rendering:: - - <t t-if="a_test"> - <t t-debug=""> - </t> - - will stop execution if debugging is active (exact condition depend on the - browser and its development tools) -``t-js`` - the node's body is javascript code executed during template rendering. - Takes a ``context`` parameter, which is the name under which the rendering - context will be available in the ``t-js``'s body:: - - <t t-set="foo" t-value="42"/> - <t t-js="ctx"> - console.log("Foo is", ctx.foo); - </t> - -Helpers -------- - -.. js:attribute:: core.qweb - - (core is the ``web.core`` module) An instance of :js:class:`QWeb2.Engine` with all module-defined template - files loaded, and references to standard helper objects ``_`` - (underscore), ``_t`` (translation function) and JSON_. - - :js:func:`core.qweb.render <QWeb2.Engine.render>` can be used to - easily render basic module templates - -.. _reference/qweb/api: - -API ---- - -.. js:class:: QWeb2.Engine - - The QWeb "renderer", handles most of QWeb's logic (loading, - parsing, compiling and rendering templates). - - Odoo Web instantiates one for the user in the core module, and - exports it to ``core.qweb``. It also loads all the template files - of the various modules into that QWeb instance. - - A :js:class:`QWeb2.Engine` also serves as a "template namespace". - - .. js:function:: QWeb2.Engine.render(template[, context]) - - Renders a previously loaded template to a String, using - ``context`` (if provided) to find the variables accessed - during template rendering (e.g. strings to display). - - :param String template: the name of the template to render - :param Object context: the basic namespace to use for template - rendering - :returns: String - - The engine exposes an other method which may be useful in some - cases (e.g. if you need a separate template namespace with, in - Odoo Web, Kanban views get their own :js:class:`QWeb2.Engine` - instance so their templates don't collide with more general - "module" templates): - - .. js:function:: QWeb2.Engine.add_template(templates) - - Loads a template file (a collection of templates) in the QWeb - instance. The templates can be specified as: - - An XML string - QWeb will attempt to parse it to an XML document then load - it. - - A URL - QWeb will attempt to download the URL content, then load - the resulting XML string. - - A ``Document`` or ``Node`` - QWeb will traverse the first level of the document (the - child nodes of the provided root) and load any named - template or template override. - - :type templates: String | Document | Node - - A :js:class:`QWeb2.Engine` also exposes various attributes for - behavior customization: - - .. js:attribute:: QWeb2.Engine.prefix - - Prefix used to recognize directives during parsing. A string. By - default, ``t``. - - .. js:attribute:: QWeb2.Engine.debug - - Boolean flag putting the engine in "debug mode". Normally, - QWeb intercepts any error raised during template execution. In - debug mode, it leaves all exceptions go through without - intercepting them. - - .. js:attribute:: QWeb2.Engine.jQuery - - The jQuery instance used during template inheritance processing. - Defaults to ``window.jQuery``. - - .. js:attribute:: QWeb2.Engine.preprocess_node - - A ``Function``. If present, called before compiling each DOM - node to template code. In Odoo Web, this is used to - automatically translate text content and some attributes in - templates. Defaults to ``null``. - -.. [#genshif] it is similar in that to Genshi_, although it does not use (and - has no support for) `XML namespaces`_ - -.. [#othertemplates] although it uses a few others, either for historical - reasons or because they remain better fits for the - use case. Odoo 9.0 still depends on Jinja_ and Mako_. - -.. _templating: - https://en.wikipedia.org/wiki/Template_processor - -.. _Jinja: http://jinja.pocoo.org -.. _Mako: https://www.makotemplates.org -.. _Genshi: https://genshi.edgewall.org -.. _XML namespaces: https://en.wikipedia.org/wiki/XML_namespace -.. _HTML: https://en.wikipedia.org/wiki/HTML -.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting -.. _JSON: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON -.. _CSS selector: https://api.jquery.com/category/selectors/ -.. _XPATH: https://developer.mozilla.org/en-US/docs/Web/XPath diff --git a/doc/reference/reports.rst b/doc/reference/reports.rst deleted file mode 100644 index 140f91024c1..00000000000 --- a/doc/reference/reports.rst +++ /dev/null @@ -1,326 +0,0 @@ -:banner: banners/reports.jpg - -.. highlight:: xml - -.. _reference/reports: - -.. _reference/reports/report: - -============ -QWeb Reports -============ - -Reports are written in HTML/QWeb, like website views in Odoo. You can use -the usual :ref:`QWeb control flow tools <reference/qweb>`. The PDF rendering -itself is performed by wkhtmltopdf_. - -Reports are declared using a :ref:`report action <reference/actions/report>`, -and a :ref:`reference/reports/templates` for the action to use. - -If useful or necessary, it is possible to specify a -:ref:`reference/reports/paper_formats` for the report report. - -.. _reference/reports/templates: - -Report template -=============== - -Report templates will always provide the following variables: - -``time`` - a reference to :mod:`python:time` from the Python standard library -``user`` - ``res.user`` record for the user printing the report -``res_company`` - record for the current ``user``'s company -``website`` - the current website object, if any (this item can be present but ``None``) -``web_base_url`` - the base url for the webserver -``context_timestamp`` - a function taking :class:`python:datetime.datetime` in UTC\ [#unzoned]_ and - converting it to the timezone of the user printing the report - -Minimal viable template ------------------------ - -A minimal template would look like:: - - <template id="report_invoice"> - <t t-call="web.html_container"> - <t t-foreach="docs" t-as="o"> - <t t-call="web.external_layout"> - <div class="page"> - <h2>Report title</h2> - <p>This object's name is <span t-field="o.name"/></p> - </div> - </t> - </t> - </t> - </template> - -Calling ``external_layout`` will add the default header and footer on your -report. The PDF body will be the content inside the ``<div -class="page">``. The template's ``id`` must be the name specified in the -report declaration; for example ``account.report_invoice`` for the above -report. Since this is a QWeb template, you can access all the fields of the -``docs`` objects received by the template. - -By default, the rendering context will also expose the following items: - -``docs`` - records for the current report -``doc_ids`` - list of ids for the ``docs`` records -``doc_model`` - model for the ``docs`` records - -If you wish to access other records/models in the template, you will need -:ref:`a custom report <reference/reports/custom_reports>`, however in that case -you will have to provide the items above if you need them. - -Translatable Templates ----------------------- - -If you wish to translate reports (to the language of a partner, for example), -you need to define two templates: - -* The main report template -* The translatable document - -You can then call the translatable document from your main template with the attribute -``t-lang`` set to a language code (for example ``fr`` or ``en_US``) or to a record field. -You will also need to re-browse the related records with the proper context if you use -fields that are translatable (like country names, sales conditions, etc.) - -.. warning:: - - If your report template does not use translatable record fields, re-browsing the record - in another language is *not* necessary and will impact performances. - -For example, let's look at the Sale Order report from the Sale module:: - - <!-- Main template --> - <template id="report_saleorder"> - <t t-call="web.html_container"> - <t t-foreach="docs" t-as="doc"> - <t t-call="sale.report_saleorder_document" t-lang="doc.partner_id.lang"/> - </t> - </t> - </template> - - <!-- Translatable template --> - <template id="report_saleorder_document"> - <!-- Re-browse of the record with the partner lang --> - <t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" /> - <t t-call="web.external_layout"> - <div class="page"> - <div class="oe_structure"/> - <div class="row"> - <div class="col-6"> - <strong t-if="doc.partner_shipping_id == doc.partner_invoice_id">Invoice and shipping address:</strong> - <strong t-if="doc.partner_shipping_id != doc.partner_invoice_id">Invoice address:</strong> - <div t-field="doc.partner_invoice_id" t-options="{"no_marker": True}"/> - <...> - <div class="oe_structure"/> - </div> - </t> - </template> - - -The main template calls the translatable template with ``doc.partner_id.lang`` as a -``t-lang`` parameter, so it will be rendered in the language of the partner. This way, -each Sale Order will be printed in the language of the corresponding customer. If you wish -to translate only the body of the document, but keep the header and footer in a default -language, you could call the report's external layout this way:: - - <t t-call="web.external_layout" t-lang="en_US"> - -.. tip:: - - Please take note that this works only when calling external templates, you will not be - able to translate part of a document by setting a ``t-lang`` attribute on an xml node other - than ``t-call``. If you wish to translate part of a template, you can create an external - template with this partial template and call it from the main one with the ``t-lang`` - attribute. - - -Barcodes --------- - -Barcodes are images returned by a controller and can easily be embedded in -reports thanks to the QWeb syntax (e.g. see :ref:`reference/qweb/attributes`): - -.. code-block:: html - - <img t-att-src="'/report/barcode/QR/%s' % 'My text in qr code'"/> - -More parameters can be passed as a query string - -.. code-block:: html - - <img t-att-src="'/report/barcode/? - type=%s&value=%s&width=%s&height=%s'%('QR', 'text', 200, 200)"/> - - -Useful Remarks --------------- -* Twitter Bootstrap and FontAwesome classes can be used in your report - template -* Local CSS can be put directly in the template -* Global CSS can be inserted in the main report layout by inheriting its - template and inserting your CSS:: - - <template id="report_saleorder_style" inherit_id="report.style"> - <xpath expr="."> - <t> - .example-css-class { - background-color: red; - } - </t> - </xpath> - </template> - -* If it appears that your PDF report is missing the styles, please check - :ref:`these instructions <reference/backend/reporting/printed-reports/pdf-without-styles>`. - -.. _reference/reports/paper_formats: - -Paper Format -============ - -Paper formats are records of ``report.paperformat`` and can contain the -following attributes: - -``name`` (mandatory) - only useful as a mnemonic/description of the report when looking for one - in a list of some sort -``description`` - a small description of your format -``format`` - either a predefined format (A0 to A9, B0 to B10, Legal, Letter, - Tabloid,...) or ``custom``; A4 by default. You cannot use a non-custom - format if you define the page dimensions. -``dpi`` - output DPI; 90 by default -``margin_top``, ``margin_bottom``, ``margin_left``, ``margin_right`` - margin sizes in mm -``page_height``, ``page_width`` - page dimensions in mm -``orientation`` - Landscape or Portrait -``header_line`` - boolean to display a header line -``header_spacing`` - header spacing in mm - -Example:: - - <record id="paperformat_frenchcheck" model="report.paperformat"> - <field name="name">French Bank Check</field> - <field name="default" eval="True"/> - <field name="format">custom</field> - <field name="page_height">80</field> - <field name="page_width">175</field> - <field name="orientation">Portrait</field> - <field name="margin_top">3</field> - <field name="margin_bottom">3</field> - <field name="margin_left">3</field> - <field name="margin_right">3</field> - <field name="header_line" eval="False"/> - <field name="header_spacing">3</field> - <field name="dpi">80</field> - </record> - -.. _reference/reports/custom_reports: - -Custom Reports -============== - -By default, the reporting system builds rendering values based on the target -model specified through the ``model`` field. - -However, it will first look for a model named -:samp:`report.{module.report_name}` and call that model's -``_get_report_values(doc_ids, data)`` in order to prepare the rendering data for -the template. - -This can be used to include arbitrary items to use or display while rendering -the template, such as data from additional models: - -.. code-block:: python - - from odoo import api, models - - class ParticularReport(models.AbstractModel): - _name = 'report.module.report_name' - - def _get_report_values(self, docids, data=None): - # get the report action back as we will need its data - report = self.env['ir.actions.report']._get_report_from_name('module.report_name') - # get the records selected for this rendering of the report - obj = self.env[report.model].browse(docids) - # return a custom rendering context - return { - 'lines': docids.get_lines() - } - -.. warning:: - - When using a custom report, the "default" document-related items - (``doc_ids``, ``doc_model`` and ``docs``) will *not* be included. If you - want them, you will need to include them yourself. - - In the example above, the rendering context will contain the "global" values - as well as the ``lines`` we put in there but nothing else. - -.. _reference/reports/custom_fonts: - -Custom fonts -============ -If you want to use custom fonts you will need to add your custom font and the related less/CSS to the ``web.reports_assets_common`` assets bundle. -Adding your custom font(s) to ``web.assets_common`` or ``web.assets_backend`` will not make your font available in QWeb reports. - -Example:: - - <template id="report_assets_common_custom_fonts" name="Custom QWeb fonts" inherit_id="web.report_assets_common"> - <xpath expr="." position="inside"> - <link href="/your_module/static/src/less/fonts.less" rel="stylesheet" type="text/less"/> - </xpath> - </template> - -You will need to define your ``@font-face`` within this less file, even if you've used in another assets bundle (other than ``web.reports_assets_common``). - -Example:: - - @font-face { - font-family: 'MonixBold'; - src: local('MonixBold'), local('MonixBold'), url(/your_module/static/src/fonts/MonixBold-Regular.otf) format('opentype'); - } - - .h1-title-big { - font-family: MonixBold; - font-size: 60px; - color: #3399cc; - } - -After you've added the less into your assets bundle you can use the classes - in this example ``h1-title-big`` - in your custom QWeb report. - -Reports are web pages -===================== - -Reports are dynamically generated by the report module and can be accessed -directly via URL: - -For example, you can access a Sale Order report in html mode by going to -\http://<server-address>/report/html/sale.report_saleorder/38 - -Or you can access the pdf version at -\http://<server-address>/report/pdf/sale.report_saleorder/38 - -.. [#unzoned] it does not matter what timezone the :class:`python:datetime` - object is actually in (including no timezone), its timezone will - unconditionally be *set* to UTC before being adjusted to the - user's - -.. _wkhtmltopdf: https://wkhtmltopdf.org diff --git a/doc/reference/security.rst b/doc/reference/security.rst deleted file mode 100644 index 7c235b08338..00000000000 --- a/doc/reference/security.rst +++ /dev/null @@ -1,389 +0,0 @@ -:banner: banners/security.jpg - -.. _reference/security: - -================ -Security in Odoo -================ - -Aside from manually managing access using custom code, Odoo provides two main -data-driven mechanisms to manage or restrict access to data. - -Both mechanisms are linked to specific users through *groups*: a user belongs -to any number of groups, and security mechanisms are associated to groups, -thus applying security mechamisms to users. - -.. _reference/security/acl: - -Access Control -============== - -Managed by the ``ir.model.access`` records, defines access to a whole model. - -Each access control has a model to which it grants permissions, the -permissions it grants and optionally a group. - -Access controls are additive, for a given model a user has access all -permissions granted to any of its groups: if the user belongs to one group -which allows writing and another which allows deleting, they can both write -and delete. - -If no group is specified, the access control applies to all users, otherwise -it only applies to the members of the given group. - -Available permissions are creation (``perm_create``), searching and reading -(``perm_read``), updating existing records (``perm_write``) and deleting -existing records (``perm_unlink``) - -.. _reference/security/rules: - -Record Rules -============ - -Record rules are conditions that records must satisfy for an operation -(create, read, update or delete) to be allowed. It is applied record-by-record -after access control has been applied. - -A record rule has: - -* a model on which it applies -* a set of permissions to which it applies (e.g. if ``perm_read`` is set, the - rule will only be checked when reading a record) -* a set of user groups to which the rule applies, if no group is specified - the rule is *global* -* a :ref:`domain <reference/orm/domains>` used to check whether a given record - matches the rule (and is accessible) or does not (and is not accessible). - The domain is evaluated with two variables in context: ``user`` is the - current user's record and ``time`` is the `time module`_ - -Global rules and group rules (rules restricted to specific groups versus -groups applying to all users) are used quite differently: - -* Global rules are subtractive, they *must all* be matched for a record to be - accessible -* Group rules are additive, if *any* of them matches (and all global rules - match) then the record is accessible - -This means the first *group rule* restricts access, but any further -*group rule* expands it, while *global rules* can only ever restrict access -(or have no effect). - -.. warning:: record rules do not apply to the Superuser account - :class: aphorism - -.. _reference/security/fields: - -Field Access -============ - -An ORM :class:`~odoo.fields.Field` can have a ``groups`` attribute -providing a list of groups (as a comma-separated string of -:term:`external identifiers`). - -If the current user is not in one of the listed groups, he will not have -access to the field: - -* restricted fields are automatically removed from requested views -* restricted fields are removed from :meth:`~odoo.models.Model.fields_get` - responses -* attempts to (explicitly) read from or write to restricted fields results in - an access error - -.. todo:: - - field access groups apply to the Superuser in fields_get but not in - read/write... - -.. _time module: https://docs.python.org/3/library/time.html - - -.. _reference/security/pitfalls: - -Security Pitfalls -================= - -As a developer, it is important to understand the security mechanisms and avoid -common mistakes leading to insecure code. - -Unsafe Public Methods ---------------------- - -Any public method can be executed via a :ref:`RPC call -<webservices/odoo/calling_methods>` with the chosen parameters. The methods -starting with a ``_`` are not callable from an action button or external API. - -On public methods, the record on which a method is executed and the parameters -can not be trusted, ACL being only verified during CRUD operations. - -.. code-block:: python - - # this method is public and its arguments can not be trusted - def action_done(self): - if self.state == "draft" and self.user_has_groups('base.manager'): - self._set_state("done") - - # this method is private and can only be called from other python methods - def _set_state(self, new_state): - self.sudo().write({"state": new_state}) - -Making a method private is obviously not enough and care must be taken to use it -properly. - -Bypassing the ORM ------------------ -You should never use the database cursor directly when the ORM can do the same -thing! By doing so you are bypassing all the ORM features, possibly the -automated behaviours like translations, invalidation of fields, ``active``, -access rights and so on. - -And chances are that you are also making the code harder to read and probably -less secure. - -.. code-block:: python - - # very very wrong - self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in (' + ','.join(map(str, ids))+') AND state=%s AND obj_price > 0', ('draft',)) - auction_lots_ids = [x[0] for x in self.env.cr.fetchall()] - - # no injection, but still wrong - self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in %s '\ - 'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',)) - auction_lots_ids = [x[0] for x in self.env.cr.fetchall()] - - # better - auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)]) - - -SQL injections -~~~~~~~~~~~~~~ -Care must be taken not to introduce SQL injections vulnerabilities when using -manual SQL queries. The vulnerability is present when user input is either -incorrectly filtered or badly quoted, allowing an attacker to introduce -undesirable clauses to a SQL query (such as circumventing filters or -executing ``UPDATE`` or ``DELETE`` commands). - -The best way to be safe is to never, NEVER use Python string concatenation (+) -or string parameters interpolation (%) to pass variables to a SQL query string. - -The second reason, which is almost as important, is that it is the job of the -database abstraction layer (psycopg2) to decide how to format query parameters, -not your job! For example psycopg2 knows that when you pass a list of values -it needs to format them as a comma-separated list, enclosed in parentheses ! - -.. code-block:: python - - # the following is very bad: - # - it's a SQL injection vulnerability - # - it's unreadable - # - it's not your job to format the list of ids - self.env.cr.execute('SELECT distinct child_id FROM account_account_consol_rel ' + - 'WHERE parent_id IN ('+','.join(map(str, ids))+')') - - # better - self.env.cr.execute('SELECT DISTINCT child_id '\ - 'FROM account_account_consol_rel '\ - 'WHERE parent_id IN %s', - (tuple(ids),)) - -This is very important, so please be careful also when refactoring, and most -importantly do not copy these patterns! - -Here is a memorable example to help you remember what the issue is about (but -do not copy the code there). Before continuing, please be sure to read the -online documentation of pyscopg2 to learn of to use it properly: - -- `The problem with query parameters <http://initd.org/psycopg/docs/usage.html#the-problem-with-the-query-parameters>`_ -- `How to pass parameters with psycopg2 <http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries>`_ -- `Advanced parameter types <http://initd.org/psycopg/docs/usage.html#adaptation-of-python-values-to-sql-types>`_ -- `Psycopg documentation <https://www.psycopg.org/docs/sql.html>`_ - -Unescaped field content ------------------------ - -When rendering content using JavaScript and XML, one may be tempted to use -a ``t-raw`` to display rich-text content. This should be avoided as a frequent -`XSS <https://en.wikipedia.org/wiki/Cross-site_scripting>`_ vector. - -It is very hard to control the integrity of the data from the computation until -the final integration in the browser DOM. A ``t-raw`` that is correctly escaped -at the time of introduction may no longer be safe at the next bugfix or -refactoring. - -.. code-block:: javascript - - QWeb.render('insecure_template', { - info_message: "You have an <strong>important</strong> notification", - }) - -.. code-block:: xml - - <div t-name="insecure_template"> - <div id="information-bar"><t t-raw="info_message" /></div> - </div> - -The above code may feel safe as the message content is controlled but is a bad -practice that may lead to unexpected security vulnerabilities once this code -evolves in the future. - -.. code-block:: javascript - - // XSS possible with unescaped user provided content ! - QWeb.render('insecure_template', { - info_message: "You have an <strong>important</strong> notification on " \ - + "the product <strong>" + product.name + "</strong>", - }) - -While formatting the template differently would prevent such vulnerabilities. - -.. code-block:: javascript - - QWeb.render('secure_template', { - message: "You have an important notification on the product:", - subject: product.name - }) - -.. code-block:: xml - - <div t-name="secure_template"> - <div id="information-bar"> - <div class="info"><t t-esc="message" /></div> - <div class="subject"><t t-esc="subject" /></div> - </div> - </div> - -.. code-block:: css - - .subject { - font-weight: bold; - } - -Escaping vs Sanitizing ----------------------- - -.. important:: - - Escaping is always 100% mandatory when you mix data and code, no matter how - safe the data - -**Escaping** converts *TEXT* to *CODE*. It is absolutely mandatory to do it -every time you mix *DATA/TEXT* with *CODE* (e.g. generating HTML or python code -to be evaluated inside a `safe_eval`), because *CODE* always requires *TEXT* to -be encoded. It is critical for security, but it's also a question of -correctness. Even when there is no security risk (because the text is 100% -guarantee to be safe or trusted), it is still required (e.g. to avoid breaking -the layout in generated HTML). - -Escaping will never break any feature, as long as the developer identifies which -variable contains *TEXT* and which contains *CODE*. - -.. code-block:: python - - >>> from odoo.tools import html_escape, html_sanitize - >>> data = "<R&D>" # `data` is some TEXT coming from somewhere - - # Escaping turns it into CODE, good! - >>> code = html_escape(data) - >>> code - '<R&D>' - - # Now you can mix it with other code... - >>> self.message_post(body="<strong>%s</strong>" % code) - -**Sanitizing** converts *CODE* to *SAFER CODE* (but not necessary *safe* code). -It does not work on *TEXT*. Sanitizing is only necessary when *CODE* is -untrusted, because it comes in full or in part from some user-provided data. If -the user-provided data is in the form of *TEXT* (e.g. the content from a form -filled by a user), and if that data was correctly escaped before putting it in -*CODE*, then sanitizing is useless (but can still be done). If however, the -user-provided data was **not escaped**, then sanitizing will **not** work as -expected. - -.. code-block:: python - - # Sanitizing without escaping is BROKEN: data is corrupted! - >>> html_sanitize(data) - '' - - # Sanitizing *after* escaping is OK! - >>> html_sanitize(code) - '<p><R&D></p>' - -Sanitizing can break features, depending on whether the *CODE* is expected to -contain patterns that are not safe. That's why `fields.Html` and -`tools.html_sanitize()` have options to fine-tune the level of sanitization for -styles, etc. Those options have to be carefully considered depending on where -the data comes from, and the desired features. The sanitization safety is -balanced against sanitization breakages: the safer the sanitisation the more -likely it is to break things. - -.. code-block:: python - - >>code = "<p class='text-warning'>Important Information</p>" - # this will remove the style, which may break features - # but is necessary if the source is untrusted - >> html_sanitize(code, strip_classes=True) - '<p>Important Information</p>' - -Evaluating content ------------------- -Some may want to ``eval`` to parse user provided content. Using ``eval`` should -be avoided at all cost. A safer, sandboxed, method :class:`~odoo.tools.safe_eval` -can be used instead but still gives tremendous capabilities to the user running -it and must be reserved for trusted privileged users only as it breaks the -barrier between code and data. - -.. code-block:: python - - # very bad - domain = eval(self.filter_domain) - return self.search(domain) - - # better but still not recommended - from odoo.tools import safe_eval - domain = safe_eval(self.filter_domain) - return self.search(domain) - - # good - from ast import literal_eval - domain = literal_eval(self.filter_domain) - return self.search(domain) - -Parsing content does not need ``eval`` - -========== ================== ================================ -Language Data type Suitable parser -========== ================== ================================ -Python int, float, etc. int(), float() -Javascript int, float, etc. parseInt(), parseFloat() -Python dict json.loads(), ast.literal_eval() -Javascript object, list, etc. JSON.parse() -========== ================== ================================ - -Accessing object attributes ---------------------------- - -If the values of a record needs to be retrieved or modified dynamically, one may -want to use the ``getattr`` and ``setattr`` methods. - -.. code-block:: python - - # unsafe retrieval of a field value - def _get_state_value(self, res_id, state_field): - record = self.sudo().browse(res_id) - return getattr(record, state_field, False) - -This code is however not safe as it allows to access any property of the record, -including private attributes or methods. - -The ``__getitem__`` of a recordset has been defined and accessing a dynamic -field value can be easily achieved safely: - -.. code-block:: python - - # better retrieval of a field value - def _get_state_value(self, res_id, state_field): - record = self.sudo().browse(res_id) - return record[state_field] - -The above method is obviously still too optimistic and additional verifications -on the record id and field value must be done. diff --git a/doc/reference/static/res.country.state.csv b/doc/reference/static/res.country.state.csv deleted file mode 100644 index b1960d9a4c5..00000000000 --- a/doc/reference/static/res.country.state.csv +++ /dev/null @@ -1,15 +0,0 @@ -"id","country_id:id","name","code" -state_au_1,au,"Australian Capital Territory","ACT" -state_au_2,au,"New South Wales","NSW" -state_au_3,au,"Northern Territory","NT" -state_au_4,au,"Queensland","QLD" -state_au_5,au,"South Australia","SA" -state_au_6,au,"Tasmania","TAS" -state_au_7,au,"Victoria","VIC" -state_au_8,au,"Western Australia","WA" -state_us_1,us,"Alabama","AL" -state_us_2,us,"Alaska","AK" -state_us_3,us,"Arizona","AZ" -state_us_4,us,"Arkansas","AR" -state_us_5,us,"California","CA" -state_us_6,us,"Colorado","CO" diff --git a/doc/reference/testing.rst b/doc/reference/testing.rst deleted file mode 100644 index dc0b9e1823a..00000000000 --- a/doc/reference/testing.rst +++ /dev/null @@ -1,638 +0,0 @@ -:banner: banners/testing_modules.jpg - -.. _reference/testing: - - -=============== -Testing Odoo -=============== - -There are many ways to test an application. In Odoo, we have three kinds of -tests - -- Python unit tests (see `Testing Python code`_): useful for testing model business logic -- JS unit tests (see `Testing JS code`_): useful to test the javascript code in isolation -- Tours (see `Integration Testing`_): tours simulate a real situation. They ensures that the - python and the javascript parts properly talk to each other. - -Testing Python code -=================== - -Odoo provides support for testing modules using unittest. - -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 - -.. warning:: - - 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:: odoo.tests.common.TransactionCase - :members: browse_ref, ref - -.. autoclass:: odoo.tests.common.SingleTransactionCase - :members: browse_ref, ref - -.. autoclass:: odoo.tests.common.SavepointCase - -.. autoclass:: odoo.tests.common.HttpCase - :members: browse_ref, ref, url_open, browser_js - -.. autofunction:: odoo.tests.common.tagged - -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:: - - # coding: utf-8 - from odoo.tests import HttpCase, tagged - - # This test should only be executed after all modules have been installed. - @tagged('-at_install', 'post_install') - class WebsiteVisitorTests(HttpCase): - def test_create_visitor_on_tracked_page(self): - Page = self.env['website.page'] - -The most common situation is to use -:class:`~odoo.tests.common.TransactionCase` and test a property 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... - -.. note:: - - Test methods must start with ``test_`` - -.. autoclass:: odoo.tests.common.Form - :members: - -.. autoclass:: odoo.tests.common.M2MProxy - :members: add, remove, clear - -.. autoclass:: odoo.tests.common.O2MProxy - :members: new, edit, remove - -Running tests -------------- - -Tests are automatically run when installing or updating modules if -:option:`--test-enable <odoo-bin --test-enable>` was enabled when starting the -Odoo server. - -.. _unittest documentation: https://docs.python.org/3/library/unittest.html - -Test selection --------------- - -In Odoo, Python tests can be tagged to facilitate the test selection when -running tests. - -Subclasses of :class:`odoo.tests.common.BaseCase` (usually through -:class:`~odoo.tests.common.TransactionCase`, -:class:`~odoo.tests.common.SavepointCase` or -:class:`~odoo.tests.common.HttpCase`) are automatically tagged with -``standard``, ``at_install`` and their source module's name by default. - -Invocation -^^^^^^^^^^ - -:option:`--test-tags <odoo-bin --test-tags>` can be used to select/filter tests -to run on the command-line. - -This option defaults to ``+standard`` meaning tests tagged ``standard`` -(explicitly or implicitly) will be run by default when starting Odoo -with :option:`--test-enable <odoo-bin --test-enable>`. - -When writing tests, the :func:`~odoo.tests.common.tagged` decorator can be -used on **test classes** to add or remove tags. - -The decorator's arguments are tag names, as strings. - -.. danger:: :func:`~odoo.tests.common.tagged` is a class decorator, it has no - effect on functions or methods - -Tags can be prefixed with the minus (``-``) sign, to *remove* them instead of -add or select them e.g. if you don't want your test to be executed by -default you can remove the ``standard`` tag: - -.. code-block:: python - - from odoo.tests import TransactionCase, tagged - - @tagged('-standard', 'nice') - class NiceTest(TransactionCase): - ... - -This test will not be selected by default, to run it the relevant tag will -have to be selected explicitely: - -.. code-block:: console - - $ odoo-bin --test-enable --test-tags nice - -Note that only the tests tagged ``nice`` are going to be executed. To run -*both* ``nice`` and ``standard`` tests, provide multiple values to -:option:`--test-tags <odoo-bin --test-tags>`: on the command-line, values -are *additive* (you're selecting all tests with *any* of the specified tags) - -.. code-block:: console - - $ odoo-bin --test-enable --test-tags nice,standard - -The config switch parameter also accepts the ``+`` and ``-`` prefixes. The -``+`` prefix is implied and therefore, totaly optional. The ``-`` (minus) -prefix is made to deselect tests tagged with the prefixed tags, even if they -are selected by other specified tags e.g. if there are ``standard`` tests which -are also tagged as ``slow`` you can run all standard tests *except* the slow -ones: - -.. code-block:: console - - $ odoo-bin --test-enable --test-tags 'standard,-slow' - -When you write a test that does not inherit from the -:class:`~odoo.tests.common.BaseCase`, this test will not have the default tags, -you have to add them explicitely to have the test included in the default test -suite. This is a common issue when using a simple ``unittest.TestCase`` as -they're not going to get run: - -.. code-block:: python - - import unittest - from odoo.tests import tagged - - @tagged('standard', 'at_install') - class SmallTest(unittest.TestCase): - ... - -Special tags -^^^^^^^^^^^^ - -- ``standard``: All Odoo tests that inherit from - :class:`~odoo.tests.common.BaseCase` are implicitely tagged standard. - :option:`--test-tags <odoo-bin --test-tags>` also defaults to ``standard``. - - That means untagged test will be executed by default when tests are enabled. -- ``at_install``: Means that the test will be executed right after the module - installation and before other modules are installed. This is a default - implicit tag. -- ``post_install``: Means that the test will be executed after all the modules - are installed. This is what you want for HttpCase tests most of the time. - - Note that this is *not exclusive* with ``at_install``, however since you - will generally not want both ``post_install`` is usually paired with - ``-at_install`` when tagging a test class. -- *module_name*: Odoo tests classes extending - :class:`~odoo.tests.common.BaseCase` are implicitely tagged with the - technical name of their module. This allows easily selecting or excluding - specific modules when testing e.g. if you want to only run tests from - ``stock_account``: - - .. code-block:: console - - $ odoo-bin --test-enable --test-tags stock_account - -Examples -^^^^^^^^ - -.. important:: - - Tests will be executed only in the installed or updated modules. So - modules have to be selected with the :option:`-u <odoo-bin -u>` or - :option:`-i <odoo-bin -i>` switches. For simplicity, those switches are - not specified in the examples below. - -Run only the tests from the sale module: - -.. code-block:: console - - $ odoo-bin --test-enable --test-tags sale - -Run the tests from the sale module but not the ones tagged as slow: - -.. code-block:: console - - $ odoo-bin --test-enable --test-tags 'sale,-slow' - -Run only the tests from stock or tagged as slow: - -.. code-block:: console - - $ odoo-bin --test-enable --test-tags '-standard, slow, stock' - -.. note:: ``-standard`` is implicit (not required), and present for clarity - -Testing JS code -=============== - -Testing a complex system is an important safeguard to prevent regressions and to -guarantee that some basic functionality still works. Since Odoo has a non trivial -codebase in Javascript, it is necessary to test it. In this section, we will -discuss the practice of testing JS code in isolation: these tests stay in the -browser, and are not supposed to reach the server. - -Qunit test suite ----------------- - -The Odoo framework uses the QUnit_ library testing framework as a test runner. -QUnit defines the concepts of *tests* and *modules* (a set of related tests), -and gives us a web based interface to execute the tests. - -For example, here is what a pyUtils test could look like: - -.. code-block:: javascript - - QUnit.module('py_utils'); - - QUnit.test('simple arithmetic', function (assert) { - assert.expect(2); - - var result = pyUtils.py_eval("1 + 2"); - assert.strictEqual(result, 3, "should properly evaluate sum"); - result = pyUtils.py_eval("42 % 5"); - assert.strictEqual(result, 2, "should properly evaluate modulo operator"); - }); - -The main way to run the test suite is to have a running Odoo server, then -navigate a web browser to ``/web/tests``. The test suite will then be executed -by the web browser Javascript engine. - -.. image:: ./images/tests.png - :align: center - -The web UI has many useful features: it can run only some submodules, or -filter tests that match a string. It can show every assertions, failed or passed, -rerun specific tests, ... - -.. warning:: - - While the test suite is running, make sure that: - - - your browser window is focused, - - it is not zoomed in/out. It needs to have exactly 100% zoom level. - - If this is not the case, some tests will fail, without a proper explanation. - -Testing Infrastructure ----------------------- - -Here is a high level overview of the most important parts of the testing -infrastructure: - -- there is an asset bundle named `web.qunit_suite`_. This bundle contains - the main code (assets common + assets backend), some libraries, the QUnit test - runner and the test bundles listed below. - -- a bundle named `web.tests_assets`_ includes most of the assets and utils required - by the test suite: custom QUnit asserts, test helpers, lazy loaded assets, etc. - -- another asset bundle, `web.qunit_suite_tests`_, contains all the test scripts. - This is typically where the test files are added to the suite. - -- there is a `controller`_ in web, mapped to the route */web/tests*. This controller - simply renders the *web.qunit_suite* template. - -- to execute the tests, one can simply point its browser to the route */web/tests*. - In that case, the browser will download all assets, and QUnit will take over. - -- there is some code in `qunit_config.js`_ which logs in the console some - information when a test passes or fails. - -- we want the runbot to also run these tests, so there is a test (in `test_js.py`_) - which simply spawns a browser and points it to the *web/tests* url. Note that - the browser_js method spawns a Chrome headless instance. - - -Modularity and testing ----------------------- - -With the way Odoo is designed, any addon can modify the behaviour of other parts -of the system. For example, the *voip* addon can modify the *FieldPhone* widget -to use extra features. This is not really good from the perspective of the -testing system, since this means that a test in the addon web will fail whenever -the voip addon is installed (note that the runbot runs the tests with all addons -installed). - -At the same time, our testing sytem is good, because it can detect whenever -another module breaks some core functionality. There is no complete solution to -this issue. For now, we solve this on a case by case basis. - -Usually, it is not a good idea to modify some other behaviour. For our voip -example, it is certainly cleaner to add a new *FieldVOIPPhone* widget and -modify the few views that needs it. This way, the *FieldPhone* widget is not -impacted, and both can be tested. - -Adding a new test case ----------------------- - -Let us assume that we are maintaining an addon *my_addon*, and that we -want to add a test for some javascript code (for example, some utility function -myFunction, located in *my_addon.utils*). The process to add a new test case is -the following: - -1. create a new file *my_addon/static/tests/utils_tests.js*. This file contains the basic code to - add a QUnit module *my_addon > utils*. - - .. code-block:: javascript - - odoo.define('my_addon.utils_tests', function (require) { - "use strict"; - - var utils = require('my_addon.utils'); - - QUnit.module('my_addon', {}, function () { - - QUnit.module('utils'); - - }); - }); - - -2. In *my_addon/assets.xml*, add the file to the main test assets: - - .. code-block:: xml - - <?xml version="1.0" encoding="utf-8"?> - <odoo> - <template id="qunit_suite_tests" name="my addon tests" inherit_id="web.qunit_suite_tests"> - <xpath expr="//script[last()]" position="after"> - <script type="text/javascript" src="/my_addon/static/tests/utils_tests.js"/> - </xpath> - </template> - </odoo> - -3. Restart the server and update *my_addon*, or do it from the interface (to - make sure the new test file is loaded) - -4. Add a test case after the definition of the *utils* sub test suite: - - .. code-block:: javascript - - QUnit.test("some test case that we want to test", function (assert) { - assert.expect(1); - - var result = utils.myFunction(someArgument); - assert.strictEqual(result, expectedResult); - }); - -5. Visit */web/tests/* to make sure the test is executed - -Helper functions and specialized assertions -------------------------------------------- - -Without help, it is quite difficult to test some parts of Odoo. In particular, -views are tricky, because they communicate with the server and may perform many -rpcs, which needs to be mocked. This is why we developed some specialized -helper functions, located in `test_utils.js`_. - -- Mock test functions: these functions help setting up a test environment. The - most important use case is mocking the answers given by the Odoo server. These - functions use a `mock server`_. This is a javascript class that simulates - answers to the most common model methods: read, search_read, nameget, ... - -- DOM helpers: useful to simulate events/actions on some specific target. For - example, testUtils.dom.click performs a click on a target. Note that it is - safer than doing it manually, because it also checks that the target exists, - and is visible. - -- create helpers: they are probably the most important functions exported by - `test_utils.js`_. These helpers are useful to create a widget, with a mock - environment, and a lot of small detail to simulate as much as possible the - real conditions. The most important is certainly `createView`_. - -- `qunit assertions`_: QUnit can be extended with specialized assertions. For - Odoo, we frequently test some DOM properties. This is why we made some - assertions to help with that. For example, the *containsOnce* assertion takes - a widget/jQuery/HtmlElement and a selector, then checks if the target contains - exactly one match for the css selector. - -For example, with these helpers, here is what a simple form test could look like: - -.. code-block:: javascript - - QUnit.test('simple group rendering', function (assert) { - assert.expect(1); - - var form = testUtils.createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '<form string="Partners">' + - '<group>' + - '<field name="foo"/>' + - '</group>' + - '</form>', - res_id: 1, - }); - - assert.containsOnce(form, 'table.o_inner_group'); - - form.destroy(); - }); - -Notice the use of the testUtils.createView helper and of the containsOnce -assertion. Also, the form controller was properly destroyed at the end of -the test. - -Best Practices --------------- - -In no particular order: - -- all test files should be added in *some_addon/static/tests/* -- for bug fixes, make sure that the test fails without the bug fix, and passes - with it. This ensures that it actually works. -- try to have the minimal amount of code necessary for the test to work. -- usually, two small tests are better than one large test. A smaller test is - easier to understand and to fix. -- always cleanup after a test. For example, if your test instantiates a widget, - it should destroy it at the end. -- no need to have full and complete code coverage. But adding a few tests helps - a lot: it makes sure that your code is not completely broken, and whenever a - bug is fixed, it is really much easier to add a test to an existing test suite. -- if you want to check some negative assertion (for example, that a HtmlElement - does not have a specific css class), then try to add the positive assertion in - the same test (for example, by doing an action that changes the state). This - will help avoid the test to become dead in the future (for example, if the css - class is changed). - -Tips ----- - -- running only one test: you can (temporarily!) change the *QUnit.test(...)* - definition into *QUnit.only(...)*. This is useful to make sure that QUnit - only runs this specific test. -- debug flag: most create utility functions have a debug mode (activated by the - debug: true parameter). In that case, the target widget will be put in the DOM - instead of the hidden qunit specific fixture, and more information will be - logged. For example, all mocked network communications will be available in the - console. -- when working on a failing test, it is common to add the debug flag, then - comment the end of the test (in particular, the destroy call). With this, it - is possible to see the state of the widget directly, and even better, to - manipulate the widget by clicking/interacting with it. - - -Integration Testing -=================== - -Testing Python code and JS code separately is very useful, but it does not prove -that the web client and the server work together. In order to do that, we can -write another kind of test: tours. A tour is a mini scenario of some interesting -business flow. It explains a sequence of steps that should be followed. The -test runner will then create a Chrome headless browser, point it to the proper url -and simulate the click and inputs, according to the scenario. - -Screenshots and screencasts during browser_js tests ---------------------------------------------------- - -When running tests that use HttpCase.browser_js from the command line, the Chrome -browser is used in headless mode. By default, if a test fails, a PNG screenshot is -taken at the moment of the failure and written in - -.. code-block:: console - - '/tmp/odoo_tests/{db_name}/screenshots/' - -Two new command line arguments were added since Odoo 13.0 to control this behavior: -:option:`--screenshots <odoo-bin --screenshots>` and :option:`--screencasts <odoo-bin --screencasts>` - - -Performance Testing -=================== - -Query counts ------------- - -One of the ways to test performance is to measure database queries. Manually, this can be tested with the -`--log-sql` CLI parameter. If you want to establish the maximum number of queries for an operation, -you can use the :meth:`~odoo.tests.common.BaseCase.assertQueryCount` method, integrated in Odoo test classes. - -.. code-block:: python - - with self.assertQueryCount(11): - do_something() - -.. _reference/testing/populate: - -Database population -------------------- - -Odoo CLI offers a :ref:`database population<reference/cmdline/populate>` feature. - -.. code-block:: console - - odoo-bin populate - -Instead of the tedious manual, or programmatic, specification of test data, -one can use this feature to fill a database on demand with the desired number of test data. -This can be used to detect diverse bugs or performance issues in tested flows. - -.. _reference/testing/populate/methods: - -To specify this feature for a given model, the following methods and attributes can be defined. - -.. currentmodule:: odoo.models - -.. autoattribute:: Model._populate_sizes -.. autoattribute:: Model._populate_dependencies -.. automethod:: Model._populate -.. automethod:: Model._populate_factories - -.. note:: - - You have to define at least :meth:`~odoo.models.Model._populate` or :meth:`~odoo.models.Model._populate_factories` - on the model to enable database population. - -Example model -^^^^^^^^^^^^^ - -.. code-block:: python - - from odoo.tools import populate - - class CustomModel(models.Model) - _inherit = "custom.some_model" - _populate_sizes = {"small": 100, "medium": 2000, "large": 10000} - _populate_dependencies = ["custom.some_other_model"] - - def _populate_factories(self): - # Record ids of previously populated models are accessible in the registry - some_other_ids = self.env.registry.populated_models["custom.some_other_model"] - - def get_some_field(values=None, random=None, **kwargs): - """ Choose a value for some_field depending on other fields values. - - :param dict values: - :param random: seeded :class:`random.Random` object - """ - field_1 = values['field_1'] - if field_1 in [value2, value3]: - return random.choice(some_field_values) - return False - - return [ - ("field_1", populate.randomize([value1, value2, value3])), - ("field_2", populate.randomize([value_a, value_b], [0.5, 0.5])), - ("some_other_id", populate.randomize(some_other_ids)), - ("some_field", populate.compute(get_some_field, seed="some_field")), - ('active', populate.cartesian([True, False])), - ] - - def _populate(self, size): - records = super()._populate(size) - - # If you want to update the generated records - # E.g setting the parent-child relationships - records.do_something() - - return records - -Population tools -^^^^^^^^^^^^^^^^ - -Multiple population tools are available to easily create -the needed data generators. - -.. automodule:: odoo.tools.populate - :members: cartesian, compute, constant, iterate, randint, randomize - -.. _qunit: https://qunitjs.com/ -.. _qunit_config.js: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/qunit_config.js#L49 -.. _web.tests_assets: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/views/webclient_templates.xml#L594 -.. _web.qunit_suite: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/views/webclient_templates.xml#L660 -.. _web.qunit_suite_tests: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/views/webclient_templates.xml#L680 -.. _controller: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/controllers/main.py#L637 -.. _test_js.py: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/tests/test_js.py#L13 -.. _test_utils.js: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/test_utils.js -.. _mock server: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/mock_server.js -.. _qunit assertions: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/qunit_asserts.js -.. _createView: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/test_utils_create.js#L267 diff --git a/doc/reference/translations.rst b/doc/reference/translations.rst deleted file mode 100644 index cc6c92cbb5d..00000000000 --- a/doc/reference/translations.rst +++ /dev/null @@ -1,214 +0,0 @@ -:banner: banners/translate.jpg - -.. _reference/translations: - - -=================== -Translating Modules -=================== - -This section explains how to provide translation abilities to your module. - -.. note:: If you want to contribute to the translation of Odoo itself, please refer to the - `Odoo Wiki page <https://github.com/odoo/odoo/wiki/Translations>`_. - -Exporting translatable term -=========================== - -A number of terms in your modules are "implicitly translatable" as a result, -even if you haven't done any specific work towards translation you can export -your module's translatable terms and may find content to work with. - -.. todo:: needs technical features - -Translations export is performed via the administration interface by logging into -the backend interface and opening :menuselection:`Settings --> Translations ---> Import / Export --> Export Translations` - -* leave the language to the default (new language/empty template) -* select the `PO File`_ format -* select your module -* click :guilabel:`Export` and download the file - -.. image:: translations/po-export.* - :align: center - :width: 75% - -This gives you a file called :file:`{yourmodule}.pot` which should be moved to -the :file:`{yourmodule}/i18n/` directory. The file is a *PO Template* which -simply lists translatable strings and from which actual translations (PO files) -can be created. PO files can be created using msginit_, with a dedicated -translation tool like POEdit_ or by simply copying the template to a new file -called :file:`{language}.po`. Translation files should be put in -:file:`{yourmodule}/i18n/`, next to :file:`{yourmodule}.pot`, and will be -automatically loaded by Odoo when the corresponding language is installed (via -:menuselection:`Settings --> Translations --> Languages`) - -.. note:: translations for all loaded languages are also installed or updated - when installing or updating a module - -Implicit exports -================ - -Odoo automatically exports translatable strings from "data"-type content: - -* in non-QWeb views, all text nodes are exported as well as the content of - the ``string``, ``help``, ``sum``, ``confirm`` and ``placeholder`` - attributes -* QWeb templates (both server-side and client-side), all text nodes are - exported except inside ``t-translation="off"`` blocks, the content of the - ``title``, ``alt``, ``label`` and ``placeholder`` attributes are also - exported -* for :class:`~odoo.fields.Field`, unless their model is marked with - ``_translate = False``: - - * their ``string`` and ``help`` attributes are exported - * if ``selection`` is present and a list (or tuple), it's exported - * if their ``translate`` attribute is set to ``True``, all of their existing - values (across all records) are exported -* help/error messages of :attr:`~odoo.models.Model._constraints` and - :attr:`~odoo.models.Model._sql_constraints` are exported - -Explicit exports -================ - -When it comes to more "imperative" situations in Python code or Javascript -code, Odoo cannot automatically export translatable terms so they -must be marked explicitly for export. This is done by wrapping a literal -string in a function call. - -In Python, the wrapping function is :func:`odoo._`:: - - title = _("Bank Accounts") - -In JavaScript, the wrapping function is generally :js:func:`odoo.web._t`: - -.. code-block:: javascript - - title = _t("Bank Accounts"); - -.. warning:: - - Only literal strings can be marked for exports, not expressions or - variables. For situations where strings are formatted, this means the - format string must be marked, not the formatted string - -The lazy version of `_` and `_t` is :func:`odoo._lt` in python and -:js:func:`odoo.web._lt` in javascript. The translation lookup is executed only -at rendering and can be used to declare translatable properties in class methods -of global variables. - -Variables -^^^^^^^^^ -**Don't** the extract may work but it will not translate the text correctly:: - - _("Scheduled meeting with %s" % invitee.name) - -**Do** set the dynamic variables as a parameter of the translation lookup (this -will fallback on source in case of missing placeholder in the translation):: - - _("Scheduled meeting with %s", invitee.name) - - -Blocks -^^^^^^ -**Don't** split your translation in several blocks or multiples lines:: - - # bad, trailing spaces, blocks out of context - _("You have ") + len(invoices) + _(" invoices waiting") - _t("You have ") + invoices.length + _t(" invoices waiting"); - - # bad, multiple small translations - _("Reference of the document that generated ") + \ - _("this sales order request.") - -**Do** keep in one block, giving the full context to translators:: - - # good, allow to change position of the number in the translation - _("You have %s invoices wainting") % len(invoices) - _.str.sprintf(_t("You have %s invoices wainting"), invoices.length); - - # good, full sentence is understandable - _("Reference of the document that generated " + \ - "this sales order request.") - -Plural -^^^^^^ -**Don't** pluralize terms the English-way:: - - msg = _("You have %(count)s invoice", count=invoice_count) - if invoice_count > 1: - msg += _("s") - -**Do** keep in mind every language has different plural forms:: - - if invoice_count > 1: - msg = _("You have %(count)s invoices", count=invoice_count) - else: - msg = _("You have one invoice") - -Read vs Run Time -^^^^^^^^^^^^^^^^ - -**Don't** invoke translation lookup at server launch:: - - ERROR_MESSAGE = { - # bad, evaluated at server launch with no user language - 'access_error': _('Access Error'), - 'missing_error': _('Missing Record'), - } - - class Record(models.Model): - - def _raise_error(self, code): - raise UserError(ERROR_MESSAGE[code]) - -**Don't** invoke translation lookup when the javascript file is read:: - - # bad, js _t is evaluated too early - var core = require('web.core'); - var _t = core._t; - var map_title = { - access_error: _t('Access Error'), - missing_error: _t('Missing Record'), - }; - - -**Do** use lazy translation lookup method:: - - ERROR_MESSAGE = { - 'access_error': _lt('Access Error'), - 'missing_error': _lt('Missing Record'), - } - - class Record(models.Model): - - def _raise_error(self, code): - # translation lookup executed at error rendering - raise UserError(ERROR_MESSAGE[code]) - - -or **do** evaluate dynamically the translatable content:: - - # good, evaluated at run time - def _get_error_message(self): - return { - access_error: _('Access Error'), - missing_error: _('Missing Record'), - } - -**Do** in the case where the translation lookup is done when the JS file is -*read*, use `_lt` instead of `_t` to translate the term when it is *used*:: - - # good, js _lt is evaluated lazily - var core = require('web.core'); - var _lt = core._lt; - var map_title = { - access_error: _lt('Access Error'), - missing_error: _lt('Missing Record'), - }; - - -.. _PO File: https://en.wikipedia.org/wiki/Gettext#Translating -.. _msginit: https://www.gnu.org/software/gettext/manual/gettext.html#Creating -.. _POEdit: https://poedit.net/ diff --git a/doc/reference/translations/po-export.png b/doc/reference/translations/po-export.png Binary files differdeleted file mode 100644 index 91574b40ee8..00000000000 --- a/doc/reference/translations/po-export.png +++ /dev/null diff --git a/doc/reference/views.rst b/doc/reference/views.rst deleted file mode 100644 index a8ad929c6a2..00000000000 --- a/doc/reference/views.rst +++ /dev/null @@ -1,2139 +0,0 @@ -:banner: banners/views.jpg - -.. _reference/views: - -===== -Views -===== - -Views are what define how records should be displayed to end-users. They are specified in XML which -means that they can be edited independently from the models that they represent. They are flexible -and allow a high level of customization of the screens that they control. There exist various types -of views. Each of them represents a mode of visualization: *form*, *list*, *kanban*, etc. - -.. todo:: Build doc of ir_ui_view.py ? - -.. _reference/views/structure: - -Generic structure -================= - -Basic views generally share the common structure defined below. Placeholders are denoted in all -caps. - -.. code-block:: xml - - <record id="MODEL_view_TYPE" model="ir.ui.view"> - <field name="name">NAME</field> - <field name="model">MODEL</field> - <field name="arch" type="xml"> - <VIEW_TYPE> - <VIEW_SPECIFICATIONS/> - </VIEW_TYPE> - </field> - </record> - -Fields -====== - -View objects expose a number of fields. They are optional unless specified otherwise. - -* ``name`` (mandatory) :class:`~odoo.fields.Char` - - Only useful as a mnemonic/description of the view when looking for one in a list of some sort. - -* ``model`` :class:`~odoo.fields.Char` - - The model linked to the view, if applicable. - -* ``priority`` :class:`~odoo.fields.Integer` - - When a view is requested by ``(model, type)``, the view matching the model and - the type, with the lowest priority will be returned (it is the default view). - - It also defines the order of views application during :ref:`view - inheritance <reference/views/inheritance>`. - -* ``groups_id`` :class:`~odoo.fields.Many2many` -> :class:`odoo.addons.base.models.res_users.Groups` - - The groups allowed to use/access the current view. - - If the view extends an existing view, the extension will only be applied - for a given user if the user has access to the provided ``groups_id``. - -* ``arch`` :class:`~odoo.fields.Text` - - The description of the view layout. - -Attributes -========== - -.. todo:: - - view attributes & view element attributes - attrs & states attributes are missing generic information - -The different view types have a wide variety of attributes allowing customizations of -the generic behaviors. Some main attributes will be explained here. They do not all have -an impact on all view types. - -.. note:: The current context and user access rights may also impact the view abilities. - -.. todo:: info on create/... in the context ? - -* ``create`` - - Disable/enable record creation on the view. - -* ``edit`` (``form`` & ``list`` & ``gantt``) - - Disable/enable record editing on the view. - -* ``delete`` (``form`` & ``list``) - - Disable/enable record deletion on the view through the **Action** dropdown. - -* ``duplicate`` (``form`` & ``list``) - - Disable/enable record duplication on the view through the **Action** dropdown. - -* ``decoration-{$name}`` (``list`` & ``gantt``) - - Define a conditional display of a record in the style of a row's text based on the corresponding - record's attributes. - - Values are Python expressions. For each record, the expression is evaluated - with the record's attributes as context values and if ``true``, the - corresponding style is applied to the row. Here are some of the other values - available in the context: - - * ``uid``: the id of the current user, - * ``today``: the current local date as a string of the form ``YYYY-MM-DD``, - * ``now``: same as ``today`` with the addition of the current time. - This value is formatted as ``YYYY-MM-DD hh:mm:ss``. - - .. code-block:: xml - - <tree decoration-info="state == 'draft'" - decoration-danger="state == 'help_needed'" - decoration-bf="state='busy'"> - <TREE_VIEW_CONTENT> - </tree> - - .. warning:: - Supported values differ for the two view types. The Gantt view only supports ``success``, - ``info``, ``warning``, ``danger`` and ``secondary`` displays. The list view supports ``bf``, - ``it``, ``success``, ``info``, ``warning``, ``danger``, ``muted`` and ``primary`` displays. - -* ``sample`` (``kanban`` & ``list`` & ``gantt`` & ``graph`` & ``pivot`` & ``cohort`` & ``dashboard``) - - Populate the view with a set of sample records if none are found for the current model. - This attribute is false by default. - - These fake records will have heuristics for certain field names/models. For example, - a field 'display_name' on the model 'res.users' will be populated with sample people names - while an 'email' field will be in the form 'firstname.lastname@sample.demo'. - - The user will not be able to interact with these data and they will be discarded as soon as - an action is performed (record created, column added, etc.) - -* ``banner_route`` - a route address to be fetched and prepended to the view. - - If this attribute is set, the - :ref:`controller route url<reference/controllers>` will be fetched and - displayed above the view. The json response from the controller should - contain an "html" key. - - If the html contains a stylesheet <link> tag, it will be - removed and appended to <head>. - - To interact with the backend you can use <a type="action"> tags. Please take - a look at the documentation of the _onActionClicked method of - AbstractController (*addons/web/static/src/js/views/abstract_controller.js*) - for more details. - - Only views extending AbstractView and AbstractController can use this - attribute, like :ref:`reference/views/form`, :ref:`reference/views/kanban`, - :ref:`reference/views/list`, ... - - Example: - - .. code-block:: xml - - <tree banner_route="/module_name/hello" /> - - .. code-block:: python - - class MyController(odoo.http.Controller): - @http.route('/module_name/hello', auth='user', type='json') - def hello(self): - return { - 'html': """ - <div> - <link href="/module_name/static/src/css/banner.css" - rel="stylesheet"> - <h1>hello, world</h1> - </div> """ - } - -.. todo:: Views main content section, with field, group & separator ? - -.. _reference/views/inheritance: - -Inheritance -=========== - -Inheritance fields ------------------- - -The two following :class:`~odoo.addons.base.ir_ui_view.View` fields are used to specify -inherited views. - -* ``inherit_id`` :class:`~odoo.fields.Many2one` - - the current view's parent view, unset by default. Specify the parent using - the `ref` attribute: - - .. code-block:: xml - - <field name="inherit_id" ref="library.view_book_form"/> - -* ``mode`` :class:`~odoo.fields.Selection`: `extension / primary` - - inheritance mode, ``extension`` by default if ``inherit_id`` is set, - ``primary`` otherwise. - - An example of where you would want to override ``mode`` while using - ``inherit_id`` is delegation inheritance. - In that case your derived model will be separate from its parent and views - matching with one won't match with the other. Suppose you inherit from a view - associated with the parent model and want to customize the derived view to - show data from the derived model. The ``mode`` of the derived view needs to - be set to ``primary``, because it's the base (and maybe only) view for that - derived model. Otherwise the :ref:`view matching <reference/views/inheritance/view-matching>` - rules won't apply. - -.. _reference/views/inheritance/view-matching: - -View matching -------------- - -* if a view is requested by ``(model, type)``, the view with the right model - and type, ``mode=primary`` and the lowest priority is matched. -* when a view is requested by ``id``, if its mode is not ``primary`` its - *closest* parent with mode ``primary`` is matched. - -View resolution ---------------- - -Resolution generates the final ``arch`` for a requested/matched ``primary`` -view: - -#. if the view has a parent, the parent is fully resolved then the current - view's inheritance specs are applied -#. if the view has no parent, its ``arch`` is used as-is -#. the current view's children with mode ``extension`` are looked up and their - inheritance specs are applied depth-first (a child view is applied, then - its children, then its siblings) - -The result of applying children views yields the final ``arch`` - -.. todo:: NOTE on fields_view_get and link to ORM ? - -Inheritance specs ------------------ - -Inheritance specs are comprised of an element locator, to match -the inherited element in the parent view, and children element that -will be used to modify the inherited element. - -There are three types of element locators for matching a target element: - -* An ``xpath`` element with an ``expr`` attribute. ``expr`` is an XPath_ - expression\ [#hasclass]_ applied to the current ``arch``, the first node - it finds is the match -* a ``field`` element with a ``name`` attribute, matches the first ``field`` - with the same ``name``. All other attributes are ignored during matching -* any other element: the first element with the same name and identical - attributes (ignoring ``position`` and ``version`` attributes) is matched - -.. code-block:: xml - - <xpath expr="page[@name='pg']/group[@name='gp']/field" position="inside"> - <field name="description"/> - </xpath> - - <field name="res_id" position="after"/> - - <div name="name" position="replace"> - <div name="name2"> - <field name="name2"/> - </div> - </div> - -The inheritance spec may have an optional ``position`` attribute specifying -how the matched node should be altered: - -``inside`` (default) - the content of the inheritance spec is appended to the matched node -``replace`` - the content of the inheritance spec replaces the matched node. - Any text node containing only ``$0`` within the contents of the spec will - be replaced by a complete copy of the matched node, effectively wrapping - the matched node. -``after`` - the content of the inheritance spec is added to the matched node's - parent, after the matched node -``before`` - the content of the inheritance spec is added to the matched node's - parent, before the matched node -``attributes`` - the content of the inheritance spec should be ``attribute`` elements - with a ``name`` attribute and an optional body: - - * if the ``attribute`` element has a body, a new attributed named - after its ``name`` is created on the matched node with the - ``attribute`` element's text as value - * if the ``attribute`` element has no body, the attribute named after - its ``name`` is removed from the matched node. If no such attribute - exists, an error is raised - - .. code-block:: xml - - <field name="sale_information" position="attributes"> - <attribute name="invisible">0</attribute> - <attribute name="attrs"> - {'invisible': [('sale_ok', '=', False)], 'readonly': [('editable', '=', False)]} - </attribute> - </field> - -``move`` - can be used as a direct child of a inheritance spec - with a ``inside``, ``replace``, ``after`` or ``before`` ``position`` attribute - to move a node. - - .. code-block:: xml - - <xpath expr="//@target" position="after"> - <xpath expr="//@node" position="move"/> - </xpath> - - <field name="target_field" position="after"> - <field name="my_field" position="move"/> - </field> - - -A view's specs are applied sequentially. - -.. [#hasclass] an extension function is added for simpler matching in QWeb - views: ``hasclass(*classes)`` matches if the context node has - all the specified classes - -.. _reference/views/types: - -View types -========== - -.. _reference/views/activity: - -Activity --------- - -The Activity view is used to display the activities linked to the records. The -data are displayed in a chart with the records forming the rows and the activity -types the columns. The first cell of each row displays a (customizable, see -``templates``, quite similarly to :ref:`reference/views/kanban`) card representing -the corresponding record. When clicking on others cells, a detailed description -of all activities of the same type for the record is displayed. - -.. warning:: - - The Activity view is only available when the ``mail`` module is installed, - and for the models that inherit from the ``mail.activity.mixin``. - -The root element of the Activity view is ``<activity>``, it accepts the following -attributes: - -- ``string`` (mandatory) - A title, which should describe the view - -Possible children of the view element are: - -``field`` - declares fields to use in activity *logic*. If the field is simply displayed - in the activity view, it does not need to be pre-declared. - - Possible attributes are: - - ``name`` (required) - the name of the field to fetch - -``templates`` - defines the :ref:`reference/qweb` templates. Cards definition may be - split into multiple templates for clarity, but activity views *must* define at - least one root template ``activity-box``, which will be rendered once for each - record. - - The activity view uses mostly-standard :ref:`javascript qweb - <reference/qweb/javascript>` and provides the following context variables - (see :ref:`reference/views/kanban` for more details): - - ``widget`` - the current :js:class:`ActivityRecord`, can be used to fetch some - meta-information. These methods are also available directly in the - template context and don't need to be accessed via ``widget`` - ``record`` - an object with all the requested fields as its attributes. Each field has - two attributes ``value`` and ``raw_value`` - -.. _reference/views/calendar: - -Calendar --------- - -Calendar views display records as events in a daily, weekly, monthly or yearly -calendar. Their root element is ``<calendar>``. Available attributes on the -calendar view are: - -``date_start`` (required) - name of the record's field holding the start date for the event -``date_stop`` - name of the record's field holding the end date for the event, if - ``date_stop`` is provided records become movable (via drag and drop) - directly in the calendar -``date_delay`` - alternative to ``date_stop``, provides the duration of the event instead of - its end date (unit: day) -``color`` - name of a record field to use for *color segmentation*. Records in the - same color segment are allocated the same highlight color in the calendar, - colors are allocated semi-randomly. - Displayed the display_name/avatar of the visible record in the sidebar -``form_view_id`` - view to open when the user create or edit an event. Note that if this attribute - is not set, the calendar view will fall back to the id of the form view in the - current action, if any. -``event_open_popup`` - If the option 'event_open_popup' is set to true, then the calendar view will - open events (or records) in a FormViewDialog. Otherwise, it will open events - in a new form view (with a do_action) -``quick_add`` - enables quick-event creation on click: only asks the user for a ``name`` - and tries to create a new event with just that and the clicked event - time. Falls back to a full form dialog if the quick creation fails -``all_day`` - name of a boolean field on the record indicating whether the corresponding - event is flagged as day-long (and duration is irrelevant) -``mode`` - Default display mode when loading the calendar. - Possible attributes are: ``day``, ``week``, ``month``, ``year`` -``scales`` - Comma-separated list of scales to provide. By default, all scales are - available. See mode for possible scale values. - -``<field>`` - declares fields to aggregate or to use in kanban *logic*. If the field is - simply displayed in the calendar cards. - - Fields can have additional attributes: - - * ``invisible`` - use "True" to hide the value in the cards - * ``avatar_field`` - only for x2many field, to display the avatar instead of the display_name - in the cards - * ``write_model`` and ``write_field`` - - you can add a filter and save the result in the defined model, the - filter is added in the sidebar - * ``filter`` and ``color`` - - use "True" to add this field in filter in the sidebar. You can specify - a ``color`` field used to colorize the checkbox. - -.. _reference/views/cohort: - -Cohort ------- - -.. raw:: html - - <span class="badge" style="background-color:#AD5E99">Enterprise feature</span> - -The cohort view is used to display and understand the way some data changes over -a period of time. For example, imagine that for a given business, clients can -subscribe to some service. The cohort view can then display the total number -of subscriptions each month, and study the rate at which client leave the service -(churn). When clicking on a cell, the cohort view will redirect you to a new action -in which you will only see the records contained in the cell's time interval; -this action contains a list view and a form view. - -.. note:: By default the cohort view will use the same list and form views as those - defined on the action. You can pass a list view and a form view - to the context of the action in order to set/override the views that will be - used (the context keys to use being `form_view_id` and `list_view_id`) - -For example, here is a very simple cohort view: - -.. code-block:: xml - - <cohort string="Subscription" date_start="date_start" date_stop="date" interval="month"/> - -The root element of the Cohort view is <cohort>, it accepts the following -attributes: - - -- ``string`` (mandatory) - A title, which should describe the view - -- ``date_start`` (mandatory) - A valid date or datetime field. This field is understood by the view as the - beginning date of a record - -- ``date_stop`` (mandatory) - A valid date or datetime field. This field is understood by the view as the - end date of a record. This is the field that will determine the churn. - -- ``mode`` (optional) - A string to describe the mode. It should be either 'churn' or - 'retention' (default). Churn mode will start at 0% and accumulate over time - whereas retention will start at 100% and decrease over time. - -- ``timeline`` (optional) - A string to describe the timeline. It should be either 'backward' or 'forward' (default). - Forward timeline will display data from date_start to date_stop, whereas backward timeline - will display data from date_stop to date_start (when the date_start is in future / greater - than date_stop). - -- ``interval`` (optional) - A string to describe a time interval. It should be 'day', 'week', 'month'' - (default) or 'year'. - -- ``measure`` (optional) - A field that can be aggregated. This field will be used to compute the values - for each cell. If not set, the cohort view will count the number of occurrences. - -``<field>`` (optional) - allows to specify a particular field in order to manage it from the available measures, it's - main use is for hiding a field from the selectable measures: - -- ``name`` (required) - the name of the field to use in the view. -- ``string`` (optional) - the name that would be used to display the field in the cohort view, overrides the - default python String attribute of the field. -- ``invisible`` (optional) - if true, the field will not appear either in the active measures nor in the selectable - measures (useful for fields that do not make sense aggregated, such as fields in different - units, e.g. € and $). - -.. _reference/views/dashboard: - -Dashboard ---------- - -.. raw:: html - - <span class="badge" style="background-color:#AD5E99">Enterprise feature</span> - -Like pivot and graph view, The dashboard view is used to display aggregate data. -However, the dashboard can embed sub views, which makes it possible to have a -more complete and interesting look on a given dataset. - -The dashboard view can display sub views, aggregates for some fields (over a -domain), or even *formulas* (expressions which involves one or more aggregates). -For example, here is a very simple dashboard: - -.. code-block:: xml - - <dashboard> - <view type="graph" ref="sale_report.view_order_product_graph"/> - <group string="Sale"> - <aggregate name="price_total" field="price_total" widget="monetary"/> - <aggregate name="order_id" field="order_id" string="Orders"/> - <formula name="price_average" string="Price Average" - value="record.price_total / record.order_id" widget="percentage"/> - </group> - <view type="pivot" ref="sale_report.view_order_product_pivot"/> - </dashboard> - -The root element of the Dashboard view is <dashboard>, it does not accept any -attributes. - -There are 5 possible type of tags in a dashboard view: - -``view`` - declares a sub view. - - Admissible attributes are: - - - ``type`` (mandatory) - The type of the sub view. For example, *graph* or *pivot*. - - - ``ref`` (optional) - An xml id for a view. If not given, the default view for the model will - be used. - - - ``name`` (optional) - A string which identifies this element. It is mostly - useful to be used as a target for an xpath. - -``group`` - defines a column layout. This is actually very similar to the group element - in a form view. - - Admissible attributes are: - - - ``string`` (optional) - A description which will be displayed as a group title. - - - ``colspan`` (optional) - The number of subcolumns in this group tag. By default, 6. - - - ``col`` (optional) - The number of columns spanned by this group tag (only makes sense inside - another group). By default, 6. - - -``aggregate`` - declares an aggregate. This is the value of an aggregate for a given field - over the current domain. - - Note that aggregates are supposed to be used inside a group tag (otherwise - the style will not be properly applied). - - Admissible attributes are: - - - ``field`` (mandatory) - The field name to use for computing the aggregate. Possible field types - are: - - - ``integer`` (default group operator is sum) - - ``float`` (default group operator is sum) - - ``many2one`` (default group operator is count distinct) - - - ``name`` (mandatory) - A string to identify this aggregate (useful for formulas) - - - ``string`` (optional) - A short description that will be displayed above the value. If not - given, it will fall back to the field string. - - - ``domain`` (optional) - An additional restriction on the set of records that we want to aggregate. - This domain will be combined with the current domain. - - - ``domain_label`` (optional) - When the user clicks on an aggregate with a domain, it will be added to - the search view as a facet. The string displayed for this facet can - be customized with this attribute. - - - ``group_operator`` (optional) - A valid postgreSQL aggregate function identifier to use when aggregating - values (see https://www.postgresql.org/docs/9.5/static/functions-aggregate.html). - If not provided, By default, the group_operator from the field definition is used. - Note that no aggregation of field values is achieved if the group_operator value is "". - - .. note:: The special aggregate function ``count_distinct`` (defined in odoo) can also be used here - - .. code-block:: xml - - <aggregate name="price_total_max" field="price_total" group_operator="max"/> - - - - - ``col`` (optional) - The number of columns spanned by this tag (only makes sense inside a - group). By default, 1. - - - ``widget`` (optional) - A widget to format the value (like the widget attribute for fields). - For example, monetary. - - - ``help`` (optional) - A help message to dipslay in a tooltip (equivalent of help for a field in python) - - - ``measure`` (optional) - This attribute is the name of a field describing the measure that has to be used - in the graph and pivot views when clicking on the aggregate. - The special value __count__ can be used to use the count measure. - - .. code-block:: xml - - <aggregate name="total_ojects" string="Total Objects" field="id" group_operator="count" measure="__count__"/> - - - ``clickable`` (optional) - A boolean indicating if this aggregate should be clickable or not (default to true). - Clicking on a clickable aggregate will change the measures used by the subviews - and add the value of the domain attribute (if any) to the search view. - - - ``value_label`` (optional) - A string put on the right of the aggregate value. - For example, it can be useful to indicate the unit of measure - of the aggregate value. - -``formula`` - declares a derived value. Formulas are values computed from aggregates. - - Note that like aggregates, formulas are supposed to be used inside a group - tag (otherwise the style will not be properly applied). - - Admissible attributes are: - - - ``value`` (mandatory) - A string expression that will be evaluated, with the builtin python - evaluator (in the web client). Every aggregate can be used in the - context, in the ``record`` variable. For example, - ``record.price_total / record.order_id``. - - - ``name`` (optional) - A string to identify this formula - - - ``string`` (optional) - A short description that will be displayed above the formula. - - - ``col`` (optional) - The number of columns spanned by this tag (only makes sense inside a - group). By default, 1. - - - ``widget`` (optional) - A widget to format the value (like the widget attribute for fields). - For example, monetary. By default, it is 'float'. - - - ``help`` (optional) - A help message to dipslay in a tooltip (equivalent of help for a field in python) - - - ``value_label`` (optional) - A string put on the right of the formula value. - For example, it can be useful to indicate the unit of measure - of the formula value. - -``widget`` - Declares a specialized widget to be used to display the information. This is - a mechanism similar to the widgets in the form view. - - Admissible attributes are: - - - ``name`` (mandatory) - A string to identify which widget should be instantiated. The view will - look into the ``widget_registry`` to get the proper class. - - - ``col`` (optional) - The number of columns spanned by this tag (only makes sense inside a - group). By default, 1. - -.. _reference/views/form: - -Form ----- - -Form views are used to display the data from a single record. Their root -element is ``<form>``. They are composed of regular HTML_ with additional -structural and semantic components. - -Structural components -~~~~~~~~~~~~~~~~~~~~~ - -Structural components provide structure or "visual" features with little -logic. They are used as elements or sets of elements in form views. - -``notebook`` - defines a tabbed section. Each tab is defined through a ``page`` child - element. Pages can have the following attributes: - - * ``string`` (required) - the title of the tab - * ``accesskey`` - an HTML accesskey_ - * ``attrs`` - standard dynamic attributes based on record values - - .. note:: Note that ``notebook`` should not be placed within ``group`` - -``group`` - used to define column layouts in forms. By default, groups define 2 columns - and most direct children of groups take a single column. ``field`` direct - children of groups display a label by default, and the label and the field - itself have a colspan of 1 each. - - The number of columns in a ``group`` can be customized using the ``col`` - attribute, the number of columns taken by an element can be customized using - ``colspan``. - - Children are laid out horizontally (tries to fill the next column before - changing row). - - Groups can have a ``string`` attribute, which is displayed as the group's - title -``newline`` - only useful within ``group`` elements, ends the current row early and - immediately switches to a new row (without filling any remaining column - beforehand) -``separator`` - small horizontal spacing, with a ``string`` attribute behaves as a section - title -``sheet`` - can be used as a direct child to ``form`` for a narrower and more responsive - form layout -``header`` - combined with ``sheet``, provides a full-width location above the sheet - itself, generally used to display workflow buttons and status widgets - -Semantic components -~~~~~~~~~~~~~~~~~~~ - -Semantic components tie into and allow interaction with the Odoo -system. Available semantic components are: - -``button`` - call into the Odoo system, similar to :ref:`list view buttons - <reference/views/list/button>`. In addition, the following attribute can be - specified: - - ``special`` - for form views opened in dialogs: ``save`` to save the record and close the - dialog, ``cancel`` to close the dialog without saving. - ``confirm`` - confirmation message to display (and for the user to accept) before - performing the button's Odoo call (also works in Kanban views). - -``field`` - renders (and allow editing of, possibly) a single field of the current - record. Using several times a field in a form view is supported and the fields - can receive different values for modifiers 'invisible' and 'readonly'. However, - the behavior is not guaranteed when several fields exist with different values - for modifier 'required'. Possible attributes of the field node are: - - ``name`` (mandatory) - the name of the field to render - ``id`` - the node id. Useful when there are several occurrences of the same field in - the view (see ``label`` component below). Default is the field name. - ``widget`` - fields have a default rendering based on their type - (e.g. :class:`~odoo.fields.Char`, - :class:`~odoo.fields.Many2one`). The ``widget`` attributes allows using - a different rendering method and context. - - .. todo:: list of widgets - - & options & specific attributes (e.g. widget=statusbar - statusbar_visible clickable) - ``options`` - JSON object specifying configuration option for the field's widget - (including default widgets) - ``class`` - HTML class to set on the generated element, common field classes are: - - ``oe_inline`` - prevent the usual line break following fields - ``oe_left``, ``oe_right`` - floats_ the field to the corresponding direction - ``oe_read_only``, ``oe_edit_only`` - only displays the field in the corresponding form mode - ``oe_avatar`` - for image fields, displays images as "avatar" (square, 90x90 maximum - size, some image decorations) - ``groups`` - only displays the field for specific users - ``on_change`` - calls the specified method when this field's value is edited, can generate - update other fields or display warnings for the user - - .. deprecated:: 8.0 - - Use :func:`odoo.api.onchange` on the model - - ``attrs`` - dynamic meta-parameters based on record values - ``domain`` - for relational fields only, filters to apply when displaying existing - records for selection - ``context`` - for relational fields only, context to pass when fetching possible values - ``readonly`` - display the field in both readonly and edit mode, but never make it - editable - ``required`` - generates an error and prevents saving the record if the field doesn't - have a value - ``nolabel`` - don't automatically display the field's label, only makes sense if the - field is a direct child of a ``group`` element - ``placeholder`` - help message to display in *empty* fields. Can replace field labels in - complex forms. *Should not* be an example of data as users are liable to - confuse placeholder text with filled fields - ``mode`` - for :class:`~odoo.fields.One2many`, display mode (view type) to use for - the field's linked records. One of ``tree``, ``form``, ``kanban`` or - ``graph``. The default is ``tree`` (a list display) - ``help`` - tooltip displayed for users when hovering the field or its label - ``filename`` - for binary fields, name of the related field providing the name of the - file - ``password`` - indicates that a :class:`~odoo.fields.Char` field stores a password and - that its data shouldn't be displayed - ``kanban_view_ref`` - for opening specific kanban view when selecting records from m2o/m2m in mobile - environment - -``label`` - when a ``field`` component isn't placed directly inside a ``group``, or when - its ``nolabel`` attribute is set, the field's label isn't automatically - displayed alongside its value. The ``label`` component is the manual alternative - of displaying the label of a field. Possible attributes are: - - ``for`` (mandatory) - the reference to the field associated with the label. Can be either the name - of a field, or its id (``id`` attribute set on the ``field``). When there are - several occurrences of the same field in the view, and there are several - ``label`` components associated with these ``field`` nodes, those labels - must have unique ``for`` attributes (in this case referencing the ``id`` - attribute of the corresponding ``field`` nodes). - ``string`` - the label to display. Display the field's label (coming from the field - definition in the model) by default. - ``class`` - same as for ``field`` component. - ``attrs`` - same as for ``field`` component. - - -Generic structure -~~~~~~~~~~~~~~~~~ - -.. code-block:: xml - - <form> - <header> - <field name="state" widget="statusbar"/> - </header> - <sheet> - <div class="oe_button_box"> - <BUTTONS/> - </div> - <group> - <group> - <field name="fname"/> - </group> - </group> - <notebook> - <page string="Page1"> - <group> - <CONTENT/> - </group> - </page> - <page string="Page2"> - <group> - <CONTENT/> - </group> - </page> - </notebook> - </sheet> - </form> - -.. todo:: classes for forms - -.. todo:: widgets? - -.. _reference/views/gantt: - -Gantt ------ - -.. raw:: html - - <span class="badge" style="background-color:#AD5E99">Enterprise feature</span> - -Gantt views appropriately display Gantt charts (for scheduling). - -The root element of gantt views is ``<gantt/>``, it has no children but can -take the following attributes: - -``date_start`` (required) - name of the field providing the start datetime of the event for each - record. -``date_stop`` (required) - name of the field providing the end duration of the event for each - record. -``color`` - name of the field used to color the pills according to its value -``decoration-{$name}`` - allow changing the style of a row's text based on the corresponding - record's attributes. - - Values are Python expressions. For each record, the expression is evaluated - with the record's attributes as context values and if ``true``, the - corresponding style is applied to the row. Here are some of the other values - available in the context: - - * ``uid``: the id of the current user, - * ``today``: the current local date as a string of the form ``YYYY-MM-DD``, - * ``now``: same as ``today`` with the addition of the current time. - This value is formatted as ``YYYY-MM-DD hh:mm:ss``. - - ``{$name}`` can be one of the following `bootstrap contextual color`_ (``danger``, - ``info``, ``secondary``, ``success`` or ``warning``). -``default_group_by`` - name of a field to group tasks by -``consolidation`` - field name to display consolidation value in record cell -``consolidation_max`` - dictionnary with the "group by" field as key and the maximum consolidation - value that can be reached before displaying the cell in red - (e.g. ``{"user_id": 100}``) -``consolidation_exclude`` - name of the field that describes if the task has to be excluded - from the consolidation - if set to true it displays a striped zone in the consolidation line -``create``, ``cell_create``, ``edit``, ``delete``, ``plan`` - allows *dis*\ abling the corresponding action in the view by setting the - corresponding attribute to ``false`` (default: ``true``). - - * ``create``: If enabled, an ``Add`` button will be available in the control - panel to create records. - * ``cell_create``: If enabled and ``create`` enabled, a "**+**" button will be - displayed while hovering on a time slot cell to create a new record on that slot. - * ``edit``: If enabled, the opened records will be in edit mode (thus editable). - * ``plan``: If enabled and ``edit`` enabled, a "magnifying glass" button will be displayed - on time slots to plan unassigned records into that time slot. - - .. admonition:: Example - - When you do not want to create records on the gantt view and the beginning and end - dates are required on the model, the planning feature should be disabled - because no record will ever be found. -``offset`` - Depending on the scale, the number of units to add to today to compute the - default period. Examples: An offset of +1 in default_scale week will open the - gantt view for next week, and an offset of -2 in default_scale month will open - the gantt view of 2 months ago. -``progress`` - name of a field providing the completion percentage for the record's event, - between 0 and 100 -``string`` - title of the gantt view -``precision`` - JSON object specifying snapping precisions for the pills in each scale. - - * Possible values for scale ``day`` are (default: ``hour``): - - ``hour``: records times snap to full hours (ex: 7:12 becomes 8:00) - - ``hour:half``: records times snap to half hours (ex: 7:12 becomes 7:30) - - ``hour:quarter``: records times snap to half hours (ex: 7:12 becomes 7:15) - - * Possible values for scale ``week`` are (default: ``day:half``): - - ``day``: records times snap to full days (ex: 7:28 AM becomes 11:59:59 PM of the previous day, 10:32 PM becomes 12:00 PM of the current day) - - ``day:half``: records times snap to half hours (ex: 7:28 AM becomes 12:00 PM) - - * Possible values for scale ``month`` are (default: ``day:half``): - - ``day``: records times snap to full days (ex: 7:28 AM becomes 11:59:59 PM of the previous day, 10:32 PM becomes 12:00 PM of the current day) - - ``day:half``: records times snap to half hours (ex: 7:28 AM becomes 12:00 PM) - - * Scale ``year`` always snap to full day. - - Example of precision attribute: ``{"day": "hour:quarter", "week": "day:half", "month": "day"}`` -``total_row`` - boolean to control whether the row containing the total count of records should - be displayed. (default: ``false``) -``collapse_first_level`` - boolean to control whether it is possible to collapse each row if grouped by - one field. (default: ``false``, the collapse starts when grouping by two fields) -``display_unavailability`` - boolean to mark the dates returned by the ``gantt_unavailability`` function of - the model as available inside the gantt view. Records can still be scheduled - in them, but their unavailability is visually displayed. (default: ``false``) -``default_scale`` - default scale when rendering the view. Possible values are (default: ``month``): - - * ``day`` - * ``week`` - * ``month`` - * ``year`` - -``scales`` - comma-separated list of allowed scales for this view. By default, all scales - are allowed. For possible scale values to use in this list, see ``default_scale``. - -``templates`` - defines the :ref:`reference/qweb` template ``gantt-popover`` which is used - when the user hovers over one of the records in the gantt view. - - The gantt view uses mostly-standard :ref:`javascript qweb - <reference/qweb/javascript>` and provides the following context variables: - - ``widget`` - the current :js:class:`GanttRow`, can be used to fetch some - meta-information. The ``getColor`` method to convert in a color integer is - also available directly in the template context without using ``widget``. - - ``on_create`` - If specified when clicking the add button on the view, instead of opening a generic dialog, launch a client action. - this should hold the xmlid of the action (eg: ``on_create="%(my_module.my_wizard)d"`` - -``form_view_id`` - view to open when the user create or edit a record. Note that if this attribute - is not set, the gantt view will fall back to the id of the form view in the - current action, if any. - -``dynamic_range`` - if set to true, the gantt view will start at the first record, - instead of starting at the beginning of the year/month/day. - -``thumbnails`` - This allows to display a thumbnail next to groups name if the group is a relationnal field. - This expects a python dict which keys are the name of the field on the active model. - Values are the names of the field holding the thumbnail on the related model. - - Example: tasks have a field user_id that reference res.users. The res.users model has a field image that holds the avatar, - then: - - .. code-block:: xml - - <gantt - date_start="date_start" - date_stop="date_stop" - thumbnails="{'user_id': 'image_128'}" - > - </gantt> - - will display the users avatars next to their names when grouped by user_id. - -.. _reference/views/graph: - -Graph ------ - -The graph view is used to visualize aggregations over a number of records or -record groups. Its root element is ``<graph>`` which can take the following -attributes: - -``type`` - one of ``bar`` (default), ``pie`` and ``line``, the type of graph to use -``stacked`` - only used for ``bar`` charts. If present and set to ``True``, stacks bars - within a group -``disable_linking`` - set to ``True`` to prevent from redirecting clicks on graph to list view -``order`` - if set, x-axis values will be sorted by default according their measure with - respect to the given order (``asc`` or ``desc``). Only used for ``bar`` and - ``pie`` charts. - -The only allowed element within a graph view is ``field`` which can have the -following attributes: - -``name`` (required) - the name of a field to use in the view. If used for grouping (rather - than aggregating) - -``title`` (optional) - string displayed on the top of the graph. - -``invisible`` (optional) - if true, the field will not appear either in the active measures nor in the - selectable measures. - -``type`` - indicates whether the field should be used as a grouping criteria or as an - aggregated value within a group. Possible values are: - - ``row`` (default) - groups by the specified field. All graph types support at least one level - of grouping, some may support more. - ``col`` - authorized in graph views but only used by pivot tables - ``measure`` - field to aggregate within a group - -``interval`` - on date and datetime fields, groups by the specified interval (``day``, - ``week``, ``month``, ``quarter`` or ``year``) instead of grouping on the - specific datetime (fixed second resolution) or date (fixed day resolution). - -The measures are automatically generated from the model fields; only the -aggregatable fields are used. Those measures are also alphabetically -sorted on the string of the field. - -.. warning:: - - graph view aggregations are performed on database content, non-stored - function fields can not be used in graph views - -.. _reference/views/kanban: - -Kanban ------- - -The kanban view is a `kanban board`_ visualisation: it displays records as -"cards", halfway between a :ref:`list view <reference/views/list>` and a -non-editable :ref:`form view <reference/views/form>`. Records may be grouped -in columns for use in workflow visualisation or manipulation (e.g. tasks or -work-progress management), or ungrouped (used simply to visualize records). - -.. note:: The kanban view will load and display a maximum of ten columns. - Any column after that will be closed (but can still be opened by - the user). - -The root element of the Kanban view is ``<kanban>``, it can use the following -attributes: - -``default_group_by`` - whether the kanban view should be grouped if no grouping is specified via - the action or the current search. Should be the name of the field to group - by when no grouping is otherwise specified -``default_order`` - cards sorting order used if the user has not already sorted the records (via - the list view) -``class`` - adds HTML classes to the root HTML element of the Kanban view -``examples`` - if set to a key in the `KanbanExamplesRegistry`_, examples on column setups will be available in the grouped kanban view. `Here <https://github.com/odoo/odoo/blob/99821fdcf89aa66ac9561a972c6823135ebf65c0/addons/project/static/src/js/project_task_kanban_examples.js#L27>`_ is an example of how to define those setups. -``group_create`` - whether the "Add a new column" bar is visible or not. Default: true. -``group_delete`` - whether groups can be deleted via the context menu. Default: true. -``group_edit`` - whether groups can be edited via the context menu. Default: true. -``archivable`` - whether records belonging to a column can be archived / restored if an - ``active`` field is defined on the model. Default: true. -``quick_create`` - whether it should be possible to create records without switching to the - form view. By default, ``quick_create`` is enabled when the Kanban view is - grouped by many2one, selection, char or boolean fields, and disabled when not. -``quick_create_view`` - ``form`` view reference, specifying the view used for records quick creation. -``records_draggable`` - whether it should be possible to drag records when kanban is grouped. Default: true. - - Set to ``true`` to always enable it, and to ``false`` to always disable it. - -.. todo:: VFE missing information on on_create attribute of kanban views. - -Possible children of the view element are: - -``field`` - declares fields to use in kanban *logic*. If the field is simply displayed in - the kanban view, it does not need to be pre-declared. - - Possible attributes are: - - ``name`` (required) - the name of the field to fetch - -``progressbar`` - declares a progressbar element to put on top of kanban columns. - - Possible attributes are: - - ``field`` (required) - the name of the field whose values are used to subgroup column's records in - the progressbar - - ``colors`` (required) - JSON mapping the above field values to either "danger", "warning", "success" - or "muted" colors - - ``sum_field`` (optional) - the name of the field whose column's records' values will be summed and - displayed next to the progressbar (if omitted, displays the total number of - records) - -``templates`` - defines a list of :ref:`reference/qweb` templates. Cards definition may be - split into multiple templates for clarity, but kanban views *must* define at - least one root template ``kanban-box``, which will be rendered once for each - record. - - The kanban view uses mostly-standard :ref:`javascript qweb - <reference/qweb/javascript>` and provides the following context variables: - - ``widget`` - the current :js:class:`KanbanRecord`, can be used to fetch some - meta-information. These methods are also available directly in the - template context and don't need to be accessed via ``widget`` - ``record`` - an object with all the requested fields as its attributes. Each field has - two attributes ``value`` and ``raw_value``, the former is formatted - according to current user parameters, the latter is the direct value from - a :meth:`~odoo.models.Model.read` (except for date and datetime fields - that are `formatted according to user's locale - <https://github.com/odoo/odoo/blob/a678bd4e/addons/web_kanban/static/src/js/kanban_record.js#L102>`_) - ``context`` - the current context, coming from the action, and the one2many or many2many - field in the case of a Kanban view embedded in a Form view - ``user_context`` - self-explanatory - ``read_only_mode`` - self-explanatory - ``selection_mode`` - set to true when kanban view is opened in mobile environment from m2o/m2m field - for selecting records. - - .. note:: clicking on m2o/m2m field in mobile environment opens kanban view - - - .. rubric:: buttons and fields - - While most of the Kanban templates are standard :ref:`reference/qweb`, the - Kanban view processes ``field``, ``button`` and ``a`` elements specially: - - * by default fields are replaced by their formatted value, unless the - ``widget`` attribute is specified, in which case their rendering and - behavior depends on the corresponding widget. Possible values are (among - others): - - ``handle`` - for ``sequence`` (or ``integer``) fields by which records are - sorted, allows to drag&drop records to reorder them. - - .. todo:: list widgets? - - * buttons and links with a ``type`` attribute become perform Odoo-related - operations rather than their standard HTML function. Possible types are: - - ``action``, ``object`` - standard behavior for :ref:`Odoo buttons - <reference/views/list/button>`, most attributes relevant to standard - Odoo buttons can be used. - ``open`` - opens the card's record in the form view in read-only mode - ``edit`` - opens the card's record in the form view in editable mode - ``delete`` - deletes the card's record and removes the card - - .. todo:: - - * kanban-specific CSS - * kanban structures/widgets (vignette, details, ...) - -If you need to extend the Kanban view, see :js:class::`the JS API <KanbanRecord>`. - -Calendar -~~~~~~~~ - -Calendar views display records as events in a daily, weekly or monthly -calendar. Their root element is ``<calendar>``. Available attributes on the -calendar view are: - -``date_start`` (required) - name of the record's field holding the start date for the event -``date_stop`` - name of the record's field holding the end date for the event, if - ``date_stop`` is provided records become movable (via drag and drop) - directly in the calendar -``date_delay`` - alternative to ``date_stop``, provides the duration of the event instead of - its end date (unit: day) -``color`` - name of a record field to use for *color segmentation*. Records in the - same color segment are allocated the same highlight color in the calendar, - colors are allocated semi-randomly. - Displayed the display_name/avatar of the visible record in the sidebar -``form_view_id`` - view to open when the user create or edit an event. Note that if this attribute - is not set, the calendar view will fall back to the id of the form view in the - current action, if any. -``event_open_popup`` - If the option 'event_open_popup' is set to true, then the calendar view will - open events (or records) in a FormViewDialog. Otherwise, it will open events - in a new form view (with a do_action) -``quick_add`` - enables quick-event creation on click: only asks the user for a ``name`` - and tries to create a new event with just that and the clicked event - time. Falls back to a full form dialog if the quick creation fails -``all_day`` - name of a boolean field on the record indicating whether the corresponding - event is flagged as day-long (and duration is irrelevant) -``mode`` - Default display mode when loading the calendar. - Possible attributes are: ``day``, ``week``, ``month`` -``create``, ``delete`` - allows *dis*\ abling the corresponding action in the view by setting the - corresponding attribute to ``false`` -``<field>`` - declares fields to aggregate or to use in kanban *logic*. If the field is - simply displayed in the calendar cards. - -.. _reference/views/list: - -List ----- - -The root element of list views is ``<tree>``\ [#treehistory]_. The list view's -root can have the following attributes: - -``editable`` - by default, selecting a list view's row opens the corresponding - :ref:`form view <reference/views/form>`. The ``editable`` attributes makes - the list view itself editable in-place. - - Valid values are ``top`` and ``bottom``, making *new* records appear - respectively at the top or bottom of the list. - - The architecture for the inline :ref:`form view <reference/views/form>` is - derived from the list view. Most attributes valid on a :ref:`form view - <reference/views/form>`'s fields and buttons are thus accepted by list - views although they may not have any meaning if the list view is - non-editable - - .. note:: if the ``edit`` attribute is set to ``false``, the ``editable`` option will be ignored. - -``multi_edit`` - editable or not editable list can activate the multi-editing feature by defining - the `multi_edit=1` - -``default_order`` - overrides the ordering of the view, replacing the model's order (:attr:`~odoo.models.BaseModel._order` model attribute). - The value is a comma-separated list of fields, postfixed by ``desc`` to - sort in reverse order: - - .. code-block:: xml - - <tree default_order="sequence,name desc"> - -``decoration-{$name}`` - allow changing the style of a row's text based on the corresponding - record's attributes. - - ``{$name}`` can be ``bf`` (``font-weight: bold``), ``it`` - (``font-style: italic``), or any `bootstrap contextual color`_ (``danger``, - ``info``, ``muted``, ``primary``, ``success`` or ``warning``). -``create``, ``edit``, ``delete``, ``duplicate``, ``import``, ``export_xlsx`` - allows *dis*\ abling the corresponding action in the view by setting the - corresponding attribute to ``false`` -``limit`` - the default size of a page. It must be a positive integer -``groups_limit`` - when the list view is grouped, the default number of groups of a page. It - must be a position integer -``expand`` - when the list view is grouped, automatically open the first level of groups - if set to true (default: false) - -Possible children elements of the list view are: - -.. _reference/views/list/button: - -``button`` - displays a button in a list cell - - ``icon`` - icon to use to display the button - ``string`` - * if there is no ``icon``, the button's text - * if there is an ``icon``, ``alt`` text for the icon - ``type`` - type of button, indicates how it clicking it affects Odoo: - - ``object`` - call a method on the list's model. The button's ``name`` is the - method, which is called with the current row's record id and the - current context. - - .. web client also supports a @args, which allows providing - additional arguments as JSON. Should that be documented? Does - not seem to be used anywhere - - ``action`` - load an execute an ``ir.actions``, the button's ``name`` is the - database id of the action. The context is expanded with the list's - model (as ``active_model``), the current row's record - (``active_id``) and all the records currently loaded in the list - (``active_ids``, may be just a subset of the database records - matching the current search) - ``name`` - see ``type`` - ``args`` - see ``type`` - ``attrs`` - dynamic attributes based on record values. - - A mapping of attributes to domains, domains are evaluated in the - context of the current row's record, if ``True`` the corresponding - attribute is set on the cell. - - Possible attribute is ``invisible`` (hides the button). - ``states`` - shorthand for ``invisible`` ``attrs``: a list of states, comma separated, - requires that the model has a ``state`` field and that it is - used in the view. - - Makes the button ``invisible`` if the record is *not* in one of the - listed states - - .. danger:: - - Using ``states`` in combination with ``attrs`` may lead to - unexpected results as domains are combined with a logical AND. - ``context`` - merged into the view's context when performing the button's Odoo call - - .. todo:: declared but unused: help - -``field`` - defines a column where the corresponding field should be displayed for - each record. Can use the following attributes: - - ``name`` - the name of the field to display in the current model. A given name - can only be used once per view - ``string`` - the title of the field's column (by default, uses the ``string`` of - the model's field) - ``invisible`` - fetches and stores the field, but doesn't display the column in the - table. Necessary for fields which shouldn't be displayed but are - used by e.g. ``@colors`` - ``groups`` - lists the groups which should be able to see the field - ``widget`` - alternate representations for a field's display. Possible list view - values are (among others): - - ``progressbar`` - displays ``float`` fields as a progress bar. - ``handle`` - for ``sequence`` (or ``integer``) fields by which records are - sorted, instead of displaying the field's value just displays a - drag&drop icon to reorder records. - ``sum``, ``avg`` - displays the corresponding aggregate at the bottom of the column. The - aggregation is only computed on *currently displayed* records. The - aggregation operation must match the corresponding field's - ``group_operator`` - ``attrs`` - dynamic attributes based on record values. Only effects the current - field, so e.g. ``invisible`` will hide the field but leave the same - field of other records visible, it will not hide the column itself - ``width`` (for ``editable``) - when there is no data in the list, the width of a column can be forced - by setting this attribute. The value can be an absolute width (e.g. - '100px'), or a relative weight (e.g. '3', meaning that this column will - be 3 times larger than the others). Note that when there are records in - the list, we let the browser automatically adapt the column's widths - according to their content, and this attribute is thus ignored. - ``decoration-{$name}`` - allow changing the style of a cell's text based on the corresponding - record's attributes. - - ``{$name}`` can be ``bf`` (``font-weight: bold``), ``it`` - (``font-style: italic``), or any `bootstrap contextual color`_ (``danger``, - ``info``, ``muted``, ``primary``, ``success`` or ``warning``). - ``nolabel`` - if set to "1", the column header will remain empty. Also, the column - won't be sortable. - - .. note:: - - if the list view is ``editable``, any field attribute from the - :ref:`form view <reference/views/form>` is also valid and will - be used when setting up the inline form view. - - .. note:: - - In case of list sub-views (One2many/Many2many display in a form view), - The attribute ``column_invisible`` can be useful to hide a column - depending on the parent object. - - .. code-block:: xml - - <field name="product_is_late" attrs="{'column_invisible': [('parent.has_late_products', '=', False)]}"/> - - .. note:: When a list view is grouped, numeric fields are aggregated and - displayed for each group. Also, if there are too many records in - a group, a pager will appear on the right of the group row. For - this reason, it is not a good practice to have a numeric field in - the last column, when the list view is in a situation where it can - be grouped (it is however fine for x2manys field in a form view: - they cannot be grouped). - -``groupby`` - defines custom headers (with buttons) for the current view when grouping - records on many2one fields. It is also possible to add `field`, inside the - `groupby` which can be used for modifiers. These fields thus belong on the - many2one comodel. These extra fields will be fetched in batch. - - ``name`` - the name of a many2one field (on the current model). Custom header will be - displayed when grouping the view on this field name. - - .. code-block:: xml - - <groupby name="partner_id"> - <field name="name"/> <!-- name of partner_id --> - <button type="edit" name"edit" string="Edit/> - <button type="object" name="my_method" string="Button1" - attrs="{'invisible': [('name', '=', 'Georges')]}"/> - </groupby> - - A special button (`type="edit"`) can be defined to open the many2one form view. - -``control`` - defines custom controls for the current view. - - This makes sense if the parent ``tree`` view is inside a One2many field. - - Does not support any attribute, but can have children: - - ``create`` - adds a button to create a new element on the current list. - - .. note:: If any ``create`` is defined, it will overwrite the default - "add a line" button. - - The following attributes are supported: - - ``string`` (required) - The text displayed on the button. - - ``context`` - This context will be merged into the existing context - when retrieving the default value of the new record. - - For example it can be used to override default values. - - - The following example will override the default "add a line" button - by replacing it with 3 new buttons: - "Add a product", "Add a section" and "Add a note". - - "Add a product" will set the field 'display_type' to its default value. - - The two other buttons will set the field 'display_type' - to be respectively 'line_section' and 'line_note'. - - .. code-block:: xml - - <control> - <create - string="Add a product" - /> - <create - string="Add a section" - context="{'default_display_type': 'line_section'}" - /> - <create - string="Add a note" - context="{'default_display_type': 'line_note'}" - /> - </control> - -.. [#treehistory] for historical reasons, it has its origin in tree-type views - later repurposed to a more table/list-type display - -.. _reference/views/map: - -Map ---- - -.. raw:: html - - <span class="badge" style="background-color:#AD5E99">Enterprise feature</span> - -This view is able to display records on a map and the routes between them. The record are represented by pins. It also allows the visualization of fields from the model in a popup tied to the record's pin. - -.. note:: - - The model on which the view is applied should contains a res.partner many2one since the view relies on the res.partner's address and coordinates fields to localize the records. - -.. _reference/views/map/api: - -Api -~~~ - -The view uses location data platforms' api to fetch the tiles (the map's background), do the geoforwarding (converting addresses to a set of coordinates) and fetch the routes. -The view implements two api, the default one, openstreet map is able to fetch `tiles`_ and do `geoforwarding`_. This api does not require a token. -As soon as a valid `MapBox`_ token is provided in the general settings the view switches to the Mapbox api. This api is faster and allows the computation of routes. The token are available by `signing up`_ to MapBox - -.. _reference/views/structural components: - -Structural components -~~~~~~~~~~~~~~~~~~~~~ - -The view's root element is ``<map>`` multiple attributes are allowed - -``res_partner`` - Contains the res.partner many2one. If not provided the view will resort to create an empty map. -``default_order`` - If a field is provided the view will override the model's default order. The field must be part of the model on which the view is applied not from res.partner -``routing`` - if ``true`` the routes between the records will be shown. The view still needs a valid MapBox token and at least two located records. (i.e the records has a res.partner many2one and the partner has a address or valid coordinates) -``hide_name`` - if ``true`` hide a name from the marker's popup (default: false) -``hide_address`` - if ``true`` hide a address from the marker's popup (default: false) - -The ``<map>`` element can contain multiple ``<field>`` elements. Each ``<field>`` element will be interpreted as a line in the marker's popup. The field's attributes are the following: - -``name`` - The field to display. -``string`` - This string will be displayed before the field's content. It Can be used as a description. -``limit`` - The size of a page (default: 80). It must be a positive integer. - -No attribute or element is mandatory but as stated above if no res.partner many2one is provided the view won't be able to locate records. - -For example here is a map: - .. code-block:: xml - - <map res_partner="partner_id" default_order="date_begin" routing="true" hide_name="true"> - <field name="partner_id" string="Customer Name"/> - </map> - -.. _reference/views/pivot: - -Pivot ------ - -The pivot view is used to visualize aggregations as a `pivot table`_. Its root -element is ``<pivot>`` which can take the following attributes: - -``disable_linking`` - Set to ``True`` to remove table cell's links to list view. -``display_quantity`` - Set to ``true`` to display the Quantity column by default. -``default_order`` - The name of the measure and the order (asc or desc) to use as default order - in the view. - - .. code-block:: xml - - <pivot default_order="foo asc"> - <field name="foo" type="measure"/> - </pivot> - -The only allowed element within a pivot view is ``field`` which can have the -following attributes: - -``name`` (required) - the name of a field to use in the view. If used for grouping (rather - than aggregating) - -``string`` - the name that will be used to display the field in the pivot view, - overrides the default python String attribute of the field. - -``type`` - indicates whether the field should be used as a grouping criteria or as an - aggregated value within a group. Possible values are: - - ``row`` (default) - groups by the specified field, each group gets its own row. - ``col`` - creates column-wise groups - ``measure`` - field to aggregate within a group - ``interval`` - on date and datetime fields, groups by the specified interval (``day``, - ``week``, ``month``, ``quarter`` or ``year``) instead of grouping on the - specific datetime (fixed second resolution) or date (fixed day resolution). - -``invisible`` - if true, the field will not appear either in the active measures nor in the - selectable measures (useful for fields that do not make sense aggregated, - such as fields in different units, e.g. € and $). - -The measures are automatically generated from the model fields; only the -aggregatable fields are used. Those measures are also alphabetically -sorted on the string of the field. - -.. warning:: - - like the graph view, the pivot aggregates data on database content - which means that non-stored function fields can not be used in pivot views - - -In Pivot view a ``field`` can have a ``widget`` attribute to dictate its format. -The widget should be a field formatter, of which the most interesting are -``date``, ``datetime``, ``float_time``, and ``monetary``. - -For instance a timesheet pivot view could be defined as:: - - <pivot string="Timesheet"> - <field name="employee_id" type="row"/> - <field name="date" interval="month" type="col"/> - <field name="unit_amount" type="measure" widget="float_time"/> - </pivot> - -.. _reference/views/qweb: - -QWeb ----- - -QWeb views are standard :ref:`reference/qweb` templates inside a view's -``arch``. They don't have a specific root element. Because QWeb views don't -have a specific root element, their type must be specified explicitly (it can -not be inferred from the root element of the ``arch`` field). - -QWeb views have two use cases: - -* they can be used as frontend templates, in which case - :ref:`reference/data/template` should be used as a shortcut. -* they can be used as actual qweb views (opened inside an action), in which - case they should be defined as regular view with an explicit ``type`` (it - can not be inferred) and a model. - -The main additions of qweb-as-view to the basic qweb-as-template are: - -* qweb-as-view has a special case for a ``<nav>`` element bearing the CSS - class ``o_qweb_cp_buttons``: its contents should be buttons and will be - extracted and moved to the control panel's button area, the ``<nav>`` itself - will be removed, this is a work-around to control panel views not existing - yet -* qweb-as-view rendering adds several items to the standard qweb rendering - context: - - ``model`` - the model to which the qweb view is bound - ``domain`` - the domain provided by the search view - ``context`` - the context provided by the search view - ``records`` - a lazy proxy to ``model.search(domain)``, this can be used if you just - want to iterate the records and not perform more complex operations - (e.g. grouping) -* qweb-as-view also provides additional rendering hooks: - - - ``_qweb_prepare_context(view_id, domain)`` prepares the rendering context - specific to qweb-as-view - - ``qweb_render_view(view_id, domain)`` is the method called by the client - and will call the context-preparation methods and ultimately - ``env['ir.qweb'].render()``. - -.. _reference/views/search: - -Search ------- - -Search views are a break from previous view types in that they don't display -*content*: although they apply to a specific model, they are used to filter -other view's content (generally aggregated views -e.g. :ref:`reference/views/list` or :ref:`reference/views/graph`). Beyond that -difference in use case, they are defined the same way. - -The root element of search views is ``<search>``. It takes no attributes. - -.. @string is not displayed anywhere, should be removed - -Possible children elements of the search view are: - -``field`` - fields define domains or contexts with user-provided values. When search - domains are generated, field domains are composed with one another and - with filters using **AND**. - - Fields can have the following attributes: - - ``name`` - the name of the field to filter on - ``string`` - the field's label - ``operator`` - by default, fields generate domains of the form :samp:`[({name}, - {operator}, {provided_value})]` where ``name`` is the field's name and - ``provided_value`` is the value provided by the user, possibly - filtered or transformed (e.g. a user is expected to provide the - *label* of a selection field's value, not the value itself). - - The ``operator`` attribute allows overriding the default operator, - which depends on the field's type (e.g. ``=`` for float fields but - ``ilike`` for char fields) - ``filter_domain`` - complete domain to use as the field's search domain, can use a - ``self`` variable to inject the provided value in the custom - domain. Can be used to generate significantly more flexible domains - than ``operator`` alone (e.g. searches on multiple fields at once) - - If both ``operator`` and ``filter_domain`` are provided, - ``filter_domain`` takes precedence. - ``context`` - allows adding context keys, including the user-provided values (which - as for ``domain`` are available as a ``self`` variable, an array of - values e.g. ``[id_1, id_2]`` for a :class:`~odoo.fields.Many2one` field). - By default, fields don't generate domains. - - .. note:: the domain and context are inclusive and both are generated - if a ``context`` is specified. To only generate context - values, set ``filter_domain`` to an empty list: - ``filter_domain="[]"`` - ``groups`` - make the field only available to specific users - ``domain`` - if the field can provide an auto-completion - (e.g. :class:`~odoo.fields.Many2one`), filters the possible - completion results. - -``filter`` - a filter is a predefined toggle in the search view, it can only be enabled - or disabled. Its main purposes are to add data to the search context (the - context passed to the data view for searching/filtering), or to append new - sections to the search filter. - - Filters can have the following attributes: - - ``string`` (required) - the label of the filter - ``domain`` (optional) - an Odoo :ref:`domain <reference/orm/domains>`, will be appended to the - action's domain as part of the search domain. - ``date`` (optional) - the name of a field of type ``date`` or ``datetime``. - Using this attribute has the effect to create - a set of filters available in a submenu - of the filters menu. The filters proposed are time dependent - but not dynamic in the sense that their domains are evaluated - at the time of the control panel instantiation. - - Example: - - .. code-block:: xml - - <filter name="filter_create_date" date="create_date" string="Creation Date"/> - - The example above allows to easily search for records with creation date field - values in one of the periods below (if the current month is August 2019). - - .. code-block:: text - - Create Date > - August - July - June - Q4 - Q3 - Q2 - Q1 - -------------- - 2019 - 2018 - 2017 - - Muti selection of options is allowed. - - ``default_period`` (optional) - only makes sense for a filter with non empty ``date`` attribute. - determines which period is activated if the filter is in the - default set of filters activated at the view initialization. If not provided, - 'this_month' is used by default. - - To choose among the following options: - today, this_week, this_month, last_month, antepenultimate_month, - fourth_quarter, third_quarter, second_quarter, first_quarter, - this_year, last_year, antepenultimate_year. - - Example: - - .. code-block:: xml - - <filter name="filter_create_date" date="create_date" string="Creation Date" default_period="this_week"/> - - ``context`` - a Python dictionary, merged into the action's domain to generate the - search domain - - The key ``group_by`` can be used to define a groupby available in the - 'Group By' menu. The 'group_by' value can be a valid field name. - - .. code-block:: xml - - <filter name="groupby_category" string="Category" context = {'group_by': 'category_id'}/> - - The groupby defined above allows to group data by category. - - When the field is of type ``date`` or ``datetime``, the filter generates a submenu of the Group By - menu in which the following interval options are available: day, week, month, quarter, year. - - In case the filter is in the default set of filters activated at the view initialization, - the records are grouped by month by default. This can be changed by using the syntax - 'date_field:interval' as in the following example. - - Example: - - .. code-block:: xml - - <filter name="groupby_create_date" string="Creation Date" context = {'group_by': 'create_date:week'}/> - - .. note:: - The results of read_groups grouped on a field may be influenced by its group_expand attribute, - allowing to display empty groups when needed. For more information, please refer to - :class:`~odoo.fields.Field` attributes documentation. - - ``name`` - logical name for the filter, can be used to :ref:`enable it by default - <reference/views/search/defaults>`, can also be used as - :ref:`inheritance hook <reference/views/inheritance>` - ``help`` - a longer explanatory text for the filter, may be displayed as a - tooltip - ``groups`` - makes a filter only available to specific users - - .. tip:: - - .. versionadded:: 7.0 - - Sequences of filters (without non-filters separating them) are treated - as inclusively composited: they will be composed with ``OR`` rather - than the usual ``AND``, e.g. - - :: - - <filter domain="[('state', '=', 'draft')]"/> - <filter domain="[('state', '=', 'done')]"/> - - if both filters are selected, will select the records whose ``state`` - is ``draft`` or ``done``, but - - :: - - <filter domain="[('state', '=', 'draft')]"/> - <separator/> - <filter domain="[('delay', '<', 15)]"/> - - if both filters are selected, will select the records whose ``state`` - is ``draft`` **and** ``delay`` is below 15. - -``separator`` - can be used to separates groups of filters in simple search views - -``group`` - can be used to separate groups of filters, more readable than - ``separator`` in complex search views - -``searchpanel`` - allows to display a search panel on the left of any multi records view. - By default, the list and kanban views have the searchpanel enabled. - The search panel can be activated on other views with the attribute: - - * ``view_types`` a comma separated list of view types on which to enable the search panel - default: 'tree,kanban' - - This tool allows to quickly filter data on the basis of given fields. The fields - are specified as direct children of the ``searchpanel`` with tag name ``field``, - and the following attributes: - - * ``name`` (mandatory) the name of the field to filter on - - * ``select`` determines the behavior and display. Possible values are - - * ``one`` (default) at most one value can be selected. Supported field types are - many2one and selection. - - * ``multi`` several values can be selected (checkboxes). Supported field - types are many2one, many2many and selection. - - * ``groups``: restricts to specific users - - * ``string``: determines the label to display - - * ``icon``: specifies which icon is used - - * ``color``: determines the icon color - - * ``enable_counters``: default is false. If set to true the record counters will be computed and - displayed if non-zero. - - This feature has been implemented in case performances would be too bad. - - Another way to solve performance issues is to properly override the - ``search_panel_select_range`` and ``search_panel_select_multi_range`` methods. - - * ``expand``: default is false. If set to false categories or filters with 0 records will be hidden. - - * ``limit``: default is 200. Integer determining the maximal number of values to fetch for the field. - If the limit is reached, no values will be displayed in the search panel and an error message will - appear instead because we consider that is useless / bad performance-wise. All values will be - fetched if set to 0. - - Additional optional attributes are available according to the chosen case: - - For the ``one`` case: - - * ``hierarchize``: (only available for many2one fields) default is true. Handles the display style of categories : - - If set to true child categories will appear under their related parent. - If not, all categories will be displayed on the same level. - - For the ``multi`` case: - - * ``domain``: determines conditions that the comodel records have to satisfy. - - A domain might be used to express a dependency on another field (with select="one") - of the search panel. Consider - /!\ This attribute is incompatible with a select="one" with enabled counters; if a select="multi" - has a `domain` attribute, all select="one" will have their counters disabled. - - .. code-block:: xml - - <searchpanel> - <field name="department_id"/> - <field name="manager_id" select="multi" domain="[('department_id', '=', department_id)]"/> - <searchpanel/> - - In the above example, the range of values for manager_id (manager names) available at screen - will depend on the value currently selected for the field ``department_id``. - - * ``groupby``: field name of the comodel (only available for many2one and many2many fields). Values will be grouped by that field. - - -.. _reference/views/search/defaults: - -Search defaults -~~~~~~~~~~~~~~~ - -Search fields and filters can be configured through the action's ``context`` -using :samp:`search_default_{name}` keys. For fields, the value should be the -value to set in the field, for filters it's a boolean value or a number. For instance, -assuming ``foo`` is a field and ``bar`` is a filter an action context of: - -.. code-block:: python - - { - 'search_default_foo': 'acro', - 'search_default_bar': 1 - } - -will automatically enable the ``bar`` filter and search the ``foo`` field for -*acro*. - -A numeric value (between 1 and 99) can be used to describe the order of default groupbys. -For instance if ``foo`` and ``bar`` refer to two groupbys - -.. code-block:: python - - { - 'search_default_foo': 2, - 'search_default_bar': 1 - } - -has the effect to activate first ``bar`` then ``foo``. - -.. todo:: View Grid - -.. _bootstrap contextual color: https://getbootstrap.com/docs/3.3/components/#available-variations -.. _geoforwarding: https://nominatim.org/release-docs/develop/ -.. _tiles: https://wiki.openstreetmap.org/wiki/Tile_data_server -.. _MapBox: https://docs.mapbox.com/api/ -.. _signing up: https://account.mapbox.com/auth/signup/ -.. _accesskey: https://www.w3.org/TR/html5/editing.html#the-accesskey-attribute -.. _floats: https://developer.mozilla.org/en-US/docs/Web/CSS/float -.. _HTML: https://en.wikipedia.org/wiki/HTML -.. _kanban board: https://en.wikipedia.org/wiki/Kanban_board -.. _pivot table: https://en.wikipedia.org/wiki/Pivot_table -.. _XPath: https://en.wikipedia.org/wiki/XPath -.. _KanbanExamplesRegistry: https://github.com/odoo/odoo/blob/99821fdcf89aa66ac9561a972c6823135ebf65c0/addons/web/static/src/js/views/kanban/kanban_examples_registry.js |
