From d2079b1fcac718d102ecfd035c9bd38abe4aa20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 6 Nov 2020 12:42:46 +0000 Subject: [PATCH] [DOC] mail: clean some docstrings and helpers As I was passing by I found some docstrings or helpers could be updated or rephrased a bit more clearly. This is free as in free beers, without beers. Some code about mail features (posting) may also be re-indented to ease understanding of future modifications. Or just because we had to read it and go through many files. Still free beers. LINKS Task ID-2477444 Prepares Task ID-2377974 (trace management cleaning task) Prepares Task ID-2070632 (channel members main task) Prepares Task ID-2419762 (channel members followup task) COM PR odoo/odoo#67382 UPG PR odoo/upgrade#2245 --- addons/mail/controllers/bus.py | 13 +++--- addons/mail/models/mail_followers.py | 22 ++++++---- addons/mail/models/mail_message.py | 48 +++++++++++++++++++-- addons/mail/models/mail_thread.py | 30 ++++++------- addons/mass_mailing/models/mailing_trace.py | 38 +++++++++++++++- 5 files changed, 119 insertions(+), 32 deletions(-) diff --git a/addons/mail/controllers/bus.py b/addons/mail/controllers/bus.py index ccbe6bdeb5f..0e653f1a800 100644 --- a/addons/mail/controllers/bus.py +++ b/addons/mail/controllers/bus.py @@ -50,11 +50,14 @@ class MailChatController(BusController): email_from = mail_channel.anonymous_name or mail_channel.create_uid.company_id.catchall_formatted # post a message without adding followers to the channel. email_from=False avoid to get author from email data body = tools.plaintext2html(message_content) - message = mail_channel.with_context(mail_create_nosubscribe=True).message_post(author_id=author_id, - email_from=email_from, body=body, - message_type='comment', - subtype_xmlid='mail.mt_comment') - return message and message.id or False + message = mail_channel.with_context(mail_create_nosubscribe=True).message_post( + author_id=author_id, + email_from=email_from, + body=body, + message_type='comment', + subtype_xmlid='mail.mt_comment' + ) + return message.id if message else False @route(['/mail/chat_history'], type="json", auth="public", cors="*") def mail_chat_history(self, uuid, last_id=False, limit=20): diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py index 864ae31ad23..2bfca4e70e1 100644 --- a/addons/mail/models/mail_followers.py +++ b/addons/mail/models/mail_followers.py @@ -260,10 +260,8 @@ GROUP BY fol.id%s%s""" % ( res_model and the document res_ids. This method does not handle access rights. This is the role of the caller to ensure there is no security breach. - :param partner_subtypes: optional subtypes for new partner followers. If not given, default - ones are computed; - :param channel_subtypes: optional subtypes for new channel followers. If not given, default - ones are computed; + :param partner_subtypes: see ``_add_followers``. If not given, default ones are computed. + :param channel_subtypes: see ``_add_followers``. If not given, default ones are computed. :param customer_ids: see ``_add_default_followers`` :param check_existing: see ``_add_followers``; :param existing_policy: see ``_add_followers``; @@ -329,10 +327,18 @@ GROUP BY fol.id%s%s""" % ( * second one is a dict which keys are follower ids. Value is a dict of values valid for updating the related follower record; - :param check_existing: if True, check for existing followers for given documents and handle - them according to existing_policy parameter. Setting to False allows to save some computation - if caller is sure there are no conflict for followers; - :param existing policy: if check_existing, tells what to do with already-existing followers: + :param partner_subtypes: optional subtypes for new partner followers. This + is a dict whose keys are partner IDs and value subtype IDs for that + partner. + :param channel_subtypes: optional subtypes for new channel followers. This + is a dict whose keys are channel IDs and value subtype IDs for that + channel. + :param check_existing: if True, check for existing followers for given + documents and handle them according to existing_policy parameter. + Setting to False allows to save some computation if caller is sure + there are no conflict for followers; + :param existing policy: if check_existing, tells what to do with already + existing followers: * skip: simply skip existing followers, do not touch them; * force: update existing with given subtypes only; diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index a1c2bf6176f..ca0b8912e38 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -5,7 +5,6 @@ import logging import re from binascii import Error as binascii_error -from collections import defaultdict from operator import itemgetter from odoo import _, api, Command, fields, models, modules, tools @@ -19,8 +18,51 @@ _image_dataurl = re.compile(r'(data:image/[a-z]+?);base64,([a-z0-9+/\n]{3,}=*)\n class Message(models.Model): - """ Messages model: system notification (replacing res.log notifications), - comments (OpenChatter discussion) and incoming emails. """ + """ Message model: notification (system, replacing res.log notifications), + comment (user input), email (incoming emails) and user_notification + (user-specific notification) + + Note:: State management / Error codes / Failure types summary + + * mail.notification + * notification_status + 'ready', 'sent', 'bounce', 'exception', 'canceled' + * notification_type + 'inbox', 'email', 'sms' (SMS addon), 'snail' (snailmail addon) + * failure_type + # mail + "SMTP", "RECIPIENT", "BOUNCE", "UNKNOWN" + # sms (SMS addon) + 'sms_number_missing', 'sms_number_format', 'sms_credit', + 'sms_server', 'sms_acc' + # snailmail (snailmail addon) + 'sn_credit', 'sn_trial', 'sn_price', 'sn_fields', + 'sn_format', 'sn_error' + + * mail.mail + * state + 'outgoing', 'sent', 'received', 'exception', 'cancel' + * failure_reason: text + + * sms.sms (SMS addon) + * state + 'outgoing', 'sent', 'error', 'canceled' + * error_code + 'sms_number_missing', 'sms_number_format', 'sms_credit', + 'sms_server', 'sms_acc', + # mass mode specific codes + 'sms_blacklist', 'sms_duplicate' + + * snailmail.letter (snailmail addon) + * state + 'pending', 'sent', 'error', 'canceled' + * error_code + 'CREDIT_ERROR', 'TRIAL_ERROR', 'NO_PRICE_AVAILABLE', 'FORMAT_ERROR', + 'UNKNOWN_ERROR', + + See ``mailing.trace`` model in mass_mailing application for mailing trace + information. + """ _name = 'mail.message' _description = 'Message' _order = 'id desc' diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 1d5bee86c35..1dc1a8d2de0 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1761,13 +1761,15 @@ class MailThread(models.AbstractModel): :param str body: body of the message, usually raw HTML that will be sanitized :param str subject: subject of the message - :param str message_type: see mail_message.message_type field. Can be anything but + :param str message_type: see mail_message.message_type field. Can be anything but user_notification, reserved for message_notify :param int parent_id: handle thread formation - :param int subtype_id: subtype_id of the message, mainly use fore - followers mechanism - :param list(int) partner_ids: partner_ids to notify - :param list(int) channel_ids: channel_ids to notify + :param int subtype_id: subtype_id of the message, used mainly use for + followers notification mechanism; + :param list(int) partner_ids: partner_ids to notify in addition to partners + computed based on subtype / followers matching; + :param list(int) channel_ids: channel_ids to notify in addition to partners + computed based on subtype / followers matching; :param list(tuple(str,str), tuple(str,str, dict) or int) attachments : list of attachment tuples in the form ``(name,content)`` or ``(name,content, info)``, where content is NOT base64 encoded :param list id attachment_ids: list of existing attachement to link to this message @@ -1783,13 +1785,17 @@ class MailThread(models.AbstractModel): msg_kwargs = dict((key, val) for key, val in kwargs.items() if key in self.env['mail.message']._fields) notif_kwargs = dict((key, val) for key, val in kwargs.items() if key not in msg_kwargs) + # preliminary value safety check + partner_ids = set(partner_ids or []) + channel_ids = set(channel_ids or []) if self._name == 'mail.thread' or not self.id or message_type == 'user_notification': - raise ValueError('message_post should only be call to post message on record. Use message_notify instead') - + raise ValueError(_('Posting a message should be done on a business document. Use message_notify to send a notification to an user.')) if 'model' in msg_kwargs or 'res_id' in msg_kwargs: - raise ValueError("message_post doesn't support model and res_id parameters anymore. Please call message_post on record.") + raise ValueError(_("message_post does not support model and res_id parameters anymore. Please call message_post on record.")) if 'subtype' in kwargs: - raise ValueError("message_post doesn't support subtype parameter anymore. Please give a valid subtype_id or subtype_xmlid value instead.") + raise ValueError(_("message_post does not support subtype parameter anymore. Please give a valid subtype_id or subtype_xmlid value instead.")) + if any(not isinstance(pc_id, int) for pc_id in partner_ids | channel_ids): + raise ValueError(_('message_post partner_ids and channel_ids must be integer list, not commands.')) self = self._fallback_lang() # add lang to context imediatly since it will be usefull in various flows latter. @@ -1798,12 +1804,6 @@ class MailThread(models.AbstractModel): self.check_access_rule('read') record_name = record_name or self.display_name - partner_ids = set(partner_ids or []) - channel_ids = set(channel_ids or []) - - if any(not isinstance(pc_id, int) for pc_id in partner_ids | channel_ids): - raise ValueError('message_post partner_ids and channel_ids must be integer list, not commands') - # Find the message's author author_id, email_from = self._message_compute_author(author_id, email_from, raise_exception=True) diff --git a/addons/mass_mailing/models/mailing_trace.py b/addons/mass_mailing/models/mailing_trace.py index e2e24dc8265..71b12543fc8 100644 --- a/addons/mass_mailing/models/mailing_trace.py +++ b/addons/mass_mailing/models/mailing_trace.py @@ -8,7 +8,43 @@ class MailingTrace(models.Model): """ MailingTrace models the statistics collected about emails. Those statistics are stored in a separated model and table to avoid bloating the mail_mail table with statistics values. This also allows to delete emails send with mass mailing - without loosing the statistics about them. """ + without loosing the statistics about them. + + Note:: State management / Error codes / Failure types summary + + * state + 'outgoing', 'sent', 'opened', 'replied', + 'exception', 'bounced', 'ignored' + * failure_type + # mass_mailing + "SMTP", "RECIPIENT", "BOUNCE", "UNKNOWN" + # mass_mailing_sms + 'sms_number_missing', 'sms_number_format', 'sms_credit', + 'sms_server', 'sms_acc' + # mass_mailing_sms mass mode specific codes + 'sms_blacklist', 'sms_duplicate' + * ignored: + * mail: set in get_mail_values in composer, if email is blacklisted + (mail) or in opt_out / seen list (mass_mailing) or email_to is void + or incorrectly formatted (mass_mailing) - based on mail cancel state + * sms: set in _prepare_mass_sms_trace_values in composer if sms is + in cancel state; either blacklisted (sms) or in opt_out / seen list + (sms); + * difference: void mail -> cancel -> ignore, void sms -> error + sms_number_missing -> exception + * difference: invalid mail -> cancel -> ignore, invalid sms -> error + sms_number_format -> sent + bounce; + * exception: set in _postprocess_sent_message (_postprocess_iap_sent_sms) + if mail (sms) not sent with failure type, reset if sent; also set for + sms in _prepare_mass_sms_trace_values if void number + * sent: set in _postprocess_sent_message (_postprocess_iap_sent_sms) if + mail (sms) sent + * clicked: triggered by add_click + * opened: triggered by add_click + blank gif (mail) + gateway reply (mail) + * replied: triggered by gateway reply (mail) + * bounced: triggered by gateway bounce (mail) or in _prepare_mass_sms_trace_values + if sms_number_format error when sending sms (sms) + """ _name = 'mailing.trace' _description = 'Mailing Statistics' _rec_name = 'id'