Skip to content

[20.0][MIG] base_user_role: Migration to 20.0 - #6

Merged
alan196 merged 205 commits into
Jarsa:20.0from
Jarsa-dev:20.0-mig-base_user_role
Oct 10, 2026
Merged

alan196 merged 205 commits into
Jarsa:20.0from
Jarsa-dev:20.0-mig-base_user_role

Conversation

@alan196

@alan196 alan196 commented Oct 10, 2026

Copy link
Copy Markdown

Mirror of the upstream migration OCA#488 plus fixes:

  • Role groups are light in Odoo 20 (res.groups._get_light_group_xmlids()): a role group is a container like base.default_user_group, so it must not turn its users into regular users by itself; the light/regular tier now comes from the groups implied by the roles. The override returns the role group ids (SetDefinitions.get_id() resolves ids too) instead of creating __custom__ XML IDs with _ensure_xml_id() from a compute path, and drops the arbitrary limit=1000. The _apply_group_regular() calls on role create/unlink from the upstream PR are kept.
  • Tests: restored the original assertions of test_create_role_from_user and test_group_groups_into_role (no need to discard base.group_user_regular anymore), added test_role_group_is_light (light-only role → light_user, role implying a regular group → regular_user, group back to regular after the role is deleted) and test_user_form_role_readonly (native role radio read-only when the user has role lines, as requested in the upstream review).
  • FontAwesome icons (fa-book, fa-users, fa-gears) replaced with the 20.0 icon-font names (lock, group, settings).
  • _inherits = {"res.groups": "group_id"} # noqa: RUF012 instead of the ClassVar annotation objected to in the upstream review (ruff RUF012 is enabled by the repo pre-commit).

The upstream commits are untouched; the fixes are the two last commits.

Upstream CI is red only because of the codecov upload step (SSL handshake failure); both test jobs there report 0 failed, 0 error(s).

Tested on Odoo 20.0 (odoo/odoo 20.0, database with demo data):

  • -i base_user_role --test-enable --test-tags /base_user_role: 28 tests, 0 failed, 0 errors, no deprecation warnings.
  • -u base --test-tags /base:TestGroupsOdoo,/base:TestUsers2,/base:TestUsers,/base:TestResUsersForm with the module installed (light/regular group invariants of core): 52 tests, 0 failed, 0 errors.
  • Repo pre-commit: all hooks passed.

Assisted-by: Claude Fable 5.1

sebalix and others added 30 commits October 2, 2026 08:11
…608)

* [ADD] New module 'base_user_role'

* [FIX] base_user_role - Review

* [FIX] base_user_role - Review s/is_active/is_enabled/

* [FIX] base_user_role - Review s/is_active/is_enabled/

* [IMP] base_user_role - Translations updated (template + FR)

* [FIX] base_user_role - Lint
* FIX: Update users' groups when a role is deleted
* IMP: add some test cases to check user's groups when its last role line is removed
* FIX: ensure that the user's groups are updated when its last role line is removed
Currently translated at 100,0% (37 of 37 strings)

Translation: server-backend-11.0/server-backend-11.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-11-0/server-backend-11-0-base_user_role/da/
Updated by Update PO files to match POT (msgmerge) hook in Weblate.
the groups that have changed with respecto the role.
…users.role.line"

This reverts commit b537941d5a2d1fcb3fbacc602124c1b3f6e1d495.
LauraVForgeFlow and others added 26 commits October 2, 2026 08:11
Currently translated at 100.0% (82 of 82 strings)

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/sv/
Searching by user in the res.users.role list/search view raised
'Cannot convert res.users.role.role_user_ids to SQL because it is not
stored' because role_user_ids is a non-stored computed One2many.

