[MOV] tutorials/getting_started/*: promote advanced chapters to independent tutorials

task-2991663

X-original-commit: a8c78a80ba
Part-of: odoo/documentation#3660
This commit is contained in:
Antoine Vandevenne (anv)
2023-02-20 16:48:19 +00:00
parent 018d708b44
commit c3a66c28a0
31 changed files with 190 additions and 271 deletions

View File

@@ -84,8 +84,6 @@ Static web data
None of these elements are mandatory. Some modules may only add data files (e.g. country-specific
accounting configuration), while others may only add business objects. During this training, we will
create business objects, object views and data files.
:ref:`Web controllers <tutorials/getting_started/G_website>` and
:ref:`static web data <tutorials/getting_started/I_jswidget>` are advanced topics.
Module structure
----------------

View File

@@ -9,8 +9,8 @@ intended to store business data. In a business application such as Odoo, one of
to consider is who\ [#who]_ can access the data. Odoo provides a security mechanism to allow access
to the data for specific groups of users.
The topic of security is covered in more detail in :ref:`tutorials/getting_started/B_acl_irrules`.
This chapter aims to cover the minimum required for our new module.
The topic of security is covered in more detail in :doc:`../restrict_data_access`. This chapter aims
to cover the minimum required for our new module.
Data Files (CSV)
================
@@ -105,9 +105,7 @@ Here is an example for our previous ``test.model``:
- ``model_id/id`` refers to the model which the access right applies to. The standard way to refer
to the model is ``model_<model_name>``, where ``<model_name>`` is the ``_name`` of the model
with the ``.`` replaced by ``_``. Seems cumbersome? Indeed it is...
- ``group_id/id`` refers to the group which the access right applies to. We will cover the concept
of groups in the :ref:`advanced topic <tutorials/getting_started/N_security>` dedicated to the
security.
- ``group_id/id`` refers to the group which the access right applies to.
- ``perm_read,perm_write,perm_create,perm_unlink``: read, write, create and unlink permissions
.. exercise:: Add access rights.

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/A_i18n:
================================
Advanced A: Internationalization
================================
Hopefully, this topic will be written soon :-)

View File

@@ -1,526 +0,0 @@
.. _tutorials/getting_started/B_acl_irrules:
================================
Advanced B: ACL and Record Rules
================================
ACL stands for "Access Control List"
.. warning::
This tutorial assumes you have completed the :ref:`Core Training <tutorials/getting_started>`.
To follow the exercise, it is recommended that you fetch the branch
{BRANCH}-core from the `technical training solutions
<https://github.com/odoo/technical-training-solutions/tree/{BRANCH}-core>`_ repository. It
contains a version of the module created during the core training we can use as a starting
point.
So far we have mostly concerned ourselves with implementing useful features.
However in most business scenarios *security* quickly becomes a concern:
currently,
* Any employee (which is what ``group_user`` stands for) can create, read,
update or delete properties, property types, or property tags.
* If ``estate_account`` is installed then only agents allowed to interact
with invoicing can confirm sales as that's necessary to :ref:`create an
invoice <tutorials/getting_started/14_other_module/create>`.
However:
* We do not want third parties to be able to access properties directly.
* Not all our employees may be real-estate agents (e.g. administrative
personnel, property managers, ...), we don't want non-agents to see the
available properties.
* Real-estate agents don't need or get to decide what property types or tags are
*available*.
* Real-estate agents can have *exclusive* properties, we do not want one agent
to be able to manage another's exclusives.
* All real-estate agents should be able to confirm the sale of a property they
can manage, but we do not want them to be able to validate or mark as paid
any invoice in the system.
.. note::
We may actually be fine with some or most of these for a small business.
Because it's easier for users to disable unnecessary security rules than it
is to create them from nothing, it's better to err on the side of caution
and limit access: users can relax that access if necessary or convenient.
Groups
======
.. seealso::
The documentation related to this topic can be found in :ref:`the security
reference <reference/security>`.
:doc:`/contributing/development/coding_guidelines` document the format and
location of master data items.
.. admonition:: **Goal**
At the end of this section,
- We can make employees *real-estate agents* or *real-estate managers*.
- The ``admin`` user is a real-estate manager.
- We have a new *real-estate agent* employee with no access to invoicing
or administration.
It would not be practical to attach individual security rules to employees any
time we need a change so *groups* link security rules and users. They correspond
to roles that can be assigned to employees.
For most Odoo applications [#app]_ a good baseline is to have *user* and
*manager* (or administrator) roles: the manager can change the configuration of
the application and oversee the entirety of its use while the user can well,
use the application [#appuser]_.
This baseline seems sufficient for us:
* Real estate managers can configure the system (manage available types and
tags) as well as oversee every property in the pipeline.
* Real estate agents can manage the properties under their care, or properties
which are not specifically under the care of any agent.
In keeping with Odoo's data-driven nature, a group is no more than a record of
the ``res.groups`` model. They are normally part of a module's :ref:`master data
<tutorials/getting_started/C_data>`, defined in one of the module's data files.
As simple example `can be found here <https://github.com/odoo/odoo/blob/532c083cbbe0ee6e7a940e2bdc9c677bd56b62fa/addons/hr/security/hr_security.xml#L9-L14>`_.
.. exercise::
#. Create the ``security.xml`` file in the appropriate folder and add it to the ``__manifest__.py`` file.
#. If not already, add a ``'category'`` field to your ``__manifest__.py`` with value ``Real Estate/Brokerage``.
#. Add a record creating a group with the id ``estate_group_user``, the name "Agent"
and the category ``base.module_category_real_estate_brokerage``.
#. Below that, add a record creating a group with the id ``estate_group_manager``,
the name "Manager" and the category ``base.module_category_real_estate_brokerage``.
The ``estate_group_manager`` group needs to imply ``estate_group_user``.
.. note::
Where does that **category** comes from ? It's a *module category*.
Here we used the category id ``base.module_category_real_estate_brokerage``
which was automatically generated by Odoo based on the `category` set in the ``__manifest__.py`` of the module.
You can also find here the list of
`default module categories <https://github.com/odoo/odoo/blob/71da80deb044852a2af6b111d695f94aad7803ac/odoo/addons/base/data/ir_module_category_data.xml>`_
provided by Odoo.
.. tip::
Since we modified data files, remember to restart Odoo and update the
module using ``-u estate``.
If you go to :menuselection:`Settings --> Manage Users` and open the
``admin`` user ("Mitchell Admin"), you should see a new section:
.. figure:: B_acl_irrules/groups.png
Set the admin user to be a *Real Estate manager*.
.. exercise::
Via the web interface, create a new user with only the "real estate agent"
access. The user should not have any Invoicing or Administration access.
Use a private tab or window to log in with the new user (remember to set
a password), as the real-estate agent you should only see the real estate
application, and possibly the Discuss (chat) application:
.. figure:: B_acl_irrules/agent.png
Access Rights
=============
.. seealso:: The documentation related to this topic can be found at
:ref:`reference/security/acl`.
.. admonition:: **Goal**
At the end of this section,
- Employees who are not at least real-estate agents will not see the
real-estate application.
- Real-estate agents will not be able to update the property types or tags.
Access rights were first introduced in :ref:`tutorials/getting_started/05_securityintro`.
Access rights are a way to give users access to models *via* groups: associate
an access right to a group, then all users with that group will have the access.
For instance we don't want real-estate agents to be able to modify what property
types are available, so we would not link that access to the "user" group.
Access rights can only give access, they can't remove it: when access is
checked, the system looks to see if *any* access right associated with the user
(via any group) grants that access.
====== ====== ==== ====== ======
group create read update delete
------ ------ ---- ------ ------
A X X
B X
C X
====== ====== ==== ====== ======
A user with the groups A and C will be able to do anything but delete the object
while one with B and C will be able to read and update it, but not create or delete it.
.. note::
* The group of an access right can be omitted, this means the ACL applies
to *every user*, this is a useful but risky fallback as depending on the
applications installed it can grant even non-users access to the model.
* If no access right applies to a user, they are not granted access
(default-deny).
* If a menu item points to a model to which a user doesn't have access and
has no submenus which the user can see, the menu will not be displayed.
.. exercise:: Update the access rights file to:
* Give full access to all objects to your Real Estate Manager group.
* Give agents (real estate users) only read access to types and tags.
* Give nobody the right to delete properties.
* Check that your agent user is not able to alter types or tags, or to
delete properties, but that they can otherwise create or update
properties.
.. warning::
Remember to give different xids to your ``ir.model.access`` records
otherwise they will overwrite one another.
Since the "demo" user was not made a real-estate agent or manager, they should
not even be able to see the real-estate application. Use a private tab or window
to check for this (the "demo" user has the password "demo").
Record Rules
============
.. seealso:: The documentation related to this topic can be found at
:ref:`reference/security/rules`.
.. admonition:: **Goal**
At the end of this section, agents will not be able to see the properties
exclusive to their colleagues; but managers will still be able to see
everything.
Access rights can grant access to an entire model but often we need to be
more specific: while an agent can interact with properties in general we may not
want them to update or even see properties managed by one of their colleagues.
Record *rules* provide that precision: they can grant or reject access to
individual records:
.. code-block:: xml
<record id="rule_id" model="ir.rule">
<field name="name">A description of the rule's role</field>
<field name="model_id" ref="model_to_manage"/>
<field name="perm_read" eval="False"/>
<field name="groups" eval="[Command.link(ref('base.group_user'))]"/>
<field name="domain_force">[
'|', ('user_id', '=', user.id),
('user_id', '=', False)
]</field>
</record>
The :ref:`reference/orm/domains` is how access is managed: if the record passes
then access is granted, otherwise access is rejected.
.. tip::
Because rules tends to be rather complex and not created in bulk, they're
usually created in XML rather than the CSV used for access rights.
The rule above:
* Only applies to the "create", "update" (write) and "delete" (unlink)
operations: here we want every employee to be able to see other users' records
but only the author / assignee can update a record.
* Is :ref:`non-global <reference/security/rules/global>` so we can provide an
additional rule for e.g. managers.
* Allows the operation if the current user (``user.id``) is set (e.g. created,
or is assigned) on the record, or if the record has no associated user at all.
.. note::
If no rule is defined or applies to a model and operation, then the
operation is allowed (*default-allow*), this can have odd effects
if access rights are not set up correctly (are too permissive).
.. exercise::
Define a rule which limits agents to only being able to see or modify
properties which have no salesperson, or for which they are the salesperson.
You may want to create a second real-estate agent user, or create a few
properties for which the salesperson is a manager or some other user.
Verify that your real estate manager(s) can still see all properties. If
not, why not? Remember:
The ``estate_group_manager`` group needs to imply ``estate_group_user``.
Security Override
=================
Bypassing Security
------------------
.. admonition:: **Goal**
At the end of this section, agents should be able to confirm property sales
without needing invoicing access.
If you try to mark a property as "sold" as the real estate agent, you should get
an access error:
.. figure:: B_acl_irrules/error.png
This happens because ``estate_account`` tries to create an invoice during the
process, but creating an invoice requires the right to all invoice management.
We want agents to be able to confirm a sale without them having full invoicing
access, which means we need to *bypass* the normal security checks of Odoo in
order to create an invoice *despite* the current user not having the right to
do so.
There are two main ways to bypass existing security checks in Odoo, either
wilfully or as a side-effect:
* The ``sudo()`` method will create a new recordset in "sudo mode", this ignores
all access rights and record rules (although hard-coded group and user checks
may still apply).
* Performing raw SQL queries will bypass access rights and record rules as a
side-effect of bypassing the ORM itself.
.. exercise::
Update ``estate_account`` to bypass access rights and rules when creating
the invoice.
.. danger::
These features should generally be avoided, and only used with extreme care,
after having checked that the current user and operation should be able to
bypass normal access rights validation.
Operations performed in such modes should also rely on user input as little
as possible, and should validate it to the maximum extent they can.
Programmatically checking security
----------------------------------
.. admonition:: **Goal**
At the end of this section, the creation of the invoice should be resilient
to security issues regardless to changes to ``estate``.
In Odoo, access rights and record rules are only checked *when performing data
access via the ORM* e.g. creating, reading, searching, writing, or unlinking a
record via ORM methods. Other methods do *not* necessarily check against any
sort of access rights.
In the previous section, we bypassed the record rules when creating the invoice
in ``action_sold``. This bypass can be reached by any user without any access
right being checked:
- Add a print to ``action_sold`` in ``estate_account`` before the creation of
the invoice (as creating the invoice accesses the property, therefore triggers
an ACL check) e.g.::
print(" reached ".center(100, '='))
You should see ``reached`` in your Odoo log, followed by an access error.
.. danger:: Just because you're already in Python code does not mean any access
right or rule has or will be checked.
*Currently* the accesses are implicitly checked by accessing data on ``self`` as
well as calling ``super()`` (which does the same and *updates* ``self``),
triggering access errors and cancelling the transaction "uncreating" our
invoice.
*However* if this changes in the future, or we add side-effects to the method
(e.g. reporting the sale to a government agency), or bugs are introduced in
``estate``, ... it would be possible for non-agents to trigger operations they
should not have access to.
Therefore when performing non-CRUD operations, or legitimately bypassing the
ORM or security, or when triggering other side-effects, it is extremely
important to perform *explicit security checks*.
Explicit security checks can be performed by:
* Checking who the current user is (``self.env.user``) and match them against
specific models or records.
* Checking that the current user has specific groups hard-coded to allow or deny
an operation (``self.env.user.has_group``).
* Calling the ``check_access_rights(operation)`` method on a recordset, this
verifies whether the current user has access to the model itself.
* Calling ``check_access_rule(operations)`` on a non-empty recordset, this
verifies that the current user is allowed to perform the operation on *every*
record of the set.
.. warning:: Checking access rights and checking record rules are separate
operations, if you're checking record rules you usually want to
also check access rights beforehand.
.. exercise::
Before creating the invoice, use ``check_access_rights`` and
``check_access_rule`` to ensure that the current user can update properties
in general as well as the specific property the invoice is for.
Re-run the bypass script, check that the error occurs before the print.
.. _tutorials/getting_started/B_acl_irrules/multicompany:
Multi-company security
======================
.. seealso::
:ref:`reference/howtos/company` for an overview of multi-company facilities
in general, and :ref:`multi-company security rules <howto/company/security>`
in particular.
Documentation on rules in general can, again, be found at
:ref:`reference/security/rules`.
.. admonition:: **Goal**
At the end of this section, agents should only have access to properties
of their agency (or agencies).
For one reason or another we might need to manage our real-estate business
as multiple companies e.g. we might have largely autonomous agencies, a
franchise setup, or multiple brands (possibly from having acquired other
real-estate businesses) which remain legally or financially separate from one
another.
Odoo can be used to manage multiple companies inside the same system, however
the actual handling is up to individual modules: Odoo itself provides the tools
to manage the issue of company-dependent fields and *multi-company rules*,
which is what we're going to concern ourselves with.
We want different agencies to be "siloed" from one another, with properties
belonging to a given agency and users (whether agents or managers) only able to
see properties linked to their agency.
As before, because this is based on non-trivial records it's easier for a user
to relax rules than to tighten them so it makes sense to default to a
relatively stronger security model.
Multi-company rules are simply record rules based on the ``company_ids`` or
``company_id`` fields:
* ``company_ids`` is all the companies to which the current user has access
* ``company_id`` is the currently active company (the one the user is currently
working in / for).
Multi-company rules will *usually* use the former i.e. check if the record is
associated with *one* of the companies the user has access to:
.. code-block:: xml
<record model="ir.rule" id="hr_appraisal_plan_comp_rule">
<field name="name">Appraisal Plan multi-company</field>
<field name="model_id" ref="model_hr_appraisal_plan"/>
<field name="domain_force">[
'|', ('company_id', '=', False),
('company_id', 'in', company_ids)
]</field>
</record>
.. danger::
Multi-company rules are usually :ref:`global <reference/security/rules/global>`,
otherwise there is a high risk that additional rules would allow bypassing
the multi-company rules.
.. exercise::
* Add a ``company_id`` field to ``estate.property``, it should be required
(we don't want agency-less properties), and should default to the current
user's current company.
* Create a new company, with a new estate agent in that company.
* The manager should be a member of both companies.
* The old agent should only be a member of the old company.
* Create a few properties in each company (either use the company selector
as the manager or use the agents). Unset the default salesman to avoid
triggering *that* rule.
* All agents can see all companies, which is not desirable, add the record
rule restricting this behaviour.
.. warning:: remember to ``--update`` your module when you change its model or
data
Visibility != security
======================
.. admonition:: **Goal**
At the end of this section, real-estate agents should not see the Settings
menu of the real-estate application, but should still be able to set the
property type or tags.
Specific Odoo models can be associated directly with groups (or companies, or
users). It is important to figure out whether this association is a *security*
or a *visibility* feature before using it:
* *Visibility* features mean a user can still access the model or record
otherwise, either through another part of the interface or by :doc:`performing
operations remotely using RPC <../../api/external_api>`, things might just not be
visible in the web interface in some contexts.
* *Security* features mean a user can not access records, fields or operations.
Here are some examples:
* Groups on *model fields* (in Python) are a security feature, users outside the
group will not be able to retrieve the field, or even know it exists.
Example: in server actions, `only system users can see or update Python code
<https://github.com/odoo/odoo/blob/7058e338a980268df1c502b8b2860bdd8be9f727/odoo/addons/base/models/ir_actions.py#L414-L417>`_.
* Groups on *view elements* (in XML) are a visibility feature, users outside the
group will not be able to see the element or its content in the form but they
will otherwise be able to interact with the object (including that field).
Example: `only managers have an immediate filter to see their teams' leaves
<https://github.com/odoo/odoo/blob/8e19904bcaff8300803a7b596c02ec45fcf36ae6/addons/hr_holidays/report/hr_leave_reports.xml#L16>`_.
* Groups on menus and actions are visibility features, the menu or action will
not be shown in the interface but that doesn't prevent directly interacting
with the underlying object.
Example: `only system administrators can see the elearning settings menu
<https://github.com/odoo/odoo/blob/ff828a3e0c5386dc54e6a46fd71de9272ef3b691/addons/website_slides/views/website_slides_menu_views.xml#L64-L69>`_.
.. exercise::
Real Estate agents can not add property types or tags, but can see their
options from the Property form view when creating it.
The Settings menu just adds noise to their interface, make it only
visible to managers.
Despite not having access to the Property Types and Property Tags menus anymore,
agents can still access the underlying objects since they can still select
tags or a type to set on their properties.
.. [#app] An Odoo Application is a group of related modules covering a business
area or field, usually composed of a base module and a number of
expansions on that base to add optional or specific features, or link
to other business areas.
.. [#appuser] For applications which would be used by most or every employees,
the "application user" role might be done away with and its
abilities granted to all employees directly e.g. generally all
employees can submit expenses or take time off.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -1,421 +0,0 @@
.. _tutorials/getting_started/C_data:
================================
Advanced C: Master and Demo Data
================================
.. tip:: This tutorial assumes you followed the Core Training.
To do the exercise, fetch the branch {BRANCH}-core from the `technical training solutions
<https://github.com/odoo/technical-training-solutions/tree/{BRANCH}-core>`_ repository. It
contains a basic module we will use as a starting point
Data Types
==========
Master Data
-----------
Master data is usually part of the technical or business requirements for the module. In other
words, such data is often necessary for the module to work properly. This data will always be
installed when installing the module.
We already met technical data previously since we have defined
:ref:`security rules <tutorials/getting_started/N_security>`, :ref:`views<reference/views>` and
:ref:`actions<reference/actions>`. Those are one kind of master data.
On top of technical data, business data can be defined, e.g. countries, currencies, units of measure,
as well as complete country localization (legal reports, tax definitions, chart of account), and much
more...
Demo Data
---------
In additional to master data, which are requirements for a module to work properly, we also like
having data for demonstration purposes:
* Help the sales representatives make their demos quickly.
* Have a set of working data for developers to test new features and see how these new features look
with data they might not have added themselves.
* Test that the data is loaded correctly, without raising an error.
* Setup most of the features to be used quickly when creating a new database.
Demo data is automatically loaded when you start the server if you don't explicitly say you don't
want it. This can be done in the database manager or with the command line.
.. code-block:: console
$ ./odoo-bin -h
Usage: odoo-bin [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
Common options:
[...]
--without-demo=WITHOUT_DEMO
disable loading demo data for modules to be installed
(comma-separated, use "all" for all modules). Requires
-d and -i. Default is none
[...]
$ ./odoo-bin --addons-path=... -d db -i account --without-demo=all
Data Declaration
================
Manifest
--------
**Reference**: the documentation related to this topic can be found in
:ref:`Module Manifests<reference/module/manifest>`.
Data is declared either in CSV or in XML.
Each file containing data must be added in the manifest for them to be loaded.
The keys to use in the manifest to add new data are ``data`` for the master data and ``demo`` for
the demo data. Both values should be a list of strings representing the relative paths to the files
declaring the data.
Usually, demo data is in a ``demo`` folder, views and actions are in a ``views``
folder, security related data is in a ``security`` folder, and other data is in a
``data`` folder.
If your work tree looks like this:
.. code-block:: bash
estate
├── data
│ └── master_data.xml
├── demo
│ └── demo_data.xml
├── models
│ ├── *.py
│ └── __init__.py
├── security
│ └── ir.model.access.csv
├── views
│ └── estate_property_offer_views.xml
├── __init__.py
└── __manifest__.py
Your manifest should look like this:
.. code-block:: python
# -*- coding: utf-8 -*-
{
"name": "Real Estate",
"depends": [
...
],
"data": [
"security/ir.model.access.csv", # CSV and XML files are loaded at the same place
"views/estate_property_offer_views.xml", # Views are data too
"data/master_data.xml", # Split the data in multiple files depending on the model
],
"demo": [
"demo/demo_data.xml",
]
"application": True,
}
CSV
---
**Reference**: the documentation related to this topic can be found in
:ref:`CSV data files<reference/data/csvdatafiles>`.
The easiest way to declare simple data is by using the CSV format. This is however limited in terms
of features: use it for long lists of simple models, but prefer XML otherwise.
.. code-block:: text
id,field_a,field_b,related_id:id
id1,valueA1,valueB1,module.relatedid
id2,valueA2,valueB2,module.relatedid
.. tip:: Your IDE has probably an extension to have a syntax highlighting of the CSV files
* `Atom <https://atom.io/packages/rainbow-csv>`__.
* `PyCharm/IntelliJ <https://plugins.jetbrains.com/plugin/10037-csv-plugin>`__.
* `Vim <https://github.com/mechatroner/rainbow_csv>`__.
* `Visual Studio <https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv>`__.
.. exercise:: Add some standard Real Estate Property Types for the `estate` module: Residential,
Commercial, Industrial and Land. These should always be installed.
XML
---
**Reference**: the documentation related to this topic can be found in
:ref:`Data Files<reference/data>`.
When the data to create is more complex it can be useful, or even necessary, to do it in XML.
.. code-block:: xml
<odoo>
<record id="id1" model="tutorial.example">
<field name="field_a">valueA1</field>
<field name="field_b">valueB1</field>
</record>
<record id="id2" model="tutorial.example">
<field name="field_a">valueA2</field>
<field name="field_b">valueB2</field>
</record>
</odoo>
.. exercise:: Create some demo data for the `estate` module.
================== ==================== ======================
Field Values Values
================== ==================== ======================
name Big Villa Trailer home
state New Canceled
description A nice and big villa Home in a trailer park
postcode 12345 54321
date_availability 2020-02-02 1970-01-01
expected_price 1,600,000 100,000
selling_price 120,000
bedrooms 6 1
living_area 100 10
facades 4 4
garage True False
garden True
garden_area 100000
garden_orientation South
================== ==================== ======================
Data Extension
~~~~~~~~~~~~~~
During the Core Training, we saw in the :ref:`tutorials/getting_started/13_inheritance` chapter we
could inherit (extend) an existing view. This was a special case of data extension: any data can be
extended in a module.
When you are adding new fields to an existing model in a new module, you might want to populate
those fields on the records created in the modules you are depending on. This is done by giving the
`xml_id` of the record you want to extend. It won't replace it, in this case we will set the
``field_c`` to the given value for both records.
.. code-block:: xml
<odoo>
<record id="id1" model="tutorial.example">
<field name="field_c">valueC1</field>
</record>
<record id="id2" model="tutorial.example">
<field name="field_c">valueC2</field>
</record>
</odoo>
``ref``
~~~~~~~
Related fields can be set using the ``ref`` key. The value of that key is the ``xml_id`` of the
record you want to link. Remember the ``xml_id`` is composed of the name of the module where the
data is first declared, followed by a dot, followed by the ``id`` of the record (just the ``id``
works too if you are in the module declaring it).
.. code-block:: xml
<odoo>
<record id="id1" model="tutorial.example">
<field name="related_id" ref="module.relatedid"/>
</record>
</odoo>
.. exercise:: Create some demo data offers for the properties you created.
Create offers using the partners defined in ``base``
============== ========= ======= ========
Partner Estate Price Validity
============== ========= ======= ========
Azure Interior Big Villa 10000 14
Azure Interior Big Villa 1500000 14
Deco Addict Big Villa 1500001 14
============== ========= ======= ========
.. exercise:: Ensure both of your demo properties are created with their Property Type set to Residential.
``eval``
~~~~~~~~
The value to assign to a field is not always a simple string and you might need to compute it.
It can also be used to optimize the insertion of related values, or because a constraint forces you
to add the related values in batch. See ::ref:`Add X2many fields
<tutorials/getting_started/C_data/x2m>`.
.. code-block:: xml
<odoo>
<record id="id1" model="tutorial.example">
<field name="year" eval="datetime.now().year+1"/>
</record>
</odoo>
.. exercise:: The offers you added should always be in a date relative to the installation of the
module.
``search``
~~~~~~~~~~
Sometimes, you need to call the ORM to do a ``search``. This is not feasible with the CSV format.
.. code-block:: xml
<odoo>
<record id="id1" model="account.move.line">
<field name="account_id" search="[
('user_type_id', '=', ref('account.data_account_type_direct_costs')),
('company_id', '=', obj().env.company.id)]
"/>
</record>
</odoo>
In this code snippet, it is needed because the master data depends on the localization
installed.
``function``
~~~~~~~~~~~~
You might also need to execute python code when loading data.
.. code-block:: xml
<function model="tutorial.example" name="action_validate">
<value eval="[ref('demo_invoice_1')]"/>
</function>
.. exercise:: Validate one of the demo data offers by using the "Accept Offer" button. Refuse the
others.
.. _tutorials/getting_started/C_data/x2m:
Add X2many fields
-----------------
**Reference**: the documentation related to this topic can be found in
:class:`~odoo.fields.Command`.
If you need to add related data in a One2many or a Many2many field, you can do so by using the
:class:`~odoo.fields.Command` methods.
.. code-block:: xml
<odoo>
<record id="id1" model="tutorial.example">
<field name="related_ids" eval="[
Command.create({
'name': 'My name',
}),
Command.create({
'name': 'Your name',
}),
Command.link(ref('model.xml_id')),
]"/>
</record>
</odoo>
.. exercise:: Create one new Property, but this time with some offers created directly inside the
One2many field linked to the Offers.
Accessing the data
==================
.. warning:: You should never access demo data outside of the demo data declaration, not even in
tests.
There are multiple ways to access the master/demo data.
In python code, you can use the ``env.ref(self, xml_id, raise_if_not_found=True)`` method. It
returns the recordset linked to the ``xml_id`` you specify.
In XML, you can use the `ref` key like this
.. code-block:: xml
<odoo>
<record id="id1" model="tutorial.example">
<field name="related_id" ref="module.relatedid"/>
</record>
</odoo>
It will call the ref method, and store the id of the record returned on the field ``related_id`` of
the record of type ``tutorial.example`` with id ``id1``.
In CSV, the title of the column must be suffixed with ``:id`` or ``/id``.
.. code-block:: text
id,parent_id:id,name
"child1","module.parent","Name1"
"child2","module.parent","Name2"
"child3","module.parent","Name3"
In SQL, it is more complicated, see :ref:`the advanced section
<tutorials/getting_started/C_data/xml_id>`.
.. warning:: Data can always be deleted by the user. Always code defensively, taking this into
account.
Advanced
========
.. _tutorials/getting_started/C_data/xml_id:
What is the XML id?
-------------------
Because we don't want a column ``xml_id`` in every single SQL table of the database, we need a
mechanism to store it. This is done with the ``ir.model.data`` model.
It contains the name of the record (the ``xml_id``) along with the module in which it is defined,
the model defining it, and the id of it.
No update
---------
The records created with the ``noupdate`` flag won't be updated when upgrading the module that
created them, but it will be created if it didn't exist yet.
.. note:: ``odoo-bin -i module`` will bypass this setting and always load the data. But normally
one shouldn't do this on a production database.
.. code-block:: xml
<odoo noupdate="1">
<record id="id1" model="model">
<field name="fieldA" eval="True"/>
</record>
</odoo>
Import as SQL
-------------
In some cases, it makes sense to do the import directly in SQL. This is however discouraged as it
bypasses all the features of the ORM, computed fields (including metadata) and python constraints.
.. note:: Generally using raw SQL also bypasses ACLs and increases the risks of injections.
**Reference**: :ref:`Security in Odoo<reference/security>`
* It can help to speed the import time by a lot
`with huge files <https://github.com/odoo/enterprise/blob/d46cceef8c594b9056d0115edb7169e207a5986f/product_unspsc/hooks.py#L19>`__.
* For more complex imports like for the
`translations <https://github.com/odoo/odoo/blob/e1f8d549895cd9c459e6350430f30d541d02838a/odoo/addons/base/models/ir_translation.py#L24>`__.
* It can be necessary to
`initialize the database <https://github.com/odoo/odoo/blob/e1f8d549895cd9c459e6350430f30d541d02838a/odoo/addons/base/data/base_data.sql>`__.

View File

@@ -1,14 +0,0 @@
.. _tutorials/getting_started/D_mixins:
==================
Advanced D: Mixins
==================
If you need to interface with common Odoo features such as the chatter, you can rely on
:doc:`mixins <../../reference/backend/mixins>`.
They are Odoo models exposing useful methods through inheritance.
To learn and play with mixins, visit `this repository <https://github.com/tivisse/odoodays-2018/>`_.
This module for a plant nursery is training material developed for the OXP 2018. You don't need to
code it on your side. But you can check the presentations in the :file:`/static/pdf` directory and
play with the module to discover some magic features in Odoo.

View File

@@ -1,301 +0,0 @@
.. _tutorials/getting_started/E_unittest:
=============================
Advanced E: Python Unit Tests
=============================
.. tip:: This tutorial assumes you followed the Core Training.
To do the exercise, fetch the branch {BRANCH}-core from the `technical training solutions
<https://github.com/odoo/technical-training-solutions/tree/{BRANCH}-core>`_ repository. It
contains a basic module we will use as a starting point
**Reference**:
`Odoo's Test Framework: Learn Best Practices <https://www.youtube.com/watch?v=JEIscps0OOQ>`__
(Odoo Experience 2020) on YouTube.
Writing tests is a necessity for multiple reasons. Here is a non-exhaustive list:
* Ensure code will not be broken in the future
* Define the scope of your code
* Give examples of use cases
* It is one way to technically document the code
* Help your coding by defining your goal before working towards it
Running Tests
=============
Before knowing how to write tests, we need to know how to run them.
.. code-block:: console
$ odoo-bin -h
Usage: odoo-bin [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
[...]
Testing Configuration:
--test-file=TEST_FILE
Launch a python test file.
--test-enable Enable unit tests.
--test-tags=TEST_TAGS
Comma-separated list of specs to filter which tests to
execute. Enable unit tests if set. A filter spec has
the format: [-][tag][/module][:class][.method] The '-'
specifies if we want to include or exclude tests
matching this spec. The tag will match tags added on a
class with a @tagged decorator (all Test classes have
'standard' and 'at_install' tags until explicitly
removed, see the decorator documentation). '*' will
match all tags. If tag is omitted on include mode, its
value is 'standard'. If tag is omitted on exclude
mode, its value is '*'. The module, class, and method
will respectively match the module name, test class
name and test method name. Example: --test-tags
:TestClass.test_func,/test_module,external Filtering
and executing the tests happens twice: right after
each module installation/update and at the end of the
modules loading. At each stage tests are filtered by
--test-tags specs and additionally by dynamic specs
'at_install' and 'post_install' correspondingly.
--screencasts=DIR Screencasts will go in DIR/{db_name}/screencasts.
--screenshots=DIR Screenshots will go in DIR/{db_name}/screenshots.
Defaults to /tmp/odoo_tests.
$ # run all the tests of account, and modules installed by account
$ # the dependencies already installed are not tested
$ # this takes some time because you need to install the modules, but at_install
$ # and post_install are respected
$ odoo-bin -i account --test-enable
$ # run all the tests in this file
$ odoo-bin --test-file=addons/account/tests/test_account_move_entry.py
$ # test tags can help you filter quite easily
$ odoo-bin --test-tags=/account:TestAccountMove.test_custom_currency_on_account_1
Integration Bots
================
.. note:: This section is only for Odoo employees and people that are contributing to
`github.com/odoo`. We highly recommend having your own CI otherwise.
When a test is written, it is important to make sure it always passes when modifications are
applied to the source code. To automate this task, we use a development practice called
Continuous Integration (CI). This is why we have some bots running all the tests at different
moments.
Whether you are working at Odoo or not, if you are trying to merge something inside `odoo/odoo`,
`odoo/enterprise`, `odoo/upgrade` or on odoo.sh, you will have to go through the CI. If you are
working on another project, you should think of adding your own CI.
Runbot
------
**Reference**: the documentation related to this topic can be found in
`Runbot FAQ <https://runbot.odoo.com/doc>`__.
Most of the tests are run on `Runbot <https://runbot.odoo.com>`__ every time a commit is pushed on
GitHub.
You can see the state of a commit/branch by filtering on the runbot dashboard.
A **bundle** is created for each branch. A bundle consists of a configuration and
batches.
A **batch** is a set of builds, depending on the parameters of the bundle.
A batch is green (i.e. passes the tests) if all the builds are green.
A **build** is when we launch a server. It can be divided in sub-builds. Usually there are builds
for the community version, the enterprise version (only if there is an enterprise branch but you
can force the build), and the migration of the branch.
A build is green if every sub-build is green.
A **sub-build** only does some parts of what a full build does. It is used to speed up the CI
process. Generally it is used to split the post install tests in 4 parallel instances.
A sub-build is green if all the tests are passing and there are no errors/warnings logged.
.. note::
* All tests are run regardless of the modifications done. Correcting a typo in an error message or
refactoring a whole module triggers the same tests. All modules will be installed as well. This means
something might not work even if the Runbot is green, i.e. your changes depend on a module that the
module the changes are in doesn't depend on.
* The localization modules (i.e. country-specific modules) are not installed on Runbot (except
the generic one). Some modules with external dependencies can also be excluded.
* There is a nightly build running additional tests: module operations, localization, single
module installs, multi-builds for nondeterministic bugs, etc.
These are not kept in the standard CI to shorten the time of execution.
You can also login to a build built by Runbot. There are 3 users usable: `admin`, `demo` and
`portal`. The password is the same as the login. This is useful to quickly test things on different
versions without having to build it locally. The full logs are also available; these are used for
monitoring.
Robodoo
-------
You will most likely have to gain a little bit more experience before having the rights to summon
robodoo, but here are a few remarks anyways.
Robodoo is the guy spamming the CI status as tags on your PRs, but he is also the guy that kindly
integrates your commits into the main repositories.
When the last batch is green, the reviewer can ask robodoo to merge your PR (it is more
a `rebase` than a `merge`). It will then go to the mergebot.
Mergebot
--------
`Mergebot <https://mergebot.odoo.com>`__ is the last testing phase before merging a PR.
It will take the commits in your branch not yet present on the target, stage it and rerun the tests
one more time, including the enterprise version even if you are only changing something in
community.
This step can fail with a `Staging failed` error message. This could be due to
* a nondeterministic bug that is already on the target. If you are an Odoo employee, you can check
those here: https://runbot.odoo.com/runbot/errors
* a nondeterministic bug that you introduced but wasn't detected in the CI before
* an incompatibility with another commit merged right before and what you are trying to merge
* an incompatibility with the enterprise repository if you only did changes in the community repo
Always check that the issue does not come from you before asking the merge bot to retry: rebase
your branch on the target and rerun the tests locally.
Modules
=======
Because Odoo is modular, the tests need to be also modular. This means tests are defined in
the module that adds the functionality you are adding in, and tests cannot depend on functionality
coming from modules your module doesn't depend on.
**Reference**: the documentation related to this topic can be found in
:ref:`Special Tags<reference/testing/tags>`.
.. code-block:: python
from odoo.tests.common import TransactionCase
from odoo.tests import tagged
# The CI will run these tests after all the modules are installed,
# not right after installing the one defining it.
@tagged('post_install', '-at_install') # add `post_install` and remove `at_install`
class PostInstallTestCase(TransactionCase):
def test_01(self):
...
@tagged('at_install') # this is the default
class AtInstallTestCase(TransactionCase):
def test_01(self):
...
If the behavior you want to test can be changed by the installation of another module, you need to
ensure that the tag `at_install` is set; otherwise, you can use the tag `post_install` to speed up
the CI and ensure it is not changed if it shouldn't.
Writing a test
==============
**Reference**: the documentation related to this topic can be found in
`Python unittest <https://docs.python.org/3/library/unittest.html>`__
and :ref:`Testing Odoo<reference/testing>`.
Here are a few things to take into consideration before writing a test
* The tests should be independent of the data currently in the database (including demo data)
* Tests should not impact the database by leaving/changing residual data. This is usually done by
the test framework by doing a rollback. Therefore, you must never call ``cr.commit`` in a test
(nor anywhere else in the business code).
* For a bug fix, the test should fail before applying the fix and pass after.
* Don't test something that is already tested elsewhere; you can trust the ORM. Most of the tests
in business modules should only test the business flows.
* You shouldn't need to flush data into the database.
.. note:: Remember that ``onchange`` only applies in the Form views, not by changing the attributes
in python. This also applies in the tests. If you want to emulate a Form view, you can use
``odoo.tests.common.Form``.
The tests should be in a ``tests`` folder at the root of your module. Each test file name
should start with `test_` and be imported in the ``__init__.py`` of the test folder. You shouldn't
import the test folder/module in the ``__init__.py`` of the module.
.. code-block:: bash
estate
├── models
│ ├── *.py
│ └── __init__.py
├── tests
│ ├── test_*.py
│ └── __init__.py
├── __init__.py
└── __manifest__.py
All the tests should extend ``odoo.tests.common.TransactionCase``. You usually define a
``setUpClass`` and the tests. After writing the `setUpClass`, you have an `env` available in the
class and can start interacting with the ORM.
These test classes are built on top of the ``unittest`` python module.
.. code-block:: python
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
from odoo.tests import tagged
# The CI will run these tests after all the modules are installed,
# not right after installing the one defining it.
@tagged('post_install', '-at_install')
class EstateTestCase(TransactionCase):
@classmethod
def setUpClass(cls):
# add env on cls and many other things
super(EstateTestCase, cls).setUpClass()
# create the data for each tests. By doing it in the setUpClass instead
# of in a setUp or in each test case, we reduce the testing time and
# the duplication of code.
cls.properties = cls.env['estate.property'].create([...])
def test_creation_area(self):
"""Test that the total_area is computed like it should."""
self.properties.living_area = 20
self.assertRecordValues(self.properties, [
{'name': ..., 'total_area': ...},
{'name': ..., 'total_area': ...},
])
def test_action_sell(self):
"""Test that everything behaves like it should when selling a property."""
self.properties.action_sold()
self.assertRecordValues(self.properties, [
{'name': ..., 'state': ...},
{'name': ..., 'state': ...},
])
with self.assertRaises(UserError):
self.properties.forbidden_action_on_sold_property()
.. note:: For better readability, split your tests into multiple files depending on the scope of the
tests. You can also have a Common class that most of the tests should inherit from; this common
class can define the whole setup for the module. For instance, in
`account <{GITHUB_PATH}/addons/account/tests/common.py>`__.
.. exercise:: Update the code so no one can:
- Create an offer for a sold property
- Sell a property with no accepted offers on it
and create tests for both of these cases. Additionally check that selling a property that can
be sold is correctly marked as sold after selling it.
.. exercise:: Someone keeps breaking the reset of Garden Area and Orientation when you uncheck the
Garden checkbox. Make sure it doesn't happen again.
.. tip:: Tip: remember the note about `Form` a little bit above.

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/F_jstour:
====================
Advanced F: JS Tours
====================
Hopefully, this topic will be written soon :-)

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/G_website:
=================================
Advanced G: Controllers & Website
=================================
Hopefully, this topic will be written soon :-)

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/H_adv_views:
==========================
Advanced H: Advanced Views
==========================
Hopefully, this topic will be written soon :-)

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/I_jswidget:
============================
Advanced I: Custom JS Widget
============================
Hopefully, this topic will be written soon :-)

View File

@@ -1,315 +0,0 @@
.. _tutorials/getting_started/J_reports:
=======================
Advanced J: PDF Reports
=======================
.. warning::
This tutorial assumes you have completed the :ref:`Core Training <tutorials/getting_started>`
and have installed :ref:`wkhtmltopdf <setup/install/source/prepare>`.
To follow the exercise, it is recommended that you fetch the branch {BRANCH}-core from the
`technical training solutions
<https://github.com/odoo/technical-training-solutions/tree/{BRANCH}-core>`_ repository. It
contains a version of the module created during the core training we can use as a starting
point.
We were previously :ref:`introduced to QWeb <tutorials/getting_started/15_qwebintro>`
in the Core Training where it was used to build a kanban view. Now we will expand on one of QWeb's
other main uses: creating PDF reports. A common business requirement is the ability to create documents
to send to customers and to use internally. These reports can be used to summarize and display
information in an organized template to support the business in different ways. Odoo
can additionally add our company's header and footer to our reports with minimal extra effort.
The documentation related to this topic can be found in :ref:`reference/qweb`,
:ref:`reference/reports/report`, and the :ref:`reference/actions/report`
section of the Actions reference.
File Structure
==============
The bulk of a PDF report is its QWeb template. It also typically needs a corresponding
``ir.actions.report`` to include the report within a module's business logic.
There is no strict rule for the file names or where they are located, but these two parts are
typically stored in 2 separate files within a ``report`` folder at the top level of your module's
directory. If a module has many or multiple long report templates, then they are often organized
logically across different files named after the report(s) they contain. All actions
for the reports are usually stored in the same file ending with ``_reports.xml``, regardless of the
number of reports it contains.
Therefore, it is expected that your work tree will look something like this:
.. code-block:: bash
estate
├── models
│ ├── *.py
│ └── __init__.py
├── report
│ ├── estate_property_templates.xml
│ └── estate_property_reports.xml
├── security
│ └── ir.model.access.csv
├── views
│ └── *.xml
├── __init__.py
└── __manifest__.py
Note that you will often see other non-QWeb and non-XML files containing "report" in their name also within
the report folder. These are unrelated to the reports covered in this tutorial and are covered in
:ref:`another advanced topic <tutorials/getting_started/K_dashboard>`. For now you can think of them
as customized views that use direct SQL queries (sometimes referred to as SQL Views).
Don't forget to add whatever files your template and action view will be into to your ``__manifest__.py``.
In this case, you will want to add the files to the ``data`` list and remember that the files listed in a manifest
are loaded sequentially!
Basic Report
============
.. note::
**Goal**: at the end of this section, we will can print a report that displays all offers for a
property.
.. image:: J_reports/simple_report.png
:align: center
:alt: Simple PDF report
In our real estate example there are many useful reports that we could create. One simple report we
can create is one that displays all of a property's offers.
Report Data
-----------
Before we do anything we first need some data to populate our reports or else this tutorial
won't be very interesting. When creating reports, you will need some data to test your report code
and check that the resulting look is as expected. It is a good idea to test with data that will cover most
or all of your expected use cases. A good representation set for our simple report is:
* At least 3 properties where 1 is "sold", 1 is "offer received" and 1 is "new".
* At least 2-3 offers for our "sold" and "offer received" properties
If you don't have a set of data like this already, you can either:
* Complete :ref:`tutorials/getting_started/C_data` (if you haven't done so already) and add the
extra cases to your demo data (you may need to create a new database to load in the demo data).
* Manually create the data in your database.
* Copy this `data file
<https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-J_reports/estate/data/estate_demo.xml>`_
into a new directory (data) in your estate module and copy `these lines
<https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-J_reports/estate/__manifest__.py#L21-L23>`_
into your __manifest__.py file (you may need to create a new database to load in the demo data).
Before continuing, click through your data in your database and make sure your data is as expected.
Of course you can add the data after you write your report code, but then you will not be able to
incrementally test portions of your code as you write it. This can make checking for mistakes and
debugging your code more difficult in the long run for complicated reports.
Minimal Template
----------------
A minimal viable template is viewable under the "Minimal viable template" section of the
:ref:`reference/reports/templates` documentation. We can modify this example to build
our minimal property offers template file:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="report_property_offers">
<t t-foreach="docs" t-as="property">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div class="page">
<h2>
<span t-field="property.name"/>
</h2>
<div>
<strong>Expected Price: </strong>
<span t-field="property.expected_price"/>
</div>
<table class="table">
<thead>
<tr>
<th>Price</th>
</tr>
</thead>
<tbody>
<t t-set="offers" t-value="property.mapped('offer_ids')"/>
<tr t-foreach="offers" t-as="offer">
<td>
<span t-field="offer.price"/>
</td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>
Most of the Odoo specific (i.e. non-HTML) items in our file are explained in the minimal viable template section.
Some additional features in our template are:
* The use of the ``class="table"`` attribute, so our table has some nice formatting. Twitter Bootstrap
(we're using its table class in this case), and Font Awesome (useful for adding icons) classes can
be used in your report template.
* The use of ``t-set``, ``t-value``, ``t-foreach``, and ``t-as`` so that we can loop over all the ``offer_ids``.
If you are already familiar with website templating engines, then the QWeb directives (i.e. the `t-` commands)
probably don't need much explanation and you can just look at its :ref:`documentation <reference/qweb>` and
skip ahead to the next subsection.
Otherwise you are encouraged to read more about them (
`Wikipedia <https://en.wikipedia.org/wiki/Template_processor>`__ has a good high level description), but
the general idea is that QWeb provides the ability to dynamically generate web code based on Odoo data and
simple commands. I.e. QWeb can access recordset data (and methods) and process simple programming operations
such as setting and accessing temporary variables. For example, in the above example:
* ``t-set`` creates a temporary variable called "offers" that has its value set by ``t-value`` to the current
``estate.property`` recordset's ``offer_ids``.
* The ``t-foreach`` and ``t-as`` usage is the equivalent to the Python:
.. code-block:: Python
for offer in offers:
Report Action
-------------
Now that we have a template, we need to make it accessible in our app via a ``ir.actions.report``.
A practical example of ``ir.actions.report`` is
`here <https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/event/report/event_event_reports.xml#L20-L30>`__
corresponding to
`this template <https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/event/report/event_event_templates.xml#L5>`__.
Its contents are all explained in :ref:`the documentation <reference/actions/report>`.
An ``ir.actions.report`` is primarily used via the Print menu of a model's view. In the practical
example, the ``binding_model_id`` specifies which model's views the report should show, and Odoo
will auto-magically add it for you. Another common use case of the report action is to link it to
a button as we learned in :ref:`tutorials/getting_started/10_actions`. This is handy for reports
that only make sense under specific conditions. For example, if we wanted to make a "Final Sale"
report, then we can link it to a "Print Sale Info" button that appears in the form view only when
the property is "Sold".
.. image:: J_reports/print_menu.png
:align: center
:alt: Print Menu Button
You may have noticed or are wondered why our report template loops through a recordset. When our
template is passed more than one record, it can produce one PDF report for all the records.
Using the Print menu in the list view with multiple records selected will demonstrate this.
Make a Report
-------------
Finally, you now know where to create your files and how the content of the files should look. Happy report making!
.. exercise:: Make a report.
- Add the property offers report from the minimal template subsection to the Print menu of the Property views.
- Improve the report by adding more data. Refer to the **Goal** of this section to see what additional
data you can add and feel free to add even more.
- Bonus: Make an extra flexible report by adding in some logic so that when there are no offers on a property
then we don't create a table and instead write something about how there are no offers yet. Hint: you will
need to use ``t-if`` and ``t-else``.
Remember to check that your PDF reports match your data as expected.
Sub-templates
=============
.. note::
**Goal**: at the end of this section, we will have a sub-template that we use in 2 reports.
.. image:: J_reports/report_subtemplate.png
:align: center
:alt: Report using a subtemplate
There are two main reasons for using sub-templates. One is to make the code easier to read when working with
extra-long or complicated templates. The other is to reuse code where possible. Our simple property offers
report is useful, but listing property offers information can be useful for more than just one report template.
One example is a report that lists all of a salesman's properties' offers.
See if you can understand how to call a sub-template by reading the
:ref:`documentation <reference/qweb/sub-templates>` on it and/or by looking at an
`example <https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/portal/static/src/xml/portal_chatter.xml#L147-L160>`__
(remember QWeb uses the same control flows regardless if it is for a report or a view in Odoo.)
.. exercise:: Create and use a sub-template.
- Split the table portion of the offers into its own template. Remember to check that your
original report still prints correctly afterwards.
- Add a new report for ``res.users`` that allows you to print all of the Real Estate Properties
that are visible in their form view (i.e. in the "Settings" app). Include the offers for each
of those saleman's properties in the same report. Hint: since the ``binding_model_id`` in this
case will not be within the estate module, you will need to use ``ref="base.model_res_users"``.
Your end result should look similar to the image in the **Goal** of this section.
Remember to check that your reports match your data as expected!
Report Inheritance
==================
.. note::
**Goal**: at the end of this section, we will inherit the property report in the ``estate_account``
module.
.. image:: J_reports/inherited_report.png
:align: center
:alt: An inherited report
Inheritance in QWeb uses the same ``xpath`` elements as :ref:`views inheritance <reference/views/inheritance>`.
A QWeb template refers to its parent template in a different way though. It is even easier to do by just adding
the ``inherit_id`` attribute to the ``template`` element and setting it equal to the *module.parent_template_id*.
We didn't add any new fields to any of the estate models in `estate_account`, but we can still add information
to our existing property report. For example, we know that any "Sold" properties will already have an invoice
created for them, so we can add this information to our report.
.. exercise:: Inherit a report.
- Extend the property report to include some information about the invoice. You can look at the **Goal** of this
section for inspiration (i.e. print a line when the property is Done, otherwise print nothing).
Again, remember to check that your reports match your data as expected!
Additional Features
===================
All the following extra features are described further in the :ref:`reference/reports/report`
documentation, including how to implement each of them.
Translations
------------
We all know Odoo is used in multiple languages thanks to automated and manual translating. QWeb reports are no
exception! Note that sometimes the translations do not work properly if there are unnecessary spaces in your
template's text content, so try to avoid them when possible (especially leading spaces).
Reports are web pages
---------------------
You probably are tired of hearing that QWeb creates HTML, but we're saying it again! One of the
neat features of reports being written in QWeb is they can be viewed within the web browser.
This can be useful if you want to embed a hyperlink that leads to a specific report. Note that
the usual security checks will still apply to prevent unauthorized users from accessing the reports.
Barcodes
--------
Odoo has a built-in barcode image creator that allows for barcodes to be embedded in your reports.
Check out the corresponding
`code <https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/web/controllers/main.py#L2044-L2046>`__
to see all the supported barcode types.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,378 +0,0 @@
.. _tutorials/getting_started/K_dashboard:
======================
Advanced K: Dashboards
======================
.. warning::
This tutorial assumes you have completed the :ref:`Core Training <tutorials/getting_started>`
and have access to Odoo Enterprise features.
To follow the exercise, it is recommended that you fetch the branch
{BRANCH}-core from the `technical training solutions
<https://github.com/odoo/technical-training-solutions/tree/{BRANCH}-core>`_ repository. It
contains a version of the module created during the core training we can use as a starting
point.
The term "Dashboard" is used in Odoo for objects that display data, but involves different
implementations. This tutorial will only focus on the Enterprise view that is used to provide
aggregated data visualization. They can be added as a ``view_mode`` to an existing model (i.e. a
view you can switch to via the view buttons in the top right of a view), but they are also often
used as a view for to a special model customized for data visualization. You may hear these
special views referred to as SQL views.
It is useful to note there is a "Dashboard" app in Odoo Community. This app allows users to create
their own customized view of data, but the customization is only visible to each user and can
only be viewed within the "Dashboard" app. Technically it is possible to make global dashboards
using this ``board`` module, but it is much easier to do as an Enterprise view. Plus it looks nicer
and has extra features not available in ``board``. Some other dashboards within Odoo also exist,
but they are custom made and are beyond the scope of this tutorial.
The documentation related to this topic can be found in :ref:`reference/views/dashboard`.
File Structure
==============
You probably have already guessed that since dashboard views are an Enterprise view, they must have
a dependency on an Enterprise module. The Enterprise module is ``web_dashboard``. Don't forget to
add it to your manifest file! It is standard to add dashboards intended to be used as a
``view_mode`` for one of your module's models (in the ``model`` folder) to the views directory
(i.e. the same file that contains the other views for the same model).
It is standard to create a separate Enterprise module to add extra Enterprise views and features to
a Community module. This is done in a similar manner as the module link technique covered within
:ref:`tutorials/getting_started/14_other_module`. The difference is that instead of linking 2
different modules, we are extending our `estate` module. We do this by creating a new module and
adding both the Community module and its necessary Enterprise module dependencies to its manifest.
You will commonly see "enterprise" in the module's directory name. To keep this tutorial simple, we
will add dashboards to our existing ``estate`` module.
SQL Views have 2 parts: their xml file (don't forget to add it to your manifest file) and their
Python file (don't forget to add it to the appropriate ``__init.py__`` files). The former is the
same format as the ``view_mode`` xml while the latter contains a custom model and SQL code to
populate its fields. It is standard to add SQL view files to the ``report/`` directory. It
is also common to include "report" in the name of the SQL view's files. You may be
wondering why do we put the files in a report directory? We saw earlier that the dashboard is
for data visualization, therefore it is not editable. You can think of dashboards as interactive
reports where you can click on statistics, graphs, and charts to see the specific data contributing
to them. Note it is also standard to store the xml code for
:ref:`PDF report templates <tutorials/getting_started/J_reports>` in the report directory.
It is expected that your work tree will look something like:
.. code-block:: bash
estate
├── models
│ ├── *.py
│ └── __init__.py
├── report
│ ├── __init__.py
│ ├── estate_report.py
│ └── estate_report_views.xml
├── security
│ └── ir.model.access.csv
├── views
│ ├── *.xml
│ └── estate_property_views.xml
├── __init__.py
└── __manifest__.py
Dashboard View
==============
.. note::
**Goal**: at the end of this section, we will have a new dashboard view that displays
different property statistics.
.. image:: K_dashboard/simple_dashboard.png
:align: center
:alt: Basic Dashboard view
Dashboards can display data in different ways, including:
* showing an ``aggregate`` of a field
* using aggregated fields in a ``formula``
* using a ``widget``
* using another ``view`` as a subview
There are many useful statistics and visuals we can provide for our real estate example using
these options. A full example to reference while doing the exercises in this section is
`viewable here <https://github.com/odoo/enterprise/blob/6fd3244ae168dc73c348a9c1870796e89d8ef594/crm_enterprise/views/crm_lead_views.xml#L106-L133>`__
(restricted github repository link).
Data
----
To fully enjoy our dashboard view, we will need good test data to populate it. Test data will
allow us to check that the resulting look and statistics are correct. It is a good idea to test
with data that will cover most or all of your expected use cases, but is also easy to verify with
that your statistics are correct. In our goal's case we are looking at count, sum, average,
minimum, and maximum statistics, therefore a good representation set for our dashboard is:
* At least 3 properties with different property types, expected prices, and average living area.
* At least 1 sold property and at least 1 canceled property
If you don't have a set of data like this already, you can either:
* Complete :ref:`tutorials/getting_started/C_data` (if you haven't done so already) and add the
extra cases to your demo data (you may need to create a new database to load in the demo data).
* Manually create the data in your database.
* Copy this `data file
<https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-K_dashboard/estate/data/estate_demo.xml>`_
into a new directory called ``data`` in your estate module and copy `these lines
<https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-K_dashboard/estate/__manifest__.py#L21-L23>`_
into your __manifest__.py file (you may need to create a new database to load in the demo data).
Click through your database data and make sure it is what you expect. Of course you can add the
data after you write your dashboard code and then test that your view is working as expected.
Aggregations
------------
Building a dashboard view is very similar to what you have previously done in
:ref:`tutorials/getting_started/07_basicviews`. For the dashboard view, we use the `dashboard` root
element and choose from its possible tags (see all the possibilities and their attributes in the
:ref:`reference/views/dashboard` documentation). So a simple dashboard example is:
.. code-block:: xml
<dashboard>
<group>
<aggregate name="min_expected_price" string="Min Expected Price" field="expected_price"
group_operator="min" help="Lowest expected price."/>
</group>
</dashboard>
In this example, ``<group>`` adds styling and ``<aggregate>`` declares an aggregation. We
indicate which ``field`` we want to aggregate, what ``string`` to display with the value, and
how to aggregate it with the `group_operator` attribute. The `group_operator` can use any valid
PostgreSQL aggregate function plus the special Odoo defined ``count_distinct``.
Hopefully you remember how to add views to a window action `view_mode` (hint, it was
covered in :ref:`tutorials/getting_started/06_firstui`). Now let's make some dashboards!
.. exercise:: Make a dashboard view.
- Create a dashboard of aggregated values for the ``estate.property`` model. You can
look at the **Goal** of this section for some inspiration. Remember to check that your
statistics are calculating as you expect and note that the calculated values take into
consideration any applied view filters!
- Bonus: Add in some aggregations that need a `domain` to make sense (remember domains were
also covered in :ref:`tutorials/getting_started/07_basicviews`).
Pie Charts
----------
Adding pie charts to dashboards is a piece of cake using the `<widget>` element. An example is:
.. code-block:: xml
<dashboard>
<group>
<widget name="pie_chart" title="Property Types" attrs="{'groupby': 'property_type_id'}"/>
</group>
</dashboard>
In this example, we indicate that we're using the `pie_chart` widget with the `name` attribute and
the ``title`` for the pie chart, and that we're grouping it by property type.
.. exercise:: Add some pie charts.
- Add the pie charts from the **Goal** of this section to your dashboard. Hint: you will need
to add `'measure': selling_price` to your pie chart `attrs` if you want to show selling
prices grouped by property type.
- Hover over and click on the pie charts to check your charts counts values and don't forget
that filters will also apply to the charts.
- Bonus: Add a domain to your selling price pie chart to only include "sold" properties (i.e.
not "offer_accepted" ones). Note that the `'` will need to be escaped since it is declared
as part of the `attrs`.
Subviews
--------
Similar to how we can use the list view within the form view (we saw this automatically happen for
One2many relationships in :ref:`tutorials/getting_started/08_relations`), we can add other views
within our dashboard view. The most commonly added are the pivot and graph views, but the cohort
view is also an option. These views are covered in more depth in
:ref:`tutorials/getting_started/H_adv_views`. For this topic, you are only required to know their
names. A dashboard with only subviews is:
.. code-block:: xml
<dashboard>
<view type="graph"/>
<view type="pivot"/>
</dashboard>
The `ref` attribute can be added to `<view>` elements to use a specific XML id for that view. If
no XML id is provided for a graph or pivot view, then the default view will be used.
The cohort view will not work in the dashboard without a specific XML id. If you have already
created some of these views, then you are welcome to add them to your dashboard! Sample graph and
pivot views are included in the `solution code
<https://github.com/odoo/technical-training-solutions/blob/{BRANCH}-K_dashboard/estate/views/estate_property_views.xml#L169-L191>`_
that you are welcome to use as well.
.. exercise:: Add subviews.
- Add in a graph and a pivot view to your dashboard. Try playing around with the layout of
your subviews in relation to your pie charts and aggregated values and refer to the **Goal**
of this section for an often used layout. Remember to check that your subviews are
displaying your data as expected (and yes, they are also affected by the filters!).
SQL Views
=========
.. warning::
This section expects you to have a basic knowledge of SQL. If you have little to no SQL
knowledge then `this is a good tutorial to start with <https://selectstarsql.com/>`__
and these `exercises <https://www.pgexercises.com/>`__ are good for those who need
a refresher or extra practice.
.. note::
**Goal**: at the end of this section, we will have a new SQL view that displays different
property statistics.
.. image:: K_dashboard/report_dashboard.png
:align: center
:alt: SQL view
Occasionally we want to show data that goes beyond what our model already has in it. We could add
a lot of stored computed or related fields (non-stored fields cannot be aggregated
or displayed in pie charts), but it would be impractical to store a bunch of fields only for this
purpose. We can instead add a custom SQL view to minimize the computational load and keep our
model clean of unnecessary fields.
Model
-----
We will start with the more difficult part: our special report model. This file starts the same as
any other model, except that we add 2 attributes ``_auto`` and ``_rec_name``::
from odoo import fields, models, tools
class EstateReport(models.Model):
_name = 'estate.report'
_description = "Stock Report"
_rec_name = 'id'
_auto = False
``_auto = False`` indicates that we do not want to store the model in the database, and we will
create a custom table by overriding the ``BaseModel.init()`` method. ``_rec_name`` indicates
which of the model's fields represents a record's name (i.e. the name that will be used in the
navigation breadcrumb when opening a record's form view). In this case, I left it as 'id' because
our property offers don't have a name. We will need the `tools` import later (i.e.
``odoo/odoo/tools``, which is full of all sorts of useful helper methods you will probably use in
the future). Note that it is standard to include ``report`` in the model's name.
Remember, your new model will need to be added to your security file, as you learned in
:ref:`tutorials/getting_started/05_securityintro`!
Then we define the fields we need for our dashboard the same way as any other model (like you
learned in :ref:`tutorials/getting_started/04_basicmodel`), except that every field is
``readonly=True``. After all, our model is for read-only purposes only.
Now we override the ``BaseModel.init()`` method mentioned earlier::
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""CREATE or REPLACE VIEW %s as (
SELECT
%s
FROM
%s
)""" % (self._table, self._select(), self._from()))
We use ``tools.drop_view_if_exists`` to ensure that we don't create a conflicting view and then
execute the SQL query. It is standard to separate the different parts of the query to
allow for easier model extension. Exactly how the query is split up across methods is not
standardized, but you will often see at minimum ``_select`` and ``_from`` methods [or something
similar], and of course all these methods will return strings. The columns from the SELECT
will populate our model's fields, so ensure that your column names match your field names
or use alias names that match.
.. exercise:: Create report model.
- Create a report model with the following fields:
========================= ========================= =========================
Field Type Note
========================= ========================= =========================
id Integer Corresponds to ``id`` of ``estate.property.offer``
offer_state Selection Equals ``state`` choices of ``estate.property.offer``
property_id Many2one ``estate.property``
property_state Selection Equals ``state`` choices of ``estate.property``
property_type_id Many2one ``estate.property.type``
========================= ========================= =========================
and write the SQL query necessary to populate the fields (hint, you will need 2 JOINs).
You won't be able to check if your model is correct until we create a view for it, but you are
welcome to check your query directly in your database to see if the results are as you expect.
If you struggle with this exercise, then
`here is an example <https://github.com/odoo/odoo/blob/7417d8fc138b9de550bc631435bcc08628c29bed/addons/crm/report/crm_activity_report.py>`__
to reference.
View
----
Now that we have our model, we can make its dashboard view. There is no difference in how it's made,
except that its file is in the ``report`` folder. Since it is a new model not linked to
any other model, we will also have to add a new menuitem to view our dashboard. Typically, SQL views
are added under a first-level menu called *Reporting* (because it's a report, surprise!). Do you
remember how to add a ``menuitem``? If not, revisit :ref:`tutorials/getting_started/06_firstui`) again.
.. exercise:: Create report view.
- Recreate the dashboard in the **Goal** of this section. Hint: it uses the ``formula`` element,
which we did not need for our previous dashboard.
- Bonus: Create ``list`` and ``form`` views for your new report model so we don't have to see the ugly
defaults when you click on your pie charts.
Extra Tips
----------
**Tip 1** A common mistake in SQL views is not considering the duplication of certain data
due to table JOINs. For example, in our **Goal**, we have a pie chart of the offers' property types.
We may be tempted to add a similar pie chart with a domain to only include canceled properties,
so we think we are only counting the number of canceled properties by property type. In reality, we
are still looking at all the offers per property, so any property with more than 1 offer will be
counted per offer. This example is easily double-checked by clicking on the pie chart to see its
list view:
.. image:: K_dashboard/report_list_detail.png
:align: center
:alt: Pie chart list view
But for cases such as average aggregations or using a subview such as the pivot view, it is easy to
miss this mistake. It is also easy to miss this mistake when you have insufficient test data.
To add a number of properties canceled by property type pie chart to this
report, we would either have to do a hack (too advanced for this tutorial) or simply exclude it
from this report.
**Tip 2** If you have a field that you do not want as a measure (i.e., in your pivot or
graph views), then you can add ``store=False`` to it, and it will not show.
**Tip 3** If you have a SQL View that depends on context, then instead of overriding
``BaseModel.init()`` set the ``_table_query`` property::
@property
def _table_query(self):
return 'SELECT %s FROM %s' % (self._select(), self._from())
The *select* and *from* methods remain the same.
`Here is an example <{GITHUB_PATH}/addons/account/report/account_invoice_report.py>`__
of a report that depends on the currently selected companies (in a multi-company environment) context to
determine the currency exchange rates to use for accurately displaying amounts when the selected companies
have different currencies.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/L_cron:
======================================
Advanced L: Scheduled & Server Actions
======================================
Hopefully, this topic will be written soon :-)

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/M_migration:
======================
Advanced M: Migrations
======================
Hopefully, this topic will be written soon :-)

View File

@@ -1,27 +0,0 @@
.. _tutorials/getting_started/N_security:
====================
Advanced N: Security
====================
Every day we hear about data leaks and attacks in the newspapers.
We expect you to be aware and careful about how to avoid security breaches in your future
developments.
You already defined new models in your training. Did you check the access rights and define record
rules on it ? Are you sure your module is not an open door to SQL injections ? Did you use getattr
or t-raw in your code?
- No or wrong access rules on models.
- Introduction of public methods that should actually be private.
- Wrong manipulation of access tokens.
- No or wrong `ir.rules` (restriction rules) on models.
- Introduction of SQL injections.
- etc.
Please check this `presentation from Olivier Dony (odo)
<https://docs.google.com/presentation/d/1oDINxPtHWz31V8-2W0h2u2ubaKgz9lmbyfx9DJI4lTw/edit>`_ about
common mistakes that you should **absolutely** avoid in your future developments.
Also, please always use this `security checklist
<https://docs.google.com/presentation/d/1oDINxPtHWz31V8-2W0h2u2ubaKgz9lmbyfx9DJI4lTw/edit#slide=id.g2faad955b1_0_6>`_
to double-check your development and avoid further embarrassing issues.

View File

@@ -1,9 +0,0 @@
:orphan:
.. _tutorials/getting_started/O_perf:
========================
Advanced O: Performances
========================
Hopefully, this topic will be written soon :-)