diff options
| author | Victor Feyens <vfe@odoo.com> | 2019-04-02 13:13:02 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2019-11-25 14:17:31 +0000 |
| commit | 0eadc0aa5ea53943738e43b3ec1c9bff3ef30922 (patch) | |
| tree | 1a3358a721e1c5d6d6d537f84a97153f6a00a8d3 | |
| parent | b7983df87a97765b83d5c012c9f252a501d94d77 (diff) | |
[IMP] mail: better prevent activity type removal and add a fallback mechanism
Current activity types master data cannot be unlinked through a quite harsh
override of unlink using xml id existence, introduced at 2602642a78dff6c15d615fadf4c55a6b6ca315df.
Some of them are real master data, other could be unlinked if users want it.
Finally custom activity types may cause errors when removed if used in some
side models like server actions or automated actions.
To allow more flexibility we remove the removal constraint and implement
a fallback mechanism. It is implemented in mail.activity.mixin when calling
activity_schedule(xmlid, ...). If the given xmlid is not found
_default_activity_type() specifies the default activity to use. It can
be overridden in modules if some specific behavior is wanted.
e.g.
def _default_activity_type(self):
"""Define a default fallback activity type when xml id not found
only used in in activity_schedule() for now.
"""
try:
return self.env.ref('mail.mail_activity_data_todo')
except Exception:
return False
In this commit we also
* restrict deletion of activity type (hr.plan and ir.actions.server);
* fallback on default activity type if xmlid ref not found in
activity_schedule;
* correct calls to activity_schedule and replace some manual creation calls
by activity_schedule;
Task ID 1961156
PR #39013
Related: odoo/enterprise#6238
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Co-authored-by: Victor Feyens <vfe@odoo.com>
Co-authored-by: Michaël Mattiello <mcm@odoo.com>
| -rw-r--r-- | addons/hr/models/hr_plan.py | 3 | ||||
| -rw-r--r-- | addons/hr/wizard/hr_plan_wizard.py | 14 | ||||
| -rw-r--r-- | addons/l10n_it_edi/models/ir_mail_server.py | 18 | ||||
| -rw-r--r-- | addons/mail/models/ir_actions.py | 3 | ||||
| -rw-r--r-- | addons/mail/models/mail_activity.py | 39 | ||||
| -rw-r--r-- | addons/note/controllers/note.py | 17 | ||||
| -rw-r--r-- | addons/purchase_stock/models/purchase.py | 10 | ||||
| -rw-r--r-- | addons/stock/models/stock_rule.py | 17 |
8 files changed, 58 insertions, 63 deletions
diff --git a/addons/hr/models/hr_plan.py b/addons/hr/models/hr_plan.py index c1d897e9d38..dd45a280a6f 100644 --- a/addons/hr/models/hr_plan.py +++ b/addons/hr/models/hr_plan.py @@ -13,7 +13,8 @@ class HrPlanActivityType(models.Model): activity_type_id = fields.Many2one( 'mail.activity.type', 'Activity Type', default=lambda self: self.env.ref('mail.mail_activity_data_todo'), - domain=lambda self: ['|', ('res_model_id', '=', False), ('res_model_id', '=', self.env['ir.model']._get('hr.employee').id)] + domain=lambda self: ['|', ('res_model_id', '=', False), ('res_model_id', '=', self.env['ir.model']._get('hr.employee').id)], + ondelete='restrict' ) summary = fields.Char('Summary') responsible = fields.Selection([ diff --git a/addons/hr/wizard/hr_plan_wizard.py b/addons/hr/wizard/hr_plan_wizard.py index 7dc74bbb035..c0f2d663e4c 100644 --- a/addons/hr/wizard/hr_plan_wizard.py +++ b/addons/hr/wizard/hr_plan_wizard.py @@ -24,14 +24,12 @@ class HrPlanWizard(models.TransientModel): responsible = activity_type.get_responsible_id(self.employee_id) if self.env['hr.employee'].with_user(responsible).check_access_rights('read', raise_exception=False): - self.env['mail.activity'].create({ - 'res_id': self.employee_id.id, - 'res_model_id': self.env['ir.model']._get('hr.employee').id, - 'summary': activity_type.summary, - 'note': activity_type.note, - 'activity_type_id': activity_type.activity_type_id.id, - 'user_id': responsible.id, - }) + self.employee_id.activity_schedule( + activity_type_id=activity_type.activity_type_id.id, + summary=activity_type.summary, + note=activity_type.note, + user_id=responsible.id + ) return { 'type': 'ir.actions.act_window', diff --git a/addons/l10n_it_edi/models/ir_mail_server.py b/addons/l10n_it_edi/models/ir_mail_server.py index 2674cfba5bf..8df9763f147 100644 --- a/addons/l10n_it_edi/models/ir_mail_server.py +++ b/addons/l10n_it_edi/models/ir_mail_server.py @@ -244,11 +244,10 @@ class FetchmailServer(models.Model): related_invoice.message_post( body=(_("Errors in the E-Invoice :<br/>%s") % (error)) ) - activity_vals = { - 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, - 'invoice_user_id': related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id - } - related_invoice.activity_schedule(summary='Rejection notice', **activity_vals) + related_invoice.activity_schedule( + 'mail.mail_activity_data_todo', + summary='Rejection notice', + user_id=related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id) elif receipt_type == 'MC': # Failed delivery notice @@ -304,11 +303,10 @@ class FetchmailServer(models.Model): body=(_("Outcome notice: %s<br/>%s") % (related_invoice.l10n_it_send_state, info)) ) if related_invoice.l10n_it_send_state == 'delivered_refused': - activity_vals = { - 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, - 'invoice_user_id': related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id - } - related_invoice.activity_schedule(summary='Outcome notice: Refused', **activity_vals) + related_invoice.activity_schedule( + 'mail.mail_activity_todo', + user_id=related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id, + summary='Outcome notice: Refused') # elif receipt_type == 'MT': # Metadata file diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index ef9495f52fd..22a39a29164 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -29,7 +29,8 @@ class ServerActions(models.Model): # Next Activity activity_type_id = fields.Many2one( 'mail.activity.type', string='Activity', - domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', model_id)]") + domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', model_id)]", + ondelete='restrict') activity_summary = fields.Char('Summary') activity_note = fields.Html('Note') activity_date_deadline_range = fields.Integer(string='Due Date In') diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 8fc502ee6de..55e2db647bd 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -59,7 +59,7 @@ class MailActivityType(models.Model): help='Specify a model if the activity should be specific to a model' ' and not available when managing activities for other models.') default_next_type_id = fields.Many2one('mail.activity.type', 'Default Next Activity', - domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]") + domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]", ondelete='restrict') force_next = fields.Boolean("Trigger Next Activity", default=False) next_type_ids = fields.Many2many( 'mail.activity.type', 'mail_activity_rel', 'activity_id', 'recommended_id', @@ -91,11 +91,6 @@ class MailActivityType(models.Model): for activity_type in self: activity_type.initial_res_model_id = activity_type.res_model_id - def unlink(self): - if any(self.get_external_id().values()) and not self._context.get(MODULE_UNINSTALL_FLAG): - raise exceptions.ValidationError("You can not delete activity type that are used as master data.") - return super(MailActivityType, self).unlink() - class MailActivity(models.Model): """ An actual activity to perform. Activities are linked to @@ -575,6 +570,16 @@ class MailActivityMixin(models.AbstractModel): _name = 'mail.activity.mixin' _description = 'Activity Mixin' + def _default_activity_type(self): + """Define a default fallback activity type when requested xml id wasn't found. + + Can be overriden to specify the default activity type of a model. + It is only called in in activity_schedule() for now. + """ + return self.env.ref('mail.mail_activity_data_todo', raise_if_not_found=False) \ + or self.env['mail.activity.type'].search([('res_model', '=', self._name)], limit=1) \ + or self.env['mail.activity.type'].search([('res_model_id', '=', False)], limit=1) + activity_ids = fields.One2many( 'mail.activity', 'res_id', 'Activities', auto_join=True, @@ -732,15 +737,16 @@ class MailActivityMixin(models.AbstractModel): if isinstance(date_deadline, datetime): _logger.warning("Scheduled deadline should be a date (got %s)", date_deadline) if act_type_xmlid: - activity_type = self.sudo().env.ref(act_type_xmlid) + activity_type = self.env.ref(act_type_xmlid, raise_if_not_found=False) or self._default_activity_type() else: - activity_type = self.env['mail.activity.type'].sudo().browse(act_values['activity_type_id']) + activity_type_id = act_values.get('activity_type_id', False) + activity_type = activity_type_id and self.env['mail.activity.type'].sudo().browse(activity_type_id) model_id = self.env['ir.model']._get(self._name).id activities = self.env['mail.activity'] for record in self: create_vals = { - 'activity_type_id': activity_type.id, + 'activity_type_id': activity_type and activity_type.id, 'summary': summary or activity_type.summary, 'automated': True, 'note': note or activity_type.default_description, @@ -792,7 +798,10 @@ class MailActivityMixin(models.AbstractModel): return False Data = self.env['ir.model.data'].sudo() - activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + activity_types_ids = [Data.xmlid_to_res_id(xmlid, raise_if_not_found=False) for xmlid in act_type_xmlids] + activity_types_ids = [act_type_id for act_type_id in activity_types_ids if act_type_id] + if not any(activity_types_ids): + return False domain = [ '&', '&', '&', ('res_model', '=', self._name), @@ -819,7 +828,10 @@ class MailActivityMixin(models.AbstractModel): return False Data = self.env['ir.model.data'].sudo() - activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + activity_types_ids = [Data.xmlid_to_res_id(xmlid, raise_if_not_found=False) for xmlid in act_type_xmlids] + activity_types_ids = [act_type_id for act_type_id in activity_types_ids if act_type_id] + if not any(activity_types_ids): + return False domain = [ '&', '&', '&', ('res_model', '=', self._name), @@ -841,7 +853,10 @@ class MailActivityMixin(models.AbstractModel): return False Data = self.env['ir.model.data'].sudo() - activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + activity_types_ids = [Data.xmlid_to_res_id(xmlid, raise_if_not_found=False) for xmlid in act_type_xmlids] + activity_types_ids = [act_type_id for act_type_id in activity_types_ids if act_type_id] + if not any(activity_types_ids): + return False domain = [ '&', '&', '&', ('res_model', '=', self._name), diff --git a/addons/note/controllers/note.py b/addons/note/controllers/note.py index bdcf2e85059..6cafea730cf 100644 --- a/addons/note/controllers/note.py +++ b/addons/note/controllers/note.py @@ -12,16 +12,9 @@ class NoteController(http.Controller): """ Route to create note and their activity directly from the systray """ note = request.env['note.note'].create({'memo': note}) if date_deadline: - activity_values = { - 'note': note.memo, - 'date_deadline': date_deadline, - 'res_model_id': request.env.ref("note.model_note_note").id, - 'res_id': note.id, - 'note_id': note.id, - } - if not activity_type_id: - activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id - if activity_type_id: - activity_values['activity_type_id'] = activity_type_id - request.env['mail.activity'].create(activity_values) + note.activity_schedule( + activity_type_id=activity_type_id or request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id, + note=note.memo, + date_deadline=date_deadline + ) return note.id diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index d697538db9f..7616fa86269 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -306,13 +306,9 @@ class PurchaseOrderLine(models.Model): if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1: # If the quantity is now below the invoiced quantity, create an activity on the vendor bill # inviting the user to create a refund. - activity = self.env['mail.activity'].sudo().create({ - 'activity_type_id': self.env.ref('mail.mail_activity_data_warning').id, - 'note': _('The quantities on your purchase order indicate less than billed. You should ask for a refund. '), - 'res_id': line.invoice_lines[0].invoice_id.id, - 'res_model_id': self.env.ref('account.model_account_move').id, - }) - activity._onchange_activity_type_id() + line.invoice_lines[0].move_id.activity_schedule( + 'mail.mail_activity_data_warning', + note=_('The quantities on your purchase order indicate less than billed. You should ask for a refund.')) # If the user increased quantity of existing line or created a new line pickings = line.order_id.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit')) diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index b8add883f27..94b6b9ea646 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -298,18 +298,11 @@ class StockRule(models.Model): existing_activity = self.env['mail.activity'].search([('res_id', '=', product_id.product_tmpl_id.id), ('res_model_id', '=', self.env.ref('product.model_product_template').id), ('note', '=', note)]) if not existing_activity: - # If the user deleted warning activity type. - try: - activity_type_id = self.env.ref('mail.mail_activity_data_warning').id - except: - activity_type_id = False - self.env['mail.activity'].create({ - 'activity_type_id': activity_type_id, - 'note': note, - 'user_id': product_id.responsible_id.id or SUPERUSER_ID, - 'res_id': product_id.product_tmpl_id.id, - 'res_model_id': self.env.ref('product.model_product_template').id, - }) + product_id.product_tmpl_id.activity_schedule( + 'mail.mail_activity_data_warning', + note=note, + user_id=product_id.responsible_id.id or SUPERUSER_ID, + ) class ProcurementGroup(models.Model): |
