diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-02-14 18:09:24 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-02-14 18:09:24 +0100 |
| commit | c1fe9f18d5a1f6c13548ea2af964bde40b3b6861 (patch) | |
| tree | 811d2b54d9d6867741ffd6ce26e3baf292d4e1fb | |
| parent | 18228f9c4e1642a3c0afb24223bccfb3354c928a (diff) | |
| parent | 69719f598aad2eb8ff87687bc47900030688f0ce (diff) | |
[MERGE] forward port branch 10.0 up to 69719f598aa
| -rw-r--r-- | addons/base_import_module/i18n/base_import_module.pot | 5 | ||||
| -rw-r--r-- | addons/base_import_module/views/base_import_module_view.xml | 1 | ||||
| -rw-r--r-- | addons/google_calendar/models/google_calendar.py | 4 | ||||
| -rw-r--r-- | addons/l10n_it/__manifest__.py | 2 | ||||
| -rw-r--r-- | addons/point_of_sale/models/pos_order.py | 10 | ||||
| -rw-r--r-- | addons/stock/models/product.py | 4 | ||||
| -rw-r--r-- | addons/stock/views/stock_picking_views.xml | 8 | ||||
| -rw-r--r-- | addons/web_editor/static/lib/summernote/src/js/EventHandler.js | 10 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/backend.js | 9 | ||||
| -rw-r--r-- | doc/cla/individual/naglis.md | 11 | ||||
| -rw-r--r-- | odoo/addons/base/ir/ir_model.py | 7 | ||||
| -rw-r--r-- | odoo/addons/test_new_api/tests/test_new_fields.py | 41 | ||||
| -rw-r--r-- | odoo/fields.py | 16 | ||||
| -rw-r--r-- | odoo/models.py | 7 |
14 files changed, 114 insertions, 21 deletions
diff --git a/addons/base_import_module/i18n/base_import_module.pot b/addons/base_import_module/i18n/base_import_module.pot index 8af3356b3f4..b46e0ea5dfa 100644 --- a/addons/base_import_module/i18n/base_import_module.pot +++ b/addons/base_import_module/i18n/base_import_module.pot @@ -133,6 +133,11 @@ msgid "No file sent." msgstr "" #. module: base_import_module +#: model:ir.ui.view,arch_db:base_import_module.view_base_module_import +msgid "Note: you can only import data modules (.xml files and static assets)" +msgstr "" + +#. module: base_import_module #: code:addons/base_import_module/controllers/main.py:25 #, python-format msgid "Only administrators can upload a module" diff --git a/addons/base_import_module/views/base_import_module_view.xml b/addons/base_import_module/views/base_import_module_view.xml index 60af0d3fc61..b132c7f3a83 100644 --- a/addons/base_import_module/views/base_import_module_view.xml +++ b/addons/base_import_module/views/base_import_module_view.xml @@ -8,6 +8,7 @@ <form string="Import module"> <field name="state" invisible="1"/> <separator string="Import Module" colspan="4"/> + <p class="alert alert-warning">Note: you can only import data modules (.xml files and static assets)</p> <group states="init" col="4"> <label string="Select module package to import (.zip file):" colspan="4"/> <field name="module_file" colspan="4"/> diff --git a/addons/google_calendar/models/google_calendar.py b/addons/google_calendar/models/google_calendar.py index b71f93fc87b..d2fa4839b1f 100644 --- a/addons/google_calendar/models/google_calendar.py +++ b/addons/google_calendar/models/google_calendar.py @@ -443,7 +443,9 @@ class GoogleCalendar(models.AbstractModel): if google_attendee.get('found'): continue - attendee = ResPartner.search([('email', '=', google_attendee['email'])], limit=1) + attendee = ResPartner.search([('email', '=ilike', google_attendee['email']), ('user_ids', '!=', False)], limit=1) + if not attendee: + attendee = ResPartner.search([('email', '=ilike', google_attendee['email'])], limit=1) if not attendee: data = { 'email': partner_email, diff --git a/addons/l10n_it/__manifest__.py b/addons/l10n_it/__manifest__.py index 7d3eca9daab..7acf2ee6b1c 100644 --- a/addons/l10n_it/__manifest__.py +++ b/addons/l10n_it/__manifest__.py @@ -13,7 +13,7 @@ Piano dei conti italiano di un'impresa generica. Italian accounting chart and localization. """, 'category': 'Localization', - 'website': 'http://www.openerp-italia.org/', + 'website': 'http://www.odoo.com/', 'data': [ 'data/l10n_it_chart_data.xml', 'data/account.account.template.csv', diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index dedbc839d85..4eb99789963 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -391,13 +391,11 @@ class PosOrder(models.Model): aml = order.statement_ids.mapped('journal_entry_ids') | order.account_move.line_ids | order.invoice_id.move_id.line_ids aml = aml.filtered(lambda r: not r.reconciled and r.account_id.internal_type == 'receivable' and r.partner_id == order.partner_id.commercial_partner_id) - # Reconcile returns first - # to avoid mixing up the credit of a payment and the credit of a return - # in the receivable account - aml_returns = aml.filtered(lambda l: (l.journal_id.type == 'sale' and l.credit) or (l.journal_id.type != 'sale' and l.debit)) try: - aml_returns.reconcile() - (aml - aml_returns).reconcile() + # Cash returns will be well reconciled + # Whereas freight returns won't be + # "c'est la vie..." + aml.reconcile() except Exception: # There might be unexpected situations where the automatic reconciliation won't # work. We don't want the user to be blocked because of this, since the automatic diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 7adcdb992e0..8192b4eec4c 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -262,7 +262,9 @@ class Product(models.Model): # TODO: Still optimization possible when searching virtual quantities ids = [] - for product in self.search([]): + # Order the search on `id` to prevent the default order on the product name which slows + # down the search because of the join on the translation table to get the translated names. + for product in self.search([], order='id'): if OPERATORS[operator](product[field], value): ids.append(product.id) return [('id', 'in', ids)] diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index afbc82a6b30..3890b275819 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -55,8 +55,8 @@ <field name="use_existing_lots" groups="stock.group_production_lot"/> </group> <group string="Locations"> - <field name="default_location_src_id" attrs="{'required': [('code', '=', 'internal')]}"/> - <field name="default_location_dest_id" attrs="{'required': [('code', 'in', ('internal', 'incoming'))]}"/> + <field name="default_location_src_id" attrs="{'required': [('code', '=', 'internal')]}" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]"/> + <field name="default_location_dest_id" attrs="{'required': [('code', 'in', ('internal', 'incoming'))]}" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]"/> </group> </group> </sheet> @@ -229,8 +229,8 @@ <group> <group> <field name="partner_id" attrs="{'invisible': [('picking_type_code', '=', 'internal')]}"/> - <field name="location_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}"/> - <field name="location_dest_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('picking_type_code', '=', 'incoming')]}"/> + <field name="location_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]"/> + <field name="location_dest_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('picking_type_code', '=', 'incoming')]}" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]"/> <field name="backorder_id" readonly="1" attrs="{'invisible': [('backorder_id','=',False)]}"/> </group> <group> diff --git a/addons/web_editor/static/lib/summernote/src/js/EventHandler.js b/addons/web_editor/static/lib/summernote/src/js/EventHandler.js index 1cb9cd8e1c7..d8dcbe18a8b 100644 --- a/addons/web_editor/static/lib/summernote/src/js/EventHandler.js +++ b/addons/web_editor/static/lib/summernote/src/js/EventHandler.js @@ -207,11 +207,15 @@ define([ var layoutInfo = dom.makeLayoutInfo(target); /* ODOO: (start_modification */ var $editable = layoutInfo.editable(); - if (!event.isDefaultPrevented()) { - modules.editor.saveRange($editable); + if (event.setStyleInfoFromEditable) { + var styleInfo = modules.editor.styleFromNode($editable); + } else { + if (!event.isDefaultPrevented()) { + modules.editor.saveRange($editable); + } + var styleInfo = modules.editor.currentStyle(target); } /* ODOO: end_modification) */ - var styleInfo = modules.editor.currentStyle(target); self.updateStyleInfo(styleInfo, layoutInfo); }, 0); }; diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js index 1c5feb7a7d4..884ef68d674 100644 --- a/addons/web_editor/static/src/js/backend.js +++ b/addons/web_editor/static/src/js/backend.js @@ -57,7 +57,8 @@ var FieldTextHtmlSimple = widget.extend({ this.$translate.remove(); this.$translate = $(); // Triggers a mouseup to refresh the editor toolbar - this.$content.trigger('mouseup'); + var mouseupEvent = $.Event('mouseup', {'setStyleInfoFromEditable': true}); + this.$content.trigger(mouseupEvent); return def; }, initialize_content: function () { @@ -170,7 +171,11 @@ var FieldTextHtmlSimple = widget.extend({ transcoder.class_to_style(this.$content); transcoder.font_to_img(this.$content); } - this.internal_set_value(this.$content.html()); + var value = this.$content.html(); + if (this.get('value') === false && value === '<p><br></p>') { + value = false; + } + this.internal_set_value(value); }, destroy_content: function () { $(".oe-view-manager-content").off("scroll"); diff --git a/doc/cla/individual/naglis.md b/doc/cla/individual/naglis.md new file mode 100644 index 00000000000..dde081dfaa1 --- /dev/null +++ b/doc/cla/individual/naglis.md @@ -0,0 +1,11 @@ +Lithuania, 2019-02-09 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Naglis Jonaitis naglis@mailbox.org https://github.com/naglis diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index 911692cd780..521a94238f5 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -443,6 +443,13 @@ class IrModelFields(models.Model): """ return self.sudo().search([('model', '=', model_name), ('name', '=', name)]) + @tools.ormcache('model_name', 'name') + def _get_id(self, model_name, name): + self.env.cr.execute("SELECT id FROM ir_model_fields WHERE model=%s AND name=%s", + (model_name, name)) + result = self.env.cr.fetchone() + return result and result[0] + @api.multi def _drop_column(self): tables_to_drop = set() diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 1d982c4bb9b..6dc80305c7f 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -276,6 +276,16 @@ class TestFields(common.TransactionCase): self.assertEqual(record.bar, 'Ho') self.assertEqual(record.counts, {'compute': 0, 'inverse': 1}) + def test_13_inverse_access(self): + """ test access rights on inverse fields """ + foo = self.env['test_new_api.category'].create({'name': 'Foo'}) + user = self.env['res.users'].create({'name': 'Foo', 'login': 'foo'}) + self.assertFalse(user.has_group('base.group_system')) + # add group on non-stored inverse field + self.patch(type(foo).display_name, 'groups', 'base.group_system') + with self.assertRaises(AccessError): + foo.sudo(user).display_name = 'Forbidden' + def test_14_search(self): """ test search on computed fields """ discussion = self.env.ref('test_new_api.discussion_0') @@ -572,6 +582,10 @@ class TestFields(common.TransactionCase): self.env['ir.property'].create({'name': 'foo', 'fields_id': field.id, 'value': 'default', 'type': 'char'}) + # assumption: users don't have access to 'ir.property' + accesses = self.env['ir.model.access'].search([('model_id.model', '=', 'ir.property')]) + accesses.write(dict.fromkeys(['perm_read', 'perm_write', 'perm_create', 'perm_unlink'], False)) + # create/modify a record, and check the value for each user record = self.env['test_new_api.company'].create({'foo': 'main'}) record.invalidate_cache() @@ -591,6 +605,13 @@ class TestFields(common.TransactionCase): self.assertEqual(record.sudo(user1).foo, False) self.assertEqual(record.sudo(user2).foo, 'default') + # set field with 'force_company' in context + record.sudo(user0).with_context(force_company=company1.id).foo = 'beta' + record.invalidate_cache() + self.assertEqual(record.sudo(user0).foo, 'main') + self.assertEqual(record.sudo(user1).foo, 'beta') + self.assertEqual(record.sudo(user2).foo, 'default') + # create company record and attribute company_record = self.env['test_new_api.company'].create({'foo': 'ABC'}) attribute_record = self.env['test_new_api.company.attr'].create({ @@ -610,6 +631,26 @@ class TestFields(common.TransactionCase): self.assertEqual(attribute_record.bar, 'DEFDEF') self.assertFalse(self.env.has_todo()) + # add group on company-dependent field + self.assertFalse(user0.has_group('base.group_system')) + self.patch(type(record).foo, 'groups', 'base.group_system') + with self.assertRaises(AccessError): + record.sudo(user0).foo = 'forbidden' + + user0.write({'groups_id': [(4, self.env.ref('base.group_system').id)]}) + record.sudo(user0).foo = 'yes we can' + + # add ir.rule to prevent access on record + self.assertTrue(user0.has_group('base.group_user')) + model_id = self.env['ir.model'].search([('model', '=', record._name)]).id + rule = self.env['ir.rule'].create({ + 'model_id': model_id, + 'groups': [self.env.ref('base.group_user').id], + 'domain_force': str([('id', '!=', record.id)]), + }) + with self.assertRaises(AccessError): + record.sudo(user0).foo = 'forbidden' + def test_28_sparse(self): """ test sparse fields. """ record = self.env['test_new_api.sparse'].create({}) diff --git a/odoo/fields.py b/odoo/fields.py index a4f6bc7e27b..511fa0a3235 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -661,13 +661,25 @@ class Field(object): return model.env['ir.property'].get(self.name, self.model_name) def _compute_company_dependent(self, records): - Property = records.env['ir.property'] + # read property as superuser, as the current user may not have access + context = records.env.context + if 'force_company' not in context: + field_id = records.env['ir.model.fields']._get_id(self.model_name, self.name) + company = records.env['res.company']._company_default_get(self.model_name, field_id) + context = dict(context, force_company=company.id) + Property = records.env(user=SUPERUSER_ID, context=context)['ir.property'] values = Property.get_multi(self.name, self.model_name, records.ids) for record in records: record[self.name] = values.get(record.id) def _inverse_company_dependent(self, records): - Property = records.env['ir.property'] + # update property as superuser, as the current user may not have access + context = records.env.context + if 'force_company' not in context: + field_id = records.env['ir.model.fields']._get_id(self.model_name, self.name) + company = records.env['res.company']._company_default_get(self.model_name, field_id) + context = dict(context, force_company=company.id) + Property = records.env(user=SUPERUSER_ID, context=context)['ir.property'] values = { record.id: self.convert_to_write(record[self.name], record) for record in records diff --git a/odoo/models.py b/odoo/models.py index 92e6cd7c3d6..706fc3b2a7c 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3533,10 +3533,15 @@ class BaseModel(object): protected_fields = map(self._fields.get, new_vals) with self.env.protecting(protected_fields, self): # write old-style fields with (low-level) method _write - if old_vals: + if old_vals or new_vals: + # if log_access is enabled, this updates 'write_date' and + # 'write_uid' and check access rules, even when old_vals is + # empty self._write(old_vals) if new_vals: + self.check_field_access_rights('write', list(new_vals)) + self.modified(set(new_vals) - set(old_vals)) # put the values of fields into cache, and inverse them |
