From fa492d87f42f97d05ba93255a77e24c35158fe62 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 5 Jun 2019 06:53:46 +0000 Subject: [PATCH 1/3] [IMP] mail, website_mail, base: extra info on error messages Display extra information on error messages: - Access error based on ACLs: user, fields (if applies) - Access error based on record rules: user, ids - Missing error: model, operation, user, ids closes odoo/odoo#33945 Signed-off-by: Nicolas Martinelli (nim) Co-authored-by: jev-odoo --- addons/mail/models/mail_message.py | 10 +++--- addons/website_mail/models/mail_message.py | 5 ++- odoo/addons/base/ir/ir_actions.py | 5 ++- odoo/addons/base/ir/ir_model.py | 1 + odoo/models.py | 38 ++++++++++++++++------ 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 95ff558c900..9087de93f0a 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -582,8 +582,9 @@ class Message(models.Model): WHERE message.message_type = %%s AND (message.subtype_id IS NULL OR subtype.internal IS TRUE) AND message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,)) if self._cr.fetchall(): raise AccessError( - _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % - (self._description, operation)) + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) + ) # Read mail_message.ids to have their values message_values = dict((res_id, {}) for res_id in self.ids) @@ -682,8 +683,9 @@ class Message(models.Model): if not other_ids: return raise AccessError( - _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % - (self._description, operation)) + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('Records:'), list(other_ids)[:6], _('User:'), self._uid) + ) @api.model def _get_record_name(self, values): diff --git a/addons/website_mail/models/mail_message.py b/addons/website_mail/models/mail_message.py index e60392f62bd..c3d64567e1b 100644 --- a/addons/website_mail/models/mail_message.py +++ b/addons/website_mail/models/mail_message.py @@ -54,7 +54,10 @@ class MailMessage(models.Model): if self.user_has_groups('base.group_public'): self.env.cr.execute('SELECT id FROM "%s" WHERE website_published IS FALSE AND id = ANY (%%s)' % (self._table), (self.ids,)) if self.env.cr.fetchall(): - raise AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation)) + raise AccessError( + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) + ) return super(MailMessage, self).check_access_rule(operation=operation) diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index e947147d0d9..2664b90093f 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -360,7 +360,10 @@ class IrActionsActWindow(models.Model): existing = self.filtered(lambda rec: rec.id in ids) if len(existing) < len(self): # mark missing records in cache with a failed value - exc = MissingError(_("Record does not exist or has been deleted.")) + exc = MissingError( + _("Record does not exist or has been deleted.") + + '\n\n({} {}, {} {})'.format(_('Records:'), (self - existing).ids[:6], _('User:'), self._uid) + ) (self - existing)._cache.update(fields.FailedValue(exc)) return existing diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index 60f248c4ba4..3a12f2d7c82 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -958,6 +958,7 @@ class IrModelAccess(models.Model): else: msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)" msg_params = (model_name,) + msg_tail += ' - ({} {}, {} {})'.format(_('Operation:'), mode, _('User:'), self._uid) _logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, self._uid, model_name) msg = '%s %s' % (msg_heads[mode], msg_tail) raise AccessError(msg % msg_params) diff --git a/odoo/models.py b/odoo/models.py index de5fb780cda..577ef080197 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2981,9 +2981,13 @@ class BaseModel(object): if invalid_fields: _logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, self._uid, self._name, ', '.join(invalid_fields)) - raise AccessError(_('The requested operation cannot be completed due to security restrictions. ' - 'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ - (self._description, operation)) + raise AccessError( + _( + 'The requested operation cannot be completed due to security restrictions. ' + 'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)' + ) % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('User:'), self._uid, _('Fields:'), ', '.join(invalid_fields)) + ) return fields @@ -3197,8 +3201,8 @@ class BaseModel(object): if forbidden: # store an access error exception in existing records exc = AccessError( - _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ - (self._name, 'read') + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, 'read') + + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) ) forbidden._cache.update(FailedValue(exc)) @@ -3282,8 +3286,10 @@ class BaseModel(object): if self._uid == SUPERUSER_ID: return _logger.info('Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s', operation, forbidden_ids, self._uid, self._name) - raise AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ - (self._description, operation)) + raise AccessError( + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {}, {})'.format(_('Records:'), forbidden_ids[:6], _('User:'), self._uid) + ) else: # If we get here, the missing_ids are not in the database if operation in ('read','unlink'): @@ -3292,7 +3298,13 @@ class BaseModel(object): # errors for non-transactional search/read sequences coming from clients return _logger.info('Failed operation on deleted record(s): %s, uid: %s, model: %s', operation, self._uid, self._name) - raise MissingError(_('Missing document(s)') + ':' + _('One of the documents you are trying to access has been deleted, please try again after refreshing.')) + raise MissingError( + _('Missing document(s)') + ':' + _('One of the documents you are trying to access has been deleted, please try again after refreshing.') + + '\n\n({} {}, {} {}, {} {}, {} {})'.format( + _('Document type:'), self._description, _('Operation:'), operation, + _('Records:'), missing_ids[:6], _('User:'), self._uid, + ) + ) @api.model def check_access_rights(self, operation, raise_exception=True): @@ -3680,7 +3692,10 @@ class BaseModel(object): for sub_ids in cr.split_for_in_conditions(set(self.ids)): cr.execute(query, params + (sub_ids,)) if cr.rowcount != len(sub_ids): - raise MissingError(_('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description) + raise MissingError( + _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description + + '\n\n({} {}, {} {})'.format(_('Records:'), sub_ids[:6], _('User:'), self._uid) + ) # TODO: optimize for name in direct: @@ -4457,7 +4472,10 @@ class BaseModel(object): existing = self.browse(ids + new_ids) if len(existing) < len(self): # mark missing records in cache with a failed value - exc = MissingError(_("Record does not exist or has been deleted.")) + exc = MissingError( + _("Record does not exist or has been deleted.") + + '\n\n({} {}, {} {})'.format(_('Records:'), (self - existing).ids[:6], _('User:'), self._uid) + ) (self - existing)._cache.update(FailedValue(exc)) return existing From ea4ddff117ef5015c7e8c2db551a9399930177b7 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 20 Jun 2019 08:11:08 +0000 Subject: [PATCH 2/3] [FIX] point_of_sale: apply fiscal position - Create 2 included taxes: 21 % and 6 % - Create a product A with 21 % as default tax - Create a fiscal position to map the tax 21 % to tax 6 % - Allow the fiscal position in the POS - Add product in a POS order - Apply the fiscal position The subtotal is not recomputed correctly. On the other hand, if the fiscal is first set then the product added, there is no problem. This is because the `fix_tax_included_price` is only called by `add_product` and not when changing the fiscal position. opw-2020755 closes odoo/odoo#34260 Signed-off-by: Nicolas Martinelli (nim) --- addons/point_of_sale/static/src/js/screens.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 02069ce2cdd..48db503921b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -2095,6 +2095,10 @@ var set_fiscal_position_button = ActionButtonWidget.extend({ confirm: function (fiscal_position) { var order = self.pos.get_order(); order.fiscal_position = fiscal_position; + // Fix the taxes on existing lines + _.each(order.get_orderlines(), function (line) { + order.fix_tax_included_price(line); + }); order.trigger('change'); } }); From 7b16abbae1d3af0e57730be872cef6b1f7d7a244 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 14 May 2019 08:42:25 +0000 Subject: [PATCH 3/3] [IMP] core: add support for multi-version migration scripts A special folder named `0.0.0` can contain scripts that are run on upgrade of any version. They are useful to make some sanity checks or other verifications to ensure database consistency. The first version of this patch used the more eye-catching `any` for the migration folder, but it was problematic for upgrading from an older version that doesn't contain this patch. Using a version "number" containing two dots is required to avoid it being prefixed with the server version (see `convert_version` method) and resulting in a version like `10.0.any`. Such version would have been executed, even without this patch, when upgrading from an older major server version (9.0.1.0 < 10.0.any). closes odoo/odoo#34268 Signed-off-by: Christophe Simonis --- odoo/modules/migration.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py index 78025cbfa11..2bff1201bac 100644 --- a/odoo/modules/migration.py +++ b/odoo/modules/migration.py @@ -28,6 +28,8 @@ class MigrationManager(object): Python file names must start by `pre-` or `post-` and will be executed, respectively, before and after the module initialisation. `end-` scripts are run after all modules have been updated. + A special folder named `0.0.0` can contain scripts that will be run on any version change. + In `pre` stage, `0.0.0` scripts are run first, while in `post` and `end`, they are run last. Example: `-- migrations @@ -40,6 +42,8 @@ class MigrationManager(object): |-- 9.0.1.1 # processed only on a 9.0 server | |-- pre-delete_table_z.py | `-- post-clean-data.py + |-- 0.0.0 + | `-- end-invariants.py # processed on all version update `-- foo.py # not processed """ @@ -86,7 +90,7 @@ class MigrationManager(object): return version # the version number already containt the server version return "%s.%s" % (release.major_version, version) - def _get_migration_versions(pkg): + def _get_migration_versions(pkg, stage): versions = list(set( ver for lv in self.migrations[pkg.name].values() @@ -94,6 +98,13 @@ class MigrationManager(object): if lf )) versions.sort(key=lambda k: parse_version(convert_version(k))) + if "0.0.0" in versions: + # reorder versions + versions.remove("0.0.0") + if stage == "pre": + versions.insert(0, "0.0.0") + else: + versions.append("0.0.0") return versions def _get_migration_files(pkg, version, stage): @@ -120,10 +131,11 @@ class MigrationManager(object): parsed_installed_version = parse_version(installed_version) current_version = parse_version(convert_version(pkg.data['version'])) - versions = _get_migration_versions(pkg) + versions = _get_migration_versions(pkg, stage) for version in versions: - if parsed_installed_version < parse_version(convert_version(version)) <= current_version: + if ((version == "0.0.0" and parsed_installed_version < current_version) + or parsed_installed_version < parse_version(convert_version(version)) <= current_version): strfmt = {'addon': pkg.name, 'stage': stage,