Thursday, September 17, 2026
11 changes · master
Enhancements to existing features
Time off entries now display as full visual bars for half-day and full-day absences in monthly and quarterly Gantt views. This makes leave schedules easier to read at a glance and reduces confusion when reviewing team availability.
Original PR description
**What:** - Use precision to show full pill for half-day events in gantt view for month and quarter view task-6361123
The employee kanban view in Payroll has been adjusted to resolve usability issues. This makes employee information easier to review and helps HR teams work more smoothly from the employee overview.
Original PR description
task-id: 6530918 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Half-day time off entries now appear as full, easy-to-see blocks in monthly and quarterly Gantt planning views. This makes leave schedules clearer for managers and employees, reducing the chance of overlooking shorter absences.
Original PR description
**What:** - Use precision to show full pill for half-day events in gantt view for month and quarter view task-6361123
This update strengthens checks around spreadsheet shortcut handling in Documents, helping prevent shortcut issues that could disrupt key document workflows. It improves reliability where shortcuts are important for day-to-day functionality.
Original PR description
*Note: only the last commit really belongs to this PR.* Increase integrity of shortcuts where it is critical for functionality. Task-4266789
The web testing tools now match form values exactly by default, reducing false matches in automated checks. Teams can still use flexible pattern matching when partial or case-insensitive matching is needed.
Original PR description
This commit changes the ':value' pseudo-selector to only match the exact value of the matching nodes, instead of only matching a substring. The motivation behind this change is that, contrary to ':contains' wich evaluates the text content of a node (which can itself be affected by other nodes which are not taken account for), ':value' only evaluates the "value" property of the node, which consists of a single string. As such, in most cases, the entire value should already known in advance, and having an exact match would then make sense as a default. In edge cases where this is not wanted, ':value' can still be given a regular expression (surrounded by //), which then accepts any kind of match (partial, exact, case (in)sensitive, etc.). --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
Belgian payroll now includes a dedicated work entry type for employees returning on a reduced schedule after more than 12 months of illness. This helps classify the absence correctly as unassimilated for payroll processing.
Original PR description
In Belgian payroll, when an employee returns to work on a reduced working schedule due to illness for more than 12 months, the absence becomes unassimilated. Add the work entry type 'Illness half-time>12m - workers initiative' (LEAVE12305) to support unassimilated partial illness after 12 months. Task-ID: 6484739 --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
This change improves Odoo's internal test infrastructure by allowing repeated test data to be created once and reused safely across related tests. It should reduce time spent running large test suites, especially for accounting tests, helping developers validate changes faster without changing end-user functionality.
Original PR description
With the abundance of tests we are accumulating, common test classes have become the go to way to make test data readily available for all of your module's tests and/or their dependent modules.…
The web debug dialog now includes a one-click option to copy the fully computed view structure. This makes it easier for support teams, consultants, and developers to diagnose view issues, share details in tickets, and compare environments without error-prone manual selection.
Original PR description
Description of the issue/feature this PR addresses: The "Computed Arch" debug dialog displays the fully computed view architecture, after inheritance, xpaths and studio customizations have been…
Description of the issue/feature this PR addresses: The "Computed Arch" debug dialog displays the fully computed view architecture, after inheritance, xpaths and studio customizations have been applied, in a read-only pre block. Extracting that text previously required manually selecting it in the browser, which is unreliable for long or deeply indented XML. Being able to copy this text to the clipboard helps with several common scenarios: - Debugging inherited views: when a view looks wrong and it is not clear which of several inheriting modules caused it, the computed arch can be diffed against the base view's arch to see exactly what changed. - Writing new xpath inherit rules: the exact final structure (attribute values, node ordering) is needed to write a correct expr="//field[@name='x']", which is easier to do in a scratch file or editor with search than by eyeballing a read-only dialog. - Bug reports and support tickets: the computed arch can be pasted into a Slack message, GitHub issue or support ticket so others can inspect the view without needing access to the database. - Studio customizations: consultants often need the underlying computed arch to understand why a Studio-added field appears in an unexpected position. - Comparing environments: the arch from a customer's staging instance can be diffed against production to spot config drift, e.g. a view customization made directly on prod. Current behavior before PR: In the "Computed Arch" debug dialog, the only way to get the arch text out of the browser is to manually click-drag to select it inside the pre block, then copy it. This is error-prone for long or deeply indented XML, where whitespace and line breaks are easy to select incorrectly or truncate. Desired behavior after PR is merged: A "Copy Arch" button is added next to "Close" in the dialog's footer. Clicking it copies the full computed arch to the clipboard in one click, with a brief "Copied" confirmation, removing the need to manually select the text. --- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Forward-Port-Of: odoo/odoo#286894 Forward-Port-Of: odoo/odoo#286619
Calendar now includes a reusable default reminder that can support appointment types without extra setup. Attendee fields also show a clearer “Unassigned” label instead of the more technical “Undefined Attendee,” making calendar screens easier to understand.
Original PR description
This PR adds a calendar.alarm. This one is required as default alarm for the appointment types. To avoid creating a new data file and updating the manifest, this change is not done appointment but in calendar as it can already be useful in that app. To avoid a technical label, this PR replaces the default falsy label of CalendarEvent.partner_ids, which is "Undefined Attendee", by "Unassigned". Enterprise PR: https://github.com/odoo/enterprise/pull/131714 Task-6576867
This update advances the master branch release version to 20.1 alpha. It helps prepare the platform for the next development and testing cycle without changing day-to-day business functionality.
The payment popover now validates the information it receives more strictly and includes a new automated test to confirm payment details display correctly. This reduces the risk of display issues in accounting screens and improves confidence in future changes.
Original PR description
Introduces a strict `useProps` schema to the popover and adds a hoot test to verify the popover shows the data correctly. no task-id
With the abundance of tests we are accumulating, common test classes
have become the go to way to make test data readily available for all of
your module's tests and/or their dependent modules.
Unfortunately the cost of some of the `setUpClass` implementation has
also been rising as tests require more and more data.
As an example `AccountTestInvoicingCommon` which, with it's override of
`BaseCommon`'s `setUpClass` methods, is called 170 times during the
split from `account` to `auth_signup`.
With each call taking multiple seconds. This is a significant time loss
as (most of the time) the data generated is exactly the same from one
run to another.
(stats collected using `master-profile-setupclass-wbr` branch on runbot)
We realized the following:
- We can run the whole test suite within a singular transaction without
it being much more costly.
- We could provide a way for common classes to define data that could
be shared between test classes that share the same properties.
The changes only concern `odoo.tests.common.TransactionCase` and their
children.
### Transaction Strategy
Previously `TransactionCase`'s transactions would look like this:
(each box representing a transaction, with nested boxes being savepoints)
```
┌─ TestCaseA.setUpClass ─────────────────────────────────┐
│┌─ TestCaseA.setUp (test_foo) ─────────────────────────┐│
││ test_foo() ││
│└──────────────────────────────────────────────────────┘│
│┌─ TestCaseA.setUp (test_bar) ─────────────────────────┐│
││ test_bar() ││
│└──────────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────────┘
┌─ TestCaseB.setUpClass ─────────────────────────────────┐
│┌─ TestCaseB.setUp (test_baz) ─────────────────────────┐│
││ test_baz() ││
│└──────────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────────┘
```
This will also be the case if no `setUpCommonData` is defined on your test class.
However, if `setUpCommonData` IS defined on your class, or a parent class, the
following plan will be executed instead:
Sample python code
```python
from odoo.tests.common import TransactionCase
class ModuleCommon(TransactionCase):
@classmethod
def setUpCommonData(cls):
cls.data = cls.env['module.model'].create({'foo': 'bar'})
class ModuleTestA(ModuleCommon):
def test_bar(self):
self.assertEqual(self.data.foo, 'bar')
class ModuleTestB(ModuleCommon):
def test_foo(self):
self.data.foo = 'foo'
self.assertEqual(self.data.foo, 'foo')
class ModuleCommonExtended(ModuleCommon):
@classmethod
def setUpCommonData(cls):
cls.data_2 = cls.env['module.model'].create({'foo': 'baz'})
class ModuleTestC(ModuleCommonExtended):
def test_baz(self):
self.assertEqual(self.data.foo, 'bar')
self.assertEqual(self.data_2.foo, 'baz')
```
```
┌─ ModuleCommon.setUpCommonData ───────────────────────────┐
│ │
│┌─ ModuleTestA.setUpClass ───────────────────────────────┐│
││┌─ ModuleTestA.setUp (test_bar) ───────────────────────┐││
│││ test_bar() │││
││└──────────────────────────────────────────────────────┘││
│└────────────────────────────────────────────────────────┘│
│ │
│┌─ ModuleTestB.setUpClass ───────────────────────────────┐│
││┌─ ModuleTestB.setUp (test_foo) ───────────────────────┐││
│││ test_foo() │││
││└──────────────────────────────────────────────────────┘││
│└────────────────────────────────────────────────────────┘│
│ │
│┌─ ModuleCommonExtended.setUpCommonData ─────────────────┐│
││┌─ TestCaseC.setUpClass ───────────────────────────────┐││
│││┌─ TestCaseC.setUp (test_baz) ───────────────────────┐│││
││││ test_baz() ││││
│││└────────────────────────────────────────────────────┘│││
││└──────────────────────────────────────────────────────┘││
│└────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────┘
```
### Class selection and dependency strategy
One of our goals for `setUpCommonData` is that it could pretty much be a
drop-in replacement for `setUpClass`.
As indicated by the schema above however, it is not really compatible with our
new system, we want to be able to single out `setUpCommonData` for tests that
rely on that singular common class, which is why calling `super` is NOT allowed
within `setUpCommonData` (or at least calling `super().setUpCommonData`).
We take care of picking the class on which to call `setUpCommonData` and for
you, while the order is defined by the test class's `__mro__`.
Some common classes also provide methods that can be overwritten to change the
data generated by `setUpClass`, for which we provide the decorator
`odoo.tests.common.data_depends`.
`data_depends` takes a list of strings that are used as attributes to generate
a hash for our class; these can be regular attributes or methods (note that we
do not call the method, we simply store the reference to it).
The hash key for a class is defined as follows:
```python
tuple(
tuple(
Type[TransactionCase], # The class that defined `setUpCommonData`
Tuple[Any], # The dependency data
Type[TransactionCase], # The class that will be used to call `setUpCommonData`
),
...
)
```
The dependency data is built by iterating over the `__mro__` of said class and
adding each new definition for each attribute defined by `@data_depends`.
The class to install is the first class in the `__mro__` which has different
dependency data, with the added condition that it must be a subclass of the
previous installed class if there is one.
Consider the following:
```python
from odoo.tests.common import TransactionCase, data_depends
class ModuleCommon(TransactionCase):
_name = 'foo'
@data_depends('_name')
@classmethod
def setUpCommonData(cls):
cls.data = cls.env['module.model'].create({'name': cls._name})
class ModuleTestA(ModuleCommon):
def test_data_name(self):
self.assertEqual(self.data.name, self._name)
class ModuleTestB(ModuleCommon):
_name = 'bar'
def test_data_name(self):
self.assertEqual(self.data.name, self._name)
class ModuleTestC(ModuleCommon):
_name = 'bar'
def test_data_name(self):
self.assertEqual(self.data.name, self._name)
```
Whilst all classes share the exact same `__mro__`, their dependencies differ.
This will of course cause a split as all test can not be ran within the same
transaction/savepoint.
```
┌─ ModuleCommon.setUpCommonData (_name = 'foo') ───────────────────────────────┐
│ │
│┌─ ModuleTestA.setUpClass ───────────────────────────────────────────────────┐│
││┌─ ModuleTestA.setUp (test_data_name) ─────────────────────────────────────┐││
│││ test_data_name() │││
││└──────────────────────────────────────────────────────────────────────────┘││
│└────────────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────────┘
┌─ ModuleCommon.setUpCommonData (_name = 'bar', install on ModuleTestB) ───────┐
│ │
│┌─ ModuleTestB.setUpClass ───────────────────────────────────────────────────┐│
││┌─ ModuleTestB.setUp (test_data_name) ─────────────────────────────────────┐││
│││ test_data_name() │││
││└──────────────────────────────────────────────────────────────────────────┘││
│└────────────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────────┘
┌─ ModuleCommon.setUpCommonData (_name = 'bar', install on ModuleTestC) ───────┐
│ │
│┌─ ModuleTestC.setUpClass ───────────────────────────────────────────────────┐│
││┌─ ModuleTestC.setUp (test_data_name) ─────────────────────────────────────┐││
│││ test_data_name() │││
││└──────────────────────────────────────────────────────────────────────────┘││
│└────────────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────────┘
```
The system takes care of organizing and ordering tests in a way to reuse data as
much as possible (according to the dependencies defined on the tests).
NOTE: While we do re-order tests we still follow `test_sequence` if defined.
#### Edge cases
A)
Consider the following:
```python
from odoo.tests.common import TransactionCase
class ModuleACommon(TransactionCase):
@classmethod
def setUpCommonData(cls):
cls.module_a_data = cls.env['module_a.model'].create({'name': 'foo'})
class ModuleBCommon(TransactionCase):
@classmethod
def setUpCommonData(cls):
cls.env = cls.env(context={'some_context': 'bar'}) # This could be creating+changing company
cls.module_b_data = cls.env['module_b.model'].create({'name': 'bar'})
class ModuleCTestA(ModuleACommon, ModuleBCommon):
def test_data(self):
self.assertEqual(module_a_data.name, 'foo')
class ModuleCTestB(ModuleACommon, ModuleBCommon):
def test_data(self):
self.assertEqual(module_b_data.name, 'bar')
```
`ModuleCTestA` and `ModuleCTestB` actually share the same `__mro__` _AND_
dependencies.
In this case we can also note that `ModuleBCommon` will have an impact on
`ModuleACommon` due to changing the environment (according to `__mro__`).
This is the reason we include the install class in our hash key, the
dependencies would otherwise be the exact same between our test cases, but we
in fact need to know which class the data was installed upon to make sure the
data is available.
```
┌─ ModuleBCommon.setUpCommonData (install on ModuleCTestA) ───────┐
│┌─ ModuleACommon.setUpCommonData (install on ModuleCTestA) ─────┐│
││ ││
││┌─ ModuleCTestA.setUpClass ───────────────────────────────────┐││
│││┌─ ModuleCTestA.setUp (test_data) ──────────────────────────┐│││
││││ test_data() ││││
│││└───────────────────────────────────────────────────────────┘│││
││└─────────────────────────────────────────────────────────────┘││
│└───────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
┌─ ModuleBCommon.setUpCommonData (install on ModuleCTestB) ───────┐
│┌─ ModuleACommon.setUpCommonData (install on ModuleCTestB) ─────┐│
││ ││
││┌─ ModuleCTestB.setUpClass ───────────────────────────────────┐││
│││┌─ ModuleCTestB.setUp (test_data) ──────────────────────────┐│││
││││ test_data() ││││
│││└───────────────────────────────────────────────────────────┘│││
││└─────────────────────────────────────────────────────────────┘││
│└───────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
```
B)
Consider the following:
```python
from odoo.tests.common import TransactionCase
class ModuleCommon(TransactionCase):
@classmethod
def setUpCommonData(cls):
cls.data = {
'company': cls.env['res.company'].create({'name': 'test'})
}
class ModuleTestA(ModuleCommon):
test_sequence = 0
def test_foo(self):
self.data['company'] = self.env['res.company'].create({'name': 'test_2'})
class ModuleTestB(ModuleCommon):
test_sequence = 1
def test_bar(self):
self.assertEqual(self.data['company'].name, 'test')
```
The above code will actually result in a cache error; the company referenced
within `cls.data` does not exist anymore.
It is also not possible to add a dependency on `setUpCommonData` as `data` does
not exist outside of running the tests.
It is not recommended to use dictionaries within `setUpCommonData` (or use
frozen ones) as they can not be rollbacked, one should stick to orm records.