Commit Graph
401 Commits
Author SHA1 Message Date
std-odoo 608bffff59 [FIX] base: fix the default properties value
Bug
===
If we re-write the same definition record, the default values were
applied again (even if the definition record didn't change).

This is because the compute on the properties is called even if the
definition record didn't change.

Task-2965523

X-original-commit: 70d80771f2430f83c91e7ef46eeebde8de70c3fe
Part-of: odoo/odoo#101487
2022-09-28 19:31:53 +02:00
std-odoo 50abdbe9d9 [FIX] base: fix the property onchange
Bug
===
- create a new helpdesk ticket without properties
- create a new property
- change the customer
=> The added property is removed

The fix in the onchange of models.py needs to be more specific, and
should be used only when the definition record is changed.

Task-2965523

X-original-commit: 909ee004183068d9ffa94abe0d1472a8f0534f34
Part-of: odoo/odoo#101487
2022-09-28 19:31:52 +02:00
std-odoo 1b4c5e92ff [IMP] base: log a message each time a definition is modified
Purpose
=======
Log a message each time a user change a properties definition
by writing on the record properties values.

Task-2965523

X-original-commit: 6fa6fcd13e248d4bdf1febb0d8399cfe634037f5
Part-of: odoo/odoo#101487
2022-09-28 19:31:52 +02:00
std-odoo a6e7a560ef [IMP] base, web: improve the properties behavior when a record is not accessible
Purpose
=======
Improve the behavior / visual of the properties field when the user does
not have access to some relational properties (many2many, many2one).

When the user does not have access to one many2many value,
show the others record in the list.

Fix a traceback when the model is not set on the definition.

Task-2965523

X-original-commit: cb8b751a335b01192aab185cd0230fa0f036b974
Part-of: odoo/odoo#101487
2022-09-28 19:31:51 +02:00
std-odoo f5c7555d1b [IMP] web, base: shorten generated property names
Purpose
=======
Shorten the property name to take less space in database (while keeping
a very low probability of collision).

E.G.: "aa34746a6851ee4ea1f8d95746e45788" -> "aa34746a6851ee4e"

Automatically generate property name in python if they are missing.

Task-2965523

X-original-commit: d597083ff5e0a4abde67a450a61e04e6bf119c26
Part-of: odoo/odoo#101487
2022-09-28 19:31:51 +02:00
std-odoo 7a135ff585 [IMP] base: prefetch the properties definition
Purpose
=======
Most of the time we will need to read some field on the definition
record when we read the record itself (e.g. fetching some project
information on a task).

Because of this, it makes sense to prefetch by default the properties
definition, and it reduces the number of queries as well.

Task-2965523

X-original-commit: 1295e76f0dc3f3162a1b81a58ed1dc73d0fce3e7
Part-of: odoo/odoo#101487
2022-09-28 19:31:50 +02:00
Michael (mcm)andluvi 710271240f [IMP] web: remove readonly mode of form view
This commit does multiple things:
- The readonly mode of form view is removed but not for the fields.
  it means that the fields in the view are always in edit mode except
  if we force them to be readonly.
- The control panel is revamped to take less vertical space and shows now
  the record editing (dirtiness)/validity status after editing the record.
- The record is saved only when leaving the view or by clicking the save
  button when hovering the record status in the control panel.
- The record can still be discarded by clicking the discard button when
  hovering the status text in control panel.

task id: 2822553

X-original-commit: 77824ad44b6945a9811120380747f87ef6362ae2
Part-of: odoo/odoo#101118
Co-authored-by: luvi <luvi@odoo.com>
2022-09-27 12:43:50 +02:00
Tom De CaluwéandRaphael Collet 3a4a7b161b [FIX] core: recompute fields triggered by indirectly modified relational fields
The cache currectly fails to correctly invalidate relational fields that depend
on a non-relational field. Two passes of invalidation are done, to reflect
dependencies on both the old and the new written values. In the first pass
only relational fields are considered, as explained in the comments:

> It is best explained with a simple example: consider two sales orders SO1 and
SO2. The computed total amount on sales orders indirectly depends on the
many2one field 'order_id' linking lines to their sales order.  Now consider the
following code:
>
> line = so1.line_ids[0]      # pick a line from SO1
> line.order_id = so2         # move the line to SO2
>
> In this situation, the total amount must be recomputed on *both* sales order:
the line's order before the modification, and the line's order after the
modification.

The written values can be seen as the roots of a dependency forest (a
collection of dependency trees). Before this commit all non-relational roots
and their corresponding trees were filtered out during the first pass. However,
this approach is wrong, as relational fields can also depend on non-relational
fields. Instead, the complete dependency forest has to be traversed, skipping
invalidation for non-relational fields during the first pass.

The test that was previously included accidentally succeeded because of a
separate and unrelated bug in the orm domain parser: in certain one2many or
many2many leafs the domain parser would not take into consideration the domain
included in the definition of the field. As a result, the test still passed
by accident, because the records that no longer matched the domain after the
write were still invalidated during the second pass.

The problem can clearly be demonstrated, however, when the dependency is
generated by a compute function.

closes odoo/odoo#101038

X-original-commit: d4a5827b42d80f0f830455dcd2056701eb09aed1
Signed-off-by: Rémy Voet <ryv@odoo.com>
Signed-off-by: Raphael Collet <rco@odoo.com>
Co-authored-by: Raphael Collet <rco@odoo.com>
2022-09-24 19:12:26 +02:00
Xavier-Do bc1c4e8019 [FIX] test_new_api: use a proper patch
The tour test_01_admin_widget_x2many was failing because of a missing
discussion. This discussion was in fact coming from a patch
during at install tests

This wasn't detected on runbot during merge since at_install and
post_install are executed in different builds. This was detected
during the nightly "all no auto tag" build.

Simply use a proper patch to avoid keeping the default value at the
end of the test.

closes odoo/odoo#100491

Signed-off-by: Christophe Monniez (moc) <moc@odoo.com>
2022-09-19 14:14:58 +02:00
ef00294e71 [IMP] core: store translated fields as JSONB columns
Translated fields no longer use the model ir.translation.  Instead they store
all their values as JSON, and store them into JSONB columns in the model's
table.  The field's column value is either NULL or a JSON dict mapping language
codes to text (the field's value in the corresponding language), and must
contain an entry for key 'en_US' (as it is used as a fallback for all other
languages).  Empty text is allowed in translation values, but not NULL.

Here are examples for a field with translate=True:

    NULL
    {"en_US": "Foo"}
    {"en_US": "Foo", "fr_FR": "Bar", "nl_NL": "Baz"}
    {"en_US": "Foo", "fr_FR": "", "nl_NL": "Baz"}

Like before, writing False to the field makes it NULL, i.e., False in all
languages.  However, writing "" to the field makes its value empty in the
current language, but does not discard the values in the other languages.

Here are examples for a field with translate=xml_translate:

    NULL
    {"en_US": "<div>Foo<p>Bar</p></div>", "fr_FR": "<div>Fou<p>Barre</p></div>"}

Change for callable(translate) fields: one can now write any value in any
language on such a field.  The new value will be adapted in all languages, based
on the mapping of terms between languages in the old values.  Basically the
structure of the value must remain the same in all languages, like before.

Reading a translated field is now both simpler and faster than the former
implementation.  We fetch the value of the field in the current language by
coalescing its value with the 'en_US' value of the field:

    SELECT id, COALESCE(name->>'fr_FR', name->>'en_US') AS name ...

The raw cache of the field contains either None or a dict which is conceptually
a subset of the JSON value in database (except for missing languages).  For the
sake of simplicity, most cache operations deal with the dict and return the text
value in the current language.

Trigram indexes have been adapted to the new storing strategy, and should enable
to search in any language.  Before this change, only the source value of the
field ('en_US') could be indexed.

Computed stored translated fields are not supported by the framework, because of
the complexity of the computation itself: the field would need to be computed in
all active languages.  We chose to not provide any hook to compute a field in
all languages at once, and the framework always invokes a compute method once to
recompute it.

Code translations are no longer stored into the database.  They become static,
and are extracted from the PO files when needed.  The worker simply uses a cache
with extracted code translations for performance.  This is reasonable, since
fr_FR code translations for all modules takes around 2MB of memory, and the
cache can be shared among all registries in the worker.  Changing code
translations requires to update the corresponding PO file and reloading the
worker(s).

Performance summary:
 (+) reading 'model' translated fields is faster
 (+) reading 'model_terms' translated fields is much faster (no need to inject
     translations into the source value)
 (+) searching translated fields with operator 'ilike' is much faster when the
     field is indexed with 'trigram'
 (+) updating translated fields requires less ORM flushing
 (-) importing translations from PO files is 2x slower

Some extra fixes:
 - make field 'name' of ir.actions.actions translated; because of the PG
   inheritance, this is necessary to make the column definition consistent in
   all models that inherit from ir.actions.actions.
 - add some backend API for the web/website client for editing translations
 - move methods get_field_string() to model ir.model.fields
 - move _load_module_terms to model ir.module.module
 - adapt tests in test_impex, test_new_api
 - because env.lang is injected into SQL queries, its returned value is
   now guaranteed to correspond to a valid active language or None
 - remove wizard to insert missing translations (no longer makes sense)

task-id: 2081307

Co-authored-by: Fabien Pinckaers <fp@openerp.com>
Co-authored-by: Raphael Collet <rco@odoo.com>
2022-09-15 22:37:50 +02:00
Rémy Voet (ryv) dfa34aa3fe [IMP] core: reduce cost of modified.
For non-stored computed fields, `_modified_triggers` will traverse the
tree (at the cost of extra queries) only to know which record to
invalidate in cache. But in most cases, these fields have no data in
cache, so they can be ignored from the start, which allows us to prune
entire subtrees from the merged tree.

By example:
With simple write on `show_operations` of one `stock.picking.type`, the
`_modified_triggers` will fetch every ids (from database) of `stock.picking`
and `stock.move` related to this `stock.picking.type`
(For `stock.move`, it is because of the
`show_operations = fields.Boolean(related='picking_id.picking_type_id.show_operations'`))
In that case, there isn't any data of `show_operations` (`stock.move`)
in cache, then there are nothing to invalidate and
the `_modified_triggers` cost
is high (extra queries/processing) for nothing.

Then we cut parts of the tree when we know that
they won't invalidate anything (=> if the cache is empty for the field).
Also refactor the way to merge trees to be more efficient.

Performance improvements:
- For all tests at-install done by the runbot, we gain -+ 3.5% of
queries.
- In the example above, we reduce the number of queries (potentially
bottleneck queries in large DB) from 7 to 4.
- In term of CPU (without counting time in SQL), the new version is -+
20 % faster (on install of stock,purchase,mrp and with
--test-tags=/stock,/purchase).

closes odoo/odoo#76322
task-2780812

closes odoo/odoo#99274

Related: odoo/enterprise#30951
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-09-02 23:16:06 +02:00
Rémy Voet (ryv) 54d75a28cd [IMP] test_new_api: add performance test for modified.
Part-of: odoo/odoo#99274
2022-09-02 23:16:06 +02:00
std-odoo 87307a9010 [IMP] base: add new "Properties" fields
Purpose
=======

Add a new field "Properties" to be able to light customization of workflows
based on a parent model. Those properties acts in some ways like Odoo fields
without requiring specific columns e.g. add new properties on tasks of a
specific project.

Usage
=====

Define properties on a parent model (e.g. project) with

```
    attributes_definition = fields.PropertiesDefinition('Message Properties')
```

It defines properties available on children: types, default, value, model for
relational properties,...

Use it on children records (e.g. task) with

```
    attributes = fields.Properties(
        string='Properties',
        definition='parent_id.attributes_definition',
    )
```

Technical
=========

Parent | Properties definition
------------------------------
The properties definition is stored on the parent, on a JSON field.
This definition contains the type of the properties, the default value,
the model of the many2one,...
```
[
    {
        'name': 'name',
        'string': 'Name',
        'type': 'char',
        'default': 'Default Name',
    }, {
        'name': 'partner_id',
        'string': 'Partner',
        'type': 'many2one',
        'comodel': 'res.partner',
    },
]
```

Child | Properties values
-------------------------
The value is stored on the child, using a Properties field.
```
{
    'name': 'Mitchel',
    'partner_id': 1337,
}
```

When we read this field, we will automatically read the definition on
the parent, and merge both JSON into one, so the web client has the
value of each property, and their definition.

```
[
    {
        'name': 'name',
        'string': 'Name',
        'type': 'char',
        'default': 'Default Name',
        'value': 'Mitchel',
    }, {
        'name': 'partner_id',
        'string': 'Partner',
        'type': 'many2one',
        'comodel': 'res.partner',
        'value': 1337,
    },
]
```

Integrity
---------
If we remove a property on the parent, we won't update the child value.

Instead, when we read the child properties, we will filter them based
on the parent. So the removed properties will be removed the next time
we write on the field.

In the same logic, the many2one existence is checked when we read the
field. There's no foreign key between the integer stored in the JSON
in the SQL row corresponding to the record in database.

Write
-----
We can write on the Properties field with a list of field definition
+ value.

Some types are not JSONifiable (like the date, datetime), they are
stored as string in database and parsed when we read the value.

In order to update the parent definition by writing on the child,
you need to add the dict key `definition_changed` or
`definition_deleted`. This is because we need to be able to know
if the definition has been changed without doing extra SQL queries.

Access rights
-------------
A user can add a many2one / many2many property to a model only if he
has the access rights to it.

Many2one / Many2many
--------------------

The model choice of a many2one / many2many properties was subject to
changes.

First implementation stored models in both parent and children to easily
spot changes and avoid complex queries when fetching records, trying to
synchronize them, ...

As this leads to storing a lot of duplicated content we choose to instead
reset the value on the child if the model has been change. We generate a
new name for the property. So it behaves like if we removed the property
and created a new one.

To be able to restore the old value (e.g. if by mistake we changed the
model, and go back to the old model), we store the initial states.

Task-2852259

Part-of: odoo/odoo#95184
2022-08-29 23:46:06 +02:00
Romain Derie cf844e34dd [IMP] core: allow some users to bypass the sanitize of HTML field
Add the possibility to flag a HTML field as `sanitize_overridable`.
The sanitizer will then be bypassed if the user doing the operation is
part of the new group `base.group_sanitize_override`.
The "Settings" users are part of that new group.

If such a user wrote some HTML that would have normally been removed by
the sanitizer, then users without the right to bypass the sanitizer
won't be able to write on that field anymore.
Otherwise, it would sanitize the previously written data.
Such cases are detected, and the modification prevented by the system,
which will warn the user about it.

For instance, with a `field.html(sanitize_overridable=True)`: one being
part of `group_sanitize_override` could write `<script></script>`.
Then, someone not part of the group trying to add an element inside that
field like appending a `<p/>` -> `<script></script><p>New Content</p>`
would not be able to because it would go through the sanitizer and
ultimately, removing part of the original value:
`<p>New Content</p>` (`<script></script>` would be removed).

== Real use case ==
In the website builder, there is 2 editor rights:
- group_website_publisher: restricted editor
- group_website_designer: editor & designer

The designer editor can edit pages and views, while the restricted
editor can't do anything unless he is part of other groups.
In edit mode, the restricted editor will only be able to edit fields of
record he has access to.
For instance, being a sales manager allows you to edit a product
description on the website.
Being an event manager -> edit event. Slide manager -> Slides etc.
As those restricted editor are able to edit those fields, they are
(almost) always sanitized to prevent them to introduce malicious code.

Since those fields are sanitized, even admins / designer editor are not
able to fully use the website builder in such fields.

Some clients don't really care about that sanitation, they'd prefer to
avoid it as they trust their manager and would prefer to have the full
builder capability instead.
This is typically the case in small project (butcher, hairdresser,
reseller etc) and in SMEs.

With the new `sanitize_overridable` feature, they will be able to do
that, as the "Designer & Editor" group now also receive the group
`base.group_sanitize_override` (done in next commit).

Part-of: odoo/odoo#97398
2022-08-24 23:03:23 +02:00
Xavier Morel 992fb00b64 [FIX] test_new_api: properly conclude tours
- the x2many tour would finish on a click canceling the creation but
  not wait for that to resolve, leading to the driver catching it in
  the act (checking before the client had had the time to switch),
  just need to wait for the list view to be displayed
- the constraints tour would just drop in the middle of a creation
  with an error dialog open, so need to finish the entire thing and go
  back to the list view

Part-of: odoo/odoo#96517
2022-08-04 09:14:10 +02:00
Fabien Pinckaers 3363e55cac [IMP] cleanup of help messages in all modules
This commit aims at removing unuseful help message to:
1/ reduce translators work, to focus on more useful translations
2/ not sending unuseful information in load_views
3/ reduce help message to useful messages, so that we can mark
   fields having a tooltip in the future UI.
4/ some cleanup of existing messages too

The main use cases:
- REMOVED: help redundant with the field name, providing no extra info
- MOVED TO COMMENT: technical help messages, that should not be in UX

closes odoo/odoo#97279

Signed-off-by: Fabien Pinckaers <fp@odoo.com>
2022-08-02 00:26:53 +02:00
+2 bc0a0cead6 [IMP] web,*: enable owl list & form views
The new list and form views were merged recently [1], but they
weren't activated because they weren't 100% ready yet. This is now
the case. This commit adds those views to the view registry. As a
consequence, a lot of qunit tests and tours needed to be adapted,
mostly for selector changes.

We also add legacy list and form views to the view registry, with
keys 'legacy_list' and 'legacy_form'. This allows to force those
legacy views when necessary. For instance, we did it in views
using complex custom legacy x2many field widgets that haven't been
converted yet (we have a compatibility layer but it isn't complete
and doesn't support every advanced usecases).

[1] odoo/odoo#92475

Part-of: odoo/odoo#78221
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Bruno Boi <boi@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: Simon Genin (ges) <ges@odoo.com>
Co-authored-by: Francois (fge) <fge@odoo.com>
Co-authored-by: Michael Mattiello (mcm) <mcm@odoo.com>
Co-authored-by: Julien Mougenot <jum@odoo.com>
Co-authored-by: Lucas Perais (lpe) <lpe@odoo.com>
Co-authored-by: Jorge Pinna Puissant <jpp@odoo.com>
Co-authored-by: luvi <luvi@odoo.com>
2022-07-22 16:21:45 +02:00
Raphael Collet 519d8fe492 [FIX] core: complex flushing when searching on one2many fields
Consider two models A and B, where
 - model A has a many2one_reference field 'res_id' with model field 'res_model';
 - model B has an auto-join one2many field 'stuff_ids' to A using field 'res_id';
 - the field 'res_model' is not flushed on some record.

      model     | A                 | B
     -----------+-------------------+-------------------
      memory    | res_model = B     |
     -----------+-------------------+-------------------
      database  | res_model = NULL  | id = 42
                | res_id = 42       |
                | foo = 'bar'       |

Now, perform a search on model B that should return record with id=42 by
matching some condition on the unflushed record in model A, like:

    B.search([('stuff_ids.foo', '=', 'bar')])

Before this patch, the search method would not flush the field
'res_model', which causes the method to return incorrect results.  This
patch fixes the issue by ensuring that searches on one2many fields flush
all the fields on which the one2many field depends.

The issue was discovered while working on task 2735672.

closes odoo/odoo#96115

Signed-off-by: Raphael Collet <rco@odoo.com>
2022-07-16 17:05:45 +02:00
Denis Ledoux f2f5ce7790 [IMP] repair: convert repair uom and location onchanges to compute
This allows to create a repair.order record without
the need to call the onchanges to set the uom and locations
or to set them manually during the `create` call.

For instance, this makes easier to create repair orders
using XMLRPC when you do not use multiple UOMs or multiple locations.

closes odoo/odoo#95321

Signed-off-by: Raphael Collet <rco@odoo.com>
2022-07-07 17:30:35 +02:00
Romeo Fragomeli 1fcd098af5 [REF] *: BS5: migration
Automated change made by a lot of RegEx to change all think that is
possible to automate.

https://getbootstrap.com/docs/5.1/migration

Task ID: 2766483

Part-of: odoo/odoo#95450
2022-07-07 13:30:24 +02:00
Raphael ColletandVincent Schippefilt eb67feb590 [FIX] *: cache consistency
In module mail, invalidating 'message_ids' on a mail thread also
invalidates its inverse field 'res_id' on messages.  If you haven't
flushed it before, your cache will be inconsistent, as shown by the test
/mail:TestMailgateway.test_message_process_bounce_records_channel.

In module purchase_stock, add depends on report.stock.quantity.  This
ensures that when the model is queried after changes in other models,
the data on which the SQL view depends is flushed to the database before
querying that model's table.

closes odoo/odoo#66938

Related: odoo/enterprise#16722
Signed-off-by: Raphael Collet <rco@odoo.com>
Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
2022-07-05 11:35:01 +02:00
Raphael Collet c0fea2cec8 [FIX] core: update inverse of many2many in order
Consider two models A and B, a many2many field F from A to B and its
inverse relation G from B to A.  When F is modified, G is updated
accordingly in cache.  The statement

    records.write({F: [Command.link(b.id)]})

is expected to add b.id to records.F, and add records.ids to b.G.  In
order to avoid nondeterminism, the additions should be done at the end
of the recordset, and in order.  In other words, if none of "records"
are in b.G, then after the statement above, b.G should be updated as

    b.G = b.G + records

This patch ensures that "records" are added in order.

closes odoo/odoo#94909

X-original-commit: b8fbc460673827497f6817e1751b5d365d8ae29a
Signed-off-by: Vincent Schippefilt (vsc) <vsc@odoo.com>
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-06-30 09:29:58 +02:00
Florian Charlier dae28c4b46 [IMP] base: add _is_internal method to res.users
No method was readily available to know if a user is `internal` (has
group `base.group_user`), which was inconsistent with other base groups.

_is_internal is now used in the codebase where it is clear that
`.has_group('base.group_user')` is called on a single record.

Part-of: odoo/odoo#85703
2022-06-14 09:35:57 +02:00
Raphael Collet 6cf8db906f [REF] *: adapt code to new flush API
closes odoo/odoo#87527

Related: odoo/upgrade#3497
Related: odoo/enterprise#26939
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-05-25 18:00:47 +02:00
Victor Feyens 42bad1a6d2 [IMP] *: remove useless keys from manifests
Remove most values uselessly specified because giving the same value as 
the default one (see _DEFAULT_MANIFEST in odoo/modules/module.py)

* auto_install is Falsy by default
* author is Odoo SA by default
* summary & description are empty strings by default
* application is False by default
* test, demo, depends and data are empty lists by default

This will reduce noise/inconsistencies between manifests specifications, 
simplify analysis of manifests content, ...

closes odoo/odoo#90209

Related: odoo/enterprise#26807
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
2022-05-03 13:31:16 +02:00
Denis Ledoux b03c227e88 [REF] models: refactor fields_view_get, load_views
Refactor the `load_views` API so it no longer sends multiple times the same
fields description.

e.g.
When `load_views` is called to get the kanban, tree and form views,
the list of fields of the model was sent 4 times:
- Once for each view, with only the fields used in the view,
  in `['fields_views']['kanban']['fields']` for instance
- Once globally, with all the fields of the model, in `['fields']`

The goal of this revision is to change that so it sends the list of all fields
only once.

In addition, if a view contains x2many fields,
the fields description of the comodel is also sent.
It was sent in the `views` key of the view fields dict.
e.g.
When calling `load_views` of `res.partner` to get the kanban,
tree and form views,
the `res.partner` fields description was actually sent 6 times:
- Once for each view
- Once globally
- Once for each view of the many2many field `child_ids` of the form view, in
  - `['fields_views']['form']['fields']['child_ids']['views']['kanban']['fields']`
  - `['fields_views']['form']['fields']['child_ids']['views']['form']['fields']`

The change suggested in this revision is to:
- Remove the fields description for each view in `['fields_views']`.
  As it no longer contains the fields,
  the key becomes `['views']` instead of `['fields_views']`.
- Replace the dict key `['fields']` by `['models']`,
  which is a dict with as key the model name and as values
  the model fields description. It contains the fields description
  for all models implied in the view:
  the model of the main view and the model of all one2many and many2many fields.

With this change, the fields description will only be sent once by model
implied in the view.

In addition, the web client was getting the information about the fields
sometimes in the global fields description list (e.g. `['fields']`),
sometimes in the fields description list of the view type
(e.g. `['fields_views']['form']['fields']`),
making it a pain to try to make changes / performance gain
in these field description dictionaries, because you never knew in which dict
the web client was getting its info.
Now, as there is only one place to get the fields description from,
it's clearer and cleaner.

- one2many and many2many fields views are passed directly in the main view
  architecture rather than being put in the `views` key
  of the field description.
  This is actually easier to treat by the web client,
  and this will allow in a future work to cache an entire view in one block
  of text rather than having to combine multiple cached blocks of text
  to return one view.
- one2many and many2many fields which do not have directly embedded views
  have their views directly injected in the architecture,
  so the web client doesn't have to do RPC calls to `load_views`
  for each one2many and many2many fields not having embedded views.
  For instance, this allow to reduce the number of RPC calls to `load_views`
  from 8 to 1 when loading the form of `product.product`.
  Currently, this behavior is limited to 1 level deep but we consider making it
  go all the way down in future works. We did not do it for the moment because
  in certain cases it rises the processing time and the size (bytes) too much.
  e.g. the sale.order view can be 5 levels deep,
  meaning you can reach 4 dialogs on top the main view.
  ```
  sale.order form > order_line > sale.order.line form > invoice_lines >
  account.move.line form > asset_ids > account.asset form >
  depreciation_move_ids > account.move form.
  ```
  This will also benefit in future works to cache an entire view in one block
  of text rather to having to combine multiple cached block of text
  to get one view.
- `fields_view_get` becomes `get_view`.
  As it no longer returns the fields description,
  keeping the `fields` in the name `fields_view_get` no longer makes sense.
  Hence removing `fields` from the method name, it becomes `view_get`.
  As it gets renamed anyway, we take the opportunity to rename it `get_view`,
  which is more in line with the general getter/setter guidelines
  in the model object world.
- `_fields_view_get` becomes `_get_view`. For the same reasons than above.
- `load_views` becomes `get_views`.
  This is not mandatory, there is no technical reason to rename `load_views` as
  it practically sends the same info as before,
  the view architectures and their fields description. Just in another way.
  We just take the opportunity of this pull request to suggest a cleaner API:
  `_get_view`, `get_view` and `get_views`.
- Arguments `toolbar=False, submenu=False` fo the methods
  `_fields_view_get` and `fields_view_get` are converted to a kwargs `**options`
  in `_get_view` and `get_view`.
  The rationale is that submenu was already no longer used (deprecated)
  and the mobile options is introduced.
  The mobile options is necessary to tell the server to send the mobile views
  for x2many fields (kanban instead of tree).
  Instead of adding a new argument each time we add a new option to
  `fields_view_get`, it seems wiser to have a kwargs `**options` to avoid
  to re-write all overrides each time a new option is introduced.
- `_fields_view_get` returned a dict containing the arch in text and some of the
  view information. Now, `get_view` returns a tuple with the view architecture
  as an `etree` node, and the view as a browse record. The rationale is that all
  overrides of `_fields_view_get` were about modifying the arch only
  (e.g. changing the address format/re-organizing the address related field
  nodes of the partner according to the company country).
  To do so, all these overrides were doing `etree.fromstring` to parse the arch
  which was sent in text to convert it to an `etree`,
  then operations were done on the `etree`,
  and then `etree.tostring` was called to convert back the arch to string.
  With this change of signature to send the arch as an `etree`,
  all these back and forth `etree.fromstring` -> `etree.tostring` are avoided,
  allowing some performance gain and less code in the end.
- A cleanup of the keys returned in the dict of `fields_view_get`
  has been performed in `get_view`:
  - `fields` is removed, as explained above,
  - `view_id` is renamed `id`,
  - `name` is removed, it was unused by the web client,
  - `type` is removed, it was unused by the web client,
  - `field_parent` is removed, it was unused by the web client,
  - `base_model` is removed, it was unused by the web client.
- `filters` is moved from the global dict returned by `load_views`
  (now `get_views`) to the dict returned by `fields_view_get` (now `get_view`)
  as it applies only to the `search` view type.
- Retro-compatible methods for the 3 methods
  `fields_view_get`, `_fields_view_get` and `load_views` are provided,
  with deprecation warnings in them.

- The web client could cache the model fields description
  (as it already caches the views),
  so it doesn't need to fetch them again if it asks for another view of a model
  for which he already has the fields description.
  If we do so, `get_views` could return only the list of models used by
  the views, without the fields description as of now,
  and the web client would then call `fields_get` independently only for
  the models for which it doesn't have yet the fields description.
  This would avoid the server to return the fields description
  and to call `fields_get`, which is costly, for each `get_views`,
  therefore gaining performances.
- Inject the views of the one2many and many2many fields all the way down,
  unlimited depth level, as explained above.
- Cache with `ormcache` the architecture of back-end views.
  This is already done for qweb views, it's not done for back-end views.
  Therefore the postprocessing of the views is performed for each `get_views`,
  which is costly, while the view architecture doesn't change for users
  belonging to the same groups, according to the groups implied by the view.

This pull request is co-authored by
Aaron Bohy (aab) for the web client part and
Denis Ledoux (dle) for the server part.

Part-of: odoo/odoo#87522
2022-04-29 09:57:44 +02:00
Raphael Collet 857ef8f5fd [FIX] core: field display_name should be "" instead of False
This is a followup of #86567, where we change the computation of
display_name to match the values of name_get().  Forcing its value to
False instead of the empty string causes many regressions in frontend
applications like website_blog.

closes odoo/odoo#88438

X-original-commit: d467881f66b11021005005584854f30059971b6c
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
2022-04-11 16:02:17 +02:00
Raphael Collet 3ba3695642 [FIX] core: avoid NULLs in subselects generated for one2many fields
This fixes a problem that occurs with conditions on one2many fields,
where most conditions are translated with a subquery like

    "model".id IN (
        SELECT "comodel"."model_id" FROM "comodel" WHERE ...
    )

The issue occurs when the subquery return NULL values, i.e., when the
field "model_id" in the subquery above can be NULL.  The fix simply
consists in adding the condition "model_id IS NOT NULL" in the WHERE
clause of the subquery.

The problem with NULL values returned by subqueries is the fact that
they make the SQL condition undetermined instead of false, and this
discards some of the results returned by a query.  Simply consider:

    id NOT IN (1, 2, 3)

The value id=3 makes the condition false, while i=4 makes it true.  Now
consider that the set also includes a NULL value, like:

    id NOT IN (1, 2, 3, NULL)

The value id=3 makes the condition false, but the value id=4 makes it
undetermined.  Therefore this condition is actually undetermined for all
possible values of id, and a query containing that condition simply
returns nothing!

closes odoo/odoo#87285

X-original-commit: 6d680bac0333c6c7ce9f056cba0e1f7e9fa22f9c
Signed-off-by: Vincent Schippefilt (vsc) <vsc@odoo.com>
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-03-25 17:47:44 +01:00
Raphael ColletandDidier Debondt 036ef5b96d [FIX] core: use EXISTS in domain conditions on many2many fields
This fixes two problems that occur with conditions on many2many fields:
the subtle bug caused by NULL values that are returned by subqueries,
and the performance of those subqueries.

The problem with NULL values returned by subqueries is the fact that
they make the SQL condition undetermined instead of false, and this
discards some of the results returned by a query.  Simply consider:

    id NOT IN (1, 2, 3)

The value id=3 makes the condition false, while i=4 makes it true.  Now
consider that the set also includes a NULL value, like:

    id NOT IN (1, 2, 3, NULL)

The value id=3 makes the condition false, but the value id=4 makes it
undetermined.  Therefore this condition is actually undetermined for all
possible values of id, and a query containing that condition simply
returns nothing!

Regarding performance, consider a domain like [('tag_ids', '=', False)].
It is currently translated in SQL as

    "model".id NOT IN (
        SELECT "model_id"
        FROM "model_tag_rel"
        WHERE "model_id" IS NOT NULL
    )

PostgreSQL does not handle well this "NOT IN" expression when the
relation table is large, and uses some very inefficient query plan in
that case.  The inefficient query plan is possibly related to the
special handling of NULL values, by the way.  This commit changes the
above expression to a "NOT EXISTS" expression (as below), which is
executed with a much more efficient query plan, even with large tables.

    NOT EXISTS (
        SELECT 1
        FROM "model_tag_rel"
        WHERE "model_tag_rel"."model_id" = "model".id
    )

This change was motivated by a use-case with 2.7M account moves, 11M
account move lines and a many2many relation table with 2.3M lines
(`account_move_line_account_tax_rel`).  The query was timing out (15
minutes), and it now takes 15 seconds.

We have replaced all the conditions on many2many fields to use the
relational operators EXISTS and NOT EXISTS.

A test has been added to reflect a case where the many2many table
contains NULL values, like the one for channel_partner in regards to
partner_id and guest_id.

task-2753782

X-original-commit: 334b2aa2aa437d2ed86130f2ed426025f8fe4b86
Part-of: odoo/odoo#87285
Co-authored-by: Didier Debondt <did@odoo.com>
2022-03-25 17:47:43 +01:00
Raphael Collet f5ff26f42e [FIX] core: name_get() should return "" instead of False
Because of 4347491670, the method
name_get() can now return records with False as label, and this makes
code crash when it expects a string.  Make sure that name_get() always
return strings as labels, while keeping False as empty value for the
field 'display_name'.

closes odoo/odoo#86688

X-original-commit: ff37a2fef89df292b52b1501c28fcf841e1628f5
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-03-18 19:50:10 +01:00
Raphael Collet b08676389c [FIX] fields: ondelete being None on many2many field
The issue is simply creating a related stored many2many field, which is
pretty easy to do with Studio.  When creating the field, Odoo crashes
with a traceback caused by ondelete being None on the field.

The source of the bug is the fact that the attribute field.ondelete is
set to a sensible default ('cascade') on non-related fields only.  The
fix consists in setting the default value on the attribute itself, so
that the setup of the field never falls on a case where field.ondelete
is unset.

closes odoo/odoo#86303

X-original-commit: d0e0d80806a1b63ac986e9bcbaf88f9fc5243319
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-03-11 20:36:12 +00:00
Rémy Voet (ryv) e1785e820a [IMP] base: add prefetching group feature
The field prefetching mechanism was poorly customizable.  Before this,
we could only tell if a field was prefetched with other fields or not at
all.  We have no way to inform the framework, like: "When I need data of
that field, prefetch these other fields, which are likely be used in the
same transaction".

From now on, the `prefetch` attribute is used as a grouping key for
prefetching fields.  When a field is fetched, all the fields with the
same value for `prefetch` are taken for prefetching.

For example, consider a small set of fields that are rarely used, except
for one flow A using them.  You want to prefetch those fields only in
the flow A, and you want to fetch them in a single query.  With the new
feature, simply set `prefetch=A` for some string `A` on those fields,
and they will be grouped for prefetching.

closes odoo/odoo#85220

Signed-off-by: Rémy Voet <ryv@odoo.com>
2022-03-03 11:03:55 +00:00
Jorge Pinna Puissant bc20eb4790 [IMP] test_lint, *: enable no-unused-vars rule
Part-of: odoo/odoo#85569
2022-03-02 13:05:42 +00:00
Raphael Collet 15215d650a [FIX] core: check rules before flush() in unlink()
Just before deleting records, we mark fields on dependent records as "to
compute", and their actual computation should happen after the records
have been deleted.  The problem is that checking security rules may
trigger the computations that have been prepared.  So we should check
security rules before marking dependent records.

closes odoo/odoo#85574

X-original-commit: 79908bc757a239c97bd2b4cda505b7b8df769325
Signed-off-by: Raphael Collet <rco@odoo.com>
2022-03-01 10:53:51 +00:00
Julien Castiaux e655e8da8a Revert "[IMP] base: add prefetching group feature"
This reverts commit 041fe5e21e.

Part-of: odoo/odoo#85199
2022-02-23 12:59:52 +00:00
Rémy Voet (ryv) 041fe5e21e [IMP] base: add prefetching group feature
The field prefetching mechanism was poorly customizable.  Before this,
we could only tell if a field was prefetched with other fields or not at
all.  We have no way to inform the framework, like: "When I need data of
that field, prefetch these other fields, which are likely be used in the
same transaction".

From now on, the `prefetch` attribute is used as a grouping key for
prefetching fields.  When a field is fetched, all the fields with the
same value for `prefetch` are taken for prefetching.

For example, consider a small set of fields that are rarely used, except
for one flow A using them.  You want to prefetch those fields only in
the flow A, and you want to fetch them in a single query.  With the new
feature, simply set `prefetch=A` for some string `A` on those fields,
and they will be grouped for prefetching.

Part-of: odoo/odoo#83818
2022-02-23 10:01:59 +00:00
Yannick TivisseandRémy Voet c983ae0fa2 [FIX] core: update cache on parent_path when updated
Purpose
=======

The parent_path is invalidated from cache when computing it from scratch
(with method `_parent_store_compute`) but not when it is recomputed
(method `_parent_store_update`).

The issue is that a computed field depending on 'parent_path'
won't receive the updated value using the cache, and could lead
to inconsistencies if the developer is not aware of that.

Specification
=============

On `_parent_store_update`, set the new value of 'parent_path' in the
cache before marking the records as modified.

task-2766452

closes odoo/odoo#84795

X-original-commit: 861854a7598f31c0b971560484e3eb576af006b4
Signed-off-by: Raphael Collet <rco@odoo.com>
Signed-off-by: Rémy Voet <ryv@odoo.com>
Co-authored-by: Rémy Voet <ryv@odoo.com>
2022-02-17 16:41:47 +00:00
Victor Feyens 65baadeb75 [IMP] core: do not try to translate selection values for nothing
When we are only trying to know accepted raw values, there is no
need to fetch the translations for the description part of the
selection tuples.

This performance problem was first observed on res.config.settings
record creation, where the values are pruned to avoid writes and
invalidation for unmodified related values (cf create override
in base/models/res_config.py & 5a4e7b711e).

After more analysis, the 'problem' also impacts record updates on
any related field (readonly or not).

* Res.config.settings performance gains:

In a database with test_main_flows (& its diverse dependencies),
this reduces by ~50% the number of queries done when triggering
the creation of res.config.settings record through the interface
(aka with all the related updatable fields receiving their current
value as create value).

For the creation of 150 res.config.settings records, with full cache
invalidation between each record creation, we notice a gain of:

* 11853 to 6303 total queries
* 11.2s to 7.5s total creation time

Part-of: odoo/odoo#83104
2022-02-08 11:07:46 +00:00
Victor Feyens 856c1691c7 [IMP] core: improve test coverage of selection fields
Part-of: odoo/odoo#83104
2022-02-08 11:07:46 +00:00
Victor Feyens 4d4108e3d1 [IMP] base: correctly prefetch currency fields on monetary updates
closes odoo/odoo#81423

Signed-off-by: Raphael Collet <rco@odoo.com>
2022-02-07 14:17:50 +00:00
Victor Feyens a6c786090c [IMP] test_new_api: test precompute monetary fields
Part-of: odoo/odoo#81423
2022-02-07 14:17:50 +00:00
Thibault Delavallée ef9429231c [IMP] various: update query counters
Notably after odoo/odoo@f9442f47eb query counters are heavily impacted
and a lot of them has lessened.

closes odoo/odoo#83832

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
2022-02-02 18:14:54 +00:00
Rémy Voet (ryv)andrco-odoo f9442f47eb [IMP] core: bulk insert
The `_create` don't batch the SQL request to insert of data which it
isn't efficient we there is a several record to create.
Batch the SQL insertion and measure performance
depending of batch size to avoid any regression (see result in the
PR: https://github.com/odoo/odoo/pull/80961).

task-2299314

closes odoo/odoo#80961

Signed-off-by: Raphael Collet <rco@odoo.com>
Co-authored-by: rco-odoo <rco@odoo.com>
2022-02-01 14:37:43 +00:00
Rémy Voet (ryv) ae41be0c5c [REM] base: remove column_format of Field class
The `column_format` of the `Field` class was unused and create useless
noise in the ORM. Then remove it and simplify some flows.

task-2735546

Part-of: odoo/odoo#82727
2022-02-01 10:54:18 +00:00
Rémy Voet (ryv) 6000179384 [REM] base: remove _sequence attribute of BaseModel
The `_sequence` was only used for the insertion of data in the DB for
the `id` value. But in Odoo, the `id` is always a `SERIAL`, then the
PostgreSQL fill already the value by the right SQL sequence. Then,
avoid doing the job of the DB by ourselves.
Sadly, we need to manage the case when we create an empty record to
always get a valid query.

task-2735546

Part-of: odoo/odoo#82727
2022-02-01 10:54:18 +00:00
Rémy Voet (ryv) 5a573c6f18 [IMP] base: don't prefetch translate field by default
Issue
-----
Via the field prefetch mechanism, when we need a value of one field
(not in cache of course), the ORM will prefetch all fields
(which has the attribute to `prefetch=True`, the default value of this
attribute is `True`) for all record ids in `_prefetch_ids`.
Then, for each translate fields (where translate is not a callable)
the ORM need to make a `LEFT JOIN` on the `ir_translation` to fetch the
translated value. For big model, it leads to a simple `SELECT` with
several `LEFT JOIN` on ir_translation but each LEFT JOIN have a cost
in the planner time (a small cost in the execution time) of PostgreSQL.

By example, for `product.template` (stock/sale/purchase installed),
there are 6 LEFT JOIN to get all translated fields (5 of this
fields are rarely used).

Proposed solution
-----------------
Deactivate the prefetch by default for all translate fields expect if
this field is the `_rec_name` of the model (which is more likely to
be used).

In the example on the `product.template`:
Without prefetching the translated fields, there is only one LEFT JOIN
(the name, which is translated but is the `_rec_name` of the model).
With the 6 translated fields to fetch, the
query takes 5 ms to plan and 2 ms to execute VS with 1 translate field,
it 1 ms to plan and 1.5 ms to execute.

Side change note
----------------
- All translate of fields of `website.seo.metadata` should be prefetch
to avoid lot of website errors (it is because, website put in cache data
in sudo before reading it without sudo)
- `description` (`mail.message.subtype`), `subject` (`mail.template`),
`body_html` (`mail.template`) should be prefetch to avoid lot of extra
query from mail module.
- `vat_label` (`res.country`) should be prefetch to avoid a extra query
for each website page.
- Increase some queryCount (when it is legit, due to `subtitle` of
`blog_post` or `description` of `event.type.ticket`, etc)

task-2738029

closes odoo/odoo#82896

Signed-off-by: Raphael Collet <rco@odoo.com>
2022-01-28 14:09:56 +00:00
Raphael Collet 4347491670 [FIX] core: unexpected display_name "False"
If a form view contains the field 'display_name', when creating a new
record, the initial value of 'display_name' is the string "False", and
that weird value also appears in the breadcrumb instead of "New".  In
order to avoid this unexpected behavior, the conversion of a value to a
display name should be False, like any other field would.

closes odoo/odoo#81788

Signed-off-by: Raphael Collet <rco@odoo.com>
2021-12-22 09:39:08 +00:00
Raphael Collet 1616d47efa [FIX] core: computed inversed fields partly assigned
This fixes inconsistencies when dealing with fields that are computed
and inversed by the same methods.

Consider two fields F1, F2 with the same compute and inverse methods.
Consider a record where we wrote on a dependency of the common compute
method.  At this point, both fields F1 and F2 are marked to be computed.

Now let us write on F1 only.  Here is what happens:
 - the write discards the computation of F1, but not F2
 - the inverse method of F1 is called:
    - the method accesses F2
       -> this calls the compute method, which assigns both F1 and F2
    - the method accesses F1
       -> the value of F1 has been replaced by the computation above

The issue comes from a combination of factors:
 - the value of F2 must be determined by the computation;
 - the computation assigns both F1 and F2;
 - the computation is done while inversing F1 (and F2).

The solution is to force the computation before actually writing on the
fields and calling their inverse methods.  Note that this is necessary
only when part of the fields computed by a common method are updated.
When all fields computed by a common method are updated, the computation
will automatically be cancelled.

closes odoo/odoo#81172

X-original-commit: fb4d6ee4ba8bcb5cd8030105ac57e9e02850bfc4
Signed-off-by: Raphael Collet <rco@odoo.com>
2021-12-09 20:26:52 +00:00
Raphael Collet 6af689fd0a [FIX] base: fix search on company depend fields
This is a bunch of fixes (65 corner cases) for the search on
company-dependent fields:

Without a default value:
- `char` fields:
    - operators `not like`/`not ilike` don't return records with unset value
    - `(..., '=', False)` doesn't return records with unset value
    - `(..., '!=', '<string>')` doesn't return records with unset value
    - `(..., 'in', [..., False])` doesn't return records with unset value
    - `(..., 'not in', value)` without `False` inside `value` doesn't return records with unset value
- `date` and `datetime` fields:
    - `(..., '!=', <Date/datetime>)` doesn't return records with unset value
    - `(..., '=', False)` doesn't return records with unset value
- `many2one` fields:
    - operators `not like`/`not ilike` don't return records with unset value
    - `(..., 'in', [..., False])` doesn't return records with unset value
    - `(..., 'not in', value)` without `False` inside `value` doesn't return records with unset value
- `boolean` fields:
    - `(..., '=', False)` and `(..., '!=', True)` don't return records with unset and `False` values
- `integer`/`float` fields:
    - `(..., '!=', <number>)` doesn't return records with unset value

With a truthy default value:
- `many2one` fields:
    - operators `not like`/`not ilike` don't return records with unset value
    - `(..., '=', False)` returns the record with the default value (which isn't `False`)
- `boolean` fields:
    - `(..., '=', False)` doesn't return records with unset and `False` value
    - `(..., '!=', False)` returns records with unset and `False` value
- `integer`/`float` fields:
    - all `(..., operator, value)` which include value 0, return all records with unset value, even if the default value does not satisfy the domain

closes odoo/odoo#80994

X-original-commit: 3e3be652ece83420782070bdb13da2c3a4930046
Signed-off-by: Raphael Collet <rco@odoo.com>
2021-12-07 17:10:48 +00:00
d04a5b5c8c [REF] core: compute fields before database insertion.
Until now, stored compute fields were computed after database insertion.
This meant that required fields should not be computed, for instance,
unless some hackish code was added to make it work.  Another trick was
to provide some default value, but this actually prevents the field for
being computed after insertion.

This commit provides a field parameter to specify that the field should
be precomputed: adding precompute=True on the field definition force the
method create() to compute its value before inserting the new record in
the database.  For the reason explained below, precomputing fields is
not always correct, and therefore the default remains to not precompute
a field.

Some stored fields must be computed after insertion, for instance:
* statistics fields computed with search/read_group/...
* fields referencing the current record (res.partner.commercial_partner_id)
* fields referencing another record that does not exist yet (think about
  records created by one2many fields)
* fields depending on the create_date/write_date/create_uid/write_uid

Those fields shouldn't be defined with precompute=True, which triggers
their computation post record creation.  This is why, by safety, we
consider the default behavior to be precompute=False.

closes odoo/odoo#80449

Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
Co-authored-by: Raphael Collet <rco@odoo.com>
Co-authored-by: Yannick Tivisse <yti@odoo.com>
2021-11-30 14:30:48 +00:00