Initial commit: Test packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:52 +02:00
commit 080accd21c
338 changed files with 32413 additions and 0 deletions

View file

@ -0,0 +1,48 @@
# Test - Base Automation
This module contains tests related to base automation. Those are
present in a separate module as it contains models used only to perform
tests independently to functional aspects of other models.
## Installation
```bash
pip install odoo-bringout-oca-ocb-test_base_automation
```
## Dependencies
This addon depends on:
- base_automation
## Manifest Information
- **Name**: Test - Base Automation
- **Version**: 1.0
- **Category**: Hidden
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_base_automation`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Test_base_automation Module - test_base_automation
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for test_base_automation. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [base_automation](../../odoo-bringout-oca-ocb-base_automation)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon test_base_automation or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-ocb-test_base_automation"
# or
uv pip install odoo-bringout-oca-ocb-test_base_automation"
```

View file

@ -0,0 +1,15 @@
# Models
Detected core models and extensions in test_base_automation.
```mermaid
classDiagram
class base_automation_lead_test
class base_automation_line_test
class base_automation_linked_test
class base_automation_link_test
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: test_base_automation. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon test_base_automation
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,34 @@
# Security
Access control and security definitions in test_base_automation.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../test_base_automation/security/ir.model.access.csv)**
- 6 model access rules
## Record Rules
Row-level security rules defined in:
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../test_base_automation/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon test_base_automation
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,42 @@
[project]
name = "odoo-bringout-oca-ocb-test_base_automation"
version = "16.0.0"
description = "Test - Base Automation - Base Automation Tests: Ensure Flow Robustness"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-base_automation>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["test_base_automation"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Test - Base Automation',
'version': '1.0',
'category': 'Hidden',
'sequence': 9877,
'summary': 'Base Automation Tests: Ensure Flow Robustness',
'description': """This module contains tests related to base automation. Those are
present in a separate module as it contains models used only to perform
tests independently to functional aspects of other models.""",
'depends': ['base_automation'],
'data': [
'security/ir.model.access.csv',
],
'installable': True,
'license': 'LGPL-3',
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_base_automation

View file

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil import relativedelta
from odoo import fields, models, api
class LeadTest(models.Model):
_name = "base.automation.lead.test"
_description = "Automated Rule Test"
name = fields.Char(string='Subject', required=True)
user_id = fields.Many2one('res.users', string='Responsible')
state = fields.Selection([('draft', 'New'), ('cancel', 'Cancelled'), ('open', 'In Progress'),
('pending', 'Pending'), ('done', 'Closed')],
string="Status", readonly=True, default='draft')
active = fields.Boolean(default=True)
partner_id = fields.Many2one('res.partner', string='Partner')
date_action_last = fields.Datetime(string='Last Action', readonly=True)
employee = fields.Boolean(compute='_compute_employee_deadline', store=True)
line_ids = fields.One2many('base.automation.line.test', 'lead_id')
priority = fields.Boolean()
deadline = fields.Boolean(compute='_compute_employee_deadline', store=True)
is_assigned_to_admin = fields.Boolean(string='Assigned to admin user')
@api.depends('partner_id.employee', 'priority')
def _compute_employee_deadline(self):
# this method computes two fields on purpose; don't split it
for record in self:
record.employee = record.partner_id.employee
if not record.priority:
record.deadline = False
else:
record.deadline = record.create_date + relativedelta.relativedelta(days=3)
def write(self, vals):
result = super().write(vals)
# force recomputation of field 'deadline' via 'employee': the action
# based on 'deadline' must be triggered
self.mapped('employee')
return result
class LineTest(models.Model):
_name = "base.automation.line.test"
_description = "Automated Rule Line Test"
name = fields.Char()
lead_id = fields.Many2one('base.automation.lead.test', ondelete='cascade')
user_id = fields.Many2one('res.users')
class ModelWithAccess(models.Model):
_name = "base.automation.link.test"
_description = "Automated Rule Link Test"
name = fields.Char()
linked_id = fields.Many2one('base.automation.linked.test', ondelete='cascade')
class ModelWithoutAccess(models.Model):
_name = "base.automation.linked.test"
_description = "Automated Rule Linked Test"
name = fields.Char()
another_field = fields.Char()
class Project(models.Model):
_name = _description = 'test_base_automation.project'
name = fields.Char()
task_ids = fields.One2many('test_base_automation.task', 'project_id')
class Task(models.Model):
_name = _description = 'test_base_automation.task'
name = fields.Char()
parent_id = fields.Many2one('test_base_automation.task')
project_id = fields.Many2one(
'test_base_automation.project',
compute='_compute_project_id', recursive=True, store=True, readonly=False,
)
@api.depends('parent_id.project_id')
def _compute_project_id(self):
for task in self:
if not task.project_id:
task.project_id = task.parent_id.project_id

View file

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_base_automation_lead_test,access_base_automation_lead_test,model_base_automation_lead_test,base.group_system,1,1,1,1
access_base_automation_line_test,access_base_automation_line_test,model_base_automation_line_test,base.group_system,1,1,1,1
access_base_automation_link_test,access_base_automation_link_test,model_base_automation_link_test,,1,1,1,1
access_base_automation_linked_test,access_base_automation_linked_test,model_base_automation_linked_test,,1,1,1,1
access_test_base_automation_project,access_test_base_automation_project,model_test_base_automation_project,,1,1,1,1
access_test_base_automation_task,access_test_base_automation_task,model_test_base_automation_task,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_base_automation_lead_test access_base_automation_lead_test model_base_automation_lead_test base.group_system 1 1 1 1
3 access_base_automation_line_test access_base_automation_line_test model_base_automation_line_test base.group_system 1 1 1 1
4 access_base_automation_link_test access_base_automation_link_test model_base_automation_link_test 1 1 1 1
5 access_base_automation_linked_test access_base_automation_linked_test model_base_automation_linked_test 1 1 1 1
6 access_test_base_automation_project access_test_base_automation_project model_test_base_automation_project 1 1 1 1
7 access_test_base_automation_task access_test_base_automation_task model_test_base_automation_task 1 1 1 1

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_flow

View file

@ -0,0 +1,462 @@
# # -*- coding: utf-8 -*-
# # Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
import sys
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
from odoo.tests import common, tagged
from odoo.exceptions import AccessError
@tagged('post_install', '-at_install')
class BaseAutomationTest(TransactionCaseWithUserDemo):
def setUp(self):
super(BaseAutomationTest, self).setUp()
self.user_root = self.env.ref('base.user_root')
self.user_admin = self.env.ref('base.user_admin')
self.test_mail_template_automation = self.env['mail.template'].create({
'name': 'Template Automation',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'body_html': """<div>Email automation</div>""",
})
self.res_partner_1 = self.env['res.partner'].create({'name': 'My Partner'})
self.env['base.automation'].create([
{
'name': 'Base Automation: test rule on create',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_create',
'active': True,
'filter_domain': "[('state', '=', 'draft')]",
}, {
'name': 'Base Automation: test rule on write',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_write',
'active': True,
'filter_domain': "[('state', '=', 'done')]",
'filter_pre_domain': "[('state', '=', 'open')]",
}, {
'name': 'Base Automation: test rule on recompute',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_write',
'active': True,
'filter_domain': "[('employee', '=', True)]",
}, {
'name': 'Base Automation: test recursive rule',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': """
record = model.browse(env.context['active_id'])
if 'partner_id' in env.context['old_values'][record.id]:
record.write({'state': 'draft'})""",
'trigger': 'on_write',
'active': True,
}, {
'name': 'Base Automation: test rule on secondary model',
'model_id': self.env.ref('test_base_automation.model_base_automation_line_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_create',
'active': True,
}, {
'name': 'Base Automation: test rule on write check context',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': """
record = model.browse(env.context['active_id'])
if 'user_id' in env.context['old_values'][record.id]:
record.write({'is_assigned_to_admin': (record.user_id.id == 1)})""",
'trigger': 'on_write',
'active': True,
}, {
'name': 'Base Automation: test rule with trigger',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'trigger_field_ids': [(4, self.env.ref('test_base_automation.field_base_automation_lead_test__state').id)],
'state': 'code',
'code': """
record = model.browse(env.context['active_id'])
record['name'] = record.name + 'X'""",
'trigger': 'on_write',
'active': True,
}, {
'name': 'Base Automation: test send an email',
'mail_post_method': 'email',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'template_id': self.test_mail_template_automation.id,
'trigger_field_ids': [(4, self.env.ref('test_base_automation.field_base_automation_lead_test__deadline').id)],
'state': 'mail_post',
'code': """
record = model.browse(env.context['active_id'])
record['name'] = record.name + 'X'""",
'trigger': 'on_write',
'active': True,
'filter_domain': "[('deadline', '!=', False)]",
'filter_pre_domain': "[('deadline', '=', False)]",
}
])
def tearDown(self):
super().tearDown()
self.env['base.automation']._unregister_hook()
def create_lead(self, **kwargs):
vals = {
'name': "Lead Test",
'user_id': self.user_root.id,
}
vals.update(kwargs)
return self.env['base.automation.lead.test'].create(vals)
def test_00_check_to_state_open_pre(self):
"""
Check that a new record (with state = open) doesn't change its responsible
when there is a precondition filter which check that the state is open.
"""
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open')
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
def test_01_check_to_state_draft_post(self):
"""
Check that a new record changes its responsible when there is a postcondition
filter which check that the state is draft.
"""
lead = self.create_lead()
self.assertEqual(lead.state, 'draft', "Lead state should be 'draft'")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on creation of Lead with state 'draft'.")
def test_02_check_from_draft_to_done_with_steps(self):
"""
A new record is created and goes from states 'open' to 'done' via the
other states (open, pending and cancel). We have a rule with:
- precondition: the record is in "open"
- postcondition: that the record is "done".
If the state goes from 'open' to 'done' the responsible is changed.
If those two conditions aren't verified, the responsible remains the same.
"""
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open', "Lead state should be 'open'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
# change state to pending and check that responsible has not changed
lead.write({'state': 'pending'})
self.assertEqual(lead.state, 'pending', "Lead state should be 'pending'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.")
# change state to done and check that responsible has not changed
lead.write({'state': 'done'})
self.assertEqual(lead.state, 'done', "Lead state should be 'done'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.")
def test_03_check_from_draft_to_done_without_steps(self):
"""
A new record is created and goes from states 'open' to 'done' via the
other states (open, pending and cancel). We have a rule with:
- precondition: the record is in "open"
- postcondition: that the record is "done".
If the state goes from 'open' to 'done' the responsible is changed.
If those two conditions aren't verified, the responsible remains the same.
"""
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open', "Lead state should be 'open'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
# change state to done and check that responsible has changed
lead.write({'state': 'done'})
self.assertEqual(lead.state, 'done', "Lead state should be 'done'")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on write of Lead with state from 'open' to 'done'.")
def test_10_recomputed_field(self):
"""
Check that a rule is executed whenever a field is recomputed after a
change on another model.
"""
partner = self.res_partner_1
partner.write({'employee': False})
lead = self.create_lead(state='open', partner_id=partner.id)
self.assertFalse(lead.employee, "Customer field should updated to False")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.")
# change partner, recompute on lead should trigger the rule
partner.write({'employee': True})
self.env.flush_all()
self.assertTrue(lead.employee, "Customer field should updated to True")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on write of Lead when Customer becomes True.")
def test_11_recomputed_field(self):
"""
Check that a rule is executed whenever a field is recomputed and the
context contains the target field
"""
partner = self.res_partner_1
lead = self.create_lead(state='draft', partner_id=partner.id)
self.assertFalse(lead.deadline, 'There should not be a deadline defined')
# change priority and user; this triggers deadline recomputation, and
# the server action should set the boolean field to True
lead.write({'priority': True, 'user_id': self.user_root.id})
self.assertTrue(lead.deadline, 'Deadline should be defined')
self.assertTrue(lead.is_assigned_to_admin, 'Lead should be assigned to admin')
def test_11b_recomputed_field(self):
mail_automation = self.env['base.automation'].search([('name', '=', 'Base Automation: test send an email')])
send_mail_count = 0
def _patched_get_actions(*args, **kwargs):
obj = args[0]
if '__action_done' not in obj._context:
obj = obj.with_context(__action_done={})
return mail_automation.with_env(obj.env)
def _patched_send_mail(*args, **kwargs):
nonlocal send_mail_count
send_mail_count += 1
patchers = [
patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._get_actions', _patched_get_actions),
patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail),
]
self.startPatcher(patchers[0])
lead = self.create_lead()
self.assertFalse(lead.priority)
self.assertFalse(lead.deadline)
self.startPatcher(patchers[1])
lead.write({'priority': True})
self.assertTrue(lead.priority)
self.assertTrue(lead.deadline)
self.assertEqual(send_mail_count, 1)
def test_12_recursive(self):
""" Check that a rule is executed recursively by a secondary change. """
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open')
self.assertEqual(lead.user_id, self.user_root)
# change partner; this should trigger the rule that modifies the state
partner = self.res_partner_1
lead.write({'partner_id': partner.id})
self.assertEqual(lead.state, 'draft')
def test_20_direct_line(self):
"""
Check that a rule is executed after creating a line record.
"""
line = self.env['base.automation.line.test'].create({'name': "Line"})
self.assertEqual(line.user_id, self.user_demo)
def test_20_indirect_line(self):
"""
Check that creating a lead with a line executes rules on both records.
"""
lead = self.create_lead(line_ids=[(0, 0, {'name': "Line"})])
self.assertEqual(lead.state, 'draft', "Lead state should be 'draft'")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should change on creation of Lead test line.")
self.assertEqual(len(lead.line_ids), 1, "New test line is not created")
self.assertEqual(lead.line_ids.user_id, self.user_demo, "Responsible should be change on creation of Lead test line.")
def test_21_trigger_fields(self):
"""
Check that the rule with trigger is executed only once per pertinent update.
"""
lead = self.create_lead(name="X")
lead.priority = True
partner1 = self.res_partner_1
lead.partner_id = partner1.id
self.assertEqual(lead.name, 'X', "No update until now.")
lead.state = 'open'
self.assertEqual(lead.name, 'XX', "One update should have happened.")
lead.state = 'done'
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
lead.state = 'done'
self.assertEqual(lead.name, 'XXX', "No update should have happened.")
lead.state = 'cancel'
self.assertEqual(lead.name, 'XXXX', "One update should have happened.")
# change the rule to trigger on partner_id
rule = self.env['base.automation'].search([('name', '=', 'Base Automation: test rule with trigger')])
rule.write({'trigger_field_ids': [(6, 0, [self.env.ref('test_base_automation.field_base_automation_lead_test__partner_id').id])]})
partner2 = self.env['res.partner'].create({'name': 'A new partner'})
lead.name = 'X'
lead.state = 'open'
self.assertEqual(lead.name, 'X', "No update should have happened.")
lead.partner_id = partner2
self.assertEqual(lead.name, 'XX', "One update should have happened.")
lead.partner_id = partner2
self.assertEqual(lead.name, 'XX', "No update should have happened.")
lead.partner_id = partner1
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
def test_30_modelwithoutaccess(self):
"""
Ensure a domain on a M2O without user access doesn't fail.
We create a base automation with a filter on a model the user haven't access to
- create a group
- restrict acl to this group and set only admin in it
- create base.automation with a filter
- create a record in the restricted model in admin
- create a record in the non restricted model in demo
"""
Model = self.env['base.automation.link.test']
Comodel = self.env['base.automation.linked.test']
access = self.env.ref("test_base_automation.access_base_automation_linked_test")
access.group_id = self.env['res.groups'].create({
'name': "Access to base.automation.linked.test",
"users": [(6, 0, [self.user_admin.id,])]
})
# sanity check: user demo has no access to the comodel of 'linked_id'
with self.assertRaises(AccessError):
Comodel.with_user(self.user_demo).check_access_rights('read')
# check base automation with filter that performs Comodel.search()
self.env['base.automation'].create({
'name': 'test no access',
'model_id': self.env['ir.model']._get_id("base.automation.link.test"),
'trigger': 'on_create_or_write',
'filter_pre_domain': "[('linked_id.another_field', '=', 'something')]",
'state': 'code',
'active': True,
'code': "action = [rec.name for rec in records]"
})
Comodel.create([
{'name': 'a first record', 'another_field': 'something'},
{'name': 'another record', 'another_field': 'something different'},
])
rec1 = Model.create({'name': 'a record'})
rec1.write({'name': 'a first record'})
rec2 = Model.with_user(self.user_demo).create({'name': 'another record'})
rec2.write({'name': 'another value'})
# check base automation with filter that performs Comodel.name_search()
self.env['base.automation'].create({
'name': 'test no name access',
'model_id': self.env['ir.model']._get_id("base.automation.link.test"),
'trigger': 'on_create_or_write',
'filter_pre_domain': "[('linked_id', '=', 'whatever')]",
'state': 'code',
'active': True,
'code': "action = [rec.name for rec in records]"
})
rec3 = Model.create({'name': 'a random record'})
rec3.write({'name': 'a first record'})
rec4 = Model.with_user(self.user_demo).create({'name': 'again another record'})
rec4.write({'name': 'another value'})
@common.tagged('post_install', '-at_install')
class TestCompute(common.TransactionCase):
def test_inversion(self):
""" If a stored field B depends on A, an update to the trigger for A
should trigger the recomputaton of A, then B.
However if a search() is performed during the computation of A
??? and _order is affected ??? a flush will be triggered, forcing the
computation of B, based on the previous A.
This happens if a rule has has a non-empty filter_pre_domain, even if
it's an empty list (``'[]'`` as opposed to ``False``).
"""
company1 = self.env['res.partner'].create({
'name': "Gorofy",
'is_company': True,
})
company2 = self.env['res.partner'].create({
'name': "Awiclo",
'is_company': True
})
r = self.env['res.partner'].create({
'name': 'Bob',
'is_company': False,
'parent_id': company1.id
})
self.assertEqual(r.display_name, 'Gorofy, Bob')
r.parent_id = company2
self.assertEqual(r.display_name, 'Awiclo, Bob')
self.env['base.automation'].create({
'name': "test rule",
'filter_pre_domain': False,
'trigger': 'on_create_or_write',
'state': 'code', # no-op action
'model_id': self.env.ref('base.model_res_partner').id,
})
r.parent_id = company1
self.assertEqual(r.display_name, 'Gorofy, Bob')
self.env['base.automation'].create({
'name': "test rule",
'filter_pre_domain': '[]',
'trigger': 'on_create_or_write',
'state': 'code', # no-op action
'model_id': self.env.ref('base.model_res_partner').id,
})
r.parent_id = company2
self.assertEqual(r.display_name, 'Awiclo, Bob')
def test_recursion(self):
project = self.env['test_base_automation.project'].create({})
# this action is executed every time a task is assigned to project
self.env['base.automation'].create({
'name': 'dummy',
'model_id': self.env['ir.model']._get_id('test_base_automation.task'),
'state': 'code',
'trigger': 'on_create_or_write',
'filter_domain': repr([('project_id', '=', project.id)]),
})
# create one task in project with 10 subtasks; all the subtasks are
# automatically assigned to project, too
task = self.env['test_base_automation.task'].create({'project_id': project.id})
subtasks = task.create([{'parent_id': task.id} for _ in range(10)])
subtasks.flush_model()
# This test checks what happens when a stored recursive computed field
# is marked to compute on many records, and automated actions are
# triggered depending on that field. In this case, we trigger the
# recomputation of 'project_id' on 'subtasks' by deleting their parent
# task.
#
# An issue occurs when the domain of automated actions is evaluated by
# method search(), because the latter flushes the fields to search on,
# which are also the ones being recomputed. Combined with the fact
# that recursive fields are not computed in batch, this leads to a huge
# amount of recursive calls between the automated action and flush().
#
# The execution of task.unlink() looks like this:
# - mark 'project_id' to compute on subtasks
# - delete task
# - flush()
# - recompute 'project_id' on subtask1
# - call compute on subtask1
# - in action, search([('id', 'in', subtask1.ids), ('project_id', '=', pid)])
# - flush(['id', 'project_id'])
# - recompute 'project_id' on subtask2
# - call compute on subtask2
# - in action search([('id', 'in', subtask2.ids), ('project_id', '=', pid)])
# - flush(['id', 'project_id'])
# - recompute 'project_id' on subtask3
# - call compute on subtask3
# - in action, search([('id', 'in', subtask3.ids), ('project_id', '=', pid)])
# - flush(['id', 'project_id'])
# - recompute 'project_id' on subtask4
# ...
limit = sys.getrecursionlimit()
try:
sys.setrecursionlimit(100)
task.unlink()
finally:
sys.setrecursionlimit(limit)