Add a search method redirecting the query to the stored line_ids.user_id
relation.
Currently translated at 63.4% (52 of 82 strings)

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/de/
Currently translated at 100.0% (82 of 82 strings)

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/de/
Currently translated at 100.0% (82 of 82 strings)

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/da/
Currently translated at 91.4% (75 of 82 strings)

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/sl/
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/
The self-writable Notification field on users, which allows choosing
to be notified by email or via push, actually sets or unsets a group on
the user. This would conflict with role-based management of groups: when
setting push notification, the field would be set and push notification
received but the group would immediately be reset by base_user_role
(and some UI element would not be shown) and, in some case like module
activation, the notification type would be reset to mail, the field
being recomputed from the user's groups.

This change excludes the group in question form being unset or set by
roles. Exclusions can be added by overriding a method.
This commit adds a new feature to the module: New button to
open wizard so it's possible to select multiple
users at once, keeping the ability to choose the dates.
…_type

Deleting all roles from a user with notification type 'inbox' may trigger

```
ERROR: new row for relation "res_users" violates check constraint "res_users_notification_type"
```

because notification_type `inbox` is not allowed when the user is no longer has
the share flag (which is set for users without group base.group_user).

The issue is only triggered due to intermittent flushes, such as the one caused
by website's `_check_one_user_type` code constraint on res.users.

Fixes

```
  File "/home/odoo/src/oca-server-backend/base_user_role/tests/test_user_role.py", line 312, in test_notification_type_reset
    user.write(
  File "/home/odoo/odoo/18.0/odoo/addons/auth_totp_mail/models/res_users.py", line 11, in write
    res = super().write(vals)
          ^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/addons/auth_signup/models/res_users.py", line 362, in write
    return super().write(vals)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/addons/mail/models/discuss/res_users.py", line 17, in write
    res = super().write(vals)
          ^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/addons/mail/models/res_users.py", line 108, in write
    write_res = super(Users, self).write(vals)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/addons/resource/models/res_users.py", line 17, in write
    rslt = super().write(vals)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/src/oca-server-backend/base_user_role/models/user.py", line 60, in write
    res = super().write(vals)
          ^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/addons/base/models/res_users.py", line 1920, in write
    res = super(UsersView, self).write(values)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/addons/base/models/res_users.py", line 1609, in write
    return super(UsersImplied, self).write(values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/addons/base/models/res_users.py", line 759, in write
    res = super(Users, self).write(values)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 4812, in write
    field.write(self, value)
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 4552, in write
    self.write_batch([(records, value)])
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 4573, in write_batch
    self.write_real(records_commands_list, create)
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 4769, in write_real
    flush()
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 4720, in flush
    comodel.browse(to_delete).unlink()
  File "/home/odoo/src/oca-server-backend/base_user_role/models/role.py", line 180, in unlink
    users.set_groups_from_roles(force=True)
  File "/home/odoo/src/oca-server-backend/base_user_role/models/user.py", line 121, in set_groups_from_roles
    super(ResUsers, user).write(vals)
  File "/home/odoo/odoo/18.0/odoo/odoo/addons/base/models/res_users.py", line 1920, in write
    res = super(UsersView, self).write(values)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/addons/base/models/res_users.py", line 1611, in write
    res = super(UsersImplied, self.with_context(no_add_implied_groups=True)).write(values)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/addons/base/models/res_users.py", line 759, in write
    res = super(Users, self).write(values)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 4834, in write
    real_recs._validate_fields(vals, inverse_fields)
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 1631, in _validate_fields
    check(self)
  File "/home/odoo/odoo/18.0/odoo/addons/website/models/res_users.py", line 105, in _check_one_user_type
    internal_users = self.env.ref('base.group_user').users & self
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 3102, in __get__
    return super().__get__(records, owner)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 1274, in __get__
    recs._fetch_field(self)
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 4119, in _fetch_field
    self.fetch(fnames)
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 4157, in fetch
    fetched = self._fetch_query(query, fields_to_fetch)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 4268, in _fetch_query
    field.read(fetched)
  File "/home/odoo/odoo/18.0/odoo/odoo/fields.py", line 5063, in read
    for id1, id2 in records.env.execute_query(query.select(sql_id1, sql_id2)):
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/odoo/odoo/18.0/odoo/odoo/api.py", line 992, in execute_query
    self.flush_query(query)
  File "/home/odoo/odoo/18.0/odoo/odoo/api.py", line 984, in flush_query
    self[model_name].flush_model(field_names)
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 6773, in flush_model
    self._flush(fnames)
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 6851, in _flush
    model.browse(some_ids)._write_multi(vals_list)
  File "/home/odoo/odoo/18.0/odoo/odoo/models.py", line 4937, in _write_multi
    self.env.execute_query(SQL(
  File "/home/odoo/odoo/18.0/odoo/odoo/api.py", line 993, in execute_query
    self.cr.execute(query)
  File "/home/odoo/odoo/18.0/odoo/odoo/sql_db.py", line 364, in execute
    res = self._obj.execute(query, params)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.CheckViolation: new row for relation "res_users" violates check constraint "res_users_notification_type"
DETAIL:  Failing row contains (81, 1, 151, t, 2026-02-10 15:41:40.105622, user_test_roles, null, null, 1, 84, <p data-o-mail-quote="1">--<br data-o-mail-quote="1">USER TEST (..., t, 2026-02-10 15:41:40.105622, null, f, inbox, null, null, null, null).
```
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/
Per the `create` and `write` methods on the `res.users` model [^1],
`base.group_multi_company` group membership is a derivative of
company membership.

