Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][FIX] base_multi_company: support in False search operators #760

Merged
merged 1 commit into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions base_multi_company/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Contributors
* Rodrigo Ferreira <[email protected]>
* Florian da Costa <[email protected]>
* Denis Roussel <[email protected]>
* Jairo Llopis (`Moduon <https://www.moduon.team/>`__)

Maintainers
~~~~~~~~~~~
Expand Down
13 changes: 13 additions & 0 deletions base_multi_company/hooks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright 2015-2016 Pedro M. Baeza <[email protected]>
# Copyright 2017 LasLabs Inc.
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html
import warnings

from odoo import SUPERUSER_ID, api

__all__ = [
Expand All @@ -15,6 +17,10 @@
:param: env: Environment
:param: rule_ref: XML-ID of the security rule to change.
"""
warnings.warn(

Check warning on line 20 in base_multi_company/hooks.py

View check run for this annotation

Codecov / codecov/patch

base_multi_company/hooks.py#L20

Added line #L20 was not covered by tests
"This hook is deprecated. Use `fill_company_ids` instead.",
DeprecationWarning,
)
rule = env.ref(rule_ref)
if not rule: # safeguard if it's deleted
return
Expand All @@ -38,6 +44,12 @@
"""
env = api.Environment(cr, SUPERUSER_ID, {})
set_security_rule(env, rule_ref)
fill_company_ids(cr, model_name)

Check warning on line 47 in base_multi_company/hooks.py

View check run for this annotation

Codecov / codecov/patch

base_multi_company/hooks.py#L47

Added line #L47 was not covered by tests


def fill_company_ids(cr, model_name):
"""Fill company_ids with company_id values."""
env = api.Environment(cr, SUPERUSER_ID, {})

Check warning on line 52 in base_multi_company/hooks.py

View check run for this annotation

Codecov / codecov/patch

base_multi_company/hooks.py#L52

Added line #L52 was not covered by tests
# Copy company values
model = env[model_name]
table_name = model._fields["company_ids"].relation
Expand Down Expand Up @@ -65,6 +77,7 @@
rule_ref (string): XML ID of security rule to remove the
`domain_force` from.
"""
warnings.warn("This hook is deprecated.", DeprecationWarning)
env = api.Environment(cr, SUPERUSER_ID, {})
# Change access rule
rule = env.ref(rule_ref)
Expand Down
35 changes: 9 additions & 26 deletions base_multi_company/models/multi_company_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Copyright 2023 Tecnativa - Pedro M. Baeza
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

import warnings

from odoo import api, fields, models


Expand Down Expand Up @@ -49,7 +51,12 @@ def _inverse_company_id(self):
record.company_ids = [(6, 0, record.company_id.ids)]

def _search_company_id(self, operator, value):
return [("company_ids", operator, value)]
domain = [("company_ids", operator, value)]
new_op = {"in": "=", "not in": "!="}.get(operator)
if new_op and (False in value or None in value):
# We need to workaround an ORM issue to find records with no company
domain = ["|", ("company_ids", new_op, False)] + domain
return domain

@api.model_create_multi
def create(self, vals_list):
Expand All @@ -67,6 +74,7 @@ def write(self, vals):

@api.model
def _patch_company_domain(self, args):
warnings.warn("This method is deprecated.", DeprecationWarning)
# In some situations the 'in' operator is used with company_id in a
# name_search or search_read. ORM does not convert to a proper WHERE clause when using
# the 'in' operator.
Expand Down Expand Up @@ -97,28 +105,3 @@ def _patch_company_domain(self, args):
else:
new_args.append(arg)
return new_args

@api.model
def _name_search(
self, name, args=None, operator="ilike", limit=100, name_get_uid=None
):
new_args = self._patch_company_domain(args)
return super()._name_search(
name,
args=new_args,
operator=operator,
limit=limit,
name_get_uid=name_get_uid,
)

@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
new_domain = self._patch_company_domain(domain)
return super().search_read(new_domain, fields, offset, limit, order)

@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
args = self._patch_company_domain(args)
return super().search(
args, offset=offset, limit=limit, order=order, count=count
)
1 change: 1 addition & 0 deletions base_multi_company/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
* Rodrigo Ferreira <[email protected]>
* Florian da Costa <[email protected]>
* Denis Roussel <[email protected]>
* Jairo Llopis (`Moduon <https://www.moduon.team/>`__)
46 changes: 14 additions & 32 deletions base_multi_company/readme/DEVELOPER.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Implementation
Multi Company Abstract
----------------------

The `multi.company.abstract` model is meant to be inherited by any model that
The ``multi.company.abstract`` model is meant to be inherited by any model that
wants to implement multi-company functionality. The logic does not require a
pre-existing company field on the inheriting model, but will not be affected
if one does exist.

When inheriting the `multi.company.abstract` model, you must take care that
it is the first model listed in the `_inherit` array
When inheriting the ``multi.company.abstract`` model, you must take care that
it is the first model listed in the ``_inherit`` array

.. code-block:: python

Expand All @@ -19,53 +19,35 @@ it is the first model listed in the `_inherit` array
_name = "product.template"
_description = "Product Template (Multi-Company)"

The following fields are provided by `multi.company.abstract`:
The following fields are provided by ``multi.company.abstract``:

* `company_ids` - All of the companies that this record belongs to. This is a
special `res.company.assignment` view, which allows for the circumvention of
* ``company_ids`` - All of the companies that this record belongs to. This is a
special ``res.company.assignment`` view, which allows for the circumvention of
standard cross-company security policies. These policies would normally
restrict a user from seeing another company unless it is currently operating
under that company. Be aware of apples to oranges issues when comparing the
records from this field against actual company records.
* `company_id` - Passes through a singleton company based on the current user,
* ``company_id`` - Passes through a singleton company based on the current user,
and the allowed companies for the record.
* `no_company_ids` - As there is a limitation in Odoo ORM to get real False values
in Many2many fields (solved on 2022-03-23 https://github.com/odoo/odoo/pull/81344).

Hooks
-----

A generic `post_init_hook` and `uninstall_hook` is provided, which will alter
a pre-existing single-company security rule to be multi-company aware.
A generic ``fill_company_ids`` hook is provided, to be used in submodules'
``post_init_hook``, which will convert the ``company_id`` field to a
``company_ids`` field, respecting previous company assignments.

These hooks will unfortunately not work in every circumstance, but they cut out
It will unfortunately not work in every circumstance, but it cuts out
significant boilerplate when relevant.

.. code-block:: python

import logging

_logger = logging.getLogger(__name__)

try:
from odoo.addons.base_multi_company import hooks
except ImportError:
_logger.info('Cannot find `base_multi_company` module in addons path.')

from odoo.addons.base_multi_company import hooks

def post_init_hook(cr, registry):
hooks.post_init_hook(
hooks.fill_company_ids(
cr,
'product.product_comp_rule',
'product.template',
)


def uninstall_hook(cr, registry):
hooks.uninstall_hook(
cr,
'product.product_comp_rule',
)

A module implementing these hooks would need to first identify the proper rule
for the record (`product.product_comp_rule` in the above example).
Other hooks are deprecated and no longer needed.
1 change: 1 addition & 0 deletions base_multi_company/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ <h3><a class="toc-backref" href="#toc-entry-4">Contributors</a></h3>
<li>Rodrigo Ferreira &lt;<a class="reference external" href="mailto:rodrigosferreira91&#64;gmail.com">rodrigosferreira91&#64;gmail.com</a>&gt;</li>
<li>Florian da Costa &lt;<a class="reference external" href="mailto:florian.dacosta&#64;akretion.com">florian.dacosta&#64;akretion.com</a>&gt;</li>
<li>Denis Roussel &lt;<a class="reference external" href="mailto:denis.roussel&#64;acsone.eu">denis.roussel&#64;acsone.eu</a>&gt;</li>
<li>Jairo Llopis (<a class="reference external" href="https://www.moduon.team/">Moduon</a>)</li>
</ul>
</div>
<div class="section" id="maintainers">
Expand Down
27 changes: 27 additions & 0 deletions base_multi_company/tests/test_multi_company_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from odoo_test_helper import FakeModelLoader

from odoo.fields import Command
from odoo.tests import common


Expand Down Expand Up @@ -287,3 +288,29 @@ def test_set_company_id(self):
tester.company_id = False

self.assertFalse(tester.sudo().company_ids)

def test_rule_in_false(self):
# Create an ir.rule imitating base.res_partner_rule
self.env["ir.rule"].create(
{
"name": "Test rule",
"model_id": self.tester_model.id,
"domain_force": repr(
[("company_id", "in", [False, self.company_1.id])]
),
"groups": [Command.link(self.ref("base.group_user"))],
}
)
user = common.new_test_user(
self.env,
login="test_user",
groups="base.group_user",
company_id=self.company_1.id,
)
# Record has no company, so user can read it
self.assertFalse(self.record_1.company_ids)
self.assertFalse(self.record_1.company_id)
self.assertEqual(
self.record_1.with_user(user).read(["name"]),
[{"id": self.record_1.id, "name": "test"}],
)
Loading