diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-09-24 16:59:15 +0000 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-09-24 17:45:37 +0000 |
| commit | d67b2483e5bfa5d8c1e6ef1a64fb933f5e080ad5 (patch) | |
| tree | 25a7fd39ac993f3c04c5d8408beb628b3c9948ce /odoo/addons/base | |
| parent | 38d8b0526f0fccc32df5a11f5d086a3522ed5fcf (diff) | |
| parent | 9ed4872ea03f28012118ed48a43d0800617a84f3 (diff) | |
[MERGE] forward port branch saas-12.4 up to 9ed4872ea03
closes odoo/odoo#37372
Signed-off-by: Christophe Simonis <chs@odoo.com>
Diffstat (limited to 'odoo/addons/base')
| -rw-r--r-- | odoo/addons/base/data/res_currency_data.xml | 2 | ||||
| -rw-r--r-- | odoo/addons/base/models/assetsbundle.py | 16 | ||||
| -rw-r--r-- | odoo/addons/base/models/ir_actions.py | 34 | ||||
| -rw-r--r-- | odoo/addons/base/models/ir_cron.py | 1 | ||||
| -rw-r--r-- | odoo/addons/base/models/res_config.py | 6 | ||||
| -rw-r--r-- | odoo/addons/base/models/res_users.py | 11 | ||||
| -rw-r--r-- | odoo/addons/base/tests/__init__.py | 1 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_non_regression.py | 32 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_user_has_group.py | 142 |
9 files changed, 142 insertions, 103 deletions
diff --git a/odoo/addons/base/data/res_currency_data.xml b/odoo/addons/base/data/res_currency_data.xml index 39e77487d3a..db5cb7a3467 100644 --- a/odoo/addons/base/data/res_currency_data.xml +++ b/odoo/addons/base/data/res_currency_data.xml @@ -869,7 +869,7 @@ <record id="KWD" model="res.currency"> <field name="name">KWD</field> <field name="symbol"> د.ك </field> - <field name="rounding">0.01</field> + <field name="rounding">0.001</field> <field name="active" eval="False"/> <field name="currency_unit_label">Dinar</field> <field name="currency_subunit_label">Fils</field> diff --git a/odoo/addons/base/models/assetsbundle.py b/odoo/addons/base/models/assetsbundle.py index d1554594aa3..f5f344e6c5f 100644 --- a/odoo/addons/base/models/assetsbundle.py +++ b/odoo/addons/base/models/assetsbundle.py @@ -483,13 +483,15 @@ class AssetsBundle(object): compiled = compiled.strip() # Post process the produced css to add required vendor prefixes here - compiled = re.sub(r'(appearance: (\w+);)', r'-webkit-appearance: \2; -moz-appearance: \2; \1', compiled); - - compiled = re.sub(r'(display: ((?:inline-)?)flex((?: ?!important)?);)', r'display: -webkit-\2box\3; display: -webkit-\2flex\3; \1', compiled) # For PhantomJS tests and wkhtmltopdf - compiled = re.sub(r'(justify-content: flex-(\w+)((?: ?!important)?);)', r'-webkit-box-pack: \2\3; \1', compiled) # For wkhtmltopdf - compiled = re.sub(r'(flex-flow: (\w+ \w+);)', r'-webkit-flex-flow: \2; \1', compiled) # For PhantomJS tests - compiled = re.sub(r'(flex-direction: (column);)', r'-webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: \2; \1', compiled) # For wkhtmltopdf - compiled = re.sub(r'(flex: ((\d)+ \d+ (?:\d+|auto));)', r'-webkit-box-flex: \3; -webkit-flex: \2; \1', compiled) # For PhantomJS tests and wkhtmltopdf + compiled = re.sub(r'(appearance: (\w+);)', r'-webkit-appearance: \2; -moz-appearance: \2; \1', compiled) + + # Most of those are only useful for wkhtmltopdf (some for old PhantomJS) + compiled = re.sub(r'(display: ((?:inline-)?)flex((?: ?!important)?);)', r'display: -webkit-\2box\3; display: -webkit-\2flex\3; \1', compiled) + compiled = re.sub(r'(justify-content: flex-(\w+)((?: ?!important)?);)', r'-webkit-box-pack: \2\3; \1', compiled) + compiled = re.sub(r'(flex-flow: (\w+ \w+);)', r'-webkit-flex-flow: \2; \1', compiled) + compiled = re.sub(r'(flex-direction: (column);)', r'-webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: \2; \1', compiled) + compiled = re.sub(r'(flex-wrap: (\w+);)', r'-webkit-flex-wrap: \2; \1', compiled) + compiled = re.sub(r'(flex: ((\d)+ \d+ (?:\d+|auto));)', r'-webkit-box-flex: \3; -webkit-flex: \2; \1', compiled) return compiled diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index 38d9ad6911f..5680c1f88e3 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -318,9 +318,7 @@ class IrActionsServer(models.Model): The available actions are : - 'Execute Python Code': a block of python code that will be executed - - 'Run a Client Action': choose a client action to launch - - 'Create or Copy a new Record': create a new record with new values, or - copy an existing record in your database + - 'Create a new Record': create a new record with new values - 'Write on a Record': update the values of a record - 'Execute several actions': define an action that triggers several other server actions @@ -361,11 +359,12 @@ class IrActionsServer(models.Model): default='object_write', required=True, help="Type of server action. The following values are available:\n" "- 'Execute Python Code': a block of python code that will be executed\n" - "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" - "- 'Write on a Record': update the values of a record\n" + "- 'Create': create a new record with new values\n" + "- 'Update a Record': update the values of a record\n" "- 'Execute several actions': define an action that triggers several other server actions\n" - "- 'Add Followers': add followers to a record (available in Discuss)\n" - "- 'Send Email': automatically send an email (available in email_template)") + "- 'Send Email': automatically send an email (Discuss)\n" + "- 'Add Followers': add followers to a record (Discuss)\n" + "- 'Create Next Activity': create an activity (Discuss)") # Generic sequence = fields.Integer(default=5, help="When dealing with multiple actions, the execution order is " @@ -443,15 +442,7 @@ class IrActionsServer(models.Model): @api.model def run_action_object_write(self, action, eval_context=None): - """ Write server action. - - - 1. evaluate the value mapping - - 2. depending on the write configuration: - - - `current`: id = active_id - - `other`: id = from reference object - - `expression`: id = from expression evaluation - """ + """Apply specified write changes to active_id.""" res = {} for exp in action.fields_lines: res[exp.col1.name] = exp.eval_value(eval_context=eval_context)[exp.id] @@ -465,16 +456,9 @@ class IrActionsServer(models.Model): @api.model def run_action_object_create(self, action, eval_context=None): - """ Create and Copy server action. - - - 1. evaluate the value mapping - - 2. depending on the write configuration: + """Create specified model object with specified values. - - `new`: new record in the base model - - `copy_current`: copy the current record (id = active_id) + gives custom values - - `new_other`: new record in target model - - `copy_other`: copy the current record (id from reference object) - + gives custom values + If applicable, link active_id.<self.link_field_id> to the new record. """ res = {} for exp in action.fields_lines: diff --git a/odoo/addons/base/models/ir_cron.py b/odoo/addons/base/models/ir_cron.py index 3542b8adc2a..a9821e782f7 100644 --- a/odoo/addons/base/models/ir_cron.py +++ b/odoo/addons/base/models/ir_cron.py @@ -228,6 +228,7 @@ class ir_cron(models.Model): try: registry = odoo.registry(db_name) registry[cls._name]._process_job(job_cr, job, lock_cr) + _logger.info('Job `%s` done.', job['cron_name']) except Exception: _logger.exception('Unexpected exception while processing cron job %r', job) finally: diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py index 3dca42a8d98..948134d9e17 100644 --- a/odoo/addons/base/models/res_config.py +++ b/odoo/addons/base/models/res_config.py @@ -517,19 +517,19 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) # Special case when value is the id of a deleted record, we do not want to # block the settings screen value = self.env[field.comodel_name].browse(int(value)).exists().id - except ValueError: + except (ValueError, TypeError): _logger.warning(WARNING_MESSAGE, value, field, icp) value = False elif field.type == 'integer': try: value = int(value) - except ValueError: + except (ValueError, TypeError): _logger.warning(WARNING_MESSAGE, value, field, icp) value = 0 elif field.type == 'float': try: value = float(value) - except ValueError: + except (ValueError, TypeError): _logger.warning(WARNING_MESSAGE, value, field, icp) value = 0.0 elif field.type == 'boolean': diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index 66b0b2f3e49..dba2a76ffed 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -984,22 +984,19 @@ class UsersImplied(models.Model): # complete 'groups_id' with implied groups user = self.new(values) gs = user.groups_id._origin - group_public = self.env.ref('base.group_public', raise_if_not_found=False) - group_portal = self.env.ref('base.group_portal', raise_if_not_found=False) - if group_public and group_public in gs: - gs = group_public - elif group_portal and group_portal in gs: - gs = group_portal gs = gs | gs.trans_implied_ids values['groups_id'] = type(self).groups_id.convert_to_write(gs, user) return super(UsersImplied, self).create(vals_list) def write(self, values): + users_before = self.filtered(lambda u: u.has_group('base.group_user')) res = super(UsersImplied, self).write(values) if values.get('groups_id'): # add implied groups for all users for user in self: - if not user.has_group('base.group_user'): + if not user.has_group('base.group_user') and user in users_before: + # if we demoted a user, we strip him of all its previous privileges + # (but we should not do it if we are simply adding a technical group to a portal user) vals = {'groups_id': [(5, 0, 0)] + values['groups_id']} super(UsersImplied, user).write(vals) gs = set(concat(g.trans_implied_ids for g in user.groups_id)) diff --git a/odoo/addons/base/tests/__init__.py b/odoo/addons/base/tests/__init__.py index db7b07f81af..ca045e0c6bb 100644 --- a/odoo/addons/base/tests/__init__.py +++ b/odoo/addons/base/tests/__init__.py @@ -37,5 +37,4 @@ from . import test_res_partner from . import test_res_partner_bank from . import test_reports from . import test_tests_tags -from . import test_non_regression from . import test_base_document_layout diff --git a/odoo/addons/base/tests/test_non_regression.py b/odoo/addons/base/tests/test_non_regression.py deleted file mode 100644 index 1af583c16fa..00000000000 --- a/odoo/addons/base/tests/test_non_regression.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Non-Regression Tests -""" - -from odoo.tests.common import TransactionCase - - -class TestNR(TransactionCase): - def test_issue26036(self): - # Coming from https://github.com/odoo/odoo/pull/26036 - U = self.env["res.users"] - G = self.env["res.groups"] - group_user = self.env.ref('base.group_user') - group_no_one = self.env.ref('base.group_no_one') - - group_A = G.create({"name": "A"}) - group_AA = G.create({"name": "AA", "implied_ids": [(6, 0, [group_A.id])]}) - group_B = G.create({"name": "B"}) - group_BB = G.create({"name": "BB", "implied_ids": [(6, 0, [group_B.id])]}) - group_C = G.create({"name": "C"}) - - user_a = U.create({"name": "a", "login": "a", "groups_id": [(6, 0, [group_AA.id, group_user.id])]}) - user_b = U.create({"name": "b", "login": "b", "groups_id": [(6, 0, [group_BB.id])]}) - - self.assertEqual(user_a.groups_id, (group_AA + group_A + group_user + group_no_one)) - - (user_a + user_b).write({"groups_id": [(4, group_C.id)]}) - - self.assertEqual(user_a.groups_id, (group_AA + group_A + group_C + group_user + group_no_one)) - # As user_b is not an internal user, all its groups are removed - self.assertEqual(user_b.groups_id, group_C) diff --git a/odoo/addons/base/tests/test_user_has_group.py b/odoo/addons/base/tests/test_user_has_group.py index 4f5b2bc61dd..f5ea69ea711 100644 --- a/odoo/addons/base/tests/test_user_has_group.py +++ b/odoo/addons/base/tests/test_user_has_group.py @@ -28,6 +28,8 @@ class TestHasGroup(TransactionCase): self.grp_internal = self.env.ref(self.grp_internal_xml_id) self.grp_portal_xml_id = 'base.group_portal' self.grp_portal = self.env.ref(self.grp_portal_xml_id) + self.grp_public_xml_id = 'base.group_public' + self.grp_public = self.env.ref(self.grp_public_xml_id) def test_env_uid(self): Users = self.env['res.users'].with_user(self.test_user) @@ -51,6 +53,10 @@ class TestHasGroup(TransactionCase): ) def test_portal_creation(self): + """Here we check that portal user creation fails if it tries to create a user + who would also have group_user by implied_group. + Otherwise, it succeeds with the groups we asked for. + """ grp_public = self.env.ref('base.group_public') grp_test_portal_xml_id = 'test_user_has_group.portal_implied_group' grp_test_portal = self.env['res.groups']._load_records([ @@ -64,14 +70,16 @@ class TestHasGroup(TransactionCase): dict(xml_id=grp_test_internal2_xml_id, values={'name': 'Test Group Internal 2'}) ]) self.grp_portal.implied_ids = grp_test_portal - grp_test_internal1.implied_ids = self.grp_internal - grp_test_internal2.implied_ids = self.grp_internal + + grp_test_internal1.implied_ids = False + grp_test_internal2.implied_ids = False + portal_user = self.env['res.users'].create({ 'login': 'portalTest', 'name': 'Portal test', 'sel_groups_%s_%s_%s' % (self.grp_internal.id, self.grp_portal.id, grp_public.id): self.grp_portal.id, 'sel_groups_%s_%s' % (grp_test_internal1.id, grp_test_internal2.id): grp_test_internal2.id, - }) + }) self.assertTrue( portal_user.has_group(self.grp_portal_xml_id), @@ -81,45 +89,52 @@ class TestHasGroup(TransactionCase): portal_user.has_group(grp_test_portal_xml_id), "The portal user should belong to '%s'" % grp_test_portal_xml_id, ) - self.assertFalse( + self.assertTrue( portal_user.has_group(grp_test_internal2_xml_id), - "The portal user should not belong to '%s'" % grp_test_internal2_xml_id + "The portal user should belong to '%s'" % grp_test_internal2_xml_id ) self.assertFalse( portal_user.has_group(self.grp_internal_xml_id), "The portal user should not belong to '%s'" % self.grp_internal_xml_id ) + portal_user.unlink() # otherwise, badly modifying the implication would raise + + grp_test_internal1.implied_ids = self.grp_internal + grp_test_internal2.implied_ids = self.grp_internal + + with self.assertRaises(ValidationError): # current group implications forbid to create a portal user + portal_user = self.env['res.users'].create({ + 'login': 'portalFail', + 'name': 'Portal fail', + 'sel_groups_%s_%s_%s' % (self.grp_internal.id, self.grp_portal.id, grp_public.id): self.grp_portal.id, + 'sel_groups_%s_%s' % (grp_test_internal1.id, grp_test_internal2.id): grp_test_internal2.id, + }) + def test_portal_write(self): - grp_remove_xml_id = 'test_portal_write.group_to_remove' - grp_remove = self.env['res.groups']._load_records([ - dict(xml_id=grp_remove_xml_id, values={'name': 'Group to remove'}) - ]) + """Check that adding a new group to a portal user works as expected, + except if it implies group_user/public, in chich case it should raise. + """ + grp_test_portal = self.env["res.groups"].create({"name": "implied by portal"}) + self.grp_portal.implied_ids = grp_test_portal + portal_user = self.env['res.users'].create({ 'login': 'portalTest2', 'name': 'Portal test 2', - 'groups_id': [(6, 0, [grp_remove.id])], + 'groups_id': [(6, 0, [self.grp_portal.id])], }) - grp_test_portal_xml_id = 'test_portal_write.portal_implied_group' - grp_test_portal = self.env['res.groups']._load_records([ - dict(xml_id=grp_test_portal_xml_id, values={'name': 'Test Group Portal'}) - ]) - self.grp_portal.implied_ids = grp_test_portal - portal_user.write({'groups_id': [(4, self.grp_portal.id, 0)]}) - self.assertFalse( - portal_user.has_group(grp_remove_xml_id), - "The portal user should not belong to '%s'" % grp_remove_xml_id - ) - self.assertTrue( - portal_user.has_group(self.grp_portal_xml_id), - "The portal user should belong to '%s'" % self.grp_portal_xml_id, - ) - self.assertTrue( - portal_user.has_group(grp_test_portal_xml_id), - "The portal user should belong to '%s'" % grp_test_portal_xml_id, + self.assertEqual( + portal_user.groups_id, (self.grp_portal + grp_test_portal), + "The portal user should have the implied group.", ) + grp_fail = self.env["res.groups"].create( + {"name": "fail", "implied_ids": [(6, 0, [self.grp_internal.id])]}) + + with self.assertRaises(ValidationError): + portal_user.write({'groups_id': [(4, grp_fail.id)]}) + def test_two_user_types(self): #Create a user with two groups of user types kind (Internal and Portal) grp_test = self.env['res.groups']._load_records([ @@ -161,3 +176,76 @@ class TestHasGroup(TransactionCase): with self.assertRaises(ValidationError): grp_test.write({'implied_ids': [(4, self.grp_portal.id)]}) + + def test_demote_user(self): + """When a user is demoted to the status of portal/public, + we should strip him of all his (previous) rights + """ + group_0 = self.env.ref(self.group0) # the group to which test_user already belongs + group_U = self.env["res.groups"].create({"name": "U", "implied_ids": [(6, 0, [self.grp_internal.id])]}) + self.grp_internal.implied_ids = False # only there to simplify the test by not having to care about its trans_implied_ids + + self.test_user.write({'groups_id': [(4, group_U.id)]}) + self.assertEqual( + self.test_user.groups_id, (group_0 + group_U + self.grp_internal), + "We should have our 2 groups and the implied user group", + ) + + # Now we demote him. The JS framework sends 3 and 4 commands, + # which is what we write here, but it should work even with a 5 command or whatever. + self.test_user.write({'groups_id': [ + (3, self.grp_internal.id), + (3, self.grp_public.id), + (4, self.grp_portal.id), + ]}) + + # if we screw up the removing groups/adding the implied ids, we could end up in two situations: + # 1. we have a portal user with way too much rights (e.g. 'Contact Creation', which does not imply any other group) + # 2. because a group may be (transitively) implying group_user, then it would raise an exception + # so as a compromise we remove all groups when demoting a user + # (even technical display groups, e.g. TaxB2B, which could be re-added later) + self.assertEqual( + self.test_user.groups_id, (self.grp_portal), + "Here the portal group does not imply any other group, so we should only have this group.", + ) + + def test_implied_groups(self): + """ We check that the adding of implied ids works correctly for normal users and portal users. + In the second case, working normally means raising if a group implies to give 'group_user' + rights to a portal user. + """ + U = self.env["res.users"] + G = self.env["res.groups"] + group_user = self.env.ref('base.group_user') + group_portal = self.env.ref('base.group_portal') + group_no_one = self.env.ref('base.group_no_one') + + group_A = G.create({"name": "A"}) + group_AA = G.create({"name": "AA", "implied_ids": [(6, 0, [group_A.id])]}) + group_B = G.create({"name": "B"}) + group_BB = G.create({"name": "BB", "implied_ids": [(6, 0, [group_B.id])]}) + + # user_a is a normal user, so we expect groups to be added when we add them, + # as well as 'implied_groups'; otherwise nothing else should happen. + # By contrast, for a portal user we want implied groups not to be added + # if and only if it would not give group_user (or group_public) privileges + user_a = U.create({"name": "a", "login": "a", "groups_id": [(6, 0, [group_AA.id, group_user.id])]}) + self.assertEqual(user_a.groups_id, (group_AA + group_A + group_user + group_no_one)) + + user_b = U.create({"name": "b", "login": "b", "groups_id": [(6, 0, [group_portal.id, group_AA.id])]}) + self.assertEqual(user_b.groups_id, (group_AA + group_A + group_portal)) + + # user_b is not an internal user, but giving it a new group just added a new group + (user_a + user_b).write({"groups_id": [(4, group_BB.id)]}) + self.assertEqual(user_a.groups_id, (group_AA + group_A + group_BB + group_B + group_user + group_no_one)) + self.assertEqual(user_b.groups_id, (group_AA + group_A + group_BB + group_B + group_portal)) + + # now we create a group that implies the group_user + # adding it to a user should work normally, whereas adding it to a portal user should raise + group_C = G.create({"name": "C", "implied_ids": [(6, 0, [group_user.id])]}) + + user_a.write({"groups_id": [(4, group_C.id)]}) + self.assertEqual(user_a.groups_id, (group_AA + group_A + group_BB + group_B + group_C + group_user + group_no_one)) + + with self.assertRaises(ValidationError): + user_b.write({"groups_id": [(4, group_C.id)]}) |