[^1]: https://github.com/odoo/odoo/blob/2c2641a0d725e3548328e756fd8b9301308a3f6c/odoo/addons/base/models/res_users.py#L1902-L1930
Currently translated at 100.0% (86 of 86 strings)

Translation: server-backend-19.0/server-backend-19.0-base_user_role
Translate-URL: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_user_role/it/
Assisted-by: OpenAI Codex
In Odoo 20 every group without a privilege becomes a "regular" group
(`res.groups._apply_group_regular()`), so a role group turned all its
users into regular users even when the role only implied light groups,
and `base.group_user_regular` leaked into the role's `implied_ids`.

Role groups are containers, like `base.default_user_group`: declare them
in `_get_light_group_xmlids()` so the light/regular tier of a user comes
from the groups implied by its roles. Return the group ids instead of
creating `__custom__` XML IDs with `_ensure_xml_id()`: `SetDefinitions`
resolves ids as well, nothing is written from a compute path and the
arbitrary `limit=1000` is not needed (the domain on `role_id` also keeps
pylint-odoo `no-search-all` quiet).

Restore the original assertions in `test_create_role_from_user` and
`test_group_groups_into_role` (no need to discard
`base.group_user_regular` anymore) and add tests for the light/regular
behaviour and for the read-only `role` radio on the user form.

Assisted-by: Claude Fable 5.1
FontAwesome is gone from the web client in 20.0: use the Material
Symbols names of the Odoo icon font (`lock`, as the Access Rights
button of the group form, `group` and `settings`).

Keep the plain `_inherits = {...}` syntax used across the Odoo code
base; ruff RUF012 (enabled by the repo pre-commit) is silenced inline.

Assisted-by: Claude Fable 5.1
@jarsabot

Copy link
Copy Markdown
    **WARNING**: Could not link to Odoo task. Record ID not found in the title of the MR, supported formats:
{ record type }#{ number }

Available types: 'task#', 't#' (case insensitive)

Examples:

  • task#123

  • i#123

@codecov-commenter

codecov-commenter commented Oct 10, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (20.0@4d18107). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             20.0       #6   +/-   ##
=======================================
  Coverage        ?   89.39%           
=======================================
  Files           ?       12           
  Lines           ?      509           
  Branches        ?       39           
=======================================
  Hits            ?      455           
  Misses          ?       44           
  Partials        ?       10           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The default user template implies regular groups as soon as sales or
purchase are installed, so the test created its light user explicitly
with the internal group only.

Assisted-by: Claude Fable 5.1
@alan196
alan196 merged commit 2b676e3 into Jarsa:20.0 Oct 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.